fix(accounting): Banana I&E export uses the 'Category' column (not 'ContraAccount')
Real Banana Income & Expense files name the category column 'Category', not 'ContraAccount' (which the doc listed but is a double-entry concept) — so the income/expense account never landed and Banana warned 'ContraAccount column not found'. Use 'Category'. VatCode stays (it only warns on a non-VAT-enabled file; amounts are gross). Test updated.
This commit is contained in:
@@ -210,13 +210,13 @@ describe('exportPostings', () => {
|
|||||||
it('banana_ie format is Income & Expense columns, tab-separated .txt', async () => {
|
it('banana_ie format is Income & Expense columns, tab-separated .txt', async () => {
|
||||||
const { content, filename, contentType } = await ledgerService.exportPostings({ ...period, format: 'banana_ie' });
|
const { content, filename, contentType } = await ledgerService.exportPostings({ ...period, format: 'banana_ie' });
|
||||||
const [header, row] = content.trim().split('\r\n');
|
const [header, row] = content.trim().split('\r\n');
|
||||||
expect(header).toBe('Date\tDoc\tDescription\tIncome\tExpenses\tContraAccount\tVatCode');
|
expect(header).toBe('Date\tDoc\tDescription\tIncome\tExpenses\tCategory\tVatCode');
|
||||||
// The mock period holds one revenue posting (gross 108.10) → Income filled,
|
// The mock period holds one revenue posting (gross 108.10) → Income filled,
|
||||||
// Expenses empty, ContraAccount = the revenue account.
|
// Expenses empty, Category = the revenue account.
|
||||||
const cells = row.split('\t');
|
const cells = row.split('\t');
|
||||||
expect(cells[3]).toBe('108.10'); // Income
|
expect(cells[3]).toBe('108.10'); // Income
|
||||||
expect(cells[4]).toBe(''); // Expenses
|
expect(cells[4]).toBe(''); // Expenses
|
||||||
expect(cells[5]).not.toBe(''); // ContraAccount (revenue account)
|
expect(cells[5]).not.toBe(''); // Category (revenue account)
|
||||||
expect(filename).toMatch(/_banana_ie\.txt$/);
|
expect(filename).toMatch(/_banana_ie\.txt$/);
|
||||||
expect(contentType).toMatch(/text\/plain/);
|
expect(contentType).toMatch(/text\/plain/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -429,19 +429,21 @@ async function exportPostings({ from, to, currency, format = 'generic' } = {}) {
|
|||||||
} else if (fmt === 'banana_ie') {
|
} else if (fmt === 'banana_ie') {
|
||||||
// Banana Income & Expense (cash-book / Einnahmen-Ausgaben) import — for a
|
// Banana Income & Expense (cash-book / Einnahmen-Ausgaben) import — for a
|
||||||
// file that is NOT double-entry. Columns: Date, Doc, Description, Income,
|
// file that is NOT double-entry. Columns: Date, Doc, Description, Income,
|
||||||
// Expenses, ContraAccount (the income/expense account — banana.ch doc 9946),
|
// Expenses, Category (the income/expense category account), VatCode. NOTE:
|
||||||
// VatCode. Revenue → gross in Income + the revenue account; cost → gross in
|
// the category column's NameXml in an I&E file is "Category" (verified
|
||||||
// Expenses + the expense account. Amount is gross; VatCode expands the VAT.
|
// against a real Banana file) — NOT the double-entry "ContraAccount".
|
||||||
// Same TAB-separated .txt shape as double-entry.
|
// Revenue → gross in Income + the revenue account; cost → gross in Expenses
|
||||||
headers = ['Date', 'Doc', 'Description', 'Income', 'Expenses', 'ContraAccount', 'VatCode'];
|
// + the expense account. VatCode warns harmlessly on a non-VAT file. Same
|
||||||
|
// TAB-separated .txt shape as double-entry.
|
||||||
|
headers = ['Date', 'Doc', 'Description', 'Income', 'Expenses', 'Category', 'VatCode'];
|
||||||
rowOf = (p) => {
|
rowOf = (p) => {
|
||||||
const isRevenue = p.source === 'revenue';
|
const isRevenue = p.source === 'revenue';
|
||||||
const amount = minorToDecimal(p.grossMinor);
|
const amount = minorToDecimal(p.grossMinor);
|
||||||
// Contra = the P&L account: revenue account (credit side) for income,
|
// Category = the P&L account: revenue account (credit side) for income,
|
||||||
// expense account (debit side) for costs.
|
// expense account (debit side) for costs.
|
||||||
const contra = isRevenue ? p.creditAccount : p.debitAccount;
|
const category = isRevenue ? p.creditAccount : p.debitAccount;
|
||||||
return [dateOnly(p.date), p.docNumber, p.description,
|
return [dateOnly(p.date), p.docNumber, p.description,
|
||||||
isRevenue ? amount : '', isRevenue ? '' : amount, contra, p.vatCode];
|
isRevenue ? amount : '', isRevenue ? '' : amount, category, p.vatCode];
|
||||||
};
|
};
|
||||||
} else if (fmt === 'bexio') {
|
} else if (fmt === 'bexio') {
|
||||||
// bexio manual-entry import.
|
// bexio manual-entry import.
|
||||||
|
|||||||
Reference in New Issue
Block a user