@dainamite/billing 0.19.0 → 0.21.0
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/CHANGELOG.md +172 -0
- package/README.md +14 -0
- package/dist/modules/billing/acl.js +13 -1
- package/dist/modules/billing/acl.js.map +2 -2
- package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
- package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
- package/dist/modules/billing/api/accounts/route.js +46 -4
- package/dist/modules/billing/api/accounts/route.js.map +2 -2
- package/dist/modules/billing/api/export/account/[id]/route.js +16 -12
- package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
- package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/pdf/route.js +69 -0
- package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
- package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
- package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/add-line/route.js +3 -3
- package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
- package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/edit-line/route.js +3 -3
- package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/post/route.js +2 -2
- package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/remove-line/route.js +3 -3
- package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/route.js +45 -46
- package/dist/modules/billing/api/invoices/route.js.map +2 -2
- package/dist/modules/billing/api/items/bulk/route.js +1 -1
- package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
- package/dist/modules/billing/api/items/change-price/route.js +44 -0
- package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
- package/dist/modules/billing/api/items/route.js +19 -7
- package/dist/modules/billing/api/items/route.js.map +2 -2
- package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
- package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/payment-imports/route.js +57 -0
- package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
- package/dist/modules/billing/api/payments/[id]/route.js +60 -0
- package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/payments/allocate/route.js +35 -0
- package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
- package/dist/modules/billing/api/payments/assign/route.js +36 -0
- package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
- package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
- package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
- package/dist/modules/billing/api/payments/ignore/route.js +35 -0
- package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
- package/dist/modules/billing/api/payments/import/route.js +63 -0
- package/dist/modules/billing/api/payments/import/route.js.map +7 -0
- package/dist/modules/billing/api/payments/restore/route.js +35 -0
- package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
- package/dist/modules/billing/api/payments/route.js +114 -0
- package/dist/modules/billing/api/payments/route.js.map +7 -0
- package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
- package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
- package/dist/modules/billing/api/payments/unassign/route.js +35 -0
- package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
- package/dist/modules/billing/api/run-outcomes/route.js +44 -7
- package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
- package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
- package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
- package/dist/modules/billing/api/runs/[id]/route.js +53 -0
- package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/runs/retry-failed/route.js +3 -3
- package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
- package/dist/modules/billing/api/runs/route.js +11 -5
- package/dist/modules/billing/api/runs/route.js.map +2 -2
- package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
- package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
- package/dist/modules/billing/api/settings/route.js +92 -0
- package/dist/modules/billing/api/settings/route.js.map +7 -0
- package/dist/modules/billing/api/statements/route.js +3 -0
- package/dist/modules/billing/api/statements/route.js.map +2 -2
- package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
- package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
- package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
- package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
- package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
- package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
- package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/page.js +8 -2
- package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/page.js +58 -0
- package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
- package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/runs/page.js +58 -7
- package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
- package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/statements/page.js +1 -1
- package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/topups/page.js +1 -1
- package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
- package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
- package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
- package/dist/modules/billing/backend/config/billing/page.js +472 -0
- package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
- package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
- package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
- package/dist/modules/billing/ce.js.map +2 -2
- package/dist/modules/billing/cli.js +22 -1
- package/dist/modules/billing/cli.js.map +2 -2
- package/dist/modules/billing/commands/accounts.js +6 -0
- package/dist/modules/billing/commands/accounts.js.map +2 -2
- package/dist/modules/billing/commands/index.js +3 -0
- package/dist/modules/billing/commands/index.js.map +2 -2
- package/dist/modules/billing/commands/invoices.js +272 -170
- package/dist/modules/billing/commands/invoices.js.map +2 -2
- package/dist/modules/billing/commands/items.js +108 -0
- package/dist/modules/billing/commands/items.js.map +2 -2
- package/dist/modules/billing/commands/memos.js +179 -0
- package/dist/modules/billing/commands/memos.js.map +7 -0
- package/dist/modules/billing/commands/payments.js +379 -0
- package/dist/modules/billing/commands/payments.js.map +7 -0
- package/dist/modules/billing/commands/runs.js +154 -7
- package/dist/modules/billing/commands/runs.js.map +2 -2
- package/dist/modules/billing/commands/settings.js +59 -0
- package/dist/modules/billing/commands/settings.js.map +7 -0
- package/dist/modules/billing/commands/topups.js +9 -2
- package/dist/modules/billing/commands/topups.js.map +2 -2
- package/dist/modules/billing/commands/transactions.js +2 -2
- package/dist/modules/billing/commands/transactions.js.map +2 -2
- package/dist/modules/billing/components/AccountForm.js +149 -42
- package/dist/modules/billing/components/AccountForm.js.map +2 -2
- package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
- package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
- package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
- package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
- package/dist/modules/billing/components/AccountPicker.js +56 -0
- package/dist/modules/billing/components/AccountPicker.js.map +7 -0
- package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
- package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
- package/dist/modules/billing/components/BillingDataTable.js +21 -0
- package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
- package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
- package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
- package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
- package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
- package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
- package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
- package/dist/modules/billing/components/CustomerLabel.js +33 -0
- package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
- package/dist/modules/billing/components/InvoicesTable.js +270 -0
- package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
- package/dist/modules/billing/components/ItemForm.js +176 -44
- package/dist/modules/billing/components/ItemForm.js.map +2 -2
- package/dist/modules/billing/components/ItemPriceField.js +46 -0
- package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
- package/dist/modules/billing/components/LineFormDialog.js +25 -2
- package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
- package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
- package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
- package/dist/modules/billing/components/MoneyInput.js +14 -0
- package/dist/modules/billing/components/MoneyInput.js.map +7 -0
- package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
- package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
- package/dist/modules/billing/components/PaymentsTable.js +171 -0
- package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
- package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
- package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
- package/dist/modules/billing/components/PrepaidPanel.js +14 -4
- package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
- package/dist/modules/billing/components/RunCyclesSection.js +209 -0
- package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
- package/dist/modules/billing/components/TestRunActions.js +127 -0
- package/dist/modules/billing/components/TestRunActions.js.map +7 -0
- package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
- package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
- package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
- package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
- package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
- package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
- package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
- package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
- package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountView.js +73 -0
- package/dist/modules/billing/components/account/AccountView.js.map +7 -0
- package/dist/modules/billing/components/account/accountRow.js +39 -0
- package/dist/modules/billing/components/account/accountRow.js.map +7 -0
- package/dist/modules/billing/components/customerNames.js +42 -0
- package/dist/modules/billing/components/customerNames.js.map +7 -0
- package/dist/modules/billing/data/entities.js +615 -59
- package/dist/modules/billing/data/entities.js.map +2 -2
- package/dist/modules/billing/data/entityIds.js +10 -1
- package/dist/modules/billing/data/entityIds.js.map +2 -2
- package/dist/modules/billing/data/validators.js +269 -15
- package/dist/modules/billing/data/validators.js.map +2 -2
- package/dist/modules/billing/events.js +70 -0
- package/dist/modules/billing/events.js.map +2 -2
- package/dist/modules/billing/i18n/en.json +457 -21
- package/dist/modules/billing/i18n/pl.json +457 -21
- package/dist/modules/billing/index.js +6 -2
- package/dist/modules/billing/index.js.map +2 -2
- package/dist/modules/billing/lib/accountFinancials.js +58 -0
- package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
- package/dist/modules/billing/lib/accountLock.js +14 -0
- package/dist/modules/billing/lib/accountLock.js.map +7 -0
- package/dist/modules/billing/lib/accountStatus.js +9 -7
- package/dist/modules/billing/lib/accountStatus.js.map +2 -2
- package/dist/modules/billing/lib/accountSummary.js +106 -0
- package/dist/modules/billing/lib/accountSummary.js.map +7 -0
- package/dist/modules/billing/lib/allocation.js +356 -0
- package/dist/modules/billing/lib/allocation.js.map +7 -0
- package/dist/modules/billing/lib/allocationEvents.js +22 -0
- package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
- package/dist/modules/billing/lib/billPeriod.js +15 -1
- package/dist/modules/billing/lib/billPeriod.js.map +2 -2
- package/dist/modules/billing/lib/billRunEngine.js +241 -106
- package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
- package/dist/modules/billing/lib/billRunLabel.js.map +1 -1
- package/dist/modules/billing/lib/billingSettings.js +116 -0
- package/dist/modules/billing/lib/billingSettings.js.map +7 -0
- package/dist/modules/billing/lib/commandRoute.js +81 -0
- package/dist/modules/billing/lib/commandRoute.js.map +7 -0
- package/dist/modules/billing/lib/customerLookup.js +97 -0
- package/dist/modules/billing/lib/customerLookup.js.map +7 -0
- package/dist/modules/billing/lib/dateOnly.js +13 -0
- package/dist/modules/billing/lib/dateOnly.js.map +7 -0
- package/dist/modules/billing/lib/documentParties.js +16 -0
- package/dist/modules/billing/lib/documentParties.js.map +7 -0
- package/dist/modules/billing/lib/documentTypes.js +27 -0
- package/dist/modules/billing/lib/documentTypes.js.map +7 -0
- package/dist/modules/billing/lib/dueDate.js +18 -0
- package/dist/modules/billing/lib/dueDate.js.map +7 -0
- package/dist/modules/billing/lib/iban.js +29 -0
- package/dist/modules/billing/lib/iban.js.map +7 -0
- package/dist/modules/billing/lib/invoiceDelivery.js +131 -0
- package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
- package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
- package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
- package/dist/modules/billing/lib/invoiceWriter.js +107 -99
- package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
- package/dist/modules/billing/lib/itemBillingState.js +27 -0
- package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
- package/dist/modules/billing/lib/itemPriceChange.js +18 -0
- package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
- package/dist/modules/billing/lib/itemSelector.js.map +2 -2
- package/dist/modules/billing/lib/lineAmounts.js +32 -0
- package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
- package/dist/modules/billing/lib/memoGuards.js +25 -0
- package/dist/modules/billing/lib/memoGuards.js.map +7 -0
- package/dist/modules/billing/lib/navigation.js +48 -0
- package/dist/modules/billing/lib/navigation.js.map +7 -0
- package/dist/modules/billing/lib/numbering.js +120 -0
- package/dist/modules/billing/lib/numbering.js.map +7 -0
- package/dist/modules/billing/lib/overdue.js +36 -0
- package/dist/modules/billing/lib/overdue.js.map +7 -0
- package/dist/modules/billing/lib/paymentCsv.js +125 -0
- package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
- package/dist/modules/billing/lib/paymentGateway.js +15 -0
- package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
- package/dist/modules/billing/lib/paymentImport.js +164 -0
- package/dist/modules/billing/lib/paymentImport.js.map +7 -0
- package/dist/modules/billing/lib/paymentMatcher.js +36 -0
- package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
- package/dist/modules/billing/lib/prepaidConfig.js +17 -25
- package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
- package/dist/modules/billing/lib/prepaidConsume.js +26 -7
- package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
- package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
- package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
- package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
- package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
- package/dist/modules/billing/lib/seeds/demo.js +51 -3
- package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
- package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
- package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
- package/dist/modules/billing/lib/servicePeriods.js +121 -0
- package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
- package/dist/modules/billing/lib/tenantLock.js.map +1 -1
- package/dist/modules/billing/lib/testRunCleanup.js +24 -0
- package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
- package/dist/modules/billing/lib/topupCapture.js +2 -2
- package/dist/modules/billing/lib/topupCapture.js.map +2 -2
- package/dist/modules/billing/lib/topupReceipt.js +58 -79
- package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
- package/dist/modules/billing/lib/usageRater.js.map +1 -1
- package/dist/modules/billing/lib/usageRunner.js +14 -6
- package/dist/modules/billing/lib/usageRunner.js.map +2 -2
- package/dist/modules/billing/lib/vat.js +39 -0
- package/dist/modules/billing/lib/vat.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
- package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
- package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
- package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
- package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
- package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
- package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
- package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
- package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
- package/dist/modules/billing/setup.js +7 -184
- package/dist/modules/billing/setup.js.map +2 -2
- package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
- package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
- package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
- package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
- package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
- package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
- package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
- package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
- package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
- package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
- package/dist/modules/billing/workers/reap-stale-bill-runs.js +2 -2
- package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +2 -2
- package/generated/entities/billing_account/index.ts +26 -0
- package/generated/entities/billing_account_balance/index.ts +13 -0
- package/generated/entities/billing_account_transaction/index.ts +21 -0
- package/generated/entities/billing_account_usage/index.ts +18 -0
- package/generated/entities/billing_allocation/index.ts +18 -0
- package/generated/entities/billing_document_sequence/index.ts +11 -0
- package/generated/entities/billing_draft_invoice_edit/index.ts +14 -0
- package/generated/entities/billing_invoice/index.ts +38 -0
- package/generated/entities/billing_invoice_line/index.ts +24 -0
- package/generated/entities/billing_item/index.ts +26 -0
- package/generated/entities/billing_payment/index.ts +25 -0
- package/generated/entities/billing_payment_import/index.ts +14 -0
- package/generated/entities/billing_run/index.ts +20 -0
- package/generated/entities/billing_run_outcome/index.ts +18 -0
- package/generated/entities/billing_settings/index.ts +26 -0
- package/generated/entities/billing_statement/index.ts +22 -0
- package/generated/entities/billing_topup/index.ts +20 -0
- package/generated/entities.ids.generated.ts +35 -0
- package/package.json +15 -10
- package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +170 -0
- package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
- package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
- package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
- package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
- package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
- package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
- package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
- package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
- package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
- package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
- package/src/modules/billing/__tests__/commands/invoices.test.ts +437 -355
- package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
- package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
- package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
- package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
- package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
- package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
- package/src/modules/billing/__tests__/entityRegistry.test.ts +326 -0
- package/src/modules/billing/__tests__/events.test.ts +142 -0
- package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
- package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
- package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
- package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
- package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +231 -20
- package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
- package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
- package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
- package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
- package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +189 -0
- package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
- package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
- package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
- package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
- package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
- package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
- package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
- package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
- package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
- package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
- package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
- package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
- package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
- package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
- package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
- package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
- package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
- package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
- package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
- package/src/modules/billing/__tests__/setup.test.ts +13 -4
- package/src/modules/billing/__tests__/validators.test.ts +193 -3
- package/src/modules/billing/acl.ts +12 -0
- package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
- package/src/modules/billing/api/accounts/route.ts +58 -4
- package/src/modules/billing/api/export/account/[id]/route.ts +20 -18
- package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
- package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
- package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
- package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
- package/src/modules/billing/api/invoices/add-line/route.ts +3 -3
- package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
- package/src/modules/billing/api/invoices/edit-line/route.ts +4 -4
- package/src/modules/billing/api/invoices/post/route.ts +7 -6
- package/src/modules/billing/api/invoices/remove-line/route.ts +3 -3
- package/src/modules/billing/api/invoices/route.ts +81 -86
- package/src/modules/billing/api/items/bulk/route.ts +2 -1
- package/src/modules/billing/api/items/change-price/route.ts +54 -0
- package/src/modules/billing/api/items/route.ts +23 -7
- package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
- package/src/modules/billing/api/payment-imports/route.ts +57 -0
- package/src/modules/billing/api/payments/[id]/route.ts +63 -0
- package/src/modules/billing/api/payments/allocate/route.ts +35 -0
- package/src/modules/billing/api/payments/assign/route.ts +36 -0
- package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
- package/src/modules/billing/api/payments/ignore/route.ts +35 -0
- package/src/modules/billing/api/payments/import/route.ts +72 -0
- package/src/modules/billing/api/payments/restore/route.ts +35 -0
- package/src/modules/billing/api/payments/route.ts +147 -0
- package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
- package/src/modules/billing/api/payments/unassign/route.ts +35 -0
- package/src/modules/billing/api/run-outcomes/route.ts +50 -7
- package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
- package/src/modules/billing/api/runs/[id]/route.ts +59 -0
- package/src/modules/billing/api/runs/retry-failed/route.ts +4 -3
- package/src/modules/billing/api/runs/route.ts +17 -9
- package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
- package/src/modules/billing/api/settings/route.ts +116 -0
- package/src/modules/billing/api/statements/route.ts +5 -0
- package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
- package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
- package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
- package/src/modules/billing/backend/billing/accounts/page.tsx +71 -5
- package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +445 -96
- package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
- package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
- package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
- package/src/modules/billing/backend/billing/items/page.tsx +19 -3
- package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
- package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
- package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
- package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
- package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
- package/src/modules/billing/backend/billing/runs/[id]/page.tsx +50 -19
- package/src/modules/billing/backend/billing/runs/page.tsx +60 -9
- package/src/modules/billing/backend/billing/statements/[id]/page.tsx +87 -6
- package/src/modules/billing/backend/billing/statements/page.tsx +2 -2
- package/src/modules/billing/backend/billing/topups/page.tsx +2 -2
- package/src/modules/billing/backend/billing/transactions/page.tsx +2 -2
- package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
- package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
- package/src/modules/billing/backend/config/billing/page.tsx +549 -0
- package/src/modules/billing/ce.ts +8 -7
- package/src/modules/billing/cli.ts +31 -1
- package/src/modules/billing/commands/accounts.ts +6 -0
- package/src/modules/billing/commands/index.ts +3 -0
- package/src/modules/billing/commands/invoices.ts +371 -238
- package/src/modules/billing/commands/items.ts +143 -0
- package/src/modules/billing/commands/memos.ts +218 -0
- package/src/modules/billing/commands/payments.ts +428 -0
- package/src/modules/billing/commands/runs.ts +208 -6
- package/src/modules/billing/commands/settings.ts +77 -0
- package/src/modules/billing/commands/topups.ts +12 -3
- package/src/modules/billing/commands/transactions.ts +2 -2
- package/src/modules/billing/components/AccountForm.tsx +199 -53
- package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
- package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
- package/src/modules/billing/components/AccountPicker.tsx +70 -0
- package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
- package/src/modules/billing/components/BillingDataTable.tsx +34 -0
- package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
- package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
- package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
- package/src/modules/billing/components/CustomerLabel.tsx +53 -0
- package/src/modules/billing/components/InvoicesTable.tsx +344 -0
- package/src/modules/billing/components/ItemForm.tsx +233 -44
- package/src/modules/billing/components/ItemPriceField.tsx +81 -0
- package/src/modules/billing/components/LineFormDialog.tsx +25 -1
- package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
- package/src/modules/billing/components/MoneyInput.tsx +26 -0
- package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
- package/src/modules/billing/components/PaymentsTable.tsx +213 -0
- package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
- package/src/modules/billing/components/PrepaidPanel.tsx +20 -7
- package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
- package/src/modules/billing/components/TestRunActions.tsx +152 -0
- package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
- package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
- package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
- package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
- package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
- package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
- package/src/modules/billing/components/account/AccountView.tsx +101 -0
- package/src/modules/billing/components/account/accountRow.ts +70 -0
- package/src/modules/billing/components/customerNames.ts +62 -0
- package/src/modules/billing/data/entities.ts +685 -15
- package/src/modules/billing/data/entityIds.ts +25 -11
- package/src/modules/billing/data/validators.ts +388 -8
- package/src/modules/billing/events.ts +82 -3
- package/src/modules/billing/i18n/en.json +457 -21
- package/src/modules/billing/i18n/pl.json +457 -21
- package/src/modules/billing/index.ts +6 -2
- package/src/modules/billing/lib/accountFinancials.ts +84 -0
- package/src/modules/billing/lib/accountLock.ts +32 -0
- package/src/modules/billing/lib/accountStatus.ts +11 -9
- package/src/modules/billing/lib/accountSummary.ts +172 -0
- package/src/modules/billing/lib/allocation.ts +515 -0
- package/src/modules/billing/lib/allocationEvents.ts +26 -0
- package/src/modules/billing/lib/billPeriod.ts +19 -0
- package/src/modules/billing/lib/billRunEngine.ts +362 -162
- package/src/modules/billing/lib/billRunLabel.ts +1 -1
- package/src/modules/billing/lib/billingSettings.ts +145 -0
- package/src/modules/billing/lib/commandRoute.ts +112 -0
- package/src/modules/billing/lib/customerLookup.ts +157 -0
- package/src/modules/billing/lib/dateOnly.ts +22 -0
- package/src/modules/billing/lib/documentParties.ts +23 -0
- package/src/modules/billing/lib/documentTypes.ts +37 -0
- package/src/modules/billing/lib/dueDate.ts +26 -0
- package/src/modules/billing/lib/iban.ts +36 -0
- package/src/modules/billing/lib/invoiceDelivery.ts +197 -0
- package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
- package/src/modules/billing/lib/invoiceWriter.ts +162 -148
- package/src/modules/billing/lib/itemBillingState.ts +67 -0
- package/src/modules/billing/lib/itemPriceChange.ts +56 -0
- package/src/modules/billing/lib/itemSelector.ts +4 -1
- package/src/modules/billing/lib/lineAmounts.ts +61 -0
- package/src/modules/billing/lib/memoGuards.ts +41 -0
- package/src/modules/billing/lib/navigation.ts +64 -0
- package/src/modules/billing/lib/numbering.ts +174 -0
- package/src/modules/billing/lib/overdue.ts +60 -0
- package/src/modules/billing/lib/paymentCsv.ts +171 -0
- package/src/modules/billing/lib/paymentGateway.ts +52 -0
- package/src/modules/billing/lib/paymentImport.ts +228 -0
- package/src/modules/billing/lib/paymentMatcher.ts +88 -0
- package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
- package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
- package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
- package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
- package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
- package/src/modules/billing/lib/prepaidConfig.ts +27 -36
- package/src/modules/billing/lib/prepaidConsume.ts +29 -6
- package/src/modules/billing/lib/prepaidPeriodClose.ts +410 -242
- package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
- package/src/modules/billing/lib/seeds/demo.ts +79 -6
- package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
- package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
- package/src/modules/billing/lib/servicePeriods.ts +226 -0
- package/src/modules/billing/lib/tenantLock.ts +1 -1
- package/src/modules/billing/lib/testRunCleanup.ts +42 -0
- package/src/modules/billing/lib/topupCapture.ts +3 -4
- package/src/modules/billing/lib/topupReceipt.ts +70 -103
- package/src/modules/billing/lib/usageRater.ts +1 -1
- package/src/modules/billing/lib/usageRunner.ts +17 -5
- package/src/modules/billing/lib/vat.ts +56 -0
- package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
- package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
- package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
- package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
- package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
- package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
- package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
- package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
- package/src/modules/billing/setup.ts +11 -237
- package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
- package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
- package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
- package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
- package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
- package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
- package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
- package/src/modules/billing/workers/reap-stale-bill-runs.ts +4 -4
- package/dist/modules/billing/lib/invoiceStatus.js +0 -38
- package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
- package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
- package/src/modules/billing/lib/invoiceStatus.ts +0 -61
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
3
|
+
import { BillingAllocation } from '../data/entities'
|
|
4
|
+
import type { AllocatedBy, AllocationSourceType } from '../data/validators'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Allocation service (Spec C; generalised for Spec D credit memos).
|
|
8
|
+
*
|
|
9
|
+
* An allocation moves money from a SOURCE (`payment` → `billing_payments`, or
|
|
10
|
+
* `credit_memo` → a posted credit memo in `billing_invoices`) to a receivable
|
|
11
|
+
* (`invoice` / `debit_memo`). Rules:
|
|
12
|
+
*
|
|
13
|
+
* - Receivable amounts are RECOMPUTED from Σ active allocations, never
|
|
14
|
+
* incremented: `paid_total` (payment sources), `credited_total` (credit
|
|
15
|
+
* memo sources), `outstanding = gross − paid − credited`, status
|
|
16
|
+
* `posted | partially_paid | paid | credited` (P-D2, Spec D P-D3).
|
|
17
|
+
* - A source's unallocated amount (payment `unallocated_amount`, credit memo
|
|
18
|
+
* `outstanding`) is decremented with a guarded UPDATE, so it can never go
|
|
19
|
+
* negative even if a caller forgets the account lock.
|
|
20
|
+
* - Callers hold `withAccountLock` (P-D3); every function here takes the
|
|
21
|
+
* transactional EntityManager of that lock.
|
|
22
|
+
* - Unallocated money of an account is ACCOUNT CREDIT; `applyAccountCredit`
|
|
23
|
+
* spends it on open receivables, oldest due date first (D12, P-D5).
|
|
24
|
+
*
|
|
25
|
+
* Money is handled in integer cents internally (2 dp documents).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ─── Pure helpers ────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export function toCents(value: string | number | null | undefined): number {
|
|
31
|
+
const n = typeof value === 'number' ? value : Number.parseFloat(value ?? '0')
|
|
32
|
+
return Number.isFinite(n) ? Math.round(n * 100) : 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function fromCents(cents: number): string {
|
|
36
|
+
return (cents / 100).toFixed(4)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type OpenReceivable = {
|
|
40
|
+
id: string
|
|
41
|
+
outstandingCents: number
|
|
42
|
+
dueDate: string | null
|
|
43
|
+
issueDate: string | null
|
|
44
|
+
number: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Oldest due date first (nulls last), then issue date, then number. */
|
|
48
|
+
export function sortOpenReceivables<T extends Omit<OpenReceivable, 'outstandingCents'>>(rows: T[]): T[] {
|
|
49
|
+
return [...rows].sort((a, b) => {
|
|
50
|
+
if (a.dueDate !== b.dueDate) {
|
|
51
|
+
if (!a.dueDate) return 1
|
|
52
|
+
if (!b.dueDate) return -1
|
|
53
|
+
return a.dueDate < b.dueDate ? -1 : 1
|
|
54
|
+
}
|
|
55
|
+
if (a.issueDate !== b.issueDate) {
|
|
56
|
+
if (!a.issueDate) return 1
|
|
57
|
+
if (!b.issueDate) return -1
|
|
58
|
+
return a.issueDate < b.issueDate ? -1 : 1
|
|
59
|
+
}
|
|
60
|
+
return a.number.localeCompare(b.number)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Spreads `amountCents` over receivables in the given order, never above outstanding. */
|
|
65
|
+
export function planAllocation(
|
|
66
|
+
amountCents: number,
|
|
67
|
+
receivables: ReadonlyArray<Pick<OpenReceivable, 'id' | 'outstandingCents'>>,
|
|
68
|
+
): { allocations: Array<{ invoiceId: string; amountCents: number }>; remainderCents: number } {
|
|
69
|
+
let remaining = Math.max(0, amountCents)
|
|
70
|
+
const allocations: Array<{ invoiceId: string; amountCents: number }> = []
|
|
71
|
+
for (const receivable of receivables) {
|
|
72
|
+
if (remaining <= 0) break
|
|
73
|
+
const take = Math.min(remaining, Math.max(0, receivable.outstandingCents))
|
|
74
|
+
if (take <= 0) continue
|
|
75
|
+
allocations.push({ invoiceId: receivable.id, amountCents: take })
|
|
76
|
+
remaining -= take
|
|
77
|
+
}
|
|
78
|
+
return { allocations, remainderCents: remaining }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ReceivableState = {
|
|
82
|
+
status: string
|
|
83
|
+
totalGrossCents: number
|
|
84
|
+
paidCents: number
|
|
85
|
+
creditedCents: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Paid / credited / outstanding / status of a receivable from its allocation sums. */
|
|
89
|
+
export function recomputeReceivable(state: ReceivableState): {
|
|
90
|
+
paidCents: number
|
|
91
|
+
creditedCents: number
|
|
92
|
+
outstandingCents: number
|
|
93
|
+
status: string
|
|
94
|
+
} {
|
|
95
|
+
const { totalGrossCents: gross, paidCents: paid, creditedCents: credited } = state
|
|
96
|
+
const outstandingCents = Math.max(0, gross - paid - credited)
|
|
97
|
+
if (state.status === 'draft') return { paidCents: paid, creditedCents: credited, outstandingCents, status: 'draft' }
|
|
98
|
+
let status: string
|
|
99
|
+
if (gross > 0 && outstandingCents === 0) status = credited >= gross ? 'credited' : 'paid'
|
|
100
|
+
else if (paid + credited > 0) status = 'partially_paid'
|
|
101
|
+
else status = 'posted'
|
|
102
|
+
return { paidCents: paid, creditedCents: credited, outstandingCents, status }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Payment status from its allocation state (terminal / unmatched states are kept). */
|
|
106
|
+
export function paymentStatusFor(payment: {
|
|
107
|
+
status: string
|
|
108
|
+
billAccountId: string | null | undefined
|
|
109
|
+
amountCents: number
|
|
110
|
+
unallocatedCents: number
|
|
111
|
+
}): string {
|
|
112
|
+
if (payment.status === 'ignored' || payment.status === 'converted_to_topup') return payment.status
|
|
113
|
+
if (!payment.billAccountId) return 'unmatched'
|
|
114
|
+
if (payment.unallocatedCents <= 0) return 'allocated'
|
|
115
|
+
if (payment.unallocatedCents < payment.amountCents) return 'partially_allocated'
|
|
116
|
+
return 'matched'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Effects (emitted by callers after commit) ───────────────────
|
|
120
|
+
|
|
121
|
+
export type InvoiceStatusChange = { invoiceId: string; from: string; to: string }
|
|
122
|
+
|
|
123
|
+
export type AllocationEffects = {
|
|
124
|
+
created: Array<{ allocationId: string; sourceType: AllocationSourceType; sourceId: string; invoiceId: string; amount: string }>
|
|
125
|
+
removed: Array<{ allocationId: string; sourceType: AllocationSourceType; sourceId: string; invoiceId: string; amount: string }>
|
|
126
|
+
invoiceStatusChanges: InvoiceStatusChange[]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function emptyEffects(): AllocationEffects {
|
|
130
|
+
return { created: [], removed: [], invoiceStatusChanges: [] }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function mergeEffects(target: AllocationEffects, extra: AllocationEffects): AllocationEffects {
|
|
134
|
+
target.created.push(...extra.created)
|
|
135
|
+
target.removed.push(...extra.removed)
|
|
136
|
+
target.invoiceStatusChanges.push(...extra.invoiceStatusChanges)
|
|
137
|
+
return target
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── DB service (inside withAccountLock) ─────────────────────────
|
|
141
|
+
|
|
142
|
+
export type ArScope = { tenantId: string; organizationId: string }
|
|
143
|
+
|
|
144
|
+
export type AllocationSource = { type: AllocationSourceType; id: string }
|
|
145
|
+
|
|
146
|
+
type ReceivableRow = {
|
|
147
|
+
id: string
|
|
148
|
+
bill_account_id: string
|
|
149
|
+
currency_code: string
|
|
150
|
+
status: string
|
|
151
|
+
document_type: string
|
|
152
|
+
test_run: boolean
|
|
153
|
+
total_gross: string
|
|
154
|
+
outstanding: string
|
|
155
|
+
due_date: string | null
|
|
156
|
+
issue_date: string | null
|
|
157
|
+
number: string
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type SourceRow = {
|
|
161
|
+
id: string
|
|
162
|
+
bill_account_id: string | null
|
|
163
|
+
currency_code: string
|
|
164
|
+
unallocated: string
|
|
165
|
+
status: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const RECEIVABLE_TYPES = ['invoice', 'debit_memo']
|
|
169
|
+
const OPEN_STATUSES = ['posted', 'partially_paid']
|
|
170
|
+
|
|
171
|
+
function isoDay(value: unknown): string | null {
|
|
172
|
+
if (!value) return null
|
|
173
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10)
|
|
174
|
+
return String(value).slice(0, 10)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function loadReceivable(tem: EntityManager, scope: ArScope, invoiceId: string): Promise<ReceivableRow | null> {
|
|
178
|
+
const rows = (await tem.execute(
|
|
179
|
+
`SELECT id, bill_account_id, currency_code, status, document_type, test_run, total_gross, outstanding,
|
|
180
|
+
due_date, issue_date, number
|
|
181
|
+
FROM billing_invoices
|
|
182
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
|
|
183
|
+
[invoiceId, scope.tenantId, scope.organizationId],
|
|
184
|
+
)) as unknown as ReceivableRow[]
|
|
185
|
+
return rows?.[0] ?? null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function loadSource(tem: EntityManager, scope: ArScope, source: AllocationSource): Promise<SourceRow | null> {
|
|
189
|
+
const sql =
|
|
190
|
+
source.type === 'payment'
|
|
191
|
+
? `SELECT id, bill_account_id, currency_code, unallocated_amount AS unallocated, status
|
|
192
|
+
FROM billing_payments
|
|
193
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`
|
|
194
|
+
: `SELECT id, bill_account_id, currency_code, outstanding AS unallocated, status
|
|
195
|
+
FROM billing_invoices
|
|
196
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
|
|
197
|
+
AND document_type = 'credit_memo'`
|
|
198
|
+
const rows = (await tem.execute(sql, [source.id, scope.tenantId, scope.organizationId])) as unknown as SourceRow[]
|
|
199
|
+
return rows?.[0] ?? null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Guarded decrement (negative `deltaCents` restores). 0 rows → insufficient. */
|
|
203
|
+
async function adjustSourceUnallocated(
|
|
204
|
+
tem: EntityManager,
|
|
205
|
+
scope: ArScope,
|
|
206
|
+
source: AllocationSource,
|
|
207
|
+
deltaCents: number,
|
|
208
|
+
): Promise<boolean> {
|
|
209
|
+
const amount = fromCents(Math.abs(deltaCents))
|
|
210
|
+
if (source.type === 'payment') {
|
|
211
|
+
const rows = (await tem.execute(
|
|
212
|
+
deltaCents >= 0
|
|
213
|
+
? `UPDATE billing_payments
|
|
214
|
+
SET unallocated_amount = unallocated_amount - ?::numeric, updated_at = now()
|
|
215
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND unallocated_amount >= ?::numeric
|
|
216
|
+
RETURNING id`
|
|
217
|
+
: `UPDATE billing_payments
|
|
218
|
+
SET unallocated_amount = LEAST(amount, unallocated_amount + ?::numeric), updated_at = now()
|
|
219
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ?
|
|
220
|
+
RETURNING id`,
|
|
221
|
+
deltaCents >= 0
|
|
222
|
+
? [amount, source.id, scope.tenantId, scope.organizationId, amount]
|
|
223
|
+
: [amount, source.id, scope.tenantId, scope.organizationId],
|
|
224
|
+
)) as unknown as Array<{ id: string }>
|
|
225
|
+
if (!rows?.length) return false
|
|
226
|
+
await tem.execute(
|
|
227
|
+
`UPDATE billing_payments
|
|
228
|
+
SET status = CASE
|
|
229
|
+
WHEN unallocated_amount <= 0 THEN 'allocated'
|
|
230
|
+
WHEN unallocated_amount < amount THEN 'partially_allocated'
|
|
231
|
+
ELSE 'matched' END
|
|
232
|
+
WHERE id = ? AND status IN ('matched', 'partially_allocated', 'allocated')`,
|
|
233
|
+
[source.id],
|
|
234
|
+
)
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
// Credit memo: its `outstanding` is the unallocated credit (Spec D, P-D5).
|
|
238
|
+
const rows = (await tem.execute(
|
|
239
|
+
deltaCents >= 0
|
|
240
|
+
? `UPDATE billing_invoices
|
|
241
|
+
SET outstanding = outstanding - ?::numeric,
|
|
242
|
+
status = CASE WHEN outstanding - ?::numeric <= 0 THEN 'paid' ELSE 'posted' END,
|
|
243
|
+
updated_at = now()
|
|
244
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND document_type = 'credit_memo'
|
|
245
|
+
AND outstanding >= ?::numeric
|
|
246
|
+
RETURNING id`
|
|
247
|
+
: `UPDATE billing_invoices
|
|
248
|
+
SET outstanding = LEAST(total_gross, outstanding + ?::numeric), status = 'posted', updated_at = now()
|
|
249
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND document_type = 'credit_memo'
|
|
250
|
+
RETURNING id`,
|
|
251
|
+
deltaCents >= 0
|
|
252
|
+
? [amount, amount, source.id, scope.tenantId, scope.organizationId, amount]
|
|
253
|
+
: [amount, source.id, scope.tenantId, scope.organizationId],
|
|
254
|
+
)) as unknown as Array<{ id: string }>
|
|
255
|
+
return Boolean(rows?.length)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Recomputes paid / credited / outstanding / status of a receivable from its active allocations. */
|
|
259
|
+
export async function recomputeInvoice(
|
|
260
|
+
tem: EntityManager,
|
|
261
|
+
scope: ArScope,
|
|
262
|
+
invoiceId: string,
|
|
263
|
+
): Promise<InvoiceStatusChange | null> {
|
|
264
|
+
const invoice = await loadReceivable(tem, scope, invoiceId)
|
|
265
|
+
if (!invoice || !RECEIVABLE_TYPES.includes(invoice.document_type)) return null
|
|
266
|
+
const sums = (await tem.execute(
|
|
267
|
+
`SELECT COALESCE(SUM(amount) FILTER (WHERE source_type = 'payment'), 0) AS paid,
|
|
268
|
+
COALESCE(SUM(amount) FILTER (WHERE source_type = 'credit_memo'), 0) AS credited
|
|
269
|
+
FROM billing_allocations
|
|
270
|
+
WHERE invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
|
|
271
|
+
[invoiceId, scope.tenantId, scope.organizationId],
|
|
272
|
+
)) as unknown as Array<{ paid: string; credited: string }>
|
|
273
|
+
const next = recomputeReceivable({
|
|
274
|
+
status: invoice.status,
|
|
275
|
+
totalGrossCents: toCents(invoice.total_gross),
|
|
276
|
+
paidCents: toCents(sums?.[0]?.paid),
|
|
277
|
+
creditedCents: toCents(sums?.[0]?.credited),
|
|
278
|
+
})
|
|
279
|
+
await tem.execute(
|
|
280
|
+
`UPDATE billing_invoices
|
|
281
|
+
SET paid_total = ?::numeric, credited_total = ?::numeric, outstanding = ?::numeric, status = ?, updated_at = now()
|
|
282
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
|
|
283
|
+
[
|
|
284
|
+
fromCents(next.paidCents),
|
|
285
|
+
fromCents(next.creditedCents),
|
|
286
|
+
fromCents(next.outstandingCents),
|
|
287
|
+
next.status,
|
|
288
|
+
invoiceId,
|
|
289
|
+
scope.tenantId,
|
|
290
|
+
scope.organizationId,
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
return next.status !== invoice.status ? { invoiceId, from: invoice.status, to: next.status } : null
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function allocate(
|
|
297
|
+
tem: EntityManager,
|
|
298
|
+
scope: ArScope,
|
|
299
|
+
params: { source: AllocationSource; invoiceId: string; amountCents: number; by: AllocatedBy; userId?: string | null },
|
|
300
|
+
): Promise<AllocationEffects> {
|
|
301
|
+
const effects = emptyEffects()
|
|
302
|
+
if (params.amountCents <= 0) {
|
|
303
|
+
throw new CrudHttpError(400, { error: 'Allocation amount must be positive', code: 'billing.allocation.invalid_amount' })
|
|
304
|
+
}
|
|
305
|
+
const [source, invoice] = await Promise.all([
|
|
306
|
+
loadSource(tem, scope, params.source),
|
|
307
|
+
loadReceivable(tem, scope, params.invoiceId),
|
|
308
|
+
])
|
|
309
|
+
if (!source) throw new CrudHttpError(404, { error: 'Payment source not found', code: 'billing.allocation.source_missing' })
|
|
310
|
+
if (!invoice) throw new CrudHttpError(404, { error: 'Invoice not found', code: 'billing.allocation.invoice_missing' })
|
|
311
|
+
if (!source.bill_account_id || source.bill_account_id !== invoice.bill_account_id) {
|
|
312
|
+
throw new CrudHttpError(409, {
|
|
313
|
+
error: 'Payment and invoice belong to different accounts',
|
|
314
|
+
code: 'billing.allocation.account_mismatch',
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
if (source.currency_code !== invoice.currency_code) {
|
|
318
|
+
throw new CrudHttpError(409, { error: 'Currencies differ', code: 'billing.allocation.currency_mismatch' })
|
|
319
|
+
}
|
|
320
|
+
if (
|
|
321
|
+
invoice.test_run ||
|
|
322
|
+
!RECEIVABLE_TYPES.includes(invoice.document_type) ||
|
|
323
|
+
!OPEN_STATUSES.includes(invoice.status)
|
|
324
|
+
) {
|
|
325
|
+
throw new CrudHttpError(409, { error: 'Invoice is not open for payment', code: 'billing.allocation.invoice_not_open' })
|
|
326
|
+
}
|
|
327
|
+
if (params.amountCents > toCents(invoice.outstanding)) {
|
|
328
|
+
throw new CrudHttpError(409, {
|
|
329
|
+
error: 'Allocation exceeds the invoice outstanding amount',
|
|
330
|
+
code: 'billing.allocation.exceeds_outstanding',
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
const decremented = await adjustSourceUnallocated(tem, scope, params.source, params.amountCents)
|
|
334
|
+
if (!decremented) {
|
|
335
|
+
throw new CrudHttpError(409, {
|
|
336
|
+
error: 'Not enough unallocated amount',
|
|
337
|
+
code: 'billing.allocation.insufficient_unallocated',
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
const now = new Date()
|
|
341
|
+
const row = tem.create(BillingAllocation, {
|
|
342
|
+
organizationId: scope.organizationId,
|
|
343
|
+
tenantId: scope.tenantId,
|
|
344
|
+
sourceType: params.source.type,
|
|
345
|
+
sourceId: params.source.id,
|
|
346
|
+
invoiceId: params.invoiceId,
|
|
347
|
+
billAccountId: invoice.bill_account_id,
|
|
348
|
+
amount: fromCents(params.amountCents),
|
|
349
|
+
currencyCode: invoice.currency_code,
|
|
350
|
+
allocatedBy: params.by,
|
|
351
|
+
userId: params.userId ?? null,
|
|
352
|
+
createdAt: now,
|
|
353
|
+
updatedAt: now,
|
|
354
|
+
})
|
|
355
|
+
tem.persist(row)
|
|
356
|
+
await tem.flush()
|
|
357
|
+
effects.created.push({
|
|
358
|
+
allocationId: row.id,
|
|
359
|
+
sourceType: params.source.type,
|
|
360
|
+
sourceId: params.source.id,
|
|
361
|
+
invoiceId: params.invoiceId,
|
|
362
|
+
amount: row.amount,
|
|
363
|
+
})
|
|
364
|
+
const change = await recomputeInvoice(tem, scope, params.invoiceId)
|
|
365
|
+
if (change) effects.invoiceStatusChanges.push(change)
|
|
366
|
+
return effects
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export async function unallocate(
|
|
370
|
+
tem: EntityManager,
|
|
371
|
+
scope: ArScope,
|
|
372
|
+
params: { allocationId: string; userId?: string | null },
|
|
373
|
+
): Promise<AllocationEffects> {
|
|
374
|
+
const effects = emptyEffects()
|
|
375
|
+
const rows = (await tem.execute(
|
|
376
|
+
`UPDATE billing_allocations
|
|
377
|
+
SET deleted_at = now(), updated_at = now(), deallocated_by_user_id = ?
|
|
378
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
|
|
379
|
+
RETURNING id, source_type, source_id, invoice_id, amount`,
|
|
380
|
+
[params.userId ?? null, params.allocationId, scope.tenantId, scope.organizationId],
|
|
381
|
+
)) as unknown as Array<{ id: string; source_type: AllocationSourceType; source_id: string; invoice_id: string; amount: string }>
|
|
382
|
+
const removed = rows?.[0]
|
|
383
|
+
if (!removed) throw new CrudHttpError(404, { error: 'Allocation not found', code: 'billing.allocation.not_found' })
|
|
384
|
+
await adjustSourceUnallocated(tem, scope, { type: removed.source_type, id: removed.source_id }, -toCents(removed.amount))
|
|
385
|
+
effects.removed.push({
|
|
386
|
+
allocationId: removed.id,
|
|
387
|
+
sourceType: removed.source_type,
|
|
388
|
+
sourceId: removed.source_id,
|
|
389
|
+
invoiceId: removed.invoice_id,
|
|
390
|
+
amount: removed.amount,
|
|
391
|
+
})
|
|
392
|
+
const change = await recomputeInvoice(tem, scope, removed.invoice_id)
|
|
393
|
+
if (change) effects.invoiceStatusChanges.push(change)
|
|
394
|
+
return effects
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Open receivables of an account in auto-apply order. */
|
|
398
|
+
export async function loadOpenReceivables(
|
|
399
|
+
tem: EntityManager,
|
|
400
|
+
scope: ArScope,
|
|
401
|
+
account: { id: string; currencyCode: string },
|
|
402
|
+
onlyIds?: string[],
|
|
403
|
+
): Promise<OpenReceivable[]> {
|
|
404
|
+
const params: unknown[] = [scope.tenantId, scope.organizationId, account.id, account.currencyCode]
|
|
405
|
+
let idFilter = ''
|
|
406
|
+
if (onlyIds && onlyIds.length > 0) {
|
|
407
|
+
idFilter = ` AND id = ANY(?::uuid[])`
|
|
408
|
+
params.push(`{${onlyIds.join(',')}}`)
|
|
409
|
+
}
|
|
410
|
+
const rows = (await tem.execute(
|
|
411
|
+
`SELECT id, outstanding, due_date, issue_date, number
|
|
412
|
+
FROM billing_invoices
|
|
413
|
+
WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
|
|
414
|
+
AND deleted_at IS NULL AND test_run = false
|
|
415
|
+
AND document_type IN ('invoice', 'debit_memo')
|
|
416
|
+
AND status IN ('posted', 'partially_paid')
|
|
417
|
+
AND outstanding > 0${idFilter}`,
|
|
418
|
+
params,
|
|
419
|
+
)) as unknown as Array<{ id: string; outstanding: string; due_date: unknown; issue_date: unknown; number: string }>
|
|
420
|
+
return sortOpenReceivables(
|
|
421
|
+
(rows ?? []).map((row) => ({
|
|
422
|
+
id: row.id,
|
|
423
|
+
outstandingCents: toCents(row.outstanding),
|
|
424
|
+
dueDate: isoDay(row.due_date),
|
|
425
|
+
issueDate: isoDay(row.issue_date),
|
|
426
|
+
number: row.number,
|
|
427
|
+
})),
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Credit sources of an account (unallocated payments + open credit memos), oldest first. */
|
|
432
|
+
export async function loadCreditSources(
|
|
433
|
+
tem: EntityManager,
|
|
434
|
+
scope: ArScope,
|
|
435
|
+
account: { id: string; currencyCode: string },
|
|
436
|
+
): Promise<Array<{ source: AllocationSource; availableCents: number }>> {
|
|
437
|
+
const rows = (await tem.execute(
|
|
438
|
+
`SELECT 'payment' AS source_type, id, unallocated_amount AS available, received_at AS at, created_at
|
|
439
|
+
FROM billing_payments
|
|
440
|
+
WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
|
|
441
|
+
AND deleted_at IS NULL AND status IN ('matched', 'partially_allocated') AND unallocated_amount > 0
|
|
442
|
+
UNION ALL
|
|
443
|
+
SELECT 'credit_memo' AS source_type, id, outstanding AS available, issue_date AS at, created_at
|
|
444
|
+
FROM billing_invoices
|
|
445
|
+
WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
|
|
446
|
+
AND deleted_at IS NULL AND test_run = false AND document_type = 'credit_memo'
|
|
447
|
+
AND status = 'posted' AND outstanding > 0
|
|
448
|
+
ORDER BY at ASC, created_at ASC`,
|
|
449
|
+
[
|
|
450
|
+
scope.tenantId,
|
|
451
|
+
scope.organizationId,
|
|
452
|
+
account.id,
|
|
453
|
+
account.currencyCode,
|
|
454
|
+
scope.tenantId,
|
|
455
|
+
scope.organizationId,
|
|
456
|
+
account.id,
|
|
457
|
+
account.currencyCode,
|
|
458
|
+
],
|
|
459
|
+
)) as unknown as Array<{ source_type: AllocationSourceType; id: string; available: string }>
|
|
460
|
+
return (rows ?? []).map((row) => ({
|
|
461
|
+
source: { type: row.source_type, id: row.id },
|
|
462
|
+
availableCents: toCents(row.available),
|
|
463
|
+
}))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Spends the account's credit on its open receivables (D12): sources oldest
|
|
468
|
+
* first, receivables oldest due first. `onlySources` restricts the spend
|
|
469
|
+
* (e.g. the payment just imported); default = all credit of the account.
|
|
470
|
+
*/
|
|
471
|
+
export async function applyAccountCredit(
|
|
472
|
+
tem: EntityManager,
|
|
473
|
+
scope: ArScope,
|
|
474
|
+
account: { id: string; currencyCode: string },
|
|
475
|
+
opts: { onlySources?: AllocationSource[]; by?: AllocatedBy } = {},
|
|
476
|
+
): Promise<AllocationEffects> {
|
|
477
|
+
const effects = emptyEffects()
|
|
478
|
+
let sources = await loadCreditSources(tem, scope, account)
|
|
479
|
+
if (opts.onlySources) {
|
|
480
|
+
const wanted = new Set(opts.onlySources.map((s) => `${s.type}:${s.id}`))
|
|
481
|
+
sources = sources.filter((s) => wanted.has(`${s.source.type}:${s.source.id}`))
|
|
482
|
+
}
|
|
483
|
+
if (sources.length === 0) return effects
|
|
484
|
+
const receivables = await loadOpenReceivables(tem, scope, account)
|
|
485
|
+
for (const credit of sources) {
|
|
486
|
+
let available = credit.availableCents
|
|
487
|
+
for (const receivable of receivables) {
|
|
488
|
+
if (available <= 0) break
|
|
489
|
+
const take = Math.min(available, receivable.outstandingCents)
|
|
490
|
+
if (take <= 0) continue
|
|
491
|
+
mergeEffects(
|
|
492
|
+
effects,
|
|
493
|
+
await allocate(tem, scope, {
|
|
494
|
+
source: credit.source,
|
|
495
|
+
invoiceId: receivable.id,
|
|
496
|
+
amountCents: take,
|
|
497
|
+
by: opts.by ?? 'credit',
|
|
498
|
+
}),
|
|
499
|
+
)
|
|
500
|
+
receivable.outstandingCents -= take
|
|
501
|
+
available -= take
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return effects
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Unapplied credit of an account (Σ unallocated payments + open credit memos). */
|
|
508
|
+
export async function getAccountCredit(
|
|
509
|
+
em: EntityManager,
|
|
510
|
+
scope: ArScope,
|
|
511
|
+
account: { id: string; currencyCode: string },
|
|
512
|
+
): Promise<string> {
|
|
513
|
+
const sources = await loadCreditSources(em, scope, account)
|
|
514
|
+
return fromCents(sources.reduce((sum, s) => sum + s.availableCents, 0))
|
|
515
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { emitBillingEvent } from '../events'
|
|
2
|
+
import type { AllocationEffects, ArScope } from './allocation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Emits the events of an allocation change AFTER the transaction committed
|
|
6
|
+
* (Spec C): one `billing.payment.allocated` / `unallocated` per payment
|
|
7
|
+
* allocation row, and `billing.invoice.paid` / `partially_paid` / `credited` (Spec D) per receivable whose status changed.
|
|
8
|
+
*/
|
|
9
|
+
export async function emitAllocationEffects(scope: ArScope, effects: AllocationEffects): Promise<void> {
|
|
10
|
+
for (const row of effects.created) {
|
|
11
|
+
if (row.sourceType !== 'payment') continue
|
|
12
|
+
await emitBillingEvent('billing.payment.allocated', { ...scope, paymentId: row.sourceId, invoiceId: row.invoiceId, allocationId: row.allocationId, amount: row.amount })
|
|
13
|
+
}
|
|
14
|
+
for (const row of effects.removed) {
|
|
15
|
+
if (row.sourceType !== 'payment') continue
|
|
16
|
+
await emitBillingEvent('billing.payment.unallocated', { ...scope, paymentId: row.sourceId, invoiceId: row.invoiceId, allocationId: row.allocationId, amount: row.amount })
|
|
17
|
+
}
|
|
18
|
+
// Last transition per invoice wins (several allocations can touch one invoice).
|
|
19
|
+
const finalStatus = new Map<string, string>()
|
|
20
|
+
for (const change of effects.invoiceStatusChanges) finalStatus.set(change.invoiceId, change.to)
|
|
21
|
+
for (const [invoiceId, status] of finalStatus) {
|
|
22
|
+
if (status === 'paid') await emitBillingEvent('billing.invoice.paid', { ...scope, invoiceId })
|
|
23
|
+
else if (status === 'partially_paid') await emitBillingEvent('billing.invoice.partially_paid', { ...scope, invoiceId })
|
|
24
|
+
else if (status === 'credited') await emitBillingEvent('billing.invoice.credited', { ...scope, invoiceId })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -173,6 +173,25 @@ export function isCycleDue(nextBillDate: Date, asOfDate: Date): boolean {
|
|
|
173
173
|
return a <= b
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The cycle boundary `k` cycles away from `anchor` (k may be negative),
|
|
178
|
+
* computed directly from the anchor — never by chaining single steps — so
|
|
179
|
+
* month-end clamping does not drift (Jan 31 → Feb 28 → Mar 31, not Mar 28).
|
|
180
|
+
*/
|
|
181
|
+
export function shiftCycles(anchor: Date | string | number, cycle: BillCycle, k: number): Date {
|
|
182
|
+
const base = cloneAtMidnight(anchor)
|
|
183
|
+
switch (cycle) {
|
|
184
|
+
case 'weekly':
|
|
185
|
+
return addDays(base, 7 * k)
|
|
186
|
+
case 'monthly':
|
|
187
|
+
return shiftMonths(base, k)
|
|
188
|
+
case 'quarterly':
|
|
189
|
+
return shiftMonths(base, 3 * k)
|
|
190
|
+
case 'annually':
|
|
191
|
+
return shiftYears(base, k)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
/**
|
|
177
196
|
* Test seam: re-export the day-add helper so the engine can compute
|
|
178
197
|
* `bill_period_end + 1 day` consistently with this module's calendar
|