@dainamite/billing 0.20.0 → 0.21.0

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