@happyvertical/smrt-commerce 0.56.2 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -11
- package/README.md +137 -89
- package/agents/accounting-snapshot-mapper.md +3 -3
- package/agents/commercial-ui.md +2 -2
- package/agents/contract-money.md +9 -12
- package/agents/cost-billing.md +3 -6
- package/agents/customer-vendor-ui.md +1 -2
- package/agents/document-money.md +58 -31
- package/agents/invoice-calculations.md +31 -56
- package/agents/invoice-preparation.md +62 -73
- package/agents/invoice-quantity.md +9 -21
- package/agents/minor-unit-migration.md +4 -4
- package/agents/money-calculator.md +7 -8
- package/agents/money-model.md +18 -22
- package/agents/money-upgrade.md +92 -0
- package/agents/revenue-journal.md +2 -2
- package/agents/tax-codes.md +17 -14
- package/agents/tax-summary.md +8 -6
- package/dist/billing/period-close.d.ts.map +1 -1
- package/dist/billing/runtime.d.ts +5 -2
- package/dist/billing/runtime.d.ts.map +1 -1
- package/dist/chunks/{ContractLineItemCollection--BrGMtNz.js → ContractLineItemCollection-BZcrlrrY.js} +19 -24
- package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
- package/dist/chunks/{CustomerCollection-CjGaZSpf.js → CustomerCollection-BIPH2ZUx.js} +2 -2
- package/dist/chunks/{CustomerCollection-CjGaZSpf.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
- package/dist/chunks/{DocumentTaxCollection-KOkaQT2M.js → DocumentTaxCollection-DDzlywMW.js} +14 -7
- package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-BUe_8FT1.js → InvoiceCollection-cinzAqt6.js} +50 -161
- package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-DF2eaJed.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
- package/dist/chunks/{TaxCodeCollection-DF2eaJed.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
- package/dist/chunks/{TaxComponentCollection-3x24xSNh.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-3x24xSNh.js.map → TaxComponentCollection-C4zxnyVu.js.map} +1 -1
- package/dist/chunks/VendorCollection-D6ZcUYy8.js +100 -0
- package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +1 -0
- package/dist/chunks/{allocation-integrity-Cq0g6CfO.js → allocation-integrity-WmWyXhtG.js} +6 -6
- package/dist/chunks/{allocation-integrity-Cq0g6CfO.js.map → allocation-integrity-WmWyXhtG.js.map} +1 -1
- package/dist/chunks/calculations-C9daQLsh.js +82 -0
- package/dist/chunks/calculations-C9daQLsh.js.map +1 -0
- package/dist/chunks/contract-money-EuToOghh.js +301 -0
- package/dist/chunks/contract-money-EuToOghh.js.map +1 -0
- package/dist/chunks/detect-CY9ufaFb.js +328 -0
- package/dist/chunks/detect-CY9ufaFb.js.map +1 -0
- package/dist/chunks/{document-money-oUDvdjOf.js → document-money-DV5t4vlK.js} +388 -62
- package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
- package/dist/chunks/{invoice-posting-CF3fE_Ec.js → invoice-posting-C2i5dOVZ.js} +20 -22
- package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +1 -0
- package/dist/chunks/{revenue-journal-DY9fro8E.js → revenue-journal-LLA9Kh_i.js} +3 -14
- package/dist/chunks/revenue-journal-LLA9Kh_i.js.map +1 -0
- package/dist/chunks/tenant-money-barrier-6TlqUeVj.js +255 -0
- package/dist/chunks/tenant-money-barrier-6TlqUeVj.js.map +1 -0
- package/dist/chunks/types-Bbm0Xflc.js.map +1 -1
- package/dist/chunks/{units-CNcjH_eE.js → units-8ElbjVZK.js} +18 -3
- package/dist/chunks/units-8ElbjVZK.js.map +1 -0
- package/dist/collections/ContractCollection.d.ts.map +1 -1
- package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
- package/dist/collections/InvoiceCollection.d.ts.map +1 -1
- package/dist/collections/InvoiceLineItemCollection.d.ts +0 -30
- package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
- package/dist/collections/TaxCodeCollection.d.ts +5 -1
- package/dist/collections/TaxCodeCollection.d.ts.map +1 -1
- package/dist/collections/TaxSettingsCollection.d.ts +15 -8
- package/dist/collections/TaxSettingsCollection.d.ts.map +1 -1
- package/dist/collections/TaxSummaryReportCollection.d.ts +3 -3
- package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -1
- package/dist/collections/index.d.ts +0 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/commerce-money-tools.d.ts.map +1 -1
- package/dist/contract-money.d.ts +1 -13
- package/dist/contract-money.d.ts.map +1 -1
- package/dist/document-money-internal.d.ts +7 -3
- package/dist/document-money-internal.d.ts.map +1 -1
- package/dist/document-money.d.ts +1 -11
- package/dist/document-money.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +667 -1650
- package/dist/index.js.map +1 -1
- package/dist/invoice-accounting-sync.d.ts.map +1 -1
- package/dist/invoice-mcp.d.ts +3 -3
- package/dist/invoice-mcp.d.ts.map +1 -1
- package/dist/invoice-posting.d.ts +11 -5
- package/dist/invoice-posting.d.ts.map +1 -1
- package/dist/invoice-record-screen.d.ts +1 -1
- package/dist/invoice-record-screen.d.ts.map +1 -1
- package/dist/invoices.d.ts +6 -1
- package/dist/invoices.d.ts.map +1 -1
- package/dist/invoices.js +2 -2
- package/dist/manifest.json +37 -765
- package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
- package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
- package/dist/models/Contract.d.ts.map +1 -1
- package/dist/models/ContractLineItem.d.ts +6 -22
- package/dist/models/ContractLineItem.d.ts.map +1 -1
- package/dist/models/Invoice.d.ts +18 -48
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/InvoiceLineItem.d.ts +8 -37
- package/dist/models/InvoiceLineItem.d.ts.map +1 -1
- package/dist/models/TaxSettings.d.ts +0 -10
- package/dist/models/TaxSettings.d.ts.map +1 -1
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/playground.js +1 -1
- package/dist/playground.js.map +1 -1
- package/dist/recipes.d.ts +8 -11
- package/dist/recipes.d.ts.map +1 -1
- package/dist/revenue-journal.d.ts +0 -8
- package/dist/revenue-journal.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +79 -661
- package/dist/svelte/components/InvoiceEditor.svelte +7 -148
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts +4 -1
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceTotals.svelte +14 -118
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -27
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
- package/dist/svelte/components/MoneyInvoiceEditor.svelte +3 -23
- package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +3 -26
- package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +2 -3
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -2
- package/dist/svelte/invoices/calculations.d.ts +3 -108
- package/dist/svelte/invoices/calculations.d.ts.map +1 -1
- package/dist/svelte/invoices/calculations.js +12 -309
- package/dist/svelte/invoices/messages.d.ts +1 -37
- package/dist/svelte/invoices/messages.d.ts.map +1 -1
- package/dist/svelte/invoices/messages.js +1 -37
- package/dist/svelte/invoices/money-editor.d.ts +1 -1
- package/dist/svelte/invoices/money-editor.d.ts.map +1 -1
- package/dist/svelte/invoices/types.d.ts +14 -114
- package/dist/svelte/invoices/types.d.ts.map +1 -1
- package/dist/svelte/playground/InvoicePreparationPreview.svelte +85 -94
- package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -1
- package/dist/svelte/playground.js +1 -1
- package/dist/tax/index.d.ts +2 -1
- package/dist/tax/index.d.ts.map +1 -1
- package/dist/tax/standard-codes.d.ts +12 -0
- package/dist/tax/standard-codes.d.ts.map +1 -0
- package/dist/tax/tenant-bootstrap.d.ts +29 -0
- package/dist/tax/tenant-bootstrap.d.ts.map +1 -0
- package/dist/tenant-money-barrier.d.ts +12 -44
- package/dist/tenant-money-barrier.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -23
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui.js +1 -1
- package/dist/ui.js.map +1 -1
- package/dist/upgrade/detect.d.ts +129 -0
- package/dist/upgrade/detect.d.ts.map +1 -0
- package/dist/upgrade/money-v2.d.ts +81 -0
- package/dist/upgrade/money-v2.d.ts.map +1 -0
- package/package.json +14 -13
- package/agents/line-amount-migration.md +0 -50
- package/dist/chunks/ContractLineItemCollection--BrGMtNz.js.map +0 -1
- package/dist/chunks/DocumentTaxCollection-KOkaQT2M.js.map +0 -1
- package/dist/chunks/InvoiceCollection-BUe_8FT1.js.map +0 -1
- package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js +0 -468
- package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js.map +0 -1
- package/dist/chunks/TaxSettingsCollection-DYYF-In-.js +0 -319
- package/dist/chunks/TaxSettingsCollection-DYYF-In-.js.map +0 -1
- package/dist/chunks/calculations-DaHR17il.js +0 -333
- package/dist/chunks/calculations-DaHR17il.js.map +0 -1
- package/dist/chunks/document-money-oUDvdjOf.js.map +0 -1
- package/dist/chunks/invoice-posting-CF3fE_Ec.js.map +0 -1
- package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
- package/dist/chunks/tenant-money-barrier-y6EAa6vY.js +0 -135
- package/dist/chunks/tenant-money-barrier-y6EAa6vY.js.map +0 -1
- package/dist/chunks/units-CNcjH_eE.js.map +0 -1
- package/dist/collections/MoneyMigrationDocumentCollection.d.ts +0 -12
- package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +0 -1
- package/dist/migrations/commerceLineAmounts.d.ts +0 -84
- package/dist/migrations/commerceLineAmounts.d.ts.map +0 -1
- package/dist/models/MoneyMigrationDocument.d.ts +0 -28
- package/dist/models/MoneyMigrationDocument.d.ts.map +0 -1
- package/dist/svelte/components/InvoiceLineEditor.svelte +0 -92
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +0 -8
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +0 -1
- package/dist/svelte/invoices/fields.d.ts +0 -6
- package/dist/svelte/invoices/fields.d.ts.map +0 -1
- package/dist/svelte/invoices/fields.js +0 -22
- package/dist/tax/legacy.d.ts +0 -48
- package/dist/tax/legacy.d.ts.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ E-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, pay
|
|
|
12
12
|
- **LicenseSale**: industry-neutral licensing primitive. Carries an *immutable* rights snapshot (`rightsMedium`, `rightsDistributionScope`, `rightsExclusivity`, `rightsDuration`, `rightsTerritory`, `rightsSublicensing`, `rightsDerivatives` — typed `Meta<T>` fields), licensee identity (`licenseeEmail`, optional `licenseeLegalEntity` / `licenseeJurisdiction`), and a signed-PDF reference (`pdfUrl`, `pdfHash`, optional `onChainHashRegistryRef`). Once saved at `ContractStatus.ACCEPTED`, the rights snapshot is frozen — mutating any of the seven rights fields and re-saving throws. The only legal transition out of ACCEPTED is `revoke()` (moves to CANCELLED without touching rights). Useful for stock media, music licensing, code-asset marketplaces, license keys, anywhere rights are sold for a fee.
|
|
13
13
|
- **ContractLineItem**: items on contracts.
|
|
14
14
|
- **Invoice**: status machine `DRAFT → SENT → VIEWED → PARTIAL → PAID` (also OVERDUE, CANCELLED, WRITTEN_OFF). `recognizeRevenue()` creates balanced AR journal entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable).
|
|
15
|
-
- **InvoiceLineItem**: fractional-quantity line items with safe minor-unit validation/recalculation on save;
|
|
15
|
+
- **InvoiceLineItem**: fractional-quantity line items with safe minor-unit validation/recalculation on save; the existing-column migration is documented in [invoice quantity persistence](agents/invoice-quantity.md).
|
|
16
16
|
- **Payment** / **PaymentAllocation**: tracks payments against invoices (status: see Gotchas). Carries optional backend-adapter fields for PaymentBackend-routed flows: `backendId` (rail adapter id — `base-usdc`, `btc`, `stripe`, distinct from `externalProvider` which names an accounting sync destination), `backendTxRef` (chain tx hash or gateway settlement id), `nativeAmount` / `nativeCurrency` (what actually arrived), `usdAtQuote` / `usdAtConfirmation` (drift accounting for volatile-currency rails). `Payment.usdDrift()` returns the confirmation - quote delta, or `0` when either side is unset.
|
|
17
17
|
- **PaymentInstrument**: reusable saved payment method ("card on file"). Stores only provider references and non-sensitive display metadata (`providerCustomerId`, `providerPaymentMethodId`, brand / last4 / expiry), never raw card data. `isDefault` is domain-managed, not API-writable: `PaymentInstrumentCollection.setDefaultForCustomer(customerId, instrumentId)` validates the target and clears the customer's other defaults (one per customer).
|
|
18
18
|
- **PaymentIntent**: short-lived pre-payment commitment with multi-option semantics. Locks a USD price for a fixed window (default 15 minutes) and lists one or more `PaymentOption`s describing different rails (`backendId`, `currency`, `payTo`, `nativeAmount`, optional `chain` / `memo` / `x402Capable` / `expiresAt`). First option to receive payment wins; the others are implicitly retired. State machine `awaiting_payment → paid → (issued | retired)` plus `expired` and `cancelled`. Mutate via dedicated `markPaid` / `markIssued` / `expire` / `cancel` / `retire` helpers — direct status assignment bypasses the invariant checks. Idempotency: see the natural-key Gotcha.
|
|
@@ -34,11 +34,16 @@ language for end users and update them when a recipe's fields change. See
|
|
|
34
34
|
See [payment allocation integrity](agents/payment-allocations.md) for atomic splits,
|
|
35
35
|
retry identity, parent corrections and supported dialects.
|
|
36
36
|
See [tax codes](agents/tax-codes.md) for tenant-owned rate components, default codes,
|
|
37
|
-
and
|
|
38
|
-
See [document money persistence](agents/document-money.md) for the
|
|
39
|
-
|
|
37
|
+
and the standard codes every tenant gets.
|
|
38
|
+
See [document money persistence](agents/document-money.md) for the one money
|
|
39
|
+
model: tax-code resolution, atomic aggregate writes, issued snapshots, and
|
|
40
|
+
frozen fields. Every Invoice and Contract money write needs a tenant. Host
|
|
41
|
+
bootstrap is `ensureCommerceTenant(db, tenantId, { cookbook })`, which returns
|
|
42
|
+
`{ tenantId }`. Money data written by an earlier version is refused with
|
|
43
|
+
`COMMERCE_DATA_NEEDS_UPGRADE` until `smrt db:upgrade-commerce-money` converts
|
|
44
|
+
it; see [money data upgrade](agents/money-upgrade.md).
|
|
40
45
|
See [Contract money persistence](agents/contract-money.md) for the Contract
|
|
41
|
-
aggregate
|
|
46
|
+
aggregate and purchase recoverability.
|
|
42
47
|
|
|
43
48
|
## Ledger Integration
|
|
44
49
|
|
|
@@ -128,8 +133,14 @@ for usage.
|
|
|
128
133
|
`WRITTEN_OFF` (a later payment, by card or on a rail (#3188), is recorded
|
|
129
134
|
and reinstates the payer; the status stays, so "paid in full" for a
|
|
130
135
|
written-off invoice is read from its allocations).
|
|
131
|
-
-
|
|
132
|
-
server-managed
|
|
136
|
+
- **Billing invoices are money-model invoices.** The provider taxes them
|
|
137
|
+
(`providerTaxAmount`, server-managed, not API-writable, added to line tax),
|
|
138
|
+
so every billing line is an explicit `taxMode: 'override'` to the seller's
|
|
139
|
+
`out-of-scope` code. Charges are written before credits so the running
|
|
140
|
+
subtotal never goes negative, and provider tax is written in the save that
|
|
141
|
+
issues the invoice. Revenue posting is idempotent. `ledger.bookCurrency` is
|
|
142
|
+
required (ISO 4217, validated by `BillingRuntime`) and must equal the
|
|
143
|
+
billing currency. `toAccountingInput({ mapping })` emits major units.
|
|
133
144
|
- **Payment rails (#3138).** A runtime has one issuing `provider` and any
|
|
134
145
|
number of `paymentProviders` (unique names, one inbox namespace each; a
|
|
135
146
|
provider with `capabilities.issuesInvoices === false` can never issue).
|
|
@@ -167,11 +178,13 @@ for usage.
|
|
|
167
178
|
## Gotchas
|
|
168
179
|
|
|
169
180
|
- **Optional tenancy**: all models `@TenantScoped({ mode: 'optional' })` + nullable tenantId
|
|
170
|
-
- **Money units and arithmetic**: money fields use signed integer minor units; rates and quantities remain decimal where their contracts require them. Exact rounding, tax-code resolution,
|
|
171
|
-
- **
|
|
172
|
-
- **
|
|
181
|
+
- **Money units and arithmetic**: money fields use signed integer minor units; rates and quantities remain decimal where their contracts require them. Exact rounding, tax-code resolution, safe-integer validation, and journal units are defined in the [commercial money model](agents/money-model.md) and [money calculator](agents/money-calculator.md). Do not reintroduce floating-point tolerances on money paths.
|
|
182
|
+
- **Money documents need a tenant**: models are optionally tenant-scoped, but every Invoice and Contract money write requires one (`MoneyValidationError` `INVALID_MONEY_INPUT`, `Commerce money documents require a tenant`).
|
|
183
|
+
- **Earlier-version money data is refused**: never add a read or conversion path for it. Detection and the one-shot upgrade are in [money data upgrade](agents/money-upgrade.md).
|
|
184
|
+
- **Book currency is required**: `recognizeRevenue()` and `cancelWithRevenueReversal()` take a required `bookCurrency`, which must equal the invoice currency.
|
|
185
|
+
- **Asset-native amounts stay outside line rescales**: `Payment.nativeAmount` and `PaymentIntent.paymentOptions[].nativeAmount` are per-asset values identified by `nativeCurrency`; normalize each asset to its own minor unit before any type conversion, and never apply the commerce ×100 line-money rescale to them.
|
|
173
186
|
- **Invoice controls payment status**: not the Payment model — use `Invoice.updatePaymentStatus()`
|
|
174
|
-
- **Tax
|
|
187
|
+
- **Tax comes from tax codes**: the server resolves each line's code and computes line tax and header totals; client totals and a line's stored `taxRate` are never inputs
|
|
175
188
|
- **Profile linking**: separate `ProfileCollection.create()` needed to fetch actual Profile object
|
|
176
189
|
- **PaymentIntent natural key**: `conflictColumns: ['tenant_id', 'offering_ref', 'licensee_email', 'idempotency_key']` — a retried `create` with the same tuple upserts the existing row. Use `PaymentIntentCollection.getOrCreateByIdempotencyKey()` to branch on `{ intent, created }`. Empty natural-key inputs (e.g. blank `idempotencyKey`) disable dedup by design.
|
|
177
190
|
- **Payout state-machine guards**: `markSent` requires a non-empty `backendTxRef`; `markConfirmed` only valid from `SENT`; `markFailed` only valid from `PENDING` / `SENT` (a confirmed payout can't fail — that path is a refund). `resetFromFailed()` is the dedicated escape hatch from `FAILED` back to `PENDING` after an operator fixes the underlying problem; it clears `backendTxRef` so the next attempt picks up a fresh one.
|
package/README.md
CHANGED
|
@@ -11,70 +11,80 @@ pnpm add @happyvertical/smrt-commerce
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
+
import { withTenant } from '@happyvertical/smrt-tenancy';
|
|
14
15
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
CustomerCollection,
|
|
17
|
+
ContractCollection,
|
|
18
|
+
InvoiceCollection,
|
|
19
|
+
InvoiceLineItemCollection,
|
|
20
|
+
PaymentCollection,
|
|
21
|
+
PaymentMethod,
|
|
22
|
+
ensureCommerceTenant,
|
|
20
23
|
} from '@happyvertical/smrt-commerce';
|
|
21
24
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
await
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Create an invoice
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const invoice = await invoices.create({
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
25
|
+
// Once per tenant, after the host creates it: tax settings and standard codes.
|
|
26
|
+
await ensureCommerceTenant(db, tenantId);
|
|
27
|
+
|
|
28
|
+
// Money documents (invoices, contracts, and their lines) belong to a tenant.
|
|
29
|
+
await withTenant({ tenantId }, async () => {
|
|
30
|
+
// Create a customer linked to a profile
|
|
31
|
+
const customers = await CustomerCollection.create({ db });
|
|
32
|
+
const customer = await customers.create({
|
|
33
|
+
profileId: 'profile-uuid',
|
|
34
|
+
creditLimit: 1000000, // $10,000.00 — money is integer minor units
|
|
35
|
+
paymentTerms: 'Net 30',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Create an order (STI contract type)
|
|
39
|
+
const contracts = await ContractCollection.create({ db });
|
|
40
|
+
const order = await contracts.create({
|
|
41
|
+
_meta_type: 'Order',
|
|
42
|
+
customerId: customer.id,
|
|
43
|
+
currency: 'CAD',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Create an invoice. The server resolves each line's tax code and computes
|
|
47
|
+
// line tax and the header totals; header amounts are never inputs.
|
|
48
|
+
const invoices = await InvoiceCollection.create({ db });
|
|
49
|
+
const invoice = await invoices.create({
|
|
50
|
+
customerId: customer.id,
|
|
51
|
+
contractId: order.id,
|
|
52
|
+
invoiceNumber: await invoices.generateInvoiceNumber(),
|
|
53
|
+
currency: 'CAD',
|
|
54
|
+
});
|
|
55
|
+
const lines = await InvoiceLineItemCollection.create({ db });
|
|
56
|
+
await lines.create({
|
|
57
|
+
invoiceId: invoice.id,
|
|
58
|
+
description: 'Consulting',
|
|
59
|
+
quantity: 10,
|
|
60
|
+
unitPrice: 10000, // $100.00
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Issuing freezes the money snapshot; then recognize revenue (creates a
|
|
64
|
+
// balanced journal in smrt-ledgers from the frozen facts).
|
|
65
|
+
const issued = await invoices.get({ id: invoice.id }, { cache: false });
|
|
66
|
+
issued.markSent();
|
|
67
|
+
await issued.save();
|
|
68
|
+
await issued.recognizeRevenue({
|
|
69
|
+
arAccountId: 'ar-account-id',
|
|
70
|
+
revenueAccountId: 'revenue-account-id',
|
|
71
|
+
taxAccountId: 'tax-account-id',
|
|
72
|
+
bookCurrency: 'CAD', // must equal the invoice currency
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Record a payment with ledger integration
|
|
76
|
+
const payments = await PaymentCollection.create({ db });
|
|
77
|
+
const payment = await payments.create({
|
|
78
|
+
contractId: order.id,
|
|
79
|
+
customerId: customer.id,
|
|
80
|
+
amount: issued.totalAmount,
|
|
81
|
+
method: PaymentMethod.CREDIT_CARD,
|
|
82
|
+
});
|
|
83
|
+
await payment.recordPayment({
|
|
84
|
+
ledgerId: 'ledger-id',
|
|
85
|
+
receivablesAccountId: 'ar-account-id',
|
|
86
|
+
cashAccountId: 'bank-account-id',
|
|
87
|
+
});
|
|
78
88
|
});
|
|
79
89
|
```
|
|
80
90
|
|
|
@@ -104,7 +114,7 @@ const totals = calculateMoneyDocument({
|
|
|
104
114
|
```
|
|
105
115
|
|
|
106
116
|
`resolveLineTaxCode()` resolves caller-loaded codes without I/O, and the
|
|
107
|
-
Svelte `InvoiceEditor`
|
|
117
|
+
Svelte `InvoiceEditor` renders the money controls from its required `money`
|
|
108
118
|
prop. Frozen editor documents render a supplied stored snapshot without
|
|
109
119
|
recalculation. See the [commerce money calculator guide](../../docs/content/commerce-money.md)
|
|
110
120
|
for pricing modes, rounding, input and result types, the native-form editor
|
|
@@ -119,40 +129,68 @@ one settings row per tenant supplies sales/purchase defaults, amount mode,
|
|
|
119
129
|
rounding scope, and labelled registration numbers. A referenced component rate
|
|
120
130
|
is immutable: archive it and create a replacement for a rate change.
|
|
121
131
|
|
|
122
|
-
`ensureTenantTaxCodes(db, tenantId)` idempotently creates the
|
|
123
|
-
non-taxable codes
|
|
132
|
+
`ensureTenantTaxCodes(db, tenantId)` idempotently creates the standard
|
|
133
|
+
non-taxable codes, `exempt`, `zero-rated`, and `out-of-scope`
|
|
134
|
+
(`STANDARD_TAX_CODES`). `applyTaxCodeExamplePack(db, tenantId)` installs illustrative
|
|
124
135
|
regional examples for review, and `applyCommerceCookbookSettings(db, tenantId,
|
|
125
136
|
cookbook)` explicitly materializes a validated cookbook's defaults after schema
|
|
126
137
|
setup and authentication establish the tenant. See the [commerce money
|
|
127
138
|
calculator guide](../../docs/content/commerce-money.md#tenant-tax-catalogue) for
|
|
128
139
|
the supported kinds, seed behavior, and cookbook rate units.
|
|
129
140
|
|
|
130
|
-
### Persisted Invoice
|
|
141
|
+
### Persisted Invoice money
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
`
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
Commerce has one money model. Tax codes resolve each line's tax: the line's
|
|
144
|
+
override, else `exempt` for an exempt customer, else the product's code, the
|
|
145
|
+
customer's code, then the tenant default. The server computes line
|
|
146
|
+
`taxAmount` and the header totals and ignores any client total. Every Invoice
|
|
147
|
+
and Contract money write needs a tenant; without one it fails with
|
|
148
|
+
`MoneyValidationError` `INVALID_MONEY_INPUT`.
|
|
149
|
+
`ensureCommerceTenant(db, tenantId, { cookbook })` is the one bootstrap call a
|
|
150
|
+
host makes after creating a tenant. It seeds the tenant's tax settings, the
|
|
151
|
+
standard codes, and the cookbook's defaults, and returns `{ tenantId }`.
|
|
138
152
|
|
|
139
153
|
Issuing an Invoice (leaving `DRAFT`) or assigning `arJournalId` freezes its
|
|
140
154
|
money snapshot. Header and line money changes then fail with `FROZEN_DOCUMENT`,
|
|
141
155
|
while later lifecycle and payment updates retain the snapshot. Provider tax
|
|
142
156
|
stays separate in `providerTaxAmount` and contributes to the Invoice headers
|
|
143
|
-
without creating an application tax code.
|
|
157
|
+
without creating an application tax code.
|
|
144
158
|
`InvoiceLineItemCollection.getTotalForInvoice()` sums line amounts for
|
|
145
|
-
compatibility; use persisted `Invoice.totalAmount` for the amount due.
|
|
159
|
+
compatibility; use persisted `Invoice.totalAmount` for the amount due.
|
|
160
|
+
`recognizeRevenue()` and `cancelWithRevenueReversal()` require a `bookCurrency`
|
|
161
|
+
equal to the invoice currency, and `toAccountingInput({ mapping })` requires
|
|
162
|
+
explicit QuickBooks realm mapping. The
|
|
146
163
|
[commerce money calculator guide](../../docs/content/commerce-money.md#persisted-invoice-money)
|
|
147
|
-
and [document-money contract](agents/document-money.md) cover persisted Invoice
|
|
164
|
+
and [document-money contract](agents/document-money.md) cover persisted Invoice
|
|
148
165
|
semantics, credits, refunds, and posting. The materialized Invoice tax-report
|
|
149
166
|
descriptor and tenant-safe drilldown are documented in
|
|
150
|
-
[tax-summary contract](agents/tax-summary.md).
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
[tax-summary contract](agents/tax-summary.md). Contract money persistence,
|
|
168
|
+
PurchaseContract tax contributors, and the materialized tax-summary host and
|
|
169
|
+
drilldown are available. Applications opt in by registering
|
|
170
|
+
`createTaxSummaryMaterializedReportHost()` in their server-only report-host
|
|
171
|
+
registry; the route binds the authenticated tenant and read permission. See the
|
|
172
|
+
tax-summary contract for registration details.
|
|
173
|
+
|
|
174
|
+
### Upgrading money data from an earlier version
|
|
175
|
+
|
|
176
|
+
Commerce refuses money data written by an earlier version with
|
|
177
|
+
`COMMERCE_DATA_NEEDS_UPGRADE` instead of reading it. That is a schema that
|
|
178
|
+
still has the earlier money columns or the `commerce_money_migration_documents` table,
|
|
179
|
+
an invoice or contract without a tenant, or a tenant with invoices or contracts but no tax settings row or with
|
|
180
|
+
documents in the earlier format. `smrt db:status`
|
|
181
|
+
reports it, so `smrt app doctor` reports `migration-required`. Run the normal
|
|
182
|
+
schema migration, then upgrade once:
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
smrt db:upgrade-commerce-money --check # report only
|
|
186
|
+
smrt db:upgrade-commerce-money # every tenant
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Each tenant converts in its own transaction. Stored rates become tax codes,
|
|
190
|
+
issued documents keep every stored total, and drafts are recomputed. The
|
|
191
|
+
earlier columns are dropped once no tenant needs the upgrade. See
|
|
192
|
+
[money data upgrade](agents/money-upgrade.md) and the
|
|
193
|
+
[CLI guide](../cli/agents/commerce-money-upgrade.md).
|
|
156
194
|
|
|
157
195
|
## API
|
|
158
196
|
|
|
@@ -318,6 +356,7 @@ const billing = await BillingRuntime.create({
|
|
|
318
356
|
provider: createStripeBillingProvider({ stripe, webhookSecret }),
|
|
319
357
|
billingRelationships, // smrt-tenancy BillingRelationshipService
|
|
320
358
|
ledger: {
|
|
359
|
+
bookCurrency: 'USD', // ISO 4217; must equal the billing currency
|
|
321
360
|
arAccountId,
|
|
322
361
|
revenueAccountId,
|
|
323
362
|
taxAccountId,
|
|
@@ -465,7 +504,13 @@ const evaluator = await SpendingPolicyEvaluator.create({
|
|
|
465
504
|
upgrade (`<subscriptionId>:<periodStart>`, number 0) count as billed
|
|
466
505
|
coverage, so nothing is billed again after it.
|
|
467
506
|
- **Tax** comes from the provider (Stripe Tax) using the account customer's
|
|
468
|
-
`defaultBillingAddress`; the invoice records it as `providerTaxAmount
|
|
507
|
+
`defaultBillingAddress`; the invoice records it as `providerTaxAmount` in the
|
|
508
|
+
save that issues it. Billing invoices are ordinary money-model invoices in
|
|
509
|
+
the seller's tenant; period close seeds its standard codes if needed. Every
|
|
510
|
+
billing line is an explicit override to the seller's `out-of-scope` code, so
|
|
511
|
+
the seller's default code never taxes it a second time. Charges are written
|
|
512
|
+
before credits, so the running subtotal never goes negative. Revenue posting
|
|
513
|
+
uses `ledger.bookCurrency` and is idempotent.
|
|
469
514
|
- **Events** are verified, stored in smrt-jobs' durable delivery inbox, and
|
|
470
515
|
applied against current provider state: paid invoices record a payment and
|
|
471
516
|
allocation; failures and overdue notices mark the payer `past_due` and its
|
|
@@ -751,14 +796,17 @@ The same models carry `@field({ ui: { widget } })` hints in the manifest:
|
|
|
751
796
|
|
|
752
797
|
## Invoice preparation UI
|
|
753
798
|
|
|
754
|
-
`
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
799
|
+
`InvoiceEditor` from `@happyvertical/smrt-commerce/svelte` is the invoice form,
|
|
800
|
+
built on the commercial money model, v2, which is the only invoice money model.
|
|
801
|
+
It requires a `money` prop (`MoneyEditorProps`): a lossless draft with
|
|
802
|
+
descriptions, fractional quantities, unit prices, percentage or amount
|
|
803
|
+
discounts and a tax code per line (inherited or overridden), plus the
|
|
804
|
+
authorized tax codes. Its previews run the same exact calculator the server
|
|
805
|
+
uses, and the server re-resolves tax codes and recomputes every stored amount.
|
|
806
|
+
An issued document renders only its frozen stored snapshot.
|
|
807
|
+
`InvoiceAllocationFields` and `InvoiceReview` add caller-owned sources and
|
|
808
|
+
approvals. See [the editor contract](agents/invoice-preparation.md), the
|
|
809
|
+
[line calculation helper](agents/invoice-calculations.md), and the
|
|
762
810
|
[existing-database storage migration](agents/invoice-quantity.md).
|
|
763
811
|
|
|
764
812
|
## Exact cost billing
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
snapshot. `src/invoice-accounting-sync.ts` is the owning persistence and
|
|
5
5
|
provider boundary. It re-reads the issued Invoice, InvoiceLineItem and
|
|
6
6
|
DocumentTax rows and verifies their shared `moneySnapshotId` before calling the
|
|
7
|
-
pure mapper. `Invoice.toAccountingInput()`
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
pure mapper. `Invoice.toAccountingInput({ mapping })` always requires explicit
|
|
8
|
+
QuickBooks realm mapping and is routed through the frozen boundary; calling it
|
|
9
|
+
without options throws `INVALID_MONEY_INPUT`.
|
|
10
10
|
|
|
11
11
|
The released `@happyvertical/accounting` 0.103.7 path accepts frozen invoices
|
|
12
12
|
and positive credit memos. Commerce sends persisted pre-discount unit price,
|
package/agents/commercial-ui.md
CHANGED
|
@@ -36,8 +36,8 @@ Do not mix currencies in totals or infer an unknown allocation as zero.
|
|
|
36
36
|
|
|
37
37
|
The original invoice display DTOs in `src/svelte/types.ts` and their components
|
|
38
38
|
accept decimal major-unit amounts. Preserve that published behavior. Applications
|
|
39
|
-
adapting model amounts into those
|
|
40
|
-
do not pass a model's integer amount directly or silently change a
|
|
39
|
+
adapting model amounts into those major-unit components must explicitly convert
|
|
40
|
+
units; do not pass a model's integer amount directly or silently change a prop's
|
|
41
41
|
meaning. New editor DTOs are separate contracts.
|
|
42
42
|
|
|
43
43
|
Approved or otherwise retained invoice facts use the explicit `retained` display
|
package/agents/contract-money.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Contract money runs through `contract-money.ts`. A tenant operation enters
|
|
4
4
|
`withTenantMoneyRuntime` before it locks and reloads its Contract and sibling
|
|
5
|
-
lines on the scoped executor
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
lines on the scoped executor, resolves tax codes, and replaces the Contract
|
|
6
|
+
aggregate plus `DocumentTax` rows in the same transaction. Every Contract money
|
|
7
|
+
write needs a tenant, and a tenant whose data needs the upgrade is refused (see
|
|
8
|
+
[money data upgrade](money-upgrade.md)).
|
|
8
9
|
|
|
9
10
|
Draft Contract line writes recompute every sibling. `subtotal` is tax-exclusive
|
|
10
11
|
net after discount, `taxAmount` is the component total, and `totalAmount` is
|
|
@@ -13,12 +14,8 @@ component says so. A Contract leaving draft snapshots registrations, party tax
|
|
|
13
14
|
identity, per-line component amounts, and `moneySnapshotId`; later money edits
|
|
14
15
|
are refused with `FROZEN_DOCUMENT`.
|
|
15
16
|
|
|
16
|
-
`
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
legacy-to-v2 migration. Non-draft quotes freeze just like other Contracts, but
|
|
22
|
-
quote snapshots (including custom descendants) are excluded by their hydrated
|
|
23
|
-
STI subtype, not the mutable `contractType` scalar, from report candidates and
|
|
24
|
-
purchase tax facts. Awarding requires a separate PurchaseOrder.
|
|
17
|
+
`SupplierQuote` uses purchase defaults and recoverability. Non-draft quotes
|
|
18
|
+
freeze just like other Contracts, but quote snapshots (including custom
|
|
19
|
+
descendants) are excluded by their hydrated STI subtype, not the mutable
|
|
20
|
+
`contractType` scalar, from report candidates and purchase tax facts. Awarding
|
|
21
|
+
requires a separate PurchaseOrder.
|
package/agents/cost-billing.md
CHANGED
|
@@ -3,13 +3,10 @@
|
|
|
3
3
|
`@happyvertical/smrt-commerce/cost-billing` is an independent, transport-neutral
|
|
4
4
|
entry point without model, server or Svelte dependencies. It does not persist,
|
|
5
5
|
post, authorize, select a currency/jurisdiction, or implement credit-note lifecycle.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`calculateInvoiceMinorLine` retains negative ties toward positive infinity; the
|
|
9
|
-
new opt-in helpers use symmetric half-away-from-zero ties. Positive-only invoice
|
|
10
|
-
editor validation remains distinct from these generic signed calculations. The
|
|
6
|
+
It does not change Invoice calculations or the invoice editor; both use the
|
|
7
|
+
commerce money calculator, whose ties also round half away from zero. The
|
|
11
8
|
`roundingScope: 'invoice'` behavior in this guide is a standalone cost-billing
|
|
12
|
-
convention;
|
|
9
|
+
convention; document arithmetic uses `roundingScope: 'document'` and stores
|
|
13
10
|
allocated tax facts. See the [commercial money model](money-model.md) and
|
|
14
11
|
[money calculator](money-calculator.md).
|
|
15
12
|
|
|
@@ -179,8 +179,7 @@ money-v2 fields (#4011): line `taxAmount`, `discountTotal` on Invoice and
|
|
|
179
179
|
Contract, `Invoice.creditedAmount`, and `DocumentTax` `taxableMinor` /
|
|
180
180
|
`taxMinor` / `recoverableMinor`; the test asserts every integer named
|
|
181
181
|
`*Amount`, `*Total`, `*Minor` or `subtotal` on those models (and Contract's
|
|
182
|
-
STI subtypes) is hinted.
|
|
183
|
-
state, not a user-facing amount, and stays unhinted. Payout and Payment's
|
|
182
|
+
STI subtypes) is hinted. Payout and Payment's
|
|
184
183
|
native/USD drift amounts are not hinted: their currency is a payout rail or
|
|
185
184
|
fixed USD, not the record's `currency`. The line-item FKs
|
|
186
185
|
(`ContractLineItem.contractId`, `InvoiceLineItem.invoiceId`,
|
package/agents/document-money.md
CHANGED
|
@@ -1,28 +1,51 @@
|
|
|
1
1
|
# Document money persistence
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Commerce has one money model. Tax codes resolve each line's tax, the server
|
|
4
|
+
computes line `taxAmount` and the header totals, and issuing a document freezes
|
|
5
|
+
it with a money snapshot. Every Invoice and Contract money write needs a
|
|
6
|
+
tenant; a tenantless write fails with `MoneyValidationError`
|
|
7
|
+
`INVALID_MONEY_INPUT` (`Commerce money documents require a tenant`).
|
|
8
|
+
|
|
9
|
+
### Tenant settings and bootstrap (#4009)
|
|
10
|
+
|
|
11
|
+
Each tenant has one `TaxSettings` row. It is created once, wherever the first
|
|
12
|
+
caller reaches it: `getOrCreateForTenant`, the cookbook path, a generated
|
|
13
|
+
create, or the runtime barrier ahead of a tenant's first document. Creation
|
|
14
|
+
runs in one transaction under a tenant advisory lock on PostgreSQL (the
|
|
15
|
+
embedded write queue elsewhere), re-reads before inserting, and inserts the
|
|
16
|
+
deterministic settings id, so racing first writers converge on one row.
|
|
17
|
+
|
|
18
|
+
`ensureCommerceTenant(db, tenantId, { cookbook? })` is the one host bootstrap
|
|
19
|
+
call after tenant creation. It seeds the settings row, the standard tax codes
|
|
20
|
+
(`exempt`, `zero-rated`, `out-of-scope`), and the cookbook's commerce tax
|
|
21
|
+
settings when one is given. It returns `{ tenantId }` and is idempotent;
|
|
22
|
+
re-running it with a cookbook re-applies the cookbook defaults.
|
|
23
|
+
|
|
24
|
+
Money data written by an earlier version is refused, never read. A schema with
|
|
25
|
+
the earlier version's money columns or table, or an Invoice or Contract row
|
|
26
|
+
without a tenant, refuses the whole database. A tenant with Invoice or Contract
|
|
27
|
+
rows but no settings row, or with old-format documents (an issued document
|
|
28
|
+
without a money snapshot, or a line with neither a tax code nor a component
|
|
29
|
+
snapshot), is refused on its own. All fail with `COMMERCE_DATA_NEEDS_UPGRADE`
|
|
30
|
+
and name `smrt db:upgrade-commerce-money`.
|
|
31
|
+
See [money data upgrade](money-upgrade.md). Rows written with raw SQL bypass
|
|
32
|
+
the model and are not recalculated.
|
|
8
33
|
|
|
9
34
|
Every Invoice/InvoiceLineItem get, list, preview, mutation, posting, and
|
|
10
|
-
accounting-sync phase
|
|
11
|
-
rows. The
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
and runtime never creates schema.
|
|
18
|
-
|
|
19
|
-
In `v2`, saving an invoice line locks and reloads its parent on the caller's
|
|
35
|
+
accounting-sync phase checks for old-format data, then acquires the tenant
|
|
36
|
+
settings row before document rows. The lock is held through the owning
|
|
37
|
+
transaction. Nested coordinators reuse an explicitly registered transaction
|
|
38
|
+
executor; adapter shape is never used as transaction proof. Runtime code never
|
|
39
|
+
creates schema.
|
|
40
|
+
|
|
41
|
+
Saving an invoice line locks and reloads its parent on the caller's
|
|
20
42
|
transaction, reloads every sibling, resolves current tax defaults, calculates
|
|
21
43
|
the document once, and writes line snapshots, `DocumentTax`, and header totals
|
|
22
44
|
atomically. Tax resolution order is line override, customer exemption (unless
|
|
23
|
-
the line overrides), product, customer, then
|
|
24
|
-
the
|
|
25
|
-
tenant-owned and
|
|
45
|
+
the line overrides), product, customer code, then the tenant default; `none`
|
|
46
|
+
mode uses the tenant's `out-of-scope` code. Product reads use the products
|
|
47
|
+
package's collection on that same transaction executor; tenant-owned and
|
|
48
|
+
catalog-global products are accepted.
|
|
26
49
|
|
|
27
50
|
The first transition away from `DRAFT`, or setting `arJournalId`, freezes the
|
|
28
51
|
money document. The invoice and every line receive the same stable
|
|
@@ -69,18 +92,17 @@ draft overlay through the owning resolver/calculator without writes and rejects
|
|
|
69
92
|
frozen documents. A supplied line id must name a stored line exactly once; omit
|
|
70
93
|
the id for a new preview-only line. `getInvoiceMoneyFacts({ db, tenantId,
|
|
71
94
|
invoiceId })` reads the authoritative stored header, lines, `moneySnapshotId`,
|
|
72
|
-
and `DocumentTax` rows without recalculation. Both facades
|
|
73
|
-
|
|
74
|
-
partially migrated amounts are never presented as money-v2 facts.
|
|
95
|
+
and `DocumentTax` rows without recalculation. Both facades take the tenant
|
|
96
|
+
settings lock and refuse a tenant whose data needs the upgrade.
|
|
75
97
|
|
|
76
98
|
### Record-screen composition
|
|
77
99
|
|
|
78
100
|
`loadInvoiceRecordScreenData({ db, tenantId, invoiceId })` is the commerce
|
|
79
101
|
server adapter for a generic invoice record page. A caller must already have
|
|
80
102
|
that exact active tenant; the adapter never switches principals based on its
|
|
81
|
-
argument. It holds the
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
argument. It holds the tenant money lock while it reads the stored header,
|
|
104
|
+
lines, `DocumentTax`, and related credit-note summaries, so it never mixes a
|
|
105
|
+
stale header with a different snapshot of those rows. Its totals
|
|
84
106
|
are a direct projection of persisted minor-unit values and component snapshots;
|
|
85
107
|
it does not resolve a code or recalculate tax.
|
|
86
108
|
|
|
@@ -91,9 +113,10 @@ may expose `onCreateCreditNote` only with a server-derived
|
|
|
91
113
|
`canCreateCreditNote` authorization result. The wrapper hides the action for
|
|
92
114
|
credit-note records and never treats a client callback as authorization.
|
|
93
115
|
|
|
94
|
-
`InvoiceLineItem.amount` is
|
|
95
|
-
`
|
|
96
|
-
|
|
116
|
+
`InvoiceLineItem.amount` is the post-discount amount in the document's
|
|
117
|
+
`lineAmountType`: tax-exclusive net for `exclusive`, tax-inclusive for
|
|
118
|
+
`inclusive`. `taxAmount` is separate. `taxRate` is derived by the server from
|
|
119
|
+
the resolved components (`0.05` means 5%); it is never an input. `taxCodeId` and component identity may be null for
|
|
97
120
|
a genuine unassigned zero-tax line. Optional `productId` and
|
|
98
121
|
`revenueAccountId` are nullable UUID references so PostgreSQL never receives an
|
|
99
122
|
empty-string sentinel.
|
|
@@ -103,7 +126,7 @@ labels, percentage, liability account, taxable minor units, and tax minor
|
|
|
103
126
|
units. Provider tax remains the invoice header's `providerTaxAmount`; commerce
|
|
104
127
|
does not invent a tax code or component for provider-owned tax.
|
|
105
128
|
|
|
106
|
-
Revenue recognition
|
|
129
|
+
Revenue recognition reads only the frozen invoice, lines, and
|
|
107
130
|
`DocumentTax` rows. It groups each line's stored net by its snapshotted revenue
|
|
108
131
|
account (falling back to the caller's revenue account), groups component tax by
|
|
109
132
|
the snapshotted liability account (falling back to the caller's tax account),
|
|
@@ -118,10 +141,14 @@ the invoice parent lock and transaction, so retries converge. Account IDs from
|
|
|
118
141
|
frozen snapshots are still authorization checked: every used account must be
|
|
119
142
|
active and owned by the invoice tenant.
|
|
120
143
|
|
|
121
|
-
A posted
|
|
144
|
+
A posted invoice cannot move directly to `CANCELLED`. Call
|
|
122
145
|
`cancelWithRevenueReversal({ bookCurrency })`; it reads the original posted
|
|
123
146
|
journal, posts one deterministic journal with every debit and credit reversed,
|
|
124
147
|
and stores `arReversalJournalId` with the status transition atomically. The
|
|
125
148
|
original journal remains posted. Failed link or reversal writes roll back the
|
|
126
|
-
entire operation, and retry returns the same reversal.
|
|
127
|
-
|
|
149
|
+
entire operation, and retry returns the same reversal.
|
|
150
|
+
|
|
151
|
+
Billing period close writes ordinary money-model invoices. The payment provider
|
|
152
|
+
taxes them, so each billing line is an explicit override to the seller's
|
|
153
|
+
`out-of-scope` code, and the provider's tax is stored in `providerTaxAmount`
|
|
154
|
+
in the save that issues the invoice.
|