@happyvertical/smrt-commerce 0.57.2 → 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.
Files changed (162) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +130 -0
  3. package/agents/accounting-snapshot-mapper.md +11 -0
  4. package/agents/contract-billing-schedules.md +67 -0
  5. package/agents/contract-money.md +81 -0
  6. package/agents/customer-funds.md +96 -0
  7. package/agents/historical-sales.md +132 -0
  8. package/agents/money-calculator.md +36 -0
  9. package/agents/money-model.md +6 -3
  10. package/agents/observed-purchase-prices.md +37 -0
  11. package/agents/payment-allocations.md +59 -0
  12. package/agents/received-sales-contract-money.md +38 -0
  13. package/dist/accounting-snapshot.d.ts +8 -1
  14. package/dist/accounting-snapshot.d.ts.map +1 -1
  15. package/dist/allocation-integrity.d.ts.map +1 -1
  16. package/dist/billing/period-close.d.ts.map +1 -1
  17. package/dist/chunks/{ContractLineItemCollection-BZcrlrrY.js → ContractLineItemCollection-J4xtRtzE.js} +6 -6
  18. package/dist/chunks/{ContractLineItemCollection-BZcrlrrY.js.map → ContractLineItemCollection-J4xtRtzE.js.map} +1 -1
  19. package/dist/chunks/DocumentTaxCollection-C42x0YLW.js +4385 -0
  20. package/dist/chunks/DocumentTaxCollection-C42x0YLW.js.map +1 -0
  21. package/dist/chunks/HistoricalCustomerFundsCollections-B5x8gTW3.js +170 -0
  22. package/dist/chunks/HistoricalCustomerFundsCollections-B5x8gTW3.js.map +1 -0
  23. package/dist/chunks/{InvoiceCollection-cinzAqt6.js → InvoiceCollection-BIcge9Wl.js} +131 -18
  24. package/dist/chunks/InvoiceCollection-BIcge9Wl.js.map +1 -0
  25. package/dist/chunks/{TaxCodeCollection-DaBOPG9c.js → TaxCodeCollection-RWdVaheF.js} +3 -4
  26. package/dist/chunks/TaxCodeCollection-RWdVaheF.js.map +1 -0
  27. package/dist/chunks/{TaxComponentCollection-C4zxnyVu.js → TaxComponentCollection-Dt2jeWiC.js} +3 -3
  28. package/dist/chunks/{TaxComponentCollection-C4zxnyVu.js.map → TaxComponentCollection-Dt2jeWiC.js.map} +1 -1
  29. package/dist/chunks/{allocation-integrity-WmWyXhtG.js → allocation-integrity-DJ7-LY1j.js} +7 -7
  30. package/dist/chunks/{allocation-integrity-WmWyXhtG.js.map → allocation-integrity-DJ7-LY1j.js.map} +1 -1
  31. package/dist/chunks/{contract-money-EuToOghh.js → contract-money-DSRTMoXm.js} +45 -12
  32. package/dist/chunks/contract-money-DSRTMoXm.js.map +1 -0
  33. package/dist/chunks/customer-funds-internal-BQzoJXK5.js +24 -0
  34. package/dist/chunks/customer-funds-internal-BQzoJXK5.js.map +1 -0
  35. package/dist/chunks/{detect-CY9ufaFb.js → detect-DAE-_NAk.js} +5 -2
  36. package/dist/chunks/{detect-CY9ufaFb.js.map → detect-DAE-_NAk.js.map} +1 -1
  37. package/dist/chunks/historical-commerce-internal-C9ButfuH.js +18 -0
  38. package/dist/chunks/historical-commerce-internal-C9ButfuH.js.map +1 -0
  39. package/dist/chunks/{invoice-posting-C2i5dOVZ.js → invoice-posting-BeTPw3h9.js} +5 -5
  40. package/dist/chunks/invoice-posting-BeTPw3h9.js.map +1 -0
  41. package/dist/chunks/retention-accounting-Brdj9x-V.js +2323 -0
  42. package/dist/chunks/retention-accounting-Brdj9x-V.js.map +1 -0
  43. package/dist/chunks/retention-accounting-internal-CBQRvd74.js +17 -0
  44. package/dist/chunks/retention-accounting-internal-CBQRvd74.js.map +1 -0
  45. package/dist/chunks/{tenant-money-barrier-6TlqUeVj.js → tenant-money-barrier-DoF6lR8h.js} +5 -5
  46. package/dist/chunks/{tenant-money-barrier-6TlqUeVj.js.map → tenant-money-barrier-DoF6lR8h.js.map} +1 -1
  47. package/dist/chunks/types-Bbm0Xflc.js.map +1 -1
  48. package/dist/collections/DocumentTaxCollection.d.ts +4 -1
  49. package/dist/collections/DocumentTaxCollection.d.ts.map +1 -1
  50. package/dist/collections/HistoricalCustomerFundsCollections.d.ts +10 -0
  51. package/dist/collections/HistoricalCustomerFundsCollections.d.ts.map +1 -0
  52. package/dist/collections/HistoricalSalesLineCollection.d.ts +6 -0
  53. package/dist/collections/HistoricalSalesLineCollection.d.ts.map +1 -0
  54. package/dist/collections/PaymentAllocationCollection.d.ts +2 -6
  55. package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -1
  56. package/dist/collections/PurchasePriceObservationCollection.d.ts +6 -0
  57. package/dist/collections/PurchasePriceObservationCollection.d.ts.map +1 -0
  58. package/dist/collections/index.d.ts +1 -0
  59. package/dist/collections/index.d.ts.map +1 -1
  60. package/dist/contract-billing-schedule.d.ts +83 -0
  61. package/dist/contract-billing-schedule.d.ts.map +1 -0
  62. package/dist/contract-money.d.ts +2 -0
  63. package/dist/contract-money.d.ts.map +1 -1
  64. package/dist/customer-funds-internal.d.ts +6 -0
  65. package/dist/customer-funds-internal.d.ts.map +1 -0
  66. package/dist/customer-funds.d.ts +108 -0
  67. package/dist/customer-funds.d.ts.map +1 -0
  68. package/dist/document-money.d.ts +11 -0
  69. package/dist/document-money.d.ts.map +1 -1
  70. package/dist/historical-commerce-internal.d.ts +3 -0
  71. package/dist/historical-commerce-internal.d.ts.map +1 -0
  72. package/dist/historical-customer-funds-types.d.ts +113 -0
  73. package/dist/historical-customer-funds-types.d.ts.map +1 -0
  74. package/dist/historical-customer-funds.d.ts +23 -0
  75. package/dist/historical-customer-funds.d.ts.map +1 -0
  76. package/dist/historical-funds-internal.d.ts +5 -0
  77. package/dist/historical-funds-internal.d.ts.map +1 -0
  78. package/dist/historical-sales-money.d.ts +22 -0
  79. package/dist/historical-sales-money.d.ts.map +1 -0
  80. package/dist/historical-sales-reports.d.ts +31 -0
  81. package/dist/historical-sales-reports.d.ts.map +1 -0
  82. package/dist/historical-sales-types.d.ts +106 -0
  83. package/dist/historical-sales-types.d.ts.map +1 -0
  84. package/dist/historical-sales-validation.d.ts +12 -0
  85. package/dist/historical-sales-validation.d.ts.map +1 -0
  86. package/dist/index.d.ts +21 -0
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1685 -1640
  89. package/dist/index.js.map +1 -1
  90. package/dist/invoice-accounting-sync.d.ts +0 -1
  91. package/dist/invoice-accounting-sync.d.ts.map +1 -1
  92. package/dist/invoice-mcp.d.ts.map +1 -1
  93. package/dist/invoice-posting.d.ts.map +1 -1
  94. package/dist/invoice-retention.d.ts +16 -0
  95. package/dist/invoice-retention.d.ts.map +1 -0
  96. package/dist/invoices.d.ts +2 -0
  97. package/dist/invoices.d.ts.map +1 -1
  98. package/dist/invoices.js +2 -2
  99. package/dist/manifest.json +7563 -4419
  100. package/dist/models/Contract.d.ts +14 -0
  101. package/dist/models/Contract.d.ts.map +1 -1
  102. package/dist/models/DocumentTax.d.ts +2 -0
  103. package/dist/models/DocumentTax.d.ts.map +1 -1
  104. package/dist/models/HistoricalCustomerFunds.d.ts +36 -0
  105. package/dist/models/HistoricalCustomerFunds.d.ts.map +1 -0
  106. package/dist/models/HistoricalCustomerFundsAllocation.d.ts +32 -0
  107. package/dist/models/HistoricalCustomerFundsAllocation.d.ts.map +1 -0
  108. package/dist/models/HistoricalSalesLine.d.ts +29 -0
  109. package/dist/models/HistoricalSalesLine.d.ts.map +1 -0
  110. package/dist/models/Invoice.d.ts +10 -1
  111. package/dist/models/Invoice.d.ts.map +1 -1
  112. package/dist/models/Payment.d.ts +7 -1
  113. package/dist/models/Payment.d.ts.map +1 -1
  114. package/dist/models/PaymentAllocation.d.ts +3 -1
  115. package/dist/models/PaymentAllocation.d.ts.map +1 -1
  116. package/dist/models/PaymentIntent.d.ts.map +1 -1
  117. package/dist/models/Payout.d.ts.map +1 -1
  118. package/dist/models/PurchasePriceObservation.d.ts +15 -0
  119. package/dist/models/PurchasePriceObservation.d.ts.map +1 -0
  120. package/dist/models/TaxCode.d.ts +1 -1
  121. package/dist/models/TaxCode.d.ts.map +1 -1
  122. package/dist/models/index.d.ts +1 -0
  123. package/dist/models/index.d.ts.map +1 -1
  124. package/dist/money.d.ts +5 -1
  125. package/dist/money.d.ts.map +1 -1
  126. package/dist/money.js +62 -15
  127. package/dist/money.js.map +1 -1
  128. package/dist/observed-purchase-price-internal.d.ts +3 -0
  129. package/dist/observed-purchase-price-internal.d.ts.map +1 -0
  130. package/dist/observed-purchase-price.d.ts +71 -0
  131. package/dist/observed-purchase-price.d.ts.map +1 -0
  132. package/dist/received-purchase-money.d.ts +87 -0
  133. package/dist/received-purchase-money.d.ts.map +1 -0
  134. package/dist/recipes.d.ts +4 -1
  135. package/dist/recipes.d.ts.map +1 -1
  136. package/dist/retention-accounting-internal.d.ts +3 -0
  137. package/dist/retention-accounting-internal.d.ts.map +1 -0
  138. package/dist/retention-accounting.d.ts +164 -0
  139. package/dist/retention-accounting.d.ts.map +1 -0
  140. package/dist/retention-money.d.ts +43 -0
  141. package/dist/retention-money.d.ts.map +1 -0
  142. package/dist/smrt-knowledge.json +1866 -113
  143. package/dist/tax/cookbook.d.ts.map +1 -1
  144. package/dist/tax-summary.d.ts +1 -0
  145. package/dist/tax-summary.d.ts.map +1 -1
  146. package/dist/types/index.d.ts +20 -1
  147. package/dist/types/index.d.ts.map +1 -1
  148. package/package.json +16 -16
  149. package/dist/chunks/CustomerCollection-BIPH2ZUx.js +0 -99
  150. package/dist/chunks/CustomerCollection-BIPH2ZUx.js.map +0 -1
  151. package/dist/chunks/DocumentTaxCollection-DDzlywMW.js +0 -1666
  152. package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +0 -1
  153. package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +0 -1
  154. package/dist/chunks/TaxCodeCollection-DaBOPG9c.js.map +0 -1
  155. package/dist/chunks/VendorCollection-D6ZcUYy8.js +0 -100
  156. package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +0 -1
  157. package/dist/chunks/calculations-C9daQLsh.js +0 -82
  158. package/dist/chunks/calculations-C9daQLsh.js.map +0 -1
  159. package/dist/chunks/contract-money-EuToOghh.js.map +0 -1
  160. package/dist/chunks/document-money-DV5t4vlK.js +0 -1186
  161. package/dist/chunks/document-money-DV5t4vlK.js.map +0 -1
  162. package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"document-money-DV5t4vlK.js","names":["field","saved"],"sources":["../../src/models/InvoiceLineItem.ts","../../src/collections/InvoiceLineItemCollection.ts","../../src/document-money.ts"],"sourcesContent":["/**\n * InvoiceLineItem model - individual items on an invoice\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n DOCUMENT_MONEY_READ_REBIND,\n DOCUMENT_MONEY_WRITE,\n type DocumentMoneyReadRebindCapability,\n type DocumentMoneyWriteCapability,\n} from '../document-money-internal.js';\nimport { MoneyValidationError } from '../money.js';\nimport type {\n InvoiceCreditCalculationBasis,\n InvoiceLineItemOptions,\n InvoiceTaxComponentSnapshot,\n} from '../types/index.js';\n\n/**\n * InvoiceLineItem represents a single line item on an invoice.\n *\n * Line items contain details of what is being billed, including\n * quantity, pricing, and optional source tracking (e.g., from ad campaigns).\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * invoiceId: invoice.id,\n * description: 'Display Advertising - Summer Campaign',\n * quantity: 50000, // impressions\n * unitPrice: 1, // 1 cent per impression (minor units)\n * taxRate: 0.05,\n * sourceType: 'campaign',\n * sourceId: 'campaign-uuid',\n * periodStart: new Date('2025-06-01'),\n * periodEnd: new Date('2025-06-30')\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: {\n include: ['list', 'get', 'create', 'update', 'delete'],\n writable: [\n 'invoiceId',\n 'description',\n 'sku',\n 'productId',\n 'quantity',\n 'unitPrice',\n 'discount',\n 'discountType',\n 'discountRate',\n 'taxRate',\n 'taxMode',\n 'taxCodeId',\n 'sourceType',\n 'sourceId',\n 'periodStart',\n 'periodEnd',\n 'revenueAccountId',\n 'sortOrder',\n ],\n },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class InvoiceLineItem extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n /** @internal Collection read guard returns this snapshot on its root DB. */\n rebindMoneyRead(\n capability: DocumentMoneyReadRebindCapability,\n db: DatabaseInterface,\n ): this {\n if (capability !== DOCUMENT_MONEY_READ_REBIND)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice line read rebinding requires the owning capability',\n );\n this._db = db;\n this.options.db = db;\n return this;\n }\n\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global invoice line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent invoice\n */\n @field({ description: 'The invoice this line belongs to.' })\n @foreignKey('Invoice', { role: 'items' })\n invoiceId: string = '';\n\n @foreignKey('InvoiceLineItem', { nullable: true })\n creditedLineId: string | null = null;\n\n @field({\n description:\n 'Frozen source basis used to cap and reproduce an issued credit line.',\n })\n creditCalculationBasisJson = '';\n\n get creditCalculationBasis(): InvoiceCreditCalculationBasis | null {\n try {\n const value: unknown = JSON.parse(this.creditCalculationBasisJson);\n if (!value || typeof value !== 'object' || !('kind' in value))\n return null;\n const basis = value as Record<string, unknown>;\n if (\n (basis.kind !== 'quantity' && basis.kind !== 'net_amount') ||\n typeof basis.sourceQuantity !== 'string' ||\n !Number.isSafeInteger(basis.sourceUnitPrice) ||\n (basis.sourceDiscountType !== 'none' &&\n basis.sourceDiscountType !== 'percent' &&\n basis.sourceDiscountType !== 'amount') ||\n typeof basis.sourceDiscountRate !== 'string' ||\n !Number.isSafeInteger(basis.sourceDiscountMinor) ||\n (basis.kind === 'quantity' && typeof basis.quantity !== 'string') ||\n (basis.kind === 'net_amount' && !Number.isSafeInteger(basis.netMinor))\n )\n return null;\n return basis as unknown as InvoiceCreditCalculationBasis;\n } catch {\n return null;\n }\n }\n\n set creditCalculationBasis(value: InvoiceCreditCalculationBasis | null) {\n this.creditCalculationBasisJson = value ? JSON.stringify(value) : '';\n }\n\n /**\n * Item description\n */\n @field({ description: 'What is being billed on this line.' })\n description: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n @crossPackageRef('@happyvertical/smrt-products:Product', { nullable: true })\n productId: string | null = null;\n\n /** Nonnegative decimal quantity (hours, weight, units), up to six fractional digits. */\n @field({ description: 'How many.' })\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis),\n * in the document's pricing mode: tax-inclusive when its `lineAmountType`\n * is `inclusive`.\n *\n * Money is exact, so it is stored as minor units and never as a float —\n * `$19.99` is `1999`. An integer literal is what maps this to an INTEGER\n * column (#2361).\n */\n @field({\n description:\n \"The price of one, before discount; includes tax when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2361).\n */\n @field({ ui: { format: 'money' } })\n discount: number = 0;\n\n discountType: 'none' | 'percent' | 'amount' = 'none';\n\n @field({ type: 'decimal' })\n discountRate = '0.0';\n\n /**\n * Tax rate as a decimal fraction (e.g., 0.05 for 5%).\n *\n * A genuine rate, not money: it is inherently fractional, so the `0.0`\n * initializer is load-bearing and maps it to a DECIMAL column. INTEGER would\n * truncate every rate to 0 (#2361).\n */\n @field({\n description:\n 'The stored fractional tax rate for this line (0.05 means 5%).',\n })\n taxRate: number = 0.0;\n\n taxMode: 'inherit' | 'override' = 'inherit';\n\n @foreignKey('TaxCode', { nullable: true })\n taxCodeId: string | null = null;\n\n taxCodeCode: string | null = null;\n\n taxCodeName: string | null = null;\n\n taxComponentsJson: string | null = null;\n\n get taxComponents(): InvoiceTaxComponentSnapshot[] {\n if (this.taxComponentsJson === null) return [];\n try {\n const value: unknown = JSON.parse(this.taxComponentsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (entry): entry is InvoiceTaxComponentSnapshot =>\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as InvoiceTaxComponentSnapshot).taxComponentId ===\n 'string' &&\n typeof (entry as InvoiceTaxComponentSnapshot).name === 'string' &&\n typeof (entry as InvoiceTaxComponentSnapshot).percent === 'string' &&\n Number.isSafeInteger((entry as InvoiceTaxComponentSnapshot).taxMinor),\n );\n } catch {\n return [];\n }\n }\n\n set taxComponents(value: InvoiceTaxComponentSnapshot[]) {\n this.taxComponentsJson = JSON.stringify(value);\n }\n\n /** Exact sum of frozen component taxes, in integer minor units. */\n @field({ ui: { format: 'money' } })\n taxAmount = 0;\n\n /** Stable document snapshot identity; ordinary lifecycle saves do not change it. */\n moneySnapshotId = '';\n\n /**\n * Calculated line amount, in integer minor units (#2361): net of discount\n * and before tax for `exclusive` / `none` documents, and tax-inclusive for\n * `inclusive` ones; {@link taxAmount} holds the tax.\n */\n @field({\n description:\n \"The line amount: net of discount, and including tax only when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n amount: number = 0;\n\n // ============================================================================\n // Source Tracking\n // ============================================================================\n\n /**\n * Type of source ('campaign' | 'contract' | 'manual' | etc.)\n */\n sourceType: string = '';\n\n /**\n * ID of the source (e.g., campaign ID, contract ID)\n */\n sourceId: string = '';\n\n /**\n * Service period start (for time-based billing)\n */\n periodStart: Date | null = null;\n\n /**\n * Service period end (for time-based billing)\n */\n periodEnd: Date | null = null;\n\n // ============================================================================\n // Accounting\n // ============================================================================\n\n /**\n * Revenue account ID (cross-package ref to smrt-ledgers)\n * Used for revenue recognition to specific accounts\n */\n @crossPackageRef('@happyvertical/smrt-ledgers:Account', { nullable: true })\n revenueAccountId: string | null = null;\n\n /**\n * Sort order within the invoice\n */\n sortOrder: number = 0;\n\n constructor(options: InvoiceLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.invoiceId !== undefined) this.invoiceId = options.invoiceId;\n if (options.creditedLineId !== undefined)\n this.creditedLineId = options.creditedLineId;\n if (options.creditCalculationBasisJson !== undefined)\n this.creditCalculationBasisJson = options.creditCalculationBasisJson;\n if (options.creditCalculationBasis !== undefined)\n this.creditCalculationBasis = structuredClone(\n options.creditCalculationBasis,\n );\n if (options.description !== undefined)\n this.description = options.description;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.discountType !== undefined)\n this.discountType = options.discountType;\n if (options.discountRate !== undefined)\n this.discountRate = options.discountRate;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.taxMode !== undefined) this.taxMode = options.taxMode;\n if (options.taxCodeId !== undefined) this.taxCodeId = options.taxCodeId;\n if (options.taxCodeCode !== undefined)\n this.taxCodeCode = options.taxCodeCode;\n if (options.taxCodeName !== undefined)\n this.taxCodeName = options.taxCodeName;\n if (options.taxComponentsJson !== undefined)\n this.taxComponentsJson = options.taxComponentsJson;\n if (options.taxComponents !== undefined)\n this.taxComponents = structuredClone(options.taxComponents);\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.moneySnapshotId !== undefined)\n this.moneySnapshotId = options.moneySnapshotId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.sourceType !== undefined) this.sourceType = options.sourceType;\n if (options.sourceId !== undefined) this.sourceId = options.sourceId;\n if (options.periodStart !== undefined)\n this.periodStart = options.periodStart;\n if (options.periodEnd !== undefined) this.periodEnd = options.periodEnd;\n if (options.revenueAccountId !== undefined)\n this.revenueAccountId = options.revenueAccountId;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n /** Refuse an old-format row read from the database. */\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 return this;\n }\n\n /** Persist through the invoice money aggregate. */\n override async save(): Promise<this> {\n const { saveInvoiceLineItem } = await import('../document-money.js');\n return (await saveInvoiceLineItem(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 line money write capability is required');\n // Money is integer minor units: refuse a major-unit price before it can\n // reach the database (#2401), ahead of the aggregate recompute.\n for (const [field, value] of [\n ['unitPrice', this.unitPrice],\n ['discount', this.discount],\n ] as const)\n if (!Number.isSafeInteger(value))\n throw new MoneyValidationError(\n 'UNSAFE_MONEY',\n `Invoice line ${field} ${value} must be a safe integer number of minor units`,\n );\n return (await super.save()) as this;\n }\n\n override async delete(\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n const { deleteInvoiceLineItem } = await import('../document-money.js');\n await deleteInvoiceLineItem(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 line money write capability is required');\n await super.delete(options);\n }\n\n /**\n * Check if line item has source tracking\n */\n hasSource(): boolean {\n return !!this.sourceType && !!this.sourceId;\n }\n\n /**\n * Check if line item has a service period\n */\n hasPeriod(): boolean {\n return !!this.periodStart && !!this.periodEnd;\n }\n}\n\nexport default InvoiceLineItem;\n","/**\n * InvoiceLineItemCollection - Collection manager for InvoiceLineItem 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 { InvoiceLineItem } from '../models/InvoiceLineItem.js';\nimport { withTenantMoneyCollectionRead } from '../tenant-money-barrier.js';\nimport { assertCommerceMoneySchemaCurrent } from '../upgrade/detect.js';\n\nexport class InvoiceLineItemCollection extends SmrtCollection<InvoiceLineItem> {\n static readonly _itemClass = InvoiceLineItem;\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 InvoiceLineItem)\n result.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n else if (Array.isArray(result))\n for (const item of result)\n if (item instanceof InvoiceLineItem)\n item.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n return result;\n }\n\n override async get(\n filter: string | SmrtWhereClause<InvoiceLineItem>,\n options: Parameters<SmrtCollection<InvoiceLineItem>['get']>[1] = {},\n ): Promise<InvoiceLineItem | null> {\n return this.withMoneyRead(() => super.get(filter, options));\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<InvoiceLineItem>[],\n >(\n options: Omit<SmrtListOptions<InvoiceLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n override async list<\n const Select extends readonly SmrtSelectField<InvoiceLineItem>[],\n >(\n options: Omit<SmrtListOptions<InvoiceLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<InvoiceLineItem, Select>[]>;\n override async list(\n options: Omit<SmrtListOptions<InvoiceLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n override async list(options?: undefined): Promise<InvoiceLineItem[]>;\n override async list(\n options: Omit<SmrtListOptions<InvoiceLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<InvoiceLineItem[]>;\n override async list(\n options:\n | (Omit<SmrtListOptions<InvoiceLineItem>, 'select'> & {\n select?: undefined;\n })\n | undefined,\n ): Promise<SmrtObject[]>;\n override async list(\n options: SmrtListOptions<InvoiceLineItem> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n override async list(\n options: SmrtListOptions<InvoiceLineItem> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n return this.withMoneyRead(() => super.list(options));\n }\n\n /**\n * Find line items by invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Array of line items, sorted by sortOrder\n */\n async findByInvoice(invoiceId: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { invoiceId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n /**\n * Find line items by source\n *\n * @param sourceType - Source type (e.g., 'campaign', 'contract')\n * @param sourceId - Source ID\n * @returns Array of line items\n */\n async findBySource(\n sourceType: string,\n sourceId: string,\n ): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType, sourceId },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Find line items by source type only\n *\n * @param sourceType - Source type (e.g., 'campaign')\n * @returns Array of line items\n */\n async findBySourceType(sourceType: string): Promise<InvoiceLineItem[]> {\n return await this.list({\n where: { sourceType },\n orderBy: 'created_at DESC',\n });\n }\n\n /**\n * Calculate total amount for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Total amount\n */\n async getTotalForInvoice(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n return lineItems.reduce((sum, item) => sum + item.amount, 0);\n }\n\n /**\n * Get the next sort order for an invoice\n *\n * @param invoiceId - Invoice ID\n * @returns Next sort order number\n */\n async getNextSortOrder(invoiceId: string): Promise<number> {\n const lineItems = await this.findByInvoice(invoiceId);\n if (lineItems.length === 0) return 0;\n\n const maxSortOrder = Math.max(...lineItems.map((item) => item.sortOrder));\n return maxSortOrder + 1;\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all invoice line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of invoice line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<InvoiceLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global invoice line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global invoice line items\n */\n async findGlobal(): Promise<InvoiceLineItem[]> {\n return queryGlobal<InvoiceLineItem>(this);\n }\n\n /**\n * Find invoice line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global invoice line items\n */\n async findWithGlobals(tenantId: string): Promise<InvoiceLineItem[]> {\n return queryWithGlobals<InvoiceLineItem>(\n this,\n tenantId,\n 'InvoiceLineItem.findWithGlobals',\n );\n }\n}\n","/** Transactional Invoice money aggregate persistence. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { CustomerCollection } from './collections/CustomerCollection.js';\nimport {\n DocumentTaxCollection,\n type DocumentTaxWriteInput,\n} from './collections/DocumentTaxCollection.js';\nimport { InvoiceCollection } from './collections/InvoiceCollection.js';\nimport { InvoiceLineItemCollection } from './collections/InvoiceLineItemCollection.js';\nimport { TaxCodeCollection } from './collections/TaxCodeCollection.js';\nimport { TaxComponentCollection } from './collections/TaxComponentCollection.js';\nimport {\n allocateFrozenCredit,\n type FrozenCreditAllocation,\n type IssuedCreditConsumption,\n} from './credit-allocation.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n lockDocumentMoneyParents,\n withDocumentMoneyObjectTransaction,\n withDocumentMoneyTransaction,\n} from './document-money-internal.js';\nimport type { DocumentTax } from './models/DocumentTax.js';\nimport type { Invoice } from './models/Invoice.js';\nimport { InvoiceLineItem } from './models/InvoiceLineItem.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport {\n calculateMoneyDocument,\n type MoneyDiscountInput,\n MoneyValidationError,\n type ResolvableTaxCodeInput,\n resolveLineTaxCode,\n} from './money.js';\nimport {\n lockTenantMoneyRuntime,\n withTenantMoneyRuntime,\n} from './tenant-money-barrier.js';\nimport type { InvoiceTaxComponentSnapshot } from './types/index.js';\nimport { InvoiceStatus } from './types/index.js';\n\ntype ProductTaxDefaults = {\n tenantId: string | null;\n defaultSalesTaxCodeId: string;\n};\n\ntype DeleteOptions = { expectedUpdatedAt?: Date | string };\n\nexport interface PreviewInvoiceMoneyInput {\n db: DatabaseInterface;\n tenantId: string;\n invoiceId: string;\n draft?: {\n header?: Partial<\n Pick<\n Invoice,\n | 'currency'\n | 'lineAmountType'\n | 'taxRoundingScope'\n | 'providerTaxAmount'\n | 'customerId'\n >\n >;\n lines?: Array<\n Partial<\n Pick<\n InvoiceLineItem,\n | 'id'\n | 'description'\n | 'productId'\n | 'quantity'\n | 'unitPrice'\n | 'discountType'\n | 'discountRate'\n | 'discount'\n | 'taxMode'\n | 'taxCodeId'\n >\n >\n >;\n };\n}\n\nexport interface PreviewInvoiceMoneyResult {\n source: 'calculated';\n header: {\n currency: string;\n lineAmountType: Invoice['lineAmountType'];\n taxRoundingScope: Invoice['taxRoundingScope'];\n subtotalMinor: number;\n discountTotalMinor: number;\n taxAmountMinor: number;\n providerTaxAmountMinor: number;\n totalAmountMinor: number;\n };\n lines: Array<{\n id: string;\n grossMinor: number;\n discountMinor: number;\n amountMinor: number;\n taxMinor: number;\n taxCodeId: string | null;\n taxCodeCode: string | null;\n taxCodeName: string | null;\n taxComponents: InvoiceLineItem['taxComponents'];\n }>;\n taxes: DocumentTaxWriteInput[];\n}\n\nexport interface GetInvoiceMoneyFactsInput {\n db: DatabaseInterface;\n tenantId: string;\n invoiceId: string;\n}\n\nexport interface InvoiceMoneyFacts {\n source: 'stored';\n invoiceId: string;\n moneySnapshotId: string;\n kind: Invoice['kind'];\n status: Invoice['status'];\n header: PreviewInvoiceMoneyResult['header'];\n lines: PreviewInvoiceMoneyResult['lines'];\n taxes: DocumentTaxWriteInput[];\n}\n\nconst FROZEN_INVOICE_FIELDS = [\n 'kind',\n 'creditedInvoiceId',\n 'tenantId',\n 'customerId',\n 'contractId',\n 'invoiceNumber',\n 'reference',\n 'issueDate',\n 'dueDate',\n 'subtotal',\n 'discountTotal',\n 'taxAmount',\n 'providerTaxAmount',\n 'totalAmount',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'sellerTaxRegistrationsJson',\n 'partyTaxId',\n 'moneySnapshotId',\n 'notes',\n 'customerNotes',\n 'terms',\n] as const;\n\nconst FROZEN_LINE_FIELDS = [\n 'tenantId',\n 'invoiceId',\n 'creditedLineId',\n 'creditCalculationBasisJson',\n 'description',\n 'sku',\n 'productId',\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 'sourceType',\n 'sourceId',\n 'periodStart',\n 'periodEnd',\n 'revenueAccountId',\n 'sortOrder',\n 'moneySnapshotId',\n] as const;\n\n/** Frozen-field identity: an absent optional reference (`''`) equals a stored NULL. */\nfunction comparable(value: unknown): unknown {\n if (value instanceof Date) return value.toISOString();\n return value === '' || value === undefined ? null : value;\n}\n\nfunction assertFieldsUnchanged<T extends object>(\n proposed: T,\n persisted: T,\n fields: readonly (keyof T)[],\n label: string,\n): void {\n for (const field of fields) {\n if (comparable(proposed[field]) !== comparable(persisted[field])) {\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `${label} ${String(field)} is frozen`,\n );\n }\n }\n}\n\nfunction assertTenantId(value: string | null): asserts value is string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money requires a tenant-owned invoice',\n );\n}\n\nfunction requireId(value: string | null | undefined, label: string): string {\n if (!value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} requires a persisted ID`,\n );\n return value;\n}\n\n/** Lock the tenant's settings row; every money document belongs to a tenant. */\nasync function lockRuntimeSettings(\n db: DatabaseInterface,\n tenantId: string | null,\n): Promise<TaxSettings> {\n if (!tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Commerce money documents require a tenant',\n );\n return lockTenantMoneyRuntime(db, tenantId);\n}\n\nfunction isFrozen(invoice: Invoice): boolean {\n return (\n invoice.status !== InvoiceStatus.DRAFT ||\n !!invoice.arJournalId ||\n !!invoice.moneySnapshotId\n );\n}\n\nfunction aggregateSignature(invoice: Invoice): string {\n return JSON.stringify([\n invoice.lineAmountType,\n invoice.taxRoundingScope,\n invoice.subtotal,\n invoice.discountTotal,\n invoice.taxAmount,\n invoice.totalAmount,\n ]);\n}\n\nasync function loadInvoice(\n db: DatabaseInterface,\n id: string,\n): Promise<{ collection: InvoiceCollection; invoice: Invoice }> {\n const collection = await InvoiceCollection.create({ db });\n const invoice = await collection.get({ id }, { cache: false });\n if (!invoice)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice ${id} was not found`,\n );\n return { collection, invoice };\n}\n\nasync function findInvoice(\n db: DatabaseInterface,\n id: string,\n): Promise<Invoice | null> {\n const collection = await InvoiceCollection.create({ db });\n return collection.get({ id }, { cache: false });\n}\n\n/** @internal Acquire the invoice money lock and reload persisted authority. */\nexport async function lockAndReloadInvoice(\n db: DatabaseInterface,\n invoiceId: string,\n tenantId: string,\n): Promise<{ collection: InvoiceCollection; invoice: Invoice }> {\n const first = await loadInvoice(db, invoiceId);\n await lockDocumentMoneyParents([\n { id: invoiceId, tenantId, collection: first.collection },\n ]);\n const invoice = await first.collection.get(\n { id: invoiceId },\n { cache: false },\n );\n if (!invoice || invoice.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant identity changed while acquiring its money lock',\n );\n return { collection: first.collection, invoice };\n}\n\nasync function lockAndReloadMoneyInvoice(\n db: DatabaseInterface,\n invoiceId: string,\n tenantId: string,\n creditedInvoiceId: string | null,\n): Promise<{\n collection: InvoiceCollection;\n invoice: Invoice;\n source: Invoice | null;\n}> {\n const collection = await InvoiceCollection.create({ db });\n const ids = [invoiceId, ...(creditedInvoiceId ? [creditedInvoiceId] : [])];\n const loaded = await Promise.all(\n ids.map((id) => collection.get({ id }, { cache: false })),\n );\n if (loaded.some((row) => !row || row.tenantId !== tenantId))\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money parents must exist in the active tenant',\n );\n await lockDocumentMoneyParents(\n ids.map((id) => ({ id, tenantId, collection })),\n );\n const invoice = await collection.get({ id: invoiceId }, { cache: false });\n const source = creditedInvoiceId\n ? await collection.get({ id: creditedInvoiceId }, { cache: false })\n : null;\n if (\n !invoice ||\n invoice.tenantId !== tenantId ||\n (source && source.tenantId !== tenantId)\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money parent identity changed while acquiring its lock',\n );\n return { collection, invoice, source };\n}\n\nfunction discountInput(line: InvoiceLineItem): MoneyDiscountInput {\n if (line.discountType === 'percent')\n return { kind: 'percent', rate: String(line.discountRate) };\n if (line.discountType === 'amount' || line.discount !== 0)\n return { kind: 'amount', minor: line.discount };\n if (line.discountType !== 'none')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Unknown discount type: ${line.discountType}`,\n );\n return { kind: 'none' };\n}\n\ntype CreditPreparation = {\n allocationByLineId: Map<string, FrozenCreditAllocation>;\n sourceByLineId: Map<string, InvoiceLineItem>;\n issuedLines: InvoiceLineItem[];\n issuedParentById: Map<string, Invoice>;\n sourceInvoice: Invoice;\n sourceTaxes: DocumentTax[];\n exhaustsDocument: boolean;\n};\n\nfunction issuedCredit(invoice: Invoice): boolean {\n return (\n invoice.kind === 'credit_note' &&\n invoice.status !== InvoiceStatus.DRAFT &&\n invoice.status !== InvoiceStatus.CANCELLED\n );\n}\n\nasync function refreshIssuedCreditBalance(\n db: DatabaseInterface,\n source: Invoice,\n): Promise<void> {\n const sourceId = requireId(source.id, 'Credited invoice');\n const credits = await (await InvoiceCollection.create({ db })).list({\n where: { tenantId: source.tenantId, creditedInvoiceId: sourceId },\n });\n const issued = credits.filter(issuedCredit).sort((left, right) => {\n const date = left.issueDate.getTime() - right.issueDate.getTime();\n if (date) return date;\n return String(left.id) < String(right.id)\n ? -1\n : String(left.id) > String(right.id)\n ? 1\n : 0;\n });\n const total = issued.reduce((sum, credit) => sum + credit.totalAmount, 0);\n if (!Number.isSafeInteger(total) || total < 0)\n throw new MoneyValidationError(\n 'UNSAFE_MONEY',\n 'Issued credit total is not a nonnegative safe integer',\n );\n let remainingSource = Math.max(0, source.totalAmount - source.amountPaid);\n for (const credit of credits) {\n const included = issued.includes(credit);\n const applied = included\n ? Math.min(credit.totalAmount, remainingSource)\n : 0;\n if (included) remainingSource -= applied;\n if (credit.creditedAmount !== applied) {\n credit.creditedAmount = applied;\n credit.updatePaymentStatus(credit.amountPaid);\n await credit.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n }\n source.creditedAmount = total;\n source.updatePaymentStatus(source.amountPaid);\n await source.saveDerived(DOCUMENT_MONEY_WRITE);\n}\n\nfunction lineNet(line: InvoiceLineItem, parent: Invoice): number {\n return parent.lineAmountType === 'inclusive'\n ? line.amount - line.taxAmount\n : line.amount;\n}\n\nasync function prepareCreditLines(\n db: DatabaseInterface,\n invoice: Invoice,\n lines: InvoiceLineItem[],\n): Promise<CreditPreparation> {\n if (!invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'A credit note requires a credited invoice',\n );\n const sourceInvoice = await findInvoice(db, invoice.creditedInvoiceId);\n if (\n sourceInvoice?.kind !== 'invoice' ||\n sourceInvoice.tenantId !== invoice.tenantId ||\n sourceInvoice.status === InvoiceStatus.DRAFT ||\n sourceInvoice.status === InvoiceStatus.CANCELLED ||\n !sourceInvoice.moneySnapshotId\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must be an issued, non-cancelled invoice in the same tenant',\n );\n if (sourceInvoice.currency !== invoice.currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n 'Credit note currency must match its credited invoice',\n );\n if (sourceInvoice.customerId !== invoice.customerId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credit note customer must match its credited invoice',\n );\n invoice.lineAmountType = sourceInvoice.lineAmountType;\n invoice.taxRoundingScope = sourceInvoice.taxRoundingScope;\n const lineCollection = await InvoiceLineItemCollection.create({ db });\n const sourceLines = await lineCollection.findByInvoice(\n requireId(sourceInvoice.id, 'Credited invoice'),\n );\n const sourceTaxes = await (\n await DocumentTaxCollection.create({ db })\n ).findByDocument(\n requireId(sourceInvoice.tenantId, 'Credited invoice tenant'),\n 'invoice',\n requireId(sourceInvoice.id, 'Credited invoice'),\n );\n const sourceByLineId = new Map(\n sourceLines.map((line) => [requireId(line.id, 'Credited line'), line]),\n );\n const invoices = await InvoiceCollection.create({ db });\n const priorCredits = (\n await invoices.list({\n where: {\n tenantId: invoice.tenantId,\n creditedInvoiceId: invoice.creditedInvoiceId,\n },\n })\n ).filter((row) => row.id !== invoice.id && issuedCredit(row));\n const issuedLines: InvoiceLineItem[] = [];\n const issuedParentById = new Map(\n priorCredits.map((row) => [requireId(row.id, 'Issued credit note'), row]),\n );\n const consumptions: IssuedCreditConsumption[] = [];\n for (const credit of priorCredits) {\n for (const line of await lineCollection.findByInvoice(\n requireId(credit.id, 'Issued credit note'),\n )) {\n if (!line.creditedLineId) continue;\n issuedLines.push(line);\n const basis = line.creditCalculationBasis;\n consumptions.push({\n creditedLineId: line.creditedLineId,\n quantity: basis?.kind === 'quantity' ? basis.quantity : '0',\n netMinor: lineNet(line, credit),\n discountMinor: line.discount,\n });\n }\n }\n const targeted = lines.filter((line) => line.creditedLineId);\n const allocation = targeted.length\n ? allocateFrozenCredit(\n sourceLines.map((line) => ({\n id: requireId(line.id, 'Credited line'),\n quantity: String(line.quantity),\n netMinor: lineNet(line, sourceInvoice),\n discountMinor: line.discount,\n unitPrice: line.unitPrice,\n discountType: line.discountType,\n discountRate: line.discountRate,\n })),\n consumptions,\n targeted.map((line) => {\n const basis = line.creditCalculationBasis;\n return basis?.kind === 'net_amount'\n ? {\n creditedLineId: requireId(line.creditedLineId, 'Credited line'),\n netMinor: basis.netMinor,\n }\n : {\n creditedLineId: requireId(line.creditedLineId, 'Credited line'),\n quantity:\n basis?.kind === 'quantity'\n ? basis.quantity\n : String(line.quantity),\n };\n }),\n )\n : { allocations: [], exhaustsDocument: false };\n const allocationByLineId = new Map<string, FrozenCreditAllocation>();\n const targetBySource = new Map(\n targeted.map((line) => [\n requireId(line.creditedLineId, 'Credited line'),\n line,\n ]),\n );\n for (const row of allocation.allocations) {\n const line = targetBySource.get(row.creditedLineId);\n if (!line)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credit allocation lost its target line',\n );\n line.creditCalculationBasis = row.basis;\n allocationByLineId.set(requireId(line.id, 'Credit line'), row);\n }\n return {\n allocationByLineId,\n sourceByLineId,\n issuedLines,\n issuedParentById,\n sourceInvoice,\n sourceTaxes,\n exhaustsDocument: allocation.exhaustsDocument,\n };\n}\n\nasync function recomputeInvoiceAggregate(\n db: DatabaseInterface,\n invoice: Invoice,\n settings: TaxSettings,\n freezeSnapshotId = '',\n persist = true,\n proposedLines?: InvoiceLineItem[],\n): Promise<{ lines: InvoiceLineItem[]; taxRows: DocumentTaxWriteInput[] }> {\n assertTenantId(invoice.tenantId);\n const tenantId = invoice.tenantId;\n const linesCollection = await InvoiceLineItemCollection.create({ db });\n const codesCollection = await TaxCodeCollection.create({ db });\n const componentsCollection = await TaxComponentCollection.create({ db });\n const invoiceId = requireId(invoice.id, 'Invoice calculation');\n const lines =\n proposedLines ?? (await linesCollection.findByInvoice(invoiceId));\n const credit =\n invoice.kind === 'credit_note'\n ? await prepareCreditLines(db, invoice, lines)\n : null;\n const codes = await codesCollection.list({ where: { tenantId } });\n const components = await componentsCollection.list({ where: { tenantId } });\n const componentById = new Map(\n components.map((row) => [requireId(row.id, 'Tax component'), row]),\n );\n const codeById = new Map(\n codes.map((row) => [requireId(row.id, 'Tax code'), row]),\n );\n const codeInputs: Record<string, ResolvableTaxCodeInput> = {};\n for (const code of codes) {\n const codeId = requireId(code.id, 'Tax code');\n codeInputs[codeId] = {\n id: codeId,\n kind: code.kind,\n active: code.active,\n appliesTo: code.appliesTo,\n components: code.components.map((part) => {\n const component = componentById.get(part.taxComponentId);\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: requireId(component.id, 'Tax component'),\n percent: component.percent,\n compound: part.compound,\n };\n }),\n };\n }\n if (credit) {\n for (const source of credit.sourceByLineId.values()) {\n if (!source.taxCodeId) continue;\n const frozenKind = credit.sourceTaxes.find(\n (row) => row.taxCodeId === source.taxCodeId,\n )?.kind;\n codeInputs[source.taxCodeId] = {\n id: source.taxCodeId,\n kind:\n frozenKind ??\n (source.taxComponents.length ? 'taxable' : 'zero-rated'),\n active: true,\n appliesTo: 'sales',\n components: source.taxComponents.map((component) => ({\n taxComponentId: component.taxComponentId,\n percent: component.percent,\n compound: false,\n })),\n };\n }\n }\n const exempt = await codesCollection.findByCode('exempt', tenantId);\n const outOfScope = await codesCollection.findByCode('out-of-scope', tenantId);\n const customers = await CustomerCollection.create({ db });\n const customer = invoice.customerId\n ? await customers.get({ id: invoice.customerId }, { cache: false })\n : null;\n if (customer && customer.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice customer must belong to its tenant',\n );\n\n // Products owns catalog reads. Load it lazily, on the aggregate transaction,\n // so commerce keeps the same runtime-only package edge used for ledgers.\n const productIds = [\n ...new Set(\n lines\n .filter((line) => !line.creditedLineId)\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 `Invoice product ${productId} was not found`,\n );\n if (product.tenantId !== null && product.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice product ${productId} must be global or belong to its tenant`,\n );\n productById.set(productId, product);\n }\n }\n\n type ResolvedMetadata = {\n id: string;\n code: string | null;\n name: string | null;\n kind: 'taxable' | 'zero-rated' | 'exempt' | 'out-of-scope';\n components: InvoiceTaxComponentSnapshot[];\n };\n const resolvedCodes = new Map<string, ResolvedMetadata | null>();\n const calculated = calculateMoneyDocument({\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n roundingScope: invoice.taxRoundingScope,\n lines: lines.map((line) => {\n if (line.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice line ${line.id} must belong to its parent tenant`,\n );\n const lineId = requireId(line.id, 'Invoice line');\n if (line.creditedLineId) {\n if (!credit)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Only a credit note may reference a credited line',\n );\n const source = credit.sourceByLineId.get(line.creditedLineId);\n const allocation = credit.allocationByLineId.get(lineId);\n if (!source || !allocation)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Credited line is not part of the frozen source invoice',\n );\n const taxCode = source.taxCodeId\n ? (codeInputs[source.taxCodeId] ?? null)\n : null;\n resolvedCodes.set(\n lineId,\n source.taxCodeId\n ? {\n id: source.taxCodeId,\n code: source.taxCodeCode,\n name: source.taxCodeName,\n kind: taxCode?.kind ?? 'zero-rated',\n components: structuredClone(source.taxComponents),\n }\n : null,\n );\n line.description ||= source.description;\n line.sku ||= source.sku;\n line.productId = source.productId;\n line.revenueAccountId = source.revenueAccountId;\n if (allocation.basis.kind === 'quantity') {\n line.quantity = Number(allocation.basis.quantity);\n line.unitPrice = allocation.basis.sourceUnitPrice;\n line.discountType = allocation.discountMinor ? 'amount' : 'none';\n line.discountRate = '0';\n } else {\n line.quantity = 1;\n line.unitPrice = allocation.netMinor ?? allocation.basis.netMinor;\n line.discountType = 'none';\n line.discountRate = '0';\n }\n line.discount = allocation.discountMinor;\n return {\n id: lineId,\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n netAmountMinor:\n allocation.basis.kind === 'net_amount'\n ? (allocation.netMinor ?? allocation.basis.netMinor)\n : undefined,\n discount: allocation.discountMinor\n ? ({ kind: 'amount', minor: allocation.discountMinor } as const)\n : ({ kind: 'none' } as const),\n taxCode,\n };\n }\n if (invoice.kind === 'invoice' && line.creditCalculationBasis)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Ordinary invoice lines cannot carry a credit calculation basis',\n );\n if (invoice.kind === 'credit_note' && line.taxMode !== 'override')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Free-form credit lines require an explicit tax-code override',\n );\n const resolved = resolveLineTaxCode(\n {\n lineAmountType: invoice.lineAmountType,\n lineOverride: line.taxMode === 'override' ? line.taxCodeId : null,\n },\n {\n direction: 'sales',\n codes: codeInputs,\n customerTaxExempt: customer?.taxExempt ?? false,\n exemptCodeId: exempt?.id,\n productCodeId: line.productId\n ? productById.get(line.productId)?.defaultSalesTaxCodeId || null\n : null,\n partyCodeId: customer?.defaultTaxCodeId,\n appDefaultCodeId: settings.defaultSalesTaxCodeId,\n outOfScopeCodeId: outOfScope?.id,\n },\n );\n resolvedCodes.set(\n lineId,\n resolved.taxCode\n ? (() => {\n const code = codeById.get(resolved.taxCode.id);\n if (!code)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax code ${resolved.taxCode.id} was not loaded`,\n );\n return {\n id: requireId(code.id, 'Tax code'),\n code: code.code,\n name: code.name,\n kind: code.kind,\n components: code.components.map((part) => {\n const component = componentById.get(part.taxComponentId);\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: 0,\n };\n }),\n };\n })()\n : null,\n );\n return {\n id: requireId(line.id, 'Invoice line'),\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n discount: discountInput(line),\n taxCode: resolved.taxCode,\n };\n }),\n });\n\n // When every original line is exhausted, consume the exact remaining frozen\n // component and taxable residual. This preserves document-rounding cents\n // without consulting today's rates; free-form goodwill lines remain separate.\n if (credit?.exhaustsDocument) {\n const resultById = new Map(calculated.lines.map((row) => [row.id, row]));\n const targeted = lines.filter((line) => !!line.creditedLineId);\n for (const line of targeted) {\n const source = credit.sourceByLineId.get(\n requireId(line.creditedLineId, 'Credited line'),\n );\n const result = resultById.get(requireId(line.id, 'Credit line'));\n if (!source || !result) continue;\n const prior = credit.issuedLines.filter(\n (candidate) => candidate.creditedLineId === source.id,\n );\n const priorNet = prior.reduce((sum, candidate) => {\n const parent = credit.issuedParentById.get(candidate.invoiceId);\n return sum + (parent ? lineNet(candidate, parent) : 0);\n }, 0);\n const desiredNet = lineNet(source, credit.sourceInvoice) - priorNet;\n const netDelta = desiredNet - result.netMinor;\n result.netMinor = desiredNet;\n result.amountMinor += netDelta;\n for (const sourceComponent of source.taxComponents) {\n const component = result.components.find(\n (candidate) =>\n candidate.taxComponentId === sourceComponent.taxComponentId,\n );\n if (!component) continue;\n const priorTax = prior.reduce(\n (sum, candidate) =>\n sum +\n (candidate.taxComponents.find(\n (part) => part.taxComponentId === sourceComponent.taxComponentId,\n )?.taxMinor ?? 0),\n 0,\n );\n const desiredTax = sourceComponent.taxMinor - priorTax;\n const taxDelta = desiredTax - component.taxMinor;\n component.taxMinor = desiredTax;\n result.taxMinor += taxDelta;\n if (invoice.lineAmountType === 'inclusive')\n result.amountMinor += taxDelta;\n }\n }\n const rebuilt = new Map<\n string,\n {\n taxCodeId: string | null;\n taxComponentId: string | null;\n taxableMinor: number;\n taxMinor: number;\n }\n >();\n for (const result of calculated.lines) {\n const metadata = resolvedCodes.get(result.id) ?? null;\n const parts = result.components.length\n ? result.components\n : [{ taxComponentId: null, taxMinor: 0 }];\n for (const part of parts) {\n const key = `${metadata?.id ?? ''}\\u0000${part.taxComponentId ?? ''}`;\n const row = rebuilt.get(key) ?? {\n taxCodeId: metadata?.id ?? null,\n taxComponentId: part.taxComponentId,\n taxableMinor: 0,\n taxMinor: 0,\n };\n row.taxableMinor += result.netMinor;\n row.taxMinor += part.taxMinor;\n rebuilt.set(key, row);\n }\n }\n calculated.taxes = [...rebuilt.values()].sort((left, right) => {\n const a = `${left.taxCodeId ?? ''}\\u0000${left.taxComponentId ?? ''}`;\n const b = `${right.taxCodeId ?? ''}\\u0000${right.taxComponentId ?? ''}`;\n return a < b ? -1 : a > b ? 1 : 0;\n });\n calculated.subtotalMinor = calculated.lines.reduce(\n (sum, row) => sum + row.netMinor,\n 0,\n );\n calculated.taxMinor = calculated.lines.reduce(\n (sum, row) => sum + row.taxMinor,\n 0,\n );\n calculated.totalMinor = calculated.subtotalMinor + calculated.taxMinor;\n }\n\n if (credit) {\n const resultById = new Map(calculated.lines.map((row) => [row.id, row]));\n for (const [lineId, allocation] of credit.allocationByLineId) {\n if (allocation.basis.kind !== 'quantity') continue;\n const result = resultById.get(lineId);\n const hasAmountBasisConsumption = credit.issuedLines.some(\n (line) =>\n line.creditedLineId === allocation.creditedLineId &&\n line.creditCalculationBasis?.kind === 'net_amount',\n );\n if (\n result &&\n Math.abs(result.netMinor) > Math.abs(allocation.remainingNetMinor) &&\n (!allocation.exhaustsLine || hasAmountBasisConsumption)\n )\n throw new MoneyValidationError(\n 'CREDIT_EXCEEDS_LINE',\n 'Credit quantity exceeds the remaining frozen net amount',\n );\n }\n }\n\n const lineById = new Map(\n lines.map((line) => [requireId(line.id, 'Invoice line'), line]),\n );\n for (const result of calculated.lines) {\n const line = lineById.get(result.id);\n if (!line)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Calculated line ${result.id} was not loaded`,\n );\n const code = resolvedCodes.get(requireId(line.id, 'Invoice line')) ?? null;\n line.discount = result.discountMinor;\n line.amount = result.amountMinor;\n line.taxAmount = result.taxMinor;\n line.taxCodeId = code?.id ?? null;\n line.taxCodeCode = code?.code ?? null;\n line.taxCodeName = code?.name ?? null;\n line.taxComponents = result.components.map((entry) => {\n const component = code?.components.find(\n (candidate) => candidate.taxComponentId === entry.taxComponentId,\n );\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Calculated component ${entry.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: component.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: entry.taxMinor,\n };\n });\n line.taxRate = code\n ? code.components.reduce(\n (sum, part) => sum + Number(part.percent) / 100,\n 0,\n )\n : 0;\n if (freezeSnapshotId) line.moneySnapshotId = freezeSnapshotId;\n if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n\n const taxRows = calculated.taxes.map((tax): DocumentTaxWriteInput => {\n const code = tax.taxCodeId\n ? ([...resolvedCodes.values()].find(\n (candidate) => candidate?.id === tax.taxCodeId,\n ) ?? null)\n : null;\n const snapshotComponent = tax.taxComponentId\n ? code?.components.find(\n (candidate) => candidate.taxComponentId === tax.taxComponentId,\n )\n : null;\n const currentComponent = tax.taxComponentId\n ? componentById.get(tax.taxComponentId)\n : null;\n const sourceTax = credit?.sourceTaxes.find(\n (row) =>\n row.taxCodeId === tax.taxCodeId &&\n row.taxComponentId === tax.taxComponentId,\n );\n if (tax.taxCodeId && !code)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Document tax code ${tax.taxCodeId} was not loaded`,\n );\n if (tax.taxComponentId && !snapshotComponent)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Document tax component ${tax.taxComponentId} was not loaded`,\n );\n return {\n tenantId,\n documentType: invoice.kind,\n documentId: invoiceId,\n currency: invoice.currency,\n taxCodeId: code ? requireId(code.id, 'Tax code') : null,\n taxCodeCode: code?.code ?? null,\n kind: code?.kind ?? null,\n taxComponentId: snapshotComponent?.taxComponentId ?? null,\n taxComponentName: snapshotComponent?.name ?? null,\n percent: snapshotComponent?.percent ?? null,\n liabilityAccountId:\n sourceTax?.liabilityAccountId ||\n currentComponent?.liabilityAccountId ||\n null,\n taxableMinor: tax.taxableMinor,\n taxMinor: tax.taxMinor,\n recoverableMinor: 0,\n };\n });\n if (persist) {\n const documentTaxes = await DocumentTaxCollection.create({ db });\n await documentTaxes.replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId,\n documentType: invoice.kind,\n documentId: invoiceId,\n rows: taxRows,\n });\n }\n invoice.subtotal = calculated.subtotalMinor;\n invoice.discountTotal = calculated.discountTotalMinor;\n invoice.taxAmount = calculated.taxMinor + invoice.providerTaxAmount;\n invoice.totalAmount = calculated.totalMinor + invoice.providerTaxAmount;\n return { lines, taxRows };\n}\n\nasync function previewInvoiceMoneyOnScope(\n input: PreviewInvoiceMoneyInput,\n settings: TaxSettings,\n): Promise<PreviewInvoiceMoneyResult> {\n if (!input.tenantId || getTenantId() !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview requires its active principal tenant',\n );\n const { invoice } = await loadInvoice(input.db, input.invoiceId);\n if (invoice.tenantId !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview cannot cross tenant boundaries',\n );\n if (invoice.status !== InvoiceStatus.DRAFT || invoice.moneySnapshotId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Frozen invoice money cannot be previewed as a proposed draft',\n );\n const proposedHeader = input.draft?.header;\n if (proposedHeader)\n for (const field of [\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'providerTaxAmount',\n 'customerId',\n ] as const)\n if (proposedHeader[field] !== undefined)\n invoice[field] = proposedHeader[field] as never;\n const storedLines = await (\n await InvoiceLineItemCollection.create({ db: input.db })\n ).findByInvoice(input.invoiceId);\n const storedById = new Map(storedLines.map((line) => [line.id, line]));\n const providedIds =\n input.draft?.lines\n ?.map((line) => line.id)\n .filter((id): id is string => !!id) ?? [];\n if (new Set(providedIds).size !== providedIds.length)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice preview line ids must be distinct',\n );\n const unknownId = providedIds.find((id) => !storedById.has(id));\n if (unknownId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Invoice preview line ${unknownId} was not found on the invoice`,\n );\n const proposedLines = input.draft?.lines\n ? input.draft.lines.map((proposed) => {\n const line = proposed.id ? storedById.get(proposed.id) : undefined;\n const result = line ?? new InvoiceLineItem();\n for (const field of [\n 'description',\n 'productId',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n ] as const)\n if (proposed[field] !== undefined)\n result[field] = proposed[field] as never;\n result.id ||= crypto.randomUUID();\n result.invoiceId = input.invoiceId;\n result.tenantId = input.tenantId;\n return result;\n })\n : storedLines;\n const calculated = await recomputeInvoiceAggregate(\n input.db,\n invoice,\n settings,\n '',\n false,\n proposedLines,\n );\n return {\n source: 'calculated',\n header: {\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n taxRoundingScope: invoice.taxRoundingScope,\n subtotalMinor: invoice.subtotal,\n discountTotalMinor: invoice.discountTotal,\n taxAmountMinor: invoice.taxAmount,\n providerTaxAmountMinor: invoice.providerTaxAmount,\n totalAmountMinor: invoice.totalAmount,\n },\n lines: calculated.lines.map((line) => ({\n id: requireId(line.id, 'Preview line'),\n grossMinor: line.amount + line.discount,\n discountMinor: line.discount,\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n taxCodeId: line.taxCodeId,\n taxCodeCode: line.taxCodeCode,\n taxCodeName: line.taxCodeName,\n taxComponents: structuredClone(line.taxComponents),\n })),\n taxes: calculated.taxRows,\n };\n}\n\n/** Calculate a proposed draft invoice in memory through the owning resolver. */\nexport async function previewInvoiceMoney(\n input: PreviewInvoiceMoneyInput,\n): Promise<PreviewInvoiceMoneyResult> {\n return withTenantMoneyRuntime(input.db, input.tenantId, async (scope) =>\n previewInvoiceMoneyOnScope({ ...input, db: scope.db }, scope.settings),\n );\n}\n\nasync function getInvoiceMoneyFactsOnScope(\n input: GetInvoiceMoneyFactsInput,\n): Promise<InvoiceMoneyFacts> {\n if (!input.tenantId || getTenantId() !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money facts require their active principal tenant',\n );\n const { invoice } = await loadInvoice(input.db, input.invoiceId);\n if (invoice.tenantId !== input.tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice money facts cannot cross tenant boundaries',\n );\n const lines = await (\n await InvoiceLineItemCollection.create({ db: input.db })\n ).findByInvoice(input.invoiceId);\n const taxes = await (\n await DocumentTaxCollection.create({ db: input.db })\n ).findByDocument(input.tenantId, invoice.kind, input.invoiceId);\n return {\n source: 'stored',\n invoiceId: input.invoiceId,\n moneySnapshotId: invoice.moneySnapshotId,\n kind: invoice.kind,\n status: invoice.status,\n header: {\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n taxRoundingScope: invoice.taxRoundingScope,\n subtotalMinor: invoice.subtotal,\n discountTotalMinor: invoice.discountTotal,\n taxAmountMinor: invoice.taxAmount,\n providerTaxAmountMinor: invoice.providerTaxAmount,\n totalAmountMinor: invoice.totalAmount,\n },\n lines: lines.map((line) => ({\n id: requireId(line.id, 'Invoice line'),\n grossMinor: line.amount + line.discount,\n discountMinor: line.discount,\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n taxCodeId: line.taxCodeId,\n taxCodeCode: line.taxCodeCode,\n taxCodeName: line.taxCodeName,\n taxComponents: structuredClone(line.taxComponents),\n })),\n taxes: taxes.map((row) => ({\n tenantId: requireId(row.tenantId, 'Document tax tenant'),\n documentType: row.documentType,\n documentId: row.documentId,\n currency: row.currency,\n taxCodeId: row.taxCodeId,\n taxCodeCode: row.taxCodeCode,\n kind: row.kind,\n taxComponentId: row.taxComponentId,\n taxComponentName: row.taxComponentName,\n percent: row.percent,\n liabilityAccountId: row.liabilityAccountId,\n taxableMinor: row.taxableMinor,\n taxMinor: row.taxMinor,\n recoverableMinor: row.recoverableMinor,\n })),\n };\n}\n\n/** Read authoritative persisted money facts without resolution or recalculation. */\nexport async function getInvoiceMoneyFacts(\n input: GetInvoiceMoneyFactsInput,\n): Promise<InvoiceMoneyFacts> {\n return withTenantMoneyRuntime(input.db, input.tenantId, async (scope) =>\n getInvoiceMoneyFactsOnScope({ ...input, db: scope.db }),\n );\n}\n\n/** Persist an invoice and recompute its complete money aggregate. */\nexport async function saveInvoice(input: Invoice): Promise<Invoice> {\n const priorRevision = input.updated_at;\n try {\n return await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (invoice) => {\n if (invoice.kind !== 'invoice' && invoice.kind !== 'credit_note')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice kind must be invoice or credit_note',\n );\n if (invoice.kind === 'invoice' && invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Only a credit note may reference a credited invoice',\n );\n if (invoice.kind === 'credit_note' && !invoice.creditedInvoiceId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'A credit note requires a credited invoice',\n );\n const candidateTenantId = getTenantId() ?? invoice.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const existing = invoice.id ? await findInvoice(tx, invoice.id) : null;\n const tenantId = existing?.tenantId ?? candidateTenantId;\n if (\n candidateTenantId &&\n existing?.tenantId &&\n existing.tenantId !== candidateTenantId\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant does not match its active money barrier',\n );\n assertTenantId(tenantId);\n if (!existing) {\n invoice.tenantId = tenantId;\n invoice.creditedAmount = 0;\n let source: Invoice | null = null;\n if (invoice.kind === 'credit_note') {\n source = await findInvoice(\n tx,\n requireId(invoice.creditedInvoiceId, 'Credited invoice'),\n );\n if (!source || source.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must exist in the active tenant',\n );\n await lockDocumentMoneyParents([\n {\n id: requireId(source.id, 'Credited invoice'),\n tenantId,\n collection: await InvoiceCollection.create({ db: tx }),\n },\n ]);\n if (\n source.kind !== 'invoice' ||\n source.status === InvoiceStatus.DRAFT ||\n source.status === InvoiceStatus.CANCELLED ||\n !source.moneySnapshotId\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit target must be an issued, non-cancelled invoice',\n );\n if (\n invoice.currency !== source.currency ||\n invoice.customerId !== source.customerId\n )\n throw new MoneyValidationError(\n invoice.currency !== source.currency\n ? 'CURRENCY_MISMATCH'\n : 'INVALID_MONEY_INPUT',\n 'Credit note currency and customer must match its source invoice',\n );\n }\n invoice.lineAmountType =\n invoice.kind === 'credit_note' && source\n ? source.lineAmountType\n : settings.defaultLineAmountType;\n invoice.taxRoundingScope =\n invoice.kind === 'credit_note' && source\n ? source.taxRoundingScope\n : settings.taxRoundingScope;\n invoice.subtotal = 0;\n invoice.discountTotal = 0;\n invoice.taxAmount = invoice.providerTaxAmount;\n invoice.totalAmount = invoice.providerTaxAmount;\n if (invoice.status !== InvoiceStatus.DRAFT || invoice.arJournalId) {\n const customers = await CustomerCollection.create({ db: tx });\n const customer = invoice.customerId\n ? await customers.get(\n { id: invoice.customerId },\n { cache: false },\n )\n : null;\n invoice.moneySnapshotId = crypto.randomUUID();\n invoice.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n invoice.partyTaxId = customer?.taxId ?? '';\n }\n return invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n if (invoice.tenantId !== existing.tenantId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice tenant identity cannot change',\n );\n if (\n invoice.kind !== existing.kind ||\n invoice.creditedInvoiceId !== existing.creditedInvoiceId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice kind and credited invoice identity are immutable',\n );\n const existingId = requireId(existing.id, 'Invoice');\n const locked = await lockAndReloadMoneyInvoice(\n tx,\n existingId,\n tenantId,\n existing.creditedInvoiceId,\n );\n invoice.creditedAmount = locked.invoice.creditedAmount;\n if (isFrozen(locked.invoice)) {\n if (\n invoice.arReversalJournalId !== locked.invoice.arReversalJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice reversal journal identity is server-managed',\n );\n if (\n locked.invoice.arJournalId &&\n invoice.arJournalId !== locked.invoice.arJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice revenue journal identity is frozen once assigned',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.kind === 'invoice' &&\n locked.invoice.isPaid()\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A paid invoice cannot be cancelled',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.kind === 'credit_note' &&\n locked.invoice.amountPaid > 0\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A refunded credit note cannot be cancelled',\n );\n if (\n invoice.status === InvoiceStatus.CANCELLED &&\n locked.invoice.status !== InvoiceStatus.CANCELLED &&\n locked.invoice.arJournalId &&\n !locked.invoice.arReversalJournalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'A posted invoice requires an atomic reversal before cancellation',\n );\n assertFieldsUnchanged(\n invoice,\n locked.invoice,\n FROZEN_INVOICE_FIELDS,\n 'Invoice',\n );\n const saved = await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n if (locked.source)\n await refreshIssuedCreditBalance(tx, locked.source);\n return saved;\n }\n const freezing =\n invoice.status !== InvoiceStatus.DRAFT || !!invoice.arJournalId;\n const snapshotId = freezing\n ? locked.invoice.moneySnapshotId || crypto.randomUUID()\n : '';\n await recomputeInvoiceAggregate(tx, invoice, settings, snapshotId);\n if (freezing) {\n const customers = await CustomerCollection.create({ db: tx });\n const customer = invoice.customerId\n ? await customers.get({ id: invoice.customerId }, { cache: false })\n : null;\n invoice.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n invoice.partyTaxId = customer?.taxId ?? '';\n invoice.moneySnapshotId = snapshotId;\n }\n const saved = await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n if (locked.source) await refreshIssuedCreditBalance(tx, locked.source);\n return saved;\n }),\n );\n } catch (error) {\n input.updated_at = priorRevision;\n throw error;\n }\n}\n\n/** Persist a line and recompute its complete invoice aggregate. */\nexport async function saveInvoiceLineItem(\n input: InvoiceLineItem,\n): Promise<InvoiceLineItem> {\n return withDocumentMoneyObjectTransaction(input, async (line, tx) => {\n const candidateTenantId = getTenantId() ?? line.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const persisted = line.id\n ? await (await InvoiceLineItemCollection.create({ db: tx })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n const parentId = persisted?.invoiceId ?? line.invoiceId;\n const first = await loadInvoice(tx, parentId);\n if (candidateTenantId && first.invoice.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice line tenant does not match its active money barrier',\n );\n assertTenantId(first.invoice.tenantId);\n const { invoice, source } = await lockAndReloadMoneyInvoice(\n tx,\n parentId,\n first.invoice.tenantId,\n first.invoice.creditedInvoiceId,\n );\n if (persisted && line.invoiceId !== persisted.invoiceId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice line cannot move between parents',\n );\n line.invoiceId = requireId(invoice.id, 'Invoice');\n line.tenantId = invoice.tenantId;\n if (invoice.kind === 'invoice' && line.creditedLineId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Ordinary invoice lines cannot reference credited lines',\n );\n if (\n invoice.kind === 'credit_note' &&\n line.creditedLineId &&\n (!source || source.id !== invoice.creditedInvoiceId)\n )\n throw new MoneyValidationError(\n 'CREDIT_TARGET_NOT_ISSUED',\n 'Credit source changed while saving its line',\n );\n if (isFrozen(invoice)) {\n if (!persisted)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot add a line to a frozen invoice',\n );\n assertFieldsUnchanged(\n line,\n persisted,\n FROZEN_LINE_FIELDS,\n 'Invoice line',\n );\n return line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n await line.saveDerived(DOCUMENT_MONEY_WRITE);\n const priorAggregate = aggregateSignature(invoice);\n await recomputeInvoiceAggregate(tx, invoice, settings);\n if (aggregateSignature(invoice) !== priorAggregate) {\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const refreshed = await (\n await InvoiceLineItemCollection.create({ db: tx })\n ).get({ id: requireId(line.id, 'Invoice line') }, { cache: false });\n if (refreshed) {\n for (const field of [\n 'discount',\n 'amount',\n 'taxAmount',\n 'taxRate',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'moneySnapshotId',\n 'updated_at',\n ] as const) {\n line[field] = refreshed[field] as never;\n }\n }\n return line;\n });\n}\n\nexport async function deleteInvoiceLineItem(\n input: InvoiceLineItem,\n options: DeleteOptions,\n): Promise<void> {\n await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (line) => {\n const candidateTenantId = getTenantId() ?? line.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const persisted = line.id\n ? await (await InvoiceLineItemCollection.create({ db: tx })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n if (!persisted) return;\n const first = await loadInvoice(tx, persisted.invoiceId);\n if (candidateTenantId && first.invoice.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice line tenant does not match its active money barrier',\n );\n assertTenantId(first.invoice.tenantId);\n const { invoice } = await lockAndReloadInvoice(\n tx,\n persisted.invoiceId,\n first.invoice.tenantId,\n );\n if (isFrozen(invoice))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a line from a frozen invoice',\n );\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n const priorAggregate = aggregateSignature(invoice);\n await recomputeInvoiceAggregate(tx, invoice, settings);\n if (aggregateSignature(invoice) !== priorAggregate) {\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n }),\n );\n}\n\nexport async function deleteInvoice(\n input: Invoice,\n options: DeleteOptions,\n): Promise<void> {\n await withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (invoice) => {\n const candidateTenantId = getTenantId() ?? invoice.tenantId ?? null;\n const settings = await lockRuntimeSettings(tx, candidateTenantId);\n const persisted = invoice.id ? await findInvoice(tx, invoice.id) : null;\n if (!persisted) return;\n if (candidateTenantId && persisted.tenantId !== candidateTenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice tenant does not match its active money barrier',\n );\n if (isFrozen(persisted))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a frozen invoice',\n );\n assertTenantId(persisted.tenantId);\n const persistedId = requireId(persisted.id, 'Invoice');\n await lockAndReloadInvoice(tx, persistedId, persisted.tenantId);\n const documentTaxes = await DocumentTaxCollection.create({ db: tx });\n await documentTaxes.replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId: persisted.tenantId,\n documentType: 'invoice',\n documentId: persistedId,\n rows: [],\n });\n await invoice.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n }),\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"invoice-posting-C2i5dOVZ.js","names":[],"sources":["../../src/invoice-posting.ts"],"sourcesContent":["/** Atomic revenue posting and cancellation reversal. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport type { Journal, JournalEntryData } from '@happyvertical/smrt-ledgers';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ISO_4217_MINOR_UNITS } from '@happyvertical/smrt-ui/currency';\nimport { deterministicId } from './billing/units.js';\nimport { DocumentTaxCollection } from './collections/DocumentTaxCollection.js';\nimport { InvoiceLineItemCollection } from './collections/InvoiceLineItemCollection.js';\nimport { lockAndReloadInvoice } from './document-money.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n withDocumentMoneyTransaction,\n} from './document-money-internal.js';\nimport type { Invoice } from './models/Invoice.js';\nimport { MoneyValidationError } from './money.js';\nimport { buildRevenueJournalEntries } from './revenue-journal.js';\nimport { lockTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n type CancelRevenueOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n} from './types/index.js';\n\ntype PostingMetadata = {\n kind:\n | 'invoice-revenue'\n | 'credit-note-revenue'\n | 'invoice-cancellation-reversal'\n | 'credit-note-cancellation-reversal';\n invoiceId: string;\n moneySnapshotId: string;\n originalJournalId?: string;\n};\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} must be a nonempty value`,\n );\n return value;\n}\n\nfunction bookCurrency(value: string | undefined): string {\n if (\n !value ||\n value !== value.toUpperCase() ||\n !ISO_4217_MINOR_UNITS.has(value) ||\n ISO_4217_MINOR_UNITS.get(value) === null\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Revenue posting requires a supported ISO 4217 bookCurrency',\n );\n return value;\n}\n\nfunction assertTenantAuthority(tenantId: string): void {\n if (getTenantId() !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice posting requires its active tenant context',\n );\n}\n\nfunction canonicalEntries(entries: readonly JournalEntryData[]): string {\n return JSON.stringify(\n entries\n .map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit ?? 0,\n credit: entry.credit ?? 0,\n currency: entry.currency ?? '',\n memo: entry.memo ?? '',\n }))\n .sort((left, right) => {\n const leftKey = JSON.stringify(left);\n const rightKey = JSON.stringify(right);\n return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;\n }),\n );\n}\n\nasync function validateAccounts(\n db: DatabaseInterface,\n tenantId: string,\n accountIds: readonly string[],\n): Promise<void> {\n const { AccountCollection } = await import('@happyvertical/smrt-ledgers');\n const accounts = await AccountCollection.create({ db });\n for (const accountId of [...new Set(accountIds)]) {\n const account = await accounts.get({ id: accountId }, { cache: false });\n if (!account || account.tenantId !== tenantId || !account.active)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Posting account ${accountId} must be active and owned by the invoice tenant`,\n );\n }\n}\n\nasync function entriesForJournal(\n db: DatabaseInterface,\n journalId: string,\n): Promise<JournalEntryData[]> {\n const { JournalEntryCollection } = await import(\n '@happyvertical/smrt-ledgers'\n );\n const entries = await (\n await JournalEntryCollection.create({ db })\n ).findByJournal(journalId);\n return entries.map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit,\n credit: entry.credit,\n currency: entry.currency,\n memo: entry.memo,\n }));\n}\n\nfunction metadataMatches(journal: Journal, expected: PostingMetadata): boolean {\n const actual = journal.metadata as Partial<PostingMetadata>;\n return (\n actual.kind === expected.kind &&\n actual.invoiceId === expected.invoiceId &&\n actual.moneySnapshotId === expected.moneySnapshotId &&\n actual.originalJournalId === expected.originalJournalId\n );\n}\n\nasync function createOrReadPostedJournal(\n db: DatabaseInterface,\n input: {\n id: string;\n tenantId: string;\n sourceRef: string;\n description: string;\n date: Date;\n metadata: PostingMetadata;\n entries: readonly JournalEntryData[];\n },\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db });\n const existing = await journals.get({ id: input.id }, { cache: false });\n if (existing) {\n const storedEntries = await entriesForJournal(db, input.id);\n if (\n existing.status !== 'posted' ||\n existing.tenantId !== input.tenantId ||\n existing.sourceModule !== 'smrt-commerce' ||\n existing.sourceRef !== input.sourceRef ||\n !metadataMatches(existing, input.metadata) ||\n canonicalEntries(storedEntries) !== canonicalEntries(input.entries)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `Deterministic journal ${input.id} conflicts with the frozen invoice snapshot`,\n );\n return existing;\n }\n const journal = await journals.create({\n id: input.id,\n tenantId: input.tenantId,\n date: input.date,\n description: input.description,\n sourceModule: 'smrt-commerce',\n sourceRef: input.sourceRef,\n metadata: input.metadata,\n _insertOnly: true,\n });\n for (const entry of input.entries) await journal.addEntry(entry);\n await journal.post();\n if (!journal.id)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Posted revenue journal is missing its identity',\n );\n return journal;\n}\n\n/**\n * Reload a journal posted inside a money transaction on `db`, so the caller\n * gets an instance whose reads do not run on the finished transaction.\n */\nexport async function rebindJournal(\n db: DatabaseInterface,\n journal: Journal,\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n return (\n (await (\n await JournalCollection.create({ db })\n ).get({ id: required(journal.id, 'Journal ID') }, { cache: false })) ??\n journal\n );\n}\n\n/** Post an issued invoice's revenue journal from its frozen money facts. */\nexport async function recognizeInvoiceRevenue(\n input: Invoice,\n options: RecognizeRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.status === InvoiceStatus.DRAFT || !invoice.moneySnapshotId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue recognition requires an issued frozen invoice',\n );\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n\n const lines = await (\n await InvoiceLineItemCollection.create({ db: tx })\n ).findByInvoice(invoiceId);\n const taxes = await (\n await DocumentTaxCollection.create({ db: tx })\n ).findByDocument(tenantId, invoice.kind, invoiceId);\n const built = buildRevenueJournalEntries(\n {\n id: invoiceId,\n number: invoice.invoiceNumber,\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n subtotalMinor: invoice.subtotal,\n taxMinor: invoice.taxAmount,\n totalMinor: invoice.totalAmount,\n providerTaxMinor: invoice.providerTaxAmount,\n },\n lines.map((line) => ({\n id: required(line.id, 'Invoice line ID'),\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n revenueAccountId: line.revenueAccountId,\n })),\n taxes.map((tax) => ({\n currency: tax.currency,\n taxCodeId: tax.taxCodeId,\n taxCodeCode: tax.taxCodeCode,\n taxComponentId: tax.taxComponentId,\n liabilityAccountId: tax.liabilityAccountId,\n taxMinor: tax.taxMinor,\n })),\n {\n arAccountId: options.arAccountId,\n revenueAccountId: options.revenueAccountId,\n taxAccountId: options.taxAccountId,\n bookCurrency: currency,\n direction: invoice.kind === 'credit_note' ? -1 : 1,\n },\n );\n await validateAccounts(\n tx,\n tenantId,\n built.entries.map((entry) => entry.accountId),\n );\n const postingKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const journalId = await deterministicId([\n postingKind,\n tenantId,\n invoiceId,\n invoice.moneySnapshotId,\n ]);\n const metadata: PostingMetadata = {\n kind: postingKind,\n invoiceId,\n moneySnapshotId: invoice.moneySnapshotId,\n };\n const journal = await createOrReadPostedJournal(tx, {\n id: journalId,\n tenantId,\n sourceRef: `${postingKind}:${invoiceId}:${invoice.moneySnapshotId}`,\n description: `${invoice.kind === 'credit_note' ? 'Credit note' : 'Revenue'}: Invoice ${invoice.invoiceNumber}`,\n date: invoice.issueDate,\n metadata,\n entries: built.entries,\n });\n if (invoice.arJournalId && invoice.arJournalId !== journalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different revenue journal',\n );\n invoice.arJournalId = journalId;\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arJournalId = journalId;\n return journal;\n }),\n );\n}\n\nfunction reverseEntries(\n entries: readonly JournalEntryData[],\n currency: string,\n): JournalEntryData[] {\n return entries.map((entry, index) => {\n if (\n entry.currency !== currency ||\n !Number.isSafeInteger(entry.debit ?? 0) ||\n !Number.isSafeInteger(entry.credit ?? 0) ||\n (entry.debit ?? 0) < 0 ||\n (entry.credit ?? 0) < 0 ||\n (entry.debit ?? 0) > 0 === (entry.credit ?? 0) > 0\n )\n throw new MoneyValidationError(\n entry.currency !== currency ? 'CURRENCY_MISMATCH' : 'UNSAFE_MONEY',\n `Original journal entry ${index + 1} is not an exact ${currency} movement`,\n );\n return {\n accountId: required(entry.accountId, 'Original journal account ID'),\n debit: entry.credit ?? 0,\n credit: entry.debit ?? 0,\n currency,\n memo: `Reversal: ${entry.memo ?? ''}`,\n };\n });\n}\n\n/** Atomically reverse a posted invoice and move it to CANCELLED. */\nexport async function cancelInvoiceWithRevenueReversal(\n input: Invoice,\n options: CancelRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n if (\n (invoice.kind === 'invoice' && invoice.isPaid()) ||\n (invoice.kind === 'credit_note' && invoice.amountPaid > 0)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Paid invoices and refunded credit notes cannot be cancelled',\n );\n if (!invoice.arJournalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue must be posted before using cancellation reversal',\n );\n const snapshotId = required(\n invoice.moneySnapshotId,\n 'Invoice money snapshot ID',\n );\n const originalJournalId = invoice.arJournalId;\n const originalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db: tx });\n const original = await journals.get(\n { id: originalJournalId },\n { cache: false },\n );\n if (\n original?.status !== 'posted' ||\n original.tenantId !== tenantId ||\n original.sourceModule !== 'smrt-commerce' ||\n original.sourceRef !== `${originalKind}:${invoiceId}:${snapshotId}` ||\n !metadataMatches(original, {\n kind: originalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n })\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice revenue journal must be posted and tenant-owned before reversal',\n );\n const originalEntries = await entriesForJournal(tx, originalJournalId);\n const reversalEntries = reverseEntries(originalEntries, currency);\n await validateAccounts(\n tx,\n tenantId,\n reversalEntries.map((entry) => entry.accountId),\n );\n const reversalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-cancellation-reversal' as const)\n : ('invoice-cancellation-reversal' as const);\n const reversalId = await deterministicId([\n reversalKind,\n tenantId,\n invoiceId,\n snapshotId,\n originalJournalId,\n ]);\n const metadata: PostingMetadata = {\n kind: reversalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n originalJournalId,\n };\n const reversal = await createOrReadPostedJournal(tx, {\n id: reversalId,\n tenantId,\n sourceRef: `invoice-cancellation:${invoiceId}:${snapshotId}`,\n description: `Cancellation reversal: Invoice ${invoice.invoiceNumber}`,\n date: new Date(),\n metadata,\n entries: reversalEntries,\n });\n if (\n invoice.arReversalJournalId &&\n invoice.arReversalJournalId !== reversalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different cancellation reversal',\n );\n invoice.arReversalJournalId = reversalId;\n if (invoice.status !== InvoiceStatus.CANCELLED) invoice.cancel();\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arReversalJournalId = reversalId;\n callerInvoice.status = InvoiceStatus.CANCELLED;\n return reversal;\n }),\n );\n}\n"],"mappings":"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"}