@happyvertical/smrt-commerce 0.55.14 → 0.55.15

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 (50) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +29 -0
  3. package/agents/payment-allocations.md +45 -0
  4. package/agents/purchasing-ui.md +27 -0
  5. package/dist/allocation-integrity.d.ts +13 -0
  6. package/dist/allocation-integrity.d.ts.map +1 -0
  7. package/dist/billing/payment-attempts.d.ts.map +1 -1
  8. package/dist/chunks/money-DfrRxrel.js +50 -0
  9. package/dist/chunks/money-DfrRxrel.js.map +1 -0
  10. package/dist/chunks/{types-CRnln577.js → types-zVnIjSOk.js} +29 -2
  11. package/dist/chunks/{types-CRnln577.js.map → types-zVnIjSOk.js.map} +1 -1
  12. package/dist/collections/PaymentAllocationCollection.d.ts +14 -0
  13. package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +175 -93
  17. package/dist/index.js.map +1 -1
  18. package/dist/manifest.json +43 -2
  19. package/dist/models/Contract.d.ts +7 -1
  20. package/dist/models/Contract.d.ts.map +1 -1
  21. package/dist/models/Invoice.d.ts.map +1 -1
  22. package/dist/models/Payment.d.ts +2 -0
  23. package/dist/models/Payment.d.ts.map +1 -1
  24. package/dist/models/PaymentAllocation.d.ts +7 -33
  25. package/dist/models/PaymentAllocation.d.ts.map +1 -1
  26. package/dist/playground.js +23 -0
  27. package/dist/playground.js.map +1 -1
  28. package/dist/smrt-knowledge.json +79 -8
  29. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte +106 -0
  30. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte.d.ts +49 -0
  31. package/dist/svelte/components/PurchaseOrderHeaderForm.svelte.d.ts.map +1 -0
  32. package/dist/svelte/index.d.ts +4 -0
  33. package/dist/svelte/index.d.ts.map +1 -1
  34. package/dist/svelte/index.js +3 -0
  35. package/dist/svelte/playground.d.ts +93 -0
  36. package/dist/svelte/playground.d.ts.map +1 -1
  37. package/dist/svelte/playground.js +20 -0
  38. package/dist/svelte/purchasing-i18n.d.ts +9 -0
  39. package/dist/svelte/purchasing-i18n.d.ts.map +1 -1
  40. package/dist/svelte/purchasing-i18n.js +9 -0
  41. package/dist/svelte/purchasing-types.d.ts +13 -0
  42. package/dist/svelte/purchasing-types.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +7 -0
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/ui.d.ts.map +1 -1
  46. package/dist/ui.js +9 -0
  47. package/dist/ui.js.map +1 -1
  48. package/package.json +12 -12
  49. package/dist/chunks/money-WSMvSgWq.js +0 -8
  50. package/dist/chunks/money-WSMvSgWq.js.map +0 -1
@@ -3,18 +3,19 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-commerce",
6
- "packageVersion": "0.55.14",
6
+ "packageVersion": "0.55.15",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "a3fea9eb29864094a1c486d948d877079483c49f10364bf2536df5c1b96ce96d",
11
- "packageJson": "06acfd50f882e4ee2674ded31297c884b95b2d714cf4fcabd05caa434c38d117",
12
- "agents": "afd56c9556f96a402fdb6dd6ad41b97b29367b2aeea4b17bc3b181955391731b",
10
+ "manifest": "e38a26fbb921dfac665389a7d1fb4e65e00ba0840f65da003d9d882884fe0409",
11
+ "packageJson": "bff099dbc9b6b2415bfb5f963b627f54b0d42a3addd19da0a23cabb1e730644d",
12
+ "agents": "5a4901577a0f007cf44f04658c4394e1b1fe292f24701d0ff00ccf66095b4217",
13
13
  "moduleDoc:agents/invoice-quantity.md": "bc3b617d4fb8cc87511763b281283107557ba64795e64522193ef967a3715d32",
14
+ "moduleDoc:agents/payment-allocations.md": "eed7ea2cafe89fb59c1bb4a96ff71db4183b4bf6a06a49d4eb4a86f907458d78",
14
15
  "moduleDoc:agents/customer-vendor-ui.md": "1187b4d14e6bab661a2bb3312315239952dc955f90eceb177719f48dd0979150",
15
16
  "moduleDoc:agents/commercial-ui.md": "308729a481c82903d06837e7052c04509a92cf17d17345f71dce88d8d42d0431",
16
17
  "moduleDoc:agents/quote-ui.md": "514568e4e7815c47113627ac044a1f31a6933377b3f6c70f5d54b2508d94200c",
17
- "moduleDoc:agents/purchasing-ui.md": "51ab713d89949c9779973191dfa3529e5120d087321236fc48ad1948c88f9866",
18
+ "moduleDoc:agents/purchasing-ui.md": "c7d633c5ba45078c51f4066f68d90112cd748ac089ac34d209f9bf025739589f",
18
19
  "moduleDoc:agents/cost-billing.md": "def9486f25bbe6d7259d50dc6e18e2bd29f60f995d8fcf201a9e8c2ceaa83823"
19
20
  },
20
21
  "exports": [
@@ -2181,6 +2182,7 @@
2181
2182
  "fields": [],
2182
2183
  "relationships": [],
2183
2184
  "methods": [
2185
+ "allocatePayment",
2184
2186
  "findByAllocator",
2185
2187
  "findByDateRange",
2186
2188
  "findByInvoice",
@@ -2194,6 +2196,14 @@
2194
2196
  "getUnallocatedFromPayment"
2195
2197
  ],
2196
2198
  "methodSignatures": [
2199
+ {
2200
+ "name": "allocatePayment",
2201
+ "async": true,
2202
+ "params": [
2203
+ "input: object"
2204
+ ],
2205
+ "returns": "Promise<PaymentAllocation[]>"
2206
+ },
2197
2207
  {
2198
2208
  "name": "findByAllocator",
2199
2209
  "async": true,
@@ -2284,6 +2294,14 @@
2284
2294
  }
2285
2295
  ],
2286
2296
  "surfaces": [
2297
+ {
2298
+ "kind": "api",
2299
+ "name": "paymentallocations.allocatePayment",
2300
+ "operation": "allocatePayment",
2301
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection",
2302
+ "path": "/paymentallocations/allocatePayment",
2303
+ "method": "POST"
2304
+ },
2287
2305
  {
2288
2306
  "kind": "api",
2289
2307
  "name": "paymentallocations.findByPayment",
@@ -2372,6 +2390,12 @@
2372
2390
  "path": "/paymentallocations/findWithGlobals",
2373
2391
  "method": "POST"
2374
2392
  },
2393
+ {
2394
+ "kind": "cli",
2395
+ "name": "paymentallocationcollection_allocatePayment",
2396
+ "operation": "allocatePayment",
2397
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
2398
+ },
2375
2399
  {
2376
2400
  "kind": "cli",
2377
2401
  "name": "paymentallocationcollection_findByPayment",
@@ -2438,6 +2462,12 @@
2438
2462
  "operation": "findWithGlobals",
2439
2463
  "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
2440
2464
  },
2465
+ {
2466
+ "kind": "mcp",
2467
+ "name": "paymentallocationcollection_allocatepayment",
2468
+ "operation": "allocatePayment",
2469
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
2470
+ },
2441
2471
  {
2442
2472
  "kind": "mcp",
2443
2473
  "name": "paymentallocationcollection_findbypayment",
@@ -6368,6 +6398,20 @@
6368
6398
  "columnType": "TEXT",
6369
6399
  "default": "",
6370
6400
  "description": "Where it came from, for example your shop, a marketplace or a phone call."
6401
+ },
6402
+ {
6403
+ "name": "expectedDate",
6404
+ "type": "meta",
6405
+ "required": false,
6406
+ "default": "",
6407
+ "description": "The date you expect the delivery, as YYYY-MM-DD. Leave blank if it is not known."
6408
+ },
6409
+ {
6410
+ "name": "shipTo",
6411
+ "type": "meta",
6412
+ "required": false,
6413
+ "default": "",
6414
+ "description": "Where this order should be delivered, including any delivery instructions."
6371
6415
  }
6372
6416
  ],
6373
6417
  "relationships": [
@@ -15165,6 +15209,14 @@
15165
15209
  "operation": "findWithGlobals",
15166
15210
  "objectName": "@happyvertical/smrt-commerce:InvoiceLineItemCollection"
15167
15211
  },
15212
+ {
15213
+ "kind": "api",
15214
+ "name": "paymentallocations.allocatePayment",
15215
+ "operation": "allocatePayment",
15216
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection",
15217
+ "path": "/paymentallocations/allocatePayment",
15218
+ "method": "POST"
15219
+ },
15168
15220
  {
15169
15221
  "kind": "api",
15170
15222
  "name": "paymentallocations.findByPayment",
@@ -15253,6 +15305,12 @@
15253
15305
  "path": "/paymentallocations/findWithGlobals",
15254
15306
  "method": "POST"
15255
15307
  },
15308
+ {
15309
+ "kind": "cli",
15310
+ "name": "paymentallocationcollection_allocatePayment",
15311
+ "operation": "allocatePayment",
15312
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
15313
+ },
15256
15314
  {
15257
15315
  "kind": "cli",
15258
15316
  "name": "paymentallocationcollection_findByPayment",
@@ -15319,6 +15377,12 @@
15319
15377
  "operation": "findWithGlobals",
15320
15378
  "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
15321
15379
  },
15380
+ {
15381
+ "kind": "mcp",
15382
+ "name": "paymentallocationcollection_allocatepayment",
15383
+ "operation": "allocatePayment",
15384
+ "objectName": "@happyvertical/smrt-commerce:PaymentAllocationCollection"
15385
+ },
15322
15386
  {
15323
15387
  "kind": "mcp",
15324
15388
  "name": "paymentallocationcollection_findbypayment",
@@ -19769,13 +19833,18 @@
19769
19833
  "polymorphicAssociations": 0,
19770
19834
  "uuidColumns": 162
19771
19835
  },
19772
- "agentDoc": "# @happyvertical/smrt-commerce\n\nE-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, payout remittance, and optional ledger integration.\n\n## Models\n\n- **Customer** / **Vendor**: linked to Profile via string ID (not FK). Customer has creditLimit, paymentTerms, customerType (DTC / WHOLESALE / RETAIL). Vendor has leadTimeDays, minimumOrder, and `payoutAddresses: Record<string, string>` — a flat map from payout-rail-qualified currency code (`USDC-base`, `BTC`, `USD-stripe`, ...) to destination string (EVM address, BTC address, Stripe Connect account id, IBAN). Use `getPayoutAddress(currency)` for a `Map.get`-style lookup; missing entries return `undefined` (caller decides skip-vs-error).\n- **Contract** (STI base → Estimate, Order, Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder, Cart, LicenseSale): 9 contract types sharing one table. Carries `channelId` (open-ended string — `dtc-web`, `wholesale-b2b`, `pos-store-N`, etc.) so the same model serves DTC checkout, B2B portals, and POS.\n- **WholesaleOrder**: B2B order. Conventional pairing — customer has `customerType: 'wholesale'`, NET-30/60 terms, delivered via wholesale-portal channel.\n- **ProductionOrder**: manufacturing equivalent of a PurchaseOrder — commission your factory to make finished goods. Consumes raw materials per BOM (`@happyvertical/smrt-manufacturing`) and produces SKU stock (`@happyvertical/smrt-inventory`).\n- **Cart**: transient order-in-progress. Same shape as Order; the application promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout instead of copying data between tables.\n- **LicenseSale**: industry-neutral licensing primitive. Carries an *immutable* rights snapshot (`rightsMedium`, `rightsDistributionScope`, `rightsExclusivity`, `rightsDuration`, `rightsTerritory`, `rightsSublicensing`, `rightsDerivatives` — typed `Meta<T>` fields), licensee identity (`licenseeEmail`, optional `licenseeLegalEntity` / `licenseeJurisdiction`), and a signed-PDF reference (`pdfUrl`, `pdfHash`, optional `onChainHashRegistryRef`). Once saved at `ContractStatus.ACCEPTED`, the rights snapshot is frozen — mutating any of the seven rights fields and re-saving throws. The only legal transition out of ACCEPTED is `revoke()` (moves to CANCELLED without touching rights). Useful for stock media, music licensing, code-asset marketplaces, license keys, anywhere rights are sold for a fee.\n- **ContractLineItem**: items on contracts.\n- **Invoice**: status machine `DRAFT → SENT → VIEWED → PARTIAL → PAID` (also OVERDUE, CANCELLED, WRITTEN_OFF). `recognizeRevenue()` creates balanced AR journal entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable).\n- **InvoiceLineItem**: fractional-quantity line items with safe minor-unit validation/recalculation on save; editor-mode recovery and existing-column migration are documented in [invoice quantity persistence](agents/invoice-quantity.md).\n- **Payment** / **PaymentAllocation**: tracks payments against invoices. Status controlled by `Invoice.updatePaymentStatus()`, not Payment model. Carries optional backend-adapter fields for PaymentBackend-routed flows: `backendId` (rail adapter id — `base-usdc`, `btc`, `stripe`, distinct from `externalProvider` which names an accounting sync destination), `backendTxRef` (chain tx hash or gateway settlement id), `nativeAmount` / `nativeCurrency` (what actually arrived), `usdAtQuote` / `usdAtConfirmation` (drift accounting for volatile-currency rails). `Payment.usdDrift()` returns the confirmation - quote delta, or `0` when either side is unset.\n- **PaymentInstrument**: reusable saved payment method (\"card on file\"). Stores only provider references and non-sensitive display metadata (`providerCustomerId`, `providerPaymentMethodId`, brand / last4 / expiry), never raw card data. Use `PaymentInstrumentCollection.setDefaultForCustomer()` to change the default so the single-default-per-customer invariant is enforced; generated create/update routes cannot write `isDefault` directly.\n- **PaymentIntent**: short-lived pre-payment commitment with multi-option semantics. Locks a USD price for a fixed window (default 15 minutes) and lists one or more `PaymentOption`s describing different rails (`backendId`, `currency`, `payTo`, `nativeAmount`, optional `chain` / `memo` / `x402Capable` / `expiresAt`). First option to receive payment wins; the others are implicitly retired. State machine `awaiting_payment → paid → (issued | retired)` plus `expired` and `cancelled`. Mutate via dedicated `markPaid` / `markIssued` / `expire` / `cancel` / `retire` helpers — direct status assignment bypasses the invariant checks. Idempotency via natural key `(tenant_id, offering_ref, licensee_email, idempotency_key)` and `PaymentIntentCollection.getOrCreateByIdempotencyKey()`.\n- **Payout**: operator-to-supplier remittance. Distinct from Payment because direction, status machine, and chain semantics differ. Status machine `pending → sent → confirmed → failed` (failed is terminal but resettable via `resetFromFailed()` after fixing the underlying problem). References source `paymentId` (plain string) and destination `vendorId` (foreign key). Amount invariant `supplierNet === grossAmount - operatorFee` enforced exactly on save (integer minor units, no tolerance — #2401). `PayoutCollection.createFromPayment()` is the typical entry point; it pulls native amount / currency from the source Payment.\n- **Fulfillment** / **FulfillmentLineItem**: shipment/delivery tracking.\n\n## Recipes (#3590)\n\n`src/recipes.ts` declares four minimal `SmrtRecipe`s, emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`: `commerce.customers`\n(Customer), `commerce.vendors` (Vendor), `commerce.sales` (Order, requires\ncustomers), `commerce.purchases` (PurchaseOrder, requires vendors). Headers\nonly; line items, fulfilment, invoices, and payments are later recipes. Each\nhas user-facing help in `src/<name>.recipe.md` (#3591), and the fields those\nrecipes show carry `@field({ description })` text; keep both in plain\nlanguage for end users and update them when a recipe's fields change. See\n[core recipes](../core/agents/recipes.md).\n\n## Ledger Integration\n\n`@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.\n\n## Billing-period close (#3060)\n\n`src/billing/` closes periods from smrt-subscriptions charges; see the README\nfor usage.\n\n- **Port, not provider.** Period close, events, and credit checkout call only\n the `BillingProvider` port (integer minor units). `stripe.ts` adapts\n `@happyvertical/accounting`, which takes currency *major* units; convert with\n `units.ts` and never hand-roll a Stripe call here. `units.ts` exponents are\n an explicit ISO 4217 table matching the SDK's, never `Intl` display digits\n (CLDR differs for IQD, MGA, … and drifts by ICU release). The SDK's\n `*Minor` fields are already ISO minor units. Capabilities a rail may lack\n (`chargeSavedPaymentMethod`, `createSetupCheckout`, `getCheckout`,\n `setDefaultPaymentMethod`, `markInvoiceUncollectible`) are optional port\n methods: detect them by presence, and keep additions optional (#3139).\n `sendAndReconcile` refuses a provider invoice whose subtotal or total\n disagrees with the local invoice.\n- **Replay safety is identity.** Close, invoice, line, payment, allocation,\n and claim ids are `deterministicId()`s; `BillingLineSource` (conflict key\n `source_type, source_id`) is the only double-billing guard, so never bill a\n source without claiming it, and never widen that key. Each close step is\n persisted before the next; `acquireCloseLease()` serializes workers. A\n close that nets to a credit keeps its claims and ends `carried_forward`;\n its subtotal is a `credit_carry_forward` source for the payer's next billed\n close, which marks the carried close `completed`.\n- **Cycles and flat-plan coverage (#3116).** `cycles.ts` is the schedule\n (pure; `BillingAccount.billingAnchorAt`, null = calendar months). A close\n with no period closes each payer's `lastEndedBillingPeriod()`. Flat-plan\n claims are the exception to \"id from the source\": their `sourceId` is\n `<subscriptionId>:seq:<chainSequence>` and their `periodStart/End` is the\n exact time they bill. `claimFlatWindow()` reads the last number, then the\n coverage (claims ending after the window start), claims one gap as number\n n + 1, and retries on conflict; reading in that order is what makes it\n safe. That numbering, not the period, stops overlaps when schedules or\n payers change — never claim a flat plan outside it, and keep both reads\n bounded (no whole-history scans). Each numbered claim is its own invoice\n line keyed `lineKey|periodStart` (a function of the claim alone, so a\n resumed close rebuilds the same line ids). Proration is\n `prorateMinorUnits()` (half up, BigInt) against the period's full cycle.\n- **System tables.** `BillingAccount`, `BillingPeriodClose`, and\n `BillingLineSource` are not tenant-scoped and have no generated surface.\n Collections stay unexported; models are root exports (#3082). Invoices,\n payments, and journals are written in the seller's tenant; payer-owned\n subscriptions and credit grants in a system context, only after\n verification.\n- **Events** use smrt-jobs' delivery inbox under provider\n `<name>-billing:<sellerTenantId>` and a provider-filtered\n `ForgeProjectionRuntime`, so a runtime never claims another seller's\n events; any other runtime sharing the inbox must pass `providers` too. Only\n events this package acts on are stored: foreign checkouts are `ignored`,\n and only `smrt_*` checkout metadata is kept; it is\n HMAC-signed with the webhook secret at creation (empty values are omitted:\n Stripe drops them), so another integration on the same account cannot forge\n a credit purchase or a card setup; credit is granted only when the\n collected total equals it plus provider tax. Checkout amounts come from the\n re-read session (`getCheckout()`), never the Stripe event, whose amounts are\n in Stripe's unit. Rotating the webhook secret strands in-flight checkouts\n (up to 24 h). `observe()` re-reads provider state, so ordering never depends\n on delivery order; the only provider writes in `observe()` are idempotent\n (making a saved card the default). The standing hook runs inside the event\n transaction and must be idempotent.\n- **Card on file and top-ups (#3139).** A saved card is the payer customer's\n default `PaymentInstrument` (seller tenant, id from the provider method).\n `autoChargeInvoices` bills such payers `charge_automatically`, decided once\n and saved as `Invoice.collectionMethod` before the first push, which every\n retry reuses (never recompute it: a lost push response would record a\n method the provider was not asked for, #3190); activation follows `paid`, never send. An auto top-up attempt\n is a `PENDING` `Payment` (`reference` `auto-top-up:<policyId>`, id from the\n policy and `count()` of its attempts) inserted before the charge, so racing\n evaluations derive one id and one charge; the charge key is derived from\n that id. `applyAutoTopUpOutcome()` must run in a transaction and starts with\n a conditional `UPDATE … WHERE status = 'pending'` row lock, so the hook and\n the `payment` webhook settle once; the grant is keyed by the charge key.\n Never settle an attempt outside that function. Taxed accounts are charged\n with provider tax by default (#3194): the attempt row holds the credit\n (the charge subtotal), the tax is journaled to tax payable at settlement,\n and the attempt's tax decision is recorded in its `notes` so a re-drive\n repeats the same charge request (the provider refuses a key reused with\n different tax). `claimAttempt()` is the only place that decides a\n skip and reports it (`onAutoTopUpSkipped`); `cardOnFile()` and period close\n use `defaultCardInstrument()` — never copy that predicate. A provider write-off moves the local invoice to\n `WRITTEN_OFF` (a later payment, by card or on a rail (#3188), is recorded\n and reinstates the payer; the status stays, so \"paid in full\" for a\n written-off invoice is read from its allocations).\n- **`Invoice.providerTaxAmount`** is added to line-item tax; it is\n server-managed (not API-writable). `toAccountingInput()` emits major units.\n- **Payment rails (#3138).** A runtime has one issuing `provider` and any\n number of `paymentProviders` (unique names, one inbox namespace each; a\n provider with `capabilities.issuesInvoices === false` can never issue).\n `observe()` resolves the provider from the delivery namespace, never the\n payload. Crypto rails implement the port over `@happyvertical/payments`'\n `CryptoCheckoutGateway`; never call BTCPay (or any gateway) from here.\n Settlement is decided by the pure `decideAttempt()` from gateway state and\n `paymentPolicy` — the gateway's `settled` is authoritative and nothing here\n counts confirmations. A `BillingPaymentAttempt` (`(provider, checkout_id)`\n deterministic id) records every state and settles once; grants and payments\n are keyed by checkout id. Invoice payments close the issuer's invoice out of\n band in `observe()` (idempotent) before `project()` records the payment, and\n `settlePaidInvoice` skips (records nothing for) a `paidOutOfBand` invoice\n the rail latched with `outOfBandRequestedAt`; other out-of-band closes are\n recorded as `OTHER` payments. Rails never save cards or charge tax: a taxed\n credit purchase on a rail needs an explicit `automaticTax: false`. Exceptions are flags for operators;\n nothing reverses or refunds automatically.\n- **Known limits**: write-offs post no bad-debt journal (AR stays until paid\n or journaled by an operator); discounts still reach Stripe as negative lines (the SDK's\n line `discount` would change the lines a replayed push reconciles, so it\n needs its own migration); `RetailCharge` has no adjustment ledger. Flat\n plans are monthly, in arrears; plan changes are not prorated (#3119).\n\n## Cross-Package References\n\n- `customerId` → `@foreignKey('Customer')` (hard reference within package)\n- `profileId` → plain string to smrt-profiles\n- `arJournalId`, `revenueJournalId` → plain string to smrt-ledgers\n- `skuId` (on `PaymentIntent`, `LicenseSale`) → plain string to smrt-products\n- `paymentId` (on `PaymentIntent`, `Payout`, `LicenseSale`) → plain string to `Payment` (same package, but kept as plain string for cross-model consistency)\n- `vendorId` on `Payout` → `@foreignKey(Vendor)` (hard reference within package)\n- Billing records reference tenants with `@crossPackageRef('@happyvertical/smrt-users:Tenant')` and charges, plan periods, and adjustments by plain `sourceId`\n\n## Gotchas\n\n- **Optional tenancy**: all models `@TenantScoped({ mode: 'optional' })` + nullable tenantId\n- **Currency is integer minor units** (cents, satoshis) — `$19.99` is `1999`, like affiliates. Money is exact, so money fields initialize `= 0`, never `= 0.0`: the integer literal is what maps them to INTEGER columns (BIGINT on fresh PostgreSQL/DuckDB databases). Writing a fractional major-unit value is the bug — the model rejects it via `assertIntegerMinorUnits` on `Invoice`/`Payment`/`PaymentAllocation`/`Payout`/`Contract` before the database sees it, because PostgreSQL would reject it with `22P02` while SQLite's affinity silently stores it. `pnpm --filter @happyvertical/smrt-commerce test:postgres` is the lane that holds the line (#2361, #2401, #2373).\n- **Rates are the opposite**: `InvoiceLineItem.taxRate` and `ContractLineItem.taxRate` are inherently fractional and must initialize `= 0.0`. INTEGER would truncate every rate to 0. `ContractLineItem.quantity` is decimal for the same reason (contracts price fractional hours/weight/bandwidth); `InvoiceLineItem.quantity` is also decimal (six fractional places); existing integer columns require the explicit [quantity migration](agents/invoice-quantity.md).\n- **Rounding happens where a rate meets money, and nowhere else**: `InvoiceLineItem` uses exact shared invoice arithmetic to round fractional quantity × price and then tax to minor units; `ContractLineItem.calculateAmount()` uses `Math.round()` for tax. Everything downstream is exact integer arithmetic, which is why **there are no `EPSILON` tolerances left on any money path** (#2401). `Invoice`, `PaymentAllocation`, `Payout` and `PaymentIntent` compare with `===` / `>` / `>=`. Do not reintroduce a tolerance: against integers it forgives a real one-cent discrepancy rather than float fuzz. (`FulfillmentLineItem`'s quantity epsilon is unrelated — quantities are genuinely decimal there.)\n- **Migrating an existing database to minor units**: `preflightCommerceMoneyMinorUnits(db)` reports, per column, whether it still holds major units and which rows would be rounded or exceed JavaScript's safe-integer range; `migrateCommerceMoneyToMinorUnits(db)` converts them. Both are exported from the package root. The rescale is idempotent via a `_smrt_backfills` marker, changes the column type in place on PostgreSQL/DuckDB (`ALTER … TYPE BIGINT USING round(col * 100)`), and on SQLite rescales values only — SQLite cannot alter a declared type in place, so those columns come back in `declaredTypeChangePending` and need the table-rebuild path (#2370).\n- **`Payment.nativeAmount` is NOT in that migration, and must not be.** Its minor unit is a property of the *row's asset*: satoshis on a BTC rail (×1e8), cents on a fiat or stablecoin rail (×1e2), in one column discriminated only by `nativeCurrency`. A blanket ×100 turns 0.00713 BTC into `1` instead of `713000` sats, and a round 0.01 BTC even passes the preflight's integrality check while being wrong by six orders of magnitude. Convert it in two deliberate steps — normalise each row to its own asset's minor units while the column is still floating-point, then `rescaleMoneyColumnsToMinorUnits(db, COMMERCE_NATIVE_UNIT_COLUMNS, { scale: 1, … })` for the type change. `COMMERCE_NATIVE_UNIT_COLUMNS` is exported with a worked example; step one is deliberately the deployment's to write, because only it knows which `nativeCurrency` values it has used.\n- **`PaymentIntent.paymentOptions[].nativeAmount` has the same per-asset problem inside a JSON column and is likewise not migrated.** An intent's price lock defaults to 15 minutes, so the supported answer is to let open intents expire and be re-quoted: an unconverted option fails the exact `!==` reconciliation against the payment that arrives, which is a loud failure rather than a silently mis-scaled quote.\n- **Range and existing deployments**: fresh PostgreSQL/DuckDB INTEGER columns are BIGINT, and the runtime rejects values outside JavaScript's safe-integer range rather than rounding them. Existing PostgreSQL `int4` columns do not self-repair; widen them through the explicit migration tracked in #2424.\n- **Journals posted from commerce carry minor units**: `Payment.recordPayment()` and `Invoice.recognizeRevenue()` write `amount` / `totalAmount` straight into smrt-ledgers entries. smrt-ledgers is unit-agnostic (its `BALANCE_EPSILON` only checks debits === credits), and integer entries balance exactly, so this is strictly tighter than the major-unit journals commerce used to post.\n- **Invoice controls payment status**: not the Payment model — use `Invoice.updatePaymentStatus()`\n- **Tax rate is external**: no tax rate field on Invoice — rate must be calculated externally\n- **Profile linking**: separate `ProfileCollection.create()` needed to fetch actual Profile object\n- **PaymentIntent natural key**: `conflictColumns: ['tenant_id', 'offering_ref', 'licensee_email', 'idempotency_key']` — a retried `create` with the same tuple upserts the existing row. Use `getOrCreateByIdempotencyKey()` to branch on `{ intent, created }`. Empty natural-key inputs (e.g. blank `idempotencyKey`) disable dedup by design.\n- **Payout state-machine guards**: `markSent` requires a non-empty `backendTxRef`; `markConfirmed` only valid from `SENT`; `markFailed` only valid from `PENDING` / `SENT` (a confirmed payout can't fail — that path is a refund). `resetFromFailed()` is the dedicated escape hatch from `FAILED` back to `PENDING` after an operator fixes the underlying problem; it clears `backendTxRef` so the next attempt picks up a fresh one.\n- **PaymentInstrument defaults**: `isDefault` is domain-managed, not API-writable. Call `setDefaultForCustomer(customerId, instrumentId)` so the target is validated and the customer's other instruments are cleared.\n- **LicenseSale immutability**: rights snapshot freezes on save-with-status-ACCEPTED. The captured snapshot lives in a module-scoped `WeakMap<LicenseSale, string>` so it doesn't interact with the schema or round-trip through `_meta_data`. Drafts (status != ACCEPTED) remain mutable. To \"change\" an issued license: `revoke()` it, then issue a new `LicenseSale` row.\n- **Vendor.payoutAddresses normalization**: the constructor accepts either a `Record<string, string>` or a pre-serialized JSON string. `initialize()` re-normalizes after the framework's option-override pass; `save()` re-normalizes defensively against direct field assignment. Non-string values inside the input map are silently dropped to preserve the typed invariant downstream.\n\n## Commercial UI\n\n`CustomerSelect`/`VendorSelect` are the registered selectors for Customer and\nVendor (`selects` in `src/ui.ts`); models carry `ui.widget` hints. See\n[customer/vendor UI](agents/customer-vendor-ui.md#selectors-3602).\n\nSee [commercial UI composition](agents/commercial-ui.md) for native form recovery, amount compatibility and backend ownership. Package-owned guides under `agents/` ship with the package.\n\n## Quote and estimate UI\n\n`src/svelte/quote-types.ts` separates unparsed draft strings from explicit\nminor-unit revision DTOs. Native row submitters, caller-owned retry identity and\nextension conventions are documented in [agents/quote-ui.md](agents/quote-ui.md).\n\n## Purchasing UI\n\n`PurchaseOrderEditor` preserves allocation IDs and explicit zeroes, including\nretained reduction rows; the server validates review fingerprints before recording.\nSee [agents/purchasing-ui.md](agents/purchasing-ui.md) for caller responsibilities.\n\n## Exact cost billing\n\nPure signed cost billing and explicit tax rounding are available through the\nindependent `cost-billing` subpath; see [contract](agents/cost-billing.md).\n",
19836
+ "agentDoc": "# @happyvertical/smrt-commerce\n\nE-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, payout remittance, and optional ledger integration.\n\n## Models\n\n- **Customer** / **Vendor**: linked to Profile via string ID (not FK). Customer has creditLimit, paymentTerms, customerType (DTC / WHOLESALE / RETAIL). Vendor has leadTimeDays, minimumOrder, and `payoutAddresses: Record<string, string>` — a flat map from payout-rail-qualified currency code (`USDC-base`, `BTC`, `USD-stripe`, ...) to destination string (EVM address, BTC address, Stripe Connect account id, IBAN). Use `getPayoutAddress(currency)` for a `Map.get`-style lookup; missing entries return `undefined` (caller decides skip-vs-error).\n- **Contract** (STI base → Estimate, Order, Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder, Cart, LicenseSale): 9 contract types sharing one table. Carries `channelId` (open-ended string — `dtc-web`, `wholesale-b2b`, `pos-store-N`, etc.) so the same model serves DTC checkout, B2B portals, and POS.\n- **WholesaleOrder**: B2B order. Conventional pairing — customer has `customerType: 'wholesale'`, NET-30/60 terms, delivered via wholesale-portal channel.\n- **ProductionOrder**: manufacturing equivalent of a PurchaseOrder — commission your factory to make finished goods. Consumes raw materials per BOM (`@happyvertical/smrt-manufacturing`) and produces SKU stock (`@happyvertical/smrt-inventory`).\n- **Cart**: transient order-in-progress. Same shape as Order; the application promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout instead of copying data between tables.\n- **LicenseSale**: industry-neutral licensing primitive. Carries an *immutable* rights snapshot (`rightsMedium`, `rightsDistributionScope`, `rightsExclusivity`, `rightsDuration`, `rightsTerritory`, `rightsSublicensing`, `rightsDerivatives` — typed `Meta<T>` fields), licensee identity (`licenseeEmail`, optional `licenseeLegalEntity` / `licenseeJurisdiction`), and a signed-PDF reference (`pdfUrl`, `pdfHash`, optional `onChainHashRegistryRef`). Once saved at `ContractStatus.ACCEPTED`, the rights snapshot is frozen — mutating any of the seven rights fields and re-saving throws. The only legal transition out of ACCEPTED is `revoke()` (moves to CANCELLED without touching rights). Useful for stock media, music licensing, code-asset marketplaces, license keys, anywhere rights are sold for a fee.\n- **ContractLineItem**: items on contracts.\n- **Invoice**: status machine `DRAFT → SENT → VIEWED → PARTIAL → PAID` (also OVERDUE, CANCELLED, WRITTEN_OFF). `recognizeRevenue()` creates balanced AR journal entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable).\n- **InvoiceLineItem**: fractional-quantity line items with safe minor-unit validation/recalculation on save; editor-mode recovery and existing-column migration are documented in [invoice quantity persistence](agents/invoice-quantity.md).\n- **Payment** / **PaymentAllocation**: tracks payments against invoices. Status controlled by `Invoice.updatePaymentStatus()`, not Payment model. Carries optional backend-adapter fields for PaymentBackend-routed flows: `backendId` (rail adapter id — `base-usdc`, `btc`, `stripe`, distinct from `externalProvider` which names an accounting sync destination), `backendTxRef` (chain tx hash or gateway settlement id), `nativeAmount` / `nativeCurrency` (what actually arrived), `usdAtQuote` / `usdAtConfirmation` (drift accounting for volatile-currency rails). `Payment.usdDrift()` returns the confirmation - quote delta, or `0` when either side is unset.\n- **PaymentInstrument**: reusable saved payment method (\"card on file\"). Stores only provider references and non-sensitive display metadata (`providerCustomerId`, `providerPaymentMethodId`, brand / last4 / expiry), never raw card data. Use `PaymentInstrumentCollection.setDefaultForCustomer()` to change the default so the single-default-per-customer invariant is enforced; generated create/update routes cannot write `isDefault` directly.\n- **PaymentIntent**: short-lived pre-payment commitment with multi-option semantics. Locks a USD price for a fixed window (default 15 minutes) and lists one or more `PaymentOption`s describing different rails (`backendId`, `currency`, `payTo`, `nativeAmount`, optional `chain` / `memo` / `x402Capable` / `expiresAt`). First option to receive payment wins; the others are implicitly retired. State machine `awaiting_payment → paid → (issued | retired)` plus `expired` and `cancelled`. Mutate via dedicated `markPaid` / `markIssued` / `expire` / `cancel` / `retire` helpers — direct status assignment bypasses the invariant checks. Idempotency via natural key `(tenant_id, offering_ref, licensee_email, idempotency_key)` and `PaymentIntentCollection.getOrCreateByIdempotencyKey()`.\n- **Payout**: operator-to-supplier remittance. Distinct from Payment because direction, status machine, and chain semantics differ. Status machine `pending → sent → confirmed → failed` (failed is terminal but resettable via `resetFromFailed()` after fixing the underlying problem). References source `paymentId` (plain string) and destination `vendorId` (foreign key). Amount invariant `supplierNet === grossAmount - operatorFee` enforced exactly on save (integer minor units, no tolerance — #2401). `PayoutCollection.createFromPayment()` is the typical entry point; it pulls native amount / currency from the source Payment.\n- **Fulfillment** / **FulfillmentLineItem**: shipment/delivery tracking.\n\n## Recipes (#3590)\n\n`src/recipes.ts` declares four minimal `SmrtRecipe`s, emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`: `commerce.customers`\n(Customer), `commerce.vendors` (Vendor), `commerce.sales` (Order, requires\ncustomers), `commerce.purchases` (PurchaseOrder, requires vendors). Headers\nonly; line items, fulfilment, invoices, and payments are later recipes. Each\nhas user-facing help in `src/<name>.recipe.md` (#3591), and the fields those\nrecipes show carry `@field({ description })` text; keep both in plain\nlanguage for end users and update them when a recipe's fields change. See\n[core recipes](../core/agents/recipes.md).\n\nSee [payment allocation integrity](agents/payment-allocations.md) for atomic splits,\nretry identity, parent corrections and supported dialects.\n\n## Ledger Integration\n\n`@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.\n\n## Billing-period close (#3060)\n\n`src/billing/` closes periods from smrt-subscriptions charges; see the README\nfor usage.\n\n- **Port, not provider.** Period close, events, and credit checkout call only\n the `BillingProvider` port (integer minor units). `stripe.ts` adapts\n `@happyvertical/accounting`, which takes currency *major* units; convert with\n `units.ts` and never hand-roll a Stripe call here. `units.ts` exponents are\n an explicit ISO 4217 table matching the SDK's, never `Intl` display digits\n (CLDR differs for IQD, MGA, … and drifts by ICU release). The SDK's\n `*Minor` fields are already ISO minor units. Capabilities a rail may lack\n (`chargeSavedPaymentMethod`, `createSetupCheckout`, `getCheckout`,\n `setDefaultPaymentMethod`, `markInvoiceUncollectible`) are optional port\n methods: detect them by presence, and keep additions optional (#3139).\n `sendAndReconcile` refuses a provider invoice whose subtotal or total\n disagrees with the local invoice.\n- **Replay safety is identity.** Close, invoice, line, payment, allocation,\n and claim ids are `deterministicId()`s; `BillingLineSource` (conflict key\n `source_type, source_id`) is the only double-billing guard, so never bill a\n source without claiming it, and never widen that key. Each close step is\n persisted before the next; `acquireCloseLease()` serializes workers. A\n close that nets to a credit keeps its claims and ends `carried_forward`;\n its subtotal is a `credit_carry_forward` source for the payer's next billed\n close, which marks the carried close `completed`.\n- **Cycles and flat-plan coverage (#3116).** `cycles.ts` is the schedule\n (pure; `BillingAccount.billingAnchorAt`, null = calendar months). A close\n with no period closes each payer's `lastEndedBillingPeriod()`. Flat-plan\n claims are the exception to \"id from the source\": their `sourceId` is\n `<subscriptionId>:seq:<chainSequence>` and their `periodStart/End` is the\n exact time they bill. `claimFlatWindow()` reads the last number, then the\n coverage (claims ending after the window start), claims one gap as number\n n + 1, and retries on conflict; reading in that order is what makes it\n safe. That numbering, not the period, stops overlaps when schedules or\n payers change — never claim a flat plan outside it, and keep both reads\n bounded (no whole-history scans). Each numbered claim is its own invoice\n line keyed `lineKey|periodStart` (a function of the claim alone, so a\n resumed close rebuilds the same line ids). Proration is\n `prorateMinorUnits()` (half up, BigInt) against the period's full cycle.\n- **System tables.** `BillingAccount`, `BillingPeriodClose`, and\n `BillingLineSource` are not tenant-scoped and have no generated surface.\n Collections stay unexported; models are root exports (#3082). Invoices,\n payments, and journals are written in the seller's tenant; payer-owned\n subscriptions and credit grants in a system context, only after\n verification.\n- **Events** use smrt-jobs' delivery inbox under provider\n `<name>-billing:<sellerTenantId>` and a provider-filtered\n `ForgeProjectionRuntime`, so a runtime never claims another seller's\n events; any other runtime sharing the inbox must pass `providers` too. Only\n events this package acts on are stored: foreign checkouts are `ignored`,\n and only `smrt_*` checkout metadata is kept; it is\n HMAC-signed with the webhook secret at creation (empty values are omitted:\n Stripe drops them), so another integration on the same account cannot forge\n a credit purchase or a card setup; credit is granted only when the\n collected total equals it plus provider tax. Checkout amounts come from the\n re-read session (`getCheckout()`), never the Stripe event, whose amounts are\n in Stripe's unit. Rotating the webhook secret strands in-flight checkouts\n (up to 24 h). `observe()` re-reads provider state, so ordering never depends\n on delivery order; the only provider writes in `observe()` are idempotent\n (making a saved card the default). The standing hook runs inside the event\n transaction and must be idempotent.\n- **Card on file and top-ups (#3139).** A saved card is the payer customer's\n default `PaymentInstrument` (seller tenant, id from the provider method).\n `autoChargeInvoices` bills such payers `charge_automatically`, decided once\n and saved as `Invoice.collectionMethod` before the first push, which every\n retry reuses (never recompute it: a lost push response would record a\n method the provider was not asked for, #3190); activation follows `paid`, never send. An auto top-up attempt\n is a `PENDING` `Payment` (`reference` `auto-top-up:<policyId>`, id from the\n policy and `count()` of its attempts) inserted before the charge, so racing\n evaluations derive one id and one charge; the charge key is derived from\n that id. `applyAutoTopUpOutcome()` must run in a transaction and starts with\n a conditional `UPDATE … WHERE status = 'pending'` row lock, so the hook and\n the `payment` webhook settle once; the grant is keyed by the charge key.\n Never settle an attempt outside that function. Taxed accounts are charged\n with provider tax by default (#3194): the attempt row holds the credit\n (the charge subtotal), the tax is journaled to tax payable at settlement,\n and the attempt's tax decision is recorded in its `notes` so a re-drive\n repeats the same charge request (the provider refuses a key reused with\n different tax). `claimAttempt()` is the only place that decides a\n skip and reports it (`onAutoTopUpSkipped`); `cardOnFile()` and period close\n use `defaultCardInstrument()` — never copy that predicate. A provider write-off moves the local invoice to\n `WRITTEN_OFF` (a later payment, by card or on a rail (#3188), is recorded\n and reinstates the payer; the status stays, so \"paid in full\" for a\n written-off invoice is read from its allocations).\n- **`Invoice.providerTaxAmount`** is added to line-item tax; it is\n server-managed (not API-writable). `toAccountingInput()` emits major units.\n- **Payment rails (#3138).** A runtime has one issuing `provider` and any\n number of `paymentProviders` (unique names, one inbox namespace each; a\n provider with `capabilities.issuesInvoices === false` can never issue).\n `observe()` resolves the provider from the delivery namespace, never the\n payload. Crypto rails implement the port over `@happyvertical/payments`'\n `CryptoCheckoutGateway`; never call BTCPay (or any gateway) from here.\n Settlement is decided by the pure `decideAttempt()` from gateway state and\n `paymentPolicy` — the gateway's `settled` is authoritative and nothing here\n counts confirmations. A `BillingPaymentAttempt` (`(provider, checkout_id)`\n deterministic id) records every state and settles once; grants and payments\n are keyed by checkout id. Invoice payments close the issuer's invoice out of\n band in `observe()` (idempotent) before `project()` records the payment, and\n `settlePaidInvoice` skips (records nothing for) a `paidOutOfBand` invoice\n the rail latched with `outOfBandRequestedAt`; other out-of-band closes are\n recorded as `OTHER` payments. Rails never save cards or charge tax: a taxed\n credit purchase on a rail needs an explicit `automaticTax: false`. Exceptions are flags for operators;\n nothing reverses or refunds automatically.\n- **Known limits**: write-offs post no bad-debt journal (AR stays until paid\n or journaled by an operator); discounts still reach Stripe as negative lines (the SDK's\n line `discount` would change the lines a replayed push reconciles, so it\n needs its own migration); `RetailCharge` has no adjustment ledger. Flat\n plans are monthly, in arrears; plan changes are not prorated (#3119).\n\n## Cross-Package References\n\n- `customerId` → `@foreignKey('Customer')` (hard reference within package)\n- `profileId` → plain string to smrt-profiles\n- `arJournalId`, `revenueJournalId` → plain string to smrt-ledgers\n- `skuId` (on `PaymentIntent`, `LicenseSale`) → plain string to smrt-products\n- `paymentId` (on `PaymentIntent`, `Payout`, `LicenseSale`) → plain string to `Payment` (same package, but kept as plain string for cross-model consistency)\n- `vendorId` on `Payout` → `@foreignKey(Vendor)` (hard reference within package)\n- Billing records reference tenants with `@crossPackageRef('@happyvertical/smrt-users:Tenant')` and charges, plan periods, and adjustments by plain `sourceId`\n\n## Gotchas\n\n- **Optional tenancy**: all models `@TenantScoped({ mode: 'optional' })` + nullable tenantId\n- **Currency is integer minor units** (cents, satoshis) — `$19.99` is `1999`, like affiliates. Money is exact, so money fields initialize `= 0`, never `= 0.0`: the integer literal is what maps them to INTEGER columns (BIGINT on fresh PostgreSQL/DuckDB databases). Writing a fractional major-unit value is the bug — the model rejects it via `assertIntegerMinorUnits` on `Invoice`/`Payment`/`PaymentAllocation`/`Payout`/`Contract` before the database sees it, because PostgreSQL would reject it with `22P02` while SQLite's affinity silently stores it. `pnpm --filter @happyvertical/smrt-commerce test:postgres` is the lane that holds the line (#2361, #2401, #2373).\n- **Rates are the opposite**: `InvoiceLineItem.taxRate` and `ContractLineItem.taxRate` are inherently fractional and must initialize `= 0.0`. INTEGER would truncate every rate to 0. `ContractLineItem.quantity` is decimal for the same reason (contracts price fractional hours/weight/bandwidth); `InvoiceLineItem.quantity` is also decimal (six fractional places); existing integer columns require the explicit [quantity migration](agents/invoice-quantity.md).\n- **Rounding happens where a rate meets money, and nowhere else**: `InvoiceLineItem` uses exact shared invoice arithmetic to round fractional quantity × price and then tax to minor units; `ContractLineItem.calculateAmount()` uses `Math.round()` for tax. Everything downstream is exact integer arithmetic, which is why **there are no `EPSILON` tolerances left on any money path** (#2401). `Invoice`, `PaymentAllocation`, `Payout` and `PaymentIntent` compare with `===` / `>` / `>=`. Do not reintroduce a tolerance: against integers it forgives a real one-cent discrepancy rather than float fuzz. (`FulfillmentLineItem`'s quantity epsilon is unrelated — quantities are genuinely decimal there.)\n- **Migrating an existing database to minor units**: `preflightCommerceMoneyMinorUnits(db)` reports, per column, whether it still holds major units and which rows would be rounded or exceed JavaScript's safe-integer range; `migrateCommerceMoneyToMinorUnits(db)` converts them. Both are exported from the package root. The rescale is idempotent via a `_smrt_backfills` marker, changes the column type in place on PostgreSQL/DuckDB (`ALTER … TYPE BIGINT USING round(col * 100)`), and on SQLite rescales values only — SQLite cannot alter a declared type in place, so those columns come back in `declaredTypeChangePending` and need the table-rebuild path (#2370).\n- **`Payment.nativeAmount` is NOT in that migration, and must not be.** Its minor unit is a property of the *row's asset*: satoshis on a BTC rail (×1e8), cents on a fiat or stablecoin rail (×1e2), in one column discriminated only by `nativeCurrency`. A blanket ×100 turns 0.00713 BTC into `1` instead of `713000` sats, and a round 0.01 BTC even passes the preflight's integrality check while being wrong by six orders of magnitude. Convert it in two deliberate steps — normalise each row to its own asset's minor units while the column is still floating-point, then `rescaleMoneyColumnsToMinorUnits(db, COMMERCE_NATIVE_UNIT_COLUMNS, { scale: 1, … })` for the type change. `COMMERCE_NATIVE_UNIT_COLUMNS` is exported with a worked example; step one is deliberately the deployment's to write, because only it knows which `nativeCurrency` values it has used.\n- **`PaymentIntent.paymentOptions[].nativeAmount` has the same per-asset problem inside a JSON column and is likewise not migrated.** An intent's price lock defaults to 15 minutes, so the supported answer is to let open intents expire and be re-quoted: an unconverted option fails the exact `!==` reconciliation against the payment that arrives, which is a loud failure rather than a silently mis-scaled quote.\n- **Range and existing deployments**: fresh PostgreSQL/DuckDB INTEGER columns are BIGINT, and the runtime rejects values outside JavaScript's safe-integer range rather than rounding them. Existing PostgreSQL `int4` columns do not self-repair; widen them through the explicit migration tracked in #2424.\n- **Journals posted from commerce carry minor units**: `Payment.recordPayment()` and `Invoice.recognizeRevenue()` write `amount` / `totalAmount` straight into smrt-ledgers entries. smrt-ledgers is unit-agnostic (its `BALANCE_EPSILON` only checks debits === credits), and integer entries balance exactly, so this is strictly tighter than the major-unit journals commerce used to post.\n- **Invoice controls payment status**: not the Payment model — use `Invoice.updatePaymentStatus()`\n- **Tax rate is external**: no tax rate field on Invoice — rate must be calculated externally\n- **Profile linking**: separate `ProfileCollection.create()` needed to fetch actual Profile object\n- **PaymentIntent natural key**: `conflictColumns: ['tenant_id', 'offering_ref', 'licensee_email', 'idempotency_key']` — a retried `create` with the same tuple upserts the existing row. Use `getOrCreateByIdempotencyKey()` to branch on `{ intent, created }`. Empty natural-key inputs (e.g. blank `idempotencyKey`) disable dedup by design.\n- **Payout state-machine guards**: `markSent` requires a non-empty `backendTxRef`; `markConfirmed` only valid from `SENT`; `markFailed` only valid from `PENDING` / `SENT` (a confirmed payout can't fail — that path is a refund). `resetFromFailed()` is the dedicated escape hatch from `FAILED` back to `PENDING` after an operator fixes the underlying problem; it clears `backendTxRef` so the next attempt picks up a fresh one.\n- **PaymentInstrument defaults**: `isDefault` is domain-managed, not API-writable. Call `setDefaultForCustomer(customerId, instrumentId)` so the target is validated and the customer's other instruments are cleared.\n- **LicenseSale immutability**: rights snapshot freezes on save-with-status-ACCEPTED. The captured snapshot lives in a module-scoped `WeakMap<LicenseSale, string>` so it doesn't interact with the schema or round-trip through `_meta_data`. Drafts (status != ACCEPTED) remain mutable. To \"change\" an issued license: `revoke()` it, then issue a new `LicenseSale` row.\n- **Vendor.payoutAddresses normalization**: the constructor accepts either a `Record<string, string>` or a pre-serialized JSON string. `initialize()` re-normalizes after the framework's option-override pass; `save()` re-normalizes defensively against direct field assignment. Non-string values inside the input map are silently dropped to preserve the typed invariant downstream.\n\n## Commercial UI\n\n`CustomerSelect`/`VendorSelect` are the registered selectors for Customer and\nVendor (`selects` in `src/ui.ts`); models carry `ui.widget` hints. See\n[customer/vendor UI](agents/customer-vendor-ui.md#selectors-3602).\n\nSee [commercial UI composition](agents/commercial-ui.md) for native form recovery, amount compatibility and backend ownership. Package-owned guides under `agents/` ship with the package.\n\n## Quote and estimate UI\n\n`src/svelte/quote-types.ts` separates unparsed draft strings from explicit\nminor-unit revision DTOs. Native row submitters, caller-owned retry identity and\nextension conventions are documented in [agents/quote-ui.md](agents/quote-ui.md).\n\n## Purchasing UI\n\n`PurchaseOrderEditor` preserves allocation IDs and explicit zeroes, including\nretained reduction rows; the server validates review fingerprints before recording.\nSee [agents/purchasing-ui.md](agents/purchasing-ui.md) for caller responsibilities.\n\n## Exact cost billing\n\nPure signed cost billing and explicit tax rounding are available through the\nindependent `cost-billing` subpath; see [contract](agents/cost-billing.md).\n",
19773
19837
  "moduleDocs": [
19774
19838
  {
19775
19839
  "path": "agents/invoice-quantity.md",
19776
19840
  "module": "invoice-quantity",
19777
19841
  "content": "# Invoice quantity persistence and editor recovery\n\nInvoiceLineItem accepts nonnegative decimal quantities with at most six decimal\nplaces. The `1.0` default declares a floating-point quantity column. Unit price,\nflat discount, and calculated amount remain safe integer minor units. Save always\nvalidates resolved fields and recalculates amount; a supplied amount is never\ntrusted. Gross quantity × price and then tax on discounted gross round to minor\nunits, with ties toward positive infinity (including existing negative-price\ncredit lines). Tax is a resolved fraction in [0, 1], with eight decimal places.\nAccounting export retains an explicit zero rate instead of omitting it.\n\n`applyEditorDraft(draft, authorizedContext)` resolves the shared invoice draft\nhelper into description/SKU/quantity/price/flat discount/tax and stores cloned\n`invoiceEditorStateJson` TEXT, accessed through a guarded typed `invoiceEditorState` getter/setter. It does not save.\n`getEditorDraft(authorizedContext)` returns a clone only when currency, inherited\ntax authority, and all source fields still agree. Otherwise it returns null so the\ncaller can reconstruct a flat-discount/explicit-tax editor from resolved fields.\nNormal save clears stale editor state and never derives money from metadata.\nAll other object metadata, authorization, and transaction ownership are unchanged.\n\n## Existing integer columns\n\nRun a backup and pause invoice writers for this explicit migration. With a root\ndatabase handle and its known engine (`sqlite`, `postgres`, or `duckdb`), call\n`preflightInvoiceEditorStorage(db, engine)`, resolve every reported invalid row,\nthen `migrateInvoiceEditorStorage(db, engine)`. The preflight rejects negative,\nnonfinite, unsafe, excess-precision, and numerically non-roundtripping quantities.\nThe migration uses a transaction (and a PostgreSQL writer lock) and never rescales\nor rounds stored values. PostgreSQL widens INTEGER/BIGINT to DOUBLE PRECISION;\nDuckDB widens to DOUBLE. Repeating it leaves an already widened declaration alone.\nAn absent table is reported as absent and is never marked migrated.\n\nSQLite uses the public core table-rebuild planner to change the declaration to\nREAL while preserving existing columns, constraints, indexes, and triggers. If\nincoming foreign keys make that rebuild unsafe, the migration throws with the\nplanner reason and rolls back; it does not disable constraints or lose children.\nResolve that maintenance condition before deploying fractional writers. The same\ntransaction adds the nullable TEXT `invoice_editor_state_json` column with an empty\nstring default on every engine. This column is required for updated model saves.\nAn existing floating quantity column still receives the missing state column.\nNo schema mutation runs implicitly during runtime initialization or model save.\n\nThe actual DuckDB quantity/storage migration is tested. Complete Commerce model\npersistence on DuckDB is currently unsupported because its hard foreign keys use\nON UPDATE CASCADE, which DuckDB cannot preserve; the schema builder refuses that\ncontract. SQLite and PostgreSQL exercise complete save/reload model behavior.\n"
19778
19842
  },
19843
+ {
19844
+ "path": "agents/payment-allocations.md",
19845
+ "module": "payment-allocations",
19846
+ "content": "# Payment allocation integrity\n\n`PaymentAllocation.save()` owns the financial boundary for every direct or\ncollection write. Both payment and invoice must exist, belong to the same\nexact tenant (global is distinct), have the same customer and currency, and\nhave room for the positive safe-integer minor-unit amount. Zero-total invoices\nhave no room. Missing or zero-total invoice fixtures no longer bypass the cap.\nPending payments remain allocatable; this command does not establish settlement\nproof. Generated REST/MCP/CLI allocation writes remain closed. Applications\nmust authorize the actor before invoking these backend commands.\n\nUse `PaymentAllocationCollection.allocatePayment({ paymentId, allocations,\nallocatedBy })` for a split. Each allocation is `{ id, invoiceId, amount }`, with\na caller-generated stable UUID retained across retries. Exact replay\nreuses the existing rows without applying money again; an existing id with\ndifferent parents or amount fails. Replay preserves an already-paid invoice's\nrecorded payment date. Reversing\npayment clears that date; reaching full payment again records the new date.\nThe complete batch and invoice payment-status refresh share one transaction. Invoices must\nalready be in a status that permits payment (normally SENT); WRITTEN_OFF retains\nits terminal status. A failed leg rolls back every leg and parent revision.\nDirect allocation saves continue to leave status refresh to the caller. Allocation\nwrites advance both parent revisions: reload a previously held parent with\n`await parent.loadFromId()` before applying later edits or status refresh.\nStale parent saves continue to fail the core revision guard; no implicit\nrevision refresh bypasses another editor. In-package billing follows this rule.\n\nOnce inserted, allocation tenant/payment/invoice identity is immutable. Amount\ncorrections are capped against the other allocations. Parent saves reject\nchanges to tenant, customer, currency or amount that would invalidate committed\nallocations. A payment may be increased or reduced to its allocated total;\ninvoice totals continue to derive from persisted invoice lines where present.\nStandalone payments remain saveable when allocation storage has not been\nprovisioned. An existing allocation table always participates in validation;\nstorage probes and permission errors propagate instead of disabling the guard.\n\nAll participating writes bind collections to the owning transaction executor.\nThey claim parent revisions before reading balances. Concurrent writers can\nreceive a revision conflict or database serialization failure; reload and retry\nthe complete transaction with the same allocation ids. Never retry only the\nlast leg inside an already failed transaction. PostgreSQL Repeatable Read\nsnapshots fail closed through the same revision writes. SQLite serializes the\nowning write transaction. DuckDB is not a supported commerce allocation store:\nthe existing declared foreign keys require ON UPDATE CASCADE, which its DDL\nboundary explicitly refuses; no weaker schema is substituted.\n"
19847
+ },
19779
19848
  {
19780
19849
  "path": "agents/customer-vendor-ui.md",
19781
19850
  "module": "customer-vendor-ui",
@@ -19794,7 +19863,7 @@
19794
19863
  {
19795
19864
  "path": "agents/purchasing-ui.md",
19796
19865
  "module": "purchasing-ui",
19797
- "content": "# Purchasing and award presentation\n\nPublic `@happyvertical/smrt-commerce/svelte` exports `PurchaseSourceSelector`,\n`PurchaseOrderEditor`, their Props and `Purchase*` DTOs. Registry slots are\n`purchase-source-selector` and `purchase-order-editor`. Interactive playground\nentries cover ordinary amendment and explicit retained-allocation reduction.\n\nThese components use public Commerce PurchaseOrder/Contract as their owning\nfinancial domain without adding a backend command, ledger or fixed approval\npolicy. The caller decides instrument options, source availability, allocation\nlabels, prior commitments, reviewed actuals, floors/ceilings and exact source\nidentity. Construction work packages, accepted budgets, contractor entitlement\nand vendor award policy stay in the consumer. Capability/disabled props only\ncontrol presentation. Every POST needs authoritative server authorization.\n\n`PurchaseSourceSelector` uses a native GET to the supplied action with `name`\n(default `source`) and optional hidden search/context fields. It never writes an\naward. Disabled sources remain visible with caller notices; unknown saved IDs\nremain visible as unavailable. Keep source selection outside a reduction form;\nthe server must reject a downward amendment under an ordinary amendment mode\nand enforce the retained source/instrument during reductions.\n\n`PurchaseOrderEditor` receives `operation` (`create`, `amend`, `reduce`), a\nretained `source`, `currency`, explicit `minorUnitDigits`, raw `values`,\n`instruments`, native POST `action`, and caller-owned `hiddenFields`. Supply\nsource ID, currency, predecessor, tenant and retry IDs in those hidden fields\nunder the exact names your endpoint expects. `names` adapts all built-in fields.\n`labels` adapts approval language; `extensions` and `attachments` compose domain\nfields and authorized Assets evidence without fetching bytes in this component.\n`formAttributes` supports native encoding/ID and caller-owned form-retry Svelte\nattachments; `onsubmit` supports caller transport enhancement. Tokens are never\nminted or rotated here. See the shared [form/action contract](commercial-ui.md)\nand repository `docs/content/form-retry.md`.\n\nAll editable money is **raw major-unit text**, including malformed values and\nempty unknown amounts. The caller parses against the exact currency contract;\nthere is no UI floating-point calculation or implicit blank-to-zero conversion.\nSaved/source/review numbers are safe integer minor units formatted by the\nshared quotation helper. `sourceMinor: null` renders Unknown. Displayed bounds\nare guidance; the server validates floors, ceilings and source eligibility.\n\nEvery allocation posts its stable `id` and amount, even when zero. Mark every\npredecessor allocation `retained: true`. A `reduce` form requires those amounts\nexplicitly and never offers their removal. Supplying fewer predecessor rows is\nnot safe: the server must compare the complete ID set and reject omissions.\nOptional `rowActions` uses native formnovalidate add/remove POSTs carrying all\ncurrent fields; the caller re-renders unchanged rejected strings and IDs.\nNew-row IDs belong to the caller. Row editing never records a commitment.\n\nPressing Enter in an input activates review or record, preserving draft rows.\nExplicit add/remove actions remain available without JavaScript.\nDefault submitter `intent` values are `review`, `record`, `edit`, all configurable.\nOnly a caller-produced `review` snapshot or a retained `retryFingerprint` exposes\nexplicit confirmation. `review.fingerprint` posts under `reviewFingerprint` by\ndefault. After an uncertain outcome, re-render the exact submitted values,\nrequest identity and fingerprint; never fabricate a new intent/token. Confirmation\nis an unchecked required checkbox on native renders and when a new review\nfingerprint arrives in a hydrated form. Its value defaults to\n`yes`. The server must bind fingerprint to all submitted values and authority,\nreject changed/stale review data, and replay identical committed retries.\nRendering a review snapshot or selecting a source never accepts/records anything.\n\nValidation: `pnpm --filter @happyvertical/smrt-commerce exec vitest run --config\nvitest.purchasing.config.ts`, owning build/typecheck/pack checks, and the shared\nquote native/hydrated browser harness with `QUOTE_BROWSER_EXTENSION` pointing at\n`packages/commerce/test-support/purchasing-browser-extension.mjs`. Full repository\nCI/E2E is consolidated at the #3423 release boundary by owner instruction.\n\nInvalid or unsafe display amounts/scales render localized unavailable text.\nStrict money helpers continue to reject malformed data; presentation recovery\ndoes not validate or authorize submitted money.\n\nThe `attachments` snippet renders outside the editor form so an Assets\n`AttachmentPanel`/`AttachmentUpload` keeps its own multipart action and request\nidentity. The attachment region remains disabled while the editor is read-only\nor busy. Use `extensions` only for controls belonging to the editor submission;\nnever place another form there.\n\nComposition regression: run the quote SSR lane, then the maintained quote browser\nhost with `QUOTE_BROWSER_EXTENSION=packages/commerce/test-support/quote-attachments-browser.mjs`.\nIt submits actual Assets upload bytes and the separate editor action with and\nwithout JavaScript at 390px.\n"
19866
+ "content": "# Purchasing and award presentation\n\nPublic `@happyvertical/smrt-commerce/svelte` exports `PurchaseSourceSelector`,\n`PurchaseOrderEditor`, their Props and `Purchase*` DTOs. Registry slots are\n`purchase-source-selector` and `purchase-order-editor`. Interactive playground\nentries cover ordinary amendment and explicit retained-allocation reduction.\n\nThese components use public Commerce PurchaseOrder/Contract as their owning\nfinancial domain without adding a backend command, ledger or fixed approval\npolicy. The caller decides instrument options, source availability, allocation\nlabels, prior commitments, reviewed actuals, floors/ceilings and exact source\nidentity. Construction work packages, accepted budgets, contractor entitlement\nand vendor award policy stay in the consumer. Capability/disabled props only\ncontrol presentation. Every POST needs authoritative server authorization.\n\n`PurchaseSourceSelector` uses a native GET to the supplied action with `name`\n(default `source`) and optional hidden search/context fields. It never writes an\naward. Disabled sources remain visible with caller notices; unknown saved IDs\nremain visible as unavailable. Keep source selection outside a reduction form;\nthe server must reject a downward amendment under an ordinary amendment mode\nand enforce the retained source/instrument during reductions.\n\n`PurchaseOrderEditor` receives `operation` (`create`, `amend`, `reduce`), a\nretained `source`, `currency`, explicit `minorUnitDigits`, raw `values`,\n`instruments`, native POST `action`, and caller-owned `hiddenFields`. Supply\nsource ID, currency, predecessor, tenant and retry IDs in those hidden fields\nunder the exact names your endpoint expects. `names` adapts all built-in fields.\n`labels` adapts approval language; `extensions` and `attachments` compose domain\nfields and authorized Assets evidence without fetching bytes in this component.\n`formAttributes` supports native encoding/ID and caller-owned form-retry Svelte\nattachments; `onsubmit` supports caller transport enhancement. Tokens are never\nminted or rotated here. See the shared [form/action contract](commercial-ui.md)\nand repository `docs/content/form-retry.md`.\n\nAll editable money is **raw major-unit text**, including malformed values and\nempty unknown amounts. The caller parses against the exact currency contract;\nthere is no UI floating-point calculation or implicit blank-to-zero conversion.\nSaved/source/review numbers are safe integer minor units formatted by the\nshared quotation helper. `sourceMinor: null` renders Unknown. Displayed bounds\nare guidance; the server validates floors, ceilings and source eligibility.\n\nEvery allocation posts its stable `id` and amount, even when zero. Mark every\npredecessor allocation `retained: true`. A `reduce` form requires those amounts\nexplicitly and never offers their removal. Supplying fewer predecessor rows is\nnot safe: the server must compare the complete ID set and reject omissions.\nOptional `rowActions` uses native formnovalidate add/remove POSTs carrying all\ncurrent fields; the caller re-renders unchanged rejected strings and IDs.\nNew-row IDs belong to the caller. Row editing never records a commitment.\n\nPressing Enter in an input activates review or record, preserving draft rows.\nExplicit add/remove actions remain available without JavaScript.\nDefault submitter `intent` values are `review`, `record`, `edit`, all configurable.\nOnly a caller-produced `review` snapshot or a retained `retryFingerprint` exposes\nexplicit confirmation. `review.fingerprint` posts under `reviewFingerprint` by\ndefault. After an uncertain outcome, re-render the exact submitted values,\nrequest identity and fingerprint; never fabricate a new intent/token. Confirmation\nis an unchecked required checkbox on native renders and when a new review\nfingerprint arrives in a hydrated form. Its value defaults to\n`yes`. The server must bind fingerprint to all submitted values and authority,\nreject changed/stale review data, and replay identical committed retries.\nRendering a review snapshot or selecting a source never accepts/records anything.\n\nValidation: `pnpm --filter @happyvertical/smrt-commerce exec vitest run --config\nvitest.purchasing.config.ts`, owning build/typecheck/pack checks, and the shared\nquote native/hydrated browser harness with `QUOTE_BROWSER_EXTENSION` pointing at\n`packages/commerce/test-support/purchasing-browser-extension.mjs`. Full repository\nCI/E2E is consolidated at the #3423 release boundary by owner instruction.\n\nInvalid or unsafe display amounts/scales render localized unavailable text.\nStrict money helpers continue to reject malformed data; presentation recovery\ndoes not validate or authorize submitted money.\n\nThe `attachments` snippet renders outside the editor form so an Assets\n`AttachmentPanel`/`AttachmentUpload` keeps its own multipart action and request\nidentity. The attachment region remains disabled while the editor is read-only\nor busy. Use `extensions` only for controls belonging to the editor submission;\nnever place another form there.\n\nComposition regression: run the quote SSR lane, then the maintained quote browser\nhost with `QUOTE_BROWSER_EXTENSION=packages/commerce/test-support/quote-attachments-browser.mjs`.\nIt submits actual Assets upload bytes and the separate editor action with and\nwithout JavaScript at 390px.\n\n## Purchase-order header\n\n`PurchaseOrderHeaderForm` (`purchase-order-header-form` registry slot) edits only\n`vendorId`, `reference`, `notes`, `expectedDate` and `shipTo`. It does not amend\nawards, allocations, totals or status. Supply `action`, caller-authorized `vendors`,\nraw `values`, canonical-key `errors`, optional `message`, `hiddenFields` and\n`names` for an existing endpoint. The native POST submitter defaults to\n`intent=saveHeader`; `intent`, `onsubmit` and `formAttributes` adapt transport.\nReadonly/busy are presentation controls, never authorization. The server must\ncheck active authority, vendor eligibility and any order-status edit policy.\n\nAll header input remains text: even rejected calendar dates survive a rerender.\nThe server validates `expectedDate` as YYYY-MM-DD or empty before saving. An\nunknown retained vendor appears explicitly unavailable but retains its ID on POST\nso a rejected submission cannot silently clear it. The caller must reject an\nunavailable vendor or explicitly permit keeping the existing relationship.\n`shipTo` is a delivery address/instructions snapshot, not a Profile reference.\n`PurchaseOrder.expectedDate` and `.shipTo` use existing STI metadata, default to\nempty strings, and accept `PurchaseOrderOptions`; no table migration is needed.\nExisting Contract fields own the other three values. Header persistence uses the\nexisting SQLite/PostgreSQL Contract schema; DuckDB rejects its existing\nON UPDATE CASCADE foreign keys and is not a supported Contract storage lane. Caller retry identities\nare rendered unchanged; the form itself never persists or retries a mutation.\n\nHeader regression evidence uses the purchasing SSR suite and the shared quote\nbrowser host with `QUOTE_BROWSER_EXTENSION=packages/commerce/test-support/purchasing-header-browser.mjs`.\n"
19798
19867
  },
19799
19868
  {
19800
19869
  "path": "agents/cost-billing.md",
@@ -20639,13 +20708,15 @@
20639
20708
  }
20640
20709
  },
20641
20710
  "help": {
20642
- "markdown": "## Overview\n\nPurchases is where you order from your vendors and keep track of what you have\nbought. Every purchase order belongs to one of your vendors and records the\namounts, the dates and where the order stands.\n\n## Tasks\n\n### Place a purchase order\n\n1. Choose the **New** button above the list.\n2. Enter the **{field:vendorId}** you are buying from. Add them under Vendors\n first so they are on your list.\n3. Enter the **{field:subtotal}** and **{field:taxAmount}**, then the\n **{field:totalAmount}** you expect to pay.\n4. Set the **{field:issueDate}**, and the **{field:dueDate}** for when the\n goods or the payment are due.\n5. Add your own **{field:reference}**, such as the vendor's quote number, so\n you can match it to their paperwork.\n6. Save.\n\n### Follow an order until it arrives\n\n1. Open the purchase order from the list and choose Edit.\n2. Update **{field:status}** as the order moves along, for example from draft to\n sent, accepted and completed.\n3. Use **{field:notes}** to record what the vendor told you, such as delays or\n partial shipments.\n\n### Record the agreement\n\n1. Open the purchase order and choose Edit.\n2. Write down the **{field:terms}** you agreed with the vendor, such as payment\n or delivery conditions.\n3. Set **{field:expiryDate}** if the vendor's offer is only good until a certain\n day.\n",
20711
+ "markdown": "## Overview\n\nPurchases is where you order from your vendors and keep track of what you have\nbought. Every purchase order belongs to one of your vendors and records the\namounts, the dates and where the order stands.\n\n## Tasks\n\n### Place a purchase order\n\n1. Choose the **New** button above the list.\n2. Enter the **{field:vendorId}** you are buying from. Add them under Vendors\n first so they are on your list.\n3. Enter the **{field:subtotal}** and **{field:taxAmount}**, then the\n **{field:totalAmount}** you expect to pay.\n4. Set the **{field:issueDate}**, and the **{field:dueDate}** for when the\n goods or the payment are due.\n5. Add your own **{field:reference}**, such as the vendor's quote number, so\n you can match it to their paperwork.\n6. Enter **{field:expectedDate}** as YYYY-MM-DD if you know when the delivery\n should arrive. Leave it blank until a date is agreed.\n7. Use **{field:shipTo}** for the delivery address and instructions, such as\n which entrance or loading dock to use.\n8. Save.\n\n### Follow an order until it arrives\n\n1. Open the purchase order from the list and choose Edit.\n2. Update **{field:status}** as the order moves along, for example from draft to\n sent, accepted and completed.\n3. Use **{field:notes}** to record what the vendor told you, such as delays or\n partial shipments.\n\n### Record the agreement\n\n1. Open the purchase order and choose Edit.\n2. Write down the **{field:terms}** you agreed with the vendor, such as payment\n or delivery conditions.\n3. Set **{field:expiryDate}** if the vendor's offer is only good until a certain\n day.\n",
20643
20712
  "fieldRefs": [
20644
20713
  "dueDate",
20714
+ "expectedDate",
20645
20715
  "expiryDate",
20646
20716
  "issueDate",
20647
20717
  "notes",
20648
20718
  "reference",
20719
+ "shipTo",
20649
20720
  "status",
20650
20721
  "subtotal",
20651
20722
  "taxAmount",
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ import {
3
+ Form,
4
+ FormGroup,
5
+ Input,
6
+ Select,
7
+ Textarea,
8
+ } from '@happyvertical/smrt-ui/forms';
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { Button } from '@happyvertical/smrt-ui/ui';
11
+ import type { HTMLFormAttributes } from 'svelte/elements';
12
+ import { P } from '../purchasing-i18n.js';
13
+ import type { PurchaseOrderHeaderValues } from '../purchasing-types.js';
14
+ import type { QuoteFormAttributes } from './QuoteEditor.svelte';
15
+
16
+ /** Native header-only form; it never changes source allocations or writes a record. */
17
+ export interface Props {
18
+ /** Caller-owned native POST endpoint. */
19
+ action: string;
20
+ /** Header values supplied or retained by the caller. */
21
+ values?: PurchaseOrderHeaderValues;
22
+ /** Authorized vendor choices; the server must revalidate eligibility. */
23
+ vendors: readonly { value: string; label: string; disabled?: boolean }[];
24
+ /** Field-name overrides for an existing endpoint. */
25
+ names?: Partial<Record<keyof PurchaseOrderHeaderValues, string>>;
26
+ /** Canonical field errors returned by the server. */
27
+ errors?: Partial<Record<keyof PurchaseOrderHeaderValues, string>>;
28
+ /** Form-level server error or uncertain-outcome message. */
29
+ message?: string;
30
+ /** Caller-owned identity, authorization and retry fields. */
31
+ hiddenFields?: readonly { name: string; value: string }[];
32
+ /** Presentation only; server authorization remains mandatory. */
33
+ readonly?: boolean;
34
+ /** Disable the form while transport is in progress. */
35
+ busy?: boolean;
36
+ /** Native submitter identity. */
37
+ intent?: { name: string; value: string };
38
+ /** Localized title and submit label overrides. */
39
+ labels?: { title?: string; submit?: string };
40
+ /** Native encoding/ID and caller-owned Svelte attachments. */
41
+ formAttributes?: QuoteFormAttributes;
42
+ /** Optional transport enhancement; preventDefault belongs to the caller. */
43
+ onsubmit?: HTMLFormAttributes['onsubmit'];
44
+ }
45
+ let {
46
+ action,
47
+ values = {},
48
+ vendors,
49
+ names = {},
50
+ errors = {},
51
+ message,
52
+ hiddenFields = [],
53
+ readonly = false,
54
+ busy = false,
55
+ intent = { name: 'intent', value: 'saveHeader' },
56
+ labels = {},
57
+ formAttributes = {},
58
+ onsubmit,
59
+ }: Props = $props();
60
+ const { t } = useI18n();
61
+ const title = $derived(labels.title ?? t(P['commerce.purchase.header']));
62
+ const textFields = [
63
+ { key: 'reference', label: 'commerce.purchase.reference', multiline: false },
64
+ {
65
+ key: 'expectedDate',
66
+ label: 'commerce.purchase.expected_date',
67
+ multiline: false,
68
+ },
69
+ { key: 'shipTo', label: 'commerce.purchase.ship_to', multiline: true },
70
+ { key: 'notes', label: 'commerce.purchase.notes', multiline: true },
71
+ ] as const;
72
+ </script>
73
+
74
+ <section aria-label={title}>
75
+ <h2>{title}</h2>
76
+ {#if message}<p role="alert">{message}</p>{/if}
77
+ <Form {...formAttributes} {action} method="post" {onsubmit} preventDefault={false}>
78
+ {#each hiddenFields as field}<Input type="hidden" name={field.name} value={field.value} />{/each}
79
+ <fieldset disabled={readonly || busy}>
80
+ <legend>{title}</legend>
81
+ <FormGroup label={t(P['commerce.purchase.vendor'])} error={errors.vendorId}>
82
+ <Select name={names.vendorId ?? 'vendorId'} value={values.vendorId ?? ''} density="touch">
83
+ <option value="">{t(P['commerce.purchase.vendor_prompt'])}</option>
84
+ {#if values.vendorId && !vendors.some(vendor => vendor.value === values.vendorId)}
85
+ <option value={values.vendorId}>{t(P['commerce.purchase.vendor_unavailable'], { id: values.vendorId })}</option>
86
+ {/if}
87
+ {#each vendors as vendor}
88
+ <option value={vendor.value} disabled={vendor.disabled && vendor.value !== values.vendorId}>
89
+ {vendor.disabled ? t(P['commerce.purchase.vendor_unavailable'], { id: vendor.label }) : vendor.label}
90
+ </option>
91
+ {/each}
92
+ </Select>
93
+ </FormGroup>
94
+ {#each textFields as field}
95
+ <FormGroup label={t(P[field.label])} error={errors[field.key]}>
96
+ {#if field.multiline}
97
+ <Textarea name={names[field.key] ?? field.key} value={values[field.key] ?? ''} />
98
+ {:else}
99
+ <Input name={names[field.key] ?? field.key} value={values[field.key] ?? ''} density="touch" />
100
+ {/if}
101
+ </FormGroup>
102
+ {/each}
103
+ {#if !readonly}<Button type="submit" name={intent.name} value={intent.value} density="touch">{labels.submit ?? t(P['commerce.purchase.save_header'])}</Button>{/if}
104
+ </fieldset>
105
+ </Form>
106
+ </section>
@@ -0,0 +1,49 @@
1
+ import type { HTMLFormAttributes } from 'svelte/elements';
2
+ import type { PurchaseOrderHeaderValues } from '../purchasing-types.js';
3
+ import type { QuoteFormAttributes } from './QuoteEditor.svelte';
4
+ /** Native header-only form; it never changes source allocations or writes a record. */
5
+ export interface Props {
6
+ /** Caller-owned native POST endpoint. */
7
+ action: string;
8
+ /** Header values supplied or retained by the caller. */
9
+ values?: PurchaseOrderHeaderValues;
10
+ /** Authorized vendor choices; the server must revalidate eligibility. */
11
+ vendors: readonly {
12
+ value: string;
13
+ label: string;
14
+ disabled?: boolean;
15
+ }[];
16
+ /** Field-name overrides for an existing endpoint. */
17
+ names?: Partial<Record<keyof PurchaseOrderHeaderValues, string>>;
18
+ /** Canonical field errors returned by the server. */
19
+ errors?: Partial<Record<keyof PurchaseOrderHeaderValues, string>>;
20
+ /** Form-level server error or uncertain-outcome message. */
21
+ message?: string;
22
+ /** Caller-owned identity, authorization and retry fields. */
23
+ hiddenFields?: readonly {
24
+ name: string;
25
+ value: string;
26
+ }[];
27
+ /** Presentation only; server authorization remains mandatory. */
28
+ readonly?: boolean;
29
+ /** Disable the form while transport is in progress. */
30
+ busy?: boolean;
31
+ /** Native submitter identity. */
32
+ intent?: {
33
+ name: string;
34
+ value: string;
35
+ };
36
+ /** Localized title and submit label overrides. */
37
+ labels?: {
38
+ title?: string;
39
+ submit?: string;
40
+ };
41
+ /** Native encoding/ID and caller-owned Svelte attachments. */
42
+ formAttributes?: QuoteFormAttributes;
43
+ /** Optional transport enhancement; preventDefault belongs to the caller. */
44
+ onsubmit?: HTMLFormAttributes['onsubmit'];
45
+ }
46
+ declare const PurchaseOrderHeaderForm: import("svelte").Component<Props, {}, "">;
47
+ type PurchaseOrderHeaderForm = ReturnType<typeof PurchaseOrderHeaderForm>;
48
+ export default PurchaseOrderHeaderForm;
49
+ //# sourceMappingURL=PurchaseOrderHeaderForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PurchaseOrderHeaderForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/PurchaseOrderHeaderForm.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,uFAAuF;AACvF,MAAM,WAAW,KAAK;IACpB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,yEAAyE;IACzE,OAAO,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IACzE,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,yBAAyB,EAAE,MAAM,CAAC,CAAC,CAAC;IACjE,qDAAqD;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,yBAAyB,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1D,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iCAAiC;IACjC,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,kDAAkD;IAClD,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,8DAA8D;IAC9D,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CAC3C;AA2ED,QAAA,MAAM,uBAAuB,2CAAwC,CAAC;AACtE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC1E,eAAe,uBAAuB,CAAC"}
@@ -97,4 +97,8 @@ export type PurchaseOrderEditorProps = ComponentProps<typeof PurchaseOrderEditor
97
97
  export type PurchaseSourceSelectorProps = ComponentProps<typeof PurchaseSourceSelector>;
98
98
  export type { PurchaseAllocationDraft, PurchaseDraftValues, PurchaseFieldNames, PurchaseReviewSnapshot, PurchaseSourceOption, } from './purchasing-types.js';
99
99
  export { purchaseFieldNames } from './purchasing-types.js';
100
+ import PurchaseOrderHeaderForm from './components/PurchaseOrderHeaderForm.svelte';
101
+ export { PurchaseOrderHeaderForm };
102
+ export type PurchaseOrderHeaderFormProps = ComponentProps<typeof PurchaseOrderHeaderForm>;
103
+ export type { PurchaseOrderHeaderValues } from './purchasing-types.js';
100
104
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"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"}
@@ -97,3 +97,6 @@ ModuleUIRegistry.register('@happyvertical/smrt-commerce', 'purchase-order-editor
97
97
  ModuleUIRegistry.register('@happyvertical/smrt-commerce', 'purchase-source-selector', PurchaseSourceSelector);
98
98
  ModuleUIRegistry.register('@happyvertical/smrt-commerce', 'customer-select', CustomerSelect);
99
99
  ModuleUIRegistry.register('@happyvertical/smrt-commerce', 'vendor-select', VendorSelect);
100
+ import PurchaseOrderHeaderForm from './components/PurchaseOrderHeaderForm.svelte';
101
+ export { PurchaseOrderHeaderForm };
102
+ ModuleUIRegistry.register('@happyvertical/smrt-commerce', 'purchase-order-header-form', PurchaseOrderHeaderForm);