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
@@ -0,0 +1,36 @@
/**
* Migration: snapshot the chosen VAT code on quotes + invoices.
*
* The invoice/quote editors now pick an output VAT code from the central
* vat_codes registry (Settings → Accounting) instead of free-typing a rate.
* We snapshot the CODE STRING (e.g. "UN81") on the document at create time —
* alongside the existing vat_rate — so the Treuhänder/accounting export emits
* exactly the code the document was issued with, immutably. Editing or deleting
* a vat_codes row later never changes a historical document's export code.
*
* quotes.vat_code nullable string (snapshot)
* invoices.vat_code nullable string (snapshot)
*
* Legacy rows stay null; the export falls back to the rate→code map for those.
* Idempotent: columns guarded.
*/
exports.up = async function (knex) {
for (const tbl of ['quotes', 'invoices']) {
if ((await knex.schema.hasTable(tbl)) && !(await knex.schema.hasColumn(tbl, 'vat_code'))) {
await knex.schema.alterTable(tbl, (table) => {
table.string('vat_code', 16);
});
}
}
};
exports.down = async function (knex) {
for (const tbl of ['quotes', 'invoices']) {
if ((await knex.schema.hasTable(tbl)) && (await knex.schema.hasColumn(tbl, 'vat_code'))) {
await knex.schema.alterTable(tbl, (table) => {
table.dropColumn('vat_code');
});
}
}
};