@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.
Files changed (181) hide show
  1. package/AGENTS.md +24 -11
  2. package/README.md +137 -89
  3. package/agents/accounting-snapshot-mapper.md +3 -3
  4. package/agents/commercial-ui.md +2 -2
  5. package/agents/contract-money.md +9 -12
  6. package/agents/cost-billing.md +3 -6
  7. package/agents/customer-vendor-ui.md +1 -2
  8. package/agents/document-money.md +58 -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 +8 -6
  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--BrGMtNz.js → ContractLineItemCollection-BZcrlrrY.js} +19 -24
  23. package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
  24. package/dist/chunks/{CustomerCollection-CjGaZSpf.js → CustomerCollection-BIPH2ZUx.js} +2 -2
  25. package/dist/chunks/{CustomerCollection-CjGaZSpf.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
  26. package/dist/chunks/{DocumentTaxCollection-KOkaQT2M.js → DocumentTaxCollection-DDzlywMW.js} +14 -7
  27. package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
  28. package/dist/chunks/{InvoiceCollection-BUe_8FT1.js → InvoiceCollection-cinzAqt6.js} +50 -161
  29. package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
  30. package/dist/chunks/{TaxCodeCollection-DF2eaJed.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
  31. package/dist/chunks/{TaxCodeCollection-DF2eaJed.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
  32. package/dist/chunks/{TaxComponentCollection-3x24xSNh.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
  33. package/dist/chunks/{TaxComponentCollection-3x24xSNh.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-Cq0g6CfO.js → allocation-integrity-WmWyXhtG.js} +6 -6
  37. package/dist/chunks/{allocation-integrity-Cq0g6CfO.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-oUDvdjOf.js → document-money-DV5t4vlK.js} +388 -62
  45. package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
  46. package/dist/chunks/{invoice-posting-CF3fE_Ec.js → invoice-posting-C2i5dOVZ.js} +20 -22
  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-Bbm0Xflc.js.map +1 -1
  53. package/dist/chunks/{units-CNcjH_eE.js → units-8ElbjVZK.js} +18 -3
  54. package/dist/chunks/units-8ElbjVZK.js.map +1 -0
  55. package/dist/collections/ContractCollection.d.ts.map +1 -1
  56. package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
  57. package/dist/collections/InvoiceCollection.d.ts.map +1 -1
  58. package/dist/collections/InvoiceLineItemCollection.d.ts +0 -30
  59. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
  60. package/dist/collections/TaxCodeCollection.d.ts +5 -1
  61. package/dist/collections/TaxCodeCollection.d.ts.map +1 -1
  62. package/dist/collections/TaxSettingsCollection.d.ts +15 -8
  63. package/dist/collections/TaxSettingsCollection.d.ts.map +1 -1
  64. package/dist/collections/TaxSummaryReportCollection.d.ts +3 -3
  65. package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -1
  66. package/dist/collections/index.d.ts +0 -1
  67. package/dist/collections/index.d.ts.map +1 -1
  68. package/dist/commerce-money-tools.d.ts.map +1 -1
  69. package/dist/contract-money.d.ts +1 -13
  70. package/dist/contract-money.d.ts.map +1 -1
  71. package/dist/document-money-internal.d.ts +7 -3
  72. package/dist/document-money-internal.d.ts.map +1 -1
  73. package/dist/document-money.d.ts +1 -11
  74. package/dist/document-money.d.ts.map +1 -1
  75. package/dist/index.d.ts +6 -5
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +667 -1650
  78. package/dist/index.js.map +1 -1
  79. package/dist/invoice-accounting-sync.d.ts.map +1 -1
  80. package/dist/invoice-mcp.d.ts +3 -3
  81. package/dist/invoice-mcp.d.ts.map +1 -1
  82. package/dist/invoice-posting.d.ts +11 -5
  83. package/dist/invoice-posting.d.ts.map +1 -1
  84. package/dist/invoice-record-screen.d.ts +1 -1
  85. package/dist/invoice-record-screen.d.ts.map +1 -1
  86. package/dist/invoices.d.ts +6 -1
  87. package/dist/invoices.d.ts.map +1 -1
  88. package/dist/invoices.js +2 -2
  89. package/dist/manifest.json +37 -765
  90. package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
  91. package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
  92. package/dist/models/Contract.d.ts.map +1 -1
  93. package/dist/models/ContractLineItem.d.ts +6 -22
  94. package/dist/models/ContractLineItem.d.ts.map +1 -1
  95. package/dist/models/Invoice.d.ts +18 -48
  96. package/dist/models/Invoice.d.ts.map +1 -1
  97. package/dist/models/InvoiceLineItem.d.ts +8 -37
  98. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  99. package/dist/models/TaxSettings.d.ts +0 -10
  100. package/dist/models/TaxSettings.d.ts.map +1 -1
  101. package/dist/models/index.d.ts +0 -1
  102. package/dist/models/index.d.ts.map +1 -1
  103. package/dist/playground.js +1 -1
  104. package/dist/playground.js.map +1 -1
  105. package/dist/recipes.d.ts +8 -11
  106. package/dist/recipes.d.ts.map +1 -1
  107. package/dist/revenue-journal.d.ts +0 -8
  108. package/dist/revenue-journal.d.ts.map +1 -1
  109. package/dist/smrt-knowledge.json +79 -661
  110. package/dist/svelte/components/InvoiceEditor.svelte +7 -148
  111. package/dist/svelte/components/InvoiceEditor.svelte.d.ts +4 -1
  112. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
  113. package/dist/svelte/components/InvoiceTotals.svelte +14 -118
  114. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -27
  115. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
  116. package/dist/svelte/components/MoneyInvoiceEditor.svelte +3 -23
  117. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +3 -26
  118. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -1
  119. package/dist/svelte/index.d.ts +2 -3
  120. package/dist/svelte/index.d.ts.map +1 -1
  121. package/dist/svelte/index.js +1 -2
  122. package/dist/svelte/invoices/calculations.d.ts +3 -108
  123. package/dist/svelte/invoices/calculations.d.ts.map +1 -1
  124. package/dist/svelte/invoices/calculations.js +12 -309
  125. package/dist/svelte/invoices/messages.d.ts +1 -37
  126. package/dist/svelte/invoices/messages.d.ts.map +1 -1
  127. package/dist/svelte/invoices/messages.js +1 -37
  128. package/dist/svelte/invoices/money-editor.d.ts +1 -1
  129. package/dist/svelte/invoices/money-editor.d.ts.map +1 -1
  130. package/dist/svelte/invoices/types.d.ts +14 -114
  131. package/dist/svelte/invoices/types.d.ts.map +1 -1
  132. package/dist/svelte/playground/InvoicePreparationPreview.svelte +85 -94
  133. package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -1
  134. package/dist/svelte/playground.js +1 -1
  135. package/dist/tax/index.d.ts +2 -1
  136. package/dist/tax/index.d.ts.map +1 -1
  137. package/dist/tax/standard-codes.d.ts +12 -0
  138. package/dist/tax/standard-codes.d.ts.map +1 -0
  139. package/dist/tax/tenant-bootstrap.d.ts +29 -0
  140. package/dist/tax/tenant-bootstrap.d.ts.map +1 -0
  141. package/dist/tenant-money-barrier.d.ts +12 -44
  142. package/dist/tenant-money-barrier.d.ts.map +1 -1
  143. package/dist/types/index.d.ts +10 -23
  144. package/dist/types/index.d.ts.map +1 -1
  145. package/dist/ui.js +1 -1
  146. package/dist/ui.js.map +1 -1
  147. package/dist/upgrade/detect.d.ts +129 -0
  148. package/dist/upgrade/detect.d.ts.map +1 -0
  149. package/dist/upgrade/money-v2.d.ts +81 -0
  150. package/dist/upgrade/money-v2.d.ts.map +1 -0
  151. package/package.json +14 -13
  152. package/agents/line-amount-migration.md +0 -50
  153. package/dist/chunks/ContractLineItemCollection--BrGMtNz.js.map +0 -1
  154. package/dist/chunks/DocumentTaxCollection-KOkaQT2M.js.map +0 -1
  155. package/dist/chunks/InvoiceCollection-BUe_8FT1.js.map +0 -1
  156. package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js +0 -468
  157. package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js.map +0 -1
  158. package/dist/chunks/TaxSettingsCollection-DYYF-In-.js +0 -319
  159. package/dist/chunks/TaxSettingsCollection-DYYF-In-.js.map +0 -1
  160. package/dist/chunks/calculations-DaHR17il.js +0 -333
  161. package/dist/chunks/calculations-DaHR17il.js.map +0 -1
  162. package/dist/chunks/document-money-oUDvdjOf.js.map +0 -1
  163. package/dist/chunks/invoice-posting-CF3fE_Ec.js.map +0 -1
  164. package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
  165. package/dist/chunks/tenant-money-barrier-y6EAa6vY.js +0 -135
  166. package/dist/chunks/tenant-money-barrier-y6EAa6vY.js.map +0 -1
  167. package/dist/chunks/units-CNcjH_eE.js.map +0 -1
  168. package/dist/collections/MoneyMigrationDocumentCollection.d.ts +0 -12
  169. package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +0 -1
  170. package/dist/migrations/commerceLineAmounts.d.ts +0 -84
  171. package/dist/migrations/commerceLineAmounts.d.ts.map +0 -1
  172. package/dist/models/MoneyMigrationDocument.d.ts +0 -28
  173. package/dist/models/MoneyMigrationDocument.d.ts.map +0 -1
  174. package/dist/svelte/components/InvoiceLineEditor.svelte +0 -92
  175. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +0 -8
  176. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +0 -1
  177. package/dist/svelte/invoices/fields.d.ts +0 -6
  178. package/dist/svelte/invoices/fields.d.ts.map +0 -1
  179. package/dist/svelte/invoices/fields.js +0 -22
  180. package/dist/tax/legacy.d.ts +0 -48
  181. package/dist/tax/legacy.d.ts.map +0 -1
@@ -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.
@@ -0,0 +1,92 @@
1
+ # Money data upgrade
2
+
3
+ Commerce has one money model. It does not read money data written by an
4
+ earlier version, and it does not convert that data on the fly. It refuses it
5
+ and names the upgrade command. The operator steps are in the CLI guide,
6
+ [commerce money upgrade command](../../cli/agents/commerce-money-upgrade.md).
7
+
8
+ ## Detection
9
+
10
+ `src/upgrade/detect.ts` owns detection. Four shapes count as old data:
11
+
12
+ - **Old schema.** The database still has `tax_settings.money_model`,
13
+ `tax_settings.money_migration_id`, `*_line_items.legacy_amount`, or the
14
+ `commerce_money_migration_documents` table. This refuses the whole database.
15
+ - **Documents without a tenant.** An Invoice or Contract row, header or line,
16
+ with no tenant. The current model writes none, and the upgrade cannot convert
17
+ one: assign it to a tenant or delete it. This refuses the whole database and
18
+ keeps the old schema in place until it is resolved.
19
+ - **Tenant without settings.** A tenant has Invoice or Contract rows but no
20
+ `tax_settings` row. This refuses that tenant only.
21
+ - **Old-format documents.** An issued Invoice or Contract without a money
22
+ snapshot, or a line with neither a tax code nor a component snapshot. The
23
+ current model never writes either, so these stay detectable after `db:migrate --drop-columns`
24
+ removed the old columns. This refuses that tenant only.
25
+
26
+ `assertCommerceMoneySchemaCurrent(db)` checks the schema.
27
+ `assertTenantMoneyCurrent(db, tenantId)` checks the schema, then the tenant.
28
+ On PostgreSQL every cross-tenant scan runs on a connection of its own (opened
29
+ from the database URL), in a transaction that raises `smrt.system_context`,
30
+ so the caller's connection and row policies are never touched; concurrent
31
+ checks for one database and tenant share a single scan. Both cache a passing
32
+ result per database (keyed by its connection URL, so
33
+ request executors share it) and tenant, so the check runs once per process.
34
+ Invoice, Contract and line models also run the check from `initialize()` on
35
+ every row hydrated from the database, which covers unscoped reads and every
36
+ collection class, including generated STI subtype collections. A
37
+ tenantless collection read still checks the schema, and a tenant's first
38
+ `TaxSettings` row is refused while that tenant holds old documents. Every runtime money read and write, the money MCP tools, the tax
39
+ summary, and `ensureCommerceTenant()` call them first.
40
+
41
+ A refusal is a `CommerceDataNeedsUpgradeError` with `code`
42
+ `COMMERCE_DATA_NEEDS_UPGRADE`. Its message ends with the command, for example
43
+ `Run smrt db:upgrade-commerce-money --tenant-id <id> to upgrade it.`
44
+ `commerceMoneyUpgradeCommand(tenantId?)` builds that command text.
45
+
46
+ `findCommerceMoneyUpgradeNeeds(db)` scans the whole database. It is
47
+ operator-only: it ignores the caller's row permissions, so never wire it into
48
+ a principal-facing surface. It returns `oldSchema`, `tenantlessDocuments`
49
+ (rows to assign or delete) and each tenant that needs the upgrade, with a
50
+ reason (`legacy-marker`, `interrupted-migration`, `missing-settings`, or
51
+ `old-format-documents`).
52
+ `smrt db:status` uses it to report the `commerce-money-upgrade` error
53
+ precondition, so `smrt app doctor` reports `migration-required`.
54
+
55
+ ## Upgrade
56
+
57
+ `upgradeCommerceMoney(db, { tenantId? })` in `src/upgrade/money-v2.ts` is the
58
+ one-shot conversion behind `smrt db:upgrade-commerce-money`. Run the normal
59
+ schema migration first. Each tenant converts in its own transaction:
60
+
61
+ 1. Its tax settings row and standard codes are seeded.
62
+ 2. Each stored fractional rate maps to a code. A zero rate becomes `exempt`
63
+ for an exempt customer, otherwise `out-of-scope`. Any other rate becomes a
64
+ one-component `rate-<percent>` code, created once.
65
+ 3. Issued documents are backfilled from their stored values: line net, line
66
+ tax, tax-code snapshot, `DocumentTax` rows, and a money snapshot. No stored
67
+ total changes.
68
+ 4. Drafts are recomputed through the runtime from their prices. Only
69
+ old-format lines (no tax code and no component snapshot) are mapped; a
70
+ draft header keeps its amount type and rounding. The result lists each
71
+ draft whose total changed.
72
+
73
+ An issued document whose stored lines do not add up exactly to its stored
74
+ header fails its tenant. So do an invalid stored rate, an invoice posted under
75
+ the earlier version (its revenue journal carries that version's provenance,
76
+ which current posting and reversal refuse; void it and clear the link, or reset
77
+ the data), an existing `rate-<percent>` code or component that does not
78
+ mean exactly that rate, and an edited standard `exempt` or `out-of-scope`
79
+ code. Nothing in that tenant changes, and the result lists
80
+ each failing document. A tenant whose earlier
81
+ migration never finished also fails; roll it back with the version that started
82
+ it, or reset its data. Converted tenants are skipped on a rerun.
83
+
84
+ Once no tenant needs the upgrade, the old columns and table are dropped. The
85
+ upgrade leaves no mode flag. Read `failures` and `remaining` on the result; the
86
+ CLI exits nonzero on either. New `rate-<percent>` components have no liability
87
+ account and are listed in `componentsWithoutLiabilityAccount`; posting falls
88
+ back to the caller's `taxAccountId` until one is assigned.
89
+
90
+ The runtime never imports `money-v2.ts`. Delete the `upgrade/` directory, the
91
+ CLI command, and the detection call sites together once no supported version
92
+ can have written old-format data.
@@ -13,8 +13,8 @@ debit receivables and credit revenue/tax; signed credits naturally swap sides, a
13
13
  For inclusive documents, stored line net is `amountMinor - taxMinor`; for exclusive
14
14
  and no-tax pricing it is `amountMinor`. Revenue is grouped by each line's stored
15
15
  account, falling back to the caller's revenue account. Component tax uses the
16
- stored liability account and falls back to `taxAccountId`. A `legacy-*` code that
17
- needs this fallback returns a structured warning. Provider tax is posted once as
16
+ stored liability account and falls back to `taxAccountId`. The result has only
17
+ `entries`; there are no warnings. Provider tax is posted once as
18
18
  its own entry to `taxAccountId`. Zero-tax and null-component reporting rows do not
19
19
  produce entries.
20
20
 
@@ -4,23 +4,22 @@
4
4
  change a rate, archive the old component, create a replacement and update the
5
5
  editable `TaxCode.components` list. Never edit a used component's percent.
6
6
 
7
- Every tenant seeds `exempt`, `zero-rated`, `out-of-scope`, and
8
- `legacy-unreconciled`. Those kinds have no components because they report
9
- differently. A legacy zero rate means out-of-scope unless the linked customer
10
- was explicitly exempt; malformed or out-of-range legacy rates stay visible
11
- through `legacy-unreconciled` and the mapping report.
7
+ Every tenant seeds `exempt`, `zero-rated`, and `out-of-scope`
8
+ (`STANDARD_TAX_CODES`, seeded by `ensureTenantTaxCodes(db, tenantId)` in
9
+ `src/tax/standard-codes.ts`). Those kinds have no components because they
10
+ report differently. They are reserved: billing lines and zero rates rely on
11
+ them never carrying tax, so `ensureTenantTaxCodes` (and through it the
12
+ bootstrap, billing period close and the upgrade) refuses a standard code
13
+ whose kind or components were changed. `TaxCodeCollection.findByCode(code, tenantId?)` finds a
14
+ code by its tenant-unique `code`, optionally for an explicit tenant.
15
+ The [money data upgrade](money-upgrade.md) adds a one-component
16
+ `rate-<percent>` code for each nonzero rate it finds in earlier-version data.
12
17
 
13
18
  `TaxSettings` owns tenant defaults, tax registrations, default line amount type
14
19
  and rounding scope. Cookbook policies adapt the portable settings document;
15
- they do not replace persisted tax settings. The per-tenant migration marker is
16
- owned by the money-snapshot migration. Ordinary saves and generated creates
17
- cannot change it: a new instance whose tenant natural key would upsert over a
18
- `migrating` or `v2` row is refused rather than resetting that tenant to legacy.
19
- The ordinary save transaction locks and re-reads the tenant marker before its
20
- natural-key upsert, serializing that check with migration claim and activation.
21
- If the tenant has no marker to lock, its first ordinary save is insert-only: a
22
- concurrent migration that creates the marker wins and the stale save is refused
23
- instead of adopting and overwriting the migration-owned row.
20
+ they do not replace persisted tax settings. There is one settings row per
21
+ tenant, created once under a tenant lock (see
22
+ [document money](document-money.md#tenant-settings-and-bootstrap-4009)).
24
23
 
25
24
  `applyCommerceCookbookSettings(db, tenantId, cookbook)` is the explicit
26
25
  server-side activation boundary for core's portable `taxRate`, inclusion, and
@@ -34,6 +33,10 @@ import cookbook from './smrt.cookbook.json' with { type: 'json' };
34
33
  await applyCommerceCookbookSettings(db, session.tenantId, cookbook);
35
34
  ```
36
35
 
36
+ `ensureCommerceTenant(db, tenantId, { cookbook })` wraps this call with the
37
+ settings row and standard code seed; prefer it for a newly created tenant
38
+ (#4009).
39
+
37
40
  `smrt cookbook apply` writes project files and does not have a database or
38
41
  tenant, so it deliberately performs no storage writes. Activation creates or
39
42
  reuses a component and default tax code, then updates tenant `TaxSettings` in
@@ -32,8 +32,9 @@ movement to every component.
32
32
  ## Materialized document report path
33
33
 
34
34
  `TaxSummaryReport` is a tenant-scoped, manual materialized report. Its owner,
35
- `TaxSummaryReportCollection.refreshTaxSummary()`, refuses tenants whose
36
- `TaxSettings.moneyModel` is not `v2`: legacy and migrating documents are not a
35
+ `TaxSummaryReportCollection.refreshTaxSummary()`, requires a tenant and refuses
36
+ one whose money data needs the upgrade (`COMMERCE_DATA_NEEDS_UPGRADE`, see
37
+ [money data upgrade](money-upgrade.md)): earlier-version documents are not a
37
38
  complete stored-tax source. The caller injects the public Expense and Journal
38
39
  source adapters because commerce cannot depend on expenses; omitting either
39
40
  adapter is a refusal, never a zero-ITC fallback.
@@ -68,13 +69,14 @@ matching `DocumentTax.documentType` pairs: `invoice`/`invoice` or
68
69
  `credit_note`/`credit_note`. Credit rows retain positive stored values and pass
69
70
  through the aggregation's single credit sign step.
70
71
 
71
- Refresh holds the tenant `TaxSettings` parent lock through the v2 gate, all
72
- source reads, aggregation, and replacement. Every source-adapter call receives
72
+ Refresh holds the tenant `TaxSettings` parent lock through the upgrade check,
73
+ all source reads, aggregation, and replacement. Every source-adapter call receives
73
74
  the same transaction-bound database executor. Replacement rows use a stable
74
75
  UUID derived from tenant, period, currency, row kind, and dimension, so retry
75
76
  and concurrent same-period refreshes converge to one row set. Materialized
76
- `list`, `get`, `count`, and Invoice drilldowns all recheck the tenant's marker;
77
- `legacy` and `migrating` refuse even existing report rows.
77
+ `list`, `get`, `count`, and Invoice drilldowns all require a tenant context and
78
+ recheck it; a tenant that needs the upgrade is refused even for existing report
79
+ rows.
78
80
 
79
81
  Each document drilldown fact carries its printed invoice number or Contract
80
82
  reference, tenant-scoped customer/vendor party id, issue date, stored taxable
@@ -1 +1 @@
1
- {"version":3,"file":"period-close.d.ts","sourceRoot":"","sources":["../../src/billing/period-close.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,iDAAiD;IACjD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,OAAO,GACP,MAAM,GACN,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,yEAAyE;AACzE,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB;gBAAzB,MAAM,EAAE,iBAAiB;CAW/C;AAmDD,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,GAAG,OAAa,GAAG,aAAa,CAQrE;AAkCD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,MAAM,CAAC,EACxD,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAsED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,KAAK,GAAE,gBAAqB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAwE5B"}
1
+ {"version":3,"file":"period-close.d.ts","sourceRoot":"","sources":["../../src/billing/period-close.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,iDAAiD;IACjD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,OAAO,GACP,MAAM,GACN,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,yEAAyE;AACzE,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB;gBAAzB,MAAM,EAAE,iBAAiB;CAW/C;AAmDD,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,GAAG,OAAa,GAAG,aAAa,CAQrE;AAkCD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,MAAM,CAAC,EACxD,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAsED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,KAAK,GAAE,gBAAqB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAwE5B"}