feat(accounting): snapshot vat_code on quotes/invoices + export prefers it (foundation)

First slice of the VAT-consolidation: migration 130 adds a nullable vat_code
snapshot column to quotes + invoices, and the Treuhänder export now prefers the
invoice's snapshotted code over the (mutable) rate→code map, so a historical
invoice's VatCode never changes when codes are re-mapped. Schema-drift guarded;
behaviour-neutral until the editors start writing the snapshot (next slices).

Part of: VAT registry → Settings→Accounting, invoice VAT dropdown, registration/
reclaim toggle.
This commit is contained in:
Luca
2026-06-15 23:53:32 +02:00
parent 53a16f9f6f
commit 0a7dc1cf5d
2 changed files with 42 additions and 1 deletions
+6 -1
View File
@@ -254,6 +254,8 @@ async function buildPostings({ from, to, currency } = {}) {
const postings = [];
// 1) Revenue invoices → Dr Debitoren / Cr Ertrag (gross, output VAT code).
// Migration 130 — prefer the snapshotted vat_code; guard for pre-130 DBs.
const hasInvVatCode = await db.schema.hasColumn('invoices', 'vat_code');
const invoices = await db('invoices')
.leftJoin('customer_accounts', 'invoices.customer_account_id', 'customer_accounts.id')
.leftJoin('events', 'invoices.event_id', 'events.id')
@@ -263,6 +265,7 @@ async function buildPostings({ from, to, currency } = {}) {
.orderBy('invoices.issue_date', 'asc')
.select(
'invoices.id', 'invoices.invoice_number', 'invoices.issue_date', 'invoices.vat_rate',
...(hasInvVatCode ? ['invoices.vat_code'] : []),
'invoices.net_amount_minor', 'invoices.vat_amount_minor', 'invoices.total_amount_minor',
'customer_accounts.company_name as customer_company_name',
'customer_accounts.first_name as customer_first_name',
@@ -273,7 +276,9 @@ async function buildPostings({ from, to, currency } = {}) {
);
for (const inv of invoices) {
const revAcct = cfg.settings.defaultRevenue;
const vatCode = cfg.outputVatMap[rateKey(inv.vat_rate)] || '';
// Prefer the snapshot taken at issue time; legacy rows fall back to the
// (mutable) rate→code map.
const vatCode = inv.vat_code || cfg.outputVatMap[rateKey(inv.vat_rate)] || '';
const label = buildCustomerLabel(inv);
postings.push({
date: inv.issue_date,