@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,197 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
4
|
+
import type { SendEmailOptions } from '@open-mercato/shared/lib/email/send'
|
|
5
|
+
import { BillingAccount, BillingInvoice } from '../data/entities'
|
|
6
|
+
import { emitBillingEvent } from '../events'
|
|
7
|
+
import { DEFAULT_INVOICE_LANGUAGE } from './invoiceLanguages'
|
|
8
|
+
import { formatIban } from './iban'
|
|
9
|
+
import { documentLocale, formatDocumentDate, formatDocumentMoney, type DocumentTranslate } from './pdf/invoiceModel'
|
|
10
|
+
import { getInvoicePdf, loadDocumentTranslator, type TranslatorLoader } from './pdf/invoicePdf'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Emails a posted billing document to the account's invoice address with
|
|
14
|
+
* the frozen PDF attached (Spec B), through the platform's standard
|
|
15
|
+
* `sendEmail()` — no custom transport.
|
|
16
|
+
*
|
|
17
|
+
* - posted (non-draft, non-test) documents only → 409 `billing.invoice.not_posted`;
|
|
18
|
+
* - the connector's placeholder addresses (`@invalid.local`) and malformed
|
|
19
|
+
* emails are refused → 422 `billing.invoice.email_invalid`, nothing sent;
|
|
20
|
+
* - every attempt appends `{ at, to, status, error?, trigger }` to
|
|
21
|
+
* `metadata.deliveries` (atomic jsonb append — concurrent sends never
|
|
22
|
+
* lose an entry) and emits `billing.invoice.sent` / `send_failed`;
|
|
23
|
+
* - transport errors (e.g. `EMAIL_TRANSPORT_NOT_CONFIGURED`) are recorded
|
|
24
|
+
* as a `failed` delivery and returned, never thrown — posting is
|
|
25
|
+
* unaffected and the operator sees the reason.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type DeliveryTrigger = 'manual' | 'auto'
|
|
29
|
+
|
|
30
|
+
export type InvoiceDelivery = {
|
|
31
|
+
at: string
|
|
32
|
+
to: string
|
|
33
|
+
status: 'sent' | 'failed'
|
|
34
|
+
error?: string | null
|
|
35
|
+
trigger: DeliveryTrigger
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SendInvoiceResult = { invoiceId: string; to: string; status: 'sent' | 'failed'; error: string | null }
|
|
39
|
+
|
|
40
|
+
export type SendEmailFn = (options: SendEmailOptions) => Promise<void>
|
|
41
|
+
|
|
42
|
+
export type SendInvoiceDeps = {
|
|
43
|
+
sendEmail?: SendEmailFn
|
|
44
|
+
loadTranslator?: TranslatorLoader
|
|
45
|
+
now?: () => Date
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PLACEHOLDER_DOMAIN = '@invalid.local'
|
|
49
|
+
const emailSchema = z.string().trim().email()
|
|
50
|
+
|
|
51
|
+
export function isDeliverableEmail(value: string | null | undefined): value is string {
|
|
52
|
+
if (!value) return false
|
|
53
|
+
if (value.trim().toLowerCase().endsWith(PLACEHOLDER_DOMAIN)) return false
|
|
54
|
+
return emailSchema.safeParse(value).success
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasSuccessfulDelivery(
|
|
58
|
+
invoice: Pick<BillingInvoice, 'metadata'>,
|
|
59
|
+
trigger?: DeliveryTrigger,
|
|
60
|
+
): boolean {
|
|
61
|
+
const deliveries = (invoice.metadata?.deliveries ?? []) as InvoiceDelivery[]
|
|
62
|
+
return Array.isArray(deliveries) && deliveries.some((d) => d.status === 'sent' && (!trigger || d.trigger === trigger))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function escapeHtml(value: string): string {
|
|
66
|
+
return value.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] ?? c)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fill(template: string, params: Record<string, string>): string {
|
|
70
|
+
return template.replace(/\{(\w+)\}/g, (match, key: string) => params[key] ?? match)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Subject + plain-text and HTML bodies in the document language. Pure. */
|
|
74
|
+
export function buildInvoiceEmail(params: {
|
|
75
|
+
invoice: Pick<BillingInvoice, 'number' | 'totalGross' | 'currencyCode' | 'dueDate'>
|
|
76
|
+
buyerName: string
|
|
77
|
+
sellerName: string | null
|
|
78
|
+
iban: string | null
|
|
79
|
+
language: string
|
|
80
|
+
translate: DocumentTranslate
|
|
81
|
+
}): { subject: string; text: string; html: string } {
|
|
82
|
+
const { invoice, translate: t } = params
|
|
83
|
+
const locale = documentLocale(params.language)
|
|
84
|
+
const values = {
|
|
85
|
+
number: invoice.number,
|
|
86
|
+
name: params.buyerName,
|
|
87
|
+
amount: formatDocumentMoney(invoice.totalGross, invoice.currencyCode, locale).replace(/[ ]/g, ' '),
|
|
88
|
+
dueDate: formatDocumentDate(invoice.dueDate ?? null, locale),
|
|
89
|
+
iban: params.iban ? formatIban(params.iban) : '',
|
|
90
|
+
}
|
|
91
|
+
const paragraphs = [
|
|
92
|
+
fill(t('billing.email.invoice.greeting', 'Hello {name},'), values),
|
|
93
|
+
fill(t('billing.email.invoice.body', 'Please find attached invoice {number} for {amount}, due on {dueDate}.'), values),
|
|
94
|
+
params.iban
|
|
95
|
+
? fill(t('billing.email.invoice.payment', 'Please pay to IBAN {iban} with the transfer title {number}.'), values)
|
|
96
|
+
: '',
|
|
97
|
+
[t('billing.email.invoice.closing', 'Kind regards,'), params.sellerName ?? ''].filter(Boolean).join('\n'),
|
|
98
|
+
].filter(Boolean)
|
|
99
|
+
return {
|
|
100
|
+
subject: fill(t('billing.email.invoice.subject', 'Invoice {number}'), values),
|
|
101
|
+
text: paragraphs.join('\n\n'),
|
|
102
|
+
html: paragraphs.map((p) => `<p>${escapeHtml(p).replace(/\n/g, '<br/>')}</p>`).join(''),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function defaultSendEmail(options: SendEmailOptions): Promise<void> {
|
|
107
|
+
const { sendEmail } = await import('@open-mercato/shared/lib/email/send')
|
|
108
|
+
await sendEmail(options)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function appendDelivery(em: EntityManager, invoice: BillingInvoice, delivery: InvoiceDelivery): Promise<void> {
|
|
112
|
+
await em.execute(
|
|
113
|
+
`UPDATE billing_invoices
|
|
114
|
+
SET metadata = jsonb_set(COALESCE(metadata, '{}'::jsonb), '{deliveries}',
|
|
115
|
+
COALESCE(metadata->'deliveries', '[]'::jsonb) || ?::jsonb),
|
|
116
|
+
updated_at = now()
|
|
117
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
|
|
118
|
+
[JSON.stringify([delivery]), invoice.id, invoice.tenantId, invoice.organizationId],
|
|
119
|
+
)
|
|
120
|
+
const deliveries = Array.isArray(invoice.metadata?.deliveries) ? (invoice.metadata?.deliveries as InvoiceDelivery[]) : []
|
|
121
|
+
invoice.metadata = { ...(invoice.metadata ?? {}), deliveries: [...deliveries, delivery] }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function sendInvoice(
|
|
125
|
+
em: EntityManager,
|
|
126
|
+
params: { invoiceId: string; tenantId: string; organizationId: string; trigger: DeliveryTrigger },
|
|
127
|
+
deps: SendInvoiceDeps = {},
|
|
128
|
+
): Promise<SendInvoiceResult> {
|
|
129
|
+
const scope = { tenantId: params.tenantId, organizationId: params.organizationId }
|
|
130
|
+
const invoice = await em.findOne(BillingInvoice, { id: params.invoiceId, ...scope, deletedAt: null } as never)
|
|
131
|
+
if (!invoice) throw new CrudHttpError(404, { error: 'Invoice not found' })
|
|
132
|
+
if (invoice.status === 'draft' || invoice.testRun) {
|
|
133
|
+
throw new CrudHttpError(409, {
|
|
134
|
+
error: 'Only posted invoices can be sent',
|
|
135
|
+
code: 'billing.invoice.not_posted',
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The live account address wins (an operator may fix a bad address and
|
|
140
|
+
// resend); the frozen buyer snapshot is the fallback for a deleted account.
|
|
141
|
+
const account = await em.findOne(BillingAccount, { id: invoice.billAccountId, ...scope } as never)
|
|
142
|
+
const buyer = (invoice.buyerSnapshot ?? {}) as Record<string, unknown>
|
|
143
|
+
const to = account?.invoiceEmail ?? (typeof buyer.invoice_email === 'string' ? buyer.invoice_email : '')
|
|
144
|
+
if (!isDeliverableEmail(to)) {
|
|
145
|
+
throw new CrudHttpError(422, {
|
|
146
|
+
error: 'The billing account has no deliverable invoice email',
|
|
147
|
+
code: 'billing.invoice.email_invalid',
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const language =
|
|
152
|
+
(typeof buyer.invoice_language === 'string' && buyer.invoice_language) ||
|
|
153
|
+
account?.invoiceLanguage ||
|
|
154
|
+
DEFAULT_INVOICE_LANGUAGE
|
|
155
|
+
const translate = await (deps.loadTranslator ?? loadDocumentTranslator)(language)
|
|
156
|
+
const seller = (invoice.sellerSnapshot ?? {}) as Record<string, unknown>
|
|
157
|
+
const email = buildInvoiceEmail({
|
|
158
|
+
invoice,
|
|
159
|
+
buyerName: (typeof buyer.name === 'string' && buyer.name) || account?.name || '',
|
|
160
|
+
sellerName: typeof seller.legal_name === 'string' ? seller.legal_name : null,
|
|
161
|
+
iban: typeof seller.iban === 'string' ? seller.iban : null,
|
|
162
|
+
language,
|
|
163
|
+
translate,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const now = deps.now ?? (() => new Date())
|
|
167
|
+
let error: string | null = null
|
|
168
|
+
try {
|
|
169
|
+
const pdf = await getInvoicePdf(em, invoice, { loadTranslator: deps.loadTranslator })
|
|
170
|
+
await (deps.sendEmail ?? defaultSendEmail)({
|
|
171
|
+
to,
|
|
172
|
+
subject: email.subject,
|
|
173
|
+
text: email.text,
|
|
174
|
+
html: email.html,
|
|
175
|
+
attachments: [{ filename: pdf.fileName, content: Buffer.from(pdf.bytes).toString('base64'), contentType: 'application/pdf' }],
|
|
176
|
+
tenantId: params.tenantId,
|
|
177
|
+
organizationId: params.organizationId,
|
|
178
|
+
})
|
|
179
|
+
} catch (err) {
|
|
180
|
+
error = err instanceof Error ? err.message : String(err)
|
|
181
|
+
console.warn('[billing.invoice.send] delivery failed', invoice.id, error)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const status: InvoiceDelivery['status'] = error ? 'failed' : 'sent'
|
|
185
|
+
await appendDelivery(em, invoice, { at: now().toISOString(), to, status, error, trigger: params.trigger })
|
|
186
|
+
await emitBillingEvent(status === 'sent' ? 'billing.invoice.sent' : 'billing.invoice.send_failed', {
|
|
187
|
+
invoiceId: invoice.id,
|
|
188
|
+
invoiceNumber: invoice.number,
|
|
189
|
+
billAccountId: invoice.billAccountId,
|
|
190
|
+
to,
|
|
191
|
+
trigger: params.trigger,
|
|
192
|
+
error,
|
|
193
|
+
tenantId: invoice.tenantId,
|
|
194
|
+
organizationId: invoice.organizationId,
|
|
195
|
+
})
|
|
196
|
+
return { invoiceId: invoice.id, to, status, error }
|
|
197
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoice languages the built-in billing document template can render.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the account form's language picklist and the
|
|
5
|
+
* `invoiceLanguage` validator. Adding a language means adding the template
|
|
6
|
+
* labels for it AND one entry here — never one without the other.
|
|
7
|
+
*
|
|
8
|
+
* `label` is the language's own name (endonym), so it reads correctly no
|
|
9
|
+
* matter which UI locale the operator uses — it is intentionally not routed
|
|
10
|
+
* through i18n.
|
|
11
|
+
*
|
|
12
|
+
* Spec: .ai/specs/2026-11-14-billing-account-workspace.md (D22). Narrowing the
|
|
13
|
+
* list per organization (`BillingSettings.enabled_invoice_languages`) arrives
|
|
14
|
+
* with the standalone-ledger spec; until then every supported language is
|
|
15
|
+
* selectable.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const SUPPORTED_INVOICE_LANGUAGES = [
|
|
19
|
+
{ code: 'en', label: 'English' },
|
|
20
|
+
{ code: 'pl', label: 'Polski' },
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
export type InvoiceLanguageCode = (typeof SUPPORTED_INVOICE_LANGUAGES)[number]['code']
|
|
24
|
+
|
|
25
|
+
export const INVOICE_LANGUAGE_CODES = SUPPORTED_INVOICE_LANGUAGES.map((l) => l.code) as [
|
|
26
|
+
InvoiceLanguageCode,
|
|
27
|
+
...InvoiceLanguageCode[],
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_INVOICE_LANGUAGE: InvoiceLanguageCode = 'en'
|
|
31
|
+
|
|
32
|
+
export function isSupportedInvoiceLanguage(value: string): value is InvoiceLanguageCode {
|
|
33
|
+
return (INVOICE_LANGUAGE_CODES as readonly string[]).includes(value)
|
|
34
|
+
}
|
|
@@ -1,62 +1,61 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
SalesInvoice,
|
|
5
|
-
SalesInvoiceLine,
|
|
6
|
-
} from '@open-mercato/core/modules/sales/data/entities'
|
|
7
|
-
import type { SalesDocumentNumberGenerator } from '@open-mercato/core/modules/sales/services/salesDocumentNumberGenerator'
|
|
2
|
+
import { BillingInvoice, BillingInvoiceLine } from '../data/entities'
|
|
8
3
|
import type { BillingAccount, BillingItem } from '../data/entities'
|
|
9
4
|
import type { BillPeriod } from './billPeriod'
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from './
|
|
5
|
+
import type { RecurringLine } from './servicePeriods'
|
|
6
|
+
import { draftNumber, testNumber } from './numbering'
|
|
7
|
+
import { sumTotals } from './vat'
|
|
8
|
+
import { lineVat, priceBasisOf, unitPriceNetOf, type PriceBasis } from './lineAmounts'
|
|
14
9
|
|
|
15
10
|
/**
|
|
16
|
-
* Writes a
|
|
11
|
+
* Writes a billing-owned draft invoice (`billing_invoices` +
|
|
12
|
+
* `billing_invoice_lines`) for one bill date of an account — Spec 0: billing
|
|
13
|
+
* no longer records anything in core `sales`.
|
|
17
14
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* upstream column is `NOT NULL`. "Post" then just transitions
|
|
27
|
-
* status `draft → posted`.
|
|
28
|
-
*
|
|
29
|
-
* 2. Test-mode invoices use a `TEST-<uuid>` placeholder number (not
|
|
30
|
-
* the real sequence) so test runs do not burn production numbers
|
|
31
|
-
* and can be repeated idempotently.
|
|
32
|
-
*
|
|
33
|
-
* VAT is NOT computed here — `core/sales`'s tax service owns the
|
|
34
|
-
* per-line `tax_rate` / `tax_amount` calculation, and runs on POST.
|
|
35
|
-
* Drafts therefore carry zeroes in the tax columns; the totals reflect
|
|
36
|
-
* net amounts only.
|
|
15
|
+
* - Real drafts get a temporary `DRAFT-…` number; the series number is
|
|
16
|
+
* claimed only at post (gap-free numbering — no number is burnt by a
|
|
17
|
+
* deleted draft).
|
|
18
|
+
* - Test drafts get `TEST-…` and `test_run = true`; they are never posted.
|
|
19
|
+
* - Spec B (B-D2): every line carries its item's VAT rate; tax is rounded
|
|
20
|
+
* per line (half-up, 2 dp) and totals are sums of the rounded lines.
|
|
21
|
+
* - Spec F (F-D2): line amounts are on the item's `price_basis`; net / tax
|
|
22
|
+
* / gross come from `lineVat`, so a typed gross is reproduced exactly.
|
|
37
23
|
*/
|
|
38
24
|
|
|
39
25
|
export type WriteMode = 'real' | 'test' | 'dry'
|
|
40
26
|
|
|
41
27
|
export type InvoiceLineDescriptor = {
|
|
42
|
-
/** Maps to billing_item_id
|
|
28
|
+
/** Maps to `billing_invoice_lines.billing_item_id`. */
|
|
43
29
|
billingItemId: string
|
|
44
30
|
/** From the spec's `metadata.billing_type` for traceability. */
|
|
45
31
|
billingType: 'one_time' | 'recurring' | 'usage'
|
|
46
32
|
description: string
|
|
47
|
-
/**
|
|
48
|
-
|
|
33
|
+
/** Unit price on `priceBasis` (numeric string). The writer derives `unit_price_net`. */
|
|
34
|
+
unitPrice: string
|
|
49
35
|
/** Quantity (string for the numeric(18,4) column). */
|
|
50
36
|
quantity: string
|
|
51
|
-
/**
|
|
52
|
-
|
|
37
|
+
/** Extended line amount on `priceBasis` (unit × quantity × proration). */
|
|
38
|
+
lineAmount: string
|
|
39
|
+
/** Spec F: the basis `unitPrice` / `lineAmount` are on (the item's `price_basis`). */
|
|
40
|
+
priceBasis: PriceBasis
|
|
41
|
+
/** Spec B: VAT rate in percent (numeric string), from the item. */
|
|
42
|
+
taxRate: string
|
|
53
43
|
/** Optional per-line metadata extension — e.g. usage tier breakdown. */
|
|
54
44
|
metadataExtra?: Record<string, unknown>
|
|
45
|
+
/**
|
|
46
|
+
* Spec A: inclusive service period the line covers (recurring + usage).
|
|
47
|
+
* Written to line metadata and used for the invoice's display period.
|
|
48
|
+
*/
|
|
49
|
+
servicePeriodStart?: Date
|
|
50
|
+
servicePeriodEnd?: Date
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
export type WriteInvoiceParams = {
|
|
58
54
|
mode: WriteMode
|
|
59
55
|
account: BillingAccount
|
|
56
|
+
/** Spec A: the bill date this invoice is for — the dedupe key. */
|
|
57
|
+
billDate: Date
|
|
58
|
+
/** Display period: min/max of the line service periods (see `displayPeriod`). */
|
|
60
59
|
period: BillPeriod
|
|
61
60
|
billRunId: string
|
|
62
61
|
lines: InvoiceLineDescriptor[]
|
|
@@ -66,148 +65,105 @@ export type WrittenInvoice = {
|
|
|
66
65
|
invoiceId: string | null
|
|
67
66
|
invoiceNumber: string | null
|
|
68
67
|
totalNet: string
|
|
68
|
+
totalGross: string
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
/** @deprecated kept for test-suite compatibility; the writer has no cache any more. */
|
|
72
|
+
export function __resetInvoiceWriterCacheForTests(): void {}
|
|
72
73
|
|
|
73
|
-
/**
|
|
74
|
-
* Test hook — clears the shared resolver's status cache so the engine
|
|
75
|
-
* test suite stays deterministic across mocked-EM environments.
|
|
76
|
-
*/
|
|
77
|
-
export function __resetInvoiceWriterCacheForTests(): void {
|
|
78
|
-
__resetInvoiceStatusCacheForTests()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function generateTestInvoiceNumber(): string {
|
|
82
|
-
// Match the pattern operators search by: `TEST-` prefix + random
|
|
83
|
-
// suffix so the test wipe-out endpoint can filter via `LIKE 'TEST-%'`
|
|
84
|
-
// (defensive — primary filter is `metadata.test_run=true`).
|
|
85
|
-
const random = Math.floor(Math.random() * 1e9)
|
|
86
|
-
.toString(16)
|
|
87
|
-
.padStart(8, '0')
|
|
88
|
-
return `TEST-${Date.now().toString(16)}-${random}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 2dp half-up rounding that survives binary floating-point edge cases
|
|
93
|
-
* (e.g. `1.005 * 100 === 100.49999...` would otherwise round DOWN to
|
|
94
|
-
* `1.00` under naive `Math.round`). The `+ EPSILON` nudge pushes
|
|
95
|
-
* representations like 100.4999...8 up to the true half-up boundary.
|
|
96
|
-
* Matches the spec's "2 decimal places, half-up" rounding policy.
|
|
97
|
-
*/
|
|
74
|
+
/** 2dp half-up rounding (survives 1.005-style binary edges). */
|
|
98
75
|
function roundHalfUp2dp(value: number): number {
|
|
99
|
-
|
|
76
|
+
const sign = value < 0 ? -1 : 1
|
|
77
|
+
return (sign * Math.round((Math.abs(value) + Number.EPSILON) * 100)) / 100
|
|
100
78
|
}
|
|
101
79
|
|
|
102
80
|
function format4dp(value: number): string {
|
|
103
81
|
return roundHalfUp2dp(value).toFixed(4)
|
|
104
82
|
}
|
|
105
83
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
84
|
+
/** VAT rate of an item as a numeric string; items predating Spec B read as 0. */
|
|
85
|
+
export function itemTaxRate(item: Pick<BillingItem, 'taxRate'>): string {
|
|
86
|
+
const rate = Number.parseFloat(String(item.taxRate ?? '0'))
|
|
87
|
+
return Number.isFinite(rate) && rate >= 0 ? String(rate) : '0'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isoDate(date: Date): string {
|
|
91
|
+
return new Date(date).toISOString().slice(0, 10)
|
|
112
92
|
}
|
|
113
93
|
|
|
114
94
|
export async function writeDraftInvoice(
|
|
115
95
|
em: EntityManager,
|
|
116
|
-
container: AwilixContainer,
|
|
117
96
|
params: WriteInvoiceParams,
|
|
118
97
|
): Promise<WrittenInvoice> {
|
|
119
|
-
const
|
|
98
|
+
const amounts = params.lines.map((line) =>
|
|
99
|
+
lineVat({ amount: line.lineAmount, priceBasis: line.priceBasis, taxRate: line.taxRate }),
|
|
100
|
+
)
|
|
101
|
+
const totals = sumTotals(amounts)
|
|
120
102
|
|
|
121
103
|
if (params.mode === 'dry') {
|
|
122
104
|
// Compute totals; persist nothing.
|
|
123
|
-
return { invoiceId: null, invoiceNumber: null, totalNet }
|
|
105
|
+
return { invoiceId: null, invoiceNumber: null, totalNet: totals.subtotalNet, totalGross: totals.totalGross }
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
const { account, period, billRunId, lines, mode } = params
|
|
127
|
-
|
|
128
|
-
let invoiceNumber: string
|
|
129
|
-
if (mode === 'test') {
|
|
130
|
-
invoiceNumber = generateTestInvoiceNumber()
|
|
131
|
-
} else {
|
|
132
|
-
const numberGenerator = container.resolve(
|
|
133
|
-
'salesDocumentNumberGenerator',
|
|
134
|
-
) as SalesDocumentNumberGenerator
|
|
135
|
-
const generated = await numberGenerator.generate({
|
|
136
|
-
organizationId: account.organizationId,
|
|
137
|
-
tenantId: account.tenantId,
|
|
138
|
-
kind: 'invoice',
|
|
139
|
-
})
|
|
140
|
-
invoiceNumber = generated.number
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const statusEntryId = await resolveInvoiceStatusEntryId(
|
|
144
|
-
em,
|
|
145
|
-
account.tenantId,
|
|
146
|
-
account.organizationId,
|
|
147
|
-
'draft',
|
|
148
|
-
)
|
|
149
|
-
|
|
109
|
+
const invoiceNumber = mode === 'test' ? testNumber() : draftNumber()
|
|
150
110
|
const now = new Date()
|
|
151
|
-
|
|
111
|
+
|
|
112
|
+
const invoice = em.create(BillingInvoice, {
|
|
152
113
|
organizationId: account.organizationId,
|
|
153
114
|
tenantId: account.tenantId,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
115
|
+
billAccountId: account.id,
|
|
116
|
+
documentType: 'invoice',
|
|
117
|
+
number: invoiceNumber,
|
|
118
|
+
series: null,
|
|
119
|
+
status: 'draft',
|
|
120
|
+
testRun: mode === 'test',
|
|
121
|
+
billRunId,
|
|
122
|
+
billDate: params.billDate,
|
|
123
|
+
periodStart: period.periodStart,
|
|
124
|
+
periodEnd: period.periodEnd,
|
|
158
125
|
currencyCode: account.currencyCode,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
paidTotalAmount: '0',
|
|
166
|
-
outstandingAmount: totalNet,
|
|
167
|
-
metadata: {
|
|
168
|
-
bill_run_id: billRunId,
|
|
169
|
-
bill_account_id: account.id,
|
|
170
|
-
bill_period_start: period.periodStart.toISOString().slice(0, 10),
|
|
171
|
-
bill_period_end: period.periodEnd.toISOString().slice(0, 10),
|
|
172
|
-
test_run: mode === 'test' ? true : undefined,
|
|
173
|
-
},
|
|
126
|
+
subtotalNet: totals.subtotalNet,
|
|
127
|
+
taxTotal: totals.taxTotal,
|
|
128
|
+
totalGross: totals.totalGross,
|
|
129
|
+
paidTotal: '0',
|
|
130
|
+
creditedTotal: '0',
|
|
131
|
+
outstanding: totals.totalGross,
|
|
174
132
|
createdAt: now,
|
|
175
133
|
updatedAt: now,
|
|
176
134
|
})
|
|
177
135
|
em.persist(invoice)
|
|
136
|
+
await em.flush() // need the invoice id for the lines
|
|
178
137
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
em.persist(invoiceLine)
|
|
206
|
-
}
|
|
207
|
-
|
|
138
|
+
lines.forEach((line, index) => {
|
|
139
|
+
const amount = amounts[index]
|
|
140
|
+
em.persist(
|
|
141
|
+
em.create(BillingInvoiceLine, {
|
|
142
|
+
organizationId: account.organizationId,
|
|
143
|
+
tenantId: account.tenantId,
|
|
144
|
+
invoiceId: invoice.id,
|
|
145
|
+
lineNumber: index + 1,
|
|
146
|
+
billingItemId: line.billingItemId,
|
|
147
|
+
billingType: line.billingType,
|
|
148
|
+
description: line.description,
|
|
149
|
+
quantity: line.quantity,
|
|
150
|
+
unitPriceNet:
|
|
151
|
+
line.priceBasis === 'gross' ? unitPriceNetOf(line.unitPrice, 'gross', line.taxRate) : line.unitPrice,
|
|
152
|
+
taxRate: line.taxRate,
|
|
153
|
+
netAmount: format4dp(amount.net),
|
|
154
|
+
taxAmount: format4dp(amount.tax),
|
|
155
|
+
grossAmount: format4dp(amount.gross),
|
|
156
|
+
servicePeriodStart: line.servicePeriodStart ?? null,
|
|
157
|
+
servicePeriodEnd: line.servicePeriodEnd ?? null,
|
|
158
|
+
metadata: line.metadataExtra ?? null,
|
|
159
|
+
createdAt: now,
|
|
160
|
+
updatedAt: now,
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
})
|
|
208
164
|
await em.flush()
|
|
209
165
|
|
|
210
|
-
return { invoiceId: invoice.id, invoiceNumber, totalNet }
|
|
166
|
+
return { invoiceId: invoice.id, invoiceNumber, totalNet: totals.subtotalNet, totalGross: totals.totalGross }
|
|
211
167
|
}
|
|
212
168
|
|
|
213
169
|
/**
|
|
@@ -238,9 +194,11 @@ export function buildInvoiceLinesFromItems(
|
|
|
238
194
|
billingItemId: item.id,
|
|
239
195
|
billingType: 'one_time',
|
|
240
196
|
description: item.description,
|
|
241
|
-
|
|
197
|
+
unitPrice: amount,
|
|
242
198
|
quantity: '1',
|
|
243
|
-
|
|
199
|
+
lineAmount: amount,
|
|
200
|
+
priceBasis: priceBasisOf(item.priceBasis),
|
|
201
|
+
taxRate: itemTaxRate(item),
|
|
244
202
|
})
|
|
245
203
|
} else if (item.type === 'recurring') {
|
|
246
204
|
const rate = item.rateJson as { unit_price?: number } | null
|
|
@@ -250,11 +208,67 @@ export function buildInvoiceLinesFromItems(
|
|
|
250
208
|
billingItemId: item.id,
|
|
251
209
|
billingType: 'recurring',
|
|
252
210
|
description: item.description,
|
|
253
|
-
|
|
211
|
+
unitPrice,
|
|
254
212
|
quantity: '1',
|
|
255
|
-
|
|
213
|
+
lineAmount: unitPrice,
|
|
214
|
+
priceBasis: priceBasisOf(item.priceBasis),
|
|
215
|
+
taxRate: itemTaxRate(item),
|
|
256
216
|
})
|
|
257
217
|
}
|
|
258
218
|
}
|
|
259
219
|
return lines
|
|
260
220
|
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Display period of an invoice: min start / max end over the lines' service
|
|
224
|
+
* periods, falling back to `fallback` (the arrears window) when no line
|
|
225
|
+
* carries one (e.g. a one_time-only invoice).
|
|
226
|
+
*/
|
|
227
|
+
export function displayPeriod(lines: readonly InvoiceLineDescriptor[], fallback: BillPeriod): BillPeriod {
|
|
228
|
+
let start: Date | null = null
|
|
229
|
+
let end: Date | null = null
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
if (line.servicePeriodStart && (!start || line.servicePeriodStart < start)) start = line.servicePeriodStart
|
|
232
|
+
if (line.servicePeriodEnd && (!end || line.servicePeriodEnd > end)) end = line.servicePeriodEnd
|
|
233
|
+
}
|
|
234
|
+
return { periodStart: start ?? fallback.periodStart, periodEnd: end ?? fallback.periodEnd }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Spec A: invoice lines for computed recurring service periods (charges and
|
|
239
|
+
* credits). Full-period charges keep the bare item description; partial
|
|
240
|
+
* periods and credits append the covered dates so the line is
|
|
241
|
+
* self-explanatory on any document (language-neutral — the PDF renders
|
|
242
|
+
* localized labels later).
|
|
243
|
+
*/
|
|
244
|
+
export function buildRecurringLineDescriptors(
|
|
245
|
+
item: Pick<BillingItem, 'id' | 'description' | 'chargeTiming' | 'taxRate' | 'priceBasis'>,
|
|
246
|
+
lines: readonly RecurringLine[],
|
|
247
|
+
): InvoiceLineDescriptor[] {
|
|
248
|
+
return lines.map((line) => {
|
|
249
|
+
const amount = format4dp(line.amount)
|
|
250
|
+
const dated = line.proration !== null || line.kind === 'credit'
|
|
251
|
+
return {
|
|
252
|
+
billingItemId: item.id,
|
|
253
|
+
billingType: 'recurring' as const,
|
|
254
|
+
description: dated
|
|
255
|
+
? `${item.description} · ${isoDate(line.servicePeriodStart)} – ${isoDate(line.servicePeriodEnd)}`
|
|
256
|
+
: item.description,
|
|
257
|
+
unitPrice: amount,
|
|
258
|
+
quantity: '1',
|
|
259
|
+
lineAmount: amount,
|
|
260
|
+
// Proration and credit lines inherit the item's rate (Spec B) and basis (Spec F).
|
|
261
|
+
priceBasis: priceBasisOf(item.priceBasis),
|
|
262
|
+
taxRate: itemTaxRate(item),
|
|
263
|
+
servicePeriodStart: line.servicePeriodStart,
|
|
264
|
+
servicePeriodEnd: line.servicePeriodEnd,
|
|
265
|
+
metadataExtra: {
|
|
266
|
+
charge_timing: item.chargeTiming,
|
|
267
|
+
...(line.proration
|
|
268
|
+
? { proration: { active_days: line.proration.activeDays, period_days: line.proration.periodDays } }
|
|
269
|
+
: {}),
|
|
270
|
+
...(line.kind === 'credit' ? { credit_for: item.id } : {}),
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
}
|