@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,23 +1,33 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import type { AwilixContainer } from 'awilix'
|
|
3
|
+
import { UniqueConstraintViolationException } from '@mikro-orm/core'
|
|
3
4
|
import {
|
|
4
5
|
BillingAccount,
|
|
5
6
|
BillingAccountUsage,
|
|
7
|
+
BillingInvoice,
|
|
6
8
|
BillingItem,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
BillingRun,
|
|
10
|
+
BillingRunOutcome,
|
|
9
11
|
} from '../data/entities'
|
|
10
|
-
import { advanceNextBillDate, deriveBillPeriod, isCycleDue } from './billPeriod'
|
|
11
12
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
addDays,
|
|
14
|
+
advanceNextBillDate,
|
|
15
|
+
cloneAtMidnight,
|
|
16
|
+
deriveBillPeriod,
|
|
17
|
+
isCycleDue,
|
|
18
|
+
shiftCycles,
|
|
19
|
+
type BillPeriod,
|
|
20
|
+
} from './billPeriod'
|
|
21
|
+
import { shouldIncludeItem, type SelectableItem } from './itemSelector'
|
|
15
22
|
import {
|
|
16
23
|
buildInvoiceLinesFromItems,
|
|
24
|
+
buildRecurringLineDescriptors,
|
|
25
|
+
displayPeriod,
|
|
17
26
|
writeDraftInvoice,
|
|
18
27
|
type InvoiceLineDescriptor,
|
|
19
28
|
type WriteMode,
|
|
20
29
|
} from './invoiceWriter'
|
|
30
|
+
import { computeRecurringLines, type RecurringSkip } from './servicePeriods'
|
|
21
31
|
import { withTenantLock } from './tenantLock'
|
|
22
32
|
import { processUsageForAccount } from './usageRunner'
|
|
23
33
|
import { processPrepaidAccount } from './prepaidPeriodClose'
|
|
@@ -39,23 +49,24 @@ import type { BillCycle } from '../data/validators'
|
|
|
39
49
|
* nested `em.transactional(...)` (PG SAVEPOINT). A per-account failure
|
|
40
50
|
* rolls back only that account's work, leaving siblings unaffected.
|
|
41
51
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* - `
|
|
52
|
+
* Cycles (Spec A): every run — manual or scheduled, any mode — bills ALL
|
|
53
|
+
* due bill dates up to `asOfDate`, one draft per bill date (never
|
|
54
|
+
* combined), capped at `CATCH_UP_HARD_CAP` per account. The account's
|
|
55
|
+
* `next_bill_date` and each recurring item's `billed_to_date` watermark are
|
|
56
|
+
* advanced in memory cycle by cycle; only `real` persists them:
|
|
57
|
+
* - `real` — persist drafts, advance `next_bill_date` / watermarks, mark
|
|
58
|
+
* usage rated. Production path.
|
|
59
|
+
* - `test` — persist drafts flagged `metadata.test_run=true` for every
|
|
60
|
+
* cycle, but do NOT advance state. Repeatable.
|
|
61
|
+
* - `dry` — compute every cycle; persist nothing. Preview only.
|
|
48
62
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* per missed cycle (never combined).
|
|
53
|
-
* - Scheduled triggers always process exactly ONE cycle per account per
|
|
54
|
-
* run, regardless of how far behind the account is.
|
|
63
|
+
* Recurring items are priced by `servicePeriods.computeRecurringLines`
|
|
64
|
+
* (in advance / in arrears, proration, credits). `one_time` items use the
|
|
65
|
+
* selector; usage is always billed in arrears for `[D − cycle, D − 1]`.
|
|
55
66
|
*
|
|
56
|
-
* Anti-duplicate guards (
|
|
57
|
-
* 1. Open
|
|
58
|
-
* 2. `billed_to_date` per BillingItem.
|
|
67
|
+
* Anti-duplicate guards (every mode except `dry`):
|
|
68
|
+
* 1. Open draft for the same `(account, bill_date, test_run)`.
|
|
69
|
+
* 2. `billed_to_date` per BillingItem (the watermark).
|
|
59
70
|
*/
|
|
60
71
|
|
|
61
72
|
export type RunMode = WriteMode
|
|
@@ -68,12 +79,11 @@ export type RunBillRunParams = {
|
|
|
68
79
|
mode: RunMode
|
|
69
80
|
scopedAccountIds?: string[]
|
|
70
81
|
parentRunId?: string | null
|
|
71
|
-
catchUp?: boolean
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
export type RunBillRunResult = {
|
|
75
|
-
billRun:
|
|
76
|
-
outcomes:
|
|
85
|
+
billRun: BillingRun
|
|
86
|
+
outcomes: BillingRunOutcome[]
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
type RunOutput = RunBillRunResult & {
|
|
@@ -81,9 +91,38 @@ type RunOutput = RunBillRunResult & {
|
|
|
81
91
|
pendingEvents: DomainEvent[]
|
|
82
92
|
}
|
|
83
93
|
|
|
94
|
+
type SkippedItemWarning = {
|
|
95
|
+
item_id: string
|
|
96
|
+
reason: string
|
|
97
|
+
service_period_start: string | null
|
|
98
|
+
service_period_end: string | null
|
|
99
|
+
}
|
|
100
|
+
|
|
84
101
|
type AccountWarnings = {
|
|
85
102
|
currency_mismatch_items?: string[]
|
|
86
103
|
unmatched_usage_uoms?: string[]
|
|
104
|
+
/** Spec A: bill dates whose draft has a negative total (credits > charges). */
|
|
105
|
+
negative_total_bill_dates?: string[]
|
|
106
|
+
/** Spec A: the account hit CATCH_UP_HARD_CAP; later cycles wait for the next run. */
|
|
107
|
+
catch_up_cap_reached?: boolean
|
|
108
|
+
/** Spec A: diagnostic — partial periods not prorated, no-credit ends, caps. */
|
|
109
|
+
skipped_items?: SkippedItemWarning[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** `charged` is the prepaid analogue of `invoiced` (Spec F, P-D6). */
|
|
113
|
+
export type CycleStatus = 'invoiced' | 'charged' | 'previewed' | 'empty' | 'skipped_existing'
|
|
114
|
+
|
|
115
|
+
export type CycleRecord = {
|
|
116
|
+
bill_date: string
|
|
117
|
+
period_start: string | null
|
|
118
|
+
period_end: string | null
|
|
119
|
+
status: CycleStatus
|
|
120
|
+
line_count: number
|
|
121
|
+
total_net: string
|
|
122
|
+
invoice_id: string | null
|
|
123
|
+
/** Prepaid only: what left the balance, and the statement for the period. */
|
|
124
|
+
total_debit?: string
|
|
125
|
+
statement_id?: string | null
|
|
87
126
|
}
|
|
88
127
|
|
|
89
128
|
type AccountProcessSuccess = {
|
|
@@ -99,6 +138,8 @@ type AccountProcessSuccess = {
|
|
|
99
138
|
statementEvents?: DomainEvent[]
|
|
100
139
|
cyclesEmitted: number
|
|
101
140
|
usageRecordsRated: number
|
|
141
|
+
/** Spec A: per-bill-date breakdown (postpaid). */
|
|
142
|
+
cycles?: CycleRecord[]
|
|
102
143
|
}
|
|
103
144
|
|
|
104
145
|
type AccountProcessFailed = {
|
|
@@ -108,8 +149,10 @@ type AccountProcessFailed = {
|
|
|
108
149
|
|
|
109
150
|
type AccountProcessResult = AccountProcessSuccess | AccountProcessFailed
|
|
110
151
|
|
|
111
|
-
const
|
|
112
|
-
const
|
|
152
|
+
export const CATCH_UP_HARD_CAP = 60 // safety net so a misconfigured account can't loop forever
|
|
153
|
+
const SKIPPED_ITEMS_WARNING_CAP = 200
|
|
154
|
+
/** Skip reasons that are normal state, not worth surfacing on the outcome. */
|
|
155
|
+
const QUIET_SKIP_REASONS = new Set(['item-inactive', 'item-soft-deleted'])
|
|
113
156
|
|
|
114
157
|
export async function runBillRun(
|
|
115
158
|
em: EntityManager,
|
|
@@ -136,14 +179,13 @@ async function executeRunWithinLock(
|
|
|
136
179
|
params: RunBillRunParams,
|
|
137
180
|
): Promise<RunOutput> {
|
|
138
181
|
const now = new Date()
|
|
139
|
-
const billRun = em.create(
|
|
182
|
+
const billRun = em.create(BillingRun, {
|
|
140
183
|
organizationId: params.organizationId,
|
|
141
184
|
tenantId: params.tenantId,
|
|
142
185
|
triggeredBy: params.triggeredBy,
|
|
143
186
|
parentRunId: params.parentRunId ?? null,
|
|
144
187
|
dryRun: params.mode === 'dry',
|
|
145
188
|
testMode: params.mode === 'test',
|
|
146
|
-
catchUp: Boolean(params.catchUp),
|
|
147
189
|
scopedAccountIds: params.scopedAccountIds ?? null,
|
|
148
190
|
asOfDate: params.asOfDate,
|
|
149
191
|
startedAt: now,
|
|
@@ -157,7 +199,7 @@ async function executeRunWithinLock(
|
|
|
157
199
|
await em.flush()
|
|
158
200
|
|
|
159
201
|
const accounts = await selectTargetAccounts(em, params)
|
|
160
|
-
const outcomes:
|
|
202
|
+
const outcomes: BillingRunOutcome[] = []
|
|
161
203
|
const pendingEvents: DomainEvent[] = []
|
|
162
204
|
let totalUsageRecordsRated = 0
|
|
163
205
|
|
|
@@ -166,7 +208,7 @@ async function executeRunWithinLock(
|
|
|
166
208
|
if (processResult.status !== 'failed') {
|
|
167
209
|
totalUsageRecordsRated += processResult.usageRecordsRated
|
|
168
210
|
}
|
|
169
|
-
const outcome = em.create(
|
|
211
|
+
const outcome = em.create(BillingRunOutcome, {
|
|
170
212
|
organizationId: params.organizationId,
|
|
171
213
|
tenantId: params.tenantId,
|
|
172
214
|
billRunId: billRun.id,
|
|
@@ -179,6 +221,7 @@ async function executeRunWithinLock(
|
|
|
179
221
|
: (processResult.warnings as Record<string, unknown> | null),
|
|
180
222
|
draftInvoiceId: processResult.status === 'failed' ? null : processResult.draftInvoiceId,
|
|
181
223
|
statementId: processResult.status === 'failed' ? null : (processResult.statementId ?? null),
|
|
224
|
+
cycles: processResult.status === 'failed' ? null : (processResult.cycles ?? null),
|
|
182
225
|
isActive: true,
|
|
183
226
|
createdAt: new Date(),
|
|
184
227
|
updatedAt: new Date(),
|
|
@@ -222,7 +265,7 @@ async function selectTargetAccounts(
|
|
|
222
265
|
async function processAccountSafely(
|
|
223
266
|
em: EntityManager,
|
|
224
267
|
container: AwilixContainer,
|
|
225
|
-
billRun:
|
|
268
|
+
billRun: BillingRun,
|
|
226
269
|
account: BillingAccount,
|
|
227
270
|
params: RunBillRunParams,
|
|
228
271
|
): Promise<AccountProcessResult> {
|
|
@@ -241,7 +284,7 @@ async function processAccountSafely(
|
|
|
241
284
|
async function processAccount(
|
|
242
285
|
em: EntityManager,
|
|
243
286
|
container: AwilixContainer,
|
|
244
|
-
billRun:
|
|
287
|
+
billRun: BillingRun,
|
|
245
288
|
account: BillingAccount,
|
|
246
289
|
params: RunBillRunParams,
|
|
247
290
|
): Promise<AccountProcessResult> {
|
|
@@ -253,7 +296,6 @@ async function processAccount(
|
|
|
253
296
|
asOfDate: params.asOfDate,
|
|
254
297
|
mode: params.mode,
|
|
255
298
|
triggeredBy: params.triggeredBy,
|
|
256
|
-
catchUp: params.catchUp,
|
|
257
299
|
})
|
|
258
300
|
if (result.status === 'failed') {
|
|
259
301
|
return { status: 'failed', errorMessage: result.errorMessage }
|
|
@@ -265,111 +307,236 @@ async function processAccount(
|
|
|
265
307
|
statementId: result.statementId,
|
|
266
308
|
statementEvents: result.statementEvents,
|
|
267
309
|
cyclesEmitted: result.cyclesEmitted,
|
|
310
|
+
cycles: result.cycles,
|
|
268
311
|
usageRecordsRated: 0,
|
|
269
312
|
}
|
|
270
313
|
}
|
|
271
314
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
315
|
+
return processPostpaidAccount(em, container, billRun, account, params)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function isoDate(date: Date): string {
|
|
319
|
+
return cloneAtMidnight(date).toISOString().slice(0, 10)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function toSkipWarning(skip: RecurringSkip): SkippedItemWarning {
|
|
323
|
+
return {
|
|
324
|
+
item_id: skip.billingItemId,
|
|
325
|
+
reason: skip.reason,
|
|
326
|
+
service_period_start: skip.servicePeriodStart ? isoDate(skip.servicePeriodStart) : null,
|
|
327
|
+
service_period_end: skip.servicePeriodEnd ? isoDate(skip.servicePeriodEnd) : null,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
276
330
|
|
|
277
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Postpaid cycle loop (Spec A). Items are loaded once; the bill date and
|
|
333
|
+
* the per-item watermarks advance in memory so test / dry can simulate every
|
|
334
|
+
* due cycle exactly like real does — only persistence differs by mode.
|
|
335
|
+
*/
|
|
336
|
+
async function processPostpaidAccount(
|
|
337
|
+
em: EntityManager,
|
|
338
|
+
container: AwilixContainer,
|
|
339
|
+
billRun: BillingRun,
|
|
340
|
+
account: BillingAccount,
|
|
341
|
+
params: RunBillRunParams,
|
|
342
|
+
): Promise<AccountProcessResult> {
|
|
343
|
+
const cycle = account.billCycle as BillCycle
|
|
344
|
+
const testRun = params.mode === 'test'
|
|
345
|
+
const items = (await em.find(BillingItem, {
|
|
346
|
+
tenantId: account.tenantId,
|
|
347
|
+
organizationId: account.organizationId,
|
|
348
|
+
billAccountId: account.id,
|
|
349
|
+
deletedAt: null,
|
|
350
|
+
} as never)) as BillingItem[]
|
|
351
|
+
const recurringItems = items.filter((it) => it.type === 'recurring')
|
|
352
|
+
const oneTimeItems = items.filter((it) => it.type === 'one_time')
|
|
353
|
+
const usageItems = items.filter((it) => it.type === 'usage')
|
|
354
|
+
|
|
355
|
+
// In-memory cycle state — persisted per cycle only in real mode.
|
|
356
|
+
const watermarks = new Map<string, Date | null>(
|
|
357
|
+
recurringItems.map((it) => [it.id, it.billedToDate ? cloneAtMidnight(it.billedToDate) : null]),
|
|
358
|
+
)
|
|
359
|
+
const billedOneTimes = new Set<string>(oneTimeItems.filter((it) => it.billedToDate).map((it) => it.id))
|
|
360
|
+
let billDate = cloneAtMidnight(account.nextBillDate)
|
|
361
|
+
// Usage already counted by an earlier cycle of THIS run (test / dry never
|
|
362
|
+
// mark records, so later simulated cycles must not re-count them).
|
|
363
|
+
let usageCountedThrough: Date | null = null
|
|
364
|
+
|
|
365
|
+
const cycles: CycleRecord[] = []
|
|
366
|
+
const warnings: AccountWarnings = {}
|
|
367
|
+
const skippedItems: SkippedItemWarning[] = []
|
|
278
368
|
let lastDraftInvoiceId: string | null = null
|
|
279
|
-
let
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
369
|
+
let usageRecordsRated = 0
|
|
370
|
+
|
|
371
|
+
const pushSkips = (skips: readonly RecurringSkip[]) => {
|
|
372
|
+
for (const skip of skips) {
|
|
373
|
+
if (QUIET_SKIP_REASONS.has(skip.reason)) continue
|
|
374
|
+
if (skippedItems.length < SKIPPED_ITEMS_WARNING_CAP) skippedItems.push(toSkipWarning(skip))
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const addWarning = (key: 'currency_mismatch_items' | 'unmatched_usage_uoms' | 'negative_total_bill_dates', values: string[]) => {
|
|
378
|
+
if (values.length === 0) return
|
|
379
|
+
warnings[key] = Array.from(new Set([...(warnings[key] ?? []), ...values]))
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
while (isCycleDue(billDate, params.asOfDate)) {
|
|
383
|
+
if (cycles.length >= CATCH_UP_HARD_CAP) {
|
|
384
|
+
warnings.catch_up_cap_reached = true
|
|
385
|
+
break
|
|
386
|
+
}
|
|
387
|
+
const nextBillDate = advanceNextBillDate(billDate, cycle)
|
|
388
|
+
const arrearsPeriod: BillPeriod = deriveBillPeriod(billDate, cycle)
|
|
389
|
+
|
|
390
|
+
// ── Recurring: service periods against the in-memory watermarks ──
|
|
391
|
+
const lines: InvoiceLineDescriptor[] = []
|
|
392
|
+
const watermarkUpdates = new Map<string, Date | null>()
|
|
393
|
+
const chargedItems: BillingItem[] = []
|
|
394
|
+
for (const item of recurringItems) {
|
|
395
|
+
const unitPrice = (item.rateJson as { unit_price?: number } | null)?.unit_price
|
|
396
|
+
if (typeof unitPrice !== 'number') continue
|
|
397
|
+
const computed = computeRecurringLines(
|
|
398
|
+
{
|
|
399
|
+
id: item.id,
|
|
400
|
+
chargeTiming: item.chargeTiming ?? 'in_advance',
|
|
401
|
+
prorate: item.prorate ?? true,
|
|
402
|
+
billStartDate: item.billStartDate,
|
|
403
|
+
billEndDate: item.billEndDate ?? null,
|
|
404
|
+
billedToDate: watermarks.get(item.id) ?? null,
|
|
405
|
+
unitPrice,
|
|
406
|
+
isActive: item.isActive,
|
|
407
|
+
deletedAt: item.deletedAt ?? null,
|
|
408
|
+
},
|
|
409
|
+
{ billDate, cycle },
|
|
410
|
+
)
|
|
411
|
+
pushSkips(computed.skipped)
|
|
412
|
+
if (computed.newBilledToDate?.getTime() !== watermarks.get(item.id)?.getTime()) {
|
|
413
|
+
watermarkUpdates.set(item.id, computed.newBilledToDate)
|
|
414
|
+
}
|
|
415
|
+
if (computed.lines.length > 0) {
|
|
416
|
+
lines.push(...buildRecurringLineDescriptors(item, computed.lines))
|
|
417
|
+
chargedItems.push(item)
|
|
296
418
|
}
|
|
297
419
|
}
|
|
298
420
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
deletedAt: null,
|
|
304
|
-
} as never)
|
|
305
|
-
|
|
306
|
-
const usageItems = (items as BillingItem[]).filter((it) => it.type === 'usage')
|
|
307
|
-
|
|
308
|
-
// Non-usage path: one_time + recurring filtered by the selector.
|
|
309
|
-
const { included } = selectItemsForPeriod(items as SelectableItem[], period)
|
|
310
|
-
const billableItems = included as unknown as BillingItem[]
|
|
311
|
-
|
|
312
|
-
const currencyMismatchIds = billableItems
|
|
313
|
-
.filter((it) => it.currencyMismatch)
|
|
314
|
-
.map((it) => it.id)
|
|
315
|
-
if (currencyMismatchIds.length > 0) {
|
|
316
|
-
aggregatedWarnings.currency_mismatch_items = [
|
|
317
|
-
...(aggregatedWarnings.currency_mismatch_items ?? []),
|
|
318
|
-
...currencyMismatchIds,
|
|
319
|
-
]
|
|
421
|
+
// ── One-time: first bill date whose [D − cycle, D + cycle − 1] window overlaps ──
|
|
422
|
+
const oneTimeWindow: BillPeriod = {
|
|
423
|
+
periodStart: shiftCycles(billDate, cycle, -1),
|
|
424
|
+
periodEnd: addDays(nextBillDate, -1),
|
|
320
425
|
}
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
426
|
+
const oneTimesThisCycle = oneTimeItems.filter(
|
|
427
|
+
(it) => !billedOneTimes.has(it.id) && shouldIncludeItem(it as SelectableItem, oneTimeWindow).include,
|
|
428
|
+
)
|
|
429
|
+
lines.push(...buildInvoiceLinesFromItems(oneTimesThisCycle))
|
|
430
|
+
chargedItems.push(...oneTimesThisCycle)
|
|
431
|
+
|
|
432
|
+
// ── Usage: always in arrears ──
|
|
433
|
+
const usageResult = await processUsageForAccount(em, account, arrearsPeriod, usageItems, {
|
|
434
|
+
after: usageCountedThrough,
|
|
435
|
+
})
|
|
436
|
+
addWarning('unmatched_usage_uoms', usageResult.unmatchedUoms)
|
|
437
|
+
lines.push(
|
|
438
|
+
...usageResult.lines.map((line) => ({
|
|
439
|
+
...line,
|
|
440
|
+
servicePeriodStart: arrearsPeriod.periodStart,
|
|
441
|
+
servicePeriodEnd: arrearsPeriod.periodEnd,
|
|
442
|
+
})),
|
|
443
|
+
)
|
|
444
|
+
addWarning(
|
|
445
|
+
'currency_mismatch_items',
|
|
446
|
+
chargedItems.filter((it) => it.currencyMismatch).map((it) => it.id),
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
const period = displayPeriod(lines, arrearsPeriod)
|
|
450
|
+
const record: CycleRecord = {
|
|
451
|
+
bill_date: isoDate(billDate),
|
|
452
|
+
period_start: lines.length ? isoDate(period.periodStart) : null,
|
|
453
|
+
period_end: lines.length ? isoDate(period.periodEnd) : null,
|
|
454
|
+
status: 'empty',
|
|
455
|
+
line_count: lines.length,
|
|
456
|
+
total_net: '0.0000',
|
|
457
|
+
invoice_id: null,
|
|
331
458
|
}
|
|
332
459
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
460
|
+
// ── Dedupe: an open draft already exists for this bill date ──
|
|
461
|
+
const existing =
|
|
462
|
+
params.mode === 'dry' ? null : await findExistingDraftForBillDate(em, account, billDate, testRun)
|
|
463
|
+
if (existing && params.mode === 'real') {
|
|
464
|
+
// Real state is inconsistent (draft written, account not advanced) —
|
|
465
|
+
// stop here without advancing, as before Spec A.
|
|
466
|
+
cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })
|
|
467
|
+
return {
|
|
468
|
+
status: 'skipped_existing_draft',
|
|
469
|
+
warnings: finalizeWarnings(warnings, skippedItems),
|
|
470
|
+
draftInvoiceId: existing,
|
|
471
|
+
cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,
|
|
472
|
+
usageRecordsRated,
|
|
473
|
+
cycles,
|
|
474
|
+
}
|
|
347
475
|
}
|
|
348
476
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
477
|
+
if (existing) {
|
|
478
|
+
cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })
|
|
479
|
+
} else if (lines.length > 0) {
|
|
480
|
+
const written = await writeDraftOnce(em, {
|
|
481
|
+
mode: params.mode,
|
|
482
|
+
account,
|
|
483
|
+
billDate,
|
|
484
|
+
period,
|
|
485
|
+
billRunId: billRun.id,
|
|
486
|
+
lines,
|
|
487
|
+
})
|
|
488
|
+
if (written === 'conflict') {
|
|
489
|
+
// Unique (account, bill_date, test_run) backstop fired: a concurrent
|
|
490
|
+
// writer got there first. Same semantics as the dedupe check above.
|
|
491
|
+
cycles.push({ ...record, status: 'skipped_existing' })
|
|
492
|
+
if (params.mode === 'real') {
|
|
493
|
+
return {
|
|
494
|
+
status: 'skipped_existing_draft',
|
|
495
|
+
warnings: finalizeWarnings(warnings, skippedItems),
|
|
496
|
+
draftInvoiceId: lastDraftInvoiceId,
|
|
497
|
+
cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,
|
|
498
|
+
usageRecordsRated,
|
|
499
|
+
cycles,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
billDate = nextBillDate
|
|
503
|
+
for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)
|
|
504
|
+
for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)
|
|
505
|
+
usageCountedThrough = arrearsPeriod.periodEnd
|
|
506
|
+
continue
|
|
507
|
+
}
|
|
508
|
+
if (written.invoiceId) lastDraftInvoiceId = written.invoiceId
|
|
509
|
+
if (Number.parseFloat(written.totalNet) < 0) addWarning('negative_total_bill_dates', [record.bill_date])
|
|
510
|
+
cycles.push({
|
|
511
|
+
...record,
|
|
512
|
+
status: params.mode === 'dry' ? 'previewed' : 'invoiced',
|
|
513
|
+
total_net: written.totalNet,
|
|
514
|
+
invoice_id: written.invoiceId,
|
|
515
|
+
})
|
|
516
|
+
} else {
|
|
517
|
+
cycles.push(record)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ── Advance state: in memory always, persisted in real mode ──
|
|
521
|
+
for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)
|
|
522
|
+
for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)
|
|
523
|
+
usageCountedThrough = arrearsPeriod.periodEnd
|
|
357
524
|
|
|
358
525
|
if (params.mode === 'real') {
|
|
359
526
|
const now = new Date()
|
|
360
|
-
for (const item of
|
|
361
|
-
item.
|
|
527
|
+
for (const item of recurringItems) {
|
|
528
|
+
if (!watermarkUpdates.has(item.id)) continue
|
|
529
|
+
item.billedToDate = watermarkUpdates.get(item.id) ?? null
|
|
530
|
+
item.updatedAt = now
|
|
531
|
+
}
|
|
532
|
+
for (const item of oneTimesThisCycle) {
|
|
533
|
+
item.billedToDate = oneTimeWindow.periodEnd
|
|
362
534
|
item.updatedAt = now
|
|
363
535
|
}
|
|
364
|
-
// Mark consumed usage records — single bulk update so we don't
|
|
365
|
-
// round-trip per record on accounts with thousands of usage rows.
|
|
366
536
|
if (usageResult.matchedUoms.length > 0) {
|
|
367
|
-
// Predicate-based bulk update: marks every still-unrated record
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
// memory-bounded even for telco accounts with millions of
|
|
371
|
-
// usage records per cycle (spec Performance Considerations
|
|
372
|
-
// "Shape B").
|
|
537
|
+
// Predicate-based bulk update: marks every still-unrated record for
|
|
538
|
+
// the matched uoms up to the period end with a single SQL UPDATE —
|
|
539
|
+
// no id list ever leaves Postgres (spec Performance "Shape B").
|
|
373
540
|
await em.nativeUpdate(
|
|
374
541
|
BillingAccountUsage,
|
|
375
542
|
{
|
|
@@ -378,68 +545,100 @@ async function processAccount(
|
|
|
378
545
|
billAccountId: account.id,
|
|
379
546
|
uomCode: { $in: usageResult.matchedUoms },
|
|
380
547
|
ratedInBillRunId: null,
|
|
381
|
-
periodEnd: { $lte:
|
|
548
|
+
periodEnd: { $lte: arrearsPeriod.periodEnd },
|
|
382
549
|
deletedAt: null,
|
|
383
550
|
} as never,
|
|
384
551
|
{ ratedInBillRunId: billRun.id, updatedAt: now } as never,
|
|
385
552
|
)
|
|
553
|
+
usageRecordsRated += usageResult.ratedCount
|
|
386
554
|
}
|
|
387
|
-
|
|
388
|
-
account.
|
|
389
|
-
account.nextBillDate,
|
|
390
|
-
account.billCycle as BillCycle,
|
|
391
|
-
)
|
|
392
|
-
account.lastBillDate = period.periodEnd
|
|
555
|
+
account.nextBillDate = nextBillDate
|
|
556
|
+
account.lastBillDate = arrearsPeriod.periodEnd
|
|
393
557
|
account.updatedAt = now
|
|
394
558
|
await em.flush()
|
|
395
|
-
} else {
|
|
396
|
-
// Test / dry: do not advance state. One iteration only.
|
|
397
|
-
break
|
|
398
559
|
}
|
|
399
560
|
|
|
400
|
-
|
|
561
|
+
billDate = nextBillDate
|
|
401
562
|
}
|
|
402
563
|
|
|
403
|
-
const
|
|
404
|
-
(aggregatedWarnings.currency_mismatch_items ?? []).length > 0
|
|
405
|
-
const hasUnmatchedUsage =
|
|
406
|
-
(aggregatedWarnings.unmatched_usage_uoms ?? []).length > 0
|
|
407
|
-
const hasWarnings = hasCurrencyWarnings || hasUnmatchedUsage
|
|
564
|
+
const finalWarnings = finalizeWarnings(warnings, skippedItems)
|
|
408
565
|
return {
|
|
409
|
-
status:
|
|
410
|
-
warnings:
|
|
566
|
+
status: hasRealWarnings(warnings) ? 'success_with_warnings' : 'success',
|
|
567
|
+
warnings: finalWarnings,
|
|
411
568
|
draftInvoiceId: lastDraftInvoiceId,
|
|
412
|
-
cyclesEmitted,
|
|
413
|
-
usageRecordsRated
|
|
569
|
+
cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,
|
|
570
|
+
usageRecordsRated,
|
|
571
|
+
cycles,
|
|
414
572
|
}
|
|
415
573
|
}
|
|
416
574
|
|
|
417
|
-
|
|
575
|
+
function finalizeWarnings(
|
|
576
|
+
warnings: AccountWarnings,
|
|
577
|
+
skippedItems: SkippedItemWarning[],
|
|
578
|
+
): AccountWarnings | null {
|
|
579
|
+
const merged: AccountWarnings = { ...warnings, ...(skippedItems.length ? { skipped_items: skippedItems } : {}) }
|
|
580
|
+
return Object.keys(merged).length > 0 ? merged : null
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** Skipped items are diagnostics; they don't downgrade the outcome status. */
|
|
584
|
+
function hasRealWarnings(warnings: AccountWarnings): boolean {
|
|
585
|
+
return Boolean(
|
|
586
|
+
warnings.currency_mismatch_items?.length ||
|
|
587
|
+
warnings.unmatched_usage_uoms?.length ||
|
|
588
|
+
warnings.negative_total_bill_dates?.length ||
|
|
589
|
+
warnings.catch_up_cap_reached,
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Existing invoice for `(account, bill_date, test_run)` in ANY status — a
|
|
595
|
+
* posted invoice for the same bill date also blocks re-billing (e.g. after
|
|
596
|
+
* an operator moved `next_bill_date` back). Test drafts never block real
|
|
597
|
+
* drafts and vice versa. The partial unique index
|
|
598
|
+
* `billing_invoices_bill_date_uq` is the backstop (see `writeDraftOnce`).
|
|
599
|
+
*/
|
|
600
|
+
async function findExistingDraftForBillDate(
|
|
418
601
|
em: EntityManager,
|
|
419
602
|
account: BillingAccount,
|
|
420
|
-
|
|
603
|
+
billDate: Date,
|
|
604
|
+
testRun: boolean,
|
|
421
605
|
): Promise<string | null> {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
606
|
+
const existing = await em.findOne(
|
|
607
|
+
BillingInvoice,
|
|
608
|
+
{
|
|
609
|
+
tenantId: account.tenantId,
|
|
610
|
+
organizationId: account.organizationId,
|
|
611
|
+
billAccountId: account.id,
|
|
612
|
+
billDate,
|
|
613
|
+
testRun,
|
|
614
|
+
documentType: 'invoice',
|
|
615
|
+
deletedAt: null,
|
|
616
|
+
} as never,
|
|
617
|
+
)
|
|
618
|
+
return existing?.id ?? null
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Writes the draft inside its own savepoint so a unique-index conflict
|
|
623
|
+
* (concurrent writer for the same bill date) doesn't abort the account's
|
|
624
|
+
* transaction — it is reported as 'conflict' and handled like a dedupe hit.
|
|
625
|
+
*/
|
|
626
|
+
async function writeDraftOnce(
|
|
627
|
+
em: EntityManager,
|
|
628
|
+
params: Parameters<typeof writeDraftInvoice>[1],
|
|
629
|
+
): Promise<Awaited<ReturnType<typeof writeDraftInvoice>> | 'conflict'> {
|
|
630
|
+
if (params.mode === 'dry') return writeDraftInvoice(em, params)
|
|
631
|
+
try {
|
|
632
|
+
return await em.transactional((tem) => writeDraftInvoice(tem as EntityManager, params))
|
|
633
|
+
} catch (err) {
|
|
634
|
+
if (err instanceof UniqueConstraintViolationException) return 'conflict'
|
|
635
|
+
throw err
|
|
636
|
+
}
|
|
438
637
|
}
|
|
439
638
|
|
|
440
639
|
function computeFinalRunStatus(
|
|
441
|
-
outcomes: readonly
|
|
442
|
-
):
|
|
640
|
+
outcomes: readonly BillingRunOutcome[],
|
|
641
|
+
): BillingRun['status'] {
|
|
443
642
|
if (outcomes.length === 0) return 'completed'
|
|
444
643
|
const anyFailed = outcomes.some((o) => o.status === 'failed')
|
|
445
644
|
const anySuccess = outcomes.some(
|
|
@@ -451,15 +650,16 @@ function computeFinalRunStatus(
|
|
|
451
650
|
}
|
|
452
651
|
|
|
453
652
|
function buildRunSummary(
|
|
454
|
-
outcomes: readonly
|
|
653
|
+
outcomes: readonly BillingRunOutcome[],
|
|
455
654
|
totalAccounts: number,
|
|
456
655
|
usageRecordsRated: number,
|
|
457
656
|
): Record<string, unknown> {
|
|
657
|
+
const allCycles = outcomes.flatMap((o) => (Array.isArray(o.cycles) ? (o.cycles as CycleRecord[]) : []))
|
|
458
658
|
return {
|
|
459
659
|
accounts_processed: totalAccounts,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
660
|
+
// Spec A: one draft per invoiced cycle (catch-up writes several per account).
|
|
661
|
+
drafts_created: allCycles.filter((c) => c.status === 'invoiced' && c.invoice_id).length,
|
|
662
|
+
cycles_processed: allCycles.length,
|
|
463
663
|
drafts_skipped_existing: outcomes.filter((o) => o.status === 'skipped_existing_draft')
|
|
464
664
|
.length,
|
|
465
665
|
// SPEC-002 prepaid period close.
|