feat(reports): adjust financial report calculations to maintain sign for accuracy

This commit is contained in:
2025-08-10 20:13:04 +00:00
parent 10ca6f2992
commit db46612ea2
2 changed files with 73 additions and 42 deletions

View File

@@ -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();

View File

@@ -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