@happyvertical/smrt-commerce 0.55.14 → 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.
Files changed (70) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +29 -0
  3. package/agents/invoice-calculations.md +28 -1
  4. package/agents/payment-allocations.md +64 -0
  5. package/agents/purchasing-ui.md +27 -0
  6. package/dist/allocation-integrity.d.ts +13 -0
  7. package/dist/allocation-integrity.d.ts.map +1 -0
  8. package/dist/billing/payment-attempts.d.ts.map +1 -1
  9. package/dist/chunks/{calculations-DwBf83UE.js → calculations-Bz7RB3qK.js} +36 -2
  10. package/dist/chunks/calculations-Bz7RB3qK.js.map +1 -0
  11. package/dist/chunks/money-DfrRxrel.js +50 -0
  12. package/dist/chunks/money-DfrRxrel.js.map +1 -0
  13. package/dist/chunks/{types-CRnln577.js → types-zVnIjSOk.js} +29 -2
  14. package/dist/chunks/{types-CRnln577.js.map → types-zVnIjSOk.js.map} +1 -1
  15. package/dist/chunks/{units-BhVZswof.js → units-BpZ0_Dpk.js} +2 -2
  16. package/dist/chunks/{units-BhVZswof.js.map → units-BpZ0_Dpk.js.map} +1 -1
  17. package/dist/collections/PaymentAllocationCollection.d.ts +14 -0
  18. package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +203 -100
  22. package/dist/index.js.map +1 -1
  23. package/dist/invoices.js +1 -1
  24. package/dist/manifest.json +43 -2
  25. package/dist/models/Contract.d.ts +7 -1
  26. package/dist/models/Contract.d.ts.map +1 -1
  27. package/dist/models/Invoice.d.ts +5 -3
  28. package/dist/models/Invoice.d.ts.map +1 -1
  29. package/dist/models/Payment.d.ts +2 -0
  30. package/dist/models/Payment.d.ts.map +1 -1
  31. package/dist/models/PaymentAllocation.d.ts +7 -33
  32. package/dist/models/PaymentAllocation.d.ts.map +1 -1
  33. package/dist/playground.js +23 -0
  34. package/dist/playground.js.map +1 -1
  35. package/dist/smrt-knowledge.json +79 -8
  36. package/dist/svelte/components/InvoiceEditor.svelte +3 -2
  37. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
  38. package/dist/svelte/components/InvoiceLineEditor.svelte +4 -0
  39. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +1 -1
  40. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte +106 -0
  41. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte.d.ts +49 -0
  42. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte.d.ts.map +1 -0
  43. package/dist/svelte/index.d.ts +4 -0
  44. package/dist/svelte/index.d.ts.map +1 -1
  45. package/dist/svelte/index.js +3 -0
  46. package/dist/svelte/invoices/calculations.d.ts +12 -3
  47. package/dist/svelte/invoices/calculations.d.ts.map +1 -1
  48. package/dist/svelte/invoices/calculations.js +40 -1
  49. package/dist/svelte/invoices/messages.d.ts +1 -0
  50. package/dist/svelte/invoices/messages.d.ts.map +1 -1
  51. package/dist/svelte/invoices/messages.js +1 -0
  52. package/dist/svelte/invoices/types.d.ts +3 -2
  53. package/dist/svelte/invoices/types.d.ts.map +1 -1
  54. package/dist/svelte/playground.d.ts +93 -0
  55. package/dist/svelte/playground.d.ts.map +1 -1
  56. package/dist/svelte/playground.js +20 -0
  57. package/dist/svelte/purchasing-i18n.d.ts +9 -0
  58. package/dist/svelte/purchasing-i18n.d.ts.map +1 -1
  59. package/dist/svelte/purchasing-i18n.js +9 -0
  60. package/dist/svelte/purchasing-types.d.ts +13 -0
  61. package/dist/svelte/purchasing-types.d.ts.map +1 -1
  62. package/dist/types/index.d.ts +7 -0
  63. package/dist/types/index.d.ts.map +1 -1
  64. package/dist/ui.d.ts.map +1 -1
  65. package/dist/ui.js +9 -0
  66. package/dist/ui.js.map +1 -1
  67. package/package.json +12 -12
  68. package/dist/chunks/calculations-DwBf83UE.js.map +0 -1
  69. package/dist/chunks/money-WSMvSgWq.js +0 -8
  70. package/dist/chunks/money-WSMvSgWq.js.map +0 -1
package/AGENTS.md CHANGED
@@ -31,6 +31,9 @@ recipes show carry `@field({ description })` text; keep both in plain
31
31
  language for end users and update them when a recipe's fields change. See
32
32
  [core recipes](../core/agents/recipes.md).
33
33
 
34
+ See [payment allocation integrity](agents/payment-allocations.md) for atomic splits,
35
+ retry identity, parent corrections and supported dialects.
36
+
34
37
  ## Ledger Integration
35
38
 
36
39
  `@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.
package/README.md CHANGED
@@ -107,6 +107,13 @@ await payment.recordPayment({
107
107
 
108
108
  `CustomerCollection`, `VendorCollection`, `ContractCollection`, `InvoiceCollection`, `InvoiceLineItemCollection`, `PaymentCollection`, `PaymentAllocationCollection`, `FulfillmentCollection`
109
109
 
110
+ `PaymentAllocationCollection.allocatePayment()` splits a payment across invoices
111
+ in one transaction using stable allocation IDs for retries. Direct allocation
112
+ saves also enforce matching owners, customers and currencies and both balance
113
+ caps. Read [payment allocation integrity](agents/payment-allocations.md) for
114
+ replay, parent revision refresh and compatibility requirements.
115
+
116
+
110
117
  ### Enums
111
118
 
112
119
  | Export | Values |
@@ -137,6 +144,18 @@ await payment.recordPayment({
137
144
 
138
145
  Invoice and Payment integrate with `@happyvertical/smrt-ledgers` via dynamic import (optional dependency). Invoice stores `arJournalId` and `revenueJournalId` as plain string references to ledger journals. `recognizeRevenue()` creates a balanced AR entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable). `recordPayment()` creates a balanced cash receipt entry (DR: Cash, CR: Accounts Receivable). Both methods return null or throw if the ledgers package is not installed.
139
146
 
147
+ `Payment.recordPayment()` claims the payment revision and commits the journal,
148
+ entries, and completed payment in one database transaction. A failed completion
149
+ rolls back the posting and restores the instance's settlement fields for retry.
150
+ The claimed persisted status is authoritative even if a caller locally resets
151
+ status or marks the instance failed without saving. Concurrent or stale instances
152
+ fail instead of posting twice; reload the payment
153
+ and retry only if it is still pending. An already completed payment retains the
154
+ `Payment already recorded` error. A caller's enclosing transaction remains the
155
+ owner: if it rolls back after settlement returns, reload the payment before retry.
156
+ These guarantees cover new postings, not orphaned journals written by older
157
+ versions. The returned journal uses the caller's database executor.
158
+
140
159
  ### Cross-Package References
141
160
 
142
161
  Customer and Vendor link to `@happyvertical/smrt-profiles` via plain `profileId` string. Invoice and Payment reference `@happyvertical/smrt-ledgers` journals via plain string IDs (`arJournalId`, `revenueJournalId`, `journalId`). All models use `@TenantScoped({ mode: 'optional' })` with nullable `tenantId`.
@@ -583,3 +602,13 @@ The independent `@happyvertical/smrt-commerce/cost-billing` entry point provides
583
602
  exact signed reimbursement/fee calculations, ordered generic markup provenance,
584
603
  and explicit line or invoice tax rounding. See [the cost billing contract](agents/cost-billing.md).
585
604
  It does not alter Invoice persistence, editor defaults or settlement behavior.
605
+
606
+ ### Purchase-order header editing
607
+
608
+ Import `PurchaseOrderHeaderForm` from `@happyvertical/smrt-commerce/svelte` for a
609
+ native POST of vendor, reference, notes, expected date and ship-to fields. Pass
610
+ `action`, `vendors` and raw `values`; return canonical field `errors` and the same
611
+ `hiddenFields` after server validation. This header-only form leaves purchase
612
+ allocations and totals unchanged. `PurchaseOrder` stores `expectedDate` and
613
+ `shipTo` in existing STI metadata. See [the purchasing contract](agents/purchasing-ui.md#purchase-order-header)
614
+ for validation, retained values and transport details.
@@ -2,7 +2,7 @@
2
2
 
3
3
  `@happyvertical/smrt-commerce/invoices` is a Svelte-free entry usable by server handlers and browsers. `calculateInvoiceLine`, `calculateInvoiceDraft`, and `resolveInvoiceLineDraft` share exact decimal arithmetic. The UI consumes these helpers for previews; preview results confer no authority.
4
4
 
5
- Drafts retain unparsed text. Quantity accepts nonnegative decimals with at most six fractional digits and must round-trip through the model numeric representation. Prices and flat discounts accept only whole currency minor units expressed in major-unit text. Currency uses Commerce billing's explicit ISO exponent table (0/3/4 exceptions; other uppercase three-letter codes use 2); servers must authorize their supported currency set. No exchange conversion occurs. Rates are percentages from 0 through 100 with at most six fractional digits. `discountType` is `flat` or `percent`; `taxMode` is `inherit` or `override`. An override of `0` is zero tax; an empty override is invalid. Inherited lines ignore their dormant override string.
5
+ Drafts retain unparsed text. Quantity accepts nonnegative decimals with at most six fractional digits and must round-trip through the model numeric representation. Prices and flat discounts accept only whole currency minor units expressed in major-unit text. Currency uses the shared explicit ISO minor-unit table; unknown codes and codes without a minor unit are rejected. Servers must authorize their supported currency set. No exchange conversion occurs. Rates are percentages from 0 through 100 with at most six fractional digits. `discountType` is `flat` or `percent`; `taxMode` is `inherit` or `override`. An override of `0` is zero tax; an empty override is invalid. Inherited lines ignore their dormant override string.
6
6
 
7
7
  Calculation order is quantity × price rounded half up to minor units, discount rounded half up and subtracted, then tax on the discounted subtotal rounded half up. Flat discounts cannot exceed gross. Numeric model quantities and rates use their canonical decimal representation (including supported exponent notation), never a `toFixed` expansion of binary approximation. No floating-point multiplication or epsilon comparison determines money. Unsafe individual or aggregate results are rejected; invalid lines produce errors without monetary totals. Header validation requires customer, real issue/due dates with due date not before issue date, valid currency shape/default rate, at least one line, and unique nonblank row keys.
8
8
 
@@ -15,3 +15,30 @@ Trailing zeroes do not make an exact amount invalid: `100.00` JPY is 100 minor u
15
15
  Parse native form values into the typed draft (never pass unvalidated arbitrary JSON as the typed object), enforce request/row limits, authorize customer and source references in the active tenant, and validate edits against invoice lifecycle state. Replace inherited rate/currency policy with server-authorized context, call the shared validator/resolver, and save resolved rows and invoice within the application's transaction/retry contract. Ignore any client-supplied calculated amount. Default tax remains externally determined; this module is not a tax determination service. Caller-owned extension allocations, budget availability and approval evidence require their own server validation and are not part of line pricing.
16
16
 
17
17
  Changing currency changes the precision and denomination of the entered values, not their numeric text. The UI must not silently convert entered amounts or rewrite invalid drafts. Reopening or changing authorized tax context requires fresh server validation.
18
+
19
+
20
+ ## Explicit tax rounding scope
21
+
22
+ `calculateInvoiceDraft(values, { roundingScope: 'invoice' })` and the shared
23
+ `InvoiceEditor` / `InvoiceLineEditor` `roundingScope` prop accept `line` or
24
+ `invoice`. Omission preserves per-line rounding. The server chooses this option
25
+ from its authorized policy, and supplies it again when rendering rejected forms
26
+ or reopening retained drafts; it is not a client authority field.
27
+
28
+ Invoice scope groups numerically equal effective percentages, including textual
29
+ variants such as `5` and `5.000000`, and rounds each rate group once through
30
+ `calculateCostBillingTax`. Different rates remain independent components; explicit
31
+ zero overrides remain exempt. Quantity and discount rounding are unchanged. Two
32
+ 10-minor lines at 5% therefore have tax 1 in invoice scope and 2 in line scope.
33
+ This is an additive single-rate-per-line editor, not a tax determination or
34
+ compound-tax solver. Invalid scope is a validation error. Drafts still reject
35
+ negative quantities/prices; the signed model calculator is unchanged.
36
+
37
+ `lineResults` and `resolveInvoiceLineDraft` remain standalone per-line
38
+ calculations, useful for validation and existing line-mode model storage. They
39
+ are **not allocations** of invoice-rounded tax: their tax/total fields may not
40
+ sum to the aggregate. In invoice mode the UI displays each line's subtotal and
41
+ an explanation, with tax and total shown only at invoice level. Persist the
42
+ aggregate with the authorized scope and retained draft when using invoice
43
+ rounding; do not reconstruct it by summing standalone resolved line totals.
44
+ No persistence schema or `InvoiceLineItem` tax behavior is changed by this API.
@@ -0,0 +1,64 @@
1
+ # Payment allocation integrity
2
+
3
+ `PaymentAllocation.save()` owns the financial boundary for every direct or
4
+ collection write. Both payment and invoice must exist, belong to the same
5
+ exact tenant (global is distinct), have the same customer and currency, and
6
+ have room for the positive safe-integer minor-unit amount. Zero-total invoices
7
+ have no room. Missing or zero-total invoice fixtures no longer bypass the cap.
8
+ Pending payments remain allocatable; this command does not establish settlement
9
+ proof. Generated REST/MCP/CLI allocation writes remain closed. Applications
10
+ must authorize the actor before invoking these backend commands.
11
+
12
+ Use `PaymentAllocationCollection.allocatePayment({ paymentId, allocations,
13
+ allocatedBy })` for a split. Each allocation is `{ id, invoiceId, amount }`, with
14
+ a caller-generated stable UUID retained across retries. Exact replay
15
+ reuses the existing rows without applying money again; an existing id with
16
+ different parents or amount fails. Replay preserves an already-paid invoice's
17
+ recorded payment date. Reversing
18
+ payment clears that date; reaching full payment again records the new date.
19
+ The complete batch and invoice payment-status refresh share one transaction. Invoices must
20
+ already be in a status that permits payment (normally SENT); WRITTEN_OFF retains
21
+ its terminal status. A failed leg rolls back every leg and parent revision.
22
+ Direct allocation saves continue to leave status refresh to the caller. Allocation
23
+ writes advance both parent revisions: reload a previously held parent with
24
+ `await parent.loadFromId()` before applying later edits or status refresh.
25
+ Stale parent saves continue to fail the core revision guard; no implicit
26
+ revision refresh bypasses another editor. In-package billing follows this rule.
27
+
28
+ Once inserted, allocation tenant/payment/invoice identity is immutable. Amount
29
+ corrections are capped against the other allocations. Parent saves reject
30
+ changes to tenant, customer, currency or amount that would invalidate committed
31
+ allocations. A payment may be increased or reduced to its allocated total;
32
+ invoice totals continue to derive from persisted invoice lines where present.
33
+ Standalone payments remain saveable when allocation storage has not been
34
+ provisioned. An existing allocation table always participates in validation;
35
+ storage probes and permission errors propagate instead of disabling the guard.
36
+
37
+ All participating writes bind collections to the owning transaction executor.
38
+ They claim parent revisions before reading balances. Concurrent writers can
39
+ receive a revision conflict or database serialization failure; reload and retry
40
+ the complete transaction with the same allocation ids. Never retry only the
41
+ last leg inside an already failed transaction. PostgreSQL Repeatable Read
42
+ snapshots fail closed through the same revision writes. SQLite serializes the
43
+ owning write transaction. DuckDB is not a supported commerce allocation store:
44
+ the existing declared foreign keys require ON UPDATE CASCADE, which its DDL
45
+ boundary explicitly refuses; no weaker schema is substituted.
46
+
47
+ ### Undelivered invoice payment progress
48
+
49
+ Allocation may move an undelivered DRAFT invoice to PARTIAL or PAID without a
50
+ customer delivery event. Neither operation invents `sentAt` or `viewedAt`.
51
+ After allocations are removed, reload the parent and call
52
+ `updatePaymentStatus(remainingTotal)` and `save()` within the caller transaction.
53
+ Zero remaining allocations restore DRAFT only for an undelivered invoice;
54
+ a delivered invoice returns to SENT/VIEWED according to its delivery history.
55
+ DRAFT cannot conceal positive allocations. A DRAFT save checks both its supplied
56
+ delivery timestamps and the currently persisted timestamps, so clearing them
57
+ locally during reversal cannot bypass the undelivered requirement. This guard
58
+ does not introduce an immutable lifetime delivery-history record.
59
+
60
+ If a prepaid invoice is actually delivered later, call `markSent()` and save.
61
+ For PARTIAL/PAID invoices it records the first `sentAt` while preserving payment
62
+ status, paid amount, paid date and any existing view timestamp. Repeating this
63
+ call preserves the first send timestamp. It records the caller's delivery
64
+ event; it does not send a customer message or manufacture earlier delivery.
@@ -80,3 +80,30 @@ Composition regression: run the quote SSR lane, then the maintained quote browse
80
80
  host with `QUOTE_BROWSER_EXTENSION=packages/commerce/test-support/quote-attachments-browser.mjs`.
81
81
  It submits actual Assets upload bytes and the separate editor action with and
82
82
  without JavaScript at 390px.
83
+
84
+ ## Purchase-order header
85
+
86
+ `PurchaseOrderHeaderForm` (`purchase-order-header-form` registry slot) edits only
87
+ `vendorId`, `reference`, `notes`, `expectedDate` and `shipTo`. It does not amend
88
+ awards, allocations, totals or status. Supply `action`, caller-authorized `vendors`,
89
+ raw `values`, canonical-key `errors`, optional `message`, `hiddenFields` and
90
+ `names` for an existing endpoint. The native POST submitter defaults to
91
+ `intent=saveHeader`; `intent`, `onsubmit` and `formAttributes` adapt transport.
92
+ Readonly/busy are presentation controls, never authorization. The server must
93
+ check active authority, vendor eligibility and any order-status edit policy.
94
+
95
+ All header input remains text: even rejected calendar dates survive a rerender.
96
+ The server validates `expectedDate` as YYYY-MM-DD or empty before saving. An
97
+ unknown retained vendor appears explicitly unavailable but retains its ID on POST
98
+ so a rejected submission cannot silently clear it. The caller must reject an
99
+ unavailable vendor or explicitly permit keeping the existing relationship.
100
+ `shipTo` is a delivery address/instructions snapshot, not a Profile reference.
101
+ `PurchaseOrder.expectedDate` and `.shipTo` use existing STI metadata, default to
102
+ empty strings, and accept `PurchaseOrderOptions`; no table migration is needed.
103
+ Existing Contract fields own the other three values. Header persistence uses the
104
+ existing SQLite/PostgreSQL Contract schema; DuckDB rejects its existing
105
+ ON UPDATE CASCADE foreign keys and is not a supported Contract storage lane. Caller retry identities
106
+ are rendered unchanged; the form itself never persists or retries a mutation.
107
+
108
+ Header regression evidence uses the purchasing SSR suite and the shared quote
109
+ browser host with `QUOTE_BROWSER_EXTENSION=packages/commerce/test-support/purchasing-header-browser.mjs`.
@@ -0,0 +1,13 @@
1
+ import type { Invoice } from './models/Invoice.js';
2
+ import type { Payment } from './models/Payment.js';
3
+ export declare function allocationOwner(value: string | null): string | null;
4
+ /** Claim the persisted parent before reading dependants, including at repeatable read. */
5
+ export declare function claimAllocationParent(parent: Payment | Invoice, kind: 'payment' | 'invoice'): Promise<void>;
6
+ /** Exact totals must never silently exceed JavaScript's integer range. */
7
+ export declare function allocationTotal(rows: {
8
+ amount: number;
9
+ id?: string | null;
10
+ }[], excluding?: string | null): bigint;
11
+ /** Parent corrections cannot invalidate an already committed allocation. */
12
+ export declare function assertAllocationParent(parent: Payment | Invoice, kind: 'payment' | 'invoice'): Promise<void>;
13
+ //# sourceMappingURL=allocation-integrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocation-integrity.d.ts","sourceRoot":"","sources":["../src/allocation-integrity.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAEnE;AAED,0FAA0F;AAC1F,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,GAAG,OAAO,EACzB,IAAI,EAAE,SAAS,GAAG,SAAS,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EAC9C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAQR;AAED,4EAA4E;AAC5E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,OAAO,GAAG,OAAO,EACzB,IAAI,EAAE,SAAS,GAAG,SAAS,GAC1B,OAAO,CAAC,IAAI,CAAC,CAyDf"}
@@ -1 +1 @@
1
- {"version":3,"file":"payment-attempts.d.ts","sourceRoot":"","sources":["../../src/billing/payment-attempts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"payment-attempts.d.ts","sourceRoot":"","sources":["../../src/billing/payment-attempts.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { calculateCostBillingTax } from "../cost-billing.js";
1
2
  import { ISO_4217_MINOR_UNITS } from "@happyvertical/smrt-ui/currency";
2
3
  //#region src/svelte/invoices/currency.ts
3
4
  var CURRENCY_PATTERN = /^[A-Z]{3}$/;
@@ -172,8 +173,10 @@ function calendarDate(value) {
172
173
  const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
173
174
  return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
174
175
  }
175
- function calculateInvoiceDraft(values) {
176
+ function calculateInvoiceDraft(values, options = {}) {
176
177
  const errors = {};
178
+ const roundingScope = options.roundingScope === void 0 ? "line" : options.roundingScope;
179
+ if (roundingScope !== "line" && roundingScope !== "invoice") errors.roundingScope = "invalid-mode";
177
180
  if (!values.customerId?.trim()) errors.customerId = "required";
178
181
  if (!calendarDate(values.issuedOn)) errors.issuedOn = "invalid-date";
179
182
  if (!calendarDate(values.dueOn) || values.dueOn < values.issuedOn) errors.dueOn = "invalid-date";
@@ -197,6 +200,37 @@ function calculateInvoiceDraft(values) {
197
200
  if (!result.valid) continue;
198
201
  for (const key of Object.keys(sums)) sums[key] += BigInt(result[key]);
199
202
  }
203
+ if (roundingScope === "invoice" && !Object.keys(errors).length && lineResults.every((result) => result.valid)) {
204
+ const taxable = lineResults.map((result, index) => {
205
+ if (!result.valid) throw new Error("Validated invoice line expected");
206
+ const [whole, fraction = ""] = result.effectiveTaxRate.split(".");
207
+ const digits = fraction.replace(/0+$/, "");
208
+ const percent = `${BigInt(whole)}${digits ? `.${digits}` : ""}`;
209
+ return {
210
+ id: values.lines[index].key,
211
+ amountMinor: result.subtotalMinor,
212
+ category: percent
213
+ };
214
+ });
215
+ try {
216
+ const tax = calculateCostBillingTax({
217
+ lines: taxable,
218
+ rules: [...new Set(taxable.map((row) => row.category))].map((percent) => ({
219
+ category: percent,
220
+ components: [{
221
+ id: percent,
222
+ percent
223
+ }]
224
+ })),
225
+ roundingScope: "invoice"
226
+ });
227
+ sums.taxMinor = BigInt(tax.taxMinor);
228
+ sums.totalMinor = sums.subtotalMinor + sums.taxMinor;
229
+ } catch (cause) {
230
+ if (!(cause instanceof RangeError)) throw cause;
231
+ errors.total = "range";
232
+ }
233
+ }
200
234
  if (Object.values(sums).some((value) => value > MAX_MINOR)) errors.total = "range";
201
235
  if (Object.keys(errors).length || lineResults.some((result) => !result.valid)) return {
202
236
  valid: false,
@@ -216,4 +250,4 @@ function calculateInvoiceDraft(values) {
216
250
  //#endregion
217
251
  export { currencyMinorUnitExponent as a, resolveInvoiceLineDraft as i, calculateInvoiceLine as n, normalizeCurrency as o, calculateInvoiceMinorLine as r, calculateInvoiceDraft as t };
218
252
 
219
- //# sourceMappingURL=calculations-DwBf83UE.js.map
253
+ //# sourceMappingURL=calculations-Bz7RB3qK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculations-Bz7RB3qK.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 { calculateCostBillingTax } from '../../cost-billing.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 /** 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 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 tax = calculateCostBillingTax({\n lines: taxable,\n rules: [...new Set(taxable.map((row) => row.category))].map(\n (percent) => ({\n category: percent,\n components: [{ id: percent, percent }],\n }),\n ),\n roundingScope: 'invoice',\n });\n sums.taxMinor = BigInt(tax.taxMinor);\n sums.totalMinor = sums.subtotalMinor + sums.taxMinor;\n } catch (cause) {\n if (!(cause instanceof RangeError)) 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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"}
@@ -0,0 +1,50 @@
1
+ import { isPostgresDatabase } from "@happyvertical/smrt-core";
2
+ import { getTenantId } from "@happyvertical/smrt-tenancy";
3
+ //#region src/allocation-integrity.ts
4
+ function allocationOwner(value) {
5
+ return value || getTenantId() || null;
6
+ }
7
+ async function claimAllocationParent(parent, kind) {
8
+ if (!parent.id) return;
9
+ const { PaymentCollection } = await import("../index.js").then((n) => n.r);
10
+ const { InvoiceCollection } = await import("../index.js").then((n) => n.t);
11
+ const persisted = await (kind === "payment" ? await PaymentCollection.create({ db: parent.db }) : await InvoiceCollection.create({ db: parent.db })).get({ id: parent.id }, { cache: false });
12
+ if (!persisted) return;
13
+ const expected = parent.isPersisted ? parent.updated_at : persisted.updated_at;
14
+ if (!expected) throw new Error("Allocation parent has no persisted revision");
15
+ await persisted.claimRevision(expected);
16
+ parent.updated_at = persisted.updated_at;
17
+ }
18
+ function allocationTotal(rows, excluding) {
19
+ return rows.reduce((total, row) => {
20
+ if (row.id === excluding) return total;
21
+ if (!Number.isSafeInteger(row.amount) || row.amount <= 0) throw new Error("Invalid persisted payment allocation amount");
22
+ return total + BigInt(row.amount);
23
+ }, 0n);
24
+ }
25
+ async function assertAllocationParent(parent, kind) {
26
+ if (!parent.id) return;
27
+ const { PaymentAllocationCollection } = await import("../index.js").then((n) => n.n);
28
+ const allocations = await PaymentAllocationCollection.create({ db: parent.db });
29
+ if (!(isPostgresDatabase(parent.db) ? (await parent.db.query("SELECT to_regclass(?) AS relation", [allocations.tableName])).rows[0]?.relation != null : await parent.db.tableExists(allocations.tableName))) return;
30
+ const rows = kind === "payment" ? await allocations.findByPayment(parent.id) : await allocations.findByInvoice(parent.id);
31
+ if (!rows.length) return;
32
+ const { PaymentCollection } = await import("../index.js").then((n) => n.r);
33
+ const { InvoiceCollection } = await import("../index.js").then((n) => n.t);
34
+ const opposite = kind === "payment" ? await InvoiceCollection.create({ db: parent.db }) : await PaymentCollection.create({ db: parent.db });
35
+ const cap = kind === "payment" ? parent.amount : parent.totalAmount;
36
+ if (allocationTotal(rows) > BigInt(cap)) throw new Error("Parent amount cannot be less than committed allocations");
37
+ for (const row of rows) {
38
+ const other = await opposite.get({ id: kind === "payment" ? row.invoiceId : row.paymentId }, { cache: false });
39
+ if (!other || row.tenantId !== allocationOwner(parent.tenantId) || other.tenantId !== row.tenantId || (other.customerId || null) !== (parent.customerId || null) || other.currency !== parent.currency) throw new Error("Parent correction would invalidate payment allocation tenant, customer or currency");
40
+ }
41
+ }
42
+ //#endregion
43
+ //#region src/money.ts
44
+ function assertIntegerMinorUnits(context, label, entries) {
45
+ for (const [field, value] of entries) if (!Number.isFinite(value) || !Number.isInteger(value)) throw new Error(`${context} ${label}: ${field} must be an integer number of minor units (cents, satoshis) \u2014 got ${value}. Money is exact: $19.99 is 1999, not 19.99.`);
46
+ }
47
+ //#endregion
48
+ export { claimAllocationParent as a, assertAllocationParent as i, allocationOwner as n, allocationTotal as r, assertIntegerMinorUnits as t };
49
+
50
+ //# sourceMappingURL=money-DfrRxrel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money-DfrRxrel.js","names":[],"sources":["../../src/allocation-integrity.ts","../../src/money.ts"],"sourcesContent":["/** Transactional allocation invariants shared by the owning models. */\nimport { isPostgresDatabase } from '@happyvertical/smrt-core';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport type { Invoice } from './models/Invoice.js';\nimport type { Payment } from './models/Payment.js';\n\nexport function allocationOwner(value: string | null): string | null {\n return value || getTenantId() || null;\n}\n\n/** Claim the persisted parent before reading dependants, including at repeatable read. */\nexport async function claimAllocationParent(\n parent: Payment | Invoice,\n kind: 'payment' | 'invoice',\n): Promise<void> {\n if (!parent.id) return;\n const { PaymentCollection } = await import(\n './collections/PaymentCollection.js'\n );\n const { InvoiceCollection } = await import(\n './collections/InvoiceCollection.js'\n );\n const collection =\n kind === 'payment'\n ? await PaymentCollection.create({ db: parent.db })\n : await InvoiceCollection.create({ db: parent.db });\n const persisted = await collection.get({ id: parent.id }, { cache: false });\n if (!persisted) return;\n const expected = parent.isPersisted\n ? parent.updated_at\n : persisted.updated_at;\n if (!expected) throw new Error('Allocation parent has no persisted revision');\n await persisted.claimRevision(expected);\n parent.updated_at = persisted.updated_at;\n}\n\n/** Exact totals must never silently exceed JavaScript's integer range. */\nexport function allocationTotal(\n rows: { amount: number; id?: string | null }[],\n excluding?: string | null,\n): bigint {\n return rows.reduce((total, row) => {\n if (row.id === excluding) return total;\n if (!Number.isSafeInteger(row.amount) || row.amount <= 0) {\n throw new Error('Invalid persisted payment allocation amount');\n }\n return total + BigInt(row.amount);\n }, 0n);\n}\n\n/** Parent corrections cannot invalidate an already committed allocation. */\nexport async function assertAllocationParent(\n parent: Payment | Invoice,\n kind: 'payment' | 'invoice',\n): Promise<void> {\n if (!parent.id) return;\n const { PaymentAllocationCollection } = await import(\n './collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create({\n db: parent.db,\n });\n // Payment-only deployments need not provision allocation storage. PostgreSQL's\n // information_schema hides inaccessible tables, so use its catalog lookup and\n // let the subsequent read report permission errors for an existing table.\n const exists = isPostgresDatabase(parent.db)\n ? (\n await parent.db.query('SELECT to_regclass(?) AS relation', [\n allocations.tableName,\n ])\n ).rows[0]?.relation != null\n : await parent.db.tableExists(allocations.tableName);\n if (!exists) return;\n const rows =\n kind === 'payment'\n ? await allocations.findByPayment(parent.id)\n : await allocations.findByInvoice(parent.id);\n if (!rows.length) return;\n const { PaymentCollection } = await import(\n './collections/PaymentCollection.js'\n );\n const { InvoiceCollection } = await import(\n './collections/InvoiceCollection.js'\n );\n const opposite =\n kind === 'payment'\n ? await InvoiceCollection.create({ db: parent.db })\n : await PaymentCollection.create({ db: parent.db });\n const cap =\n kind === 'payment'\n ? (parent as Payment).amount\n : (parent as Invoice).totalAmount;\n if (allocationTotal(rows) > BigInt(cap))\n throw new Error('Parent amount cannot be less than committed allocations');\n for (const row of rows) {\n const other = await opposite.get(\n { id: kind === 'payment' ? row.invoiceId : row.paymentId },\n { cache: false },\n );\n if (\n !other ||\n row.tenantId !== allocationOwner(parent.tenantId) ||\n other.tenantId !== row.tenantId ||\n (other.customerId || null) !== (parent.customerId || null) ||\n other.currency !== parent.currency\n ) {\n throw new Error(\n 'Parent correction would invalidate payment allocation tenant, customer or currency',\n );\n }\n }\n}\n","/**\n * Money is exact: integer minor units, checked at the model boundary (#2401).\n *\n * Every money column in this package is INTEGER minor units — cents,\n * satoshis — so `$19.99` is `1999`. Before #2401 the models carried\n * `EPSILON = 0.01` tolerances on every comparison, because the values really\n * were floats and `0.1 * 3` really is `0.30000000000000004`. With exact\n * integers those tolerances are not merely unnecessary, they are wrong: a\n * one-cent discrepancy is a one-cent discrepancy, not rounding fuzz.\n *\n * Dropping the tolerances is only safe if the values are *actually* integers,\n * which is what this guard establishes. A fractional major-unit write (`19.99`\n * into a cents column) is the bug the whole convention exists to prevent;\n * PostgreSQL catches it with a `22P02` and SQLite's type affinity does not\n * catch it at all, so the model catches it first, on every engine and every\n * write surface.\n */\n\n/**\n * Throw unless every named value is a finite integer.\n *\n * @param context - Model name for the error message, e.g. `Invoice`.\n * @param label - Row identifier used in the error, e.g. an invoice number.\n * @param entries - `[fieldName, value]` pairs to check.\n * @throws When any value is non-finite or has a fractional part.\n */\nexport function assertIntegerMinorUnits(\n context: string,\n label: string,\n entries: ReadonlyArray<readonly [string, number]>,\n): void {\n for (const [field, value] of entries) {\n if (!Number.isFinite(value) || !Number.isInteger(value)) {\n throw new Error(\n `${context} ${label}: ${field} must be an integer number of minor ` +\n `units (cents, satoshis) — got ${value}. Money is exact: $19.99 is ` +\n '1999, not 19.99.',\n );\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,31 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
1
13
  //#region src/types/index.ts
14
+ var types_exports = /* @__PURE__ */ __exportAll({
15
+ ContractStatus: () => ContractStatus,
16
+ ContractType: () => ContractType,
17
+ CustomerStatus: () => CustomerStatus,
18
+ CustomerType: () => CustomerType,
19
+ FulfillmentStatus: () => FulfillmentStatus,
20
+ FulfillmentType: () => FulfillmentType,
21
+ InvoiceStatus: () => InvoiceStatus,
22
+ PaymentInstrumentStatus: () => PaymentInstrumentStatus,
23
+ PaymentIntentStatus: () => PaymentIntentStatus,
24
+ PaymentMethod: () => PaymentMethod,
25
+ PaymentStatus: () => PaymentStatus,
26
+ PayoutStatus: () => PayoutStatus,
27
+ VendorStatus: () => VendorStatus
28
+ });
2
29
  var CustomerStatus = /* @__PURE__ */ ((CustomerStatus2) => {
3
30
  CustomerStatus2["ACTIVE"] = "active";
4
31
  CustomerStatus2["INACTIVE"] = "inactive";
@@ -105,6 +132,6 @@ var InvoiceStatus = /* @__PURE__ */ ((InvoiceStatus2) => {
105
132
  return InvoiceStatus2;
106
133
  })(InvoiceStatus || {});
107
134
  //#endregion
108
- export { FulfillmentStatus as a, PaymentInstrumentStatus as c, PaymentStatus as d, PayoutStatus as f, CustomerType as i, PaymentIntentStatus as l, ContractType as n, FulfillmentType as o, VendorStatus as p, CustomerStatus as r, InvoiceStatus as s, ContractStatus as t, PaymentMethod as u };
135
+ export { FulfillmentStatus as a, PaymentInstrumentStatus as c, PaymentStatus as d, PayoutStatus as f, __exportAll as h, CustomerType as i, PaymentIntentStatus as l, types_exports as m, ContractType as n, FulfillmentType as o, VendorStatus as p, CustomerStatus as r, InvoiceStatus as s, ContractStatus as t, PaymentMethod as u };
109
136
 
110
- //# sourceMappingURL=types-CRnln577.js.map
137
+ //# sourceMappingURL=types-zVnIjSOk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-CRnln577.js","names":["CustomerStatus","CustomerType","VendorStatus","ContractType","ContractStatus","FulfillmentType","FulfillmentStatus","PaymentMethod","PaymentStatus","PaymentInstrumentStatus","PayoutStatus","PaymentIntentStatus","InvoiceStatus"],"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Commerce package type definitions\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type {\n InvoiceCalculationContext,\n InvoiceLineDraft,\n} from '../svelte/invoices/calculations.js';\n\n// ============================================================================\n// Customer/Vendor Types\n// ============================================================================\n\nexport enum CustomerStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n SUSPENDED = 'suspended',\n}\n\n/**\n * Customer classification — drives channel access, pricing tier, and credit terms.\n *\n * - DTC: direct-to-consumer (end shopper). Pays via storefront checkout.\n * - WHOLESALE: B2B buyer with negotiated terms (NET-30, line sheets, etc.).\n * - RETAIL: in-store walk-in / point-of-sale customer.\n */\nexport enum CustomerType {\n DTC = 'dtc',\n WHOLESALE = 'wholesale',\n RETAIL = 'retail',\n}\n\nexport enum VendorStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n SUSPENDED = 'suspended',\n}\n\n// ============================================================================\n// Contract Types\n// ============================================================================\n\nexport enum ContractType {\n ESTIMATE = 'estimate',\n ORDER = 'order',\n LEASE = 'lease',\n AGREEMENT = 'agreement',\n PURCHASE_ORDER = 'purchase_order',\n WHOLESALE_ORDER = 'wholesale_order',\n PRODUCTION_ORDER = 'production_order',\n CART = 'cart',\n /**\n * Industry-neutral licensing primitive. The `LicenseSale` STI subtype\n * carries an immutable rights snapshot, a licensee email, optional\n * legal-entity / jurisdiction fields, and a signed-PDF reference.\n * Any application that sells rights for a fee (stock media,\n * music licensing, code-asset marketplace, license keys, etc.)\n * uses this subtype.\n */\n LICENSE_SALE = 'license_sale',\n}\n\nexport enum ContractStatus {\n DRAFT = 'draft',\n SENT = 'sent',\n ACCEPTED = 'accepted',\n DECLINED = 'declined',\n COMPLETED = 'completed',\n CANCELLED = 'cancelled',\n}\n\n// ============================================================================\n// Fulfillment Types\n// ============================================================================\n\nexport enum FulfillmentType {\n SHIPMENT = 'shipment',\n DELIVERY = 'delivery',\n PICKUP = 'pickup',\n DIGITAL = 'digital',\n SERVICE = 'service',\n}\n\nexport enum FulfillmentStatus {\n PENDING = 'pending',\n PROCESSING = 'processing',\n SHIPPED = 'shipped',\n DELIVERED = 'delivered',\n CANCELLED = 'cancelled',\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\nexport enum PaymentMethod {\n CASH = 'cash',\n CHECK = 'check',\n CREDIT_CARD = 'credit_card',\n BANK_TRANSFER = 'bank_transfer',\n CRYPTO = 'crypto',\n OTHER = 'other',\n}\n\nexport enum PaymentStatus {\n PENDING = 'pending',\n COMPLETED = 'completed',\n FAILED = 'failed',\n REFUNDED = 'refunded',\n CANCELLED = 'cancelled',\n}\n\n/**\n * Lifecycle status for a saved {@link PaymentInstrument} (card on file).\n * `active` while chargeable, `expired` once the card lapses, `removed` when the\n * customer detaches it.\n */\nexport enum PaymentInstrumentStatus {\n ACTIVE = 'active',\n EXPIRED = 'expired',\n REMOVED = 'removed',\n}\n\n// ============================================================================\n// Payout Types\n// ============================================================================\n\n/**\n * Status machine for {@link Payout}.\n *\n * `pending → sent → confirmed → failed`. `failed` is terminal but the\n * model exposes a dedicated `resetFromFailed()` so an operator can\n * deliberately requeue a payout after fixing the underlying problem;\n * the reset moves the row back to `pending` and clears the failure\n * metadata.\n *\n * Skipping intermediate states is rejected — a `pending` payout cannot\n * jump straight to `confirmed`, and a `sent` payout cannot regress to\n * `pending` without going through `resetFromFailed()` first.\n */\nexport enum PayoutStatus {\n PENDING = 'pending',\n SENT = 'sent',\n CONFIRMED = 'confirmed',\n FAILED = 'failed',\n}\n\n// ============================================================================\n// PaymentIntent Types\n// ============================================================================\n\n/**\n * Status machine for {@link PaymentIntent}.\n *\n * `awaiting_payment → paid → (issued | retired)`, with `expired` and\n * `cancelled` as alternate terminal states reachable from\n * `awaiting_payment`.\n *\n * - `AWAITING_PAYMENT` — open quote, accepting any of the listed\n * payment options.\n * - `PAID` — one option was satisfied by an incoming payment. The\n * other options are implicitly retired; later inbound funds to a\n * retired option must be flagged for refund by the consumer.\n * - `ISSUED` — downstream rights / contract / fulfillment have been\n * created and linked. Terminal happy-path state.\n * - `RETIRED` — paid but the satisfaction was reversed (refund,\n * chargeback, etc.). Terminal.\n * - `EXPIRED` — the USD-price-lock window passed without payment.\n * Terminal.\n * - `CANCELLED` — the buyer or the system explicitly cancelled the\n * intent before it was paid. Terminal.\n */\nexport enum PaymentIntentStatus {\n AWAITING_PAYMENT = 'awaiting_payment',\n PAID = 'paid',\n ISSUED = 'issued',\n RETIRED = 'retired',\n EXPIRED = 'expired',\n CANCELLED = 'cancelled',\n}\n\n/**\n * One way for a {@link PaymentIntent} to be satisfied. The intent lists\n * an array of these; satisfying any one of them transitions the intent\n * to `PAID` and implicitly retires the rest.\n *\n * Industry-neutral: a marketplace that accepts USDC-on-Base and BTC\n * lists two options; a SaaS billing flow that accepts Stripe and\n * PayPal lists two; a kiosk that accepts only one rail lists one.\n */\nexport interface PaymentOption {\n /**\n * Stable id of the `PaymentBackend` adapter that fulfills this\n * option — must match a `backendId` the consumer's payment-routing\n * layer recognises. Examples: `base-usdc`, `solana-usdc`, `btc`,\n * `stripe`, `paypal`.\n */\n backendId: string;\n /**\n * Payout-rail-qualified currency code, e.g. `USDC-base`, `BTC`,\n * `USD-stripe`. The same convention used by\n * `Vendor.payoutAddresses` and `Payment.nativeCurrency`.\n */\n currency: string;\n /**\n * Optional chain identifier for blockchain-backed options (`base`,\n * `ethereum`, `solana`, ...). Pure-fiat options leave this empty.\n */\n chain?: string;\n /**\n * Destination address / account id the buyer should send funds to\n * for this option (EVM address, BTC address, Stripe payment-intent\n * id, etc.).\n */\n payTo: string;\n /**\n * Amount denominated in `currency`. Stored at decimal precision —\n * a USDC option might be `199.0`, a BTC option `0.00713`.\n */\n nativeAmount: number;\n /**\n * Optional on-chain memo / payment reference (Solana memo, BTC OP_RETURN\n * tag, Stripe metadata key). Some backends use this to disambiguate\n * which intent an inbound payment satisfies.\n */\n memo?: string;\n /**\n * Whether this option supports the x402 HTTP-402 payment-required\n * flow. Consumers that build agent-driven flows use this flag to\n * filter the offered options.\n */\n x402Capable?: boolean;\n /**\n * Optional per-option expiry — when a single rail's quote becomes\n * stale earlier than the intent-wide price-lock window (e.g. a\n * volatile-currency option that re-quotes more aggressively). ISO\n * 8601 string. Empty / missing means \"inherit the intent's\n * `priceLockExpiresAt`\".\n */\n expiresAt?: string;\n}\n\n// ============================================================================\n// Invoice Types\n// ============================================================================\n\nexport enum InvoiceStatus {\n DRAFT = 'draft',\n SENT = 'sent',\n VIEWED = 'viewed',\n PARTIAL = 'partial',\n PAID = 'paid',\n OVERDUE = 'overdue',\n CANCELLED = 'cancelled',\n WRITTEN_OFF = 'written_off',\n}\n\n// ============================================================================\n// Common Interfaces\n// ============================================================================\n\n/**\n * Address structure used for billing and shipping\n */\nexport interface Address {\n street1?: string;\n street2?: string;\n city?: string;\n state?: string;\n postalCode?: string;\n country?: string;\n}\n\n/**\n * Options for recording a payment with ledger integration\n */\nexport interface RecordPaymentOptions {\n /** The ledger to record the journal entry in */\n ledgerId: string;\n /** Account ID for receivables (credit side) */\n receivablesAccountId: string;\n /** Account ID for cash/bank (debit side) */\n cashAccountId: string;\n}\n\n/**\n * Options for recognizing revenue on an invoice\n */\nexport interface RecognizeRevenueOptions {\n /** Account ID for accounts receivable (debit side) */\n arAccountId: string;\n /** Account ID for revenue (credit side) */\n revenueAccountId: string;\n /** Account ID for tax payable (credit side, optional) */\n taxAccountId?: string;\n}\n\n// ============================================================================\n// Accounting-provider sync shapes\n// ============================================================================\n\n/**\n * A single line item in the format expected by `@happyvertical/accounting`\n * providers (QBO, Stripe, …). Mirrors the object built by\n * {@link InvoiceLineItem.toAccountingLineItem}. The SDK isn't a dependency of\n * this package, so the shape is declared locally rather than imported.\n */\nexport interface AccountingLineItemInput {\n description: string;\n sku?: string;\n quantity: number;\n unitPrice: number;\n discount?: number;\n taxRate?: number;\n amount: number;\n periodStart?: Date;\n periodEnd?: Date;\n}\n\n/**\n * An invoice in the format expected by `@happyvertical/accounting` providers.\n * Mirrors the object built by {@link Invoice.toAccountingInput}; money is in\n * currency **major** units, as the SDK expects.\n */\nexport interface AccountingInvoiceInput {\n id: string;\n externalId?: string;\n invoiceNumber: string;\n customerId: string;\n customerExternalId?: string;\n issueDate: Date;\n dueDate: Date;\n lineItems: AccountingLineItemInput[];\n subtotal: number;\n taxAmount: number;\n totalAmount: number;\n currency: string;\n reference?: string;\n memo?: string;\n}\n\n// ============================================================================\n// Model constructor option interfaces\n//\n// Each model's `constructor(options)` accepts a partial bag of its own fields.\n// Declaring a dedicated `XxxOptions extends SmrtObjectOptions` interface keeps\n// the constructors strongly typed (no `any`) while still allowing the\n// framework's base options (db, ai, fs, _className, …) through.\n// ============================================================================\n\n/**\n * Constructor options for {@link Customer}.\n */\nexport interface CustomerOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n profileId?: string;\n creditLimit?: number;\n paymentTerms?: string;\n taxExempt?: boolean;\n taxId?: string;\n defaultShippingAddress?: Address;\n defaultBillingAddress?: Address;\n status?: CustomerStatus;\n customerType?: CustomerType | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Vendor}.\n */\nexport interface VendorOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n profileId?: string;\n leadTimeDays?: number;\n minimumOrderAmount?: number;\n paymentTerms?: string;\n currency?: string;\n defaultContactEmail?: string;\n defaultContactPhone?: string;\n status?: VendorStatus;\n notes?: string;\n payoutAddresses?: Record<string, string> | string | null;\n}\n\n/**\n * Constructor options for {@link Contract} and its STI subtypes.\n */\nexport interface ContractOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractType?: ContractType;\n status?: ContractStatus;\n customerId?: string | null;\n vendorId?: string | null;\n subtotal?: number;\n taxAmount?: number;\n totalAmount?: number;\n currency?: string;\n issueDate?: Date;\n dueDate?: Date | null;\n expiryDate?: Date | null;\n reference?: string;\n notes?: string;\n terms?: string;\n channelId?: string;\n}\n\n/**\n * Constructor options for {@link LicenseSale} (Contract STI subtype). Extends\n * {@link ContractOptions} with the licensing-specific meta fields.\n */\nexport interface LicenseSaleOptions extends ContractOptions {\n skuId?: string;\n paymentId?: string;\n licenseeEmail?: string;\n licenseeLegalEntity?: string;\n licenseeJurisdiction?: string;\n rightsMedium?: string;\n rightsDistributionScope?: string;\n rightsExclusivity?: string;\n rightsDuration?: string;\n rightsTerritory?: string;\n rightsSublicensing?: boolean;\n rightsDerivatives?: boolean;\n pdfUrl?: string;\n pdfHash?: string;\n onChainHashRegistryRef?: string;\n}\n\n/**\n * Constructor options for {@link ContractLineItem}.\n */\nexport interface ContractLineItemOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string;\n description?: string;\n quantity?: number;\n unitPrice?: number;\n discount?: number;\n taxRate?: number;\n amount?: number;\n productId?: string;\n sku?: string;\n startDate?: Date | null;\n endDate?: Date | null;\n billingPeriod?: string;\n sortOrder?: number;\n}\n\n/**\n * How the provider collects an invoice (#3190): `charge_automatically`\n * charges the payer's saved card; `send_invoice` asks the payer to pay. Empty\n * for an invoice never pushed to a provider, or pushed before it was recorded.\n */\nexport type InvoiceCollectionMethod =\n | ''\n | 'send_invoice'\n | 'charge_automatically';\n\n/**\n * Constructor options for {@link Invoice}.\n */\nexport interface InvoiceOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n customerId?: string;\n contractId?: string;\n invoiceNumber?: string;\n reference?: string;\n issueDate?: Date;\n dueDate?: Date;\n paidDate?: Date | null;\n subtotal?: number;\n taxAmount?: number;\n providerTaxAmount?: number;\n totalAmount?: number;\n amountPaid?: number;\n currency?: string;\n status?: InvoiceStatus;\n arJournalId?: string;\n revenueJournalId?: string;\n externalId?: string;\n customerExternalId?: string;\n externalProvider?: string;\n syncedAt?: Date | null;\n collectionMethod?: InvoiceCollectionMethod;\n sentAt?: Date | null;\n viewedAt?: Date | null;\n remindersSent?: number;\n lastReminderAt?: Date | null;\n notes?: string;\n customerNotes?: string;\n terms?: string;\n}\n\n/** Retained editing modes, validated against resolved model fields before reuse. */\nexport interface InvoiceLineEditorState {\n /** Lossless editor draft; never authoritative for ordinary model saves. */\n draft: InvoiceLineDraft;\n /** Context used to resolve that draft; caller authority must still match on reload. */\n context: InvoiceCalculationContext;\n}\n\n/** Constructor options for {@link InvoiceLineItem}. */\nexport interface InvoiceLineItemOptions extends SmrtObjectOptions {\n /** Optional retained editor modes and their original calculation context. */\n invoiceEditorState?: InvoiceLineEditorState | null;\n /** Serialized editor state loaded from persistence; malformed JSON is ignored. */\n invoiceEditorStateJson?: string;\n tenantId?: string | null;\n invoiceId?: string;\n description?: string;\n sku?: string;\n quantity?: number;\n unitPrice?: number;\n discount?: number;\n taxRate?: number;\n amount?: number;\n sourceType?: string;\n sourceId?: string;\n periodStart?: Date | null;\n periodEnd?: Date | null;\n revenueAccountId?: string;\n sortOrder?: number;\n}\n\n/**\n * Constructor options for {@link Fulfillment}.\n */\nexport interface FulfillmentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string;\n fulfillmentType?: FulfillmentType;\n status?: FulfillmentStatus;\n trackingNumber?: string;\n carrier?: string;\n shippingAddress?: Address;\n shippedAt?: Date | null;\n deliveredAt?: Date | null;\n estimatedDelivery?: Date | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link FulfillmentLineItem}.\n */\nexport interface FulfillmentLineItemOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n fulfillmentId?: string;\n contractLineItemId?: string;\n quantityFulfilled?: number;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Payment}.\n */\nexport interface PaymentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string | null;\n customerId?: string | null;\n amount?: number;\n currency?: string;\n method?: PaymentMethod;\n status?: PaymentStatus;\n transactionId?: string;\n reference?: string;\n journalId?: string;\n paidAt?: Date | null;\n notes?: string;\n externalId?: string;\n externalProvider?: string;\n syncedAt?: Date | null;\n backendId?: string;\n backendTxRef?: string;\n nativeAmount?: number;\n nativeCurrency?: string;\n usdAtQuote?: number;\n usdAtConfirmation?: number;\n}\n\n/**\n * Constructor options for {@link PaymentInstrument}.\n */\nexport interface PaymentInstrumentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n customerId?: string;\n backendId?: string;\n providerCustomerId?: string;\n providerPaymentMethodId?: string;\n type?: string;\n brand?: string;\n last4?: string;\n expMonth?: number;\n expYear?: number;\n isDefault?: boolean;\n status?: PaymentInstrumentStatus;\n}\n\n/**\n * Constructor options for {@link PaymentAllocation}.\n */\nexport interface PaymentAllocationOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n paymentId?: string;\n invoiceId?: string;\n amount?: number;\n allocatedAt?: Date;\n allocatedBy?: string;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link PaymentIntent}.\n */\nexport interface PaymentIntentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n skuId?: string;\n offeringRef?: string;\n licenseeEmail?: string;\n customerId?: string;\n paymentOptions?: PaymentOption[] | string;\n usdPriceLocked?: number;\n priceLockWindowMs?: number;\n priceLockExpiresAt?: Date | number | string | null;\n status?: PaymentIntentStatus;\n idempotencyKey?: string;\n paidOptionBackendId?: string;\n paymentId?: string;\n paidAt?: Date | number | string | null;\n issuedAt?: Date | number | string | null;\n expiredAt?: Date | number | string | null;\n cancelledAt?: Date | number | string | null;\n retiredAt?: Date | number | string | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Payout}.\n */\nexport interface PayoutOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n paymentId?: string;\n vendorId?: string;\n grossAmount?: number;\n operatorFee?: number;\n supplierNet?: number;\n currency?: string;\n backendId?: string;\n backendTxRef?: string;\n status?: PayoutStatus;\n sentAt?: Date | number | string | null;\n confirmedAt?: Date | number | string | null;\n failedAt?: Date | number | string | null;\n failureReason?: string;\n notes?: string;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"types-zVnIjSOk.js","names":["CustomerStatus","CustomerType","VendorStatus","ContractType","ContractStatus","FulfillmentType","FulfillmentStatus","PaymentMethod","PaymentStatus","PaymentInstrumentStatus","PayoutStatus","PaymentIntentStatus","InvoiceStatus"],"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Commerce package type definitions\n * @packageDocumentation\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport type {\n InvoiceCalculationContext,\n InvoiceLineDraft,\n} from '../svelte/invoices/calculations.js';\n\n// ============================================================================\n// Customer/Vendor Types\n// ============================================================================\n\nexport enum CustomerStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n SUSPENDED = 'suspended',\n}\n\n/**\n * Customer classification — drives channel access, pricing tier, and credit terms.\n *\n * - DTC: direct-to-consumer (end shopper). Pays via storefront checkout.\n * - WHOLESALE: B2B buyer with negotiated terms (NET-30, line sheets, etc.).\n * - RETAIL: in-store walk-in / point-of-sale customer.\n */\nexport enum CustomerType {\n DTC = 'dtc',\n WHOLESALE = 'wholesale',\n RETAIL = 'retail',\n}\n\nexport enum VendorStatus {\n ACTIVE = 'active',\n INACTIVE = 'inactive',\n SUSPENDED = 'suspended',\n}\n\n// ============================================================================\n// Contract Types\n// ============================================================================\n\nexport enum ContractType {\n ESTIMATE = 'estimate',\n ORDER = 'order',\n LEASE = 'lease',\n AGREEMENT = 'agreement',\n PURCHASE_ORDER = 'purchase_order',\n WHOLESALE_ORDER = 'wholesale_order',\n PRODUCTION_ORDER = 'production_order',\n CART = 'cart',\n /**\n * Industry-neutral licensing primitive. The `LicenseSale` STI subtype\n * carries an immutable rights snapshot, a licensee email, optional\n * legal-entity / jurisdiction fields, and a signed-PDF reference.\n * Any application that sells rights for a fee (stock media,\n * music licensing, code-asset marketplace, license keys, etc.)\n * uses this subtype.\n */\n LICENSE_SALE = 'license_sale',\n}\n\nexport enum ContractStatus {\n DRAFT = 'draft',\n SENT = 'sent',\n ACCEPTED = 'accepted',\n DECLINED = 'declined',\n COMPLETED = 'completed',\n CANCELLED = 'cancelled',\n}\n\n// ============================================================================\n// Fulfillment Types\n// ============================================================================\n\nexport enum FulfillmentType {\n SHIPMENT = 'shipment',\n DELIVERY = 'delivery',\n PICKUP = 'pickup',\n DIGITAL = 'digital',\n SERVICE = 'service',\n}\n\nexport enum FulfillmentStatus {\n PENDING = 'pending',\n PROCESSING = 'processing',\n SHIPPED = 'shipped',\n DELIVERED = 'delivered',\n CANCELLED = 'cancelled',\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\nexport enum PaymentMethod {\n CASH = 'cash',\n CHECK = 'check',\n CREDIT_CARD = 'credit_card',\n BANK_TRANSFER = 'bank_transfer',\n CRYPTO = 'crypto',\n OTHER = 'other',\n}\n\nexport enum PaymentStatus {\n PENDING = 'pending',\n COMPLETED = 'completed',\n FAILED = 'failed',\n REFUNDED = 'refunded',\n CANCELLED = 'cancelled',\n}\n\n/**\n * Lifecycle status for a saved {@link PaymentInstrument} (card on file).\n * `active` while chargeable, `expired` once the card lapses, `removed` when the\n * customer detaches it.\n */\nexport enum PaymentInstrumentStatus {\n ACTIVE = 'active',\n EXPIRED = 'expired',\n REMOVED = 'removed',\n}\n\n// ============================================================================\n// Payout Types\n// ============================================================================\n\n/**\n * Status machine for {@link Payout}.\n *\n * `pending → sent → confirmed → failed`. `failed` is terminal but the\n * model exposes a dedicated `resetFromFailed()` so an operator can\n * deliberately requeue a payout after fixing the underlying problem;\n * the reset moves the row back to `pending` and clears the failure\n * metadata.\n *\n * Skipping intermediate states is rejected — a `pending` payout cannot\n * jump straight to `confirmed`, and a `sent` payout cannot regress to\n * `pending` without going through `resetFromFailed()` first.\n */\nexport enum PayoutStatus {\n PENDING = 'pending',\n SENT = 'sent',\n CONFIRMED = 'confirmed',\n FAILED = 'failed',\n}\n\n// ============================================================================\n// PaymentIntent Types\n// ============================================================================\n\n/**\n * Status machine for {@link PaymentIntent}.\n *\n * `awaiting_payment → paid → (issued | retired)`, with `expired` and\n * `cancelled` as alternate terminal states reachable from\n * `awaiting_payment`.\n *\n * - `AWAITING_PAYMENT` — open quote, accepting any of the listed\n * payment options.\n * - `PAID` — one option was satisfied by an incoming payment. The\n * other options are implicitly retired; later inbound funds to a\n * retired option must be flagged for refund by the consumer.\n * - `ISSUED` — downstream rights / contract / fulfillment have been\n * created and linked. Terminal happy-path state.\n * - `RETIRED` — paid but the satisfaction was reversed (refund,\n * chargeback, etc.). Terminal.\n * - `EXPIRED` — the USD-price-lock window passed without payment.\n * Terminal.\n * - `CANCELLED` — the buyer or the system explicitly cancelled the\n * intent before it was paid. Terminal.\n */\nexport enum PaymentIntentStatus {\n AWAITING_PAYMENT = 'awaiting_payment',\n PAID = 'paid',\n ISSUED = 'issued',\n RETIRED = 'retired',\n EXPIRED = 'expired',\n CANCELLED = 'cancelled',\n}\n\n/**\n * One way for a {@link PaymentIntent} to be satisfied. The intent lists\n * an array of these; satisfying any one of them transitions the intent\n * to `PAID` and implicitly retires the rest.\n *\n * Industry-neutral: a marketplace that accepts USDC-on-Base and BTC\n * lists two options; a SaaS billing flow that accepts Stripe and\n * PayPal lists two; a kiosk that accepts only one rail lists one.\n */\nexport interface PaymentOption {\n /**\n * Stable id of the `PaymentBackend` adapter that fulfills this\n * option — must match a `backendId` the consumer's payment-routing\n * layer recognises. Examples: `base-usdc`, `solana-usdc`, `btc`,\n * `stripe`, `paypal`.\n */\n backendId: string;\n /**\n * Payout-rail-qualified currency code, e.g. `USDC-base`, `BTC`,\n * `USD-stripe`. The same convention used by\n * `Vendor.payoutAddresses` and `Payment.nativeCurrency`.\n */\n currency: string;\n /**\n * Optional chain identifier for blockchain-backed options (`base`,\n * `ethereum`, `solana`, ...). Pure-fiat options leave this empty.\n */\n chain?: string;\n /**\n * Destination address / account id the buyer should send funds to\n * for this option (EVM address, BTC address, Stripe payment-intent\n * id, etc.).\n */\n payTo: string;\n /**\n * Amount denominated in `currency`. Stored at decimal precision —\n * a USDC option might be `199.0`, a BTC option `0.00713`.\n */\n nativeAmount: number;\n /**\n * Optional on-chain memo / payment reference (Solana memo, BTC OP_RETURN\n * tag, Stripe metadata key). Some backends use this to disambiguate\n * which intent an inbound payment satisfies.\n */\n memo?: string;\n /**\n * Whether this option supports the x402 HTTP-402 payment-required\n * flow. Consumers that build agent-driven flows use this flag to\n * filter the offered options.\n */\n x402Capable?: boolean;\n /**\n * Optional per-option expiry — when a single rail's quote becomes\n * stale earlier than the intent-wide price-lock window (e.g. a\n * volatile-currency option that re-quotes more aggressively). ISO\n * 8601 string. Empty / missing means \"inherit the intent's\n * `priceLockExpiresAt`\".\n */\n expiresAt?: string;\n}\n\n// ============================================================================\n// Invoice Types\n// ============================================================================\n\nexport enum InvoiceStatus {\n DRAFT = 'draft',\n SENT = 'sent',\n VIEWED = 'viewed',\n PARTIAL = 'partial',\n PAID = 'paid',\n OVERDUE = 'overdue',\n CANCELLED = 'cancelled',\n WRITTEN_OFF = 'written_off',\n}\n\n// ============================================================================\n// Common Interfaces\n// ============================================================================\n\n/**\n * Address structure used for billing and shipping\n */\nexport interface Address {\n street1?: string;\n street2?: string;\n city?: string;\n state?: string;\n postalCode?: string;\n country?: string;\n}\n\n/**\n * Options for recording a payment with ledger integration\n */\nexport interface RecordPaymentOptions {\n /** The ledger to record the journal entry in */\n ledgerId: string;\n /** Account ID for receivables (credit side) */\n receivablesAccountId: string;\n /** Account ID for cash/bank (debit side) */\n cashAccountId: string;\n}\n\n/**\n * Options for recognizing revenue on an invoice\n */\nexport interface RecognizeRevenueOptions {\n /** Account ID for accounts receivable (debit side) */\n arAccountId: string;\n /** Account ID for revenue (credit side) */\n revenueAccountId: string;\n /** Account ID for tax payable (credit side, optional) */\n taxAccountId?: string;\n}\n\n// ============================================================================\n// Accounting-provider sync shapes\n// ============================================================================\n\n/**\n * A single line item in the format expected by `@happyvertical/accounting`\n * providers (QBO, Stripe, …). Mirrors the object built by\n * {@link InvoiceLineItem.toAccountingLineItem}. The SDK isn't a dependency of\n * this package, so the shape is declared locally rather than imported.\n */\nexport interface AccountingLineItemInput {\n description: string;\n sku?: string;\n quantity: number;\n unitPrice: number;\n discount?: number;\n taxRate?: number;\n amount: number;\n periodStart?: Date;\n periodEnd?: Date;\n}\n\n/**\n * An invoice in the format expected by `@happyvertical/accounting` providers.\n * Mirrors the object built by {@link Invoice.toAccountingInput}; money is in\n * currency **major** units, as the SDK expects.\n */\nexport interface AccountingInvoiceInput {\n id: string;\n externalId?: string;\n invoiceNumber: string;\n customerId: string;\n customerExternalId?: string;\n issueDate: Date;\n dueDate: Date;\n lineItems: AccountingLineItemInput[];\n subtotal: number;\n taxAmount: number;\n totalAmount: number;\n currency: string;\n reference?: string;\n memo?: string;\n}\n\n// ============================================================================\n// Model constructor option interfaces\n//\n// Each model's `constructor(options)` accepts a partial bag of its own fields.\n// Declaring a dedicated `XxxOptions extends SmrtObjectOptions` interface keeps\n// the constructors strongly typed (no `any`) while still allowing the\n// framework's base options (db, ai, fs, _className, …) through.\n// ============================================================================\n\n/**\n * Constructor options for {@link Customer}.\n */\nexport interface CustomerOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n profileId?: string;\n creditLimit?: number;\n paymentTerms?: string;\n taxExempt?: boolean;\n taxId?: string;\n defaultShippingAddress?: Address;\n defaultBillingAddress?: Address;\n status?: CustomerStatus;\n customerType?: CustomerType | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Vendor}.\n */\nexport interface VendorOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n profileId?: string;\n leadTimeDays?: number;\n minimumOrderAmount?: number;\n paymentTerms?: string;\n currency?: string;\n defaultContactEmail?: string;\n defaultContactPhone?: string;\n status?: VendorStatus;\n notes?: string;\n payoutAddresses?: Record<string, string> | string | null;\n}\n\n/**\n * Constructor options for {@link Contract} and its STI subtypes.\n */\nexport interface ContractOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractType?: ContractType;\n status?: ContractStatus;\n customerId?: string | null;\n vendorId?: string | null;\n subtotal?: number;\n taxAmount?: number;\n totalAmount?: number;\n currency?: string;\n issueDate?: Date;\n dueDate?: Date | null;\n expiryDate?: Date | null;\n reference?: string;\n notes?: string;\n terms?: string;\n channelId?: string;\n}\n\n/** Purchase-order header options stored in the existing STI metadata. */\nexport interface PurchaseOrderOptions extends ContractOptions {\n /** Expected delivery calendar date (YYYY-MM-DD); empty means unspecified. */\n expectedDate?: string;\n /** Delivery address/instructions snapshot. */\n shipTo?: string;\n}\n\n/**\n * Constructor options for {@link LicenseSale} (Contract STI subtype). Extends\n * {@link ContractOptions} with the licensing-specific meta fields.\n */\nexport interface LicenseSaleOptions extends ContractOptions {\n skuId?: string;\n paymentId?: string;\n licenseeEmail?: string;\n licenseeLegalEntity?: string;\n licenseeJurisdiction?: string;\n rightsMedium?: string;\n rightsDistributionScope?: string;\n rightsExclusivity?: string;\n rightsDuration?: string;\n rightsTerritory?: string;\n rightsSublicensing?: boolean;\n rightsDerivatives?: boolean;\n pdfUrl?: string;\n pdfHash?: string;\n onChainHashRegistryRef?: string;\n}\n\n/**\n * Constructor options for {@link ContractLineItem}.\n */\nexport interface ContractLineItemOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string;\n description?: string;\n quantity?: number;\n unitPrice?: number;\n discount?: number;\n taxRate?: number;\n amount?: number;\n productId?: string;\n sku?: string;\n startDate?: Date | null;\n endDate?: Date | null;\n billingPeriod?: string;\n sortOrder?: number;\n}\n\n/**\n * How the provider collects an invoice (#3190): `charge_automatically`\n * charges the payer's saved card; `send_invoice` asks the payer to pay. Empty\n * for an invoice never pushed to a provider, or pushed before it was recorded.\n */\nexport type InvoiceCollectionMethod =\n | ''\n | 'send_invoice'\n | 'charge_automatically';\n\n/**\n * Constructor options for {@link Invoice}.\n */\nexport interface InvoiceOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n customerId?: string;\n contractId?: string;\n invoiceNumber?: string;\n reference?: string;\n issueDate?: Date;\n dueDate?: Date;\n paidDate?: Date | null;\n subtotal?: number;\n taxAmount?: number;\n providerTaxAmount?: number;\n totalAmount?: number;\n amountPaid?: number;\n currency?: string;\n status?: InvoiceStatus;\n arJournalId?: string;\n revenueJournalId?: string;\n externalId?: string;\n customerExternalId?: string;\n externalProvider?: string;\n syncedAt?: Date | null;\n collectionMethod?: InvoiceCollectionMethod;\n sentAt?: Date | null;\n viewedAt?: Date | null;\n remindersSent?: number;\n lastReminderAt?: Date | null;\n notes?: string;\n customerNotes?: string;\n terms?: string;\n}\n\n/** Retained editing modes, validated against resolved model fields before reuse. */\nexport interface InvoiceLineEditorState {\n /** Lossless editor draft; never authoritative for ordinary model saves. */\n draft: InvoiceLineDraft;\n /** Context used to resolve that draft; caller authority must still match on reload. */\n context: InvoiceCalculationContext;\n}\n\n/** Constructor options for {@link InvoiceLineItem}. */\nexport interface InvoiceLineItemOptions extends SmrtObjectOptions {\n /** Optional retained editor modes and their original calculation context. */\n invoiceEditorState?: InvoiceLineEditorState | null;\n /** Serialized editor state loaded from persistence; malformed JSON is ignored. */\n invoiceEditorStateJson?: string;\n tenantId?: string | null;\n invoiceId?: string;\n description?: string;\n sku?: string;\n quantity?: number;\n unitPrice?: number;\n discount?: number;\n taxRate?: number;\n amount?: number;\n sourceType?: string;\n sourceId?: string;\n periodStart?: Date | null;\n periodEnd?: Date | null;\n revenueAccountId?: string;\n sortOrder?: number;\n}\n\n/**\n * Constructor options for {@link Fulfillment}.\n */\nexport interface FulfillmentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string;\n fulfillmentType?: FulfillmentType;\n status?: FulfillmentStatus;\n trackingNumber?: string;\n carrier?: string;\n shippingAddress?: Address;\n shippedAt?: Date | null;\n deliveredAt?: Date | null;\n estimatedDelivery?: Date | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link FulfillmentLineItem}.\n */\nexport interface FulfillmentLineItemOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n fulfillmentId?: string;\n contractLineItemId?: string;\n quantityFulfilled?: number;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Payment}.\n */\nexport interface PaymentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n contractId?: string | null;\n customerId?: string | null;\n amount?: number;\n currency?: string;\n method?: PaymentMethod;\n status?: PaymentStatus;\n transactionId?: string;\n reference?: string;\n journalId?: string;\n paidAt?: Date | null;\n notes?: string;\n externalId?: string;\n externalProvider?: string;\n syncedAt?: Date | null;\n backendId?: string;\n backendTxRef?: string;\n nativeAmount?: number;\n nativeCurrency?: string;\n usdAtQuote?: number;\n usdAtConfirmation?: number;\n}\n\n/**\n * Constructor options for {@link PaymentInstrument}.\n */\nexport interface PaymentInstrumentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n customerId?: string;\n backendId?: string;\n providerCustomerId?: string;\n providerPaymentMethodId?: string;\n type?: string;\n brand?: string;\n last4?: string;\n expMonth?: number;\n expYear?: number;\n isDefault?: boolean;\n status?: PaymentInstrumentStatus;\n}\n\n/**\n * Constructor options for {@link PaymentAllocation}.\n */\nexport interface PaymentAllocationOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n paymentId?: string;\n invoiceId?: string;\n amount?: number;\n allocatedAt?: Date;\n allocatedBy?: string;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link PaymentIntent}.\n */\nexport interface PaymentIntentOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n skuId?: string;\n offeringRef?: string;\n licenseeEmail?: string;\n customerId?: string;\n paymentOptions?: PaymentOption[] | string;\n usdPriceLocked?: number;\n priceLockWindowMs?: number;\n priceLockExpiresAt?: Date | number | string | null;\n status?: PaymentIntentStatus;\n idempotencyKey?: string;\n paidOptionBackendId?: string;\n paymentId?: string;\n paidAt?: Date | number | string | null;\n issuedAt?: Date | number | string | null;\n expiredAt?: Date | number | string | null;\n cancelledAt?: Date | number | string | null;\n retiredAt?: Date | number | string | null;\n notes?: string;\n}\n\n/**\n * Constructor options for {@link Payout}.\n */\nexport interface PayoutOptions extends SmrtObjectOptions {\n tenantId?: string | null;\n paymentId?: string;\n vendorId?: string;\n grossAmount?: number;\n operatorFee?: number;\n supplierNet?: number;\n currency?: string;\n backendId?: string;\n backendTxRef?: string;\n status?: PayoutStatus;\n sentAt?: Date | number | string | null;\n confirmedAt?: Date | number | string | null;\n failedAt?: Date | number | string | null;\n failureReason?: string;\n notes?: string;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as currencyMinorUnitExponent, o as normalizeCurrency } from "./calculations-DwBf83UE.js";
1
+ import { a as currencyMinorUnitExponent, o as normalizeCurrency } from "./calculations-Bz7RB3qK.js";
2
2
  //#region src/billing/units.ts
3
3
  function minorToMajorUnits(amount, currency) {
4
4
  if (!Number.isSafeInteger(amount)) throw new Error(`Amount ${amount} must be a safe integer number of minor units.`);
@@ -42,4 +42,4 @@ function tenantKey(id) {
42
42
  //#endregion
43
43
  export { majorToMinorUnits as a, isUuid as i, deterministicId as n, minorToMajorUnits as o, isTenantId as r, tenantKey as s, canonicalTenantId as t };
44
44
 
45
- //# sourceMappingURL=units-BhVZswof.js.map
45
+ //# sourceMappingURL=units-BpZ0_Dpk.js.map