@dainamite/billing 0.20.0 → 0.22.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 +103 -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 +47 -4
- package/dist/modules/billing/api/accounts/route.js.map +2 -2
- package/dist/modules/billing/api/export/account/[id]/route.js +11 -7
- 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 +70 -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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +43 -6
- 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 +1 -1
- package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
- package/dist/modules/billing/api/runs/route.js +9 -3
- 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/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 +269 -167
- 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 +151 -4
- 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 +560 -4
- 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 +50 -0
- package/dist/modules/billing/events.js.map +2 -2
- package/dist/modules/billing/i18n/de.json +880 -0
- package/dist/modules/billing/i18n/en.json +457 -21
- package/dist/modules/billing/i18n/es.json +880 -0
- package/dist/modules/billing/i18n/pl.json +458 -22
- 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 +237 -102
- package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
- 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 +133 -0
- package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
- package/dist/modules/billing/lib/invoiceLanguages.js +28 -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 +276 -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/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/generated/entities/billing_account/index.ts +3 -0
- package/generated/entities/billing_allocation/index.ts +18 -0
- package/generated/entities/billing_document_sequence/index.ts +11 -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 +4 -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 +0 -1
- package/generated/entities/billing_run_outcome/index.ts +1 -0
- package/generated/entities/billing_settings/index.ts +26 -0
- package/generated/entities/billing_statement/index.ts +1 -0
- package/generated/entities.ids.generated.ts +7 -0
- package/package.json +5 -1
- package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +171 -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 +436 -354
- 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 +8 -1
- package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
- package/src/modules/billing/__tests__/i18n/dictionaries.test.ts +64 -0
- 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 +228 -17
- 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 +206 -0
- package/src/modules/billing/__tests__/lib/invoiceLanguages.test.ts +49 -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 +269 -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 +197 -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 +59 -4
- package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
- package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
- package/src/modules/billing/api/invoices/[id]/pdf/route.ts +80 -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 +2 -2
- package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
- package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
- package/src/modules/billing/api/invoices/post/route.ts +7 -6
- package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
- 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 +49 -6
- 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 +2 -1
- package/src/modules/billing/api/runs/route.ts +15 -7
- 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 +70 -4
- package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
- 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 +18 -2
- 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 +49 -18
- package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
- package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
- package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
- package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
- package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
- 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/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 +364 -231
- 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 +205 -3
- 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 +19 -6
- 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 +656 -6
- package/src/modules/billing/data/entityIds.ts +9 -0
- package/src/modules/billing/data/validators.ts +388 -8
- package/src/modules/billing/events.ts +56 -3
- package/src/modules/billing/i18n/de.json +880 -0
- package/src/modules/billing/i18n/en.json +457 -21
- package/src/modules/billing/i18n/es.json +880 -0
- package/src/modules/billing/i18n/pl.json +458 -22
- 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 +350 -150
- 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 +196 -0
- package/src/modules/billing/lib/invoiceLanguages.ts +57 -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 +329 -0
- package/src/modules/billing/lib/pdf/invoicePdf.ts +220 -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 +407 -239
- 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/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/dist/modules/billing/lib/invoiceStatus.js +0 -38
- package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
- package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
- package/src/modules/billing/lib/invoiceStatus.ts +0 -61
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an item stands against today (account Items tab): has it been billed,
|
|
3
|
+
* and through which date? Pure — computed from the item row and today's date.
|
|
4
|
+
*
|
|
5
|
+
* inactive the item is switched off
|
|
6
|
+
* metered usage item (billed from usage records, no watermark)
|
|
7
|
+
* scheduled starts after today, nothing billed yet
|
|
8
|
+
* awaiting_run started, but no Bill Run has charged it yet
|
|
9
|
+
* billed one-time item already charged
|
|
10
|
+
* current recurring, charged through today or later (paid ahead)
|
|
11
|
+
* due recurring, charged only through a past date — the next
|
|
12
|
+
* Bill Run will charge the rest
|
|
13
|
+
* fully_billed recurring with an end date, charged through that end
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type ItemBillingStatus =
|
|
17
|
+
| 'inactive'
|
|
18
|
+
| 'metered'
|
|
19
|
+
| 'scheduled'
|
|
20
|
+
| 'awaiting_run'
|
|
21
|
+
| 'billed'
|
|
22
|
+
| 'current'
|
|
23
|
+
| 'due'
|
|
24
|
+
| 'fully_billed'
|
|
25
|
+
|
|
26
|
+
export type ItemBillingInput = {
|
|
27
|
+
type: 'one_time' | 'recurring' | 'usage'
|
|
28
|
+
is_active: boolean
|
|
29
|
+
bill_start_date: string
|
|
30
|
+
bill_end_date?: string | null
|
|
31
|
+
billed_to_date?: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type ItemBillingState = {
|
|
35
|
+
status: ItemBillingStatus
|
|
36
|
+
/** The relevant date for the label: start, billed-through or due-since day (YYYY-MM-DD). */
|
|
37
|
+
date: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function day(value: string | null | undefined): string | null {
|
|
41
|
+
return value ? value.slice(0, 10) : null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nextDay(iso: string): string {
|
|
45
|
+
const date = new Date(`${iso}T00:00:00Z`)
|
|
46
|
+
date.setUTCDate(date.getUTCDate() + 1)
|
|
47
|
+
return date.toISOString().slice(0, 10)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `today` is a YYYY-MM-DD string (the viewer's calendar day). */
|
|
51
|
+
export function itemBillingState(item: ItemBillingInput, today: string): ItemBillingState {
|
|
52
|
+
if (!item.is_active) return { status: 'inactive', date: null }
|
|
53
|
+
if (item.type === 'usage') return { status: 'metered', date: null }
|
|
54
|
+
const start = day(item.bill_start_date) ?? today
|
|
55
|
+
const billedTo = day(item.billed_to_date)
|
|
56
|
+
const end = day(item.bill_end_date)
|
|
57
|
+
|
|
58
|
+
if (item.type === 'one_time') {
|
|
59
|
+
if (billedTo) return { status: 'billed', date: null }
|
|
60
|
+
return start > today ? { status: 'scheduled', date: start } : { status: 'awaiting_run', date: null }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!billedTo) return start > today ? { status: 'scheduled', date: start } : { status: 'awaiting_run', date: null }
|
|
64
|
+
if (end && billedTo >= end) return { status: 'fully_billed', date: billedTo }
|
|
65
|
+
if (billedTo >= today) return { status: 'current', date: billedTo }
|
|
66
|
+
return { status: 'due', date: nextDay(billedTo) }
|
|
67
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { toIsoDate, toUtcMidnight } from './dateOnly'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Change the price of a recurring item from a date on (Spec F follow-up):
|
|
5
|
+
* the current item ends the day before `effectiveDate`, and a copy with the
|
|
6
|
+
* new price starts on `effectiveDate` and keeps the original end date.
|
|
7
|
+
*
|
|
8
|
+
* Nothing is billed twice: if the old item was already billed past the
|
|
9
|
+
* change (in advance), the engine credits the unused days (prorate on) and
|
|
10
|
+
* the new item charges them at the new price; with prorate off the old item
|
|
11
|
+
* keeps them and the new item starts charging at its next full period.
|
|
12
|
+
*
|
|
13
|
+
* Pure — validates and computes the dates; the command does the writes.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type PriceChangeItem = {
|
|
17
|
+
type: string
|
|
18
|
+
isActive: boolean
|
|
19
|
+
deletedAt?: Date | string | null
|
|
20
|
+
billStartDate: Date | string
|
|
21
|
+
billEndDate?: Date | string | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PriceChangePlan = {
|
|
25
|
+
/** New end date of the current item: the day before the change. */
|
|
26
|
+
oldItemEndDate: Date
|
|
27
|
+
/** Start of the new item: the effective date. */
|
|
28
|
+
newItemStartDate: Date
|
|
29
|
+
/** The new item keeps the original end date (null = open-ended). */
|
|
30
|
+
newItemEndDate: Date | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type PriceChangeError =
|
|
34
|
+
| 'billing.item.price_change_recurring_only'
|
|
35
|
+
| 'billing.item.price_change_inactive'
|
|
36
|
+
| 'billing.item.price_change_before_start'
|
|
37
|
+
| 'billing.item.price_change_after_end'
|
|
38
|
+
|
|
39
|
+
export function planPriceChange(
|
|
40
|
+
item: PriceChangeItem,
|
|
41
|
+
effectiveDate: Date | string,
|
|
42
|
+
): { ok: true; plan: PriceChangePlan } | { ok: false; error: PriceChangeError } {
|
|
43
|
+
if (item.type !== 'recurring') return { ok: false, error: 'billing.item.price_change_recurring_only' }
|
|
44
|
+
if (!item.isActive || item.deletedAt) return { ok: false, error: 'billing.item.price_change_inactive' }
|
|
45
|
+
const effective = toUtcMidnight(effectiveDate)
|
|
46
|
+
const start = toUtcMidnight(item.billStartDate)
|
|
47
|
+
// The current item must keep at least one day.
|
|
48
|
+
if (effective.getTime() <= start.getTime()) return { ok: false, error: 'billing.item.price_change_before_start' }
|
|
49
|
+
const end = item.billEndDate ? toUtcMidnight(item.billEndDate) : null
|
|
50
|
+
if (end && effective.getTime() > end.getTime()) return { ok: false, error: 'billing.item.price_change_after_end' }
|
|
51
|
+
const oldItemEndDate = new Date(effective)
|
|
52
|
+
oldItemEndDate.setUTCDate(oldItemEndDate.getUTCDate() - 1)
|
|
53
|
+
return { ok: true, plan: { oldItemEndDate, newItemStartDate: effective, newItemEndDate: end } }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { toIsoDate }
|
|
@@ -20,7 +20,10 @@ import type { BillPeriod } from './billPeriod'
|
|
|
20
20
|
* `billed_to_date IS NULL` AND the item's `[bill_start_date,
|
|
21
21
|
* bill_end_date]` window overlaps the bill period.
|
|
22
22
|
*
|
|
23
|
-
* `recurring` —
|
|
23
|
+
* `recurring` — used by PREPAID period close only (Spec A, A-D1). The
|
|
24
|
+
* postpaid engine prices recurring items with `servicePeriods`
|
|
25
|
+
* (in advance / in arrears, proration, credits). Here: one line per
|
|
26
|
+
* full cycle. Eligible only when:
|
|
24
27
|
* - `bill_start_date <= bill_period_start`
|
|
25
28
|
* - AND (`bill_end_date IS NULL` OR `bill_end_date >= bill_period_end`)
|
|
26
29
|
* - AND (`billed_to_date IS NULL` OR `billed_to_date < bill_period_end`)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { lineAmountsFromGross, lineAmountsFromNet, type VatAmounts } from './vat'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Basis-aware line VAT — the single entry point for every billing line
|
|
5
|
+
* (Spec F, P-D3): invoice drafts, dry / test previews and prepaid debits.
|
|
6
|
+
*
|
|
7
|
+
* An item price is stored as typed, on its `price_basis`:
|
|
8
|
+
* - `net`: tax = round2(net × rate), gross = net + tax
|
|
9
|
+
* - `gross`: net = round2(gross / (1 + rate)), tax = gross − net
|
|
10
|
+
* so a gross the operator typed is exactly the gross on the document.
|
|
11
|
+
* Rounding happens once per line, on the extended amount
|
|
12
|
+
* (unit × quantity × proration), never per unit.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export type PriceBasis = 'net' | 'gross'
|
|
16
|
+
|
|
17
|
+
export type LineVatInput = {
|
|
18
|
+
/** Extended line amount on `priceBasis` (unit × quantity × proration). */
|
|
19
|
+
amount: number | string
|
|
20
|
+
priceBasis: PriceBasis
|
|
21
|
+
/** VAT rate in percent. */
|
|
22
|
+
taxRate: number | string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toNumber(value: number | string): number {
|
|
26
|
+
const n = typeof value === 'number' ? value : Number.parseFloat(value)
|
|
27
|
+
return Number.isFinite(n) ? n : 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function lineVat(input: LineVatInput): VatAmounts {
|
|
31
|
+
const amount = toNumber(input.amount)
|
|
32
|
+
const rate = toNumber(input.taxRate)
|
|
33
|
+
return input.priceBasis === 'gross' ? lineAmountsFromGross(amount, rate) : lineAmountsFromNet(amount, rate)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Resolve a stored basis value (anything but `gross` reads as `net`). */
|
|
37
|
+
export function priceBasisOf(value: unknown): PriceBasis {
|
|
38
|
+
return value === 'gross' ? 'gross' : 'net'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Net unit price for `billing_invoice_lines.unit_price_net` (display only,
|
|
43
|
+
* 4 dp). Net basis keeps the typed unit price; gross basis derives it.
|
|
44
|
+
*/
|
|
45
|
+
export function unitPriceNetOf(unitPrice: number | string, priceBasis: PriceBasis, taxRate: number | string): string {
|
|
46
|
+
const unit = toNumber(unitPrice)
|
|
47
|
+
if (priceBasis !== 'gross') return unit.toFixed(4)
|
|
48
|
+
const rate = toNumber(taxRate)
|
|
49
|
+
const net = rate > 0 ? unit / (1 + rate / 100) : unit
|
|
50
|
+
const sign = net < 0 ? -1 : 1
|
|
51
|
+
return ((sign * Math.round((Math.abs(net) + Number.EPSILON) * 10000)) / 10000).toFixed(4)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The counterpart of a typed unit price, for the ItemForm preview: the gross
|
|
56
|
+
* of a net price or the net of a gross price (2 dp, same rule as the lines).
|
|
57
|
+
*/
|
|
58
|
+
export function counterpartAmount(amount: number | string, priceBasis: PriceBasis, taxRate: number | string): number {
|
|
59
|
+
const vat = lineVat({ amount, priceBasis, taxRate })
|
|
60
|
+
return priceBasis === 'gross' ? vat.net : vat.gross
|
|
61
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { toCents } from './allocation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Credit memo guard (Spec D, P-D4). Pure core + one aggregate query.
|
|
6
|
+
*
|
|
7
|
+
* Σ gross of posted credit memos on the original (incl. the new one)
|
|
8
|
+
* ≤ original gross + Σ gross of posted debit memos on it
|
|
9
|
+
*
|
|
10
|
+
* Evaluated inside the per-account lock at post (authoritative) and,
|
|
11
|
+
* without the lock, when a draft memo is created (early feedback).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type CorrectionTotals = { creditCents: number; debitCents: number }
|
|
15
|
+
|
|
16
|
+
export function creditHeadroomCents(originalGrossCents: number, totals: CorrectionTotals): number {
|
|
17
|
+
return originalGrossCents + totals.debitCents - totals.creditCents
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function creditFits(originalGrossCents: number, totals: CorrectionTotals, newCreditCents: number): boolean {
|
|
21
|
+
return newCreditCents <= creditHeadroomCents(originalGrossCents, totals)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Σ gross of POSTED (non-draft) memos correcting an invoice, per type. */
|
|
25
|
+
export async function loadCorrectionTotals(
|
|
26
|
+
em: EntityManager,
|
|
27
|
+
scope: { tenantId: string; organizationId: string },
|
|
28
|
+
originalId: string,
|
|
29
|
+
opts: { excludeId?: string } = {},
|
|
30
|
+
): Promise<CorrectionTotals> {
|
|
31
|
+
const rows = (await em.execute(
|
|
32
|
+
`SELECT document_type, COALESCE(SUM(total_gross), 0) AS gross
|
|
33
|
+
FROM billing_invoices
|
|
34
|
+
WHERE corrects_invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
|
|
35
|
+
AND status <> 'draft' AND test_run = false AND id <> ?
|
|
36
|
+
GROUP BY document_type`,
|
|
37
|
+
[originalId, scope.tenantId, scope.organizationId, opts.excludeId ?? '00000000-0000-0000-0000-000000000000'],
|
|
38
|
+
)) as unknown as Array<{ document_type: string; gross: string }>
|
|
39
|
+
const of = (type: string) => toCents(rows?.find((r) => r.document_type === type)?.gross ?? '0')
|
|
40
|
+
return { creditCents: of('credit_memo'), debitCents: of('debit_memo') }
|
|
41
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an item page returns to (Spec E, D23).
|
|
3
|
+
*
|
|
4
|
+
* Billing items always belong to an account, so the account is the natural
|
|
5
|
+
* parent: back / after-save / after-delete land there. The only exception is
|
|
6
|
+
* an item opened from the global Items list (`?from=items`), which returns
|
|
7
|
+
* to that list so the operator keeps their place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const ITEMS_LIST_HREF = '/backend/billing/items'
|
|
11
|
+
|
|
12
|
+
/** Query value the global Items list appends when opening an item. */
|
|
13
|
+
export const FROM_ITEMS_LIST = 'items'
|
|
14
|
+
|
|
15
|
+
/** Tabs of the Billing Account workspace (Spec E / Spec F, F8), deep-linkable via `?tab=`. */
|
|
16
|
+
export const ACCOUNT_TABS = ['overview', 'items', 'usage', 'invoices', 'payments', 'balance', 'statements', 'runs'] as const
|
|
17
|
+
export type AccountTab = (typeof ACCOUNT_TABS)[number]
|
|
18
|
+
|
|
19
|
+
export function isAccountTab(value: unknown): value is AccountTab {
|
|
20
|
+
return typeof value === 'string' && (ACCOUNT_TABS as readonly string[]).includes(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The account workspace, optionally on one tab (`overview` is the default and is left out). */
|
|
24
|
+
export function accountHref(billAccountId: string, tab?: AccountTab): string {
|
|
25
|
+
const base = `/backend/billing/accounts/${encodeURIComponent(billAccountId)}`
|
|
26
|
+
return tab && tab !== 'overview' ? `${base}?tab=${tab}` : base
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveItemReturnTarget(params: {
|
|
30
|
+
from?: string | null
|
|
31
|
+
billAccountId?: string | null
|
|
32
|
+
}): string {
|
|
33
|
+
if (params.from === FROM_ITEMS_LIST) return ITEMS_LIST_HREF
|
|
34
|
+
const accountId = (params.billAccountId ?? '').trim()
|
|
35
|
+
return accountId ? accountHref(accountId, 'items') : ITEMS_LIST_HREF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function itemDetailHref(itemId: string, opts: { fromItemsList?: boolean } = {}): string {
|
|
39
|
+
const base = `${ITEMS_LIST_HREF}/${encodeURIComponent(itemId)}`
|
|
40
|
+
return opts.fromItemsList ? `${base}?from=${FROM_ITEMS_LIST}` : base
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Where an invoice detail page returns to (Spec B): the account it was
|
|
45
|
+
* opened from (`?from=account`), otherwise the global Invoices list.
|
|
46
|
+
*/
|
|
47
|
+
export const INVOICES_LIST_HREF = '/backend/billing/invoices'
|
|
48
|
+
export const FROM_ACCOUNT = 'account'
|
|
49
|
+
|
|
50
|
+
export function invoiceDetailHref(invoiceId: string, opts: { fromAccountId?: string | null } = {}): string {
|
|
51
|
+
const base = `${INVOICES_LIST_HREF}/${encodeURIComponent(invoiceId)}`
|
|
52
|
+
const accountId = (opts.fromAccountId ?? '').trim()
|
|
53
|
+
return accountId ? `${base}?from=${FROM_ACCOUNT}&accountId=${encodeURIComponent(accountId)}` : base
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveInvoiceReturnTarget(params: { from?: string | null; accountId?: string | null }): string {
|
|
57
|
+
const accountId = (params.accountId ?? '').trim()
|
|
58
|
+
return params.from === FROM_ACCOUNT && accountId ? accountHref(accountId, 'invoices') : INVOICES_LIST_HREF
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** PDF of a billing document — frozen when posted, a watermarked preview otherwise. */
|
|
62
|
+
export function invoicePdfHref(invoiceId: string): string {
|
|
63
|
+
return `/api/billing/invoices/${encodeURIComponent(invoiceId)}/pdf`
|
|
64
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Document numbering (Spec 0, decision E-D6 / P-D3).
|
|
5
|
+
*
|
|
6
|
+
* Each series has a mask, e.g. `INV/{YYYY}/{MM}/{SEQUENCE_NUM_MONTH:4}`:
|
|
7
|
+
* {YYYY} {YY} {MM} {DD} — parts of the issue date
|
|
8
|
+
* {SEQUENCE_NUM} — counter never resets
|
|
9
|
+
* {SEQUENCE_NUM_YEAR} — counter resets every year
|
|
10
|
+
* {SEQUENCE_NUM_MONTH} — counter resets every month
|
|
11
|
+
* …:N — optional zero-pad to N digits
|
|
12
|
+
*
|
|
13
|
+
* The counter row is `(tenant, org, series, scope_key)` where scope_key is
|
|
14
|
+
* '' / 'YYYY' / 'YYYY-MM'. A number is claimed only at post, by one upsert
|
|
15
|
+
* inside the caller's transaction — a rollback releases it (gap-free) and
|
|
16
|
+
* concurrent posts serialize on the row.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const DOCUMENT_SERIES = ['invoice', 'credit_memo', 'receipt'] as const
|
|
20
|
+
export type DocumentSeries = (typeof DOCUMENT_SERIES)[number]
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_NUMBER_MASKS: Record<DocumentSeries, string> = {
|
|
23
|
+
invoice: 'INV/{YYYY}/{MM}/{SEQUENCE_NUM_MONTH:4}',
|
|
24
|
+
credit_memo: 'CN/{YYYY}/{MM}/{SEQUENCE_NUM_MONTH:4}',
|
|
25
|
+
receipt: 'RC/{YYYY}/{MM}/{SEQUENCE_NUM_MONTH:4}',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type SequenceScope = 'none' | 'year' | 'month'
|
|
29
|
+
|
|
30
|
+
export type ParsedMask = {
|
|
31
|
+
scope: SequenceScope
|
|
32
|
+
pad: number
|
|
33
|
+
hasYear: boolean
|
|
34
|
+
hasMonth: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type MaskError =
|
|
38
|
+
| 'mask_empty'
|
|
39
|
+
| 'mask_too_long'
|
|
40
|
+
| 'mask_unknown_token'
|
|
41
|
+
| 'mask_missing_sequence'
|
|
42
|
+
| 'mask_multiple_sequences'
|
|
43
|
+
| 'mask_year_scope_needs_year'
|
|
44
|
+
| 'mask_month_scope_needs_year_and_month'
|
|
45
|
+
|
|
46
|
+
export const MAX_MASK_LENGTH = 64
|
|
47
|
+
|
|
48
|
+
const TOKEN_RE = /\{([A-Z_]+)(?::(\d{1,2}))?\}/g
|
|
49
|
+
const DATE_TOKENS = new Set(['YYYY', 'YY', 'MM', 'DD'])
|
|
50
|
+
const SEQUENCE_TOKENS: Record<string, SequenceScope> = {
|
|
51
|
+
SEQUENCE_NUM: 'none',
|
|
52
|
+
SEQUENCE_NUM_YEAR: 'year',
|
|
53
|
+
SEQUENCE_NUM_MONTH: 'month',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tokensOf(mask: string): Array<{ name: string; pad: number }> {
|
|
57
|
+
return Array.from(mask.matchAll(TOKEN_RE), (m) => ({ name: m[1], pad: m[2] ? Number(m[2]) : 0 }))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Validation errors for a mask; empty array = valid. */
|
|
61
|
+
export function validateMask(mask: string): MaskError[] {
|
|
62
|
+
const trimmed = (mask ?? '').trim()
|
|
63
|
+
if (!trimmed) return ['mask_empty']
|
|
64
|
+
const errors: MaskError[] = []
|
|
65
|
+
if (trimmed.length > MAX_MASK_LENGTH) errors.push('mask_too_long')
|
|
66
|
+
const tokens = tokensOf(trimmed)
|
|
67
|
+
// Any brace not consumed by a known-shaped token is an unknown token.
|
|
68
|
+
const stripped = trimmed.replace(TOKEN_RE, '')
|
|
69
|
+
if (/[{}]/.test(stripped) || tokens.some((t) => !DATE_TOKENS.has(t.name) && !(t.name in SEQUENCE_TOKENS))) {
|
|
70
|
+
errors.push('mask_unknown_token')
|
|
71
|
+
}
|
|
72
|
+
const sequences = tokens.filter((t) => t.name in SEQUENCE_TOKENS)
|
|
73
|
+
if (sequences.length === 0) errors.push('mask_missing_sequence')
|
|
74
|
+
if (sequences.length > 1) errors.push('mask_multiple_sequences')
|
|
75
|
+
const hasYear = tokens.some((t) => t.name === 'YYYY' || t.name === 'YY')
|
|
76
|
+
const hasMonth = tokens.some((t) => t.name === 'MM')
|
|
77
|
+
const scope = sequences[0] ? SEQUENCE_TOKENS[sequences[0].name] : 'none'
|
|
78
|
+
if (scope === 'year' && !hasYear) errors.push('mask_year_scope_needs_year')
|
|
79
|
+
if (scope === 'month' && (!hasYear || !hasMonth)) errors.push('mask_month_scope_needs_year_and_month')
|
|
80
|
+
return errors
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function parseMask(mask: string): ParsedMask {
|
|
84
|
+
const tokens = tokensOf(mask)
|
|
85
|
+
const sequence = tokens.find((t) => t.name in SEQUENCE_TOKENS)
|
|
86
|
+
return {
|
|
87
|
+
scope: sequence ? SEQUENCE_TOKENS[sequence.name] : 'none',
|
|
88
|
+
pad: sequence?.pad ?? 0,
|
|
89
|
+
hasYear: tokens.some((t) => t.name === 'YYYY' || t.name === 'YY'),
|
|
90
|
+
hasMonth: tokens.some((t) => t.name === 'MM'),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function datePart(date: Date, part: 'YYYY' | 'YY' | 'MM' | 'DD'): string {
|
|
95
|
+
const d = new Date(date)
|
|
96
|
+
switch (part) {
|
|
97
|
+
case 'YYYY':
|
|
98
|
+
return String(d.getUTCFullYear())
|
|
99
|
+
case 'YY':
|
|
100
|
+
return String(d.getUTCFullYear()).slice(-2)
|
|
101
|
+
case 'MM':
|
|
102
|
+
return String(d.getUTCMonth() + 1).padStart(2, '0')
|
|
103
|
+
case 'DD':
|
|
104
|
+
return String(d.getUTCDate()).padStart(2, '0')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Counter scope for a mask on a given issue date: '', 'YYYY' or 'YYYY-MM'. */
|
|
109
|
+
export function sequenceScopeKey(mask: string, issueDate: Date): string {
|
|
110
|
+
const { scope } = parseMask(mask)
|
|
111
|
+
if (scope === 'year') return datePart(issueDate, 'YYYY')
|
|
112
|
+
if (scope === 'month') return `${datePart(issueDate, 'YYYY')}-${datePart(issueDate, 'MM')}`
|
|
113
|
+
return ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function renderNumber(mask: string, params: { seq: number; issueDate: Date }): string {
|
|
117
|
+
return mask.replace(TOKEN_RE, (_match, name: string, pad?: string) => {
|
|
118
|
+
if (name in SEQUENCE_TOKENS) return String(params.seq).padStart(pad ? Number(pad) : 0, '0')
|
|
119
|
+
if (DATE_TOKENS.has(name)) return datePart(params.issueDate, name as 'YYYY' | 'YY' | 'MM' | 'DD')
|
|
120
|
+
return _match
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function maskFor(masks: Partial<Record<string, string>> | null | undefined, series: DocumentSeries): string {
|
|
125
|
+
const candidate = masks?.[series]
|
|
126
|
+
return candidate && validateMask(candidate).length === 0 ? candidate : DEFAULT_NUMBER_MASKS[series]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function randomHex(length: number): string {
|
|
130
|
+
let out = ''
|
|
131
|
+
while (out.length < length) out += Math.floor(Math.random() * 0x100000000).toString(16).padStart(8, '0')
|
|
132
|
+
return out.slice(0, length)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Temporary number for an unposted real draft (no sequence consumed). */
|
|
136
|
+
export function draftNumber(): string {
|
|
137
|
+
return `DRAFT-${randomHex(8).toUpperCase()}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Number for test-run drafts — never posted, never numbered. */
|
|
141
|
+
export function testNumber(): string {
|
|
142
|
+
return `TEST-${Date.now().toString(16)}-${randomHex(8)}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type NumberScope = { tenantId: string; organizationId: string }
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Claims the next number of a series. MUST run inside the caller's
|
|
149
|
+
* transaction (post command / receipt capture) so a rollback releases it.
|
|
150
|
+
*/
|
|
151
|
+
export async function claimDocumentNumber(
|
|
152
|
+
em: EntityManager,
|
|
153
|
+
scope: NumberScope,
|
|
154
|
+
series: DocumentSeries,
|
|
155
|
+
mask: string,
|
|
156
|
+
issueDate: Date,
|
|
157
|
+
): Promise<{ number: string; seq: number; scopeKey: string }> {
|
|
158
|
+
const scopeKey = sequenceScopeKey(mask, issueDate)
|
|
159
|
+
const now = new Date()
|
|
160
|
+
const rows = (await em.execute(
|
|
161
|
+
`INSERT INTO billing_document_sequences
|
|
162
|
+
(tenant_id, organization_id, series, scope_key, next_value, created_at, updated_at)
|
|
163
|
+
VALUES (?, ?, ?, ?, 2, ?, ?)
|
|
164
|
+
ON CONFLICT (tenant_id, organization_id, series, scope_key)
|
|
165
|
+
DO UPDATE SET next_value = billing_document_sequences.next_value + 1, updated_at = EXCLUDED.updated_at
|
|
166
|
+
RETURNING next_value - 1 AS seq`,
|
|
167
|
+
[scope.tenantId, scope.organizationId, series, scopeKey, now, now],
|
|
168
|
+
)) as unknown as Array<{ seq: number | string }> | undefined
|
|
169
|
+
const seq = Number(rows?.[0]?.seq)
|
|
170
|
+
if (!Number.isFinite(seq) || seq < 1) {
|
|
171
|
+
throw new Error(`Failed to claim a ${series} number`)
|
|
172
|
+
}
|
|
173
|
+
return { number: renderNumber(mask, { seq, issueDate }), seq, scopeKey }
|
|
174
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overdue is computed, never stored (Spec B, B-D6):
|
|
3
|
+
*
|
|
4
|
+
* status ∈ {posted, partially_paid} AND due_date < today AND outstanding > 0
|
|
5
|
+
*
|
|
6
|
+
* Receivable documents only (invoices and debit memos). One definition shared
|
|
7
|
+
* by the in-memory predicate (UI / tests) and the SQL fragment (invoice list,
|
|
8
|
+
* account summary), so the two can never disagree. No worker until dunning.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const OPEN_STATUSES = ['posted', 'partially_paid'] as const
|
|
12
|
+
export const RECEIVABLE_DOCUMENT_TYPES = ['invoice', 'debit_memo'] as const
|
|
13
|
+
|
|
14
|
+
export type OverdueCandidate = {
|
|
15
|
+
status: string
|
|
16
|
+
documentType?: string | null
|
|
17
|
+
dueDate: Date | string | null | undefined
|
|
18
|
+
outstanding: string | number | null | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function dayNumber(value: Date | string): number {
|
|
22
|
+
const date = typeof value === 'string' ? new Date(`${value.slice(0, 10)}T00:00:00Z`) : value
|
|
23
|
+
return Math.floor(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) / 86_400_000)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isOverdue(doc: OverdueCandidate, today: Date): boolean {
|
|
27
|
+
return daysOverdue(doc, today) > 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whole days past the due date; 0 when not overdue. */
|
|
31
|
+
export function daysOverdue(doc: OverdueCandidate, today: Date): number {
|
|
32
|
+
if (!(OPEN_STATUSES as readonly string[]).includes(doc.status)) return 0
|
|
33
|
+
if (doc.documentType && !(RECEIVABLE_DOCUMENT_TYPES as readonly string[]).includes(doc.documentType)) return 0
|
|
34
|
+
if (!doc.dueDate) return 0
|
|
35
|
+
const outstanding = typeof doc.outstanding === 'number' ? doc.outstanding : Number.parseFloat(doc.outstanding ?? '0')
|
|
36
|
+
if (!(outstanding > 0)) return 0
|
|
37
|
+
return Math.max(0, dayNumber(today) - dayNumber(doc.dueDate))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `YYYY-MM-DD` of a date in UTC — the `today` parameter for SQL. */
|
|
41
|
+
export function isoDay(date: Date): string {
|
|
42
|
+
return date.toISOString().slice(0, 10)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* SQL predicate for overdue documents on alias `alias`. Bind one parameter:
|
|
47
|
+
* today as `YYYY-MM-DD`.
|
|
48
|
+
*/
|
|
49
|
+
export function overdueSql(alias: string): string {
|
|
50
|
+
return (
|
|
51
|
+
`(${alias}.status IN ('posted', 'partially_paid')` +
|
|
52
|
+
` AND ${alias}.document_type IN ('invoice', 'debit_memo')` +
|
|
53
|
+
` AND ${alias}.due_date < ?::date AND ${alias}.outstanding > 0)`
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** SQL expression for days overdue (0 when not overdue). Bind today twice. */
|
|
58
|
+
export function daysOverdueSql(alias: string): string {
|
|
59
|
+
return `(CASE WHEN ${overdueSql(alias)} THEN (?::date - ${alias}.due_date) ELSE 0 END)`
|
|
60
|
+
}
|