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:
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user