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',
|
reference: 'UST-2024-Q4',
|
||||||
lines: [
|
lines: [
|
||||||
{ accountNumber: '1771', debit: 8740, description: 'USt-Saldo' }, // Total collected VAT
|
{ accountNumber: '1771', debit: 8740, description: 'USt-Saldo' }, // Total collected VAT
|
||||||
{ accountNumber: '1571', credit: 7266.50, description: 'Vorsteuer-Saldo' }, // Total input VAT
|
{ accountNumber: '1571', credit: 7191.50, description: 'Vorsteuer-Saldo' }, // Total input VAT
|
||||||
{ accountNumber: '1800', credit: 1473.50, description: 'USt-Zahllast' },
|
{ accountNumber: '1800', credit: 1548.50, description: 'USt-Zahllast' },
|
||||||
],
|
],
|
||||||
skrType: 'SKR03',
|
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 () => {
|
tap.test('should calculate income statement (GuV) before closing', async () => {
|
||||||
const incomeStatement = await api.generateIncomeStatement({
|
const incomeStatement = await api.generateIncomeStatement({
|
||||||
dateFrom: new Date('2024-01-01'),
|
dateFrom: new Date('2024-01-01'),
|
||||||
dateTo: new Date('2024-12-31'),
|
dateTo: new Date('2024-12-31'),
|
||||||
|
skrType: 'SKR03',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(incomeStatement).toBeDefined();
|
expect(incomeStatement).toBeDefined();
|
||||||
expect(incomeStatement.totalRevenue).toBeGreaterThan(0);
|
expect(incomeStatement.totalRevenue).toBeGreaterThan(0);
|
||||||
expect(incomeStatement.totalExpenses).toBeGreaterThan(0);
|
expect(incomeStatement.totalExpenses).toBeGreaterThan(0);
|
||||||
|
|
||||||
// The net income should be:
|
// Assert the exact expected values based on actual bookings
|
||||||
// Revenue: 46000 (sales)
|
// Revenue: 46000 (8400 account)
|
||||||
// Less expenses:
|
// Expenses: 5000 + 18000 + 3600 + 10000 + 2000 + 150 + 5000 + 5500 + 200 = 49450
|
||||||
// - Cost of goods: 5000
|
// Less credit balances: -1000 (insurance accrual) -3000 (inventory increase) = -4000
|
||||||
// - Personnel: 29600
|
// Net expenses: 49450 - 4000 = 45450
|
||||||
// - Rent: 2000
|
// Net income: 46000 - 45450 = 550
|
||||||
// - Office: 200
|
|
||||||
// - Vehicle: 150
|
expect(Math.round(incomeStatement.totalRevenue)).toEqual(46000);
|
||||||
// - Marketing: 5000
|
expect(Math.round(incomeStatement.totalExpenses)).toEqual(45450);
|
||||||
// - Professional: 5500
|
expect(Math.round(incomeStatement.netIncome)).toEqual(550);
|
||||||
// - Depreciation: 11040
|
|
||||||
// - Insurance: -1000 (accrual adjustment)
|
|
||||||
// - Inventory: -3000 (increase)
|
|
||||||
const expectedNetIncome = 46000 - 5000 - 29600 - 2000 - 200 - 150 - 5000 - 5500 - 11040 + 1000 + 3000;
|
|
||||||
|
|
||||||
console.log('Income Statement Summary:');
|
console.log('Income Statement Summary:');
|
||||||
console.log('Revenue:', incomeStatement.totalRevenue);
|
console.log('Revenue:', incomeStatement.totalRevenue);
|
||||||
@@ -365,8 +371,7 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
|||||||
reference: 'AB-2024-001',
|
reference: 'AB-2024-001',
|
||||||
lines: [
|
lines: [
|
||||||
{ accountNumber: '8400', debit: 46000, description: 'Erlöse abschließen' },
|
{ accountNumber: '8400', debit: 46000, description: 'Erlöse abschließen' },
|
||||||
{ accountNumber: '5900', debit: 3000, description: 'Bestandsveränderungen abschließen' },
|
{ accountNumber: '9400', credit: 46000, description: 'GuV-Konto' },
|
||||||
{ accountNumber: '9400', credit: 49000, description: 'GuV-Konto' },
|
|
||||||
],
|
],
|
||||||
skrType: 'SKR03',
|
skrType: 'SKR03',
|
||||||
});
|
});
|
||||||
@@ -378,23 +383,23 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
|||||||
reference: 'AB-2024-002',
|
reference: 'AB-2024-002',
|
||||||
lines: [
|
lines: [
|
||||||
{ accountNumber: '9400', debit: 45450, description: 'GuV-Konto' },
|
{ 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: '5400', credit: 5000, description: 'Wareneingang abschließen' },
|
||||||
{ accountNumber: '6000', credit: 18000, description: 'Löhne und Gehälter 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: '6100', credit: 3600, description: 'SV AG-Anteil abschließen' },
|
||||||
{ accountNumber: '7000', credit: 10000, description: 'AfA abschließen' },
|
{ accountNumber: '7000', credit: 10000, description: 'AfA abschließen' },
|
||||||
{ accountNumber: '7100', credit: 2000, description: 'Miete 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: '7400', credit: 150, description: 'Kfz abschließen' },
|
||||||
{ accountNumber: '6600', credit: 5000, description: 'Werbung abschließen' },
|
{ accountNumber: '6600', credit: 5000, description: 'Werbung abschließen' },
|
||||||
{ accountNumber: '6700', credit: 5500, description: 'Beratung abschließen' },
|
{ accountNumber: '6700', credit: 5500, description: 'Beratung abschließen' },
|
||||||
{ accountNumber: '6800', credit: 200, description: 'Bürobedarf abschließen' },
|
{ accountNumber: '6800', credit: 200, description: 'Bürobedarf abschließen' },
|
||||||
{ accountNumber: '5900', debit: 3000, description: 'Bestandsveränderungen abschließen (credit balance)' },
|
|
||||||
],
|
],
|
||||||
skrType: 'SKR03',
|
skrType: 'SKR03',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transfer profit/loss to equity
|
// 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) {
|
if (guv_result > 0) {
|
||||||
await api.postJournalEntry({
|
await api.postJournalEntry({
|
||||||
date: new Date('2024-12-31'),
|
date: new Date('2024-12-31'),
|
||||||
@@ -418,11 +423,26 @@ tap.test('should perform closing entries (Abschlussbuchungen)', async () => {
|
|||||||
skrType: 'SKR03',
|
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 () => {
|
tap.test('should generate final balance sheet (Schlussbilanz)', async () => {
|
||||||
const balanceSheet = await api.generateBalanceSheet({
|
const balanceSheet = await api.generateBalanceSheet({
|
||||||
date: new Date('2024-12-31'),
|
dateTo: new Date('2024-12-31'),
|
||||||
|
skrType: 'SKR03',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(balanceSheet).toBeDefined();
|
expect(balanceSheet).toBeDefined();
|
||||||
@@ -459,10 +479,10 @@ tap.test('should generate final balance sheet (Schlussbilanz)', async () => {
|
|||||||
console.log('-------------------------------');
|
console.log('-------------------------------');
|
||||||
console.log('Eigenkapital:');
|
console.log('Eigenkapital:');
|
||||||
console.log(' Gezeichnetes Kapital: 150,000.00 €');
|
console.log(' Gezeichnetes Kapital: 150,000.00 €');
|
||||||
console.log(' Gewinnrücklagen: 38,550.00 €'); // 35000 + 3550 profit
|
console.log(' Gewinnrücklagen: 35,550.00 €'); // 35000 + 550 profit
|
||||||
console.log(' Jahresgewinn: 3,550.00 €');
|
console.log(' Jahresgewinn: 550.00 €');
|
||||||
console.log(' -----------');
|
console.log(' -----------');
|
||||||
console.log(' Summe Eigenkapital: 188,550.00 €\n');
|
console.log(' Summe Eigenkapital: 185,550.00 €\n');
|
||||||
|
|
||||||
console.log('Fremdkapital:');
|
console.log('Fremdkapital:');
|
||||||
console.log(' Darlehen: 30,000.00 €');
|
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 totalAssets = balanceSheet.assets.totalAssets;
|
||||||
const totalLiabilitiesAndEquity = balanceSheet.liabilities.totalLiabilities + balanceSheet.equity.totalEquity;
|
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);
|
expect(Math.abs(totalAssets - totalLiabilitiesAndEquity)).toBeLessThan(0.01);
|
||||||
console.log('✓ Balance Sheet is balanced!');
|
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({
|
const trialBalance = await api.generateTrialBalance({
|
||||||
dateFrom: new Date('2024-01-01'),
|
dateFrom: new Date('2024-01-01'),
|
||||||
dateTo: new Date('2024-12-31'),
|
dateTo: new Date('2024-12-31'),
|
||||||
|
skrType: 'SKR03',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(trialBalance).toBeDefined();
|
expect(trialBalance).toBeDefined();
|
||||||
|
@@ -122,11 +122,11 @@ export class Reports {
|
|||||||
const entry: IIncomeStatementEntry = {
|
const entry: IIncomeStatementEntry = {
|
||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
accountName: account.accountName,
|
accountName: account.accountName,
|
||||||
amount: Math.abs(balance),
|
amount: balance, // Keep the sign for correct calculation
|
||||||
};
|
};
|
||||||
|
|
||||||
revenueEntries.push(entry);
|
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 = {
|
const entry: IIncomeStatementEntry = {
|
||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
accountName: account.accountName,
|
accountName: account.accountName,
|
||||||
amount: Math.abs(balance),
|
amount: balance, // Keep the sign - negative balance reduces expenses
|
||||||
};
|
};
|
||||||
|
|
||||||
expenseEntries.push(entry);
|
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) => {
|
revenueEntries.forEach((entry) => {
|
||||||
entry.percentage =
|
entry.percentage =
|
||||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
totalRevenue !== 0 ? (Math.abs(entry.amount) / Math.abs(totalRevenue)) * 100 : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
expenseEntries.forEach((entry) => {
|
expenseEntries.forEach((entry) => {
|
||||||
entry.percentage =
|
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
|
// Sort entries by account number
|
||||||
@@ -214,7 +215,7 @@ export class Reports {
|
|||||||
const entry: IBalanceSheetEntry = {
|
const entry: IBalanceSheetEntry = {
|
||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
accountName: account.accountName,
|
accountName: account.accountName,
|
||||||
amount: Math.abs(balance),
|
amount: balance, // Keep the sign for display
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classify as current or fixed based on account class
|
// Classify as current or fixed based on account class
|
||||||
@@ -224,7 +225,7 @@ export class Reports {
|
|||||||
fixedAssets.push(entry);
|
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 = {
|
const entry: IBalanceSheetEntry = {
|
||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
accountName: account.accountName,
|
accountName: account.accountName,
|
||||||
amount: Math.abs(balance),
|
amount: balance, // Keep the sign for display
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classify as current or long-term based on account number
|
// Classify as current or long-term based on account number
|
||||||
@@ -253,7 +254,7 @@ export class Reports {
|
|||||||
longTermLiabilities.push(entry);
|
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 = {
|
const entry: IBalanceSheetEntry = {
|
||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
accountName: account.accountName,
|
accountName: account.accountName,
|
||||||
amount: Math.abs(balance),
|
amount: balance, // Keep the sign for display
|
||||||
};
|
};
|
||||||
|
|
||||||
equityEntries.push(entry);
|
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);
|
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({
|
equityEntries.push({
|
||||||
accountNumber: '9999',
|
accountNumber: '9999',
|
||||||
accountName: 'Current Year Profit/Loss',
|
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
|
// Sort entries
|
||||||
|
Reference in New Issue
Block a user