@happyvertical/smrt-commerce 0.56.2 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -11
- package/README.md +137 -89
- package/agents/accounting-snapshot-mapper.md +3 -3
- package/agents/commercial-ui.md +2 -2
- package/agents/contract-money.md +9 -12
- package/agents/cost-billing.md +3 -6
- package/agents/customer-vendor-ui.md +1 -2
- package/agents/document-money.md +58 -31
- package/agents/invoice-calculations.md +31 -56
- package/agents/invoice-preparation.md +62 -73
- package/agents/invoice-quantity.md +9 -21
- package/agents/minor-unit-migration.md +4 -4
- package/agents/money-calculator.md +7 -8
- package/agents/money-model.md +18 -22
- package/agents/money-upgrade.md +92 -0
- package/agents/revenue-journal.md +2 -2
- package/agents/tax-codes.md +17 -14
- package/agents/tax-summary.md +8 -6
- package/dist/billing/period-close.d.ts.map +1 -1
- package/dist/billing/runtime.d.ts +5 -2
- package/dist/billing/runtime.d.ts.map +1 -1
- package/dist/chunks/{ContractLineItemCollection--BrGMtNz.js → ContractLineItemCollection-BZcrlrrY.js} +19 -24
- package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
- package/dist/chunks/{CustomerCollection-CjGaZSpf.js → CustomerCollection-BIPH2ZUx.js} +2 -2
- package/dist/chunks/{CustomerCollection-CjGaZSpf.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
- package/dist/chunks/{DocumentTaxCollection-KOkaQT2M.js → DocumentTaxCollection-DDzlywMW.js} +14 -7
- package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-BUe_8FT1.js → InvoiceCollection-cinzAqt6.js} +50 -161
- package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-DF2eaJed.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
- package/dist/chunks/{TaxCodeCollection-DF2eaJed.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
- package/dist/chunks/{TaxComponentCollection-3x24xSNh.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-3x24xSNh.js.map → TaxComponentCollection-C4zxnyVu.js.map} +1 -1
- package/dist/chunks/VendorCollection-D6ZcUYy8.js +100 -0
- package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +1 -0
- package/dist/chunks/{allocation-integrity-Cq0g6CfO.js → allocation-integrity-WmWyXhtG.js} +6 -6
- package/dist/chunks/{allocation-integrity-Cq0g6CfO.js.map → allocation-integrity-WmWyXhtG.js.map} +1 -1
- package/dist/chunks/calculations-C9daQLsh.js +82 -0
- package/dist/chunks/calculations-C9daQLsh.js.map +1 -0
- package/dist/chunks/contract-money-EuToOghh.js +301 -0
- package/dist/chunks/contract-money-EuToOghh.js.map +1 -0
- package/dist/chunks/detect-CY9ufaFb.js +328 -0
- package/dist/chunks/detect-CY9ufaFb.js.map +1 -0
- package/dist/chunks/{document-money-oUDvdjOf.js → document-money-DV5t4vlK.js} +388 -62
- package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
- package/dist/chunks/{invoice-posting-CF3fE_Ec.js → invoice-posting-C2i5dOVZ.js} +20 -22
- package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +1 -0
- package/dist/chunks/{revenue-journal-DY9fro8E.js → revenue-journal-LLA9Kh_i.js} +3 -14
- package/dist/chunks/revenue-journal-LLA9Kh_i.js.map +1 -0
- package/dist/chunks/tenant-money-barrier-6TlqUeVj.js +255 -0
- package/dist/chunks/tenant-money-barrier-6TlqUeVj.js.map +1 -0
- package/dist/chunks/types-Bbm0Xflc.js.map +1 -1
- package/dist/chunks/{units-CNcjH_eE.js → units-8ElbjVZK.js} +18 -3
- package/dist/chunks/units-8ElbjVZK.js.map +1 -0
- package/dist/collections/ContractCollection.d.ts.map +1 -1
- package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
- package/dist/collections/InvoiceCollection.d.ts.map +1 -1
- package/dist/collections/InvoiceLineItemCollection.d.ts +0 -30
- package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
- package/dist/collections/TaxCodeCollection.d.ts +5 -1
- package/dist/collections/TaxCodeCollection.d.ts.map +1 -1
- package/dist/collections/TaxSettingsCollection.d.ts +15 -8
- package/dist/collections/TaxSettingsCollection.d.ts.map +1 -1
- package/dist/collections/TaxSummaryReportCollection.d.ts +3 -3
- package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -1
- package/dist/collections/index.d.ts +0 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/commerce-money-tools.d.ts.map +1 -1
- package/dist/contract-money.d.ts +1 -13
- package/dist/contract-money.d.ts.map +1 -1
- package/dist/document-money-internal.d.ts +7 -3
- package/dist/document-money-internal.d.ts.map +1 -1
- package/dist/document-money.d.ts +1 -11
- package/dist/document-money.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +667 -1650
- package/dist/index.js.map +1 -1
- package/dist/invoice-accounting-sync.d.ts.map +1 -1
- package/dist/invoice-mcp.d.ts +3 -3
- package/dist/invoice-mcp.d.ts.map +1 -1
- package/dist/invoice-posting.d.ts +11 -5
- package/dist/invoice-posting.d.ts.map +1 -1
- package/dist/invoice-record-screen.d.ts +1 -1
- package/dist/invoice-record-screen.d.ts.map +1 -1
- package/dist/invoices.d.ts +6 -1
- package/dist/invoices.d.ts.map +1 -1
- package/dist/invoices.js +2 -2
- package/dist/manifest.json +37 -765
- package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
- package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
- package/dist/models/Contract.d.ts.map +1 -1
- package/dist/models/ContractLineItem.d.ts +6 -22
- package/dist/models/ContractLineItem.d.ts.map +1 -1
- package/dist/models/Invoice.d.ts +18 -48
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/InvoiceLineItem.d.ts +8 -37
- package/dist/models/InvoiceLineItem.d.ts.map +1 -1
- package/dist/models/TaxSettings.d.ts +0 -10
- package/dist/models/TaxSettings.d.ts.map +1 -1
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/playground.js +1 -1
- package/dist/playground.js.map +1 -1
- package/dist/recipes.d.ts +8 -11
- package/dist/recipes.d.ts.map +1 -1
- package/dist/revenue-journal.d.ts +0 -8
- package/dist/revenue-journal.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +79 -661
- package/dist/svelte/components/InvoiceEditor.svelte +7 -148
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts +4 -1
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceTotals.svelte +14 -118
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -27
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
- package/dist/svelte/components/MoneyInvoiceEditor.svelte +3 -23
- package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +3 -26
- package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +2 -3
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -2
- package/dist/svelte/invoices/calculations.d.ts +3 -108
- package/dist/svelte/invoices/calculations.d.ts.map +1 -1
- package/dist/svelte/invoices/calculations.js +12 -309
- package/dist/svelte/invoices/messages.d.ts +1 -37
- package/dist/svelte/invoices/messages.d.ts.map +1 -1
- package/dist/svelte/invoices/messages.js +1 -37
- package/dist/svelte/invoices/money-editor.d.ts +1 -1
- package/dist/svelte/invoices/money-editor.d.ts.map +1 -1
- package/dist/svelte/invoices/types.d.ts +14 -114
- package/dist/svelte/invoices/types.d.ts.map +1 -1
- package/dist/svelte/playground/InvoicePreparationPreview.svelte +85 -94
- package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -1
- package/dist/svelte/playground.js +1 -1
- package/dist/tax/index.d.ts +2 -1
- package/dist/tax/index.d.ts.map +1 -1
- package/dist/tax/standard-codes.d.ts +12 -0
- package/dist/tax/standard-codes.d.ts.map +1 -0
- package/dist/tax/tenant-bootstrap.d.ts +29 -0
- package/dist/tax/tenant-bootstrap.d.ts.map +1 -0
- package/dist/tenant-money-barrier.d.ts +12 -44
- package/dist/tenant-money-barrier.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -23
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui.js +1 -1
- package/dist/ui.js.map +1 -1
- package/dist/upgrade/detect.d.ts +129 -0
- package/dist/upgrade/detect.d.ts.map +1 -0
- package/dist/upgrade/money-v2.d.ts +81 -0
- package/dist/upgrade/money-v2.d.ts.map +1 -0
- package/package.json +14 -13
- package/agents/line-amount-migration.md +0 -50
- package/dist/chunks/ContractLineItemCollection--BrGMtNz.js.map +0 -1
- package/dist/chunks/DocumentTaxCollection-KOkaQT2M.js.map +0 -1
- package/dist/chunks/InvoiceCollection-BUe_8FT1.js.map +0 -1
- package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js +0 -468
- package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js.map +0 -1
- package/dist/chunks/TaxSettingsCollection-DYYF-In-.js +0 -319
- package/dist/chunks/TaxSettingsCollection-DYYF-In-.js.map +0 -1
- package/dist/chunks/calculations-DaHR17il.js +0 -333
- package/dist/chunks/calculations-DaHR17il.js.map +0 -1
- package/dist/chunks/document-money-oUDvdjOf.js.map +0 -1
- package/dist/chunks/invoice-posting-CF3fE_Ec.js.map +0 -1
- package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
- package/dist/chunks/tenant-money-barrier-y6EAa6vY.js +0 -135
- package/dist/chunks/tenant-money-barrier-y6EAa6vY.js.map +0 -1
- package/dist/chunks/units-CNcjH_eE.js.map +0 -1
- package/dist/collections/MoneyMigrationDocumentCollection.d.ts +0 -12
- package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +0 -1
- package/dist/migrations/commerceLineAmounts.d.ts +0 -84
- package/dist/migrations/commerceLineAmounts.d.ts.map +0 -1
- package/dist/models/MoneyMigrationDocument.d.ts +0 -28
- package/dist/models/MoneyMigrationDocument.d.ts.map +0 -1
- package/dist/svelte/components/InvoiceLineEditor.svelte +0 -92
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +0 -8
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +0 -1
- package/dist/svelte/invoices/fields.d.ts +0 -6
- package/dist/svelte/invoices/fields.d.ts.map +0 -1
- package/dist/svelte/invoices/fields.js +0 -22
- package/dist/tax/legacy.d.ts +0 -48
- package/dist/tax/legacy.d.ts.map +0 -1
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { MoneyValidationError, calculateMoneyDocument } from "../money.js";
|
|
2
|
-
import { ISO_4217_MINOR_UNITS } from "@happyvertical/smrt-ui/currency";
|
|
3
|
-
//#region src/svelte/invoices/currency.ts
|
|
4
|
-
var CURRENCY_PATTERN = /^[A-Z]{3}$/;
|
|
5
|
-
function normalizeCurrency(currency) {
|
|
6
|
-
const code = String(currency ?? "").trim().toUpperCase();
|
|
7
|
-
if (!CURRENCY_PATTERN.test(code)) throw new Error(`Currency '${currency}' must be a three-letter code.`);
|
|
8
|
-
return code;
|
|
9
|
-
}
|
|
10
|
-
function currencyMinorUnitExponent(currency) {
|
|
11
|
-
const code = normalizeCurrency(currency);
|
|
12
|
-
const exponent = ISO_4217_MINOR_UNITS.get(code);
|
|
13
|
-
if (exponent === void 0) throw new Error(`Unsupported currency '${code}'.`);
|
|
14
|
-
if (exponent === null) throw new Error(`Currency '${code}' does not define a minor unit.`);
|
|
15
|
-
return exponent;
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/svelte/invoices/calculations.ts
|
|
19
|
-
var MAX_MINOR = BigInt(Number.MAX_SAFE_INTEGER);
|
|
20
|
-
var MAX_TEXT = 128;
|
|
21
|
-
function decimal(value, precision) {
|
|
22
|
-
if (typeof value !== "string" || value.length > MAX_TEXT || !/^\d+(?:\.\d+)?$/.test(value)) return "invalid-decimal";
|
|
23
|
-
const [whole, fraction = ""] = value.split(".");
|
|
24
|
-
if (fraction.length > precision) return "precision";
|
|
25
|
-
return {
|
|
26
|
-
coefficient: BigInt(whole + fraction),
|
|
27
|
-
scale: 10n ** BigInt(fraction.length)
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function roundHalfUp(numerator, denominator) {
|
|
31
|
-
return (numerator * 2n + denominator) / (denominator * 2n);
|
|
32
|
-
}
|
|
33
|
-
function minor(value, digits) {
|
|
34
|
-
const parsed = decimal(value, MAX_TEXT);
|
|
35
|
-
if (typeof parsed === "string") return parsed;
|
|
36
|
-
const scaled = parsed.coefficient * 10n ** BigInt(digits);
|
|
37
|
-
if (scaled % parsed.scale !== 0n) return "precision";
|
|
38
|
-
const result = scaled / parsed.scale;
|
|
39
|
-
return result > MAX_MINOR ? "range" : result;
|
|
40
|
-
}
|
|
41
|
-
function rate(value) {
|
|
42
|
-
const parsed = decimal(value, 6);
|
|
43
|
-
if (typeof parsed === "string") return parsed;
|
|
44
|
-
return parsed.coefficient > 100n * parsed.scale ? "range" : parsed;
|
|
45
|
-
}
|
|
46
|
-
function numberDecimal(value, precision) {
|
|
47
|
-
if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER) throw new RangeError("Invoice rate or quantity is outside the supported range.");
|
|
48
|
-
const [mantissa, exponent = "0"] = value.toString().split("e");
|
|
49
|
-
const [whole, fraction = ""] = mantissa.split(".");
|
|
50
|
-
const places = fraction.length - Number(exponent);
|
|
51
|
-
if (places > precision) throw new RangeError("Invoice rate or quantity exceeds the supported decimal precision.");
|
|
52
|
-
const coefficient = BigInt(whole + fraction);
|
|
53
|
-
return places < 0 ? {
|
|
54
|
-
coefficient: coefficient * 10n ** BigInt(-places),
|
|
55
|
-
scale: 1n
|
|
56
|
-
} : {
|
|
57
|
-
coefficient,
|
|
58
|
-
scale: 10n ** BigInt(places)
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
function roundSignedLegacy(numerator, denominator) {
|
|
62
|
-
return numerator >= 0n ? roundHalfUp(numerator, denominator) : -((-numerator * 2n + denominator - 1n) / (denominator * 2n));
|
|
63
|
-
}
|
|
64
|
-
function decimalText(value) {
|
|
65
|
-
if (value.scale === 1n) return value.coefficient.toString();
|
|
66
|
-
const places = value.scale.toString().length - 1;
|
|
67
|
-
const digits = value.coefficient.toString().padStart(places + 1, "0");
|
|
68
|
-
const split = digits.length - places;
|
|
69
|
-
return `${digits.slice(0, split)}.${digits.slice(split)}`.replace(/0+$/, "").replace(/\.$/, "");
|
|
70
|
-
}
|
|
71
|
-
function legacyTaxCode(percent) {
|
|
72
|
-
const normalized = percent.replace(/^0+(?=\d)/, "").replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
73
|
-
return normalized === "0" ? {
|
|
74
|
-
id: "legacy-zero",
|
|
75
|
-
kind: "zero-rated",
|
|
76
|
-
components: []
|
|
77
|
-
} : {
|
|
78
|
-
id: `legacy-${normalized}`,
|
|
79
|
-
kind: "taxable",
|
|
80
|
-
components: [{
|
|
81
|
-
taxComponentId: `legacy-${normalized}`,
|
|
82
|
-
percent: normalized,
|
|
83
|
-
compound: false
|
|
84
|
-
}]
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function calculateInvoiceMinorLine(input, options = {}) {
|
|
88
|
-
for (const amount of [input.unitPrice, input.discount]) if (!Number.isSafeInteger(amount)) throw new RangeError("Invoice money must be safe integer minor units.");
|
|
89
|
-
const moneyModel = options.moneyModel ?? "legacy";
|
|
90
|
-
if (moneyModel === "legacy" && input.discount < 0 || input.taxRate > 1) throw new RangeError("Invoice discount or tax rate is outside the supported range.");
|
|
91
|
-
const quantity = numberDecimal(input.quantity, 6);
|
|
92
|
-
const tax = numberDecimal(input.taxRate, 8);
|
|
93
|
-
if (moneyModel === "legacy") {
|
|
94
|
-
const gross = roundSignedLegacy(quantity.coefficient * BigInt(input.unitPrice), quantity.scale);
|
|
95
|
-
const subtotal = gross - BigInt(input.discount);
|
|
96
|
-
const taxMinor = roundSignedLegacy(subtotal * tax.coefficient, tax.scale);
|
|
97
|
-
const total = subtotal + taxMinor;
|
|
98
|
-
for (const amount of [
|
|
99
|
-
gross,
|
|
100
|
-
subtotal,
|
|
101
|
-
taxMinor,
|
|
102
|
-
total
|
|
103
|
-
]) if (amount > MAX_MINOR || amount < -MAX_MINOR) throw new RangeError("Invoice line amount exceeds safe integer minor units.");
|
|
104
|
-
return {
|
|
105
|
-
grossMinor: Number(gross),
|
|
106
|
-
discountMinor: input.discount,
|
|
107
|
-
subtotalMinor: Number(subtotal),
|
|
108
|
-
taxMinor: Number(taxMinor),
|
|
109
|
-
totalMinor: Number(total)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (moneyModel !== "v2") throw new TypeError("Unknown invoice money model");
|
|
113
|
-
const percent = decimalText({
|
|
114
|
-
coefficient: tax.coefficient * 100n,
|
|
115
|
-
scale: tax.scale
|
|
116
|
-
});
|
|
117
|
-
let result;
|
|
118
|
-
try {
|
|
119
|
-
result = calculateMoneyDocument({
|
|
120
|
-
currency: "USD",
|
|
121
|
-
lineAmountType: "exclusive",
|
|
122
|
-
roundingScope: "line",
|
|
123
|
-
lines: [{
|
|
124
|
-
id: "legacy-line",
|
|
125
|
-
quantity: input.quantity,
|
|
126
|
-
unitPrice: input.unitPrice,
|
|
127
|
-
discount: input.discount === 0 ? { kind: "none" } : {
|
|
128
|
-
kind: "amount",
|
|
129
|
-
minor: input.discount
|
|
130
|
-
},
|
|
131
|
-
taxCode: legacyTaxCode(percent)
|
|
132
|
-
}]
|
|
133
|
-
}).lines[0];
|
|
134
|
-
} catch (cause) {
|
|
135
|
-
if (cause instanceof MoneyValidationError) throw new RangeError(cause.message, { cause });
|
|
136
|
-
throw cause;
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
grossMinor: result.grossMinor,
|
|
140
|
-
discountMinor: result.discountMinor,
|
|
141
|
-
subtotalMinor: result.netMinor,
|
|
142
|
-
taxMinor: result.taxMinor,
|
|
143
|
-
totalMinor: result.netMinor + result.taxMinor
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function resolveInvoiceLineDraft(line, context) {
|
|
147
|
-
const result = calculateInvoiceLine(line, context);
|
|
148
|
-
if (!result.valid) throw new RangeError(`Invalid invoice line: ${JSON.stringify(result.errors)}`);
|
|
149
|
-
const quantity = Number(line.quantity);
|
|
150
|
-
const sourceQuantity = decimal(line.quantity, 6);
|
|
151
|
-
const storedQuantity = numberDecimal(quantity, 6);
|
|
152
|
-
if (typeof sourceQuantity === "string" || sourceQuantity.coefficient * storedQuantity.scale !== storedQuantity.coefficient * sourceQuantity.scale) throw new RangeError("Invoice quantity cannot round-trip through numeric persistence.");
|
|
153
|
-
const [whole, fraction = ""] = result.effectiveTaxRate.split(".");
|
|
154
|
-
const rateDigits = BigInt(whole + fraction).toString().padStart(fraction.length + 3, "0");
|
|
155
|
-
const split = rateDigits.length - fraction.length - 2;
|
|
156
|
-
const taxRate = Number(`${rateDigits.slice(0, split)}.${rateDigits.slice(split)}`);
|
|
157
|
-
const unitPrice = minor(line.unitPrice, currencyMinorUnitExponent(context.currency));
|
|
158
|
-
if (typeof unitPrice !== "bigint") throw new RangeError("Invalid invoice unit price.");
|
|
159
|
-
const resolved = {
|
|
160
|
-
description: line.description,
|
|
161
|
-
sku: line.sku,
|
|
162
|
-
quantity,
|
|
163
|
-
unitPrice: Number(unitPrice),
|
|
164
|
-
discount: result.discountMinor,
|
|
165
|
-
taxRate,
|
|
166
|
-
amount: result.totalMinor
|
|
167
|
-
};
|
|
168
|
-
const model = calculateInvoiceMinorLine(resolved);
|
|
169
|
-
if (model.totalMinor !== result.totalMinor || model.subtotalMinor !== result.subtotalMinor || model.taxMinor !== result.taxMinor) throw new RangeError("Invoice draft and persisted calculation disagree.");
|
|
170
|
-
return resolved;
|
|
171
|
-
}
|
|
172
|
-
function calculateInvoiceLine(line, context) {
|
|
173
|
-
const errors = {};
|
|
174
|
-
if (!line.key?.trim()) errors.key = "required";
|
|
175
|
-
if (!line.description?.trim()) errors.description = "required";
|
|
176
|
-
if (!/^[A-Z]{3}$/.test(context.currency)) return {
|
|
177
|
-
valid: false,
|
|
178
|
-
errors: { unitPrice: "invalid-currency" }
|
|
179
|
-
};
|
|
180
|
-
const digits = currencyMinorUnitExponent(context.currency);
|
|
181
|
-
const quantity = decimal(line.quantity, 6);
|
|
182
|
-
const price = minor(line.unitPrice, digits);
|
|
183
|
-
if (typeof quantity === "string") errors.quantity = quantity;
|
|
184
|
-
else if (quantity.coefficient > MAX_MINOR * quantity.scale) errors.quantity = "range";
|
|
185
|
-
else try {
|
|
186
|
-
const persisted = numberDecimal(Number(line.quantity), 6);
|
|
187
|
-
if (quantity.coefficient * persisted.scale !== persisted.coefficient * quantity.scale) errors.quantity = "precision";
|
|
188
|
-
} catch {
|
|
189
|
-
errors.quantity = "precision";
|
|
190
|
-
}
|
|
191
|
-
if (typeof price === "string") errors.unitPrice = price;
|
|
192
|
-
if (line.discountType !== "flat" && line.discountType !== "percent") errors.discountType = "invalid-mode";
|
|
193
|
-
if (line.taxMode !== "inherit" && line.taxMode !== "override") errors.taxMode = "invalid-mode";
|
|
194
|
-
const discount = line.discountType === "percent" ? rate(line.discountValue) : minor(line.discountValue, digits);
|
|
195
|
-
if (typeof discount === "string") errors.discountValue = discount;
|
|
196
|
-
const effectiveTaxRate = line.taxMode === "inherit" ? context.inheritedTaxRate : line.taxRate;
|
|
197
|
-
const tax = rate(effectiveTaxRate);
|
|
198
|
-
if (typeof tax === "string") errors.taxRate = tax;
|
|
199
|
-
if (Object.keys(errors).length || typeof quantity === "string" || typeof price === "string" || typeof discount === "string" || typeof tax === "string") return {
|
|
200
|
-
valid: false,
|
|
201
|
-
errors
|
|
202
|
-
};
|
|
203
|
-
let calculated;
|
|
204
|
-
try {
|
|
205
|
-
calculated = calculateMoneyDocument({
|
|
206
|
-
currency: context.currency,
|
|
207
|
-
lineAmountType: "exclusive",
|
|
208
|
-
roundingScope: "line",
|
|
209
|
-
lines: [{
|
|
210
|
-
id: line.key,
|
|
211
|
-
quantity: Number(line.quantity),
|
|
212
|
-
unitPrice: Number(price),
|
|
213
|
-
discount: line.discountType === "percent" ? {
|
|
214
|
-
kind: "percent",
|
|
215
|
-
rate: line.discountValue
|
|
216
|
-
} : discount === 0n ? { kind: "none" } : {
|
|
217
|
-
kind: "amount",
|
|
218
|
-
minor: Number(discount)
|
|
219
|
-
},
|
|
220
|
-
taxCode: legacyTaxCode(effectiveTaxRate)
|
|
221
|
-
}]
|
|
222
|
-
}).lines[0];
|
|
223
|
-
} catch (cause) {
|
|
224
|
-
if (cause instanceof Error && "code" in cause && cause.code === "DISCOUNT_EXCEEDS_GROSS") return {
|
|
225
|
-
valid: false,
|
|
226
|
-
errors: { discountValue: "discount-exceeds-gross" }
|
|
227
|
-
};
|
|
228
|
-
return {
|
|
229
|
-
valid: false,
|
|
230
|
-
errors: { taxRate: "range" }
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
valid: true,
|
|
235
|
-
effectiveTaxRate,
|
|
236
|
-
grossMinor: calculated.grossMinor,
|
|
237
|
-
discountMinor: calculated.discountMinor,
|
|
238
|
-
subtotalMinor: calculated.netMinor,
|
|
239
|
-
taxMinor: calculated.taxMinor,
|
|
240
|
-
totalMinor: calculated.netMinor + calculated.taxMinor
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function calendarDate(value) {
|
|
244
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || value.startsWith("0000")) return false;
|
|
245
|
-
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
246
|
-
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
247
|
-
}
|
|
248
|
-
function calculateInvoiceDraft(values, options = {}) {
|
|
249
|
-
const errors = {};
|
|
250
|
-
const roundingScope = options.roundingScope === void 0 ? "line" : options.roundingScope;
|
|
251
|
-
if (roundingScope !== "line" && roundingScope !== "invoice") errors.roundingScope = "invalid-mode";
|
|
252
|
-
if (!values.customerId?.trim()) errors.customerId = "required";
|
|
253
|
-
if (!calendarDate(values.issuedOn)) errors.issuedOn = "invalid-date";
|
|
254
|
-
if (!calendarDate(values.dueOn) || values.dueOn < values.issuedOn) errors.dueOn = "invalid-date";
|
|
255
|
-
if (!/^[A-Z]{3}$/.test(values.currency)) errors.currency = "invalid-currency";
|
|
256
|
-
const inheritedRate = rate(values.taxRate);
|
|
257
|
-
if (typeof inheritedRate === "string") errors.taxRate = inheritedRate;
|
|
258
|
-
if (values.lines.length === 0) errors.lines = "required";
|
|
259
|
-
if (new Set(values.lines.map((line) => line.key)).size !== values.lines.length) errors.lines = "duplicate-key";
|
|
260
|
-
const lineResults = values.lines.map((line) => calculateInvoiceLine(line, {
|
|
261
|
-
currency: values.currency,
|
|
262
|
-
inheritedTaxRate: values.taxRate
|
|
263
|
-
}));
|
|
264
|
-
const sums = {
|
|
265
|
-
grossMinor: 0n,
|
|
266
|
-
discountMinor: 0n,
|
|
267
|
-
subtotalMinor: 0n,
|
|
268
|
-
taxMinor: 0n,
|
|
269
|
-
totalMinor: 0n
|
|
270
|
-
};
|
|
271
|
-
for (const result of lineResults) {
|
|
272
|
-
if (!result.valid) continue;
|
|
273
|
-
for (const key of Object.keys(sums)) sums[key] += BigInt(result[key]);
|
|
274
|
-
}
|
|
275
|
-
if (roundingScope === "invoice" && !Object.keys(errors).length && lineResults.every((result) => result.valid)) {
|
|
276
|
-
const taxable = lineResults.map((result, index) => {
|
|
277
|
-
if (!result.valid) throw new Error("Validated invoice line expected");
|
|
278
|
-
const [whole, fraction = ""] = result.effectiveTaxRate.split(".");
|
|
279
|
-
const digits = fraction.replace(/0+$/, "");
|
|
280
|
-
const percent = `${BigInt(whole)}${digits ? `.${digits}` : ""}`;
|
|
281
|
-
return {
|
|
282
|
-
id: values.lines[index].key,
|
|
283
|
-
amountMinor: result.subtotalMinor,
|
|
284
|
-
category: percent
|
|
285
|
-
};
|
|
286
|
-
});
|
|
287
|
-
try {
|
|
288
|
-
const calculated = calculateMoneyDocument({
|
|
289
|
-
currency: values.currency,
|
|
290
|
-
lineAmountType: "exclusive",
|
|
291
|
-
roundingScope: "document",
|
|
292
|
-
lines: taxable.map((row, index) => {
|
|
293
|
-
const result = lineResults[index];
|
|
294
|
-
if (!result.valid) throw new Error("Validated invoice line expected");
|
|
295
|
-
return {
|
|
296
|
-
id: row.id,
|
|
297
|
-
quantity: 1,
|
|
298
|
-
unitPrice: result.grossMinor,
|
|
299
|
-
discount: result.discountMinor === 0 ? { kind: "none" } : {
|
|
300
|
-
kind: "amount",
|
|
301
|
-
minor: result.discountMinor
|
|
302
|
-
},
|
|
303
|
-
taxCode: legacyTaxCode(row.category)
|
|
304
|
-
};
|
|
305
|
-
})
|
|
306
|
-
});
|
|
307
|
-
sums.taxMinor = BigInt(calculated.taxMinor);
|
|
308
|
-
sums.totalMinor = sums.subtotalMinor + sums.taxMinor;
|
|
309
|
-
} catch (cause) {
|
|
310
|
-
if (!(cause instanceof RangeError || cause instanceof MoneyValidationError)) throw cause;
|
|
311
|
-
errors.total = "range";
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (Object.values(sums).some((value) => value > MAX_MINOR)) errors.total = "range";
|
|
315
|
-
if (Object.keys(errors).length || lineResults.some((result) => !result.valid)) return {
|
|
316
|
-
valid: false,
|
|
317
|
-
errors,
|
|
318
|
-
lineResults
|
|
319
|
-
};
|
|
320
|
-
return {
|
|
321
|
-
valid: true,
|
|
322
|
-
lineResults,
|
|
323
|
-
grossMinor: Number(sums.grossMinor),
|
|
324
|
-
discountMinor: Number(sums.discountMinor),
|
|
325
|
-
subtotalMinor: Number(sums.subtotalMinor),
|
|
326
|
-
taxMinor: Number(sums.taxMinor),
|
|
327
|
-
totalMinor: Number(sums.totalMinor)
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
//#endregion
|
|
331
|
-
export { currencyMinorUnitExponent as a, resolveInvoiceLineDraft as i, calculateInvoiceLine as n, normalizeCurrency as o, calculateInvoiceMinorLine as r, calculateInvoiceDraft as t };
|
|
332
|
-
|
|
333
|
-
//# sourceMappingURL=calculations-DaHR17il.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"calculations-DaHR17il.js","names":[],"sources":["../../src/svelte/invoices/currency.ts","../../src/svelte/invoices/calculations.ts"],"sourcesContent":["/** Svelte-free currency precision shared by invoice previews and billing. */\n\nimport { ISO_4217_MINOR_UNITS } from '@happyvertical/smrt-ui/currency';\n\nconst CURRENCY_PATTERN = /^[A-Z]{3}$/;\n\n/** Upper-case, validate, and return an ISO 4217-shaped currency code. */\nexport function normalizeCurrency(currency: string): string {\n const code = String(currency ?? '')\n .trim()\n .toUpperCase();\n if (!CURRENCY_PATTERN.test(code)) {\n throw new Error(`Currency '${currency}' must be a three-letter code.`);\n }\n return code;\n}\n\n/**\n * The ISO 4217 minor-unit exponent of a currency (2 for USD and RSD, 0 for\n * JPY, 3 for IQD). Unknown codes and currencies without a minor unit fail\n * closed because assigning either an inferred or invented exponent changes\n * the represented amount.\n */\nexport function currencyMinorUnitExponent(currency: string): number {\n const code = normalizeCurrency(currency);\n const exponent = ISO_4217_MINOR_UNITS.get(code);\n if (exponent === undefined)\n throw new Error(`Unsupported currency '${code}'.`);\n if (exponent === null)\n throw new Error(`Currency '${code}' does not define a minor unit.`);\n return exponent;\n}\n","/** Exact, transport-neutral invoice draft validation shared by browser and server. */\nimport {\n calculateMoneyDocument,\n type MoneyLineResult,\n type MoneyTaxCodeInput,\n MoneyValidationError,\n} from '../../money.js';\nimport { currencyMinorUnitExponent } from './currency.js';\n\n/** Unparsed editable invoice line; invalid values remain caller-owned. */\nexport interface InvoiceLineDraft {\n /** Stable caller-owned row identity. */\n key: string;\n /** Human-readable item description. */\n description: string;\n /** Optional stock or service code. */\n sku: string;\n /** Nonnegative decimal quantity, at most six decimal places. */\n quantity: string;\n /** Unit price in currency major units. */\n unitPrice: string;\n /** Flat major-unit discount or percentage; unknown drafts remain representable. */\n discountType: string;\n /** Discount in major units or percent according to discountType. */\n discountValue: string;\n /** Inherit the authorized default or use an explicit override. */\n taxMode: string;\n /** Override percent; zero is an explicit exemption, not inheritance. */\n taxRate: string;\n}\n\n/** Editable header and lines; clients never supply authoritative calculated totals. */\nexport interface InvoiceDraftValues {\n /** Customer reference; authorization/existence must be checked by the server. */\n customerId: string;\n /** Calendar issue date, YYYY-MM-DD. */\n issuedOn: string;\n /** Calendar due date, YYYY-MM-DD. */\n dueOn: string;\n /** Uppercase three-letter currency, with Commerce's explicit minor-unit table. */\n currency: string;\n /** Free-form payment terms. */\n paymentTerms: string;\n /** Default tax percent; the server must substitute its authorized rate. */\n taxRate: string;\n /** Caller-owned ordered lines. */\n lines: readonly InvoiceLineDraft[];\n}\n\n/** Fields on which line validation may report an error. */\nexport type InvoiceLineField = keyof InvoiceLineDraft;\n/** Stable validation reasons for caller-localized error messages. */\nexport type InvoiceCalculationError =\n | 'required'\n | 'invalid-decimal'\n | 'precision'\n | 'range'\n | 'invalid-mode'\n | 'discount-exceeds-gross'\n | 'invalid-date'\n | 'duplicate-key'\n | 'invalid-currency';\n/** Currency and server-authorized inherited percentage for a line. */\nexport interface InvoiceCalculationContext {\n /** Uppercase currency code. */\n currency: string;\n /** Default tax percent, for example \"5\". */\n inheritedTaxRate: string;\n}\n/** All monetary outputs are safe integer minor units. */\nexport interface InvoiceCalculatedAmounts {\n /** Rounded quantity times unit price before discounts. */\n grossMinor: number;\n /** Discount rounded to minor units before tax. */\n discountMinor: number;\n /** Gross less discount. */\n subtotalMinor: number;\n /** Rounded tax on the discounted subtotal. */\n taxMinor: number;\n /** Subtotal plus tax. */\n totalMinor: number;\n}\n/** Success or field errors, with no plausible monetary output for an invalid line. */\nexport type InvoiceLineCalculation =\n | ({ valid: true; effectiveTaxRate: string } & InvoiceCalculatedAmounts)\n | {\n valid: false;\n errors: Partial<Record<InvoiceLineField, InvoiceCalculationError>>;\n };\n/** Header error keys include whole-invoice totals and line membership. */\nexport type InvoiceDraftField =\n | Exclude<keyof InvoiceDraftValues, 'lines'>\n | 'lines'\n | 'total'\n | 'roundingScope';\n/** Server-authorized tax rounding; absent means the historical per-line behavior. */\nexport interface InvoiceDraftCalculationOptions {\n /** Invoice scope groups equal effective percentages and rounds each group once. */\n roundingScope?: 'line' | 'invoice';\n}\n/**\n * Aggregate result retains standalone line calculations for validation/audit.\n * With invoice rounding, line tax/total are estimates, NOT allocated invoice\n * amounts, and must not be summed or persisted as authoritative invoice tax.\n */\nexport type InvoiceDraftCalculation =\n | ({\n valid: true;\n lineResults: readonly InvoiceLineCalculation[];\n } & InvoiceCalculatedAmounts)\n | {\n valid: false;\n errors: Partial<Record<InvoiceDraftField, InvoiceCalculationError>>;\n lineResults: readonly InvoiceLineCalculation[];\n };\n\ninterface Decimal {\n coefficient: bigint;\n scale: bigint;\n}\nconst MAX_MINOR = BigInt(Number.MAX_SAFE_INTEGER);\nconst MAX_TEXT = 128;\n\nfunction decimal(\n value: string,\n precision: number,\n): Decimal | InvoiceCalculationError {\n if (\n typeof value !== 'string' ||\n value.length > MAX_TEXT ||\n !/^\\d+(?:\\.\\d+)?$/.test(value)\n )\n return 'invalid-decimal';\n const [whole, fraction = ''] = value.split('.');\n if (fraction.length > precision) return 'precision';\n return {\n coefficient: BigInt(whole + fraction),\n scale: 10n ** BigInt(fraction.length),\n };\n}\n\nfunction roundHalfUp(numerator: bigint, denominator: bigint): bigint {\n return (numerator * 2n + denominator) / (denominator * 2n);\n}\n\nfunction minor(\n value: string,\n digits: number,\n): bigint | InvoiceCalculationError {\n const parsed = decimal(value, MAX_TEXT);\n if (typeof parsed === 'string') return parsed;\n const scaled = parsed.coefficient * 10n ** BigInt(digits);\n if (scaled % parsed.scale !== 0n) return 'precision';\n const result = scaled / parsed.scale;\n return result > MAX_MINOR ? 'range' : result;\n}\n\nfunction rate(value: string): Decimal | InvoiceCalculationError {\n const parsed = decimal(value, 6);\n if (typeof parsed === 'string') return parsed;\n return parsed.coefficient > 100n * parsed.scale ? 'range' : parsed;\n}\n\n/** Existing model monetary inputs, independent of currency display precision. */\nexport interface InvoiceMinorLineInput {\n /** Nonnegative quantity with at most six fractional digits. */\n quantity: number;\n /** Signed integer minor units; negative prices support existing credit lines. */\n unitPrice: number;\n /** Legacy uses a nonnegative flat discount; v2 stores the gross sign. */\n discount: number;\n /** Resolved tax fraction, at most eight fractional digits, in [0, 1]. */\n taxRate: number;\n}\n\n/** Explicit migration boundary for persisted legacy invoice-line arithmetic. */\nexport interface InvoiceMinorLineCalculationOptions {\n /** Existing callers remain legacy until #3933's per-tenant marker selects v2. */\n moneyModel?: 'legacy' | 'v2';\n}\n\nfunction numberDecimal(value: number, precision: number): Decimal {\n if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER)\n throw new RangeError(\n 'Invoice rate or quantity is outside the supported range.',\n );\n // The shortest canonical decimal preserves the caller's Number. toFixed can\n // invent different decimal digits that round back to that same binary value.\n const [mantissa, exponent = '0'] = value.toString().split('e');\n const [whole, fraction = ''] = mantissa.split('.');\n const places = fraction.length - Number(exponent);\n if (places > precision)\n throw new RangeError(\n 'Invoice rate or quantity exceeds the supported decimal precision.',\n );\n const coefficient = BigInt(whole + fraction);\n return places < 0\n ? { coefficient: coefficient * 10n ** BigInt(-places), scale: 1n }\n : { coefficient, scale: 10n ** BigInt(places) };\n}\n\nfunction roundSignedLegacy(numerator: bigint, denominator: bigint): bigint {\n return numerator >= 0n\n ? roundHalfUp(numerator, denominator)\n : -((-numerator * 2n + denominator - 1n) / (denominator * 2n));\n}\n\nfunction decimalText(value: Decimal): string {\n if (value.scale === 1n) return value.coefficient.toString();\n const places = value.scale.toString().length - 1;\n const digits = value.coefficient.toString().padStart(places + 1, '0');\n const split = digits.length - places;\n return `${digits.slice(0, split)}.${digits.slice(split)}`\n .replace(/0+$/, '')\n .replace(/\\.$/, '');\n}\n\nfunction legacyTaxCode(percent: string): MoneyTaxCodeInput {\n const normalized =\n percent\n .replace(/^0+(?=\\d)/, '')\n .replace(/(\\.\\d*?)0+$/, '$1')\n .replace(/\\.$/, '') || '0';\n return normalized === '0'\n ? { id: 'legacy-zero', kind: 'zero-rated', components: [] }\n : {\n id: `legacy-${normalized}`,\n kind: 'taxable',\n components: [\n {\n taxComponentId: `legacy-${normalized}`,\n percent: normalized,\n compound: false,\n },\n ],\n };\n}\n\n/**\n * Exact model calculation, including existing negative-price credit lines.\n * Throws before persistence for unsafe money, invalid quantity, or invalid tax.\n */\nexport function calculateInvoiceMinorLine(\n input: InvoiceMinorLineInput,\n options: InvoiceMinorLineCalculationOptions = {},\n): InvoiceCalculatedAmounts {\n for (const amount of [input.unitPrice, input.discount])\n if (!Number.isSafeInteger(amount))\n throw new RangeError('Invoice money must be safe integer minor units.');\n const moneyModel = options.moneyModel ?? 'legacy';\n if ((moneyModel === 'legacy' && input.discount < 0) || input.taxRate > 1)\n throw new RangeError(\n 'Invoice discount or tax rate is outside the supported range.',\n );\n const quantity = numberDecimal(input.quantity, 6);\n const tax = numberDecimal(input.taxRate, 8);\n if (moneyModel === 'legacy') {\n const gross = roundSignedLegacy(\n quantity.coefficient * BigInt(input.unitPrice),\n quantity.scale,\n );\n const discount = BigInt(input.discount);\n const subtotal = gross - discount;\n const taxMinor = roundSignedLegacy(subtotal * tax.coefficient, tax.scale);\n const total = subtotal + taxMinor;\n for (const amount of [gross, subtotal, taxMinor, total])\n if (amount > MAX_MINOR || amount < -MAX_MINOR)\n throw new RangeError(\n 'Invoice line amount exceeds safe integer minor units.',\n );\n return {\n grossMinor: Number(gross),\n discountMinor: input.discount,\n subtotalMinor: Number(subtotal),\n taxMinor: Number(taxMinor),\n totalMinor: Number(total),\n };\n }\n if (moneyModel !== 'v2') throw new TypeError('Unknown invoice money model');\n const percent = decimalText({\n coefficient: tax.coefficient * 100n,\n scale: tax.scale,\n });\n let result: MoneyLineResult;\n try {\n result = calculateMoneyDocument({\n currency: 'USD',\n lineAmountType: 'exclusive',\n roundingScope: 'line',\n lines: [\n {\n id: 'legacy-line',\n quantity: input.quantity,\n unitPrice: input.unitPrice,\n discount:\n input.discount === 0\n ? { kind: 'none' }\n : { kind: 'amount', minor: input.discount },\n taxCode: legacyTaxCode(percent),\n },\n ],\n }).lines[0];\n } catch (cause) {\n if (cause instanceof MoneyValidationError)\n throw new RangeError(cause.message, { cause });\n throw cause;\n }\n return {\n grossMinor: result.grossMinor,\n discountMinor: result.discountMinor,\n subtotalMinor: result.netMinor,\n taxMinor: result.taxMinor,\n totalMinor: result.netMinor + result.taxMinor,\n };\n}\n\n/** Validated line fields suitable for the existing model's resolved money columns. */\nexport interface InvoiceResolvedLine extends InvoiceMinorLineInput {\n /** Validated item description. */\n description: string;\n /** Caller stock code. */\n sku: string;\n /** Recomputed total; never copied from a browser total field. */\n amount: number;\n}\n\n/**\n * Resolve a validated editor draft to model fields. Percentage discounts become\n * exact flat minor units; inherited tax becomes the authorized effective rate.\n * Keep the raw draft alongside these fields when reopening its editing modes.\n */\nexport function resolveInvoiceLineDraft(\n line: InvoiceLineDraft,\n context: InvoiceCalculationContext,\n): InvoiceResolvedLine {\n const result = calculateInvoiceLine(line, context);\n if (!result.valid)\n throw new RangeError(\n `Invalid invoice line: ${JSON.stringify(result.errors)}`,\n );\n const quantity = Number(line.quantity);\n const sourceQuantity = decimal(line.quantity, 6);\n const storedQuantity = numberDecimal(quantity, 6);\n if (\n typeof sourceQuantity === 'string' ||\n sourceQuantity.coefficient * storedQuantity.scale !==\n storedQuantity.coefficient * sourceQuantity.scale\n )\n throw new RangeError(\n 'Invoice quantity cannot round-trip through numeric persistence.',\n );\n const [whole, fraction = ''] = result.effectiveTaxRate.split('.');\n const rateDigits = BigInt(whole + fraction)\n .toString()\n .padStart(fraction.length + 3, '0');\n const split = rateDigits.length - fraction.length - 2;\n const taxRate = Number(\n `${rateDigits.slice(0, split)}.${rateDigits.slice(split)}`,\n );\n const unitPrice = minor(\n line.unitPrice,\n currencyMinorUnitExponent(context.currency),\n );\n if (typeof unitPrice !== 'bigint')\n throw new RangeError('Invalid invoice unit price.');\n const resolved = {\n description: line.description,\n sku: line.sku,\n quantity,\n unitPrice: Number(unitPrice),\n discount: result.discountMinor,\n taxRate,\n amount: result.totalMinor,\n };\n const model = calculateInvoiceMinorLine(resolved);\n if (\n model.totalMinor !== result.totalMinor ||\n model.subtotalMinor !== result.subtotalMinor ||\n model.taxMinor !== result.taxMinor\n )\n throw new RangeError('Invoice draft and persisted calculation disagree.');\n return resolved;\n}\n\n/**\n * Validate and calculate one line using decimal integer arithmetic. Round half\n * up separately at gross, percentage discount, and tax boundaries. No rounding\n * is permitted when parsing a price or flat discount into currency minor units.\n */\nexport function calculateInvoiceLine(\n line: InvoiceLineDraft,\n context: InvoiceCalculationContext,\n): InvoiceLineCalculation {\n const errors: Partial<Record<InvoiceLineField, InvoiceCalculationError>> = {};\n if (!line.key?.trim()) errors.key = 'required';\n if (!line.description?.trim()) errors.description = 'required';\n if (!/^[A-Z]{3}$/.test(context.currency))\n return { valid: false, errors: { unitPrice: 'invalid-currency' } };\n const digits = currencyMinorUnitExponent(context.currency);\n const quantity = decimal(line.quantity, 6);\n const price = minor(line.unitPrice, digits);\n if (typeof quantity === 'string') errors.quantity = quantity;\n else if (quantity.coefficient > MAX_MINOR * quantity.scale)\n errors.quantity = 'range';\n else {\n try {\n const persisted = numberDecimal(Number(line.quantity), 6);\n if (\n quantity.coefficient * persisted.scale !==\n persisted.coefficient * quantity.scale\n )\n errors.quantity = 'precision';\n } catch {\n errors.quantity = 'precision';\n }\n }\n if (typeof price === 'string') errors.unitPrice = price;\n if (line.discountType !== 'flat' && line.discountType !== 'percent')\n errors.discountType = 'invalid-mode';\n if (line.taxMode !== 'inherit' && line.taxMode !== 'override')\n errors.taxMode = 'invalid-mode';\n const discount =\n line.discountType === 'percent'\n ? rate(line.discountValue)\n : minor(line.discountValue, digits);\n if (typeof discount === 'string') errors.discountValue = discount;\n const effectiveTaxRate =\n line.taxMode === 'inherit' ? context.inheritedTaxRate : line.taxRate;\n const tax = rate(effectiveTaxRate);\n if (typeof tax === 'string') errors.taxRate = tax;\n if (\n Object.keys(errors).length ||\n typeof quantity === 'string' ||\n typeof price === 'string' ||\n typeof discount === 'string' ||\n typeof tax === 'string'\n )\n return { valid: false, errors };\n let calculated: MoneyLineResult;\n try {\n calculated = calculateMoneyDocument({\n currency: context.currency,\n lineAmountType: 'exclusive',\n roundingScope: 'line',\n lines: [\n {\n id: line.key,\n quantity: Number(line.quantity),\n unitPrice: Number(price),\n discount:\n line.discountType === 'percent'\n ? { kind: 'percent', rate: line.discountValue }\n : discount === 0n\n ? { kind: 'none' }\n : { kind: 'amount', minor: Number(discount) },\n taxCode: legacyTaxCode(effectiveTaxRate),\n },\n ],\n }).lines[0];\n } catch (cause) {\n if (\n cause instanceof Error &&\n 'code' in cause &&\n cause.code === 'DISCOUNT_EXCEEDS_GROSS'\n )\n return {\n valid: false,\n errors: { discountValue: 'discount-exceeds-gross' },\n };\n return { valid: false, errors: { taxRate: 'range' } };\n }\n return {\n valid: true,\n effectiveTaxRate,\n grossMinor: calculated.grossMinor,\n discountMinor: calculated.discountMinor,\n subtotalMinor: calculated.netMinor,\n taxMinor: calculated.taxMinor,\n totalMinor: calculated.netMinor + calculated.taxMinor,\n };\n}\n\nfunction calendarDate(value: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value) || value.startsWith('0000'))\n return false;\n const date = new Date(`${value}T00:00:00.000Z`);\n return (\n Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value\n );\n}\n\n/** Validate the header and sum validated lines without floating-point arithmetic. */\nexport function calculateInvoiceDraft(\n values: InvoiceDraftValues,\n options: InvoiceDraftCalculationOptions = {},\n): InvoiceDraftCalculation {\n const errors: Partial<Record<InvoiceDraftField, InvoiceCalculationError>> =\n {};\n const roundingScope =\n options.roundingScope === undefined ? 'line' : options.roundingScope;\n if (roundingScope !== 'line' && roundingScope !== 'invoice')\n errors.roundingScope = 'invalid-mode';\n if (!values.customerId?.trim()) errors.customerId = 'required';\n if (!calendarDate(values.issuedOn)) errors.issuedOn = 'invalid-date';\n if (!calendarDate(values.dueOn) || values.dueOn < values.issuedOn)\n errors.dueOn = 'invalid-date';\n if (!/^[A-Z]{3}$/.test(values.currency)) errors.currency = 'invalid-currency';\n const inheritedRate = rate(values.taxRate);\n if (typeof inheritedRate === 'string') errors.taxRate = inheritedRate;\n if (values.lines.length === 0) errors.lines = 'required';\n if (\n new Set(values.lines.map((line) => line.key)).size !== values.lines.length\n )\n errors.lines = 'duplicate-key';\n const lineResults = values.lines.map((line) =>\n calculateInvoiceLine(line, {\n currency: values.currency,\n inheritedTaxRate: values.taxRate,\n }),\n );\n const sums = {\n grossMinor: 0n,\n discountMinor: 0n,\n subtotalMinor: 0n,\n taxMinor: 0n,\n totalMinor: 0n,\n };\n for (const result of lineResults) {\n if (!result.valid) continue;\n for (const key of Object.keys(sums) as Array<keyof typeof sums>)\n sums[key] += BigInt(result[key]);\n }\n if (\n roundingScope === 'invoice' &&\n !Object.keys(errors).length &&\n lineResults.every((result) => result.valid)\n ) {\n // Rates have already passed exact decimal validation. Normalize textual\n // equivalents without numeric conversion before assigning component IDs.\n const taxable = lineResults.map((result, index) => {\n if (!result.valid) throw new Error('Validated invoice line expected');\n const [whole, fraction = ''] = result.effectiveTaxRate.split('.');\n const digits = fraction.replace(/0+$/, '');\n const percent = `${BigInt(whole)}${digits ? `.${digits}` : ''}`;\n return {\n id: values.lines[index].key,\n amountMinor: result.subtotalMinor,\n category: percent,\n };\n });\n try {\n const calculated = calculateMoneyDocument({\n currency: values.currency,\n lineAmountType: 'exclusive',\n roundingScope: 'document',\n lines: taxable.map((row, index) => {\n const result = lineResults[index];\n if (!result.valid) throw new Error('Validated invoice line expected');\n return {\n id: row.id,\n quantity: 1,\n unitPrice: result.grossMinor,\n discount:\n result.discountMinor === 0\n ? ({ kind: 'none' } as const)\n : ({ kind: 'amount', minor: result.discountMinor } as const),\n taxCode: legacyTaxCode(row.category),\n };\n }),\n });\n sums.taxMinor = BigInt(calculated.taxMinor);\n sums.totalMinor = sums.subtotalMinor + sums.taxMinor;\n } catch (cause) {\n if (\n !(cause instanceof RangeError || cause instanceof MoneyValidationError)\n )\n throw cause;\n errors.total = 'range';\n }\n }\n if (Object.values(sums).some((value) => value > MAX_MINOR))\n errors.total = 'range';\n if (Object.keys(errors).length || lineResults.some((result) => !result.valid))\n return { valid: false, errors, lineResults };\n return {\n valid: true,\n lineResults,\n grossMinor: Number(sums.grossMinor),\n discountMinor: Number(sums.discountMinor),\n subtotalMinor: Number(sums.subtotalMinor),\n taxMinor: Number(sums.taxMinor),\n totalMinor: Number(sums.totalMinor),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"document-money-oUDvdjOf.js","names":["saved"],"sources":["../../src/document-money.ts"],"sourcesContent":["/** Transactional Invoice money aggregate persistence. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from './collections/CustomerCollection.js';\nimport {\n DocumentTaxCollection,\n type DocumentTaxWriteInput,\n} from './collections/DocumentTaxCollection.js';\nimport { InvoiceCollection } from './collections/InvoiceCollection.js';\nimport { InvoiceLineItemCollection } from './collections/InvoiceLineItemCollection.js';\nimport { TaxCodeCollection } from './collections/TaxCodeCollection.js';\nimport { TaxComponentCollection } from './collections/TaxComponentCollection.js';\nimport {\n allocateFrozenCredit,\n type FrozenCreditAllocation,\n type IssuedCreditConsumption,\n} from './credit-allocation.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n lockDocumentMoneyParents,\n withDocumentMoneyObjectTransaction,\n withDocumentMoneyTransaction,\n} from './document-money-internal.js';\nimport type { DocumentTax } from './models/DocumentTax.js';\nimport type { Invoice } from './models/Invoice.js';\nimport { InvoiceLineItem } from './models/InvoiceLineItem.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport {\n calculateMoneyDocument,\n type MoneyDiscountInput,\n MoneyValidationError,\n type ResolvableTaxCodeInput,\n resolveLineTaxCode,\n} from './money.js';\nimport {\n lockTenantMoneyMigrationOwner,\n lockTenantMoneyRuntime,\n withTenantMoneyRuntime,\n} from './tenant-money-barrier.js';\nimport type { InvoiceTaxComponentSnapshot } from './types/index.js';\nimport { InvoiceStatus } from './types/index.js';\n\ntype ProductTaxDefaults = {\n tenantId: string | null;\n defaultSalesTaxCodeId: string;\n};\n\ntype DeleteOptions = { expectedUpdatedAt?: Date | string };\n\nexport interface PreviewInvoiceMoneyInput {\n db: DatabaseInterface;\n tenantId: string;\n invoiceId: string;\n draft?: {\n header?: Partial<\n Pick<\n Invoice,\n | 'currency'\n | 'lineAmountType'\n | 'taxRoundingScope'\n | 'providerTaxAmount'\n | 'customerId'\n >\n >;\n lines?: Array<\n Partial<\n Pick<\n InvoiceLineItem,\n | 'id'\n | 'description'\n | 'productId'\n | 'quantity'\n | 'unitPrice'\n | 'discountType'\n | 'discountRate'\n | 'discount'\n | 'taxMode'\n | 'taxCodeId'\n >\n >\n >;\n };\n}\n\nexport interface PreviewInvoiceMoneyResult {\n source: 'calculated';\n header: {\n currency: string;\n lineAmountType: Invoice['lineAmountType'];\n taxRoundingScope: Invoice['taxRoundingScope'];\n subtotalMinor: number;\n discountTotalMinor: number;\n taxAmountMinor: number;\n providerTaxAmountMinor: number;\n totalAmountMinor: number;\n };\n lines: Array<{\n id: string;\n grossMinor: number;\n discountMinor: number;\n amountMinor: number;\n taxMinor: number;\n taxCodeId: string | null;\n taxCodeCode: string | null;\n taxCodeName: string | null;\n taxComponents: InvoiceLineItem['taxComponents'];\n }>;\n taxes: DocumentTaxWriteInput[];\n}\n\nexport interface GetInvoiceMoneyFactsInput {\n db: DatabaseInterface;\n tenantId: string;\n invoiceId: string;\n}\n\nexport interface InvoiceMoneyFacts {\n source: 'stored';\n invoiceId: string;\n moneySnapshotId: string;\n kind: Invoice['kind'];\n status: Invoice['status'];\n header: PreviewInvoiceMoneyResult['header'];\n lines: PreviewInvoiceMoneyResult['lines'];\n taxes: DocumentTaxWriteInput[];\n}\n\nconst FROZEN_INVOICE_FIELDS = [\n 'kind',\n 'creditedInvoiceId',\n 'tenantId',\n 'customerId',\n 'contractId',\n 'invoiceNumber',\n 'reference',\n 'issueDate',\n 'dueDate',\n 'subtotal',\n 'discountTotal',\n 'taxAmount',\n 'providerTaxAmount',\n 'totalAmount',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'sellerTaxRegistrationsJson',\n 'partyTaxId',\n 'moneySnapshotId',\n 'notes',\n 'customerNotes',\n 'terms',\n] as const;\n\nconst FROZEN_LINE_FIELDS = [\n 'tenantId',\n 'invoiceId',\n 'creditedLineId',\n 'creditCalculationBasisJson',\n 'description',\n 'sku',\n 'productId',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'taxAmount',\n 'taxRate',\n 'amount',\n 'sourceType',\n 'sourceId',\n 'periodStart',\n 'periodEnd',\n 'revenueAccountId',\n 'sortOrder',\n 'moneySnapshotId',\n] as const;\n\nfunction comparable(value: unknown): unknown {\n return value instanceof Date ? value.toISOString() : (value ?? null);\n}\n\nfunction assertFieldsUnchanged<T extends object>(\n proposed: T,\n persisted: T,\n fields: readonly (keyof T)[],\n label: string,\n): void {\n for (const field of fields) {\n if (comparable(proposed[field]) !== comparable(persisted[field])) {\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `${label} ${String(field)} is frozen`,\n );\n }\n }\n}\n\nfunction assertTenantId(value: string | null): asserts value is string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money v2 requires a tenant-owned invoice',\n );\n}\n\nfunction requireId(value: string | null | undefined, label: string): string {\n if (!value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} requires a persisted ID`,\n );\n return value;\n}\n\nasync function lockRuntimeSettings(\n db: DatabaseInterface,\n tenantId: string | null,\n): Promise<TaxSettings | null> {\n if (!tenantId) return null;\n return (await lockTenantMoneyRuntime(db, tenantId)).settings;\n}\n\nfunction isFrozen(invoice: Invoice): boolean {\n return (\n invoice.status !== InvoiceStatus.DRAFT ||\n !!invoice.arJournalId ||\n !!invoice.moneySnapshotId\n );\n}\n\nfunction aggregateSignature(invoice: Invoice): string {\n return JSON.stringify([\n invoice.lineAmountType,\n invoice.taxRoundingScope,\n invoice.subtotal,\n invoice.discountTotal,\n invoice.taxAmount,\n invoice.totalAmount,\n ]);\n}\n\nasync function loadInvoice(\n db: DatabaseInterface,\n id: string,\n): Promise<{ collection: InvoiceCollection; invoice: Invoice }> {\n const collection = await InvoiceCollection.create({ db });\n const invoice = await collection.get({ id }, { cache: false });\n if (!invoice)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice ${id} was not found`,\n );\n return { collection, invoice };\n}\n\nasync function findInvoice(\n db: DatabaseInterface,\n id: string,\n): Promise<Invoice | null> {\n const collection = await InvoiceCollection.create({ db });\n return collection.get({ id }, { cache: false });\n}\n\n/** @internal Acquire the invoice money lock and reload persisted authority. */\nexport async function lockAndReloadInvoice(\n db: DatabaseInterface,\n invoiceId: string,\n tenantId: string,\n): Promise<{ collection: InvoiceCollection; invoice: Invoice }> {\n const first = await loadInvoice(db, invoiceId);\n await lockDocumentMoneyParents([\n { id: invoiceId, tenantId, collection: first.collection },\n ]);\n const invoice = await first.collection.get(\n { id: invoiceId },\n { cache: false },\n );\n if (!invoice || invoice.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant identity changed while acquiring its money lock',\n );\n return { collection: first.collection, invoice };\n}\n\nasync function lockAndReloadMoneyInvoice(\n db: DatabaseInterface,\n invoiceId: string,\n tenantId: string,\n creditedInvoiceId: string | null,\n): Promise<{\n collection: InvoiceCollection;\n invoice: Invoice;\n source: Invoice | null;\n}> {\n const collection = await InvoiceCollection.create({ db });\n const ids = [invoiceId, ...(creditedInvoiceId ? [creditedInvoiceId] : [])];\n const loaded = await Promise.all(\n ids.map((id) => collection.get({ id }, { cache: false })),\n );\n if (loaded.some((row) => !row || row.tenantId !== tenantId))\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money parents must exist in the active tenant',\n );\n await lockDocumentMoneyParents(\n ids.map((id) => ({ id, tenantId, collection })),\n );\n const invoice = await collection.get({ id: invoiceId }, { cache: false });\n const source = creditedInvoiceId\n ? await collection.get({ id: creditedInvoiceId }, { cache: false })\n : null;\n if (\n !invoice ||\n invoice.tenantId !== tenantId ||\n (source && source.tenantId !== tenantId)\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money parent identity changed while acquiring its lock',\n );\n return { collection, invoice, source };\n}\n\nfunction discountInput(line: InvoiceLineItem): MoneyDiscountInput {\n if (line.discountType === 'percent')\n return { kind: 'percent', rate: String(line.discountRate) };\n if (line.discountType === 'amount' || line.discount !== 0)\n return { kind: 'amount', minor: line.discount };\n if (line.discountType !== 'none')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Unknown discount type: ${line.discountType}`,\n );\n return { kind: 'none' };\n}\n\ntype CreditPreparation = {\n allocationByLineId: Map<string, FrozenCreditAllocation>;\n sourceByLineId: Map<string, InvoiceLineItem>;\n issuedLines: InvoiceLineItem[];\n issuedParentById: Map<string, Invoice>;\n sourceInvoice: Invoice;\n sourceTaxes: DocumentTax[];\n exhaustsDocument: boolean;\n};\n\nfunction issuedCredit(invoice: Invoice): boolean {\n return (\n invoice.kind === 'credit_note' &&\n invoice.status !== InvoiceStatus.DRAFT &&\n invoice.status !== InvoiceStatus.CANCELLED\n );\n}\n\nasync function refreshIssuedCreditBalance(\n db: DatabaseInterface,\n source: Invoice,\n): Promise<void> {\n const sourceId = requireId(source.id, 'Credited invoice');\n const credits = await (await InvoiceCollection.create({ db })).list({\n where: { tenantId: source.tenantId, creditedInvoiceId: sourceId },\n });\n const issued = credits.filter(issuedCredit).sort((left, right) => {\n const date = left.issueDate.getTime() - right.issueDate.getTime();\n if (date) return date;\n return String(left.id) < String(right.id)\n ? -1\n : String(left.id) > String(right.id)\n ? 1\n : 0;\n });\n const total = issued.reduce((sum, credit) => sum + credit.totalAmount, 0);\n if (!Number.isSafeInteger(total) || total < 0)\n throw new MoneyValidationError(\n 'UNSAFE_MONEY',\n 'Issued credit total is not a nonnegative safe integer',\n );\n let remainingSource = Math.max(0, source.totalAmount - source.amountPaid);\n for (const credit of credits) {\n const included = issued.includes(credit);\n const applied = included\n ? Math.min(credit.totalAmount, remainingSource)\n : 0;\n if (included) remainingSource -= applied;\n if (credit.creditedAmount !== applied) {\n credit.creditedAmount = applied;\n credit.updatePaymentStatus(credit.amountPaid);\n await credit.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n }\n }\n source.creditedAmount = total;\n source.updatePaymentStatus(source.amountPaid);\n await source.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });\n}\n\nfunction lineNet(line: InvoiceLineItem, parent: Invoice): number {\n return parent.lineAmountType === 'inclusive'\n ? line.amount - line.taxAmount\n : line.amount;\n}\n\nasync function prepareCreditLines(\n db: DatabaseInterface,\n invoice: Invoice,\n lines: InvoiceLineItem[],\n): Promise<CreditPreparation> {\n if (!invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'A credit note requires a credited invoice',\n );\n const sourceInvoice = await findInvoice(db, invoice.creditedInvoiceId);\n if (\n sourceInvoice?.kind !== 'invoice' ||\n sourceInvoice.tenantId !== invoice.tenantId ||\n sourceInvoice.status === InvoiceStatus.DRAFT ||\n sourceInvoice.status === InvoiceStatus.CANCELLED ||\n !sourceInvoice.moneySnapshotId\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must be an issued, non-cancelled invoice in the same tenant',\n );\n if (sourceInvoice.currency !== invoice.currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n 'Credit note currency must match its credited invoice',\n );\n if (sourceInvoice.customerId !== invoice.customerId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credit note customer must match its credited invoice',\n );\n invoice.lineAmountType = sourceInvoice.lineAmountType;\n invoice.taxRoundingScope = sourceInvoice.taxRoundingScope;\n const lineCollection = await InvoiceLineItemCollection.create({ db });\n const sourceLines = await lineCollection.findByInvoice(\n requireId(sourceInvoice.id, 'Credited invoice'),\n );\n const sourceTaxes = await (\n await DocumentTaxCollection.create({ db })\n ).findByDocument(\n requireId(sourceInvoice.tenantId, 'Credited invoice tenant'),\n 'invoice',\n requireId(sourceInvoice.id, 'Credited invoice'),\n );\n const sourceByLineId = new Map(\n sourceLines.map((line) => [requireId(line.id, 'Credited line'), line]),\n );\n const invoices = await InvoiceCollection.create({ db });\n const priorCredits = (\n await invoices.list({\n where: {\n tenantId: invoice.tenantId,\n creditedInvoiceId: invoice.creditedInvoiceId,\n },\n })\n ).filter((row) => row.id !== invoice.id && issuedCredit(row));\n const issuedLines: InvoiceLineItem[] = [];\n const issuedParentById = new Map(\n priorCredits.map((row) => [requireId(row.id, 'Issued credit note'), row]),\n );\n const consumptions: IssuedCreditConsumption[] = [];\n for (const credit of priorCredits) {\n for (const line of await lineCollection.findByInvoice(\n requireId(credit.id, 'Issued credit note'),\n )) {\n if (!line.creditedLineId) continue;\n issuedLines.push(line);\n const basis = line.creditCalculationBasis;\n consumptions.push({\n creditedLineId: line.creditedLineId,\n quantity: basis?.kind === 'quantity' ? basis.quantity : '0',\n netMinor: lineNet(line, credit),\n discountMinor: line.discount,\n });\n }\n }\n const targeted = lines.filter((line) => line.creditedLineId);\n const allocation = targeted.length\n ? allocateFrozenCredit(\n sourceLines.map((line) => ({\n id: requireId(line.id, 'Credited line'),\n quantity: String(line.quantity),\n netMinor: lineNet(line, sourceInvoice),\n discountMinor: line.discount,\n unitPrice: line.unitPrice,\n discountType: line.discountType,\n discountRate: line.discountRate,\n })),\n consumptions,\n targeted.map((line) => {\n const basis = line.creditCalculationBasis;\n return basis?.kind === 'net_amount'\n ? {\n creditedLineId: requireId(line.creditedLineId, 'Credited line'),\n netMinor: basis.netMinor,\n }\n : {\n creditedLineId: requireId(line.creditedLineId, 'Credited line'),\n quantity:\n basis?.kind === 'quantity'\n ? basis.quantity\n : String(line.quantity),\n };\n }),\n )\n : { allocations: [], exhaustsDocument: false };\n const allocationByLineId = new Map<string, FrozenCreditAllocation>();\n const targetBySource = new Map(\n targeted.map((line) => [\n requireId(line.creditedLineId, 'Credited line'),\n line,\n ]),\n );\n for (const row of allocation.allocations) {\n const line = targetBySource.get(row.creditedLineId);\n if (!line)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credit allocation lost its target line',\n );\n line.creditCalculationBasis = row.basis;\n allocationByLineId.set(requireId(line.id, 'Credit line'), row);\n }\n return {\n allocationByLineId,\n sourceByLineId,\n issuedLines,\n issuedParentById,\n sourceInvoice,\n sourceTaxes,\n exhaustsDocument: allocation.exhaustsDocument,\n };\n}\n\nasync function recomputeInvoiceAggregate(\n db: DatabaseInterface,\n invoice: Invoice,\n settings: TaxSettings,\n freezeSnapshotId = '',\n persist = true,\n proposedLines?: InvoiceLineItem[],\n): Promise<{ lines: InvoiceLineItem[]; taxRows: DocumentTaxWriteInput[] }> {\n assertTenantId(invoice.tenantId);\n const tenantId = invoice.tenantId;\n const linesCollection = await InvoiceLineItemCollection.create({ db });\n const codesCollection = await TaxCodeCollection.create({ db });\n const componentsCollection = await TaxComponentCollection.create({ db });\n const invoiceId = requireId(invoice.id, 'Invoice calculation');\n const lines =\n proposedLines ?? (await linesCollection.findByInvoice(invoiceId));\n const credit =\n invoice.kind === 'credit_note'\n ? await prepareCreditLines(db, invoice, lines)\n : null;\n const codes = await codesCollection.list({ where: { tenantId } });\n const components = await componentsCollection.list({ where: { tenantId } });\n const componentById = new Map(\n components.map((row) => [requireId(row.id, 'Tax component'), row]),\n );\n const codeById = new Map(\n codes.map((row) => [requireId(row.id, 'Tax code'), row]),\n );\n const codeInputs: Record<string, ResolvableTaxCodeInput> = {};\n for (const code of codes) {\n const codeId = requireId(code.id, 'Tax code');\n codeInputs[codeId] = {\n id: codeId,\n kind: code.kind,\n active: code.active,\n appliesTo: code.appliesTo,\n components: code.components.map((part) => {\n const component = componentById.get(part.taxComponentId);\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax component ${part.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: requireId(component.id, 'Tax component'),\n percent: component.percent,\n compound: part.compound,\n };\n }),\n };\n }\n if (credit) {\n for (const source of credit.sourceByLineId.values()) {\n if (!source.taxCodeId) continue;\n const frozenKind = credit.sourceTaxes.find(\n (row) => row.taxCodeId === source.taxCodeId,\n )?.kind;\n codeInputs[source.taxCodeId] = {\n id: source.taxCodeId,\n kind:\n frozenKind ??\n (source.taxComponents.length ? 'taxable' : 'zero-rated'),\n active: true,\n appliesTo: 'sales',\n components: source.taxComponents.map((component) => ({\n taxComponentId: component.taxComponentId,\n percent: component.percent,\n compound: false,\n })),\n };\n }\n }\n const exempt = await codesCollection.findByCode('exempt');\n const outOfScope = await codesCollection.findByCode('out-of-scope');\n const customers = await CustomerCollection.create({ db });\n const customer = invoice.customerId\n ? await customers.get({ id: invoice.customerId }, { cache: false })\n : null;\n if (customer && customer.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice customer must belong to its tenant',\n );\n\n // Products owns catalog reads. Load it lazily, on the aggregate transaction,\n // so commerce keeps the same runtime-only package edge used for ledgers.\n const productIds = [\n ...new Set(\n lines\n .filter((line) => !line.creditedLineId)\n .map((line) => line.productId)\n .filter((productId): productId is string => !!productId),\n ),\n ];\n const productById = new Map<string, ProductTaxDefaults>();\n if (productIds.length > 0) {\n const { ProductCollection } = await import(\n '@happyvertical/smrt-products/collections'\n );\n const products = await ProductCollection.create({ db });\n for (const productId of productIds) {\n const product = await products.get({ id: productId }, { cache: false });\n if (!product)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice product ${productId} was not found`,\n );\n if (product.tenantId !== null && product.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice product ${productId} must be global or belong to its tenant`,\n );\n productById.set(productId, product);\n }\n }\n\n type ResolvedMetadata = {\n id: string;\n code: string | null;\n name: string | null;\n kind: 'taxable' | 'zero-rated' | 'exempt' | 'out-of-scope';\n components: InvoiceTaxComponentSnapshot[];\n };\n const resolvedCodes = new Map<string, ResolvedMetadata | null>();\n const calculated = calculateMoneyDocument({\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n roundingScope: invoice.taxRoundingScope,\n lines: lines.map((line) => {\n if (line.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice line ${line.id} must belong to its parent tenant`,\n );\n const lineId = requireId(line.id, 'Invoice line');\n if (line.creditedLineId) {\n if (!credit)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Only a credit note may reference a credited line',\n );\n const source = credit.sourceByLineId.get(line.creditedLineId);\n const allocation = credit.allocationByLineId.get(lineId);\n if (!source || !allocation)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credited line is not part of the frozen source invoice',\n );\n const taxCode = source.taxCodeId\n ? (codeInputs[source.taxCodeId] ?? null)\n : null;\n resolvedCodes.set(\n lineId,\n source.taxCodeId\n ? {\n id: source.taxCodeId,\n code: source.taxCodeCode,\n name: source.taxCodeName,\n kind: taxCode?.kind ?? 'zero-rated',\n components: structuredClone(source.taxComponents),\n }\n : null,\n );\n line.description ||= source.description;\n line.sku ||= source.sku;\n line.productId = source.productId;\n line.revenueAccountId = source.revenueAccountId;\n if (allocation.basis.kind === 'quantity') {\n line.quantity = Number(allocation.basis.quantity);\n line.unitPrice = allocation.basis.sourceUnitPrice;\n line.discountType = allocation.discountMinor ? 'amount' : 'none';\n line.discountRate = '0';\n } else {\n line.quantity = 1;\n line.unitPrice = allocation.netMinor ?? allocation.basis.netMinor;\n line.discountType = 'none';\n line.discountRate = '0';\n }\n line.discount = allocation.discountMinor;\n return {\n id: lineId,\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n netAmountMinor:\n allocation.basis.kind === 'net_amount'\n ? (allocation.netMinor ?? allocation.basis.netMinor)\n : undefined,\n discount: allocation.discountMinor\n ? ({ kind: 'amount', minor: allocation.discountMinor } as const)\n : ({ kind: 'none' } as const),\n taxCode,\n };\n }\n if (invoice.kind === 'invoice' && line.creditCalculationBasis)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Ordinary invoice lines cannot carry a credit calculation basis',\n );\n if (invoice.kind === 'credit_note' && line.taxMode !== 'override')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Free-form credit lines require an explicit tax-code override',\n );\n const resolved = resolveLineTaxCode(\n {\n lineAmountType: invoice.lineAmountType,\n lineOverride: line.taxMode === 'override' ? line.taxCodeId : null,\n },\n {\n direction: 'sales',\n codes: codeInputs,\n customerTaxExempt: customer?.taxExempt ?? false,\n exemptCodeId: exempt?.id,\n productCodeId: line.productId\n ? productById.get(line.productId)?.defaultSalesTaxCodeId || null\n : null,\n partyCodeId: customer?.defaultTaxCodeId,\n appDefaultCodeId: settings.defaultSalesTaxCodeId,\n outOfScopeCodeId: outOfScope?.id,\n },\n );\n resolvedCodes.set(\n lineId,\n resolved.taxCode\n ? (() => {\n const code = codeById.get(resolved.taxCode.id);\n if (!code)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax code ${resolved.taxCode.id} was not loaded`,\n );\n return {\n id: requireId(code.id, 'Tax code'),\n code: code.code,\n name: code.name,\n kind: code.kind,\n components: code.components.map((part) => {\n const component = componentById.get(part.taxComponentId);\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax component ${part.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: part.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: 0,\n };\n }),\n };\n })()\n : null,\n );\n return {\n id: requireId(line.id, 'Invoice line'),\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n discount: discountInput(line),\n taxCode: resolved.taxCode,\n };\n }),\n });\n\n // When every original line is exhausted, consume the exact remaining frozen\n // component and taxable residual. This preserves document-rounding cents\n // without consulting today's rates; free-form goodwill lines remain separate.\n if (credit?.exhaustsDocument) {\n const resultById = new Map(calculated.lines.map((row) => [row.id, row]));\n const targeted = lines.filter((line) => !!line.creditedLineId);\n for (const line of targeted) {\n const source = credit.sourceByLineId.get(\n requireId(line.creditedLineId, 'Credited line'),\n );\n const result = resultById.get(requireId(line.id, 'Credit line'));\n if (!source || !result) continue;\n const prior = credit.issuedLines.filter(\n (candidate) => candidate.creditedLineId === source.id,\n );\n const priorNet = prior.reduce((sum, candidate) => {\n const parent = credit.issuedParentById.get(candidate.invoiceId);\n return sum + (parent ? lineNet(candidate, parent) : 0);\n }, 0);\n const desiredNet = lineNet(source, credit.sourceInvoice) - priorNet;\n const netDelta = desiredNet - result.netMinor;\n result.netMinor = desiredNet;\n result.amountMinor += netDelta;\n for (const sourceComponent of source.taxComponents) {\n const component = result.components.find(\n (candidate) =>\n candidate.taxComponentId === sourceComponent.taxComponentId,\n );\n if (!component) continue;\n const priorTax = prior.reduce(\n (sum, candidate) =>\n sum +\n (candidate.taxComponents.find(\n (part) => part.taxComponentId === sourceComponent.taxComponentId,\n )?.taxMinor ?? 0),\n 0,\n );\n const desiredTax = sourceComponent.taxMinor - priorTax;\n const taxDelta = desiredTax - component.taxMinor;\n component.taxMinor = desiredTax;\n result.taxMinor += taxDelta;\n if (invoice.lineAmountType === 'inclusive')\n result.amountMinor += taxDelta;\n }\n }\n const rebuilt = new Map<\n string,\n {\n taxCodeId: string | null;\n taxComponentId: string | null;\n taxableMinor: number;\n taxMinor: number;\n }\n >();\n for (const result of calculated.lines) {\n const metadata = resolvedCodes.get(result.id) ?? null;\n const parts = result.components.length\n ? result.components\n : [{ taxComponentId: null, taxMinor: 0 }];\n for (const part of parts) {\n const key = `${metadata?.id ?? ''}\\u0000${part.taxComponentId ?? ''}`;\n const row = rebuilt.get(key) ?? {\n taxCodeId: metadata?.id ?? null,\n taxComponentId: part.taxComponentId,\n taxableMinor: 0,\n taxMinor: 0,\n };\n row.taxableMinor += result.netMinor;\n row.taxMinor += part.taxMinor;\n rebuilt.set(key, row);\n }\n }\n calculated.taxes = [...rebuilt.values()].sort((left, right) => {\n const a = `${left.taxCodeId ?? ''}\\u0000${left.taxComponentId ?? ''}`;\n const b = `${right.taxCodeId ?? ''}\\u0000${right.taxComponentId ?? ''}`;\n return a < b ? -1 : a > b ? 1 : 0;\n });\n calculated.subtotalMinor = calculated.lines.reduce(\n (sum, row) => sum + row.netMinor,\n 0,\n );\n calculated.taxMinor = calculated.lines.reduce(\n (sum, row) => sum + row.taxMinor,\n 0,\n );\n calculated.totalMinor = calculated.subtotalMinor + calculated.taxMinor;\n }\n\n if (credit) {\n const resultById = new Map(calculated.lines.map((row) => [row.id, row]));\n for (const [lineId, allocation] of credit.allocationByLineId) {\n if (allocation.basis.kind !== 'quantity') continue;\n const result = resultById.get(lineId);\n const hasAmountBasisConsumption = credit.issuedLines.some(\n (line) =>\n line.creditedLineId === allocation.creditedLineId &&\n line.creditCalculationBasis?.kind === 'net_amount',\n );\n if (\n result &&\n Math.abs(result.netMinor) > Math.abs(allocation.remainingNetMinor) &&\n (!allocation.exhaustsLine || hasAmountBasisConsumption)\n )\n throw new MoneyValidationError(\n 'CREDIT_EXCEEDS_LINE',\n 'Credit quantity exceeds the remaining frozen net amount',\n );\n }\n }\n\n const lineById = new Map(\n lines.map((line) => [requireId(line.id, 'Invoice line'), line]),\n );\n for (const result of calculated.lines) {\n const line = lineById.get(result.id);\n if (!line)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Calculated line ${result.id} was not loaded`,\n );\n const code = resolvedCodes.get(requireId(line.id, 'Invoice line')) ?? null;\n line.discount = result.discountMinor;\n line.amount = result.amountMinor;\n line.taxAmount = result.taxMinor;\n line.taxCodeId = code?.id ?? null;\n line.taxCodeCode = code?.code ?? null;\n line.taxCodeName = code?.name ?? null;\n line.taxComponents = result.components.map((entry) => {\n const component = code?.components.find(\n (candidate) => candidate.taxComponentId === entry.taxComponentId,\n );\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Calculated component ${entry.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: component.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: entry.taxMinor,\n };\n });\n line.taxRate = code\n ? code.components.reduce(\n (sum, part) => sum + Number(part.percent) / 100,\n 0,\n )\n : 0;\n if (freezeSnapshotId) line.moneySnapshotId = freezeSnapshotId;\n if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE, false);\n }\n\n const taxRows = calculated.taxes.map((tax): DocumentTaxWriteInput => {\n const code = tax.taxCodeId\n ? ([...resolvedCodes.values()].find(\n (candidate) => candidate?.id === tax.taxCodeId,\n ) ?? null)\n : null;\n const snapshotComponent = tax.taxComponentId\n ? code?.components.find(\n (candidate) => candidate.taxComponentId === tax.taxComponentId,\n )\n : null;\n const currentComponent = tax.taxComponentId\n ? componentById.get(tax.taxComponentId)\n : null;\n const sourceTax = credit?.sourceTaxes.find(\n (row) =>\n row.taxCodeId === tax.taxCodeId &&\n row.taxComponentId === tax.taxComponentId,\n );\n if (tax.taxCodeId && !code)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Document tax code ${tax.taxCodeId} was not loaded`,\n );\n if (tax.taxComponentId && !snapshotComponent)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Document tax component ${tax.taxComponentId} was not loaded`,\n );\n return {\n tenantId,\n documentType: invoice.kind,\n documentId: invoiceId,\n currency: invoice.currency,\n taxCodeId: code ? requireId(code.id, 'Tax code') : null,\n taxCodeCode: code?.code ?? null,\n kind: code?.kind ?? null,\n taxComponentId: snapshotComponent?.taxComponentId ?? null,\n taxComponentName: snapshotComponent?.name ?? null,\n percent: snapshotComponent?.percent ?? null,\n liabilityAccountId:\n sourceTax?.liabilityAccountId ||\n currentComponent?.liabilityAccountId ||\n null,\n taxableMinor: tax.taxableMinor,\n taxMinor: tax.taxMinor,\n recoverableMinor: 0,\n };\n });\n if (persist) {\n const documentTaxes = await DocumentTaxCollection.create({ db });\n await documentTaxes.replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId,\n documentType: invoice.kind,\n documentId: invoiceId,\n rows: taxRows,\n });\n }\n invoice.subtotal = calculated.subtotalMinor;\n invoice.discountTotal = calculated.discountTotalMinor;\n invoice.taxAmount = calculated.taxMinor + invoice.providerTaxAmount;\n invoice.totalAmount = calculated.totalMinor + invoice.providerTaxAmount;\n return { lines, taxRows };\n}\n\nasync function previewInvoiceMoneyOnScope(\n input: PreviewInvoiceMoneyInput,\n settings: TaxSettings,\n): Promise<PreviewInvoiceMoneyResult> {\n if (!input.tenantId || getTenantId() !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview requires its active principal tenant',\n );\n const { invoice } = await loadInvoice(input.db, input.invoiceId);\n if (invoice.tenantId !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview cannot cross tenant boundaries',\n );\n if (invoice.status !== InvoiceStatus.DRAFT || invoice.moneySnapshotId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Frozen invoice money cannot be previewed as a proposed draft',\n );\n const proposedHeader = input.draft?.header;\n if (proposedHeader)\n for (const field of [\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'providerTaxAmount',\n 'customerId',\n ] as const)\n if (proposedHeader[field] !== undefined)\n invoice[field] = proposedHeader[field] as never;\n const storedLines = await (\n await InvoiceLineItemCollection.create({ db: input.db })\n ).findByInvoice(input.invoiceId);\n const storedById = new Map(storedLines.map((line) => [line.id, line]));\n const providedIds =\n input.draft?.lines\n ?.map((line) => line.id)\n .filter((id): id is string => !!id) ?? [];\n if (new Set(providedIds).size !== providedIds.length)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview line ids must be distinct',\n );\n const unknownId = providedIds.find((id) => !storedById.has(id));\n if (unknownId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice preview line ${unknownId} was not found on the invoice`,\n );\n const proposedLines = input.draft?.lines\n ? input.draft.lines.map((proposed) => {\n const line = proposed.id ? storedById.get(proposed.id) : undefined;\n const result = line ?? new InvoiceLineItem();\n for (const field of [\n 'description',\n 'productId',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n ] as const)\n if (proposed[field] !== undefined)\n result[field] = proposed[field] as never;\n result.id ||= crypto.randomUUID();\n result.invoiceId = input.invoiceId;\n result.tenantId = input.tenantId;\n return result;\n })\n : storedLines;\n const calculated = await recomputeInvoiceAggregate(\n input.db,\n invoice,\n settings,\n '',\n false,\n proposedLines,\n );\n return {\n source: 'calculated',\n header: {\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n taxRoundingScope: invoice.taxRoundingScope,\n subtotalMinor: invoice.subtotal,\n discountTotalMinor: invoice.discountTotal,\n taxAmountMinor: invoice.taxAmount,\n providerTaxAmountMinor: invoice.providerTaxAmount,\n totalAmountMinor: invoice.totalAmount,\n },\n lines: calculated.lines.map((line) => ({\n id: requireId(line.id, 'Preview line'),\n grossMinor: line.amount + line.discount,\n discountMinor: line.discount,\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n taxCodeId: line.taxCodeId,\n taxCodeCode: line.taxCodeCode,\n taxCodeName: line.taxCodeName,\n taxComponents: structuredClone(line.taxComponents),\n })),\n taxes: calculated.taxRows,\n };\n}\n\n/** Calculate a proposed draft invoice in memory through the owning resolver. */\nexport async function previewInvoiceMoney(\n input: PreviewInvoiceMoneyInput,\n): Promise<PreviewInvoiceMoneyResult> {\n return withTenantMoneyRuntime(input.db, input.tenantId, 'v2', async (scope) =>\n previewInvoiceMoneyOnScope(\n { ...input, db: scope.db },\n scope.barrier.settings as TaxSettings,\n ),\n );\n}\n\nasync function getInvoiceMoneyFactsOnScope(\n input: GetInvoiceMoneyFactsInput,\n): Promise<InvoiceMoneyFacts> {\n if (!input.tenantId || getTenantId() !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money facts require their active principal tenant',\n );\n const { invoice } = await loadInvoice(input.db, input.invoiceId);\n if (invoice.tenantId !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money facts cannot cross tenant boundaries',\n );\n const lines = await (\n await InvoiceLineItemCollection.create({ db: input.db })\n ).findByInvoice(input.invoiceId);\n const taxes = await (\n await DocumentTaxCollection.create({ db: input.db })\n ).findByDocument(input.tenantId, invoice.kind, input.invoiceId);\n return {\n source: 'stored',\n invoiceId: input.invoiceId,\n moneySnapshotId: invoice.moneySnapshotId,\n kind: invoice.kind,\n status: invoice.status,\n header: {\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n taxRoundingScope: invoice.taxRoundingScope,\n subtotalMinor: invoice.subtotal,\n discountTotalMinor: invoice.discountTotal,\n taxAmountMinor: invoice.taxAmount,\n providerTaxAmountMinor: invoice.providerTaxAmount,\n totalAmountMinor: invoice.totalAmount,\n },\n lines: lines.map((line) => ({\n id: requireId(line.id, 'Invoice line'),\n grossMinor: line.amount + line.discount,\n discountMinor: line.discount,\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n taxCodeId: line.taxCodeId,\n taxCodeCode: line.taxCodeCode,\n taxCodeName: line.taxCodeName,\n taxComponents: structuredClone(line.taxComponents),\n })),\n taxes: taxes.map((row) => ({\n tenantId: requireId(row.tenantId, 'Document tax tenant'),\n documentType: row.documentType,\n documentId: row.documentId,\n currency: row.currency,\n taxCodeId: row.taxCodeId,\n taxCodeCode: row.taxCodeCode,\n kind: row.kind,\n taxComponentId: row.taxComponentId,\n taxComponentName: row.taxComponentName,\n percent: row.percent,\n liabilityAccountId: row.liabilityAccountId,\n taxableMinor: row.taxableMinor,\n taxMinor: row.taxMinor,\n recoverableMinor: row.recoverableMinor,\n })),\n };\n}\n\n/** Read authoritative persisted money facts without resolution or recalculation. */\nexport async function getInvoiceMoneyFacts(\n input: GetInvoiceMoneyFactsInput,\n): Promise<InvoiceMoneyFacts> {\n return withTenantMoneyRuntime(input.db, input.tenantId, 'v2', async (scope) =>\n getInvoiceMoneyFactsOnScope({ ...input, db: scope.db }),\n );\n}\n\n/** Persist an invoice through either legacy compatibility or the v2 aggregate. */\nexport async function saveInvoice(input: Invoice): Promise<Invoice> {\n const priorRevision = input.updated_at;\n try {\n return await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (invoice) => {\n if (invoice.kind !== 'invoice' && invoice.kind !== 'credit_note')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice kind must be invoice or credit_note',\n );\n if (invoice.kind === 'invoice' && invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Only a credit note may reference a credited invoice',\n );\n if (invoice.kind === 'credit_note' && !invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'A credit note requires a credited invoice',\n );\n const candidateTenantId = getTenantId() ?? invoice.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const existing = invoice.id ? await findInvoice(tx, invoice.id) : null;\n const tenantId = existing?.tenantId ?? candidateTenantId;\n if (\n candidateTenantId &&\n existing?.tenantId &&\n existing.tenantId !== candidateTenantId\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant does not match its active money barrier',\n );\n if (settings?.moneyModel !== 'v2') {\n return invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: true,\n });\n }\n assertTenantId(tenantId);\n if (!existing) {\n invoice.tenantId = tenantId;\n invoice.creditedAmount = 0;\n let source: Invoice | null = null;\n if (invoice.kind === 'credit_note') {\n source = await findInvoice(\n tx,\n requireId(invoice.creditedInvoiceId, 'Credited invoice'),\n );\n if (!source || source.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must exist in the active tenant',\n );\n await lockDocumentMoneyParents([\n {\n id: requireId(source.id, 'Credited invoice'),\n tenantId,\n collection: await InvoiceCollection.create({ db: tx }),\n },\n ]);\n if (\n source.kind !== 'invoice' ||\n source.status === InvoiceStatus.DRAFT ||\n source.status === InvoiceStatus.CANCELLED ||\n !source.moneySnapshotId\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must be an issued, non-cancelled invoice',\n );\n if (\n invoice.currency !== source.currency ||\n invoice.customerId !== source.customerId\n )\n throw new MoneyValidationError(\n invoice.currency !== source.currency\n ? 'CURRENCY_MISMATCH'\n : 'INVALID_MONEY_INPUT',\n 'Credit note currency and customer must match its source invoice',\n );\n }\n invoice.lineAmountType =\n invoice.kind === 'credit_note' && source\n ? source.lineAmountType\n : settings.defaultLineAmountType;\n invoice.taxRoundingScope =\n invoice.kind === 'credit_note' && source\n ? source.taxRoundingScope\n : settings.taxRoundingScope;\n invoice.subtotal = 0;\n invoice.discountTotal = 0;\n invoice.taxAmount = invoice.providerTaxAmount;\n invoice.totalAmount = invoice.providerTaxAmount;\n if (invoice.status !== InvoiceStatus.DRAFT || invoice.arJournalId) {\n const customers = await CustomerCollection.create({ db: tx });\n const customer = invoice.customerId\n ? await customers.get(\n { id: invoice.customerId },\n { cache: false },\n )\n : null;\n invoice.moneySnapshotId = crypto.randomUUID();\n invoice.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n invoice.partyTaxId = customer?.taxId ?? '';\n }\n return invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n }\n if (invoice.tenantId !== existing.tenantId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice tenant identity cannot change',\n );\n if (\n invoice.kind !== existing.kind ||\n invoice.creditedInvoiceId !== existing.creditedInvoiceId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice kind and credited invoice identity are immutable',\n );\n const existingId = requireId(existing.id, 'Invoice');\n const locked = await lockAndReloadMoneyInvoice(\n tx,\n existingId,\n tenantId,\n existing.creditedInvoiceId,\n );\n invoice.creditedAmount = locked.invoice.creditedAmount;\n if (isFrozen(locked.invoice)) {\n if (\n invoice.arReversalJournalId !== locked.invoice.arReversalJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice reversal journal identity is server-managed',\n );\n if (\n locked.invoice.arJournalId &&\n invoice.arJournalId !== locked.invoice.arJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice revenue journal identity is frozen once assigned',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.kind === 'invoice' &&\n locked.invoice.isPaid()\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A paid invoice cannot be cancelled',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.kind === 'credit_note' &&\n locked.invoice.amountPaid > 0\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A refunded credit note cannot be cancelled',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.arJournalId &&\n !locked.invoice.arReversalJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A posted money-v2 invoice requires an atomic reversal before cancellation',\n );\n assertFieldsUnchanged(\n invoice,\n locked.invoice,\n FROZEN_INVOICE_FIELDS,\n 'Invoice',\n );\n const saved = await invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n if (locked.source)\n await refreshIssuedCreditBalance(tx, locked.source);\n return saved;\n }\n const freezing =\n invoice.status !== InvoiceStatus.DRAFT || !!invoice.arJournalId;\n const snapshotId = freezing\n ? locked.invoice.moneySnapshotId || crypto.randomUUID()\n : '';\n await recomputeInvoiceAggregate(tx, invoice, settings, snapshotId);\n if (freezing) {\n const customers = await CustomerCollection.create({ db: tx });\n const customer = invoice.customerId\n ? await customers.get({ id: invoice.customerId }, { cache: false })\n : null;\n invoice.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n invoice.partyTaxId = customer?.taxId ?? '';\n invoice.moneySnapshotId = snapshotId;\n }\n const saved = await invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n if (locked.source) await refreshIssuedCreditBalance(tx, locked.source);\n return saved;\n }),\n );\n } catch (error) {\n input.updated_at = priorRevision;\n throw error;\n }\n}\n\n/** Migration-only draft materializer; caller already owns the tenant migration transaction. */\nexport async function materializeInvoiceMoneyDraft(input: {\n db: DatabaseInterface;\n invoiceId: string;\n tenantId: string;\n migrationId: string;\n}): Promise<{ invoiceId: string; taxRows: DocumentTaxWriteInput[] }> {\n const settings = await lockTenantMoneyMigrationOwner(\n input.db,\n input.tenantId,\n input.migrationId,\n );\n const { invoice } = await lockAndReloadInvoice(\n input.db,\n input.invoiceId,\n input.tenantId,\n );\n if (isFrozen(invoice))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Only an unfrozen draft invoice can be migration-recomputed',\n );\n const result = await recomputeInvoiceAggregate(input.db, invoice, settings);\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });\n return { invoiceId: input.invoiceId, taxRows: result.taxRows };\n}\n\n/** Persist a line and recompute its complete invoice aggregate. */\nexport async function saveInvoiceLineItem(\n input: InvoiceLineItem,\n): Promise<InvoiceLineItem> {\n return withDocumentMoneyObjectTransaction(input, async (line, tx) => {\n const candidateTenantId = getTenantId() ?? line.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n if (settings?.moneyModel !== 'v2')\n return line.saveDerived(DOCUMENT_MONEY_WRITE, true);\n const persisted = line.id\n ? await (await InvoiceLineItemCollection.create({ db: tx })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n const parentId = persisted?.invoiceId ?? line.invoiceId;\n const first = await loadInvoice(tx, parentId);\n if (candidateTenantId && first.invoice.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice line tenant does not match its active money barrier',\n );\n assertTenantId(first.invoice.tenantId);\n const { invoice, source } = await lockAndReloadMoneyInvoice(\n tx,\n parentId,\n first.invoice.tenantId,\n first.invoice.creditedInvoiceId,\n );\n if (persisted && line.invoiceId !== persisted.invoiceId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice line cannot move between parents',\n );\n line.invoiceId = requireId(invoice.id, 'Invoice');\n line.tenantId = invoice.tenantId;\n if (invoice.kind === 'invoice' && line.creditedLineId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Ordinary invoice lines cannot reference credited lines',\n );\n if (\n invoice.kind === 'credit_note' &&\n line.creditedLineId &&\n (!source || source.id !== invoice.creditedInvoiceId)\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit source changed while saving its line',\n );\n if (isFrozen(invoice)) {\n if (!persisted)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot add a line to a frozen invoice',\n );\n assertFieldsUnchanged(\n line,\n persisted,\n FROZEN_LINE_FIELDS,\n 'Invoice line',\n );\n return line.saveDerived(DOCUMENT_MONEY_WRITE, false);\n }\n await line.saveDerived(DOCUMENT_MONEY_WRITE, false);\n const priorAggregate = aggregateSignature(invoice);\n await recomputeInvoiceAggregate(tx, invoice, settings);\n if (aggregateSignature(invoice) !== priorAggregate) {\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n }\n const refreshed = await (\n await InvoiceLineItemCollection.create({ db: tx })\n ).get({ id: requireId(line.id, 'Invoice line') }, { cache: false });\n if (refreshed) {\n for (const field of [\n 'discount',\n 'amount',\n 'taxAmount',\n 'taxRate',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'moneySnapshotId',\n 'updated_at',\n ] as const) {\n line[field] = refreshed[field] as never;\n }\n }\n return line;\n });\n}\n\nexport async function deleteInvoiceLineItem(\n input: InvoiceLineItem,\n options: DeleteOptions,\n): Promise<void> {\n await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (line) => {\n const candidateTenantId = getTenantId() ?? line.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const persisted = line.id\n ? await (await InvoiceLineItemCollection.create({ db: tx })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n if (!persisted) return;\n const first = await loadInvoice(tx, persisted.invoiceId);\n if (candidateTenantId && first.invoice.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice line tenant does not match its active money barrier',\n );\n if (settings?.moneyModel !== 'v2') {\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n return;\n }\n assertTenantId(first.invoice.tenantId);\n const { invoice } = await lockAndReloadInvoice(\n tx,\n persisted.invoiceId,\n first.invoice.tenantId,\n );\n if (isFrozen(invoice))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a line from a frozen invoice',\n );\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n const priorAggregate = aggregateSignature(invoice);\n await recomputeInvoiceAggregate(tx, invoice, settings);\n if (aggregateSignature(invoice) !== priorAggregate) {\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\n }\n }),\n );\n}\n\nexport async function deleteInvoice(\n input: Invoice,\n options: DeleteOptions,\n): Promise<void> {\n await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (invoice) => {\n const candidateTenantId = getTenantId() ?? invoice.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const persisted = invoice.id ? await findInvoice(tx, invoice.id) : null;\n if (!persisted) return;\n if (candidateTenantId && persisted.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant does not match its active money barrier',\n );\n if (settings?.moneyModel === 'v2' && isFrozen(persisted))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a frozen invoice',\n );\n if (settings?.moneyModel === 'v2') {\n assertTenantId(persisted.tenantId);\n const persistedId = requireId(persisted.id, 'Invoice');\n await lockAndReloadInvoice(tx, persistedId, persisted.tenantId);\n const documentTaxes = await DocumentTaxCollection.create({ db: tx });\n await documentTaxes.replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId: persisted.tenantId,\n documentType: 'invoice',\n documentId: persistedId,\n rows: [],\n });\n }\n await invoice.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n }),\n );\n}\n"],"mappings":"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"}
|