feat(reports): adjust financial report calculations to maintain sign for accuracy
This commit is contained in:
@@ -317,37 +317,43 @@ tap.test('should perform year-end adjustments (Jahresabschlussbuchungen)', async
|
||||
reference: 'UST-2024-Q4',
|
||||
lines: [
|
||||
{ accountNumber: '1771', debit: 8740, description: 'USt-Saldo' }, // Total collected VAT
|
||||
{ accountNumber: '1571', credit: 7266.50, description: 'Vorsteuer-Saldo' }, // Total input VAT
|
||||
{ accountNumber: '1800', credit: 1473.50, description: 'USt-Zahllast' },
|
||||
{ accountNumber: '1571', credit: 7191.50, description: 'Vorsteuer-Saldo' }, // Total input VAT
|
||||
{ accountNumber: '1800', credit: 1548.50, description: 'USt-Zahllast' },
|
||||
],
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
|
||||
// Assert VAT accounts are cleared
|
||||
const ust19 = await api.getAccountBalance('1771');
|
||||
const vorst19 = await api.getAccountBalance('1571');
|
||||
const ustZahllast = await api.getAccountBalance('1800');
|
||||
|
||||
expect(Math.abs(ust19.balance)).toBeLessThan(0.01);
|
||||
expect(Math.abs(vorst19.balance)).toBeLessThan(0.01);
|
||||
expect(Math.abs(ustZahllast.balance - 1548.50)).toBeLessThan(0.01);
|
||||
});
|
||||
|
||||
tap.test('should calculate income statement (GuV) before closing', async () => {
|
||||
const incomeStatement = await api.generateIncomeStatement({
|
||||
dateFrom: new Date('2024-01-01'),
|
||||
dateTo: new Date('2024-12-31'),
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
|
||||
expect(incomeStatement).toBeDefined();
|
||||
expect(incomeStatement.totalRevenue).toBeGreaterThan(0);
|
||||
expect(incomeStatement.totalExpenses).toBeGreaterThan(0);
|
||||
|
||||
// The net income should be:
|
||||
// Revenue: 46000 (sales)
|
||||
// Less expenses:
|
||||
// - Cost of goods: 5000
|
||||
// - Personnel: 29600
|
||||
// - Rent: 2000
|
||||
// - Office: 200
|
||||
// - Vehicle: 150
|
||||
// - Marketing: 5000
|
||||
// - Professional: 5500
|
||||
// - Depreciation: 11040
|
||||
// - Insurance: -1000 (accrual adjustment)
|
||||
// - Inventory: -3000 (increase)
|
||||
const expectedNetIncome = 46000 - 5000 - 29600 - 2000 - 200 - 150 - 5000 - 5500 - 11040 + 1000 + 3000;
|
||||
// Assert the exact expected values based on actual bookings
|
||||
// Revenue: 46000 (8400 account)
|
||||
// Expenses: 5000 + 18000 + 3600 + 10000 + 2000 + 150 + 5000 + 5500 + 200 = 49450
|
||||
// Less credit balances: -1000 (insurance accrual) -3000 (inventory increase) = -4000
|
||||
// Net expenses: 49450 - 4000 = 45450
|
||||
// Net income: 46000 - 45450 = 550
|
||||
|
||||
expect(Math.round(incomeStatement.totalRevenue)).toEqual(46000);
|
||||
expect(Math.round(incomeStatement.totalExpenses)).toEqual(45450);
|
||||
expect(Math.round(incomeStatement.netIncome)).toEqual(550);
|
||||
|
||||
console.log('Income Statement Summary:');
|
||||
console.log('Revenue:', incomeStatement.totalRevenue);
|
||||
@@ -365,8 +371,7 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
||||
reference: 'AB-2024-001',
|
||||
lines: [
|
||||
{ accountNumber: '8400', debit: 46000, description: 'Erlöse abschließen' },
|
||||
{ accountNumber: '5900', debit: 3000, description: 'Bestandsveränderungen abschließen' },
|
||||
{ accountNumber: '9400', credit: 49000, description: 'GuV-Konto' },
|
||||
{ accountNumber: '9400', credit: 46000, description: 'GuV-Konto' },
|
||||
],
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
@@ -378,23 +383,23 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
||||
reference: 'AB-2024-002',
|
||||
lines: [
|
||||
{ accountNumber: '9400', debit: 45450, description: 'GuV-Konto' },
|
||||
{ accountNumber: '7300', debit: 1000, description: 'Versicherung abschließen (credit balance)' },
|
||||
{ accountNumber: '5900', debit: 3000, description: 'Bestandsveränderungen abschließen (credit balance)' },
|
||||
{ accountNumber: '5400', credit: 5000, description: 'Wareneingang abschließen' },
|
||||
{ accountNumber: '6000', credit: 18000, description: 'Löhne und Gehälter abschließen' },
|
||||
{ accountNumber: '6100', credit: 3600, description: 'SV AG-Anteil abschließen' },
|
||||
{ accountNumber: '7000', credit: 10000, description: 'AfA abschließen' },
|
||||
{ accountNumber: '7100', credit: 2000, description: 'Miete abschließen' },
|
||||
{ accountNumber: '7300', debit: 1000, description: 'Versicherung abschließen (credit balance)' },
|
||||
{ accountNumber: '7400', credit: 150, description: 'Kfz abschließen' },
|
||||
{ accountNumber: '6600', credit: 5000, description: 'Werbung abschließen' },
|
||||
{ accountNumber: '6700', credit: 5500, description: 'Beratung abschließen' },
|
||||
{ accountNumber: '6800', credit: 200, description: 'Bürobedarf abschließen' },
|
||||
{ accountNumber: '5900', debit: 3000, description: 'Bestandsveränderungen abschließen (credit balance)' },
|
||||
],
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
|
||||
// Transfer profit/loss to equity
|
||||
const guv_result = 49000 - 45450; // Profit of 3550
|
||||
const guv_result = 46000 - 45450; // Profit of 550
|
||||
if (guv_result > 0) {
|
||||
await api.postJournalEntry({
|
||||
date: new Date('2024-12-31'),
|
||||
@@ -418,11 +423,26 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
}
|
||||
|
||||
// Assert GuV account is closed and equity is updated
|
||||
const guv = await api.getAccountBalance('9400');
|
||||
const ruecklagen = await api.getAccountBalance('2900');
|
||||
|
||||
expect(Math.abs(guv.balance)).toBeLessThan(0.01);
|
||||
expect(Math.round(ruecklagen.balance)).toEqual(35550); // 35000 + 550
|
||||
|
||||
// Assert all P&L accounts are closed (zero balance)
|
||||
const plAccounts = ['8400', '5400', '5900', '6000', '6100', '6600', '6700', '6800', '7000', '7100', '7300', '7400'];
|
||||
for (const accNum of plAccounts) {
|
||||
const balance = await api.getAccountBalance(accNum);
|
||||
expect(Math.abs(balance.balance)).toBeLessThan(0.01);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should generate final balance sheet (Schlussbilanz)', async () => {
|
||||
const balanceSheet = await api.generateBalanceSheet({
|
||||
date: new Date('2024-12-31'),
|
||||
dateTo: new Date('2024-12-31'),
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
|
||||
expect(balanceSheet).toBeDefined();
|
||||
@@ -459,10 +479,10 @@ tap.test('should generate final balance sheet (Schlussbilanz)', async () => {
|
||||
console.log('-------------------------------');
|
||||
console.log('Eigenkapital:');
|
||||
console.log(' Gezeichnetes Kapital: 150,000.00 €');
|
||||
console.log(' Gewinnrücklagen: 38,550.00 €'); // 35000 + 3550 profit
|
||||
console.log(' Jahresgewinn: 3,550.00 €');
|
||||
console.log(' Gewinnrücklagen: 35,550.00 €'); // 35000 + 550 profit
|
||||
console.log(' Jahresgewinn: 550.00 €');
|
||||
console.log(' -----------');
|
||||
console.log(' Summe Eigenkapital: 188,550.00 €\n');
|
||||
console.log(' Summe Eigenkapital: 185,550.00 €\n');
|
||||
|
||||
console.log('Fremdkapital:');
|
||||
console.log(' Darlehen: 30,000.00 €');
|
||||
@@ -478,6 +498,11 @@ tap.test('should generate final balance sheet (Schlussbilanz)', async () => {
|
||||
const totalAssets = balanceSheet.assets.totalAssets;
|
||||
const totalLiabilitiesAndEquity = balanceSheet.liabilities.totalLiabilities + balanceSheet.equity.totalEquity;
|
||||
|
||||
console.log('Balance Sheet Check:');
|
||||
console.log(' Total Assets:', totalAssets);
|
||||
console.log(' Total Liabilities + Equity:', totalLiabilitiesAndEquity);
|
||||
console.log(' Difference:', Math.abs(totalAssets - totalLiabilitiesAndEquity));
|
||||
|
||||
expect(Math.abs(totalAssets - totalLiabilitiesAndEquity)).toBeLessThan(0.01);
|
||||
console.log('✓ Balance Sheet is balanced!');
|
||||
});
|
||||
@@ -486,6 +511,7 @@ tap.test('should generate trial balance (Summen- und Saldenliste)', async () =>
|
||||
const trialBalance = await api.generateTrialBalance({
|
||||
dateFrom: new Date('2024-01-01'),
|
||||
dateTo: new Date('2024-12-31'),
|
||||
skrType: 'SKR03',
|
||||
});
|
||||
|
||||
expect(trialBalance).toBeDefined();
|
||||
|
@@ -122,11 +122,11 @@ export class Reports {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
amount: balance, // Keep the sign for correct calculation
|
||||
};
|
||||
|
||||
revenueEntries.push(entry);
|
||||
totalRevenue += Math.abs(balance);
|
||||
totalRevenue += balance; // Revenue accounts normally have credit balance (positive)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,23 +138,24 @@ export class Reports {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
amount: balance, // Keep the sign - negative balance reduces expenses
|
||||
};
|
||||
|
||||
expenseEntries.push(entry);
|
||||
totalExpenses += Math.abs(balance);
|
||||
totalExpenses += balance; // Expense accounts normally have debit balance (positive)
|
||||
// But credit balances (negative) reduce total expenses
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
// Calculate percentages using absolute values to avoid negative percentages
|
||||
revenueEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
totalRevenue !== 0 ? (Math.abs(entry.amount) / Math.abs(totalRevenue)) * 100 : 0;
|
||||
});
|
||||
|
||||
expenseEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
totalRevenue !== 0 ? (Math.abs(entry.amount) / Math.abs(totalRevenue)) * 100 : 0;
|
||||
});
|
||||
|
||||
// Sort entries by account number
|
||||
@@ -214,7 +215,7 @@ export class Reports {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
amount: balance, // Keep the sign for display
|
||||
};
|
||||
|
||||
// Classify as current or fixed based on account class
|
||||
@@ -224,7 +225,7 @@ export class Reports {
|
||||
fixedAssets.push(entry);
|
||||
}
|
||||
|
||||
totalAssets += Math.abs(balance);
|
||||
totalAssets += balance; // Add with sign to get correct total
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ export class Reports {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
amount: balance, // Keep the sign for display
|
||||
};
|
||||
|
||||
// Classify as current or long-term based on account number
|
||||
@@ -253,7 +254,7 @@ export class Reports {
|
||||
longTermLiabilities.push(entry);
|
||||
}
|
||||
|
||||
totalLiabilities += Math.abs(balance);
|
||||
totalLiabilities += balance; // Add with sign to get correct total
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,23 +269,27 @@ export class Reports {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
amount: balance, // Keep the sign for display
|
||||
};
|
||||
|
||||
equityEntries.push(entry);
|
||||
totalEquity += Math.abs(balance);
|
||||
totalEquity += balance; // Add with sign to get correct total
|
||||
}
|
||||
}
|
||||
|
||||
// Add current year profit/loss
|
||||
// Add current year profit/loss only if accounts haven't been closed
|
||||
// Check if revenue/expense accounts have non-zero balances (indicates not closed)
|
||||
const incomeStatement = await this.getIncomeStatement(params);
|
||||
if (incomeStatement.netIncome !== 0) {
|
||||
|
||||
// Only add current year profit/loss if we have unclosed revenue/expense accounts
|
||||
// (i.e., the income statement shows non-zero revenue or expenses)
|
||||
if (incomeStatement.netIncome !== 0 && (incomeStatement.totalRevenue !== 0 || incomeStatement.totalExpenses !== 0)) {
|
||||
equityEntries.push({
|
||||
accountNumber: '9999',
|
||||
accountName: 'Current Year Profit/Loss',
|
||||
amount: Math.abs(incomeStatement.netIncome),
|
||||
amount: incomeStatement.netIncome, // Keep the sign
|
||||
});
|
||||
totalEquity += Math.abs(incomeStatement.netIncome);
|
||||
totalEquity += incomeStatement.netIncome; // Add with sign
|
||||
}
|
||||
|
||||
// Sort entries
|
||||
|
Reference in New Issue
Block a user