diff --git a/backend/src/services/invoiceService.js b/backend/src/services/invoiceService.js index 4f708097..3248fad3 100644 --- a/backend/src/services/invoiceService.js +++ b/backend/src/services/invoiceService.js @@ -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), diff --git a/backend/src/services/quoteService.js b/backend/src/services/quoteService.js index 514864fa..c93abffc 100644 --- a/backend/src/services/quoteService.js +++ b/backend/src/services/quoteService.js @@ -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