@happyvertical/smrt-commerce 0.55.15 → 0.55.16
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/invoice-calculations.md +28 -1
- package/agents/payment-allocations.md +19 -0
- package/dist/chunks/{calculations-DwBf83UE.js → calculations-Bz7RB3qK.js} +36 -2
- package/dist/chunks/calculations-Bz7RB3qK.js.map +1 -0
- package/dist/chunks/{units-BhVZswof.js → units-BpZ0_Dpk.js} +2 -2
- package/dist/chunks/{units-BhVZswof.js.map → units-BpZ0_Dpk.js.map} +1 -1
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/dist/invoices.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/models/Invoice.d.ts +5 -3
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/svelte/components/InvoiceEditor.svelte +3 -2
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceLineEditor.svelte +4 -0
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/invoices/calculations.d.ts +12 -3
- package/dist/svelte/invoices/calculations.d.ts.map +1 -1
- package/dist/svelte/invoices/calculations.js +40 -1
- package/dist/svelte/invoices/messages.d.ts +1 -0
- package/dist/svelte/invoices/messages.d.ts.map +1 -1
- package/dist/svelte/invoices/messages.js +1 -0
- package/dist/svelte/invoices/types.d.ts +3 -2
- package/dist/svelte/invoices/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/chunks/calculations-DwBf83UE.js.map +0 -1
package/dist/invoices.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as resolveInvoiceLineDraft, n as calculateInvoiceLine, r as calculateInvoiceMinorLine, t as calculateInvoiceDraft } from "./chunks/calculations-
|
|
1
|
+
import { i as resolveInvoiceLineDraft, n as calculateInvoiceLine, r as calculateInvoiceMinorLine, t as calculateInvoiceDraft } from "./chunks/calculations-Bz7RB3qK.js";
|
|
2
2
|
export { calculateInvoiceDraft, calculateInvoiceLine, calculateInvoiceMinorLine, resolveInvoiceLineDraft };
|
package/dist/manifest.json
CHANGED
package/dist/models/Invoice.d.ts
CHANGED
|
@@ -254,11 +254,13 @@ export declare class Invoice extends SmrtObject {
|
|
|
254
254
|
* SENT → PAID is a structurally legal edge.
|
|
255
255
|
*
|
|
256
256
|
* Only enforced for the payment-derived statuses (PAID / PARTIAL and the
|
|
257
|
-
* unpaid
|
|
258
|
-
* function of amountPaid —
|
|
257
|
+
* unpaid states DRAFT / SENT / VIEWED). Lifecycle statuses that aren't a
|
|
258
|
+
* function of amountPaid — OVERDUE, CANCELLED, WRITTEN_OFF — are left
|
|
259
259
|
* to their own transition rules.
|
|
260
260
|
*/
|
|
261
261
|
private assertPaymentStatusConsistent;
|
|
262
|
+
/** A payment reversal cannot erase an actual delivery event. */
|
|
263
|
+
private assertDraftDeliveryHistory;
|
|
262
264
|
/**
|
|
263
265
|
* Enforce `totalAmount === subtotal + taxAmount` exactly. Used when the
|
|
264
266
|
* invoice has no line items to recompute from.
|
|
@@ -326,7 +328,7 @@ export declare class Invoice extends SmrtObject {
|
|
|
326
328
|
* Update payment amount and status.
|
|
327
329
|
*
|
|
328
330
|
* Called when PaymentAllocations change. Handles both forward transitions
|
|
329
|
-
* (SENT → PARTIAL → PAID) and reversals (PAID → PARTIAL → original status).
|
|
331
|
+
* (DRAFT/SENT → PARTIAL → PAID) and reversals (PAID → PARTIAL → original status).
|
|
330
332
|
*
|
|
331
333
|
* Terminal statuses (CANCELLED, WRITTEN_OFF) are not modified.
|
|
332
334
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Invoice.d.ts","sourceRoot":"","sources":["../../src/models/Invoice.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAIL,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,OAAO,EAAoB,MAAM,6BAA6B,CAAC;AAQ7E,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,aAAa,EACb,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"Invoice.d.ts","sourceRoot":"","sources":["../../src/models/Invoice.ts"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-commerce",
|
|
6
|
-
"packageVersion": "0.55.
|
|
6
|
+
"packageVersion": "0.55.16",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "257cdf3e07aaf5bcb7b621e6169b5692d8d5b05fa089969891edfce7409f8351",
|
|
11
|
+
"packageJson": "287bfec433d0fb58647280d0a08a0237e76e582eda16f32f5af695cfa1cd4dc5",
|
|
12
12
|
"agents": "5a4901577a0f007cf44f04658c4394e1b1fe292f24701d0ff00ccf66095b4217",
|
|
13
13
|
"moduleDoc:agents/invoice-quantity.md": "bc3b617d4fb8cc87511763b281283107557ba64795e64522193ef967a3715d32",
|
|
14
|
-
"moduleDoc:agents/payment-allocations.md": "
|
|
14
|
+
"moduleDoc:agents/payment-allocations.md": "46c0b921f0e40060cd1caec2dcefb01ed6612e31e4bf220a1a0b61e4e2a58212",
|
|
15
15
|
"moduleDoc:agents/customer-vendor-ui.md": "1187b4d14e6bab661a2bb3312315239952dc955f90eceb177719f48dd0979150",
|
|
16
16
|
"moduleDoc:agents/commercial-ui.md": "308729a481c82903d06837e7052c04509a92cf17d17345f71dce88d8d42d0431",
|
|
17
17
|
"moduleDoc:agents/quote-ui.md": "514568e4e7815c47113627ac044a1f31a6933377b3f6c70f5d54b2508d94200c",
|
|
@@ -19843,7 +19843,7 @@
|
|
|
19843
19843
|
{
|
|
19844
19844
|
"path": "agents/payment-allocations.md",
|
|
19845
19845
|
"module": "payment-allocations",
|
|
19846
|
-
"content": "# Payment allocation integrity\n\n`PaymentAllocation.save()` owns the financial boundary for every direct or\ncollection write. Both payment and invoice must exist, belong to the same\nexact tenant (global is distinct), have the same customer and currency, and\nhave room for the positive safe-integer minor-unit amount. Zero-total invoices\nhave no room. Missing or zero-total invoice fixtures no longer bypass the cap.\nPending payments remain allocatable; this command does not establish settlement\nproof. Generated REST/MCP/CLI allocation writes remain closed. Applications\nmust authorize the actor before invoking these backend commands.\n\nUse `PaymentAllocationCollection.allocatePayment({ paymentId, allocations,\nallocatedBy })` for a split. Each allocation is `{ id, invoiceId, amount }`, with\na caller-generated stable UUID retained across retries. Exact replay\nreuses the existing rows without applying money again; an existing id with\ndifferent parents or amount fails. Replay preserves an already-paid invoice's\nrecorded payment date. Reversing\npayment clears that date; reaching full payment again records the new date.\nThe complete batch and invoice payment-status refresh share one transaction. Invoices must\nalready be in a status that permits payment (normally SENT); WRITTEN_OFF retains\nits terminal status. A failed leg rolls back every leg and parent revision.\nDirect allocation saves continue to leave status refresh to the caller. Allocation\nwrites advance both parent revisions: reload a previously held parent with\n`await parent.loadFromId()` before applying later edits or status refresh.\nStale parent saves continue to fail the core revision guard; no implicit\nrevision refresh bypasses another editor. In-package billing follows this rule.\n\nOnce inserted, allocation tenant/payment/invoice identity is immutable. Amount\ncorrections are capped against the other allocations. Parent saves reject\nchanges to tenant, customer, currency or amount that would invalidate committed\nallocations. A payment may be increased or reduced to its allocated total;\ninvoice totals continue to derive from persisted invoice lines where present.\nStandalone payments remain saveable when allocation storage has not been\nprovisioned. An existing allocation table always participates in validation;\nstorage probes and permission errors propagate instead of disabling the guard.\n\nAll participating writes bind collections to the owning transaction executor.\nThey claim parent revisions before reading balances. Concurrent writers can\nreceive a revision conflict or database serialization failure; reload and retry\nthe complete transaction with the same allocation ids. Never retry only the\nlast leg inside an already failed transaction. PostgreSQL Repeatable Read\nsnapshots fail closed through the same revision writes. SQLite serializes the\nowning write transaction. DuckDB is not a supported commerce allocation store:\nthe existing declared foreign keys require ON UPDATE CASCADE, which its DDL\nboundary explicitly refuses; no weaker schema is substituted.\n"
|
|
19846
|
+
"content": "# Payment allocation integrity\n\n`PaymentAllocation.save()` owns the financial boundary for every direct or\ncollection write. Both payment and invoice must exist, belong to the same\nexact tenant (global is distinct), have the same customer and currency, and\nhave room for the positive safe-integer minor-unit amount. Zero-total invoices\nhave no room. Missing or zero-total invoice fixtures no longer bypass the cap.\nPending payments remain allocatable; this command does not establish settlement\nproof. Generated REST/MCP/CLI allocation writes remain closed. Applications\nmust authorize the actor before invoking these backend commands.\n\nUse `PaymentAllocationCollection.allocatePayment({ paymentId, allocations,\nallocatedBy })` for a split. Each allocation is `{ id, invoiceId, amount }`, with\na caller-generated stable UUID retained across retries. Exact replay\nreuses the existing rows without applying money again; an existing id with\ndifferent parents or amount fails. Replay preserves an already-paid invoice's\nrecorded payment date. Reversing\npayment clears that date; reaching full payment again records the new date.\nThe complete batch and invoice payment-status refresh share one transaction. Invoices must\nalready be in a status that permits payment (normally SENT); WRITTEN_OFF retains\nits terminal status. A failed leg rolls back every leg and parent revision.\nDirect allocation saves continue to leave status refresh to the caller. Allocation\nwrites advance both parent revisions: reload a previously held parent with\n`await parent.loadFromId()` before applying later edits or status refresh.\nStale parent saves continue to fail the core revision guard; no implicit\nrevision refresh bypasses another editor. In-package billing follows this rule.\n\nOnce inserted, allocation tenant/payment/invoice identity is immutable. Amount\ncorrections are capped against the other allocations. Parent saves reject\nchanges to tenant, customer, currency or amount that would invalidate committed\nallocations. A payment may be increased or reduced to its allocated total;\ninvoice totals continue to derive from persisted invoice lines where present.\nStandalone payments remain saveable when allocation storage has not been\nprovisioned. An existing allocation table always participates in validation;\nstorage probes and permission errors propagate instead of disabling the guard.\n\nAll participating writes bind collections to the owning transaction executor.\nThey claim parent revisions before reading balances. Concurrent writers can\nreceive a revision conflict or database serialization failure; reload and retry\nthe complete transaction with the same allocation ids. Never retry only the\nlast leg inside an already failed transaction. PostgreSQL Repeatable Read\nsnapshots fail closed through the same revision writes. SQLite serializes the\nowning write transaction. DuckDB is not a supported commerce allocation store:\nthe existing declared foreign keys require ON UPDATE CASCADE, which its DDL\nboundary explicitly refuses; no weaker schema is substituted.\n\n### Undelivered invoice payment progress\n\nAllocation may move an undelivered DRAFT invoice to PARTIAL or PAID without a\ncustomer delivery event. Neither operation invents `sentAt` or `viewedAt`.\nAfter allocations are removed, reload the parent and call\n`updatePaymentStatus(remainingTotal)` and `save()` within the caller transaction.\nZero remaining allocations restore DRAFT only for an undelivered invoice;\na delivered invoice returns to SENT/VIEWED according to its delivery history.\nDRAFT cannot conceal positive allocations. A DRAFT save checks both its supplied\ndelivery timestamps and the currently persisted timestamps, so clearing them\nlocally during reversal cannot bypass the undelivered requirement. This guard\ndoes not introduce an immutable lifetime delivery-history record.\n\nIf a prepaid invoice is actually delivered later, call `markSent()` and save.\nFor PARTIAL/PAID invoices it records the first `sentAt` while preserving payment\nstatus, paid amount, paid date and any existing view timestamp. Repeating this\ncall preserves the first send timestamp. It records the caller's delivery\nevent; it does not send a customer message or manufacture earlier delivery.\n"
|
|
19847
19847
|
},
|
|
19848
19848
|
{
|
|
19849
19849
|
"path": "agents/customer-vendor-ui.md",
|
|
@@ -30,6 +30,7 @@ let {
|
|
|
30
30
|
id,
|
|
31
31
|
title,
|
|
32
32
|
values,
|
|
33
|
+
roundingScope = 'line',
|
|
33
34
|
customers = [],
|
|
34
35
|
errors = {},
|
|
35
36
|
fields = {},
|
|
@@ -57,7 +58,7 @@ const busy = $derived(
|
|
|
57
58
|
pending || retryStatus === 'submitting' || retryStatus === 'busy',
|
|
58
59
|
);
|
|
59
60
|
let draft = $derived(values);
|
|
60
|
-
const calculation = $derived(calculateInvoiceDraft(draft));
|
|
61
|
+
const calculation = $derived(calculateInvoiceDraft(draft, { roundingScope }));
|
|
61
62
|
function refresh(event: Event) {
|
|
62
63
|
const data = new FormData(event.currentTarget as HTMLFormElement);
|
|
63
64
|
const text = (name: string) => String(data.get(name) ?? '');
|
|
@@ -111,7 +112,7 @@ function lineErrors(key: string) {
|
|
|
111
112
|
</fieldset>
|
|
112
113
|
{#if draft.lines.length === 0}<p>{t(M['commerce.invoice.empty'])}</p>{/if}
|
|
113
114
|
{#each draft.lines as line, index (line.key)}
|
|
114
|
-
<InvoiceLineEditor {line} currency={draft.currency} inheritedTaxRate={draft.taxRate} number={index + 1} id={`${formId}-line-${index}`} fields={rowNames} errors={lineErrors(line.key)} {canEdit} pending={busy} intentField={names.intent} {removePrefix} />
|
|
115
|
+
<InvoiceLineEditor {line} {roundingScope} currency={draft.currency} inheritedTaxRate={draft.taxRate} number={index + 1} id={`${formId}-line-${index}`} fields={rowNames} errors={lineErrors(line.key)} {canEdit} pending={busy} intentField={names.intent} {removePrefix} />
|
|
115
116
|
{/each}
|
|
116
117
|
{#if canEdit}<Button type="submit" name={names.intent} value={addIntent} formnovalidate disabled={busy} variant="secondary">{t(M['commerce.invoice.add'])}</Button>{/if}
|
|
117
118
|
<div aria-live="polite" class="summary">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceEditor.svelte.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAI9B,6FAA6F;AAC7F,MAAM,WAAW,KAAM,SAAQ,kBAAkB;CAAG;
|
|
1
|
+
{"version":3,"file":"InvoiceEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceEditor.svelte.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAI9B,6FAA6F;AAC7F,MAAM,WAAW,KAAM,SAAQ,kBAAkB;CAAG;AAuHpD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -13,6 +13,7 @@ let {
|
|
|
13
13
|
line,
|
|
14
14
|
currency,
|
|
15
15
|
inheritedTaxRate,
|
|
16
|
+
roundingScope = 'line',
|
|
16
17
|
number = 1,
|
|
17
18
|
id,
|
|
18
19
|
fields = {},
|
|
@@ -71,9 +72,12 @@ function refresh(event: Event) {
|
|
|
71
72
|
{#if calculation.valid}
|
|
72
73
|
<dl class="totals">
|
|
73
74
|
<div><dt>{t(M['commerce.invoice.subtotal'])}</dt><dd><CurrencyDisplay amount={calculation.subtotalMinor} {currency} unit="cents" /></dd></div>
|
|
75
|
+
{#if roundingScope === 'line'}
|
|
74
76
|
<div><dt>{t(M['commerce.invoice.tax'])}</dt><dd><CurrencyDisplay amount={calculation.taxMinor} {currency} unit="cents" /></dd></div>
|
|
75
77
|
<div><dt>{t(M['commerce.invoice.total'])}</dt><dd><CurrencyDisplay amount={calculation.totalMinor} {currency} unit="cents" /></dd></div>
|
|
78
|
+
{/if}
|
|
76
79
|
</dl>
|
|
80
|
+
{#if roundingScope === 'invoice'}<p>{t(M['commerce.invoice.invoiceRounding'])}</p>{/if}
|
|
77
81
|
{:else}<p role="status">{t(M['commerce.invoice.invalid'])}</p>{/if}
|
|
78
82
|
{#if canEdit}<Button type="submit" name={intentField} value={`${removePrefix}${draft.key}`} formnovalidate disabled={pending} variant="secondary">{t(M['commerce.invoice.remove'])}</Button>{/if}
|
|
79
83
|
</fieldset>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceLineEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,mFAAmF;AACnF,MAAM,WAAW,KAAM,SAAQ,sBAAsB;CAAG;
|
|
1
|
+
{"version":3,"file":"InvoiceLineEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,mFAAmF;AACnF,MAAM,WAAW,KAAM,SAAQ,sBAAsB;CAAG;AAuFxD,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -69,8 +69,17 @@ export type InvoiceLineCalculation = ({
|
|
|
69
69
|
errors: Partial<Record<InvoiceLineField, InvoiceCalculationError>>;
|
|
70
70
|
};
|
|
71
71
|
/** Header error keys include whole-invoice totals and line membership. */
|
|
72
|
-
export type InvoiceDraftField = Exclude<keyof InvoiceDraftValues, 'lines'> | 'lines' | 'total';
|
|
73
|
-
/**
|
|
72
|
+
export type InvoiceDraftField = Exclude<keyof InvoiceDraftValues, 'lines'> | 'lines' | 'total' | 'roundingScope';
|
|
73
|
+
/** Server-authorized tax rounding; absent means the historical per-line behavior. */
|
|
74
|
+
export interface InvoiceDraftCalculationOptions {
|
|
75
|
+
/** Invoice scope groups equal effective percentages and rounds each group once. */
|
|
76
|
+
roundingScope?: 'line' | 'invoice';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Aggregate result retains standalone line calculations for validation/audit.
|
|
80
|
+
* With invoice rounding, line tax/total are estimates, NOT allocated invoice
|
|
81
|
+
* amounts, and must not be summed or persisted as authoritative invoice tax.
|
|
82
|
+
*/
|
|
74
83
|
export type InvoiceDraftCalculation = ({
|
|
75
84
|
valid: true;
|
|
76
85
|
lineResults: readonly InvoiceLineCalculation[];
|
|
@@ -117,5 +126,5 @@ export declare function resolveInvoiceLineDraft(line: InvoiceLineDraft, context:
|
|
|
117
126
|
*/
|
|
118
127
|
export declare function calculateInvoiceLine(line: InvoiceLineDraft, context: InvoiceCalculationContext): InvoiceLineCalculation;
|
|
119
128
|
/** Validate the header and sum validated lines without floating-point arithmetic. */
|
|
120
|
-
export declare function calculateInvoiceDraft(values: InvoiceDraftValues): InvoiceDraftCalculation;
|
|
129
|
+
export declare function calculateInvoiceDraft(values: InvoiceDraftValues, options?: InvoiceDraftCalculationOptions): InvoiceDraftCalculation;
|
|
121
130
|
//# sourceMappingURL=calculations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calculations.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/calculations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"calculations.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/calculations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Exact, transport-neutral invoice draft validation shared by browser and server. */
|
|
2
|
+
import { calculateCostBillingTax } from '../../cost-billing.js';
|
|
2
3
|
import { currencyMinorUnitExponent } from './currency.js';
|
|
3
4
|
const MAX_MINOR = BigInt(Number.MAX_SAFE_INTEGER);
|
|
4
5
|
const MAX_TEXT = 128;
|
|
@@ -209,8 +210,11 @@ function calendarDate(value) {
|
|
|
209
210
|
return (Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value);
|
|
210
211
|
}
|
|
211
212
|
/** Validate the header and sum validated lines without floating-point arithmetic. */
|
|
212
|
-
export function calculateInvoiceDraft(values) {
|
|
213
|
+
export function calculateInvoiceDraft(values, options = {}) {
|
|
213
214
|
const errors = {};
|
|
215
|
+
const roundingScope = options.roundingScope === undefined ? 'line' : options.roundingScope;
|
|
216
|
+
if (roundingScope !== 'line' && roundingScope !== 'invoice')
|
|
217
|
+
errors.roundingScope = 'invalid-mode';
|
|
214
218
|
if (!values.customerId?.trim())
|
|
215
219
|
errors.customerId = 'required';
|
|
216
220
|
if (!calendarDate(values.issuedOn))
|
|
@@ -243,6 +247,41 @@ export function calculateInvoiceDraft(values) {
|
|
|
243
247
|
for (const key of Object.keys(sums))
|
|
244
248
|
sums[key] += BigInt(result[key]);
|
|
245
249
|
}
|
|
250
|
+
if (roundingScope === 'invoice' &&
|
|
251
|
+
!Object.keys(errors).length &&
|
|
252
|
+
lineResults.every((result) => result.valid)) {
|
|
253
|
+
// Rates have already passed exact decimal validation. Normalize textual
|
|
254
|
+
// equivalents without numeric conversion before assigning component IDs.
|
|
255
|
+
const taxable = lineResults.map((result, index) => {
|
|
256
|
+
if (!result.valid)
|
|
257
|
+
throw new Error('Validated invoice line expected');
|
|
258
|
+
const [whole, fraction = ''] = result.effectiveTaxRate.split('.');
|
|
259
|
+
const digits = fraction.replace(/0+$/, '');
|
|
260
|
+
const percent = `${BigInt(whole)}${digits ? `.${digits}` : ''}`;
|
|
261
|
+
return {
|
|
262
|
+
id: values.lines[index].key,
|
|
263
|
+
amountMinor: result.subtotalMinor,
|
|
264
|
+
category: percent,
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
const tax = calculateCostBillingTax({
|
|
269
|
+
lines: taxable,
|
|
270
|
+
rules: [...new Set(taxable.map((row) => row.category))].map((percent) => ({
|
|
271
|
+
category: percent,
|
|
272
|
+
components: [{ id: percent, percent }],
|
|
273
|
+
})),
|
|
274
|
+
roundingScope: 'invoice',
|
|
275
|
+
});
|
|
276
|
+
sums.taxMinor = BigInt(tax.taxMinor);
|
|
277
|
+
sums.totalMinor = sums.subtotalMinor + sums.taxMinor;
|
|
278
|
+
}
|
|
279
|
+
catch (cause) {
|
|
280
|
+
if (!(cause instanceof RangeError))
|
|
281
|
+
throw cause;
|
|
282
|
+
errors.total = 'range';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
246
285
|
if (Object.values(sums).some((value) => value > MAX_MINOR))
|
|
247
286
|
errors.total = 'range';
|
|
248
287
|
if (Object.keys(errors).length || lineResults.some((result) => !result.valid))
|
|
@@ -29,6 +29,7 @@ export declare const invoicePreparationMessages: {
|
|
|
29
29
|
readonly 'commerce.invoice.tax': "commerce.invoice.tax";
|
|
30
30
|
readonly 'commerce.invoice.total': "commerce.invoice.total";
|
|
31
31
|
readonly 'commerce.invoice.invalid': "commerce.invoice.invalid";
|
|
32
|
+
readonly 'commerce.invoice.invoiceRounding': "commerce.invoice.invoiceRounding";
|
|
32
33
|
readonly 'commerce.invoice.preview': "commerce.invoice.preview";
|
|
33
34
|
readonly 'commerce.invoice.retained': "commerce.invoice.retained";
|
|
34
35
|
readonly 'commerce.preparation.allocation': "commerce.preparation.allocation";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,0BAA0B
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DrC,CAAC"}
|
|
@@ -30,6 +30,7 @@ export const invoicePreparationMessages = defineMessages({
|
|
|
30
30
|
'commerce.invoice.tax': 'Tax',
|
|
31
31
|
'commerce.invoice.total': 'Total',
|
|
32
32
|
'commerce.invoice.invalid': 'Totals unavailable until quantity, price, discount, tax and currency are valid.',
|
|
33
|
+
'commerce.invoice.invoiceRounding': 'Tax is rounded across the invoice for each effective rate; see the invoice totals.',
|
|
33
34
|
'commerce.invoice.preview': 'Calculated preview; the server revalidates prices, tax and totals.',
|
|
34
35
|
'commerce.invoice.retained': 'Retained reference: {reference}',
|
|
35
36
|
'commerce.preparation.allocation': 'Allocation {number}',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FormRetryStatus } from '@happyvertical/smrt-ui/form-retry';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
4
|
+
import type { InvoiceDraftCalculationOptions } from './calculations.js';
|
|
4
5
|
/** Authorized source projection supplied by the application, not fetched by UI. */
|
|
5
6
|
export interface InvoicePreparationSource {
|
|
6
7
|
/** Opaque source reference submitted verbatim. */
|
|
@@ -115,7 +116,7 @@ export interface InvoiceFieldNames {
|
|
|
115
116
|
intent: string;
|
|
116
117
|
}
|
|
117
118
|
/** General invoice line fields; never renders a nested form. */
|
|
118
|
-
export interface InvoiceLineEditorProps {
|
|
119
|
+
export interface InvoiceLineEditorProps extends InvoiceDraftCalculationOptions {
|
|
119
120
|
/** Lossless caller-owned line values. */
|
|
120
121
|
line: InvoiceLineDraft;
|
|
121
122
|
/** Current currency code for shared calculation. */
|
|
@@ -142,7 +143,7 @@ export interface InvoiceLineEditorProps {
|
|
|
142
143
|
onchange?: (line: InvoiceLineDraft) => void;
|
|
143
144
|
}
|
|
144
145
|
/** General native invoice editor with optional domain extensions. */
|
|
145
|
-
export interface InvoiceEditorProps {
|
|
146
|
+
export interface InvoiceEditorProps extends InvoiceDraftCalculationOptions {
|
|
146
147
|
/** Caller native endpoint. */
|
|
147
148
|
action: string;
|
|
148
149
|
/** Native method, default POST. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC;AAExE,mFAAmF;AACnF,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,kFAAkF;AAClF,MAAM,WAAW,sBAAsB;IACrC,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,oFAAoF;AACpF,MAAM,WAAW,wBAAwB;IACvC,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,6BAA6B;IAC5C,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wEAAwE;AACxE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC/C,0CAA0C;IAC1C,OAAO,CAAC,EAAE,SAAS,wBAAwB,EAAE,CAAC;IAC9C,4DAA4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC3C,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,2DAA2D;AAC3D,MAAM,WAAW,qBAAqB;IACpC,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAuB,SAAQ,8BAA8B;IAC5E,yCAAyC;IACzC,IAAI,EAAE,gBAAgB,CAAC;IACvB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACxC,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC7C;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAmB,SAAQ,8BAA8B;IACxE,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,4BAA4B;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,EAAE,kBAAkB,CAAC;IAC3B,iFAAiF;IACjF,SAAS,CAAC,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrD,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpC,oCAAoC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5C,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IACxD,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACzC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CAC3C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-commerce",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.16",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Commerce models for the SMRT framework - contracts, fulfillments, payments",
|
|
6
6
|
"type": "module",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@happyvertical/accounting": "^0.103.4",
|
|
63
63
|
"@happyvertical/payments": "^0.103.4",
|
|
64
|
-
"@happyvertical/smrt-core": "0.55.
|
|
65
|
-
"@happyvertical/smrt-jobs": "0.55.
|
|
66
|
-
"@happyvertical/smrt-ledgers": "0.55.
|
|
67
|
-
"@happyvertical/smrt-subscriptions": "0.55.
|
|
68
|
-
"@happyvertical/smrt-tenancy": "0.55.
|
|
69
|
-
"@happyvertical/smrt-types": "0.55.
|
|
70
|
-
"@happyvertical/smrt-ui": "0.55.
|
|
64
|
+
"@happyvertical/smrt-core": "0.55.16",
|
|
65
|
+
"@happyvertical/smrt-jobs": "0.55.16",
|
|
66
|
+
"@happyvertical/smrt-ledgers": "0.55.16",
|
|
67
|
+
"@happyvertical/smrt-subscriptions": "0.55.16",
|
|
68
|
+
"@happyvertical/smrt-tenancy": "0.55.16",
|
|
69
|
+
"@happyvertical/smrt-types": "0.55.16",
|
|
70
|
+
"@happyvertical/smrt-ui": "0.55.16"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"svelte": "^5.57.0"
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@happyvertical/smrt-vitest": "0.55.
|
|
81
|
+
"@happyvertical/smrt-vitest": "0.55.16",
|
|
82
82
|
"@sveltejs/package": "^2.5.8",
|
|
83
83
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
84
84
|
"@testing-library/jest-dom": "^7.0.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"calculations-DwBf83UE.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 { 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/** Aggregate result retains every line result for granular rendering. */\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 /** Nonnegative flat discount in integer minor units. */\n discount: number;\n /** Resolved tax fraction, at most eight fractional digits, in [0, 1]. */\n taxRate: number;\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 roundSigned(numerator: bigint, denominator: bigint): bigint {\n // Preserve Math.round's existing credit-line ties toward positive infinity.\n return numerator >= 0n\n ? roundHalfUp(numerator, denominator)\n : -((-numerator * 2n + denominator - 1n) / (denominator * 2n));\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): 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 if (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 const gross = roundSigned(\n quantity.coefficient * BigInt(input.unitPrice),\n quantity.scale,\n );\n const discount = BigInt(input.discount);\n const subtotal = gross - discount;\n const taxMinor = roundSigned(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\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 const gross = roundHalfUp(quantity.coefficient * price, quantity.scale);\n const reduction =\n typeof discount === 'bigint'\n ? discount\n : roundHalfUp(gross * discount.coefficient, discount.scale * 100n);\n if (gross > MAX_MINOR) return { valid: false, errors: { quantity: 'range' } };\n if (reduction > gross)\n return {\n valid: false,\n errors: { discountValue: 'discount-exceeds-gross' },\n };\n const subtotal = gross - reduction;\n const taxMinor = roundHalfUp(subtotal * tax.coefficient, tax.scale * 100n);\n const total = subtotal + taxMinor;\n if (total > MAX_MINOR) return { valid: false, errors: { taxRate: 'range' } };\n return {\n valid: true,\n effectiveTaxRate,\n grossMinor: Number(gross),\n discountMinor: Number(reduction),\n subtotalMinor: Number(subtotal),\n taxMinor: Number(taxMinor),\n totalMinor: Number(total),\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): InvoiceDraftCalculation {\n const errors: Partial<Record<InvoiceDraftField, InvoiceCalculationError>> =\n {};\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 (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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"}
|