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.
|
// D.2 — `ensureInt` + `ensureNumber` consolidated into utils/numericHelpers.
|
||||||
const { ensureInt, ensureNumber } = require('../utils/numericHelpers');
|
const { ensureInt, ensureNumber } = require('../utils/numericHelpers');
|
||||||
|
const { hasColumnCached } = require('../utils/schemaCache');
|
||||||
|
|
||||||
function formatNumberInTemplate(format, year, seq) {
|
function formatNumberInTemplate(format, year, seq) {
|
||||||
return format
|
return format
|
||||||
@@ -955,6 +956,11 @@ async function createInvoice(payload, adminId, trx = db) {
|
|||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_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 inserted = await trx('invoices').insert(row).returning('id');
|
||||||
const invoiceId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
|
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,
|
currency: original.currency,
|
||||||
language: original.language,
|
language: original.language,
|
||||||
vat_rate: original.vat_rate,
|
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),
|
shipping_amount_minor: -ensureInt(original.shipping_amount_minor || 0),
|
||||||
net_amount_minor: -ensureInt(original.net_amount_minor),
|
net_amount_minor: -ensureInt(original.net_amount_minor),
|
||||||
vat_amount_minor: -ensureInt(original.vat_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')) {
|
if (payload.projectId !== undefined && await hasColumnCached('quotes', 'project_id')) {
|
||||||
row.project_id = payload.projectId || null;
|
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 inserted = await trx('quotes').insert(row).returning('id');
|
||||||
const quoteId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
|
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')) {
|
if (Object.prototype.hasOwnProperty.call(payload, 'projectId') && await hasColumnCached('quotes', 'project_id')) {
|
||||||
updates.project_id = payload.projectId || null;
|
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);
|
await trx('quotes').where({ id }).update(updates);
|
||||||
|
|
||||||
// When linked to a project, cascade across the deal lineage so the linked
|
// When linked to a project, cascade across the deal lineage so the linked
|
||||||
|
|||||||
Reference in New Issue
Block a user