import assert from "node:assert/strict"; import test from "node:test"; import { buildExportZip, getBankAccountBookingLine, getCreatedDocumentRevenueLines } from "../src/utils/export/datev"; import { Uint8ArrayReader, TextWriter, ZipReader } from "@zip.js/zip.js"; import { statementallocations } from "../db/schema"; test("exports gross amounts for DATEV automatic revenue accounts", () => { const revenueLines = getCreatedDocumentRevenueLines({ rows: [ { mode: "position", quantity: 1, price: 100, discountPercent: 0, taxPercent: 19 }, { mode: "position", quantity: 2, price: 50, discountPercent: 0, taxPercent: 7 }, { mode: "position", quantity: 1, price: 50, discountPercent: 0, taxPercent: 0 }, ], }); assert.deepEqual(revenueLines, [ { account: "8400", amount: 119 }, { account: "8334", amount: 107 }, { account: "8290", amount: 50 }, ]); }); test("preserves negative gross amounts for cancellations", () => { const revenueLines = getCreatedDocumentRevenueLines({ rows: [ { mode: "position", quantity: -1, price: 100, discountPercent: 0, taxPercent: 19 }, ], }); assert.deepEqual(revenueLines, [{ account: "8400", amount: -119 }]); }); for (const [amount, taxKey, side] of [[-119, "9", "H"], [-107, "8", "H"], [119, "3", "S"], [107, "2", "S"], [119, "9", "S"], [-119, "3", "H"], [-100, "40", "H"]] as const) { test(`exports gross ${amount} with tax key ${taxKey} and cost centre`, () => { const columns = getBankAccountBookingLine({ amount, datevTaxKey: taxKey, account: { number: 4900, label: "Konto" }, costcentre: { id: "internal-uuid", number: "KST-1001" }, bankstatement: { id: 42, date: "2026-09-23", account: { datevNumber: "1200" } }, }).split(";"); assert.equal(columns.length, 124); assert.equal(columns[0], `${Math.abs(amount)},00`); assert.equal(columns[1], `"${side}"`); assert.equal(columns[6], "1200"); assert.equal(columns[7], "4900"); assert.equal(columns[8], `"${taxKey}"`); assert.equal(columns[36], '"KST-1001"'); assert.equal(columns[112], "0"); }); } test("exports legacy allocations and additional accounts without inventing a tax key", () => { const columns = getBankAccountBookingLine({ amount: -12.34, ownaccount: { number: "4901", name: "Zusatzkonto" }, bankstatement: { id: 1, date: "2026-09-23", account: { datevNumber: "1200" } }, }).split(";"); assert.equal(columns[0], "12,34"); assert.equal(columns[7], "4901"); assert.equal(columns[8], '""'); assert.equal(columns[36], '""'); }); test("ZIP export carries allocation metadata into the matching CSV columns and leaves invoice payments untaxed", async () => { const bank = { id: 42, date: "2026-09-23", text: "Zahlung" }; const rows = [ { allocation: { id: "a", amount: -59.5, datevTaxKey: "9" }, bs: bank, ba: { datevNumber: "1200" }, acc: { number: 4900 }, costcentre: { number: "1001" } }, { allocation: { id: "b", amount: -59.5, datevTaxKey: "9" }, bs: bank, ba: { datevNumber: "1200" }, own: { number: 4901 }, costcentre: { number: "1002" } }, { allocation: { id: "c", amount: -119, datevTaxKey: "9" }, bs: bank, ba: { datevNumber: "1200" }, ii: { id: 3, date: bank.date, reference: "ER-3" }, ii_vend: { vendorNumber: 70001 } }, { allocation: { id: "d", amount: 119, datevTaxKey: "9", manualBookingDate: bank.date }, bs: null, acc: { number: 4900 }, contra_acc: { number: 1200 } }, ]; const db = { select: () => { let result: any[] = []; const query: any = { from: (table: any) => { result = table === statementallocations ? rows : []; return query; }, leftJoin: () => query, where: () => query, orderBy: () => query, then: (resolve: any) => Promise.resolve(result).then(resolve), }; return query; }, }; const archive = await buildExportZip({ db } as any, 1, "2026-09-01", "2026-09-30", "123", "456"); const reader = new ZipReader(new Uint8ArrayReader(archive)); try { const entries = await reader.getEntries(); const csvEntry = entries.find(entry => entry.filename.startsWith("EXTF_Buchungsstapel")); assert.ok(csvEntry && "getData" in csvEntry); const csv = await csvEntry.getData!(new TextWriter()); const [, header, ...bookings] = csv.trim().split("\n").map(line => line.split(";")); assert.equal(bookings.length, 4); assert.equal(header.length, 124); for (const [i, costcentre] of ["1001", "1002"].entries()) { assert.equal(bookings[i].length, header.length); assert.equal(bookings[i][header.indexOf("Umsatz")], "59,50"); assert.equal(bookings[i][header.indexOf("BU-Schluessel")], '"9"'); assert.equal(bookings[i][header.indexOf("KOST1 - Kostenstelle")], `"${costcentre}"`); assert.equal(bookings[i][header.indexOf("Festschreibung")], "0"); } assert.equal(bookings[2][8], '""'); assert.equal(bookings[3][8], '"9"'); assert.equal(bookings[3][6], "4900"); assert.equal(bookings[3][7], "1200"); } finally { await reader.close(); } });