@happyvertical/smrt-commerce 0.56.2 → 0.57.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 (181) hide show
  1. package/AGENTS.md +24 -11
  2. package/README.md +137 -89
  3. package/agents/accounting-snapshot-mapper.md +3 -3
  4. package/agents/commercial-ui.md +2 -2
  5. package/agents/contract-money.md +9 -12
  6. package/agents/cost-billing.md +3 -6
  7. package/agents/customer-vendor-ui.md +1 -2
  8. package/agents/document-money.md +58 -31
  9. package/agents/invoice-calculations.md +31 -56
  10. package/agents/invoice-preparation.md +62 -73
  11. package/agents/invoice-quantity.md +9 -21
  12. package/agents/minor-unit-migration.md +4 -4
  13. package/agents/money-calculator.md +7 -8
  14. package/agents/money-model.md +18 -22
  15. package/agents/money-upgrade.md +92 -0
  16. package/agents/revenue-journal.md +2 -2
  17. package/agents/tax-codes.md +17 -14
  18. package/agents/tax-summary.md +8 -6
  19. package/dist/billing/period-close.d.ts.map +1 -1
  20. package/dist/billing/runtime.d.ts +5 -2
  21. package/dist/billing/runtime.d.ts.map +1 -1
  22. package/dist/chunks/{ContractLineItemCollection--BrGMtNz.js → ContractLineItemCollection-BZcrlrrY.js} +19 -24
  23. package/dist/chunks/ContractLineItemCollection-BZcrlrrY.js.map +1 -0
  24. package/dist/chunks/{CustomerCollection-CjGaZSpf.js → CustomerCollection-BIPH2ZUx.js} +2 -2
  25. package/dist/chunks/{CustomerCollection-CjGaZSpf.js.map → CustomerCollection-BIPH2ZUx.js.map} +1 -1
  26. package/dist/chunks/{DocumentTaxCollection-KOkaQT2M.js → DocumentTaxCollection-DDzlywMW.js} +14 -7
  27. package/dist/chunks/DocumentTaxCollection-DDzlywMW.js.map +1 -0
  28. package/dist/chunks/{InvoiceCollection-BUe_8FT1.js → InvoiceCollection-cinzAqt6.js} +50 -161
  29. package/dist/chunks/InvoiceCollection-cinzAqt6.js.map +1 -0
  30. package/dist/chunks/{TaxCodeCollection-DF2eaJed.js → TaxCodeCollection-DaBOPG9c.js} +11 -4
  31. package/dist/chunks/{TaxCodeCollection-DF2eaJed.js.map → TaxCodeCollection-DaBOPG9c.js.map} +1 -1
  32. package/dist/chunks/{TaxComponentCollection-3x24xSNh.js → TaxComponentCollection-C4zxnyVu.js} +3 -3
  33. package/dist/chunks/{TaxComponentCollection-3x24xSNh.js.map → TaxComponentCollection-C4zxnyVu.js.map} +1 -1
  34. package/dist/chunks/VendorCollection-D6ZcUYy8.js +100 -0
  35. package/dist/chunks/VendorCollection-D6ZcUYy8.js.map +1 -0
  36. package/dist/chunks/{allocation-integrity-Cq0g6CfO.js → allocation-integrity-WmWyXhtG.js} +6 -6
  37. package/dist/chunks/{allocation-integrity-Cq0g6CfO.js.map → allocation-integrity-WmWyXhtG.js.map} +1 -1
  38. package/dist/chunks/calculations-C9daQLsh.js +82 -0
  39. package/dist/chunks/calculations-C9daQLsh.js.map +1 -0
  40. package/dist/chunks/contract-money-EuToOghh.js +301 -0
  41. package/dist/chunks/contract-money-EuToOghh.js.map +1 -0
  42. package/dist/chunks/detect-CY9ufaFb.js +328 -0
  43. package/dist/chunks/detect-CY9ufaFb.js.map +1 -0
  44. package/dist/chunks/{document-money-oUDvdjOf.js → document-money-DV5t4vlK.js} +388 -62
  45. package/dist/chunks/document-money-DV5t4vlK.js.map +1 -0
  46. package/dist/chunks/{invoice-posting-CF3fE_Ec.js → invoice-posting-C2i5dOVZ.js} +20 -22
  47. package/dist/chunks/invoice-posting-C2i5dOVZ.js.map +1 -0
  48. package/dist/chunks/{revenue-journal-DY9fro8E.js → revenue-journal-LLA9Kh_i.js} +3 -14
  49. package/dist/chunks/revenue-journal-LLA9Kh_i.js.map +1 -0
  50. package/dist/chunks/tenant-money-barrier-6TlqUeVj.js +255 -0
  51. package/dist/chunks/tenant-money-barrier-6TlqUeVj.js.map +1 -0
  52. package/dist/chunks/types-Bbm0Xflc.js.map +1 -1
  53. package/dist/chunks/{units-CNcjH_eE.js → units-8ElbjVZK.js} +18 -3
  54. package/dist/chunks/units-8ElbjVZK.js.map +1 -0
  55. package/dist/collections/ContractCollection.d.ts.map +1 -1
  56. package/dist/collections/ContractLineItemCollection.d.ts.map +1 -1
  57. package/dist/collections/InvoiceCollection.d.ts.map +1 -1
  58. package/dist/collections/InvoiceLineItemCollection.d.ts +0 -30
  59. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -1
  60. package/dist/collections/TaxCodeCollection.d.ts +5 -1
  61. package/dist/collections/TaxCodeCollection.d.ts.map +1 -1
  62. package/dist/collections/TaxSettingsCollection.d.ts +15 -8
  63. package/dist/collections/TaxSettingsCollection.d.ts.map +1 -1
  64. package/dist/collections/TaxSummaryReportCollection.d.ts +3 -3
  65. package/dist/collections/TaxSummaryReportCollection.d.ts.map +1 -1
  66. package/dist/collections/index.d.ts +0 -1
  67. package/dist/collections/index.d.ts.map +1 -1
  68. package/dist/commerce-money-tools.d.ts.map +1 -1
  69. package/dist/contract-money.d.ts +1 -13
  70. package/dist/contract-money.d.ts.map +1 -1
  71. package/dist/document-money-internal.d.ts +7 -3
  72. package/dist/document-money-internal.d.ts.map +1 -1
  73. package/dist/document-money.d.ts +1 -11
  74. package/dist/document-money.d.ts.map +1 -1
  75. package/dist/index.d.ts +6 -5
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +667 -1650
  78. package/dist/index.js.map +1 -1
  79. package/dist/invoice-accounting-sync.d.ts.map +1 -1
  80. package/dist/invoice-mcp.d.ts +3 -3
  81. package/dist/invoice-mcp.d.ts.map +1 -1
  82. package/dist/invoice-posting.d.ts +11 -5
  83. package/dist/invoice-posting.d.ts.map +1 -1
  84. package/dist/invoice-record-screen.d.ts +1 -1
  85. package/dist/invoice-record-screen.d.ts.map +1 -1
  86. package/dist/invoices.d.ts +6 -1
  87. package/dist/invoices.d.ts.map +1 -1
  88. package/dist/invoices.js +2 -2
  89. package/dist/manifest.json +37 -765
  90. package/dist/migrations/invoiceEditorStorage.d.ts +2 -4
  91. package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -1
  92. package/dist/models/Contract.d.ts.map +1 -1
  93. package/dist/models/ContractLineItem.d.ts +6 -22
  94. package/dist/models/ContractLineItem.d.ts.map +1 -1
  95. package/dist/models/Invoice.d.ts +18 -48
  96. package/dist/models/Invoice.d.ts.map +1 -1
  97. package/dist/models/InvoiceLineItem.d.ts +8 -37
  98. package/dist/models/InvoiceLineItem.d.ts.map +1 -1
  99. package/dist/models/TaxSettings.d.ts +0 -10
  100. package/dist/models/TaxSettings.d.ts.map +1 -1
  101. package/dist/models/index.d.ts +0 -1
  102. package/dist/models/index.d.ts.map +1 -1
  103. package/dist/playground.js +1 -1
  104. package/dist/playground.js.map +1 -1
  105. package/dist/recipes.d.ts +8 -11
  106. package/dist/recipes.d.ts.map +1 -1
  107. package/dist/revenue-journal.d.ts +0 -8
  108. package/dist/revenue-journal.d.ts.map +1 -1
  109. package/dist/smrt-knowledge.json +79 -661
  110. package/dist/svelte/components/InvoiceEditor.svelte +7 -148
  111. package/dist/svelte/components/InvoiceEditor.svelte.d.ts +4 -1
  112. package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -1
  113. package/dist/svelte/components/InvoiceTotals.svelte +14 -118
  114. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +4 -27
  115. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
  116. package/dist/svelte/components/MoneyInvoiceEditor.svelte +3 -23
  117. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts +3 -26
  118. package/dist/svelte/components/MoneyInvoiceEditor.svelte.d.ts.map +1 -1
  119. package/dist/svelte/index.d.ts +2 -3
  120. package/dist/svelte/index.d.ts.map +1 -1
  121. package/dist/svelte/index.js +1 -2
  122. package/dist/svelte/invoices/calculations.d.ts +3 -108
  123. package/dist/svelte/invoices/calculations.d.ts.map +1 -1
  124. package/dist/svelte/invoices/calculations.js +12 -309
  125. package/dist/svelte/invoices/messages.d.ts +1 -37
  126. package/dist/svelte/invoices/messages.d.ts.map +1 -1
  127. package/dist/svelte/invoices/messages.js +1 -37
  128. package/dist/svelte/invoices/money-editor.d.ts +1 -1
  129. package/dist/svelte/invoices/money-editor.d.ts.map +1 -1
  130. package/dist/svelte/invoices/types.d.ts +14 -114
  131. package/dist/svelte/invoices/types.d.ts.map +1 -1
  132. package/dist/svelte/playground/InvoicePreparationPreview.svelte +85 -94
  133. package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -1
  134. package/dist/svelte/playground.js +1 -1
  135. package/dist/tax/index.d.ts +2 -1
  136. package/dist/tax/index.d.ts.map +1 -1
  137. package/dist/tax/standard-codes.d.ts +12 -0
  138. package/dist/tax/standard-codes.d.ts.map +1 -0
  139. package/dist/tax/tenant-bootstrap.d.ts +29 -0
  140. package/dist/tax/tenant-bootstrap.d.ts.map +1 -0
  141. package/dist/tenant-money-barrier.d.ts +12 -44
  142. package/dist/tenant-money-barrier.d.ts.map +1 -1
  143. package/dist/types/index.d.ts +10 -23
  144. package/dist/types/index.d.ts.map +1 -1
  145. package/dist/ui.js +1 -1
  146. package/dist/ui.js.map +1 -1
  147. package/dist/upgrade/detect.d.ts +129 -0
  148. package/dist/upgrade/detect.d.ts.map +1 -0
  149. package/dist/upgrade/money-v2.d.ts +81 -0
  150. package/dist/upgrade/money-v2.d.ts.map +1 -0
  151. package/package.json +14 -13
  152. package/agents/line-amount-migration.md +0 -50
  153. package/dist/chunks/ContractLineItemCollection--BrGMtNz.js.map +0 -1
  154. package/dist/chunks/DocumentTaxCollection-KOkaQT2M.js.map +0 -1
  155. package/dist/chunks/InvoiceCollection-BUe_8FT1.js.map +0 -1
  156. package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js +0 -468
  157. package/dist/chunks/InvoiceLineItemCollection-CPQGghyd.js.map +0 -1
  158. package/dist/chunks/TaxSettingsCollection-DYYF-In-.js +0 -319
  159. package/dist/chunks/TaxSettingsCollection-DYYF-In-.js.map +0 -1
  160. package/dist/chunks/calculations-DaHR17il.js +0 -333
  161. package/dist/chunks/calculations-DaHR17il.js.map +0 -1
  162. package/dist/chunks/document-money-oUDvdjOf.js.map +0 -1
  163. package/dist/chunks/invoice-posting-CF3fE_Ec.js.map +0 -1
  164. package/dist/chunks/revenue-journal-DY9fro8E.js.map +0 -1
  165. package/dist/chunks/tenant-money-barrier-y6EAa6vY.js +0 -135
  166. package/dist/chunks/tenant-money-barrier-y6EAa6vY.js.map +0 -1
  167. package/dist/chunks/units-CNcjH_eE.js.map +0 -1
  168. package/dist/collections/MoneyMigrationDocumentCollection.d.ts +0 -12
  169. package/dist/collections/MoneyMigrationDocumentCollection.d.ts.map +0 -1
  170. package/dist/migrations/commerceLineAmounts.d.ts +0 -84
  171. package/dist/migrations/commerceLineAmounts.d.ts.map +0 -1
  172. package/dist/models/MoneyMigrationDocument.d.ts +0 -28
  173. package/dist/models/MoneyMigrationDocument.d.ts.map +0 -1
  174. package/dist/svelte/components/InvoiceLineEditor.svelte +0 -92
  175. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +0 -8
  176. package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +0 -1
  177. package/dist/svelte/invoices/fields.d.ts +0 -6
  178. package/dist/svelte/invoices/fields.d.ts.map +0 -1
  179. package/dist/svelte/invoices/fields.js +0 -22
  180. package/dist/tax/legacy.d.ts +0 -48
  181. package/dist/tax/legacy.d.ts.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"invoice-posting-CF3fE_Ec.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":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"revenue-journal-DY9fro8E.js","names":[],"sources":["../../src/revenue-journal.ts"],"sourcesContent":["/** Pure journal-entry construction from frozen commercial money snapshots. */\n\nimport { ISO_4217_MINOR_UNITS } from '@happyvertical/smrt-ui/currency';\nimport { MoneyValidationError } from './money.js';\n\nconst MAX_SAFE_MONEY = BigInt(Number.MAX_SAFE_INTEGER);\n\nexport interface RevenueJournalDocumentInput {\n id: string;\n number?: string | null;\n currency: string;\n lineAmountType: 'exclusive' | 'inclusive' | 'none';\n subtotalMinor: number;\n taxMinor: number;\n totalMinor: number;\n providerTaxMinor: number;\n}\n\nexport interface RevenueJournalLineInput {\n id: string;\n /** Stored gross-for-inclusive or net-for-exclusive line amount. */\n amountMinor: number;\n /** Stored total line tax, excluding document-level provider tax. */\n taxMinor: number;\n revenueAccountId?: string | null;\n}\n\n/** Stored DocumentTax snapshot fields used by revenue posting. */\nexport interface RevenueJournalTaxInput {\n currency: string;\n taxCodeId: string | null;\n taxCodeCode: string | null;\n taxComponentId: string | null;\n liabilityAccountId: string | null;\n taxMinor: number;\n}\n\nexport interface RevenueJournalOptions {\n arAccountId: string;\n revenueAccountId: string;\n taxAccountId?: string | null;\n /** Explicit ledger book currency; conversion is outside this helper. */\n bookCurrency: string;\n /** Use -1 for an exact reversal or credit direction. */\n direction?: 1 | -1;\n}\n\nexport interface RevenueJournalEntry {\n accountId: string;\n debit?: number;\n credit?: number;\n currency: string;\n memo: string;\n}\n\nexport interface RevenueJournalWarning {\n code: 'LEGACY_TAX_ACCOUNT_FALLBACK';\n taxCodeId: string;\n taxCodeCode: string;\n taxComponentId: string;\n accountId: string;\n}\n\nexport interface RevenueJournalResult {\n entries: RevenueJournalEntry[];\n warnings: RevenueJournalWarning[];\n}\n\nfunction fail(\n code: 'INVALID_MONEY_INPUT' | 'UNSAFE_MONEY' | 'CURRENCY_MISMATCH',\n message: string,\n): never {\n throw new MoneyValidationError(code, message);\n}\n\nfunction identifier(value: string | null | undefined, label: string): string {\n if (typeof value !== 'string' || value.length === 0 || value.trim() !== value)\n fail('INVALID_MONEY_INPUT', `${label} must be a nonempty value`);\n return value;\n}\n\nfunction money(value: number, label: string): bigint {\n if (!Number.isSafeInteger(value))\n fail('UNSAFE_MONEY', `${label} must be safe integer minor units`);\n return BigInt(value);\n}\n\nfunction safe(value: bigint, label: string): number {\n if (value > MAX_SAFE_MONEY || value < -MAX_SAFE_MONEY)\n fail('UNSAFE_MONEY', `${label} exceeds safe integer minor units`);\n return Number(value);\n}\n\nfunction currency(value: string, label: string): string {\n if (\n typeof value !== 'string' ||\n value !== value.toUpperCase() ||\n !ISO_4217_MINOR_UNITS.has(value) ||\n ISO_4217_MINOR_UNITS.get(value) === null\n ) {\n fail('INVALID_MONEY_INPUT', `${label} must be a supported ISO 4217 code`);\n }\n return value;\n}\n\nfunction compareCodeUnits(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction add(group: Map<string, bigint>, key: string, value: bigint): void {\n group.set(key, (group.get(key) ?? 0n) + value);\n}\n\nfunction entry(\n accountId: string,\n movement: bigint,\n documentCurrency: string,\n memo: string,\n): RevenueJournalEntry | null {\n if (movement === 0n) return null;\n const amount = safe(movement < 0n ? -movement : movement, 'Journal entry');\n return movement > 0n\n ? { accountId, debit: amount, currency: documentCurrency, memo }\n : { accountId, credit: amount, currency: documentCurrency, memo };\n}\n\n/**\n * Build balanced entries from persisted values only. Positive source amounts\n * debit receivables and credit revenue/tax; direction -1 swaps those sides.\n */\nexport function buildRevenueJournalEntries(\n document: RevenueJournalDocumentInput,\n lines: readonly RevenueJournalLineInput[],\n taxes: readonly RevenueJournalTaxInput[],\n options: RevenueJournalOptions,\n): RevenueJournalResult {\n identifier(document.id, 'Document ID');\n const documentCurrency = currency(document.currency, 'Document currency');\n const bookCurrency = currency(options.bookCurrency, 'Book currency');\n if (documentCurrency !== bookCurrency)\n fail(\n 'CURRENCY_MISMATCH',\n `Document currency ${documentCurrency} does not match book currency ${bookCurrency}`,\n );\n if (\n document.lineAmountType !== 'exclusive' &&\n document.lineAmountType !== 'inclusive' &&\n document.lineAmountType !== 'none'\n ) {\n fail('INVALID_MONEY_INPUT', 'Unknown line amount type');\n }\n if (\n options.direction !== undefined &&\n options.direction !== 1 &&\n options.direction !== -1\n )\n fail('INVALID_MONEY_INPUT', 'Journal direction must be 1 or -1');\n const direction = BigInt(options.direction ?? 1);\n const arAccountId = identifier(options.arAccountId, 'AR account ID');\n const fallbackRevenue = identifier(\n options.revenueAccountId,\n 'Revenue account ID',\n );\n const fallbackTax = options.taxAccountId ?? null;\n if (fallbackTax !== null) identifier(fallbackTax, 'Tax account ID');\n\n const subtotal = money(document.subtotalMinor, 'Document subtotal');\n const documentTax = money(document.taxMinor, 'Document tax');\n const total = money(document.totalMinor, 'Document total');\n const providerTax = money(document.providerTaxMinor, 'Provider tax');\n if (subtotal + documentTax !== total)\n fail('INVALID_MONEY_INPUT', 'Document subtotal plus tax must equal total');\n\n const revenueGroups = new Map<string, bigint>();\n const lineIds = new Set<string>();\n let lineNetTotal = 0n;\n let lineTaxTotal = 0n;\n for (const line of lines) {\n const lineId = identifier(line.id, 'Line ID');\n if (lineIds.has(lineId))\n fail('INVALID_MONEY_INPUT', `Duplicate line ID: ${lineId}`);\n lineIds.add(lineId);\n const amount = money(line.amountMinor, `Line ${lineId} amount`);\n const tax = money(line.taxMinor, `Line ${lineId} tax`);\n const net = document.lineAmountType === 'inclusive' ? amount - tax : amount;\n const accountId = line.revenueAccountId\n ? identifier(line.revenueAccountId, `Line ${lineId} revenue account ID`)\n : fallbackRevenue;\n add(revenueGroups, accountId, net);\n lineNetTotal += net;\n lineTaxTotal += tax;\n }\n if (lineNetTotal !== subtotal)\n fail(\n 'INVALID_MONEY_INPUT',\n 'Stored line net total does not match document subtotal',\n );\n if (lineTaxTotal + providerTax !== documentTax)\n fail(\n 'INVALID_MONEY_INPUT',\n 'Stored line tax plus provider tax does not match document tax',\n );\n\n const taxGroups = new Map<string, bigint>();\n const warnings: RevenueJournalWarning[] = [];\n const taxRowIds = new Set<string>();\n let componentTaxTotal = 0n;\n for (const row of taxes) {\n if (currency(row.currency, 'DocumentTax currency') !== documentCurrency)\n fail(\n 'CURRENCY_MISMATCH',\n 'DocumentTax currency does not match document currency',\n );\n const tax = money(row.taxMinor, 'DocumentTax tax');\n if (tax === 0n) continue;\n if (row.taxComponentId === null)\n fail(\n 'INVALID_MONEY_INPUT',\n 'A nonzero DocumentTax row requires a component',\n );\n const componentId = identifier(row.taxComponentId, 'Tax component ID');\n const taxCodeId = identifier(row.taxCodeId, 'Tax code ID');\n const taxCodeCode = identifier(row.taxCodeCode, 'Tax code code');\n const taxRowId = `${taxCodeId}\\0${componentId}`;\n if (taxRowIds.has(taxRowId))\n fail(\n 'INVALID_MONEY_INPUT',\n `Duplicate DocumentTax code/component row: ${taxCodeId}/${componentId}`,\n );\n taxRowIds.add(taxRowId);\n const accountId = row.liabilityAccountId\n ? identifier(row.liabilityAccountId, 'Tax liability account ID')\n : fallbackTax;\n if (accountId === null)\n fail(\n 'INVALID_MONEY_INPUT',\n `Tax component ${componentId} has no posting account`,\n );\n add(taxGroups, accountId, tax);\n componentTaxTotal += tax;\n if (row.liabilityAccountId === null && taxCodeCode.startsWith('legacy-')) {\n warnings.push({\n code: 'LEGACY_TAX_ACCOUNT_FALLBACK',\n taxCodeId,\n taxCodeCode,\n taxComponentId: componentId,\n accountId,\n });\n }\n }\n if (componentTaxTotal !== lineTaxTotal)\n fail(\n 'INVALID_MONEY_INPUT',\n 'Stored component tax does not match stored line tax',\n );\n if (providerTax !== 0n && fallbackTax === null)\n fail('INVALID_MONEY_INPUT', 'Provider tax requires a fallback tax account');\n\n const label = document.number ?? document.id;\n const entries: RevenueJournalEntry[] = [];\n const ar = entry(\n arAccountId,\n total * direction,\n documentCurrency,\n `Receivable: ${label}`,\n );\n if (ar) entries.push(ar);\n for (const [accountId, amount] of [...revenueGroups].sort(([a], [b]) =>\n compareCodeUnits(a, b),\n )) {\n const built = entry(\n accountId,\n -amount * direction,\n documentCurrency,\n `Revenue: ${label}`,\n );\n if (built) entries.push(built);\n }\n for (const [accountId, amount] of [...taxGroups].sort(([a], [b]) =>\n compareCodeUnits(a, b),\n )) {\n const built = entry(\n accountId,\n -amount * direction,\n documentCurrency,\n `Tax: ${label}`,\n );\n if (built) entries.push(built);\n }\n if (providerTax !== 0n) {\n const built = entry(\n fallbackTax as string,\n -providerTax * direction,\n documentCurrency,\n `Provider tax: ${label}`,\n );\n if (built) entries.push(built);\n }\n\n let debits = 0n;\n let credits = 0n;\n for (const built of entries) {\n debits += BigInt(built.debit ?? 0);\n credits += BigInt(built.credit ?? 0);\n }\n if (debits !== credits)\n fail('INVALID_MONEY_INPUT', 'Built journal entries are not balanced');\n safe(debits, 'Journal debit total');\n return { entries, warnings };\n}\n"],"mappings":"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"}
@@ -1,135 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { MoneyValidationError } from "../money.js";
3
- import { t as TaxSettingsCollection, u as withDocumentMoneyTransaction } from "./TaxSettingsCollection-DYYF-In-.js";
4
- //#region src/tenant-money-barrier.ts
5
- var tenant_money_barrier_exports = /* @__PURE__ */ __exportAll({
6
- lockTenantMoneyMigrationClaim: () => lockTenantMoneyMigrationClaim,
7
- lockTenantMoneyMigrationOwner: () => lockTenantMoneyMigrationOwner,
8
- lockTenantMoneyRuntime: () => lockTenantMoneyRuntime,
9
- withTenantMoneyCollectionRead: () => withTenantMoneyCollectionRead,
10
- withTenantMoneyMigration: () => withTenantMoneyMigration,
11
- withTenantMoneyRuntime: () => withTenantMoneyRuntime
12
- });
13
- function requiredText(value, label) {
14
- if (!value || value.trim() !== value) throw new MoneyValidationError("INVALID_MONEY_INPUT", `${label} must be a nonempty trimmed string`);
15
- return value;
16
- }
17
- async function hasSettingsStorage(db) {
18
- return !db.tableExists || await db.tableExists("tax_settings");
19
- }
20
- var activeRuntimeScopes = /* @__PURE__ */ new WeakMap();
21
- var activeMigrationScopes = /* @__PURE__ */ new WeakMap();
22
- function runtimeScope(db, tenantId, barrier) {
23
- return {
24
- db,
25
- tenantId,
26
- barrier,
27
- phase: "runtime"
28
- };
29
- }
30
- async function lockTenantMoneyRuntime(tx, tenantId, requirement = "legacy-or-v2") {
31
- requiredText(tenantId, "Money tenant");
32
- if (!await hasSettingsStorage(tx)) {
33
- if (requirement === "v2") throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money v2 requires tenant tax settings storage");
34
- return {
35
- moneyModel: "legacy",
36
- settings: null,
37
- persistedBarrier: false
38
- };
39
- }
40
- const settings = await (await TaxSettingsCollection.create({ db: tx })).lockForTenant(tenantId);
41
- if (!settings) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Tenant money settings could not be materialized");
42
- if (settings.moneyModel === "migrating") throw new MoneyValidationError("FROZEN_DOCUMENT", "Tenant money migration is in progress");
43
- if (settings.moneyModel === "v2") return {
44
- moneyModel: "v2",
45
- settings,
46
- persistedBarrier: true
47
- };
48
- if (requirement === "v2") throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money v2 is not active for this tenant");
49
- return {
50
- moneyModel: "legacy",
51
- settings,
52
- persistedBarrier: true
53
- };
54
- }
55
- async function withTenantMoneyRuntime(db, tenantId, requirement, operation, existingScope) {
56
- requiredText(tenantId, "Money tenant");
57
- if (existingScope) {
58
- if (existingScope.phase !== "runtime" || existingScope.db !== db || existingScope.tenantId !== tenantId || requirement === "v2" && existingScope.barrier.moneyModel !== "v2") throw new MoneyValidationError("INVALID_MONEY_INPUT", "Tenant money runtime scope does not match the requested executor");
59
- return operation(existingScope);
60
- }
61
- return withDocumentMoneyTransaction(db, async (tx) => {
62
- const active = activeRuntimeScopes.get(tx)?.get(tenantId);
63
- if (active) {
64
- if (requirement === "v2" && active.barrier.moneyModel !== "v2") throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money v2 is not active for this tenant");
65
- return operation(active);
66
- }
67
- const scope = runtimeScope(tx, tenantId, await lockTenantMoneyRuntime(tx, tenantId, requirement));
68
- const scopes = activeRuntimeScopes.get(tx) ?? /* @__PURE__ */ new Map();
69
- scopes.set(tenantId, scope);
70
- activeRuntimeScopes.set(tx, scopes);
71
- try {
72
- return await operation(scope);
73
- } finally {
74
- scopes.delete(tenantId);
75
- if (scopes.size === 0) activeRuntimeScopes.delete(tx);
76
- }
77
- });
78
- }
79
- async function withTenantMoneyMigration(db, tenantId, migrationId, operation, existingScope) {
80
- requiredText(tenantId, "Money migration tenant");
81
- requiredText(migrationId, "Money migration owner");
82
- if (existingScope) {
83
- if (existingScope.phase !== "migration" || existingScope.db !== db || existingScope.tenantId !== tenantId || existingScope.migrationId !== migrationId) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Tenant money migration scope does not match the requested executor");
84
- return operation(existingScope);
85
- }
86
- return withDocumentMoneyTransaction(db, async (tx) => {
87
- const active = activeMigrationScopes.get(tx)?.get(tenantId);
88
- if (active) {
89
- if (active.migrationId !== migrationId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Tenant money migration owner does not match");
90
- return operation(active);
91
- }
92
- const scope = {
93
- db: tx,
94
- tenantId,
95
- migrationId,
96
- settings: await lockTenantMoneyMigrationOwner(tx, tenantId, migrationId),
97
- phase: "migration"
98
- };
99
- const scopes = activeMigrationScopes.get(tx) ?? /* @__PURE__ */ new Map();
100
- scopes.set(tenantId, scope);
101
- activeMigrationScopes.set(tx, scopes);
102
- try {
103
- return await operation(scope);
104
- } finally {
105
- scopes.delete(tenantId);
106
- if (scopes.size === 0) activeMigrationScopes.delete(tx);
107
- }
108
- });
109
- }
110
- async function withTenantMoneyCollectionRead(db, tenantId, operation) {
111
- const migration = activeMigrationScopes.get(db)?.get(tenantId);
112
- if (migration) return operation(migration.db);
113
- return withTenantMoneyRuntime(db, tenantId, "legacy-or-v2", (scope) => operation(scope.db));
114
- }
115
- async function lockTenantMoneyMigrationClaim(tx, tenantId) {
116
- requiredText(tenantId, "Money migration tenant");
117
- if (!await hasSettingsStorage(tx)) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money migration requires installed tax settings storage");
118
- const settings = await (await TaxSettingsCollection.create({ db: tx })).lockForTenant(tenantId);
119
- if (!settings) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Tenant money settings could not be materialized");
120
- if (settings.moneyModel === "v2") throw new MoneyValidationError("FROZEN_DOCUMENT", "Tenant money model is already v2");
121
- if (settings.moneyModel === "migrating") throw new MoneyValidationError("FROZEN_DOCUMENT", "Tenant money migration is already in progress");
122
- return settings;
123
- }
124
- async function lockTenantMoneyMigrationOwner(tx, tenantId, migrationId) {
125
- requiredText(tenantId, "Money migration tenant");
126
- requiredText(migrationId, "Money migration owner");
127
- if (!await hasSettingsStorage(tx)) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Money migration requires installed tax settings storage");
128
- const settings = await (await TaxSettingsCollection.create({ db: tx })).lockForTenant(tenantId, false);
129
- if (settings?.moneyModel !== "migrating" || settings.moneyMigrationId !== migrationId) throw new MoneyValidationError("FROZEN_DOCUMENT", "Tenant money migration owner does not match");
130
- return settings;
131
- }
132
- //#endregion
133
- export { withTenantMoneyMigration as a, withTenantMoneyCollectionRead as i, lockTenantMoneyRuntime as n, withTenantMoneyRuntime as o, tenant_money_barrier_exports as r, lockTenantMoneyMigrationOwner as t };
134
-
135
- //# sourceMappingURL=tenant-money-barrier-y6EAa6vY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-money-barrier-y6EAa6vY.js","names":[],"sources":["../../src/tenant-money-barrier.ts"],"sourcesContent":["/** Shared tenant serialization barrier for commerce money runtime and migration. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { TaxSettingsCollection } from './collections/TaxSettingsCollection.js';\nimport { withDocumentMoneyTransaction } from './document-money-internal.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport { MoneyValidationError } from './money.js';\n\nexport type TenantMoneyRuntimeRequirement = 'legacy-or-v2' | 'v2';\n\nexport type TenantMoneyRuntimeBarrier =\n | {\n readonly moneyModel: 'legacy';\n readonly settings: null;\n readonly persistedBarrier: false;\n }\n | {\n readonly moneyModel: 'legacy';\n readonly settings: TaxSettings;\n readonly persistedBarrier: true;\n }\n | {\n readonly moneyModel: 'v2';\n readonly settings: TaxSettings;\n readonly persistedBarrier: true;\n };\n\ndeclare const tenantMoneyScopeBrand: unique symbol;\n\n/** Transaction-affine proof that a tenant marker is locked on `db`. */\nexport interface TenantMoneyRuntimeScope {\n readonly db: DatabaseInterface;\n readonly tenantId: string;\n readonly barrier: TenantMoneyRuntimeBarrier;\n readonly phase: 'runtime';\n readonly [tenantMoneyScopeBrand]: true;\n}\n\ndeclare const tenantMoneyMigrationScopeBrand: unique symbol;\n\n/** Transaction-affine proof held only by the active migration owner. */\nexport interface TenantMoneyMigrationScope {\n readonly db: DatabaseInterface;\n readonly tenantId: string;\n readonly migrationId: string;\n readonly settings: TaxSettings;\n readonly phase: 'migration';\n readonly [tenantMoneyMigrationScopeBrand]: true;\n}\n\nfunction requiredText(value: string, label: string): string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} must be a nonempty trimmed string`,\n );\n return value;\n}\n\nasync function hasSettingsStorage(db: DatabaseInterface): Promise<boolean> {\n return !db.tableExists || (await db.tableExists('tax_settings'));\n}\n\nconst activeRuntimeScopes = new WeakMap<\n object,\n Map<string, TenantMoneyRuntimeScope>\n>();\nconst activeMigrationScopes = new WeakMap<\n object,\n Map<string, TenantMoneyMigrationScope>\n>();\n\nfunction runtimeScope(\n db: DatabaseInterface,\n tenantId: string,\n barrier: TenantMoneyRuntimeBarrier,\n): TenantMoneyRuntimeScope {\n return {\n db,\n tenantId,\n barrier,\n phase: 'runtime',\n } as TenantMoneyRuntimeScope;\n}\n\n/**\n * Lock and re-read the tenant money marker before any runtime money work.\n *\n * Older schemas without TaxSettings stay byte-compatible legacy and are never\n * modified. Once the table exists, a missing deterministic legacy row is\n * materialized solely to provide the serialization point shared by migration.\n */\nexport async function lockTenantMoneyRuntime(\n tx: DatabaseInterface,\n tenantId: string,\n requirement: TenantMoneyRuntimeRequirement = 'legacy-or-v2',\n): Promise<TenantMoneyRuntimeBarrier> {\n requiredText(tenantId, 'Money tenant');\n if (!(await hasSettingsStorage(tx))) {\n if (requirement === 'v2')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money v2 requires tenant tax settings storage',\n );\n return {\n moneyModel: 'legacy',\n settings: null,\n persistedBarrier: false,\n };\n }\n const collection = await TaxSettingsCollection.create({ db: tx });\n const settings = await collection.lockForTenant(tenantId);\n if (!settings)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Tenant money settings could not be materialized',\n );\n if (settings.moneyModel === 'migrating')\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Tenant money migration is in progress',\n );\n if (settings.moneyModel === 'v2')\n return { moneyModel: 'v2', settings, persistedBarrier: true };\n if (requirement === 'v2')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money v2 is not active for this tenant',\n );\n return { moneyModel: 'legacy', settings, persistedBarrier: true };\n}\n\n/**\n * Run tenant money work under the shared marker lock on exactly one executor.\n *\n * Supplying an existing scope proves re-entry on the same executor. Calls made\n * through the owning transaction wrapper also reuse its exact registered\n * executor. No optional adapter-property heuristic can grant this capability.\n */\nexport async function withTenantMoneyRuntime<T>(\n db: DatabaseInterface,\n tenantId: string,\n requirement: TenantMoneyRuntimeRequirement,\n operation: (scope: TenantMoneyRuntimeScope) => Promise<T>,\n existingScope?: TenantMoneyRuntimeScope,\n): Promise<T> {\n requiredText(tenantId, 'Money tenant');\n if (existingScope) {\n if (\n existingScope.phase !== 'runtime' ||\n existingScope.db !== db ||\n existingScope.tenantId !== tenantId ||\n (requirement === 'v2' && existingScope.barrier.moneyModel !== 'v2')\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Tenant money runtime scope does not match the requested executor',\n );\n return operation(existingScope);\n }\n return withDocumentMoneyTransaction(db, async (tx) => {\n const active = activeRuntimeScopes.get(tx as object)?.get(tenantId);\n if (active) {\n if (requirement === 'v2' && active.barrier.moneyModel !== 'v2')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money v2 is not active for this tenant',\n );\n return operation(active);\n }\n const barrier = await lockTenantMoneyRuntime(tx, tenantId, requirement);\n const scope = runtimeScope(tx, tenantId, barrier);\n const scopes = activeRuntimeScopes.get(tx as object) ?? new Map();\n scopes.set(tenantId, scope);\n activeRuntimeScopes.set(tx as object, scopes);\n try {\n return await operation(scope);\n } finally {\n scopes.delete(tenantId);\n if (scopes.size === 0) activeRuntimeScopes.delete(tx as object);\n }\n });\n}\n\n/**\n * Run migration work under the exact owner marker and transaction executor.\n * This is the only scope that lets guarded collections read while the marker\n * is `migrating`; possession cannot be inferred from adapter shape.\n */\nexport async function withTenantMoneyMigration<T>(\n db: DatabaseInterface,\n tenantId: string,\n migrationId: string,\n operation: (scope: TenantMoneyMigrationScope) => Promise<T>,\n existingScope?: TenantMoneyMigrationScope,\n): Promise<T> {\n requiredText(tenantId, 'Money migration tenant');\n requiredText(migrationId, 'Money migration owner');\n if (existingScope) {\n if (\n existingScope.phase !== 'migration' ||\n existingScope.db !== db ||\n existingScope.tenantId !== tenantId ||\n existingScope.migrationId !== migrationId\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Tenant money migration scope does not match the requested executor',\n );\n return operation(existingScope);\n }\n return withDocumentMoneyTransaction(db, async (tx) => {\n const active = activeMigrationScopes.get(tx as object)?.get(tenantId);\n if (active) {\n if (active.migrationId !== migrationId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Tenant money migration owner does not match',\n );\n return operation(active);\n }\n const settings = await lockTenantMoneyMigrationOwner(\n tx,\n tenantId,\n migrationId,\n );\n const scope = {\n db: tx,\n tenantId,\n migrationId,\n settings,\n phase: 'migration',\n } as TenantMoneyMigrationScope;\n const scopes = activeMigrationScopes.get(tx as object) ?? new Map();\n scopes.set(tenantId, scope);\n activeMigrationScopes.set(tx as object, scopes);\n try {\n return await operation(scope);\n } finally {\n scopes.delete(tenantId);\n if (scopes.size === 0) activeMigrationScopes.delete(tx as object);\n }\n });\n}\n\n/** @internal Guard a generated collection read for runtime or migration. */\nexport async function withTenantMoneyCollectionRead<T>(\n db: DatabaseInterface,\n tenantId: string,\n operation: (boundDb: DatabaseInterface) => Promise<T>,\n): Promise<T> {\n const migration = activeMigrationScopes.get(db as object)?.get(tenantId);\n if (migration) return operation(migration.db);\n return withTenantMoneyRuntime(db, tenantId, 'legacy-or-v2', (scope) =>\n operation(scope.db),\n );\n}\n\n/** Lock a tenant at the legacy marker before the caller claims migration. */\nexport async function lockTenantMoneyMigrationClaim(\n tx: DatabaseInterface,\n tenantId: string,\n): Promise<TaxSettings> {\n requiredText(tenantId, 'Money migration tenant');\n if (!(await hasSettingsStorage(tx)))\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money migration requires installed tax settings storage',\n );\n const collection = await TaxSettingsCollection.create({ db: tx });\n const settings = await collection.lockForTenant(tenantId);\n if (!settings)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Tenant money settings could not be materialized',\n );\n if (settings.moneyModel === 'v2')\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Tenant money model is already v2',\n );\n if (settings.moneyModel === 'migrating')\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Tenant money migration is already in progress',\n );\n return settings;\n}\n\n/** Lock and verify the exact owner of an active tenant migration. */\nexport async function lockTenantMoneyMigrationOwner(\n tx: DatabaseInterface,\n tenantId: string,\n migrationId: string,\n): Promise<TaxSettings> {\n requiredText(tenantId, 'Money migration tenant');\n requiredText(migrationId, 'Money migration owner');\n if (!(await hasSettingsStorage(tx)))\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Money migration requires installed tax settings storage',\n );\n const collection = await TaxSettingsCollection.create({ db: tx });\n const settings = await collection.lockForTenant(tenantId, false);\n if (\n settings?.moneyModel !== 'migrating' ||\n settings.moneyMigrationId !== migrationId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Tenant money migration owner does not match',\n );\n return settings;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"units-CNcjH_eE.js","names":[],"sources":["../../src/billing/units.ts"],"sourcesContent":["/**\n * Money-unit and identity helpers for billing-period close (#3060).\n *\n * Commerce stores money as integer minor units. `@happyvertical/accounting`\n * takes and returns currency *major* units and converts to the provider's own\n * representation itself, so the only conversion this package performs is the\n * ISO 4217 one: minor units ÷ 10^exponent.\n */\n\nimport {\n currencyMinorUnitExponent,\n normalizeCurrency,\n} from '../svelte/invoices/currency.js';\n\nexport {\n currencyMinorUnitExponent,\n normalizeCurrency,\n} from '../svelte/invoices/currency.js';\n\n/** Integer minor units → major units (`1999` USD → `19.99`). */\nexport function minorToMajorUnits(amount: number, currency: string): number {\n if (!Number.isSafeInteger(amount)) {\n throw new Error(\n `Amount ${amount} must be a safe integer number of minor units.`,\n );\n }\n return amount / 10 ** currencyMinorUnitExponent(currency);\n}\n\n/**\n * Major units → integer minor units. Rejects a value that is not a whole\n * number of minor units instead of rounding it: a provider amount that does\n * not round-trip exactly means the two sides disagree about the currency.\n */\nexport function majorToMinorUnits(amount: number, currency: string): number {\n if (!Number.isFinite(amount)) {\n throw new Error(`Provider amount ${amount} is not a finite number.`);\n }\n const scaled = amount * 10 ** currencyMinorUnitExponent(currency);\n const minor = Math.round(scaled);\n if (Math.abs(scaled - minor) > 1e-6 || !Number.isSafeInteger(minor)) {\n throw new Error(\n `Provider amount ${amount} ${normalizeCurrency(currency)} is not a whole number of minor units.`,\n );\n }\n return minor;\n}\n\n/**\n * A stable UUID derived from its parts (SHA-256, version/variant bits set).\n * Deterministic ids are what make every period-close write replay-safe: a\n * retry recomputes the same identity instead of creating a second row.\n */\nexport async function deterministicId(parts: string[]): Promise<string> {\n const bytes = new TextEncoder().encode(JSON.stringify(parts));\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));\n const uuid = digest.slice(0, 16);\n uuid[6] = (uuid[6] & 0x0f) | 0x50;\n uuid[8] = (uuid[8] & 0x3f) | 0x80;\n const hex = Array.from(uuid, (byte) => byte.toString(16).padStart(2, '0'));\n const joined = hex.join('');\n return [\n joined.slice(0, 8),\n joined.slice(8, 12),\n joined.slice(12, 16),\n joined.slice(16, 20),\n joined.slice(20),\n ].join('-');\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Validate and lower-case a tenant UUID. */\nexport function canonicalTenantId(id: string, label = 'Tenant ID'): string {\n if (typeof id !== 'string' || !UUID_PATTERN.test(id)) {\n throw new Error(`${label} must be a UUID.`);\n }\n return id.toLowerCase();\n}\n\n/** Whether `id` is a UUID (any case), such as a billing record id. */\nexport function isUuid(id: string): boolean {\n return typeof id === 'string' && UUID_PATTERN.test(id);\n}\n\n/** Whether `id` is a tenant UUID (any case). */\nexport function isTenantId(id: string): boolean {\n return typeof id === 'string' && UUID_PATTERN.test(id);\n}\n\n/** Lower-case a possibly-empty tenant id for comparison. */\nexport function tenantKey(id: string | null | undefined): string {\n return id ? String(id).toLowerCase() : '';\n}\n"],"mappings":";;AAoBO,SAAS,kBAAkB,QAAgB,UAA0B;CAC1E,IAAI,CAAC,OAAO,cAAc,MAAM,GAC9B,MAAM,IAAI,MACR,UAAU,OAAM,+CAClB;CAEF,OAAO,SAAS,MAAM,0BAA0B,QAAQ;AAC1D;AAOO,SAAS,kBAAkB,QAAgB,UAA0B;CAC1E,IAAI,CAAC,OAAO,SAAS,MAAM,GACzB,MAAM,IAAI,MAAM,mBAAmB,OAAM,yBAA0B;CAErE,MAAM,SAAS,SAAS,MAAM,0BAA0B,QAAQ;CAChE,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,IAAI,KAAK,IAAI,SAAS,KAAK,IAAI,QAAQ,CAAC,OAAO,cAAc,KAAK,GAChE,MAAM,IAAI,MACR,mBAAmB,OAAM,GAAI,kBAAkB,QAAQ,EAAC,uCAC1D;CAEF,OAAO;AACT;AAOA,eAAsB,gBAAgB,OAAkC;CACtE,MAAM,QAAQ,IAAI,YAAY,CAAA,CAAE,OAAO,KAAK,UAAU,KAAK,CAAC;CAE5D,MAAM,OAAO,IADM,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK,CAC5D,CAAA,CAAO,MAAM,GAAG,EAAE;CAC/B,KAAK,KAAM,KAAK,KAAK,KAAQ;CAC7B,KAAK,KAAM,KAAK,KAAK,KAAQ;CAE7B,MAAM,SADM,MAAM,KAAK,OAAO,SAAS,KAAK,SAAS,EAAE,CAAA,CAAE,SAAS,GAAG,GAAG,CACzD,CAAA,CAAI,KAAK,EAAE;CAC1B,OAAO;EACL,OAAO,MAAM,GAAG,CAAC;EACjB,OAAO,MAAM,GAAG,EAAE;EAClB,OAAO,MAAM,IAAI,EAAE;EACnB,OAAO,MAAM,IAAI,EAAE;EACnB,OAAO,MAAM,EAAE;CACjB,CAAA,CAAE,KAAK,GAAG;AACZ;AAEA,IAAM,eACJ;AAGK,SAAS,kBAAkB,IAAY,QAAQ,aAAqB;CACzE,IAAI,OAAO,OAAO,YAAY,CAAC,aAAa,KAAK,EAAE,GACjD,MAAM,IAAI,MAAM,GAAG,MAAK,iBAAkB;CAE5C,OAAO,GAAG,YAAY;AACxB;AAGO,SAAS,OAAO,IAAqB;CAC1C,OAAO,OAAO,OAAO,YAAY,aAAa,KAAK,EAAE;AACvD;AAGO,SAAS,WAAW,IAAqB;CAC9C,OAAO,OAAO,OAAO,YAAY,aAAa,KAAK,EAAE;AACvD;AAGO,SAAS,UAAU,IAAuC;CAC/D,OAAO,KAAK,OAAO,EAAE,CAAA,CAAE,YAAY,IAAI;AACzC"}
@@ -1,12 +0,0 @@
1
- import { SmrtCollection } from '@happyvertical/smrt-core';
2
- import { MoneyMigrationDocument } from '../models/MoneyMigrationDocument.js';
3
- export declare class MoneyMigrationDocumentCollection extends SmrtCollection<MoneyMigrationDocument> {
4
- static readonly _itemClass: typeof MoneyMigrationDocument;
5
- findDocument(input: {
6
- tenantId: string;
7
- migrationId: string;
8
- documentType: 'invoice' | 'contract';
9
- documentId: string;
10
- }): Promise<MoneyMigrationDocument | null>;
11
- }
12
- //# sourceMappingURL=MoneyMigrationDocumentCollection.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MoneyMigrationDocumentCollection.d.ts","sourceRoot":"","sources":["../../src/collections/MoneyMigrationDocumentCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,qBAAa,gCAAiC,SAAQ,cAAc,CAAC,sBAAsB,CAAC;IAC1F,MAAM,CAAC,QAAQ,CAAC,UAAU,gCAA0B;IAE9C,YAAY,CAAC,KAAK,EAAE;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,SAAS,GAAG,UAAU,CAAC;QACrC,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;CAU3C"}
@@ -1,84 +0,0 @@
1
- /** Commerce line-amount preflight, migration and rollback. */
2
- import type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';
3
- export type CommerceLineAmountReasonCode = 'DRAFT_RECOMPUTED' | 'ISSUED_BACKFILLED' | 'UNRECONCILED' | 'TAX_RATE_OVER_ONE' | 'HAND_ENTERED_AMOUNT' | 'NEGATIVE_HALF_UNIT_GROSS' | 'MISSING_HEADER_TOTALS' | 'STALE_EDITOR_BLOB' | 'ORPHAN_LINE' | 'CROSS_TENANT_LINE' | 'MISSING_LINE_TENANT' | 'CONTRACT_DIRECTION_UNKNOWN' | 'ACCOUNTING_SYNC_UNSETTLED';
4
- export interface CommerceMoneyTotals {
5
- subtotalMinor: number;
6
- taxMinor: number;
7
- totalMinor: number;
8
- }
9
- export interface CommerceLineAmountDocumentReport {
10
- tenantId: string;
11
- documentType: 'invoice' | 'contract';
12
- documentId: string;
13
- status: string;
14
- arJournalIdSet: boolean;
15
- old: CommerceMoneyTotals;
16
- next: CommerceMoneyTotals;
17
- delta: CommerceMoneyTotals;
18
- reasons: CommerceLineAmountReasonCode[];
19
- }
20
- export interface CommerceLineAmountTenantSummary {
21
- tenantId: string;
22
- documentCount: number;
23
- changingDraftCount: number;
24
- issuedUnpostedFallbackTaxCount: number;
25
- reasonCounts: Partial<Record<CommerceLineAmountReasonCode, number>>;
26
- }
27
- export interface CommerceLineAmountsReport {
28
- version: 1;
29
- documents: CommerceLineAmountDocumentReport[];
30
- tenants: CommerceLineAmountTenantSummary[];
31
- }
32
- /** Explicit report destinations. Omit `out` to receive only structured data. */
33
- export interface CommerceLineAmountsReportOutput {
34
- jsonPath: string;
35
- csvPath: string;
36
- }
37
- export interface PreflightCommerceLineAmountsOptions {
38
- /**
39
- * Optional artifact paths. Omitting this never writes files; the preflight
40
- * still returns the complete structured CommerceLineAmountsReport.
41
- */
42
- out?: CommerceLineAmountsReportOutput;
43
- /** Optional tenant restriction. Omit only from a system migration context. */
44
- tenantId?: string;
45
- }
46
- export interface MigrateCommerceLineAmountsOptions {
47
- tenantId: string;
48
- acceptDraftChanges?: boolean;
49
- /**
50
- * Stable owner identity chosen before the attempt starts. Supplying it on
51
- * the first call makes failures after the tenant claim safely resumable;
52
- * a retry must present the same identity.
53
- */
54
- migrationId?: string;
55
- }
56
- export interface CommerceLineAmountsMigrationResult {
57
- tenantId: string;
58
- migrationId: string;
59
- migrated: number;
60
- resumed: number;
61
- activated: boolean;
62
- report: CommerceLineAmountsReport;
63
- }
64
- export interface RollbackCommerceLineAmountsOptions {
65
- tenantId: string;
66
- migrationId: string;
67
- }
68
- export interface CommerceLineAmountsRollbackResult {
69
- tenantId: string;
70
- migrationId: string;
71
- restored: number;
72
- alreadyRestored: number;
73
- }
74
- /** Stable JSON bytes for review artifacts and retry comparisons. */
75
- export declare function serializeCommerceLineAmountsReportJson(report: CommerceLineAmountsReport): string;
76
- /** Stable one-row-per-document CSV bytes. Tenant summaries remain in JSON. */
77
- export declare function serializeCommerceLineAmountsReportCsv(report: CommerceLineAmountsReport): string;
78
- /** Read-only migration inventory. It never creates TaxSettings or claims a barrier. */
79
- export declare function preflightCommerceLineAmounts(db: DatabaseInterface, options?: PreflightCommerceLineAmountsOptions): Promise<CommerceLineAmountsReport>;
80
- /** Claim one tenant, migrate each document durably, verify, then activate v2. */
81
- export declare function migrateCommerceLineAmounts(db: DatabaseInterface, options: MigrateCommerceLineAmountsOptions): Promise<CommerceLineAmountsMigrationResult>;
82
- /** Restore exact pre-images only while every current document still matches its post-image. */
83
- export declare function rollbackCommerceLineAmounts(db: DatabaseInterface, options: RollbackCommerceLineAmountsOptions): Promise<CommerceLineAmountsRollbackResult>;
84
- //# sourceMappingURL=commerceLineAmounts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commerceLineAmounts.d.ts","sourceRoot":"","sources":["../../src/migrations/commerceLineAmounts.ts"],"names":[],"mappings":"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"}
@@ -1,28 +0,0 @@
1
- /** Durable, internal rollback journal for one commerce money document. */
2
- import { SmrtObject, type SmrtObjectOptions } from '@happyvertical/smrt-core';
3
- export type MoneyMigrationDocumentType = 'invoice' | 'contract';
4
- export type MoneyMigrationDocumentState = 'prepared' | 'applied' | 'rolled_back';
5
- export interface MoneyMigrationDocumentOptions extends SmrtObjectOptions {
6
- tenantId?: string;
7
- migrationId?: string;
8
- documentType?: MoneyMigrationDocumentType;
9
- documentId?: string;
10
- state?: MoneyMigrationDocumentState;
11
- preImageJson?: string;
12
- preChecksum?: string;
13
- postImageJson?: string;
14
- postChecksum?: string;
15
- }
16
- export declare class MoneyMigrationDocument extends SmrtObject {
17
- tenantId: string;
18
- migrationId: string;
19
- documentType: MoneyMigrationDocumentType;
20
- documentId: string;
21
- state: MoneyMigrationDocumentState;
22
- preImageJson: string;
23
- preChecksum: string;
24
- postImageJson: string;
25
- postChecksum: string;
26
- constructor(options?: MoneyMigrationDocumentOptions);
27
- }
28
- //# sourceMappingURL=MoneyMigrationDocument.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MoneyMigrationDocument.d.ts","sourceRoot":"","sources":["../../src/models/MoneyMigrationDocument.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,UAAU,CAAC;AAChE,MAAM,MAAM,2BAA2B,GACnC,UAAU,GACV,SAAS,GACT,aAAa,CAAC;AAElB,MAAM,WAAW,6BAA8B,SAAQ,iBAAiB;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,0BAA0B,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,2BAA2B,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAca,sBAAuB,SAAQ,UAAU;IAEpD,QAAQ,SAAM;IAGd,WAAW,SAAM;IAGjB,YAAY,EAAE,0BAA0B,CAAa;IAGrD,UAAU,SAAM;IAEhB,KAAK,EAAE,2BAA2B,CAAc;IAGhD,YAAY,SAAM;IAElB,WAAW,SAAM;IAGjB,aAAa,SAAM;IAEnB,YAAY,SAAM;gBAEN,OAAO,GAAE,6BAAkC;CAkBxD"}
@@ -1,92 +0,0 @@
1
- <script lang="ts">
2
- import { CurrencyDisplay } from '@happyvertical/smrt-ui';
3
- import { FormGroup, Input, Select } from '@happyvertical/smrt-ui/forms';
4
- import { useI18n } from '@happyvertical/smrt-ui/i18n';
5
- import { Button } from '@happyvertical/smrt-ui/ui';
6
- import { calculateInvoiceLine } from '../invoices/calculations.js';
7
- import { invoiceLineFieldNames } from '../invoices/fields.js';
8
- import { invoicePreparationMessages as M } from '../invoices/messages.js';
9
- import type { InvoiceLineEditorProps } from '../invoices/types.js';
10
- /** General invoice line fields and exact non-authoritative calculation preview. */
11
- export interface Props extends InvoiceLineEditorProps {}
12
- let {
13
- line,
14
- currency,
15
- inheritedTaxRate,
16
- roundingScope = 'line',
17
- number = 1,
18
- id,
19
- fields = {},
20
- errors = {},
21
- canEdit = true,
22
- pending = false,
23
- intentField = 'intent',
24
- removePrefix = 'removeLine:',
25
- onchange,
26
- }: Props = $props();
27
- const { t } = useI18n();
28
- const instanceId = $props.id();
29
- const prefix = $derived(id ?? `invoice-line-${instanceId}`);
30
- const names = $derived({ ...invoiceLineFieldNames, ...fields });
31
- let draft = $derived(line);
32
- const calculation = $derived(
33
- calculateInvoiceLine(draft, { currency, inheritedTaxRate }),
34
- );
35
- function refresh(event: Event) {
36
- const controls = (event.currentTarget as HTMLFieldSetElement).elements;
37
- draft = {
38
- ...draft,
39
- ...Object.fromEntries(
40
- Object.entries(names).map(([key, name]) => [
41
- key,
42
- (controls.namedItem(name) as HTMLInputElement | null)?.value ??
43
- draft[key as keyof typeof draft],
44
- ]),
45
- ),
46
- };
47
- onchange?.(draft);
48
- }
49
- </script>
50
- <fieldset oninput={refresh} onchange={refresh} disabled={!canEdit} class="invoice-line" data-smrt-component="InvoiceLineEditor">
51
- <legend>{t(M['commerce.invoice.line'], { number })}</legend>
52
- <Input type="hidden" name={names.key} value={draft.key} interaction={false} />
53
- {#each ['description', 'sku', 'quantity', 'unitPrice'] as field}
54
- <FormGroup id={`${prefix}-${field}`} label={t(M[`commerce.invoice.${field}` as 'commerce.invoice.description'], { currency })} error={errors[field as keyof typeof errors]}>
55
- <Input name={names[field as keyof typeof names]} value={draft[field as 'description']} inputmode={field === 'quantity' || field === 'unitPrice' ? 'decimal' : undefined} />
56
- </FormGroup>
57
- {/each}
58
- <FormGroup id={`${prefix}-discountType`} label={t(M['commerce.invoice.discountType'])} error={errors.discountType}>
59
- <Select name={names.discountType} value={draft.discountType}>
60
- {#if draft.discountType !== 'flat' && draft.discountType !== 'percent'}<option value={draft.discountType}>{draft.discountType}</option>{/if}
61
- <option value="flat">{t(M['commerce.invoice.flat'])}</option><option value="percent">{t(M['commerce.invoice.percent'])}</option>
62
- </Select>
63
- </FormGroup>
64
- <FormGroup id={`${prefix}-discountValue`} label={t(M['commerce.invoice.discountValue'])} error={errors.discountValue}><Input name={names.discountValue} value={draft.discountValue} inputmode="decimal" /></FormGroup>
65
- <FormGroup id={`${prefix}-taxMode`} label={t(M['commerce.invoice.taxMode'])} error={errors.taxMode}>
66
- <Select name={names.taxMode} value={draft.taxMode}>
67
- {#if draft.taxMode !== 'inherit' && draft.taxMode !== 'override'}<option value={draft.taxMode}>{draft.taxMode}</option>{/if}
68
- <option value="inherit">{t(M['commerce.invoice.inherit'])}</option><option value="override">{t(M['commerce.invoice.override'])}</option>
69
- </Select>
70
- </FormGroup>
71
- <FormGroup id={`${prefix}-taxRate`} label={t(M['commerce.invoice.taxRate'])} error={errors.taxRate}><Input name={names.taxRate} value={draft.taxRate} inputmode="decimal" /></FormGroup>
72
- {#if calculation.valid}
73
- <dl class="totals">
74
- <div><dt>{t(M['commerce.invoice.subtotal'])}</dt><dd><CurrencyDisplay amount={calculation.subtotalMinor} {currency} unit="cents" /></dd></div>
75
- {#if roundingScope === 'line'}
76
- <div><dt>{t(M['commerce.invoice.tax'])}</dt><dd><CurrencyDisplay amount={calculation.taxMinor} {currency} unit="cents" /></dd></div>
77
- <div><dt>{t(M['commerce.invoice.total'])}</dt><dd><CurrencyDisplay amount={calculation.totalMinor} {currency} unit="cents" /></dd></div>
78
- {/if}
79
- </dl>
80
- {#if roundingScope === 'invoice'}<p>{t(M['commerce.invoice.invoiceRounding'])}</p>{/if}
81
- {:else}<p role="status">{t(M['commerce.invoice.invalid'])}</p>{/if}
82
- {#if canEdit}<Button type="submit" name={intentField} value={`${removePrefix}${draft.key}`} formnovalidate disabled={pending} variant="secondary">{t(M['commerce.invoice.remove'])}</Button>{/if}
83
- </fieldset>
84
- <style>
85
- @layer smrt.reset, smrt.structure, smrt.theme;
86
- @layer smrt.theme {
87
- .invoice-line { display: grid; grid-template-columns: minmax(0,1fr); gap: var(--smrt-spacing-3); min-width: 0; margin: 0; padding: var(--smrt-spacing-4); border: var(--smrt-hairline-strong); border-radius: var(--smrt-radius-small); }
88
- legend { padding-inline: var(--smrt-spacing-2); }
89
- .totals { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-4); }
90
- dd { margin: 0; } p, dl { margin: 0; }
91
- }
92
- </style>
@@ -1,8 +0,0 @@
1
- import type { InvoiceLineEditorProps } from '../invoices/types.js';
2
- /** General invoice line fields and exact non-authoritative calculation preview. */
3
- export interface Props extends InvoiceLineEditorProps {
4
- }
5
- declare const InvoiceLineEditor: import("svelte").Component<Props, {}, "">;
6
- type InvoiceLineEditor = ReturnType<typeof InvoiceLineEditor>;
7
- export default InvoiceLineEditor;
8
- //# sourceMappingURL=InvoiceLineEditor.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"InvoiceLineEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,mFAAmF;AACnF,MAAM,WAAW,KAAM,SAAQ,sBAAsB;CAAG;AAuFxD,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -1,6 +0,0 @@
1
- import type { InvoiceFieldNames, InvoiceLineFieldNames } from './types.js';
2
- /** Stable native header field defaults. */
3
- export declare const invoiceFieldNames: InvoiceFieldNames;
4
- /** Stable repeated native line field defaults. */
5
- export declare const invoiceLineFieldNames: InvoiceLineFieldNames;
6
- //# sourceMappingURL=fields.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC3E,2CAA2C;AAC3C,eAAO,MAAM,iBAAiB,EAAE,iBAQ/B,CAAC;AACF,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,EAAE,qBAUnC,CAAC"}
@@ -1,22 +0,0 @@
1
- /** Stable native header field defaults. */
2
- export const invoiceFieldNames = {
3
- customerId: 'customerId',
4
- issuedOn: 'issuedOn',
5
- dueOn: 'dueOn',
6
- currency: 'currency',
7
- paymentTerms: 'paymentTerms',
8
- taxRate: 'invoiceTaxRate',
9
- intent: 'intent',
10
- };
11
- /** Stable repeated native line field defaults. */
12
- export const invoiceLineFieldNames = {
13
- key: 'lineKey',
14
- description: 'lineDescription',
15
- sku: 'lineSku',
16
- quantity: 'lineQuantity',
17
- unitPrice: 'lineUnitPrice',
18
- discountType: 'lineDiscountType',
19
- discountValue: 'lineDiscountValue',
20
- taxMode: 'lineTaxMode',
21
- taxRate: 'lineTaxRate',
22
- };
@@ -1,48 +0,0 @@
1
- import type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';
2
- import type { TaxCode } from '../models/TaxCode.js';
3
- export interface LegacyTaxMapReport {
4
- tenantId: string;
5
- createdComponents: number;
6
- createdCodes: number;
7
- missingLiabilityAccountComponentIds: string[];
8
- /** Every legacy source rate and the durable tax code selected for it. */
9
- mappings: Array<{
10
- sourceRate: string;
11
- taxCodeId: string;
12
- taxComponentId: string | null;
13
- }>;
14
- /** Invalid legacy rates are retained through `legacy-unreconciled`. */
15
- unreconciledRates: string[];
16
- }
17
- export interface MapLegacyTaxRatesOptions {
18
- /** Limit a resumable migration to one tenant. Omit only for a system admin run. */
19
- tenantId?: string;
20
- }
21
- export interface LegacyTaxRateRow {
22
- tenantId?: string;
23
- tenant_id?: string;
24
- taxRate?: unknown;
25
- tax_rate?: unknown;
26
- customerTaxExempt?: unknown;
27
- customer_tax_exempt?: unknown;
28
- }
29
- /**
30
- * Ensure the three non-taxable codes every tenant needs. Repeating the call is
31
- * safe; callers must use a transaction-capable database so the check/create
32
- * sequence is atomic with their surrounding migration.
33
- */
34
- export declare function ensureTenantTaxCodes(db: DatabaseInterface, tenantId: string): Promise<TaxCode[]>;
35
- /**
36
- * Map old fractional line tax rates to rate components and one-component tax
37
- * codes. A historical zero means no tax was applied, therefore it maps to
38
- * out-of-scope unless the linked customer was explicitly exempt.
39
- */
40
- export declare function mapLegacyTaxRates(db: DatabaseInterface, options?: MapLegacyTaxRatesOptions): Promise<LegacyTaxMapReport[]>;
41
- /**
42
- * Executor-affine mapping entry point for a caller that already owns an open
43
- * transaction. It never opens or commits a nested transaction. `tenantRows`
44
- * are supplied by the wrapper after its source read so each tenant remains
45
- * isolated and a failed mapping rolls back as one unit.
46
- */
47
- export declare function mapLegacyTaxRatesInTransaction(tx: DatabaseInterface, tenantId: string, tenantRows: readonly LegacyTaxRateRow[]): Promise<LegacyTaxMapReport>;
48
- //# sourceMappingURL=legacy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"legacy.d.ts","sourceRoot":"","sources":["../../src/tax/legacy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAI7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGpD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,mCAAmC,EAAE,MAAM,EAAE,CAAC;IAC9C,yEAAyE;IACzE,QAAQ,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC,CAAC;IACH,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAgCD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,EAAE,CAAC,CAsEpB;AAmCD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,iBAAiB,EACrB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkB/B;AAED;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,SAAS,gBAAgB,EAAE,GACtC,OAAO,CAAC,kBAAkB,CAAC,CAyF7B"}