feat(accounting): snapshot the chosen VAT code on quote/invoice create + storno

Wires the vat_code snapshot (migration 130) through the write paths: quote
create/update, the main invoice create, and the Storno carry-over (so a
cancellation exports the same code as the invoice it reverses). Guarded with
hasColumnCached; reads payload.vatCode (sent by the editor dropdown, coming in a
later slice — inert until then, falls back to the rate→code map). 72 tests pass.
This commit is contained in:
Luca
2026-06-15 23:57:23 +02:00
parent 0a7dc1cf5d
commit 5b52969e36
2 changed files with 19 additions and 0 deletions
+10
View File
@@ -43,6 +43,7 @@ function getHierarchyHelpers() {
// D.2 — `ensureInt` + `ensureNumber` consolidated into utils/numericHelpers.
const { ensureInt, ensureNumber } = require('../utils/numericHelpers');
const { hasColumnCached } = require('../utils/schemaCache');
function formatNumberInTemplate(format, year, seq) {
return format
@@ -955,6 +956,11 @@ async function createInvoice(payload, adminId, trx = db) {
created_at: new Date(),
updated_at: new Date(),
};
// Migration 130 — snapshot the chosen output VAT code (immutable; the
// accounting export emits exactly this rather than re-deriving from the map).
if (payload.vatCode !== undefined && await hasColumnCached('invoices', 'vat_code')) {
row.vat_code = payload.vatCode ? String(payload.vatCode).slice(0, 16) : null;
}
const inserted = await trx('invoices').insert(row).returning('id');
const invoiceId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
@@ -2212,6 +2218,10 @@ async function createStorno(originalId, adminId, trx = db) {
currency: original.currency,
language: original.language,
vat_rate: original.vat_rate,
// Migration 130 — carry the original's VAT-code snapshot onto the Storno so
// both documents export the same code. Conditional spread = safe on pre-130
// DBs (undefined → omitted).
...(original.vat_code ? { vat_code: original.vat_code } : {}),
shipping_amount_minor: -ensureInt(original.shipping_amount_minor || 0),
net_amount_minor: -ensureInt(original.net_amount_minor),
vat_amount_minor: -ensureInt(original.vat_amount_minor),
+9
View File
@@ -558,6 +558,11 @@ async function createQuote(payload, adminId) {
if (payload.projectId !== undefined && await hasColumnCached('quotes', 'project_id')) {
row.project_id = payload.projectId || null;
}
// Migration 130 — snapshot the chosen output VAT code (immutable; the export
// emits exactly this rather than re-deriving from the mutable rate→code map).
if (payload.vatCode !== undefined && await hasColumnCached('quotes', 'vat_code')) {
row.vat_code = payload.vatCode ? String(payload.vatCode).slice(0, 16) : null;
}
const inserted = await trx('quotes').insert(row).returning('id');
const quoteId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
@@ -682,6 +687,10 @@ async function updateQuote(id, payload, adminId) {
if (Object.prototype.hasOwnProperty.call(payload, 'projectId') && await hasColumnCached('quotes', 'project_id')) {
updates.project_id = payload.projectId || null;
}
// Migration 130 — VAT code snapshot.
if (Object.prototype.hasOwnProperty.call(payload, 'vatCode') && await hasColumnCached('quotes', 'vat_code')) {
updates.vat_code = payload.vatCode ? String(payload.vatCode).slice(0, 16) : null;
}
await trx('quotes').where({ id }).update(updates);
// When linked to a project, cascade across the deal lineage so the linked