@happyvertical/smrt-commerce 0.56.0 → 0.56.1

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 (211) hide show
  1. package/AGENTS.md +9 -8
  2. package/README.md +76 -0
  3. package/agents/accounting-snapshot-mapper.md +111 -0
  4. package/agents/contract-money.md +18 -0
  5. package/agents/cost-billing.md +7 -2
  6. package/agents/document-money.md +126 -0
  7. package/agents/invoice-calculations.md +14 -2
  8. package/agents/line-amount-migration.md +50 -0
  9. package/agents/minor-unit-migration.md +20 -0
  10. package/agents/money-calculator.md +45 -0
  11. package/agents/money-model.md +113 -0
  12. package/agents/revenue-journal.md +23 -0
  13. package/agents/tax-codes.md +48 -0
  14. package/agents/tax-summary.md +126 -0
  15. package/dist/accounting-snapshot.d.ts +79 -0
  16. package/dist/accounting-snapshot.d.ts.map +1 -0
  17. package/dist/billing/runtime.d.ts +2 -0
  18. package/dist/billing/runtime.d.ts.map +1 -1
  19. package/dist/chunks/ContractLineItemCollection-DYjhj_pU.js +265 -0
  20. package/dist/chunks/ContractLineItemCollection-DYjhj_pU.js.map +1 -0
  21. package/dist/chunks/CustomerCollection-DQIujn4f.js +99 -0
  22. package/dist/chunks/CustomerCollection-DQIujn4f.js.map +1 -0
  23. package/dist/chunks/DocumentTaxCollection-BEpoqXAQ.js +1639 -0
  24. package/dist/chunks/DocumentTaxCollection-BEpoqXAQ.js.map +1 -0
  25. package/dist/chunks/InvoiceCollection-CKQflFjc.js +1047 -0
  26. package/dist/chunks/InvoiceCollection-CKQflFjc.js.map +1 -0
  27. package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js +467 -0
  28. package/dist/chunks/InvoiceLineItemCollection-CHm9zdqh.js.map +1 -0
  29. package/dist/chunks/TaxCodeCollection-DOpzW4y8.js +149 -0
  30. package/dist/chunks/TaxCodeCollection-DOpzW4y8.js.map +1 -0
  31. package/dist/chunks/TaxComponentCollection-Cs2uXwEl.js +129 -0
  32. package/dist/chunks/TaxComponentCollection-Cs2uXwEl.js.map +1 -0
  33. package/dist/chunks/TaxSettingsCollection-BBTZDH1S.js +319 -0
  34. package/dist/chunks/TaxSettingsCollection-BBTZDH1S.js.map +1 -0
  35. package/dist/chunks/{money-DfrRxrel.js → allocation-integrity-DI3G16BN.js} +7 -12
  36. package/dist/chunks/allocation-integrity-DI3G16BN.js.map +1 -0
  37. package/dist/chunks/{calculations-Bz7RB3qK.js → calculations-DaHR17il.js} +134 -54
  38. package/dist/chunks/calculations-DaHR17il.js.map +1 -0
  39. package/dist/chunks/credit-allocation-yspz0Lab.js +121 -0
  40. package/dist/chunks/credit-allocation-yspz0Lab.js.map +1 -0
  41. package/dist/chunks/document-money-DNVhDJyC.js +860 -0
  42. package/dist/chunks/document-money-DNVhDJyC.js.map +1 -0
  43. package/dist/chunks/invoice-posting-BwV8g7z0.js +229 -0
  44. package/dist/chunks/invoice-posting-BwV8g7z0.js.map +1 -0
  45. package/dist/chunks/money-rational-CcW7gzG5.js +64 -0
  46. package/dist/chunks/money-rational-CcW7gzG5.js.map +1 -0
  47. package/dist/chunks/revenue-journal-DY9fro8E.js +140 -0
  48. package/dist/chunks/revenue-journal-DY9fro8E.js.map +1 -0
  49. package/dist/chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  50. package/dist/chunks/tenant-money-barrier-DjRXgMnX.js +135 -0
  51. package/dist/chunks/tenant-money-barrier-DjRXgMnX.js.map +1 -0
  52. package/dist/chunks/{types-zVnIjSOk.js → types-LDETvQ1C.js} +3 -14
  53. package/dist/chunks/types-LDETvQ1C.js.map +1 -0
  54. package/dist/chunks/{units-BpZ0_Dpk.js → units-CNcjH_eE.js} +2 -2
  55. package/dist/chunks/{units-BpZ0_Dpk.js.map → units-CNcjH_eE.js.map} +1 -1
  56. package/dist/collections/ContractCollection.d.ts +35 -1
  57. package/dist/collections/ContractCollection.d.ts.map +1 -1
  58. package/dist/collections/ContractLineItemCollection.d.ts +26 -1
  59. package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
  60. package/dist/collections/DocumentTaxCollection.d.ts +75 -0
  61. package/dist/collections/DocumentTaxCollection.d.ts.map +1 -0
  62. package/dist/collections/InvoiceCollection.d.ts +26 -1
  63. package/dist/collections/InvoiceCollection.d.ts.map +1 -1
  64. package/dist/collections/InvoiceLineItemCollection.d.ts +26 -1
  65. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
  66. package/dist/collections/MoneyMigrationDocumentCollection.d.ts +12 -0
  67. package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +1 -0
  68. package/dist/collections/PaymentAllocationCollection.d.ts +2 -0
  69. package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -1
  70. package/dist/collections/TaxCodeCollection.d.ts +7 -0
  71. package/dist/collections/TaxCodeCollection.d.ts.map +1 -0
  72. package/dist/collections/TaxComponentCollection.d.ts +6 -0
  73. package/dist/collections/TaxComponentCollection.d.ts.map +1 -0
  74. package/dist/collections/TaxSettingsCollection.d.ts +16 -0
  75. package/dist/collections/TaxSettingsCollection.d.ts.map +1 -0
  76. package/dist/collections/TaxSummaryReportCollection.d.ts +151 -0
  77. package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -0
  78. package/dist/collections/index.d.ts +6 -0
  79. package/dist/collections/index.d.ts.map +1 -1
  80. package/dist/commerce-money-tools.d.ts +9 -0
  81. package/dist/commerce-money-tools.d.ts.map +1 -0
  82. package/dist/contract-money.d.ts +27 -0
  83. package/dist/contract-money.d.ts.map +1 -0
  84. package/dist/cost-billing.d.ts +0 -1
  85. package/dist/cost-billing.d.ts.map +1 -1
  86. package/dist/cost-billing.js +10 -15
  87. package/dist/cost-billing.js.map +1 -1
  88. package/dist/credit-allocation.d.ts +48 -0
  89. package/dist/credit-allocation.d.ts.map +1 -0
  90. package/dist/document-money-internal.d.ts +44 -0
  91. package/dist/document-money-internal.d.ts.map +1 -0
  92. package/dist/document-money.d.ts +85 -0
  93. package/dist/document-money.d.ts.map +1 -0
  94. package/dist/index.d.ts +15 -4
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +5395 -4246
  97. package/dist/index.js.map +1 -1
  98. package/dist/invoice-accounting-sync.d.ts +96 -0
  99. package/dist/invoice-accounting-sync.d.ts.map +1 -0
  100. package/dist/invoice-mcp.d.ts +16 -0
  101. package/dist/invoice-mcp.d.ts.map +1 -0
  102. package/dist/invoice-posting.d.ts +9 -0
  103. package/dist/invoice-posting.d.ts.map +1 -0
  104. package/dist/invoice-record-screen.d.ts +60 -0
  105. package/dist/invoice-record-screen.d.ts.map +1 -0
  106. package/dist/invoices.d.ts +2 -0
  107. package/dist/invoices.d.ts.map +1 -1
  108. package/dist/invoices.js +3 -2
  109. package/dist/manifest.json +10365 -4562
  110. package/dist/migrations/commerceLineAmounts.d.ts +84 -0
  111. package/dist/migrations/commerceLineAmounts.d.ts.map +1 -0
  112. package/dist/models/Contract.d.ts +25 -1
  113. package/dist/models/Contract.d.ts.map +1 -1
  114. package/dist/models/ContractLineItem.d.ts +30 -1
  115. package/dist/models/ContractLineItem.d.ts.map +1 -1
  116. package/dist/models/Customer.d.ts +7 -0
  117. package/dist/models/Customer.d.ts.map +1 -1
  118. package/dist/models/DocumentTax.d.ts +50 -0
  119. package/dist/models/DocumentTax.d.ts.map +1 -0
  120. package/dist/models/Invoice.d.ts +46 -2
  121. package/dist/models/Invoice.d.ts.map +1 -1
  122. package/dist/models/InvoiceLineItem.d.ts +33 -2
  123. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  124. package/dist/models/MoneyMigrationDocument.d.ts +28 -0
  125. package/dist/models/MoneyMigrationDocument.d.ts.map +1 -0
  126. package/dist/models/Payment.d.ts +5 -1
  127. package/dist/models/Payment.d.ts.map +1 -1
  128. package/dist/models/PaymentAllocation.d.ts.map +1 -1
  129. package/dist/models/TaxCode.d.ts +22 -0
  130. package/dist/models/TaxCode.d.ts.map +1 -0
  131. package/dist/models/TaxComponent.d.ts +21 -0
  132. package/dist/models/TaxComponent.d.ts.map +1 -0
  133. package/dist/models/TaxSettings.d.ts +33 -0
  134. package/dist/models/TaxSettings.d.ts.map +1 -0
  135. package/dist/models/TaxSummaryReport.d.ts +35 -0
  136. package/dist/models/TaxSummaryReport.d.ts.map +1 -0
  137. package/dist/models/Vendor.d.ts +9 -0
  138. package/dist/models/Vendor.d.ts.map +1 -1
  139. package/dist/models/index.d.ts +6 -0
  140. package/dist/models/index.d.ts.map +1 -1
  141. package/dist/money-rational.d.ts +15 -0
  142. package/dist/money-rational.d.ts.map +1 -0
  143. package/dist/money.d.ts +106 -25
  144. package/dist/money.d.ts.map +1 -1
  145. package/dist/money.js +274 -0
  146. package/dist/money.js.map +1 -0
  147. package/dist/recipes.d.ts +15 -15
  148. package/dist/revenue-journal.d.ts +61 -0
  149. package/dist/revenue-journal.d.ts.map +1 -0
  150. package/dist/smrt-knowledge.json +5711 -1029
  151. package/dist/svelte/components/InvoiceActions.svelte +12 -0
  152. package/dist/svelte/components/InvoiceActions.svelte.d.ts +5 -0
  153. package/dist/svelte/components/InvoiceActions.svelte.d.ts.map +1 -1
  154. package/dist/svelte/components/InvoiceEditor.svelte +15 -1
  155. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
  156. package/dist/svelte/components/InvoiceLineItems.svelte +3 -3
  157. package/dist/svelte/components/InvoiceRecordScreen.svelte +94 -0
  158. package/dist/svelte/components/InvoiceRecordScreen.svelte.d.ts +21 -0
  159. package/dist/svelte/components/InvoiceRecordScreen.svelte.d.ts.map +1 -0
  160. package/dist/svelte/components/InvoiceTotals.svelte +2 -2
  161. package/dist/svelte/components/MoneyInvoiceEditor.svelte +282 -0
  162. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +31 -0
  163. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -0
  164. package/dist/svelte/components/PurchaseOrderEditor.svelte +53 -1
  165. package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts +5 -0
  166. package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts.map +1 -1
  167. package/dist/svelte/components/QuoteEditor.svelte +54 -1
  168. package/dist/svelte/components/QuoteEditor.svelte.d.ts +5 -0
  169. package/dist/svelte/components/QuoteEditor.svelte.d.ts.map +1 -1
  170. package/dist/svelte/i18n.d.ts +16 -0
  171. package/dist/svelte/i18n.d.ts.map +1 -1
  172. package/dist/svelte/i18n.js +16 -0
  173. package/dist/svelte/index.d.ts +7 -2
  174. package/dist/svelte/index.d.ts.map +1 -1
  175. package/dist/svelte/index.js +4 -1
  176. package/dist/svelte/invoices/calculations.d.ts +7 -2
  177. package/dist/svelte/invoices/calculations.d.ts.map +1 -1
  178. package/dist/svelte/invoices/calculations.js +141 -47
  179. package/dist/svelte/invoices/money-editor.d.ts +123 -0
  180. package/dist/svelte/invoices/money-editor.d.ts.map +1 -0
  181. package/dist/svelte/invoices/money-editor.js +101 -0
  182. package/dist/svelte/invoices/types.d.ts +5 -1
  183. package/dist/svelte/invoices/types.d.ts.map +1 -1
  184. package/dist/svelte/purchasing-i18n.d.ts +5 -0
  185. package/dist/svelte/purchasing-i18n.d.ts.map +1 -1
  186. package/dist/svelte/purchasing-i18n.js +5 -0
  187. package/dist/svelte/quote-i18n.d.ts +5 -0
  188. package/dist/svelte/quote-i18n.d.ts.map +1 -1
  189. package/dist/svelte/quote-i18n.js +5 -0
  190. package/dist/tax/cookbook.d.ts +22 -0
  191. package/dist/tax/cookbook.d.ts.map +1 -0
  192. package/dist/tax/example-pack.d.ts +25 -0
  193. package/dist/tax/example-pack.d.ts.map +1 -0
  194. package/dist/tax/identity.d.ts +5 -0
  195. package/dist/tax/identity.d.ts.map +1 -0
  196. package/dist/tax/index.d.ts +4 -0
  197. package/dist/tax/index.d.ts.map +1 -0
  198. package/dist/tax/legacy.d.ts +48 -0
  199. package/dist/tax/legacy.d.ts.map +1 -0
  200. package/dist/tax-summary-report-adapter.d.ts +37 -0
  201. package/dist/tax-summary-report-adapter.d.ts.map +1 -0
  202. package/dist/tax-summary.d.ts +129 -0
  203. package/dist/tax-summary.d.ts.map +1 -0
  204. package/dist/tenant-money-barrier.d.ts +66 -0
  205. package/dist/tenant-money-barrier.d.ts.map +1 -0
  206. package/dist/types/index.d.ts +137 -1
  207. package/dist/types/index.d.ts.map +1 -1
  208. package/package.json +19 -11
  209. package/dist/chunks/calculations-Bz7RB3qK.js.map +0 -1
  210. package/dist/chunks/money-DfrRxrel.js.map +0 -1
  211. package/dist/chunks/types-zVnIjSOk.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-money-DNVhDJyC.js","names":["saved"],"sources":["../../src/document-money.ts"],"sourcesContent":["/** 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 lockTenantMoneyMigrationOwner,\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\nfunction comparable(value: unknown): unknown {\n return value instanceof Date ? value.toISOString() : (value ?? null);\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 'Money v2 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\nasync function lockRuntimeSettings(\n db: DatabaseInterface,\n tenantId: string | null,\n): Promise<TaxSettings | null> {\n if (!tenantId) return null;\n return (await lockTenantMoneyRuntime(db, tenantId)).settings;\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 recomputeLegacy: false,\n });\n }\n }\n source.creditedAmount = total;\n source.updatePaymentStatus(source.amountPaid);\n await source.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });\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');\n const outOfScope = await codesCollection.findByCode('out-of-scope');\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, false);\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, 'v2', async (scope) =>\n previewInvoiceMoneyOnScope(\n { ...input, db: scope.db },\n scope.barrier.settings as TaxSettings,\n ),\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, 'v2', async (scope) =>\n getInvoiceMoneyFactsOnScope({ ...input, db: scope.db }),\n );\n}\n\n/** Persist an invoice through either legacy compatibility or the v2 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 if (settings?.moneyModel !== 'v2') {\n return invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: true,\n });\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 recomputeLegacy: false,\n });\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 money-v2 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 recomputeLegacy: false,\n });\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 recomputeLegacy: false,\n });\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/** Migration-only draft materializer; caller already owns the tenant migration transaction. */\nexport async function materializeInvoiceMoneyDraft(input: {\n db: DatabaseInterface;\n invoiceId: string;\n tenantId: string;\n migrationId: string;\n}): Promise<{ invoiceId: string; taxRows: DocumentTaxWriteInput[] }> {\n const settings = await lockTenantMoneyMigrationOwner(\n input.db,\n input.tenantId,\n input.migrationId,\n );\n const { invoice } = await lockAndReloadInvoice(\n input.db,\n input.invoiceId,\n input.tenantId,\n );\n if (isFrozen(invoice))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Only an unfrozen draft invoice can be migration-recomputed',\n );\n const result = await recomputeInvoiceAggregate(input.db, invoice, settings);\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });\n return { invoiceId: input.invoiceId, taxRows: result.taxRows };\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 if (settings?.moneyModel !== 'v2')\n return line.saveDerived(DOCUMENT_MONEY_WRITE, true);\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, false);\n }\n await line.saveDerived(DOCUMENT_MONEY_WRITE, false);\n const priorAggregate = aggregateSignature(invoice);\n await recomputeInvoiceAggregate(tx, invoice, settings);\n if (aggregateSignature(invoice) !== priorAggregate) {\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: false,\n });\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 if (settings?.moneyModel !== 'v2') {\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n return;\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 recomputeLegacy: false,\n });\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 (settings?.moneyModel === 'v2' && isFrozen(persisted))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a frozen invoice',\n );\n if (settings?.moneyModel === 'v2') {\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 }\n await invoice.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n }),\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,229 @@
1
+ import { t as DocumentTaxCollection } from "./DocumentTaxCollection-BEpoqXAQ.js";
2
+ import { s as InvoiceStatus } from "./types-LDETvQ1C.js";
3
+ import { n as deterministicId } from "./units-CNcjH_eE.js";
4
+ import { MoneyValidationError } from "../money.js";
5
+ import { o as DOCUMENT_MONEY_WRITE, u as withDocumentMoneyTransaction } from "./TaxSettingsCollection-BBTZDH1S.js";
6
+ import { n as lockTenantMoneyRuntime } from "./tenant-money-barrier-DjRXgMnX.js";
7
+ import { t as InvoiceLineItemCollection } from "./InvoiceLineItemCollection-CHm9zdqh.js";
8
+ import { r as lockAndReloadInvoice } from "./document-money-DNVhDJyC.js";
9
+ import { t as buildRevenueJournalEntries } from "./revenue-journal-DY9fro8E.js";
10
+ import { getTenantId } from "@happyvertical/smrt-tenancy";
11
+ import { ISO_4217_MINOR_UNITS } from "@happyvertical/smrt-ui/currency";
12
+ //#region src/invoice-posting.ts
13
+ function required(value, label) {
14
+ if (!value || value.trim() !== value) throw new MoneyValidationError("INVALID_MONEY_INPUT", `${label} must be a nonempty value`);
15
+ return value;
16
+ }
17
+ function bookCurrency(value) {
18
+ if (!value || value !== value.toUpperCase() || !ISO_4217_MINOR_UNITS.has(value) || ISO_4217_MINOR_UNITS.get(value) === null) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money-v2 posting requires a supported ISO 4217 bookCurrency");
19
+ return value;
20
+ }
21
+ function assertTenantAuthority(tenantId) {
22
+ if (getTenantId() !== tenantId) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Invoice posting requires its active tenant context");
23
+ }
24
+ function canonicalEntries(entries) {
25
+ return JSON.stringify(entries.map((entry) => ({
26
+ accountId: entry.accountId,
27
+ debit: entry.debit ?? 0,
28
+ credit: entry.credit ?? 0,
29
+ currency: entry.currency ?? "",
30
+ memo: entry.memo ?? ""
31
+ })).sort((left, right) => {
32
+ const leftKey = JSON.stringify(left);
33
+ const rightKey = JSON.stringify(right);
34
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
35
+ }));
36
+ }
37
+ async function validateAccounts(db, tenantId, accountIds) {
38
+ const { AccountCollection } = await import("@happyvertical/smrt-ledgers");
39
+ const accounts = await AccountCollection.create({ db });
40
+ for (const accountId of [...new Set(accountIds)]) {
41
+ const account = await accounts.get({ id: accountId }, { cache: false });
42
+ if (!account || account.tenantId !== tenantId || !account.active) throw new MoneyValidationError("INVALID_MONEY_INPUT", `Posting account ${accountId} must be active and owned by the invoice tenant`);
43
+ }
44
+ }
45
+ async function entriesForJournal(db, journalId) {
46
+ const { JournalEntryCollection } = await import("@happyvertical/smrt-ledgers");
47
+ return (await (await JournalEntryCollection.create({ db })).findByJournal(journalId)).map((entry) => ({
48
+ accountId: entry.accountId,
49
+ debit: entry.debit,
50
+ credit: entry.credit,
51
+ currency: entry.currency,
52
+ memo: entry.memo
53
+ }));
54
+ }
55
+ function metadataMatches(journal, expected) {
56
+ const actual = journal.metadata;
57
+ return actual.kind === expected.kind && actual.invoiceId === expected.invoiceId && actual.moneySnapshotId === expected.moneySnapshotId && actual.originalJournalId === expected.originalJournalId;
58
+ }
59
+ async function createOrReadPostedJournal(db, input) {
60
+ const { JournalCollection } = await import("@happyvertical/smrt-ledgers");
61
+ const journals = await JournalCollection.create({ db });
62
+ const existing = await journals.get({ id: input.id }, { cache: false });
63
+ if (existing) {
64
+ const storedEntries = await entriesForJournal(db, input.id);
65
+ if (existing.status !== "posted" || existing.tenantId !== input.tenantId || existing.sourceModule !== "smrt-commerce" || existing.sourceRef !== input.sourceRef || !metadataMatches(existing, input.metadata) || canonicalEntries(storedEntries) !== canonicalEntries(input.entries)) throw new MoneyValidationError("FROZEN_DOCUMENT", `Deterministic journal ${input.id} conflicts with the frozen invoice snapshot`);
66
+ return existing;
67
+ }
68
+ const journal = await journals.create({
69
+ id: input.id,
70
+ tenantId: input.tenantId,
71
+ date: input.date,
72
+ description: input.description,
73
+ sourceModule: "smrt-commerce",
74
+ sourceRef: input.sourceRef,
75
+ metadata: input.metadata,
76
+ _insertOnly: true
77
+ });
78
+ for (const entry of input.entries) await journal.addEntry(entry);
79
+ await journal.post();
80
+ if (!journal.id) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Posted revenue journal is missing its identity");
81
+ return journal;
82
+ }
83
+ async function recognizeInvoiceRevenueV2(input, options) {
84
+ return withDocumentMoneyTransaction(input.db, async (tx) => input.withDatabase(tx, async (callerInvoice) => {
85
+ const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;
86
+ if ((candidateTenantId ? (await lockTenantMoneyRuntime(tx, candidateTenantId)).settings : null)?.moneyModel !== "v2") return null;
87
+ const invoiceId = required(callerInvoice.id, "Invoice ID");
88
+ const tenantId = required(candidateTenantId, "Invoice tenant ID");
89
+ assertTenantAuthority(tenantId);
90
+ const currency = bookCurrency(options.bookCurrency);
91
+ const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);
92
+ if (invoice.status === InvoiceStatus.DRAFT || !invoice.moneySnapshotId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Money-v2 revenue recognition requires an issued frozen invoice");
93
+ if (invoice.currency !== currency) throw new MoneyValidationError("CURRENCY_MISMATCH", `Invoice currency ${invoice.currency} does not match book currency ${currency}`);
94
+ const lines = await (await InvoiceLineItemCollection.create({ db: tx })).findByInvoice(invoiceId);
95
+ const taxes = await (await DocumentTaxCollection.create({ db: tx })).findByDocument(tenantId, invoice.kind, invoiceId);
96
+ const built = buildRevenueJournalEntries({
97
+ id: invoiceId,
98
+ number: invoice.invoiceNumber,
99
+ currency: invoice.currency,
100
+ lineAmountType: invoice.lineAmountType,
101
+ subtotalMinor: invoice.subtotal,
102
+ taxMinor: invoice.taxAmount,
103
+ totalMinor: invoice.totalAmount,
104
+ providerTaxMinor: invoice.providerTaxAmount
105
+ }, lines.map((line) => ({
106
+ id: required(line.id, "Invoice line ID"),
107
+ amountMinor: line.amount,
108
+ taxMinor: line.taxAmount,
109
+ revenueAccountId: line.revenueAccountId
110
+ })), taxes.map((tax) => ({
111
+ currency: tax.currency,
112
+ taxCodeId: tax.taxCodeId,
113
+ taxCodeCode: tax.taxCodeCode,
114
+ taxComponentId: tax.taxComponentId,
115
+ liabilityAccountId: tax.liabilityAccountId,
116
+ taxMinor: tax.taxMinor
117
+ })), {
118
+ arAccountId: options.arAccountId,
119
+ revenueAccountId: options.revenueAccountId,
120
+ taxAccountId: options.taxAccountId,
121
+ bookCurrency: currency,
122
+ direction: invoice.kind === "credit_note" ? -1 : 1
123
+ });
124
+ await validateAccounts(tx, tenantId, built.entries.map((entry) => entry.accountId));
125
+ const postingKind = invoice.kind === "credit_note" ? "credit-note-revenue" : "invoice-revenue";
126
+ const journalId = await deterministicId([
127
+ postingKind,
128
+ tenantId,
129
+ invoiceId,
130
+ invoice.moneySnapshotId
131
+ ]);
132
+ const metadata = {
133
+ kind: postingKind,
134
+ invoiceId,
135
+ moneySnapshotId: invoice.moneySnapshotId,
136
+ warnings: built.warnings
137
+ };
138
+ const journal = await createOrReadPostedJournal(tx, {
139
+ id: journalId,
140
+ tenantId,
141
+ sourceRef: `${postingKind}:${invoiceId}:${invoice.moneySnapshotId}`,
142
+ description: `${invoice.kind === "credit_note" ? "Credit note" : "Revenue"}: Invoice ${invoice.invoiceNumber}`,
143
+ date: invoice.issueDate,
144
+ metadata,
145
+ entries: built.entries
146
+ });
147
+ if (invoice.arJournalId && invoice.arJournalId !== journalId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Invoice already links a different revenue journal");
148
+ invoice.arJournalId = journalId;
149
+ await invoice.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });
150
+ callerInvoice.arJournalId = journalId;
151
+ return journal;
152
+ }));
153
+ }
154
+ function reverseEntries(entries, currency) {
155
+ return entries.map((entry, index) => {
156
+ if (entry.currency !== currency || !Number.isSafeInteger(entry.debit ?? 0) || !Number.isSafeInteger(entry.credit ?? 0) || (entry.debit ?? 0) < 0 || (entry.credit ?? 0) < 0 || (entry.debit ?? 0) > 0 === (entry.credit ?? 0) > 0) throw new MoneyValidationError(entry.currency !== currency ? "CURRENCY_MISMATCH" : "UNSAFE_MONEY", `Original journal entry ${index + 1} is not an exact ${currency} movement`);
157
+ return {
158
+ accountId: required(entry.accountId, "Original journal account ID"),
159
+ debit: entry.credit ?? 0,
160
+ credit: entry.debit ?? 0,
161
+ currency,
162
+ memo: `Reversal: ${entry.memo ?? ""}`
163
+ };
164
+ });
165
+ }
166
+ async function cancelInvoiceWithRevenueReversal(input, options) {
167
+ return withDocumentMoneyTransaction(input.db, async (tx) => input.withDatabase(tx, async (callerInvoice) => {
168
+ const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;
169
+ if ((candidateTenantId ? (await lockTenantMoneyRuntime(tx, candidateTenantId)).settings : null)?.moneyModel !== "v2") {
170
+ callerInvoice.cancel();
171
+ await callerInvoice.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: true });
172
+ return null;
173
+ }
174
+ const invoiceId = required(callerInvoice.id, "Invoice ID");
175
+ const tenantId = required(candidateTenantId, "Invoice tenant ID");
176
+ assertTenantAuthority(tenantId);
177
+ const currency = bookCurrency(options.bookCurrency);
178
+ const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);
179
+ if (invoice.currency !== currency) throw new MoneyValidationError("CURRENCY_MISMATCH", `Invoice currency ${invoice.currency} does not match book currency ${currency}`);
180
+ if (invoice.kind === "invoice" && invoice.isPaid() || invoice.kind === "credit_note" && invoice.amountPaid > 0) throw new MoneyValidationError("FROZEN_DOCUMENT", "Paid invoices and refunded credit notes cannot be cancelled");
181
+ if (!invoice.arJournalId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Revenue must be posted before using cancellation reversal");
182
+ const snapshotId = required(invoice.moneySnapshotId, "Invoice money snapshot ID");
183
+ const originalJournalId = invoice.arJournalId;
184
+ const originalKind = invoice.kind === "credit_note" ? "credit-note-revenue" : "invoice-revenue";
185
+ const { JournalCollection } = await import("@happyvertical/smrt-ledgers");
186
+ const original = await (await JournalCollection.create({ db: tx })).get({ id: originalJournalId }, { cache: false });
187
+ if (original?.status !== "posted" || original.tenantId !== tenantId || original.sourceModule !== "smrt-commerce" || original.sourceRef !== `${originalKind}:${invoiceId}:${snapshotId}` || !metadataMatches(original, {
188
+ kind: originalKind,
189
+ invoiceId,
190
+ moneySnapshotId: snapshotId
191
+ })) throw new MoneyValidationError("FROZEN_DOCUMENT", "Invoice revenue journal must be posted and tenant-owned before reversal");
192
+ const reversalEntries = reverseEntries(await entriesForJournal(tx, originalJournalId), currency);
193
+ await validateAccounts(tx, tenantId, reversalEntries.map((entry) => entry.accountId));
194
+ const reversalKind = invoice.kind === "credit_note" ? "credit-note-cancellation-reversal" : "invoice-cancellation-reversal";
195
+ const reversalId = await deterministicId([
196
+ reversalKind,
197
+ tenantId,
198
+ invoiceId,
199
+ snapshotId,
200
+ originalJournalId
201
+ ]);
202
+ const metadata = {
203
+ kind: reversalKind,
204
+ invoiceId,
205
+ moneySnapshotId: snapshotId,
206
+ originalJournalId
207
+ };
208
+ const reversal = await createOrReadPostedJournal(tx, {
209
+ id: reversalId,
210
+ tenantId,
211
+ sourceRef: `invoice-cancellation:${invoiceId}:${snapshotId}`,
212
+ description: `Cancellation reversal: Invoice ${invoice.invoiceNumber}`,
213
+ date: /* @__PURE__ */ new Date(),
214
+ metadata,
215
+ entries: reversalEntries
216
+ });
217
+ if (invoice.arReversalJournalId && invoice.arReversalJournalId !== reversalId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Invoice already links a different cancellation reversal");
218
+ invoice.arReversalJournalId = reversalId;
219
+ if (invoice.status !== InvoiceStatus.CANCELLED) invoice.cancel();
220
+ await invoice.saveDerived(DOCUMENT_MONEY_WRITE, { recomputeLegacy: false });
221
+ callerInvoice.arReversalJournalId = reversalId;
222
+ callerInvoice.status = InvoiceStatus.CANCELLED;
223
+ return reversal;
224
+ }));
225
+ }
226
+ //#endregion
227
+ export { cancelInvoiceWithRevenueReversal, recognizeInvoiceRevenueV2 };
228
+
229
+ //# sourceMappingURL=invoice-posting-BwV8g7z0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-posting-BwV8g7z0.js","names":[],"sources":["../../src/invoice-posting.ts"],"sourcesContent":["/** Atomic money-v2 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 warnings?: unknown[];\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 'Money-v2 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/** Return null for legacy tenants so Invoice can preserve its old path. */\nexport async function recognizeInvoiceRevenueV2(\n input: Invoice,\n options: RecognizeRevenueOptions,\n): Promise<Journal | null> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n const settings = candidateTenantId\n ? (await lockTenantMoneyRuntime(tx, candidateTenantId)).settings\n : null;\n if (settings?.moneyModel !== 'v2') return null;\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 'Money-v2 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 warnings: built.warnings,\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 recomputeLegacy: false,\n });\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 v2 invoice and move it to CANCELLED. */\nexport async function cancelInvoiceWithRevenueReversal(\n input: Invoice,\n options: CancelRevenueOptions,\n): Promise<Journal | null> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n const settings = candidateTenantId\n ? (await lockTenantMoneyRuntime(tx, candidateTenantId)).settings\n : null;\n if (settings?.moneyModel !== 'v2') {\n callerInvoice.cancel();\n await callerInvoice.saveDerived(DOCUMENT_MONEY_WRITE, {\n recomputeLegacy: true,\n });\n return null;\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 recomputeLegacy: false,\n });\n callerInvoice.arReversalJournalId = reversalId;\n callerInvoice.status = InvoiceStatus.CANCELLED;\n return reversal;\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;AAoCA,SAAS,SAAS,OAAkC,OAAuB;CACzE,IAAI,CAAC,SAAS,MAAM,KAAK,MAAM,OAC7B,MAAM,IAAI,qBACR,uBACA,GAAG,MAAK,0BACV;CACF,OAAO;AACT;AAEA,SAAS,aAAa,OAAmC;CACvD,IACE,CAAC,SACD,UAAU,MAAM,YAAY,KAC5B,CAAC,qBAAqB,IAAI,KAAK,KAC/B,qBAAqB,IAAI,KAAK,MAAM,MAEpC,MAAM,IAAI,qBACR,uBACA,6DACF;CACF,OAAO;AACT;AAEA,SAAS,sBAAsB,UAAwB;CACrD,IAAI,YAAY,MAAM,UACpB,MAAM,IAAI,qBACR,uBACA,oDACF;AACJ;AAEA,SAAS,iBAAiB,SAA8C;CACtE,OAAO,KAAK,UACV,QACG,KAAK,WAAW;EACf,WAAW,MAAM;EACjB,OAAO,MAAM,SAAS;EACtB,QAAQ,MAAM,UAAU;EACxB,UAAU,MAAM,YAAY;EAC5B,MAAM,MAAM,QAAQ;CACtB,EAAE,CAAA,CACD,MAAM,MAAM,UAAU;EACrB,MAAM,UAAU,KAAK,UAAU,IAAI;EACnC,MAAM,WAAW,KAAK,UAAU,KAAK;EACrC,OAAO,UAAU,WAAW,KAAK,UAAU,WAAW,IAAI;CAC5D,CAAC,CACL;AACF;AAEA,eAAe,iBACb,IACA,UACA,YACe;CACf,MAAM,EAAE,sBAAsB,MAAM,OAAO;CAC3C,MAAM,WAAW,MAAM,kBAAkB,OAAO,EAAE,GAAG,CAAC;CACtD,KAAA,MAAW,aAAa,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,GAAG;EAChD,MAAM,UAAU,MAAM,SAAS,IAAI,EAAE,IAAI,UAAU,GAAG,EAAE,OAAO,MAAM,CAAC;EACtE,IAAI,CAAC,WAAW,QAAQ,aAAa,YAAY,CAAC,QAAQ,QACxD,MAAM,IAAI,qBACR,uBACA,mBAAmB,UAAS,gDAC9B;CACJ;AACF;AAEA,eAAe,kBACb,IACA,WAC6B;CAC7B,MAAM,EAAE,2BAA2B,MAAM,OACvC;CAKF,QAAO,OAFL,MAAM,uBAAuB,OAAO,EAAE,GAAG,CAAC,EAAA,CAC1C,cAAc,SAAS,EAAA,CACV,KAAK,WAAW;EAC7B,WAAW,MAAM;EACjB,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,UAAU,MAAM;EAChB,MAAM,MAAM;CACd,EAAE;AACJ;AAEA,SAAS,gBAAgB,SAAkB,UAAoC;CAC7E,MAAM,SAAS,QAAQ;CACvB,OACE,OAAO,SAAS,SAAS,QACzB,OAAO,cAAc,SAAS,aAC9B,OAAO,oBAAoB,SAAS,mBACpC,OAAO,sBAAsB,SAAS;AAE1C;AAEA,eAAe,0BACb,IACA,OASkB;CAClB,MAAM,EAAE,sBAAsB,MAAM,OAAO;CAC3C,MAAM,WAAW,MAAM,kBAAkB,OAAO,EAAE,GAAG,CAAC;CACtD,MAAM,WAAW,MAAM,SAAS,IAAI,EAAE,IAAI,MAAM,GAAG,GAAG,EAAE,OAAO,MAAM,CAAC;CACtE,IAAI,UAAU;EACZ,MAAM,gBAAgB,MAAM,kBAAkB,IAAI,MAAM,EAAE;EAC1D,IACE,SAAS,WAAW,YACpB,SAAS,aAAa,MAAM,YAC5B,SAAS,iBAAiB,mBAC1B,SAAS,cAAc,MAAM,aAC7B,CAAC,gBAAgB,UAAU,MAAM,QAAQ,KACzC,iBAAiB,aAAa,MAAM,iBAAiB,MAAM,OAAO,GAElE,MAAM,IAAI,qBACR,mBACA,yBAAyB,MAAM,GAAE,4CACnC;EACF,OAAO;CACT;CACA,MAAM,UAAU,MAAM,SAAS,OAAO;EACpC,IAAI,MAAM;EACV,UAAU,MAAM;EAChB,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,cAAc;EACd,WAAW,MAAM;EACjB,UAAU,MAAM;EAChB,aAAa;CACf,CAAC;CACD,KAAA,MAAW,SAAS,MAAM,SAAS,MAAM,QAAQ,SAAS,KAAK;CAC/D,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,QAAQ,IACX,MAAM,IAAI,qBACR,uBACA,gDACF;CACF,OAAO;AACT;AAGA,eAAsB,0BACpB,OACA,SACyB;CACzB,OAAO,6BAA6B,MAAM,IAAI,OAAO,OACnD,MAAM,aAAa,IAAI,OAAO,kBAAkB;EAC9C,MAAM,oBAAoB,YAAY,KAAK,cAAc;EAIzD,KAHiB,qBACZ,MAAM,uBAAuB,IAAI,iBAAiB,EAAA,CAAG,WACtD,KAAA,EACU,eAAe,MAAM,OAAO;EAC1C,MAAM,YAAY,SAAS,cAAc,IAAI,YAAY;EACzD,MAAM,WAAW,SAAS,mBAAmB,mBAAmB;EAChE,sBAAsB,QAAQ;EAC9B,MAAM,WAAW,aAAa,QAAQ,YAAY;EAClD,MAAM,EAAE,YAAY,MAAM,qBAAqB,IAAI,WAAW,QAAQ;EACtE,IAAI,QAAQ,WAAW,cAAc,SAAS,CAAC,QAAQ,iBACrD,MAAM,IAAI,qBACR,mBACA,gEACF;EACF,IAAI,QAAQ,aAAa,UACvB,MAAM,IAAI,qBACR,qBACA,oBAAoB,QAAQ,SAAQ,gCAAiC,UACvE;EAEF,MAAM,QAAQ,OACZ,MAAM,0BAA0B,OAAO,EAAE,IAAI,GAAG,CAAC,EAAA,CACjD,cAAc,SAAS;EACzB,MAAM,QAAQ,OACZ,MAAM,sBAAsB,OAAO,EAAE,IAAI,GAAG,CAAC,EAAA,CAC7C,eAAe,UAAU,QAAQ,MAAM,SAAS;EAClD,MAAM,QAAQ,2BACZ;GACE,IAAI;GACJ,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,gBAAgB,QAAQ;GACxB,eAAe,QAAQ;GACvB,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,kBAAkB,QAAQ;EAC5B,GACA,MAAM,KAAK,UAAU;GACnB,IAAI,SAAS,KAAK,IAAI,iBAAiB;GACvC,aAAa,KAAK;GAClB,UAAU,KAAK;GACf,kBAAkB,KAAK;EACzB,EAAE,GACF,MAAM,KAAK,SAAS;GAClB,UAAU,IAAI;GACd,WAAW,IAAI;GACf,aAAa,IAAI;GACjB,gBAAgB,IAAI;GACpB,oBAAoB,IAAI;GACxB,UAAU,IAAI;EAChB,EAAE,GACF;GACE,aAAa,QAAQ;GACrB,kBAAkB,QAAQ;GAC1B,cAAc,QAAQ;GACtB,cAAc;GACd,WAAW,QAAQ,SAAS,gBAAgB,KAAK;EACnD,CACF;EACA,MAAM,iBACJ,IACA,UACA,MAAM,QAAQ,KAAK,UAAU,MAAM,SAAS,CAC9C;EACA,MAAM,cACJ,QAAQ,SAAS,gBACZ,wBACA;EACP,MAAM,YAAY,MAAM,gBAAgB;GACtC;GACA;GACA;GACA,QAAQ;EACV,CAAC;EACD,MAAM,WAA4B;GAChC,MAAM;GACN;GACA,iBAAiB,QAAQ;GACzB,UAAU,MAAM;EAClB;EACA,MAAM,UAAU,MAAM,0BAA0B,IAAI;GAClD,IAAI;GACJ;GACA,WAAW,GAAG,YAAW,GAAI,UAAS,GAAI,QAAQ;GAClD,aAAa,GAAG,QAAQ,SAAS,gBAAgB,gBAAgB,UAAS,YAAa,QAAQ;GAC/F,MAAM,QAAQ;GACd;GACA,SAAS,MAAM;EACjB,CAAC;EACD,IAAI,QAAQ,eAAe,QAAQ,gBAAgB,WACjD,MAAM,IAAI,qBACR,mBACA,mDACF;EACF,QAAQ,cAAc;EACtB,MAAM,QAAQ,YAAY,sBAAsB,EAC9C,iBAAiB,MACnB,CAAC;EACD,cAAc,cAAc;EAC5B,OAAO;CACT,CAAC,CACH;AACF;AAEA,SAAS,eACP,SACA,UACoB;CACpB,OAAO,QAAQ,KAAK,OAAO,UAAU;EACnC,IACE,MAAM,aAAa,YACnB,CAAC,OAAO,cAAc,MAAM,SAAS,CAAC,KACtC,CAAC,OAAO,cAAc,MAAM,UAAU,CAAC,MACtC,MAAM,SAAS,KAAK,MACpB,MAAM,UAAU,KAAK,MACrB,MAAM,SAAS,KAAK,OAAO,MAAM,UAAU,KAAK,GAEjD,MAAM,IAAI,qBACR,MAAM,aAAa,WAAW,sBAAsB,gBACpD,0BAA0B,QAAQ,EAAC,mBAAoB,SAAQ,UACjE;EACF,OAAO;GACL,WAAW,SAAS,MAAM,WAAW,6BAA6B;GAClE,OAAO,MAAM,UAAU;GACvB,QAAQ,MAAM,SAAS;GACvB;GACA,MAAM,aAAa,MAAM,QAAQ;EACnC;CACF,CAAC;AACH;AAGA,eAAsB,iCACpB,OACA,SACyB;CACzB,OAAO,6BAA6B,MAAM,IAAI,OAAO,OACnD,MAAM,aAAa,IAAI,OAAO,kBAAkB;EAC9C,MAAM,oBAAoB,YAAY,KAAK,cAAc;EAIzD,KAHiB,qBACZ,MAAM,uBAAuB,IAAI,iBAAiB,EAAA,CAAG,WACtD,KAAA,EACU,eAAe,MAAM;GACjC,cAAc,OAAO;GACrB,MAAM,cAAc,YAAY,sBAAsB,EACpD,iBAAiB,KACnB,CAAC;GACD,OAAO;EACT;EACA,MAAM,YAAY,SAAS,cAAc,IAAI,YAAY;EACzD,MAAM,WAAW,SAAS,mBAAmB,mBAAmB;EAChE,sBAAsB,QAAQ;EAC9B,MAAM,WAAW,aAAa,QAAQ,YAAY;EAClD,MAAM,EAAE,YAAY,MAAM,qBAAqB,IAAI,WAAW,QAAQ;EACtE,IAAI,QAAQ,aAAa,UACvB,MAAM,IAAI,qBACR,qBACA,oBAAoB,QAAQ,SAAQ,gCAAiC,UACvE;EACF,IACG,QAAQ,SAAS,aAAa,QAAQ,OAAO,KAC7C,QAAQ,SAAS,iBAAiB,QAAQ,aAAa,GAExD,MAAM,IAAI,qBACR,mBACA,6DACF;EACF,IAAI,CAAC,QAAQ,aACX,MAAM,IAAI,qBACR,mBACA,2DACF;EACF,MAAM,aAAa,SACjB,QAAQ,iBACR,2BACF;EACA,MAAM,oBAAoB,QAAQ;EAClC,MAAM,eACJ,QAAQ,SAAS,gBACZ,wBACA;EACP,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAE3C,MAAM,WAAW,OAAM,MADA,kBAAkB,OAAO,EAAE,IAAI,GAAG,CAAC,EAAA,CAC1B,IAC9B,EAAE,IAAI,kBAAkB,GACxB,EAAE,OAAO,MAAM,CACjB;EACA,IACE,UAAU,WAAW,YACrB,SAAS,aAAa,YACtB,SAAS,iBAAiB,mBAC1B,SAAS,cAAc,GAAG,aAAY,GAAI,UAAS,GAAI,gBACvD,CAAC,gBAAgB,UAAU;GACzB,MAAM;GACN;GACA,iBAAiB;EACnB,CAAC,GAED,MAAM,IAAI,qBACR,mBACA,yEACF;EAEF,MAAM,kBAAkB,eAAe,MADT,kBAAkB,IAAI,iBAAiB,GACb,QAAQ;EAChE,MAAM,iBACJ,IACA,UACA,gBAAgB,KAAK,UAAU,MAAM,SAAS,CAChD;EACA,MAAM,eACJ,QAAQ,SAAS,gBACZ,sCACA;EACP,MAAM,aAAa,MAAM,gBAAgB;GACvC;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,MAAM,WAA4B;GAChC,MAAM;GACN;GACA,iBAAiB;GACjB;EACF;EACA,MAAM,WAAW,MAAM,0BAA0B,IAAI;GACnD,IAAI;GACJ;GACA,WAAW,wBAAwB,UAAS,GAAI;GAChD,aAAa,kCAAkC,QAAQ;GACvD,sBAAM,IAAI,KAAK;GACf;GACA,SAAS;EACX,CAAC;EACD,IACE,QAAQ,uBACR,QAAQ,wBAAwB,YAEhC,MAAM,IAAI,qBACR,mBACA,yDACF;EACF,QAAQ,sBAAsB;EAC9B,IAAI,QAAQ,WAAW,cAAc,WAAW,QAAQ,OAAO;EAC/D,MAAM,QAAQ,YAAY,sBAAsB,EAC9C,iBAAiB,MACnB,CAAC;EACD,cAAc,sBAAsB;EACpC,cAAc,SAAS,cAAc;EACrC,OAAO;CACT,CAAC,CACH;AACF"}
@@ -0,0 +1,64 @@
1
+ //#region src/money-rational.ts
2
+ function greatestCommonDivisor(left, right) {
3
+ let a = left < 0n ? -left : left;
4
+ let b = right < 0n ? -right : right;
5
+ while (b !== 0n) [a, b] = [b, a % b];
6
+ return a;
7
+ }
8
+ function roundSignedRational(numerator, denominator) {
9
+ if (denominator <= 0n) throw new RangeError("Rational denominator must be positive");
10
+ const result = ((numerator < 0n ? -numerator : numerator) * 2n + denominator) / (denominator * 2n);
11
+ return numerator < 0n ? -result : result;
12
+ }
13
+ function compareRemainders(left, right) {
14
+ const comparison = left.numerator % left.denominator * right.denominator - right.numerator % right.denominator * left.denominator;
15
+ return comparison < 0n ? -1 : comparison > 0n ? 1 : 0;
16
+ }
17
+ function allocateSignedRationals(values) {
18
+ let sumNumerator = 0n;
19
+ let sumDenominator = 1n;
20
+ const seen = /* @__PURE__ */ new Set();
21
+ const allocations = values.map((value) => {
22
+ if (typeof value.id !== "string" || value.id.trim().length === 0) throw new TypeError("Rational IDs must be nonempty strings");
23
+ if (seen.has(value.id)) throw new TypeError("Duplicate rational ID");
24
+ seen.add(value.id);
25
+ if (value.denominator <= 0n) throw new RangeError("Rational denominator must be positive");
26
+ const divisor = greatestCommonDivisor(sumDenominator, value.denominator);
27
+ const leftFactor = value.denominator / divisor;
28
+ const rightFactor = sumDenominator / divisor;
29
+ sumNumerator = sumNumerator * leftFactor + value.numerator * rightFactor;
30
+ sumDenominator *= leftFactor;
31
+ const reduction = greatestCommonDivisor(sumNumerator, sumDenominator);
32
+ if (reduction > 1n) {
33
+ sumNumerator /= reduction;
34
+ sumDenominator /= reduction;
35
+ }
36
+ return {
37
+ ...value,
38
+ value: value.numerator / value.denominator
39
+ };
40
+ });
41
+ const target = roundSignedRational(sumNumerator, sumDenominator);
42
+ const residual = target - allocations.reduce((sum, value) => sum + value.value, 0n);
43
+ if (residual !== 0n) {
44
+ const direction = residual > 0n ? 1n : -1n;
45
+ const ranked = [...allocations].sort((left, right) => {
46
+ const comparison = compareRemainders(left, right);
47
+ if (comparison !== 0) return direction > 0n ? -comparison : comparison;
48
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
49
+ });
50
+ const count = residual * direction;
51
+ for (let index = 0; BigInt(index) < count; index++) ranked[index].value += direction;
52
+ }
53
+ return {
54
+ target,
55
+ allocations: allocations.map(({ id, value }) => ({
56
+ id,
57
+ value
58
+ }))
59
+ };
60
+ }
61
+ //#endregion
62
+ export { roundSignedRational as n, allocateSignedRationals as t };
63
+
64
+ //# sourceMappingURL=money-rational-CcW7gzG5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money-rational-CcW7gzG5.js","names":[],"sources":["../../src/money-rational.ts"],"sourcesContent":["/** Internal exact-rational primitives shared by commerce money calculators. */\nexport interface SignedRationalInput {\n id: string;\n numerator: bigint;\n denominator: bigint;\n}\n\nfunction greatestCommonDivisor(left: bigint, right: bigint): bigint {\n let a = left < 0n ? -left : left;\n let b = right < 0n ? -right : right;\n while (b !== 0n) [a, b] = [b, a % b];\n return a;\n}\n\nexport function roundSignedRational(\n numerator: bigint,\n denominator: bigint,\n): bigint {\n if (denominator <= 0n)\n throw new RangeError('Rational denominator must be positive');\n const magnitude = numerator < 0n ? -numerator : numerator;\n const result = (magnitude * 2n + denominator) / (denominator * 2n);\n return numerator < 0n ? -result : result;\n}\n\nfunction compareRemainders(\n left: SignedRationalInput,\n right: SignedRationalInput,\n): number {\n const comparison =\n (left.numerator % left.denominator) * right.denominator -\n (right.numerator % right.denominator) * left.denominator;\n return comparison < 0n ? -1 : comparison > 0n ? 1 : 0;\n}\n\nexport function allocateSignedRationals(\n values: readonly SignedRationalInput[],\n): { target: bigint; allocations: { id: string; value: bigint }[] } {\n let sumNumerator = 0n;\n let sumDenominator = 1n;\n const seen = new Set<string>();\n const allocations = values.map((value) => {\n if (typeof value.id !== 'string' || value.id.trim().length === 0)\n throw new TypeError('Rational IDs must be nonempty strings');\n if (seen.has(value.id)) throw new TypeError('Duplicate rational ID');\n seen.add(value.id);\n if (value.denominator <= 0n)\n throw new RangeError('Rational denominator must be positive');\n const divisor = greatestCommonDivisor(sumDenominator, value.denominator);\n const leftFactor = value.denominator / divisor;\n const rightFactor = sumDenominator / divisor;\n sumNumerator = sumNumerator * leftFactor + value.numerator * rightFactor;\n sumDenominator *= leftFactor;\n const reduction = greatestCommonDivisor(sumNumerator, sumDenominator);\n if (reduction > 1n) {\n sumNumerator /= reduction;\n sumDenominator /= reduction;\n }\n return { ...value, value: value.numerator / value.denominator };\n });\n const target = roundSignedRational(sumNumerator, sumDenominator);\n const truncated = allocations.reduce((sum, value) => sum + value.value, 0n);\n const residual = target - truncated;\n if (residual !== 0n) {\n const direction = residual > 0n ? 1n : -1n;\n const ranked = [...allocations].sort((left, right) => {\n const comparison = compareRemainders(left, right);\n if (comparison !== 0) return direction > 0n ? -comparison : comparison;\n return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;\n });\n const count = residual * direction;\n for (let index = 0; BigInt(index) < count; index++)\n ranked[index].value += direction;\n }\n return {\n target,\n allocations: allocations.map(({ id, value }) => ({ id, value })),\n };\n}\n"],"mappings":";AAOA,SAAS,sBAAsB,MAAc,OAAuB;CAClE,IAAI,IAAI,OAAO,KAAK,CAAC,OAAO;CAC5B,IAAI,IAAI,QAAQ,KAAK,CAAC,QAAQ;CAC9B,OAAO,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;CACnC,OAAO;AACT;AAEO,SAAS,oBACd,WACA,aACQ;CACR,IAAI,eAAe,IACjB,MAAM,IAAI,WAAW,uCAAuC;CAE9D,MAAM,WADY,YAAY,KAAK,CAAC,YAAY,aACpB,KAAK,gBAAgB,cAAc;CAC/D,OAAO,YAAY,KAAK,CAAC,SAAS;AACpC;AAEA,SAAS,kBACP,MACA,OACQ;CACR,MAAM,aACH,KAAK,YAAY,KAAK,cAAe,MAAM,cAC3C,MAAM,YAAY,MAAM,cAAe,KAAK;CAC/C,OAAO,aAAa,KAAK,KAAK,aAAa,KAAK,IAAI;AACtD;AAEO,SAAS,wBACd,QACkE;CAClE,IAAI,eAAe;CACnB,IAAI,iBAAiB;CACrB,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,cAAc,OAAO,KAAK,UAAU;EACxC,IAAI,OAAO,MAAM,OAAO,YAAY,MAAM,GAAG,KAAK,CAAA,CAAE,WAAW,GAC7D,MAAM,IAAI,UAAU,uCAAuC;EAC7D,IAAI,KAAK,IAAI,MAAM,EAAE,GAAG,MAAM,IAAI,UAAU,uBAAuB;EACnE,KAAK,IAAI,MAAM,EAAE;EACjB,IAAI,MAAM,eAAe,IACvB,MAAM,IAAI,WAAW,uCAAuC;EAC9D,MAAM,UAAU,sBAAsB,gBAAgB,MAAM,WAAW;EACvE,MAAM,aAAa,MAAM,cAAc;EACvC,MAAM,cAAc,iBAAiB;EACrC,eAAe,eAAe,aAAa,MAAM,YAAY;EAC7D,kBAAkB;EAClB,MAAM,YAAY,sBAAsB,cAAc,cAAc;EACpE,IAAI,YAAY,IAAI;GAClB,gBAAgB;GAChB,kBAAkB;EACpB;EACA,OAAO;GAAE,GAAG;GAAO,OAAO,MAAM,YAAY,MAAM;EAAY;CAChE,CAAC;CACD,MAAM,SAAS,oBAAoB,cAAc,cAAc;CAE/D,MAAM,WAAW,SADC,YAAY,QAAQ,KAAK,UAAU,MAAM,MAAM,OAAO,EAC9C;CAC1B,IAAI,aAAa,IAAI;EACnB,MAAM,YAAY,WAAW,KAAK,KAAK,CAAC;EACxC,MAAM,SAAS,CAAC,GAAG,WAAW,CAAA,CAAE,MAAM,MAAM,UAAU;GACpD,MAAM,aAAa,kBAAkB,MAAM,KAAK;GAChD,IAAI,eAAe,GAAG,OAAO,YAAY,KAAK,CAAC,aAAa;GAC5D,OAAO,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,MAAM,KAAK,IAAI;EAC5D,CAAC;EACD,MAAM,QAAQ,WAAW;EACzB,KAAA,IAAS,QAAQ,GAAG,OAAO,KAAK,IAAI,OAAO,SACzC,OAAO,MAAK,CAAE,SAAS;CAC3B;CACA,OAAO;EACL;EACA,aAAa,YAAY,KAAK,EAAE,IAAI,aAAa;GAAE;GAAI;EAAM,EAAE;CACjE;AACF"}