@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.
Files changed (186) hide show
  1. package/AGENTS.md +24 -11
  2. package/README.md +159 -93
  3. package/agents/accounting-snapshot-mapper.md +3 -3
  4. package/agents/commercial-ui.md +2 -2
  5. package/agents/contract-money.md +9 -6
  6. package/agents/cost-billing.md +3 -6
  7. package/agents/customer-vendor-ui.md +6 -1
  8. package/agents/document-money.md +59 -31
  9. package/agents/invoice-calculations.md +31 -56
  10. package/agents/invoice-preparation.md +62 -73
  11. package/agents/invoice-quantity.md +9 -21
  12. package/agents/minor-unit-migration.md +4 -4
  13. package/agents/money-calculator.md +7 -8
  14. package/agents/money-model.md +18 -22
  15. package/agents/money-upgrade.md +92 -0
  16. package/agents/revenue-journal.md +2 -2
  17. package/agents/tax-codes.md +17 -14
  18. package/agents/tax-summary.md +12 -8
  19. package/dist/billing/period-close.d.ts.map +1 -1
  20. package/dist/billing/runtime.d.ts +5 -2
  21. package/dist/billing/runtime.d.ts.map +1 -1
  22. package/dist/chunks/{ContractLineItemCollection-DYjhj_pU.js → ContractLineItemCollection-BZcrlrrY.js} +22 -27
  23. package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
  24. package/dist/chunks/{CustomerCollection-DQIujn4f.js → CustomerCollection-BIPH2ZUx.js} +3 -3
  25. package/dist/chunks/{CustomerCollection-DQIujn4f.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
  26. package/dist/chunks/{DocumentTaxCollection-BEpoqXAQ.js → DocumentTaxCollection-DDzlywMW.js} +41 -14
  27. package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
  28. package/dist/chunks/{InvoiceCollection-CKQflFjc.js → InvoiceCollection-cinzAqt6.js} +53 -164
  29. package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
  30. package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
  31. package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
  32. package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
  33. package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.js.map → TaxComponentCollection-C4zxnyVu.js.map} +1 -1
  34. package/dist/chunks/VendorCollection-D6ZcUYy8.js +100 -0
  35. package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +1 -0
  36. package/dist/chunks/{allocation-integrity-DI3G16BN.js → allocation-integrity-WmWyXhtG.js} +6 -6
  37. package/dist/chunks/{allocation-integrity-DI3G16BN.js.map → allocation-integrity-WmWyXhtG.js.map} +1 -1
  38. package/dist/chunks/calculations-C9daQLsh.js +82 -0
  39. package/dist/chunks/calculations-C9daQLsh.js.map +1 -0
  40. package/dist/chunks/contract-money-EuToOghh.js +301 -0
  41. package/dist/chunks/contract-money-EuToOghh.js.map +1 -0
  42. package/dist/chunks/detect-CY9ufaFb.js +328 -0
  43. package/dist/chunks/detect-CY9ufaFb.js.map +1 -0
  44. package/dist/chunks/{document-money-DNVhDJyC.js → document-money-DV5t4vlK.js} +389 -63
  45. package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
  46. package/dist/chunks/{invoice-posting-BwV8g7z0.js → invoice-posting-C2i5dOVZ.js} +21 -23
  47. package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +1 -0
  48. package/dist/chunks/{revenue-journal-DY9fro8E.js → revenue-journal-LLA9Kh_i.js} +3 -14
  49. package/dist/chunks/revenue-journal-LLA9Kh_i.js.map +1 -0
  50. package/dist/chunks/tenant-money-barrier-6TlqUeVj.js +255 -0
  51. package/dist/chunks/tenant-money-barrier-6TlqUeVj.js.map +1 -0
  52. package/dist/chunks/{types-LDETvQ1C.js → types-Bbm0Xflc.js} +2 -1
  53. package/dist/chunks/types-Bbm0Xflc.js.map +1 -0
  54. package/dist/chunks/{units-CNcjH_eE.js → units-8ElbjVZK.js} +18 -3
  55. package/dist/chunks/units-8ElbjVZK.js.map +1 -0
  56. package/dist/collections/ContractCollection.d.ts.map +1 -1
  57. package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
  58. package/dist/collections/InvoiceCollection.d.ts.map +1 -1
  59. package/dist/collections/InvoiceLineItemCollection.d.ts +0 -30
  60. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
  61. package/dist/collections/TaxCodeCollection.d.ts +5 -1
  62. package/dist/collections/TaxCodeCollection.d.ts.map +1 -1
  63. package/dist/collections/TaxSettingsCollection.d.ts +15 -8
  64. package/dist/collections/TaxSettingsCollection.d.ts.map +1 -1
  65. package/dist/collections/TaxSummaryReportCollection.d.ts +3 -3
  66. package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -1
  67. package/dist/collections/index.d.ts +0 -1
  68. package/dist/collections/index.d.ts.map +1 -1
  69. package/dist/commerce-money-tools.d.ts.map +1 -1
  70. package/dist/contract-money.d.ts +1 -13
  71. package/dist/contract-money.d.ts.map +1 -1
  72. package/dist/document-money-internal.d.ts +7 -3
  73. package/dist/document-money-internal.d.ts.map +1 -1
  74. package/dist/document-money.d.ts +1 -11
  75. package/dist/document-money.d.ts.map +1 -1
  76. package/dist/index.d.ts +6 -5
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +686 -1656
  79. package/dist/index.js.map +1 -1
  80. package/dist/invoice-accounting-sync.d.ts.map +1 -1
  81. package/dist/invoice-mcp.d.ts +3 -3
  82. package/dist/invoice-mcp.d.ts.map +1 -1
  83. package/dist/invoice-posting.d.ts +11 -5
  84. package/dist/invoice-posting.d.ts.map +1 -1
  85. package/dist/invoice-record-screen.d.ts +1 -1
  86. package/dist/invoice-record-screen.d.ts.map +1 -1
  87. package/dist/invoices.d.ts +6 -1
  88. package/dist/invoices.d.ts.map +1 -1
  89. package/dist/invoices.js +2 -2
  90. package/dist/manifest.json +1041 -863
  91. package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
  92. package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
  93. package/dist/migrations/partyLinks.d.ts.map +1 -1
  94. package/dist/models/Contract.d.ts +10 -4
  95. package/dist/models/Contract.d.ts.map +1 -1
  96. package/dist/models/ContractLineItem.d.ts +9 -17
  97. package/dist/models/ContractLineItem.d.ts.map +1 -1
  98. package/dist/models/DocumentTax.d.ts.map +1 -1
  99. package/dist/models/Invoice.d.ts +18 -48
  100. package/dist/models/Invoice.d.ts.map +1 -1
  101. package/dist/models/InvoiceLineItem.d.ts +13 -32
  102. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  103. package/dist/models/TaxSettings.d.ts +0 -10
  104. package/dist/models/TaxSettings.d.ts.map +1 -1
  105. package/dist/models/index.d.ts +1 -2
  106. package/dist/models/index.d.ts.map +1 -1
  107. package/dist/playground.js +1 -1
  108. package/dist/playground.js.map +1 -1
  109. package/dist/recipes.d.ts +8 -11
  110. package/dist/recipes.d.ts.map +1 -1
  111. package/dist/revenue-journal.d.ts +0 -8
  112. package/dist/revenue-journal.d.ts.map +1 -1
  113. package/dist/smrt-knowledge.json +2886 -6121
  114. package/dist/svelte/components/InvoiceEditor.svelte +7 -148
  115. package/dist/svelte/components/InvoiceEditor.svelte.d.ts +4 -1
  116. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
  117. package/dist/svelte/components/InvoiceTotals.svelte +14 -118
  118. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -27
  119. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
  120. package/dist/svelte/components/MoneyInvoiceEditor.svelte +3 -23
  121. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +3 -26
  122. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -1
  123. package/dist/svelte/index.d.ts +2 -3
  124. package/dist/svelte/index.d.ts.map +1 -1
  125. package/dist/svelte/index.js +1 -2
  126. package/dist/svelte/invoices/calculations.d.ts +3 -108
  127. package/dist/svelte/invoices/calculations.d.ts.map +1 -1
  128. package/dist/svelte/invoices/calculations.js +12 -309
  129. package/dist/svelte/invoices/messages.d.ts +1 -37
  130. package/dist/svelte/invoices/messages.d.ts.map +1 -1
  131. package/dist/svelte/invoices/messages.js +1 -37
  132. package/dist/svelte/invoices/money-editor.d.ts +1 -1
  133. package/dist/svelte/invoices/money-editor.d.ts.map +1 -1
  134. package/dist/svelte/invoices/types.d.ts +14 -114
  135. package/dist/svelte/invoices/types.d.ts.map +1 -1
  136. package/dist/svelte/playground/InvoicePreparationPreview.svelte +85 -94
  137. package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -1
  138. package/dist/svelte/playground.js +1 -1
  139. package/dist/tax/index.d.ts +2 -1
  140. package/dist/tax/index.d.ts.map +1 -1
  141. package/dist/tax/standard-codes.d.ts +12 -0
  142. package/dist/tax/standard-codes.d.ts.map +1 -0
  143. package/dist/tax/tenant-bootstrap.d.ts +29 -0
  144. package/dist/tax/tenant-bootstrap.d.ts.map +1 -0
  145. package/dist/tenant-money-barrier.d.ts +12 -44
  146. package/dist/tenant-money-barrier.d.ts.map +1 -1
  147. package/dist/types/index.d.ts +11 -23
  148. package/dist/types/index.d.ts.map +1 -1
  149. package/dist/ui.js +1 -1
  150. package/dist/ui.js.map +1 -1
  151. package/dist/upgrade/detect.d.ts +129 -0
  152. package/dist/upgrade/detect.d.ts.map +1 -0
  153. package/dist/upgrade/money-v2.d.ts +81 -0
  154. package/dist/upgrade/money-v2.d.ts.map +1 -0
  155. package/package.json +14 -13
  156. package/agents/line-amount-migration.md +0 -50
  157. package/dist/chunks/ContractLineItemCollection-DYjhj_pU.js.map +0 -1
  158. package/dist/chunks/DocumentTaxCollection-BEpoqXAQ.js.map +0 -1
  159. package/dist/chunks/InvoiceCollection-CKQflFjc.js.map +0 -1
  160. package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js +0 -467
  161. package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js.map +0 -1
  162. package/dist/chunks/TaxSettingsCollection-BBTZDH1S.js +0 -319
  163. package/dist/chunks/TaxSettingsCollection-BBTZDH1S.js.map +0 -1
  164. package/dist/chunks/calculations-DaHR17il.js +0 -333
  165. package/dist/chunks/calculations-DaHR17il.js.map +0 -1
  166. package/dist/chunks/document-money-DNVhDJyC.js.map +0 -1
  167. package/dist/chunks/invoice-posting-BwV8g7z0.js.map +0 -1
  168. package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
  169. package/dist/chunks/tenant-money-barrier-DjRXgMnX.js +0 -135
  170. package/dist/chunks/tenant-money-barrier-DjRXgMnX.js.map +0 -1
  171. package/dist/chunks/types-LDETvQ1C.js.map +0 -1
  172. package/dist/chunks/units-CNcjH_eE.js.map +0 -1
  173. package/dist/collections/MoneyMigrationDocumentCollection.d.ts +0 -12
  174. package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +0 -1
  175. package/dist/migrations/commerceLineAmounts.d.ts +0 -84
  176. package/dist/migrations/commerceLineAmounts.d.ts.map +0 -1
  177. package/dist/models/MoneyMigrationDocument.d.ts +0 -28
  178. package/dist/models/MoneyMigrationDocument.d.ts.map +0 -1
  179. package/dist/svelte/components/InvoiceLineEditor.svelte +0 -92
  180. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +0 -8
  181. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +0 -1
  182. package/dist/svelte/invoices/fields.d.ts +0 -6
  183. package/dist/svelte/invoices/fields.d.ts.map +0 -1
  184. package/dist/svelte/invoices/fields.js +0 -22
  185. package/dist/tax/legacy.d.ts +0 -48
  186. package/dist/tax/legacy.d.ts.map +0 -1
@@ -1,27 +1,51 @@
1
1
  # Document money persistence
2
2
 
3
- Tenant money behavior is selected by the server-owned `TaxSettings.moneyModel`
4
- marker. Missing settings and `legacy` preserve the existing line-save behavior.
5
- `migrating` is a persisted maintenance guard: invoice money writes fail until
6
- the owning migration restores `legacy` or activates `v2`. Generated surfaces
7
- cannot write either the marker or its owner token.
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 acquires the same tenant settings row before document
11
- rows. The barrier is held through the owning transaction, so migration cannot
12
- start between the marker check and a money read/write. Nested coordinators
13
- reuse an explicitly registered transaction executor; adapter shape is never
14
- used as transaction proof. When the settings table exists, the first legacy
15
- runtime boundary materializes the deterministic legacy row solely as this
16
- durable serialization point. Older schemas without the table remain legacy
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
- atomically. Tax resolution order is line override, product, customer, then app
23
- default. Product reads use the products package's collection on that same
24
- transaction executor; tenant-owned and catalog-global products are accepted.
44
+ atomically. Tax resolution order is line override, customer exemption (unless
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.
25
49
 
26
50
  The first transition away from `DRAFT`, or setting `arJournalId`, freezes the
27
51
  money document. The invoice and every line receive the same stable
@@ -68,18 +92,17 @@ draft overlay through the owning resolver/calculator without writes and rejects
68
92
  frozen documents. A supplied line id must name a stored line exactly once; omit
69
93
  the id for a new preview-only line. `getInvoiceMoneyFacts({ db, tenantId,
70
94
  invoiceId })` reads the authoritative stored header, lines, `moneySnapshotId`,
71
- and `DocumentTax` rows without recalculation. Both facades require an active
72
- `v2` tenant and fail closed while migration is in progress, so legacy or
73
- 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.
74
97
 
75
98
  ### Record-screen composition
76
99
 
77
100
  `loadInvoiceRecordScreenData({ db, tenantId, invoiceId })` is the commerce
78
101
  server adapter for a generic invoice record page. A caller must already have
79
102
  that exact active tenant; the adapter never switches principals based on its
80
- argument. It holds the shared v2 money read barrier while it reads the stored
81
- header, lines, `DocumentTax`, and related credit-note summaries, so a migration
82
- cannot mix a stale header with a different snapshot of those rows. Its totals
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
83
106
  are a direct projection of persisted minor-unit values and component snapshots;
84
107
  it does not resolve a code or recalculate tax.
85
108
 
@@ -90,9 +113,10 @@ may expose `onCreateCreditNote` only with a server-derived
90
113
  `canCreateCreditNote` authorization result. The wrapper hides the action for
91
114
  credit-note records and never treats a client callback as authorization.
92
115
 
93
- `InvoiceLineItem.amount` is tax-exclusive net money in `v2`.
94
- `taxAmount` is separate, while `legacyAmount` is reserved for rollback during
95
- the compatibility migration. `taxCodeId` and component identity may be null for
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
96
120
  a genuine unassigned zero-tax line. Optional `productId` and
97
121
  `revenueAccountId` are nullable UUID references so PostgreSQL never receives an
98
122
  empty-string sentinel.
@@ -102,7 +126,7 @@ labels, percentage, liability account, taxable minor units, and tax minor
102
126
  units. Provider tax remains the invoice header's `providerTaxAmount`; commerce
103
127
  does not invent a tax code or component for provider-owned tax.
104
128
 
105
- Revenue recognition for `v2` reads only the frozen invoice, lines, and
129
+ Revenue recognition reads only the frozen invoice, lines, and
106
130
  `DocumentTax` rows. It groups each line's stored net by its snapshotted revenue
107
131
  account (falling back to the caller's revenue account), groups component tax by
108
132
  the snapshotted liability account (falling back to the caller's tax account),
@@ -117,10 +141,14 @@ the invoice parent lock and transaction, so retries converge. Account IDs from
117
141
  frozen snapshots are still authorization checked: every used account must be
118
142
  active and owned by the invoice tenant.
119
143
 
120
- A posted `v2` invoice cannot move directly to `CANCELLED`. Call
144
+ A posted invoice cannot move directly to `CANCELLED`. Call
121
145
  `cancelWithRevenueReversal({ bookCurrency })`; it reads the original posted
122
146
  journal, posts one deterministic journal with every debit and credit reversed,
123
147
  and stores `arReversalJournalId` with the status transition atomically. The
124
148
  original journal remains posted. Failed link or reversal writes roll back the
125
- entire operation, and retry returns the same reversal. Legacy tenants keep the
126
- existing recognition and cancellation behavior.
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.
@@ -1,56 +1,31 @@
1
- # General invoice calculations
2
-
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
-
5
- This guide describes the standalone, legacy-compatible invoice editor helpers.
6
- For tenant `moneyModel: 'v2'` document arithmetic, persisted tax components, and
7
- issued snapshots, use the [commercial money model](money-model.md) and [money
8
- calculator](money-calculator.md) instead.
9
-
10
- 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.
11
-
12
- 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.
13
-
14
- `resolveInvoiceLineDraft` returns the existing model's resolved integer-minor price, flat discount, fractional quantity, effective fractional tax rate, and calculated amount. It verifies exact agreement with model arithmetic. `InvoiceLineItem.applyEditorDraft()` retains the editing modes alongside resolved fields; `getEditorDraft(authorizedContext)` restores them only while current authority and fields agree. See [invoice quantity storage](invoice-quantity.md) for the required existing-database migration. `calculateInvoiceMinorLine` is the currency-independent model helper: it permits existing negative-price credit lines, retains their historical ties toward positive infinity, and rejects invalid quantities/rates or unsafe/fractional money.
15
-
16
- Trailing zeroes do not make an exact amount invalid: `100.00` JPY is 100 minor units, while `100.01` JPY is rejected. Parsing never rewrites the draft text.
17
-
18
- ## Server responsibilities
19
-
20
- 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. For these legacy-compatible helpers, default tax remains externally determined; this module is not a tax determination service. V2 tax-code resolution and stored tax facts are defined by the [commercial money model](money-model.md). Caller-owned extension allocations, budget availability and approval evidence require their own server validation and are not part of line pricing.
21
-
22
- 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.
23
-
24
-
25
- ## Explicit tax rounding scope
26
-
27
- The `roundingScope: 'invoice'` option below is the standalone editor's
28
- legacy-compatible aggregate calculation. It is distinct from v2's
29
- `roundingScope: 'document'`, which allocates rounded tax into persisted
30
- document tax facts; see the [money calculator](money-calculator.md).
31
-
32
- `calculateInvoiceDraft(values, { roundingScope: 'invoice' })` and the shared
33
- `InvoiceEditor` / `InvoiceLineEditor` `roundingScope` prop accept `line` or
34
- `invoice`. Omission preserves per-line rounding. The server chooses this option
35
- from its authorized policy, and supplies it again when rendering rejected forms
36
- or reopening retained drafts; it is not a client authority field.
37
-
38
- Invoice scope groups numerically equal effective percentages, including textual
39
- variants such as `5` and `5.000000`, and rounds each rate group once through
40
- `calculateCostBillingTax`. Different rates remain independent components; explicit
41
- zero overrides remain exempt. Quantity and discount rounding are unchanged. Two
42
- 10-minor lines at 5% therefore have tax 1 in invoice scope and 2 in line scope.
43
- This is an additive single-rate-per-line editor, not a tax determination or
44
- compound-tax solver. Invalid scope is a validation error. Drafts still reject
45
- negative quantities/prices; the signed model calculator is unchanged.
46
-
47
- `lineResults` and `resolveInvoiceLineDraft` remain standalone per-line
48
- calculations, useful for validation and existing line-mode model storage. They
49
- are **not allocations** of invoice-rounded tax: their tax/total fields may not
50
- sum to the aggregate. In invoice mode the UI displays each line's subtotal and
51
- an explanation, with tax and total shown only at invoice level. Persist the
52
- aggregate with the authorized scope and retained draft when using invoice
53
- rounding; do not reconstruct it by summing standalone resolved line totals.
54
- No persistence schema or `InvoiceLineItem` tax behavior is changed by this
55
- legacy-compatible adapter API. V2 persistence and issued tax snapshots are
56
- defined by the [commercial money model](money-model.md).
1
+ # Invoice line calculation helper
2
+
3
+ `@happyvertical/smrt-commerce/invoices` is a Svelte-free entry for server
4
+ handlers and browsers. It exports `calculateInvoiceMinorLine`,
5
+ `MoneyValidationError` and the `MoneyValidationCode` type.
6
+
7
+ Document arithmetic, tax-code resolution, persisted tax components and issued
8
+ snapshots belong to the [commercial money model](money-model.md) and the
9
+ [money calculator](money-calculator.md). The invoice editor previews with
10
+ `calculateMoneyEditorDraft`; see [the invoice editor](invoice-preparation.md).
11
+
12
+ `calculateInvoiceMinorLine({ quantity, unitPrice, discount, taxRate })`
13
+ calculates one stored line in integer minor units, independent of currency. It
14
+ returns `grossMinor`, `discountMinor`, `subtotalMinor`, `taxMinor` and
15
+ `totalMinor`, and it runs the same money calculator the documents use:
16
+
17
+ - `quantity` is nonnegative, with at most six fractional digits;
18
+ - `unitPrice` is a signed safe integer, so credit lines may be negative;
19
+ - `discount` is a flat signed safe integer that carries the gross sign;
20
+ - `taxRate` is an already-resolved fraction in [0, 1], with at most eight
21
+ fractional digits. It is an input to this helper only; it never determines
22
+ tax on a stored invoice line, whose tax comes from its tax codes.
23
+
24
+ Rounding follows the money calculator, so a negative credit line rounds
25
+ symmetrically with its positive counterpart. Numeric quantities and rates use
26
+ their canonical decimal form, including exponent notation, and never a
27
+ `toFixed` expansion. Invalid quantities or rates, fractional or unsafe money,
28
+ and a discount with the wrong sign throw `RangeError` before persistence.
29
+
30
+ The [invoice quantity migration](invoice-quantity.md) uses this helper to
31
+ check that every stored quantity round-trips exactly.
@@ -1,86 +1,75 @@
1
- # General invoice editor and allocation extensions
1
+ # Invoice editor and allocation fields
2
2
 
3
- Import `InvoiceEditor`, `InvoiceLineEditor`, `InvoiceAllocationFields`, `InvoiceReview`
4
- and their Props types from `@happyvertical/smrt-commerce/svelte`. `InvoiceEditor`
5
- is the general invoice form; `InvoiceLineEditor` and `InvoiceAllocationFields`
6
- render fields only and never nest a form. The interactive Commerce invoice preview
7
- shows general line items with an optional allocation extension.
3
+ Import `InvoiceEditor`, `InvoiceAllocationFields`, `InvoiceReview` and their
4
+ Props types from `@happyvertical/smrt-commerce/svelte`. `InvoiceEditor` is the
5
+ invoice form. It renders `MoneyInvoiceEditor` (also exported) and uses the
6
+ commercial money model, the only invoice money model. Line tax
7
+ comes from tax codes, never from a typed rate. See the
8
+ [commercial money model](money-model.md) and the
9
+ [money calculator](money-calculator.md).
8
10
 
9
- `InvoiceDraftValues` contains customerId, issuedOn, dueOn, currency, paymentTerms,
10
- taxRate and ordered lines. Each `InvoiceLineDraft` contains caller key, description,
11
- sku, fractional quantity, unitPrice, discountType, discountValue, taxMode and
12
- taxRate. Editable values are lossless strings, including invalid submitted input.
13
- Unit price and flat discount are currency-unit text; percentage values use `5`
14
- for five percent. `discountType` is `flat` or `percent`; `taxMode` is `inherit` or
15
- `override`. Explicit override `0` means zero tax; it never means inherit. Unknown
16
- modes remain representable for rejected submissions. Dates remain text so invalid
17
- values survive action failures. Customer options are caller-authorized projections;
18
- unknown retained IDs remain visible. Currency uses the shared CurrencySelect.
11
+ `InvoiceEditorProps` requires `action` and `money: MoneyEditorProps`. Optional
12
+ props are `method` (default `post`), `canEdit`, `pending`, `hiddenFields`,
13
+ `intentField` (default `intent`), `saveIntent` (default `save`), `saveLabel`
14
+ and `onsubmit`. `money` carries:
19
15
 
20
- See [server calculation and persistence semantics](invoice-calculations.md).
16
+ - `mode: 'v2'` (a fixed discriminator);
17
+ - `draft`: currency, `lineAmountType` (`exclusive`, `inclusive` or `none`),
18
+ `roundingScope` (`line` or `document`) and ordered `lines`;
19
+ - `taxCodes`: the authorized tax-code choices, each with its components;
20
+ - optionally `frozen`, `status`, `kind` (`invoice` or `credit_note`), `stored`,
21
+ `onCreateCreditNote`, `customerTaxExempt`, `sellerTaxRegistrations`,
22
+ `partyTaxId`, `showNetSubtotal` and `names`.
21
23
 
22
- `InvoiceLineEditor` also recalculates when used independently: it retains a local
23
- draft copy and calls optional `onchange(line)` with each edit. It never mutates
24
- the supplied line. Replacing the line prop applies caller/server response values.
25
-
26
- The UI invokes the exact, Svelte-free `calculateInvoiceDraft` and
27
- `calculateInvoiceLine` helpers for previews. The shared public calculation entry
28
- is `@happyvertical/smrt-commerce/invoices`. Valid totals use safe integer minor
29
- units, with exact decimal arithmetic and explicit currency precision. Invalid
30
- drafts show an unavailable-total message rather than plausible numbers. The
31
- calculator uses Commerce's explicit exponent table and its existing two-digit
32
- fallback for other uppercase three-letter codes; the server must authorize its
33
- supported currencies. Calculated values are never submitted as authority.
34
- The server revalidates customer/SKU, prices, quantity, discounts, tax policy,
35
- permissions and calculated totals. Header taxRate is a draft preview default,
36
- not a new persisted Invoice tax field; replace it with authorized tax context
37
- on the server. Client edits never establish approval or allocate inventory.
24
+ Each `MoneyEditorLineDraft` holds id, description, quantity, unitPrice,
25
+ `discountType` (`none`, `percent` or `amount`), discountValue (a nonnegative
26
+ magnitude; the calculator applies the line sign), `taxMode` (`inherit` or
27
+ `override`), `taxCodeId`, and the resolver's `inheritedTaxCodeId` and
28
+ `inheritedTaxCodeSource` (`product`, `customer`, `app`, `exempt-customer` or
29
+ `none`). Values are lossless strings, so input the server rejected comes back
30
+ unchanged. Choosing a tax code sets `taxMode` to `override`. Clearing it goes
31
+ back to the inherited code, and the editor shows that code's source.
38
32
 
39
33
  ```svelte
40
- <InvoiceEditor action="?/save" values={form?.values ?? data.values}
41
- customers={data.authorizedCustomers} errors={form?.errors}
42
- message={form?.message} canEdit={data.mayEdit}
43
- hiddenFields={[{ name: 'requestId', value: form?.requestId ?? data.requestId }]}>
44
- {#snippet children(current)}
45
- <InvoiceAllocationFields currency={current.currency}
46
- allocations={form?.allocations ?? data.allocations}
47
- sources={data.authorizedSources} mayReadSources={data.mayReadSources}
48
- canEdit={data.mayEdit} />
49
- {/snippet}
50
- </InvoiceEditor>
34
+ <InvoiceEditor action="?/save" money={form?.money ?? data.money}
35
+ canEdit={data.mayEdit}
36
+ hiddenFields={[{ name: 'requestId', value: form?.requestId ?? data.requestId }]} />
51
37
  ```
52
38
 
53
- Native header names default to customerId, issuedOn, dueOn, currency,
54
- paymentTerms, invoiceTaxRate. Repeated line names are lineKey, lineDescription,
55
- lineSku, lineQuantity, lineUnitPrice, lineDiscountType, lineDiscountValue,
56
- lineTaxMode and lineTaxRate, in displayed order. `fields` and `lineFields` adapt
57
- names. Caller `errors` use header names or `lineKey.fieldName` keys. Repeated
58
- hidden fields retain exact values. Avoid collisions with the component's names.
39
+ Native field names default to `lineAmountType`, `roundingScope`, then repeated
40
+ `lineId`, `lineDescription`, `lineQuantity`, `lineUnitPrice`,
41
+ `lineDiscountType`, `lineDiscountValue`, `lineTaxMode` and `lineTaxCodeId`, in
42
+ displayed order. Use `money.names` to rename them. `moneyEditorFieldNames`
43
+ exports the defaults, and `moneyEditorSubmission(draft)` returns the typed
44
+ shape those controls represent. Hidden fields are submitted exactly as given,
45
+ including repeated names. The default transport is a native POST, which also
46
+ works without JavaScript. `onsubmit` can enhance it.
47
+
48
+ `calculateMoneyEditorDraft(draft, taxCodes)` is the editor's only calculation:
49
+ the pure money calculator, run on the retained text. The preview gives no
50
+ authority. The server resolves tax codes (line override, else product, else
51
+ customer, else the tenant default), recomputes `taxAmount` and the totals, and
52
+ ignores any client total. Invalid input shows the calculator's message instead
53
+ of a total.
59
54
 
60
- Default submitter `intent` values are `save`, `addLine` and `removeLine:<key>`;
61
- `saveIntent`, `addIntent`, `removePrefix` and `fields.intent` adapt the contract.
62
- Save is first so implicit Enter cannot remove/add a row. Structural actions
63
- use formnovalidate, preserving every submitted draft string and request token.
64
- The caller returns updated rows for add/remove; it must not persist or rotate
65
- request identity simply because the user added a line. The default transport
66
- is native POST, including without JavaScript. With JavaScript, input changes
67
- update exact previews without mutating the supplied values object. `onsubmit`
68
- may enhance transport; caller-owned retryStatus/pending controls submission.
55
+ A frozen document (`frozen: true`) renders only `stored`, the server's issued
56
+ snapshot. It never recalculates and disables every field. If `stored` is
57
+ missing, the editor reports that. A frozen invoice shows a credit-note action,
58
+ enabled only when the host supplies `onCreateCreditNote`. A credit note never
59
+ offers one.
69
60
 
70
- Allocation fields remain optional application-specific composition. Each row
71
- has key, sourceId, currency-unit amount, optional remove and field errors.
72
- `mayReadSources=false` hides supplied labels/balances and renders references.
73
- Retained unavailable sources remain postable for server reconciliation. Native
74
- names remain sourceId/allocationAmount and checked zero-based removeRow;
75
- `fields` maps these, and addIntent defaults to addAllocation. Availability,
76
- reservations, budget policy and approval are caller/server concerns. The
77
- extension must receive the same canEdit/pending capability as its owner.
61
+ `InvoiceAllocationFields` renders source-allocation rows without a form of its
62
+ own; put it in a caller-owned native form. Each row has key, sourceId, a
63
+ currency-unit amount, optional remove, and field errors.
64
+ `mayReadSources=false` hides supplied labels and balances and renders
65
+ references instead. Unavailable sources that were retained stay postable, so
66
+ the server can reconcile them. Native names stay sourceId, allocationAmount
67
+ and the checked zero-based removeRow. `fields` renames them, and `addIntent`
68
+ defaults to `addAllocation`. Availability, reservations, budget policy and
69
+ approval belong to the caller and server.
78
70
 
79
- `review` is a caller-supplied snapshot; edits do not infer status changes.
80
- Read-only disables fields and hides mutation buttons. On rejected, denied or
81
- uncertain actions, return every original string, stable row key, errors and
82
- caller request/tenant/fingerprint identity. No component generates request keys.
71
+ `InvoiceReview` presents a review snapshot that the caller supplies. Edits
72
+ never infer a status change. No component generates request keys.
83
73
 
84
74
  Focused validation: Commerce `vitest.invoices.config.ts`,
85
- `e2e/invoices/playwright.config.ts`, `typecheck`, and `build`; consolidated
86
- release gates and calculator/model checks remain separate owning validations.
75
+ `e2e/invoices/playwright.config.ts`, `typecheck` and `build`.
@@ -1,22 +1,11 @@
1
- # Invoice quantity persistence and editor recovery
1
+ # Invoice quantity persistence
2
2
 
3
3
  InvoiceLineItem accepts nonnegative decimal quantities with at most six decimal
4
4
  places. The `1.0` default declares a floating-point quantity column. Unit price,
5
- flat discount, and calculated amount remain safe integer minor units. Save always
6
- validates resolved fields and recalculates amount; a supplied amount is never
7
- trusted. Gross quantity × price and then tax on discounted gross round to minor
8
- units, with ties toward positive infinity (including existing negative-price
9
- credit lines). Tax is a resolved fraction in [0, 1], with eight decimal places.
10
- Accounting export retains an explicit zero rate instead of omitting it.
11
-
12
- `applyEditorDraft(draft, authorizedContext)` resolves the shared invoice draft
13
- helper into description/SKU/quantity/price/flat discount/tax and stores cloned
14
- `invoiceEditorStateJson` TEXT, accessed through a guarded typed `invoiceEditorState` getter/setter. It does not save.
15
- `getEditorDraft(authorizedContext)` returns a clone only when currency, inherited
16
- tax authority, and all source fields still agree. Otherwise it returns null so the
17
- caller can reconstruct a flat-discount/explicit-tax editor from resolved fields.
18
- Normal save clears stale editor state and never derives money from metadata.
19
- All other object metadata, authorization, and transaction ownership are unchanged.
5
+ discount and amount stay safe integer minor units. Every save goes through the
6
+ invoice money aggregate, which recomputes the line and ignores
7
+ any supplied amount. Tax comes from the line's resolved tax code, never from a
8
+ stored rate. See the [commercial money model](money-model.md).
20
9
 
21
10
  ## Existing integer columns
22
11
 
@@ -34,11 +23,10 @@ SQLite uses the public core table-rebuild planner to change the declaration to
34
23
  REAL while preserving existing columns, constraints, indexes, and triggers. If
35
24
  incoming foreign keys make that rebuild unsafe, the migration throws with the
36
25
  planner reason and rolls back; it does not disable constraints or lose children.
37
- Resolve that maintenance condition before deploying fractional writers. The same
38
- transaction adds the nullable TEXT `invoice_editor_state_json` column with an empty
39
- string default on every engine. This column is required for updated model saves.
40
- An existing floating quantity column still receives the missing state column.
41
- No schema mutation runs implicitly during runtime initialization or model save.
26
+ Resolve that maintenance condition before deploying fractional writers. The
27
+ migration no longer adds an editor-state column. An `invoice_editor_state_json`
28
+ column that an earlier version added is left in place and unused. No schema
29
+ mutation runs implicitly during runtime initialization or model save.
42
30
 
43
31
  The actual DuckDB quantity/storage migration is tested. Complete Commerce model
44
32
  persistence on DuckDB is currently unsupported because its hard foreign keys use
@@ -1,8 +1,8 @@
1
- # Legacy major-unit to minor-unit migration
1
+ # Major-unit to minor-unit migration
2
2
 
3
- This guide covers the existing numeric-column rescale only. It is not the
4
- Invoice-v2 tenant money-model migration: it does not change `TaxSettings`,
5
- calculate tax, create snapshots, or activate v2.
3
+ This guide covers the numeric-column rescale only. It is not the
4
+ [money data upgrade](money-upgrade.md): it does not change `TaxSettings`,
5
+ calculate tax, or create snapshots.
6
6
 
7
7
  `preflightCommerceMoneyMinorUnits(db)` reports whether known commerce columns
8
8
  still contain major units, rows that would round, and values outside JavaScript
@@ -35,11 +35,10 @@ codes include `TAX_CODE_DUPLICATE_COMPONENT`,
35
35
  shared input, discount, percent, compound, state, scope, safety, freeze, and
36
36
  currency codes exported as `MoneyValidationCode`.
37
37
 
38
- The existing invoice functions in `src/svelte/invoices/calculations.ts` remain
39
- compatibility adapters for the legacy single-component exclusive model until the
40
- tenant migration gate enables persisted money-v2 fields. In particular,
41
- `calculateInvoiceMinorLine(input)` defaults to the legacy negative-tie rule for
42
- existing model saves. Migration-aware callers explicitly pass
43
- `{ moneyModel: 'v2' }`; there is no process-global mode. V2 callers pass the
44
- stored signed discount through unchanged; editor magnitude normalization happens
45
- before constructing that stored input.
38
+ `calculateInvoiceMinorLine(input)` in `src/svelte/invoices/calculations.ts`
39
+ (also exported from `@happyvertical/smrt-commerce/invoices`) runs one line
40
+ through this calculator with a single-component code built from its resolved
41
+ fraction. It takes no options. Its discount is signed minor units and passes
42
+ through unchanged, and its ties round half away from zero for negative
43
+ lines too. Editor magnitude normalization happens before constructing that
44
+ input. See [invoice line calculation](invoice-calculations.md).
@@ -5,9 +5,9 @@ shapes, validation codes, and worked calculator examples live in the
5
5
  [calculator guide](money-calculator.md) and
6
6
  [package documentation](../../../docs/content/commerce-money.md). Money values
7
7
  are signed safe-integer minor units; calculator tax percentages are exact text,
8
- so `'5'` means 5%. The retained `InvoiceLineItem.taxRate` and
9
- `ContractLineItem.taxRate` fields are different legacy persisted fractions:
10
- `0.05` means 5%.
8
+ so `'5'` means 5%. The `InvoiceLineItem.taxRate` and
9
+ `ContractLineItem.taxRate` fields are server-derived fractions of the resolved
10
+ components (`0.05` means 5%); they are never tax inputs.
11
11
 
12
12
  ## Calculation contract
13
13
 
@@ -39,10 +39,10 @@ catalogue, seeds, registrations, and cookbook boundary are documented in
39
39
 
40
40
  ## Stored Invoice facts
41
41
 
42
- `TaxSettings.moneyModel` selects the Invoice write behavior. `legacy` keeps the
43
- compatibility path; `migrating` rejects money writes; `v2` resolves a draft's
44
- current settings once and atomically writes the header, line snapshots, and
45
- `DocumentTax` rows. The detailed persistence, freeze, preview, credit-note, and
42
+ There is one money model. Saving a draft resolves its current tax settings
43
+ once and atomically writes the header, line snapshots, and `DocumentTax` rows.
44
+ Every money document needs a tenant, and data written by an earlier version is
45
+ refused until it is upgraded ([money data upgrade](money-upgrade.md)). The detailed persistence, freeze, preview, credit-note, and
46
46
  refund contracts are in [document money](document-money.md).
47
47
 
48
48
  Once an Invoice leaves `DRAFT`, or receives an AR journal, its money snapshot
@@ -55,7 +55,7 @@ not invent a taxable component.
55
55
 
56
56
  A credit note is an `Invoice` with `kind: 'credit_note'`. It stores positive
57
57
  amounts; posting, reporting, and accounting adapters apply that direction once.
58
- Issued-credit capacity and refunds are stable Invoice-v2 behavior. A refund is
58
+ Issued-credit capacity and refunds are stable Invoice behavior. A refund is
59
59
  a positive `Payment` of kind `refund`, allocated only to a credit note. See
60
60
  [document money](document-money.md) before presenting either balance as an
61
61
  ordinary incoming payment.
@@ -63,15 +63,13 @@ ordinary incoming payment.
63
63
  ## Invoice MCP `get` projection
64
64
 
65
65
  The standard Invoice MCP `get` tool uses `serializeInvoiceMcpGet`. For a
66
- legacy tenant, or when tenant settings are absent, it returns the ordinary
67
- public Invoice projection and omits the `money` alias. For a v2 tenant, the
68
- optional `money` value is the stored `InvoiceMoneyFacts` projection: its
69
- header, line snapshots, and `DocumentTax` facts are read from the issued
70
- snapshot and are never recalculated.
71
-
72
- A tenant in the `migrating` gate refuses this read with the stable
73
- `FROZEN_DOCUMENT` code and the public message `Tenant money migration is in
74
- progress`. If the caller's `PublicJsonOptions` hide any Invoice,
66
+ tenant invoice, the optional `money` value is the stored `InvoiceMoneyFacts`
67
+ projection: its header, line snapshots, and `DocumentTax` facts are read from
68
+ the stored snapshot and are never recalculated. An invoice without an id or
69
+ tenant returns the ordinary public Invoice projection.
70
+
71
+ A tenant whose data needs the upgrade refuses this read with
72
+ `COMMERCE_DATA_NEEDS_UPGRADE`. If the caller's `PublicJsonOptions` hide any Invoice,
75
73
  `InvoiceLineItem`, or `DocumentTax` field needed by the money projection, the
76
74
  serializer returns the base Invoice and omits the entire `money` alias. It does
77
75
  not expose restricted values through a partial plain-object alias.
@@ -84,7 +82,7 @@ serializers](../../core/agents/generators.md#mcp-get-serializers).
84
82
  ## Reporting and integrations
85
83
 
86
84
  The materialized `TaxSummaryReport` and its public descriptor/drilldown bridge
87
- read stored Invoice `DocumentTax` facts under the tenant v2 gate. See
85
+ read stored Invoice `DocumentTax` facts under the tenant money lock. See
88
86
  [tax summary aggregation](tax-summary.md). Commerce exports
89
87
  `createTaxSummaryMaterializedReportHost()` for an application-owned,
90
88
  server-only host; register that host in the app's fixed report-host registry to
@@ -104,10 +102,8 @@ mapper](accounting-snapshot-mapper.md).
104
102
 
105
103
  ## Deliberate boundaries
106
104
 
107
- The tenant migration and rollback contract is documented in
108
- [tenant line-amount migration](line-amount-migration.md).
109
- The marker is a maintenance gate, not a customer-controlled setting. Contract
110
- money persistence remains a separate aggregate contract; production report
105
+ Upgrading data written by an earlier version is documented in
106
+ [money data upgrade](money-upgrade.md). Contract money persistence remains a separate aggregate contract; production report
111
107
  hosting is an application opt-in over the public host described above. The
112
108
  model also does not provide currency conversion, cash-basis/deposit accounting,
113
109
  partial recoverability, period locking, or compound tax.