@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
|
@@ -1,24 +1,35 @@
|
|
|
1
|
+
import { UniqueConstraintViolationException } from "@mikro-orm/core";
|
|
1
2
|
import {
|
|
2
3
|
BillingAccount,
|
|
3
4
|
BillingAccountUsage,
|
|
5
|
+
BillingInvoice,
|
|
4
6
|
BillingItem,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
BillingRun,
|
|
8
|
+
BillingRunOutcome
|
|
7
9
|
} from "../data/entities.js";
|
|
8
|
-
import { advanceNextBillDate, deriveBillPeriod, isCycleDue } from "./billPeriod.js";
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
addDays,
|
|
12
|
+
advanceNextBillDate,
|
|
13
|
+
cloneAtMidnight,
|
|
14
|
+
deriveBillPeriod,
|
|
15
|
+
isCycleDue,
|
|
16
|
+
shiftCycles
|
|
17
|
+
} from "./billPeriod.js";
|
|
18
|
+
import { shouldIncludeItem } from "./itemSelector.js";
|
|
12
19
|
import {
|
|
13
20
|
buildInvoiceLinesFromItems,
|
|
21
|
+
buildRecurringLineDescriptors,
|
|
22
|
+
displayPeriod,
|
|
14
23
|
writeDraftInvoice
|
|
15
24
|
} from "./invoiceWriter.js";
|
|
25
|
+
import { computeRecurringLines } from "./servicePeriods.js";
|
|
16
26
|
import { withTenantLock } from "./tenantLock.js";
|
|
17
27
|
import { processUsageForAccount } from "./usageRunner.js";
|
|
18
28
|
import { processPrepaidAccount } from "./prepaidPeriodClose.js";
|
|
19
29
|
import { emitBillingEvent } from "../events.js";
|
|
20
|
-
const SCHEDULED_CYCLE_LIMIT = 1;
|
|
21
30
|
const CATCH_UP_HARD_CAP = 60;
|
|
31
|
+
const SKIPPED_ITEMS_WARNING_CAP = 200;
|
|
32
|
+
const QUIET_SKIP_REASONS = /* @__PURE__ */ new Set(["item-inactive", "item-soft-deleted"]);
|
|
22
33
|
async function runBillRun(em, container, params) {
|
|
23
34
|
const output = await withTenantLock(
|
|
24
35
|
em,
|
|
@@ -32,14 +43,13 @@ async function runBillRun(em, container, params) {
|
|
|
32
43
|
}
|
|
33
44
|
async function executeRunWithinLock(em, container, params) {
|
|
34
45
|
const now = /* @__PURE__ */ new Date();
|
|
35
|
-
const billRun = em.create(
|
|
46
|
+
const billRun = em.create(BillingRun, {
|
|
36
47
|
organizationId: params.organizationId,
|
|
37
48
|
tenantId: params.tenantId,
|
|
38
49
|
triggeredBy: params.triggeredBy,
|
|
39
50
|
parentRunId: params.parentRunId ?? null,
|
|
40
51
|
dryRun: params.mode === "dry",
|
|
41
52
|
testMode: params.mode === "test",
|
|
42
|
-
catchUp: Boolean(params.catchUp),
|
|
43
53
|
scopedAccountIds: params.scopedAccountIds ?? null,
|
|
44
54
|
asOfDate: params.asOfDate,
|
|
45
55
|
startedAt: now,
|
|
@@ -60,7 +70,7 @@ async function executeRunWithinLock(em, container, params) {
|
|
|
60
70
|
if (processResult.status !== "failed") {
|
|
61
71
|
totalUsageRecordsRated += processResult.usageRecordsRated;
|
|
62
72
|
}
|
|
63
|
-
const outcome = em.create(
|
|
73
|
+
const outcome = em.create(BillingRunOutcome, {
|
|
64
74
|
organizationId: params.organizationId,
|
|
65
75
|
tenantId: params.tenantId,
|
|
66
76
|
billRunId: billRun.id,
|
|
@@ -70,6 +80,7 @@ async function executeRunWithinLock(em, container, params) {
|
|
|
70
80
|
warnings: processResult.status === "failed" ? null : processResult.warnings,
|
|
71
81
|
draftInvoiceId: processResult.status === "failed" ? null : processResult.draftInvoiceId,
|
|
72
82
|
statementId: processResult.status === "failed" ? null : processResult.statementId ?? null,
|
|
83
|
+
cycles: processResult.status === "failed" ? null : processResult.cycles ?? null,
|
|
73
84
|
isActive: true,
|
|
74
85
|
createdAt: /* @__PURE__ */ new Date(),
|
|
75
86
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -117,8 +128,7 @@ async function processAccount(em, container, billRun, account, params) {
|
|
|
117
128
|
const result = await processPrepaidAccount(em, account, billRun, {
|
|
118
129
|
asOfDate: params.asOfDate,
|
|
119
130
|
mode: params.mode,
|
|
120
|
-
triggeredBy: params.triggeredBy
|
|
121
|
-
catchUp: params.catchUp
|
|
131
|
+
triggeredBy: params.triggeredBy
|
|
122
132
|
});
|
|
123
133
|
if (result.status === "failed") {
|
|
124
134
|
return { status: "failed", errorMessage: result.errorMessage };
|
|
@@ -130,76 +140,190 @@ async function processAccount(em, container, billRun, account, params) {
|
|
|
130
140
|
statementId: result.statementId,
|
|
131
141
|
statementEvents: result.statementEvents,
|
|
132
142
|
cyclesEmitted: result.cyclesEmitted,
|
|
143
|
+
cycles: result.cycles,
|
|
133
144
|
usageRecordsRated: 0
|
|
134
145
|
};
|
|
135
146
|
}
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
return processPostpaidAccount(em, container, billRun, account, params);
|
|
148
|
+
}
|
|
149
|
+
function isoDate(date) {
|
|
150
|
+
return cloneAtMidnight(date).toISOString().slice(0, 10);
|
|
151
|
+
}
|
|
152
|
+
function toSkipWarning(skip) {
|
|
153
|
+
return {
|
|
154
|
+
item_id: skip.billingItemId,
|
|
155
|
+
reason: skip.reason,
|
|
156
|
+
service_period_start: skip.servicePeriodStart ? isoDate(skip.servicePeriodStart) : null,
|
|
157
|
+
service_period_end: skip.servicePeriodEnd ? isoDate(skip.servicePeriodEnd) : null
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function processPostpaidAccount(em, container, billRun, account, params) {
|
|
161
|
+
const cycle = account.billCycle;
|
|
162
|
+
const testRun = params.mode === "test";
|
|
163
|
+
const items = await em.find(BillingItem, {
|
|
164
|
+
tenantId: account.tenantId,
|
|
165
|
+
organizationId: account.organizationId,
|
|
166
|
+
billAccountId: account.id,
|
|
167
|
+
deletedAt: null
|
|
168
|
+
});
|
|
169
|
+
const recurringItems = items.filter((it) => it.type === "recurring");
|
|
170
|
+
const oneTimeItems = items.filter((it) => it.type === "one_time");
|
|
171
|
+
const usageItems = items.filter((it) => it.type === "usage");
|
|
172
|
+
const watermarks = new Map(
|
|
173
|
+
recurringItems.map((it) => [it.id, it.billedToDate ? cloneAtMidnight(it.billedToDate) : null])
|
|
174
|
+
);
|
|
175
|
+
const billedOneTimes = new Set(oneTimeItems.filter((it) => it.billedToDate).map((it) => it.id));
|
|
176
|
+
let billDate = cloneAtMidnight(account.nextBillDate);
|
|
177
|
+
let usageCountedThrough = null;
|
|
178
|
+
const cycles = [];
|
|
179
|
+
const warnings = {};
|
|
180
|
+
const skippedItems = [];
|
|
138
181
|
let lastDraftInvoiceId = null;
|
|
139
|
-
let
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const existing = await findExistingDraftForPeriod(em, account, period);
|
|
145
|
-
if (existing) {
|
|
146
|
-
return {
|
|
147
|
-
status: "skipped_existing_draft",
|
|
148
|
-
warnings: null,
|
|
149
|
-
draftInvoiceId: existing,
|
|
150
|
-
cyclesEmitted,
|
|
151
|
-
usageRecordsRated: totalUsageRatedThisAccount
|
|
152
|
-
};
|
|
153
|
-
}
|
|
182
|
+
let usageRecordsRated = 0;
|
|
183
|
+
const pushSkips = (skips) => {
|
|
184
|
+
for (const skip of skips) {
|
|
185
|
+
if (QUIET_SKIP_REASONS.has(skip.reason)) continue;
|
|
186
|
+
if (skippedItems.length < SKIPPED_ITEMS_WARNING_CAP) skippedItems.push(toSkipWarning(skip));
|
|
154
187
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const currencyMismatchIds = billableItems.filter((it) => it.currencyMismatch).map((it) => it.id);
|
|
165
|
-
if (currencyMismatchIds.length > 0) {
|
|
166
|
-
aggregatedWarnings.currency_mismatch_items = [
|
|
167
|
-
...aggregatedWarnings.currency_mismatch_items ?? [],
|
|
168
|
-
...currencyMismatchIds
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
const itemLines = buildInvoiceLinesFromItems(billableItems);
|
|
172
|
-
const usageResult = await processUsageForAccount(em, account, period, usageItems);
|
|
173
|
-
if (usageResult.unmatchedUoms.length > 0) {
|
|
174
|
-
aggregatedWarnings.unmatched_usage_uoms = [
|
|
175
|
-
...aggregatedWarnings.unmatched_usage_uoms ?? [],
|
|
176
|
-
...usageResult.unmatchedUoms
|
|
177
|
-
];
|
|
188
|
+
};
|
|
189
|
+
const addWarning = (key, values) => {
|
|
190
|
+
if (values.length === 0) return;
|
|
191
|
+
warnings[key] = Array.from(/* @__PURE__ */ new Set([...warnings[key] ?? [], ...values]));
|
|
192
|
+
};
|
|
193
|
+
while (isCycleDue(billDate, params.asOfDate)) {
|
|
194
|
+
if (cycles.length >= CATCH_UP_HARD_CAP) {
|
|
195
|
+
warnings.catch_up_cap_reached = true;
|
|
196
|
+
break;
|
|
178
197
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
198
|
+
const nextBillDate = advanceNextBillDate(billDate, cycle);
|
|
199
|
+
const arrearsPeriod = deriveBillPeriod(billDate, cycle);
|
|
200
|
+
const lines = [];
|
|
201
|
+
const watermarkUpdates = /* @__PURE__ */ new Map();
|
|
202
|
+
const chargedItems = [];
|
|
203
|
+
for (const item of recurringItems) {
|
|
204
|
+
const unitPrice = item.rateJson?.unit_price;
|
|
205
|
+
if (typeof unitPrice !== "number") continue;
|
|
206
|
+
const computed = computeRecurringLines(
|
|
207
|
+
{
|
|
208
|
+
id: item.id,
|
|
209
|
+
chargeTiming: item.chargeTiming ?? "in_advance",
|
|
210
|
+
prorate: item.prorate ?? true,
|
|
211
|
+
billStartDate: item.billStartDate,
|
|
212
|
+
billEndDate: item.billEndDate ?? null,
|
|
213
|
+
billedToDate: watermarks.get(item.id) ?? null,
|
|
214
|
+
unitPrice,
|
|
215
|
+
isActive: item.isActive,
|
|
216
|
+
deletedAt: item.deletedAt ?? null
|
|
217
|
+
},
|
|
218
|
+
{ billDate, cycle }
|
|
185
219
|
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
pushSkips(computed.skipped);
|
|
221
|
+
if (computed.newBilledToDate?.getTime() !== watermarks.get(item.id)?.getTime()) {
|
|
222
|
+
watermarkUpdates.set(item.id, computed.newBilledToDate);
|
|
223
|
+
}
|
|
224
|
+
if (computed.lines.length > 0) {
|
|
225
|
+
lines.push(...buildRecurringLineDescriptors(item, computed.lines));
|
|
226
|
+
chargedItems.push(item);
|
|
227
|
+
}
|
|
190
228
|
}
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
229
|
+
const oneTimeWindow = {
|
|
230
|
+
periodStart: shiftCycles(billDate, cycle, -1),
|
|
231
|
+
periodEnd: addDays(nextBillDate, -1)
|
|
232
|
+
};
|
|
233
|
+
const oneTimesThisCycle = oneTimeItems.filter(
|
|
234
|
+
(it) => !billedOneTimes.has(it.id) && shouldIncludeItem(it, oneTimeWindow).include
|
|
235
|
+
);
|
|
236
|
+
lines.push(...buildInvoiceLinesFromItems(oneTimesThisCycle));
|
|
237
|
+
chargedItems.push(...oneTimesThisCycle);
|
|
238
|
+
const usageResult = await processUsageForAccount(em, account, arrearsPeriod, usageItems, {
|
|
239
|
+
after: usageCountedThrough
|
|
197
240
|
});
|
|
198
|
-
|
|
241
|
+
addWarning("unmatched_usage_uoms", usageResult.unmatchedUoms);
|
|
242
|
+
lines.push(
|
|
243
|
+
...usageResult.lines.map((line) => ({
|
|
244
|
+
...line,
|
|
245
|
+
servicePeriodStart: arrearsPeriod.periodStart,
|
|
246
|
+
servicePeriodEnd: arrearsPeriod.periodEnd
|
|
247
|
+
}))
|
|
248
|
+
);
|
|
249
|
+
addWarning(
|
|
250
|
+
"currency_mismatch_items",
|
|
251
|
+
chargedItems.filter((it) => it.currencyMismatch).map((it) => it.id)
|
|
252
|
+
);
|
|
253
|
+
const period = displayPeriod(lines, arrearsPeriod);
|
|
254
|
+
const record = {
|
|
255
|
+
bill_date: isoDate(billDate),
|
|
256
|
+
period_start: lines.length ? isoDate(period.periodStart) : null,
|
|
257
|
+
period_end: lines.length ? isoDate(period.periodEnd) : null,
|
|
258
|
+
status: "empty",
|
|
259
|
+
line_count: lines.length,
|
|
260
|
+
total_net: "0.0000",
|
|
261
|
+
invoice_id: null
|
|
262
|
+
};
|
|
263
|
+
const existing = params.mode === "dry" ? null : await findExistingDraftForBillDate(em, account, billDate, testRun);
|
|
264
|
+
if (existing && params.mode === "real") {
|
|
265
|
+
cycles.push({ ...record, status: "skipped_existing", invoice_id: existing });
|
|
266
|
+
return {
|
|
267
|
+
status: "skipped_existing_draft",
|
|
268
|
+
warnings: finalizeWarnings(warnings, skippedItems),
|
|
269
|
+
draftInvoiceId: existing,
|
|
270
|
+
cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
|
|
271
|
+
usageRecordsRated,
|
|
272
|
+
cycles
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (existing) {
|
|
276
|
+
cycles.push({ ...record, status: "skipped_existing", invoice_id: existing });
|
|
277
|
+
} else if (lines.length > 0) {
|
|
278
|
+
const written = await writeDraftOnce(em, {
|
|
279
|
+
mode: params.mode,
|
|
280
|
+
account,
|
|
281
|
+
billDate,
|
|
282
|
+
period,
|
|
283
|
+
billRunId: billRun.id,
|
|
284
|
+
lines
|
|
285
|
+
});
|
|
286
|
+
if (written === "conflict") {
|
|
287
|
+
cycles.push({ ...record, status: "skipped_existing" });
|
|
288
|
+
if (params.mode === "real") {
|
|
289
|
+
return {
|
|
290
|
+
status: "skipped_existing_draft",
|
|
291
|
+
warnings: finalizeWarnings(warnings, skippedItems),
|
|
292
|
+
draftInvoiceId: lastDraftInvoiceId,
|
|
293
|
+
cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
|
|
294
|
+
usageRecordsRated,
|
|
295
|
+
cycles
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
billDate = nextBillDate;
|
|
299
|
+
for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date);
|
|
300
|
+
for (const it of oneTimesThisCycle) billedOneTimes.add(it.id);
|
|
301
|
+
usageCountedThrough = arrearsPeriod.periodEnd;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (written.invoiceId) lastDraftInvoiceId = written.invoiceId;
|
|
305
|
+
if (Number.parseFloat(written.totalNet) < 0) addWarning("negative_total_bill_dates", [record.bill_date]);
|
|
306
|
+
cycles.push({
|
|
307
|
+
...record,
|
|
308
|
+
status: params.mode === "dry" ? "previewed" : "invoiced",
|
|
309
|
+
total_net: written.totalNet,
|
|
310
|
+
invoice_id: written.invoiceId
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
cycles.push(record);
|
|
314
|
+
}
|
|
315
|
+
for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date);
|
|
316
|
+
for (const it of oneTimesThisCycle) billedOneTimes.add(it.id);
|
|
317
|
+
usageCountedThrough = arrearsPeriod.periodEnd;
|
|
199
318
|
if (params.mode === "real") {
|
|
200
319
|
const now = /* @__PURE__ */ new Date();
|
|
201
|
-
for (const item of
|
|
202
|
-
item.
|
|
320
|
+
for (const item of recurringItems) {
|
|
321
|
+
if (!watermarkUpdates.has(item.id)) continue;
|
|
322
|
+
item.billedToDate = watermarkUpdates.get(item.id) ?? null;
|
|
323
|
+
item.updatedAt = now;
|
|
324
|
+
}
|
|
325
|
+
for (const item of oneTimesThisCycle) {
|
|
326
|
+
item.billedToDate = oneTimeWindow.periodEnd;
|
|
203
327
|
item.updatedAt = now;
|
|
204
328
|
}
|
|
205
329
|
if (usageResult.matchedUoms.length > 0) {
|
|
@@ -211,53 +335,62 @@ async function processAccount(em, container, billRun, account, params) {
|
|
|
211
335
|
billAccountId: account.id,
|
|
212
336
|
uomCode: { $in: usageResult.matchedUoms },
|
|
213
337
|
ratedInBillRunId: null,
|
|
214
|
-
periodEnd: { $lte:
|
|
338
|
+
periodEnd: { $lte: arrearsPeriod.periodEnd },
|
|
215
339
|
deletedAt: null
|
|
216
340
|
},
|
|
217
341
|
{ ratedInBillRunId: billRun.id, updatedAt: now }
|
|
218
342
|
);
|
|
343
|
+
usageRecordsRated += usageResult.ratedCount;
|
|
219
344
|
}
|
|
220
|
-
|
|
221
|
-
account.
|
|
222
|
-
account.nextBillDate,
|
|
223
|
-
account.billCycle
|
|
224
|
-
);
|
|
225
|
-
account.lastBillDate = period.periodEnd;
|
|
345
|
+
account.nextBillDate = nextBillDate;
|
|
346
|
+
account.lastBillDate = arrearsPeriod.periodEnd;
|
|
226
347
|
account.updatedAt = now;
|
|
227
348
|
await em.flush();
|
|
228
|
-
} else {
|
|
229
|
-
break;
|
|
230
349
|
}
|
|
231
|
-
|
|
350
|
+
billDate = nextBillDate;
|
|
232
351
|
}
|
|
233
|
-
const
|
|
234
|
-
const hasUnmatchedUsage = (aggregatedWarnings.unmatched_usage_uoms ?? []).length > 0;
|
|
235
|
-
const hasWarnings = hasCurrencyWarnings || hasUnmatchedUsage;
|
|
352
|
+
const finalWarnings = finalizeWarnings(warnings, skippedItems);
|
|
236
353
|
return {
|
|
237
|
-
status:
|
|
238
|
-
warnings:
|
|
354
|
+
status: hasRealWarnings(warnings) ? "success_with_warnings" : "success",
|
|
355
|
+
warnings: finalWarnings,
|
|
239
356
|
draftInvoiceId: lastDraftInvoiceId,
|
|
240
|
-
cyclesEmitted,
|
|
241
|
-
usageRecordsRated
|
|
357
|
+
cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
|
|
358
|
+
usageRecordsRated,
|
|
359
|
+
cycles
|
|
242
360
|
};
|
|
243
361
|
}
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
362
|
+
function finalizeWarnings(warnings, skippedItems) {
|
|
363
|
+
const merged = { ...warnings, ...skippedItems.length ? { skipped_items: skippedItems } : {} };
|
|
364
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
365
|
+
}
|
|
366
|
+
function hasRealWarnings(warnings) {
|
|
367
|
+
return Boolean(
|
|
368
|
+
warnings.currency_mismatch_items?.length || warnings.unmatched_usage_uoms?.length || warnings.negative_total_bill_dates?.length || warnings.catch_up_cap_reached
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
async function findExistingDraftForBillDate(em, account, billDate, testRun) {
|
|
372
|
+
const existing = await em.findOne(
|
|
373
|
+
BillingInvoice,
|
|
374
|
+
{
|
|
375
|
+
tenantId: account.tenantId,
|
|
376
|
+
organizationId: account.organizationId,
|
|
377
|
+
billAccountId: account.id,
|
|
378
|
+
billDate,
|
|
379
|
+
testRun,
|
|
380
|
+
documentType: "invoice",
|
|
381
|
+
deletedAt: null
|
|
382
|
+
}
|
|
258
383
|
);
|
|
259
|
-
|
|
260
|
-
|
|
384
|
+
return existing?.id ?? null;
|
|
385
|
+
}
|
|
386
|
+
async function writeDraftOnce(em, params) {
|
|
387
|
+
if (params.mode === "dry") return writeDraftInvoice(em, params);
|
|
388
|
+
try {
|
|
389
|
+
return await em.transactional((tem) => writeDraftInvoice(tem, params));
|
|
390
|
+
} catch (err) {
|
|
391
|
+
if (err instanceof UniqueConstraintViolationException) return "conflict";
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
261
394
|
}
|
|
262
395
|
function computeFinalRunStatus(outcomes) {
|
|
263
396
|
if (outcomes.length === 0) return "completed";
|
|
@@ -270,11 +403,12 @@ function computeFinalRunStatus(outcomes) {
|
|
|
270
403
|
return "completed";
|
|
271
404
|
}
|
|
272
405
|
function buildRunSummary(outcomes, totalAccounts, usageRecordsRated) {
|
|
406
|
+
const allCycles = outcomes.flatMap((o) => Array.isArray(o.cycles) ? o.cycles : []);
|
|
273
407
|
return {
|
|
274
408
|
accounts_processed: totalAccounts,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
409
|
+
// Spec A: one draft per invoiced cycle (catch-up writes several per account).
|
|
410
|
+
drafts_created: allCycles.filter((c) => c.status === "invoiced" && c.invoice_id).length,
|
|
411
|
+
cycles_processed: allCycles.length,
|
|
278
412
|
drafts_skipped_existing: outcomes.filter((o) => o.status === "skipped_existing_draft").length,
|
|
279
413
|
// SPEC-002 prepaid period close.
|
|
280
414
|
statements_created: outcomes.filter(
|
|
@@ -289,6 +423,7 @@ function buildRunSummary(outcomes, totalAccounts, usageRecordsRated) {
|
|
|
289
423
|
};
|
|
290
424
|
}
|
|
291
425
|
export {
|
|
426
|
+
CATCH_UP_HARD_CAP,
|
|
292
427
|
runBillRun
|
|
293
428
|
};
|
|
294
429
|
//# sourceMappingURL=billRunEngine.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/billing/lib/billRunEngine.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport {\n BillingAccount,\n BillingAccountUsage,\n BillingItem,\n BillRun,\n BillRunOutcome,\n} from '../data/entities'\nimport { advanceNextBillDate, deriveBillPeriod, isCycleDue } from './billPeriod'\nimport {\n selectItemsForPeriod,\n type SelectableItem,\n} from './itemSelector'\nimport {\n buildInvoiceLinesFromItems,\n writeDraftInvoice,\n type InvoiceLineDescriptor,\n type WriteMode,\n} from './invoiceWriter'\nimport { withTenantLock } from './tenantLock'\nimport { processUsageForAccount } from './usageRunner'\nimport { processPrepaidAccount } from './prepaidPeriodClose'\nimport type { DomainEvent } from './balanceEvents'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport type { BillCycle } from '../data/validators'\n\n/**\n * Bill Run engine \u2014 orchestrates one execution of the billing cycle for\n * a single tenant.\n *\n * Concurrency / locking:\n * - The whole run is wrapped in `withTenantLock(...)`, which holds a\n * transaction-scoped Postgres advisory lock keyed by\n * `hash(tenant_id || ':billing-run')`. A second trigger fired while a\n * run is in flight short-circuits with `TenantLockBusyError` (the\n * command layer maps that to HTTP 409).\n * - Within the outer transaction, every account is processed inside a\n * nested `em.transactional(...)` (PG SAVEPOINT). A per-account failure\n * rolls back only that account's work, leaving siblings unaffected.\n *\n * Run modes:\n * - `real` (default) \u2014 persist drafts, advance `next_bill_date`, mark\n * items `billed_to_date`. Production path.\n * - `test` \u2014 persist drafts flagged with `metadata.test_run=true`, but\n * do NOT advance state. Repeatable.\n * - `dry` \u2014 compute everything; persist nothing. Preview only.\n *\n * Catch-up:\n * - Manual triggers may set `catchUp=true`. The engine then loops cycles\n * per account until `next_bill_date > as_of_date`, emitting one draft\n * per missed cycle (never combined).\n * - Scheduled triggers always process exactly ONE cycle per account per\n * run, regardless of how far behind the account is.\n *\n * Anti-duplicate guards (both run in every mode except `dry`):\n * 1. Open-draft check on `core/sales` invoice (per period boundary).\n * 2. `billed_to_date` per BillingItem.\n */\n\nexport type RunMode = WriteMode\n\nexport type RunBillRunParams = {\n tenantId: string\n organizationId: string\n asOfDate: Date\n triggeredBy: 'schedule' | 'manual'\n mode: RunMode\n scopedAccountIds?: string[]\n parentRunId?: string | null\n catchUp?: boolean\n}\n\nexport type RunBillRunResult = {\n billRun: BillRun\n outcomes: BillRunOutcome[]\n}\n\ntype RunOutput = RunBillRunResult & {\n /** Prepaid statement / crossing events to emit AFTER the outer tx commits. */\n pendingEvents: DomainEvent[]\n}\n\ntype AccountWarnings = {\n currency_mismatch_items?: string[]\n unmatched_usage_uoms?: string[]\n}\n\ntype AccountProcessSuccess = {\n status:\n | 'success'\n | 'success_with_warnings'\n | 'skipped_existing_draft'\n | 'skipped_existing_statement'\n warnings: AccountWarnings | Record<string, unknown> | null\n draftInvoiceId: string | null\n // SPEC-002: prepaid accounts produce a statement instead of a draft invoice.\n statementId?: string | null\n statementEvents?: DomainEvent[]\n cyclesEmitted: number\n usageRecordsRated: number\n}\n\ntype AccountProcessFailed = {\n status: 'failed'\n errorMessage: string\n}\n\ntype AccountProcessResult = AccountProcessSuccess | AccountProcessFailed\n\nconst SCHEDULED_CYCLE_LIMIT = 1\nconst CATCH_UP_HARD_CAP = 60 // safety net so a misconfigured account can't loop forever\n\nexport async function runBillRun(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunBillRunResult> {\n const output = await withTenantLock(\n em,\n { tenantId: params.tenantId, lockName: 'billing-run' },\n async (tem) => executeRunWithinLock(tem, container, params),\n )\n // Emit prepaid statement / crossing events ONLY after the outer transaction\n // (held by withTenantLock) has committed \u2014 emitting inside the lock would let\n // a consumer observe an uncommitted statement or a phantom event on rollback.\n for (const ev of output.pendingEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n return { billRun: output.billRun, outcomes: output.outcomes }\n}\n\nasync function executeRunWithinLock(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunOutput> {\n const now = new Date()\n const billRun = em.create(BillRun, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n triggeredBy: params.triggeredBy,\n parentRunId: params.parentRunId ?? null,\n dryRun: params.mode === 'dry',\n testMode: params.mode === 'test',\n catchUp: Boolean(params.catchUp),\n scopedAccountIds: params.scopedAccountIds ?? null,\n asOfDate: params.asOfDate,\n startedAt: now,\n status: 'running',\n summary: null,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(billRun)\n await em.flush()\n\n const accounts = await selectTargetAccounts(em, params)\n const outcomes: BillRunOutcome[] = []\n const pendingEvents: DomainEvent[] = []\n let totalUsageRecordsRated = 0\n\n for (const account of accounts) {\n const processResult = await processAccountSafely(em, container, billRun, account, params)\n if (processResult.status !== 'failed') {\n totalUsageRecordsRated += processResult.usageRecordsRated\n }\n const outcome = em.create(BillRunOutcome, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n billRunId: billRun.id,\n billAccountId: account.id,\n status: processResult.status,\n errorMessage: processResult.status === 'failed' ? processResult.errorMessage : null,\n warnings:\n processResult.status === 'failed'\n ? null\n : (processResult.warnings as Record<string, unknown> | null),\n draftInvoiceId: processResult.status === 'failed' ? null : processResult.draftInvoiceId,\n statementId: processResult.status === 'failed' ? null : (processResult.statementId ?? null),\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(outcome)\n await em.flush()\n outcomes.push(outcome)\n\n // Collect statement / crossing events \u2014 runBillRun emits them AFTER the\n // outer tx commits (see runBillRun), never from inside the lock.\n if (processResult.status !== 'failed' && processResult.statementEvents) {\n pendingEvents.push(...processResult.statementEvents)\n }\n }\n\n billRun.status = computeFinalRunStatus(outcomes)\n billRun.finishedAt = new Date()\n billRun.updatedAt = billRun.finishedAt\n billRun.summary = buildRunSummary(outcomes, accounts.length, totalUsageRecordsRated)\n await em.flush()\n\n return { billRun, outcomes, pendingEvents }\n}\n\nasync function selectTargetAccounts(\n em: EntityManager,\n params: RunBillRunParams,\n): Promise<BillingAccount[]> {\n const where: Record<string, unknown> = {\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n nextBillDate: { $lte: params.asOfDate },\n }\n if (params.scopedAccountIds && params.scopedAccountIds.length > 0) {\n where.id = { $in: params.scopedAccountIds }\n }\n return em.find(BillingAccount, where as never)\n}\n\nasync function processAccountSafely(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n try {\n return await em.transactional(async (subEm) => {\n return processAccount(subEm, container, billRun, account, params)\n })\n } catch (err) {\n return {\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nasync function processAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n // SPEC-002: a prepaid account never gets a payable draft invoice. It still\n // runs in the Bill Run, but charges recurring / one_time items to the\n // balance and produces a non-fiscal consumption statement.\n if (account.billingMode === 'prepaid') {\n const result = await processPrepaidAccount(em, account, billRun, {\n asOfDate: params.asOfDate,\n mode: params.mode,\n triggeredBy: params.triggeredBy,\n catchUp: params.catchUp,\n })\n if (result.status === 'failed') {\n return { status: 'failed', errorMessage: result.errorMessage }\n }\n return {\n status: result.status,\n warnings: result.warnings,\n draftInvoiceId: null,\n statementId: result.statementId,\n statementEvents: result.statementEvents,\n cyclesEmitted: result.cyclesEmitted,\n usageRecordsRated: 0,\n }\n }\n\n const maxCycles =\n params.triggeredBy === 'schedule' || !params.catchUp\n ? SCHEDULED_CYCLE_LIMIT\n : CATCH_UP_HARD_CAP\n\n let cyclesEmitted = 0\n let lastDraftInvoiceId: string | null = null\n let totalUsageRatedThisAccount = 0\n const aggregatedWarnings: AccountWarnings = {}\n\n while (cyclesEmitted < maxCycles && isCycleDue(account.nextBillDate, params.asOfDate)) {\n const period = deriveBillPeriod(account.nextBillDate, account.billCycle as BillCycle)\n\n // Open-draft anti-duplicate \u2014 only applies when we'd actually persist.\n if (params.mode !== 'dry') {\n const existing = await findExistingDraftForPeriod(em, account, period)\n if (existing) {\n return {\n status: 'skipped_existing_draft',\n warnings: null,\n draftInvoiceId: existing,\n cyclesEmitted,\n usageRecordsRated: totalUsageRatedThisAccount,\n }\n }\n }\n\n const items = await em.find(BillingItem, {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n deletedAt: null,\n } as never)\n\n const usageItems = (items as BillingItem[]).filter((it) => it.type === 'usage')\n\n // Non-usage path: one_time + recurring filtered by the selector.\n const { included } = selectItemsForPeriod(items as SelectableItem[], period)\n const billableItems = included as unknown as BillingItem[]\n\n const currencyMismatchIds = billableItems\n .filter((it) => it.currencyMismatch)\n .map((it) => it.id)\n if (currencyMismatchIds.length > 0) {\n aggregatedWarnings.currency_mismatch_items = [\n ...(aggregatedWarnings.currency_mismatch_items ?? []),\n ...currencyMismatchIds,\n ]\n }\n const itemLines = buildInvoiceLinesFromItems(billableItems)\n\n // Usage path: aggregate matching usage records per uom_code, rate,\n // emit one line per matched usage item, warn about unmatched UoMs.\n const usageResult = await processUsageForAccount(em, account, period, usageItems)\n if (usageResult.unmatchedUoms.length > 0) {\n aggregatedWarnings.unmatched_usage_uoms = [\n ...(aggregatedWarnings.unmatched_usage_uoms ?? []),\n ...usageResult.unmatchedUoms,\n ]\n }\n\n const allLines: InvoiceLineDescriptor[] = [...itemLines, ...usageResult.lines]\n if (allLines.length === 0) {\n // Nothing to bill this cycle \u2014 still advance so the account moves\n // forward in real mode. In test / dry, no advance, so we'd loop\n // forever; break out.\n if (params.mode !== 'real') break\n account.nextBillDate = advanceNextBillDate(\n account.nextBillDate,\n account.billCycle as BillCycle,\n )\n account.lastBillDate = period.periodEnd\n account.updatedAt = new Date()\n cyclesEmitted += 1\n continue\n }\n\n const written = await writeDraftInvoice(em, container, {\n mode: params.mode,\n account,\n period,\n billRunId: billRun.id,\n lines: allLines,\n })\n lastDraftInvoiceId = written.invoiceId\n\n if (params.mode === 'real') {\n const now = new Date()\n for (const item of billableItems) {\n item.billedToDate = period.periodEnd\n item.updatedAt = now\n }\n // Mark consumed usage records \u2014 single bulk update so we don't\n // round-trip per record on accounts with thousands of usage rows.\n if (usageResult.matchedUoms.length > 0) {\n // Predicate-based bulk update: marks every still-unrated record\n // for the matched uoms in the account's period with a single\n // SQL UPDATE \u2014 no id list ever leaves Postgres. Stays\n // memory-bounded even for telco accounts with millions of\n // usage records per cycle (spec Performance Considerations\n // \"Shape B\").\n await em.nativeUpdate(\n BillingAccountUsage,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n uomCode: { $in: usageResult.matchedUoms },\n ratedInBillRunId: null,\n periodEnd: { $lte: period.periodEnd },\n deletedAt: null,\n } as never,\n { ratedInBillRunId: billRun.id, updatedAt: now } as never,\n )\n }\n totalUsageRatedThisAccount += usageResult.ratedCount\n account.nextBillDate = advanceNextBillDate(\n account.nextBillDate,\n account.billCycle as BillCycle,\n )\n account.lastBillDate = period.periodEnd\n account.updatedAt = now\n await em.flush()\n } else {\n // Test / dry: do not advance state. One iteration only.\n break\n }\n\n cyclesEmitted += 1\n }\n\n const hasCurrencyWarnings =\n (aggregatedWarnings.currency_mismatch_items ?? []).length > 0\n const hasUnmatchedUsage =\n (aggregatedWarnings.unmatched_usage_uoms ?? []).length > 0\n const hasWarnings = hasCurrencyWarnings || hasUnmatchedUsage\n return {\n status: hasWarnings ? 'success_with_warnings' : 'success',\n warnings: hasWarnings ? aggregatedWarnings : null,\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted,\n usageRecordsRated: totalUsageRatedThisAccount,\n }\n}\n\nasync function findExistingDraftForPeriod(\n em: EntityManager,\n account: BillingAccount,\n period: { periodStart: Date; periodEnd: Date },\n): Promise<string | null> {\n const periodStartISO = period.periodStart.toISOString().slice(0, 10)\n const periodEndISO = period.periodEnd.toISOString().slice(0, 10)\n const rows = (await em.execute(\n `SELECT id FROM sales_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND status = 'draft'\n AND metadata->>'bill_account_id' = ?\n AND metadata->>'bill_period_start' = ?\n AND metadata->>'bill_period_end' = ?\n LIMIT 1`,\n [account.tenantId, account.organizationId, account.id, periodStartISO, periodEndISO],\n )) as unknown as Array<{ id: string }> | undefined\n if (!rows || rows.length === 0) return null\n return rows[0].id ?? null\n}\n\nfunction computeFinalRunStatus(\n outcomes: readonly BillRunOutcome[],\n): BillRun['status'] {\n if (outcomes.length === 0) return 'completed'\n const anyFailed = outcomes.some((o) => o.status === 'failed')\n const anySuccess = outcomes.some(\n (o) => o.status === 'success' || o.status === 'success_with_warnings',\n )\n if (anyFailed && anySuccess) return 'partial_failure'\n if (anyFailed) return 'partial_failure'\n return 'completed'\n}\n\nfunction buildRunSummary(\n outcomes: readonly BillRunOutcome[],\n totalAccounts: number,\n usageRecordsRated: number,\n): Record<string, unknown> {\n return {\n accounts_processed: totalAccounts,\n drafts_created: outcomes.filter(\n (o) => o.draftInvoiceId !== null && o.status !== 'skipped_existing_draft',\n ).length,\n drafts_skipped_existing: outcomes.filter((o) => o.status === 'skipped_existing_draft')\n .length,\n // SPEC-002 prepaid period close.\n statements_created: outcomes.filter(\n (o) => o.statementId !== null && o.status !== 'skipped_existing_statement',\n ).length,\n statements_skipped_existing: outcomes.filter(\n (o) => o.status === 'skipped_existing_statement',\n ).length,\n accounts_failed: outcomes.filter((o) => o.status === 'failed').length,\n accounts_with_warnings: outcomes.filter((o) => o.status === 'success_with_warnings')\n .length,\n usage_records_rated: usageRecordsRated,\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport {\n BillingAccount,\n BillingAccountUsage,\n BillingInvoice,\n BillingItem,\n BillingRun,\n BillingRunOutcome,\n} from '../data/entities'\nimport {\n addDays,\n advanceNextBillDate,\n cloneAtMidnight,\n deriveBillPeriod,\n isCycleDue,\n shiftCycles,\n type BillPeriod,\n} from './billPeriod'\nimport { shouldIncludeItem, type SelectableItem } from './itemSelector'\nimport {\n buildInvoiceLinesFromItems,\n buildRecurringLineDescriptors,\n displayPeriod,\n writeDraftInvoice,\n type InvoiceLineDescriptor,\n type WriteMode,\n} from './invoiceWriter'\nimport { computeRecurringLines, type RecurringSkip } from './servicePeriods'\nimport { withTenantLock } from './tenantLock'\nimport { processUsageForAccount } from './usageRunner'\nimport { processPrepaidAccount } from './prepaidPeriodClose'\nimport type { DomainEvent } from './balanceEvents'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport type { BillCycle } from '../data/validators'\n\n/**\n * Bill Run engine \u2014 orchestrates one execution of the billing cycle for\n * a single tenant.\n *\n * Concurrency / locking:\n * - The whole run is wrapped in `withTenantLock(...)`, which holds a\n * transaction-scoped Postgres advisory lock keyed by\n * `hash(tenant_id || ':billing-run')`. A second trigger fired while a\n * run is in flight short-circuits with `TenantLockBusyError` (the\n * command layer maps that to HTTP 409).\n * - Within the outer transaction, every account is processed inside a\n * nested `em.transactional(...)` (PG SAVEPOINT). A per-account failure\n * rolls back only that account's work, leaving siblings unaffected.\n *\n * Cycles (Spec A): every run \u2014 manual or scheduled, any mode \u2014 bills ALL\n * due bill dates up to `asOfDate`, one draft per bill date (never\n * combined), capped at `CATCH_UP_HARD_CAP` per account. The account's\n * `next_bill_date` and each recurring item's `billed_to_date` watermark are\n * advanced in memory cycle by cycle; only `real` persists them:\n * - `real` \u2014 persist drafts, advance `next_bill_date` / watermarks, mark\n * usage rated. Production path.\n * - `test` \u2014 persist drafts flagged `metadata.test_run=true` for every\n * cycle, but do NOT advance state. Repeatable.\n * - `dry` \u2014 compute every cycle; persist nothing. Preview only.\n *\n * Recurring items are priced by `servicePeriods.computeRecurringLines`\n * (in advance / in arrears, proration, credits). `one_time` items use the\n * selector; usage is always billed in arrears for `[D \u2212 cycle, D \u2212 1]`.\n *\n * Anti-duplicate guards (every mode except `dry`):\n * 1. Open draft for the same `(account, bill_date, test_run)`.\n * 2. `billed_to_date` per BillingItem (the watermark).\n */\n\nexport type RunMode = WriteMode\n\nexport type RunBillRunParams = {\n tenantId: string\n organizationId: string\n asOfDate: Date\n triggeredBy: 'schedule' | 'manual'\n mode: RunMode\n scopedAccountIds?: string[]\n parentRunId?: string | null\n}\n\nexport type RunBillRunResult = {\n billRun: BillingRun\n outcomes: BillingRunOutcome[]\n}\n\ntype RunOutput = RunBillRunResult & {\n /** Prepaid statement / crossing events to emit AFTER the outer tx commits. */\n pendingEvents: DomainEvent[]\n}\n\ntype SkippedItemWarning = {\n item_id: string\n reason: string\n service_period_start: string | null\n service_period_end: string | null\n}\n\ntype AccountWarnings = {\n currency_mismatch_items?: string[]\n unmatched_usage_uoms?: string[]\n /** Spec A: bill dates whose draft has a negative total (credits > charges). */\n negative_total_bill_dates?: string[]\n /** Spec A: the account hit CATCH_UP_HARD_CAP; later cycles wait for the next run. */\n catch_up_cap_reached?: boolean\n /** Spec A: diagnostic \u2014 partial periods not prorated, no-credit ends, caps. */\n skipped_items?: SkippedItemWarning[]\n}\n\n/** `charged` is the prepaid analogue of `invoiced` (Spec F, P-D6). */\nexport type CycleStatus = 'invoiced' | 'charged' | 'previewed' | 'empty' | 'skipped_existing'\n\nexport type CycleRecord = {\n bill_date: string\n period_start: string | null\n period_end: string | null\n status: CycleStatus\n line_count: number\n total_net: string\n invoice_id: string | null\n /** Prepaid only: what left the balance, and the statement for the period. */\n total_debit?: string\n statement_id?: string | null\n}\n\ntype AccountProcessSuccess = {\n status:\n | 'success'\n | 'success_with_warnings'\n | 'skipped_existing_draft'\n | 'skipped_existing_statement'\n warnings: AccountWarnings | Record<string, unknown> | null\n draftInvoiceId: string | null\n // SPEC-002: prepaid accounts produce a statement instead of a draft invoice.\n statementId?: string | null\n statementEvents?: DomainEvent[]\n cyclesEmitted: number\n usageRecordsRated: number\n /** Spec A: per-bill-date breakdown (postpaid). */\n cycles?: CycleRecord[]\n}\n\ntype AccountProcessFailed = {\n status: 'failed'\n errorMessage: string\n}\n\ntype AccountProcessResult = AccountProcessSuccess | AccountProcessFailed\n\nexport const CATCH_UP_HARD_CAP = 60 // safety net so a misconfigured account can't loop forever\nconst SKIPPED_ITEMS_WARNING_CAP = 200\n/** Skip reasons that are normal state, not worth surfacing on the outcome. */\nconst QUIET_SKIP_REASONS = new Set(['item-inactive', 'item-soft-deleted'])\n\nexport async function runBillRun(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunBillRunResult> {\n const output = await withTenantLock(\n em,\n { tenantId: params.tenantId, lockName: 'billing-run' },\n async (tem) => executeRunWithinLock(tem, container, params),\n )\n // Emit prepaid statement / crossing events ONLY after the outer transaction\n // (held by withTenantLock) has committed \u2014 emitting inside the lock would let\n // a consumer observe an uncommitted statement or a phantom event on rollback.\n for (const ev of output.pendingEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n return { billRun: output.billRun, outcomes: output.outcomes }\n}\n\nasync function executeRunWithinLock(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunOutput> {\n const now = new Date()\n const billRun = em.create(BillingRun, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n triggeredBy: params.triggeredBy,\n parentRunId: params.parentRunId ?? null,\n dryRun: params.mode === 'dry',\n testMode: params.mode === 'test',\n scopedAccountIds: params.scopedAccountIds ?? null,\n asOfDate: params.asOfDate,\n startedAt: now,\n status: 'running',\n summary: null,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(billRun)\n await em.flush()\n\n const accounts = await selectTargetAccounts(em, params)\n const outcomes: BillingRunOutcome[] = []\n const pendingEvents: DomainEvent[] = []\n let totalUsageRecordsRated = 0\n\n for (const account of accounts) {\n const processResult = await processAccountSafely(em, container, billRun, account, params)\n if (processResult.status !== 'failed') {\n totalUsageRecordsRated += processResult.usageRecordsRated\n }\n const outcome = em.create(BillingRunOutcome, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n billRunId: billRun.id,\n billAccountId: account.id,\n status: processResult.status,\n errorMessage: processResult.status === 'failed' ? processResult.errorMessage : null,\n warnings:\n processResult.status === 'failed'\n ? null\n : (processResult.warnings as Record<string, unknown> | null),\n draftInvoiceId: processResult.status === 'failed' ? null : processResult.draftInvoiceId,\n statementId: processResult.status === 'failed' ? null : (processResult.statementId ?? null),\n cycles: processResult.status === 'failed' ? null : (processResult.cycles ?? null),\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(outcome)\n await em.flush()\n outcomes.push(outcome)\n\n // Collect statement / crossing events \u2014 runBillRun emits them AFTER the\n // outer tx commits (see runBillRun), never from inside the lock.\n if (processResult.status !== 'failed' && processResult.statementEvents) {\n pendingEvents.push(...processResult.statementEvents)\n }\n }\n\n billRun.status = computeFinalRunStatus(outcomes)\n billRun.finishedAt = new Date()\n billRun.updatedAt = billRun.finishedAt\n billRun.summary = buildRunSummary(outcomes, accounts.length, totalUsageRecordsRated)\n await em.flush()\n\n return { billRun, outcomes, pendingEvents }\n}\n\nasync function selectTargetAccounts(\n em: EntityManager,\n params: RunBillRunParams,\n): Promise<BillingAccount[]> {\n const where: Record<string, unknown> = {\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n nextBillDate: { $lte: params.asOfDate },\n }\n if (params.scopedAccountIds && params.scopedAccountIds.length > 0) {\n where.id = { $in: params.scopedAccountIds }\n }\n return em.find(BillingAccount, where as never)\n}\n\nasync function processAccountSafely(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n try {\n return await em.transactional(async (subEm) => {\n return processAccount(subEm, container, billRun, account, params)\n })\n } catch (err) {\n return {\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nasync function processAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n // SPEC-002: a prepaid account never gets a payable draft invoice. It still\n // runs in the Bill Run, but charges recurring / one_time items to the\n // balance and produces a non-fiscal consumption statement.\n if (account.billingMode === 'prepaid') {\n const result = await processPrepaidAccount(em, account, billRun, {\n asOfDate: params.asOfDate,\n mode: params.mode,\n triggeredBy: params.triggeredBy,\n })\n if (result.status === 'failed') {\n return { status: 'failed', errorMessage: result.errorMessage }\n }\n return {\n status: result.status,\n warnings: result.warnings,\n draftInvoiceId: null,\n statementId: result.statementId,\n statementEvents: result.statementEvents,\n cyclesEmitted: result.cyclesEmitted,\n cycles: result.cycles,\n usageRecordsRated: 0,\n }\n }\n\n return processPostpaidAccount(em, container, billRun, account, params)\n}\n\nfunction isoDate(date: Date): string {\n return cloneAtMidnight(date).toISOString().slice(0, 10)\n}\n\nfunction toSkipWarning(skip: RecurringSkip): SkippedItemWarning {\n return {\n item_id: skip.billingItemId,\n reason: skip.reason,\n service_period_start: skip.servicePeriodStart ? isoDate(skip.servicePeriodStart) : null,\n service_period_end: skip.servicePeriodEnd ? isoDate(skip.servicePeriodEnd) : null,\n }\n}\n\n/**\n * Postpaid cycle loop (Spec A). Items are loaded once; the bill date and\n * the per-item watermarks advance in memory so test / dry can simulate every\n * due cycle exactly like real does \u2014 only persistence differs by mode.\n */\nasync function processPostpaidAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n const cycle = account.billCycle as BillCycle\n const testRun = params.mode === 'test'\n const items = (await em.find(BillingItem, {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n deletedAt: null,\n } as never)) as BillingItem[]\n const recurringItems = items.filter((it) => it.type === 'recurring')\n const oneTimeItems = items.filter((it) => it.type === 'one_time')\n const usageItems = items.filter((it) => it.type === 'usage')\n\n // In-memory cycle state \u2014 persisted per cycle only in real mode.\n const watermarks = new Map<string, Date | null>(\n recurringItems.map((it) => [it.id, it.billedToDate ? cloneAtMidnight(it.billedToDate) : null]),\n )\n const billedOneTimes = new Set<string>(oneTimeItems.filter((it) => it.billedToDate).map((it) => it.id))\n let billDate = cloneAtMidnight(account.nextBillDate)\n // Usage already counted by an earlier cycle of THIS run (test / dry never\n // mark records, so later simulated cycles must not re-count them).\n let usageCountedThrough: Date | null = null\n\n const cycles: CycleRecord[] = []\n const warnings: AccountWarnings = {}\n const skippedItems: SkippedItemWarning[] = []\n let lastDraftInvoiceId: string | null = null\n let usageRecordsRated = 0\n\n const pushSkips = (skips: readonly RecurringSkip[]) => {\n for (const skip of skips) {\n if (QUIET_SKIP_REASONS.has(skip.reason)) continue\n if (skippedItems.length < SKIPPED_ITEMS_WARNING_CAP) skippedItems.push(toSkipWarning(skip))\n }\n }\n const addWarning = (key: 'currency_mismatch_items' | 'unmatched_usage_uoms' | 'negative_total_bill_dates', values: string[]) => {\n if (values.length === 0) return\n warnings[key] = Array.from(new Set([...(warnings[key] ?? []), ...values]))\n }\n\n while (isCycleDue(billDate, params.asOfDate)) {\n if (cycles.length >= CATCH_UP_HARD_CAP) {\n warnings.catch_up_cap_reached = true\n break\n }\n const nextBillDate = advanceNextBillDate(billDate, cycle)\n const arrearsPeriod: BillPeriod = deriveBillPeriod(billDate, cycle)\n\n // \u2500\u2500 Recurring: service periods against the in-memory watermarks \u2500\u2500\n const lines: InvoiceLineDescriptor[] = []\n const watermarkUpdates = new Map<string, Date | null>()\n const chargedItems: BillingItem[] = []\n for (const item of recurringItems) {\n const unitPrice = (item.rateJson as { unit_price?: number } | null)?.unit_price\n if (typeof unitPrice !== 'number') continue\n const computed = computeRecurringLines(\n {\n id: item.id,\n chargeTiming: item.chargeTiming ?? 'in_advance',\n prorate: item.prorate ?? true,\n billStartDate: item.billStartDate,\n billEndDate: item.billEndDate ?? null,\n billedToDate: watermarks.get(item.id) ?? null,\n unitPrice,\n isActive: item.isActive,\n deletedAt: item.deletedAt ?? null,\n },\n { billDate, cycle },\n )\n pushSkips(computed.skipped)\n if (computed.newBilledToDate?.getTime() !== watermarks.get(item.id)?.getTime()) {\n watermarkUpdates.set(item.id, computed.newBilledToDate)\n }\n if (computed.lines.length > 0) {\n lines.push(...buildRecurringLineDescriptors(item, computed.lines))\n chargedItems.push(item)\n }\n }\n\n // \u2500\u2500 One-time: first bill date whose [D \u2212 cycle, D + cycle \u2212 1] window overlaps \u2500\u2500\n const oneTimeWindow: BillPeriod = {\n periodStart: shiftCycles(billDate, cycle, -1),\n periodEnd: addDays(nextBillDate, -1),\n }\n const oneTimesThisCycle = oneTimeItems.filter(\n (it) => !billedOneTimes.has(it.id) && shouldIncludeItem(it as SelectableItem, oneTimeWindow).include,\n )\n lines.push(...buildInvoiceLinesFromItems(oneTimesThisCycle))\n chargedItems.push(...oneTimesThisCycle)\n\n // \u2500\u2500 Usage: always in arrears \u2500\u2500\n const usageResult = await processUsageForAccount(em, account, arrearsPeriod, usageItems, {\n after: usageCountedThrough,\n })\n addWarning('unmatched_usage_uoms', usageResult.unmatchedUoms)\n lines.push(\n ...usageResult.lines.map((line) => ({\n ...line,\n servicePeriodStart: arrearsPeriod.periodStart,\n servicePeriodEnd: arrearsPeriod.periodEnd,\n })),\n )\n addWarning(\n 'currency_mismatch_items',\n chargedItems.filter((it) => it.currencyMismatch).map((it) => it.id),\n )\n\n const period = displayPeriod(lines, arrearsPeriod)\n const record: CycleRecord = {\n bill_date: isoDate(billDate),\n period_start: lines.length ? isoDate(period.periodStart) : null,\n period_end: lines.length ? isoDate(period.periodEnd) : null,\n status: 'empty',\n line_count: lines.length,\n total_net: '0.0000',\n invoice_id: null,\n }\n\n // \u2500\u2500 Dedupe: an open draft already exists for this bill date \u2500\u2500\n const existing =\n params.mode === 'dry' ? null : await findExistingDraftForBillDate(em, account, billDate, testRun)\n if (existing && params.mode === 'real') {\n // Real state is inconsistent (draft written, account not advanced) \u2014\n // stop here without advancing, as before Spec A.\n cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })\n return {\n status: 'skipped_existing_draft',\n warnings: finalizeWarnings(warnings, skippedItems),\n draftInvoiceId: existing,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n }\n\n if (existing) {\n cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })\n } else if (lines.length > 0) {\n const written = await writeDraftOnce(em, {\n mode: params.mode,\n account,\n billDate,\n period,\n billRunId: billRun.id,\n lines,\n })\n if (written === 'conflict') {\n // Unique (account, bill_date, test_run) backstop fired: a concurrent\n // writer got there first. Same semantics as the dedupe check above.\n cycles.push({ ...record, status: 'skipped_existing' })\n if (params.mode === 'real') {\n return {\n status: 'skipped_existing_draft',\n warnings: finalizeWarnings(warnings, skippedItems),\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n }\n billDate = nextBillDate\n for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)\n for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)\n usageCountedThrough = arrearsPeriod.periodEnd\n continue\n }\n if (written.invoiceId) lastDraftInvoiceId = written.invoiceId\n if (Number.parseFloat(written.totalNet) < 0) addWarning('negative_total_bill_dates', [record.bill_date])\n cycles.push({\n ...record,\n status: params.mode === 'dry' ? 'previewed' : 'invoiced',\n total_net: written.totalNet,\n invoice_id: written.invoiceId,\n })\n } else {\n cycles.push(record)\n }\n\n // \u2500\u2500 Advance state: in memory always, persisted in real mode \u2500\u2500\n for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)\n for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)\n usageCountedThrough = arrearsPeriod.periodEnd\n\n if (params.mode === 'real') {\n const now = new Date()\n for (const item of recurringItems) {\n if (!watermarkUpdates.has(item.id)) continue\n item.billedToDate = watermarkUpdates.get(item.id) ?? null\n item.updatedAt = now\n }\n for (const item of oneTimesThisCycle) {\n item.billedToDate = oneTimeWindow.periodEnd\n item.updatedAt = now\n }\n if (usageResult.matchedUoms.length > 0) {\n // Predicate-based bulk update: marks every still-unrated record for\n // the matched uoms up to the period end with a single SQL UPDATE \u2014\n // no id list ever leaves Postgres (spec Performance \"Shape B\").\n await em.nativeUpdate(\n BillingAccountUsage,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n uomCode: { $in: usageResult.matchedUoms },\n ratedInBillRunId: null,\n periodEnd: { $lte: arrearsPeriod.periodEnd },\n deletedAt: null,\n } as never,\n { ratedInBillRunId: billRun.id, updatedAt: now } as never,\n )\n usageRecordsRated += usageResult.ratedCount\n }\n account.nextBillDate = nextBillDate\n account.lastBillDate = arrearsPeriod.periodEnd\n account.updatedAt = now\n await em.flush()\n }\n\n billDate = nextBillDate\n }\n\n const finalWarnings = finalizeWarnings(warnings, skippedItems)\n return {\n status: hasRealWarnings(warnings) ? 'success_with_warnings' : 'success',\n warnings: finalWarnings,\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n}\n\nfunction finalizeWarnings(\n warnings: AccountWarnings,\n skippedItems: SkippedItemWarning[],\n): AccountWarnings | null {\n const merged: AccountWarnings = { ...warnings, ...(skippedItems.length ? { skipped_items: skippedItems } : {}) }\n return Object.keys(merged).length > 0 ? merged : null\n}\n\n/** Skipped items are diagnostics; they don't downgrade the outcome status. */\nfunction hasRealWarnings(warnings: AccountWarnings): boolean {\n return Boolean(\n warnings.currency_mismatch_items?.length ||\n warnings.unmatched_usage_uoms?.length ||\n warnings.negative_total_bill_dates?.length ||\n warnings.catch_up_cap_reached,\n )\n}\n\n/**\n * Existing invoice for `(account, bill_date, test_run)` in ANY status \u2014 a\n * posted invoice for the same bill date also blocks re-billing (e.g. after\n * an operator moved `next_bill_date` back). Test drafts never block real\n * drafts and vice versa. The partial unique index\n * `billing_invoices_bill_date_uq` is the backstop (see `writeDraftOnce`).\n */\nasync function findExistingDraftForBillDate(\n em: EntityManager,\n account: BillingAccount,\n billDate: Date,\n testRun: boolean,\n): Promise<string | null> {\n const existing = await em.findOne(\n BillingInvoice,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n billDate,\n testRun,\n documentType: 'invoice',\n deletedAt: null,\n } as never,\n )\n return existing?.id ?? null\n}\n\n/**\n * Writes the draft inside its own savepoint so a unique-index conflict\n * (concurrent writer for the same bill date) doesn't abort the account's\n * transaction \u2014 it is reported as 'conflict' and handled like a dedupe hit.\n */\nasync function writeDraftOnce(\n em: EntityManager,\n params: Parameters<typeof writeDraftInvoice>[1],\n): Promise<Awaited<ReturnType<typeof writeDraftInvoice>> | 'conflict'> {\n if (params.mode === 'dry') return writeDraftInvoice(em, params)\n try {\n return await em.transactional((tem) => writeDraftInvoice(tem as EntityManager, params))\n } catch (err) {\n if (err instanceof UniqueConstraintViolationException) return 'conflict'\n throw err\n }\n}\n\nfunction computeFinalRunStatus(\n outcomes: readonly BillingRunOutcome[],\n): BillingRun['status'] {\n if (outcomes.length === 0) return 'completed'\n const anyFailed = outcomes.some((o) => o.status === 'failed')\n const anySuccess = outcomes.some(\n (o) => o.status === 'success' || o.status === 'success_with_warnings',\n )\n if (anyFailed && anySuccess) return 'partial_failure'\n if (anyFailed) return 'partial_failure'\n return 'completed'\n}\n\nfunction buildRunSummary(\n outcomes: readonly BillingRunOutcome[],\n totalAccounts: number,\n usageRecordsRated: number,\n): Record<string, unknown> {\n const allCycles = outcomes.flatMap((o) => (Array.isArray(o.cycles) ? (o.cycles as CycleRecord[]) : []))\n return {\n accounts_processed: totalAccounts,\n // Spec A: one draft per invoiced cycle (catch-up writes several per account).\n drafts_created: allCycles.filter((c) => c.status === 'invoiced' && c.invoice_id).length,\n cycles_processed: allCycles.length,\n drafts_skipped_existing: outcomes.filter((o) => o.status === 'skipped_existing_draft')\n .length,\n // SPEC-002 prepaid period close.\n statements_created: outcomes.filter(\n (o) => o.statementId !== null && o.status !== 'skipped_existing_statement',\n ).length,\n statements_skipped_existing: outcomes.filter(\n (o) => o.status === 'skipped_existing_statement',\n ).length,\n accounts_failed: outcomes.filter((o) => o.status === 'failed').length,\n accounts_with_warnings: outcomes.filter((o) => o.status === 'success_with_warnings')\n .length,\n usage_records_rated: usageRecordsRated,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/billing/lib/billRunLabel.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Human-readable label for a Bill Run.\n *\n * The
|
|
4
|
+
"sourcesContent": ["/**\n * Human-readable label for a Bill Run.\n *\n * The BillingRun entity has no `name` column \u2014 the engine keys runs by id.\n * The admin UI derives a stable, scannable label from the run's as-of\n * date so operators see `BILL_RUN_2026-05-20` instead of a raw UUID.\n */\nexport function billRunName(asOfDate: string | null | undefined): string {\n const date =\n typeof asOfDate === 'string' && asOfDate.length >= 10\n ? asOfDate.slice(0, 10)\n : 'unknown'\n return `BILL_RUN_${date}`\n}\n"],
|
|
5
5
|
"mappings": "AAOO,SAAS,YAAY,UAA6C;AACvE,QAAM,OACJ,OAAO,aAAa,YAAY,SAAS,UAAU,KAC/C,SAAS,MAAM,GAAG,EAAE,IACpB;AACN,SAAO,YAAY,IAAI;AACzB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|