@happyvertical/smrt-commerce 0.57.6 → 0.57.7

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 (30) hide show
  1. package/dist/chunks/{ContractLineItemCollection-D5hBzrct.js → ContractLineItemCollection-DQZqRiZi.js} +4 -4
  2. package/dist/chunks/{ContractLineItemCollection-D5hBzrct.js.map → ContractLineItemCollection-DQZqRiZi.js.map} +1 -1
  3. package/dist/chunks/{DocumentTaxCollection-C6cI5U89.js → DocumentTaxCollection-Dg942m_Z.js} +13 -10
  4. package/dist/chunks/DocumentTaxCollection-Dg942m_Z.js.map +1 -0
  5. package/dist/chunks/{InvoiceCollection-BolwughC.js → InvoiceCollection-XPcPyibo.js} +13 -10
  6. package/dist/chunks/InvoiceCollection-XPcPyibo.js.map +1 -0
  7. package/dist/chunks/{TaxCodeCollection-CW-tWjkU.js → TaxCodeCollection-CbTBWO8z.js} +2 -2
  8. package/dist/chunks/{TaxCodeCollection-CW-tWjkU.js.map → TaxCodeCollection-CbTBWO8z.js.map} +1 -1
  9. package/dist/chunks/{TaxComponentCollection-BiJ9aAe8.js → TaxComponentCollection-VZ_cGr2n.js} +3 -3
  10. package/dist/chunks/{TaxComponentCollection-BiJ9aAe8.js.map → TaxComponentCollection-VZ_cGr2n.js.map} +1 -1
  11. package/dist/chunks/{allocation-integrity-CH-i-hsY.js → allocation-integrity-Kr8uceRx.js} +6 -6
  12. package/dist/chunks/{allocation-integrity-CH-i-hsY.js.map → allocation-integrity-Kr8uceRx.js.map} +1 -1
  13. package/dist/chunks/{contract-money-BMsq4cpL.js → contract-money-DGNqQwf0.js} +6 -6
  14. package/dist/chunks/{contract-money-BMsq4cpL.js.map → contract-money-DGNqQwf0.js.map} +1 -1
  15. package/dist/chunks/{invoice-posting-IXic4UEP.js → invoice-posting-DVSkfSUm.js} +3 -3
  16. package/dist/chunks/{invoice-posting-IXic4UEP.js.map → invoice-posting-DVSkfSUm.js.map} +1 -1
  17. package/dist/chunks/{retention-accounting-c7kY7gFU.js → retention-accounting-Ct5Df6xx.js} +8 -8
  18. package/dist/chunks/{retention-accounting-c7kY7gFU.js.map → retention-accounting-Ct5Df6xx.js.map} +1 -1
  19. package/dist/chunks/{tenant-money-barrier-DEVLrpaB.js → tenant-money-barrier-CG78Xxja.js} +3 -3
  20. package/dist/chunks/{tenant-money-barrier-DEVLrpaB.js.map → tenant-money-barrier-CG78Xxja.js.map} +1 -1
  21. package/dist/index.js +12 -12
  22. package/dist/index.js.map +1 -1
  23. package/dist/invoices.js +1 -1
  24. package/dist/manifest.json +57 -15
  25. package/dist/models/Contract.d.ts.map +1 -1
  26. package/dist/models/Invoice.d.ts.map +1 -1
  27. package/dist/smrt-knowledge.json +3 -3
  28. package/package.json +13 -13
  29. package/dist/chunks/DocumentTaxCollection-C6cI5U89.js.map +0 -1
  30. package/dist/chunks/InvoiceCollection-BolwughC.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"contract-money-BMsq4cpL.js","names":[],"sources":["../../src/contract-money.ts"],"sourcesContent":["/** Transactional Contract money aggregate persistence. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ContractCollection } from './collections/ContractCollection.js';\nimport { ContractLineItemCollection } from './collections/ContractLineItemCollection.js';\nimport { CustomerCollection } from './collections/CustomerCollection.js';\nimport {\n DocumentTaxCollection,\n type DocumentTaxWriteInput,\n} from './collections/DocumentTaxCollection.js';\nimport { TaxCodeCollection } from './collections/TaxCodeCollection.js';\nimport { TaxComponentCollection } from './collections/TaxComponentCollection.js';\nimport { VendorCollection } from './collections/VendorCollection.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n lockDocumentMoneyParents,\n} from './document-money-internal.js';\nimport {\n type Contract,\n classifyContractPurchase,\n isContractSnapshotReportEligible,\n} from './models/Contract.js';\nimport type { ContractLineItem } from './models/ContractLineItem.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport {\n calculateMoneyDocument,\n MoneyValidationError,\n resolveLineTaxCode,\n} from './money.js';\nimport { getReceivedPurchaseMoney } from './received-purchase-money.js';\nimport { withTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n ContractStatus,\n type ContractTaxComponentSnapshot,\n} from './types/index.js';\n\ntype DeleteOptions = { expectedUpdatedAt?: Date | string };\ntype ProductTaxDefaults = {\n tenantId: string | null;\n defaultSalesTaxCodeId: string;\n defaultPurchaseTaxCodeId: string;\n};\n\nconst FROZEN_CONTRACT_FIELDS = [\n 'tenantId',\n 'moneyAuthority',\n 'receivedPurchaseMoneyJson',\n 'subtotal',\n 'discountTotal',\n 'taxAmount',\n 'totalAmount',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'sellerTaxRegistrationsJson',\n 'partyTaxId',\n 'moneySnapshotId',\n 'reference',\n 'issueDate',\n] as const;\nconst FROZEN_LINE_FIELDS = [\n 'tenantId',\n 'contractId',\n 'description',\n 'productId',\n 'sku',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'taxAmount',\n 'taxRate',\n 'amount',\n 'startDate',\n 'endDate',\n 'billingPeriod',\n 'sortOrder',\n 'moneySnapshotId',\n] as const;\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} requires an ID`,\n );\n return value;\n}\n/** Frozen-field identity: an absent optional reference (`''`) equals a stored NULL. */\nfunction same(a: unknown, b: unknown): boolean {\n const value = (item: unknown) =>\n item instanceof Date\n ? item.toISOString()\n : item === '' || item === undefined\n ? null\n : item;\n return value(a) === value(b);\n}\nfunction assertUnchanged<T extends object>(\n proposed: T,\n stored: T,\n fields: readonly (keyof T)[],\n label: string,\n): void {\n for (const field of fields)\n if (!same(proposed[field], stored[field]))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `${label} ${String(field)} is frozen`,\n );\n}\nfunction isFrozen(contract: Contract): boolean {\n return contract.status !== ContractStatus.DRAFT || !!contract.moneySnapshotId;\n}\n\nexport { classifyContractPurchase, isContractSnapshotReportEligible };\n\nfunction direction(contract: Contract): 'sales' | 'purchases' {\n return classifyContractPurchase(contract) ? 'purchases' : 'sales';\n}\nfunction discount(line: ContractLineItem) {\n if (line.discountType === 'percent')\n return { kind: 'percent' as const, rate: String(line.discountRate) };\n if (line.discountType === 'amount')\n return { kind: 'amount' as const, minor: line.discount };\n return { kind: 'none' as const };\n}\n\nasync function loadContract(\n db: DatabaseInterface,\n id: string,\n): Promise<Contract | null> {\n return (await ContractCollection.create({ db })).get(\n { id },\n { cache: false },\n );\n}\nasync function withLockedContract<T>(\n db: DatabaseInterface,\n tenantId: string,\n contractId: string,\n operation: (contract: Contract, lines: ContractLineItem[]) => Promise<T>,\n): Promise<T> {\n const contracts = await ContractCollection.create({ db });\n await lockDocumentMoneyParents([\n { id: contractId, tenantId, collection: contracts },\n ]);\n const contract = await contracts.get({ id: contractId }, { cache: false });\n if (!contract || contract.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Contract was not found in its tenant',\n );\n const lines = await (\n await ContractLineItemCollection.create({ db })\n ).findByContract(contractId);\n return operation(contract, lines);\n}\n\nasync function recompute(\n db: DatabaseInterface,\n contract: Contract,\n lines: ContractLineItem[],\n settings: TaxSettings,\n persist: boolean,\n snapshotId = '',\n): Promise<DocumentTaxWriteInput[]> {\n const tenantId = required(contract.tenantId, 'Contract tenant');\n const codes = await (await TaxCodeCollection.create({ db })).list({\n where: { tenantId },\n });\n const components = await (await TaxComponentCollection.create({ db })).list({\n where: { tenantId },\n });\n const byCode = Object.fromEntries(\n codes.flatMap((code) => {\n if (!code.id) return [];\n return [\n [\n code.id,\n {\n id: code.id,\n kind: code.kind,\n active: code.active,\n appliesTo: code.appliesTo,\n components: code.components.map((part) => ({\n taxComponentId: part.taxComponentId,\n percent:\n components.find(\n (component) => component.id === part.taxComponentId,\n )?.percent ?? '0',\n compound: part.compound,\n })),\n },\n ],\n ] as const;\n }),\n );\n const codeById = new Map(\n codes.flatMap((code) => (code.id ? ([[code.id, code]] as const) : [])),\n );\n // Product owns catalog reads. Load defaults on the aggregate transaction so\n // Contract follows the same line > product > party > tenant precedence as\n // Invoice without adding a static package edge.\n const productIds = [\n ...new Set(\n lines\n .map((line) => line.productId)\n .filter((productId): productId is string => !!productId),\n ),\n ];\n const productById = new Map<string, ProductTaxDefaults>();\n if (productIds.length > 0) {\n const { ProductCollection } = await import(\n '@happyvertical/smrt-products/collections'\n );\n const products = await ProductCollection.create({ db });\n for (const productId of productIds) {\n const product = await products.get({ id: productId }, { cache: false });\n if (!product)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} was not found`,\n );\n if (product.tenantId !== null && product.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} must be global or belong to its tenant`,\n );\n productById.set(productId, product);\n }\n }\n const customer =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : null;\n const vendor =\n 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n const resolved = new Map<string, string | null>();\n const result = calculateMoneyDocument({\n currency: contract.currency,\n lineAmountType: contract.lineAmountType,\n roundingScope: contract.taxRoundingScope,\n lines: lines.map((line) => {\n const selected = resolveLineTaxCode(\n {\n lineAmountType: contract.lineAmountType,\n lineOverride: line.taxMode === 'override' ? line.taxCodeId : null,\n },\n {\n direction: direction(contract),\n codes: byCode,\n customerTaxExempt: customer?.taxExempt ?? false,\n exemptCodeId:\n codes.find((code) => code.kind === 'exempt')?.id ?? null,\n productCodeId: line.productId\n ? direction(contract) === 'sales'\n ? productById.get(line.productId)?.defaultSalesTaxCodeId || null\n : productById.get(line.productId)?.defaultPurchaseTaxCodeId ||\n null\n : null,\n partyCodeId:\n customer?.defaultTaxCodeId ?? vendor?.defaultTaxCodeId ?? null,\n appDefaultCodeId:\n direction(contract) === 'sales'\n ? settings.defaultSalesTaxCodeId\n : settings.defaultPurchaseTaxCodeId,\n outOfScopeCodeId:\n codes.find((code) => code.kind === 'out-of-scope')?.id ?? null,\n },\n );\n resolved.set(\n required(line.id, 'Contract line'),\n selected.taxCode?.id ?? null,\n );\n return {\n id: required(line.id, 'Contract line'),\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n discount: discount(line),\n taxCode: selected.taxCode,\n };\n }),\n });\n const resultById = new Map(result.lines.map((row) => [row.id, row]));\n for (const line of lines) {\n const lineId = required(line.id, 'Contract line');\n const row = resultById.get(lineId);\n if (!row)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Calculated Contract line ${lineId} was not loaded`,\n );\n const codeId = resolved.get(lineId) ?? null;\n const code = codeId ? (codeById.get(codeId) ?? null) : null;\n line.discount = row.discountMinor;\n line.amount = row.amountMinor;\n line.taxAmount = row.taxMinor;\n line.taxCodeId = code?.id ?? null;\n line.taxCodeCode = code?.code ?? null;\n line.taxCodeName = code?.name ?? null;\n line.taxComponents = row.components.map(\n (part): ContractTaxComponentSnapshot => {\n const component = components.find(\n (item) => item.id === part.taxComponentId,\n );\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax component ${part.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: part.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: part.taxMinor,\n ...(part.compound\n ? { compound: true, taxableMinor: part.taxableMinor }\n : {}),\n };\n },\n );\n line.taxRate = row.components.some((part) => part.compound)\n ? row.netMinor === 0\n ? 0\n : row.taxMinor / row.netMinor\n : line.taxComponents.reduce(\n (sum, part) => sum + Number(part.percent) / 100,\n 0,\n );\n if (snapshotId) line.moneySnapshotId = snapshotId;\n if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const taxRows = result.taxes.map((tax): DocumentTaxWriteInput => {\n const code = tax.taxCodeId ? (codeById.get(tax.taxCodeId) ?? null) : null;\n const component = tax.taxComponentId\n ? (components.find((item) => item.id === tax.taxComponentId) ?? null)\n : null;\n return {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n currency: contract.currency,\n taxCodeId: code?.id ?? null,\n taxCodeCode: code?.code ?? null,\n kind: code?.kind ?? null,\n taxComponentId: component?.id ?? null,\n taxComponentName: component?.name ?? null,\n percent: component?.percent ?? null,\n liabilityAccountId: component?.liabilityAccountId ?? null,\n taxableMinor: tax.taxableMinor,\n netMinor: tax.netMinor ?? null,\n taxMinor: tax.taxMinor,\n recoverableMinor:\n direction(contract) === 'purchases' && component?.recoverable\n ? tax.taxMinor\n : 0,\n };\n });\n contract.subtotal = result.subtotalMinor;\n contract.discountTotal = result.discountTotalMinor;\n contract.taxAmount = result.taxMinor;\n contract.totalAmount = result.totalMinor;\n if (persist)\n await (\n await DocumentTaxCollection.create({ db })\n ).replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n rows: taxRows,\n });\n return taxRows;\n}\n\n/** Every money document belongs to a tenant. */\nfunction requireContractTenant(tenantId: string | null): string {\n if (!tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Commerce money documents require a tenant',\n );\n return tenantId;\n}\n\n/** Save a Contract and recompute its money aggregate. */\nexport async function saveContract(input: Contract): Promise<Contract> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (contract) => {\n const settings = scope.settings;\n const existing = contract.id\n ? await loadContract(scope.db, contract.id)\n : null;\n if (\n contract.billingScheduleJson !== (existing?.billingScheduleJson ?? '')\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Billing schedule is managed by schedule commands',\n );\n if (existing && existing.tenantId !== scope.tenantId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract tenant identity cannot change',\n );\n if (!existing) {\n if (\n contract.moneyAuthority !== 'calculated' ||\n contract.receivedPurchaseMoneyJson\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Use recordReceivedPurchaseMoney or recordReceivedSalesContractMoney to record received evidence',\n );\n contract.tenantId = tenantId;\n contract.lineAmountType = settings.defaultLineAmountType;\n contract.taxRoundingScope = settings.taxRoundingScope;\n // A new Contract has no lines yet: its totals start at zero and are\n // derived from lines on every later save, never taken from the caller.\n contract.subtotal = 0;\n contract.discountTotal = 0;\n contract.taxAmount = 0;\n contract.totalAmount = 0;\n if (contract.status !== ContractStatus.DRAFT) {\n contract.moneySnapshotId = crypto.randomUUID();\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n return withLockedContract(\n scope.db,\n tenantId,\n required(existing.id, 'Contract'),\n async (stored, lines) => {\n assertUnchanged(\n contract,\n stored,\n ['moneyAuthority', 'receivedPurchaseMoneyJson'],\n 'Contract money authority',\n );\n const received = getReceivedPurchaseMoney(stored);\n if (received) {\n if (\n contract.constructor !== stored.constructor ||\n contract.contractType !== stored.contractType\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase document type is frozen',\n );\n assertUnchanged(\n contract,\n stored,\n [\n ...FROZEN_CONTRACT_FIELDS,\n 'vendorId' as keyof Contract,\n 'customerId' as keyof Contract,\n ],\n 'Received purchase money',\n );\n if (lines.length)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Received money cannot mix with calculated lines',\n );\n if (\n contract.status !== ContractStatus.DRAFT &&\n !stored.moneySnapshotId\n )\n contract.moneySnapshotId = crypto.randomUUID();\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n if (isFrozen(stored)) {\n assertUnchanged(\n contract,\n stored,\n FROZEN_CONTRACT_FIELDS,\n 'Contract',\n );\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const freezing = contract.status !== ContractStatus.DRAFT;\n const snapshotId = freezing\n ? stored.moneySnapshotId || crypto.randomUUID()\n : '';\n await recompute(\n scope.db,\n contract,\n lines,\n settings,\n true,\n snapshotId,\n );\n if (freezing) {\n const party =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db: scope.db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db: scope.db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n contract.partyTaxId = party?.taxId ?? '';\n contract.moneySnapshotId = snapshotId;\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Save a line and atomically recompute its Contract aggregate. */\nexport async function saveContractLineItem(\n input: ContractLineItem,\n): Promise<ContractLineItem> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n const contractId = persisted?.contractId ?? line.contractId;\n const settings = scope.settings;\n return withLockedContract(\n scope.db,\n scope.tenantId,\n contractId,\n async (contract, lines) => {\n if (getReceivedPurchaseMoney(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase evidence cannot contain calculated lines',\n );\n if (persisted && line.contractId !== persisted.contractId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract line cannot move between parents',\n );\n if (isFrozen(contract)) {\n if (!persisted)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot add a line to a frozen Contract',\n );\n assertUnchanged(\n line,\n persisted,\n FROZEN_LINE_FIELDS,\n 'Contract line',\n );\n return line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n line.contractId = contractId;\n line.tenantId = scope.tenantId;\n await line.saveDerived(DOCUMENT_MONEY_WRITE);\n const all = [\n ...lines.filter((candidate) => candidate.id !== line.id),\n line,\n ];\n await recompute(scope.db, contract, all, settings, true);\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n return line;\n },\n );\n }),\n );\n}\n\nexport async function deleteContractLineItem(\n input: ContractLineItem,\n options: DeleteOptions,\n): Promise<void> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n if (!persisted) return;\n const settings = scope.settings;\n await withLockedContract(\n scope.db,\n scope.tenantId,\n persisted.contractId,\n async (contract, lines) => {\n if (isFrozen(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a line from a frozen Contract',\n );\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n await recompute(\n scope.db,\n contract,\n lines.filter((candidate) => candidate.id !== persisted.id),\n settings,\n true,\n );\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Recompute a Contract through its normal runtime gate. */\nexport async function recalculateContractTotals(\n contract: Contract,\n): Promise<void> {\n await saveContract(contract);\n}\n\n/** Delete only a calculated Contract under the same lock as received-fact recording. */\nexport async function deleteContract(\n input: Contract,\n options: DeleteOptions,\n): Promise<void> {\n const contractId = input.id;\n if (!contractId) return;\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async ({ db }) => {\n const existing = await loadContract(db, contractId);\n if (!existing) return;\n await withLockedContract(db, tenantId, contractId, async (stored) => {\n if (stored.billingScheduleJson)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract billing schedule history cannot be deleted',\n );\n if (getReceivedPurchaseMoney(stored))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase monetary history cannot be deleted',\n );\n await input.withDatabase(db, (bound) =>\n bound.deleteDerived(DOCUMENT_MONEY_WRITE, options),\n );\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"contract-money-DGNqQwf0.js","names":[],"sources":["../../src/contract-money.ts"],"sourcesContent":["/** Transactional Contract money aggregate persistence. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ContractCollection } from './collections/ContractCollection.js';\nimport { ContractLineItemCollection } from './collections/ContractLineItemCollection.js';\nimport { CustomerCollection } from './collections/CustomerCollection.js';\nimport {\n DocumentTaxCollection,\n type DocumentTaxWriteInput,\n} from './collections/DocumentTaxCollection.js';\nimport { TaxCodeCollection } from './collections/TaxCodeCollection.js';\nimport { TaxComponentCollection } from './collections/TaxComponentCollection.js';\nimport { VendorCollection } from './collections/VendorCollection.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n lockDocumentMoneyParents,\n} from './document-money-internal.js';\nimport {\n type Contract,\n classifyContractPurchase,\n isContractSnapshotReportEligible,\n} from './models/Contract.js';\nimport type { ContractLineItem } from './models/ContractLineItem.js';\nimport type { TaxSettings } from './models/TaxSettings.js';\nimport {\n calculateMoneyDocument,\n MoneyValidationError,\n resolveLineTaxCode,\n} from './money.js';\nimport { getReceivedPurchaseMoney } from './received-purchase-money.js';\nimport { withTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n ContractStatus,\n type ContractTaxComponentSnapshot,\n} from './types/index.js';\n\ntype DeleteOptions = { expectedUpdatedAt?: Date | string };\ntype ProductTaxDefaults = {\n tenantId: string | null;\n defaultSalesTaxCodeId: string;\n defaultPurchaseTaxCodeId: string;\n};\n\nconst FROZEN_CONTRACT_FIELDS = [\n 'tenantId',\n 'moneyAuthority',\n 'receivedPurchaseMoneyJson',\n 'subtotal',\n 'discountTotal',\n 'taxAmount',\n 'totalAmount',\n 'currency',\n 'lineAmountType',\n 'taxRoundingScope',\n 'sellerTaxRegistrationsJson',\n 'partyTaxId',\n 'moneySnapshotId',\n 'reference',\n 'issueDate',\n] as const;\nconst FROZEN_LINE_FIELDS = [\n 'tenantId',\n 'contractId',\n 'description',\n 'productId',\n 'sku',\n 'quantity',\n 'unitPrice',\n 'discountType',\n 'discountRate',\n 'discount',\n 'taxMode',\n 'taxCodeId',\n 'taxCodeCode',\n 'taxCodeName',\n 'taxComponentsJson',\n 'taxAmount',\n 'taxRate',\n 'amount',\n 'startDate',\n 'endDate',\n 'billingPeriod',\n 'sortOrder',\n 'moneySnapshotId',\n] as const;\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} requires an ID`,\n );\n return value;\n}\n/** Frozen-field identity: an absent optional reference (`''`) equals a stored NULL. */\nfunction same(a: unknown, b: unknown): boolean {\n const value = (item: unknown) =>\n item instanceof Date\n ? item.toISOString()\n : item === '' || item === undefined\n ? null\n : item;\n return value(a) === value(b);\n}\nfunction assertUnchanged<T extends object>(\n proposed: T,\n stored: T,\n fields: readonly (keyof T)[],\n label: string,\n): void {\n for (const field of fields)\n if (!same(proposed[field], stored[field]))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `${label} ${String(field)} is frozen`,\n );\n}\nfunction isFrozen(contract: Contract): boolean {\n return contract.status !== ContractStatus.DRAFT || !!contract.moneySnapshotId;\n}\n\nexport { classifyContractPurchase, isContractSnapshotReportEligible };\n\nfunction direction(contract: Contract): 'sales' | 'purchases' {\n return classifyContractPurchase(contract) ? 'purchases' : 'sales';\n}\nfunction discount(line: ContractLineItem) {\n if (line.discountType === 'percent')\n return { kind: 'percent' as const, rate: String(line.discountRate) };\n if (line.discountType === 'amount')\n return { kind: 'amount' as const, minor: line.discount };\n return { kind: 'none' as const };\n}\n\nasync function loadContract(\n db: DatabaseInterface,\n id: string,\n): Promise<Contract | null> {\n return (await ContractCollection.create({ db })).get(\n { id },\n { cache: false },\n );\n}\nasync function withLockedContract<T>(\n db: DatabaseInterface,\n tenantId: string,\n contractId: string,\n operation: (contract: Contract, lines: ContractLineItem[]) => Promise<T>,\n): Promise<T> {\n const contracts = await ContractCollection.create({ db });\n await lockDocumentMoneyParents([\n { id: contractId, tenantId, collection: contracts },\n ]);\n const contract = await contracts.get({ id: contractId }, { cache: false });\n if (!contract || contract.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Contract was not found in its tenant',\n );\n const lines = await (\n await ContractLineItemCollection.create({ db })\n ).findByContract(contractId);\n return operation(contract, lines);\n}\n\nasync function recompute(\n db: DatabaseInterface,\n contract: Contract,\n lines: ContractLineItem[],\n settings: TaxSettings,\n persist: boolean,\n snapshotId = '',\n): Promise<DocumentTaxWriteInput[]> {\n const tenantId = required(contract.tenantId, 'Contract tenant');\n const codes = await (await TaxCodeCollection.create({ db })).list({\n where: { tenantId },\n });\n const components = await (await TaxComponentCollection.create({ db })).list({\n where: { tenantId },\n });\n const byCode = Object.fromEntries(\n codes.flatMap((code) => {\n if (!code.id) return [];\n return [\n [\n code.id,\n {\n id: code.id,\n kind: code.kind,\n active: code.active,\n appliesTo: code.appliesTo,\n components: code.components.map((part) => ({\n taxComponentId: part.taxComponentId,\n percent:\n components.find(\n (component) => component.id === part.taxComponentId,\n )?.percent ?? '0',\n compound: part.compound,\n })),\n },\n ],\n ] as const;\n }),\n );\n const codeById = new Map(\n codes.flatMap((code) => (code.id ? ([[code.id, code]] as const) : [])),\n );\n // Product owns catalog reads. Load defaults on the aggregate transaction so\n // Contract follows the same line > product > party > tenant precedence as\n // Invoice without adding a static package edge.\n const productIds = [\n ...new Set(\n lines\n .map((line) => line.productId)\n .filter((productId): productId is string => !!productId),\n ),\n ];\n const productById = new Map<string, ProductTaxDefaults>();\n if (productIds.length > 0) {\n const { ProductCollection } = await import(\n '@happyvertical/smrt-products/collections'\n );\n const products = await ProductCollection.create({ db });\n for (const productId of productIds) {\n const product = await products.get({ id: productId }, { cache: false });\n if (!product)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} was not found`,\n );\n if (product.tenantId !== null && product.tenantId !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Contract product ${productId} must be global or belong to its tenant`,\n );\n productById.set(productId, product);\n }\n }\n const customer =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : null;\n const vendor =\n 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n const resolved = new Map<string, string | null>();\n const result = calculateMoneyDocument({\n currency: contract.currency,\n lineAmountType: contract.lineAmountType,\n roundingScope: contract.taxRoundingScope,\n lines: lines.map((line) => {\n const selected = resolveLineTaxCode(\n {\n lineAmountType: contract.lineAmountType,\n lineOverride: line.taxMode === 'override' ? line.taxCodeId : null,\n },\n {\n direction: direction(contract),\n codes: byCode,\n customerTaxExempt: customer?.taxExempt ?? false,\n exemptCodeId:\n codes.find((code) => code.kind === 'exempt')?.id ?? null,\n productCodeId: line.productId\n ? direction(contract) === 'sales'\n ? productById.get(line.productId)?.defaultSalesTaxCodeId || null\n : productById.get(line.productId)?.defaultPurchaseTaxCodeId ||\n null\n : null,\n partyCodeId:\n customer?.defaultTaxCodeId ?? vendor?.defaultTaxCodeId ?? null,\n appDefaultCodeId:\n direction(contract) === 'sales'\n ? settings.defaultSalesTaxCodeId\n : settings.defaultPurchaseTaxCodeId,\n outOfScopeCodeId:\n codes.find((code) => code.kind === 'out-of-scope')?.id ?? null,\n },\n );\n resolved.set(\n required(line.id, 'Contract line'),\n selected.taxCode?.id ?? null,\n );\n return {\n id: required(line.id, 'Contract line'),\n quantity: line.quantity,\n unitPrice: line.unitPrice,\n discount: discount(line),\n taxCode: selected.taxCode,\n };\n }),\n });\n const resultById = new Map(result.lines.map((row) => [row.id, row]));\n for (const line of lines) {\n const lineId = required(line.id, 'Contract line');\n const row = resultById.get(lineId);\n if (!row)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Calculated Contract line ${lineId} was not loaded`,\n );\n const codeId = resolved.get(lineId) ?? null;\n const code = codeId ? (codeById.get(codeId) ?? null) : null;\n line.discount = row.discountMinor;\n line.amount = row.amountMinor;\n line.taxAmount = row.taxMinor;\n line.taxCodeId = code?.id ?? null;\n line.taxCodeCode = code?.code ?? null;\n line.taxCodeName = code?.name ?? null;\n line.taxComponents = row.components.map(\n (part): ContractTaxComponentSnapshot => {\n const component = components.find(\n (item) => item.id === part.taxComponentId,\n );\n if (!component)\n throw new MoneyValidationError(\n 'TAX_CODE_NOT_FOUND',\n `Tax component ${part.taxComponentId} was not loaded`,\n );\n return {\n taxComponentId: part.taxComponentId,\n name: component.name,\n percent: component.percent,\n taxMinor: part.taxMinor,\n ...(part.compound\n ? { compound: true, taxableMinor: part.taxableMinor }\n : {}),\n };\n },\n );\n line.taxRate = row.components.some((part) => part.compound)\n ? row.netMinor === 0\n ? 0\n : row.taxMinor / row.netMinor\n : line.taxComponents.reduce(\n (sum, part) => sum + Number(part.percent) / 100,\n 0,\n );\n if (snapshotId) line.moneySnapshotId = snapshotId;\n if (persist) await line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const taxRows = result.taxes.map((tax): DocumentTaxWriteInput => {\n const code = tax.taxCodeId ? (codeById.get(tax.taxCodeId) ?? null) : null;\n const component = tax.taxComponentId\n ? (components.find((item) => item.id === tax.taxComponentId) ?? null)\n : null;\n return {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n currency: contract.currency,\n taxCodeId: code?.id ?? null,\n taxCodeCode: code?.code ?? null,\n kind: code?.kind ?? null,\n taxComponentId: component?.id ?? null,\n taxComponentName: component?.name ?? null,\n percent: component?.percent ?? null,\n liabilityAccountId: component?.liabilityAccountId ?? null,\n taxableMinor: tax.taxableMinor,\n netMinor: tax.netMinor ?? null,\n taxMinor: tax.taxMinor,\n recoverableMinor:\n direction(contract) === 'purchases' && component?.recoverable\n ? tax.taxMinor\n : 0,\n };\n });\n contract.subtotal = result.subtotalMinor;\n contract.discountTotal = result.discountTotalMinor;\n contract.taxAmount = result.taxMinor;\n contract.totalAmount = result.totalMinor;\n if (persist)\n await (\n await DocumentTaxCollection.create({ db })\n ).replaceDerivedForDocument(DOCUMENT_MONEY_WRITE, {\n tenantId,\n documentType: 'contract',\n documentId: required(contract.id, 'Contract'),\n rows: taxRows,\n });\n return taxRows;\n}\n\n/** Every money document belongs to a tenant. */\nfunction requireContractTenant(tenantId: string | null): string {\n if (!tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Commerce money documents require a tenant',\n );\n return tenantId;\n}\n\n/** Save a Contract and recompute its money aggregate. */\nexport async function saveContract(input: Contract): Promise<Contract> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (contract) => {\n const settings = scope.settings;\n const existing = contract.id\n ? await loadContract(scope.db, contract.id)\n : null;\n if (\n contract.billingScheduleJson !== (existing?.billingScheduleJson ?? '')\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Billing schedule is managed by schedule commands',\n );\n if (existing && existing.tenantId !== scope.tenantId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract tenant identity cannot change',\n );\n if (!existing) {\n if (\n contract.moneyAuthority !== 'calculated' ||\n contract.receivedPurchaseMoneyJson\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Use recordReceivedPurchaseMoney or recordReceivedSalesContractMoney to record received evidence',\n );\n contract.tenantId = tenantId;\n contract.lineAmountType = settings.defaultLineAmountType;\n contract.taxRoundingScope = settings.taxRoundingScope;\n // A new Contract has no lines yet: its totals start at zero and are\n // derived from lines on every later save, never taken from the caller.\n contract.subtotal = 0;\n contract.discountTotal = 0;\n contract.taxAmount = 0;\n contract.totalAmount = 0;\n if (contract.status !== ContractStatus.DRAFT) {\n contract.moneySnapshotId = crypto.randomUUID();\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n return withLockedContract(\n scope.db,\n tenantId,\n required(existing.id, 'Contract'),\n async (stored, lines) => {\n assertUnchanged(\n contract,\n stored,\n ['moneyAuthority', 'receivedPurchaseMoneyJson'],\n 'Contract money authority',\n );\n const received = getReceivedPurchaseMoney(stored);\n if (received) {\n if (\n contract.constructor !== stored.constructor ||\n contract.contractType !== stored.contractType\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase document type is frozen',\n );\n assertUnchanged(\n contract,\n stored,\n [\n ...FROZEN_CONTRACT_FIELDS,\n 'vendorId' as keyof Contract,\n 'customerId' as keyof Contract,\n ],\n 'Received purchase money',\n );\n if (lines.length)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Received money cannot mix with calculated lines',\n );\n if (\n contract.status !== ContractStatus.DRAFT &&\n !stored.moneySnapshotId\n )\n contract.moneySnapshotId = crypto.randomUUID();\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n if (isFrozen(stored)) {\n assertUnchanged(\n contract,\n stored,\n FROZEN_CONTRACT_FIELDS,\n 'Contract',\n );\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n const freezing = contract.status !== ContractStatus.DRAFT;\n const snapshotId = freezing\n ? stored.moneySnapshotId || crypto.randomUUID()\n : '';\n await recompute(\n scope.db,\n contract,\n lines,\n settings,\n true,\n snapshotId,\n );\n if (freezing) {\n const party =\n 'customerId' in contract && contract.customerId\n ? await (await CustomerCollection.create({ db: scope.db })).get(\n { id: contract.customerId },\n { cache: false },\n )\n : 'vendorId' in contract && contract.vendorId\n ? await (await VendorCollection.create({ db: scope.db })).get(\n { id: contract.vendorId },\n { cache: false },\n )\n : null;\n contract.sellerTaxRegistrations = structuredClone(\n settings.taxRegistrations,\n );\n contract.partyTaxId = party?.taxId ?? '';\n contract.moneySnapshotId = snapshotId;\n }\n return contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Save a line and atomically recompute its Contract aggregate. */\nexport async function saveContractLineItem(\n input: ContractLineItem,\n): Promise<ContractLineItem> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n return withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n const contractId = persisted?.contractId ?? line.contractId;\n const settings = scope.settings;\n return withLockedContract(\n scope.db,\n scope.tenantId,\n contractId,\n async (contract, lines) => {\n if (getReceivedPurchaseMoney(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase evidence cannot contain calculated lines',\n );\n if (persisted && line.contractId !== persisted.contractId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract line cannot move between parents',\n );\n if (isFrozen(contract)) {\n if (!persisted)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot add a line to a frozen Contract',\n );\n assertUnchanged(\n line,\n persisted,\n FROZEN_LINE_FIELDS,\n 'Contract line',\n );\n return line.saveDerived(DOCUMENT_MONEY_WRITE);\n }\n line.contractId = contractId;\n line.tenantId = scope.tenantId;\n await line.saveDerived(DOCUMENT_MONEY_WRITE);\n const all = [\n ...lines.filter((candidate) => candidate.id !== line.id),\n line,\n ];\n await recompute(scope.db, contract, all, settings, true);\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n return line;\n },\n );\n }),\n );\n}\n\nexport async function deleteContractLineItem(\n input: ContractLineItem,\n options: DeleteOptions,\n): Promise<void> {\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async (scope) =>\n input.withDatabase(scope.db, async (line) => {\n const persisted = line.id\n ? await (await ContractLineItemCollection.create({ db: scope.db })).get(\n { id: line.id },\n { cache: false },\n )\n : null;\n if (!persisted) return;\n const settings = scope.settings;\n await withLockedContract(\n scope.db,\n scope.tenantId,\n persisted.contractId,\n async (contract, lines) => {\n if (isFrozen(contract))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Cannot delete a line from a frozen Contract',\n );\n await line.deleteDerived(DOCUMENT_MONEY_WRITE, options);\n await recompute(\n scope.db,\n contract,\n lines.filter((candidate) => candidate.id !== persisted.id),\n settings,\n true,\n );\n await contract.saveDerived(DOCUMENT_MONEY_WRITE);\n },\n );\n }),\n );\n}\n\n/** Recompute a Contract through its normal runtime gate. */\nexport async function recalculateContractTotals(\n contract: Contract,\n): Promise<void> {\n await saveContract(contract);\n}\n\n/** Delete only a calculated Contract under the same lock as received-fact recording. */\nexport async function deleteContract(\n input: Contract,\n options: DeleteOptions,\n): Promise<void> {\n const contractId = input.id;\n if (!contractId) return;\n const tenantId = requireContractTenant(\n input.tenantId ?? getTenantId() ?? null,\n );\n await withTenantMoneyRuntime(input.db, tenantId, async ({ db }) => {\n const existing = await loadContract(db, contractId);\n if (!existing) return;\n await withLockedContract(db, tenantId, contractId, async (stored) => {\n if (stored.billingScheduleJson)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Contract billing schedule history cannot be deleted',\n );\n if (getReceivedPurchaseMoney(stored))\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Received purchase monetary history cannot be deleted',\n );\n await input.withDatabase(db, (bound) =>\n bound.deleteDerived(DOCUMENT_MONEY_WRITE, options),\n );\n });\n });\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
- import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-C6cI5U89.js";
1
+ import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-Dg942m_Z.js";
2
2
  import { s as InvoiceStatus } from "./types-Bbm0Xflc.js";
3
3
  import { n as deterministicId } from "./units-8ElbjVZK.js";
4
4
  import { MoneyValidationError } from "../money.js";
5
5
  import { _ as withDocumentMoneyTransaction, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
6
- import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-DEVLrpaB.js";
6
+ import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-CG78Xxja.js";
7
7
  import { t as buildRevenueJournalEntries } from "./revenue-journal-LLA9Kh_i.js";
8
8
  import { getTenantId } from "@happyvertical/smrt-tenancy";
9
9
  import { ISO_4217_MINOR_UNITS } from "@happyvertical/smrt-ui/currency";
@@ -224,4 +224,4 @@ async function cancelInvoiceWithRevenueReversal(input, options) {
224
224
  //#endregion
225
225
  export { cancelInvoiceWithRevenueReversal, rebindJournal, recognizeInvoiceRevenue };
226
226
 
227
- //# sourceMappingURL=invoice-posting-IXic4UEP.js.map
227
+ //# sourceMappingURL=invoice-posting-DVSkfSUm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-posting-IXic4UEP.js","names":[],"sources":["../../src/invoice-posting.ts"],"sourcesContent":["/** Atomic revenue posting and cancellation reversal. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport type { Journal, JournalEntryData } from '@happyvertical/smrt-ledgers';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ISO_4217_MINOR_UNITS } from '@happyvertical/smrt-ui/currency';\nimport { deterministicId } from './billing/units.js';\nimport { DocumentTaxCollection } from './collections/DocumentTaxCollection.js';\nimport { InvoiceLineItemCollection } from './collections/InvoiceLineItemCollection.js';\nimport { lockAndReloadInvoice } from './document-money.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n withDocumentMoneyTransaction,\n} from './document-money-internal.js';\nimport type { Invoice } from './models/Invoice.js';\nimport { MoneyValidationError } from './money.js';\nimport { buildRevenueJournalEntries } from './revenue-journal.js';\nimport { lockTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n type CancelRevenueOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n} from './types/index.js';\n\ntype PostingMetadata = {\n kind:\n | 'invoice-revenue'\n | 'credit-note-revenue'\n | 'invoice-cancellation-reversal'\n | 'credit-note-cancellation-reversal';\n invoiceId: string;\n moneySnapshotId: string;\n originalJournalId?: string;\n};\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} must be a nonempty value`,\n );\n return value;\n}\n\nfunction bookCurrency(value: string | undefined): string {\n if (\n !value ||\n value !== value.toUpperCase() ||\n !ISO_4217_MINOR_UNITS.has(value) ||\n ISO_4217_MINOR_UNITS.get(value) === null\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Revenue posting requires a supported ISO 4217 bookCurrency',\n );\n return value;\n}\n\nfunction assertTenantAuthority(tenantId: string): void {\n if (getTenantId() !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice posting requires its active tenant context',\n );\n}\n\nfunction canonicalEntries(entries: readonly JournalEntryData[]): string {\n return JSON.stringify(\n entries\n .map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit ?? 0,\n credit: entry.credit ?? 0,\n currency: entry.currency ?? '',\n memo: entry.memo ?? '',\n }))\n .sort((left, right) => {\n const leftKey = JSON.stringify(left);\n const rightKey = JSON.stringify(right);\n return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;\n }),\n );\n}\n\nasync function validateAccounts(\n db: DatabaseInterface,\n tenantId: string,\n accountIds: readonly string[],\n): Promise<void> {\n const { AccountCollection } = await import('@happyvertical/smrt-ledgers');\n const accounts = await AccountCollection.create({ db });\n for (const accountId of [...new Set(accountIds)]) {\n const account = await accounts.get({ id: accountId }, { cache: false });\n if (!account || account.tenantId !== tenantId || !account.active)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Posting account ${accountId} must be active and owned by the invoice tenant`,\n );\n }\n}\n\nasync function entriesForJournal(\n db: DatabaseInterface,\n journalId: string,\n): Promise<JournalEntryData[]> {\n const { JournalEntryCollection } = await import(\n '@happyvertical/smrt-ledgers'\n );\n const entries = await (\n await JournalEntryCollection.create({ db })\n ).findByJournal(journalId);\n return entries.map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit,\n credit: entry.credit,\n currency: entry.currency,\n memo: entry.memo,\n }));\n}\n\nfunction metadataMatches(journal: Journal, expected: PostingMetadata): boolean {\n const actual = journal.metadata as Partial<PostingMetadata>;\n return (\n actual.kind === expected.kind &&\n actual.invoiceId === expected.invoiceId &&\n actual.moneySnapshotId === expected.moneySnapshotId &&\n actual.originalJournalId === expected.originalJournalId\n );\n}\n\nasync function createOrReadPostedJournal(\n db: DatabaseInterface,\n input: {\n id: string;\n tenantId: string;\n sourceRef: string;\n description: string;\n date: Date;\n metadata: PostingMetadata;\n entries: readonly JournalEntryData[];\n },\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db });\n const existing = await journals.get({ id: input.id }, { cache: false });\n if (existing) {\n const storedEntries = await entriesForJournal(db, input.id);\n if (\n existing.status !== 'posted' ||\n existing.tenantId !== input.tenantId ||\n existing.sourceModule !== 'smrt-commerce' ||\n existing.sourceRef !== input.sourceRef ||\n !metadataMatches(existing, input.metadata) ||\n canonicalEntries(storedEntries) !== canonicalEntries(input.entries)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `Deterministic journal ${input.id} conflicts with the frozen invoice snapshot`,\n );\n return existing;\n }\n const journal = await journals.create({\n id: input.id,\n tenantId: input.tenantId,\n date: input.date,\n description: input.description,\n sourceModule: 'smrt-commerce',\n sourceRef: input.sourceRef,\n metadata: input.metadata,\n _insertOnly: true,\n });\n for (const entry of input.entries) await journal.addEntry(entry);\n await journal.post();\n if (!journal.id)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Posted revenue journal is missing its identity',\n );\n return journal;\n}\n\n/**\n * Reload a journal posted inside a money transaction on `db`, so the caller\n * gets an instance whose reads do not run on the finished transaction.\n */\nexport async function rebindJournal(\n db: DatabaseInterface,\n journal: Journal,\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n return (\n (await (\n await JournalCollection.create({ db })\n ).get({ id: required(journal.id, 'Journal ID') }, { cache: false })) ??\n journal\n );\n}\n\n/** Post an issued invoice's revenue journal from its frozen money facts. */\nexport async function recognizeInvoiceRevenue(\n input: Invoice,\n options: RecognizeRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.moneyAuthority === 'historical')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Original historical revenue is retained evidence; a new revenue posting requires an explicit reviewed historical accounting contract',\n );\n if (invoice.status === InvoiceStatus.DRAFT || !invoice.moneySnapshotId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue recognition requires an issued frozen invoice',\n );\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n\n const lines = await (\n await InvoiceLineItemCollection.create({ db: tx })\n ).findByInvoice(invoiceId);\n const taxes = await (\n await DocumentTaxCollection.create({ db: tx })\n ).findByDocument(tenantId, invoice.kind, invoiceId);\n const built = buildRevenueJournalEntries(\n {\n id: invoiceId,\n number: invoice.invoiceNumber,\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n subtotalMinor: invoice.subtotal,\n taxMinor: invoice.taxAmount,\n totalMinor: invoice.totalAmount,\n providerTaxMinor: invoice.providerTaxAmount,\n },\n lines.map((line) => ({\n id: required(line.id, 'Invoice line ID'),\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n revenueAccountId: line.revenueAccountId,\n })),\n taxes.map((tax) => ({\n currency: tax.currency,\n taxCodeId: tax.taxCodeId,\n taxCodeCode: tax.taxCodeCode,\n taxComponentId: tax.taxComponentId,\n liabilityAccountId: tax.liabilityAccountId,\n taxMinor: tax.taxMinor,\n })),\n {\n arAccountId: options.arAccountId,\n revenueAccountId: options.revenueAccountId,\n taxAccountId: options.taxAccountId,\n bookCurrency: currency,\n direction: invoice.kind === 'credit_note' ? -1 : 1,\n },\n );\n await validateAccounts(\n tx,\n tenantId,\n built.entries.map((entry) => entry.accountId),\n );\n const postingKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const journalId = await deterministicId([\n postingKind,\n tenantId,\n invoiceId,\n invoice.moneySnapshotId,\n ]);\n const metadata: PostingMetadata = {\n kind: postingKind,\n invoiceId,\n moneySnapshotId: invoice.moneySnapshotId,\n };\n const journal = await createOrReadPostedJournal(tx, {\n id: journalId,\n tenantId,\n sourceRef: `${postingKind}:${invoiceId}:${invoice.moneySnapshotId}`,\n description: `${invoice.kind === 'credit_note' ? 'Credit note' : 'Revenue'}: Invoice ${invoice.invoiceNumber}`,\n date: invoice.issueDate,\n metadata,\n entries: built.entries,\n });\n if (invoice.arJournalId && invoice.arJournalId !== journalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different revenue journal',\n );\n invoice.arJournalId = journalId;\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arJournalId = journalId;\n return journal;\n }),\n );\n}\n\nfunction reverseEntries(\n entries: readonly JournalEntryData[],\n currency: string,\n): JournalEntryData[] {\n return entries.map((entry, index) => {\n if (\n entry.currency !== currency ||\n !Number.isSafeInteger(entry.debit ?? 0) ||\n !Number.isSafeInteger(entry.credit ?? 0) ||\n (entry.debit ?? 0) < 0 ||\n (entry.credit ?? 0) < 0 ||\n (entry.debit ?? 0) > 0 === (entry.credit ?? 0) > 0\n )\n throw new MoneyValidationError(\n entry.currency !== currency ? 'CURRENCY_MISMATCH' : 'UNSAFE_MONEY',\n `Original journal entry ${index + 1} is not an exact ${currency} movement`,\n );\n return {\n accountId: required(entry.accountId, 'Original journal account ID'),\n debit: entry.credit ?? 0,\n credit: entry.debit ?? 0,\n currency,\n memo: `Reversal: ${entry.memo ?? ''}`,\n };\n });\n}\n\n/** Atomically reverse a posted invoice and move it to CANCELLED. */\nexport async function cancelInvoiceWithRevenueReversal(\n input: Invoice,\n options: CancelRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n if (\n (invoice.kind === 'invoice' && invoice.isPaid()) ||\n (invoice.kind === 'credit_note' && invoice.amountPaid > 0)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Paid invoices and refunded credit notes cannot be cancelled',\n );\n if (!invoice.arJournalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue must be posted before using cancellation reversal',\n );\n const snapshotId = required(\n invoice.moneySnapshotId,\n 'Invoice money snapshot ID',\n );\n const originalJournalId = invoice.arJournalId;\n const originalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db: tx });\n const original = await journals.get(\n { id: originalJournalId },\n { cache: false },\n );\n if (\n original?.status !== 'posted' ||\n original.tenantId !== tenantId ||\n original.sourceModule !== 'smrt-commerce' ||\n original.sourceRef !== `${originalKind}:${invoiceId}:${snapshotId}` ||\n !metadataMatches(original, {\n kind: originalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n })\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice revenue journal must be posted and tenant-owned before reversal',\n );\n const originalEntries = await entriesForJournal(tx, originalJournalId);\n const reversalEntries = reverseEntries(originalEntries, currency);\n await validateAccounts(\n tx,\n tenantId,\n reversalEntries.map((entry) => entry.accountId),\n );\n const reversalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-cancellation-reversal' as const)\n : ('invoice-cancellation-reversal' as const);\n const reversalId = await deterministicId([\n reversalKind,\n tenantId,\n invoiceId,\n snapshotId,\n originalJournalId,\n ]);\n const metadata: PostingMetadata = {\n kind: reversalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n originalJournalId,\n };\n const reversal = await createOrReadPostedJournal(tx, {\n id: reversalId,\n tenantId,\n sourceRef: `invoice-cancellation:${invoiceId}:${snapshotId}`,\n description: `Cancellation reversal: Invoice ${invoice.invoiceNumber}`,\n date: new Date(),\n metadata,\n entries: reversalEntries,\n });\n if (\n invoice.arReversalJournalId &&\n invoice.arReversalJournalId !== reversalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different cancellation reversal',\n );\n invoice.arReversalJournalId = reversalId;\n if (invoice.status !== InvoiceStatus.CANCELLED) invoice.cancel();\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arReversalJournalId = reversalId;\n callerInvoice.status = InvoiceStatus.CANCELLED;\n return reversal;\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"invoice-posting-DVSkfSUm.js","names":[],"sources":["../../src/invoice-posting.ts"],"sourcesContent":["/** Atomic revenue posting and cancellation reversal. */\n\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport type { Journal, JournalEntryData } from '@happyvertical/smrt-ledgers';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { ISO_4217_MINOR_UNITS } from '@happyvertical/smrt-ui/currency';\nimport { deterministicId } from './billing/units.js';\nimport { DocumentTaxCollection } from './collections/DocumentTaxCollection.js';\nimport { InvoiceLineItemCollection } from './collections/InvoiceLineItemCollection.js';\nimport { lockAndReloadInvoice } from './document-money.js';\nimport {\n DOCUMENT_MONEY_WRITE,\n withDocumentMoneyTransaction,\n} from './document-money-internal.js';\nimport type { Invoice } from './models/Invoice.js';\nimport { MoneyValidationError } from './money.js';\nimport { buildRevenueJournalEntries } from './revenue-journal.js';\nimport { lockTenantMoneyRuntime } from './tenant-money-barrier.js';\nimport {\n type CancelRevenueOptions,\n InvoiceStatus,\n type RecognizeRevenueOptions,\n} from './types/index.js';\n\ntype PostingMetadata = {\n kind:\n | 'invoice-revenue'\n | 'credit-note-revenue'\n | 'invoice-cancellation-reversal'\n | 'credit-note-cancellation-reversal';\n invoiceId: string;\n moneySnapshotId: string;\n originalJournalId?: string;\n};\n\nfunction required(value: string | null | undefined, label: string): string {\n if (!value || value.trim() !== value)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `${label} must be a nonempty value`,\n );\n return value;\n}\n\nfunction bookCurrency(value: string | undefined): string {\n if (\n !value ||\n value !== value.toUpperCase() ||\n !ISO_4217_MINOR_UNITS.has(value) ||\n ISO_4217_MINOR_UNITS.get(value) === null\n )\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Revenue posting requires a supported ISO 4217 bookCurrency',\n );\n return value;\n}\n\nfunction assertTenantAuthority(tenantId: string): void {\n if (getTenantId() !== tenantId)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Invoice posting requires its active tenant context',\n );\n}\n\nfunction canonicalEntries(entries: readonly JournalEntryData[]): string {\n return JSON.stringify(\n entries\n .map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit ?? 0,\n credit: entry.credit ?? 0,\n currency: entry.currency ?? '',\n memo: entry.memo ?? '',\n }))\n .sort((left, right) => {\n const leftKey = JSON.stringify(left);\n const rightKey = JSON.stringify(right);\n return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;\n }),\n );\n}\n\nasync function validateAccounts(\n db: DatabaseInterface,\n tenantId: string,\n accountIds: readonly string[],\n): Promise<void> {\n const { AccountCollection } = await import('@happyvertical/smrt-ledgers');\n const accounts = await AccountCollection.create({ db });\n for (const accountId of [...new Set(accountIds)]) {\n const account = await accounts.get({ id: accountId }, { cache: false });\n if (!account || account.tenantId !== tenantId || !account.active)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n `Posting account ${accountId} must be active and owned by the invoice tenant`,\n );\n }\n}\n\nasync function entriesForJournal(\n db: DatabaseInterface,\n journalId: string,\n): Promise<JournalEntryData[]> {\n const { JournalEntryCollection } = await import(\n '@happyvertical/smrt-ledgers'\n );\n const entries = await (\n await JournalEntryCollection.create({ db })\n ).findByJournal(journalId);\n return entries.map((entry) => ({\n accountId: entry.accountId,\n debit: entry.debit,\n credit: entry.credit,\n currency: entry.currency,\n memo: entry.memo,\n }));\n}\n\nfunction metadataMatches(journal: Journal, expected: PostingMetadata): boolean {\n const actual = journal.metadata as Partial<PostingMetadata>;\n return (\n actual.kind === expected.kind &&\n actual.invoiceId === expected.invoiceId &&\n actual.moneySnapshotId === expected.moneySnapshotId &&\n actual.originalJournalId === expected.originalJournalId\n );\n}\n\nasync function createOrReadPostedJournal(\n db: DatabaseInterface,\n input: {\n id: string;\n tenantId: string;\n sourceRef: string;\n description: string;\n date: Date;\n metadata: PostingMetadata;\n entries: readonly JournalEntryData[];\n },\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db });\n const existing = await journals.get({ id: input.id }, { cache: false });\n if (existing) {\n const storedEntries = await entriesForJournal(db, input.id);\n if (\n existing.status !== 'posted' ||\n existing.tenantId !== input.tenantId ||\n existing.sourceModule !== 'smrt-commerce' ||\n existing.sourceRef !== input.sourceRef ||\n !metadataMatches(existing, input.metadata) ||\n canonicalEntries(storedEntries) !== canonicalEntries(input.entries)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n `Deterministic journal ${input.id} conflicts with the frozen invoice snapshot`,\n );\n return existing;\n }\n const journal = await journals.create({\n id: input.id,\n tenantId: input.tenantId,\n date: input.date,\n description: input.description,\n sourceModule: 'smrt-commerce',\n sourceRef: input.sourceRef,\n metadata: input.metadata,\n _insertOnly: true,\n });\n for (const entry of input.entries) await journal.addEntry(entry);\n await journal.post();\n if (!journal.id)\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Posted revenue journal is missing its identity',\n );\n return journal;\n}\n\n/**\n * Reload a journal posted inside a money transaction on `db`, so the caller\n * gets an instance whose reads do not run on the finished transaction.\n */\nexport async function rebindJournal(\n db: DatabaseInterface,\n journal: Journal,\n): Promise<Journal> {\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n return (\n (await (\n await JournalCollection.create({ db })\n ).get({ id: required(journal.id, 'Journal ID') }, { cache: false })) ??\n journal\n );\n}\n\n/** Post an issued invoice's revenue journal from its frozen money facts. */\nexport async function recognizeInvoiceRevenue(\n input: Invoice,\n options: RecognizeRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.moneyAuthority === 'historical')\n throw new MoneyValidationError(\n 'INVALID_MONEY_INPUT',\n 'Original historical revenue is retained evidence; a new revenue posting requires an explicit reviewed historical accounting contract',\n );\n if (invoice.status === InvoiceStatus.DRAFT || !invoice.moneySnapshotId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue recognition requires an issued frozen invoice',\n );\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n\n const lines = await (\n await InvoiceLineItemCollection.create({ db: tx })\n ).findByInvoice(invoiceId);\n const taxes = await (\n await DocumentTaxCollection.create({ db: tx })\n ).findByDocument(tenantId, invoice.kind, invoiceId);\n const built = buildRevenueJournalEntries(\n {\n id: invoiceId,\n number: invoice.invoiceNumber,\n currency: invoice.currency,\n lineAmountType: invoice.lineAmountType,\n subtotalMinor: invoice.subtotal,\n taxMinor: invoice.taxAmount,\n totalMinor: invoice.totalAmount,\n providerTaxMinor: invoice.providerTaxAmount,\n },\n lines.map((line) => ({\n id: required(line.id, 'Invoice line ID'),\n amountMinor: line.amount,\n taxMinor: line.taxAmount,\n revenueAccountId: line.revenueAccountId,\n })),\n taxes.map((tax) => ({\n currency: tax.currency,\n taxCodeId: tax.taxCodeId,\n taxCodeCode: tax.taxCodeCode,\n taxComponentId: tax.taxComponentId,\n liabilityAccountId: tax.liabilityAccountId,\n taxMinor: tax.taxMinor,\n })),\n {\n arAccountId: options.arAccountId,\n revenueAccountId: options.revenueAccountId,\n taxAccountId: options.taxAccountId,\n bookCurrency: currency,\n direction: invoice.kind === 'credit_note' ? -1 : 1,\n },\n );\n await validateAccounts(\n tx,\n tenantId,\n built.entries.map((entry) => entry.accountId),\n );\n const postingKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const journalId = await deterministicId([\n postingKind,\n tenantId,\n invoiceId,\n invoice.moneySnapshotId,\n ]);\n const metadata: PostingMetadata = {\n kind: postingKind,\n invoiceId,\n moneySnapshotId: invoice.moneySnapshotId,\n };\n const journal = await createOrReadPostedJournal(tx, {\n id: journalId,\n tenantId,\n sourceRef: `${postingKind}:${invoiceId}:${invoice.moneySnapshotId}`,\n description: `${invoice.kind === 'credit_note' ? 'Credit note' : 'Revenue'}: Invoice ${invoice.invoiceNumber}`,\n date: invoice.issueDate,\n metadata,\n entries: built.entries,\n });\n if (invoice.arJournalId && invoice.arJournalId !== journalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different revenue journal',\n );\n invoice.arJournalId = journalId;\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arJournalId = journalId;\n return journal;\n }),\n );\n}\n\nfunction reverseEntries(\n entries: readonly JournalEntryData[],\n currency: string,\n): JournalEntryData[] {\n return entries.map((entry, index) => {\n if (\n entry.currency !== currency ||\n !Number.isSafeInteger(entry.debit ?? 0) ||\n !Number.isSafeInteger(entry.credit ?? 0) ||\n (entry.debit ?? 0) < 0 ||\n (entry.credit ?? 0) < 0 ||\n (entry.debit ?? 0) > 0 === (entry.credit ?? 0) > 0\n )\n throw new MoneyValidationError(\n entry.currency !== currency ? 'CURRENCY_MISMATCH' : 'UNSAFE_MONEY',\n `Original journal entry ${index + 1} is not an exact ${currency} movement`,\n );\n return {\n accountId: required(entry.accountId, 'Original journal account ID'),\n debit: entry.credit ?? 0,\n credit: entry.debit ?? 0,\n currency,\n memo: `Reversal: ${entry.memo ?? ''}`,\n };\n });\n}\n\n/** Atomically reverse a posted invoice and move it to CANCELLED. */\nexport async function cancelInvoiceWithRevenueReversal(\n input: Invoice,\n options: CancelRevenueOptions,\n): Promise<Journal> {\n return withDocumentMoneyTransaction(input.db, async (tx) =>\n input.withDatabase(tx, async (callerInvoice) => {\n const candidateTenantId = getTenantId() ?? callerInvoice.tenantId;\n await lockTenantMoneyRuntime(\n tx,\n required(candidateTenantId, 'Invoice tenant ID'),\n );\n const invoiceId = required(callerInvoice.id, 'Invoice ID');\n const tenantId = required(candidateTenantId, 'Invoice tenant ID');\n assertTenantAuthority(tenantId);\n const currency = bookCurrency(options.bookCurrency);\n const { invoice } = await lockAndReloadInvoice(tx, invoiceId, tenantId);\n if (invoice.currency !== currency)\n throw new MoneyValidationError(\n 'CURRENCY_MISMATCH',\n `Invoice currency ${invoice.currency} does not match book currency ${currency}`,\n );\n if (\n (invoice.kind === 'invoice' && invoice.isPaid()) ||\n (invoice.kind === 'credit_note' && invoice.amountPaid > 0)\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Paid invoices and refunded credit notes cannot be cancelled',\n );\n if (!invoice.arJournalId)\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Revenue must be posted before using cancellation reversal',\n );\n const snapshotId = required(\n invoice.moneySnapshotId,\n 'Invoice money snapshot ID',\n );\n const originalJournalId = invoice.arJournalId;\n const originalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-revenue' as const)\n : ('invoice-revenue' as const);\n const { JournalCollection } = await import('@happyvertical/smrt-ledgers');\n const journals = await JournalCollection.create({ db: tx });\n const original = await journals.get(\n { id: originalJournalId },\n { cache: false },\n );\n if (\n original?.status !== 'posted' ||\n original.tenantId !== tenantId ||\n original.sourceModule !== 'smrt-commerce' ||\n original.sourceRef !== `${originalKind}:${invoiceId}:${snapshotId}` ||\n !metadataMatches(original, {\n kind: originalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n })\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice revenue journal must be posted and tenant-owned before reversal',\n );\n const originalEntries = await entriesForJournal(tx, originalJournalId);\n const reversalEntries = reverseEntries(originalEntries, currency);\n await validateAccounts(\n tx,\n tenantId,\n reversalEntries.map((entry) => entry.accountId),\n );\n const reversalKind =\n invoice.kind === 'credit_note'\n ? ('credit-note-cancellation-reversal' as const)\n : ('invoice-cancellation-reversal' as const);\n const reversalId = await deterministicId([\n reversalKind,\n tenantId,\n invoiceId,\n snapshotId,\n originalJournalId,\n ]);\n const metadata: PostingMetadata = {\n kind: reversalKind,\n invoiceId,\n moneySnapshotId: snapshotId,\n originalJournalId,\n };\n const reversal = await createOrReadPostedJournal(tx, {\n id: reversalId,\n tenantId,\n sourceRef: `invoice-cancellation:${invoiceId}:${snapshotId}`,\n description: `Cancellation reversal: Invoice ${invoice.invoiceNumber}`,\n date: new Date(),\n metadata,\n entries: reversalEntries,\n });\n if (\n invoice.arReversalJournalId &&\n invoice.arReversalJournalId !== reversalId\n )\n throw new MoneyValidationError(\n 'FROZEN_DOCUMENT',\n 'Invoice already links a different cancellation reversal',\n );\n invoice.arReversalJournalId = reversalId;\n if (invoice.status !== InvoiceStatus.CANCELLED) invoice.cancel();\n await invoice.saveDerived(DOCUMENT_MONEY_WRITE);\n callerInvoice.arReversalJournalId = reversalId;\n callerInvoice.status = InvoiceStatus.CANCELLED;\n return reversal;\n }),\n );\n}\n"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-C6cI5U89.js";
2
+ import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-Dg942m_Z.js";
3
3
  import { d as PaymentStatus, s as InvoiceStatus, u as PaymentMethod } from "./types-Bbm0Xflc.js";
4
4
  import { o as minorToMajorUnits } from "./units-8ElbjVZK.js";
5
5
  import { i as withOrdinaryAccountingAuthority, n as withRetentionAccountingAuthority, r as hasOrdinaryAccountingAuthority, t as hasRetentionAccountingAuthority } from "./retention-accounting-internal-DNxLLVvK.js";
6
- import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-CH-i-hsY.js";
6
+ import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-Kr8uceRx.js";
7
7
  import { n as customerFundsState, t as assertCustomerFundsWrite } from "./customer-funds-internal-BQzoJXK5.js";
8
8
  import { MoneyValidationError, assertIntegerMinorUnits, calculateMoneyDocument } from "../money.js";
9
9
  import { t as HistoricalCustomerFundsAllocationCollection } from "./HistoricalCustomerFundsCollections-B5x8gTW3.js";
10
10
  import { n as calculateAvailableRefund } from "./credit-allocation-yspz0Lab.js";
11
11
  import { _ as withDocumentMoneyTransaction, m as isMoneyTransaction, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
12
- import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-BolwughC.js";
13
- import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-DEVLrpaB.js";
12
+ import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-XPcPyibo.js";
13
+ import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-CG78Xxja.js";
14
14
  import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isPostgresDatabase, smrt } from "@happyvertical/smrt-core";
15
15
  import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
16
16
  import { createHash, randomUUID } from "node:crypto";
@@ -813,7 +813,7 @@ var PaymentAllocation = class extends SmrtObject {
813
813
  if (!this.paymentId || !this.invoiceId) throw new Error("PaymentAllocation requires payment and invoice");
814
814
  return this.withTransaction(async () => {
815
815
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
816
- const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
816
+ const { InvoiceCollection } = await import("./InvoiceCollection-XPcPyibo.js").then((n) => n.n);
817
817
  const { PaymentAllocationCollection } = await Promise.resolve().then(() => PaymentAllocationCollection_exports);
818
818
  const payments = await PaymentCollection.create({ db: this.db });
819
819
  const invoices = await InvoiceCollection.create({ db: this.db });
@@ -920,7 +920,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
920
920
  async allocatePayment(input) {
921
921
  if (!input.allocations.length || new Set(input.allocations.map((item) => item.id)).size !== input.allocations.length) throw new Error("Allocation batch requires distinct stable allocation ids");
922
922
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
923
- const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
923
+ const { InvoiceCollection } = await import("./InvoiceCollection-XPcPyibo.js").then((n) => n.n);
924
924
  const { InvoiceStatus } = await import("./types-Bbm0Xflc.js").then((n) => n.m);
925
925
  const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: input.paymentId }, { cache: false });
926
926
  if (!payment) throw new Error("Payment does not exist");
@@ -963,7 +963,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
963
963
  const allocation = await this.get({ id }, { cache: false });
964
964
  if (!allocation) return;
965
965
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
966
- const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
966
+ const { InvoiceCollection } = await import("./InvoiceCollection-XPcPyibo.js").then((n) => n.n);
967
967
  const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: allocation.paymentId }, { cache: false });
968
968
  if (!payment) throw new Error("Allocation payment does not exist");
969
969
  await payment.withTransaction(async () => {
@@ -2735,4 +2735,4 @@ async function reconcileRetainedCreditAccounting(input) {
2735
2735
  //#endregion
2736
2736
  export { PaymentAllocationCollection_exports as A, invoice_accounting_sync_exports as C, syncFrozenInvoiceToAccounting as D, reconcileFrozenInvoiceAccountingSync as E, Payment as F, historicalCreditRefundCommittedTotal as M, PaymentCollection as N, toFrozenInvoiceAccountingInput as O, PaymentCollection_exports as P, calculateInvoiceMinorLine as S, readFrozenAccountingSyncState as T, reconcileOrdinaryCreditAccounting as _, reconcileRetainedBaseInvoice as a, syncOrdinaryRefundToAccounting as b, syncRetainedCreditToAccounting as c, toRetainedCreditAccountingInput as d, toRetainedInvoiceAccountingInput as f, reconcileOrdinaryBaseInvoice as g, reconcileOrdinaryAccountingStep as h, reconcileRetainedAccountingStep as i, PaymentAllocation as j, PaymentAllocationCollection as k, syncRetainedInvoiceToAccounting as l, readOrdinaryInvoiceAccountingState as m, loadRetainedInvoiceAccountingSnapshot as n, reconcileRetainedCreditAccounting as o, loadOrdinaryCreditAccountingSnapshot as p, readRetainedInvoiceAccountingState as r, releaseRetainedInvoiceToAccounting as s, loadRetainedCreditAccountingSnapshot as t, syncRetainedRefundToAccounting as u, syncOrdinaryCreditToAccounting as v, loadFrozenInvoiceAccountingSnapshot as w, toOrdinaryCreditAccountingInput as x, syncOrdinaryInvoiceToAccounting as y };
2737
2737
 
2738
- //# sourceMappingURL=retention-accounting-c7kY7gFU.js.map
2738
+ //# sourceMappingURL=retention-accounting-Ct5Df6xx.js.map