@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,76 @@
|
|
|
1
|
+
import { lineVat, type PriceBasis } from './lineAmounts'
|
|
2
|
+
import { round2 } from './vat'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Prepaid tax point (Spec F, F-D3 / P-D10) — how much a prepaid charge takes
|
|
6
|
+
* from the balance. Shared by the Bill Run period close and real-time usage
|
|
7
|
+
* consumption so the two paths can never drift apart.
|
|
8
|
+
*
|
|
9
|
+
* - `topup` (default): VAT was charged on the top-up receipt, so the
|
|
10
|
+
* balance holds VAT-inclusive money and the item amount is debited as
|
|
11
|
+
* entered (`debit = net = amount`, `tax = 0`). The item's VAT rate and
|
|
12
|
+
* price basis do not apply.
|
|
13
|
+
* - `consumption`: the receipt carried no VAT; each debit is the line's
|
|
14
|
+
* gross at the item's rate and basis (`lineVat`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type PrepaidTaxPoint = 'topup' | 'consumption'
|
|
18
|
+
|
|
19
|
+
export type PrepaidDebitInput = {
|
|
20
|
+
/** Extended line amount on `priceBasis` (negative for credits). */
|
|
21
|
+
lineAmount: number | string
|
|
22
|
+
priceBasis: PriceBasis
|
|
23
|
+
/** Item VAT rate in percent. */
|
|
24
|
+
taxRate: number | string
|
|
25
|
+
taxPoint: PrepaidTaxPoint
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PrepaidDebit = {
|
|
29
|
+
/** What leaves the balance (positive charge, negative credit back). */
|
|
30
|
+
debit: number
|
|
31
|
+
net: number
|
|
32
|
+
tax: number
|
|
33
|
+
/** VAT rate applied (0 in `topup` mode). */
|
|
34
|
+
taxRate: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toNumber(value: number | string): number {
|
|
38
|
+
const n = typeof value === 'number' ? value : Number.parseFloat(value)
|
|
39
|
+
return Number.isFinite(n) ? n : 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolvePrepaidDebit(input: PrepaidDebitInput): PrepaidDebit {
|
|
43
|
+
if (input.taxPoint === 'topup') {
|
|
44
|
+
const amount = round2(toNumber(input.lineAmount))
|
|
45
|
+
return { debit: amount, net: amount, tax: 0, taxRate: 0 }
|
|
46
|
+
}
|
|
47
|
+
const rate = Math.max(0, toNumber(input.taxRate))
|
|
48
|
+
const vat = lineVat({ amount: input.lineAmount, priceBasis: input.priceBasis, taxRate: rate })
|
|
49
|
+
return { debit: vat.gross, net: vat.net, tax: vat.tax, taxRate: rate }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type VatSummaryRow = { rate: string; net: string; tax: string; gross: string }
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Per-rate VAT summary of a set of prepaid debits (statement `vat_summary`).
|
|
56
|
+
* Empty when no debit carried VAT information (`topup` mode).
|
|
57
|
+
*/
|
|
58
|
+
export function buildVatSummary(debits: ReadonlyArray<PrepaidDebit>, taxPoint: PrepaidTaxPoint): VatSummaryRow[] {
|
|
59
|
+
if (taxPoint === 'topup') return []
|
|
60
|
+
const byRate = new Map<number, { net: number; tax: number; gross: number }>()
|
|
61
|
+
for (const debit of debits) {
|
|
62
|
+
const row = byRate.get(debit.taxRate) ?? { net: 0, tax: 0, gross: 0 }
|
|
63
|
+
row.net = round2(row.net + debit.net)
|
|
64
|
+
row.tax = round2(row.tax + debit.tax)
|
|
65
|
+
row.gross = round2(row.gross + debit.debit)
|
|
66
|
+
byRate.set(debit.taxRate, row)
|
|
67
|
+
}
|
|
68
|
+
return Array.from(byRate.entries())
|
|
69
|
+
.sort(([a], [b]) => b - a)
|
|
70
|
+
.map(([rate, row]) => ({
|
|
71
|
+
rate: String(rate),
|
|
72
|
+
net: row.net.toFixed(4),
|
|
73
|
+
tax: row.tax.toFixed(4),
|
|
74
|
+
gross: row.gross.toFixed(4),
|
|
75
|
+
}))
|
|
76
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { BillingAccount, BillingItem, BillingAccountUsage } from '../../data/entities'
|
|
3
|
+
import type { CustomerKind } from '../../data/validators'
|
|
4
|
+
import type { InvoiceLanguageCode } from '../invoiceLanguages'
|
|
5
|
+
import { DEFAULT_DEMO_SELLER, applySellerDefaults } from './sellerSettings'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Self-contained billing test-data seed.
|
|
@@ -10,9 +13,13 @@ import { BillingAccount, BillingItem, BillingAccountUsage } from '../../data/ent
|
|
|
10
13
|
* so re-running is a no-op rather than a duplicate.
|
|
11
14
|
*
|
|
12
15
|
* The accounts are dated so they are immediately billable: `next_bill_date`
|
|
13
|
-
* is the 1st of the current month
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* is the 1st of the current month. A Bill Run with the default (today's)
|
|
17
|
+
* as-of date then shows every Spec A behaviour on one invoice per account:
|
|
18
|
+
* - recurring items billed IN ADVANCE for this month (the default);
|
|
19
|
+
* - Globex's in-arrears item billed for last month;
|
|
20
|
+
* - Northwind's mid-cycle add-on: a prorated stub for the second half of
|
|
21
|
+
* last month plus this month;
|
|
22
|
+
* - usage (always in arrears) for last month.
|
|
16
23
|
*
|
|
17
24
|
* Drives `mercato billing seed`. Kept free of any customer-specific data so
|
|
18
25
|
* it ships with `@dainamite/billing`.
|
|
@@ -32,8 +39,10 @@ const ACCOUNT_NAME_PREFIX = 'Demo — '
|
|
|
32
39
|
type DemoAccountSpec = {
|
|
33
40
|
name: string
|
|
34
41
|
customerId: string
|
|
42
|
+
// Spec E: 'person' = a B2C account. Omitted → 'company'.
|
|
43
|
+
customerKind?: CustomerKind
|
|
35
44
|
currencyCode: string
|
|
36
|
-
invoiceLanguage:
|
|
45
|
+
invoiceLanguage: InvoiceLanguageCode
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
type DemoItemSpec = {
|
|
@@ -42,6 +51,18 @@ type DemoItemSpec = {
|
|
|
42
51
|
description: string
|
|
43
52
|
unitPrice: number
|
|
44
53
|
uomCode?: string
|
|
54
|
+
// Spec B: VAT %. Default 23 (PL standard rate).
|
|
55
|
+
taxRate?: string
|
|
56
|
+
// Spec A. Default 'in_advance'.
|
|
57
|
+
chargeTiming?: 'in_advance' | 'in_arrears'
|
|
58
|
+
// Spec F. Default 'net'. Consumer (B2C) prices are quoted gross.
|
|
59
|
+
priceBasis?: 'net' | 'gross'
|
|
60
|
+
// When the item starts, relative to the seeded bill date:
|
|
61
|
+
// 'billDate' (default) — 1st of this month, billed in advance from day one;
|
|
62
|
+
// 'prevMonth' — 1st of last month (in-arrears items bill it now);
|
|
63
|
+
// 'prevMonthMid' — 15th of last month: an add-on bought mid-cycle,
|
|
64
|
+
// so the first run shows a prorated stub line.
|
|
65
|
+
start?: 'billDate' | 'prevMonth' | 'prevMonthMid'
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
type DemoUsageSpec = {
|
|
@@ -65,7 +86,15 @@ const ACCOUNTS: Array<{
|
|
|
65
86
|
},
|
|
66
87
|
items: [
|
|
67
88
|
{ ref: `${SRC}-nw-rec`, type: 'recurring', description: 'Platform subscription (monthly)', unitPrice: 199 },
|
|
68
|
-
{
|
|
89
|
+
{
|
|
90
|
+
ref: `${SRC}-nw-addon`,
|
|
91
|
+
type: 'recurring',
|
|
92
|
+
description: 'Priority support add-on (monthly)',
|
|
93
|
+
unitPrice: 60,
|
|
94
|
+
taxRate: '8',
|
|
95
|
+
start: 'prevMonthMid',
|
|
96
|
+
},
|
|
97
|
+
{ ref: `${SRC}-nw-usg`, type: 'usage', description: 'Data transfer (per GB)', unitPrice: 0.5, uomCode: 'gb', start: 'prevMonth' },
|
|
69
98
|
],
|
|
70
99
|
usage: [
|
|
71
100
|
{ ref: `${SRC}-nw-usage`, uomCode: 'gb', quantity: 120, lineDescription: 'Data transfer (prev. month)' },
|
|
@@ -80,11 +109,43 @@ const ACCOUNTS: Array<{
|
|
|
80
109
|
},
|
|
81
110
|
items: [
|
|
82
111
|
{ ref: `${SRC}-gx-rec`, type: 'recurring', description: 'Cloud plan (monthly)', unitPrice: 49 },
|
|
112
|
+
{
|
|
113
|
+
ref: `${SRC}-gx-arr`,
|
|
114
|
+
type: 'recurring',
|
|
115
|
+
description: 'Managed backups (billed in arrears)',
|
|
116
|
+
unitPrice: 15,
|
|
117
|
+
// 0 % — shows a mixed-rate VAT summary on the document.
|
|
118
|
+
taxRate: '0',
|
|
119
|
+
chargeTiming: 'in_arrears',
|
|
120
|
+
start: 'prevMonth',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
usage: [],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
// B2C: a private person on a flat monthly internet plan, invoiced in Polish.
|
|
127
|
+
account: {
|
|
128
|
+
name: `${ACCOUNT_NAME_PREFIX}Jan Kowalski`,
|
|
129
|
+
customerId: 'demo-cust-jan-kowalski',
|
|
130
|
+
customerKind: 'person',
|
|
131
|
+
currencyCode: 'PLN',
|
|
132
|
+
invoiceLanguage: 'pl',
|
|
133
|
+
},
|
|
134
|
+
items: [
|
|
135
|
+
// B2C price quoted gross (79.00 PLN incl. 23 % VAT) — Spec F, F-D2.
|
|
136
|
+
{
|
|
137
|
+
ref: `${SRC}-jk-rec`,
|
|
138
|
+
type: 'recurring',
|
|
139
|
+
description: 'Internet 300 Mb/s (miesięcznie)',
|
|
140
|
+
unitPrice: 79,
|
|
141
|
+
priceBasis: 'gross',
|
|
142
|
+
},
|
|
83
143
|
],
|
|
84
144
|
usage: [],
|
|
85
145
|
},
|
|
86
146
|
]
|
|
87
147
|
|
|
148
|
+
|
|
88
149
|
/** Delete every row this seed owns within the scope (reset before re-seed). */
|
|
89
150
|
export async function resetBillingDemo(em: EntityManager, scope: BillingDemoScope): Promise<void> {
|
|
90
151
|
await em.nativeDelete(BillingAccountUsage, {
|
|
@@ -116,9 +177,16 @@ export async function seedBillingDemo(
|
|
|
116
177
|
const nextBillDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))
|
|
117
178
|
const prevMonthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1))
|
|
118
179
|
const prevMonthEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0))
|
|
180
|
+
const prevMonthMid = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 15))
|
|
181
|
+
const startFor = (item: DemoItemSpec): Date =>
|
|
182
|
+
item.start === 'prevMonth' ? prevMonthStart : item.start === 'prevMonthMid' ? prevMonthMid : nextBillDate
|
|
119
183
|
|
|
120
184
|
const result: BillingDemoResult = { accountsCreated: 0, itemsCreated: 0, usageCreated: 0 }
|
|
121
185
|
|
|
186
|
+
// ── Seller settings (Spec B: posting needs them). Only empty fields are
|
|
187
|
+
// filled, so an operator's real details are never overwritten. ──
|
|
188
|
+
await applySellerDefaults(em, scope, DEFAULT_DEMO_SELLER)
|
|
189
|
+
|
|
122
190
|
for (const spec of ACCOUNTS) {
|
|
123
191
|
// ── Account (idempotent by name within scope) ──
|
|
124
192
|
let account = await em.findOne(BillingAccount, {
|
|
@@ -130,6 +198,7 @@ export async function seedBillingDemo(
|
|
|
130
198
|
account = em.create(BillingAccount, {
|
|
131
199
|
...scope,
|
|
132
200
|
customerId: spec.account.customerId,
|
|
201
|
+
customerKind: spec.account.customerKind ?? 'company',
|
|
133
202
|
name: spec.account.name,
|
|
134
203
|
currencyCode: spec.account.currencyCode,
|
|
135
204
|
billCycle: 'monthly',
|
|
@@ -156,8 +225,12 @@ export async function seedBillingDemo(
|
|
|
156
225
|
...scope,
|
|
157
226
|
billAccountId: account.id,
|
|
158
227
|
type: item.type,
|
|
159
|
-
billStartDate:
|
|
228
|
+
billStartDate: startFor(item),
|
|
160
229
|
billEndDate: null,
|
|
230
|
+
chargeTiming: item.chargeTiming ?? 'in_advance',
|
|
231
|
+
prorate: true,
|
|
232
|
+
taxRate: item.taxRate ?? '23',
|
|
233
|
+
priceBasis: item.priceBasis ?? 'net',
|
|
161
234
|
description: item.description,
|
|
162
235
|
rateJson: { unit_price: item.unitPrice },
|
|
163
236
|
uomCode: item.uomCode ?? null,
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { PAYMENT_CSV_HEADER } from '../paymentCsv'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Demo bank statement (Spec C): a `payments-sample.csv` built from the
|
|
6
|
+
* organization's real posted invoices, so numbers and amounts line up and
|
|
7
|
+
* every matcher outcome shows up after one upload:
|
|
8
|
+
*
|
|
9
|
+
* exact amount + number → allocated, invoice paid
|
|
10
|
+
* half the amount → invoice partially paid
|
|
11
|
+
* amount + 50 → paid, 50 credit applied to the next open invoice
|
|
12
|
+
* two numbers, sum of both → both paid
|
|
13
|
+
* no reference, known payer → unmatched → assign to account
|
|
14
|
+
* a top-up receipt number → unmatched (prepaid) → convert to top-up
|
|
15
|
+
* junk title → unmatched (no reference)
|
|
16
|
+
* other currency → unmatched (currency mismatch)
|
|
17
|
+
* the same line twice → both stored (occurrence index); re-upload = duplicates
|
|
18
|
+
*
|
|
19
|
+
* Pure builder + a loader for the CLI (`mercato billing payments-sample`).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type SampleInvoice = {
|
|
23
|
+
number: string
|
|
24
|
+
totalGross: string
|
|
25
|
+
currencyCode: string
|
|
26
|
+
accountName: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SampleReceipt = { number: string; currencyCode: string; accountName: string }
|
|
30
|
+
|
|
31
|
+
function csvField(value: string): string {
|
|
32
|
+
return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function money(value: number): string {
|
|
36
|
+
return value.toFixed(2)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildPaymentsSampleCsv(params: {
|
|
40
|
+
invoices: SampleInvoice[]
|
|
41
|
+
receipt?: SampleReceipt | null
|
|
42
|
+
/** Spec D: a posted debit memo — paid by its own number like an invoice. */
|
|
43
|
+
debitMemo?: SampleInvoice | null
|
|
44
|
+
date: string
|
|
45
|
+
}): { csv: string; rows: number; missing: string[] } {
|
|
46
|
+
const { invoices, receipt, date, debitMemo } = params
|
|
47
|
+
const rows: string[][] = []
|
|
48
|
+
const missing: string[] = []
|
|
49
|
+
const gross = (inv: SampleInvoice) => Number.parseFloat(inv.totalGross)
|
|
50
|
+
const [a, b, c] = invoices
|
|
51
|
+
// Two invoices of the SAME account (references to two customers are ambiguous).
|
|
52
|
+
const rest = invoices.slice(3)
|
|
53
|
+
let d: SampleInvoice | undefined
|
|
54
|
+
let e: SampleInvoice | undefined
|
|
55
|
+
for (let i = 0; i < rest.length && !d; i += 1) {
|
|
56
|
+
const match = rest.slice(i + 1).find((other) => other.accountName === rest[i].accountName && other.currencyCode === rest[i].currencyCode)
|
|
57
|
+
if (match) {
|
|
58
|
+
d = rest[i]
|
|
59
|
+
e = match
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (a) rows.push([date, money(gross(a)), a.currencyCode, `Payment for ${a.number}`, a.accountName, ''])
|
|
64
|
+
else missing.push('exact payment (needs 1 posted invoice)')
|
|
65
|
+
if (b) rows.push([date, money(gross(b) / 2), b.currencyCode, `${b.number} part 1`, b.accountName, ''])
|
|
66
|
+
else missing.push('partial payment (needs 2 posted invoices)')
|
|
67
|
+
if (c) rows.push([date, money(gross(c) + 50), c.currencyCode, `Invoice ${c.number} + advance`, c.accountName, ''])
|
|
68
|
+
else missing.push('overpayment (needs 3 posted invoices)')
|
|
69
|
+
if (d && e) rows.push([date, money(gross(d) + gross(e)), d.currencyCode, `${d.number}, ${e.number}`, d.accountName, ''])
|
|
70
|
+
else missing.push('two invoices in one title (needs two more posted invoices of one account)')
|
|
71
|
+
if (a) rows.push([date, '25.00', a.currencyCode, 'Thank you', a.accountName, 'PL61109010140000071219812874'])
|
|
72
|
+
if (receipt) rows.push([date, '100.00', receipt.currencyCode, `Top-up ${receipt.number}`, receipt.accountName, ''])
|
|
73
|
+
else missing.push('prepaid top-up (needs a prepaid top-up receipt)')
|
|
74
|
+
if (debitMemo) {
|
|
75
|
+
rows.push([date, money(gross(debitMemo)), debitMemo.currencyCode, `Note ${debitMemo.number}`, debitMemo.accountName, ''])
|
|
76
|
+
}
|
|
77
|
+
rows.push([date, '12.34', 'PLN', 'misc transfer', 'Unknown payer', ''])
|
|
78
|
+
if (a) {
|
|
79
|
+
const other = a.currencyCode === 'EUR' ? 'PLN' : 'EUR'
|
|
80
|
+
rows.push([date, money(gross(a)), other, a.number, a.accountName, ''])
|
|
81
|
+
const dup = [date, '10.00', a.currencyCode, 'Duplicate line', a.accountName, '']
|
|
82
|
+
rows.push(dup, [...dup])
|
|
83
|
+
}
|
|
84
|
+
const csv = [PAYMENT_CSV_HEADER.join(','), ...rows.map((row) => row.map(csvField).join(','))].join('\n') + '\n'
|
|
85
|
+
return { csv, rows: rows.length, missing }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Posted, unpaid invoices of the org (oldest first) and one top-up receipt. */
|
|
89
|
+
export async function loadSampleSources(
|
|
90
|
+
em: EntityManager,
|
|
91
|
+
scope: { tenantId: string; organizationId: string },
|
|
92
|
+
): Promise<{ invoices: SampleInvoice[]; receipt: SampleReceipt | null; debitMemo: SampleInvoice | null }> {
|
|
93
|
+
const invoices = (await em.execute(
|
|
94
|
+
`SELECT i.number, i.total_gross, i.currency_code, a.name AS account_name
|
|
95
|
+
FROM billing_invoices i
|
|
96
|
+
JOIN billing_accounts a ON a.id = i.bill_account_id
|
|
97
|
+
WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.test_run = false
|
|
98
|
+
AND i.document_type = 'invoice' AND i.status = 'posted' AND a.billing_mode = 'postpaid'
|
|
99
|
+
ORDER BY i.issue_date ASC, i.number ASC
|
|
100
|
+
LIMIT 20`,
|
|
101
|
+
[scope.tenantId, scope.organizationId],
|
|
102
|
+
)) as unknown as Array<{ number: string; total_gross: string; currency_code: string; account_name: string }>
|
|
103
|
+
const receipts = (await em.execute(
|
|
104
|
+
`SELECT i.number, i.currency_code, a.name AS account_name
|
|
105
|
+
FROM billing_invoices i
|
|
106
|
+
JOIN billing_accounts a ON a.id = i.bill_account_id
|
|
107
|
+
WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.document_type = 'receipt'
|
|
108
|
+
ORDER BY i.created_at DESC
|
|
109
|
+
LIMIT 1`,
|
|
110
|
+
[scope.tenantId, scope.organizationId],
|
|
111
|
+
)) as unknown as Array<{ number: string; currency_code: string; account_name: string }>
|
|
112
|
+
const debits = (await em.execute(
|
|
113
|
+
`SELECT i.number, i.total_gross, i.currency_code, a.name AS account_name
|
|
114
|
+
FROM billing_invoices i
|
|
115
|
+
JOIN billing_accounts a ON a.id = i.bill_account_id
|
|
116
|
+
WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.test_run = false
|
|
117
|
+
AND i.document_type = 'debit_memo' AND i.status = 'posted'
|
|
118
|
+
ORDER BY i.issue_date ASC
|
|
119
|
+
LIMIT 1`,
|
|
120
|
+
[scope.tenantId, scope.organizationId],
|
|
121
|
+
)) as unknown as Array<{ number: string; total_gross: string; currency_code: string; account_name: string }>
|
|
122
|
+
return {
|
|
123
|
+
debitMemo: debits?.[0]
|
|
124
|
+
? { number: debits[0].number, totalGross: debits[0].total_gross, currencyCode: debits[0].currency_code, accountName: debits[0].account_name }
|
|
125
|
+
: null,
|
|
126
|
+
invoices: (invoices ?? []).map((row) => ({
|
|
127
|
+
number: row.number,
|
|
128
|
+
totalGross: row.total_gross,
|
|
129
|
+
currencyCode: row.currency_code,
|
|
130
|
+
accountName: row.account_name,
|
|
131
|
+
})),
|
|
132
|
+
receipt: receipts?.[0]
|
|
133
|
+
? { number: receipts[0].number, currencyCode: receipts[0].currency_code, accountName: receipts[0].account_name }
|
|
134
|
+
: null,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import type { BillingSettings } from '../../data/entities'
|
|
3
|
+
import { getBillingSettings, type SettingsScope } from '../billingSettings'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Seller details for demo / seed tenants (Spec B: posting an invoice needs
|
|
7
|
+
* legal name, address, tax ID and IBAN). Public seed API — demo use cases
|
|
8
|
+
* (`demo_gix`, `demo_puffin`, …) call `applySellerDefaults` from their own
|
|
9
|
+
* `seedDefaults` with a profile of their own.
|
|
10
|
+
*
|
|
11
|
+
* Only EMPTY fields are filled, so an operator's real details are never
|
|
12
|
+
* overwritten and re-running a seed is a no-op. Values must pass the settings
|
|
13
|
+
* validators (checksum-valid IBAN, 8/11-char BIC) so the settings page can be
|
|
14
|
+
* saved without touching them.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type SellerProfile = {
|
|
18
|
+
legalName: string
|
|
19
|
+
sellerAddress: { line1: string; line2?: string; city: string; postal_code: string; country: string }
|
|
20
|
+
taxId: string
|
|
21
|
+
bankName: string
|
|
22
|
+
iban: string
|
|
23
|
+
bic: string
|
|
24
|
+
invoiceFooter?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SELLER_FIELDS = ['legalName', 'sellerAddress', 'taxId', 'bankName', 'iban', 'bic', 'invoiceFooter'] as const
|
|
28
|
+
type SellerField = (typeof SELLER_FIELDS)[number]
|
|
29
|
+
|
|
30
|
+
/** The generic seller of the billing demo seed (primary tenant). */
|
|
31
|
+
export const DEFAULT_DEMO_SELLER: SellerProfile = {
|
|
32
|
+
legalName: 'Dainamite Demo sp. z o.o.',
|
|
33
|
+
sellerAddress: { line1: 'ul. Przykładowa 10', city: 'Kraków', postal_code: '30-001', country: 'PL' },
|
|
34
|
+
taxId: 'PL1234563218',
|
|
35
|
+
bankName: 'Demo Bank S.A.',
|
|
36
|
+
iban: 'PL61109010140000071219812874',
|
|
37
|
+
bic: 'WBKPPLPP',
|
|
38
|
+
invoiceFooter: 'Sąd Rejonowy dla Krakowa-Śródmieścia, KRS 0000000000. Kapitał zakładowy 5 000 zł.',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isEmpty(value: unknown): boolean {
|
|
42
|
+
if (value === null || value === undefined) return true
|
|
43
|
+
if (typeof value === 'string') return value.trim() === ''
|
|
44
|
+
if (typeof value === 'object') return Object.keys(value as object).length === 0
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Pure: which profile values would fill which empty settings fields. */
|
|
49
|
+
export function sellerFieldsToFill(
|
|
50
|
+
settings: Pick<BillingSettings, SellerField>,
|
|
51
|
+
profile: SellerProfile,
|
|
52
|
+
): Partial<Record<SellerField, unknown>> {
|
|
53
|
+
const out: Partial<Record<SellerField, unknown>> = {}
|
|
54
|
+
for (const field of SELLER_FIELDS) {
|
|
55
|
+
const value = profile[field]
|
|
56
|
+
if (value === undefined) continue
|
|
57
|
+
if (isEmpty(settings[field])) out[field] = value
|
|
58
|
+
}
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fill the organization's empty seller fields from `profile` and flush.
|
|
64
|
+
* Returns the names of the fields that were filled (empty when nothing changed).
|
|
65
|
+
*/
|
|
66
|
+
export async function applySellerDefaults(
|
|
67
|
+
em: EntityManager,
|
|
68
|
+
scope: SettingsScope,
|
|
69
|
+
profile: SellerProfile,
|
|
70
|
+
): Promise<string[]> {
|
|
71
|
+
const settings = await getBillingSettings(em, scope)
|
|
72
|
+
const fill = sellerFieldsToFill(settings, profile)
|
|
73
|
+
const fields = Object.keys(fill)
|
|
74
|
+
if (fields.length === 0) return []
|
|
75
|
+
Object.assign(settings, fill, { updatedAt: new Date() })
|
|
76
|
+
await em.flush()
|
|
77
|
+
return fields
|
|
78
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { BillCycle, ChargeTiming } from '../data/validators'
|
|
2
|
+
import { addDays, cloneAtMidnight, shiftCycles } from './billPeriod'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Recurring-charge computation for one bill date (Spec A).
|
|
6
|
+
*
|
|
7
|
+
* Pure and storage-agnostic: given an item's state and the account's bill
|
|
8
|
+
* date, decide which service periods to charge (or credit) and where the
|
|
9
|
+
* item's `billed_to_date` watermark moves. The engine calls it once per item
|
|
10
|
+
* per simulated bill date and owns persistence.
|
|
11
|
+
*
|
|
12
|
+
* Model:
|
|
13
|
+
* - The account's cycle grid is anchored at the bill date `D`:
|
|
14
|
+
* …, D − cycle, D, D + cycle, … (each period `[g_k, g_{k+1} − 1]`).
|
|
15
|
+
* - Horizon — how far the item is billed on `D`:
|
|
16
|
+
* in_advance → D + cycle − 1 in_arrears → D − 1
|
|
17
|
+
* - Cursor — first unbilled day: `billed_to_date + 1`, else `bill_start_date`.
|
|
18
|
+
* - The unbilled window `[cursor, min(horizon, bill_end_date)]` is cut along
|
|
19
|
+
* the grid; each grid period becomes one line (full → unit price,
|
|
20
|
+
* partial → prorated by calendar days, or skipped when `prorate=false`).
|
|
21
|
+
* - Credit (A-D2): an item ended (`bill_end_date`) before what was already
|
|
22
|
+
* billed (`billed_to_date`) gets negative lines for the unused days when
|
|
23
|
+
* `prorate=true`, and the watermark is pulled back to `bill_end_date`.
|
|
24
|
+
*
|
|
25
|
+
* The watermark is the single source of truth against double billing: every
|
|
26
|
+
* computation starts at `billed_to_date + 1`, so switching timing, re-running
|
|
27
|
+
* a bill date, or catching up can never charge a day twice.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export type RecurringItemState = {
|
|
31
|
+
id: string
|
|
32
|
+
chargeTiming: ChargeTiming
|
|
33
|
+
prorate: boolean
|
|
34
|
+
billStartDate: Date | string
|
|
35
|
+
billEndDate?: Date | string | null
|
|
36
|
+
billedToDate?: Date | string | null
|
|
37
|
+
unitPrice: number
|
|
38
|
+
isActive: boolean
|
|
39
|
+
deletedAt?: Date | string | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ProrationInfo = { activeDays: number; periodDays: number }
|
|
43
|
+
|
|
44
|
+
export type RecurringLine = {
|
|
45
|
+
billingItemId: string
|
|
46
|
+
kind: 'charge' | 'credit'
|
|
47
|
+
/** Inclusive service period the line covers. */
|
|
48
|
+
servicePeriodStart: Date
|
|
49
|
+
servicePeriodEnd: Date
|
|
50
|
+
/** 2dp half-up; negative for credits. */
|
|
51
|
+
amount: number
|
|
52
|
+
/** Null when the line covers a full grid period. */
|
|
53
|
+
proration: ProrationInfo | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type RecurringSkipReason =
|
|
57
|
+
| 'item-inactive'
|
|
58
|
+
| 'item-soft-deleted'
|
|
59
|
+
| 'partial-period-not-prorated'
|
|
60
|
+
| 'ended-after-billing-no-credit'
|
|
61
|
+
| 'period-cap-reached'
|
|
62
|
+
|
|
63
|
+
export type RecurringSkip = {
|
|
64
|
+
billingItemId: string
|
|
65
|
+
reason: RecurringSkipReason
|
|
66
|
+
servicePeriodStart: Date | null
|
|
67
|
+
servicePeriodEnd: Date | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type RecurringComputation = {
|
|
71
|
+
lines: RecurringLine[]
|
|
72
|
+
/** Watermark after these lines; unchanged when nothing was billed. */
|
|
73
|
+
newBilledToDate: Date | null
|
|
74
|
+
skipped: RecurringSkip[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Upper bound of grid periods processed per item per call (runaway guard). */
|
|
78
|
+
export const MAX_PERIODS_PER_ITEM = 120
|
|
79
|
+
|
|
80
|
+
const DAY_MS = 86_400_000
|
|
81
|
+
|
|
82
|
+
function toDate(value: Date | string | number): Date {
|
|
83
|
+
return cloneAtMidnight(value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function optDate(value: Date | string | null | undefined): Date | null {
|
|
87
|
+
return value === null || value === undefined || value === '' ? null : toDate(value)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function minDate(a: Date, b: Date): Date {
|
|
91
|
+
return a.getTime() <= b.getTime() ? a : b
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function maxDate(a: Date, b: Date): Date {
|
|
95
|
+
return a.getTime() >= b.getTime() ? a : b
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Inclusive day count of `[from, to]`. */
|
|
99
|
+
export function inclusiveDays(from: Date, to: Date): number {
|
|
100
|
+
return Math.round((to.getTime() - from.getTime()) / DAY_MS) + 1
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Half-up 2dp rounding that survives binary floating point (1.005 → 1.01). */
|
|
104
|
+
export function roundHalfUp2dp(value: number): number {
|
|
105
|
+
const sign = value < 0 ? -1 : 1
|
|
106
|
+
return (sign * Math.round((Math.abs(value) + Number.EPSILON) * 100)) / 100
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type GridPeriod = { index: number; start: Date; end: Date }
|
|
110
|
+
|
|
111
|
+
function gridPeriod(anchor: Date, cycle: BillCycle, index: number): GridPeriod {
|
|
112
|
+
const start = shiftCycles(anchor, cycle, index)
|
|
113
|
+
const end = addDays(shiftCycles(anchor, cycle, index + 1), -1)
|
|
114
|
+
return { index, start, end }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Grid period (anchored at `anchor`) containing `date`. */
|
|
118
|
+
export function gridPeriodContaining(anchor: Date, cycle: BillCycle, date: Date): GridPeriod {
|
|
119
|
+
const target = date.getTime()
|
|
120
|
+
// Coarse estimate first so long gaps don't walk one cycle at a time.
|
|
121
|
+
const approxDays: Record<BillCycle, number> = { weekly: 7, monthly: 30.44, quarterly: 91.31, annually: 365.25 }
|
|
122
|
+
let index = Math.floor((target - anchor.getTime()) / DAY_MS / approxDays[cycle])
|
|
123
|
+
let period = gridPeriod(anchor, cycle, index)
|
|
124
|
+
let guard = 0
|
|
125
|
+
while (period.start.getTime() > target && guard++ < 1000) period = gridPeriod(anchor, cycle, --index)
|
|
126
|
+
while (period.end.getTime() < target && guard++ < 2000) period = gridPeriod(anchor, cycle, ++index)
|
|
127
|
+
return period
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Slices `[from, to]` along the grid and prices each slice. Shared by the
|
|
132
|
+
* charge and credit paths so both prorate identically.
|
|
133
|
+
*/
|
|
134
|
+
function slice(
|
|
135
|
+
item: RecurringItemState,
|
|
136
|
+
anchor: Date,
|
|
137
|
+
cycle: BillCycle,
|
|
138
|
+
from: Date,
|
|
139
|
+
to: Date,
|
|
140
|
+
kind: RecurringLine['kind'],
|
|
141
|
+
): { lines: RecurringLine[]; skipped: RecurringSkip[]; lastCovered: Date | null } {
|
|
142
|
+
const lines: RecurringLine[] = []
|
|
143
|
+
const skipped: RecurringSkip[] = []
|
|
144
|
+
let lastCovered: Date | null = null
|
|
145
|
+
let period = gridPeriodContaining(anchor, cycle, from)
|
|
146
|
+
let count = 0
|
|
147
|
+
while (period.start.getTime() <= to.getTime()) {
|
|
148
|
+
if (count++ >= MAX_PERIODS_PER_ITEM) {
|
|
149
|
+
skipped.push({ billingItemId: item.id, reason: 'period-cap-reached', servicePeriodStart: period.start, servicePeriodEnd: null })
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
const activeStart = maxDate(period.start, from)
|
|
153
|
+
const activeEnd = minDate(period.end, to)
|
|
154
|
+
const periodDays = inclusiveDays(period.start, period.end)
|
|
155
|
+
const activeDays = inclusiveDays(activeStart, activeEnd)
|
|
156
|
+
const partial = activeDays < periodDays
|
|
157
|
+
if (partial && !item.prorate) {
|
|
158
|
+
skipped.push({
|
|
159
|
+
billingItemId: item.id,
|
|
160
|
+
reason: 'partial-period-not-prorated',
|
|
161
|
+
servicePeriodStart: activeStart,
|
|
162
|
+
servicePeriodEnd: activeEnd,
|
|
163
|
+
})
|
|
164
|
+
} else {
|
|
165
|
+
const gross = partial ? (item.unitPrice * activeDays) / periodDays : item.unitPrice
|
|
166
|
+
const amount = roundHalfUp2dp(gross)
|
|
167
|
+
lines.push({
|
|
168
|
+
billingItemId: item.id,
|
|
169
|
+
kind,
|
|
170
|
+
servicePeriodStart: activeStart,
|
|
171
|
+
servicePeriodEnd: activeEnd,
|
|
172
|
+
amount: kind === 'credit' ? -amount : amount,
|
|
173
|
+
proration: partial ? { activeDays, periodDays } : null,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
lastCovered = activeEnd
|
|
177
|
+
period = gridPeriod(anchor, cycle, period.index + 1)
|
|
178
|
+
}
|
|
179
|
+
return { lines, skipped, lastCovered }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function computeRecurringLines(
|
|
183
|
+
item: RecurringItemState,
|
|
184
|
+
params: { billDate: Date | string; cycle: BillCycle },
|
|
185
|
+
): RecurringComputation {
|
|
186
|
+
const billedTo = optDate(item.billedToDate)
|
|
187
|
+
if (item.deletedAt) {
|
|
188
|
+
return { lines: [], newBilledToDate: billedTo, skipped: [{ billingItemId: item.id, reason: 'item-soft-deleted', servicePeriodStart: null, servicePeriodEnd: null }] }
|
|
189
|
+
}
|
|
190
|
+
if (!item.isActive) {
|
|
191
|
+
return { lines: [], newBilledToDate: billedTo, skipped: [{ billingItemId: item.id, reason: 'item-inactive', servicePeriodStart: null, servicePeriodEnd: null }] }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const anchor = toDate(params.billDate)
|
|
195
|
+
const { cycle } = params
|
|
196
|
+
const start = toDate(item.billStartDate)
|
|
197
|
+
const end = optDate(item.billEndDate)
|
|
198
|
+
|
|
199
|
+
// ── Credit: the item ended inside what was already billed (A-D2). ──
|
|
200
|
+
if (end && billedTo && end.getTime() < billedTo.getTime()) {
|
|
201
|
+
if (!item.prorate) {
|
|
202
|
+
return {
|
|
203
|
+
lines: [],
|
|
204
|
+
newBilledToDate: billedTo,
|
|
205
|
+
skipped: [{ billingItemId: item.id, reason: 'ended-after-billing-no-credit', servicePeriodStart: addDays(end, 1), servicePeriodEnd: billedTo }],
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const credit = slice(item, anchor, cycle, maxDate(addDays(end, 1), start), billedTo, 'credit')
|
|
209
|
+
return { lines: credit.lines, newBilledToDate: end, skipped: credit.skipped }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Charge: bill the unbilled window up to the horizon. ──
|
|
213
|
+
const horizon =
|
|
214
|
+
item.chargeTiming === 'in_arrears' ? addDays(anchor, -1) : addDays(shiftCycles(anchor, cycle, 1), -1)
|
|
215
|
+
const cursor = billedTo ? maxDate(addDays(billedTo, 1), start) : start
|
|
216
|
+
const windowEnd = end ? minDate(horizon, end) : horizon
|
|
217
|
+
if (cursor.getTime() > windowEnd.getTime()) {
|
|
218
|
+
return { lines: [], newBilledToDate: billedTo, skipped: [] }
|
|
219
|
+
}
|
|
220
|
+
const charge = slice(item, anchor, cycle, cursor, windowEnd, 'charge')
|
|
221
|
+
return {
|
|
222
|
+
lines: charge.lines,
|
|
223
|
+
newBilledToDate: charge.lastCovered ?? billedTo,
|
|
224
|
+
skipped: charge.skipped,
|
|
225
|
+
}
|
|
226
|
+
}
|