@dainamite/billing 0.19.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 +172 -0
- package/README.md +14 -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 +16 -12
- 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 +3 -3
- 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 +3 -3
- 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 +3 -3
- 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 +44 -7
- 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 +3 -3
- package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
- package/dist/modules/billing/api/runs/route.js +11 -5
- 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/ce.js.map +2 -2
- 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 +272 -170
- 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 +154 -7
- 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 +615 -59
- 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 +70 -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 +241 -106
- package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
- package/dist/modules/billing/lib/billRunLabel.js.map +1 -1
- 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/tenantLock.js.map +1 -1
- 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/dist/modules/billing/workers/reap-stale-bill-runs.js +2 -2
- package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +2 -2
- package/generated/entities/billing_account/index.ts +26 -0
- package/generated/entities/billing_account_balance/index.ts +13 -0
- package/generated/entities/billing_account_transaction/index.ts +21 -0
- package/generated/entities/billing_account_usage/index.ts +18 -0
- package/generated/entities/billing_allocation/index.ts +18 -0
- package/generated/entities/billing_document_sequence/index.ts +11 -0
- package/generated/entities/billing_draft_invoice_edit/index.ts +14 -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 +26 -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 +20 -0
- package/generated/entities/billing_run_outcome/index.ts +18 -0
- package/generated/entities/billing_settings/index.ts +26 -0
- package/generated/entities/billing_statement/index.ts +22 -0
- package/generated/entities/billing_topup/index.ts +20 -0
- package/generated/entities.ids.generated.ts +35 -0
- package/package.json +15 -10
- 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 +437 -355
- 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 +326 -0
- package/src/modules/billing/__tests__/events.test.ts +142 -0
- 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 +231 -20
- 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 +20 -18
- 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 +3 -3
- package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
- package/src/modules/billing/api/invoices/edit-line/route.ts +4 -4
- package/src/modules/billing/api/invoices/post/route.ts +7 -6
- package/src/modules/billing/api/invoices/remove-line/route.ts +3 -3
- 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 +50 -7
- 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 +4 -3
- package/src/modules/billing/api/runs/route.ts +17 -9
- 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 +71 -5
- package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +445 -96
- 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 +19 -3
- 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 +50 -19
- package/src/modules/billing/backend/billing/runs/page.tsx +60 -9
- package/src/modules/billing/backend/billing/statements/[id]/page.tsx +87 -6
- package/src/modules/billing/backend/billing/statements/page.tsx +2 -2
- package/src/modules/billing/backend/billing/topups/page.tsx +2 -2
- package/src/modules/billing/backend/billing/transactions/page.tsx +2 -2
- 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/ce.ts +8 -7
- 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 +371 -238
- 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 +208 -6
- 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 +20 -7
- 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 +685 -15
- package/src/modules/billing/data/entityIds.ts +25 -11
- package/src/modules/billing/data/validators.ts +388 -8
- package/src/modules/billing/events.ts +82 -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 +362 -162
- package/src/modules/billing/lib/billRunLabel.ts +1 -1
- 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 +410 -242
- 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/tenantLock.ts +1 -1
- 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/src/modules/billing/workers/reap-stale-bill-runs.ts +4 -4
- 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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/billing/api/export/account/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport {\n BillingAccount,\n BillingAccountBalance,\n BillingAccountTransaction,\n BillingAccountUsage,\n BillingItem,\n BillingStatement,\n BillingTopup,\n BillRunOutcome,\n DraftInvoiceEdit,\n} from '../../../../data/entities'\n\n/**\n * `GET /api/billing/export/account/[id]` \u2014 GDPR portability dump.\n *\n * Returns a full JSON serialization of a single Billing Account\n * including:\n * - The account row itself.\n * - Every active + soft-deleted `BillingItem` for the account.\n * - Every `BillingAccountUsage` record.\n * - Every `BillRunOutcome` row referencing the account.\n * - Every `core/sales` invoice the engine emitted for the account\n * (matched via `metadata->>'bill_account_id'`).\n * - Every `DraftInvoiceEdit` audit row for those invoices.\n *\n * Scope: the caller's auth tenant/organization is the authority \u2014 a\n * 404 is returned even for valid UUIDs that belong to a different\n * tenant (no cross-tenant leak).\n *\n * Soft-deleted rows are included on purpose: GDPR portability is\n * about the data we still hold, not just the data the UI shows.\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.account.view'] },\n}\n\nexport async function GET(\n req: Request,\n { params }: { params: Promise<{ id: string }> },\n) {\n let container: Awaited<ReturnType<typeof createRequestContainer>> | null = null\n try {\n container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const tenantId = auth.tenantId\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json(\n { error: 'Organization scope required' },\n { status: 403 },\n )\n }\n\n const { id: accountId } = await params\n\n const em = (container.resolve('em') as EntityManager).fork()\n const account = await em.findOne(BillingAccount, {\n id: accountId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!account) {\n return NextResponse.json({ error: 'Account not found' }, { status: 404 })\n }\n\n // Time-window cap for usage records, run outcomes and invoices.\n //\n // Scale note: a telco account can carry millions of usage rows.\n // Returning everything would OOM on the export path. We default\n // to the last 12 months and let the caller override via `since` /\n // `until` for full GDPR-compliance dumps (a regulator request that\n // legitimately needs the lifetime export should set `since=1970-01-01`).\n // Each query is independently capped by `EXPORT_HARD_LIMIT` so a\n // single year of pathological volume still returns rather than\n // hanging.\n const url = new URL(req.url)\n const since = parseDate(url.searchParams.get('since'))\n ?? defaultSince()\n const until = parseDate(url.searchParams.get('until')) ?? new Date()\n\n const [items, usage, outcomes, balance, transactions, topups, statements] = await Promise.all([\n // Items are bounded by integration volume (typically <100 per\n // account); no time-window needed \u2014 they often outlive a single\n // export window and the operator needs to see active items\n // regardless of when they were created.\n em.find(\n BillingItem,\n { tenantId, organizationId, billAccountId: account.id } as never,\n { limit: EXPORT_HARD_LIMIT },\n ),\n em.find(\n BillingAccountUsage,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n periodEnd: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { periodEnd: 'DESC' } },\n ),\n em.find(\n BillRunOutcome,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n // SPEC-002 prepaid data \u2014 the balance row + the windowed money trail.\n em.findOne(BillingAccountBalance, {\n tenantId,\n billAccountId: account.id,\n } as never),\n em.find(\n BillingAccountTransaction,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n em.find(\n BillingTopup,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n em.find(\n BillingStatement,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n periodEnd: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { periodEnd: 'DESC' } },\n ),\n ])\n\n // Invoices created in the window. The `metadata->>'bill_account_id'`\n // predicate keeps non-billing core/sales invoices out.\n const invoiceRows = (await em.execute(\n `SELECT id, invoice_number, status, status_entry_id, issue_date, due_date,\n currency_code, subtotal_net_amount, grand_total_net_amount,\n grand_total_gross_amount, paid_total_amount, outstanding_amount,\n metadata, created_at, updated_at, deleted_at\n FROM sales_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND metadata->>'bill_account_id' = ?\n AND created_at >= ?\n AND created_at <= ?\n ORDER BY created_at DESC\n LIMIT ?`,\n [tenantId, organizationId, account.id, since, until, EXPORT_HARD_LIMIT],\n )) as unknown as Array<{ id: string }>\n\n const invoiceIds = invoiceRows.map((r) => r.id)\n const draftEdits =\n invoiceIds.length > 0\n ? await em.find(\n DraftInvoiceEdit,\n {\n tenantId,\n organizationId,\n invoiceId: { $in: invoiceIds },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n )\n : []\n\n return NextResponse.json({\n exportedAt: new Date().toISOString(),\n // v2 adds prepaid balance / transactions / top-ups / statements (SPEC-002).\n schemaVersion: 2,\n tenantId,\n organizationId,\n window: {\n since: since.toISOString(),\n until: until.toISOString(),\n hardLimit: EXPORT_HARD_LIMIT,\n },\n account,\n items,\n usage,\n runOutcomes: outcomes,\n invoices: invoiceRows,\n draftInvoiceEdits: draftEdits,\n // SPEC-002 prepaid section.\n balance,\n transactions,\n topups,\n statements,\n })\n } catch (err) {\n console.error('[billing/export/account.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\n/**\n * Per-collection cap to keep a single export bounded in size and time\n * even when the caller asks for `since=1970-01-01`. The full GDPR\n * lifetime dump for an outlier account requires a follow-up\n * background-job approach (chunked NDJSON to object storage) \u2014 out of\n * scope for the v1 admin export endpoint.\n */\nconst EXPORT_HARD_LIMIT = 50_000\n\n/**\n * Default time window for the export \u2014 last 12 months. Operators\n * pulling for routine GDPR requests almost always want a finite\n * window; the regulator-compliance \"lifetime\" case overrides via\n * `?since=1970-01-01`.\n */\nfunction defaultSince(): Date {\n const d = new Date()\n d.setUTCFullYear(d.getUTCFullYear() - 1)\n return d\n}\n\nfunction parseDate(value: string | null | undefined): Date | null {\n if (!value) return null\n const d = new Date(value)\n return Number.isNaN(d.getTime()) ? null : d\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'Export a billing account (GDPR portability)',\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n {\n name: 'since',\n in: 'query',\n required: false,\n description:\n 'ISO timestamp; lower bound for usage / outcomes / invoices windows. Defaults to today \u2212 12 months.',\n schema: { type: 'string', format: 'date-time' },\n },\n {\n name: 'until',\n in: 'query',\n required: false,\n description: 'ISO timestamp; upper bound. Defaults to now.',\n schema: { type: 'string', format: 'date-time' },\n },\n ],\n description:\n \"Returns a JSON dump of a single Billing Account: the account itself, every Billing Item (no time cap \u2014 items often outlive a single export window), and the Usage records / BillRunOutcomes / emitted core/sales invoices / DraftInvoiceEdit rows whose timestamps fall in the [since, until] window (defaults to the last 12 months). Each collection is independently capped by EXPORT_HARD_LIMIT = 50,000 rows. Lifetime dumps for outlier accounts need a follow-up chunked background-job approach \u2014 out of scope for this v1 endpoint.\",\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport {\n BillingAccount,\n BillingAccountBalance,\n BillingAccountTransaction,\n BillingAccountUsage,\n BillingItem,\n BillingStatement,\n BillingTopup,\n BillingRunOutcome,\n BillingDraftInvoiceEdit,\n} from '../../../../data/entities'\n\n/**\n * `GET /api/billing/export/account/[id]` \u2014 GDPR portability dump.\n *\n * Returns a full JSON serialization of a single Billing Account\n * including:\n * - The account row itself.\n * - Every active + soft-deleted `BillingItem` for the account.\n * - Every `BillingAccountUsage` record.\n * - Every `BillingRunOutcome` row referencing the account.\n * - Every billing document (`billing_invoices`) of the account.\n * - Every `BillingDraftInvoiceEdit` audit row for those invoices.\n *\n * Scope: the caller's auth tenant/organization is the authority \u2014 a\n * 404 is returned even for valid UUIDs that belong to a different\n * tenant (no cross-tenant leak).\n *\n * Soft-deleted rows are included on purpose: GDPR portability is\n * about the data we still hold, not just the data the UI shows.\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.account.view'] },\n}\n\nexport async function GET(\n req: Request,\n { params }: { params: Promise<{ id: string }> },\n) {\n let container: Awaited<ReturnType<typeof createRequestContainer>> | null = null\n try {\n container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const tenantId = auth.tenantId\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json(\n { error: 'Organization scope required' },\n { status: 403 },\n )\n }\n\n const { id: accountId } = await params\n\n const em = (container.resolve('em') as EntityManager).fork()\n const account = await em.findOne(BillingAccount, {\n id: accountId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!account) {\n return NextResponse.json({ error: 'Account not found' }, { status: 404 })\n }\n\n // Time-window cap for usage records, run outcomes and invoices.\n //\n // Scale note: a telco account can carry millions of usage rows.\n // Returning everything would OOM on the export path. We default\n // to the last 12 months and let the caller override via `since` /\n // `until` for full GDPR-compliance dumps (a regulator request that\n // legitimately needs the lifetime export should set `since=1970-01-01`).\n // Each query is independently capped by `EXPORT_HARD_LIMIT` so a\n // single year of pathological volume still returns rather than\n // hanging.\n const url = new URL(req.url)\n const since = parseDate(url.searchParams.get('since'))\n ?? defaultSince()\n const until = parseDate(url.searchParams.get('until')) ?? new Date()\n\n const [items, usage, outcomes, balance, transactions, topups, statements] = await Promise.all([\n // Items are bounded by integration volume (typically <100 per\n // account); no time-window needed \u2014 they often outlive a single\n // export window and the operator needs to see active items\n // regardless of when they were created.\n em.find(\n BillingItem,\n { tenantId, organizationId, billAccountId: account.id } as never,\n { limit: EXPORT_HARD_LIMIT },\n ),\n em.find(\n BillingAccountUsage,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n periodEnd: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { periodEnd: 'DESC' } },\n ),\n em.find(\n BillingRunOutcome,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n // SPEC-002 prepaid data \u2014 the balance row + the windowed money trail.\n em.findOne(BillingAccountBalance, {\n tenantId,\n billAccountId: account.id,\n } as never),\n em.find(\n BillingAccountTransaction,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n em.find(\n BillingTopup,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n createdAt: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n ),\n em.find(\n BillingStatement,\n {\n tenantId,\n organizationId,\n billAccountId: account.id,\n periodEnd: { $gte: since, $lte: until },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { periodEnd: 'DESC' } },\n ),\n ])\n\n // Billing documents created in the window (Spec 0: billing_invoices).\n const invoiceRows = (await em.execute(\n `SELECT id, number, document_type, series, status, test_run, bill_run_id, bill_date,\n period_start, period_end, issue_date, due_date, posted_at, currency_code,\n subtotal_net, tax_total, total_gross, paid_total, credited_total, outstanding,\n corrects_invoice_id, correction_reason, topup_id, buyer_snapshot, seller_snapshot,\n metadata, created_at, updated_at\n FROM billing_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND bill_account_id = ?\n AND created_at >= ?\n AND created_at <= ?\n ORDER BY created_at DESC\n LIMIT ?`,\n [tenantId, organizationId, account.id, since, until, EXPORT_HARD_LIMIT],\n )) as unknown as Array<{ id: string }>\n\n const invoiceIds = invoiceRows.map((r) => r.id)\n const draftEdits =\n invoiceIds.length > 0\n ? await em.find(\n BillingDraftInvoiceEdit,\n {\n tenantId,\n organizationId,\n invoiceId: { $in: invoiceIds },\n } as never,\n { limit: EXPORT_HARD_LIMIT, orderBy: { createdAt: 'DESC' } },\n )\n : []\n\n return NextResponse.json({\n exportedAt: new Date().toISOString(),\n // v2 adds prepaid balance / transactions / top-ups / statements (SPEC-002).\n // v3 (Spec 0): invoices come from billing_invoices with billing field names.\n // v4 (Spec B): items carry taxRate; the account carries paymentTermsDays /\n // autoSendInvoice; invoice metadata may carry draft_number / deliveries.\n schemaVersion: 4,\n tenantId,\n organizationId,\n window: {\n since: since.toISOString(),\n until: until.toISOString(),\n hardLimit: EXPORT_HARD_LIMIT,\n },\n account,\n items,\n usage,\n runOutcomes: outcomes,\n invoices: invoiceRows,\n draftInvoiceEdits: draftEdits,\n // SPEC-002 prepaid section.\n balance,\n transactions,\n topups,\n statements,\n })\n } catch (err) {\n console.error('[billing/export/account.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\n/**\n * Per-collection cap to keep a single export bounded in size and time\n * even when the caller asks for `since=1970-01-01`. The full GDPR\n * lifetime dump for an outlier account requires a follow-up\n * background-job approach (chunked NDJSON to object storage) \u2014 out of\n * scope for the v1 admin export endpoint.\n */\nconst EXPORT_HARD_LIMIT = 50_000\n\n/**\n * Default time window for the export \u2014 last 12 months. Operators\n * pulling for routine GDPR requests almost always want a finite\n * window; the regulator-compliance \"lifetime\" case overrides via\n * `?since=1970-01-01`.\n */\nfunction defaultSince(): Date {\n const d = new Date()\n d.setUTCFullYear(d.getUTCFullYear() - 1)\n return d\n}\n\nfunction parseDate(value: string | null | undefined): Date | null {\n if (!value) return null\n const d = new Date(value)\n return Number.isNaN(d.getTime()) ? null : d\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'Export a billing account (GDPR portability)',\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n {\n name: 'since',\n in: 'query',\n required: false,\n description:\n 'ISO timestamp; lower bound for usage / outcomes / invoices windows. Defaults to today \u2212 12 months.',\n schema: { type: 'string', format: 'date-time' },\n },\n {\n name: 'until',\n in: 'query',\n required: false,\n description: 'ISO timestamp; upper bound. Defaults to now.',\n schema: { type: 'string', format: 'date-time' },\n },\n ],\n description:\n \"Returns a JSON dump of a single Billing Account: the account itself, every Billing Item (no time cap \u2014 items often outlive a single export window), and the Usage records / BillRunOutcomes / emitted core/sales invoices / BillingDraftInvoiceEdit rows whose timestamps fall in the [since, until] window (defaults to the last 12 months). Each collection is independently capped by EXPORT_HARD_LIMIT = 50,000 rows. Lifetime dumps for outlier accounts need a follow-up chunked background-job approach \u2014 out of scope for this v1 endpoint.\",\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readRequestBody, runCommandRoute } from "../../../../lib/commandRoute.js";
|
|
3
|
+
const metadata = {
|
|
4
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.correct"] }
|
|
5
|
+
};
|
|
6
|
+
const lineSchema = z.object({
|
|
7
|
+
correctsLineId: z.string().uuid().optional(),
|
|
8
|
+
description: z.string().trim().min(1).max(500),
|
|
9
|
+
quantity: z.coerce.number().positive(),
|
|
10
|
+
unitPriceNet: z.coerce.number().nonnegative(),
|
|
11
|
+
taxRate: z.coerce.number().min(0).max(100).optional()
|
|
12
|
+
});
|
|
13
|
+
const bodySchema = z.object({
|
|
14
|
+
invoiceId: z.string().uuid(),
|
|
15
|
+
type: z.enum(["credit_memo", "debit_memo"]),
|
|
16
|
+
reason: z.string().trim().min(1).max(1e3),
|
|
17
|
+
lines: z.array(lineSchema).max(200).optional()
|
|
18
|
+
});
|
|
19
|
+
async function POST(req, { params }) {
|
|
20
|
+
const { id } = await params;
|
|
21
|
+
const raw = await readRequestBody(req);
|
|
22
|
+
return runCommandRoute(
|
|
23
|
+
req,
|
|
24
|
+
{
|
|
25
|
+
commandId: "billing.invoices.create_memo",
|
|
26
|
+
bodySchema,
|
|
27
|
+
resourceKind: "billing.invoice",
|
|
28
|
+
resourceId: (body) => body.invoiceId,
|
|
29
|
+
logLabel: "billing/invoices/[id]/memos.POST"
|
|
30
|
+
},
|
|
31
|
+
{ ...raw && typeof raw === "object" ? raw : {}, invoiceId: id }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const openApi = {
|
|
35
|
+
methods: {
|
|
36
|
+
POST: {
|
|
37
|
+
tags: ["Billing"],
|
|
38
|
+
summary: "Create a credit or debit memo",
|
|
39
|
+
description: "Creates a draft memo for a posted / partially paid / paid invoice (a fully credited invoice accepts debit memos only). `reason` is required and printed. Credit memo without `lines` = full credit of every original line; lines with `correctsLineId` keep the original VAT rate; new lines default to the organization rate. Amounts are positive \u2014 the document type gives the sign.",
|
|
40
|
+
requestBody: { schema: bodySchema.omit({ invoiceId: true }) },
|
|
41
|
+
responses: [{ status: 200, description: "`{ memoId, number, documentType, totalGross }`" }],
|
|
42
|
+
errors: [
|
|
43
|
+
{ status: 409, description: "`billing.memo.invalid_original` / `billing.memo.exceeds_invoice`" },
|
|
44
|
+
{ status: 400, description: "`billing.memo.invalid_line`" }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
POST,
|
|
51
|
+
metadata,
|
|
52
|
+
openApi
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/billing/api/invoices/%5Bid%5D/memos/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { readRequestBody, runCommandRoute } from '../../../../lib/commandRoute'\n\n/**\n * `POST /api/billing/invoices/[id]/memos` \u2014 create a DRAFT credit or debit\n * memo correcting a posted billing invoice (Spec D). Post it with\n * `POST /api/billing/invoices/post`.\n */\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.correct'] },\n}\n\nconst lineSchema = z.object({\n correctsLineId: z.string().uuid().optional(),\n description: z.string().trim().min(1).max(500),\n quantity: z.coerce.number().positive(),\n unitPriceNet: z.coerce.number().nonnegative(),\n taxRate: z.coerce.number().min(0).max(100).optional(),\n})\n\nconst bodySchema = z.object({\n invoiceId: z.string().uuid(),\n type: z.enum(['credit_memo', 'debit_memo']),\n reason: z.string().trim().min(1).max(1000),\n lines: z.array(lineSchema).max(200).optional(),\n})\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const raw = (await readRequestBody(req)) as Record<string, unknown>\n return runCommandRoute(\n req,\n {\n commandId: 'billing.invoices.create_memo',\n bodySchema,\n resourceKind: 'billing.invoice',\n resourceId: (body) => body.invoiceId,\n logLabel: 'billing/invoices/[id]/memos.POST',\n },\n { ...(raw && typeof raw === 'object' ? raw : {}), invoiceId: id },\n )\n}\n\nexport const openApi = {\n methods: {\n POST: {\n tags: ['Billing'],\n summary: 'Create a credit or debit memo',\n description:\n 'Creates a draft memo for a posted / partially paid / paid invoice (a fully credited invoice ' +\n 'accepts debit memos only). `reason` is required and printed. Credit memo without `lines` = full ' +\n 'credit of every original line; lines with `correctsLineId` keep the original VAT rate; new lines ' +\n 'default to the organization rate. Amounts are positive \u2014 the document type gives the sign.',\n requestBody: { schema: bodySchema.omit({ invoiceId: true }) },\n responses: [{ status: 200, description: '`{ memoId, number, documentType, totalGross }`' }],\n errors: [\n { status: 409, description: '`billing.memo.invalid_original` / `billing.memo.exceeds_invoice`' },\n { status: 400, description: '`billing.memo.invalid_line`' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,iBAAiB,uBAAuB;AAQ1C,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAC1E;AAEA,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC7C,UAAU,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,EACrC,cAAc,EAAE,OAAO,OAAO,EAAE,YAAY;AAAA,EAC5C,SAAS,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AACtD,CAAC;AAED,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,MAAM,EAAE,KAAK,CAAC,eAAe,YAAY,CAAC;AAAA,EAC1C,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI;AAAA,EACzC,OAAO,EAAE,MAAM,UAAU,EAAE,IAAI,GAAG,EAAE,SAAS;AAC/C,CAAC;AAED,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAwC;AACxF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,MAAO,MAAM,gBAAgB,GAAG;AACtC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,WAAW;AAAA,MACX;AAAA,MACA,cAAc;AAAA,MACd,YAAY,CAAC,SAAS,KAAK;AAAA,MAC3B,UAAU;AAAA,IACZ;AAAA,IACA,EAAE,GAAI,OAAO,OAAO,QAAQ,WAAW,MAAM,CAAC,GAAI,WAAW,GAAG;AAAA,EAClE;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,aACE;AAAA,MAIF,aAAa,EAAE,QAAQ,WAAW,KAAK,EAAE,WAAW,KAAK,CAAC,EAAE;AAAA,MAC5D,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,iDAAiD,CAAC;AAAA,MAC1F,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,mEAAmE;AAAA,QAC/F,EAAE,QAAQ,KAAK,aAAa,8BAA8B;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { BillingInvoice } from "../../../../data/entities.js";
|
|
5
|
+
import { getInvoicePdf } from "../../../../lib/pdf/invoicePdf.js";
|
|
6
|
+
const metadata = {
|
|
7
|
+
GET: { requireAuth: true, requireFeatures: ["billing.invoice.view"] }
|
|
8
|
+
};
|
|
9
|
+
async function GET(req, { params }) {
|
|
10
|
+
try {
|
|
11
|
+
const container = await createRequestContainer();
|
|
12
|
+
const auth = await getAuthFromRequest(req);
|
|
13
|
+
if (!auth || !auth.tenantId) {
|
|
14
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
15
|
+
}
|
|
16
|
+
const organizationId = auth.orgId ?? null;
|
|
17
|
+
if (!organizationId) {
|
|
18
|
+
return NextResponse.json({ error: "Organization scope required" }, { status: 403 });
|
|
19
|
+
}
|
|
20
|
+
const { id } = await params;
|
|
21
|
+
const em = container.resolve("em").fork();
|
|
22
|
+
const invoice = await em.findOne(BillingInvoice, {
|
|
23
|
+
id,
|
|
24
|
+
tenantId: auth.tenantId,
|
|
25
|
+
organizationId,
|
|
26
|
+
deletedAt: null
|
|
27
|
+
});
|
|
28
|
+
if (!invoice) {
|
|
29
|
+
return NextResponse.json({ error: "Invoice not found" }, { status: 404 });
|
|
30
|
+
}
|
|
31
|
+
const pdf = await getInvoicePdf(em, invoice);
|
|
32
|
+
const download = new URL(req.url).searchParams.get("download") === "1";
|
|
33
|
+
return new NextResponse(Buffer.from(pdf.bytes), {
|
|
34
|
+
status: 200,
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/pdf",
|
|
37
|
+
"Content-Length": String(pdf.bytes.byteLength),
|
|
38
|
+
"Content-Disposition": `${download ? "attachment" : "inline"}; filename="${pdf.fileName}"`,
|
|
39
|
+
// Drafts change with every edit; frozen documents never do.
|
|
40
|
+
"Cache-Control": pdf.frozen ? "private, max-age=300" : "no-store",
|
|
41
|
+
"X-Content-Type-Options": "nosniff"
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error("[billing/invoices/[id]/pdf.GET]", err);
|
|
46
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const openApi = {
|
|
50
|
+
methods: {
|
|
51
|
+
GET: {
|
|
52
|
+
tags: ["Billing"],
|
|
53
|
+
summary: "Download a billing document PDF",
|
|
54
|
+
parameters: [
|
|
55
|
+
{ name: "id", in: "path", required: true, schema: { type: "string", format: "uuid" } },
|
|
56
|
+
{ name: "download", in: "query", required: false, schema: { type: "string", enum: ["1"] } }
|
|
57
|
+
],
|
|
58
|
+
description: "`application/pdf`. Posted documents return the PDF frozen at post (an attachment); drafts and test documents are rendered live with a DRAFT / TEST watermark and never stored. Labels follow the account invoice language (en / pl).",
|
|
59
|
+
responses: [{ status: 200, description: "PDF document (`application/pdf`)" }],
|
|
60
|
+
errors: [{ status: 404, description: "Invoice not found in this organization" }]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export {
|
|
65
|
+
GET,
|
|
66
|
+
metadata,
|
|
67
|
+
openApi
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/billing/api/invoices/%5Bid%5D/pdf/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { BillingInvoice } from '../../../../data/entities'\nimport { getInvoicePdf } from '../../../../lib/pdf/invoicePdf'\n\n/**\n * `GET /api/billing/invoices/[id]/pdf` \u2014 the document PDF (Spec B).\n *\n * Posted documents: the frozen attachment (rendered and frozen on first\n * request if freezing failed at post). Drafts / test documents: a live\n * render with a DRAFT / TEST watermark, never stored. `?download=1` forces\n * an attachment disposition; otherwise the browser shows it inline.\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.invoice.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json({ error: 'Organization scope required' }, { status: 403 })\n }\n const { id } = await params\n const em = (container.resolve('em') as EntityManager).fork()\n const invoice = await em.findOne(BillingInvoice, {\n id,\n tenantId: auth.tenantId,\n organizationId,\n deletedAt: null,\n } as never)\n if (!invoice) {\n return NextResponse.json({ error: 'Invoice not found' }, { status: 404 })\n }\n const pdf = await getInvoicePdf(em, invoice)\n const download = new URL(req.url).searchParams.get('download') === '1'\n return new NextResponse(Buffer.from(pdf.bytes), {\n status: 200,\n headers: {\n 'Content-Type': 'application/pdf',\n 'Content-Length': String(pdf.bytes.byteLength),\n 'Content-Disposition': `${download ? 'attachment' : 'inline'}; filename=\"${pdf.fileName}\"`,\n // Drafts change with every edit; frozen documents never do.\n 'Cache-Control': pdf.frozen ? 'private, max-age=300' : 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n },\n })\n } catch (err) {\n console.error('[billing/invoices/[id]/pdf.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'Download a billing document PDF',\n parameters: [\n { name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } },\n { name: 'download', in: 'query', required: false, schema: { type: 'string', enum: ['1'] } },\n ],\n description:\n '`application/pdf`. Posted documents return the PDF frozen at post (an attachment); drafts ' +\n 'and test documents are rendered live with a DRAFT / TEST watermark and never stored. ' +\n 'Labels follow the account invoice language (en / pl).',\n responses: [{ status: 200, description: 'PDF document (`application/pdf`)' }],\n errors: [{ status: 404, description: 'Invoice not found in this organization' }],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAWvB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAAwC;AACvF,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,UAAM,iBAAkB,KAA4B,SAAS;AAC7D,QAAI,CAAC,gBAAgB;AACnB,aAAO,aAAa,KAAK,EAAE,OAAO,8BAA8B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpF;AACA,UAAM,EAAE,GAAG,IAAI,MAAM;AACrB,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,UAAM,UAAU,MAAM,GAAG,QAAQ,gBAAgB;AAAA,MAC/C;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,WAAW;AAAA,IACb,CAAU;AACV,QAAI,CAAC,SAAS;AACZ,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AACA,UAAM,MAAM,MAAM,cAAc,IAAI,OAAO;AAC3C,UAAM,WAAW,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa,IAAI,UAAU,MAAM;AACnE,WAAO,IAAI,aAAa,OAAO,KAAK,IAAI,KAAK,GAAG;AAAA,MAC9C,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB,OAAO,IAAI,MAAM,UAAU;AAAA,QAC7C,uBAAuB,GAAG,WAAW,eAAe,QAAQ,eAAe,IAAI,QAAQ;AAAA;AAAA,QAEvF,iBAAiB,IAAI,SAAS,yBAAyB;AAAA,QACvD,0BAA0B;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,mCAAmC,GAAG;AACpD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,KAAK;AAAA,MACH,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,YAAY;AAAA,QACV,EAAE,MAAM,MAAM,IAAI,QAAQ,UAAU,MAAM,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO,EAAE;AAAA,QACrF,EAAE,MAAM,YAAY,IAAI,SAAS,UAAU,OAAO,QAAQ,EAAE,MAAM,UAAU,MAAM,CAAC,GAAG,EAAE,EAAE;AAAA,MAC5F;AAAA,MACA,aACE;AAAA,MAGF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,mCAAmC,CAAC;AAAA,MAC5E,QAAQ,CAAC,EAAE,QAAQ,KAAK,aAAa,yCAAyC,CAAC;AAAA,IACjF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { daysOverdueSql, isoDay } from "../../../lib/overdue.js";
|
|
4
5
|
const metadata = {
|
|
5
6
|
GET: { requireAuth: true, requireFeatures: ["billing.invoice.view"] }
|
|
6
7
|
};
|
|
@@ -20,40 +21,59 @@ async function GET(req, { params }) {
|
|
|
20
21
|
}
|
|
21
22
|
const { id: invoiceId } = await params;
|
|
22
23
|
const em = container.resolve("em").fork();
|
|
24
|
+
const today = isoDay(/* @__PURE__ */ new Date());
|
|
23
25
|
const invoiceRows = await em.execute(
|
|
24
|
-
`SELECT id,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
AND jsonb_exists(metadata, 'bill_run_id')`,
|
|
37
|
-
[invoiceId, auth.tenantId, organizationId]
|
|
26
|
+
`SELECT i.id, i.number, i.document_type, i.series, i.status, i.test_run, i.bill_account_id,
|
|
27
|
+
a.name AS bill_account_name, i.bill_run_id, i.bill_date, i.period_start, i.period_end,
|
|
28
|
+
i.issue_date, i.due_date, i.posted_at, i.currency_code, i.subtotal_net, i.tax_total,
|
|
29
|
+
i.total_gross, i.paid_total, i.credited_total, i.outstanding, i.corrects_invoice_id,
|
|
30
|
+
i.correction_reason, i.corrects_invoice_number, i.corrects_issue_date,
|
|
31
|
+
i.buyer_snapshot, i.seller_snapshot, i.topup_id, i.metadata,
|
|
32
|
+
i.pdf_attachment_id, ${daysOverdueSql("i")}::int AS days_overdue,
|
|
33
|
+
i.created_at, i.updated_at
|
|
34
|
+
FROM billing_invoices i
|
|
35
|
+
LEFT JOIN billing_accounts a ON a.id = i.bill_account_id AND a.tenant_id = i.tenant_id
|
|
36
|
+
WHERE i.id = ? AND i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL`,
|
|
37
|
+
[today, today, invoiceId, auth.tenantId, organizationId]
|
|
38
38
|
);
|
|
39
39
|
if (!invoiceRows || invoiceRows.length === 0) {
|
|
40
40
|
return NextResponse.json({ error: "Invoice not found" }, { status: 404 });
|
|
41
41
|
}
|
|
42
42
|
const invoice = invoiceRows[0];
|
|
43
43
|
const lineRows = await em.execute(
|
|
44
|
-
`SELECT id, line_number,
|
|
45
|
-
unit_price_net,
|
|
46
|
-
|
|
47
|
-
FROM
|
|
48
|
-
WHERE invoice_id = ?
|
|
49
|
-
AND tenant_id = ?
|
|
50
|
-
AND organization_id = ?
|
|
44
|
+
`SELECT id, line_number, billing_item_id, billing_type, description, quantity,
|
|
45
|
+
unit_price_net, tax_rate, net_amount, tax_amount, gross_amount,
|
|
46
|
+
service_period_start, service_period_end, corrects_line_id, metadata
|
|
47
|
+
FROM billing_invoice_lines
|
|
48
|
+
WHERE invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
|
|
51
49
|
ORDER BY line_number ASC`,
|
|
52
50
|
[invoiceId, auth.tenantId, organizationId]
|
|
53
51
|
);
|
|
52
|
+
const allocations = await em.execute(
|
|
53
|
+
`SELECT al.id, al.source_type, al.source_id, al.amount, al.currency_code, al.allocated_by, al.created_at,
|
|
54
|
+
CASE WHEN al.source_type = 'payment' THEN COALESCE(p.reference, p.payer_name)
|
|
55
|
+
ELSE m.number END AS source_label,
|
|
56
|
+
p.received_at AS payment_received_at
|
|
57
|
+
FROM billing_allocations al
|
|
58
|
+
LEFT JOIN billing_payments p ON al.source_type = 'payment' AND p.id = al.source_id
|
|
59
|
+
LEFT JOIN billing_invoices m ON al.source_type = 'credit_memo' AND m.id = al.source_id
|
|
60
|
+
WHERE al.invoice_id = ? AND al.tenant_id = ? AND al.organization_id = ? AND al.deleted_at IS NULL
|
|
61
|
+
ORDER BY al.created_at ASC`,
|
|
62
|
+
[invoiceId, auth.tenantId, organizationId]
|
|
63
|
+
);
|
|
64
|
+
const corrections = await em.execute(
|
|
65
|
+
`SELECT id, number, document_type, status, total_gross, outstanding, currency_code, correction_reason, created_at
|
|
66
|
+
FROM billing_invoices
|
|
67
|
+
WHERE corrects_invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
|
|
68
|
+
ORDER BY created_at ASC`,
|
|
69
|
+
[invoiceId, auth.tenantId, organizationId]
|
|
70
|
+
);
|
|
54
71
|
return NextResponse.json({
|
|
72
|
+
corrections,
|
|
73
|
+
allocations,
|
|
55
74
|
invoice,
|
|
56
|
-
|
|
75
|
+
// Lines carry the document currency for display convenience.
|
|
76
|
+
lines: lineRows.map((line) => ({ ...line, currency_code: invoice.currency_code }))
|
|
57
77
|
});
|
|
58
78
|
} catch (err) {
|
|
59
79
|
console.error("[billing/invoices/[id].GET]", err);
|
|
@@ -64,8 +84,8 @@ const openApi = {
|
|
|
64
84
|
methods: {
|
|
65
85
|
GET: {
|
|
66
86
|
tags: ["Billing"],
|
|
67
|
-
summary: "Get a billing
|
|
68
|
-
description: "Returns
|
|
87
|
+
summary: "Get a billing document (with lines)",
|
|
88
|
+
description: "Returns one billing document from `billing_invoices` (invoice, memo or receipt) with its lines, the frozen buyer / seller snapshots, `bill_account_name` and `allocations[]` (Spec C: payments / credit memos that settled it, with `source_label`) and `corrections[]` (Spec D memos). 404 outside the caller's organization.",
|
|
69
89
|
parameters: [
|
|
70
90
|
{
|
|
71
91
|
name: "id",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/billing/api/invoices/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\n\n/**\n * `GET /api/billing/invoices/[id]` \u2014 billing
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { daysOverdueSql, isoDay } from '../../../lib/overdue'\n\n/**\n * `GET /api/billing/invoices/[id]` \u2014 one billing document with its lines\n * (Spec 0: `billing_invoices` / `billing_invoice_lines`). Documents of other\n * modules are never reachable here.\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.invoice.view'] },\n}\n\ntype InvoiceRow = Record<string, unknown> & { id: string; bill_account_id: string }\ntype InvoiceLineRow = Record<string, unknown> & { id: string }\n\nexport async function GET(\n req: Request,\n { params }: { params: Promise<{ id: string }> },\n) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json(\n { error: 'Organization scope required' },\n { status: 403 },\n )\n }\n\n const { id: invoiceId } = await params\n const em = (container.resolve('em') as EntityManager).fork()\n\n const today = isoDay(new Date())\n const invoiceRows = (await em.execute(\n `SELECT i.id, i.number, i.document_type, i.series, i.status, i.test_run, i.bill_account_id,\n a.name AS bill_account_name, i.bill_run_id, i.bill_date, i.period_start, i.period_end,\n i.issue_date, i.due_date, i.posted_at, i.currency_code, i.subtotal_net, i.tax_total,\n i.total_gross, i.paid_total, i.credited_total, i.outstanding, i.corrects_invoice_id,\n i.correction_reason, i.corrects_invoice_number, i.corrects_issue_date,\n i.buyer_snapshot, i.seller_snapshot, i.topup_id, i.metadata,\n i.pdf_attachment_id, ${daysOverdueSql('i')}::int AS days_overdue,\n i.created_at, i.updated_at\n FROM billing_invoices i\n LEFT JOIN billing_accounts a ON a.id = i.bill_account_id AND a.tenant_id = i.tenant_id\n WHERE i.id = ? AND i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL`,\n [today, today, invoiceId, auth.tenantId, organizationId],\n )) as unknown as InvoiceRow[]\n\n if (!invoiceRows || invoiceRows.length === 0) {\n return NextResponse.json({ error: 'Invoice not found' }, { status: 404 })\n }\n\n const invoice = invoiceRows[0]\n\n const lineRows = (await em.execute(\n `SELECT id, line_number, billing_item_id, billing_type, description, quantity,\n unit_price_net, tax_rate, net_amount, tax_amount, gross_amount,\n service_period_start, service_period_end, corrects_line_id, metadata\n FROM billing_invoice_lines\n WHERE invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL\n ORDER BY line_number ASC`,\n [invoiceId, auth.tenantId, organizationId],\n )) as unknown as InvoiceLineRow[]\n\n // Spec C: what settled this receivable (payments; Spec D adds credit memos).\n const allocations = await em.execute(\n `SELECT al.id, al.source_type, al.source_id, al.amount, al.currency_code, al.allocated_by, al.created_at,\n CASE WHEN al.source_type = 'payment' THEN COALESCE(p.reference, p.payer_name)\n ELSE m.number END AS source_label,\n p.received_at AS payment_received_at\n FROM billing_allocations al\n LEFT JOIN billing_payments p ON al.source_type = 'payment' AND p.id = al.source_id\n LEFT JOIN billing_invoices m ON al.source_type = 'credit_memo' AND m.id = al.source_id\n WHERE al.invoice_id = ? AND al.tenant_id = ? AND al.organization_id = ? AND al.deleted_at IS NULL\n ORDER BY al.created_at ASC`,\n [invoiceId, auth.tenantId, organizationId],\n )\n\n // Spec D: memos correcting this document.\n const corrections = await em.execute(\n `SELECT id, number, document_type, status, total_gross, outstanding, currency_code, correction_reason, created_at\n FROM billing_invoices\n WHERE corrects_invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL\n ORDER BY created_at ASC`,\n [invoiceId, auth.tenantId, organizationId],\n )\n\n return NextResponse.json({\n corrections,\n allocations,\n invoice,\n // Lines carry the document currency for display convenience.\n lines: lineRows.map((line) => ({ ...line, currency_code: invoice.currency_code })),\n })\n } catch (err) {\n console.error('[billing/invoices/[id].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'Get a billing document (with lines)',\n description:\n 'Returns one billing document from `billing_invoices` (invoice, memo or receipt) with its ' +\n 'lines, the frozen buyer / seller snapshots, `bill_account_name` and `allocations[]` (Spec C: ' +\n 'payments / credit memos that settled it, with `source_label`) and `corrections[]` (Spec D memos). ' +\n '404 outside the ' +\n 'caller\\'s organization.',\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,gBAAgB,cAAc;AAQhC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAKA,eAAsB,IACpB,KACA,EAAE,OAAO,GACT;AACA,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,UAAM,iBAAkB,KAA4B,SAAS;AAC7D,QAAI,CAAC,gBAAgB;AACnB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,8BAA8B;AAAA,QACvC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,EAAE,IAAI,UAAU,IAAI,MAAM;AAChC,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAE3D,UAAM,QAAQ,OAAO,oBAAI,KAAK,CAAC;AAC/B,UAAM,cAAe,MAAM,GAAG;AAAA,MAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAM+B,eAAe,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKlD,CAAC,OAAO,OAAO,WAAW,KAAK,UAAU,cAAc;AAAA,IACzD;AAEA,QAAI,CAAC,eAAe,YAAY,WAAW,GAAG;AAC5C,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAEA,UAAM,UAAU,YAAY,CAAC;AAE7B,UAAM,WAAY,MAAM,GAAG;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,CAAC,WAAW,KAAK,UAAU,cAAc;AAAA,IAC3C;AAGA,UAAM,cAAc,MAAM,GAAG;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASA,CAAC,WAAW,KAAK,UAAU,cAAc;AAAA,IAC3C;AAGA,UAAM,cAAc,MAAM,GAAG;AAAA,MAC3B;AAAA;AAAA;AAAA;AAAA,MAIA,CAAC,WAAW,KAAK,UAAU,cAAc;AAAA,IAC3C;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA,OAAO,SAAS,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,eAAe,QAAQ,cAAc,EAAE;AAAA,IACnF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,+BAA+B,GAAG;AAChD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,KAAK;AAAA,MACH,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,aACE;AAAA,MAKF,YAAY;AAAA,QACV;AAAA,UACE,MAAM;AAAA,UACN,IAAI;AAAA,UACJ,UAAU;AAAA,UACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import {
|
|
6
|
+
organizationScopeRequiredResponse,
|
|
7
|
+
resolveActiveOrganizationId
|
|
8
|
+
} from "@open-mercato/shared/lib/auth/organizationScope";
|
|
9
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
10
|
+
import { bridgeLegacyGuard, runMutationGuards } from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
11
|
+
const metadata = {
|
|
12
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.send"] }
|
|
13
|
+
};
|
|
14
|
+
function userFeatures(auth) {
|
|
15
|
+
const features = auth?.features;
|
|
16
|
+
return Array.isArray(features) ? features.filter((value) => typeof value === "string") : [];
|
|
17
|
+
}
|
|
18
|
+
async function POST(req, { params }) {
|
|
19
|
+
try {
|
|
20
|
+
const container = await createRequestContainer();
|
|
21
|
+
const auth = await getAuthFromRequest(req);
|
|
22
|
+
if (!auth || !auth.tenantId) {
|
|
23
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
24
|
+
}
|
|
25
|
+
const organizationId = resolveActiveOrganizationId(auth);
|
|
26
|
+
if (!organizationId) return organizationScopeRequiredResponse();
|
|
27
|
+
const { id } = await params;
|
|
28
|
+
const ctx = {
|
|
29
|
+
container,
|
|
30
|
+
auth,
|
|
31
|
+
organizationScope: null,
|
|
32
|
+
selectedOrganizationId: organizationId,
|
|
33
|
+
organizationIds: [organizationId],
|
|
34
|
+
request: req
|
|
35
|
+
};
|
|
36
|
+
const guardInput = {
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId,
|
|
39
|
+
userId: auth.sub ?? "",
|
|
40
|
+
resourceKind: "billing.invoice",
|
|
41
|
+
resourceId: id,
|
|
42
|
+
operation: "update",
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers
|
|
45
|
+
};
|
|
46
|
+
const legacyGuard = bridgeLegacyGuard(container);
|
|
47
|
+
const guardResult = legacyGuard ? await runMutationGuards([legacyGuard], guardInput, { userFeatures: userFeatures(auth) }) : { ok: true, afterSuccessCallbacks: [] };
|
|
48
|
+
if (!guardResult.ok) {
|
|
49
|
+
return NextResponse.json(guardResult.errorBody ?? { error: "Operation blocked by guard" }, {
|
|
50
|
+
status: guardResult.errorStatus ?? 422
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const commandBus = container.resolve("commandBus");
|
|
54
|
+
const response = await commandBus.execute("billing.invoices.send", {
|
|
55
|
+
input: { tenantId: auth.tenantId, organizationId, invoiceId: id },
|
|
56
|
+
ctx
|
|
57
|
+
});
|
|
58
|
+
const result = response.result;
|
|
59
|
+
for (const callback of guardResult.afterSuccessCallbacks) {
|
|
60
|
+
if (!callback.guard.afterSuccess) continue;
|
|
61
|
+
try {
|
|
62
|
+
await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null });
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("[billing/invoices/[id]/send.POST] guard afterSuccess failed", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return NextResponse.json(result ?? { invoiceId: id, status: "failed", error: null });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (isCrudHttpError(err)) {
|
|
70
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
71
|
+
}
|
|
72
|
+
console.error("[billing/invoices/[id]/send.POST]", err);
|
|
73
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const openApi = {
|
|
77
|
+
methods: {
|
|
78
|
+
POST: {
|
|
79
|
+
tags: ["Billing"],
|
|
80
|
+
summary: "Email a posted billing document",
|
|
81
|
+
parameters: [{ name: "id", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
82
|
+
description: 'Sends the frozen PDF to the billing account invoice email through the platform email transport. Each attempt is appended to `metadata.deliveries` and emits `billing.invoice.sent` / `billing.invoice.send_failed`. Transport failures return 200 with `status: "failed"` and the error.',
|
|
83
|
+
responses: [
|
|
84
|
+
{
|
|
85
|
+
status: 200,
|
|
86
|
+
description: "Delivery attempted",
|
|
87
|
+
schema: z.object({
|
|
88
|
+
invoiceId: z.string().uuid(),
|
|
89
|
+
to: z.string(),
|
|
90
|
+
status: z.enum(["sent", "failed"]),
|
|
91
|
+
error: z.string().nullable()
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
errors: [
|
|
96
|
+
{ status: 409, description: "`billing.invoice.not_posted` \u2014 drafts and test documents cannot be sent" },
|
|
97
|
+
{ status: 422, description: "`billing.invoice.email_invalid` \u2014 placeholder or malformed invoice email" }
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
export {
|
|
103
|
+
POST,
|
|
104
|
+
metadata,
|
|
105
|
+
openApi
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/billing/api/invoices/%5Bid%5D/send/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport {\n organizationScopeRequiredResponse,\n resolveActiveOrganizationId,\n} from '@open-mercato/shared/lib/auth/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { bridgeLegacyGuard, runMutationGuards } from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport type { SendInvoiceResult } from '../../../../lib/invoiceDelivery'\n\n/**\n * `POST /api/billing/invoices/[id]/send` \u2014 emails a posted billing document\n * with its frozen PDF to the account's invoice email (Spec B), via the\n * `billing.invoices.send` command.\n *\n * 200 `{ invoiceId, to, status: 'sent' | 'failed', error }` \u2014 a transport\n * failure is a recorded `failed` delivery, not an HTTP error.\n * 409 `billing.invoice.not_posted` (draft / test). 422\n * `billing.invoice.email_invalid` (placeholder or malformed address).\n */\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.send'] },\n}\n\nfunction userFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n return Array.isArray(features) ? features.filter((value): value is string => typeof value === 'string') : []\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = resolveActiveOrganizationId(auth)\n if (!organizationId) return organizationScopeRequiredResponse()\n const { id } = await params\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId: organizationId,\n organizationIds: [organizationId],\n request: req,\n }\n // Custom write route \u2192 run the shared mutation guards (record locks etc.)\n // as `update` on the invoice before sending.\n const guardInput = {\n tenantId: auth.tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'billing.invoice',\n resourceId: id,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const legacyGuard = bridgeLegacyGuard(container)\n const guardResult = legacyGuard\n ? await runMutationGuards([legacyGuard], guardInput, { userFeatures: userFeatures(auth) })\n : { ok: true as const, afterSuccessCallbacks: [] }\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, {\n status: guardResult.errorStatus ?? 422,\n })\n }\n\n const commandBus = container.resolve('commandBus') as CommandBus\n const response = await commandBus.execute('billing.invoices.send', {\n input: { tenantId: auth.tenantId, organizationId, invoiceId: id },\n ctx,\n })\n const result = (response as { result?: SendInvoiceResult }).result\n for (const callback of guardResult.afterSuccessCallbacks) {\n if (!callback.guard.afterSuccess) continue\n try {\n await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null })\n } catch (err) {\n console.error('[billing/invoices/[id]/send.POST] guard afterSuccess failed', err)\n }\n }\n return NextResponse.json(result ?? { invoiceId: id, status: 'failed', error: null })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[billing/invoices/[id]/send.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n tags: ['Billing'],\n summary: 'Email a posted billing document',\n parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],\n description:\n 'Sends the frozen PDF to the billing account invoice email through the platform email ' +\n 'transport. Each attempt is appended to `metadata.deliveries` and emits ' +\n '`billing.invoice.sent` / `billing.invoice.send_failed`. Transport failures return ' +\n '200 with `status: \"failed\"` and the error.',\n responses: [\n {\n status: 200,\n description: 'Delivery attempted',\n schema: z.object({\n invoiceId: z.string().uuid(),\n to: z.string(),\n status: z.enum(['sent', 'failed']),\n error: z.string().nullable(),\n }),\n },\n ],\n errors: [\n { status: 409, description: '`billing.invoice.not_posted` \u2014 drafts and test documents cannot be sent' },\n { status: 422, description: '`billing.invoice.email_invalid` \u2014 placeholder or malformed invoice email' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,10 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
3
|
import { withScopedPayload } from "@open-mercato/shared/lib/api/scoped";
|
|
4
4
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
-
import { SalesInvoice } from "@open-mercato/core/modules/sales/data/entities";
|
|
6
5
|
import { billingEntityIds } from "../../../data/entityIds.js";
|
|
7
6
|
import { billingInvoiceAddLineSchema } from "../../../data/validators.js";
|
|
8
7
|
import { createBillingCrudOpenApi } from "../../openapi.js";
|
|
8
|
+
import { BillingInvoice } from "../../../data/entities.js";
|
|
9
9
|
const routeMetadata = {
|
|
10
10
|
POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
|
|
11
11
|
};
|
|
@@ -19,7 +19,7 @@ const rawBodySchema = z.object({}).passthrough();
|
|
|
19
19
|
const crud = makeCrudRoute({
|
|
20
20
|
metadata: routeMetadata,
|
|
21
21
|
orm: {
|
|
22
|
-
entity:
|
|
22
|
+
entity: BillingInvoice,
|
|
23
23
|
idField: "id",
|
|
24
24
|
orgField: "organizationId",
|
|
25
25
|
tenantField: "tenantId",
|
|
@@ -58,7 +58,7 @@ const openApi = createBillingCrudOpenApi({
|
|
|
58
58
|
create: {
|
|
59
59
|
schema: billingInvoiceAddLineSchema,
|
|
60
60
|
responseSchema: addLineResponseSchema,
|
|
61
|
-
description: "Appends a new line to a draft invoice (e.g. operator adds a one-off adjustment). Writes a `
|
|
61
|
+
description: "Appends a new line to a draft invoice (e.g. operator adds a one-off adjustment). Writes a `BillingDraftInvoiceEdit` audit row (action=`line_added`, `before_json=null`). Recomputes invoice totals. Refuses non-draft invoices."
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
64
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/billing/api/invoices/add-line/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoiceAddLineSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\nimport { BillingInvoice } from '../../../data/entities'\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst addLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.add_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceAddLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 201,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineAdd',\n pluralName: 'BillingInvoiceLineAdds',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceAddLineSchema,\n responseSchema: addLineResponseSchema,\n description:\n 'Appends a new line to a draft invoice (e.g. operator adds a ' +\n 'one-off adjustment). Writes a `BillingDraftInvoiceEdit` audit row ' +\n '(action=`line_added`, `before_json=null`). Recomputes invoice ' +\n 'totals. Refuses non-draft invoices.',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAE/B,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,4BAA4B,MAAM,MAAM;AAAA,MACjD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAIJ;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommandRoute } from "../../../lib/commandRoute.js";
|
|
3
|
+
const metadata = {
|
|
4
|
+
POST: { requireAuth: true, requireFeatures: ["billing.invoice.correct"] }
|
|
5
|
+
};
|
|
6
|
+
const bodySchema = z.object({ invoiceId: z.string().uuid() });
|
|
7
|
+
async function POST(req) {
|
|
8
|
+
return runCommandRoute(req, {
|
|
9
|
+
commandId: "billing.invoices.delete_draft",
|
|
10
|
+
bodySchema,
|
|
11
|
+
resourceKind: "billing.invoice",
|
|
12
|
+
resourceId: (body) => body.invoiceId,
|
|
13
|
+
logLabel: "billing/invoices/delete-draft.POST"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const openApi = {
|
|
17
|
+
methods: {
|
|
18
|
+
POST: {
|
|
19
|
+
tags: ["Billing"],
|
|
20
|
+
summary: "Delete a draft memo",
|
|
21
|
+
description: "Deletes a draft credit or debit memo with its lines. Posted documents are never deleted (no void).",
|
|
22
|
+
requestBody: { schema: bodySchema },
|
|
23
|
+
responses: [{ status: 200, description: "`{ deleted: true }`" }],
|
|
24
|
+
errors: [{ status: 409, description: "`billing.memo.not_draft`" }]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
POST,
|
|
30
|
+
metadata,
|
|
31
|
+
openApi
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/invoices/delete-draft/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { runCommandRoute } from '../../../lib/commandRoute'\n\n/** `POST /api/billing/invoices/delete-draft` \u2014 delete a DRAFT credit / debit memo (Spec D, D-D7). */\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.correct'] },\n}\n\nconst bodySchema = z.object({ invoiceId: z.string().uuid() })\n\nexport async function POST(req: Request) {\n return runCommandRoute(req, {\n commandId: 'billing.invoices.delete_draft',\n bodySchema,\n resourceKind: 'billing.invoice',\n resourceId: (body) => body.invoiceId,\n logLabel: 'billing/invoices/delete-draft.POST',\n })\n}\n\nexport const openApi = {\n methods: {\n POST: {\n tags: ['Billing'],\n summary: 'Delete a draft memo',\n description: 'Deletes a draft credit or debit memo with its lines. Posted documents are never deleted (no void).',\n requestBody: { schema: bodySchema },\n responses: [{ status: 200, description: '`{ deleted: true }`' }],\n errors: [{ status: 409, description: '`billing.memo.not_draft`' }],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAIzB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,yBAAyB,EAAE;AAC1E;AAEA,MAAM,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAE5D,eAAsB,KAAK,KAAc;AACvC,SAAO,gBAAgB,KAAK;AAAA,IAC1B,WAAW;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,YAAY,CAAC,SAAS,KAAK;AAAA,IAC3B,UAAU;AAAA,EACZ,CAAC;AACH;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa,EAAE,QAAQ,WAAW;AAAA,MAClC,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,sBAAsB,CAAC;AAAA,MAC/D,QAAQ,CAAC,EAAE,QAAQ,KAAK,aAAa,2BAA2B,CAAC;AAAA,IACnE;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|