@happyvertical/smrt-commerce 0.56.1 → 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 +159 -93
- package/agents/accounting-snapshot-mapper.md +3 -3
- package/agents/commercial-ui.md +2 -2
- package/agents/contract-money.md +9 -6
- package/agents/cost-billing.md +3 -6
- package/agents/customer-vendor-ui.md +6 -1
- package/agents/document-money.md +59 -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 +12 -8
- 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-DYjhj_pU.js → ContractLineItemCollection-BZcrlrrY.js} +22 -27
- package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
- package/dist/chunks/{CustomerCollection-DQIujn4f.js → CustomerCollection-BIPH2ZUx.js} +3 -3
- package/dist/chunks/{CustomerCollection-DQIujn4f.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
- package/dist/chunks/{DocumentTaxCollection-BEpoqXAQ.js → DocumentTaxCollection-DDzlywMW.js} +41 -14
- package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-CKQflFjc.js → InvoiceCollection-cinzAqt6.js} +53 -164
- package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
- package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
- package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.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-DI3G16BN.js → allocation-integrity-WmWyXhtG.js} +6 -6
- package/dist/chunks/{allocation-integrity-DI3G16BN.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-DNVhDJyC.js → document-money-DV5t4vlK.js} +389 -63
- package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
- package/dist/chunks/{invoice-posting-BwV8g7z0.js → invoice-posting-C2i5dOVZ.js} +21 -23
- 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-LDETvQ1C.js → types-Bbm0Xflc.js} +2 -1
- package/dist/chunks/types-Bbm0Xflc.js.map +1 -0
- 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 +686 -1656
- 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 +1041 -863
- package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
- package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
- package/dist/migrations/partyLinks.d.ts.map +1 -1
- package/dist/models/Contract.d.ts +10 -4
- package/dist/models/Contract.d.ts.map +1 -1
- package/dist/models/ContractLineItem.d.ts +9 -17
- package/dist/models/ContractLineItem.d.ts.map +1 -1
- package/dist/models/DocumentTax.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 +13 -32
- 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 +1 -2
- 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 +2886 -6121
- 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 +11 -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-DYjhj_pU.js.map +0 -1
- package/dist/chunks/DocumentTaxCollection-BEpoqXAQ.js.map +0 -1
- package/dist/chunks/InvoiceCollection-CKQflFjc.js.map +0 -1
- package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js +0 -467
- package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js.map +0 -1
- package/dist/chunks/TaxSettingsCollection-BBTZDH1S.js +0 -319
- package/dist/chunks/TaxSettingsCollection-BBTZDH1S.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-DNVhDJyC.js.map +0 -1
- package/dist/chunks/invoice-posting-BwV8g7z0.js.map +0 -1
- package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
- package/dist/chunks/tenant-money-barrier-DjRXgMnX.js +0 -135
- package/dist/chunks/tenant-money-barrier-DjRXgMnX.js.map +0 -1
- package/dist/chunks/types-LDETvQ1C.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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @happyvertical/smrt-commerce
|
|
2
2
|
|
|
3
|
-
Commerce models for the s-m-r-t framework. Covers customers, vendors, contracts (
|
|
3
|
+
Commerce models for the s-m-r-t framework. Covers customers, vendors, contracts (10 STI types), invoices with ledger integration, payments, and fulfillment tracking.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -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,54 +129,99 @@ 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
|
|
|
159
197
|
### Models
|
|
160
198
|
|
|
199
|
+
#### Supplier quotations
|
|
200
|
+
|
|
201
|
+
`SupplierQuote` extends `PurchaseContract`, requires `vendorId`, and has
|
|
202
|
+
`ContractType.SUPPLIER_QUOTE`. Create it through `PurchaseContractCollection`
|
|
203
|
+
(or `ContractCollection`) with `_meta_type:
|
|
204
|
+
'@happyvertical/smrt-commerce:SupplierQuote'`. It inherits `expiryDate`,
|
|
205
|
+
`reference`, `notes`, and `terms`; it has no customer relationship. Purchase
|
|
206
|
+
branch reads include quotes and sales branch reads exclude them.
|
|
207
|
+
|
|
208
|
+
Keep received quote revisions and shared quote identity in your application.
|
|
209
|
+
Awarding a quote creates a separate `PurchaseOrder`; accepting or saving a
|
|
210
|
+
quote never converts its identity into an order. Quotes use purchase-side
|
|
211
|
+
money calculation and frozen snapshots, but never contribute incurred purchase
|
|
212
|
+
tax to reports, regardless of status. Existing Estimate and PurchaseOrder
|
|
213
|
+
report eligibility is unchanged.
|
|
214
|
+
|
|
161
215
|
#### Upgrading contract parties (#3898)
|
|
162
216
|
|
|
163
217
|
`Contract` no longer declares `customerId` or `vendorId`. Narrow a contract to
|
|
164
218
|
`SalesContract` to read `customerId`, or to `PurchaseContract` to read `vendorId`.
|
|
165
|
-
The
|
|
219
|
+
The existing leaf qualified names and the `contracts` table stay the same; a
|
|
166
220
|
`db:migrate --dry-run` against a v0.55.19 schema reports no DDL changes on
|
|
167
221
|
SQLite or PostgreSQL. Sales leaves no longer accept `vendorId`; purchase leaves
|
|
168
222
|
no longer accept `customerId`. `Order`, `Estimate`, `Agreement`, `Lease`, and
|
|
169
|
-
`WholesaleOrder` require a customer on save. `PurchaseOrder`
|
|
223
|
+
`WholesaleOrder` require a customer on save. `PurchaseOrder` and `SupplierQuote`
|
|
224
|
+
require a vendor.
|
|
170
225
|
`Cart`, `LicenseSale`, and `ProductionOrder` retain optional parties on their side.
|
|
171
226
|
|
|
172
227
|
Before upgrading existing data, run the read-only
|
|
@@ -203,6 +258,7 @@ This preflight never edits rows; an operator chooses the data correction.
|
|
|
203
258
|
| `Order` | Customer purchase order |
|
|
204
259
|
| `Lease` | Rental or lease agreement |
|
|
205
260
|
| `Agreement` | Service or maintenance agreement |
|
|
261
|
+
| `SupplierQuote` | Quotation received from a vendor; distinct from an awarded purchase order |
|
|
206
262
|
| `PurchaseOrder` | Order sent to a vendor/supplier |
|
|
207
263
|
| `ContractLineItem` | Line item within a contract |
|
|
208
264
|
| `Invoice` | Billing document with status lifecycle and ledger integration |
|
|
@@ -230,7 +286,7 @@ replay, parent revision refresh and compatibility requirements.
|
|
|
230
286
|
|
|
231
287
|
| Export | Values |
|
|
232
288
|
|--------|--------|
|
|
233
|
-
| `ContractType` | `estimate`, `order`, `lease`, `agreement`, `purchase_order`, `wholesale_order`, `production_order`, `cart`, `license_sale` |
|
|
289
|
+
| `ContractType` | `estimate`, `order`, `lease`, `agreement`, `supplier_quote`, `purchase_order`, `wholesale_order`, `production_order`, `cart`, `license_sale` |
|
|
234
290
|
| `ContractStatus` | `draft`, `sent`, `accepted`, `declined`, `completed`, `cancelled` |
|
|
235
291
|
| `CustomerStatus` | `active`, `inactive`, `suspended` |
|
|
236
292
|
| `VendorStatus` | `active`, `inactive`, `suspended` |
|
|
@@ -300,6 +356,7 @@ const billing = await BillingRuntime.create({
|
|
|
300
356
|
provider: createStripeBillingProvider({ stripe, webhookSecret }),
|
|
301
357
|
billingRelationships, // smrt-tenancy BillingRelationshipService
|
|
302
358
|
ledger: {
|
|
359
|
+
bookCurrency: 'USD', // ISO 4217; must equal the billing currency
|
|
303
360
|
arAccountId,
|
|
304
361
|
revenueAccountId,
|
|
305
362
|
taxAccountId,
|
|
@@ -447,7 +504,13 @@ const evaluator = await SpendingPolicyEvaluator.create({
|
|
|
447
504
|
upgrade (`<subscriptionId>:<periodStart>`, number 0) count as billed
|
|
448
505
|
coverage, so nothing is billed again after it.
|
|
449
506
|
- **Tax** comes from the provider (Stripe Tax) using the account customer's
|
|
450
|
-
`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.
|
|
451
514
|
- **Events** are verified, stored in smrt-jobs' durable delivery inbox, and
|
|
452
515
|
applied against current provider state: paid invoices record a payment and
|
|
453
516
|
allocation; failures and overdue notices mark the payer `past_due` and its
|
|
@@ -733,14 +796,17 @@ The same models carry `@field({ ui: { widget } })` hints in the manifest:
|
|
|
733
796
|
|
|
734
797
|
## Invoice preparation UI
|
|
735
798
|
|
|
736
|
-
`
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
|
744
810
|
[existing-database storage migration](agents/invoice-quantity.md).
|
|
745
811
|
|
|
746
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,6 +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
|
-
|
|
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
|
|
|
@@ -174,7 +174,12 @@ vitest.selects.config.ts` (also part of `pnpm test:ui`).
|
|
|
174
174
|
Minor-unit amounts on Contract, Invoice, their line items, Payment,
|
|
175
175
|
PaymentAllocation, `Customer.creditLimit` and `Vendor.minimumOrderAmount`
|
|
176
176
|
carry `ui: { format: 'money' }` (#3718; `widget: 'currency'` stays the
|
|
177
|
-
currency-code picker on the text `currency` field).
|
|
177
|
+
currency-code picker on the text `currency` field). That includes the
|
|
178
|
+
money-v2 fields (#4011): line `taxAmount`, `discountTotal` on Invoice and
|
|
179
|
+
Contract, `Invoice.creditedAmount`, and `DocumentTax` `taxableMinor` /
|
|
180
|
+
`taxMinor` / `recoverableMinor`; the test asserts every integer named
|
|
181
|
+
`*Amount`, `*Total`, `*Minor` or `subtotal` on those models (and Contract's
|
|
182
|
+
STI subtypes) is hinted. Payout and Payment's
|
|
178
183
|
native/USD drift amounts are not hinted: their currency is a payout rail or
|
|
179
184
|
fixed USD, not the record's `currency`. The line-item FKs
|
|
180
185
|
(`ContractLineItem.contractId`, `InvoiceLineItem.invoiceId`,
|