@happyvertical/smrt-commerce 0.56.1 → 0.56.2

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 (56) hide show
  1. package/README.md +22 -4
  2. package/agents/contract-money.md +6 -0
  3. package/agents/customer-vendor-ui.md +7 -1
  4. package/agents/document-money.md +4 -3
  5. package/agents/tax-summary.md +4 -2
  6. package/dist/chunks/{ContractLineItemCollection-DYjhj_pU.js → ContractLineItemCollection--BrGMtNz.js} +6 -6
  7. package/dist/chunks/ContractLineItemCollection--BrGMtNz.js.map +1 -0
  8. package/dist/chunks/{CustomerCollection-DQIujn4f.js → CustomerCollection-CjGaZSpf.js} +3 -3
  9. package/dist/chunks/{CustomerCollection-DQIujn4f.js.map → CustomerCollection-CjGaZSpf.js.map} +1 -1
  10. package/dist/chunks/{DocumentTaxCollection-BEpoqXAQ.js → DocumentTaxCollection-KOkaQT2M.js} +31 -11
  11. package/dist/chunks/DocumentTaxCollection-KOkaQT2M.js.map +1 -0
  12. package/dist/chunks/{InvoiceCollection-CKQflFjc.js → InvoiceCollection-BUe_8FT1.js} +14 -14
  13. package/dist/chunks/InvoiceCollection-BUe_8FT1.js.map +1 -0
  14. package/dist/chunks/{InvoiceLineItemCollection-CHm9zdqh.js → InvoiceLineItemCollection-CPQGghyd.js} +8 -7
  15. package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js.map +1 -0
  16. package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js → TaxCodeCollection-DF2eaJed.js} +2 -2
  17. package/dist/chunks/{TaxCodeCollection-DOpzW4y8.js.map → TaxCodeCollection-DF2eaJed.js.map} +1 -1
  18. package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.js → TaxComponentCollection-3x24xSNh.js} +3 -3
  19. package/dist/chunks/{TaxComponentCollection-Cs2uXwEl.js.map → TaxComponentCollection-3x24xSNh.js.map} +1 -1
  20. package/dist/chunks/{TaxSettingsCollection-BBTZDH1S.js → TaxSettingsCollection-DYYF-In-.js} +6 -6
  21. package/dist/chunks/{TaxSettingsCollection-BBTZDH1S.js.map → TaxSettingsCollection-DYYF-In-.js.map} +1 -1
  22. package/dist/chunks/{allocation-integrity-DI3G16BN.js → allocation-integrity-Cq0g6CfO.js} +3 -3
  23. package/dist/chunks/{allocation-integrity-DI3G16BN.js.map → allocation-integrity-Cq0g6CfO.js.map} +1 -1
  24. package/dist/chunks/{document-money-DNVhDJyC.js → document-money-oUDvdjOf.js} +10 -10
  25. package/dist/chunks/{document-money-DNVhDJyC.js.map → document-money-oUDvdjOf.js.map} +1 -1
  26. package/dist/chunks/{invoice-posting-BwV8g7z0.js → invoice-posting-CF3fE_Ec.js} +7 -7
  27. package/dist/chunks/{invoice-posting-BwV8g7z0.js.map → invoice-posting-CF3fE_Ec.js.map} +1 -1
  28. package/dist/chunks/{tenant-money-barrier-DjRXgMnX.js → tenant-money-barrier-y6EAa6vY.js} +2 -2
  29. package/dist/chunks/{tenant-money-barrier-DjRXgMnX.js.map → tenant-money-barrier-y6EAa6vY.js.map} +1 -1
  30. package/dist/chunks/{types-LDETvQ1C.js → types-Bbm0Xflc.js} +2 -1
  31. package/dist/chunks/types-Bbm0Xflc.js.map +1 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +36 -23
  35. package/dist/index.js.map +1 -1
  36. package/dist/manifest.json +1057 -151
  37. package/dist/migrations/partyLinks.d.ts.map +1 -1
  38. package/dist/models/Contract.d.ts +10 -4
  39. package/dist/models/Contract.d.ts.map +1 -1
  40. package/dist/models/ContractLineItem.d.ts +13 -5
  41. package/dist/models/ContractLineItem.d.ts.map +1 -1
  42. package/dist/models/DocumentTax.d.ts.map +1 -1
  43. package/dist/models/Invoice.d.ts.map +1 -1
  44. package/dist/models/InvoiceLineItem.d.ts +12 -2
  45. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  46. package/dist/models/index.d.ts +1 -1
  47. package/dist/models/index.d.ts.map +1 -1
  48. package/dist/smrt-knowledge.json +2925 -5578
  49. package/dist/types/index.d.ts +1 -0
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/package.json +13 -13
  52. package/dist/chunks/ContractLineItemCollection-DYjhj_pU.js.map +0 -1
  53. package/dist/chunks/DocumentTaxCollection-BEpoqXAQ.js.map +0 -1
  54. package/dist/chunks/InvoiceCollection-CKQflFjc.js.map +0 -1
  55. package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js.map +0 -1
  56. package/dist/chunks/types-LDETvQ1C.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @happyvertical/smrt-commerce
2
2
 
3
- Commerce models for the s-m-r-t framework. Covers customers, vendors, contracts (9 STI types), invoices with ledger integration, payments, and fulfillment tracking.
3
+ Commerce models for the s-m-r-t framework. Covers customers, vendors, contracts (10 STI types), invoices with ledger integration, payments, and fulfillment tracking.
4
4
 
5
5
  ## Installation
6
6
 
@@ -158,15 +158,32 @@ permission. See the tax-summary contract for registration and migration details.
158
158
 
159
159
  ### Models
160
160
 
161
+ #### Supplier quotations
162
+
163
+ `SupplierQuote` extends `PurchaseContract`, requires `vendorId`, and has
164
+ `ContractType.SUPPLIER_QUOTE`. Create it through `PurchaseContractCollection`
165
+ (or `ContractCollection`) with `_meta_type:
166
+ '@happyvertical/smrt-commerce:SupplierQuote'`. It inherits `expiryDate`,
167
+ `reference`, `notes`, and `terms`; it has no customer relationship. Purchase
168
+ branch reads include quotes and sales branch reads exclude them.
169
+
170
+ Keep received quote revisions and shared quote identity in your application.
171
+ Awarding a quote creates a separate `PurchaseOrder`; accepting or saving a
172
+ quote never converts its identity into an order. Quotes use purchase-side
173
+ money calculation and frozen snapshots, but never contribute incurred purchase
174
+ tax to reports, regardless of status. Existing Estimate and PurchaseOrder
175
+ report eligibility is unchanged.
176
+
161
177
  #### Upgrading contract parties (#3898)
162
178
 
163
179
  `Contract` no longer declares `customerId` or `vendorId`. Narrow a contract to
164
180
  `SalesContract` to read `customerId`, or to `PurchaseContract` to read `vendorId`.
165
- The nine leaf qualified names and the `contracts` table stay the same; a
181
+ The existing leaf qualified names and the `contracts` table stay the same; a
166
182
  `db:migrate --dry-run` against a v0.55.19 schema reports no DDL changes on
167
183
  SQLite or PostgreSQL. Sales leaves no longer accept `vendorId`; purchase leaves
168
184
  no longer accept `customerId`. `Order`, `Estimate`, `Agreement`, `Lease`, and
169
- `WholesaleOrder` require a customer on save. `PurchaseOrder` requires a vendor.
185
+ `WholesaleOrder` require a customer on save. `PurchaseOrder` and `SupplierQuote`
186
+ require a vendor.
170
187
  `Cart`, `LicenseSale`, and `ProductionOrder` retain optional parties on their side.
171
188
 
172
189
  Before upgrading existing data, run the read-only
@@ -203,6 +220,7 @@ This preflight never edits rows; an operator chooses the data correction.
203
220
  | `Order` | Customer purchase order |
204
221
  | `Lease` | Rental or lease agreement |
205
222
  | `Agreement` | Service or maintenance agreement |
223
+ | `SupplierQuote` | Quotation received from a vendor; distinct from an awarded purchase order |
206
224
  | `PurchaseOrder` | Order sent to a vendor/supplier |
207
225
  | `ContractLineItem` | Line item within a contract |
208
226
  | `Invoice` | Billing document with status lifecycle and ledger integration |
@@ -230,7 +248,7 @@ replay, parent revision refresh and compatibility requirements.
230
248
 
231
249
  | Export | Values |
232
250
  |--------|--------|
233
- | `ContractType` | `estimate`, `order`, `lease`, `agreement`, `purchase_order`, `wholesale_order`, `production_order`, `cart`, `license_sale` |
251
+ | `ContractType` | `estimate`, `order`, `lease`, `agreement`, `supplier_quote`, `purchase_order`, `wholesale_order`, `production_order`, `cart`, `license_sale` |
234
252
  | `ContractStatus` | `draft`, `sent`, `accepted`, `declined`, `completed`, `cancelled` |
235
253
  | `CustomerStatus` | `active`, `inactive`, `suspended` |
236
254
  | `VendorStatus` | `active`, `inactive`, `suspended` |
@@ -16,3 +16,9 @@ are refused with `FROZEN_DOCUMENT`.
16
16
  `materializeContractMoneySnapshot` is migration-only. Its caller supplies the
17
17
  already transaction-bound migration owner executor; it verifies that owner,
18
18
  locks/reloads the Contract, and never starts a runtime transaction.
19
+
20
+ `SupplierQuote` uses purchase defaults and recoverability, including during
21
+ legacy-to-v2 migration. Non-draft quotes freeze just like other Contracts, but
22
+ quote snapshots (including custom descendants) are excluded by their hydrated
23
+ STI subtype, not the mutable `contractType` scalar, from report candidates and
24
+ purchase tax facts. Awarding requires a separate PurchaseOrder.
@@ -174,7 +174,13 @@ vitest.selects.config.ts` (also part of `pnpm test:ui`).
174
174
  Minor-unit amounts on Contract, Invoice, their line items, Payment,
175
175
  PaymentAllocation, `Customer.creditLimit` and `Vendor.minimumOrderAmount`
176
176
  carry `ui: { format: 'money' }` (#3718; `widget: 'currency'` stays the
177
- currency-code picker on the text `currency` field). Payout and Payment's
177
+ currency-code picker on the text `currency` field). That includes the
178
+ money-v2 fields (#4011): line `taxAmount`, `discountTotal` on Invoice and
179
+ Contract, `Invoice.creditedAmount`, and `DocumentTax` `taxableMinor` /
180
+ `taxMinor` / `recoverableMinor`; the test asserts every integer named
181
+ `*Amount`, `*Total`, `*Minor` or `subtotal` on those models (and Contract's
182
+ STI subtypes) is hinted. `legacyAmount` is `@internal` migration-rollback
183
+ state, not a user-facing amount, and stays unhinted. Payout and Payment's
178
184
  native/USD drift amounts are not hinted: their currency is a payout rail or
179
185
  fixed USD, not the record's `currency`. The line-item FKs
180
186
  (`ContractLineItem.contractId`, `InvoiceLineItem.invoiceId`,
@@ -19,9 +19,10 @@ and runtime never creates schema.
19
19
  In `v2`, saving an invoice line locks and reloads its parent on the caller's
20
20
  transaction, reloads every sibling, resolves current tax defaults, calculates
21
21
  the document once, and writes line snapshots, `DocumentTax`, and header totals
22
- atomically. Tax resolution order is line override, product, customer, then app
23
- default. Product reads use the products package's collection on that same
24
- transaction executor; tenant-owned and catalog-global products are accepted.
22
+ atomically. Tax resolution order is line override, customer exemption (unless
23
+ the line overrides), product, customer, then app default. Product reads use
24
+ the products package's collection on that same transaction executor;
25
+ tenant-owned and catalog-global products are accepted.
25
26
 
26
27
  The first transition away from `DRAFT`, or setting `arJournalId`, freezes the
27
28
  money document. The invoice and every line receive the same stable
@@ -47,8 +47,10 @@ The Contract branch gets a bounded, hydrated candidate set from
47
47
  owner's descendant classifier before joining only those purchase ids to
48
48
  `document_taxes` on document id and tenant id. This preserves custom
49
49
  `PurchaseContract` descendants without hard-coding contract enums or party
50
- display names. Contract rows include every non-draft, non-cancelled frozen
51
- snapshot, including `DECLINED`; their `issueDate`, reference (falling back to
50
+ display names. Contract rows include non-draft, non-cancelled frozen snapshots,
51
+ including `DECLINED`, except `SupplierQuote` and its descendants; exclusion uses
52
+ the hydrated STI subtype rather than the mutable `contractType` scalar. Their
53
+ `issueDate`, reference (falling back to
52
54
  the id), and vendor party id are read from the frozen Contract. The query only
53
55
  returns stored snapshots and never recalculates tax. A nullable tax-code kind
54
56
  becomes an unassigned-source warning; it never becomes out-of-scope by
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { a as DOCUMENT_MONEY_READ_REBIND, o as DOCUMENT_MONEY_WRITE } from "./TaxSettingsCollection-BBTZDH1S.js";
3
- import { i as withTenantMoneyCollectionRead } from "./tenant-money-barrier-DjRXgMnX.js";
2
+ import { a as DOCUMENT_MONEY_READ_REBIND, o as DOCUMENT_MONEY_WRITE } from "./TaxSettingsCollection-DYYF-In-.js";
3
+ import { i as withTenantMoneyCollectionRead } from "./tenant-money-barrier-y6EAa6vY.js";
4
4
  import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
5
5
  import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
6
6
  //#region src/models/ContractLineItem.ts
@@ -54,7 +54,6 @@ var ContractLineItem = class extends SmrtObject {
54
54
  set taxComponents(value) {
55
55
  this.taxComponentsJson = JSON.stringify(value);
56
56
  }
57
- /** Exact sum of frozen component taxes, in integer minor units. */
58
57
  taxAmount = 0;
59
58
  legacyAmount = null;
60
59
  /** Stable issue/freeze identity, independent of lifecycle updated_at. */
@@ -166,16 +165,17 @@ __decorateClass([field({ description: "The order, quote or agreement this line b
166
165
  __decorateClass([field({ description: "What is being sold or bought on this line." })], ContractLineItem.prototype, "description", 2);
167
166
  __decorateClass([field({ description: "How many." })], ContractLineItem.prototype, "quantity", 2);
168
167
  __decorateClass([field({
169
- description: "The price of one, before tax.",
168
+ description: "The price of one, before discount; includes tax when the document's prices include tax.",
170
169
  ui: { format: "money" }
171
170
  })], ContractLineItem.prototype, "unitPrice", 2);
172
171
  __decorateClass([field({ ui: { format: "money" } })], ContractLineItem.prototype, "discount", 2);
173
172
  __decorateClass([field({ type: "decimal" })], ContractLineItem.prototype, "discountRate", 2);
174
173
  __decorateClass([field({ description: "The stored fractional tax rate for this line (0.05 means 5%)." })], ContractLineItem.prototype, "taxRate", 2);
175
174
  __decorateClass([foreignKey("TaxCode", { nullable: true })], ContractLineItem.prototype, "taxCodeId", 2);
175
+ __decorateClass([field({ ui: { format: "money" } })], ContractLineItem.prototype, "taxAmount", 2);
176
176
  __decorateClass([field({ nullable: true })], ContractLineItem.prototype, "legacyAmount", 2);
177
177
  __decorateClass([field({
178
- description: "The line total.",
178
+ description: "The line amount: net of discount, and including tax only when the document's prices include tax.",
179
179
  ui: { format: "money" }
180
180
  })], ContractLineItem.prototype, "amount", 2);
181
181
  __decorateClass([field({ description: "The product this line is for, if it is one you list." }), crossPackageRef("@happyvertical/smrt-products:Product")], ContractLineItem.prototype, "productId", 2);
@@ -262,4 +262,4 @@ var ContractLineItemCollection = class extends SmrtCollection {
262
262
  //#endregion
263
263
  export { ContractLineItemCollection_exports as n, ContractLineItem as r, ContractLineItemCollection as t };
264
264
 
265
- //# sourceMappingURL=ContractLineItemCollection-DYjhj_pU.js.map
265
+ //# sourceMappingURL=ContractLineItemCollection--BrGMtNz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContractLineItemCollection--BrGMtNz.js","names":[],"sources":["../../src/models/ContractLineItem.ts","../../src/collections/ContractLineItemCollection.ts"],"sourcesContent":["/**\n * ContractLineItem model - individual items on a contract\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n DOCUMENT_MONEY_READ_REBIND,\n DOCUMENT_MONEY_WRITE,\n type DocumentMoneyReadRebindCapability,\n type DocumentMoneyWriteCapability,\n} from '../document-money-internal.js';\nimport type {\n ContractLineItemOptions,\n ContractTaxComponentSnapshot,\n} from '../types/index.js';\n\n/**\n * ContractLineItem represents a single line item on a contract.\n *\n * Line items contain the details of what is being sold/purchased,\n * including quantity, pricing, and optional subscription details.\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * contractId: order.id,\n * description: 'Widget Pro',\n * quantity: 10,\n * unitPrice: 4999, // $49.99 in cents\n * taxRate: 0.08\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ContractLineItem extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n /** @internal Collection read guard returns this snapshot on its root DB. */\n rebindMoneyRead(\n capability: DocumentMoneyReadRebindCapability,\n db: DatabaseInterface,\n ): this {\n if (capability !== DOCUMENT_MONEY_READ_REBIND)\n throw new Error(\n 'Contract line read rebinding requires the owning capability',\n );\n this._db = db;\n this.options.db = db;\n return this;\n }\n\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract\n */\n @field({ description: 'The order, quote or agreement this line belongs to.' })\n @foreignKey('Contract', { role: 'items' })\n contractId: string = '';\n\n /**\n * Item description\n */\n @field({ description: 'What is being sold or bought on this line.' })\n description: string = '';\n\n /**\n * Quantity ordered. Decimal on purpose — contracts price fractional\n * quantities (hours, weight, bandwidth), as do invoice line items.\n */\n @field({ description: 'How many.' })\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis),\n * in the document's pricing mode: tax-inclusive when its `lineAmountType`\n * is `inclusive`.\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so `$49.99` is\n * `4999` (#2401).\n */\n @field({\n description:\n \"The price of one, before discount; includes tax when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2401).\n */\n @field({ ui: { format: 'money' } })\n discount: number = 0;\n\n discountType: 'none' | 'percent' | 'amount' = 'none';\n\n @field({ type: 'decimal' })\n discountRate = '0.0';\n\n /**\n * Tax rate as decimal (e.g., 0.08 for 8%). A rate is inherently fractional,\n * so it stays DECIMAL — INTEGER would truncate every meaningful value.\n */\n @field({\n description:\n 'The stored fractional tax rate for this line (0.05 means 5%).',\n })\n taxRate: number = 0.0;\n\n taxMode: 'inherit' | 'override' = 'inherit';\n\n @foreignKey('TaxCode', { nullable: true })\n taxCodeId: string | null = null;\n\n taxCodeCode: string | null = null;\n\n taxCodeName: string | null = null;\n\n taxComponentsJson: string | null = null;\n\n get taxComponents(): ContractTaxComponentSnapshot[] {\n if (this.taxComponentsJson === null) return [];\n try {\n const value: unknown = JSON.parse(this.taxComponentsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (entry): entry is ContractTaxComponentSnapshot =>\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as ContractTaxComponentSnapshot).taxComponentId ===\n 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).name === 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).percent === 'string' &&\n Number.isSafeInteger(\n (entry as ContractTaxComponentSnapshot).taxMinor,\n ),\n );\n } catch {\n return [];\n }\n }\n\n set taxComponents(value: ContractTaxComponentSnapshot[]) {\n this.taxComponentsJson = JSON.stringify(value);\n }\n\n /** Exact sum of frozen component taxes, in integer minor units. */\n @field({ ui: { format: 'money' } })\n taxAmount = 0;\n\n /**\n * @internal Pre-v2 amount preserved only by the migration path for\n * rollback; not a user-facing amount, so it carries no money hint.\n */\n @field({ nullable: true })\n legacyAmount: number | null = null;\n\n /** Stable issue/freeze identity, independent of lifecycle updated_at. */\n moneySnapshotId = '';\n\n /**\n * Calculated line amount, in integer minor units. Under money v2 it is net\n * of discount and before tax for `exclusive` / `none` documents, and\n * tax-inclusive for `inclusive` ones; {@link taxAmount} holds the tax. The\n * legacy model stores qty * price - discount + tax, derived by\n * {@link ContractLineItem.calculateAmount}, which rounds the fractional tax\n * to a whole minor unit (#2401).\n */\n @field({\n description:\n \"The line amount: net of discount, and including tax only when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n amount: number = 0;\n\n /**\n * Optional product reference (cross-package, plain string)\n */\n @field({\n description: 'The product this line is for, if it is one you list.',\n })\n @crossPackageRef('@happyvertical/smrt-products:Product')\n productId: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Start date (for lease/subscription items)\n */\n startDate: Date | null = null;\n\n /**\n * End date (for lease/subscription items)\n */\n endDate: Date | null = null;\n\n /**\n * Billing period (for subscriptions: 'monthly', 'yearly', etc.)\n */\n billingPeriod: string = '';\n\n /**\n * Sort order within the contract\n */\n sortOrder: number = 0;\n\n constructor(options: ContractLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.discountType !== undefined)\n this.discountType = options.discountType;\n if (options.discountRate !== undefined)\n this.discountRate = options.discountRate;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.taxMode !== undefined) this.taxMode = options.taxMode;\n if (options.taxCodeId !== undefined) this.taxCodeId = options.taxCodeId;\n if (options.taxCodeCode !== undefined)\n this.taxCodeCode = options.taxCodeCode;\n if (options.taxCodeName !== undefined)\n this.taxCodeName = options.taxCodeName;\n if (options.taxComponentsJson !== undefined)\n this.taxComponentsJson = options.taxComponentsJson;\n if (options.taxComponents !== undefined)\n this.taxComponents = structuredClone(options.taxComponents);\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.legacyAmount !== undefined)\n this.legacyAmount = options.legacyAmount;\n if (options.moneySnapshotId !== undefined)\n this.moneySnapshotId = options.moneySnapshotId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.startDate !== undefined) this.startDate = options.startDate;\n if (options.endDate !== undefined) this.endDate = options.endDate;\n if (options.billingPeriod !== undefined)\n this.billingPeriod = options.billingPeriod;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /**\n * Calculate the line amount, in integer minor units.\n *\n * `quantity` is decimal and `taxRate` is a fraction, so the product is not\n * generally a whole number of minor units. Rounding happens here — at the\n * one boundary where a rate meets money — so that every value that reaches a\n * money column is already exact and every downstream comparison can be a\n * plain `===` instead of an epsilon tolerance (#2401).\n */\n calculateAmount(): number {\n const subtotal = this.quantity * this.unitPrice - this.discount;\n const tax = subtotal * this.taxRate;\n return Math.round(subtotal + tax);\n }\n\n override async save(): Promise<this> {\n const { saveContractLineItem } = await import('../contract-money.js');\n return (await saveContractLineItem(this)) as this;\n }\n\n /** @internal Contract aggregate persistence only. */\n async saveDerived(\n capability: DocumentMoneyWriteCapability,\n legacy: boolean,\n ): Promise<this> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n if (legacy) this.amount = this.calculateAmount();\n return (await super.save()) as this;\n }\n\n override async delete(\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n const { deleteContractLineItem } = await import('../contract-money.js');\n await deleteContractLineItem(this, options);\n }\n\n /** @internal Contract aggregate persistence only. */\n async deleteDerived(\n capability: DocumentMoneyWriteCapability,\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n await super.delete(options);\n }\n\n /**\n * Check if this is a subscription/recurring item\n */\n isRecurring(): boolean {\n return !!this.billingPeriod && !!this.startDate;\n }\n\n /**\n * Check if subscription is active\n */\n isSubscriptionActive(): boolean {\n if (!this.isRecurring()) return false;\n if (!this.startDate) return false;\n\n const now = new Date();\n if (now < this.startDate) return false;\n if (this.endDate && now > this.endDate) return false;\n\n return true;\n }\n}\n\nexport default ContractLineItem;\n","/**\n * ContractLineItemCollection - Collection manager for ContractLineItem objects\n * @packageDocumentation\n */\n\nimport {\n SmrtCollection,\n type SmrtListOptions,\n type SmrtObject,\n type SmrtSelectedRow,\n type SmrtSelectField,\n type SmrtStiReadScope,\n type SmrtWhereClause,\n} from '@happyvertical/smrt-core';\nimport {\n getTenantId,\n queryGlobal,\n queryWithGlobals,\n} from '@happyvertical/smrt-tenancy';\nimport { DOCUMENT_MONEY_READ_REBIND } from '../document-money-internal.js';\nimport { ContractLineItem } from '../models/ContractLineItem.js';\nimport { withTenantMoneyCollectionRead } from '../tenant-money-barrier.js';\n\nexport class ContractLineItemCollection extends SmrtCollection<ContractLineItem> {\n static readonly _itemClass = ContractLineItem;\n\n private async withMoneyRead<T>(operation: () => Promise<T>): Promise<T> {\n const rootDb = this.db;\n const tenantId = getTenantId();\n if (!tenantId) return operation();\n const result = await withTenantMoneyCollectionRead(\n rootDb,\n tenantId,\n (boundDb) => this.withDatabase(boundDb, operation),\n );\n if (result instanceof ContractLineItem)\n result.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n else if (Array.isArray(result))\n for (const item of result)\n if (item instanceof ContractLineItem)\n item.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n return result;\n }\n\n override async get(\n filter: string | SmrtWhereClause<ContractLineItem>,\n options: Parameters<SmrtCollection<ContractLineItem>['get']>[1] = {},\n ): Promise<ContractLineItem | null> {\n return this.withMoneyRead(() => super.get(filter, options));\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<ContractLineItem, Select>[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n override async list(options?: undefined): Promise<ContractLineItem[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<ContractLineItem[]>;\n override async list(\n options:\n | (Omit<SmrtListOptions<ContractLineItem>, 'select'> & {\n select?: undefined;\n })\n | undefined,\n ): Promise<SmrtObject[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n return this.withMoneyRead(() => super.list(options));\n }\n\n /**\n * Find contract line items by their parent contract.\n *\n * @param contractId - Contract ID\n * @returns Array of contract line items, sorted by sortOrder\n */\n async findByContract(contractId: string): Promise<ContractLineItem[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contract line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contract line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<ContractLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contract line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contract line items\n */\n async findGlobal(): Promise<ContractLineItem[]> {\n return queryGlobal<ContractLineItem>(this);\n }\n\n /**\n * Find contract line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contract line items\n */\n async findWithGlobals(tenantId: string): Promise<ContractLineItem[]> {\n return queryWithGlobals<ContractLineItem>(\n this,\n tenantId,\n 'ContractLineItem.findWithGlobals',\n );\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { x as Customer } from "./DocumentTaxCollection-BEpoqXAQ.js";
2
- import { r as CustomerStatus } from "./types-LDETvQ1C.js";
1
+ import { S as Customer } from "./DocumentTaxCollection-KOkaQT2M.js";
2
+ import { r as CustomerStatus } from "./types-Bbm0Xflc.js";
3
3
  import { SmrtCollection } from "@happyvertical/smrt-core";
4
4
  import { queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
5
5
  //#region src/collections/CustomerCollection.ts
@@ -96,4 +96,4 @@ var CustomerCollection = class extends SmrtCollection {
96
96
  //#endregion
97
97
  export { CustomerCollection as t };
98
98
 
99
- //# sourceMappingURL=CustomerCollection-DQIujn4f.js.map
99
+ //# sourceMappingURL=CustomerCollection-CjGaZSpf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CustomerCollection-DQIujn4f.js","names":[],"sources":["../../src/collections/CustomerCollection.ts"],"sourcesContent":["/**\n * CustomerCollection - Collection manager for Customer objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Customer } from '../models/Customer.js';\nimport { CustomerStatus } from '../types/index.js';\n\nexport class CustomerCollection extends SmrtCollection<Customer> {\n static readonly _itemClass = Customer;\n\n /**\n * Find customers by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of customers linked to this profile\n */\n async findByProfile(profileId: string): Promise<Customer[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active customers\n *\n * @returns Array of active customers\n */\n async findActive(): Promise<Customer[]> {\n return await this.list({\n where: { status: CustomerStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find customers by status\n *\n * @param status - Customer status\n * @returns Array of customers\n */\n async findByStatus(status: CustomerStatus): Promise<Customer[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a customer for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Customer\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n creditLimit: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Customer> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const customer = await this.create({\n profileId,\n creditLimit: defaults.creditLimit ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: CustomerStatus.ACTIVE,\n });\n await customer.save();\n return customer;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all customers belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of customers for the tenant\n */\n async findByTenant(tenantId: string): Promise<Customer[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global customers (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global customers\n */\n async findGlobal(): Promise<Customer[]> {\n return queryGlobal<Customer>(this);\n }\n\n /**\n * Find customers for a tenant including global customers.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global customers\n */\n async findWithGlobals(tenantId: string): Promise<Customer[]> {\n return queryWithGlobals<Customer>(\n this,\n tenantId,\n 'Customer.findWithGlobals',\n );\n }\n}\n"],"mappings":";;;;;AAUO,IAAM,qBAAN,cAAiC,eAAyB;CAC/D,OAAgB,aAAa;;;;;;;CAQ7B,MAAM,cAAc,WAAwC;EAC1D,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,UAAU;GACnB,SAAS;EACX,CAAC;CACH;;;;;;CAOA,MAAM,aAAkC;EACtC,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,QAAQ,eAAe,OAAO;GACvC,SAAS;EACX,CAAC;CACH;;;;;;;CAQA,MAAM,aAAa,QAA6C;EAC9D,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,OAAO;GAChB,SAAS;EACX,CAAC;CACH;;;;;;;;CASA,MAAM,sBACJ,WACA,WAGK,CAAC,GACa;EACnB,MAAM,WAAW,MAAM,KAAK,cAAc,SAAS;EACnD,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS;EAGlB,MAAM,WAAW,MAAM,KAAK,OAAO;GACjC;GACA,aAAa,SAAS,eAAe;GACrC,cAAc,SAAS,gBAAgB;GACvC,QAAQ,eAAe;EACzB,CAAC;EACD,MAAM,SAAS,KAAK;EACpB,OAAO;CACT;;;;;;;CAYA,MAAM,aAAa,UAAuC;EACxD,OAAO,KAAK,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;CAC1C;;;;;;;;;;CAWA,MAAM,aAAkC;EACtC,OAAO,YAAsB,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,gBAAgB,UAAuC;EAC3D,OAAO,iBACL,MACA,UACA,0BACF;CACF;AACF"}
1
+ {"version":3,"file":"CustomerCollection-CjGaZSpf.js","names":[],"sources":["../../src/collections/CustomerCollection.ts"],"sourcesContent":["/**\n * CustomerCollection - Collection manager for Customer objects\n * @packageDocumentation\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Customer } from '../models/Customer.js';\nimport { CustomerStatus } from '../types/index.js';\n\nexport class CustomerCollection extends SmrtCollection<Customer> {\n static readonly _itemClass = Customer;\n\n /**\n * Find customers by profile ID\n *\n * @param profileId - Profile ID from smrt-profiles\n * @returns Array of customers linked to this profile\n */\n async findByProfile(profileId: string): Promise<Customer[]> {\n return await this.list({\n where: { profileId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find all active customers\n *\n * @returns Array of active customers\n */\n async findActive(): Promise<Customer[]> {\n return await this.list({\n where: { status: CustomerStatus.ACTIVE },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find customers by status\n *\n * @param status - Customer status\n * @returns Array of customers\n */\n async findByStatus(status: CustomerStatus): Promise<Customer[]> {\n return await this.list({\n where: { status },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Get or create a customer for a profile\n *\n * @param profileId - Profile ID\n * @param defaults - Default values if creating\n * @returns Customer\n */\n async getOrCreateForProfile(\n profileId: string,\n defaults: Partial<{\n creditLimit: number;\n paymentTerms: string;\n }> = {},\n ): Promise<Customer> {\n const existing = await this.findByProfile(profileId);\n if (existing.length > 0) {\n return existing[0];\n }\n\n const customer = await this.create({\n profileId,\n creditLimit: defaults.creditLimit ?? 0,\n paymentTerms: defaults.paymentTerms ?? '',\n status: CustomerStatus.ACTIVE,\n });\n await customer.save();\n return customer;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all customers belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of customers for the tenant\n */\n async findByTenant(tenantId: string): Promise<Customer[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global customers (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global customers\n */\n async findGlobal(): Promise<Customer[]> {\n return queryGlobal<Customer>(this);\n }\n\n /**\n * Find customers for a tenant including global customers.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global customers\n */\n async findWithGlobals(tenantId: string): Promise<Customer[]> {\n return queryWithGlobals<Customer>(\n this,\n tenantId,\n 'Customer.findWithGlobals',\n );\n }\n}\n"],"mappings":";;;;;AAUO,IAAM,qBAAN,cAAiC,eAAyB;CAC/D,OAAgB,aAAa;;;;;;;CAQ7B,MAAM,cAAc,WAAwC;EAC1D,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,UAAU;GACnB,SAAS;EACX,CAAC;CACH;;;;;;CAOA,MAAM,aAAkC;EACtC,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,QAAQ,eAAe,OAAO;GACvC,SAAS;EACX,CAAC;CACH;;;;;;;CAQA,MAAM,aAAa,QAA6C;EAC9D,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,OAAO;GAChB,SAAS;EACX,CAAC;CACH;;;;;;;;CASA,MAAM,sBACJ,WACA,WAGK,CAAC,GACa;EACnB,MAAM,WAAW,MAAM,KAAK,cAAc,SAAS;EACnD,IAAI,SAAS,SAAS,GACpB,OAAO,SAAS;EAGlB,MAAM,WAAW,MAAM,KAAK,OAAO;GACjC;GACA,aAAa,SAAS,eAAe;GACrC,cAAc,SAAS,gBAAgB;GACvC,QAAQ,eAAe;EACzB,CAAC;EACD,MAAM,SAAS,KAAK;EACpB,OAAO;CACT;;;;;;;CAYA,MAAM,aAAa,UAAuC;EACxD,OAAO,KAAK,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;CAC1C;;;;;;;;;;CAWA,MAAM,aAAkC;EACtC,OAAO,YAAsB,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,gBAAgB,UAAuC;EAC3D,OAAO,iBACL,MACA,UACA,0BACF;CACF;AACF"}
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { i as CustomerType, n as ContractType, p as VendorStatus, r as CustomerStatus, t as ContractStatus } from "./types-LDETvQ1C.js";
2
+ import { i as CustomerType, n as ContractType, p as VendorStatus, r as CustomerStatus, t as ContractStatus } from "./types-Bbm0Xflc.js";
3
3
  import { MoneyValidationError, assertIntegerMinorUnits } from "../money.js";
4
- import { a as DOCUMENT_MONEY_READ_REBIND, o as DOCUMENT_MONEY_WRITE } from "./TaxSettingsCollection-BBTZDH1S.js";
5
- import { i as withTenantMoneyCollectionRead } from "./tenant-money-barrier-DjRXgMnX.js";
4
+ import { a as DOCUMENT_MONEY_READ_REBIND, o as DOCUMENT_MONEY_WRITE } from "./TaxSettingsCollection-DYYF-In-.js";
5
+ import { i as withTenantMoneyCollectionRead } from "./tenant-money-barrier-y6EAa6vY.js";
6
6
  import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
7
7
  import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
8
8
  //#region src/party-contacts.ts
@@ -563,7 +563,7 @@ function classifyContractPurchase(contract) {
563
563
  return "vendorId" in contract;
564
564
  }
565
565
  function isContractSnapshotReportEligible(contract) {
566
- return contract.status !== ContractStatus.DRAFT && contract.status !== ContractStatus.CANCELLED;
566
+ return !(contract instanceof SupplierQuote) && contract.status !== ContractStatus.DRAFT && contract.status !== ContractStatus.CANCELLED;
567
567
  }
568
568
  var Contract = class extends SmrtObject {
569
569
  /** @internal Collection read guard returns this snapshot on its root DB. */
@@ -577,7 +577,6 @@ var Contract = class extends SmrtObject {
577
577
  contractType = ContractType.ORDER;
578
578
  status = ContractStatus.DRAFT;
579
579
  subtotal = 0;
580
- /** Informational sum of resolved line discounts, in integer minor units. */
581
580
  discountTotal = 0;
582
581
  taxAmount = 0;
583
582
  totalAmount = 0;
@@ -761,6 +760,7 @@ __decorateClass$1([field({
761
760
  description: "The amount before tax.",
762
761
  ui: { format: "money" }
763
762
  })], Contract.prototype, "subtotal", 2);
763
+ __decorateClass$1([field({ ui: { format: "money" } })], Contract.prototype, "discountTotal", 2);
764
764
  __decorateClass$1([field({
765
765
  description: "The tax charged.",
766
766
  ui: { format: "money" }
@@ -815,7 +815,7 @@ var PurchaseContract = class extends Contract {
815
815
  }
816
816
  };
817
817
  __publicField$1(PurchaseContract, "__smrtPackage__", "@happyvertical/smrt-commerce");
818
- __decorateClass$1([foreignKey(Vendor, { description: "The vendor a purchase order is placed with." })], PurchaseContract.prototype, "vendorId", 2);
818
+ __decorateClass$1([foreignKey(Vendor, { description: "The vendor supplying this quote or purchase contract." })], PurchaseContract.prototype, "vendorId", 2);
819
819
  PurchaseContract = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({ abstract: true })], PurchaseContract);
820
820
  var Estimate = class extends SalesContract {
821
821
  customerId = null;
@@ -861,6 +861,17 @@ var Agreement = class extends SalesContract {
861
861
  __publicField$1(Agreement, "__smrtPackage__", "@happyvertical/smrt-commerce");
862
862
  __decorateClass$1([foreignKey(Customer, { required: true })], Agreement.prototype, "customerId", 2);
863
863
  Agreement = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt()], Agreement);
864
+ var SupplierQuote = class extends PurchaseContract {
865
+ vendorId = null;
866
+ contractType = ContractType.SUPPLIER_QUOTE;
867
+ constructor(options = {}) {
868
+ super(options);
869
+ if (options.vendorId !== void 0) this.vendorId = options.vendorId;
870
+ }
871
+ };
872
+ __publicField$1(SupplierQuote, "__smrtPackage__", "@happyvertical/smrt-commerce");
873
+ __decorateClass$1([foreignKey(Vendor, { required: true })], SupplierQuote.prototype, "vendorId", 2);
874
+ SupplierQuote = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt()], SupplierQuote);
864
875
  var PurchaseOrder = class extends PurchaseContract {
865
876
  vendorId = null;
866
877
  contractType = ContractType.PURCHASE_ORDER;
@@ -1386,9 +1397,18 @@ __decorateClass([field({
1386
1397
  nullable: true
1387
1398
  })], DocumentTax.prototype, "percent", 2);
1388
1399
  __decorateClass([field({ nullable: true })], DocumentTax.prototype, "liabilityAccountId", 2);
1389
- __decorateClass([field({ description: "Tax-exclusive base in integer minor units." })], DocumentTax.prototype, "taxableMinor", 2);
1390
- __decorateClass([field({ description: "Tax total in integer minor units." })], DocumentTax.prototype, "taxMinor", 2);
1391
- __decorateClass([field({ description: "Recoverable purchase tax in integer minor units." })], DocumentTax.prototype, "recoverableMinor", 2);
1400
+ __decorateClass([field({
1401
+ description: "Tax-exclusive base in integer minor units.",
1402
+ ui: { format: "money" }
1403
+ })], DocumentTax.prototype, "taxableMinor", 2);
1404
+ __decorateClass([field({
1405
+ description: "Tax total in integer minor units.",
1406
+ ui: { format: "money" }
1407
+ })], DocumentTax.prototype, "taxMinor", 2);
1408
+ __decorateClass([field({
1409
+ description: "Recoverable purchase tax in integer minor units.",
1410
+ ui: { format: "money" }
1411
+ })], DocumentTax.prototype, "recoverableMinor", 2);
1392
1412
  DocumentTax = __decorateClass([TenantScoped({ mode: "required" }), smrt({
1393
1413
  tableName: "document_taxes",
1394
1414
  conflictColumns: [
@@ -1634,6 +1654,6 @@ var DocumentTaxCollection = class extends SmrtCollection {
1634
1654
  }
1635
1655
  };
1636
1656
  //#endregion
1637
- export { savePartyContacts as A, PARTY_CONTACT_MAX_LENGTHS as C, applyPartyContactIntent as D, PartyContactValidationError as E, normalizePartyContacts as O, DEFAULT_PARTY_CONTACT_FIELD_NAMES as S, PartyContactPayloadError as T, SalesContract as _, SalesContractCollection as a, Vendor as b, Cart as c, Lease as d, LicenseSale as f, PurchaseOrder as g, PurchaseContract as h, ContractCollection as i, validatePartyContactRows as j, readPartyContactRows as k, Contract as l, ProductionOrder as m, DocumentTaxCollection_exports as n, PurchaseContractCollection as o, Order as p, DocumentTax as r, Agreement as s, DocumentTaxCollection as t, Estimate as u, WholesaleOrder as v, PartyContactPartyNotFoundError as w, Customer as x, classifyContractPurchase as y };
1657
+ export { readPartyContactRows as A, DEFAULT_PARTY_CONTACT_FIELD_NAMES as C, PartyContactValidationError as D, PartyContactPayloadError as E, validatePartyContactRows as M, applyPartyContactIntent as O, Customer as S, PartyContactPartyNotFoundError as T, SalesContract as _, SalesContractCollection as a, classifyContractPurchase as b, Cart as c, Lease as d, LicenseSale as f, PurchaseOrder as g, PurchaseContract as h, ContractCollection as i, savePartyContacts as j, normalizePartyContacts as k, Contract as l, ProductionOrder as m, DocumentTaxCollection_exports as n, PurchaseContractCollection as o, Order as p, DocumentTax as r, Agreement as s, DocumentTaxCollection as t, Estimate as u, SupplierQuote as v, PARTY_CONTACT_MAX_LENGTHS as w, Vendor as x, WholesaleOrder as y };
1638
1658
 
1639
- //# sourceMappingURL=DocumentTaxCollection-BEpoqXAQ.js.map
1659
+ //# sourceMappingURL=DocumentTaxCollection-KOkaQT2M.js.map