@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.
Files changed (604) hide show
  1. package/CHANGELOG.md +103 -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 +47 -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 +70 -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/de.json +880 -0
  217. package/dist/modules/billing/i18n/en.json +457 -21
  218. package/dist/modules/billing/i18n/es.json +880 -0
  219. package/dist/modules/billing/i18n/pl.json +458 -22
  220. package/dist/modules/billing/index.js +6 -2
  221. package/dist/modules/billing/index.js.map +2 -2
  222. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  223. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  224. package/dist/modules/billing/lib/accountLock.js +14 -0
  225. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  226. package/dist/modules/billing/lib/accountStatus.js +9 -7
  227. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  228. package/dist/modules/billing/lib/accountSummary.js +106 -0
  229. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  230. package/dist/modules/billing/lib/allocation.js +356 -0
  231. package/dist/modules/billing/lib/allocation.js.map +7 -0
  232. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  233. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  234. package/dist/modules/billing/lib/billPeriod.js +15 -1
  235. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  236. package/dist/modules/billing/lib/billRunEngine.js +237 -102
  237. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  238. package/dist/modules/billing/lib/billingSettings.js +116 -0
  239. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  240. package/dist/modules/billing/lib/commandRoute.js +81 -0
  241. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  242. package/dist/modules/billing/lib/customerLookup.js +97 -0
  243. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  244. package/dist/modules/billing/lib/dateOnly.js +13 -0
  245. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  246. package/dist/modules/billing/lib/documentParties.js +16 -0
  247. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  248. package/dist/modules/billing/lib/documentTypes.js +27 -0
  249. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  250. package/dist/modules/billing/lib/dueDate.js +18 -0
  251. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  252. package/dist/modules/billing/lib/iban.js +29 -0
  253. package/dist/modules/billing/lib/iban.js.map +7 -0
  254. package/dist/modules/billing/lib/invoiceDelivery.js +133 -0
  255. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  256. package/dist/modules/billing/lib/invoiceLanguages.js +28 -0
  257. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  258. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  259. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  260. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  261. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  262. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  263. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  264. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  265. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  266. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  267. package/dist/modules/billing/lib/memoGuards.js +25 -0
  268. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  269. package/dist/modules/billing/lib/navigation.js +48 -0
  270. package/dist/modules/billing/lib/navigation.js.map +7 -0
  271. package/dist/modules/billing/lib/numbering.js +120 -0
  272. package/dist/modules/billing/lib/numbering.js.map +7 -0
  273. package/dist/modules/billing/lib/overdue.js +36 -0
  274. package/dist/modules/billing/lib/overdue.js.map +7 -0
  275. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  276. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  277. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  278. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  279. package/dist/modules/billing/lib/paymentImport.js +164 -0
  280. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  281. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  282. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  283. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  284. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  285. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  286. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  287. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  288. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  289. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  290. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  291. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +276 -0
  292. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  293. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  294. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  295. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  296. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  297. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  298. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  299. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  300. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  301. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  302. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  303. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  304. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  305. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  306. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  307. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  308. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  309. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  310. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  311. package/dist/modules/billing/lib/topupCapture.js +2 -2
  312. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  313. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  314. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  315. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  316. package/dist/modules/billing/lib/usageRunner.js +14 -6
  317. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  318. package/dist/modules/billing/lib/vat.js +39 -0
  319. package/dist/modules/billing/lib/vat.js.map +7 -0
  320. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  321. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  323. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  325. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  327. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  329. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  331. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  333. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  334. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  335. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  336. package/dist/modules/billing/setup.js +7 -184
  337. package/dist/modules/billing/setup.js.map +2 -2
  338. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  339. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  340. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  341. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  342. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  343. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  344. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  345. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  346. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  347. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  348. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  349. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  350. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  351. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  352. package/generated/entities/billing_account/index.ts +3 -0
  353. package/generated/entities/billing_allocation/index.ts +18 -0
  354. package/generated/entities/billing_document_sequence/index.ts +11 -0
  355. package/generated/entities/billing_invoice/index.ts +38 -0
  356. package/generated/entities/billing_invoice_line/index.ts +24 -0
  357. package/generated/entities/billing_item/index.ts +4 -0
  358. package/generated/entities/billing_payment/index.ts +25 -0
  359. package/generated/entities/billing_payment_import/index.ts +14 -0
  360. package/generated/entities/billing_run/index.ts +0 -1
  361. package/generated/entities/billing_run_outcome/index.ts +1 -0
  362. package/generated/entities/billing_settings/index.ts +26 -0
  363. package/generated/entities/billing_statement/index.ts +1 -0
  364. package/generated/entities.ids.generated.ts +7 -0
  365. package/package.json +5 -1
  366. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +171 -0
  367. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  368. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  369. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  370. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  371. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  372. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  373. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  374. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  375. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  376. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  377. package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
  378. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  379. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  380. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  381. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  382. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  383. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  384. package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
  385. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  386. package/src/modules/billing/__tests__/i18n/dictionaries.test.ts +64 -0
  387. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  388. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  389. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  390. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
  391. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  392. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  393. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  394. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  395. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +206 -0
  396. package/src/modules/billing/__tests__/lib/invoiceLanguages.test.ts +49 -0
  397. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  398. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  399. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  400. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  401. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  402. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  403. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  404. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  405. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  406. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  407. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +269 -0
  408. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  409. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  410. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  411. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  412. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  413. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  414. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  415. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  416. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  417. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  418. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  419. package/src/modules/billing/__tests__/validators.test.ts +197 -3
  420. package/src/modules/billing/acl.ts +12 -0
  421. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  422. package/src/modules/billing/api/accounts/route.ts +59 -4
  423. package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
  424. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  425. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +80 -0
  426. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  427. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  428. package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
  429. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  430. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  431. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  432. package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
  433. package/src/modules/billing/api/invoices/route.ts +81 -86
  434. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  435. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  436. package/src/modules/billing/api/items/route.ts +23 -7
  437. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  438. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  439. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  440. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  441. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  442. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  443. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  444. package/src/modules/billing/api/payments/import/route.ts +72 -0
  445. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  446. package/src/modules/billing/api/payments/route.ts +147 -0
  447. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  448. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  449. package/src/modules/billing/api/run-outcomes/route.ts +49 -6
  450. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  451. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  452. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
  453. package/src/modules/billing/api/runs/route.ts +15 -7
  454. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  455. package/src/modules/billing/api/settings/route.ts +116 -0
  456. package/src/modules/billing/api/statements/route.ts +5 -0
  457. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  458. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  459. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  460. package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
  461. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
  462. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  463. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  464. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  465. package/src/modules/billing/backend/billing/items/page.tsx +18 -2
  466. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  467. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  468. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  469. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  470. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  471. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  472. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
  473. package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
  474. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
  475. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  476. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  477. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  478. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  479. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  480. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  481. package/src/modules/billing/cli.ts +31 -1
  482. package/src/modules/billing/commands/accounts.ts +6 -0
  483. package/src/modules/billing/commands/index.ts +3 -0
  484. package/src/modules/billing/commands/invoices.ts +364 -231
  485. package/src/modules/billing/commands/items.ts +143 -0
  486. package/src/modules/billing/commands/memos.ts +218 -0
  487. package/src/modules/billing/commands/payments.ts +428 -0
  488. package/src/modules/billing/commands/runs.ts +205 -3
  489. package/src/modules/billing/commands/settings.ts +77 -0
  490. package/src/modules/billing/commands/topups.ts +12 -3
  491. package/src/modules/billing/commands/transactions.ts +2 -2
  492. package/src/modules/billing/components/AccountForm.tsx +199 -53
  493. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  494. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  495. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  496. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  497. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  498. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  499. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  500. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  501. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  502. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  503. package/src/modules/billing/components/ItemForm.tsx +233 -44
  504. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  505. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  506. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  507. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  508. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  509. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  510. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  511. package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
  512. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  513. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  514. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  515. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  516. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  517. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  518. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  519. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  520. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  521. package/src/modules/billing/components/account/accountRow.ts +70 -0
  522. package/src/modules/billing/components/customerNames.ts +62 -0
  523. package/src/modules/billing/data/entities.ts +656 -6
  524. package/src/modules/billing/data/entityIds.ts +9 -0
  525. package/src/modules/billing/data/validators.ts +388 -8
  526. package/src/modules/billing/events.ts +56 -3
  527. package/src/modules/billing/i18n/de.json +880 -0
  528. package/src/modules/billing/i18n/en.json +457 -21
  529. package/src/modules/billing/i18n/es.json +880 -0
  530. package/src/modules/billing/i18n/pl.json +458 -22
  531. package/src/modules/billing/index.ts +6 -2
  532. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  533. package/src/modules/billing/lib/accountLock.ts +32 -0
  534. package/src/modules/billing/lib/accountStatus.ts +11 -9
  535. package/src/modules/billing/lib/accountSummary.ts +172 -0
  536. package/src/modules/billing/lib/allocation.ts +515 -0
  537. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  538. package/src/modules/billing/lib/billPeriod.ts +19 -0
  539. package/src/modules/billing/lib/billRunEngine.ts +350 -150
  540. package/src/modules/billing/lib/billingSettings.ts +145 -0
  541. package/src/modules/billing/lib/commandRoute.ts +112 -0
  542. package/src/modules/billing/lib/customerLookup.ts +157 -0
  543. package/src/modules/billing/lib/dateOnly.ts +22 -0
  544. package/src/modules/billing/lib/documentParties.ts +23 -0
  545. package/src/modules/billing/lib/documentTypes.ts +37 -0
  546. package/src/modules/billing/lib/dueDate.ts +26 -0
  547. package/src/modules/billing/lib/iban.ts +36 -0
  548. package/src/modules/billing/lib/invoiceDelivery.ts +196 -0
  549. package/src/modules/billing/lib/invoiceLanguages.ts +57 -0
  550. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  551. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  552. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  553. package/src/modules/billing/lib/itemSelector.ts +4 -1
  554. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  555. package/src/modules/billing/lib/memoGuards.ts +41 -0
  556. package/src/modules/billing/lib/navigation.ts +64 -0
  557. package/src/modules/billing/lib/numbering.ts +174 -0
  558. package/src/modules/billing/lib/overdue.ts +60 -0
  559. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  560. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  561. package/src/modules/billing/lib/paymentImport.ts +228 -0
  562. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  563. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  564. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  565. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  566. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  567. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  568. package/src/modules/billing/lib/pdf/invoiceModel.ts +329 -0
  569. package/src/modules/billing/lib/pdf/invoicePdf.ts +220 -0
  570. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  571. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  572. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  573. package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
  574. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  575. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  576. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  577. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  578. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  579. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  580. package/src/modules/billing/lib/topupCapture.ts +3 -4
  581. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  582. package/src/modules/billing/lib/usageRater.ts +1 -1
  583. package/src/modules/billing/lib/usageRunner.ts +17 -5
  584. package/src/modules/billing/lib/vat.ts +56 -0
  585. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  586. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  587. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  588. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  589. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  590. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  591. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  592. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  593. package/src/modules/billing/setup.ts +11 -237
  594. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  595. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  596. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  597. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  598. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  599. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  600. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  601. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  602. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  603. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  604. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -1,24 +1,35 @@
1
+ import { UniqueConstraintViolationException } from "@mikro-orm/core";
1
2
  import {
2
3
  BillingAccount,
3
4
  BillingAccountUsage,
5
+ BillingInvoice,
4
6
  BillingItem,
5
7
  BillingRun,
6
8
  BillingRunOutcome
7
9
  } from "../data/entities.js";
8
- import { advanceNextBillDate, deriveBillPeriod, isCycleDue } from "./billPeriod.js";
9
10
  import {
10
- selectItemsForPeriod
11
- } from "./itemSelector.js";
11
+ addDays,
12
+ advanceNextBillDate,
13
+ cloneAtMidnight,
14
+ deriveBillPeriod,
15
+ isCycleDue,
16
+ shiftCycles
17
+ } from "./billPeriod.js";
18
+ import { shouldIncludeItem } from "./itemSelector.js";
12
19
  import {
13
20
  buildInvoiceLinesFromItems,
21
+ buildRecurringLineDescriptors,
22
+ displayPeriod,
14
23
  writeDraftInvoice
15
24
  } from "./invoiceWriter.js";
25
+ import { computeRecurringLines } from "./servicePeriods.js";
16
26
  import { withTenantLock } from "./tenantLock.js";
17
27
  import { processUsageForAccount } from "./usageRunner.js";
18
28
  import { processPrepaidAccount } from "./prepaidPeriodClose.js";
19
29
  import { emitBillingEvent } from "../events.js";
20
- const SCHEDULED_CYCLE_LIMIT = 1;
21
30
  const CATCH_UP_HARD_CAP = 60;
31
+ const SKIPPED_ITEMS_WARNING_CAP = 200;
32
+ const QUIET_SKIP_REASONS = /* @__PURE__ */ new Set(["item-inactive", "item-soft-deleted"]);
22
33
  async function runBillRun(em, container, params) {
23
34
  const output = await withTenantLock(
24
35
  em,
@@ -39,7 +50,6 @@ async function executeRunWithinLock(em, container, params) {
39
50
  parentRunId: params.parentRunId ?? null,
40
51
  dryRun: params.mode === "dry",
41
52
  testMode: params.mode === "test",
42
- catchUp: Boolean(params.catchUp),
43
53
  scopedAccountIds: params.scopedAccountIds ?? null,
44
54
  asOfDate: params.asOfDate,
45
55
  startedAt: now,
@@ -70,6 +80,7 @@ async function executeRunWithinLock(em, container, params) {
70
80
  warnings: processResult.status === "failed" ? null : processResult.warnings,
71
81
  draftInvoiceId: processResult.status === "failed" ? null : processResult.draftInvoiceId,
72
82
  statementId: processResult.status === "failed" ? null : processResult.statementId ?? null,
83
+ cycles: processResult.status === "failed" ? null : processResult.cycles ?? null,
73
84
  isActive: true,
74
85
  createdAt: /* @__PURE__ */ new Date(),
75
86
  updatedAt: /* @__PURE__ */ new Date()
@@ -117,8 +128,7 @@ async function processAccount(em, container, billRun, account, params) {
117
128
  const result = await processPrepaidAccount(em, account, billRun, {
118
129
  asOfDate: params.asOfDate,
119
130
  mode: params.mode,
120
- triggeredBy: params.triggeredBy,
121
- catchUp: params.catchUp
131
+ triggeredBy: params.triggeredBy
122
132
  });
123
133
  if (result.status === "failed") {
124
134
  return { status: "failed", errorMessage: result.errorMessage };
@@ -130,76 +140,190 @@ async function processAccount(em, container, billRun, account, params) {
130
140
  statementId: result.statementId,
131
141
  statementEvents: result.statementEvents,
132
142
  cyclesEmitted: result.cyclesEmitted,
143
+ cycles: result.cycles,
133
144
  usageRecordsRated: 0
134
145
  };
135
146
  }
136
- const maxCycles = params.triggeredBy === "schedule" || !params.catchUp ? SCHEDULED_CYCLE_LIMIT : CATCH_UP_HARD_CAP;
137
- let cyclesEmitted = 0;
147
+ return processPostpaidAccount(em, container, billRun, account, params);
148
+ }
149
+ function isoDate(date) {
150
+ return cloneAtMidnight(date).toISOString().slice(0, 10);
151
+ }
152
+ function toSkipWarning(skip) {
153
+ return {
154
+ item_id: skip.billingItemId,
155
+ reason: skip.reason,
156
+ service_period_start: skip.servicePeriodStart ? isoDate(skip.servicePeriodStart) : null,
157
+ service_period_end: skip.servicePeriodEnd ? isoDate(skip.servicePeriodEnd) : null
158
+ };
159
+ }
160
+ async function processPostpaidAccount(em, container, billRun, account, params) {
161
+ const cycle = account.billCycle;
162
+ const testRun = params.mode === "test";
163
+ const items = await em.find(BillingItem, {
164
+ tenantId: account.tenantId,
165
+ organizationId: account.organizationId,
166
+ billAccountId: account.id,
167
+ deletedAt: null
168
+ });
169
+ const recurringItems = items.filter((it) => it.type === "recurring");
170
+ const oneTimeItems = items.filter((it) => it.type === "one_time");
171
+ const usageItems = items.filter((it) => it.type === "usage");
172
+ const watermarks = new Map(
173
+ recurringItems.map((it) => [it.id, it.billedToDate ? cloneAtMidnight(it.billedToDate) : null])
174
+ );
175
+ const billedOneTimes = new Set(oneTimeItems.filter((it) => it.billedToDate).map((it) => it.id));
176
+ let billDate = cloneAtMidnight(account.nextBillDate);
177
+ let usageCountedThrough = null;
178
+ const cycles = [];
179
+ const warnings = {};
180
+ const skippedItems = [];
138
181
  let lastDraftInvoiceId = null;
139
- let totalUsageRatedThisAccount = 0;
140
- const aggregatedWarnings = {};
141
- while (cyclesEmitted < maxCycles && isCycleDue(account.nextBillDate, params.asOfDate)) {
142
- const period = deriveBillPeriod(account.nextBillDate, account.billCycle);
143
- if (params.mode !== "dry") {
144
- const existing = await findExistingDraftForPeriod(em, account, period);
145
- if (existing) {
146
- return {
147
- status: "skipped_existing_draft",
148
- warnings: null,
149
- draftInvoiceId: existing,
150
- cyclesEmitted,
151
- usageRecordsRated: totalUsageRatedThisAccount
152
- };
153
- }
182
+ let usageRecordsRated = 0;
183
+ const pushSkips = (skips) => {
184
+ for (const skip of skips) {
185
+ if (QUIET_SKIP_REASONS.has(skip.reason)) continue;
186
+ if (skippedItems.length < SKIPPED_ITEMS_WARNING_CAP) skippedItems.push(toSkipWarning(skip));
154
187
  }
155
- const items = await em.find(BillingItem, {
156
- tenantId: account.tenantId,
157
- organizationId: account.organizationId,
158
- billAccountId: account.id,
159
- deletedAt: null
160
- });
161
- const usageItems = items.filter((it) => it.type === "usage");
162
- const { included } = selectItemsForPeriod(items, period);
163
- const billableItems = included;
164
- const currencyMismatchIds = billableItems.filter((it) => it.currencyMismatch).map((it) => it.id);
165
- if (currencyMismatchIds.length > 0) {
166
- aggregatedWarnings.currency_mismatch_items = [
167
- ...aggregatedWarnings.currency_mismatch_items ?? [],
168
- ...currencyMismatchIds
169
- ];
170
- }
171
- const itemLines = buildInvoiceLinesFromItems(billableItems);
172
- const usageResult = await processUsageForAccount(em, account, period, usageItems);
173
- if (usageResult.unmatchedUoms.length > 0) {
174
- aggregatedWarnings.unmatched_usage_uoms = [
175
- ...aggregatedWarnings.unmatched_usage_uoms ?? [],
176
- ...usageResult.unmatchedUoms
177
- ];
188
+ };
189
+ const addWarning = (key, values) => {
190
+ if (values.length === 0) return;
191
+ warnings[key] = Array.from(/* @__PURE__ */ new Set([...warnings[key] ?? [], ...values]));
192
+ };
193
+ while (isCycleDue(billDate, params.asOfDate)) {
194
+ if (cycles.length >= CATCH_UP_HARD_CAP) {
195
+ warnings.catch_up_cap_reached = true;
196
+ break;
178
197
  }
179
- const allLines = [...itemLines, ...usageResult.lines];
180
- if (allLines.length === 0) {
181
- if (params.mode !== "real") break;
182
- account.nextBillDate = advanceNextBillDate(
183
- account.nextBillDate,
184
- account.billCycle
198
+ const nextBillDate = advanceNextBillDate(billDate, cycle);
199
+ const arrearsPeriod = deriveBillPeriod(billDate, cycle);
200
+ const lines = [];
201
+ const watermarkUpdates = /* @__PURE__ */ new Map();
202
+ const chargedItems = [];
203
+ for (const item of recurringItems) {
204
+ const unitPrice = item.rateJson?.unit_price;
205
+ if (typeof unitPrice !== "number") continue;
206
+ const computed = computeRecurringLines(
207
+ {
208
+ id: item.id,
209
+ chargeTiming: item.chargeTiming ?? "in_advance",
210
+ prorate: item.prorate ?? true,
211
+ billStartDate: item.billStartDate,
212
+ billEndDate: item.billEndDate ?? null,
213
+ billedToDate: watermarks.get(item.id) ?? null,
214
+ unitPrice,
215
+ isActive: item.isActive,
216
+ deletedAt: item.deletedAt ?? null
217
+ },
218
+ { billDate, cycle }
185
219
  );
186
- account.lastBillDate = period.periodEnd;
187
- account.updatedAt = /* @__PURE__ */ new Date();
188
- cyclesEmitted += 1;
189
- continue;
220
+ pushSkips(computed.skipped);
221
+ if (computed.newBilledToDate?.getTime() !== watermarks.get(item.id)?.getTime()) {
222
+ watermarkUpdates.set(item.id, computed.newBilledToDate);
223
+ }
224
+ if (computed.lines.length > 0) {
225
+ lines.push(...buildRecurringLineDescriptors(item, computed.lines));
226
+ chargedItems.push(item);
227
+ }
190
228
  }
191
- const written = await writeDraftInvoice(em, container, {
192
- mode: params.mode,
193
- account,
194
- period,
195
- billRunId: billRun.id,
196
- lines: allLines
229
+ const oneTimeWindow = {
230
+ periodStart: shiftCycles(billDate, cycle, -1),
231
+ periodEnd: addDays(nextBillDate, -1)
232
+ };
233
+ const oneTimesThisCycle = oneTimeItems.filter(
234
+ (it) => !billedOneTimes.has(it.id) && shouldIncludeItem(it, oneTimeWindow).include
235
+ );
236
+ lines.push(...buildInvoiceLinesFromItems(oneTimesThisCycle));
237
+ chargedItems.push(...oneTimesThisCycle);
238
+ const usageResult = await processUsageForAccount(em, account, arrearsPeriod, usageItems, {
239
+ after: usageCountedThrough
197
240
  });
198
- lastDraftInvoiceId = written.invoiceId;
241
+ addWarning("unmatched_usage_uoms", usageResult.unmatchedUoms);
242
+ lines.push(
243
+ ...usageResult.lines.map((line) => ({
244
+ ...line,
245
+ servicePeriodStart: arrearsPeriod.periodStart,
246
+ servicePeriodEnd: arrearsPeriod.periodEnd
247
+ }))
248
+ );
249
+ addWarning(
250
+ "currency_mismatch_items",
251
+ chargedItems.filter((it) => it.currencyMismatch).map((it) => it.id)
252
+ );
253
+ const period = displayPeriod(lines, arrearsPeriod);
254
+ const record = {
255
+ bill_date: isoDate(billDate),
256
+ period_start: lines.length ? isoDate(period.periodStart) : null,
257
+ period_end: lines.length ? isoDate(period.periodEnd) : null,
258
+ status: "empty",
259
+ line_count: lines.length,
260
+ total_net: "0.0000",
261
+ invoice_id: null
262
+ };
263
+ const existing = params.mode === "dry" ? null : await findExistingDraftForBillDate(em, account, billDate, testRun);
264
+ if (existing && params.mode === "real") {
265
+ cycles.push({ ...record, status: "skipped_existing", invoice_id: existing });
266
+ return {
267
+ status: "skipped_existing_draft",
268
+ warnings: finalizeWarnings(warnings, skippedItems),
269
+ draftInvoiceId: existing,
270
+ cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
271
+ usageRecordsRated,
272
+ cycles
273
+ };
274
+ }
275
+ if (existing) {
276
+ cycles.push({ ...record, status: "skipped_existing", invoice_id: existing });
277
+ } else if (lines.length > 0) {
278
+ const written = await writeDraftOnce(em, {
279
+ mode: params.mode,
280
+ account,
281
+ billDate,
282
+ period,
283
+ billRunId: billRun.id,
284
+ lines
285
+ });
286
+ if (written === "conflict") {
287
+ cycles.push({ ...record, status: "skipped_existing" });
288
+ if (params.mode === "real") {
289
+ return {
290
+ status: "skipped_existing_draft",
291
+ warnings: finalizeWarnings(warnings, skippedItems),
292
+ draftInvoiceId: lastDraftInvoiceId,
293
+ cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
294
+ usageRecordsRated,
295
+ cycles
296
+ };
297
+ }
298
+ billDate = nextBillDate;
299
+ for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date);
300
+ for (const it of oneTimesThisCycle) billedOneTimes.add(it.id);
301
+ usageCountedThrough = arrearsPeriod.periodEnd;
302
+ continue;
303
+ }
304
+ if (written.invoiceId) lastDraftInvoiceId = written.invoiceId;
305
+ if (Number.parseFloat(written.totalNet) < 0) addWarning("negative_total_bill_dates", [record.bill_date]);
306
+ cycles.push({
307
+ ...record,
308
+ status: params.mode === "dry" ? "previewed" : "invoiced",
309
+ total_net: written.totalNet,
310
+ invoice_id: written.invoiceId
311
+ });
312
+ } else {
313
+ cycles.push(record);
314
+ }
315
+ for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date);
316
+ for (const it of oneTimesThisCycle) billedOneTimes.add(it.id);
317
+ usageCountedThrough = arrearsPeriod.periodEnd;
199
318
  if (params.mode === "real") {
200
319
  const now = /* @__PURE__ */ new Date();
201
- for (const item of billableItems) {
202
- item.billedToDate = period.periodEnd;
320
+ for (const item of recurringItems) {
321
+ if (!watermarkUpdates.has(item.id)) continue;
322
+ item.billedToDate = watermarkUpdates.get(item.id) ?? null;
323
+ item.updatedAt = now;
324
+ }
325
+ for (const item of oneTimesThisCycle) {
326
+ item.billedToDate = oneTimeWindow.periodEnd;
203
327
  item.updatedAt = now;
204
328
  }
205
329
  if (usageResult.matchedUoms.length > 0) {
@@ -211,53 +335,62 @@ async function processAccount(em, container, billRun, account, params) {
211
335
  billAccountId: account.id,
212
336
  uomCode: { $in: usageResult.matchedUoms },
213
337
  ratedInBillRunId: null,
214
- periodEnd: { $lte: period.periodEnd },
338
+ periodEnd: { $lte: arrearsPeriod.periodEnd },
215
339
  deletedAt: null
216
340
  },
217
341
  { ratedInBillRunId: billRun.id, updatedAt: now }
218
342
  );
343
+ usageRecordsRated += usageResult.ratedCount;
219
344
  }
220
- totalUsageRatedThisAccount += usageResult.ratedCount;
221
- account.nextBillDate = advanceNextBillDate(
222
- account.nextBillDate,
223
- account.billCycle
224
- );
225
- account.lastBillDate = period.periodEnd;
345
+ account.nextBillDate = nextBillDate;
346
+ account.lastBillDate = arrearsPeriod.periodEnd;
226
347
  account.updatedAt = now;
227
348
  await em.flush();
228
- } else {
229
- break;
230
349
  }
231
- cyclesEmitted += 1;
350
+ billDate = nextBillDate;
232
351
  }
233
- const hasCurrencyWarnings = (aggregatedWarnings.currency_mismatch_items ?? []).length > 0;
234
- const hasUnmatchedUsage = (aggregatedWarnings.unmatched_usage_uoms ?? []).length > 0;
235
- const hasWarnings = hasCurrencyWarnings || hasUnmatchedUsage;
352
+ const finalWarnings = finalizeWarnings(warnings, skippedItems);
236
353
  return {
237
- status: hasWarnings ? "success_with_warnings" : "success",
238
- warnings: hasWarnings ? aggregatedWarnings : null,
354
+ status: hasRealWarnings(warnings) ? "success_with_warnings" : "success",
355
+ warnings: finalWarnings,
239
356
  draftInvoiceId: lastDraftInvoiceId,
240
- cyclesEmitted,
241
- usageRecordsRated: totalUsageRatedThisAccount
357
+ cyclesEmitted: cycles.filter((c) => c.status === "invoiced").length,
358
+ usageRecordsRated,
359
+ cycles
242
360
  };
243
361
  }
244
- async function findExistingDraftForPeriod(em, account, period) {
245
- const periodStartISO = period.periodStart.toISOString().slice(0, 10);
246
- const periodEndISO = period.periodEnd.toISOString().slice(0, 10);
247
- const rows = await em.execute(
248
- `SELECT id FROM sales_invoices
249
- WHERE tenant_id = ?
250
- AND organization_id = ?
251
- AND deleted_at IS NULL
252
- AND status = 'draft'
253
- AND metadata->>'bill_account_id' = ?
254
- AND metadata->>'bill_period_start' = ?
255
- AND metadata->>'bill_period_end' = ?
256
- LIMIT 1`,
257
- [account.tenantId, account.organizationId, account.id, periodStartISO, periodEndISO]
362
+ function finalizeWarnings(warnings, skippedItems) {
363
+ const merged = { ...warnings, ...skippedItems.length ? { skipped_items: skippedItems } : {} };
364
+ return Object.keys(merged).length > 0 ? merged : null;
365
+ }
366
+ function hasRealWarnings(warnings) {
367
+ return Boolean(
368
+ warnings.currency_mismatch_items?.length || warnings.unmatched_usage_uoms?.length || warnings.negative_total_bill_dates?.length || warnings.catch_up_cap_reached
369
+ );
370
+ }
371
+ async function findExistingDraftForBillDate(em, account, billDate, testRun) {
372
+ const existing = await em.findOne(
373
+ BillingInvoice,
374
+ {
375
+ tenantId: account.tenantId,
376
+ organizationId: account.organizationId,
377
+ billAccountId: account.id,
378
+ billDate,
379
+ testRun,
380
+ documentType: "invoice",
381
+ deletedAt: null
382
+ }
258
383
  );
259
- if (!rows || rows.length === 0) return null;
260
- return rows[0].id ?? null;
384
+ return existing?.id ?? null;
385
+ }
386
+ async function writeDraftOnce(em, params) {
387
+ if (params.mode === "dry") return writeDraftInvoice(em, params);
388
+ try {
389
+ return await em.transactional((tem) => writeDraftInvoice(tem, params));
390
+ } catch (err) {
391
+ if (err instanceof UniqueConstraintViolationException) return "conflict";
392
+ throw err;
393
+ }
261
394
  }
262
395
  function computeFinalRunStatus(outcomes) {
263
396
  if (outcomes.length === 0) return "completed";
@@ -270,11 +403,12 @@ function computeFinalRunStatus(outcomes) {
270
403
  return "completed";
271
404
  }
272
405
  function buildRunSummary(outcomes, totalAccounts, usageRecordsRated) {
406
+ const allCycles = outcomes.flatMap((o) => Array.isArray(o.cycles) ? o.cycles : []);
273
407
  return {
274
408
  accounts_processed: totalAccounts,
275
- drafts_created: outcomes.filter(
276
- (o) => o.draftInvoiceId !== null && o.status !== "skipped_existing_draft"
277
- ).length,
409
+ // Spec A: one draft per invoiced cycle (catch-up writes several per account).
410
+ drafts_created: allCycles.filter((c) => c.status === "invoiced" && c.invoice_id).length,
411
+ cycles_processed: allCycles.length,
278
412
  drafts_skipped_existing: outcomes.filter((o) => o.status === "skipped_existing_draft").length,
279
413
  // SPEC-002 prepaid period close.
280
414
  statements_created: outcomes.filter(
@@ -289,6 +423,7 @@ function buildRunSummary(outcomes, totalAccounts, usageRecordsRated) {
289
423
  };
290
424
  }
291
425
  export {
426
+ CATCH_UP_HARD_CAP,
292
427
  runBillRun
293
428
  };
294
429
  //# sourceMappingURL=billRunEngine.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/lib/billRunEngine.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport {\n BillingAccount,\n BillingAccountUsage,\n BillingItem,\n BillingRun,\n BillingRunOutcome,\n} from '../data/entities'\nimport { advanceNextBillDate, deriveBillPeriod, isCycleDue } from './billPeriod'\nimport {\n selectItemsForPeriod,\n type SelectableItem,\n} from './itemSelector'\nimport {\n buildInvoiceLinesFromItems,\n writeDraftInvoice,\n type InvoiceLineDescriptor,\n type WriteMode,\n} from './invoiceWriter'\nimport { withTenantLock } from './tenantLock'\nimport { processUsageForAccount } from './usageRunner'\nimport { processPrepaidAccount } from './prepaidPeriodClose'\nimport type { DomainEvent } from './balanceEvents'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport type { BillCycle } from '../data/validators'\n\n/**\n * Bill Run engine \u2014 orchestrates one execution of the billing cycle for\n * a single tenant.\n *\n * Concurrency / locking:\n * - The whole run is wrapped in `withTenantLock(...)`, which holds a\n * transaction-scoped Postgres advisory lock keyed by\n * `hash(tenant_id || ':billing-run')`. A second trigger fired while a\n * run is in flight short-circuits with `TenantLockBusyError` (the\n * command layer maps that to HTTP 409).\n * - Within the outer transaction, every account is processed inside a\n * nested `em.transactional(...)` (PG SAVEPOINT). A per-account failure\n * rolls back only that account's work, leaving siblings unaffected.\n *\n * Run modes:\n * - `real` (default) \u2014 persist drafts, advance `next_bill_date`, mark\n * items `billed_to_date`. Production path.\n * - `test` \u2014 persist drafts flagged with `metadata.test_run=true`, but\n * do NOT advance state. Repeatable.\n * - `dry` \u2014 compute everything; persist nothing. Preview only.\n *\n * Catch-up:\n * - Manual triggers may set `catchUp=true`. The engine then loops cycles\n * per account until `next_bill_date > as_of_date`, emitting one draft\n * per missed cycle (never combined).\n * - Scheduled triggers always process exactly ONE cycle per account per\n * run, regardless of how far behind the account is.\n *\n * Anti-duplicate guards (both run in every mode except `dry`):\n * 1. Open-draft check on `core/sales` invoice (per period boundary).\n * 2. `billed_to_date` per BillingItem.\n */\n\nexport type RunMode = WriteMode\n\nexport type RunBillRunParams = {\n tenantId: string\n organizationId: string\n asOfDate: Date\n triggeredBy: 'schedule' | 'manual'\n mode: RunMode\n scopedAccountIds?: string[]\n parentRunId?: string | null\n catchUp?: boolean\n}\n\nexport type RunBillRunResult = {\n billRun: BillingRun\n outcomes: BillingRunOutcome[]\n}\n\ntype RunOutput = RunBillRunResult & {\n /** Prepaid statement / crossing events to emit AFTER the outer tx commits. */\n pendingEvents: DomainEvent[]\n}\n\ntype AccountWarnings = {\n currency_mismatch_items?: string[]\n unmatched_usage_uoms?: string[]\n}\n\ntype AccountProcessSuccess = {\n status:\n | 'success'\n | 'success_with_warnings'\n | 'skipped_existing_draft'\n | 'skipped_existing_statement'\n warnings: AccountWarnings | Record<string, unknown> | null\n draftInvoiceId: string | null\n // SPEC-002: prepaid accounts produce a statement instead of a draft invoice.\n statementId?: string | null\n statementEvents?: DomainEvent[]\n cyclesEmitted: number\n usageRecordsRated: number\n}\n\ntype AccountProcessFailed = {\n status: 'failed'\n errorMessage: string\n}\n\ntype AccountProcessResult = AccountProcessSuccess | AccountProcessFailed\n\nconst SCHEDULED_CYCLE_LIMIT = 1\nconst CATCH_UP_HARD_CAP = 60 // safety net so a misconfigured account can't loop forever\n\nexport async function runBillRun(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunBillRunResult> {\n const output = await withTenantLock(\n em,\n { tenantId: params.tenantId, lockName: 'billing-run' },\n async (tem) => executeRunWithinLock(tem, container, params),\n )\n // Emit prepaid statement / crossing events ONLY after the outer transaction\n // (held by withTenantLock) has committed \u2014 emitting inside the lock would let\n // a consumer observe an uncommitted statement or a phantom event on rollback.\n for (const ev of output.pendingEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n return { billRun: output.billRun, outcomes: output.outcomes }\n}\n\nasync function executeRunWithinLock(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunOutput> {\n const now = new Date()\n const billRun = em.create(BillingRun, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n triggeredBy: params.triggeredBy,\n parentRunId: params.parentRunId ?? null,\n dryRun: params.mode === 'dry',\n testMode: params.mode === 'test',\n catchUp: Boolean(params.catchUp),\n scopedAccountIds: params.scopedAccountIds ?? null,\n asOfDate: params.asOfDate,\n startedAt: now,\n status: 'running',\n summary: null,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(billRun)\n await em.flush()\n\n const accounts = await selectTargetAccounts(em, params)\n const outcomes: BillingRunOutcome[] = []\n const pendingEvents: DomainEvent[] = []\n let totalUsageRecordsRated = 0\n\n for (const account of accounts) {\n const processResult = await processAccountSafely(em, container, billRun, account, params)\n if (processResult.status !== 'failed') {\n totalUsageRecordsRated += processResult.usageRecordsRated\n }\n const outcome = em.create(BillingRunOutcome, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n billRunId: billRun.id,\n billAccountId: account.id,\n status: processResult.status,\n errorMessage: processResult.status === 'failed' ? processResult.errorMessage : null,\n warnings:\n processResult.status === 'failed'\n ? null\n : (processResult.warnings as Record<string, unknown> | null),\n draftInvoiceId: processResult.status === 'failed' ? null : processResult.draftInvoiceId,\n statementId: processResult.status === 'failed' ? null : (processResult.statementId ?? null),\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(outcome)\n await em.flush()\n outcomes.push(outcome)\n\n // Collect statement / crossing events \u2014 runBillRun emits them AFTER the\n // outer tx commits (see runBillRun), never from inside the lock.\n if (processResult.status !== 'failed' && processResult.statementEvents) {\n pendingEvents.push(...processResult.statementEvents)\n }\n }\n\n billRun.status = computeFinalRunStatus(outcomes)\n billRun.finishedAt = new Date()\n billRun.updatedAt = billRun.finishedAt\n billRun.summary = buildRunSummary(outcomes, accounts.length, totalUsageRecordsRated)\n await em.flush()\n\n return { billRun, outcomes, pendingEvents }\n}\n\nasync function selectTargetAccounts(\n em: EntityManager,\n params: RunBillRunParams,\n): Promise<BillingAccount[]> {\n const where: Record<string, unknown> = {\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n nextBillDate: { $lte: params.asOfDate },\n }\n if (params.scopedAccountIds && params.scopedAccountIds.length > 0) {\n where.id = { $in: params.scopedAccountIds }\n }\n return em.find(BillingAccount, where as never)\n}\n\nasync function processAccountSafely(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n try {\n return await em.transactional(async (subEm) => {\n return processAccount(subEm, container, billRun, account, params)\n })\n } catch (err) {\n return {\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nasync function processAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n // SPEC-002: a prepaid account never gets a payable draft invoice. It still\n // runs in the Bill Run, but charges recurring / one_time items to the\n // balance and produces a non-fiscal consumption statement.\n if (account.billingMode === 'prepaid') {\n const result = await processPrepaidAccount(em, account, billRun, {\n asOfDate: params.asOfDate,\n mode: params.mode,\n triggeredBy: params.triggeredBy,\n catchUp: params.catchUp,\n })\n if (result.status === 'failed') {\n return { status: 'failed', errorMessage: result.errorMessage }\n }\n return {\n status: result.status,\n warnings: result.warnings,\n draftInvoiceId: null,\n statementId: result.statementId,\n statementEvents: result.statementEvents,\n cyclesEmitted: result.cyclesEmitted,\n usageRecordsRated: 0,\n }\n }\n\n const maxCycles =\n params.triggeredBy === 'schedule' || !params.catchUp\n ? SCHEDULED_CYCLE_LIMIT\n : CATCH_UP_HARD_CAP\n\n let cyclesEmitted = 0\n let lastDraftInvoiceId: string | null = null\n let totalUsageRatedThisAccount = 0\n const aggregatedWarnings: AccountWarnings = {}\n\n while (cyclesEmitted < maxCycles && isCycleDue(account.nextBillDate, params.asOfDate)) {\n const period = deriveBillPeriod(account.nextBillDate, account.billCycle as BillCycle)\n\n // Open-draft anti-duplicate \u2014 only applies when we'd actually persist.\n if (params.mode !== 'dry') {\n const existing = await findExistingDraftForPeriod(em, account, period)\n if (existing) {\n return {\n status: 'skipped_existing_draft',\n warnings: null,\n draftInvoiceId: existing,\n cyclesEmitted,\n usageRecordsRated: totalUsageRatedThisAccount,\n }\n }\n }\n\n const items = await em.find(BillingItem, {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n deletedAt: null,\n } as never)\n\n const usageItems = (items as BillingItem[]).filter((it) => it.type === 'usage')\n\n // Non-usage path: one_time + recurring filtered by the selector.\n const { included } = selectItemsForPeriod(items as SelectableItem[], period)\n const billableItems = included as unknown as BillingItem[]\n\n const currencyMismatchIds = billableItems\n .filter((it) => it.currencyMismatch)\n .map((it) => it.id)\n if (currencyMismatchIds.length > 0) {\n aggregatedWarnings.currency_mismatch_items = [\n ...(aggregatedWarnings.currency_mismatch_items ?? []),\n ...currencyMismatchIds,\n ]\n }\n const itemLines = buildInvoiceLinesFromItems(billableItems)\n\n // Usage path: aggregate matching usage records per uom_code, rate,\n // emit one line per matched usage item, warn about unmatched UoMs.\n const usageResult = await processUsageForAccount(em, account, period, usageItems)\n if (usageResult.unmatchedUoms.length > 0) {\n aggregatedWarnings.unmatched_usage_uoms = [\n ...(aggregatedWarnings.unmatched_usage_uoms ?? []),\n ...usageResult.unmatchedUoms,\n ]\n }\n\n const allLines: InvoiceLineDescriptor[] = [...itemLines, ...usageResult.lines]\n if (allLines.length === 0) {\n // Nothing to bill this cycle \u2014 still advance so the account moves\n // forward in real mode. In test / dry, no advance, so we'd loop\n // forever; break out.\n if (params.mode !== 'real') break\n account.nextBillDate = advanceNextBillDate(\n account.nextBillDate,\n account.billCycle as BillCycle,\n )\n account.lastBillDate = period.periodEnd\n account.updatedAt = new Date()\n cyclesEmitted += 1\n continue\n }\n\n const written = await writeDraftInvoice(em, container, {\n mode: params.mode,\n account,\n period,\n billRunId: billRun.id,\n lines: allLines,\n })\n lastDraftInvoiceId = written.invoiceId\n\n if (params.mode === 'real') {\n const now = new Date()\n for (const item of billableItems) {\n item.billedToDate = period.periodEnd\n item.updatedAt = now\n }\n // Mark consumed usage records \u2014 single bulk update so we don't\n // round-trip per record on accounts with thousands of usage rows.\n if (usageResult.matchedUoms.length > 0) {\n // Predicate-based bulk update: marks every still-unrated record\n // for the matched uoms in the account's period with a single\n // SQL UPDATE \u2014 no id list ever leaves Postgres. Stays\n // memory-bounded even for telco accounts with millions of\n // usage records per cycle (spec Performance Considerations\n // \"Shape B\").\n await em.nativeUpdate(\n BillingAccountUsage,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n uomCode: { $in: usageResult.matchedUoms },\n ratedInBillRunId: null,\n periodEnd: { $lte: period.periodEnd },\n deletedAt: null,\n } as never,\n { ratedInBillRunId: billRun.id, updatedAt: now } as never,\n )\n }\n totalUsageRatedThisAccount += usageResult.ratedCount\n account.nextBillDate = advanceNextBillDate(\n account.nextBillDate,\n account.billCycle as BillCycle,\n )\n account.lastBillDate = period.periodEnd\n account.updatedAt = now\n await em.flush()\n } else {\n // Test / dry: do not advance state. One iteration only.\n break\n }\n\n cyclesEmitted += 1\n }\n\n const hasCurrencyWarnings =\n (aggregatedWarnings.currency_mismatch_items ?? []).length > 0\n const hasUnmatchedUsage =\n (aggregatedWarnings.unmatched_usage_uoms ?? []).length > 0\n const hasWarnings = hasCurrencyWarnings || hasUnmatchedUsage\n return {\n status: hasWarnings ? 'success_with_warnings' : 'success',\n warnings: hasWarnings ? aggregatedWarnings : null,\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted,\n usageRecordsRated: totalUsageRatedThisAccount,\n }\n}\n\nasync function findExistingDraftForPeriod(\n em: EntityManager,\n account: BillingAccount,\n period: { periodStart: Date; periodEnd: Date },\n): Promise<string | null> {\n const periodStartISO = period.periodStart.toISOString().slice(0, 10)\n const periodEndISO = period.periodEnd.toISOString().slice(0, 10)\n const rows = (await em.execute(\n `SELECT id FROM sales_invoices\n WHERE tenant_id = ?\n AND organization_id = ?\n AND deleted_at IS NULL\n AND status = 'draft'\n AND metadata->>'bill_account_id' = ?\n AND metadata->>'bill_period_start' = ?\n AND metadata->>'bill_period_end' = ?\n LIMIT 1`,\n [account.tenantId, account.organizationId, account.id, periodStartISO, periodEndISO],\n )) as unknown as Array<{ id: string }> | undefined\n if (!rows || rows.length === 0) return null\n return rows[0].id ?? null\n}\n\nfunction computeFinalRunStatus(\n outcomes: readonly BillingRunOutcome[],\n): BillingRun['status'] {\n if (outcomes.length === 0) return 'completed'\n const anyFailed = outcomes.some((o) => o.status === 'failed')\n const anySuccess = outcomes.some(\n (o) => o.status === 'success' || o.status === 'success_with_warnings',\n )\n if (anyFailed && anySuccess) return 'partial_failure'\n if (anyFailed) return 'partial_failure'\n return 'completed'\n}\n\nfunction buildRunSummary(\n outcomes: readonly BillingRunOutcome[],\n totalAccounts: number,\n usageRecordsRated: number,\n): Record<string, unknown> {\n return {\n accounts_processed: totalAccounts,\n drafts_created: outcomes.filter(\n (o) => o.draftInvoiceId !== null && o.status !== 'skipped_existing_draft',\n ).length,\n drafts_skipped_existing: outcomes.filter((o) => o.status === 'skipped_existing_draft')\n .length,\n // SPEC-002 prepaid period close.\n statements_created: outcomes.filter(\n (o) => o.statementId !== null && o.status !== 'skipped_existing_statement',\n ).length,\n statements_skipped_existing: outcomes.filter(\n (o) => o.status === 'skipped_existing_statement',\n ).length,\n accounts_failed: outcomes.filter((o) => o.status === 'failed').length,\n accounts_with_warnings: outcomes.filter((o) => o.status === 'success_with_warnings')\n .length,\n usage_records_rated: usageRecordsRated,\n }\n}\n"],
5
- "mappings": "AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB,kBAAkB,kBAAkB;AAClE;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,sBAAsB;AAC/B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AAEtC,SAAS,wBAA6C;AAsFtD,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAE1B,eAAsB,WACpB,IACA,WACA,QAC2B;AAC3B,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA,EAAE,UAAU,OAAO,UAAU,UAAU,cAAc;AAAA,IACrD,OAAO,QAAQ,qBAAqB,KAAK,WAAW,MAAM;AAAA,EAC5D;AAIA,aAAW,MAAM,OAAO,eAAe;AACrC,UAAM,iBAAiB,GAAG,IAAsB,GAAG,SAAS,EAAE,YAAY,KAAK,CAAC;AAAA,EAClF;AACA,SAAO,EAAE,SAAS,OAAO,SAAS,UAAU,OAAO,SAAS;AAC9D;AAEA,eAAe,qBACb,IACA,WACA,QACoB;AACpB,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,UAAU,GAAG,OAAO,YAAY;AAAA,IACpC,gBAAgB,OAAO;AAAA,IACvB,UAAU,OAAO;AAAA,IACjB,aAAa,OAAO;AAAA,IACpB,aAAa,OAAO,eAAe;AAAA,IACnC,QAAQ,OAAO,SAAS;AAAA,IACxB,UAAU,OAAO,SAAS;AAAA,IAC1B,SAAS,QAAQ,OAAO,OAAO;AAAA,IAC/B,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,UAAU,OAAO;AAAA,IACjB,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,WAAW;AAAA,IACX,WAAW;AAAA,EACb,CAAC;AACD,KAAG,QAAQ,OAAO;AAClB,QAAM,GAAG,MAAM;AAEf,QAAM,WAAW,MAAM,qBAAqB,IAAI,MAAM;AACtD,QAAM,WAAgC,CAAC;AACvC,QAAM,gBAA+B,CAAC;AACtC,MAAI,yBAAyB;AAE7B,aAAW,WAAW,UAAU;AAC9B,UAAM,gBAAgB,MAAM,qBAAqB,IAAI,WAAW,SAAS,SAAS,MAAM;AACxF,QAAI,cAAc,WAAW,UAAU;AACrC,gCAA0B,cAAc;AAAA,IAC1C;AACA,UAAM,UAAU,GAAG,OAAO,mBAAmB;AAAA,MAC3C,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,MACjB,WAAW,QAAQ;AAAA,MACnB,eAAe,QAAQ;AAAA,MACvB,QAAQ,cAAc;AAAA,MACtB,cAAc,cAAc,WAAW,WAAW,cAAc,eAAe;AAAA,MAC/E,UACE,cAAc,WAAW,WACrB,OACC,cAAc;AAAA,MACrB,gBAAgB,cAAc,WAAW,WAAW,OAAO,cAAc;AAAA,MACzE,aAAa,cAAc,WAAW,WAAW,OAAQ,cAAc,eAAe;AAAA,MACtF,UAAU;AAAA,MACV,WAAW,oBAAI,KAAK;AAAA,MACpB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,OAAG,QAAQ,OAAO;AAClB,UAAM,GAAG,MAAM;AACf,aAAS,KAAK,OAAO;AAIrB,QAAI,cAAc,WAAW,YAAY,cAAc,iBAAiB;AACtE,oBAAc,KAAK,GAAG,cAAc,eAAe;AAAA,IACrD;AAAA,EACF;AAEA,UAAQ,SAAS,sBAAsB,QAAQ;AAC/C,UAAQ,aAAa,oBAAI,KAAK;AAC9B,UAAQ,YAAY,QAAQ;AAC5B,UAAQ,UAAU,gBAAgB,UAAU,SAAS,QAAQ,sBAAsB;AACnF,QAAM,GAAG,MAAM;AAEf,SAAO,EAAE,SAAS,UAAU,cAAc;AAC5C;AAEA,eAAe,qBACb,IACA,QAC2B;AAC3B,QAAM,QAAiC;AAAA,IACrC,UAAU,OAAO;AAAA,IACjB,gBAAgB,OAAO;AAAA,IACvB,WAAW;AAAA,IACX,cAAc,EAAE,MAAM,OAAO,SAAS;AAAA,EACxC;AACA,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS,GAAG;AACjE,UAAM,KAAK,EAAE,KAAK,OAAO,iBAAiB;AAAA,EAC5C;AACA,SAAO,GAAG,KAAK,gBAAgB,KAAc;AAC/C;AAEA,eAAe,qBACb,IACA,WACA,SACA,SACA,QAC+B;AAC/B,MAAI;AACF,WAAO,MAAM,GAAG,cAAc,OAAO,UAAU;AAC7C,aAAO,eAAe,OAAO,WAAW,SAAS,SAAS,MAAM;AAAA,IAClE,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,eAAe,eACb,IACA,WACA,SACA,SACA,QAC+B;AAI/B,MAAI,QAAQ,gBAAgB,WAAW;AACrC,UAAM,SAAS,MAAM,sBAAsB,IAAI,SAAS,SAAS;AAAA,MAC/D,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,IAClB,CAAC;AACD,QAAI,OAAO,WAAW,UAAU;AAC9B,aAAO,EAAE,QAAQ,UAAU,cAAc,OAAO,aAAa;AAAA,IAC/D;AACA,WAAO;AAAA,MACL,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,gBAAgB;AAAA,MAChB,aAAa,OAAO;AAAA,MACpB,iBAAiB,OAAO;AAAA,MACxB,eAAe,OAAO;AAAA,MACtB,mBAAmB;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,YACJ,OAAO,gBAAgB,cAAc,CAAC,OAAO,UACzC,wBACA;AAEN,MAAI,gBAAgB;AACpB,MAAI,qBAAoC;AACxC,MAAI,6BAA6B;AACjC,QAAM,qBAAsC,CAAC;AAE7C,SAAO,gBAAgB,aAAa,WAAW,QAAQ,cAAc,OAAO,QAAQ,GAAG;AACrF,UAAM,SAAS,iBAAiB,QAAQ,cAAc,QAAQ,SAAsB;AAGpF,QAAI,OAAO,SAAS,OAAO;AACzB,YAAM,WAAW,MAAM,2BAA2B,IAAI,SAAS,MAAM;AACrE,UAAI,UAAU;AACZ,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,gBAAgB;AAAA,UAChB;AAAA,UACA,mBAAmB;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,GAAG,KAAK,aAAa;AAAA,MACvC,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,eAAe,QAAQ;AAAA,MACvB,WAAW;AAAA,IACb,CAAU;AAEV,UAAM,aAAc,MAAwB,OAAO,CAAC,OAAO,GAAG,SAAS,OAAO;AAG9E,UAAM,EAAE,SAAS,IAAI,qBAAqB,OAA2B,MAAM;AAC3E,UAAM,gBAAgB;AAEtB,UAAM,sBAAsB,cACzB,OAAO,CAAC,OAAO,GAAG,gBAAgB,EAClC,IAAI,CAAC,OAAO,GAAG,EAAE;AACpB,QAAI,oBAAoB,SAAS,GAAG;AAClC,yBAAmB,0BAA0B;AAAA,QAC3C,GAAI,mBAAmB,2BAA2B,CAAC;AAAA,QACnD,GAAG;AAAA,MACL;AAAA,IACF;AACA,UAAM,YAAY,2BAA2B,aAAa;AAI1D,UAAM,cAAc,MAAM,uBAAuB,IAAI,SAAS,QAAQ,UAAU;AAChF,QAAI,YAAY,cAAc,SAAS,GAAG;AACxC,yBAAmB,uBAAuB;AAAA,QACxC,GAAI,mBAAmB,wBAAwB,CAAC;AAAA,QAChD,GAAG,YAAY;AAAA,MACjB;AAAA,IACF;AAEA,UAAM,WAAoC,CAAC,GAAG,WAAW,GAAG,YAAY,KAAK;AAC7E,QAAI,SAAS,WAAW,GAAG;AAIzB,UAAI,OAAO,SAAS,OAAQ;AAC5B,cAAQ,eAAe;AAAA,QACrB,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AACA,cAAQ,eAAe,OAAO;AAC9B,cAAQ,YAAY,oBAAI,KAAK;AAC7B,uBAAiB;AACjB;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,kBAAkB,IAAI,WAAW;AAAA,MACrD,MAAM,OAAO;AAAA,MACb;AAAA,MACA;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AACD,yBAAqB,QAAQ;AAE7B,QAAI,OAAO,SAAS,QAAQ;AAC1B,YAAM,MAAM,oBAAI,KAAK;AACrB,iBAAW,QAAQ,eAAe;AAChC,aAAK,eAAe,OAAO;AAC3B,aAAK,YAAY;AAAA,MACnB;AAGA,UAAI,YAAY,YAAY,SAAS,GAAG;AAOtC,cAAM,GAAG;AAAA,UACP;AAAA,UACA;AAAA,YACE,UAAU,QAAQ;AAAA,YAClB,gBAAgB,QAAQ;AAAA,YACxB,eAAe,QAAQ;AAAA,YACvB,SAAS,EAAE,KAAK,YAAY,YAAY;AAAA,YACxC,kBAAkB;AAAA,YAClB,WAAW,EAAE,MAAM,OAAO,UAAU;AAAA,YACpC,WAAW;AAAA,UACb;AAAA,UACA,EAAE,kBAAkB,QAAQ,IAAI,WAAW,IAAI;AAAA,QACjD;AAAA,MACF;AACA,oCAA8B,YAAY;AAC1C,cAAQ,eAAe;AAAA,QACrB,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AACA,cAAQ,eAAe,OAAO;AAC9B,cAAQ,YAAY;AACpB,YAAM,GAAG,MAAM;AAAA,IACjB,OAAO;AAEL;AAAA,IACF;AAEA,qBAAiB;AAAA,EACnB;AAEA,QAAM,uBACH,mBAAmB,2BAA2B,CAAC,GAAG,SAAS;AAC9D,QAAM,qBACH,mBAAmB,wBAAwB,CAAC,GAAG,SAAS;AAC3D,QAAM,cAAc,uBAAuB;AAC3C,SAAO;AAAA,IACL,QAAQ,cAAc,0BAA0B;AAAA,IAChD,UAAU,cAAc,qBAAqB;AAAA,IAC7C,gBAAgB;AAAA,IAChB;AAAA,IACA,mBAAmB;AAAA,EACrB;AACF;AAEA,eAAe,2BACb,IACA,SACA,QACwB;AACxB,QAAM,iBAAiB,OAAO,YAAY,YAAY,EAAE,MAAM,GAAG,EAAE;AACnE,QAAM,eAAe,OAAO,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE;AAC/D,QAAM,OAAQ,MAAM,GAAG;AAAA,IACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,CAAC,QAAQ,UAAU,QAAQ,gBAAgB,QAAQ,IAAI,gBAAgB,YAAY;AAAA,EACrF;AACA,MAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;AACvC,SAAO,KAAK,CAAC,EAAE,MAAM;AACvB;AAEA,SAAS,sBACP,UACsB;AACtB,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AAC5D,QAAM,aAAa,SAAS;AAAA,IAC1B,CAAC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW;AAAA,EAChD;AACA,MAAI,aAAa,WAAY,QAAO;AACpC,MAAI,UAAW,QAAO;AACtB,SAAO;AACT;AAEA,SAAS,gBACP,UACA,eACA,mBACyB;AACzB,SAAO;AAAA,IACL,oBAAoB;AAAA,IACpB,gBAAgB,SAAS;AAAA,MACvB,CAAC,MAAM,EAAE,mBAAmB,QAAQ,EAAE,WAAW;AAAA,IACnD,EAAE;AAAA,IACF,yBAAyB,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,wBAAwB,EAClF;AAAA;AAAA,IAEH,oBAAoB,SAAS;AAAA,MAC3B,CAAC,MAAM,EAAE,gBAAgB,QAAQ,EAAE,WAAW;AAAA,IAChD,EAAE;AAAA,IACF,6BAA6B,SAAS;AAAA,MACpC,CAAC,MAAM,EAAE,WAAW;AAAA,IACtB,EAAE;AAAA,IACF,iBAAiB,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,EAAE;AAAA,IAC/D,wBAAwB,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,uBAAuB,EAChF;AAAA,IACH,qBAAqB;AAAA,EACvB;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { UniqueConstraintViolationException } from '@mikro-orm/core'\nimport {\n BillingAccount,\n BillingAccountUsage,\n BillingInvoice,\n BillingItem,\n BillingRun,\n BillingRunOutcome,\n} from '../data/entities'\nimport {\n addDays,\n advanceNextBillDate,\n cloneAtMidnight,\n deriveBillPeriod,\n isCycleDue,\n shiftCycles,\n type BillPeriod,\n} from './billPeriod'\nimport { shouldIncludeItem, type SelectableItem } from './itemSelector'\nimport {\n buildInvoiceLinesFromItems,\n buildRecurringLineDescriptors,\n displayPeriod,\n writeDraftInvoice,\n type InvoiceLineDescriptor,\n type WriteMode,\n} from './invoiceWriter'\nimport { computeRecurringLines, type RecurringSkip } from './servicePeriods'\nimport { withTenantLock } from './tenantLock'\nimport { processUsageForAccount } from './usageRunner'\nimport { processPrepaidAccount } from './prepaidPeriodClose'\nimport type { DomainEvent } from './balanceEvents'\nimport { emitBillingEvent, type BillingEventId } from '../events'\nimport type { BillCycle } from '../data/validators'\n\n/**\n * Bill Run engine \u2014 orchestrates one execution of the billing cycle for\n * a single tenant.\n *\n * Concurrency / locking:\n * - The whole run is wrapped in `withTenantLock(...)`, which holds a\n * transaction-scoped Postgres advisory lock keyed by\n * `hash(tenant_id || ':billing-run')`. A second trigger fired while a\n * run is in flight short-circuits with `TenantLockBusyError` (the\n * command layer maps that to HTTP 409).\n * - Within the outer transaction, every account is processed inside a\n * nested `em.transactional(...)` (PG SAVEPOINT). A per-account failure\n * rolls back only that account's work, leaving siblings unaffected.\n *\n * Cycles (Spec A): every run \u2014 manual or scheduled, any mode \u2014 bills ALL\n * due bill dates up to `asOfDate`, one draft per bill date (never\n * combined), capped at `CATCH_UP_HARD_CAP` per account. The account's\n * `next_bill_date` and each recurring item's `billed_to_date` watermark are\n * advanced in memory cycle by cycle; only `real` persists them:\n * - `real` \u2014 persist drafts, advance `next_bill_date` / watermarks, mark\n * usage rated. Production path.\n * - `test` \u2014 persist drafts flagged `metadata.test_run=true` for every\n * cycle, but do NOT advance state. Repeatable.\n * - `dry` \u2014 compute every cycle; persist nothing. Preview only.\n *\n * Recurring items are priced by `servicePeriods.computeRecurringLines`\n * (in advance / in arrears, proration, credits). `one_time` items use the\n * selector; usage is always billed in arrears for `[D \u2212 cycle, D \u2212 1]`.\n *\n * Anti-duplicate guards (every mode except `dry`):\n * 1. Open draft for the same `(account, bill_date, test_run)`.\n * 2. `billed_to_date` per BillingItem (the watermark).\n */\n\nexport type RunMode = WriteMode\n\nexport type RunBillRunParams = {\n tenantId: string\n organizationId: string\n asOfDate: Date\n triggeredBy: 'schedule' | 'manual'\n mode: RunMode\n scopedAccountIds?: string[]\n parentRunId?: string | null\n}\n\nexport type RunBillRunResult = {\n billRun: BillingRun\n outcomes: BillingRunOutcome[]\n}\n\ntype RunOutput = RunBillRunResult & {\n /** Prepaid statement / crossing events to emit AFTER the outer tx commits. */\n pendingEvents: DomainEvent[]\n}\n\ntype SkippedItemWarning = {\n item_id: string\n reason: string\n service_period_start: string | null\n service_period_end: string | null\n}\n\ntype AccountWarnings = {\n currency_mismatch_items?: string[]\n unmatched_usage_uoms?: string[]\n /** Spec A: bill dates whose draft has a negative total (credits > charges). */\n negative_total_bill_dates?: string[]\n /** Spec A: the account hit CATCH_UP_HARD_CAP; later cycles wait for the next run. */\n catch_up_cap_reached?: boolean\n /** Spec A: diagnostic \u2014 partial periods not prorated, no-credit ends, caps. */\n skipped_items?: SkippedItemWarning[]\n}\n\n/** `charged` is the prepaid analogue of `invoiced` (Spec F, P-D6). */\nexport type CycleStatus = 'invoiced' | 'charged' | 'previewed' | 'empty' | 'skipped_existing'\n\nexport type CycleRecord = {\n bill_date: string\n period_start: string | null\n period_end: string | null\n status: CycleStatus\n line_count: number\n total_net: string\n invoice_id: string | null\n /** Prepaid only: what left the balance, and the statement for the period. */\n total_debit?: string\n statement_id?: string | null\n}\n\ntype AccountProcessSuccess = {\n status:\n | 'success'\n | 'success_with_warnings'\n | 'skipped_existing_draft'\n | 'skipped_existing_statement'\n warnings: AccountWarnings | Record<string, unknown> | null\n draftInvoiceId: string | null\n // SPEC-002: prepaid accounts produce a statement instead of a draft invoice.\n statementId?: string | null\n statementEvents?: DomainEvent[]\n cyclesEmitted: number\n usageRecordsRated: number\n /** Spec A: per-bill-date breakdown (postpaid). */\n cycles?: CycleRecord[]\n}\n\ntype AccountProcessFailed = {\n status: 'failed'\n errorMessage: string\n}\n\ntype AccountProcessResult = AccountProcessSuccess | AccountProcessFailed\n\nexport const CATCH_UP_HARD_CAP = 60 // safety net so a misconfigured account can't loop forever\nconst SKIPPED_ITEMS_WARNING_CAP = 200\n/** Skip reasons that are normal state, not worth surfacing on the outcome. */\nconst QUIET_SKIP_REASONS = new Set(['item-inactive', 'item-soft-deleted'])\n\nexport async function runBillRun(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunBillRunResult> {\n const output = await withTenantLock(\n em,\n { tenantId: params.tenantId, lockName: 'billing-run' },\n async (tem) => executeRunWithinLock(tem, container, params),\n )\n // Emit prepaid statement / crossing events ONLY after the outer transaction\n // (held by withTenantLock) has committed \u2014 emitting inside the lock would let\n // a consumer observe an uncommitted statement or a phantom event on rollback.\n for (const ev of output.pendingEvents) {\n await emitBillingEvent(ev.id as BillingEventId, ev.payload, { persistent: true })\n }\n return { billRun: output.billRun, outcomes: output.outcomes }\n}\n\nasync function executeRunWithinLock(\n em: EntityManager,\n container: AwilixContainer,\n params: RunBillRunParams,\n): Promise<RunOutput> {\n const now = new Date()\n const billRun = em.create(BillingRun, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n triggeredBy: params.triggeredBy,\n parentRunId: params.parentRunId ?? null,\n dryRun: params.mode === 'dry',\n testMode: params.mode === 'test',\n scopedAccountIds: params.scopedAccountIds ?? null,\n asOfDate: params.asOfDate,\n startedAt: now,\n status: 'running',\n summary: null,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(billRun)\n await em.flush()\n\n const accounts = await selectTargetAccounts(em, params)\n const outcomes: BillingRunOutcome[] = []\n const pendingEvents: DomainEvent[] = []\n let totalUsageRecordsRated = 0\n\n for (const account of accounts) {\n const processResult = await processAccountSafely(em, container, billRun, account, params)\n if (processResult.status !== 'failed') {\n totalUsageRecordsRated += processResult.usageRecordsRated\n }\n const outcome = em.create(BillingRunOutcome, {\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n billRunId: billRun.id,\n billAccountId: account.id,\n status: processResult.status,\n errorMessage: processResult.status === 'failed' ? processResult.errorMessage : null,\n warnings:\n processResult.status === 'failed'\n ? null\n : (processResult.warnings as Record<string, unknown> | null),\n draftInvoiceId: processResult.status === 'failed' ? null : processResult.draftInvoiceId,\n statementId: processResult.status === 'failed' ? null : (processResult.statementId ?? null),\n cycles: processResult.status === 'failed' ? null : (processResult.cycles ?? null),\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(outcome)\n await em.flush()\n outcomes.push(outcome)\n\n // Collect statement / crossing events \u2014 runBillRun emits them AFTER the\n // outer tx commits (see runBillRun), never from inside the lock.\n if (processResult.status !== 'failed' && processResult.statementEvents) {\n pendingEvents.push(...processResult.statementEvents)\n }\n }\n\n billRun.status = computeFinalRunStatus(outcomes)\n billRun.finishedAt = new Date()\n billRun.updatedAt = billRun.finishedAt\n billRun.summary = buildRunSummary(outcomes, accounts.length, totalUsageRecordsRated)\n await em.flush()\n\n return { billRun, outcomes, pendingEvents }\n}\n\nasync function selectTargetAccounts(\n em: EntityManager,\n params: RunBillRunParams,\n): Promise<BillingAccount[]> {\n const where: Record<string, unknown> = {\n tenantId: params.tenantId,\n organizationId: params.organizationId,\n deletedAt: null,\n nextBillDate: { $lte: params.asOfDate },\n }\n if (params.scopedAccountIds && params.scopedAccountIds.length > 0) {\n where.id = { $in: params.scopedAccountIds }\n }\n return em.find(BillingAccount, where as never)\n}\n\nasync function processAccountSafely(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n try {\n return await em.transactional(async (subEm) => {\n return processAccount(subEm, container, billRun, account, params)\n })\n } catch (err) {\n return {\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nasync function processAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n // SPEC-002: a prepaid account never gets a payable draft invoice. It still\n // runs in the Bill Run, but charges recurring / one_time items to the\n // balance and produces a non-fiscal consumption statement.\n if (account.billingMode === 'prepaid') {\n const result = await processPrepaidAccount(em, account, billRun, {\n asOfDate: params.asOfDate,\n mode: params.mode,\n triggeredBy: params.triggeredBy,\n })\n if (result.status === 'failed') {\n return { status: 'failed', errorMessage: result.errorMessage }\n }\n return {\n status: result.status,\n warnings: result.warnings,\n draftInvoiceId: null,\n statementId: result.statementId,\n statementEvents: result.statementEvents,\n cyclesEmitted: result.cyclesEmitted,\n cycles: result.cycles,\n usageRecordsRated: 0,\n }\n }\n\n return processPostpaidAccount(em, container, billRun, account, params)\n}\n\nfunction isoDate(date: Date): string {\n return cloneAtMidnight(date).toISOString().slice(0, 10)\n}\n\nfunction toSkipWarning(skip: RecurringSkip): SkippedItemWarning {\n return {\n item_id: skip.billingItemId,\n reason: skip.reason,\n service_period_start: skip.servicePeriodStart ? isoDate(skip.servicePeriodStart) : null,\n service_period_end: skip.servicePeriodEnd ? isoDate(skip.servicePeriodEnd) : null,\n }\n}\n\n/**\n * Postpaid cycle loop (Spec A). Items are loaded once; the bill date and\n * the per-item watermarks advance in memory so test / dry can simulate every\n * due cycle exactly like real does \u2014 only persistence differs by mode.\n */\nasync function processPostpaidAccount(\n em: EntityManager,\n container: AwilixContainer,\n billRun: BillingRun,\n account: BillingAccount,\n params: RunBillRunParams,\n): Promise<AccountProcessResult> {\n const cycle = account.billCycle as BillCycle\n const testRun = params.mode === 'test'\n const items = (await em.find(BillingItem, {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n deletedAt: null,\n } as never)) as BillingItem[]\n const recurringItems = items.filter((it) => it.type === 'recurring')\n const oneTimeItems = items.filter((it) => it.type === 'one_time')\n const usageItems = items.filter((it) => it.type === 'usage')\n\n // In-memory cycle state \u2014 persisted per cycle only in real mode.\n const watermarks = new Map<string, Date | null>(\n recurringItems.map((it) => [it.id, it.billedToDate ? cloneAtMidnight(it.billedToDate) : null]),\n )\n const billedOneTimes = new Set<string>(oneTimeItems.filter((it) => it.billedToDate).map((it) => it.id))\n let billDate = cloneAtMidnight(account.nextBillDate)\n // Usage already counted by an earlier cycle of THIS run (test / dry never\n // mark records, so later simulated cycles must not re-count them).\n let usageCountedThrough: Date | null = null\n\n const cycles: CycleRecord[] = []\n const warnings: AccountWarnings = {}\n const skippedItems: SkippedItemWarning[] = []\n let lastDraftInvoiceId: string | null = null\n let usageRecordsRated = 0\n\n const pushSkips = (skips: readonly RecurringSkip[]) => {\n for (const skip of skips) {\n if (QUIET_SKIP_REASONS.has(skip.reason)) continue\n if (skippedItems.length < SKIPPED_ITEMS_WARNING_CAP) skippedItems.push(toSkipWarning(skip))\n }\n }\n const addWarning = (key: 'currency_mismatch_items' | 'unmatched_usage_uoms' | 'negative_total_bill_dates', values: string[]) => {\n if (values.length === 0) return\n warnings[key] = Array.from(new Set([...(warnings[key] ?? []), ...values]))\n }\n\n while (isCycleDue(billDate, params.asOfDate)) {\n if (cycles.length >= CATCH_UP_HARD_CAP) {\n warnings.catch_up_cap_reached = true\n break\n }\n const nextBillDate = advanceNextBillDate(billDate, cycle)\n const arrearsPeriod: BillPeriod = deriveBillPeriod(billDate, cycle)\n\n // \u2500\u2500 Recurring: service periods against the in-memory watermarks \u2500\u2500\n const lines: InvoiceLineDescriptor[] = []\n const watermarkUpdates = new Map<string, Date | null>()\n const chargedItems: BillingItem[] = []\n for (const item of recurringItems) {\n const unitPrice = (item.rateJson as { unit_price?: number } | null)?.unit_price\n if (typeof unitPrice !== 'number') continue\n const computed = computeRecurringLines(\n {\n id: item.id,\n chargeTiming: item.chargeTiming ?? 'in_advance',\n prorate: item.prorate ?? true,\n billStartDate: item.billStartDate,\n billEndDate: item.billEndDate ?? null,\n billedToDate: watermarks.get(item.id) ?? null,\n unitPrice,\n isActive: item.isActive,\n deletedAt: item.deletedAt ?? null,\n },\n { billDate, cycle },\n )\n pushSkips(computed.skipped)\n if (computed.newBilledToDate?.getTime() !== watermarks.get(item.id)?.getTime()) {\n watermarkUpdates.set(item.id, computed.newBilledToDate)\n }\n if (computed.lines.length > 0) {\n lines.push(...buildRecurringLineDescriptors(item, computed.lines))\n chargedItems.push(item)\n }\n }\n\n // \u2500\u2500 One-time: first bill date whose [D \u2212 cycle, D + cycle \u2212 1] window overlaps \u2500\u2500\n const oneTimeWindow: BillPeriod = {\n periodStart: shiftCycles(billDate, cycle, -1),\n periodEnd: addDays(nextBillDate, -1),\n }\n const oneTimesThisCycle = oneTimeItems.filter(\n (it) => !billedOneTimes.has(it.id) && shouldIncludeItem(it as SelectableItem, oneTimeWindow).include,\n )\n lines.push(...buildInvoiceLinesFromItems(oneTimesThisCycle))\n chargedItems.push(...oneTimesThisCycle)\n\n // \u2500\u2500 Usage: always in arrears \u2500\u2500\n const usageResult = await processUsageForAccount(em, account, arrearsPeriod, usageItems, {\n after: usageCountedThrough,\n })\n addWarning('unmatched_usage_uoms', usageResult.unmatchedUoms)\n lines.push(\n ...usageResult.lines.map((line) => ({\n ...line,\n servicePeriodStart: arrearsPeriod.periodStart,\n servicePeriodEnd: arrearsPeriod.periodEnd,\n })),\n )\n addWarning(\n 'currency_mismatch_items',\n chargedItems.filter((it) => it.currencyMismatch).map((it) => it.id),\n )\n\n const period = displayPeriod(lines, arrearsPeriod)\n const record: CycleRecord = {\n bill_date: isoDate(billDate),\n period_start: lines.length ? isoDate(period.periodStart) : null,\n period_end: lines.length ? isoDate(period.periodEnd) : null,\n status: 'empty',\n line_count: lines.length,\n total_net: '0.0000',\n invoice_id: null,\n }\n\n // \u2500\u2500 Dedupe: an open draft already exists for this bill date \u2500\u2500\n const existing =\n params.mode === 'dry' ? null : await findExistingDraftForBillDate(em, account, billDate, testRun)\n if (existing && params.mode === 'real') {\n // Real state is inconsistent (draft written, account not advanced) \u2014\n // stop here without advancing, as before Spec A.\n cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })\n return {\n status: 'skipped_existing_draft',\n warnings: finalizeWarnings(warnings, skippedItems),\n draftInvoiceId: existing,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n }\n\n if (existing) {\n cycles.push({ ...record, status: 'skipped_existing', invoice_id: existing })\n } else if (lines.length > 0) {\n const written = await writeDraftOnce(em, {\n mode: params.mode,\n account,\n billDate,\n period,\n billRunId: billRun.id,\n lines,\n })\n if (written === 'conflict') {\n // Unique (account, bill_date, test_run) backstop fired: a concurrent\n // writer got there first. Same semantics as the dedupe check above.\n cycles.push({ ...record, status: 'skipped_existing' })\n if (params.mode === 'real') {\n return {\n status: 'skipped_existing_draft',\n warnings: finalizeWarnings(warnings, skippedItems),\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n }\n billDate = nextBillDate\n for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)\n for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)\n usageCountedThrough = arrearsPeriod.periodEnd\n continue\n }\n if (written.invoiceId) lastDraftInvoiceId = written.invoiceId\n if (Number.parseFloat(written.totalNet) < 0) addWarning('negative_total_bill_dates', [record.bill_date])\n cycles.push({\n ...record,\n status: params.mode === 'dry' ? 'previewed' : 'invoiced',\n total_net: written.totalNet,\n invoice_id: written.invoiceId,\n })\n } else {\n cycles.push(record)\n }\n\n // \u2500\u2500 Advance state: in memory always, persisted in real mode \u2500\u2500\n for (const [itemId, date] of watermarkUpdates) watermarks.set(itemId, date)\n for (const it of oneTimesThisCycle) billedOneTimes.add(it.id)\n usageCountedThrough = arrearsPeriod.periodEnd\n\n if (params.mode === 'real') {\n const now = new Date()\n for (const item of recurringItems) {\n if (!watermarkUpdates.has(item.id)) continue\n item.billedToDate = watermarkUpdates.get(item.id) ?? null\n item.updatedAt = now\n }\n for (const item of oneTimesThisCycle) {\n item.billedToDate = oneTimeWindow.periodEnd\n item.updatedAt = now\n }\n if (usageResult.matchedUoms.length > 0) {\n // Predicate-based bulk update: marks every still-unrated record for\n // the matched uoms up to the period end with a single SQL UPDATE \u2014\n // no id list ever leaves Postgres (spec Performance \"Shape B\").\n await em.nativeUpdate(\n BillingAccountUsage,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n uomCode: { $in: usageResult.matchedUoms },\n ratedInBillRunId: null,\n periodEnd: { $lte: arrearsPeriod.periodEnd },\n deletedAt: null,\n } as never,\n { ratedInBillRunId: billRun.id, updatedAt: now } as never,\n )\n usageRecordsRated += usageResult.ratedCount\n }\n account.nextBillDate = nextBillDate\n account.lastBillDate = arrearsPeriod.periodEnd\n account.updatedAt = now\n await em.flush()\n }\n\n billDate = nextBillDate\n }\n\n const finalWarnings = finalizeWarnings(warnings, skippedItems)\n return {\n status: hasRealWarnings(warnings) ? 'success_with_warnings' : 'success',\n warnings: finalWarnings,\n draftInvoiceId: lastDraftInvoiceId,\n cyclesEmitted: cycles.filter((c) => c.status === 'invoiced').length,\n usageRecordsRated,\n cycles,\n }\n}\n\nfunction finalizeWarnings(\n warnings: AccountWarnings,\n skippedItems: SkippedItemWarning[],\n): AccountWarnings | null {\n const merged: AccountWarnings = { ...warnings, ...(skippedItems.length ? { skipped_items: skippedItems } : {}) }\n return Object.keys(merged).length > 0 ? merged : null\n}\n\n/** Skipped items are diagnostics; they don't downgrade the outcome status. */\nfunction hasRealWarnings(warnings: AccountWarnings): boolean {\n return Boolean(\n warnings.currency_mismatch_items?.length ||\n warnings.unmatched_usage_uoms?.length ||\n warnings.negative_total_bill_dates?.length ||\n warnings.catch_up_cap_reached,\n )\n}\n\n/**\n * Existing invoice for `(account, bill_date, test_run)` in ANY status \u2014 a\n * posted invoice for the same bill date also blocks re-billing (e.g. after\n * an operator moved `next_bill_date` back). Test drafts never block real\n * drafts and vice versa. The partial unique index\n * `billing_invoices_bill_date_uq` is the backstop (see `writeDraftOnce`).\n */\nasync function findExistingDraftForBillDate(\n em: EntityManager,\n account: BillingAccount,\n billDate: Date,\n testRun: boolean,\n): Promise<string | null> {\n const existing = await em.findOne(\n BillingInvoice,\n {\n tenantId: account.tenantId,\n organizationId: account.organizationId,\n billAccountId: account.id,\n billDate,\n testRun,\n documentType: 'invoice',\n deletedAt: null,\n } as never,\n )\n return existing?.id ?? null\n}\n\n/**\n * Writes the draft inside its own savepoint so a unique-index conflict\n * (concurrent writer for the same bill date) doesn't abort the account's\n * transaction \u2014 it is reported as 'conflict' and handled like a dedupe hit.\n */\nasync function writeDraftOnce(\n em: EntityManager,\n params: Parameters<typeof writeDraftInvoice>[1],\n): Promise<Awaited<ReturnType<typeof writeDraftInvoice>> | 'conflict'> {\n if (params.mode === 'dry') return writeDraftInvoice(em, params)\n try {\n return await em.transactional((tem) => writeDraftInvoice(tem as EntityManager, params))\n } catch (err) {\n if (err instanceof UniqueConstraintViolationException) return 'conflict'\n throw err\n }\n}\n\nfunction computeFinalRunStatus(\n outcomes: readonly BillingRunOutcome[],\n): BillingRun['status'] {\n if (outcomes.length === 0) return 'completed'\n const anyFailed = outcomes.some((o) => o.status === 'failed')\n const anySuccess = outcomes.some(\n (o) => o.status === 'success' || o.status === 'success_with_warnings',\n )\n if (anyFailed && anySuccess) return 'partial_failure'\n if (anyFailed) return 'partial_failure'\n return 'completed'\n}\n\nfunction buildRunSummary(\n outcomes: readonly BillingRunOutcome[],\n totalAccounts: number,\n usageRecordsRated: number,\n): Record<string, unknown> {\n const allCycles = outcomes.flatMap((o) => (Array.isArray(o.cycles) ? (o.cycles as CycleRecord[]) : []))\n return {\n accounts_processed: totalAccounts,\n // Spec A: one draft per invoiced cycle (catch-up writes several per account).\n drafts_created: allCycles.filter((c) => c.status === 'invoiced' && c.invoice_id).length,\n cycles_processed: allCycles.length,\n drafts_skipped_existing: outcomes.filter((o) => o.status === 'skipped_existing_draft')\n .length,\n // SPEC-002 prepaid period close.\n statements_created: outcomes.filter(\n (o) => o.statementId !== null && o.status !== 'skipped_existing_statement',\n ).length,\n statements_skipped_existing: outcomes.filter(\n (o) => o.status === 'skipped_existing_statement',\n ).length,\n accounts_failed: outcomes.filter((o) => o.status === 'failed').length,\n accounts_with_warnings: outcomes.filter((o) => o.status === 'success_with_warnings')\n .length,\n usage_records_rated: usageRecordsRated,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }