@dainamite/billing 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/dist/modules/billing/acl.js +13 -1
- package/dist/modules/billing/acl.js.map +2 -2
- package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
- package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
- package/dist/modules/billing/api/accounts/route.js +46 -4
- package/dist/modules/billing/api/accounts/route.js.map +2 -2
- package/dist/modules/billing/api/export/account/[id]/route.js +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 +69 -0
- package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
- package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
- package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/add-line/route.js +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/en.json +457 -21
- package/dist/modules/billing/i18n/pl.json +457 -21
- package/dist/modules/billing/index.js +6 -2
- package/dist/modules/billing/index.js.map +2 -2
- package/dist/modules/billing/lib/accountFinancials.js +58 -0
- package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
- package/dist/modules/billing/lib/accountLock.js +14 -0
- package/dist/modules/billing/lib/accountLock.js.map +7 -0
- package/dist/modules/billing/lib/accountStatus.js +9 -7
- package/dist/modules/billing/lib/accountStatus.js.map +2 -2
- package/dist/modules/billing/lib/accountSummary.js +106 -0
- package/dist/modules/billing/lib/accountSummary.js.map +7 -0
- package/dist/modules/billing/lib/allocation.js +356 -0
- package/dist/modules/billing/lib/allocation.js.map +7 -0
- package/dist/modules/billing/lib/allocationEvents.js +22 -0
- package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
- package/dist/modules/billing/lib/billPeriod.js +15 -1
- package/dist/modules/billing/lib/billPeriod.js.map +2 -2
- package/dist/modules/billing/lib/billRunEngine.js +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 +131 -0
- package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
- package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
- package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
- package/dist/modules/billing/lib/invoiceWriter.js +107 -99
- package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
- package/dist/modules/billing/lib/itemBillingState.js +27 -0
- package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
- package/dist/modules/billing/lib/itemPriceChange.js +18 -0
- package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
- package/dist/modules/billing/lib/itemSelector.js.map +2 -2
- package/dist/modules/billing/lib/lineAmounts.js +32 -0
- package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
- package/dist/modules/billing/lib/memoGuards.js +25 -0
- package/dist/modules/billing/lib/memoGuards.js.map +7 -0
- package/dist/modules/billing/lib/navigation.js +48 -0
- package/dist/modules/billing/lib/navigation.js.map +7 -0
- package/dist/modules/billing/lib/numbering.js +120 -0
- package/dist/modules/billing/lib/numbering.js.map +7 -0
- package/dist/modules/billing/lib/overdue.js +36 -0
- package/dist/modules/billing/lib/overdue.js.map +7 -0
- package/dist/modules/billing/lib/paymentCsv.js +125 -0
- package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
- package/dist/modules/billing/lib/paymentGateway.js +15 -0
- package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
- package/dist/modules/billing/lib/paymentImport.js +164 -0
- package/dist/modules/billing/lib/paymentImport.js.map +7 -0
- package/dist/modules/billing/lib/paymentMatcher.js +36 -0
- package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
- package/dist/modules/billing/lib/prepaidConfig.js +17 -25
- package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
- package/dist/modules/billing/lib/prepaidConsume.js +26 -7
- package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
- package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
- package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
- package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
- package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
- package/dist/modules/billing/lib/seeds/demo.js +51 -3
- package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
- package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
- package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
- package/dist/modules/billing/lib/servicePeriods.js +121 -0
- package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
- package/dist/modules/billing/lib/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 +170 -0
- package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
- package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
- package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
- package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
- package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
- package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
- package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
- package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
- package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
- package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
- package/src/modules/billing/__tests__/commands/invoices.test.ts +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__/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 +189 -0
- package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
- package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
- package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
- package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
- package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
- package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
- package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
- package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
- package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
- package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
- package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
- package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
- package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
- package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
- package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
- package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
- package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
- package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
- package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
- package/src/modules/billing/__tests__/setup.test.ts +13 -4
- package/src/modules/billing/__tests__/validators.test.ts +193 -3
- package/src/modules/billing/acl.ts +12 -0
- package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
- package/src/modules/billing/api/accounts/route.ts +58 -4
- package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
- package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
- package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
- package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
- package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
- package/src/modules/billing/api/invoices/add-line/route.ts +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 +654 -5
- 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/en.json +457 -21
- package/src/modules/billing/i18n/pl.json +457 -21
- package/src/modules/billing/index.ts +6 -2
- package/src/modules/billing/lib/accountFinancials.ts +84 -0
- package/src/modules/billing/lib/accountLock.ts +32 -0
- package/src/modules/billing/lib/accountStatus.ts +11 -9
- package/src/modules/billing/lib/accountSummary.ts +172 -0
- package/src/modules/billing/lib/allocation.ts +515 -0
- package/src/modules/billing/lib/allocationEvents.ts +26 -0
- package/src/modules/billing/lib/billPeriod.ts +19 -0
- package/src/modules/billing/lib/billRunEngine.ts +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 +197 -0
- package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
- package/src/modules/billing/lib/invoiceWriter.ts +162 -148
- package/src/modules/billing/lib/itemBillingState.ts +67 -0
- package/src/modules/billing/lib/itemPriceChange.ts +56 -0
- package/src/modules/billing/lib/itemSelector.ts +4 -1
- package/src/modules/billing/lib/lineAmounts.ts +61 -0
- package/src/modules/billing/lib/memoGuards.ts +41 -0
- package/src/modules/billing/lib/navigation.ts +64 -0
- package/src/modules/billing/lib/numbering.ts +174 -0
- package/src/modules/billing/lib/overdue.ts +60 -0
- package/src/modules/billing/lib/paymentCsv.ts +171 -0
- package/src/modules/billing/lib/paymentGateway.ts +52 -0
- package/src/modules/billing/lib/paymentImport.ts +228 -0
- package/src/modules/billing/lib/paymentMatcher.ts +88 -0
- package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
- package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
- package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
- package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
- package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
- package/src/modules/billing/lib/prepaidConfig.ts +27 -36
- package/src/modules/billing/lib/prepaidConsume.ts +29 -6
- package/src/modules/billing/lib/prepaidPeriodClose.ts +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,116 @@
|
|
|
1
|
+
import { BillingSettings } from "../data/entities.js";
|
|
2
|
+
import { DEFAULT_NUMBER_MASKS, maskFor } from "./numbering.js";
|
|
3
|
+
const BILLING_SETTINGS_DEFAULTS = {
|
|
4
|
+
lowBalanceThresholdDefault: "0",
|
|
5
|
+
nearLimitBufferDefault: "0",
|
|
6
|
+
statementEnabled: true,
|
|
7
|
+
topupTaxRate: "0",
|
|
8
|
+
topupProvider: "stripe",
|
|
9
|
+
numberMasks: { ...DEFAULT_NUMBER_MASKS },
|
|
10
|
+
defaultPaymentTermsDays: 14,
|
|
11
|
+
defaultTaxRate: "23",
|
|
12
|
+
prepaidTaxPoint: "topup"
|
|
13
|
+
};
|
|
14
|
+
function receiptTaxRate(settings) {
|
|
15
|
+
if (settings.prepaidTaxPoint === "consumption") return 0;
|
|
16
|
+
const n = Number.parseFloat(String(settings.topupTaxRate ?? "0"));
|
|
17
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
18
|
+
}
|
|
19
|
+
function taxRateColumn(ratePercent) {
|
|
20
|
+
return (Math.round(ratePercent * 1e4) / 1e4).toString();
|
|
21
|
+
}
|
|
22
|
+
const REQUIRED_SELLER_FIELDS = ["legalName", "sellerAddress", "taxId", "iban"];
|
|
23
|
+
function missingSellerFields(settings) {
|
|
24
|
+
return REQUIRED_SELLER_FIELDS.filter((field) => {
|
|
25
|
+
const value = settings[field];
|
|
26
|
+
if (value === null || value === void 0) return true;
|
|
27
|
+
if (typeof value === "string") return value.trim() === "";
|
|
28
|
+
return Object.keys(value).length === 0;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function buildSellerSnapshot(settings) {
|
|
32
|
+
return {
|
|
33
|
+
organization_id: settings.organizationId,
|
|
34
|
+
legal_name: settings.legalName ?? null,
|
|
35
|
+
address: settings.sellerAddress ?? null,
|
|
36
|
+
tax_id: settings.taxId ?? null,
|
|
37
|
+
bank_name: settings.bankName ?? null,
|
|
38
|
+
iban: settings.iban ?? null,
|
|
39
|
+
bic: settings.bic ?? null,
|
|
40
|
+
invoice_footer: settings.invoiceFooter ?? null,
|
|
41
|
+
logo_attachment_id: settings.logoAttachmentId ?? null
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function getBillingSettings(em, scope) {
|
|
45
|
+
const where = { tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null };
|
|
46
|
+
const existing = await em.findOne(BillingSettings, where);
|
|
47
|
+
if (existing) return existing;
|
|
48
|
+
const now = /* @__PURE__ */ new Date();
|
|
49
|
+
await em.execute(
|
|
50
|
+
`INSERT INTO billing_settings
|
|
51
|
+
(tenant_id, organization_id, low_balance_threshold_default, near_limit_buffer_default,
|
|
52
|
+
statement_enabled, topup_tax_rate, topup_provider, number_masks, created_at, updated_at)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?)
|
|
54
|
+
ON CONFLICT (tenant_id, organization_id) WHERE deleted_at IS NULL DO NOTHING`,
|
|
55
|
+
[
|
|
56
|
+
scope.tenantId,
|
|
57
|
+
scope.organizationId,
|
|
58
|
+
BILLING_SETTINGS_DEFAULTS.lowBalanceThresholdDefault,
|
|
59
|
+
BILLING_SETTINGS_DEFAULTS.nearLimitBufferDefault,
|
|
60
|
+
BILLING_SETTINGS_DEFAULTS.statementEnabled,
|
|
61
|
+
BILLING_SETTINGS_DEFAULTS.topupTaxRate,
|
|
62
|
+
BILLING_SETTINGS_DEFAULTS.topupProvider,
|
|
63
|
+
JSON.stringify(BILLING_SETTINGS_DEFAULTS.numberMasks),
|
|
64
|
+
now,
|
|
65
|
+
now
|
|
66
|
+
]
|
|
67
|
+
);
|
|
68
|
+
const created = await em.findOne(BillingSettings, where);
|
|
69
|
+
if (!created) throw new Error("Billing settings could not be initialised");
|
|
70
|
+
return created;
|
|
71
|
+
}
|
|
72
|
+
function numberMaskFor(settings, series) {
|
|
73
|
+
return maskFor(settings.numberMasks ?? null, series);
|
|
74
|
+
}
|
|
75
|
+
function numberOr(value, fallback) {
|
|
76
|
+
const n = typeof value === "number" ? value : Number.parseFloat(value ?? "");
|
|
77
|
+
return Number.isFinite(n) ? n : fallback;
|
|
78
|
+
}
|
|
79
|
+
function serializeBillingSettings(settings) {
|
|
80
|
+
return {
|
|
81
|
+
id: settings.id,
|
|
82
|
+
lowBalanceThresholdDefault: numberOr(settings.lowBalanceThresholdDefault, 0),
|
|
83
|
+
nearLimitBufferDefault: numberOr(settings.nearLimitBufferDefault, 0),
|
|
84
|
+
statementEnabled: settings.statementEnabled,
|
|
85
|
+
topupTaxRate: numberOr(settings.topupTaxRate, 0),
|
|
86
|
+
prepaidTaxPoint: settings.prepaidTaxPoint === "consumption" ? "consumption" : "topup",
|
|
87
|
+
topupProvider: settings.topupProvider,
|
|
88
|
+
numberMasks: {
|
|
89
|
+
invoice: numberMaskFor(settings, "invoice"),
|
|
90
|
+
credit_memo: numberMaskFor(settings, "credit_memo"),
|
|
91
|
+
receipt: numberMaskFor(settings, "receipt")
|
|
92
|
+
},
|
|
93
|
+
legalName: settings.legalName ?? "",
|
|
94
|
+
sellerAddress: settings.sellerAddress ?? null,
|
|
95
|
+
taxId: settings.taxId ?? "",
|
|
96
|
+
bankName: settings.bankName ?? "",
|
|
97
|
+
iban: settings.iban ?? "",
|
|
98
|
+
bic: settings.bic ?? "",
|
|
99
|
+
invoiceFooter: settings.invoiceFooter ?? "",
|
|
100
|
+
logoAttachmentId: settings.logoAttachmentId ?? null,
|
|
101
|
+
defaultPaymentTermsDays: settings.defaultPaymentTermsDays ?? BILLING_SETTINGS_DEFAULTS.defaultPaymentTermsDays,
|
|
102
|
+
defaultTaxRate: numberOr(settings.defaultTaxRate, 23)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
BILLING_SETTINGS_DEFAULTS,
|
|
107
|
+
REQUIRED_SELLER_FIELDS,
|
|
108
|
+
buildSellerSnapshot,
|
|
109
|
+
getBillingSettings,
|
|
110
|
+
missingSellerFields,
|
|
111
|
+
numberMaskFor,
|
|
112
|
+
receiptTaxRate,
|
|
113
|
+
serializeBillingSettings,
|
|
114
|
+
taxRateColumn
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=billingSettings.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/billingSettings.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { BillingSettings } from '../data/entities'\nimport { DEFAULT_NUMBER_MASKS, maskFor, type DocumentSeries } from './numbering'\n\n/**\n * Per-organization billing settings (Spec 0, E-D2 / E-D5).\n *\n * Replaces the tenant-wide core `configs` rows billing used to seed. One row\n * per (tenant, organization), created lazily with today's defaults on first\n * read so every caller can rely on it existing. Spec B adds the seller /\n * payment / document fields (the INSERT below relies on their DB defaults:\n * 14 days terms, 23 % VAT, seller fields empty).\n */\n\nexport type SettingsScope = { tenantId: string; organizationId: string }\n\nexport const BILLING_SETTINGS_DEFAULTS = {\n lowBalanceThresholdDefault: '0',\n nearLimitBufferDefault: '0',\n statementEnabled: true,\n topupTaxRate: '0',\n topupProvider: 'stripe',\n numberMasks: { ...DEFAULT_NUMBER_MASKS } as Record<string, string>,\n defaultPaymentTermsDays: 14,\n defaultTaxRate: '23',\n prepaidTaxPoint: 'topup',\n} as const\n\n/**\n * VAT rate printed on top-up receipts (Spec F, P-D11): `topup_tax_rate` when\n * the prepaid tax point is `topup`, 0 in `consumption` mode (VAT is charged\n * on each debit instead \u2014 never twice).\n */\nexport function receiptTaxRate(settings: Pick<BillingSettings, 'topupTaxRate' | 'prepaidTaxPoint'>): number {\n if (settings.prepaidTaxPoint === 'consumption') return 0\n const n = Number.parseFloat(String(settings.topupTaxRate ?? '0'))\n return Number.isFinite(n) && n > 0 ? n : 0\n}\n\n/** numeric(7,4) column value for a VAT percentage. */\nexport function taxRateColumn(ratePercent: number): string {\n return (Math.round(ratePercent * 10000) / 10000).toString()\n}\n\n/** Seller fields a posted (printable) invoice requires \u2014 Spec B, P-D4. */\nexport const REQUIRED_SELLER_FIELDS = ['legalName', 'sellerAddress', 'taxId', 'iban'] as const\nexport type RequiredSellerField = (typeof REQUIRED_SELLER_FIELDS)[number]\n\nexport function missingSellerFields(\n settings: Pick<BillingSettings, RequiredSellerField>,\n): RequiredSellerField[] {\n return REQUIRED_SELLER_FIELDS.filter((field) => {\n const value = settings[field]\n if (value === null || value === undefined) return true\n if (typeof value === 'string') return value.trim() === ''\n return Object.keys(value).length === 0\n })\n}\n\n/**\n * Seller block frozen onto an invoice at post, so later settings edits never\n * change an issued document.\n */\nexport function buildSellerSnapshot(settings: BillingSettings): Record<string, unknown> {\n return {\n organization_id: settings.organizationId,\n legal_name: settings.legalName ?? null,\n address: settings.sellerAddress ?? null,\n tax_id: settings.taxId ?? null,\n bank_name: settings.bankName ?? null,\n iban: settings.iban ?? null,\n bic: settings.bic ?? null,\n invoice_footer: settings.invoiceFooter ?? null,\n logo_attachment_id: settings.logoAttachmentId ?? null,\n }\n}\n\nexport async function getBillingSettings(em: EntityManager, scope: SettingsScope): Promise<BillingSettings> {\n const where = { tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null }\n const existing = await em.findOne(BillingSettings, where as never)\n if (existing) return existing\n // Race-safe create: a concurrent first read may insert the same row; the\n // partial unique index makes ours a no-op and we re-read the winner.\n const now = new Date()\n await em.execute(\n `INSERT INTO billing_settings\n (tenant_id, organization_id, low_balance_threshold_default, near_limit_buffer_default,\n statement_enabled, topup_tax_rate, topup_provider, number_masks, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?)\n ON CONFLICT (tenant_id, organization_id) WHERE deleted_at IS NULL DO NOTHING`,\n [\n scope.tenantId,\n scope.organizationId,\n BILLING_SETTINGS_DEFAULTS.lowBalanceThresholdDefault,\n BILLING_SETTINGS_DEFAULTS.nearLimitBufferDefault,\n BILLING_SETTINGS_DEFAULTS.statementEnabled,\n BILLING_SETTINGS_DEFAULTS.topupTaxRate,\n BILLING_SETTINGS_DEFAULTS.topupProvider,\n JSON.stringify(BILLING_SETTINGS_DEFAULTS.numberMasks),\n now,\n now,\n ],\n )\n const created = await em.findOne(BillingSettings, where as never)\n if (!created) throw new Error('Billing settings could not be initialised')\n return created\n}\n\n/** Mask for a series, falling back to the default when unset or invalid. */\nexport function numberMaskFor(settings: Pick<BillingSettings, 'numberMasks'>, series: DocumentSeries): string {\n return maskFor(settings.numberMasks ?? null, series)\n}\n\nfunction numberOr(value: string | number | null | undefined, fallback: number): number {\n const n = typeof value === 'number' ? value : Number.parseFloat(value ?? '')\n return Number.isFinite(n) ? n : fallback\n}\n\n/** Plain view of the settings \u2014 what the API returns and the UI edits. */\nexport function serializeBillingSettings(settings: BillingSettings) {\n return {\n id: settings.id,\n lowBalanceThresholdDefault: numberOr(settings.lowBalanceThresholdDefault, 0),\n nearLimitBufferDefault: numberOr(settings.nearLimitBufferDefault, 0),\n statementEnabled: settings.statementEnabled,\n topupTaxRate: numberOr(settings.topupTaxRate, 0),\n prepaidTaxPoint: settings.prepaidTaxPoint === 'consumption' ? ('consumption' as const) : ('topup' as const),\n topupProvider: settings.topupProvider,\n numberMasks: {\n invoice: numberMaskFor(settings, 'invoice'),\n credit_memo: numberMaskFor(settings, 'credit_memo'),\n receipt: numberMaskFor(settings, 'receipt'),\n },\n legalName: settings.legalName ?? '',\n sellerAddress: settings.sellerAddress ?? null,\n taxId: settings.taxId ?? '',\n bankName: settings.bankName ?? '',\n iban: settings.iban ?? '',\n bic: settings.bic ?? '',\n invoiceFooter: settings.invoiceFooter ?? '',\n logoAttachmentId: settings.logoAttachmentId ?? null,\n defaultPaymentTermsDays: settings.defaultPaymentTermsDays ?? BILLING_SETTINGS_DEFAULTS.defaultPaymentTermsDays,\n defaultTaxRate: numberOr(settings.defaultTaxRate, 23),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import {
|
|
5
|
+
organizationScopeRequiredResponse,
|
|
6
|
+
resolveActiveOrganizationId
|
|
7
|
+
} from "@open-mercato/shared/lib/auth/organizationScope";
|
|
8
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
9
|
+
import { bridgeLegacyGuard, runMutationGuards } from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
10
|
+
function userFeatures(auth) {
|
|
11
|
+
const features = auth?.features;
|
|
12
|
+
return Array.isArray(features) ? features.filter((value) => typeof value === "string") : [];
|
|
13
|
+
}
|
|
14
|
+
async function readRequestBody(req) {
|
|
15
|
+
try {
|
|
16
|
+
return await req.json();
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function runCommandRoute(req, options, rawBody) {
|
|
22
|
+
try {
|
|
23
|
+
const container = await createRequestContainer();
|
|
24
|
+
const auth = await getAuthFromRequest(req);
|
|
25
|
+
if (!auth || !auth.tenantId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
26
|
+
const organizationId = resolveActiveOrganizationId(auth);
|
|
27
|
+
if (!organizationId) return organizationScopeRequiredResponse();
|
|
28
|
+
const parsed = options.bodySchema.safeParse(rawBody === void 0 ? await readRequestBody(req) : rawBody);
|
|
29
|
+
if (!parsed.success) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ error: "Invalid request", code: "billing.request.invalid", issues: parsed.error.issues },
|
|
32
|
+
{ status: 400 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const scope = { tenantId: auth.tenantId, organizationId };
|
|
36
|
+
const guardInput = {
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId,
|
|
39
|
+
userId: auth.sub ?? "",
|
|
40
|
+
resourceKind: options.resourceKind,
|
|
41
|
+
resourceId: options.resourceId(parsed.data),
|
|
42
|
+
operation: options.operation ?? "update",
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers
|
|
45
|
+
};
|
|
46
|
+
const legacyGuard = bridgeLegacyGuard(container);
|
|
47
|
+
const guard = legacyGuard ? await runMutationGuards([legacyGuard], guardInput, { userFeatures: userFeatures(auth) }) : { ok: true, afterSuccessCallbacks: [] };
|
|
48
|
+
if (!guard.ok) {
|
|
49
|
+
return NextResponse.json(guard.errorBody ?? { error: "Operation blocked by guard" }, { status: guard.errorStatus ?? 422 });
|
|
50
|
+
}
|
|
51
|
+
const ctx = {
|
|
52
|
+
container,
|
|
53
|
+
auth,
|
|
54
|
+
organizationScope: null,
|
|
55
|
+
selectedOrganizationId: organizationId,
|
|
56
|
+
organizationIds: [organizationId],
|
|
57
|
+
request: req
|
|
58
|
+
};
|
|
59
|
+
const commandBus = container.resolve("commandBus");
|
|
60
|
+
const input = options.toInput ? options.toInput(parsed.data, scope) : { ...parsed.data, ...scope };
|
|
61
|
+
const response = await commandBus.execute(options.commandId, { input, ctx });
|
|
62
|
+
for (const callback of guard.afterSuccessCallbacks) {
|
|
63
|
+
if (!callback.guard.afterSuccess) continue;
|
|
64
|
+
try {
|
|
65
|
+
await callback.guard.afterSuccess({ ...guardInput, resourceId: guardInput.resourceId ?? "", metadata: callback.metadata ?? null });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(`[${options.logLabel}] guard afterSuccess failed`, err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return NextResponse.json(response.result ?? { ok: true });
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
73
|
+
console.error(`[${options.logLabel}]`, err);
|
|
74
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
readRequestBody,
|
|
79
|
+
runCommandRoute
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=commandRoute.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/commandRoute.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { ZodType } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport {\n organizationScopeRequiredResponse,\n resolveActiveOrganizationId,\n} from '@open-mercato/shared/lib/auth/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { bridgeLegacyGuard, runMutationGuards } from '@open-mercato/shared/lib/crud/mutation-guard-registry'\n\n/**\n * Shared handler for billing's custom POST action routes (Spec C payments,\n * Spec D memos): authenticates, resolves the organization, validates the\n * body, runs the shared mutation guards (record locks etc.) as `update` on\n * the target resource, executes the command and maps `CrudHttpError`s.\n *\n * `requireFeatures` stays on each route's exported `metadata` (enforced by\n * the API dispatcher).\n */\n\nexport type CommandRouteOptions<TBody> = {\n commandId: string\n /** Body schema WITHOUT tenant / organization (they come from auth). */\n bodySchema: ZodType<TBody>\n resourceKind: string\n resourceId: (body: TBody) => string | null\n /** Mutation-guard operation (default `update`, the usual one for action routes). */\n operation?: 'create' | 'update' | 'delete'\n /** Optional input mapping (default: body + scope). */\n toInput?: (body: TBody, scope: { tenantId: string; organizationId: string }) => Record<string, unknown>\n logLabel: string\n}\n\nfunction userFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n return Array.isArray(features) ? features.filter((value): value is string => typeof value === 'string') : []\n}\n\nexport async function readRequestBody(req: Request): Promise<unknown> {\n try {\n return await req.json()\n } catch {\n return {}\n }\n}\n\nexport async function runCommandRoute<TBody>(\n req: Request,\n options: CommandRouteOptions<TBody>,\n rawBody?: unknown,\n): Promise<Response> {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const organizationId = resolveActiveOrganizationId(auth)\n if (!organizationId) return organizationScopeRequiredResponse()\n const parsed = options.bodySchema.safeParse(rawBody === undefined ? await readRequestBody(req) : rawBody)\n if (!parsed.success) {\n return NextResponse.json(\n { error: 'Invalid request', code: 'billing.request.invalid', issues: parsed.error.issues },\n { status: 400 },\n )\n }\n const scope = { tenantId: auth.tenantId, organizationId }\n const guardInput = {\n tenantId: auth.tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: options.resourceKind,\n resourceId: options.resourceId(parsed.data),\n operation: options.operation ?? ('update' as const),\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const legacyGuard = bridgeLegacyGuard(container)\n const guard = legacyGuard\n ? await runMutationGuards([legacyGuard], guardInput, { userFeatures: userFeatures(auth) })\n : { ok: true as const, afterSuccessCallbacks: [] }\n if (!guard.ok) {\n return NextResponse.json(guard.errorBody ?? { error: 'Operation blocked by guard' }, { status: guard.errorStatus ?? 422 })\n }\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n request: req,\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const input = options.toInput\n ? options.toInput(parsed.data, scope)\n : { ...(parsed.data as Record<string, unknown>), ...scope }\n const response = await commandBus.execute(options.commandId, { input, ctx })\n for (const callback of guard.afterSuccessCallbacks) {\n if (!callback.guard.afterSuccess) continue\n try {\n await callback.guard.afterSuccess({ ...guardInput, resourceId: guardInput.resourceId ?? '', metadata: callback.metadata ?? null })\n } catch (err) {\n console.error(`[${options.logLabel}] guard afterSuccess failed`, err)\n }\n }\n return NextResponse.json((response as { result?: unknown }).result ?? { ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n console.error(`[${options.logLabel}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const CUSTOMER_ENDPOINTS = {
|
|
2
|
+
company: "/api/customers/companies",
|
|
3
|
+
person: "/api/customers/people"
|
|
4
|
+
};
|
|
5
|
+
const CUSTOMER_DETAIL_PATHS = {
|
|
6
|
+
company: "/backend/customers/companies",
|
|
7
|
+
person: "/backend/customers/people"
|
|
8
|
+
};
|
|
9
|
+
const CUSTOMER_LOOKUP_CHUNK = 100;
|
|
10
|
+
function customerEndpoint(kind) {
|
|
11
|
+
return CUSTOMER_ENDPOINTS[kind];
|
|
12
|
+
}
|
|
13
|
+
function customerDetailHref(kind, customerId) {
|
|
14
|
+
return `${CUSTOMER_DETAIL_PATHS[kind]}/${encodeURIComponent(customerId)}`;
|
|
15
|
+
}
|
|
16
|
+
function str(value) {
|
|
17
|
+
if (typeof value === "string") return value.trim();
|
|
18
|
+
if (typeof value === "number") return String(value);
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
function extractItems(result) {
|
|
22
|
+
const raw = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.items) ? result.items : [];
|
|
23
|
+
return raw.filter((row) => !!row && typeof row === "object");
|
|
24
|
+
}
|
|
25
|
+
function toCustomerSummary(row) {
|
|
26
|
+
const id = str(row.id);
|
|
27
|
+
if (!id) return null;
|
|
28
|
+
const name = str(row.display_name) || str(row.displayName) || str(row.name) || id;
|
|
29
|
+
const email = str(row.primary_email) || str(row.primaryEmail) || str(row.email);
|
|
30
|
+
return { id, name, email: email || null };
|
|
31
|
+
}
|
|
32
|
+
function mapPrimaryAddress(rows) {
|
|
33
|
+
const ordered = [...rows].sort((a, b) => Number(b.is_primary === true) - Number(a.is_primary === true));
|
|
34
|
+
for (const row of ordered) {
|
|
35
|
+
const street = str(row.address_line1) || str(row.addressLine1);
|
|
36
|
+
const building = str(row.building_number) || str(row.buildingNumber);
|
|
37
|
+
const flat = str(row.flat_number) || str(row.flatNumber);
|
|
38
|
+
let line1 = street;
|
|
39
|
+
if (line1 && building) line1 = `${line1} ${building}`;
|
|
40
|
+
if (line1 && building && flat) line1 = `${line1}/${flat}`;
|
|
41
|
+
const city = str(row.city);
|
|
42
|
+
const postalCode = str(row.postal_code) || str(row.postalCode);
|
|
43
|
+
const country = (str(row.country) || str(row.country_code) || str(row.countryCode)).toUpperCase();
|
|
44
|
+
if (!line1 || !city || !postalCode || country.length < 2 || country.length > 3) continue;
|
|
45
|
+
return {
|
|
46
|
+
line1,
|
|
47
|
+
line2: str(row.address_line2) || str(row.addressLine2),
|
|
48
|
+
city,
|
|
49
|
+
postal_code: postalCode,
|
|
50
|
+
country
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function isAddressEmpty(address) {
|
|
56
|
+
return !address.line1.trim() && !address.line2.trim() && !address.city.trim() && !address.postal_code.trim();
|
|
57
|
+
}
|
|
58
|
+
function applyCustomerPrefill(current, customer, address) {
|
|
59
|
+
let next = current;
|
|
60
|
+
if (customer) {
|
|
61
|
+
if (!current.name.trim() && customer.name) next = { ...next, name: customer.name };
|
|
62
|
+
if (!current.invoiceEmail.trim() && customer.email) next = { ...next, invoiceEmail: customer.email };
|
|
63
|
+
}
|
|
64
|
+
if (address && isAddressEmpty(current.invoiceAddress)) {
|
|
65
|
+
next = { ...next, invoiceAddress: { ...address } };
|
|
66
|
+
}
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
function planCustomerLookups(rows) {
|
|
70
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
const id = (row.customerId ?? "").trim();
|
|
73
|
+
if (!id) continue;
|
|
74
|
+
const kind = row.customerKind === "person" ? "person" : "company";
|
|
75
|
+
if (!byKind.has(kind)) byKind.set(kind, /* @__PURE__ */ new Set());
|
|
76
|
+
byKind.get(kind).add(id);
|
|
77
|
+
}
|
|
78
|
+
const plan = [];
|
|
79
|
+
for (const [kind, ids] of byKind) {
|
|
80
|
+
const all = Array.from(ids);
|
|
81
|
+
for (let i = 0; i < all.length; i += CUSTOMER_LOOKUP_CHUNK) {
|
|
82
|
+
plan.push({ kind, ids: all.slice(i, i + CUSTOMER_LOOKUP_CHUNK) });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return plan;
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
CUSTOMER_LOOKUP_CHUNK,
|
|
89
|
+
applyCustomerPrefill,
|
|
90
|
+
customerDetailHref,
|
|
91
|
+
customerEndpoint,
|
|
92
|
+
extractItems,
|
|
93
|
+
mapPrimaryAddress,
|
|
94
|
+
planCustomerLookups,
|
|
95
|
+
toCustomerSummary
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=customerLookup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/customerLookup.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CustomerKind } from '../data/validators'\n\n/**\n * Pure helpers for linking a Billing Account to a customer record (Spec E).\n *\n * Billing never imports the customers module: `customer_id` is a string FK\n * and every read goes through the customers REST API from the UI (a soft\n * dependency \u2014 if it is unavailable the form degrades to a raw id and no\n * prefill). These helpers keep the response-shape knowledge in one tested\n * place so the React components stay thin.\n */\n\nexport type CustomerSummary = {\n id: string\n name: string\n email: string | null\n}\n\nexport type PrefillAddress = {\n line1: string\n line2: string\n city: string\n postal_code: string\n country: string\n}\n\nexport type PrefillTarget = {\n name: string\n invoiceEmail: string\n invoiceAddress: PrefillAddress\n}\n\ntype RawRecord = Record<string, unknown>\n\nconst CUSTOMER_ENDPOINTS: Record<CustomerKind, string> = {\n company: '/api/customers/companies',\n person: '/api/customers/people',\n}\n\nconst CUSTOMER_DETAIL_PATHS: Record<CustomerKind, string> = {\n company: '/backend/customers/companies',\n person: '/backend/customers/people',\n}\n\n/** Max ids per batched lookup \u2014 the CRUD factory caps `pageSize` at 100. */\nexport const CUSTOMER_LOOKUP_CHUNK = 100\n\nexport function customerEndpoint(kind: CustomerKind): string {\n return CUSTOMER_ENDPOINTS[kind]\n}\n\nexport function customerDetailHref(kind: CustomerKind, customerId: string): string {\n return `${CUSTOMER_DETAIL_PATHS[kind]}/${encodeURIComponent(customerId)}`\n}\n\nfunction str(value: unknown): string {\n if (typeof value === 'string') return value.trim()\n if (typeof value === 'number') return String(value)\n return ''\n}\n\n/** Extracts the row array from a list response (`{ items }` or a bare array). */\nexport function extractItems(result: unknown): RawRecord[] {\n const raw = Array.isArray(result)\n ? result\n : result && typeof result === 'object' && Array.isArray((result as { items?: unknown }).items)\n ? (result as { items: unknown[] }).items\n : []\n return raw.filter((row): row is RawRecord => !!row && typeof row === 'object')\n}\n\n/** Maps a customers list row (snake_case or camelCase) to a summary. */\nexport function toCustomerSummary(row: RawRecord): CustomerSummary | null {\n const id = str(row.id)\n if (!id) return null\n const name = str(row.display_name) || str(row.displayName) || str(row.name) || id\n const email = str(row.primary_email) || str(row.primaryEmail) || str(row.email)\n return { id, name, email: email || null }\n}\n\n/**\n * Picks the customer's primary address (else the first usable one) and maps\n * it onto the billing `invoice_address` shape. Returns null when no row has\n * the parts billing requires (line 1, city, postal code, country).\n */\nexport function mapPrimaryAddress(rows: RawRecord[]): PrefillAddress | null {\n const ordered = [...rows].sort((a, b) => Number(b.is_primary === true) - Number(a.is_primary === true))\n for (const row of ordered) {\n const street = str(row.address_line1) || str(row.addressLine1)\n const building = str(row.building_number) || str(row.buildingNumber)\n const flat = str(row.flat_number) || str(row.flatNumber)\n let line1 = street\n if (line1 && building) line1 = `${line1} ${building}`\n if (line1 && building && flat) line1 = `${line1}/${flat}`\n const city = str(row.city)\n const postalCode = str(row.postal_code) || str(row.postalCode)\n const country = (str(row.country) || str(row.country_code) || str(row.countryCode)).toUpperCase()\n if (!line1 || !city || !postalCode || country.length < 2 || country.length > 3) continue\n return {\n line1,\n line2: str(row.address_line2) || str(row.addressLine2),\n city,\n postal_code: postalCode,\n country,\n }\n }\n return null\n}\n\nfunction isAddressEmpty(address: PrefillAddress): boolean {\n return !address.line1.trim() && !address.line2.trim() && !address.city.trim() && !address.postal_code.trim()\n}\n\n/**\n * Fills ONLY empty fields from the customer record (decision E2-D2) \u2014 never\n * overwrites what the operator typed. The address is taken as a whole block\n * (and only if the form's address is still blank) so we never mix a typed\n * street with a prefilled city. Country alone does not count as \"typed\": the\n * form ships a default country.\n */\nexport function applyCustomerPrefill<T extends PrefillTarget>(\n current: T,\n customer: CustomerSummary | null,\n address: PrefillAddress | null,\n): T {\n let next = current\n if (customer) {\n if (!current.name.trim() && customer.name) next = { ...next, name: customer.name }\n if (!current.invoiceEmail.trim() && customer.email) next = { ...next, invoiceEmail: customer.email }\n }\n if (address && isAddressEmpty(current.invoiceAddress)) {\n next = { ...next, invoiceAddress: { ...address } }\n }\n return next\n}\n\n/** Groups ids by kind and chunks them for batched `?ids=` lookups. */\nexport function planCustomerLookups(\n rows: Array<{ customerId: string | null | undefined; customerKind: CustomerKind | null | undefined }>,\n): Array<{ kind: CustomerKind; ids: string[] }> {\n const byKind = new Map<CustomerKind, Set<string>>()\n for (const row of rows) {\n const id = (row.customerId ?? '').trim()\n if (!id) continue\n const kind: CustomerKind = row.customerKind === 'person' ? 'person' : 'company'\n if (!byKind.has(kind)) byKind.set(kind, new Set())\n byKind.get(kind)!.add(id)\n }\n const plan: Array<{ kind: CustomerKind; ids: string[] }> = []\n for (const [kind, ids] of byKind) {\n const all = Array.from(ids)\n for (let i = 0; i < all.length; i += CUSTOMER_LOOKUP_CHUNK) {\n plan.push({ kind, ids: all.slice(i, i + CUSTOMER_LOOKUP_CHUNK) })\n }\n }\n return plan\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function toUtcMidnight(value) {
|
|
2
|
+
const date = typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : new Date(value);
|
|
3
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
4
|
+
return date;
|
|
5
|
+
}
|
|
6
|
+
function toIsoDate(value) {
|
|
7
|
+
return toUtcMidnight(value).toISOString().slice(0, 10);
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
toIsoDate,
|
|
11
|
+
toUtcMidnight
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=dateOnly.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/dateOnly.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Date-only helpers (Spec F, P-D5).\n *\n * MikroORM v7 hydrates `date` columns as `YYYY-MM-DD` strings while the\n * engine computes with `Date`s; and `pg` serialises a `Date` query\n * parameter in the server's time zone, which can shift a `date` comparison\n * by a day. Every date-only value the billing engine compares or sends to a\n * `date` column goes through these helpers: compare as UTC midnight, query\n * with the ISO day string.\n */\n\n/** UTC-midnight `Date` for a `Date`, ISO string or timestamp. */\nexport function toUtcMidnight(value: Date | string | number): Date {\n const date = typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(value) ? new Date(`${value}T00:00:00Z`) : new Date(value)\n date.setUTCHours(0, 0, 0, 0)\n return date\n}\n\n/** `YYYY-MM-DD` (UTC) for a `Date`, ISO string or timestamp. */\nexport function toIsoDate(value: Date | string | number): string {\n return toUtcMidnight(value).toISOString().slice(0, 10)\n}\n"],
|
|
5
|
+
"mappings": "AAYO,SAAS,cAAc,OAAqC;AACjE,QAAM,OAAO,OAAO,UAAU,YAAY,sBAAsB,KAAK,KAAK,IAAI,oBAAI,KAAK,GAAG,KAAK,YAAY,IAAI,IAAI,KAAK,KAAK;AAC7H,OAAK,YAAY,GAAG,GAAG,GAAG,CAAC;AAC3B,SAAO;AACT;AAGO,SAAS,UAAU,OAAuC;AAC/D,SAAO,cAAc,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACvD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function buildBuyerSnapshot(account) {
|
|
2
|
+
return {
|
|
3
|
+
bill_account_id: account.id,
|
|
4
|
+
name: account.name,
|
|
5
|
+
customer_id: account.customerId,
|
|
6
|
+
customer_kind: account.customerKind ?? "company",
|
|
7
|
+
tax_id: account.taxId ?? null,
|
|
8
|
+
invoice_email: account.invoiceEmail,
|
|
9
|
+
invoice_language: account.invoiceLanguage,
|
|
10
|
+
invoice_address: account.invoiceAddress ?? null
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
buildBuyerSnapshot
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=documentParties.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/documentParties.ts"],
|
|
4
|
+
"sourcesContent": ["import type { BillingAccount } from '../data/entities'\n\n/**\n * Parties printed on a billing document. Posting freezes them as snapshots\n * on the invoice (buyer from the account, seller from `billingSettings`), so\n * later account / settings edits never change an issued document; drafts\n * render from the live rows.\n */\n\n/** Frozen \"issued to\" block \u2014 later account edits never change a posted document. */\nexport function buildBuyerSnapshot(account: BillingAccount): Record<string, unknown> {\n return {\n bill_account_id: account.id,\n name: account.name,\n customer_id: account.customerId,\n customer_kind: account.customerKind ?? 'company',\n tax_id: account.taxId ?? null,\n invoice_email: account.invoiceEmail,\n invoice_language: account.invoiceLanguage,\n invoice_address: account.invoiceAddress ?? null,\n }\n}\n\n"],
|
|
5
|
+
"mappings": "AAUO,SAAS,mBAAmB,SAAkD;AACnF,SAAO;AAAA,IACL,iBAAiB,QAAQ;AAAA,IACzB,MAAM,QAAQ;AAAA,IACd,aAAa,QAAQ;AAAA,IACrB,eAAe,QAAQ,gBAAgB;AAAA,IACvC,QAAQ,QAAQ,SAAS;AAAA,IACzB,eAAe,QAAQ;AAAA,IACvB,kBAAkB,QAAQ;AAAA,IAC1B,iBAAiB,QAAQ,kBAAkB;AAAA,EAC7C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function isReceivable(type) {
|
|
2
|
+
return type === "invoice" || type === "debit_memo";
|
|
3
|
+
}
|
|
4
|
+
function isMemo(type) {
|
|
5
|
+
return type === "credit_memo" || type === "debit_memo";
|
|
6
|
+
}
|
|
7
|
+
function seriesFor(type) {
|
|
8
|
+
if (type === "credit_memo") return "credit_memo";
|
|
9
|
+
if (type === "receipt") return "receipt";
|
|
10
|
+
return "invoice";
|
|
11
|
+
}
|
|
12
|
+
function displaySign(type) {
|
|
13
|
+
return type === "credit_memo" ? -1 : 1;
|
|
14
|
+
}
|
|
15
|
+
function canCorrect(original, memoType) {
|
|
16
|
+
if (original.testRun || original.documentType !== "invoice") return false;
|
|
17
|
+
if (["posted", "partially_paid", "paid"].includes(original.status)) return true;
|
|
18
|
+
return original.status === "credited" && memoType === "debit_memo";
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
canCorrect,
|
|
22
|
+
displaySign,
|
|
23
|
+
isMemo,
|
|
24
|
+
isReceivable,
|
|
25
|
+
seriesFor
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=documentTypes.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/documentTypes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Billing document type helpers (Spec 0 / Spec D). Pure.\n *\n * invoice \u2014 receivable, `invoice` series\n * debit_memo \u2014 receivable (supplementary invoice), `invoice` series (D-D8)\n * credit_memo \u2014 credit source, `credit_memo` series; amounts stored positive (D-D5)\n * receipt \u2014 prepaid top-up receipt, `receipt` series, never corrected (D-D6)\n */\n\nexport type DocumentType = 'invoice' | 'credit_memo' | 'debit_memo' | 'receipt'\n\nexport function isReceivable(type: string): boolean {\n return type === 'invoice' || type === 'debit_memo'\n}\n\nexport function isMemo(type: string): boolean {\n return type === 'credit_memo' || type === 'debit_memo'\n}\n\n/** Numbering series of a document type. */\nexport function seriesFor(type: string): 'invoice' | 'credit_memo' | 'receipt' {\n if (type === 'credit_memo') return 'credit_memo'\n if (type === 'receipt') return 'receipt'\n return 'invoice'\n}\n\n/** Sign used for DISPLAY only \u2014 credit memos read as negative amounts. */\nexport function displaySign(type: string): 1 | -1 {\n return type === 'credit_memo' ? -1 : 1\n}\n\n/** Originals a memo may correct (D-D3): posted, partially paid, paid (credited \u2192 debit only). */\nexport function canCorrect(original: { documentType: string; status: string; testRun: boolean }, memoType: string): boolean {\n if (original.testRun || original.documentType !== 'invoice') return false\n if (['posted', 'partially_paid', 'paid'].includes(original.status)) return true\n return original.status === 'credited' && memoType === 'debit_memo'\n}\n"],
|
|
5
|
+
"mappings": "AAWO,SAAS,aAAa,MAAuB;AAClD,SAAO,SAAS,aAAa,SAAS;AACxC;AAEO,SAAS,OAAO,MAAuB;AAC5C,SAAO,SAAS,iBAAiB,SAAS;AAC5C;AAGO,SAAS,UAAU,MAAqD;AAC7E,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,UAAW,QAAO;AAC/B,SAAO;AACT;AAGO,SAAS,YAAY,MAAsB;AAChD,SAAO,SAAS,gBAAgB,KAAK;AACvC;AAGO,SAAS,WAAW,UAAsE,UAA2B;AAC1H,MAAI,SAAS,WAAW,SAAS,iBAAiB,UAAW,QAAO;AACpE,MAAI,CAAC,UAAU,kBAAkB,MAAM,EAAE,SAAS,SAAS,MAAM,EAAG,QAAO;AAC3E,SAAO,SAAS,WAAW,cAAc,aAAa;AACxD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function resolvePaymentTermsDays(accountTermsDays, defaultTermsDays) {
|
|
2
|
+
if (typeof accountTermsDays === "number" && Number.isInteger(accountTermsDays) && accountTermsDays >= 0) {
|
|
3
|
+
return accountTermsDays;
|
|
4
|
+
}
|
|
5
|
+
if (typeof defaultTermsDays === "number" && Number.isInteger(defaultTermsDays) && defaultTermsDays >= 0) {
|
|
6
|
+
return defaultTermsDays;
|
|
7
|
+
}
|
|
8
|
+
return 14;
|
|
9
|
+
}
|
|
10
|
+
function computeDueDate(issueDate, termsDays) {
|
|
11
|
+
const base = new Date(issueDate);
|
|
12
|
+
return new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth(), base.getUTCDate() + termsDays));
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
computeDueDate,
|
|
16
|
+
resolvePaymentTermsDays
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=dueDate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/dueDate.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Due-date math for posted invoices (Spec B). Pure, date-only, UTC.\n *\n * `due_date = issue_date + payment terms (days)`, where the terms come from\n * the account (`payment_terms_days`) and fall back to the organization\n * default (`billing_settings.default_payment_terms_days`).\n */\n\nexport function resolvePaymentTermsDays(\n accountTermsDays: number | null | undefined,\n defaultTermsDays: number | null | undefined,\n): number {\n if (typeof accountTermsDays === 'number' && Number.isInteger(accountTermsDays) && accountTermsDays >= 0) {\n return accountTermsDays\n }\n if (typeof defaultTermsDays === 'number' && Number.isInteger(defaultTermsDays) && defaultTermsDays >= 0) {\n return defaultTermsDays\n }\n return 14\n}\n\n/** Issue date (any time of day) + N calendar days, at UTC midnight. */\nexport function computeDueDate(issueDate: Date, termsDays: number): Date {\n const base = new Date(issueDate)\n return new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth(), base.getUTCDate() + termsDays))\n}\n"],
|
|
5
|
+
"mappings": "AAQO,SAAS,wBACd,kBACA,kBACQ;AACR,MAAI,OAAO,qBAAqB,YAAY,OAAO,UAAU,gBAAgB,KAAK,oBAAoB,GAAG;AACvG,WAAO;AAAA,EACT;AACA,MAAI,OAAO,qBAAqB,YAAY,OAAO,UAAU,gBAAgB,KAAK,oBAAoB,GAAG;AACvG,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,eAAe,WAAiB,WAAyB;AACvE,QAAM,OAAO,IAAI,KAAK,SAAS;AAC/B,SAAO,IAAI,KAAK,KAAK,IAAI,KAAK,eAAe,GAAG,KAAK,YAAY,GAAG,KAAK,WAAW,IAAI,SAAS,CAAC;AACpG;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function normalizeIban(value) {
|
|
2
|
+
return value.replace(/\s+/g, "").toUpperCase();
|
|
3
|
+
}
|
|
4
|
+
function isValidIban(value) {
|
|
5
|
+
const iban = normalizeIban(value);
|
|
6
|
+
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(iban)) return false;
|
|
7
|
+
const rearranged = iban.slice(4) + iban.slice(0, 4);
|
|
8
|
+
let remainder = 0;
|
|
9
|
+
for (const char of rearranged) {
|
|
10
|
+
const digits = /[A-Z]/.test(char) ? String(char.charCodeAt(0) - 55) : char;
|
|
11
|
+
for (const digit of digits) {
|
|
12
|
+
remainder = (remainder * 10 + Number(digit)) % 97;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return remainder === 1;
|
|
16
|
+
}
|
|
17
|
+
function formatIban(value) {
|
|
18
|
+
return normalizeIban(value).replace(/(.{4})/g, "$1 ").trim();
|
|
19
|
+
}
|
|
20
|
+
function isValidBic(value) {
|
|
21
|
+
return /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(value.trim().toUpperCase());
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
formatIban,
|
|
25
|
+
isValidBic,
|
|
26
|
+
isValidIban,
|
|
27
|
+
normalizeIban
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=iban.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/billing/lib/iban.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * IBAN / BIC helpers for the seller's payment instructions (Spec B). Pure.\n *\n * `normalizeIban` strips spaces and upper-cases; `isValidIban` checks the\n * structure (country code, check digits, 11\u201330 alphanumerics after) and the\n * ISO 13616 mod-97 checksum. Country-specific lengths are not enforced \u2014 the\n * checksum already rejects typos, and a missing country table never blocks a\n * legitimate account.\n */\n\nexport function normalizeIban(value: string): string {\n return value.replace(/\\s+/g, '').toUpperCase()\n}\n\nexport function isValidIban(value: string): boolean {\n const iban = normalizeIban(value)\n if (!/^[A-Z]{2}\\d{2}[A-Z0-9]{11,30}$/.test(iban)) return false\n const rearranged = iban.slice(4) + iban.slice(0, 4)\n let remainder = 0\n for (const char of rearranged) {\n const digits = /[A-Z]/.test(char) ? String(char.charCodeAt(0) - 55) : char\n for (const digit of digits) {\n remainder = (remainder * 10 + Number(digit)) % 97\n }\n }\n return remainder === 1\n}\n\n/** Groups of 4 for display (\"PL61 1090 1014 \u2026\"). */\nexport function formatIban(value: string): string {\n return normalizeIban(value).replace(/(.{4})/g, '$1 ').trim()\n}\n\nexport function isValidBic(value: string): boolean {\n return /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(value.trim().toUpperCase())\n}\n"],
|
|
5
|
+
"mappings": "AAUO,SAAS,cAAc,OAAuB;AACnD,SAAO,MAAM,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAC/C;AAEO,SAAS,YAAY,OAAwB;AAClD,QAAM,OAAO,cAAc,KAAK;AAChC,MAAI,CAAC,iCAAiC,KAAK,IAAI,EAAG,QAAO;AACzD,QAAM,aAAa,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,GAAG,CAAC;AAClD,MAAI,YAAY;AAChB,aAAW,QAAQ,YAAY;AAC7B,UAAM,SAAS,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,IAAI;AACtE,eAAW,SAAS,QAAQ;AAC1B,mBAAa,YAAY,KAAK,OAAO,KAAK,KAAK;AAAA,IACjD;AAAA,EACF;AACA,SAAO,cAAc;AACvB;AAGO,SAAS,WAAW,OAAuB;AAChD,SAAO,cAAc,KAAK,EAAE,QAAQ,WAAW,KAAK,EAAE,KAAK;AAC7D;AAEO,SAAS,WAAW,OAAwB;AACjD,SAAO,8CAA8C,KAAK,MAAM,KAAK,EAAE,YAAY,CAAC;AACtF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|