@dainamite/billing 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -0
- package/dist/modules/billing/acl.js +13 -1
- package/dist/modules/billing/acl.js.map +2 -2
- package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
- package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
- package/dist/modules/billing/api/accounts/route.js +47 -4
- package/dist/modules/billing/api/accounts/route.js.map +2 -2
- package/dist/modules/billing/api/export/account/[id]/route.js +11 -7
- package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
- package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/pdf/route.js +70 -0
- package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
- package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
- package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/add-line/route.js +2 -2
- package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
- package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
- package/dist/modules/billing/api/invoices/edit-line/route.js +2 -2
- package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/post/route.js +2 -2
- package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/remove-line/route.js +2 -2
- package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
- package/dist/modules/billing/api/invoices/route.js +45 -46
- package/dist/modules/billing/api/invoices/route.js.map +2 -2
- package/dist/modules/billing/api/items/bulk/route.js +1 -1
- package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
- package/dist/modules/billing/api/items/change-price/route.js +44 -0
- package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
- package/dist/modules/billing/api/items/route.js +19 -7
- package/dist/modules/billing/api/items/route.js.map +2 -2
- package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
- package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/payment-imports/route.js +57 -0
- package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
- package/dist/modules/billing/api/payments/[id]/route.js +60 -0
- package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/payments/allocate/route.js +35 -0
- package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
- package/dist/modules/billing/api/payments/assign/route.js +36 -0
- package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
- package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
- package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
- package/dist/modules/billing/api/payments/ignore/route.js +35 -0
- package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
- package/dist/modules/billing/api/payments/import/route.js +63 -0
- package/dist/modules/billing/api/payments/import/route.js.map +7 -0
- package/dist/modules/billing/api/payments/restore/route.js +35 -0
- package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
- package/dist/modules/billing/api/payments/route.js +114 -0
- package/dist/modules/billing/api/payments/route.js.map +7 -0
- package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
- package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
- package/dist/modules/billing/api/payments/unassign/route.js +35 -0
- package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
- package/dist/modules/billing/api/run-outcomes/route.js +43 -6
- package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
- package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
- package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
- package/dist/modules/billing/api/runs/[id]/route.js +53 -0
- package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
- package/dist/modules/billing/api/runs/retry-failed/route.js +1 -1
- package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
- package/dist/modules/billing/api/runs/route.js +9 -3
- package/dist/modules/billing/api/runs/route.js.map +2 -2
- package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
- package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
- package/dist/modules/billing/api/settings/route.js +92 -0
- package/dist/modules/billing/api/settings/route.js.map +7 -0
- package/dist/modules/billing/api/statements/route.js +3 -0
- package/dist/modules/billing/api/statements/route.js.map +2 -2
- package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
- package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
- package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
- package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
- package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
- package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
- package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
- package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
- package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
- package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/items/page.js +8 -2
- package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
- package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
- package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/page.js +58 -0
- package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
- package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
- package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
- package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
- package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/runs/page.js +58 -7
- package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
- package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/statements/page.js +1 -1
- package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/topups/page.js +1 -1
- package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
- package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
- package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
- package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
- package/dist/modules/billing/backend/config/billing/page.js +472 -0
- package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
- package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
- package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
- package/dist/modules/billing/cli.js +22 -1
- package/dist/modules/billing/cli.js.map +2 -2
- package/dist/modules/billing/commands/accounts.js +6 -0
- package/dist/modules/billing/commands/accounts.js.map +2 -2
- package/dist/modules/billing/commands/index.js +3 -0
- package/dist/modules/billing/commands/index.js.map +2 -2
- package/dist/modules/billing/commands/invoices.js +269 -167
- package/dist/modules/billing/commands/invoices.js.map +2 -2
- package/dist/modules/billing/commands/items.js +108 -0
- package/dist/modules/billing/commands/items.js.map +2 -2
- package/dist/modules/billing/commands/memos.js +179 -0
- package/dist/modules/billing/commands/memos.js.map +7 -0
- package/dist/modules/billing/commands/payments.js +379 -0
- package/dist/modules/billing/commands/payments.js.map +7 -0
- package/dist/modules/billing/commands/runs.js +151 -4
- package/dist/modules/billing/commands/runs.js.map +2 -2
- package/dist/modules/billing/commands/settings.js +59 -0
- package/dist/modules/billing/commands/settings.js.map +7 -0
- package/dist/modules/billing/commands/topups.js +9 -2
- package/dist/modules/billing/commands/topups.js.map +2 -2
- package/dist/modules/billing/commands/transactions.js +2 -2
- package/dist/modules/billing/commands/transactions.js.map +2 -2
- package/dist/modules/billing/components/AccountForm.js +149 -42
- package/dist/modules/billing/components/AccountForm.js.map +2 -2
- package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
- package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
- package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
- package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
- package/dist/modules/billing/components/AccountPicker.js +56 -0
- package/dist/modules/billing/components/AccountPicker.js.map +7 -0
- package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
- package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
- package/dist/modules/billing/components/BillingDataTable.js +21 -0
- package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
- package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
- package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
- package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
- package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
- package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
- package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
- package/dist/modules/billing/components/CustomerLabel.js +33 -0
- package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
- package/dist/modules/billing/components/InvoicesTable.js +270 -0
- package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
- package/dist/modules/billing/components/ItemForm.js +176 -44
- package/dist/modules/billing/components/ItemForm.js.map +2 -2
- package/dist/modules/billing/components/ItemPriceField.js +46 -0
- package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
- package/dist/modules/billing/components/LineFormDialog.js +25 -2
- package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
- package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
- package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
- package/dist/modules/billing/components/MoneyInput.js +14 -0
- package/dist/modules/billing/components/MoneyInput.js.map +7 -0
- package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
- package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
- package/dist/modules/billing/components/PaymentsTable.js +171 -0
- package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
- package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
- package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
- package/dist/modules/billing/components/PrepaidPanel.js +14 -4
- package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
- package/dist/modules/billing/components/RunCyclesSection.js +209 -0
- package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
- package/dist/modules/billing/components/TestRunActions.js +127 -0
- package/dist/modules/billing/components/TestRunActions.js.map +7 -0
- package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
- package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
- package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
- package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
- package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
- package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
- package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
- package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
- package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
- package/dist/modules/billing/components/account/AccountView.js +73 -0
- package/dist/modules/billing/components/account/AccountView.js.map +7 -0
- package/dist/modules/billing/components/account/accountRow.js +39 -0
- package/dist/modules/billing/components/account/accountRow.js.map +7 -0
- package/dist/modules/billing/components/customerNames.js +42 -0
- package/dist/modules/billing/components/customerNames.js.map +7 -0
- package/dist/modules/billing/data/entities.js +560 -4
- package/dist/modules/billing/data/entities.js.map +2 -2
- package/dist/modules/billing/data/entityIds.js +10 -1
- package/dist/modules/billing/data/entityIds.js.map +2 -2
- package/dist/modules/billing/data/validators.js +269 -15
- package/dist/modules/billing/data/validators.js.map +2 -2
- package/dist/modules/billing/events.js +50 -0
- package/dist/modules/billing/events.js.map +2 -2
- package/dist/modules/billing/i18n/de.json +880 -0
- package/dist/modules/billing/i18n/en.json +457 -21
- package/dist/modules/billing/i18n/es.json +880 -0
- package/dist/modules/billing/i18n/pl.json +458 -22
- package/dist/modules/billing/index.js +6 -2
- package/dist/modules/billing/index.js.map +2 -2
- package/dist/modules/billing/lib/accountFinancials.js +58 -0
- package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
- package/dist/modules/billing/lib/accountLock.js +14 -0
- package/dist/modules/billing/lib/accountLock.js.map +7 -0
- package/dist/modules/billing/lib/accountStatus.js +9 -7
- package/dist/modules/billing/lib/accountStatus.js.map +2 -2
- package/dist/modules/billing/lib/accountSummary.js +106 -0
- package/dist/modules/billing/lib/accountSummary.js.map +7 -0
- package/dist/modules/billing/lib/allocation.js +356 -0
- package/dist/modules/billing/lib/allocation.js.map +7 -0
- package/dist/modules/billing/lib/allocationEvents.js +22 -0
- package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
- package/dist/modules/billing/lib/billPeriod.js +15 -1
- package/dist/modules/billing/lib/billPeriod.js.map +2 -2
- package/dist/modules/billing/lib/billRunEngine.js +237 -102
- package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
- package/dist/modules/billing/lib/billingSettings.js +116 -0
- package/dist/modules/billing/lib/billingSettings.js.map +7 -0
- package/dist/modules/billing/lib/commandRoute.js +81 -0
- package/dist/modules/billing/lib/commandRoute.js.map +7 -0
- package/dist/modules/billing/lib/customerLookup.js +97 -0
- package/dist/modules/billing/lib/customerLookup.js.map +7 -0
- package/dist/modules/billing/lib/dateOnly.js +13 -0
- package/dist/modules/billing/lib/dateOnly.js.map +7 -0
- package/dist/modules/billing/lib/documentParties.js +16 -0
- package/dist/modules/billing/lib/documentParties.js.map +7 -0
- package/dist/modules/billing/lib/documentTypes.js +27 -0
- package/dist/modules/billing/lib/documentTypes.js.map +7 -0
- package/dist/modules/billing/lib/dueDate.js +18 -0
- package/dist/modules/billing/lib/dueDate.js.map +7 -0
- package/dist/modules/billing/lib/iban.js +29 -0
- package/dist/modules/billing/lib/iban.js.map +7 -0
- package/dist/modules/billing/lib/invoiceDelivery.js +133 -0
- package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
- package/dist/modules/billing/lib/invoiceLanguages.js +28 -0
- package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
- package/dist/modules/billing/lib/invoiceWriter.js +107 -99
- package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
- package/dist/modules/billing/lib/itemBillingState.js +27 -0
- package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
- package/dist/modules/billing/lib/itemPriceChange.js +18 -0
- package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
- package/dist/modules/billing/lib/itemSelector.js.map +2 -2
- package/dist/modules/billing/lib/lineAmounts.js +32 -0
- package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
- package/dist/modules/billing/lib/memoGuards.js +25 -0
- package/dist/modules/billing/lib/memoGuards.js.map +7 -0
- package/dist/modules/billing/lib/navigation.js +48 -0
- package/dist/modules/billing/lib/navigation.js.map +7 -0
- package/dist/modules/billing/lib/numbering.js +120 -0
- package/dist/modules/billing/lib/numbering.js.map +7 -0
- package/dist/modules/billing/lib/overdue.js +36 -0
- package/dist/modules/billing/lib/overdue.js.map +7 -0
- package/dist/modules/billing/lib/paymentCsv.js +125 -0
- package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
- package/dist/modules/billing/lib/paymentGateway.js +15 -0
- package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
- package/dist/modules/billing/lib/paymentImport.js +164 -0
- package/dist/modules/billing/lib/paymentImport.js.map +7 -0
- package/dist/modules/billing/lib/paymentMatcher.js +36 -0
- package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
- package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
- package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
- package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
- package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js +276 -0
- package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
- package/dist/modules/billing/lib/prepaidConfig.js +17 -25
- package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
- package/dist/modules/billing/lib/prepaidConsume.js +26 -7
- package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
- package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
- package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
- package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
- package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
- package/dist/modules/billing/lib/seeds/demo.js +51 -3
- package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
- package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
- package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
- package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
- package/dist/modules/billing/lib/servicePeriods.js +121 -0
- package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
- package/dist/modules/billing/lib/testRunCleanup.js +24 -0
- package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
- package/dist/modules/billing/lib/topupCapture.js +2 -2
- package/dist/modules/billing/lib/topupCapture.js.map +2 -2
- package/dist/modules/billing/lib/topupReceipt.js +58 -79
- package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
- package/dist/modules/billing/lib/usageRater.js.map +1 -1
- package/dist/modules/billing/lib/usageRunner.js +14 -6
- package/dist/modules/billing/lib/usageRunner.js.map +2 -2
- package/dist/modules/billing/lib/vat.js +39 -0
- package/dist/modules/billing/lib/vat.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
- package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
- package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
- package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
- package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
- package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
- package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
- package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
- package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
- package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
- package/dist/modules/billing/setup.js +7 -184
- package/dist/modules/billing/setup.js.map +2 -2
- package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
- package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
- package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
- package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
- package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
- package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
- package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
- package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
- package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
- package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
- package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
- package/generated/entities/billing_account/index.ts +3 -0
- package/generated/entities/billing_allocation/index.ts +18 -0
- package/generated/entities/billing_document_sequence/index.ts +11 -0
- package/generated/entities/billing_invoice/index.ts +38 -0
- package/generated/entities/billing_invoice_line/index.ts +24 -0
- package/generated/entities/billing_item/index.ts +4 -0
- package/generated/entities/billing_payment/index.ts +25 -0
- package/generated/entities/billing_payment_import/index.ts +14 -0
- package/generated/entities/billing_run/index.ts +0 -1
- package/generated/entities/billing_run_outcome/index.ts +1 -0
- package/generated/entities/billing_settings/index.ts +26 -0
- package/generated/entities/billing_statement/index.ts +1 -0
- package/generated/entities.ids.generated.ts +7 -0
- package/package.json +5 -1
- package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +171 -0
- package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
- package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
- package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
- package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
- package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
- package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
- package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
- package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
- package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
- package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
- package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
- package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
- package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
- package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
- package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
- package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
- package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
- package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
- package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
- package/src/modules/billing/__tests__/i18n/dictionaries.test.ts +64 -0
- package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
- package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
- package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
- package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
- package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
- package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
- package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
- package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
- package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +206 -0
- package/src/modules/billing/__tests__/lib/invoiceLanguages.test.ts +49 -0
- package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
- package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
- package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
- package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
- package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
- package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
- package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
- package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
- package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
- package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +269 -0
- package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
- package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
- package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
- package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
- package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
- package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
- package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
- package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
- package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
- package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
- package/src/modules/billing/__tests__/setup.test.ts +13 -4
- package/src/modules/billing/__tests__/validators.test.ts +197 -3
- package/src/modules/billing/acl.ts +12 -0
- package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
- package/src/modules/billing/api/accounts/route.ts +59 -4
- package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
- package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
- package/src/modules/billing/api/invoices/[id]/pdf/route.ts +80 -0
- package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
- package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
- package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
- package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
- package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
- package/src/modules/billing/api/invoices/post/route.ts +7 -6
- package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
- package/src/modules/billing/api/invoices/route.ts +81 -86
- package/src/modules/billing/api/items/bulk/route.ts +2 -1
- package/src/modules/billing/api/items/change-price/route.ts +54 -0
- package/src/modules/billing/api/items/route.ts +23 -7
- package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
- package/src/modules/billing/api/payment-imports/route.ts +57 -0
- package/src/modules/billing/api/payments/[id]/route.ts +63 -0
- package/src/modules/billing/api/payments/allocate/route.ts +35 -0
- package/src/modules/billing/api/payments/assign/route.ts +36 -0
- package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
- package/src/modules/billing/api/payments/ignore/route.ts +35 -0
- package/src/modules/billing/api/payments/import/route.ts +72 -0
- package/src/modules/billing/api/payments/restore/route.ts +35 -0
- package/src/modules/billing/api/payments/route.ts +147 -0
- package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
- package/src/modules/billing/api/payments/unassign/route.ts +35 -0
- package/src/modules/billing/api/run-outcomes/route.ts +49 -6
- package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
- package/src/modules/billing/api/runs/[id]/route.ts +59 -0
- package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
- package/src/modules/billing/api/runs/route.ts +15 -7
- package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
- package/src/modules/billing/api/settings/route.ts +116 -0
- package/src/modules/billing/api/statements/route.ts +5 -0
- package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
- package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
- package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
- package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
- package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
- package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
- package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
- package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
- package/src/modules/billing/backend/billing/items/page.tsx +18 -2
- package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
- package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
- package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
- package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
- package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
- package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
- package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
- package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
- package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
- package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
- package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
- package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
- package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
- package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
- package/src/modules/billing/backend/config/billing/page.tsx +549 -0
- package/src/modules/billing/cli.ts +31 -1
- package/src/modules/billing/commands/accounts.ts +6 -0
- package/src/modules/billing/commands/index.ts +3 -0
- package/src/modules/billing/commands/invoices.ts +364 -231
- package/src/modules/billing/commands/items.ts +143 -0
- package/src/modules/billing/commands/memos.ts +218 -0
- package/src/modules/billing/commands/payments.ts +428 -0
- package/src/modules/billing/commands/runs.ts +205 -3
- package/src/modules/billing/commands/settings.ts +77 -0
- package/src/modules/billing/commands/topups.ts +12 -3
- package/src/modules/billing/commands/transactions.ts +2 -2
- package/src/modules/billing/components/AccountForm.tsx +199 -53
- package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
- package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
- package/src/modules/billing/components/AccountPicker.tsx +70 -0
- package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
- package/src/modules/billing/components/BillingDataTable.tsx +34 -0
- package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
- package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
- package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
- package/src/modules/billing/components/CustomerLabel.tsx +53 -0
- package/src/modules/billing/components/InvoicesTable.tsx +344 -0
- package/src/modules/billing/components/ItemForm.tsx +233 -44
- package/src/modules/billing/components/ItemPriceField.tsx +81 -0
- package/src/modules/billing/components/LineFormDialog.tsx +25 -1
- package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
- package/src/modules/billing/components/MoneyInput.tsx +26 -0
- package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
- package/src/modules/billing/components/PaymentsTable.tsx +213 -0
- package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
- package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
- package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
- package/src/modules/billing/components/TestRunActions.tsx +152 -0
- package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
- package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
- package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
- package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
- package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
- package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
- package/src/modules/billing/components/account/AccountView.tsx +101 -0
- package/src/modules/billing/components/account/accountRow.ts +70 -0
- package/src/modules/billing/components/customerNames.ts +62 -0
- package/src/modules/billing/data/entities.ts +656 -6
- package/src/modules/billing/data/entityIds.ts +9 -0
- package/src/modules/billing/data/validators.ts +388 -8
- package/src/modules/billing/events.ts +56 -3
- package/src/modules/billing/i18n/de.json +880 -0
- package/src/modules/billing/i18n/en.json +457 -21
- package/src/modules/billing/i18n/es.json +880 -0
- package/src/modules/billing/i18n/pl.json +458 -22
- package/src/modules/billing/index.ts +6 -2
- package/src/modules/billing/lib/accountFinancials.ts +84 -0
- package/src/modules/billing/lib/accountLock.ts +32 -0
- package/src/modules/billing/lib/accountStatus.ts +11 -9
- package/src/modules/billing/lib/accountSummary.ts +172 -0
- package/src/modules/billing/lib/allocation.ts +515 -0
- package/src/modules/billing/lib/allocationEvents.ts +26 -0
- package/src/modules/billing/lib/billPeriod.ts +19 -0
- package/src/modules/billing/lib/billRunEngine.ts +350 -150
- package/src/modules/billing/lib/billingSettings.ts +145 -0
- package/src/modules/billing/lib/commandRoute.ts +112 -0
- package/src/modules/billing/lib/customerLookup.ts +157 -0
- package/src/modules/billing/lib/dateOnly.ts +22 -0
- package/src/modules/billing/lib/documentParties.ts +23 -0
- package/src/modules/billing/lib/documentTypes.ts +37 -0
- package/src/modules/billing/lib/dueDate.ts +26 -0
- package/src/modules/billing/lib/iban.ts +36 -0
- package/src/modules/billing/lib/invoiceDelivery.ts +196 -0
- package/src/modules/billing/lib/invoiceLanguages.ts +57 -0
- package/src/modules/billing/lib/invoiceWriter.ts +162 -148
- package/src/modules/billing/lib/itemBillingState.ts +67 -0
- package/src/modules/billing/lib/itemPriceChange.ts +56 -0
- package/src/modules/billing/lib/itemSelector.ts +4 -1
- package/src/modules/billing/lib/lineAmounts.ts +61 -0
- package/src/modules/billing/lib/memoGuards.ts +41 -0
- package/src/modules/billing/lib/navigation.ts +64 -0
- package/src/modules/billing/lib/numbering.ts +174 -0
- package/src/modules/billing/lib/overdue.ts +60 -0
- package/src/modules/billing/lib/paymentCsv.ts +171 -0
- package/src/modules/billing/lib/paymentGateway.ts +52 -0
- package/src/modules/billing/lib/paymentImport.ts +228 -0
- package/src/modules/billing/lib/paymentMatcher.ts +88 -0
- package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
- package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
- package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
- package/src/modules/billing/lib/pdf/invoiceModel.ts +329 -0
- package/src/modules/billing/lib/pdf/invoicePdf.ts +220 -0
- package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
- package/src/modules/billing/lib/prepaidConfig.ts +27 -36
- package/src/modules/billing/lib/prepaidConsume.ts +29 -6
- package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
- package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
- package/src/modules/billing/lib/seeds/demo.ts +79 -6
- package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
- package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
- package/src/modules/billing/lib/servicePeriods.ts +226 -0
- package/src/modules/billing/lib/testRunCleanup.ts +42 -0
- package/src/modules/billing/lib/topupCapture.ts +3 -4
- package/src/modules/billing/lib/topupReceipt.ts +70 -103
- package/src/modules/billing/lib/usageRater.ts +1 -1
- package/src/modules/billing/lib/usageRunner.ts +17 -5
- package/src/modules/billing/lib/vat.ts +56 -0
- package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
- package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
- package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
- package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
- package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
- package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
- package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
- package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
- package/src/modules/billing/setup.ts +11 -237
- package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
- package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
- package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
- package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
- package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
- package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
- package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
- package/dist/modules/billing/lib/invoiceStatus.js +0 -38
- package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
- package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
- package/src/modules/billing/lib/invoiceStatus.ts +0 -61
|
@@ -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 { billingInvoiceRemoveLineSchema } 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",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/billing/api/invoices/remove-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 { billingInvoiceRemoveLineSchema } 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 removeLineResponseSchema = 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.remove_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceRemoveLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineRemove',\n pluralName: 'BillingInvoiceLineRemoves',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceRemoveLineSchema,\n responseSchema: removeLineResponseSchema,\n description:\n 'Removes one line from a draft invoice (hard delete \u2014 the line ' +\n 'never existed as far as future readers are concerned). Writes ' +\n 'a `BillingDraftInvoiceEdit` audit row (action=`line_removed`, ' +\n '`after_json=null`) carrying the before-snapshot for forensics. ' +\n 'Recomputes invoice 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,sCAAsC;AAC/C,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,2BAA2B,EAAE,OAAO;AAAA,EACxC,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,+BAA+B,MAAM,MAAM;AAAA,MACpD;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,EAKJ;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { z } from "zod";
|
|
3
2
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { billingInvoiceListQuerySchema } from "../../data/validators.js";
|
|
5
|
+
import { daysOverdueSql, isoDay, overdueSql } from "../../lib/overdue.js";
|
|
5
6
|
const metadata = {
|
|
6
7
|
GET: { requireAuth: true, requireFeatures: ["billing.invoice.view"] }
|
|
7
8
|
};
|
|
8
|
-
const querySchema = z.object({
|
|
9
|
-
page: z.coerce.number().int().min(1).default(1),
|
|
10
|
-
pageSize: z.coerce.number().int().min(1).max(100).default(50),
|
|
11
|
-
status: z.enum(["draft", "posted", "paid", "void"]).optional(),
|
|
12
|
-
billRunId: z.string().uuid().optional(),
|
|
13
|
-
billAccountId: z.string().uuid().optional(),
|
|
14
|
-
testRun: z.coerce.boolean().optional(),
|
|
15
|
-
search: z.string().optional()
|
|
16
|
-
}).passthrough();
|
|
17
9
|
async function GET(req) {
|
|
18
10
|
try {
|
|
19
11
|
const container = await createRequestContainer();
|
|
@@ -29,56 +21,63 @@ async function GET(req) {
|
|
|
29
21
|
);
|
|
30
22
|
}
|
|
31
23
|
const url = new URL(req.url);
|
|
32
|
-
const query =
|
|
33
|
-
Object.fromEntries(url.searchParams.entries())
|
|
34
|
-
);
|
|
24
|
+
const query = billingInvoiceListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()));
|
|
35
25
|
const em = container.resolve("em").fork();
|
|
36
|
-
const whereClauses = [
|
|
37
|
-
"tenant_id = ?",
|
|
38
|
-
"organization_id = ?",
|
|
39
|
-
"deleted_at IS NULL",
|
|
40
|
-
"jsonb_exists(metadata, 'bill_run_id')"
|
|
41
|
-
];
|
|
26
|
+
const whereClauses = ["i.tenant_id = ?", "i.organization_id = ?", "i.deleted_at IS NULL"];
|
|
42
27
|
const params = [auth.tenantId, organizationId];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
28
|
+
const filters = [
|
|
29
|
+
[query.id, "i.id = ?"],
|
|
30
|
+
[query.status, "i.status = ?"],
|
|
31
|
+
[query.documentType, "i.document_type = ?"],
|
|
32
|
+
[query.billRunId, "i.bill_run_id = ?"],
|
|
33
|
+
[query.billAccountId, "i.bill_account_id = ?"],
|
|
34
|
+
[query.testRun, "i.test_run = ?"]
|
|
35
|
+
];
|
|
36
|
+
for (const [value, clause] of filters) {
|
|
37
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
38
|
+
whereClauses.push(clause);
|
|
39
|
+
params.push(value);
|
|
46
40
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
const today = isoDay(/* @__PURE__ */ new Date());
|
|
42
|
+
if (query.overdue === true) {
|
|
43
|
+
whereClauses.push(overdueSql("i"));
|
|
44
|
+
params.push(today);
|
|
45
|
+
} else if (query.overdue === false) {
|
|
46
|
+
whereClauses.push(`NOT COALESCE(${overdueSql("i")}, false)`);
|
|
47
|
+
params.push(today);
|
|
50
48
|
}
|
|
51
|
-
if (query.
|
|
52
|
-
whereClauses.push("
|
|
53
|
-
params.push(query.
|
|
49
|
+
if (query.dueFrom) {
|
|
50
|
+
whereClauses.push("i.due_date >= ?::date");
|
|
51
|
+
params.push(query.dueFrom);
|
|
54
52
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} else {
|
|
59
|
-
whereClauses.push("(metadata->>'test_run' IS NULL OR metadata->>'test_run' != 'true')");
|
|
60
|
-
}
|
|
53
|
+
if (query.dueTo) {
|
|
54
|
+
whereClauses.push("i.due_date <= ?::date");
|
|
55
|
+
params.push(query.dueTo);
|
|
61
56
|
}
|
|
62
|
-
if (
|
|
63
|
-
whereClauses.push("
|
|
64
|
-
params.push(`%${query.search.
|
|
57
|
+
if (query.search) {
|
|
58
|
+
whereClauses.push("i.number ILIKE ?");
|
|
59
|
+
params.push(`%${query.search.replace(/[%_\\]/g, (m) => `\\${m}`)}%`);
|
|
65
60
|
}
|
|
66
61
|
const whereSql = whereClauses.join(" AND ");
|
|
67
62
|
const countRows = await em.execute(
|
|
68
|
-
`SELECT COUNT(*)::int AS total FROM
|
|
63
|
+
`SELECT COUNT(*)::int AS total FROM billing_invoices i WHERE ${whereSql}`,
|
|
69
64
|
params
|
|
70
65
|
);
|
|
71
66
|
const total = countRows?.[0]?.total ?? 0;
|
|
72
67
|
const offset = (query.page - 1) * query.pageSize;
|
|
73
68
|
const itemRows = await em.execute(
|
|
74
|
-
`SELECT id,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
`SELECT i.id, i.number, i.document_type, i.status, i.test_run, i.bill_account_id,
|
|
70
|
+
a.name AS bill_account_name, i.bill_run_id, i.bill_date, i.period_start, i.period_end,
|
|
71
|
+
i.issue_date, i.due_date, i.currency_code, i.subtotal_net, i.tax_total, i.total_gross,
|
|
72
|
+
i.paid_total, i.credited_total, i.outstanding, i.corrects_invoice_id, i.corrects_invoice_number, i.topup_id,
|
|
73
|
+
${daysOverdueSql("i")}::int AS days_overdue,
|
|
74
|
+
i.created_at, i.updated_at
|
|
75
|
+
FROM billing_invoices i
|
|
76
|
+
LEFT JOIN billing_accounts a ON a.id = i.bill_account_id AND a.tenant_id = i.tenant_id
|
|
78
77
|
WHERE ${whereSql}
|
|
79
|
-
ORDER BY created_at DESC
|
|
78
|
+
ORDER BY i.created_at DESC
|
|
80
79
|
LIMIT ? OFFSET ?`,
|
|
81
|
-
[...params, query.pageSize, offset]
|
|
80
|
+
[today, today, ...params, query.pageSize, offset]
|
|
82
81
|
);
|
|
83
82
|
return NextResponse.json({
|
|
84
83
|
items: itemRows,
|
|
@@ -96,8 +95,8 @@ const openApi = {
|
|
|
96
95
|
methods: {
|
|
97
96
|
GET: {
|
|
98
97
|
tags: ["Billing"],
|
|
99
|
-
summary: "List billing
|
|
100
|
-
description: "
|
|
98
|
+
summary: "List billing documents",
|
|
99
|
+
description: "Paginated billing documents (invoices, credit / debit memos, top-up receipts) from `billing_invoices`. Filters: status, documentType, billRunId, billAccountId, testRun (`true`/`false`), id, number search, `overdue` (`true`/`false` \u2014 posted or partially paid, past due, outstanding > 0), `dueFrom` / `dueTo` (YYYY-MM-DD). Each row adds `days_overdue`. Fields use billing names: `number`, `document_type`, `total_gross`, `outstanding`, `bill_date`, `period_start/end`, `bill_account_name`, \u2026 (0.21.0 renamed them from the core-sales names \u2014 see MIGRATION.md)."
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/billing/api/invoices/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,SAAS;
|
|
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 { billingInvoiceListQuerySchema } from '../../data/validators'\nimport { daysOverdueSql, isoDay, overdueSql } from '../../lib/overdue'\n\n/**\n * `GET /api/billing/invoices` \u2014 paginated list of billing documents\n * (Spec 0: billing-owned `billing_invoices`, never core `sales`).\n *\n * Filters (all on indexed columns): `status`, `documentType`, `billRunId`,\n * `billAccountId`, `testRun` (`true` / `false`), `id`, `search` (number).\n * Spec B: `overdue=true` (computed, B-D6 \u2014 see `lib/overdue.ts`), `dueFrom` /\n * `dueTo` (due-date window). Each row carries `bill_account_name` (joined)\n * and `days_overdue` (0 when not overdue).\n *\n * Hand-written SQL instead of the CRUD factory: documents are written by the\n * bill-run engine without query-index side effects, so the factory's\n * query-engine path would not see them. Sort: `created_at DESC`.\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.invoice.view'] },\n}\n\ntype BillingInvoiceRow = {\n id: string\n number: string\n document_type: string\n status: string\n test_run: boolean\n bill_account_id: string\n bill_account_name: string | null\n bill_run_id: string | null\n bill_date: string | null\n period_start: string | null\n period_end: string | null\n issue_date: string | null\n due_date: string | null\n currency_code: string\n subtotal_net: string\n tax_total: string\n total_gross: string\n paid_total: string\n credited_total: string\n outstanding: string\n days_overdue: number\n corrects_invoice_id: string | null\n topup_id: string | null\n created_at: string\n updated_at: string\n}\n\nexport async function GET(req: Request) {\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 url = new URL(req.url)\n const query = billingInvoiceListQuerySchema.parse(Object.fromEntries(url.searchParams.entries()))\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n const whereClauses: string[] = ['i.tenant_id = ?', 'i.organization_id = ?', 'i.deleted_at IS NULL']\n const params: unknown[] = [auth.tenantId, organizationId]\n const filters: Array<[unknown, string]> = [\n [query.id, 'i.id = ?'],\n [query.status, 'i.status = ?'],\n [query.documentType, 'i.document_type = ?'],\n [query.billRunId, 'i.bill_run_id = ?'],\n [query.billAccountId, 'i.bill_account_id = ?'],\n [query.testRun, 'i.test_run = ?'],\n ]\n for (const [value, clause] of filters) {\n if (value === undefined || value === null || value === '') continue\n whereClauses.push(clause)\n params.push(value)\n }\n const today = isoDay(new Date())\n if (query.overdue === true) {\n whereClauses.push(overdueSql('i'))\n params.push(today)\n } else if (query.overdue === false) {\n whereClauses.push(`NOT COALESCE(${overdueSql(\"i\")}, false)`)\n params.push(today)\n }\n if (query.dueFrom) {\n whereClauses.push('i.due_date >= ?::date')\n params.push(query.dueFrom)\n }\n if (query.dueTo) {\n whereClauses.push('i.due_date <= ?::date')\n params.push(query.dueTo)\n }\n if (query.search) {\n whereClauses.push('i.number ILIKE ?')\n params.push(`%${query.search.replace(/[%_\\\\]/g, (m) => `\\\\${m}`)}%`)\n }\n\n const whereSql = whereClauses.join(' AND ')\n const countRows = (await em.execute(\n `SELECT COUNT(*)::int AS total FROM billing_invoices i WHERE ${whereSql}`,\n params,\n )) as unknown as Array<{ total: number }>\n const total = countRows?.[0]?.total ?? 0\n\n const offset = (query.page - 1) * query.pageSize\n const itemRows = (await em.execute(\n `SELECT i.id, i.number, i.document_type, 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.currency_code, i.subtotal_net, i.tax_total, i.total_gross,\n i.paid_total, i.credited_total, i.outstanding, i.corrects_invoice_id, i.corrects_invoice_number, i.topup_id,\n ${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 ${whereSql}\n ORDER BY i.created_at DESC\n LIMIT ? OFFSET ?`,\n [today, today, ...params, query.pageSize, offset],\n )) as unknown as BillingInvoiceRow[]\n\n return NextResponse.json({\n items: itemRows,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: total === 0 ? 1 : Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n console.error('[billing/invoices.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: 'List billing documents',\n description:\n 'Paginated billing documents (invoices, credit / debit memos, top-up receipts) from ' +\n '`billing_invoices`. Filters: status, documentType, billRunId, billAccountId, testRun ' +\n '(`true`/`false`), id, number search, `overdue` (`true`/`false` \u2014 posted or partially paid, ' +\n 'past due, outstanding > 0), `dueFrom` / `dueTo` (YYYY-MM-DD). Each row adds `days_overdue`. ' +\n 'Fields use billing names: `number`, `document_type`, ' +\n '`total_gross`, `outstanding`, `bill_date`, `period_start/end`, `bill_account_name`, \u2026 ' +\n '(0.21.0 renamed them from the core-sales names \u2014 see MIGRATION.md).',\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -63,7 +63,7 @@ const openApi = createBillingCrudOpenApi({
|
|
|
63
63
|
create: {
|
|
64
64
|
schema: billingItemBulkCreateSchema,
|
|
65
65
|
responseSchema: bulkResponseSchema,
|
|
66
|
-
description: "Batch-creates up to 500 Billing Items under one scope envelope. Idempotent per `sourceRef` (one batched existence check). Returns `{ created, deduplicated, items: [{ sourceRef, id, deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported in the body, never an error."
|
|
66
|
+
description: "Batch-creates up to 500 Billing Items under one scope envelope. Idempotent per `sourceRef` (one batched existence check). Returns `{ created, deduplicated, items: [{ sourceRef, id, deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported in the body, never an error. Entries accept `chargeTiming` / `prorate` (recurring only; defaults `in_advance` / true)."
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/billing/api/items/bulk/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 { BillingItem } from '../../../data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingItemBulkCreateSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/items/bulk` \u2014 batch-create Billing Items.\n *\n * One scope envelope, up to 500 item payloads. Idempotent per\n * `source_ref` with a *single* batched existence check, then a\n * single flush. The CPQ connector's subscription-activated /\n * -amended / -renewed subscribers are the primary callers \u2014 bulk\n * collapses what used to be one `billing.items.create` round-trip\n * per charge.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst bulkResultEntrySchema = z.object({\n sourceRef: z.string().nullable(),\n id: z.string().uuid(),\n deduplicated: z.boolean(),\n})\n\nconst bulkResponseSchema = z.object({\n created: z.number().int().nonnegative(),\n deduplicated: z.number().int().nonnegative(),\n items: z.array(bulkResultEntrySchema),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.item,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.items.bulk_create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemBulkCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n created: result?.created ?? 0,\n deduplicated: result?.deduplicated ?? 0,\n items: result?.items ?? [],\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItemBulkCreate',\n pluralName: 'BillingItemBulkCreates',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingItemBulkCreateSchema,\n responseSchema: bulkResponseSchema,\n description:\n 'Batch-creates up to 500 Billing Items under one scope envelope. ' +\n 'Idempotent per `sourceRef` (one batched existence check). ' +\n 'Returns `{ created, deduplicated, items: [{ sourceRef, id, ' +\n 'deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported ' +\n 'in the body, never an error.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAazC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACtE;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,cAAc,EAAE,QAAQ;AAC1B,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,OAAO,EAAE,MAAM,qBAAqB;AACtC,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,SAAS,QAAQ,WAAW;AAAA,QAC5B,cAAc,QAAQ,gBAAgB;AAAA,QACtC,OAAO,QAAQ,SAAS,CAAC;AAAA,MAC3B;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,
|
|
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 { BillingItem } from '../../../data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingItemBulkCreateSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/items/bulk` \u2014 batch-create Billing Items.\n *\n * One scope envelope, up to 500 item payloads. Idempotent per\n * `source_ref` with a *single* batched existence check, then a\n * single flush. The CPQ connector's subscription-activated /\n * -amended / -renewed subscribers are the primary callers \u2014 bulk\n * collapses what used to be one `billing.items.create` round-trip\n * per charge.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst bulkResultEntrySchema = z.object({\n sourceRef: z.string().nullable(),\n id: z.string().uuid(),\n deduplicated: z.boolean(),\n})\n\nconst bulkResponseSchema = z.object({\n created: z.number().int().nonnegative(),\n deduplicated: z.number().int().nonnegative(),\n items: z.array(bulkResultEntrySchema),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.item,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.items.bulk_create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemBulkCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n created: result?.created ?? 0,\n deduplicated: result?.deduplicated ?? 0,\n items: result?.items ?? [],\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItemBulkCreate',\n pluralName: 'BillingItemBulkCreates',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingItemBulkCreateSchema,\n responseSchema: bulkResponseSchema,\n description:\n 'Batch-creates up to 500 Billing Items under one scope envelope. ' +\n 'Idempotent per `sourceRef` (one batched existence check). ' +\n 'Returns `{ created, deduplicated, items: [{ sourceRef, id, ' +\n 'deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported ' +\n 'in the body, never an error. Entries accept `chargeTiming` / `prorate` ' +\n '(recurring only; defaults `in_advance` / true).',\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAazC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACtE;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,cAAc,EAAE,QAAQ;AAC1B,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,OAAO,EAAE,MAAM,qBAAqB;AACtC,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,SAAS,QAAQ,WAAW;AAAA,QAC5B,cAAc,QAAQ,gBAAgB;AAAA,QACtC,OAAO,QAAQ,SAAS,CAAC;AAAA,MAC3B;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,EAMJ;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommandRoute } from "../../../lib/commandRoute.js";
|
|
3
|
+
const metadata = {
|
|
4
|
+
POST: { requireAuth: true, requireFeatures: ["billing.item.manage"] }
|
|
5
|
+
};
|
|
6
|
+
const bodySchema = z.object({
|
|
7
|
+
id: z.string().uuid(),
|
|
8
|
+
effectiveDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
9
|
+
unitPrice: z.coerce.number().finite().nonnegative()
|
|
10
|
+
});
|
|
11
|
+
const responseSchema = z.object({ previousItemId: z.string().uuid(), newItemId: z.string().uuid() });
|
|
12
|
+
async function POST(req) {
|
|
13
|
+
return runCommandRoute(req, {
|
|
14
|
+
commandId: "billing.items.change_price",
|
|
15
|
+
bodySchema,
|
|
16
|
+
resourceKind: "billing.item",
|
|
17
|
+
resourceId: (body) => body.id,
|
|
18
|
+
logLabel: "billing/items/change-price.POST"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const openApi = {
|
|
22
|
+
methods: {
|
|
23
|
+
POST: {
|
|
24
|
+
tags: ["Billing"],
|
|
25
|
+
summary: "Change a recurring item's price from a date",
|
|
26
|
+
description: "Ends the item the day before `effectiveDate` and creates a copy with the new `unitPrice` (on the item price basis) starting on `effectiveDate`. Already-billed days after the change are credited / re-charged by the Bill Run per the item's proration setting, so nothing is billed twice.",
|
|
27
|
+
requestBody: { schema: bodySchema },
|
|
28
|
+
responses: [{ status: 200, description: "Both item ids", schema: responseSchema }],
|
|
29
|
+
errors: [
|
|
30
|
+
{ status: 404, description: "Item not found" },
|
|
31
|
+
{
|
|
32
|
+
status: 422,
|
|
33
|
+
description: "`billing.item.price_change_recurring_only` / `_inactive` / `_before_start` (must be after the start date) / `_after_end`"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export {
|
|
40
|
+
POST,
|
|
41
|
+
metadata,
|
|
42
|
+
openApi
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/items/change-price/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { runCommandRoute } from '../../../lib/commandRoute'\n\n/**\n * `POST /api/billing/items/change-price` \u2014 change a recurring item's price\n * from a date on: the item ends the day before `effectiveDate` and a copy with\n * `unitPrice` starts on it (same account, description, timing, proration,\n * VAT, price basis, references and end date). Returns both item ids.\n */\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nconst bodySchema = z.object({\n id: z.string().uuid(),\n effectiveDate: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/),\n unitPrice: z.coerce.number().finite().nonnegative(),\n})\n\nconst responseSchema = z.object({ previousItemId: z.string().uuid(), newItemId: z.string().uuid() })\n\nexport async function POST(req: Request) {\n return runCommandRoute(req, {\n commandId: 'billing.items.change_price',\n bodySchema,\n resourceKind: 'billing.item',\n resourceId: (body) => body.id,\n logLabel: 'billing/items/change-price.POST',\n })\n}\n\nexport const openApi = {\n methods: {\n POST: {\n tags: ['Billing'],\n summary: \"Change a recurring item's price from a date\",\n description:\n 'Ends the item the day before `effectiveDate` and creates a copy with the new `unitPrice` (on the item price basis) ' +\n 'starting on `effectiveDate`. Already-billed days after the change are credited / re-charged by the Bill Run per the ' +\n \"item's proration setting, so nothing is billed twice.\",\n requestBody: { schema: bodySchema },\n responses: [{ status: 200, description: 'Both item ids', schema: responseSchema }],\n errors: [\n { status: 404, description: 'Item not found' },\n {\n status: 422,\n description:\n '`billing.item.price_change_recurring_only` / `_inactive` / `_before_start` (must be after the start date) / `_after_end`',\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AASzB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACtE;AAEA,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,eAAe,EAAE,OAAO,EAAE,MAAM,qBAAqB;AAAA,EACrD,WAAW,EAAE,OAAO,OAAO,EAAE,OAAO,EAAE,YAAY;AACpD,CAAC;AAED,MAAM,iBAAiB,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,GAAG,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAEnG,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,aACE;AAAA,MAGF,aAAa,EAAE,QAAQ,WAAW;AAAA,MAClC,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,eAAe,CAAC;AAAA,MACjF,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,iBAAiB;AAAA,QAC7C;AAAA,UACE,QAAQ;AAAA,UACR,aACE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -25,6 +25,9 @@ const billingItemListItemSchema = z.object({
|
|
|
25
25
|
id: z.string().uuid(),
|
|
26
26
|
bill_account_id: z.string().uuid(),
|
|
27
27
|
bill_account_name: z.string().nullable(),
|
|
28
|
+
/** Spec F (F2): the owning account's currency and mode, for the form. */
|
|
29
|
+
bill_account_currency: z.string().nullable(),
|
|
30
|
+
bill_account_mode: z.enum(["postpaid", "prepaid"]).nullable(),
|
|
28
31
|
type: z.string(),
|
|
29
32
|
bill_start_date: z.string(),
|
|
30
33
|
bill_end_date: z.string().nullable(),
|
|
@@ -36,6 +39,10 @@ const billingItemListItemSchema = z.object({
|
|
|
36
39
|
source_ref: z.string().nullable(),
|
|
37
40
|
currency_mismatch: z.boolean(),
|
|
38
41
|
billed_to_date: z.string().nullable(),
|
|
42
|
+
charge_timing: z.enum(["in_advance", "in_arrears"]),
|
|
43
|
+
prorate: z.boolean(),
|
|
44
|
+
tax_rate: z.union([z.string(), z.number()]),
|
|
45
|
+
price_basis: z.enum(["net", "gross"]),
|
|
39
46
|
is_active: z.boolean(),
|
|
40
47
|
organization_id: z.string().uuid(),
|
|
41
48
|
tenant_id: z.string().uuid(),
|
|
@@ -73,6 +80,10 @@ const crud = makeCrudRoute({
|
|
|
73
80
|
"source_ref",
|
|
74
81
|
"currency_mismatch",
|
|
75
82
|
"billed_to_date",
|
|
83
|
+
"charge_timing",
|
|
84
|
+
"prorate",
|
|
85
|
+
"tax_rate",
|
|
86
|
+
"price_basis",
|
|
76
87
|
"is_active",
|
|
77
88
|
"organization_id",
|
|
78
89
|
"tenant_id",
|
|
@@ -169,7 +180,7 @@ const crud = makeCrudRoute({
|
|
|
169
180
|
},
|
|
170
181
|
hooks: {
|
|
171
182
|
// Items reference their account by FK id only; the list + detail UI
|
|
172
|
-
// wants the
|
|
183
|
+
// wants the account name, currency and mode. One bounded query
|
|
173
184
|
// (≤ pageSize ids) resolves it after the page is fetched.
|
|
174
185
|
afterList: async (payload, ctx) => {
|
|
175
186
|
const items = payload?.items ?? [];
|
|
@@ -181,11 +192,12 @@ const crud = makeCrudRoute({
|
|
|
181
192
|
if (accountIds.length === 0) return;
|
|
182
193
|
const em = ctx.container.resolve("em");
|
|
183
194
|
const accounts = await em.find(BillingAccount, { id: { $in: accountIds } });
|
|
184
|
-
const
|
|
185
|
-
accounts.map((a) => [String(a.id), a.name])
|
|
186
|
-
);
|
|
195
|
+
const byId = new Map(accounts.map((a) => [String(a.id), a]));
|
|
187
196
|
for (const row of items) {
|
|
188
|
-
|
|
197
|
+
const account = byId.get(String(row.bill_account_id));
|
|
198
|
+
row.bill_account_name = account?.name ?? null;
|
|
199
|
+
row.bill_account_currency = account?.currencyCode ?? null;
|
|
200
|
+
row.bill_account_mode = account?.billingMode ?? null;
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
203
|
}
|
|
@@ -200,12 +212,12 @@ const openApi = createBillingCrudOpenApi({
|
|
|
200
212
|
create: {
|
|
201
213
|
schema: billingItemCreateSchema,
|
|
202
214
|
responseSchema: itemCreateResponseSchema,
|
|
203
|
-
description: "Creates a billing item. Idempotent via `sourceRef`: re-POSTing the same `(tenantId, billAccountId, sourceRef)` returns the existing row with `deduplicated: true` instead of creating a duplicate. The unique index on `(tenant_id, bill_account_id, source_ref)` is the correctness gate."
|
|
215
|
+
description: "Creates a billing item. Idempotent via `sourceRef`: re-POSTing the same `(tenantId, billAccountId, sourceRef)` returns the existing row with `deduplicated: true` instead of creating a duplicate. The unique index on `(tenant_id, bill_account_id, source_ref)` is the correctness gate. Recurring items accept `chargeTiming` (`in_advance` default | `in_arrears`) and `prorate` (default true); other types reject non-default values (400)."
|
|
204
216
|
},
|
|
205
217
|
update: {
|
|
206
218
|
schema: billingItemUpdateSchema,
|
|
207
219
|
responseSchema: defaultOkResponseSchema,
|
|
208
|
-
description: "Updates a billing item. `type` and `sourceRef` are immutable \u2014 to change either, soft-delete the item and create a new one."
|
|
220
|
+
description: "Updates a billing item. `type` and `sourceRef` are immutable \u2014 to change either, soft-delete the item and create a new one. `chargeTiming` / `prorate` may change at any time on recurring items \u2014 billing resumes from `billed_to_date`, so no day is billed twice."
|
|
209
221
|
},
|
|
210
222
|
del: {
|
|
211
223
|
schema: z.object({ id: z.string().uuid() }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/billing/api/items/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\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 { BillingAccount, BillingItem } from '../../data/entities'\nimport { billingEntityIds } from '../../data/entityIds'\nimport {\n billingItemCreateSchema,\n billingItemListQuerySchema,\n billingItemUpdateSchema,\n} from '../../data/validators'\nimport {\n createBillingCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\n\n// \u2500\u2500\u2500 Auth / RBAC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.item.view'] },\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\n// The list endpoint projects raw column names \u2014 snake_case.\nconst billingItemListItemSchema = z.object({\n id: z.string().uuid(),\n bill_account_id: z.string().uuid(),\n bill_account_name: z.string().nullable(),\n type: z.string(),\n bill_start_date: z.string(),\n bill_end_date: z.string().nullable(),\n description: z.string(),\n rate_json: z.record(z.string(), z.unknown()),\n uom_code: z.string().nullable(),\n subscription_id: z.string().nullable(),\n subscription_item_id: z.string().nullable(),\n source_ref: z.string().nullable(),\n currency_mismatch: z.boolean(),\n billed_to_date: z.string().nullable(),\n is_active: z.boolean(),\n organization_id: z.string().uuid(),\n tenant_id: z.string().uuid(),\n created_at: z.string(),\n updated_at: z.string(),\n})\n\n// Response for create includes `deduplicated` so integrators can tell\n// whether their POST resulted in a new row or hit the idempotency\n// short-circuit on `source_ref`.\nconst itemCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n deduplicated: z.boolean(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: billingItemListQuerySchema,\n entityId: billingEntityIds.item,\n fields: [\n 'id',\n 'bill_account_id',\n 'type',\n 'bill_start_date',\n 'bill_end_date',\n 'description',\n 'rate_json',\n 'uom_code',\n 'subscription_id',\n 'subscription_item_id',\n 'source_ref',\n 'currency_mismatch',\n 'billed_to_date',\n 'is_active',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n billStartDate: 'bill_start_date',\n billEndDate: 'bill_end_date',\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n // `?id=<uuid>` narrows the list to one row \u2014 the detail page\n // reads a single item through this filter.\n if (typeof query.id === 'string' && query.id) {\n filters.id = { $eq: query.id }\n }\n if (typeof query.search === 'string' && query.search.trim()) {\n const pattern = `%${query.search.trim().replace(/[%_]/g, (m) => `\\\\${m}`)}%`\n filters.description = { $ilike: pattern }\n }\n if (typeof query.billAccountId === 'string' && query.billAccountId) {\n filters.bill_account_id = { $eq: query.billAccountId }\n }\n if (typeof query.type === 'string' && query.type) {\n filters.type = { $eq: query.type }\n }\n if (typeof query.subscriptionId === 'string' && query.subscriptionId) {\n filters.subscription_id = { $eq: query.subscriptionId }\n }\n if (typeof query.isActive === 'boolean') {\n filters.is_active = { $eq: query.isActive }\n }\n // `activeAt=<date>` returns items whose validity window covers\n // the supplied date (Bill Run engine uses the same predicate\n // shape internally \u2014 see spec \"Mid-cycle recurring items\").\n if (query.activeAt instanceof Date) {\n filters.bill_start_date = { $lte: query.activeAt }\n filters.$or = [\n { bill_end_date: null },\n { bill_end_date: { $gte: query.activeAt } },\n ]\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'billing.items.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n id: result?.id ?? null,\n deduplicated: Boolean(result?.deduplicated),\n }),\n // Idempotent create returns 200 (vs 201). The spec mandates\n // duplicate POST returns the existing row, never creates a\n // duplicate \u2014 keeping the success status consistent makes\n // client-side handling trivial.\n status: 200,\n },\n update: {\n commandId: 'billing.items.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemUpdateSchema.parse(scoped)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'billing.items.delete',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n // The factory hands delete a `{ body, query }` envelope \u2014 the\n // id may arrive in either, depending on the caller.\n const envelope = (raw ?? {}) as {\n body?: Record<string, unknown>\n query?: Record<string, unknown>\n }\n const body = envelope.body ?? {}\n const query = envelope.query ?? {}\n const scoped = withScopedPayload(body, ctx, translate)\n const id =\n (typeof body.id === 'string' && body.id) ||\n (typeof query.id === 'string' && query.id) ||\n null\n if (!id) {\n throw new Error('id is required')\n }\n return {\n id,\n tenantId: (scoped as { tenantId?: string }).tenantId,\n organizationId: (scoped as { organizationId?: string }).organizationId,\n }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n // Items reference their account by FK id only; the list + detail UI\n // wants the human-readable account name. One bounded query\n // (\u2264 pageSize ids) resolves it after the page is fetched.\n afterList: async (payload: unknown, ctx: unknown) => {\n const items = ((payload as { items?: unknown[] } | null)?.items ?? []) as Array<\n Record<string, unknown>\n >\n const accountIds = Array.from(\n new Set(\n items\n .map((row) => row.bill_account_id)\n .filter((v): v is string => typeof v === 'string' && v.length > 0),\n ),\n )\n if (accountIds.length === 0) return\n const em = (ctx as { container: AwilixContainer }).container.resolve<EntityManager>('em')\n const accounts = await em.find(BillingAccount, { id: { $in: accountIds } })\n const nameById = new Map(\n accounts.map((a) => [String(a.id), a.name] as [string, unknown]),\n )\n for (const row of items) {\n row.bill_account_name = nameById.get(String(row.bill_account_id)) ?? null\n }\n },\n },\n})\n\nconst { POST, PUT, DELETE } = crud\nexport { POST, PUT, DELETE }\nexport const GET = crud.GET\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItem',\n pluralName: 'BillingItems',\n querySchema: billingItemListQuerySchema,\n listResponseSchema: createPagedListResponseSchema(billingItemListItemSchema),\n create: {\n schema: billingItemCreateSchema,\n responseSchema: itemCreateResponseSchema,\n description:\n 'Creates a billing item. Idempotent via `sourceRef`: re-POSTing the same ' +\n '`(tenantId, billAccountId, sourceRef)` returns the existing row with ' +\n '`deduplicated: true` instead of creating a duplicate. The unique index ' +\n 'on `(tenant_id, bill_account_id, source_ref)` is the correctness gate.',\n },\n update: {\n schema: billingItemUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description:\n 'Updates a billing item. `type` and `sourceRef` are immutable \u2014 to change ' +\n 'either, soft-delete the item and create a new one.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Soft-deletes a billing item.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAGlB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,gBAAgB,mBAAmB;AAC5C,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACpE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACnE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACxE;AAEO,MAAM,WAAW;AAGxB,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,iBAAiB,EAAE,OAAO,EAAE,KAAK;AAAA,EACjC,mBAAmB,EAAE,OAAO,EAAE,SAAS;AAAA,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\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 { BillingAccount, BillingItem } from '../../data/entities'\nimport { billingEntityIds } from '../../data/entityIds'\nimport {\n billingItemCreateSchema,\n billingItemListQuerySchema,\n billingItemUpdateSchema,\n} from '../../data/validators'\nimport {\n createBillingCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\n\n// \u2500\u2500\u2500 Auth / RBAC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.item.view'] },\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\n// The list endpoint projects raw column names \u2014 snake_case.\nconst billingItemListItemSchema = z.object({\n id: z.string().uuid(),\n bill_account_id: z.string().uuid(),\n bill_account_name: z.string().nullable(),\n /** Spec F (F2): the owning account's currency and mode, for the form. */\n bill_account_currency: z.string().nullable(),\n bill_account_mode: z.enum(['postpaid', 'prepaid']).nullable(),\n type: z.string(),\n bill_start_date: z.string(),\n bill_end_date: z.string().nullable(),\n description: z.string(),\n rate_json: z.record(z.string(), z.unknown()),\n uom_code: z.string().nullable(),\n subscription_id: z.string().nullable(),\n subscription_item_id: z.string().nullable(),\n source_ref: z.string().nullable(),\n currency_mismatch: z.boolean(),\n billed_to_date: z.string().nullable(),\n charge_timing: z.enum(['in_advance', 'in_arrears']),\n prorate: z.boolean(),\n tax_rate: z.union([z.string(), z.number()]),\n price_basis: z.enum(['net', 'gross']),\n is_active: z.boolean(),\n organization_id: z.string().uuid(),\n tenant_id: z.string().uuid(),\n created_at: z.string(),\n updated_at: z.string(),\n})\n\n// Response for create includes `deduplicated` so integrators can tell\n// whether their POST resulted in a new row or hit the idempotency\n// short-circuit on `source_ref`.\nconst itemCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n deduplicated: z.boolean(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: billingItemListQuerySchema,\n entityId: billingEntityIds.item,\n fields: [\n 'id',\n 'bill_account_id',\n 'type',\n 'bill_start_date',\n 'bill_end_date',\n 'description',\n 'rate_json',\n 'uom_code',\n 'subscription_id',\n 'subscription_item_id',\n 'source_ref',\n 'currency_mismatch',\n 'billed_to_date',\n 'charge_timing',\n 'prorate',\n 'tax_rate',\n 'price_basis',\n 'is_active',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n billStartDate: 'bill_start_date',\n billEndDate: 'bill_end_date',\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n // `?id=<uuid>` narrows the list to one row \u2014 the detail page\n // reads a single item through this filter.\n if (typeof query.id === 'string' && query.id) {\n filters.id = { $eq: query.id }\n }\n if (typeof query.search === 'string' && query.search.trim()) {\n const pattern = `%${query.search.trim().replace(/[%_]/g, (m) => `\\\\${m}`)}%`\n filters.description = { $ilike: pattern }\n }\n if (typeof query.billAccountId === 'string' && query.billAccountId) {\n filters.bill_account_id = { $eq: query.billAccountId }\n }\n if (typeof query.type === 'string' && query.type) {\n filters.type = { $eq: query.type }\n }\n if (typeof query.subscriptionId === 'string' && query.subscriptionId) {\n filters.subscription_id = { $eq: query.subscriptionId }\n }\n if (typeof query.isActive === 'boolean') {\n filters.is_active = { $eq: query.isActive }\n }\n // `activeAt=<date>` returns items whose validity window covers\n // the supplied date (Bill Run engine uses the same predicate\n // shape internally \u2014 see spec \"Mid-cycle recurring items\").\n if (query.activeAt instanceof Date) {\n filters.bill_start_date = { $lte: query.activeAt }\n filters.$or = [\n { bill_end_date: null },\n { bill_end_date: { $gte: query.activeAt } },\n ]\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'billing.items.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n id: result?.id ?? null,\n deduplicated: Boolean(result?.deduplicated),\n }),\n // Idempotent create returns 200 (vs 201). The spec mandates\n // duplicate POST returns the existing row, never creates a\n // duplicate \u2014 keeping the success status consistent makes\n // client-side handling trivial.\n status: 200,\n },\n update: {\n commandId: 'billing.items.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemUpdateSchema.parse(scoped)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'billing.items.delete',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n // The factory hands delete a `{ body, query }` envelope \u2014 the\n // id may arrive in either, depending on the caller.\n const envelope = (raw ?? {}) as {\n body?: Record<string, unknown>\n query?: Record<string, unknown>\n }\n const body = envelope.body ?? {}\n const query = envelope.query ?? {}\n const scoped = withScopedPayload(body, ctx, translate)\n const id =\n (typeof body.id === 'string' && body.id) ||\n (typeof query.id === 'string' && query.id) ||\n null\n if (!id) {\n throw new Error('id is required')\n }\n return {\n id,\n tenantId: (scoped as { tenantId?: string }).tenantId,\n organizationId: (scoped as { organizationId?: string }).organizationId,\n }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n // Items reference their account by FK id only; the list + detail UI\n // wants the account name, currency and mode. One bounded query\n // (\u2264 pageSize ids) resolves it after the page is fetched.\n afterList: async (payload: unknown, ctx: unknown) => {\n const items = ((payload as { items?: unknown[] } | null)?.items ?? []) as Array<\n Record<string, unknown>\n >\n const accountIds = Array.from(\n new Set(\n items\n .map((row) => row.bill_account_id)\n .filter((v): v is string => typeof v === 'string' && v.length > 0),\n ),\n )\n if (accountIds.length === 0) return\n const em = (ctx as { container: AwilixContainer }).container.resolve<EntityManager>('em')\n const accounts = await em.find(BillingAccount, { id: { $in: accountIds } })\n const byId = new Map(accounts.map((a) => [String(a.id), a] as const))\n for (const row of items) {\n const account = byId.get(String(row.bill_account_id))\n row.bill_account_name = account?.name ?? null\n row.bill_account_currency = account?.currencyCode ?? null\n row.bill_account_mode = account?.billingMode ?? null\n }\n },\n },\n})\n\nconst { POST, PUT, DELETE } = crud\nexport { POST, PUT, DELETE }\nexport const GET = crud.GET\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItem',\n pluralName: 'BillingItems',\n querySchema: billingItemListQuerySchema,\n listResponseSchema: createPagedListResponseSchema(billingItemListItemSchema),\n create: {\n schema: billingItemCreateSchema,\n responseSchema: itemCreateResponseSchema,\n description:\n 'Creates a billing item. Idempotent via `sourceRef`: re-POSTing the same ' +\n '`(tenantId, billAccountId, sourceRef)` returns the existing row with ' +\n '`deduplicated: true` instead of creating a duplicate. The unique index ' +\n 'on `(tenant_id, bill_account_id, source_ref)` is the correctness gate. ' +\n 'Recurring items accept `chargeTiming` (`in_advance` default | `in_arrears`) and ' +\n '`prorate` (default true); other types reject non-default values (400).',\n },\n update: {\n schema: billingItemUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description:\n 'Updates a billing item. `type` and `sourceRef` are immutable \u2014 to change ' +\n 'either, soft-delete the item and create a new one. `chargeTiming` / `prorate` ' +\n 'may change at any time on recurring items \u2014 billing resumes from `billed_to_date`, ' +\n 'so no day is billed twice.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Soft-deletes a billing item.',\n },\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import {
|
|
5
|
+
organizationScopeRequiredResponse,
|
|
6
|
+
resolveActiveOrganizationId
|
|
7
|
+
} from "@open-mercato/shared/lib/auth/organizationScope";
|
|
8
|
+
const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ["billing.payment.view"] }
|
|
10
|
+
};
|
|
11
|
+
async function GET(req, { params }) {
|
|
12
|
+
try {
|
|
13
|
+
const container = await createRequestContainer();
|
|
14
|
+
const auth = await getAuthFromRequest(req);
|
|
15
|
+
if (!auth || !auth.tenantId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
16
|
+
const organizationId = resolveActiveOrganizationId(auth);
|
|
17
|
+
if (!organizationId) return organizationScopeRequiredResponse();
|
|
18
|
+
const { id } = await params;
|
|
19
|
+
const em = container.resolve("em").fork();
|
|
20
|
+
const rows = await em.execute(
|
|
21
|
+
`SELECT id, file_name, row_count, counts, errors, user_id, created_at
|
|
22
|
+
FROM billing_payment_imports
|
|
23
|
+
WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
|
|
24
|
+
[id, auth.tenantId, organizationId]
|
|
25
|
+
);
|
|
26
|
+
if (!rows?.length) return NextResponse.json({ error: "Import not found" }, { status: 404 });
|
|
27
|
+
return NextResponse.json({ import: rows[0] });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error("[billing/payment-imports/[id].GET]", err);
|
|
30
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const openApi = {
|
|
34
|
+
methods: {
|
|
35
|
+
GET: {
|
|
36
|
+
tags: ["Billing"],
|
|
37
|
+
summary: "Get a payment import batch",
|
|
38
|
+
description: "Counts per outcome and the row errors (line number + message, capped at 200).",
|
|
39
|
+
responses: [{ status: 200, description: "`{ import }`" }],
|
|
40
|
+
errors: [{ status: 404, description: "Import not found" }]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
GET,
|
|
46
|
+
metadata,
|
|
47
|
+
openApi
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/billing/api/payment-imports/%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 organizationScopeRequiredResponse,\n resolveActiveOrganizationId,\n} from '@open-mercato/shared/lib/auth/organizationScope'\n\n/** `GET /api/billing/payment-imports/[id]` \u2014 one upload batch with its row errors (Spec C). */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.payment.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) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const organizationId = resolveActiveOrganizationId(auth)\n if (!organizationId) return organizationScopeRequiredResponse()\n const { id } = await params\n const em = (container.resolve('em') as EntityManager).fork()\n const rows = (await em.execute(\n `SELECT id, file_name, row_count, counts, errors, user_id, created_at\n FROM billing_payment_imports\n WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,\n [id, auth.tenantId, organizationId],\n )) as unknown as Array<Record<string, unknown>>\n if (!rows?.length) return NextResponse.json({ error: 'Import not found' }, { status: 404 })\n return NextResponse.json({ import: rows[0] })\n } catch (err) {\n console.error('[billing/payment-imports/[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 payment import batch',\n description: 'Counts per outcome and the row errors (line number + message, capped at 200).',\n responses: [{ status: 200, description: '`{ import }`' }],\n errors: [{ status: 404, description: 'Import not found' }],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAIA,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,SAAU,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAChG,UAAM,iBAAiB,4BAA4B,IAAI;AACvD,QAAI,CAAC,eAAgB,QAAO,kCAAkC;AAC9D,UAAM,EAAE,GAAG,IAAI,MAAM;AACrB,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,UAAM,OAAQ,MAAM,GAAG;AAAA,MACrB;AAAA;AAAA;AAAA,MAGA,CAAC,IAAI,KAAK,UAAU,cAAc;AAAA,IACpC;AACA,QAAI,CAAC,MAAM,OAAQ,QAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC1F,WAAO,aAAa,KAAK,EAAE,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,EAC9C,SAAS,KAAK;AACZ,YAAQ,MAAM,sCAAsC,GAAG;AACvD,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,aAAa;AAAA,MACb,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,eAAe,CAAC;AAAA,MACxD,QAAQ,CAAC,EAAE,QAAQ,KAAK,aAAa,mBAAmB,CAAC;AAAA,IAC3D;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|