@happyvertical/smrt-commerce 0.57.36 → 0.57.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/allocation-integrity.d.ts +1 -1
- package/dist/allocation-integrity.d.ts.map +1 -1
- package/dist/chunks/{ContractLineItemCollection-DKcR46vL.js → ContractLineItemCollection-D74LbDOI.js} +8 -8
- package/dist/chunks/ContractLineItemCollection-D74LbDOI.js.map +1 -0
- package/dist/chunks/{DocumentTaxCollection-BjY9L6lc.js → DocumentTaxCollection-BmKyJGZi.js} +30 -30
- package/dist/chunks/DocumentTaxCollection-BmKyJGZi.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-C50Hsu6G.js → InvoiceCollection-BPEzXaKm.js} +19 -14
- package/dist/chunks/InvoiceCollection-BPEzXaKm.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-CT09VQ9n.js → TaxCodeCollection-DpQeIqP4.js} +4 -4
- package/dist/chunks/TaxCodeCollection-DpQeIqP4.js.map +1 -0
- package/dist/chunks/{TaxComponentCollection-D8sFp8ZD.js → TaxComponentCollection-wFGWOaxy.js} +13 -8
- package/dist/chunks/TaxComponentCollection-wFGWOaxy.js.map +1 -0
- package/dist/chunks/{allocation-integrity-DrFgfSMd.js → allocation-integrity-DgnPec2a.js} +8 -8
- package/dist/chunks/allocation-integrity-DgnPec2a.js.map +1 -0
- package/dist/chunks/{contract-money-Cnezm9sA.js → contract-money-BxvY2Kcp.js} +14 -14
- package/dist/chunks/contract-money-BxvY2Kcp.js.map +1 -0
- package/dist/chunks/{invoice-posting-Dx-mfaRX.js → invoice-posting-C1q3CIYz.js} +3 -3
- package/dist/chunks/{invoice-posting-Dx-mfaRX.js.map → invoice-posting-C1q3CIYz.js.map} +1 -1
- package/dist/chunks/{retention-accounting-CvfeisFJ.js → retention-accounting-BAKsmbwJ.js} +18 -13
- package/dist/chunks/retention-accounting-BAKsmbwJ.js.map +1 -0
- package/dist/chunks/{tenant-money-barrier-DTELupG0.js → tenant-money-barrier-j1m0r6pe.js} +5 -5
- package/dist/chunks/tenant-money-barrier-j1m0r6pe.js.map +1 -0
- package/dist/contract-money.d.ts +3 -2
- package/dist/contract-money.d.ts.map +1 -1
- package/dist/document-money.d.ts +3 -2
- package/dist/document-money.d.ts.map +1 -1
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/invoices.js +1 -1
- package/dist/manifest.json +280 -28
- package/dist/models/Contract.d.ts +5 -5
- package/dist/models/Contract.d.ts.map +1 -1
- package/dist/models/ContractLineItem.d.ts +3 -3
- package/dist/models/ContractLineItem.d.ts.map +1 -1
- package/dist/models/DocumentTax.d.ts +3 -3
- package/dist/models/DocumentTax.d.ts.map +1 -1
- package/dist/models/Fulfillment.d.ts +2 -2
- package/dist/models/Fulfillment.d.ts.map +1 -1
- package/dist/models/FulfillmentLineItem.d.ts +2 -2
- package/dist/models/FulfillmentLineItem.d.ts.map +1 -1
- package/dist/models/Invoice.d.ts +3 -3
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/InvoiceLineItem.d.ts +3 -3
- package/dist/models/InvoiceLineItem.d.ts.map +1 -1
- package/dist/models/Payment.d.ts +2 -2
- package/dist/models/Payment.d.ts.map +1 -1
- package/dist/models/PaymentAllocation.d.ts +2 -2
- package/dist/models/PaymentAllocation.d.ts.map +1 -1
- package/dist/models/PaymentIntent.d.ts +2 -2
- package/dist/models/PaymentIntent.d.ts.map +1 -1
- package/dist/models/Payout.d.ts +2 -2
- package/dist/models/Payout.d.ts.map +1 -1
- package/dist/models/PurchasePriceObservation.d.ts +3 -3
- package/dist/models/PurchasePriceObservation.d.ts.map +1 -1
- package/dist/models/TaxCode.d.ts +2 -2
- package/dist/models/TaxCode.d.ts.map +1 -1
- package/dist/models/TaxComponent.d.ts +2 -2
- package/dist/models/TaxComponent.d.ts.map +1 -1
- package/dist/models/TaxSettings.d.ts +2 -2
- package/dist/models/TaxSettings.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +120 -21
- package/package.json +16 -16
- package/dist/chunks/ContractLineItemCollection-DKcR46vL.js.map +0 -1
- package/dist/chunks/DocumentTaxCollection-BjY9L6lc.js.map +0 -1
- package/dist/chunks/InvoiceCollection-C50Hsu6G.js.map +0 -1
- package/dist/chunks/TaxCodeCollection-CT09VQ9n.js.map +0 -1
- package/dist/chunks/TaxComponentCollection-D8sFp8ZD.js.map +0 -1
- package/dist/chunks/allocation-integrity-DrFgfSMd.js.map +0 -1
- package/dist/chunks/contract-money-Cnezm9sA.js.map +0 -1
- package/dist/chunks/retention-accounting-CvfeisFJ.js.map +0 -1
- package/dist/chunks/tenant-money-barrier-DTELupG0.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-money-BxvY2Kcp.js","names":[],"sources":["../../src/contract-money.ts"],"sourcesContent":["/** Transactional Contract money aggregate persistence. */\n\nimport type { SmrtSaveOptions } from '@happyvertical/smrt-core';\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(\n input: Contract,\n options?: SmrtSaveOptions,\n): 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 // NULL (a row written before the column had a default) and `''` both\n // mean no schedule (#4155); any other difference is a schedule change.\n contract.billingScheduleJson ??= '';\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, options);\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, options);\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, options);\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, options);\n },\n );\n }),\n );\n}\n\n/** Save a line and atomically recompute its Contract aggregate. */\nexport async function saveContractLineItem(\n input: ContractLineItem,\n options?: SmrtSaveOptions,\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, options);\n }\n line.contractId = contractId;\n line.tenantId = scope.tenantId;\n await line.saveDerived(DOCUMENT_MONEY_WRITE, options);\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-
|
|
1
|
+
import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-BmKyJGZi.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-
|
|
6
|
+
import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-j1m0r6pe.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-
|
|
227
|
+
//# sourceMappingURL=invoice-posting-C1q3CIYz.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoice-posting-Dx-mfaRX.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-C1q3CIYz.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-
|
|
2
|
+
import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-BmKyJGZi.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-
|
|
6
|
+
import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-DgnPec2a.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-
|
|
13
|
-
import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-
|
|
12
|
+
import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-BPEzXaKm.js";
|
|
13
|
+
import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-j1m0r6pe.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 { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -251,10 +251,15 @@ var Payment = class extends SmrtObject {
|
|
|
251
251
|
* (REST/MCP/CLI/direct). Fixtures/migrations that need a completed payment
|
|
252
252
|
* must drive it through `recordPayment()` (or start non-COMPLETED).
|
|
253
253
|
*/
|
|
254
|
-
async save() {
|
|
254
|
+
async save(options) {
|
|
255
255
|
return this.withTransaction(async () => {
|
|
256
256
|
if (this.kind !== "payment" && this.kind !== "refund") throw new Error("Payment kind must be payment or refund");
|
|
257
|
-
|
|
257
|
+
const callerRevision = options?.expectedUpdatedAt;
|
|
258
|
+
await claimAllocationParent(this, "payment", callerRevision);
|
|
259
|
+
if (callerRevision !== void 0 && this.updated_at) options = {
|
|
260
|
+
...options,
|
|
261
|
+
expectedUpdatedAt: this.updated_at
|
|
262
|
+
};
|
|
258
263
|
assertIntegerMinorUnits("Payment", this.id || "<new>", [
|
|
259
264
|
["amount", this.amount],
|
|
260
265
|
["nativeAmount", this.nativeAmount],
|
|
@@ -278,7 +283,7 @@ var Payment = class extends SmrtObject {
|
|
|
278
283
|
if (priorRow && (priorRow.status === PaymentStatus.COMPLETED || priorRow.status === PaymentStatus.REFUNDED)) this.assertSettledAmountsUnchanged(priorRow);
|
|
279
284
|
try {
|
|
280
285
|
await assertAllocationParent(this, "payment");
|
|
281
|
-
const result = await super.save();
|
|
286
|
+
const result = await super.save(options);
|
|
282
287
|
loadedPaymentStatus.set(this, this.status);
|
|
283
288
|
return result;
|
|
284
289
|
} finally {
|
|
@@ -958,13 +963,13 @@ var PaymentAllocation = class extends SmrtObject {
|
|
|
958
963
|
if (options.notes !== void 0) this.notes = options.notes;
|
|
959
964
|
}
|
|
960
965
|
/** Save and validate against both claimed parent rows in one transaction. */
|
|
961
|
-
async save() {
|
|
966
|
+
async save(options) {
|
|
962
967
|
assertIntegerMinorUnits("PaymentAllocation", this.id ?? "<new>", [["amount", this.amount]]);
|
|
963
968
|
if (this.amount <= 0) throw new Error("PaymentAllocation amount must be a positive number");
|
|
964
969
|
if (!this.paymentId || !this.invoiceId) throw new Error("PaymentAllocation requires payment and invoice");
|
|
965
970
|
return this.withTransaction(async () => {
|
|
966
971
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
967
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
972
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
|
|
968
973
|
const { PaymentAllocationCollection } = await Promise.resolve().then(() => PaymentAllocationCollection_exports);
|
|
969
974
|
const payments = await PaymentCollection.create({ db: this.db });
|
|
970
975
|
const invoices = await InvoiceCollection.create({ db: this.db });
|
|
@@ -1024,7 +1029,7 @@ var PaymentAllocation = class extends SmrtObject {
|
|
|
1024
1029
|
});
|
|
1025
1030
|
if (this.amount > availableRefund) throw new Error(`Refund allocation exceeds available unapplied credit (${availableRefund})`);
|
|
1026
1031
|
}
|
|
1027
|
-
return await super.save();
|
|
1032
|
+
return await super.save(options);
|
|
1028
1033
|
});
|
|
1029
1034
|
}
|
|
1030
1035
|
/** Managed application reversal must also post its inverse journal and retain evidence. */
|
|
@@ -1071,7 +1076,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
|
|
|
1071
1076
|
async allocatePayment(input) {
|
|
1072
1077
|
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");
|
|
1073
1078
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
1074
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
1079
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
|
|
1075
1080
|
const { InvoiceStatus } = await import("./types-Bbm0Xflc.js").then((n) => n.m);
|
|
1076
1081
|
const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: input.paymentId }, { cache: false });
|
|
1077
1082
|
if (!payment) throw new Error("Payment does not exist");
|
|
@@ -1114,7 +1119,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
|
|
|
1114
1119
|
const allocation = await this.get({ id }, { cache: false });
|
|
1115
1120
|
if (!allocation) return;
|
|
1116
1121
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
1117
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
1122
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
|
|
1118
1123
|
const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: allocation.paymentId }, { cache: false });
|
|
1119
1124
|
if (!payment) throw new Error("Allocation payment does not exist");
|
|
1120
1125
|
await payment.withTransaction(async () => {
|
|
@@ -2886,4 +2891,4 @@ async function reconcileRetainedCreditAccounting(input) {
|
|
|
2886
2891
|
//#endregion
|
|
2887
2892
|
export { PaymentAllocationCollection_exports as A, invoice_accounting_sync_exports as C, syncFrozenInvoiceToAccounting as D, reconcileFrozenInvoiceAccountingSync as E, Payment as F, hasConnectedPaymentAuthority as I, withConnectedPaymentAuthority as L, 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 };
|
|
2888
2893
|
|
|
2889
|
-
//# sourceMappingURL=retention-accounting-
|
|
2894
|
+
//# sourceMappingURL=retention-accounting-BAKsmbwJ.js.map
|