@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { LockMode, UniqueConstraintViolationException } from '@mikro-orm/core'
|
|
2
3
|
import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'
|
|
3
4
|
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
4
5
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
@@ -7,40 +8,61 @@ import {
|
|
|
7
8
|
ensureTenantScope,
|
|
8
9
|
} from '@open-mercato/shared/lib/commands/scope'
|
|
9
10
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
-
import {
|
|
11
|
-
|
|
11
|
+
import {
|
|
12
|
+
BillingAccount,
|
|
13
|
+
BillingDraftInvoiceEdit,
|
|
14
|
+
BillingInvoice,
|
|
15
|
+
BillingInvoiceLine,
|
|
16
|
+
} from '../data/entities'
|
|
12
17
|
import { emitBillingEvent, type BillingEventId } from '../events'
|
|
13
18
|
import { buildPostpaidCreditCrossingEvents } from '../lib/accountStatus'
|
|
19
|
+
import { buildSellerSnapshot, getBillingSettings, missingSellerFields, numberMaskFor, taxRateColumn } from '../lib/billingSettings'
|
|
20
|
+
import { claimDocumentNumber } from '../lib/numbering'
|
|
21
|
+
import { buildBuyerSnapshot } from '../lib/documentParties'
|
|
22
|
+
import { freezeInvoicePdf } from '../lib/pdf/invoicePdf'
|
|
23
|
+
import { accountLockKey } from '../lib/accountLock'
|
|
24
|
+
import { allocate, applyAccountCredit, emptyEffects, mergeEffects, toCents } from '../lib/allocation'
|
|
25
|
+
import { emitAllocationEffects } from '../lib/allocationEvents'
|
|
26
|
+
import { seriesFor } from '../lib/documentTypes'
|
|
27
|
+
import { creditFits, loadCorrectionTotals } from '../lib/memoGuards'
|
|
28
|
+
import { sendInvoice, type SendInvoiceResult } from '../lib/invoiceDelivery'
|
|
29
|
+
import { computeDueDate, resolvePaymentTermsDays } from '../lib/dueDate'
|
|
30
|
+
import { lineAmountsFromNet, sumTotals } from '../lib/vat'
|
|
31
|
+
import { deleteTestRunArtifacts } from '../lib/testRunCleanup'
|
|
14
32
|
import {
|
|
15
33
|
billingInvoiceAddLineSchema,
|
|
16
34
|
billingInvoiceEditLineSchema,
|
|
17
35
|
billingInvoicePostSchema,
|
|
18
36
|
billingInvoiceRemoveLineSchema,
|
|
37
|
+
billingInvoiceSendSchema,
|
|
19
38
|
billingWipeTestInvoicesSchema,
|
|
20
39
|
type BillingInvoiceAddLineInput,
|
|
21
40
|
type BillingInvoiceEditLineInput,
|
|
22
41
|
type BillingInvoicePostInput,
|
|
23
42
|
type BillingInvoiceRemoveLineInput,
|
|
43
|
+
type BillingInvoiceSendInput,
|
|
24
44
|
type BillingWipeTestInvoicesInput,
|
|
25
45
|
} from '../data/validators'
|
|
26
|
-
import { resolveInvoiceStatusEntryId } from '../lib/invoiceStatus'
|
|
27
46
|
|
|
28
47
|
/**
|
|
29
|
-
*
|
|
30
|
-
* created.
|
|
48
|
+
* Billing invoice commands (Spec 0 — billing-owned `billing_invoices`).
|
|
31
49
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
50
|
+
* Post:
|
|
51
|
+
* 1. Refuses non-drafts and test-run drafts.
|
|
52
|
+
* 2. Refuses when the seller settings are incomplete (Spec B, P-D4 — a
|
|
53
|
+
* posted invoice must be printable); no number is consumed.
|
|
54
|
+
* 3. In ONE transaction: claims the next number of the `invoice` series
|
|
55
|
+
* (gap-free — a rollback releases it), freezes the buyer / seller
|
|
56
|
+
* snapshots, sets `issue_date`, `due_date` (issue + payment terms),
|
|
57
|
+
* `posted_at`, `outstanding = total_gross`, `status = posted`, and keeps
|
|
58
|
+
* the replaced `DRAFT-…` number in `metadata.draft_number`.
|
|
59
|
+
* 4. After commit: freezes the PDF as an attachment (best effort), then
|
|
60
|
+
* fires `billing.invoice.posted` + one
|
|
61
|
+
* `billing.invoice.line_posted` per line, then the postpaid credit
|
|
62
|
+
* crossing events.
|
|
41
63
|
*
|
|
42
|
-
* Once posted
|
|
43
|
-
*
|
|
64
|
+
* Once posted a document is immutable; corrections are credit / debit memos
|
|
65
|
+
* (Spec D). Draft lines can be edited / added / removed with an audit row.
|
|
44
66
|
*/
|
|
45
67
|
|
|
46
68
|
function getEm(ctx: CommandRuntimeContext): EntityManager {
|
|
@@ -54,6 +76,18 @@ type PostResult = {
|
|
|
54
76
|
lineCount: number
|
|
55
77
|
}
|
|
56
78
|
|
|
79
|
+
function isoDate(date: Date | null | undefined): string | null {
|
|
80
|
+
return date ? new Date(date).toISOString().slice(0, 10) : null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function todayUtc(): Date {
|
|
84
|
+
const now = new Date()
|
|
85
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Re-exported for existing callers; lives in lib/documentParties.ts (shared with the PDF).
|
|
89
|
+
export { buildBuyerSnapshot }
|
|
90
|
+
|
|
57
91
|
const postInvoiceCommand: CommandHandler<BillingInvoicePostInput, PostResult> = {
|
|
58
92
|
id: 'billing.invoices.post',
|
|
59
93
|
|
|
@@ -61,14 +95,14 @@ const postInvoiceCommand: CommandHandler<BillingInvoicePostInput, PostResult> =
|
|
|
61
95
|
const parsed = billingInvoicePostSchema.parse(rawInput)
|
|
62
96
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
63
97
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
98
|
+
const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
64
99
|
|
|
65
100
|
const em = getEm(ctx)
|
|
66
|
-
const invoice = await em.findOne(
|
|
101
|
+
const invoice = await em.findOne(BillingInvoice, {
|
|
67
102
|
id: parsed.invoiceId,
|
|
68
|
-
|
|
69
|
-
organizationId: parsed.organizationId,
|
|
103
|
+
...scope,
|
|
70
104
|
deletedAt: null,
|
|
71
|
-
})
|
|
105
|
+
} as never)
|
|
72
106
|
if (!invoice) {
|
|
73
107
|
throw new CrudHttpError(404, { error: 'Invoice not found' })
|
|
74
108
|
}
|
|
@@ -79,119 +113,244 @@ const postInvoiceCommand: CommandHandler<BillingInvoicePostInput, PostResult> =
|
|
|
79
113
|
currentStatus: invoice.status,
|
|
80
114
|
})
|
|
81
115
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
// leak test data into accounting. Force-delete the test invoice
|
|
86
|
-
// instead (Phase 4b admin UI exposes a "Wipe test drafts"
|
|
87
|
-
// button against `DELETE /api/billing/test-invoices`).
|
|
116
|
+
if (invoice.testRun) {
|
|
117
|
+
// Test invoices live for inspection only — posting them would leak test
|
|
118
|
+
// data into accounting. Wipe them instead (POST /api/billing/test-invoices/wipe).
|
|
88
119
|
throw new CrudHttpError(409, {
|
|
89
120
|
error: 'Test-mode invoices cannot be posted',
|
|
90
121
|
code: 'billing.invoice.test_run',
|
|
91
122
|
})
|
|
92
123
|
}
|
|
93
124
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
const account = await em.findOne(BillingAccount, {
|
|
126
|
+
id: invoice.billAccountId,
|
|
127
|
+
...scope,
|
|
128
|
+
} as never)
|
|
129
|
+
if (!account) {
|
|
130
|
+
throw new CrudHttpError(409, { error: 'Billing account not found', code: 'billing.invoice.account_missing' })
|
|
131
|
+
}
|
|
132
|
+
const settings = await getBillingSettings(em, scope)
|
|
133
|
+
const missing = missingSellerFields(settings)
|
|
134
|
+
if (missing.length > 0) {
|
|
135
|
+
throw new CrudHttpError(409, {
|
|
136
|
+
error: 'Fill in the seller details in billing settings before posting invoices',
|
|
137
|
+
code: 'billing.settings.incomplete',
|
|
138
|
+
missing,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
const isCreditMemo = invoice.documentType === 'credit_memo'
|
|
142
|
+
const series = seriesFor(invoice.documentType)
|
|
143
|
+
const issueDate = todayUtc()
|
|
144
|
+
// Receivables (invoices, debit memos) are due after the terms; a credit
|
|
145
|
+
// memo is a credit source with no due date (Spec D).
|
|
146
|
+
const dueDate = isCreditMemo
|
|
147
|
+
? null
|
|
148
|
+
: computeDueDate(issueDate, resolvePaymentTermsDays(account.paymentTermsDays, settings.defaultPaymentTermsDays))
|
|
149
|
+
const settlement = emptyEffects()
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await em.transactional(async (tem) => {
|
|
153
|
+
// Re-read inside the transaction and lock the row, so two concurrent
|
|
154
|
+
// posts of the same draft can't both claim a number.
|
|
155
|
+
const target = await (tem as EntityManager).findOne(
|
|
156
|
+
BillingInvoice,
|
|
157
|
+
{ id: invoice.id, ...scope, deletedAt: null } as never,
|
|
158
|
+
{ lockMode: LockMode.PESSIMISTIC_WRITE },
|
|
159
|
+
)
|
|
160
|
+
if (!target || target.status !== 'draft') {
|
|
161
|
+
throw new CrudHttpError(409, {
|
|
162
|
+
error: 'Only draft invoices can be posted',
|
|
163
|
+
code: 'billing.invoice.not_draft',
|
|
164
|
+
currentStatus: target?.status ?? null,
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
// Spec C/D: every receivables change of an account is serialised by
|
|
168
|
+
// the per-account lock (same key as `withAccountLock`).
|
|
169
|
+
await (tem as EntityManager).execute('SELECT pg_advisory_xact_lock(hashtextextended(?, 0))', [
|
|
170
|
+
accountLockKey({ tenantId: scope.tenantId, billAccountId: target.billAccountId }),
|
|
171
|
+
])
|
|
172
|
+
let original: BillingInvoice | null = null
|
|
173
|
+
if (isCreditMemo) {
|
|
174
|
+
original = target.correctsInvoiceId
|
|
175
|
+
? await (tem as EntityManager).findOne(BillingInvoice, { id: target.correctsInvoiceId, ...scope, deletedAt: null } as never)
|
|
176
|
+
: null
|
|
177
|
+
if (!original) {
|
|
178
|
+
throw new CrudHttpError(409, { error: 'The corrected invoice no longer exists', code: 'billing.memo.invalid_original' })
|
|
179
|
+
}
|
|
180
|
+
// Cumulative guard (P-D4), authoritative here under the lock.
|
|
181
|
+
const corrections = await loadCorrectionTotals(tem as EntityManager, scope, original.id, { excludeId: target.id })
|
|
182
|
+
if (!creditFits(toCents(original.totalGross), corrections, toCents(target.totalGross))) {
|
|
183
|
+
throw new CrudHttpError(409, {
|
|
184
|
+
error: 'The credit exceeds what is left to credit on this invoice',
|
|
185
|
+
code: 'billing.memo.exceeds_invoice',
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const claimed = await claimDocumentNumber(
|
|
190
|
+
tem as EntityManager,
|
|
191
|
+
scope,
|
|
192
|
+
series,
|
|
193
|
+
numberMaskFor(settings, series),
|
|
194
|
+
issueDate,
|
|
195
|
+
)
|
|
196
|
+
const now = new Date()
|
|
197
|
+
target.metadata = { ...(target.metadata ?? {}), draft_number: target.number }
|
|
198
|
+
target.number = claimed.number
|
|
199
|
+
target.series = series
|
|
200
|
+
target.issueDate = issueDate
|
|
201
|
+
target.dueDate = dueDate
|
|
202
|
+
target.postedAt = now
|
|
203
|
+
target.buyerSnapshot = buildBuyerSnapshot(account)
|
|
204
|
+
target.sellerSnapshot = buildSellerSnapshot(settings)
|
|
205
|
+
target.status = 'posted'
|
|
206
|
+
target.paidTotal = '0'
|
|
207
|
+
target.outstanding = target.totalGross
|
|
208
|
+
target.updatedAt = now
|
|
209
|
+
await tem.flush()
|
|
210
|
+
if (isCreditMemo && original) {
|
|
211
|
+
// Settle the corrected invoice first (P-D5), the remainder becomes
|
|
212
|
+
// account credit spent on other open receivables (D12).
|
|
213
|
+
const source = { type: 'credit_memo' as const, id: target.id }
|
|
214
|
+
const take = Math.min(toCents(target.totalGross), toCents(original.outstanding))
|
|
215
|
+
if (take > 0 && ['posted', 'partially_paid'].includes(original.status)) {
|
|
216
|
+
mergeEffects(
|
|
217
|
+
settlement,
|
|
218
|
+
await allocate(tem as EntityManager, scope, { source, invoiceId: original.id, amountCents: take, by: 'credit' }),
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
mergeEffects(
|
|
222
|
+
settlement,
|
|
223
|
+
await applyAccountCredit(tem as EntityManager, scope, { id: target.billAccountId, currencyCode: target.currencyCode }, {
|
|
224
|
+
onlySources: [source],
|
|
225
|
+
}),
|
|
226
|
+
)
|
|
227
|
+
const fresh = (await (tem as EntityManager).execute(
|
|
228
|
+
`SELECT status, outstanding FROM billing_invoices WHERE id = ?`,
|
|
229
|
+
[target.id],
|
|
230
|
+
)) as unknown as Array<{ status: string; outstanding: string }>
|
|
231
|
+
if (fresh?.[0]) {
|
|
232
|
+
target.status = fresh[0].status
|
|
233
|
+
target.outstanding = fresh[0].outstanding
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
Object.assign(invoice, {
|
|
237
|
+
number: target.number,
|
|
238
|
+
series: target.series,
|
|
239
|
+
issueDate: target.issueDate,
|
|
240
|
+
dueDate: target.dueDate,
|
|
241
|
+
metadata: target.metadata,
|
|
242
|
+
buyerSnapshot: target.buyerSnapshot,
|
|
243
|
+
sellerSnapshot: target.sellerSnapshot,
|
|
244
|
+
postedAt: target.postedAt,
|
|
245
|
+
status: target.status,
|
|
246
|
+
outstanding: target.outstanding,
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (err instanceof UniqueConstraintViolationException) {
|
|
251
|
+
// The mask produced a number already issued (e.g. after a mask change
|
|
252
|
+
// within a period). The transaction rolled back; nothing was posted.
|
|
253
|
+
throw new CrudHttpError(409, {
|
|
254
|
+
error: 'The invoice number mask produced a number that is already in use',
|
|
255
|
+
code: 'billing.invoice.number_conflict',
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
throw err
|
|
259
|
+
}
|
|
108
260
|
|
|
109
|
-
const lines = await em.find(
|
|
110
|
-
invoice,
|
|
111
|
-
|
|
112
|
-
|
|
261
|
+
const lines = await em.find(BillingInvoiceLine, {
|
|
262
|
+
invoiceId: invoice.id,
|
|
263
|
+
...scope,
|
|
264
|
+
deletedAt: null,
|
|
113
265
|
} as never)
|
|
114
266
|
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
267
|
+
// Spec B: freeze the PDF after the commit, before announcing the post (the
|
|
268
|
+
// auto-send subscriber attaches it). A render / storage failure never rolls
|
|
269
|
+
// the post back — the first download renders and freezes it instead.
|
|
270
|
+
try {
|
|
271
|
+
await freezeInvoicePdf(em, invoice)
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('[billing.invoices.post] PDF could not be frozen; it will be rendered on first download', invoice.id, err)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Side effects fire AFTER the DB commit so downstream subscribers never
|
|
277
|
+
// observe a "posted" event for an invoice the DB doesn't reflect.
|
|
278
|
+
// `billing.invoice.posted` is for invoices only; memos have their own
|
|
279
|
+
// events with the same payload (Spec D, P-D9).
|
|
280
|
+
const postedEvent: BillingEventId =
|
|
281
|
+
invoice.documentType === 'credit_memo'
|
|
282
|
+
? 'billing.credit_memo.posted'
|
|
283
|
+
: invoice.documentType === 'debit_memo'
|
|
284
|
+
? 'billing.debit_memo.posted'
|
|
285
|
+
: 'billing.invoice.posted'
|
|
286
|
+
await emitAllocationEffects(scope, settlement)
|
|
287
|
+
await emitBillingEvent(postedEvent, {
|
|
288
|
+
correctsInvoiceId: invoice.correctsInvoiceId ?? null,
|
|
119
289
|
invoiceId: invoice.id,
|
|
120
|
-
invoiceNumber: invoice.
|
|
290
|
+
invoiceNumber: invoice.number,
|
|
291
|
+
documentType: invoice.documentType,
|
|
121
292
|
tenantId: invoice.tenantId,
|
|
122
293
|
organizationId: invoice.organizationId,
|
|
123
|
-
grandTotalNet: invoice.
|
|
124
|
-
grandTotalGross: invoice.
|
|
294
|
+
grandTotalNet: invoice.subtotalNet,
|
|
295
|
+
grandTotalGross: invoice.totalGross,
|
|
125
296
|
currencyCode: invoice.currencyCode,
|
|
126
|
-
billRunId:
|
|
127
|
-
billAccountId:
|
|
128
|
-
|
|
129
|
-
|
|
297
|
+
billRunId: invoice.billRunId ?? null,
|
|
298
|
+
billAccountId: invoice.billAccountId,
|
|
299
|
+
billDate: isoDate(invoice.billDate),
|
|
300
|
+
billPeriodStart: isoDate(invoice.periodStart),
|
|
301
|
+
billPeriodEnd: isoDate(invoice.periodEnd),
|
|
302
|
+
issueDate: isoDate(invoice.issueDate),
|
|
303
|
+
dueDate: isoDate(invoice.dueDate),
|
|
130
304
|
lineCount: lines.length,
|
|
131
305
|
})
|
|
132
306
|
for (const line of lines) {
|
|
133
|
-
const lineMeta = (line.metadata as Record<string, unknown> | null) ?? {}
|
|
134
307
|
await emitBillingEvent('billing.invoice.line_posted', {
|
|
135
308
|
invoiceId: invoice.id,
|
|
136
309
|
invoiceLineId: line.id,
|
|
137
|
-
billingItemId:
|
|
138
|
-
billingType:
|
|
139
|
-
description: line.description
|
|
310
|
+
billingItemId: line.billingItemId ?? null,
|
|
311
|
+
billingType: line.billingType,
|
|
312
|
+
description: line.description,
|
|
140
313
|
unitPriceNet: line.unitPriceNet,
|
|
141
314
|
quantity: line.quantity,
|
|
142
|
-
totalNetAmount: line.
|
|
315
|
+
totalNetAmount: line.netAmount,
|
|
143
316
|
tenantId: invoice.tenantId,
|
|
144
317
|
organizationId: invoice.organizationId,
|
|
145
318
|
})
|
|
146
319
|
}
|
|
147
320
|
|
|
148
321
|
// SPEC-002 P6: a newly-posted invoice raises the account's outstanding
|
|
149
|
-
// exposure — emit billing.credit.over_limit on the upward crossing.
|
|
150
|
-
// never blocks; the consuming flow decides what to do.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
id:
|
|
155
|
-
tenantId: parsed.tenantId,
|
|
156
|
-
organizationId: parsed.organizationId,
|
|
157
|
-
deletedAt: null,
|
|
158
|
-
})
|
|
159
|
-
if (account) {
|
|
160
|
-
const creditEvents = await buildPostpaidCreditCrossingEvents(
|
|
161
|
-
em,
|
|
162
|
-
account,
|
|
163
|
-
invoice.outstandingAmount,
|
|
164
|
-
)
|
|
165
|
-
for (const ev of creditEvents) {
|
|
166
|
-
await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })
|
|
167
|
-
}
|
|
322
|
+
// exposure — emit billing.credit.over_limit on the upward crossing.
|
|
323
|
+
// Billing never blocks; the consuming flow decides what to do.
|
|
324
|
+
if (!isCreditMemo) {
|
|
325
|
+
const creditEvents = await buildPostpaidCreditCrossingEvents(em, account, invoice.outstanding)
|
|
326
|
+
for (const ev of creditEvents) {
|
|
327
|
+
await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })
|
|
168
328
|
}
|
|
169
329
|
}
|
|
170
330
|
|
|
171
331
|
return {
|
|
172
332
|
invoiceId: invoice.id,
|
|
173
|
-
invoiceNumber: invoice.
|
|
333
|
+
invoiceNumber: invoice.number,
|
|
174
334
|
status: 'posted',
|
|
175
335
|
lineCount: lines.length,
|
|
176
336
|
}
|
|
177
337
|
},
|
|
178
338
|
}
|
|
179
339
|
|
|
180
|
-
// ─── Draft edit / add / remove
|
|
340
|
+
// ─── Draft edit / add / remove ───────────────────────────────────
|
|
181
341
|
//
|
|
182
342
|
// Three commands sharing the same shape:
|
|
183
343
|
// 1. Assert the invoice is still a draft (refuse otherwise).
|
|
184
344
|
// 2. Mutate the line / collection.
|
|
185
345
|
// 3. Write a `BillingDraftInvoiceEdit` audit row carrying before+after
|
|
186
|
-
// snapshots (`null` on the "no prior state" / "no resulting state"
|
|
187
|
-
// side per the spec — see entity column docs).
|
|
346
|
+
// snapshots (`null` on the "no prior state" / "no resulting state" side).
|
|
188
347
|
// 4. Recompute the invoice totals from the surviving lines.
|
|
189
348
|
//
|
|
190
|
-
// The audit row is append-only; once written, never updated.
|
|
191
|
-
// deleting the invoice does NOT delete the audit rows (compliance).
|
|
349
|
+
// The audit row is append-only; once written, never updated.
|
|
192
350
|
|
|
193
351
|
function roundHalfUp2dp(value: number): number {
|
|
194
|
-
|
|
352
|
+
const sign = value < 0 ? -1 : 1
|
|
353
|
+
return (sign * Math.round((Math.abs(value) + Number.EPSILON) * 100)) / 100
|
|
195
354
|
}
|
|
196
355
|
|
|
197
356
|
function format4dp(value: number): string {
|
|
@@ -201,13 +360,13 @@ function format4dp(value: number): string {
|
|
|
201
360
|
async function assertDraftInvoice(
|
|
202
361
|
em: EntityManager,
|
|
203
362
|
params: { invoiceId: string; tenantId: string; organizationId: string },
|
|
204
|
-
): Promise<
|
|
205
|
-
const invoice = await em.findOne(
|
|
363
|
+
): Promise<BillingInvoice> {
|
|
364
|
+
const invoice = await em.findOne(BillingInvoice, {
|
|
206
365
|
id: params.invoiceId,
|
|
207
366
|
tenantId: params.tenantId,
|
|
208
367
|
organizationId: params.organizationId,
|
|
209
368
|
deletedAt: null,
|
|
210
|
-
})
|
|
369
|
+
} as never)
|
|
211
370
|
if (!invoice) {
|
|
212
371
|
throw new CrudHttpError(404, { error: 'Invoice not found' })
|
|
213
372
|
}
|
|
@@ -223,54 +382,63 @@ async function assertDraftInvoice(
|
|
|
223
382
|
|
|
224
383
|
type LineSnapshot = {
|
|
225
384
|
id: string
|
|
226
|
-
description: string
|
|
385
|
+
description: string
|
|
227
386
|
quantity: string
|
|
228
387
|
unitPriceNet: string
|
|
229
|
-
|
|
230
|
-
|
|
388
|
+
taxRate: string
|
|
389
|
+
netAmount: string
|
|
390
|
+
taxAmount: string
|
|
391
|
+
grossAmount: string
|
|
231
392
|
metadata: Record<string, unknown> | null
|
|
232
393
|
}
|
|
233
394
|
|
|
234
|
-
function snapshotLine(line:
|
|
395
|
+
function snapshotLine(line: BillingInvoiceLine): LineSnapshot {
|
|
235
396
|
return {
|
|
236
397
|
id: line.id,
|
|
237
|
-
description: line.description
|
|
398
|
+
description: line.description,
|
|
238
399
|
quantity: line.quantity,
|
|
239
400
|
unitPriceNet: line.unitPriceNet,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
401
|
+
taxRate: line.taxRate,
|
|
402
|
+
netAmount: line.netAmount,
|
|
403
|
+
taxAmount: line.taxAmount,
|
|
404
|
+
grossAmount: line.grossAmount,
|
|
405
|
+
metadata: line.metadata ?? null,
|
|
243
406
|
}
|
|
244
407
|
}
|
|
245
408
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
409
|
+
/** Sets net / tax / gross on a line from its net amount and tax rate. */
|
|
410
|
+
function applyLineAmounts(line: BillingInvoiceLine, net: number): void {
|
|
411
|
+
const amounts = lineAmountsFromNet(net, Number.parseFloat(line.taxRate) || 0)
|
|
412
|
+
line.netAmount = format4dp(amounts.net)
|
|
413
|
+
line.taxAmount = format4dp(amounts.tax)
|
|
414
|
+
line.grossAmount = format4dp(amounts.gross)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function recomputeInvoiceTotals(em: EntityManager, invoice: BillingInvoice): Promise<void> {
|
|
418
|
+
const lines = await em.find(BillingInvoiceLine, {
|
|
419
|
+
invoiceId: invoice.id,
|
|
252
420
|
tenantId: invoice.tenantId,
|
|
253
421
|
organizationId: invoice.organizationId,
|
|
422
|
+
deletedAt: null,
|
|
254
423
|
} as never)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
invoice.
|
|
263
|
-
invoice.
|
|
264
|
-
invoice.
|
|
424
|
+
const totals = sumTotals(
|
|
425
|
+
lines.map((line) => ({
|
|
426
|
+
net: Number.parseFloat(line.netAmount),
|
|
427
|
+
tax: Number.parseFloat(line.taxAmount),
|
|
428
|
+
gross: Number.parseFloat(line.grossAmount),
|
|
429
|
+
})),
|
|
430
|
+
)
|
|
431
|
+
invoice.subtotalNet = totals.subtotalNet
|
|
432
|
+
invoice.taxTotal = totals.taxTotal
|
|
433
|
+
invoice.totalGross = totals.totalGross
|
|
434
|
+
invoice.outstanding = totals.totalGross
|
|
265
435
|
invoice.updatedAt = new Date()
|
|
266
436
|
}
|
|
267
437
|
|
|
268
438
|
function getUserIdFromCtx(ctx: CommandRuntimeContext): string {
|
|
269
439
|
const userId = (ctx.auth?.sub as string | undefined) ?? null
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
// a sentinel UUID. Operator-driven edits always have a real
|
|
273
|
-
// ctx.auth.sub.
|
|
440
|
+
// `BillingDraftInvoiceEdit.user_id` is NOT NULL. System-driven edits fall
|
|
441
|
+
// back to a sentinel UUID; operator edits always have ctx.auth.sub.
|
|
274
442
|
return userId ?? '00000000-0000-0000-0000-000000000000'
|
|
275
443
|
}
|
|
276
444
|
|
|
@@ -286,18 +454,15 @@ const editDraftLineCommand: CommandHandler<
|
|
|
286
454
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
287
455
|
|
|
288
456
|
const em = getEm(ctx)
|
|
289
|
-
const invoice = await assertDraftInvoice(em,
|
|
290
|
-
invoiceId: parsed.invoiceId,
|
|
291
|
-
tenantId: parsed.tenantId,
|
|
292
|
-
organizationId: parsed.organizationId,
|
|
293
|
-
})
|
|
457
|
+
const invoice = await assertDraftInvoice(em, parsed)
|
|
294
458
|
|
|
295
|
-
const line = await em.findOne(
|
|
459
|
+
const line = await em.findOne(BillingInvoiceLine, {
|
|
296
460
|
id: parsed.invoiceLineId,
|
|
297
|
-
invoice,
|
|
461
|
+
invoiceId: invoice.id,
|
|
298
462
|
tenantId: parsed.tenantId,
|
|
299
463
|
organizationId: parsed.organizationId,
|
|
300
|
-
|
|
464
|
+
deletedAt: null,
|
|
465
|
+
} as never)
|
|
301
466
|
if (!line) {
|
|
302
467
|
throw new CrudHttpError(404, { error: 'Invoice line not found' })
|
|
303
468
|
}
|
|
@@ -307,38 +472,22 @@ const editDraftLineCommand: CommandHandler<
|
|
|
307
472
|
|
|
308
473
|
await withAtomicFlush(em, [
|
|
309
474
|
() => {
|
|
310
|
-
if (parsed.changes.description !== undefined)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (parsed.changes.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Recompute the line total. If the operator supplied an explicit
|
|
323
|
-
// `totalNetAmount` override, honour it; otherwise compute from
|
|
324
|
-
// unit_price × quantity.
|
|
325
|
-
if (parsed.changes.totalNetAmount !== undefined) {
|
|
326
|
-
const total = format4dp(parsed.changes.totalNetAmount)
|
|
327
|
-
line.totalNetAmount = total
|
|
328
|
-
line.totalGrossAmount = total
|
|
329
|
-
} else {
|
|
330
|
-
const total = format4dp(
|
|
331
|
-
Number.parseFloat(line.unitPriceNet) * Number.parseFloat(line.quantity),
|
|
332
|
-
)
|
|
333
|
-
line.totalNetAmount = total
|
|
334
|
-
line.totalGrossAmount = total
|
|
335
|
-
}
|
|
475
|
+
if (parsed.changes.description !== undefined) line.description = parsed.changes.description
|
|
476
|
+
if (parsed.changes.quantity !== undefined) line.quantity = format4dp(parsed.changes.quantity)
|
|
477
|
+
if (parsed.changes.unitPriceNet !== undefined) line.unitPriceNet = format4dp(parsed.changes.unitPriceNet)
|
|
478
|
+
if (parsed.changes.taxRate !== undefined) line.taxRate = taxRateColumn(parsed.changes.taxRate)
|
|
479
|
+
// An explicit total override wins; otherwise unit price × quantity.
|
|
480
|
+
const net =
|
|
481
|
+
parsed.changes.totalNetAmount !== undefined
|
|
482
|
+
? parsed.changes.totalNetAmount
|
|
483
|
+
: Number.parseFloat(line.unitPriceNet) * Number.parseFloat(line.quantity)
|
|
484
|
+
applyLineAmounts(line, net)
|
|
485
|
+
line.updatedAt = new Date()
|
|
336
486
|
},
|
|
337
487
|
async () => {
|
|
338
488
|
await recomputeInvoiceTotals(em, invoice)
|
|
339
489
|
},
|
|
340
490
|
() => {
|
|
341
|
-
const after = snapshotLine(line)
|
|
342
491
|
audit = em.create(BillingDraftInvoiceEdit, {
|
|
343
492
|
tenantId: parsed.tenantId,
|
|
344
493
|
organizationId: parsed.organizationId,
|
|
@@ -347,7 +496,7 @@ const editDraftLineCommand: CommandHandler<
|
|
|
347
496
|
userId: getUserIdFromCtx(ctx),
|
|
348
497
|
action: 'line_edited',
|
|
349
498
|
beforeJson: before as Record<string, unknown>,
|
|
350
|
-
afterJson:
|
|
499
|
+
afterJson: snapshotLine(line) as Record<string, unknown>,
|
|
351
500
|
createdAt: new Date(),
|
|
352
501
|
})
|
|
353
502
|
em.persist(audit)
|
|
@@ -370,44 +519,40 @@ const addDraftLineCommand: CommandHandler<
|
|
|
370
519
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
371
520
|
|
|
372
521
|
const em = getEm(ctx)
|
|
373
|
-
const invoice = await assertDraftInvoice(em,
|
|
374
|
-
invoiceId: parsed.invoiceId,
|
|
375
|
-
tenantId: parsed.tenantId,
|
|
376
|
-
organizationId: parsed.organizationId,
|
|
377
|
-
})
|
|
522
|
+
const invoice = await assertDraftInvoice(em, parsed)
|
|
378
523
|
|
|
379
|
-
const existing = await em.find(
|
|
380
|
-
invoice,
|
|
524
|
+
const existing = await em.find(BillingInvoiceLine, {
|
|
525
|
+
invoiceId: invoice.id,
|
|
381
526
|
tenantId: parsed.tenantId,
|
|
382
527
|
organizationId: parsed.organizationId,
|
|
528
|
+
deletedAt: null,
|
|
383
529
|
} as never)
|
|
384
|
-
const nextLineNumber = existing.
|
|
530
|
+
const nextLineNumber = existing.reduce((max, l) => Math.max(max, l.lineNumber), 0) + 1
|
|
385
531
|
|
|
386
|
-
const
|
|
387
|
-
const line = em.create(
|
|
388
|
-
invoice,
|
|
532
|
+
const now = new Date()
|
|
533
|
+
const line = em.create(BillingInvoiceLine, {
|
|
389
534
|
organizationId: parsed.organizationId,
|
|
390
535
|
tenantId: parsed.tenantId,
|
|
536
|
+
invoiceId: invoice.id,
|
|
391
537
|
lineNumber: nextLineNumber,
|
|
392
|
-
|
|
538
|
+
billingItemId: parsed.billingItemId ?? null,
|
|
539
|
+
billingType: parsed.billingType ?? 'manual',
|
|
393
540
|
description: parsed.description,
|
|
394
541
|
quantity: format4dp(parsed.quantity),
|
|
395
|
-
normalizedQuantity: format4dp(parsed.quantity),
|
|
396
|
-
currencyCode: invoice.currencyCode,
|
|
397
542
|
unitPriceNet: format4dp(parsed.unitPriceNet),
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
543
|
+
// Spec B: explicit rate, else the organization default.
|
|
544
|
+
taxRate:
|
|
545
|
+
parsed.taxRate !== undefined
|
|
546
|
+
? taxRateColumn(parsed.taxRate)
|
|
547
|
+
: (await getBillingSettings(em, parsed)).defaultTaxRate,
|
|
548
|
+
netAmount: '0',
|
|
402
549
|
taxAmount: '0',
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
billing_type: parsed.billingType ?? 'manual',
|
|
408
|
-
operator_added: true,
|
|
409
|
-
},
|
|
550
|
+
grossAmount: '0',
|
|
551
|
+
metadata: { operator_added: true },
|
|
552
|
+
createdAt: now,
|
|
553
|
+
updatedAt: now,
|
|
410
554
|
})
|
|
555
|
+
applyLineAmounts(line, parsed.unitPriceNet * parsed.quantity)
|
|
411
556
|
let audit: BillingDraftInvoiceEdit | null = null
|
|
412
557
|
|
|
413
558
|
await withAtomicFlush(em, [
|
|
@@ -418,7 +563,6 @@ const addDraftLineCommand: CommandHandler<
|
|
|
418
563
|
await recomputeInvoiceTotals(em, invoice)
|
|
419
564
|
},
|
|
420
565
|
() => {
|
|
421
|
-
const after = snapshotLine(line)
|
|
422
566
|
audit = em.create(BillingDraftInvoiceEdit, {
|
|
423
567
|
tenantId: parsed.tenantId,
|
|
424
568
|
organizationId: parsed.organizationId,
|
|
@@ -427,7 +571,7 @@ const addDraftLineCommand: CommandHandler<
|
|
|
427
571
|
userId: getUserIdFromCtx(ctx),
|
|
428
572
|
action: 'line_added',
|
|
429
573
|
beforeJson: null,
|
|
430
|
-
afterJson:
|
|
574
|
+
afterJson: snapshotLine(line) as Record<string, unknown>,
|
|
431
575
|
createdAt: new Date(),
|
|
432
576
|
})
|
|
433
577
|
em.persist(audit)
|
|
@@ -450,17 +594,14 @@ const removeDraftLineCommand: CommandHandler<
|
|
|
450
594
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
451
595
|
|
|
452
596
|
const em = getEm(ctx)
|
|
453
|
-
const invoice = await assertDraftInvoice(em,
|
|
454
|
-
|
|
455
|
-
tenantId: parsed.tenantId,
|
|
456
|
-
organizationId: parsed.organizationId,
|
|
457
|
-
})
|
|
458
|
-
const line = await em.findOne(SalesInvoiceLine, {
|
|
597
|
+
const invoice = await assertDraftInvoice(em, parsed)
|
|
598
|
+
const line = await em.findOne(BillingInvoiceLine, {
|
|
459
599
|
id: parsed.invoiceLineId,
|
|
460
|
-
invoice,
|
|
600
|
+
invoiceId: invoice.id,
|
|
461
601
|
tenantId: parsed.tenantId,
|
|
462
602
|
organizationId: parsed.organizationId,
|
|
463
|
-
|
|
603
|
+
deletedAt: null,
|
|
604
|
+
} as never)
|
|
464
605
|
if (!line) {
|
|
465
606
|
throw new CrudHttpError(404, { error: 'Invoice line not found' })
|
|
466
607
|
}
|
|
@@ -495,18 +636,14 @@ const removeDraftLineCommand: CommandHandler<
|
|
|
495
636
|
},
|
|
496
637
|
}
|
|
497
638
|
|
|
498
|
-
// ─── Wipe test invoices
|
|
639
|
+
// ─── Wipe test invoices ──────────────────────────────────────────
|
|
499
640
|
//
|
|
500
|
-
// Hard-deletes
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const wipeTestInvoicesCommand: CommandHandler<
|
|
507
|
-
BillingWipeTestInvoicesInput,
|
|
508
|
-
{ invoicesRemoved: number }
|
|
509
|
-
> = {
|
|
641
|
+
// Hard-deletes test-run drafts (`test_run = true`) with their lines and
|
|
642
|
+
// draft-edit audit rows (`lib/testRunCleanup`), optionally scoped to one Bill
|
|
643
|
+
// Run so an operator can wipe a specific test run without touching other
|
|
644
|
+
// test data.
|
|
645
|
+
|
|
646
|
+
const wipeTestInvoicesCommand: CommandHandler<BillingWipeTestInvoicesInput, { invoicesRemoved: number }> = {
|
|
510
647
|
id: 'billing.invoices.wipe_test',
|
|
511
648
|
|
|
512
649
|
async execute(rawInput, ctx) {
|
|
@@ -515,39 +652,14 @@ const wipeTestInvoicesCommand: CommandHandler<
|
|
|
515
652
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
516
653
|
|
|
517
654
|
const em = getEm(ctx)
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (parsed.billRunId) {
|
|
525
|
-
billRunClause = ` AND metadata->>'bill_run_id' = ?`
|
|
526
|
-
params.push(parsed.billRunId)
|
|
527
|
-
}
|
|
528
|
-
const rows = (await em.execute(
|
|
529
|
-
`SELECT id FROM sales_invoices
|
|
530
|
-
WHERE tenant_id = ?
|
|
531
|
-
AND organization_id = ?
|
|
532
|
-
AND deleted_at IS NULL
|
|
533
|
-
AND metadata->>'test_run' = 'true'${billRunClause}`,
|
|
534
|
-
params,
|
|
535
|
-
)) as unknown as Array<{ id: string }> | undefined
|
|
536
|
-
if (!rows || rows.length === 0) {
|
|
537
|
-
return { invoicesRemoved: 0 }
|
|
538
|
-
}
|
|
539
|
-
const ids = rows.map((r) => r.id)
|
|
540
|
-
// Hard-delete lines first then invoices to avoid relying on
|
|
541
|
-
// cascade semantics that the migration may or may not encode.
|
|
542
|
-
await em.execute(
|
|
543
|
-
`DELETE FROM sales_invoice_lines WHERE invoice_id IN (${ids.map(() => '?').join(',')})`,
|
|
544
|
-
ids,
|
|
545
|
-
)
|
|
546
|
-
await em.execute(
|
|
547
|
-
`DELETE FROM sales_invoices WHERE id IN (${ids.map(() => '?').join(',')})`,
|
|
548
|
-
ids,
|
|
655
|
+
const removed = await em.transactional((tem) =>
|
|
656
|
+
deleteTestRunArtifacts(tem as EntityManager, {
|
|
657
|
+
tenantId: parsed.tenantId,
|
|
658
|
+
organizationId: parsed.organizationId,
|
|
659
|
+
billRunId: parsed.billRunId ?? null,
|
|
660
|
+
}),
|
|
549
661
|
)
|
|
550
|
-
return { invoicesRemoved:
|
|
662
|
+
return { invoicesRemoved: removed }
|
|
551
663
|
},
|
|
552
664
|
}
|
|
553
665
|
|
|
@@ -564,3 +676,24 @@ export {
|
|
|
564
676
|
removeDraftLineCommand,
|
|
565
677
|
wipeTestInvoicesCommand,
|
|
566
678
|
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* `billing.invoices.send` — emails a posted document with its frozen PDF
|
|
682
|
+
* (Spec B). Every attempt is logged in `metadata.deliveries`; a transport
|
|
683
|
+
* failure returns `status: 'failed'` instead of throwing. Not undoable: an
|
|
684
|
+
* email cannot be recalled.
|
|
685
|
+
*/
|
|
686
|
+
const sendInvoiceCommand: CommandHandler<BillingInvoiceSendInput, SendInvoiceResult> = {
|
|
687
|
+
id: 'billing.invoices.send',
|
|
688
|
+
|
|
689
|
+
async execute(rawInput, ctx) {
|
|
690
|
+
const parsed = billingInvoiceSendSchema.parse(rawInput)
|
|
691
|
+
ensureTenantScope(ctx, parsed.tenantId)
|
|
692
|
+
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
693
|
+
return sendInvoice(getEm(ctx), { ...parsed, trigger: 'manual' })
|
|
694
|
+
},
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
registerCommand(sendInvoiceCommand)
|
|
698
|
+
|
|
699
|
+
export { sendInvoiceCommand }
|