@happyvertical/smrt-commerce 0.57.3 → 0.57.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -0
- package/README.md +130 -0
- package/agents/accounting-snapshot-mapper.md +11 -0
- package/agents/contract-billing-schedules.md +67 -0
- package/agents/contract-money.md +81 -0
- package/agents/customer-funds.md +96 -0
- package/agents/historical-sales.md +132 -0
- package/agents/money-calculator.md +36 -0
- package/agents/money-model.md +6 -3
- package/agents/observed-purchase-prices.md +37 -0
- package/agents/payment-allocations.md +59 -0
- package/agents/received-sales-contract-money.md +38 -0
- package/dist/accounting-snapshot.d.ts +8 -1
- package/dist/accounting-snapshot.d.ts.map +1 -1
- package/dist/allocation-integrity.d.ts.map +1 -1
- package/dist/chunks/{ContractLineItemCollection-BZcrlrrY.js → ContractLineItemCollection-J4xtRtzE.js} +6 -6
- package/dist/chunks/{ContractLineItemCollection-BZcrlrrY.js.map → ContractLineItemCollection-J4xtRtzE.js.map} +1 -1
- package/dist/chunks/DocumentTaxCollection-C42x0YLW.js +4385 -0
- package/dist/chunks/DocumentTaxCollection-C42x0YLW.js.map +1 -0
- package/dist/chunks/HistoricalCustomerFundsCollections-B5x8gTW3.js +170 -0
- package/dist/chunks/HistoricalCustomerFundsCollections-B5x8gTW3.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-cinzAqt6.js → InvoiceCollection-BIcge9Wl.js} +131 -18
- package/dist/chunks/InvoiceCollection-BIcge9Wl.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-DaBOPG9c.js → TaxCodeCollection-RWdVaheF.js} +3 -4
- package/dist/chunks/TaxCodeCollection-RWdVaheF.js.map +1 -0
- package/dist/chunks/{TaxComponentCollection-C4zxnyVu.js → TaxComponentCollection-Dt2jeWiC.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-C4zxnyVu.js.map → TaxComponentCollection-Dt2jeWiC.js.map} +1 -1
- package/dist/chunks/{allocation-integrity-WmWyXhtG.js → allocation-integrity-DJ7-LY1j.js} +7 -7
- package/dist/chunks/{allocation-integrity-WmWyXhtG.js.map → allocation-integrity-DJ7-LY1j.js.map} +1 -1
- package/dist/chunks/{contract-money-EuToOghh.js → contract-money-DSRTMoXm.js} +45 -12
- package/dist/chunks/contract-money-DSRTMoXm.js.map +1 -0
- package/dist/chunks/customer-funds-internal-BQzoJXK5.js +24 -0
- package/dist/chunks/customer-funds-internal-BQzoJXK5.js.map +1 -0
- package/dist/chunks/{detect-CY9ufaFb.js → detect-DAE-_NAk.js} +5 -2
- package/dist/chunks/{detect-CY9ufaFb.js.map → detect-DAE-_NAk.js.map} +1 -1
- package/dist/chunks/historical-commerce-internal-C9ButfuH.js +18 -0
- package/dist/chunks/historical-commerce-internal-C9ButfuH.js.map +1 -0
- package/dist/chunks/{invoice-posting-C2i5dOVZ.js → invoice-posting-BeTPw3h9.js} +5 -5
- package/dist/chunks/invoice-posting-BeTPw3h9.js.map +1 -0
- package/dist/chunks/retention-accounting-Brdj9x-V.js +2323 -0
- package/dist/chunks/retention-accounting-Brdj9x-V.js.map +1 -0
- package/dist/chunks/retention-accounting-internal-CBQRvd74.js +17 -0
- package/dist/chunks/retention-accounting-internal-CBQRvd74.js.map +1 -0
- package/dist/chunks/{tenant-money-barrier-6TlqUeVj.js → tenant-money-barrier-DoF6lR8h.js} +5 -5
- package/dist/chunks/{tenant-money-barrier-6TlqUeVj.js.map → tenant-money-barrier-DoF6lR8h.js.map} +1 -1
- package/dist/chunks/types-Bbm0Xflc.js.map +1 -1
- package/dist/collections/DocumentTaxCollection.d.ts +4 -1
- package/dist/collections/DocumentTaxCollection.d.ts.map +1 -1
- package/dist/collections/HistoricalCustomerFundsCollections.d.ts +10 -0
- package/dist/collections/HistoricalCustomerFundsCollections.d.ts.map +1 -0
- package/dist/collections/HistoricalSalesLineCollection.d.ts +6 -0
- package/dist/collections/HistoricalSalesLineCollection.d.ts.map +1 -0
- package/dist/collections/PaymentAllocationCollection.d.ts +2 -6
- package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -1
- package/dist/collections/PurchasePriceObservationCollection.d.ts +6 -0
- package/dist/collections/PurchasePriceObservationCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +1 -0
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/contract-billing-schedule.d.ts +83 -0
- package/dist/contract-billing-schedule.d.ts.map +1 -0
- package/dist/contract-money.d.ts +2 -0
- package/dist/contract-money.d.ts.map +1 -1
- package/dist/customer-funds-internal.d.ts +6 -0
- package/dist/customer-funds-internal.d.ts.map +1 -0
- package/dist/customer-funds.d.ts +108 -0
- package/dist/customer-funds.d.ts.map +1 -0
- package/dist/document-money.d.ts +11 -0
- package/dist/document-money.d.ts.map +1 -1
- package/dist/historical-commerce-internal.d.ts +3 -0
- package/dist/historical-commerce-internal.d.ts.map +1 -0
- package/dist/historical-customer-funds-types.d.ts +113 -0
- package/dist/historical-customer-funds-types.d.ts.map +1 -0
- package/dist/historical-customer-funds.d.ts +23 -0
- package/dist/historical-customer-funds.d.ts.map +1 -0
- package/dist/historical-funds-internal.d.ts +5 -0
- package/dist/historical-funds-internal.d.ts.map +1 -0
- package/dist/historical-sales-money.d.ts +22 -0
- package/dist/historical-sales-money.d.ts.map +1 -0
- package/dist/historical-sales-reports.d.ts +31 -0
- package/dist/historical-sales-reports.d.ts.map +1 -0
- package/dist/historical-sales-types.d.ts +106 -0
- package/dist/historical-sales-types.d.ts.map +1 -0
- package/dist/historical-sales-validation.d.ts +12 -0
- package/dist/historical-sales-validation.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1668 -1643
- package/dist/index.js.map +1 -1
- package/dist/invoice-accounting-sync.d.ts +0 -1
- package/dist/invoice-accounting-sync.d.ts.map +1 -1
- package/dist/invoice-mcp.d.ts.map +1 -1
- package/dist/invoice-posting.d.ts.map +1 -1
- package/dist/invoice-retention.d.ts +16 -0
- package/dist/invoice-retention.d.ts.map +1 -0
- package/dist/invoices.d.ts +2 -0
- package/dist/invoices.d.ts.map +1 -1
- package/dist/invoices.js +2 -2
- package/dist/manifest.json +7563 -4419
- package/dist/models/Contract.d.ts +14 -0
- package/dist/models/Contract.d.ts.map +1 -1
- package/dist/models/DocumentTax.d.ts +2 -0
- package/dist/models/DocumentTax.d.ts.map +1 -1
- package/dist/models/HistoricalCustomerFunds.d.ts +36 -0
- package/dist/models/HistoricalCustomerFunds.d.ts.map +1 -0
- package/dist/models/HistoricalCustomerFundsAllocation.d.ts +32 -0
- package/dist/models/HistoricalCustomerFundsAllocation.d.ts.map +1 -0
- package/dist/models/HistoricalSalesLine.d.ts +29 -0
- package/dist/models/HistoricalSalesLine.d.ts.map +1 -0
- package/dist/models/Invoice.d.ts +10 -1
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/Payment.d.ts +7 -1
- package/dist/models/Payment.d.ts.map +1 -1
- package/dist/models/PaymentAllocation.d.ts +3 -1
- package/dist/models/PaymentAllocation.d.ts.map +1 -1
- package/dist/models/PaymentIntent.d.ts.map +1 -1
- package/dist/models/Payout.d.ts.map +1 -1
- package/dist/models/PurchasePriceObservation.d.ts +15 -0
- package/dist/models/PurchasePriceObservation.d.ts.map +1 -0
- package/dist/models/TaxCode.d.ts +1 -1
- package/dist/models/TaxCode.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/money.d.ts +5 -1
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +62 -15
- package/dist/money.js.map +1 -1
- package/dist/observed-purchase-price-internal.d.ts +3 -0
- package/dist/observed-purchase-price-internal.d.ts.map +1 -0
- package/dist/observed-purchase-price.d.ts +71 -0
- package/dist/observed-purchase-price.d.ts.map +1 -0
- package/dist/received-purchase-money.d.ts +87 -0
- package/dist/received-purchase-money.d.ts.map +1 -0
- package/dist/recipes.d.ts +4 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/retention-accounting-internal.d.ts +3 -0
- package/dist/retention-accounting-internal.d.ts.map +1 -0
- package/dist/retention-accounting.d.ts +164 -0
- package/dist/retention-accounting.d.ts.map +1 -0
- package/dist/retention-money.d.ts +43 -0
- package/dist/retention-money.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +1866 -113
- package/dist/tax/cookbook.d.ts.map +1 -1
- package/dist/tax-summary.d.ts +1 -0
- package/dist/tax-summary.d.ts.map +1 -1
- package/dist/types/index.d.ts +20 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +16 -16
- package/dist/chunks/CustomerCollection-BIPH2ZUx.js +0 -99
- package/dist/chunks/CustomerCollection-BIPH2ZUx.js.map +0 -1
- package/dist/chunks/DocumentTaxCollection-DDzlywMW.js +0 -1666
- package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +0 -1
- package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +0 -1
- package/dist/chunks/TaxCodeCollection-DaBOPG9c.js.map +0 -1
- package/dist/chunks/VendorCollection-D6ZcUYy8.js +0 -100
- package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +0 -1
- package/dist/chunks/calculations-C9daQLsh.js +0 -82
- package/dist/chunks/calculations-C9daQLsh.js.map +0 -1
- package/dist/chunks/contract-money-EuToOghh.js.map +0 -1
- package/dist/chunks/document-money-DV5t4vlK.js +0 -1186
- package/dist/chunks/document-money-DV5t4vlK.js.map +0 -1
- package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvoiceCollection-BIcge9Wl.js","names":["field","seq"],"sources":["../../src/retention-money.ts","../../src/models/Invoice.ts","../../src/collections/InvoiceCollection.ts"],"sourcesContent":["/** Explicit retained-first settlement policy; no jurisdiction defaults. */\nimport { MoneyValidationError } from './money.js';\n\nexport interface InvoiceRetentionEvidence {\n requestId: string;\n policyVersion: string;\n evidenceRef: string;\n actorId: string;\n reason: string;\n /** Explicit business calendar date for original or current evidence; recordedAt stays actual record time. */\n effectiveOn?: string;\n sameDayCreditOrder?: 'credit-before-release' | 'release-before-credit' | null;\n}\nexport interface InvoiceRetentionRelease extends InvoiceRetentionEvidence {\n amountMinor: number;\n recordedAt: string;\n}\nexport interface InvoiceRetentionState extends InvoiceRetentionEvidence {\n version: 1;\n currency: string;\n creditPolicy: 'retained-first';\n retainedMinor: number;\n recordedAt: string;\n releases: InvoiceRetentionRelease[];\n}\nexport interface InvoicePayableFacts {\n grossMinor: number;\n retainedMinor: number;\n releasedMinor: number;\n creditedRetentionMinor: number;\n retainedRemainingMinor: number;\n allocationCapMinor: number;\n grossBalanceMinor: number;\n currentlyDueMinor: number;\n}\nfunction invalid(message: string): never {\n throw new MoneyValidationError('INVALID_MONEY_INPUT', message);\n}\nexport function assertRetentionMinor(amount: number, label: string): void {\n if (!Number.isSafeInteger(amount))\n invalid(`${label} must be a safe integer minor-unit amount`);\n}\nexport function validateRetentionEvidence(\n input: InvoiceRetentionEvidence,\n): void {\n for (const key of [\n 'requestId',\n 'policyVersion',\n 'evidenceRef',\n 'actorId',\n 'reason',\n ] as const) {\n const value = input[key];\n if (typeof value !== 'string' || !value || value.trim() !== value)\n invalid(`Retention ${key} must be nonempty trimmed text`);\n }\n}\nexport function parseInvoiceRetention(\n json: string,\n): InvoiceRetentionState | null {\n if (json === '') return null;\n let state: InvoiceRetentionState;\n try {\n state = JSON.parse(json);\n } catch {\n return invalid('Invalid retention state JSON');\n }\n if (\n !state ||\n state.version !== 1 ||\n state.creditPolicy !== 'retained-first' ||\n !Array.isArray(state.releases)\n )\n invalid('Unsupported retention state or credit policy');\n validateRetentionEvidence(state);\n if (!/^[A-Z]{3}$/.test(state.currency))\n invalid('Retention currency is required');\n const keys = new Set([state.requestId]);\n for (const event of [state, ...state.releases]) {\n validateRetentionEvidence(event);\n if (\n typeof event.recordedAt !== 'string' ||\n !Number.isFinite(Date.parse(event.recordedAt))\n )\n invalid('Retention event timestamp is required');\n if (\n event.effectiveOn !== undefined &&\n (!/^\\d{4}-\\d{2}-\\d{2}$/.test(event.effectiveOn) ||\n !Number.isFinite(Date.parse(event.effectiveOn)) ||\n new Date(event.effectiveOn).toISOString().slice(0, 10) !==\n event.effectiveOn)\n )\n invalid('Original retention event requires an exact calendar date');\n if (\n event.sameDayCreditOrder !== undefined &&\n event.sameDayCreditOrder !== null &&\n event.sameDayCreditOrder !== 'release-before-credit' &&\n event.sameDayCreditOrder !== 'credit-before-release'\n )\n invalid('Unsupported original retention release/credit ordering');\n if (event !== state) {\n if (keys.has(event.requestId))\n invalid('Duplicate retention request identity');\n keys.add(event.requestId);\n }\n }\n return state;\n}\nexport function calculateInvoicePayable(input: {\n totalAmount: number;\n creditedAmount: number;\n amountPaid: number;\n currency: string;\n retentionStateJson?: string;\n}): InvoicePayableFacts {\n for (const [name, amount] of Object.entries({\n gross: input.totalAmount,\n credits: input.creditedAmount,\n paid: input.amountPaid,\n })) {\n assertRetentionMinor(amount, name);\n if (amount < 0) invalid(`${name} cannot be negative`);\n }\n if (\n input.creditedAmount > input.totalAmount ||\n input.amountPaid > input.totalAmount\n )\n invalid('Credits or payments exceed invoice gross');\n const state = parseInvoiceRetention(input.retentionStateJson ?? '');\n const retained = state?.retainedMinor ?? 0;\n assertRetentionMinor(retained, 'Retained amount');\n if (retained < 0 || retained > input.totalAmount)\n invalid('Retention exceeds invoice gross');\n if (state && state.currency !== input.currency)\n invalid('Retention currency does not match invoice');\n let released = 0n;\n for (const event of state?.releases ?? []) {\n assertRetentionMinor(event.amountMinor, 'Released amount');\n if (event.amountMinor <= 0) invalid('Release amount must be positive');\n released += BigInt(event.amountMinor);\n }\n if (released > BigInt(retained)) invalid('Releases exceed retained amount');\n const unreleased = retained - Number(released);\n const creditedRetention = Math.min(unreleased, input.creditedAmount);\n const remaining = unreleased - creditedRetention;\n const cap = Math.max(0, input.totalAmount - input.creditedAmount - remaining);\n return {\n grossMinor: input.totalAmount,\n retainedMinor: retained,\n releasedMinor: Number(released),\n creditedRetentionMinor: creditedRetention,\n retainedRemainingMinor: remaining,\n allocationCapMinor: cap,\n grossBalanceMinor:\n input.totalAmount - input.creditedAmount - input.amountPaid,\n currentlyDueMinor: Math.max(0, cap - input.amountPaid),\n };\n}\n","/**\n * Invoice model - billing document sent to customers\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 type { Journal } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n assertAllocationParent,\n claimAllocationParent,\n} from '../allocation-integrity.js';\nimport {\n DOCUMENT_MONEY_READ_REBIND,\n DOCUMENT_MONEY_WRITE,\n type DocumentMoneyReadRebindCapability,\n type DocumentMoneyWriteCapability,\n} from '../document-money-internal.js';\nimport type { FrozenInvoiceAccountingOptions } from '../invoice-accounting-sync.js';\nimport { assertIntegerMinorUnits, MoneyValidationError } from '../money.js';\nimport { calculateInvoicePayable } from '../retention-money.js';\nimport {\n type AccountingInvoiceInput,\n type CancelRevenueOptions,\n type InvoiceCollectionMethod,\n type InvoiceKind,\n type InvoiceOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n type TaxRegistrationOptions,\n} from '../types/index.js';\n\n/*\n * There is deliberately no `INVOICE_EPSILON` here any more (#2401).\n *\n * Every amount on this model is integer minor units, so `subtotal + taxAmount`\n * is exact, the sum of allocations is exact, and every comparison below is a\n * plain integer comparison. The old `0.01` tolerance existed because the values\n * were floats; keeping it against integers would silently forgive a genuine\n * one-cent discrepancy, and the \"snap `totalAmount` to `subtotal + taxAmount`\n * afterwards\" workaround it required (to stay inside the tighter smrt-ledgers\n * `BALANCE_EPSILON`) is gone with it — exact arithmetic needs no snap.\n */\n\n/**\n * Legal status transitions for an Invoice. Keyed by the prior (persisted)\n * status; the value is the set of statuses it may move to. A status mapping\n * to itself (no-op re-save) is always permitted and handled separately.\n *\n * Forward path: DRAFT → SENT → VIEWED → PARTIAL → PAID, with OVERDUE reachable\n * from any open status and CANCELLED / WRITTEN_OFF as terminal exits. Payment\n * progress (DRAFT/SENT/VIEWED ↔ PARTIAL ↔ PAID) is driven by `updatePaymentStatus`,\n * which can move both forward and backward as allocations change, so those\n * edges are bidirectional here.\n */\nconst INVOICE_STATUS_TRANSITIONS: Record<InvoiceStatus, InvoiceStatus[]> = {\n [InvoiceStatus.DRAFT]: [\n InvoiceStatus.SENT,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.SENT]: [\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.VIEWED]: [\n InvoiceStatus.SENT,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.PARTIAL]: [\n InvoiceStatus.DRAFT,\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PAID,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n [InvoiceStatus.OVERDUE]: [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.PAID,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // PAID can fall back to PARTIAL / VIEWED / SENT when a payment allocation is\n // removed or reversed (handled by updatePaymentStatus). It can also be\n // written off as bad debt in unusual reconciliation flows.\n [InvoiceStatus.PAID]: [\n InvoiceStatus.DRAFT,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.VIEWED,\n InvoiceStatus.SENT,\n InvoiceStatus.OVERDUE,\n InvoiceStatus.CANCELLED,\n InvoiceStatus.WRITTEN_OFF,\n ],\n // Terminal states: no outbound transitions.\n [InvoiceStatus.CANCELLED]: [],\n [InvoiceStatus.WRITTEN_OFF]: [],\n};\n\n/**\n * Module-scoped record of the status each Invoice instance was loaded with.\n * Used by the save-time status-transition guard to compare the prior\n * persisted status against the one being written, without adding a\n * persisted column. A WeakMap keyed by the instance keeps it out of the\n * schema and garbage-collects with the instance — same pattern LicenseSale\n * uses for its rights snapshot.\n */\nconst loadedInvoiceStatus = new WeakMap<Invoice, InvoiceStatus>();\n\n/**\n * Invoice represents a bill sent to a customer for goods or services.\n *\n * Invoices are distinct from Contracts - a Contract is an agreement,\n * while an Invoice is the billing document requesting payment.\n *\n * Invoices integrate with:\n * - `@happyvertical/smrt-ledgers` for revenue recognition (double-entry accounting)\n * - `@happyvertical/accounting` SDK for syncing with external providers (QBO, Stripe)\n *\n * @example\n * ```typescript\n * // Create an invoice\n * const invoice = await invoices.create({\n * customerId: customer.id,\n * invoiceNumber: await invoices.generateInvoiceNumber(),\n * issueDate: new Date(),\n * dueDate: addDays(new Date(), 30),\n * subtotal: 1000,\n * taxAmount: 50,\n * totalAmount: 1050,\n * currency: 'CAD'\n * });\n *\n * // Recognize revenue in ledger\n * await invoice.recognizeRevenue({\n * arAccountId: arAccount.id,\n * revenueAccountId: revenueAccount.id,\n * taxAccountId: taxAccount.id\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n // ROOT FIX (S5 audit #1390 round 4): the generated create/update surface may\n // only set the billing-document descriptors (who/when/identifiers). The\n // derived/integrity amounts (`subtotal`, `taxAmount`, `totalAmount`,\n // `amountPaid`) and `status` are EXCLUDED — totals are derived from line\n // items, amountPaid from PaymentAllocations, and status from\n // updatePaymentStatus()/the lifecycle helpers. A caller can therefore create\n // a DRAFT invoice (customer / dueDate / line-item refs) but can never forge a\n // total, an amountPaid, or a PAID status through a generated route. Ledger\n // journal refs and communication timestamps are server-managed too.\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'customerId',\n 'contractId',\n 'invoiceNumber',\n 'reference',\n 'issueDate',\n 'dueDate',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'notes',\n 'customerNotes',\n 'terms',\n 'externalId',\n 'customerExternalId',\n 'externalProvider',\n ],\n },\n // NOTE: `send` and `recognizeRevenue` are intentionally NOT exposed over\n // MCP. `recognizeRevenue` posts a balanced journal into smrt-ledgers\n // (i.e. moves money in the books) and `send` transmits a billing document\n // to a customer — neither is safe as an unauthenticated/unguarded MCP tool.\n // Generated MCP tools carry no authz; financial mutations must go through\n // application code that enforces permissions. MCP create/update honor the\n // same `api.writable` allowlist, so status/amounts are unsettable over MCP\n // too. See S5 audit #1390.\n mcp: {\n include: ['list', 'get'],\n serializers: {\n get: {\n importPath: '@happyvertical/smrt-commerce',\n exportName: 'serializeInvoiceMcpGet',\n },\n },\n },\n cli: { skipApiCheck: true },\n indexes: [\n {\n name: 'invoice_historical_source_identity',\n columns: ['tenantId', 'historicalSourceKeyHash'],\n unique: true,\n },\n ],\n})\nexport class Invoice extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n /** Command-owned original sales authority; ordinary documents stay calculated. */\n @field({\n description:\n 'Whether amounts were calculated or retained from an original issued invoice.',\n })\n moneyAuthority: 'calculated' | 'historical' = 'calculated';\n historicalSalesMoneyJson = '';\n @field({ type: 'text', nullable: true }) historicalSourceKeyHash:\n | string\n | null = null;\n\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 MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice read rebinding requires the owning capability',\n );\n this._db = db;\n this.options.db = db;\n return this;\n }\n\n @field({ description: 'Invoice or customer credit note.' })\n kind: InvoiceKind = 'invoice';\n\n @foreignKey('Invoice', { nullable: true })\n creditedInvoiceId: string | null = null;\n\n /** Issued credit-note total cached under the source invoice lock. */\n @field({ ui: { format: 'money' } })\n creditedAmount = 0;\n\n /** Server-owned immutable retention policy and append-only release evidence. */\n retentionStateJson = '';\n\n /** Command-owned durable provider retention bundle; never caller-writable. */\n @field({ sensitive: true })\n retentionAccountingStateJson = '';\n\n getPayableFacts() {\n return calculateInvoicePayable(this);\n }\n\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoices\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Customer this invoice is for\n */\n @field({ description: 'The customer being billed.' })\n @foreignKey('Customer')\n customerId: string = '';\n\n /**\n * Optional link to Contract (cross-package reference)\n */\n @field({\n description: 'The order or agreement this invoice bills for, if any.',\n })\n @foreignKey('SalesContract', { validateSubtype: true })\n contractId: string = '';\n\n // ============================================================================\n // Identification\n // ============================================================================\n\n /**\n * Invoice number (e.g., INV-2025-0001)\n * Generated via InvoiceCollection.generateInvoiceNumber()\n */\n @field({ description: 'The number printed on the invoice.' })\n invoiceNumber: string = '';\n\n /**\n * External reference (e.g., customer PO number)\n */\n reference: string = '';\n\n // ============================================================================\n // Dates\n // ============================================================================\n\n /**\n * Date invoice was issued\n */\n @field({ description: 'The date the invoice was issued.' })\n issueDate: Date = new Date();\n\n /**\n * Payment due date\n */\n @field({\n type: 'datetime',\n nullable: true,\n description:\n 'The date payment is expected; null when the original source did not establish it.',\n })\n dueDate: Date | null = new Date();\n\n /**\n * Date invoice was fully paid\n */\n @field({ description: 'The date it was paid in full.' })\n paidDate: Date | null = null;\n\n // ============================================================================\n // Amounts\n // ============================================================================\n\n /**\n * Subtotal before tax, in **integer minor units** (cents, satoshis).\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 it is stored\n * as minor units and never as a float — `$19.99` is `1999`, not `19.99`.\n * Writing a fractional major-unit value here is the bug; PostgreSQL rejects\n * it with `22P02` while SQLite's affinity silently stores it (#2361).\n */\n @field({ description: 'The amount before tax.', ui: { format: 'money' } })\n subtotal: number = 0;\n\n /**\n * Tax amount, in integer minor units (#2361). With line items this is the\n * sum of their rate-based tax plus {@link providerTaxAmount}.\n */\n @field({ description: 'The tax charged.', ui: { format: 'money' } })\n taxAmount: number = 0;\n\n /**\n * Tax calculated by the payment provider (for example Stripe Tax) from the\n * customer's tax location, in integer minor units (#3060). Server-managed:\n * billing-period close records it after the provider finalizes the invoice.\n * It is added to the line items' tax, so `taxAmount` and `totalAmount` stay\n * authoritative from line items plus this one provider figure.\n */\n @field({ ui: { format: 'money' } })\n providerTaxAmount: number = 0;\n\n /**\n * Total amount due (subtotal + tax), in integer minor units (#2361).\n */\n @field({\n description: 'What the customer owes in all, tax included.',\n ui: { format: 'money' },\n })\n totalAmount: number = 0;\n\n /**\n * Amount paid (sum of PaymentAllocations), in integer minor units (#2361).\n */\n @field({\n description: 'How much of the total has been paid so far.',\n ui: { format: 'money' },\n })\n amountPaid: number = 0;\n\n /**\n * Currency code (ISO 4217)\n */\n @field({\n description: 'The currency of the invoice.',\n ui: { widget: 'currency' },\n })\n currency: string = 'CAD';\n\n /** Pricing interpretation fixed for the document before issue. */\n lineAmountType: 'exclusive' | 'inclusive' | 'none' = 'exclusive';\n\n /** Component rounding scope fixed for the document before issue. */\n taxRoundingScope: 'line' | 'document' = 'line';\n\n /** Informational sum of resolved line discounts, in minor units. */\n @field({ ui: { format: 'money' } })\n discountTotal = 0;\n\n /** Immutable seller registration snapshot once the invoice leaves draft. */\n sellerTaxRegistrationsJson = '[]';\n\n get sellerTaxRegistrations(): TaxRegistrationOptions[] {\n try {\n const value: unknown = JSON.parse(this.sellerTaxRegistrationsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (entry): entry is TaxRegistrationOptions =>\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as TaxRegistrationOptions).taxComponentId ===\n 'string' &&\n typeof (entry as TaxRegistrationOptions).number === 'string' &&\n typeof (entry as TaxRegistrationOptions).label === 'string',\n );\n } catch {\n return [];\n }\n }\n\n set sellerTaxRegistrations(value: TaxRegistrationOptions[]) {\n this.sellerTaxRegistrationsJson = JSON.stringify(value);\n }\n\n /** Immutable customer tax identifier snapshot once issued. */\n partyTaxId = '';\n\n /** Stable issue/freeze identity, independent of lifecycle updated_at. */\n moneySnapshotId = '';\n\n // ============================================================================\n // Status\n // ============================================================================\n\n /**\n * Current invoice status\n */\n @field({\n description:\n 'Where the invoice stands: draft, sent, viewed, paid, overdue or cancelled.',\n })\n status: InvoiceStatus = InvoiceStatus.DRAFT;\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Journal ID for AR recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n arJournalId: string = '';\n\n /** Posted journal that exactly reverses arJournalId on cancellation. */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal', { nullable: true })\n arReversalJournalId: string | null = null;\n\n /**\n * Journal ID for revenue recognition (cross-package ref to smrt-ledgers)\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Journal')\n revenueJournalId: string = '';\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * External ID in accounting provider (e.g., QBO invoice ID)\n */\n externalId: string = '';\n\n /**\n * Customer's external ID in accounting provider.\n * Used to link invoice to customer in external system.\n */\n customerExternalId: string = '';\n\n /**\n * Accounting provider name ('quickbooks' | 'stripe' | etc.)\n */\n externalProvider: string = '';\n\n /**\n * When invoice was last synced to provider\n */\n syncedAt: Date | null = null;\n\n /** Server-owned prepared/in-flight/result identity for frozen accounting sync. */\n @field({ sensitive: true })\n accountingSyncStateJson = '';\n\n /**\n * How the provider collects this invoice (#3190), decided once by billing\n * period close before its first push and reused on every retry, so it is\n * what the provider was asked for. Server-managed. A host must not offer\n * another payment rail on a `charge_automatically` invoice the provider is\n * still collecting.\n */\n collectionMethod: InvoiceCollectionMethod = '';\n\n // ============================================================================\n // Communication\n // ============================================================================\n\n /**\n * When invoice was sent to customer\n */\n sentAt: Date | null = null;\n\n /**\n * When customer viewed the invoice\n */\n viewedAt: Date | null = null;\n\n /**\n * Number of payment reminders sent\n */\n remindersSent: number = 0;\n\n /**\n * When last reminder was sent\n */\n lastReminderAt: Date | null = null;\n\n // ============================================================================\n // Notes\n // ============================================================================\n\n /**\n * Internal notes (not shown to customer)\n */\n @field({\n description: 'Private notes, not shown to the customer.',\n ui: { widget: 'textarea' },\n })\n notes: string = '';\n\n /**\n * Notes shown to customer on invoice\n */\n @field({\n description: 'A message shown to the customer on the invoice.',\n ui: { widget: 'textarea' },\n })\n customerNotes: string = '';\n\n /**\n * Payment terms text\n */\n @field({\n description: 'The payment conditions, such as Net 30.',\n ui: { widget: 'textarea' },\n })\n terms: string = '';\n\n constructor(options: InvoiceOptions = {}) {\n super(options);\n if (options.kind !== undefined) this.kind = options.kind;\n if (options.creditedInvoiceId !== undefined)\n this.creditedInvoiceId = options.creditedInvoiceId;\n if (options.moneyAuthority !== undefined)\n this.moneyAuthority = options.moneyAuthority;\n if (options.historicalSalesMoneyJson !== undefined)\n this.historicalSalesMoneyJson = options.historicalSalesMoneyJson;\n if (options.historicalSourceKeyHash !== undefined)\n this.historicalSourceKeyHash = options.historicalSourceKeyHash;\n if (options.creditedAmount !== undefined)\n this.creditedAmount = options.creditedAmount;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.customerId !== undefined) this.customerId = options.customerId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.invoiceNumber !== undefined)\n this.invoiceNumber = options.invoiceNumber;\n if (options.reference !== undefined) this.reference = options.reference;\n if (options.issueDate !== undefined) this.issueDate = options.issueDate;\n if (options.dueDate !== undefined) this.dueDate = options.dueDate;\n if (options.paidDate !== undefined) this.paidDate = options.paidDate;\n if (options.subtotal !== undefined) this.subtotal = options.subtotal;\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.providerTaxAmount !== undefined)\n this.providerTaxAmount = options.providerTaxAmount;\n if (options.totalAmount !== undefined)\n this.totalAmount = options.totalAmount;\n if (options.amountPaid !== undefined) this.amountPaid = options.amountPaid;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.lineAmountType !== undefined)\n this.lineAmountType = options.lineAmountType;\n if (options.taxRoundingScope !== undefined)\n this.taxRoundingScope = options.taxRoundingScope;\n if (options.discountTotal !== undefined)\n this.discountTotal = options.discountTotal;\n if (options.sellerTaxRegistrationsJson !== undefined)\n this.sellerTaxRegistrationsJson = options.sellerTaxRegistrationsJson;\n if (options.sellerTaxRegistrations !== undefined)\n this.sellerTaxRegistrations = structuredClone(\n options.sellerTaxRegistrations,\n );\n if (options.partyTaxId !== undefined) this.partyTaxId = options.partyTaxId;\n if (options.moneySnapshotId !== undefined)\n this.moneySnapshotId = options.moneySnapshotId;\n if (options.status !== undefined) this.status = options.status;\n if (options.arJournalId !== undefined)\n this.arJournalId = options.arJournalId;\n if (options.arReversalJournalId !== undefined)\n this.arReversalJournalId = options.arReversalJournalId;\n if (options.revenueJournalId !== undefined)\n this.revenueJournalId = options.revenueJournalId;\n if (options.externalId !== undefined) this.externalId = options.externalId;\n if (options.customerExternalId !== undefined)\n this.customerExternalId = options.customerExternalId;\n if (options.externalProvider !== undefined)\n this.externalProvider = options.externalProvider;\n if (options.syncedAt !== undefined) this.syncedAt = options.syncedAt;\n if (options.accountingSyncStateJson !== undefined)\n this.accountingSyncStateJson = options.accountingSyncStateJson;\n if (options.collectionMethod !== undefined)\n this.collectionMethod = options.collectionMethod;\n if (options.sentAt !== undefined) this.sentAt = options.sentAt;\n if (options.viewedAt !== undefined) this.viewedAt = options.viewedAt;\n if (options.remindersSent !== undefined)\n this.remindersSent = options.remindersSent;\n if (options.lastReminderAt !== undefined)\n this.lastReminderAt = options.lastReminderAt;\n if (options.notes !== undefined) this.notes = options.notes;\n if (options.customerNotes !== undefined)\n this.customerNotes = options.customerNotes;\n if (options.terms !== undefined) this.terms = options.terms;\n }\n\n /**\n * Capture the persisted status the row was loaded with, so the save-time\n * transition guard can reject illegal status flips made via raw field\n * assignment (mass-assignment on a generated update route, a stale caller,\n * etc.). Only rows that already exist in the database carry a \"prior\"\n * status — freshly-constructed (not-yet-saved) invoices have no prior and\n * may start in any status.\n */\n override async initialize(): Promise<this> {\n await super.initialize();\n // A row read from the database must not be old-format money data.\n if (this.isPersisted) {\n const { assertHydratedMoneyRowCurrent } = await import(\n '../upgrade/detect.js'\n );\n await assertHydratedMoneyRowCurrent(this);\n }\n if (await this.isSaved()) {\n loadedInvoiceStatus.set(this, this.status);\n }\n return this;\n }\n\n // ============================================================================\n // Save-time financial-integrity guard (S5 audit #1390)\n // ============================================================================\n\n /**\n * Recompute and validate financial fields before every write so forged\n * totals, negative amounts, and illegal status flips can't be persisted via\n * raw mass-assignment on the generated CRUD routes.\n *\n * Behaviour:\n * - **Totals are authoritative from line items.** A draft's `subtotal` /\n * `discountTotal` / `taxAmount` / `totalAmount` are recomputed from its\n * lines and tax codes on every save, whatever the caller sent; a new\n * invoice with no lines starts at zero. Issued invoices are frozen.\n * - **amountPaid is derived/validated.** When persisted, it is recomputed\n * from PaymentAllocations rather than trusted from the caller. It may\n * never exceed `totalAmount`, and a new invoice cannot start paid.\n * - **Whole minor units and non-negativity** are enforced on all amounts.\n * - **Status transitions** are validated against the prior persisted status.\n */\n override async save(): Promise<this> {\n const { saveInvoice } = await import('../document-money.js');\n return (await saveInvoice(this)) as this;\n }\n\n /** @internal Aggregate persistence only. */\n async saveDerived(capability: DocumentMoneyWriteCapability): Promise<this> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Invoice money write capability is required');\n await claimAllocationParent(this, 'invoice');\n const persisted = await this.isSaved();\n const prior = await this.resolvePriorStatus(persisted);\n\n this.assertAmountsAreMinorUnits();\n if (persisted && this.id) await this.recomputeAmountPaid();\n else {\n // A new invoice has no PaymentAllocations behind it, so it cannot\n // start paid: payment status is derived from allocations.\n this.amountPaid = 0;\n if (\n this.status === InvoiceStatus.PAID ||\n this.status === InvoiceStatus.PARTIAL\n )\n throw new Error(\n `Invoice ${this.invoiceNumber || '<new>'}: a new invoice cannot start ` +\n `in status '${this.status}' — payment status is derived from ` +\n 'PaymentAllocations, not set at creation. Create the invoice unpaid ' +\n '(DRAFT / SENT) and record payments via allocations.',\n );\n }\n this.assertNonNegativeAmounts();\n this.assertStatusTransition(prior);\n this.assertPaymentStatusConsistent();\n const payable = calculateInvoicePayable(this);\n if (\n this.retentionAccountingStateJson &&\n [InvoiceStatus.CANCELLED, InvoiceStatus.WRITTEN_OFF].includes(this.status)\n )\n throw new MoneyValidationError(\n 'ACCOUNTING_SYNC_RECONCILIATION_REQUIRED',\n 'Provider retention cannot be voided or written off without an explicit reconciled reversal contract',\n );\n if (\n this.retentionAccountingStateJson &&\n (this.collectionMethod || this.providerTaxAmount)\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Retention accounting does not support provider collection or tax overrides',\n );\n if (\n payable.retainedRemainingMinor > 0 &&\n !this.retentionAccountingStateJson &&\n (this.externalId ||\n this.externalProvider ||\n this.accountingSyncStateJson ||\n this.collectionMethod ||\n this.providerTaxAmount)\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Unreleased retention requires an explicit provider retention contract',\n );\n await this.assertDraftDeliveryHistory(persisted);\n\n await assertAllocationParent(this, 'invoice');\n const result = (await super.save()) as this;\n loadedInvoiceStatus.set(this, this.status);\n return result;\n }\n\n override async delete(\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n const { deleteInvoice } = await import('../document-money.js');\n await deleteInvoice(this, options);\n }\n\n /** @internal 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('Invoice money write capability is required');\n await super.delete(options);\n }\n\n /**\n * Resolve the AUTHORITATIVE prior status (S5 audit #1390 round 4). The\n * {@link loadedInvoiceStatus} WeakMap is only populated when {@link initialize}\n * hydrated the row, so a `create({ id: <existing>, _skipLoad: true })` upsert\n * yields an instance whose WeakMap entry is missing — trusting it would treat\n * the write as a brand-new row and skip the transition guard entirely. Read\n * the persisted row's status directly so a create-onto-existing is correctly\n * treated as an update. `undefined` means no row exists (genuinely new).\n */\n private async resolvePriorStatus(\n persisted: boolean,\n ): Promise<InvoiceStatus | undefined> {\n if (persisted && this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) {\n return row.status as InvoiceStatus;\n }\n } catch {\n // DB not ready — fall through to the in-memory record.\n }\n }\n return loadedInvoiceStatus.get(this);\n }\n\n private async recomputeAmountPaid(): Promise<void> {\n const { PaymentAllocationCollection } = await import(\n '../collections/PaymentAllocationCollection.js'\n );\n const allocationCollection = await PaymentAllocationCollection.create({\n ...this.options,\n db: this.db,\n });\n if (!this.id) throw new Error('Persisted invoice is missing its ID');\n this.amountPaid = await allocationCollection.getTotalAllocatedToInvoice(\n this.id,\n );\n }\n\n /**\n * Whether `amountPaid` covers `totalAmount`. This is the SINGLE source of\n * truth for the **PAID** decision — both {@link updatePaymentStatus} (which\n * decides PAID) and {@link assertPaymentStatusConsistent} (which validates\n * PAID on save) call it, so the PAID-deciding comparison and the\n * PAID-validating comparison can never drift apart (S5 audit #1390\n * follow-up).\n *\n * With integer minor units this is a plain `>=` (#2401). The float-era\n * hazard it used to guard against — a total summed from `0.1 × 3` line items\n * reading as \"not covered\" by `>=` but \"covered\" by an epsilon-tolerant\n * guard, leaving a genuinely-paid invoice unsaveable — cannot arise when both\n * sides are exact integers. That also removes the documented PAID/PARTIAL\n * asymmetry: {@link updatePaymentStatus} and {@link isPartiallyPaid} now\n * agree that any `amountPaid > 0` is a partial payment, because there is no\n * such thing as sub-cent dust.\n */\n private isFullyPaid(): boolean {\n return this.amountPaid + this.creditedAmount >= this.totalAmount;\n }\n\n /**\n * Whether `amountPaid` is a non-trivial partial payment of `totalAmount`.\n * Derived from {@link isFullyPaid} so the two stay mutually exclusive.\n */\n private isPartiallyPaid(): boolean {\n return this.amountPaid + this.creditedAmount > 0 && !this.isFullyPaid();\n }\n\n /**\n * After amounts are recomputed and amountPaid is re-derived from allocations,\n * assert the persisted `status` is consistent with the derived\n * amountPaid-vs-totalAmount relationship. This blocks the raw\n * `SENT → PAID with amountPaid=0` flip (and its inverse, claiming PARTIAL/SENT\n * while fully allocated) that the status-transition map alone permits because\n * SENT → PAID is a structurally legal edge.\n *\n * Only enforced for the payment-derived statuses (PAID / PARTIAL and the\n * unpaid states DRAFT / SENT / VIEWED). Lifecycle statuses that aren't a\n * function of amountPaid — OVERDUE, CANCELLED, WRITTEN_OFF — are left\n * to their own transition rules.\n */\n private assertPaymentStatusConsistent(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const fullyPaid = this.isFullyPaid();\n const partiallyPaid = this.isPartiallyPaid();\n\n if (this.status === InvoiceStatus.DRAFT && this.amountPaid !== 0) {\n throw new Error(\n `Invoice ${label}: DRAFT requires zero derived payment allocations.`,\n );\n }\n if (this.status === InvoiceStatus.PAID && !fullyPaid) {\n throw new Error(\n `Invoice ${label}: status is PAID but derived amountPaid ${this.amountPaid} ` +\n `does not cover totalAmount ${this.totalAmount}. Payment status is ` +\n 'derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n if (this.status === InvoiceStatus.PARTIAL && !partiallyPaid) {\n throw new Error(\n `Invoice ${label}: status is PARTIAL but derived amountPaid ${this.amountPaid} ` +\n `is not a partial payment of totalAmount ${this.totalAmount}. Payment ` +\n 'status is derived from PaymentAllocations — use updatePaymentStatus().',\n );\n }\n // Claiming an unpaid open status (SENT / VIEWED) while the invoice is in\n // fact fully covered by allocations is also inconsistent.\n if (\n (this.status === InvoiceStatus.SENT ||\n this.status === InvoiceStatus.VIEWED) &&\n this.totalAmount > 0 &&\n fullyPaid\n ) {\n throw new Error(\n `Invoice ${label}: status is '${this.status}' but derived amountPaid ` +\n `${this.amountPaid} fully covers totalAmount ${this.totalAmount}. ` +\n 'Use updatePaymentStatus() so the status reflects the allocations.',\n );\n }\n }\n\n /** A payment reversal cannot erase an actual delivery event. */\n private async assertDraftDeliveryHistory(persisted: boolean): Promise<void> {\n if (this.status !== InvoiceStatus.DRAFT) return;\n if (this.sentAt || this.viewedAt) {\n throw new Error('DRAFT requires an undelivered invoice');\n }\n if (persisted && this.id) {\n const { InvoiceCollection } = await import(\n '../collections/InvoiceCollection.js'\n );\n const invoices = await InvoiceCollection.create({ db: this.db });\n const prior = await invoices.get({ id: this.id }, { cache: false });\n if (prior?.sentAt || prior?.viewedAt) {\n throw new Error(\n 'DRAFT cannot erase persisted invoice delivery history',\n );\n }\n }\n }\n\n /**\n * Reject amounts that are not whole minor units, negative financial values,\n * and an amountPaid that exceeds the total (overpayment is modelled\n * elsewhere, not by letting amountPaid float above the invoice total).\n */\n private assertAmountsAreMinorUnits(): void {\n assertIntegerMinorUnits(\n 'Invoice',\n this.invoiceNumber || this.id || '<new>',\n [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ['creditedAmount', this.creditedAmount],\n ],\n );\n }\n\n private assertNonNegativeAmounts(): void {\n const label = this.invoiceNumber || this.id || '<new>';\n const amounts = [\n ['subtotal', this.subtotal],\n ['taxAmount', this.taxAmount],\n ['providerTaxAmount', this.providerTaxAmount],\n ['totalAmount', this.totalAmount],\n ['amountPaid', this.amountPaid],\n ['creditedAmount', this.creditedAmount],\n ] as const;\n // Re-checked after the recompute: line-item sums and the allocation total\n // feed these, so integer-ness has to hold for the derived values too — it\n // is what lets every comparison below drop its epsilon.\n assertIntegerMinorUnits('Invoice', label, amounts);\n for (const [field, value] of amounts) {\n if (value < 0) {\n throw new Error(\n `Invoice ${label}: ${field} must be a non-negative number (got ${value}).`,\n );\n }\n }\n if (this.amountPaid > this.totalAmount) {\n throw new Error(\n `Invoice ${label}: amountPaid ${this.amountPaid} cannot exceed totalAmount ${this.totalAmount}.`,\n );\n }\n }\n\n /**\n * Reject an illegal status flip done via raw field assignment. Compares the\n * about-to-be-written status against the status the row was loaded with.\n * No-op transitions (status unchanged) and brand-new rows are always allowed.\n */\n private assertStatusTransition(prior: InvoiceStatus | undefined): void {\n if (prior === undefined) return; // new row — any starting status is fine\n if (prior === this.status) return; // no-op re-save\n const allowed = INVOICE_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Invoice ${this.invoiceNumber || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'. Use the guarded transition helpers ` +\n '(markSent / markViewed / updatePaymentStatus / cancel / writeOff).',\n );\n }\n }\n\n // ============================================================================\n // Status Helpers\n // ============================================================================\n\n /**\n * Check if invoice is a draft\n */\n isDraft(): boolean {\n return this.status === InvoiceStatus.DRAFT;\n }\n\n /**\n * Check if invoice has been sent\n */\n isSent(): boolean {\n return this.status === InvoiceStatus.SENT;\n }\n\n /**\n * Check if invoice is fully paid\n */\n isPaid(): boolean {\n return this.status === InvoiceStatus.PAID;\n }\n\n /**\n * Check if invoice is partially paid\n */\n isPartial(): boolean {\n return this.status === InvoiceStatus.PARTIAL;\n }\n\n /**\n * Check if invoice is overdue\n */\n isOverdue(): boolean {\n if (this.getAmountDue() === 0) return false;\n if (this.status === InvoiceStatus.OVERDUE) return true;\n if (this.isPaid() || this.status === InvoiceStatus.CANCELLED) return false;\n return this.dueDate !== null && new Date() > this.dueDate;\n }\n\n /**\n * Get remaining amount due\n */\n getAmountDue(): number {\n return this.getPayableFacts().currentlyDueMinor;\n }\n\n /** Signed amount still owed after payments and issued credit notes. */\n getBalance(): number {\n return this.totalAmount - this.amountPaid - this.creditedAmount;\n }\n\n // ============================================================================\n // Status Transitions\n // ============================================================================\n\n /**\n * Mark invoice as sent\n */\n markSent(): void {\n // Payment progress and actual delivery are independent. A prepaid invoice\n // may be sent later; preserve its allocation-derived status and first send.\n if (\n this.status === InvoiceStatus.PARTIAL ||\n this.status === InvoiceStatus.PAID\n ) {\n this.sentAt ??= new Date();\n return;\n }\n if (this.status !== InvoiceStatus.DRAFT) {\n throw new Error(\n `Cannot send invoice with status '${this.status}'. Only draft or payment-progress invoices can be sent.`,\n );\n }\n this.status = InvoiceStatus.SENT;\n this.sentAt = new Date();\n }\n\n /**\n * Record that the invoice was viewed by the customer.\n *\n * This method is idempotent - calling it multiple times will only\n * record the first view time. Status only transitions to VIEWED if\n * the current status is SENT (other statuses like PARTIAL or PAID\n * are preserved).\n */\n markViewed(): void {\n if (this.viewedAt) return; // Already viewed\n this.viewedAt = new Date();\n if (this.status === InvoiceStatus.SENT) {\n this.status = InvoiceStatus.VIEWED;\n }\n }\n\n /**\n * Update payment amount and status.\n *\n * Called when PaymentAllocations change. Handles both forward transitions\n * (DRAFT/SENT → PARTIAL → PAID) and reversals (PAID → PARTIAL → original status).\n *\n * Terminal statuses (CANCELLED, WRITTEN_OFF) are not modified.\n */\n updatePaymentStatus(amountPaid: number): void {\n this.amountPaid = amountPaid;\n\n // Do not change payment status for terminal states\n if (\n this.status === InvoiceStatus.CANCELLED ||\n this.status === InvoiceStatus.WRITTEN_OFF\n ) {\n return;\n }\n\n // Use the SAME epsilon-tolerant \"fully paid\" test the save-time guard\n // (assertPaymentStatusConsistent) applies, so a float-summed total paid\n // exactly (e.g. 0.1×3 line items paid 0.3) is treated as PAID by both —\n // not PARTIAL here and then rejected on save (S5 audit #1390 follow-up).\n if (this.isFullyPaid()) {\n if (\n this.moneyAuthority !== 'historical' &&\n (this.status !== InvoiceStatus.PAID || !this.paidDate)\n ) {\n this.paidDate = new Date();\n }\n this.status = InvoiceStatus.PAID;\n } else if (amountPaid + this.creditedAmount > 0) {\n this.status = InvoiceStatus.PARTIAL;\n this.paidDate = null; // Clear paidDate if no longer fully paid\n } else {\n // No payment remaining: revert to the appropriate pre-payment status\n this.paidDate = null;\n if (this.viewedAt) {\n this.status = InvoiceStatus.VIEWED;\n } else if (this.sentAt || this.moneyAuthority === 'historical') {\n this.status = InvoiceStatus.SENT;\n } else {\n this.status = InvoiceStatus.DRAFT;\n }\n }\n }\n\n /**\n * Cancel the invoice\n */\n cancel(): void {\n if (\n (this.kind === 'invoice' && this.isPaid()) ||\n (this.kind === 'credit_note' && this.amountPaid > 0)\n ) {\n throw new Error(\n `Cannot cancel ${this.kind} with status '${this.status}' and refunded/paid amount ${this.amountPaid}.`,\n );\n }\n this.status = InvoiceStatus.CANCELLED;\n }\n\n /** Cancel a posted invoice with an exact atomic journal reversal. */\n async cancelWithRevenueReversal(\n options: CancelRevenueOptions,\n ): Promise<Journal> {\n const { cancelInvoiceWithRevenueReversal, rebindJournal } = await import(\n '../invoice-posting.js'\n );\n return rebindJournal(\n this.db,\n await cancelInvoiceWithRevenueReversal(this, options),\n );\n }\n\n /**\n * Write off the invoice (bad debt)\n */\n writeOff(): void {\n this.status = InvoiceStatus.WRITTEN_OFF;\n }\n\n // ============================================================================\n // Ledger Integration\n // ============================================================================\n\n /**\n * Post the issued invoice's revenue journal from its frozen money facts.\n *\n * Debits accounts receivable with the stored total, credits each line's\n * revenue account (or `revenueAccountId`) and each tax component's\n * liability account (or `taxAccountId`), then links the journal to the\n * invoice atomically. The invoice must be issued (frozen).\n *\n * @param options - Account IDs and the ledger book currency\n * @returns The posted journal\n *\n * @example\n * ```typescript\n * const journal = await invoice.recognizeRevenue({\n * arAccountId: '1120',\n * revenueAccountId: '4100',\n * taxAccountId: '2130',\n * bookCurrency: 'CAD',\n * });\n * ```\n */\n async recognizeRevenue(options: RecognizeRevenueOptions): Promise<Journal> {\n const { recognizeInvoiceRevenue, rebindJournal } = await import(\n '../invoice-posting.js'\n );\n return rebindJournal(this.db, await recognizeInvoiceRevenue(this, options));\n }\n\n /**\n * Get the AR journal entry (if revenue was recognized)\n */\n async getArJournal(): Promise<Journal | null> {\n if (!this.arJournalId) return null;\n\n try {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const collection = await JournalCollection.create(this.options);\n return await collection.get({ id: this.arJournalId });\n } catch {\n // smrt-ledgers not available\n return null;\n }\n }\n\n // ============================================================================\n // Provider Sync\n // ============================================================================\n\n /**\n * Convert to InvoiceInput for SDK accounting provider sync.\n *\n * Fetches line items and maps all fields to the format expected\n * by @happyvertical/accounting providers. The SDK takes currency **major**\n * units (`12.34` USD), so every amount is converted from this package's\n * integer minor units with the currency's ISO 4217 exponent (#3060).\n *\n * @returns InvoiceInput compatible with @happyvertical/accounting\n *\n * @example\n * ```typescript\n * const provider = await getAccountingProvider({ type: 'quickbooks', ... });\n * const input = await invoice.toAccountingInput();\n * const result = await provider.invoices.push(input);\n * invoice.externalId = result.externalId;\n * invoice.syncedAt = result.syncedAt;\n * await invoice.save();\n * ```\n */\n async toAccountingInput(\n options?: FrozenInvoiceAccountingOptions,\n ): Promise<AccountingInvoiceInput> {\n if (!options) {\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Accounting conversion requires explicit realm mapping',\n );\n }\n if (!this.id || !this.tenantId) {\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Accounting conversion requires a persisted tenant invoice',\n );\n }\n const { toFrozenInvoiceAccountingInput } = await import(\n '../invoice-accounting-sync.js'\n );\n return toFrozenInvoiceAccountingInput({\n db: this.db,\n tenantId: this.tenantId,\n invoiceId: this.id,\n mapping: options.mapping,\n });\n }\n}\n\nexport default Invoice;\n","/**\n * InvoiceCollection - Collection manager for Invoice 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 { Invoice } from '../models/Invoice.js';\nimport { withTenantMoneyCollectionRead } from '../tenant-money-barrier.js';\nimport { InvoiceStatus } from '../types/index.js';\nimport { assertCommerceMoneySchemaCurrent } from '../upgrade/detect.js';\n\n/**\n * Statuses that indicate an invoice has outstanding balance.\n * Used for calculating outstanding amounts and finding unpaid invoices.\n */\nexport const UNPAID_STATUSES = [\n InvoiceStatus.SENT,\n InvoiceStatus.VIEWED,\n InvoiceStatus.PARTIAL,\n InvoiceStatus.OVERDUE,\n] as const;\n\n/**\n * Options for generating invoice numbers\n */\nexport interface InvoiceNumberOptions {\n /** Prefix for invoice numbers (default: 'INV') */\n prefix?: string;\n /** Format: 'prefix-year-seq' or 'prefix-seq' (default: 'prefix-year-seq') */\n format?: 'prefix-year-seq' | 'prefix-seq';\n}\n\nexport class InvoiceCollection extends SmrtCollection<Invoice> {\n static readonly _itemClass = Invoice;\n\n private async withMoneyRead<T>(operation: () => Promise<T>): Promise<T> {\n const rootDb = this.db;\n const tenantId = getTenantId();\n if (!tenantId) {\n // Without a tenant, still refuse a database with old money storage.\n await assertCommerceMoneySchemaCurrent(rootDb);\n return operation();\n }\n const result = await withTenantMoneyCollectionRead(\n rootDb,\n tenantId,\n (boundDb) => this.withDatabase(boundDb, operation),\n );\n if (result instanceof Invoice)\n result.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n else if (Array.isArray(result))\n for (const item of result)\n if (item instanceof Invoice)\n item.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n return result;\n }\n\n override async get(\n filter: string | SmrtWhereClause<Invoice>,\n options: Parameters<SmrtCollection<Invoice>['get']>[1] = {},\n ): Promise<Invoice | null> {\n return this.withMoneyRead(() => super.get(filter, options));\n }\n\n override async list<const Select extends readonly SmrtSelectField<Invoice>[]>(\n options: Omit<SmrtListOptions<Invoice>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n override async list<const Select extends readonly SmrtSelectField<Invoice>[]>(\n options: Omit<SmrtListOptions<Invoice>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<Invoice, Select>[]>;\n override async list(\n options: Omit<SmrtListOptions<Invoice>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n override async list(options?: undefined): Promise<Invoice[]>;\n override async list(\n options: Omit<SmrtListOptions<Invoice>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<Invoice[]>;\n override async list(\n options:\n | (Omit<SmrtListOptions<Invoice>, 'select'> & { select?: undefined })\n | undefined,\n ): Promise<SmrtObject[]>;\n override async list(\n options: SmrtListOptions<Invoice> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n override async list(\n options: SmrtListOptions<Invoice> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n return this.withMoneyRead(() => super.list(options));\n }\n\n /**\n * Find invoices by customer\n *\n * @param customerId - Customer ID\n * @returns Array of invoices\n */\n async findByCustomer(customerId: string): Promise<Invoice[]> {\n return await this.list({\n where: { customerId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by contract\n *\n * @param contractId - Contract ID\n * @returns Array of invoices\n */\n async findByContract(contractId: string): Promise<Invoice[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoices by status\n *\n * @param status - Invoice status\n * @returns Array of invoices\n */\n async findByStatus(status: InvoiceStatus): Promise<Invoice[]> {\n return await this.list({\n where: { status },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find all draft invoices\n *\n * @returns Array of draft invoices\n */\n async findDrafts(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.DRAFT);\n }\n\n /**\n * Find all sent invoices\n *\n * @returns Array of sent invoices\n */\n async findSent(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.SENT);\n }\n\n /**\n * Find all paid invoices\n *\n * @returns Array of paid invoices\n */\n async findPaid(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PAID);\n }\n\n /**\n * Find all partial invoices\n *\n * @returns Array of partially paid invoices\n */\n async findPartial(): Promise<Invoice[]> {\n return await this.findByStatus(InvoiceStatus.PARTIAL);\n }\n\n /**\n * Find overdue invoices (past due date, not paid).\n *\n * **Note**: This method finds invoices by date, but does not automatically\n * update their status to OVERDUE. Use a background job or scheduled task\n * to periodically update invoice statuses based on due dates.\n *\n * @returns Array of overdue invoices\n */\n async findOverdue(): Promise<Invoice[]> {\n const now = new Date().toISOString();\n\n // Get invoices that are sent/viewed/partial and past due\n const candidates = await this.list({\n where: {\n 'dueDate <': now,\n },\n orderBy: 'dueDate ASC',\n });\n\n return candidates.filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n );\n }\n\n /**\n * Find invoices in date range (by issue date)\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Array of invoices\n */\n async findByDateRange(startDate: Date, endDate: Date): Promise<Invoice[]> {\n return await this.list({\n where: {\n 'issueDate >=': startDate.toISOString(),\n 'issueDate <=': endDate.toISOString(),\n },\n orderBy: 'issueDate DESC',\n });\n }\n\n /**\n * Find invoice by external ID (from accounting provider)\n *\n * @param externalId - External ID\n * @returns Invoice or null\n */\n async findByExternalId(externalId: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { externalId },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Find invoices needing sync to external provider\n * (no externalId or stale syncedAt)\n *\n * @returns Array of invoices needing sync\n */\n async findNeedingSync(): Promise<Invoice[]> {\n // Get all non-draft invoices without externalId\n const allInvoices = await this.list({\n orderBy: 'issueDate DESC',\n });\n\n return allInvoices.filter(\n (inv) =>\n inv.status !== InvoiceStatus.DRAFT &&\n inv.status !== InvoiceStatus.CANCELLED &&\n !inv.externalId,\n );\n }\n\n /**\n * Find invoice by invoice number\n *\n * @param invoiceNumber - Invoice number\n * @returns Invoice or null\n */\n async findByNumber(invoiceNumber: string): Promise<Invoice | null> {\n const results = await this.list({\n where: { invoiceNumber },\n limit: 1,\n });\n return results[0] || null;\n }\n\n /**\n * Count invoices for a given year\n *\n * @param year - Year to count\n * @returns Number of invoices\n */\n async countForYear(year: number): Promise<number> {\n const startDate = new Date(year, 0, 1).toISOString();\n const endDate = new Date(year + 1, 0, 1).toISOString();\n\n const invoices = await this.list({\n where: {\n 'issueDate >=': startDate,\n 'issueDate <': endDate,\n },\n });\n\n return invoices.length;\n }\n\n /**\n * Generate the next invoice number.\n *\n * **Warning**: This method has a potential race condition if multiple\n * invoices are created concurrently. For high-concurrency environments,\n * consider using a database sequence or atomic counter instead.\n *\n * @param options - Optional configuration for number format\n * @returns Generated invoice number (e.g., 'INV-2025-0001')\n *\n * @example\n * ```typescript\n * // Default format: INV-2025-0001\n * const num = await invoices.generateInvoiceNumber();\n *\n * // Custom prefix: BILL-2025-0001\n * const num = await invoices.generateInvoiceNumber({ prefix: 'BILL' });\n *\n * // Simple sequential: INV-000001\n * const num = await invoices.generateInvoiceNumber({ format: 'prefix-seq' });\n * ```\n */\n async generateInvoiceNumber(\n options: InvoiceNumberOptions = {},\n ): Promise<string> {\n const prefix = options.prefix || 'INV';\n const format = options.format || 'prefix-year-seq';\n\n const year = new Date().getFullYear();\n\n if (format === 'prefix-year-seq') {\n const count = await this.countForYear(year);\n const seq = String(count + 1).padStart(4, '0');\n return `${prefix}-${year}-${seq}`;\n }\n\n // Simple sequential format\n const allInvoices = await this.list({});\n const seq = String(allInvoices.length + 1).padStart(6, '0');\n return `${prefix}-${seq}`;\n }\n\n /**\n * Get total outstanding (unpaid) amount for a customer\n *\n * @param customerId - Customer ID\n * @returns Total amount due\n */\n async getTotalOutstandingForCustomer(customerId: string): Promise<number> {\n const invoices = await this.list({\n where: { customerId },\n });\n\n return invoices\n .filter((inv) =>\n (UNPAID_STATUSES as readonly InvoiceStatus[]).includes(inv.status),\n )\n .reduce((sum, inv) => sum + inv.getAmountDue(), 0);\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoices belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoices for the tenant\n */\n async findByTenant(tenantId: string): Promise<Invoice[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoices (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 invoices\n */\n async findGlobal(): Promise<Invoice[]> {\n return queryGlobal<Invoice>(this);\n }\n\n /**\n * Find invoices for a tenant including global invoices.\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 invoices\n */\n async findWithGlobals(tenantId: string): Promise<Invoice[]> {\n return queryWithGlobals<Invoice>(this, tenantId, 'Invoice.findWithGlobals');\n }\n}\n"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ var TaxCode = class extends SmrtObject {
|
|
|
24
24
|
name = "";
|
|
25
25
|
kind = "taxable";
|
|
26
26
|
appliesTo = "both";
|
|
27
|
-
/** Ordered component IDs; compound
|
|
27
|
+
/** Ordered component IDs; compound includes every preceding component in this code. */
|
|
28
28
|
/** Stored JSON is guarded through the typed accessor below. */
|
|
29
29
|
componentsJson = "[]";
|
|
30
30
|
get components() {
|
|
@@ -72,10 +72,9 @@ var TaxCode = class extends SmrtObject {
|
|
|
72
72
|
for (const component of parts) {
|
|
73
73
|
if (seen.has(component.taxComponentId)) throw new MoneyValidationError("TAX_CODE_DUPLICATE_COMPONENT", "A tax code cannot contain the same component twice.");
|
|
74
74
|
seen.add(component.taxComponentId);
|
|
75
|
-
if (component.compound) throw new MoneyValidationError("COMPOUND_UNSUPPORTED", "Compound tax components are reserved for a future release.");
|
|
76
75
|
}
|
|
77
76
|
return this.withTransaction(async () => {
|
|
78
|
-
const { TaxComponentCollection } = await import("./TaxComponentCollection-
|
|
77
|
+
const { TaxComponentCollection } = await import("./TaxComponentCollection-Dt2jeWiC.js").then((n) => n.n);
|
|
79
78
|
const components = await TaxComponentCollection.create({ db: this.db });
|
|
80
79
|
for (const part of parts) {
|
|
81
80
|
const component = await components.get({ id: part.taxComponentId }, { cache: false });
|
|
@@ -153,4 +152,4 @@ var TaxCodeCollection = class extends SmrtCollection {
|
|
|
153
152
|
//#endregion
|
|
154
153
|
export { TaxCodeCollection_exports as n, TaxCode as r, TaxCodeCollection as t };
|
|
155
154
|
|
|
156
|
-
//# sourceMappingURL=TaxCodeCollection-
|
|
155
|
+
//# sourceMappingURL=TaxCodeCollection-RWdVaheF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaxCodeCollection-RWdVaheF.js","names":[],"sources":["../../src/models/TaxCode.ts","../../src/collections/TaxCodeCollection.ts"],"sourcesContent":["import { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { MoneyValidationError } from '../money.js';\nimport type { TaxCodeOptions, TaxCodePart } from '../types/index.js';\n\nexport type TaxCodeKind = 'taxable' | 'zero-rated' | 'exempt' | 'out-of-scope';\nexport type TaxCodeAppliesTo = 'sales' | 'purchases' | 'both';\n\n/** A reusable tax code whose ordered component list is safe to edit. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n conflictColumns: ['tenant_id', 'code'],\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: ['code', 'name', 'kind', 'appliesTo', 'components', 'active'],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class TaxCode extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n @tenantId()\n tenantId = '';\n\n @field({ description: 'A stable short code, for example gst-pst-bc.' })\n code = '';\n\n @field({ description: 'The name people see for this tax code.' })\n name = '';\n\n @field({ description: 'How the tax code is reported.' })\n kind: TaxCodeKind = 'taxable';\n\n @field({\n description: 'Whether the code applies to sales, purchases or both.',\n })\n appliesTo: TaxCodeAppliesTo = 'both';\n\n /** Ordered component IDs; compound includes every preceding component in this code. */\n /** Stored JSON is guarded through the typed accessor below. */\n componentsJson = '[]';\n\n @field({\n description: 'The ordered tax components in this code.',\n transient: true,\n })\n get components(): TaxCodePart[] {\n try {\n const value: unknown = JSON.parse(this.componentsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (part): part is TaxCodePart =>\n !!part &&\n typeof part === 'object' &&\n typeof (part as TaxCodePart).taxComponentId === 'string' &&\n typeof (part as TaxCodePart).compound === 'boolean',\n );\n } catch {\n return [];\n }\n }\n\n set components(value: TaxCodePart[] | string) {\n this.componentsJson =\n typeof value === 'string' ? value : JSON.stringify(value);\n }\n\n @field({\n description: 'Whether users may select this code for new documents.',\n })\n active = true;\n\n constructor(options: TaxCodeOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId ?? '';\n if (options.code !== undefined) this.code = options.code;\n if (options.name !== undefined) this.name = options.name;\n if (options.kind !== undefined) this.kind = options.kind;\n if (options.appliesTo !== undefined) this.appliesTo = options.appliesTo;\n if (options.components !== undefined)\n this.components = structuredClone(options.components);\n if (options.active !== undefined) this.active = options.active;\n }\n\n override async save(): Promise<this> {\n if (!this.code.trim() || !this.name.trim())\n throw new Error('Tax code requires a nonempty code and name');\n if (\n !['taxable', 'zero-rated', 'exempt', 'out-of-scope'].includes(this.kind)\n )\n throw new Error('Tax code kind is invalid');\n if (!['sales', 'purchases', 'both'].includes(this.appliesTo))\n throw new Error('Tax code appliesTo is invalid');\n const parts = this.parsedComponents();\n if (!parts)\n throw new Error('Tax code components must be a valid JSON array');\n const nonTaxable = this.kind !== 'taxable';\n if (nonTaxable && parts.length) {\n throw new MoneyValidationError(\n 'TAX_CODE_NON_TAXABLE_COMPONENTS',\n `${this.kind} tax codes cannot have components.`,\n );\n }\n if (!nonTaxable && !parts.length) {\n throw new MoneyValidationError(\n 'TAX_CODE_TAXABLE_EMPTY',\n 'Taxable tax codes require at least one component.',\n );\n }\n const seen = new Set<string>();\n for (const component of parts) {\n if (seen.has(component.taxComponentId)) {\n throw new MoneyValidationError(\n 'TAX_CODE_DUPLICATE_COMPONENT',\n 'A tax code cannot contain the same component twice.',\n );\n }\n seen.add(component.taxComponentId);\n }\n return this.withTransaction(async () => {\n const { TaxComponentCollection } = await import(\n '../collections/TaxComponentCollection.js'\n );\n const components = await TaxComponentCollection.create({ db: this.db });\n for (const part of parts) {\n const component = await components.get(\n { id: part.taxComponentId },\n { cache: false },\n );\n if (!component) {\n throw new Error('Tax code components must belong to the same tenant');\n }\n if (!component.updated_at)\n throw new Error('Tax code component is missing its revision');\n await component.claimRevision(component.updated_at);\n if (!this.tenantId) this.tenantId = component.tenantId;\n if (component.tenantId !== this.tenantId)\n throw new Error('Tax code components must belong to the same tenant');\n }\n return (await super.save()) as this;\n });\n }\n\n private parsedComponents(): TaxCodePart[] | null {\n try {\n const value: unknown = JSON.parse(this.componentsJson);\n if (!Array.isArray(value)) return null;\n if (\n value.some(\n (part) =>\n !part ||\n typeof part !== 'object' ||\n typeof (part as TaxCodePart).taxComponentId !== 'string' ||\n !(part as TaxCodePart).taxComponentId.trim() ||\n typeof (part as TaxCodePart).compound !== 'boolean',\n )\n )\n return null;\n return value as TaxCodePart[];\n } catch {\n return null;\n }\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { TaxCode } from '../models/TaxCode.js';\n\nexport class TaxCodeCollection extends SmrtCollection<TaxCode> {\n static readonly _itemClass = TaxCode;\n\n /**\n * Find a code by its stable `code`. Pass `tenantId` whenever it is known:\n * without an active tenancy filter the lookup is otherwise unscoped.\n */\n async findByCode(code: string, tenantId?: string): Promise<TaxCode | null> {\n return (\n (\n await this.list({\n where: tenantId ? { code, tenantId } : { code },\n limit: 1,\n })\n )[0] ?? null\n );\n }\n}\n"],"mappings":"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"}
|
package/dist/chunks/{TaxComponentCollection-C4zxnyVu.js → TaxComponentCollection-Dt2jeWiC.js}
RENAMED
|
@@ -76,8 +76,8 @@ var TaxComponent = class extends SmrtObject {
|
|
|
76
76
|
if (!this.id) return false;
|
|
77
77
|
if (!persisted.updated_at) throw new Error("Referenced tax component is missing its revision");
|
|
78
78
|
await persisted.claimRevision(persisted.updated_at);
|
|
79
|
-
const { TaxCodeCollection } = await import("./TaxCodeCollection-
|
|
80
|
-
const { DocumentTaxCollection } = await import("./DocumentTaxCollection-
|
|
79
|
+
const { TaxCodeCollection } = await import("./TaxCodeCollection-RWdVaheF.js").then((n) => n.n);
|
|
80
|
+
const { DocumentTaxCollection } = await import("./DocumentTaxCollection-C42x0YLW.js").then((n) => n.n);
|
|
81
81
|
if ((await (await TaxCodeCollection.create({ db: this.db })).list({ where: { tenantId: this.tenantId } })).some((code) => code.components.some((component) => component.taxComponentId === this.id))) return true;
|
|
82
82
|
return (await (await DocumentTaxCollection.create({ db: this.db })).list({
|
|
83
83
|
where: {
|
|
@@ -126,4 +126,4 @@ var TaxComponentCollection = class extends SmrtCollection {
|
|
|
126
126
|
//#endregion
|
|
127
127
|
export { TaxComponentCollection_exports as n, TaxComponent as r, TaxComponentCollection as t };
|
|
128
128
|
|
|
129
|
-
//# sourceMappingURL=TaxComponentCollection-
|
|
129
|
+
//# sourceMappingURL=TaxComponentCollection-Dt2jeWiC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaxComponentCollection-
|
|
1
|
+
{"version":3,"file":"TaxComponentCollection-Dt2jeWiC.js","names":[],"sources":["../../src/models/TaxComponent.ts","../../src/collections/TaxComponentCollection.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { AccountCollection } from '@happyvertical/smrt-ledgers';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { MoneyValidationError } from '../money.js';\nimport type { TaxComponentOptions } from '../types/index.js';\n\nconst PERCENT = /^(\\d+)(?:\\.(\\d{1,6}))?$/;\n\nfunction isPercent(value: string): boolean {\n const match = PERCENT.exec(value);\n if (!match) return false;\n const scaled =\n BigInt(match[1]) * 1_000_000n + BigInt((match[2] ?? '').padEnd(6, '0'));\n return scaled <= 100_000_000n;\n}\n\nfunction canonicalPercent(value: string): string {\n const [whole, fraction = ''] = String(value).split('.');\n const normalizedWhole = whole.replace(/^0+(?=\\d)/, '') || '0';\n const normalizedFraction = fraction.replace(/0+$/, '');\n return normalizedFraction\n ? `${normalizedWhole}.${normalizedFraction}`\n : normalizedWhole;\n}\n\n/**\n * One reusable tax rate component. Codes may combine components, but the\n * component's percent is immutable after it has been saved: archive it and\n * repoint editable codes to a replacement rate instead of rewriting history.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n api: {\n include: ['list', 'get', 'create', 'update'],\n writable: [\n 'name',\n 'percent',\n 'liabilityAccountId',\n 'recoverable',\n 'active',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class TaxComponent extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n @tenantId()\n tenantId = '';\n\n @field({ description: 'The name of this tax component, for example GST.' })\n name = '';\n\n /** Percent, not a fraction: 7.125 means 7.125%. Stored as DECIMAL. */\n @field({\n type: 'decimal',\n description: 'The tax percentage, for example 7.125 for 7.125 percent.',\n })\n percent = '0.0';\n\n @crossPackageRef('@happyvertical/smrt-ledgers:Account', {\n description: 'The liability account that receives this tax component.',\n })\n liabilityAccountId = '';\n\n @field({\n description:\n 'Whether purchases using this component may recover the full input tax. Partial recovery is not supported.',\n })\n recoverable = false;\n\n @field({ description: 'Whether new tax codes and overrides may select it.' })\n active = true;\n\n constructor(options: TaxComponentOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId ?? '';\n if (options.name !== undefined) this.name = options.name;\n if (options.percent !== undefined) this.percent = options.percent;\n if (options.liabilityAccountId !== undefined)\n this.liabilityAccountId = options.liabilityAccountId;\n if (options.recoverable !== undefined)\n this.recoverable = options.recoverable;\n if (options.active !== undefined) this.active = options.active;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.percent = canonicalPercent(String(this.percent));\n return this;\n }\n\n override async save(): Promise<this> {\n if (!this.name.trim())\n throw new Error('Tax component requires a nonempty name');\n if (!isPercent(this.percent)) {\n throw new MoneyValidationError(\n 'PERCENT_OUT_OF_RANGE',\n 'Tax component percent must be between 0 and 100 with at most six decimal places.',\n );\n }\n this.percent = canonicalPercent(this.percent);\n return this.withTransaction(async () => {\n const { TaxComponentCollection } = await import(\n '../collections/TaxComponentCollection.js'\n );\n const components = await TaxComponentCollection.create({ db: this.db });\n const persisted = this.id\n ? await components.get({ id: this.id }, { cache: false })\n : null;\n if (persisted && canonicalPercent(persisted.percent) !== this.percent) {\n const referenced = await this.isReferenced(persisted);\n // claimRevision advanced the row's version; the pending save must use\n // that version rather than its stale caller snapshot.\n this.updated_at = persisted.updated_at;\n if (referenced) {\n throw new MoneyValidationError(\n 'PERCENT_OUT_OF_RANGE',\n 'A referenced tax component rate is immutable; archive it and repoint its tax codes.',\n );\n }\n }\n if (this.liabilityAccountId) {\n const accounts = await AccountCollection.create({ db: this.db });\n const account = await accounts.get(\n { id: this.liabilityAccountId },\n { cache: false },\n );\n if (!account || account.tenantId !== this.tenantId) {\n throw new Error(\n 'Tax component liability account must belong to its tenant',\n );\n }\n }\n return (await super.save()) as this;\n });\n }\n\n private async isReferenced(persisted: TaxComponent): Promise<boolean> {\n if (!this.id) return false;\n if (!persisted.updated_at)\n throw new Error('Referenced tax component is missing its revision');\n // Claim before inspecting references. TaxCode.save() claims the same row\n // before attaching it, so a concurrent rate edit must reload and retry.\n await persisted.claimRevision(persisted.updated_at);\n const { TaxCodeCollection } = await import(\n '../collections/TaxCodeCollection.js'\n );\n const { DocumentTaxCollection } = await import(\n '../collections/DocumentTaxCollection.js'\n );\n const codes = await TaxCodeCollection.create({ db: this.db });\n const usedByCode = (\n await codes.list({ where: { tenantId: this.tenantId } })\n ).some((code) =>\n code.components.some((component) => component.taxComponentId === this.id),\n );\n if (usedByCode) return true;\n const documentTaxes = await DocumentTaxCollection.create({ db: this.db });\n return (\n (\n await documentTaxes.list({\n where: { tenantId: this.tenantId, taxComponentId: this.id },\n limit: 1,\n })\n ).length > 0\n );\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { TaxComponent } from '../models/TaxComponent.js';\n\nexport class TaxComponentCollection extends SmrtCollection<TaxComponent> {\n static readonly _itemClass = TaxComponent;\n}\n"],"mappings":"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"}
|
|
@@ -6,8 +6,8 @@ function allocationOwner(value) {
|
|
|
6
6
|
}
|
|
7
7
|
async function claimAllocationParent(parent, kind) {
|
|
8
8
|
if (!parent.id) return;
|
|
9
|
-
const { PaymentCollection } = await import("
|
|
10
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
9
|
+
const { PaymentCollection } = await import("./retention-accounting-Brdj9x-V.js").then((n) => n.T);
|
|
10
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BIcge9Wl.js").then((n) => n.n);
|
|
11
11
|
const persisted = await (kind === "payment" ? await PaymentCollection.create({ db: parent.db }) : await InvoiceCollection.create({ db: parent.db })).get({ id: parent.id }, { cache: false });
|
|
12
12
|
if (!persisted) return;
|
|
13
13
|
const expected = parent.isPersisted ? parent.updated_at : persisted.updated_at;
|
|
@@ -24,15 +24,15 @@ function allocationTotal(rows, excluding) {
|
|
|
24
24
|
}
|
|
25
25
|
async function assertAllocationParent(parent, kind) {
|
|
26
26
|
if (!parent.id) return;
|
|
27
|
-
const { PaymentAllocationCollection } = await import("
|
|
27
|
+
const { PaymentAllocationCollection } = await import("./retention-accounting-Brdj9x-V.js").then((n) => n.x);
|
|
28
28
|
const allocations = await PaymentAllocationCollection.create({ db: parent.db });
|
|
29
29
|
if (!(isPostgresDatabase(parent.db) ? (await parent.db.query("SELECT to_regclass(?) AS relation", [allocations.tableName])).rows[0]?.relation != null : await parent.db.tableExists(allocations.tableName))) return;
|
|
30
30
|
const rows = kind === "payment" ? await allocations.findByPayment(parent.id) : await allocations.findByInvoice(parent.id);
|
|
31
31
|
if (!rows.length) return;
|
|
32
|
-
const { PaymentCollection } = await import("
|
|
33
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
32
|
+
const { PaymentCollection } = await import("./retention-accounting-Brdj9x-V.js").then((n) => n.T);
|
|
33
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BIcge9Wl.js").then((n) => n.n);
|
|
34
34
|
const opposite = kind === "payment" ? await InvoiceCollection.create({ db: parent.db }) : await PaymentCollection.create({ db: parent.db });
|
|
35
|
-
const cap = kind === "payment" ? parent.amount : parent.totalAmount;
|
|
35
|
+
const cap = kind === "payment" ? parent.amount : parent.totalAmount - (parent.getPayableFacts().retainedMinor - parent.getPayableFacts().releasedMinor);
|
|
36
36
|
if (allocationTotal(rows) > BigInt(cap)) throw new Error("Parent amount cannot be less than committed allocations");
|
|
37
37
|
for (const row of rows) {
|
|
38
38
|
const other = await opposite.get({ id: kind === "payment" ? row.invoiceId : row.paymentId }, { cache: false });
|
|
@@ -42,4 +42,4 @@ async function assertAllocationParent(parent, kind) {
|
|
|
42
42
|
//#endregion
|
|
43
43
|
export { claimAllocationParent as i, allocationTotal as n, assertAllocationParent as r, allocationOwner as t };
|
|
44
44
|
|
|
45
|
-
//# sourceMappingURL=allocation-integrity-
|
|
45
|
+
//# sourceMappingURL=allocation-integrity-DJ7-LY1j.js.map
|
package/dist/chunks/{allocation-integrity-WmWyXhtG.js.map → allocation-integrity-DJ7-LY1j.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"allocation-integrity-
|
|
1
|
+
{"version":3,"file":"allocation-integrity-DJ7-LY1j.js","names":[],"sources":["../../src/allocation-integrity.ts"],"sourcesContent":["/** Transactional allocation invariants shared by the owning models. */\nimport { isPostgresDatabase } from '@happyvertical/smrt-core';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport type { Invoice } from './models/Invoice.js';\nimport type { Payment } from './models/Payment.js';\n\nexport function allocationOwner(value: string | null): string | null {\n return value || getTenantId() || null;\n}\n\n/** Claim the persisted parent before reading dependants, including at repeatable read. */\nexport async function claimAllocationParent(\n parent: Payment | Invoice,\n kind: 'payment' | 'invoice',\n): Promise<void> {\n if (!parent.id) return;\n const { PaymentCollection } = await import(\n './collections/PaymentCollection.js'\n );\n const { InvoiceCollection } = await import(\n './collections/InvoiceCollection.js'\n );\n const collection =\n kind === 'payment'\n ? await PaymentCollection.create({ db: parent.db })\n : await InvoiceCollection.create({ db: parent.db });\n const persisted = await collection.get({ id: parent.id }, { cache: false });\n if (!persisted) return;\n const expected = parent.isPersisted\n ? parent.updated_at\n : persisted.updated_at;\n if (!expected) throw new Error('Allocation parent has no persisted revision');\n await persisted.claimRevision(expected);\n parent.updated_at = persisted.updated_at;\n}\n\n/** Exact totals must never silently exceed JavaScript's integer range. */\nexport function allocationTotal(\n rows: { amount: number; id?: string | null }[],\n excluding?: string | null,\n): bigint {\n return rows.reduce((total, row) => {\n if (row.id === excluding) return total;\n if (!Number.isSafeInteger(row.amount) || row.amount <= 0) {\n throw new Error('Invalid persisted payment allocation amount');\n }\n return total + BigInt(row.amount);\n }, 0n);\n}\n\n/** Parent corrections cannot invalidate an already committed allocation. */\nexport async function assertAllocationParent(\n parent: Payment | Invoice,\n kind: 'payment' | 'invoice',\n): Promise<void> {\n if (!parent.id) return;\n const { PaymentAllocationCollection } = await import(\n './collections/PaymentAllocationCollection.js'\n );\n const allocations = await PaymentAllocationCollection.create({\n db: parent.db,\n });\n // Payment-only deployments need not provision allocation storage. PostgreSQL's\n // information_schema hides inaccessible tables, so use its catalog lookup and\n // let the subsequent read report permission errors for an existing table.\n const exists = isPostgresDatabase(parent.db)\n ? (\n await parent.db.query('SELECT to_regclass(?) AS relation', [\n allocations.tableName,\n ])\n ).rows[0]?.relation != null\n : await parent.db.tableExists(allocations.tableName);\n if (!exists) return;\n const rows =\n kind === 'payment'\n ? await allocations.findByPayment(parent.id)\n : await allocations.findByInvoice(parent.id);\n if (!rows.length) return;\n const { PaymentCollection } = await import(\n './collections/PaymentCollection.js'\n );\n const { InvoiceCollection } = await import(\n './collections/InvoiceCollection.js'\n );\n const opposite =\n kind === 'payment'\n ? await InvoiceCollection.create({ db: parent.db })\n : await PaymentCollection.create({ db: parent.db });\n const cap =\n kind === 'payment'\n ? (parent as Payment).amount\n : (parent as Invoice).totalAmount -\n ((parent as Invoice).getPayableFacts().retainedMinor -\n (parent as Invoice).getPayableFacts().releasedMinor);\n if (allocationTotal(rows) > BigInt(cap))\n throw new Error('Parent amount cannot be less than committed allocations');\n for (const row of rows) {\n const other = await opposite.get(\n { id: kind === 'payment' ? row.invoiceId : row.paymentId },\n { cache: false },\n );\n if (\n !other ||\n row.tenantId !== allocationOwner(parent.tenantId) ||\n other.tenantId !== row.tenantId ||\n (other.customerId || null) !== (parent.customerId || null) ||\n other.currency !== parent.currency\n ) {\n throw new Error(\n 'Parent correction would invalidate payment allocation tenant, customer or currency',\n );\n }\n }\n}\n"],"mappings":";;;AAMO,SAAS,gBAAgB,OAAqC;CACnE,OAAO,SAAS,YAAY,KAAK;AACnC;AAGA,eAAsB,sBACpB,QACA,MACe;CACf,IAAI,CAAC,OAAO,IAAI;CAChB,MAAM,EAAE,sBAAsB,MAAM,OAClC,qCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAEF,MAAM,EAAE,sBAAsB,MAAM,OAClC,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAMF,MAAM,YAAY,OAHhB,SAAS,YACL,MAAM,kBAAkB,OAAO,EAAE,IAAI,OAAO,GAAG,CAAC,IAChD,MAAM,kBAAkB,OAAO,EAAE,IAAI,OAAO,GAAG,CAAC,EAAA,CACnB,IAAI,EAAE,IAAI,OAAO,GAAG,GAAG,EAAE,OAAO,MAAM,CAAC;CAC1E,IAAI,CAAC,WAAW;CAChB,MAAM,WAAW,OAAO,cACpB,OAAO,aACP,UAAU;CACd,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,6CAA6C;CAC5E,MAAM,UAAU,cAAc,QAAQ;CACtC,OAAO,aAAa,UAAU;AAChC;AAGO,SAAS,gBACd,MACA,WACQ;CACR,OAAO,KAAK,QAAQ,OAAO,QAAQ;EACjC,IAAI,IAAI,OAAO,WAAW,OAAO;EACjC,IAAI,CAAC,OAAO,cAAc,IAAI,MAAM,KAAK,IAAI,UAAU,GACrD,MAAM,IAAI,MAAM,6CAA6C;EAE/D,OAAO,QAAQ,OAAO,IAAI,MAAM;CAClC,GAAG,EAAE;AACP;AAGA,eAAsB,uBACpB,QACA,MACe;CACf,IAAI,CAAC,OAAO,IAAI;CAChB,MAAM,EAAE,gCAAgC,MAAM,OAC5C,qCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAEF,MAAM,cAAc,MAAM,4BAA4B,OAAO,EAC3D,IAAI,OAAO,GACb,CAAC;CAWD,IAAI,EAPW,mBAAmB,OAAO,EAAE,KAErC,MAAM,OAAO,GAAG,MAAM,qCAAqC,CACzD,YAAY,SACd,CAAC,EAAA,CACD,KAAK,EAAC,EAAG,YAAY,OACvB,MAAM,OAAO,GAAG,YAAY,YAAY,SAAS,IACxC;CACb,MAAM,OACJ,SAAS,YACL,MAAM,YAAY,cAAc,OAAO,EAAE,IACzC,MAAM,YAAY,cAAc,OAAO,EAAE;CAC/C,IAAI,CAAC,KAAK,QAAQ;CAClB,MAAM,EAAE,sBAAsB,MAAM,OAClC,qCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAEF,MAAM,EAAE,sBAAsB,MAAM,OAClC,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;CAEF,MAAM,WACJ,SAAS,YACL,MAAM,kBAAkB,OAAO,EAAE,IAAI,OAAO,GAAG,CAAC,IAChD,MAAM,kBAAkB,OAAO,EAAE,IAAI,OAAO,GAAG,CAAC;CACtD,MAAM,MACJ,SAAS,YACJ,OAAmB,SACnB,OAAmB,eAClB,OAAmB,gBAAgB,CAAA,CAAE,gBACpC,OAAmB,gBAAgB,CAAA,CAAE;CAC9C,IAAI,gBAAgB,IAAI,IAAI,OAAO,GAAG,GACpC,MAAM,IAAI,MAAM,yDAAyD;CAC3E,KAAA,MAAW,OAAO,MAAM;EACtB,MAAM,QAAQ,MAAM,SAAS,IAC3B,EAAE,IAAI,SAAS,YAAY,IAAI,YAAY,IAAI,UAAU,GACzD,EAAE,OAAO,MAAM,CACjB;EACA,IACE,CAAC,SACD,IAAI,aAAa,gBAAgB,OAAO,QAAQ,KAChD,MAAM,aAAa,IAAI,aACtB,MAAM,cAAc,WAAW,OAAO,cAAc,SACrD,MAAM,aAAa,OAAO,UAE1B,MAAM,IAAI,MACR,oFACF;CAEJ;AACF"}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as ContractCollection, W as classifyContractPurchase, Z as CustomerCollection, i as getReceivedPurchaseMoney, l as VendorCollection, t as DocumentTaxCollection } from "./DocumentTaxCollection-C42x0YLW.js";
|
|
2
2
|
import { t as ContractStatus } from "./types-Bbm0Xflc.js";
|
|
3
|
-
import { t as CustomerCollection } from "./CustomerCollection-BIPH2ZUx.js";
|
|
4
3
|
import { MoneyValidationError, calculateMoneyDocument, resolveLineTaxCode } from "../money.js";
|
|
5
|
-
import {
|
|
6
|
-
import { r as withTenantMoneyRuntime } from "./tenant-money-barrier-
|
|
7
|
-
import { t as TaxCodeCollection } from "./TaxCodeCollection-
|
|
8
|
-
import { t as ContractLineItemCollection } from "./ContractLineItemCollection-
|
|
9
|
-
import { t as TaxComponentCollection } from "./TaxComponentCollection-
|
|
10
|
-
import { t as VendorCollection } from "./VendorCollection-D6ZcUYy8.js";
|
|
4
|
+
import { h as lockDocumentMoneyParents, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
|
|
5
|
+
import { r as withTenantMoneyRuntime } from "./tenant-money-barrier-DoF6lR8h.js";
|
|
6
|
+
import { t as TaxCodeCollection } from "./TaxCodeCollection-RWdVaheF.js";
|
|
7
|
+
import { t as ContractLineItemCollection } from "./ContractLineItemCollection-J4xtRtzE.js";
|
|
8
|
+
import { t as TaxComponentCollection } from "./TaxComponentCollection-Dt2jeWiC.js";
|
|
11
9
|
import { getTenantId } from "@happyvertical/smrt-tenancy";
|
|
12
10
|
//#region src/contract-money.ts
|
|
13
11
|
var FROZEN_CONTRACT_FIELDS = [
|
|
14
12
|
"tenantId",
|
|
13
|
+
"moneyAuthority",
|
|
14
|
+
"receivedPurchaseMoneyJson",
|
|
15
15
|
"subtotal",
|
|
16
16
|
"discountTotal",
|
|
17
17
|
"taxAmount",
|
|
@@ -174,10 +174,14 @@ async function recompute(db, contract, lines, settings, persist, snapshotId = ""
|
|
|
174
174
|
taxComponentId: part.taxComponentId,
|
|
175
175
|
name: component.name,
|
|
176
176
|
percent: component.percent,
|
|
177
|
-
taxMinor: part.taxMinor
|
|
177
|
+
taxMinor: part.taxMinor,
|
|
178
|
+
...part.compound ? {
|
|
179
|
+
compound: true,
|
|
180
|
+
taxableMinor: part.taxableMinor
|
|
181
|
+
} : {}
|
|
178
182
|
};
|
|
179
183
|
});
|
|
180
|
-
line.taxRate = line.taxComponents.reduce((sum, part) => sum + Number(part.percent) / 100, 0);
|
|
184
|
+
line.taxRate = row.components.some((part) => part.compound) ? row.netMinor === 0 ? 0 : row.taxMinor / row.netMinor : line.taxComponents.reduce((sum, part) => sum + Number(part.percent) / 100, 0);
|
|
181
185
|
if (snapshotId) line.moneySnapshotId = snapshotId;
|
|
182
186
|
if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
183
187
|
}
|
|
@@ -197,6 +201,7 @@ async function recompute(db, contract, lines, settings, persist, snapshotId = ""
|
|
|
197
201
|
percent: component?.percent ?? null,
|
|
198
202
|
liabilityAccountId: component?.liabilityAccountId ?? null,
|
|
199
203
|
taxableMinor: tax.taxableMinor,
|
|
204
|
+
netMinor: tax.netMinor ?? null,
|
|
200
205
|
taxMinor: tax.taxMinor,
|
|
201
206
|
recoverableMinor: direction(contract) === "purchases" && component?.recoverable ? tax.taxMinor : 0
|
|
202
207
|
};
|
|
@@ -222,8 +227,10 @@ async function saveContract(input) {
|
|
|
222
227
|
return withTenantMoneyRuntime(input.db, tenantId, async (scope) => input.withDatabase(scope.db, async (contract) => {
|
|
223
228
|
const settings = scope.settings;
|
|
224
229
|
const existing = contract.id ? await loadContract(scope.db, contract.id) : null;
|
|
230
|
+
if (contract.billingScheduleJson !== (existing?.billingScheduleJson ?? "")) throw new MoneyValidationError("FROZEN_DOCUMENT", "Billing schedule is managed by schedule commands");
|
|
225
231
|
if (existing && existing.tenantId !== scope.tenantId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Contract tenant identity cannot change");
|
|
226
232
|
if (!existing) {
|
|
233
|
+
if (contract.moneyAuthority !== "calculated" || contract.receivedPurchaseMoneyJson) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Use recordReceivedPurchaseMoney or recordReceivedSalesContractMoney to record received evidence");
|
|
227
234
|
contract.tenantId = tenantId;
|
|
228
235
|
contract.lineAmountType = settings.defaultLineAmountType;
|
|
229
236
|
contract.taxRoundingScope = settings.taxRoundingScope;
|
|
@@ -238,6 +245,18 @@ async function saveContract(input) {
|
|
|
238
245
|
return contract.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
239
246
|
}
|
|
240
247
|
return withLockedContract(scope.db, tenantId, required(existing.id, "Contract"), async (stored, lines) => {
|
|
248
|
+
assertUnchanged(contract, stored, ["moneyAuthority", "receivedPurchaseMoneyJson"], "Contract money authority");
|
|
249
|
+
if (getReceivedPurchaseMoney(stored)) {
|
|
250
|
+
if (contract.constructor !== stored.constructor || contract.contractType !== stored.contractType) throw new MoneyValidationError("FROZEN_DOCUMENT", "Received purchase document type is frozen");
|
|
251
|
+
assertUnchanged(contract, stored, [
|
|
252
|
+
...FROZEN_CONTRACT_FIELDS,
|
|
253
|
+
"vendorId",
|
|
254
|
+
"customerId"
|
|
255
|
+
], "Received purchase money");
|
|
256
|
+
if (lines.length) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Received money cannot mix with calculated lines");
|
|
257
|
+
if (contract.status !== ContractStatus.DRAFT && !stored.moneySnapshotId) contract.moneySnapshotId = crypto.randomUUID();
|
|
258
|
+
return contract.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
259
|
+
}
|
|
241
260
|
if (isFrozen(stored)) {
|
|
242
261
|
assertUnchanged(contract, stored, FROZEN_CONTRACT_FIELDS, "Contract");
|
|
243
262
|
return contract.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
@@ -262,6 +281,7 @@ async function saveContractLineItem(input) {
|
|
|
262
281
|
const contractId = persisted?.contractId ?? line.contractId;
|
|
263
282
|
const settings = scope.settings;
|
|
264
283
|
return withLockedContract(scope.db, scope.tenantId, contractId, async (contract, lines) => {
|
|
284
|
+
if (getReceivedPurchaseMoney(contract)) throw new MoneyValidationError("FROZEN_DOCUMENT", "Received purchase evidence cannot contain calculated lines");
|
|
265
285
|
if (persisted && line.contractId !== persisted.contractId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Contract line cannot move between parents");
|
|
266
286
|
if (isFrozen(contract)) {
|
|
267
287
|
if (!persisted) throw new MoneyValidationError("FROZEN_DOCUMENT", "Cannot add a line to a frozen Contract");
|
|
@@ -295,7 +315,20 @@ async function deleteContractLineItem(input, options) {
|
|
|
295
315
|
async function recalculateContractTotals(contract) {
|
|
296
316
|
await saveContract(contract);
|
|
297
317
|
}
|
|
318
|
+
async function deleteContract(input, options) {
|
|
319
|
+
const contractId = input.id;
|
|
320
|
+
if (!contractId) return;
|
|
321
|
+
const tenantId = requireContractTenant(input.tenantId ?? getTenantId() ?? null);
|
|
322
|
+
await withTenantMoneyRuntime(input.db, tenantId, async ({ db }) => {
|
|
323
|
+
if (!await loadContract(db, contractId)) return;
|
|
324
|
+
await withLockedContract(db, tenantId, contractId, async (stored) => {
|
|
325
|
+
if (stored.billingScheduleJson) throw new MoneyValidationError("FROZEN_DOCUMENT", "Contract billing schedule history cannot be deleted");
|
|
326
|
+
if (getReceivedPurchaseMoney(stored)) throw new MoneyValidationError("FROZEN_DOCUMENT", "Received purchase monetary history cannot be deleted");
|
|
327
|
+
await input.withDatabase(db, (bound) => bound.deleteDerived(DOCUMENT_MONEY_WRITE, options));
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
298
331
|
//#endregion
|
|
299
|
-
export { deleteContractLineItem, recalculateContractTotals, saveContract, saveContractLineItem };
|
|
332
|
+
export { deleteContract, deleteContractLineItem, recalculateContractTotals, saveContract, saveContractLineItem };
|
|
300
333
|
|
|
301
|
-
//# sourceMappingURL=contract-money-
|
|
334
|
+
//# sourceMappingURL=contract-money-DSRTMoXm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-money-DSRTMoXm.js","names":[],"sources":["../../src/contract-money.ts"],"sourcesContent":["/** Transactional Contract money aggregate persistence. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ContractCollection } from './collections/ContractCollection.js';\nimport { ContractLineItemCollection } from './collections/ContractLineItemCollection.js';\nimport { CustomerCollection } from './collections/CustomerCollection.js';\nimport {\n DocumentTaxCollection,\n type DocumentTaxWriteInput,\n} from './collections/DocumentTaxCollection.js';\nimport { TaxCodeCollection } from './collections/TaxCodeCollection.js';\nimport { TaxComponentCollection } from './collections/TaxComponentCollection.js';\nimport { VendorCollection } from './collections/VendorCollection.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n lockDocumentMoneyParents,\n} from './document-money-internal.js';\nimport {\n type Contract,\n classifyContractPurchase,\n isContractSnapshotReportEligible,\n} from './models/Contract.js';\nimport type { ContractLineItem } from './models/ContractLineItem.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport {\n calculateMoneyDocument,\n MoneyValidationError,\n resolveLineTaxCode,\n} from './money.js';\nimport { getReceivedPurchaseMoney } from './received-purchase-money.js';\nimport { withTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n ContractStatus,\n type ContractTaxComponentSnapshot,\n} from './types/index.js';\n\ntype DeleteOptions = { expectedUpdatedAt?: Date | string };\ntype ProductTaxDefaults = {\n tenantId: string | null;\n defaultSalesTaxCodeId: string;\n defaultPurchaseTaxCodeId: string;\n};\n\nconst FROZEN_CONTRACT_FIELDS = [\n 'tenantId',\n 'moneyAuthority',\n 'receivedPurchaseMoneyJson',\n 'subtotal',\n 'discountTotal',\n 'taxAmount',\n 'totalAmount',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'sellerTaxRegistrationsJson',\n 'partyTaxId',\n 'moneySnapshotId',\n 'reference',\n 'issueDate',\n] as const;\nconst FROZEN_LINE_FIELDS = [\n 'tenantId',\n 'contractId',\n 'description',\n 'productId',\n 'sku',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'taxAmount',\n 'taxRate',\n 'amount',\n 'startDate',\n 'endDate',\n 'billingPeriod',\n 'sortOrder',\n 'moneySnapshotId',\n] as const;\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} requires an ID`,\n );\n return value;\n}\n/** Frozen-field identity: an absent optional reference (`''`) equals a stored NULL. */\nfunction same(a: unknown, b: unknown): boolean {\n const value = (item: unknown) =>\n item instanceof Date\n ? item.toISOString()\n : item === '' || item === undefined\n ? null\n : item;\n return value(a) === value(b);\n}\nfunction assertUnchanged<T extends object>(\n proposed: T,\n stored: T,\n fields: readonly (keyof T)[],\n label: string,\n): void {\n for (const field of fields)\n if (!same(proposed[field], stored[field]))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `${label} ${String(field)} is frozen`,\n );\n}\nfunction isFrozen(contract: Contract): boolean {\n return contract.status !== ContractStatus.DRAFT || !!contract.moneySnapshotId;\n}\n\nexport { classifyContractPurchase, isContractSnapshotReportEligible };\n\nfunction direction(contract: Contract): 'sales' | 'purchases' {\n return classifyContractPurchase(contract) ? 'purchases' : 'sales';\n}\nfunction discount(line: ContractLineItem) {\n if (line.discountType === 'percent')\n return { kind: 'percent' as const, rate: String(line.discountRate) };\n if (line.discountType === 'amount')\n return { kind: 'amount' as const, minor: line.discount };\n return { kind: 'none' as const };\n}\n\nasync function loadContract(\n db: DatabaseInterface,\n id: string,\n): Promise<Contract | null> {\n return (await ContractCollection.create({ db })).get(\n { id },\n { cache: false },\n );\n}\nasync function withLockedContract<T>(\n db: DatabaseInterface,\n tenantId: string,\n contractId: string,\n operation: (contract: Contract, lines: ContractLineItem[]) => Promise<T>,\n): Promise<T> {\n const contracts = await ContractCollection.create({ db });\n await lockDocumentMoneyParents([\n { id: contractId, tenantId, collection: contracts },\n ]);\n const contract = await contracts.get({ id: contractId }, { cache: false });\n if (!contract || contract.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Contract was not found in its tenant',\n );\n const lines = await (\n await ContractLineItemCollection.create({ db })\n ).findByContract(contractId);\n return operation(contract, lines);\n}\n\nasync function recompute(\n db: DatabaseInterface,\n contract: Contract,\n lines: ContractLineItem[],\n settings: TaxSettings,\n persist: boolean,\n snapshotId = '',\n): Promise<DocumentTaxWriteInput[]> {\n const tenantId = required(contract.tenantId, 'Contract tenant');\n const codes = await (await TaxCodeCollection.create({ db })).list({\n where: { tenantId },\n });\n const components = await (await TaxComponentCollection.create({ db })).list({\n where: { tenantId },\n });\n const byCode = Object.fromEntries(\n codes.flatMap((code) => {\n if (!code.id) return [];\n return [\n [\n code.id,\n {\n id: code.id,\n kind: code.kind,\n active: code.active,\n appliesTo: code.appliesTo,\n components: code.components.map((part) => ({\n taxComponentId: part.taxComponentId,\n percent:\n components.find(\n (component) => component.id === part.taxComponentId,\n )?.percent ?? '0',\n compound: part.compound,\n })),\n },\n ],\n ] as const;\n }),\n );\n const codeById = new Map(\n codes.flatMap((code) => (code.id ? ([[code.id, code]] as const) : [])),\n );\n // Product owns catalog reads. Load defaults on the aggregate transaction so\n // Contract follows the same line > product > party > tenant precedence as\n // Invoice without adding a static package edge.\n const productIds = [\n ...new Set(\n lines\n .map((line) => line.productId)\n .filter((productId): productId is string => !!productId),\n ),\n ];\n const productById = new Map<string, ProductTaxDefaults>();\n if (productIds.length > 0) {\n const { ProductCollection } = await import(\n '@happyvertical/smrt-products/collections'\n );\n const products = await ProductCollection.create({ db });\n for (const productId of productIds) {\n const product = await products.get({ id: productId }, { cache: false });\n if (!product)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} was not found`,\n );\n if (product.tenantId !== null && product.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} must be global or belong to its tenant`,\n );\n productById.set(productId, product);\n }\n }\n const customer =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : null;\n const vendor =\n 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n const resolved = new Map<string, string | null>();\n const result = calculateMoneyDocument({\n currency: contract.currency,\n lineAmountType: contract.lineAmountType,\n roundingScope: contract.taxRoundingScope,\n lines: lines.map((line) => {\n const selected = resolveLineTaxCode(\n {\n lineAmountType: contract.lineAmountType,\n lineOverride: line.taxMode === 'override' ? line.taxCodeId : null,\n },\n {\n direction: direction(contract),\n codes: byCode,\n customerTaxExempt: customer?.taxExempt ?? false,\n exemptCodeId:\n codes.find((code) => code.kind === 'exempt')?.id ?? null,\n productCodeId: line.productId\n ? direction(contract) === 'sales'\n ? productById.get(line.productId)?.defaultSalesTaxCodeId || null\n : productById.get(line.productId)?.defaultPurchaseTaxCodeId ||\n null\n : null,\n partyCodeId:\n customer?.defaultTaxCodeId ?? vendor?.defaultTaxCodeId ?? null,\n appDefaultCodeId:\n direction(contract) === 'sales'\n ? settings.defaultSalesTaxCodeId\n : settings.defaultPurchaseTaxCodeId,\n outOfScopeCodeId:\n codes.find((code) => code.kind === 'out-of-scope')?.id ?? null,\n },\n );\n resolved.set(\n required(line.id, 'Contract line'),\n selected.taxCode?.id ?? null,\n );\n return {\n id: required(line.id, 'Contract line'),\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n discount: discount(line),\n taxCode: selected.taxCode,\n };\n }),\n });\n const resultById = new Map(result.lines.map((row) => [row.id, row]));\n for (const line of lines) {\n const lineId = required(line.id, 'Contract line');\n const row = resultById.get(lineId);\n if (!row)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Calculated Contract line ${lineId} was not loaded`,\n );\n const codeId = resolved.get(lineId) ?? null;\n const code = codeId ? (codeById.get(codeId) ?? null) : null;\n line.discount = row.discountMinor;\n line.amount = row.amountMinor;\n line.taxAmount = row.taxMinor;\n line.taxCodeId = code?.id ?? null;\n line.taxCodeCode = code?.code ?? null;\n line.taxCodeName = code?.name ?? null;\n line.taxComponents = row.components.map(\n (part): ContractTaxComponentSnapshot => {\n const component = components.find(\n (item) => item.id === part.taxComponentId,\n );\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax component ${part.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: part.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: part.taxMinor,\n ...(part.compound\n ? { compound: true, taxableMinor: part.taxableMinor }\n : {}),\n };\n },\n );\n line.taxRate = row.components.some((part) => part.compound)\n ? row.netMinor === 0\n ? 0\n : row.taxMinor / row.netMinor\n : line.taxComponents.reduce(\n (sum, part) => sum + Number(part.percent) / 100,\n 0,\n );\n if (snapshotId) line.moneySnapshotId = snapshotId;\n if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const taxRows = result.taxes.map((tax): DocumentTaxWriteInput => {\n const code = tax.taxCodeId ? (codeById.get(tax.taxCodeId) ?? null) : null;\n const component = tax.taxComponentId\n ? (components.find((item) => item.id === tax.taxComponentId) ?? null)\n : null;\n return {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n currency: contract.currency,\n taxCodeId: code?.id ?? null,\n taxCodeCode: code?.code ?? null,\n kind: code?.kind ?? null,\n taxComponentId: component?.id ?? null,\n taxComponentName: component?.name ?? null,\n percent: component?.percent ?? null,\n liabilityAccountId: component?.liabilityAccountId ?? null,\n taxableMinor: tax.taxableMinor,\n netMinor: tax.netMinor ?? null,\n taxMinor: tax.taxMinor,\n recoverableMinor:\n direction(contract) === 'purchases' && component?.recoverable\n ? tax.taxMinor\n : 0,\n };\n });\n contract.subtotal = result.subtotalMinor;\n contract.discountTotal = result.discountTotalMinor;\n contract.taxAmount = result.taxMinor;\n contract.totalAmount = result.totalMinor;\n if (persist)\n await (\n await DocumentTaxCollection.create({ db })\n ).replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n rows: taxRows,\n });\n return taxRows;\n}\n\n/** Every money document belongs to a tenant. */\nfunction requireContractTenant(tenantId: string | null): string {\n if (!tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Commerce money documents require a tenant',\n );\n return tenantId;\n}\n\n/** Save a Contract and recompute its money aggregate. */\nexport async function saveContract(input: Contract): Promise<Contract> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (contract) => {\n const settings = scope.settings;\n const existing = contract.id\n ? await loadContract(scope.db, contract.id)\n : null;\n if (\n contract.billingScheduleJson !== (existing?.billingScheduleJson ?? '')\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Billing schedule is managed by schedule commands',\n );\n if (existing && existing.tenantId !== scope.tenantId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract tenant identity cannot change',\n );\n if (!existing) {\n if (\n contract.moneyAuthority !== 'calculated' ||\n contract.receivedPurchaseMoneyJson\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Use recordReceivedPurchaseMoney or recordReceivedSalesContractMoney to record received evidence',\n );\n contract.tenantId = tenantId;\n contract.lineAmountType = settings.defaultLineAmountType;\n contract.taxRoundingScope = settings.taxRoundingScope;\n // A new Contract has no lines yet: its totals start at zero and are\n // derived from lines on every later save, never taken from the caller.\n contract.subtotal = 0;\n contract.discountTotal = 0;\n contract.taxAmount = 0;\n contract.totalAmount = 0;\n if (contract.status !== ContractStatus.DRAFT) {\n contract.moneySnapshotId = crypto.randomUUID();\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n return withLockedContract(\n scope.db,\n tenantId,\n required(existing.id, 'Contract'),\n async (stored, lines) => {\n assertUnchanged(\n contract,\n stored,\n ['moneyAuthority', 'receivedPurchaseMoneyJson'],\n 'Contract money authority',\n );\n const received = getReceivedPurchaseMoney(stored);\n if (received) {\n if (\n contract.constructor !== stored.constructor ||\n contract.contractType !== stored.contractType\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase document type is frozen',\n );\n assertUnchanged(\n contract,\n stored,\n [\n ...FROZEN_CONTRACT_FIELDS,\n 'vendorId' as keyof Contract,\n 'customerId' as keyof Contract,\n ],\n 'Received purchase money',\n );\n if (lines.length)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Received money cannot mix with calculated lines',\n );\n if (\n contract.status !== ContractStatus.DRAFT &&\n !stored.moneySnapshotId\n )\n contract.moneySnapshotId = crypto.randomUUID();\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n if (isFrozen(stored)) {\n assertUnchanged(\n contract,\n stored,\n FROZEN_CONTRACT_FIELDS,\n 'Contract',\n );\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const freezing = contract.status !== ContractStatus.DRAFT;\n const snapshotId = freezing\n ? stored.moneySnapshotId || crypto.randomUUID()\n : '';\n await recompute(\n scope.db,\n contract,\n lines,\n settings,\n true,\n snapshotId,\n );\n if (freezing) {\n const party =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db: scope.db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db: scope.db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n contract.partyTaxId = party?.taxId ?? '';\n contract.moneySnapshotId = snapshotId;\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Save a line and atomically recompute its Contract aggregate. */\nexport async function saveContractLineItem(\n input: ContractLineItem,\n): Promise<ContractLineItem> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n const contractId = persisted?.contractId ?? line.contractId;\n const settings = scope.settings;\n return withLockedContract(\n scope.db,\n scope.tenantId,\n contractId,\n async (contract, lines) => {\n if (getReceivedPurchaseMoney(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase evidence cannot contain calculated lines',\n );\n if (persisted && line.contractId !== persisted.contractId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract line cannot move between parents',\n );\n if (isFrozen(contract)) {\n if (!persisted)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot add a line to a frozen Contract',\n );\n assertUnchanged(\n line,\n persisted,\n FROZEN_LINE_FIELDS,\n 'Contract line',\n );\n return line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n line.contractId = contractId;\n line.tenantId = scope.tenantId;\n await line.saveDerived(DOCUMENT_MONEY_WRITE);\n const all = [\n ...lines.filter((candidate) => candidate.id !== line.id),\n line,\n ];\n await recompute(scope.db, contract, all, settings, true);\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n return line;\n },\n );\n }),\n );\n}\n\nexport async function deleteContractLineItem(\n input: ContractLineItem,\n options: DeleteOptions,\n): Promise<void> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n if (!persisted) return;\n const settings = scope.settings;\n await withLockedContract(\n scope.db,\n scope.tenantId,\n persisted.contractId,\n async (contract, lines) => {\n if (isFrozen(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a line from a frozen Contract',\n );\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n await recompute(\n scope.db,\n contract,\n lines.filter((candidate) => candidate.id !== persisted.id),\n settings,\n true,\n );\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Recompute a Contract through its normal runtime gate. */\nexport async function recalculateContractTotals(\n contract: Contract,\n): Promise<void> {\n await saveContract(contract);\n}\n\n/** Delete only a calculated Contract under the same lock as received-fact recording. */\nexport async function deleteContract(\n input: Contract,\n options: DeleteOptions,\n): Promise<void> {\n const contractId = input.id;\n if (!contractId) return;\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async ({ db }) => {\n const existing = await loadContract(db, contractId);\n if (!existing) return;\n await withLockedContract(db, tenantId, contractId, async (stored) => {\n if (stored.billingScheduleJson)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract billing schedule history cannot be deleted',\n );\n if (getReceivedPurchaseMoney(stored))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase monetary history cannot be deleted',\n );\n await input.withDatabase(db, (bound) =>\n bound.deleteDerived(DOCUMENT_MONEY_WRITE, options),\n );\n });\n });\n}\n"],"mappings":"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"}
|