@dainamite/billing 0.17.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 (248) hide show
  1. package/CHANGELOG.md +658 -0
  2. package/LICENSE +21 -0
  3. package/README.md +77 -0
  4. package/dist/modules/billing/acl.js +20 -0
  5. package/dist/modules/billing/acl.js.map +7 -0
  6. package/dist/modules/billing/api/accounts/route.js +182 -0
  7. package/dist/modules/billing/api/accounts/route.js.map +7 -0
  8. package/dist/modules/billing/api/export/account/[id]/route.js +170 -0
  9. package/dist/modules/billing/api/export/account/[id]/route.js.map +7 -0
  10. package/dist/modules/billing/api/invoices/[id]/route.js +85 -0
  11. package/dist/modules/billing/api/invoices/[id]/route.js.map +7 -0
  12. package/dist/modules/billing/api/invoices/add-line/route.js +69 -0
  13. package/dist/modules/billing/api/invoices/add-line/route.js.map +7 -0
  14. package/dist/modules/billing/api/invoices/edit-line/route.js +78 -0
  15. package/dist/modules/billing/api/invoices/edit-line/route.js.map +7 -0
  16. package/dist/modules/billing/api/invoices/post/route.js +74 -0
  17. package/dist/modules/billing/api/invoices/post/route.js.map +7 -0
  18. package/dist/modules/billing/api/invoices/remove-line/route.js +69 -0
  19. package/dist/modules/billing/api/invoices/remove-line/route.js.map +7 -0
  20. package/dist/modules/billing/api/invoices/route.js +109 -0
  21. package/dist/modules/billing/api/invoices/route.js.map +7 -0
  22. package/dist/modules/billing/api/items/bulk/route.js +74 -0
  23. package/dist/modules/billing/api/items/bulk/route.js.map +7 -0
  24. package/dist/modules/billing/api/items/route.js +224 -0
  25. package/dist/modules/billing/api/items/route.js.map +7 -0
  26. package/dist/modules/billing/api/openapi.js +25 -0
  27. package/dist/modules/billing/api/openapi.js.map +7 -0
  28. package/dist/modules/billing/api/run-outcomes/route.js +122 -0
  29. package/dist/modules/billing/api/run-outcomes/route.js.map +7 -0
  30. package/dist/modules/billing/api/runs/retry-failed/route.js +70 -0
  31. package/dist/modules/billing/api/runs/retry-failed/route.js.map +7 -0
  32. package/dist/modules/billing/api/runs/route.js +143 -0
  33. package/dist/modules/billing/api/runs/route.js.map +7 -0
  34. package/dist/modules/billing/api/test-invoices/wipe/route.js +65 -0
  35. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +7 -0
  36. package/dist/modules/billing/api/usage/route.js +138 -0
  37. package/dist/modules/billing/api/usage/route.js.map +7 -0
  38. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +402 -0
  39. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +7 -0
  40. package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js +25 -0
  41. package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js.map +7 -0
  42. package/dist/modules/billing/backend/billing/accounts/create/page.js +82 -0
  43. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +7 -0
  44. package/dist/modules/billing/backend/billing/accounts/create/page.meta.js +25 -0
  45. package/dist/modules/billing/backend/billing/accounts/create/page.meta.js.map +7 -0
  46. package/dist/modules/billing/backend/billing/accounts/page.js +173 -0
  47. package/dist/modules/billing/backend/billing/accounts/page.js.map +7 -0
  48. package/dist/modules/billing/backend/billing/accounts/page.meta.js +18 -0
  49. package/dist/modules/billing/backend/billing/accounts/page.meta.js.map +7 -0
  50. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +500 -0
  51. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +7 -0
  52. package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js +25 -0
  53. package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js.map +7 -0
  54. package/dist/modules/billing/backend/billing/invoices/page.js +187 -0
  55. package/dist/modules/billing/backend/billing/invoices/page.js.map +7 -0
  56. package/dist/modules/billing/backend/billing/invoices/page.meta.js +18 -0
  57. package/dist/modules/billing/backend/billing/invoices/page.meta.js.map +7 -0
  58. package/dist/modules/billing/backend/billing/items/[id]/page.js +265 -0
  59. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +7 -0
  60. package/dist/modules/billing/backend/billing/items/[id]/page.meta.js +25 -0
  61. package/dist/modules/billing/backend/billing/items/[id]/page.meta.js.map +7 -0
  62. package/dist/modules/billing/backend/billing/items/create/page.js +90 -0
  63. package/dist/modules/billing/backend/billing/items/create/page.js.map +7 -0
  64. package/dist/modules/billing/backend/billing/items/create/page.meta.js +25 -0
  65. package/dist/modules/billing/backend/billing/items/create/page.meta.js.map +7 -0
  66. package/dist/modules/billing/backend/billing/items/page.js +208 -0
  67. package/dist/modules/billing/backend/billing/items/page.js.map +7 -0
  68. package/dist/modules/billing/backend/billing/items/page.meta.js +18 -0
  69. package/dist/modules/billing/backend/billing/items/page.meta.js.map +7 -0
  70. package/dist/modules/billing/backend/billing/runs/[id]/page.js +293 -0
  71. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +7 -0
  72. package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js +21 -0
  73. package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js.map +7 -0
  74. package/dist/modules/billing/backend/billing/runs/page.js +341 -0
  75. package/dist/modules/billing/backend/billing/runs/page.js.map +7 -0
  76. package/dist/modules/billing/backend/billing/runs/page.meta.js +18 -0
  77. package/dist/modules/billing/backend/billing/runs/page.meta.js.map +7 -0
  78. package/dist/modules/billing/backend/billing/usage/create/page.js +100 -0
  79. package/dist/modules/billing/backend/billing/usage/create/page.js.map +7 -0
  80. package/dist/modules/billing/backend/billing/usage/create/page.meta.js +25 -0
  81. package/dist/modules/billing/backend/billing/usage/create/page.meta.js.map +7 -0
  82. package/dist/modules/billing/ce.js +56 -0
  83. package/dist/modules/billing/ce.js.map +7 -0
  84. package/dist/modules/billing/cli.js +67 -0
  85. package/dist/modules/billing/cli.js.map +7 -0
  86. package/dist/modules/billing/commands/accounts.js +157 -0
  87. package/dist/modules/billing/commands/accounts.js.map +7 -0
  88. package/dist/modules/billing/commands/index.js +6 -0
  89. package/dist/modules/billing/commands/index.js.map +7 -0
  90. package/dist/modules/billing/commands/invoices.js +401 -0
  91. package/dist/modules/billing/commands/invoices.js.map +7 -0
  92. package/dist/modules/billing/commands/items.js +317 -0
  93. package/dist/modules/billing/commands/items.js.map +7 -0
  94. package/dist/modules/billing/commands/runs.js +123 -0
  95. package/dist/modules/billing/commands/runs.js.map +7 -0
  96. package/dist/modules/billing/commands/usage.js +88 -0
  97. package/dist/modules/billing/commands/usage.js.map +7 -0
  98. package/dist/modules/billing/components/AccountForm.js +420 -0
  99. package/dist/modules/billing/components/AccountForm.js.map +7 -0
  100. package/dist/modules/billing/components/DetailFields.js +37 -0
  101. package/dist/modules/billing/components/DetailFields.js.map +7 -0
  102. package/dist/modules/billing/components/ItemForm.js +440 -0
  103. package/dist/modules/billing/components/ItemForm.js.map +7 -0
  104. package/dist/modules/billing/components/LineFormDialog.js +195 -0
  105. package/dist/modules/billing/components/LineFormDialog.js.map +7 -0
  106. package/dist/modules/billing/components/UsageForm.js +262 -0
  107. package/dist/modules/billing/components/UsageForm.js.map +7 -0
  108. package/dist/modules/billing/data/entities.js +433 -0
  109. package/dist/modules/billing/data/entities.js.map +7 -0
  110. package/dist/modules/billing/data/entityIds.js +12 -0
  111. package/dist/modules/billing/data/entityIds.js.map +7 -0
  112. package/dist/modules/billing/data/validators.js +396 -0
  113. package/dist/modules/billing/data/validators.js.map +7 -0
  114. package/dist/modules/billing/di.js +6 -0
  115. package/dist/modules/billing/di.js.map +7 -0
  116. package/dist/modules/billing/events.js +35 -0
  117. package/dist/modules/billing/events.js.map +7 -0
  118. package/dist/modules/billing/i18n/en.json +336 -0
  119. package/dist/modules/billing/i18n/pl.json +336 -0
  120. package/dist/modules/billing/index.js +17 -0
  121. package/dist/modules/billing/index.js.map +7 -0
  122. package/dist/modules/billing/lib/billPeriod.js +101 -0
  123. package/dist/modules/billing/lib/billPeriod.js.map +7 -0
  124. package/dist/modules/billing/lib/billRunEngine.js +256 -0
  125. package/dist/modules/billing/lib/billRunEngine.js.map +7 -0
  126. package/dist/modules/billing/lib/billRunLabel.js +8 -0
  127. package/dist/modules/billing/lib/billRunLabel.js.map +7 -0
  128. package/dist/modules/billing/lib/idempotency.js +13 -0
  129. package/dist/modules/billing/lib/idempotency.js.map +7 -0
  130. package/dist/modules/billing/lib/invoiceStatus.js +38 -0
  131. package/dist/modules/billing/lib/invoiceStatus.js.map +7 -0
  132. package/dist/modules/billing/lib/invoiceWriter.js +153 -0
  133. package/dist/modules/billing/lib/invoiceWriter.js.map +7 -0
  134. package/dist/modules/billing/lib/itemSelector.js +77 -0
  135. package/dist/modules/billing/lib/itemSelector.js.map +7 -0
  136. package/dist/modules/billing/lib/seeds/demo.js +130 -0
  137. package/dist/modules/billing/lib/seeds/demo.js.map +7 -0
  138. package/dist/modules/billing/lib/tenantLock.js +32 -0
  139. package/dist/modules/billing/lib/tenantLock.js.map +7 -0
  140. package/dist/modules/billing/lib/usageRater.js +150 -0
  141. package/dist/modules/billing/lib/usageRater.js.map +7 -0
  142. package/dist/modules/billing/lib/usageRunner.js +82 -0
  143. package/dist/modules/billing/lib/usageRunner.js.map +7 -0
  144. package/dist/modules/billing/migrations/Migration20260519000000_billing.js +144 -0
  145. package/dist/modules/billing/migrations/Migration20260519000000_billing.js.map +7 -0
  146. package/dist/modules/billing/setup.js +190 -0
  147. package/dist/modules/billing/setup.js.map +7 -0
  148. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +47 -0
  149. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +7 -0
  150. package/dist/modules/billing/translations.js +5 -0
  151. package/dist/modules/billing/translations.js.map +7 -0
  152. package/dist/modules/billing/workers/reap-stale-bill-runs.js +31 -0
  153. package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +7 -0
  154. package/package.json +148 -0
  155. package/src/modules/billing/__integration__/TC-BILL-001.spec.ts +116 -0
  156. package/src/modules/billing/__integration__/TC-BILL-002.spec.ts +169 -0
  157. package/src/modules/billing/__integration__/TC-BILL-003.spec.ts +28 -0
  158. package/src/modules/billing/__integration__/meta.ts +4 -0
  159. package/src/modules/billing/__tests__/commands/invoices.test.ts +464 -0
  160. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +284 -0
  161. package/src/modules/billing/__tests__/lib/billPeriod.test.ts +131 -0
  162. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +561 -0
  163. package/src/modules/billing/__tests__/lib/idempotency.test.ts +81 -0
  164. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +97 -0
  165. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +119 -0
  166. package/src/modules/billing/__tests__/lib/itemSelector.test.ts +192 -0
  167. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +86 -0
  168. package/src/modules/billing/__tests__/lib/tenantLock.test.ts +126 -0
  169. package/src/modules/billing/__tests__/lib/usageRater.test.ts +190 -0
  170. package/src/modules/billing/__tests__/package-isolation.test.ts +223 -0
  171. package/src/modules/billing/__tests__/setup.test.ts +107 -0
  172. package/src/modules/billing/__tests__/validators.test.ts +594 -0
  173. package/src/modules/billing/acl.ts +16 -0
  174. package/src/modules/billing/api/accounts/route.ts +198 -0
  175. package/src/modules/billing/api/export/account/[id]/route.ts +233 -0
  176. package/src/modules/billing/api/invoices/[id]/route.ts +143 -0
  177. package/src/modules/billing/api/invoices/add-line/route.ts +74 -0
  178. package/src/modules/billing/api/invoices/edit-line/route.ts +97 -0
  179. package/src/modules/billing/api/invoices/post/route.ts +97 -0
  180. package/src/modules/billing/api/invoices/remove-line/route.ts +75 -0
  181. package/src/modules/billing/api/invoices/route.ts +167 -0
  182. package/src/modules/billing/api/items/bulk/route.ts +92 -0
  183. package/src/modules/billing/api/items/route.ts +255 -0
  184. package/src/modules/billing/api/openapi.ts +33 -0
  185. package/src/modules/billing/api/run-outcomes/route.ts +149 -0
  186. package/src/modules/billing/api/runs/retry-failed/route.ts +88 -0
  187. package/src/modules/billing/api/runs/route.ts +170 -0
  188. package/src/modules/billing/api/test-invoices/wipe/route.ts +87 -0
  189. package/src/modules/billing/api/usage/route.ts +148 -0
  190. package/src/modules/billing/backend/billing/accounts/[id]/page.meta.ts +23 -0
  191. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +574 -0
  192. package/src/modules/billing/backend/billing/accounts/create/page.meta.ts +23 -0
  193. package/src/modules/billing/backend/billing/accounts/create/page.tsx +101 -0
  194. package/src/modules/billing/backend/billing/accounts/page.meta.ts +16 -0
  195. package/src/modules/billing/backend/billing/accounts/page.tsx +222 -0
  196. package/src/modules/billing/backend/billing/invoices/[id]/page.meta.ts +23 -0
  197. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +613 -0
  198. package/src/modules/billing/backend/billing/invoices/page.meta.ts +16 -0
  199. package/src/modules/billing/backend/billing/invoices/page.tsx +245 -0
  200. package/src/modules/billing/backend/billing/items/[id]/page.meta.ts +23 -0
  201. package/src/modules/billing/backend/billing/items/[id]/page.tsx +366 -0
  202. package/src/modules/billing/backend/billing/items/create/page.meta.ts +23 -0
  203. package/src/modules/billing/backend/billing/items/create/page.tsx +110 -0
  204. package/src/modules/billing/backend/billing/items/page.meta.ts +16 -0
  205. package/src/modules/billing/backend/billing/items/page.tsx +272 -0
  206. package/src/modules/billing/backend/billing/runs/[id]/page.meta.ts +19 -0
  207. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +404 -0
  208. package/src/modules/billing/backend/billing/runs/page.meta.ts +16 -0
  209. package/src/modules/billing/backend/billing/runs/page.tsx +426 -0
  210. package/src/modules/billing/backend/billing/usage/create/page.meta.ts +23 -0
  211. package/src/modules/billing/backend/billing/usage/create/page.tsx +120 -0
  212. package/src/modules/billing/ce.ts +74 -0
  213. package/src/modules/billing/cli.ts +89 -0
  214. package/src/modules/billing/commands/accounts.ts +205 -0
  215. package/src/modules/billing/commands/index.ts +13 -0
  216. package/src/modules/billing/commands/invoices.ts +542 -0
  217. package/src/modules/billing/commands/items.ts +438 -0
  218. package/src/modules/billing/commands/runs.ts +166 -0
  219. package/src/modules/billing/commands/usage.ts +118 -0
  220. package/src/modules/billing/components/AccountForm.tsx +517 -0
  221. package/src/modules/billing/components/DetailFields.tsx +60 -0
  222. package/src/modules/billing/components/ItemForm.tsx +558 -0
  223. package/src/modules/billing/components/LineFormDialog.tsx +253 -0
  224. package/src/modules/billing/components/UsageForm.tsx +326 -0
  225. package/src/modules/billing/data/entities.ts +533 -0
  226. package/src/modules/billing/data/entityIds.ts +25 -0
  227. package/src/modules/billing/data/validators.ts +562 -0
  228. package/src/modules/billing/di.ts +14 -0
  229. package/src/modules/billing/events.ts +47 -0
  230. package/src/modules/billing/i18n/en.json +336 -0
  231. package/src/modules/billing/i18n/pl.json +336 -0
  232. package/src/modules/billing/index.ts +22 -0
  233. package/src/modules/billing/lib/billPeriod.ts +181 -0
  234. package/src/modules/billing/lib/billRunEngine.ts +416 -0
  235. package/src/modules/billing/lib/billRunLabel.ts +14 -0
  236. package/src/modules/billing/lib/idempotency.ts +46 -0
  237. package/src/modules/billing/lib/invoiceStatus.ts +61 -0
  238. package/src/modules/billing/lib/invoiceWriter.ts +260 -0
  239. package/src/modules/billing/lib/itemSelector.ts +154 -0
  240. package/src/modules/billing/lib/seeds/demo.ts +199 -0
  241. package/src/modules/billing/lib/tenantLock.ts +87 -0
  242. package/src/modules/billing/lib/usageRater.ts +250 -0
  243. package/src/modules/billing/lib/usageRunner.ts +189 -0
  244. package/src/modules/billing/migrations/Migration20260519000000_billing.ts +175 -0
  245. package/src/modules/billing/setup.ts +252 -0
  246. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +89 -0
  247. package/src/modules/billing/translations.ts +9 -0
  248. package/src/modules/billing/workers/reap-stale-bill-runs.ts +57 -0
package/package.json ADDED
@@ -0,0 +1,148 @@
1
+ {
2
+ "name": "@dainamite/billing",
3
+ "version": "0.17.0",
4
+ "description": "Dainamite Billing — lightweight recurring-billing engine for Open Mercato (Bill Runs, one-time / recurring / usage items, draft invoices in core/sales with human approval).",
5
+ "type": "module",
6
+ "main": "./dist/modules/billing/index.js",
7
+ "files": [
8
+ "dist",
9
+ "src",
10
+ "README.md",
11
+ "CHANGELOG.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "node build.mjs",
15
+ "watch": "node watch.mjs",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "devDependencies": {
19
+ "chokidar": "^4.0.3"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./src/modules/billing/index.ts",
24
+ "default": "./dist/modules/billing/index.js"
25
+ },
26
+ "./modules/billing": {
27
+ "types": "./src/modules/billing/index.ts",
28
+ "default": "./dist/modules/billing/index.js"
29
+ },
30
+ "./*.ts": {
31
+ "types": "./src/*.ts",
32
+ "default": "./dist/*.js"
33
+ },
34
+ "./*.tsx": {
35
+ "types": "./src/*.tsx",
36
+ "default": "./dist/*.js"
37
+ },
38
+ "./*.json": "./src/*.json",
39
+ "./*": {
40
+ "types": [
41
+ "./src/*.ts",
42
+ "./src/*.tsx"
43
+ ],
44
+ "default": "./dist/*.js"
45
+ },
46
+ "./*/*.json": "./src/*/*.json",
47
+ "./*/*": {
48
+ "types": [
49
+ "./src/*/*.ts",
50
+ "./src/*/*.tsx"
51
+ ],
52
+ "default": "./dist/*/*.js"
53
+ },
54
+ "./*/*/*.json": "./src/*/*/*.json",
55
+ "./*/*/*": {
56
+ "types": [
57
+ "./src/*/*/*.ts",
58
+ "./src/*/*/*.tsx"
59
+ ],
60
+ "default": "./dist/*/*/*.js"
61
+ },
62
+ "./*/*/*/*.json": "./src/*/*/*/*.json",
63
+ "./*/*/*/*": {
64
+ "types": [
65
+ "./src/*/*/*/*.ts",
66
+ "./src/*/*/*/*.tsx"
67
+ ],
68
+ "default": "./dist/*/*/*/*.js"
69
+ },
70
+ "./*/*/*/*/*.json": "./src/*/*/*/*/*.json",
71
+ "./*/*/*/*/*": {
72
+ "types": [
73
+ "./src/*/*/*/*/*.ts",
74
+ "./src/*/*/*/*/*.tsx"
75
+ ],
76
+ "default": "./dist/*/*/*/*/*.js"
77
+ },
78
+ "./*/*/*/*/*/*.json": "./src/*/*/*/*/*/*.json",
79
+ "./*/*/*/*/*/*": {
80
+ "types": [
81
+ "./src/*/*/*/*/*/*.ts",
82
+ "./src/*/*/*/*/*/*.tsx"
83
+ ],
84
+ "default": "./dist/*/*/*/*/*/*.js"
85
+ },
86
+ "./*/*/*/*/*/*/*.json": "./src/*/*/*/*/*/*/*.json",
87
+ "./*/*/*/*/*/*/*": {
88
+ "types": [
89
+ "./src/*/*/*/*/*/*/*.ts",
90
+ "./src/*/*/*/*/*/*/*.tsx"
91
+ ],
92
+ "default": "./dist/*/*/*/*/*/*/*.js"
93
+ },
94
+ "./*/*/*/*/*/*/*/*.json": "./src/*/*/*/*/*/*/*/*.json",
95
+ "./*/*/*/*/*/*/*/*": {
96
+ "types": [
97
+ "./src/*/*/*/*/*/*/*/*.ts",
98
+ "./src/*/*/*/*/*/*/*/*.tsx"
99
+ ],
100
+ "default": "./dist/*/*/*/*/*/*/*/*.js"
101
+ },
102
+ "./*/*/*/*/*/*/*/*/*.json": "./src/*/*/*/*/*/*/*/*/*.json",
103
+ "./*/*/*/*/*/*/*/*/*": {
104
+ "types": [
105
+ "./src/*/*/*/*/*/*/*/*/*.ts",
106
+ "./src/*/*/*/*/*/*/*/*/*.tsx"
107
+ ],
108
+ "default": "./dist/*/*/*/*/*/*/*/*/*.js"
109
+ },
110
+ "./*/*/*/*/*/*/*/*/*/*.json": "./src/*/*/*/*/*/*/*/*/*/*.json",
111
+ "./*/*/*/*/*/*/*/*/*/*": {
112
+ "types": [
113
+ "./src/*/*/*/*/*/*/*/*/*/*.ts",
114
+ "./src/*/*/*/*/*/*/*/*/*/*.tsx"
115
+ ],
116
+ "default": "./dist/*/*/*/*/*/*/*/*/*/*.js"
117
+ }
118
+ },
119
+ "peerDependencies": {
120
+ "@mikro-orm/core": "^7.0.0",
121
+ "@mikro-orm/decorators": "^7.0.0",
122
+ "@mikro-orm/postgresql": "^7.0.0",
123
+ "@open-mercato/core": "^0.6.0",
124
+ "@open-mercato/events": "^0.6.0",
125
+ "@open-mercato/queue": "^0.6.0",
126
+ "@open-mercato/shared": "^0.6.0",
127
+ "@open-mercato/ui": "^0.6.0",
128
+ "@tanstack/react-table": "^8.0.0 || ^9.0.0",
129
+ "awilix": "^12.0.0",
130
+ "lucide-react": "^0.400.0",
131
+ "next": "^16.0.0",
132
+ "react": "^19.0.0",
133
+ "react-dom": "^19.0.0",
134
+ "zod": "^4.0.0"
135
+ },
136
+ "publishConfig": {
137
+ "access": "public"
138
+ },
139
+ "repository": {
140
+ "type": "git",
141
+ "url": "https://github.com/dainamite-technologies/dainamite-core.git",
142
+ "directory": "packages/billing"
143
+ },
144
+ "license": "MIT",
145
+ "engines": {
146
+ "node": ">=24.0.0"
147
+ }
148
+ }
@@ -0,0 +1,116 @@
1
+ import { test, expect, type APIRequestContext } from '@playwright/test'
2
+ import { getAuthToken, apiRequest } from '@open-mercato/core/helpers/integration/api'
3
+
4
+ /**
5
+ * TC-BILL-001: Billing Account CRUD via REST API
6
+ * Category: BILL — API
7
+ * Priority: High
8
+ *
9
+ * Covers the foundation entity of the billing engine. Also pins the
10
+ * list-response casing contract: `GET /api/billing/accounts` projects
11
+ * raw column names, so the items come back snake_case.
12
+ *
13
+ * KNOWN COVERAGE GAP: the `GET /api/billing/accounts?id=<id>` path —
14
+ * used by every billing detail page — is currently NOT
15
+ * integration-tested. Two specs that exercised it ("lists the
16
+ * account with snake_case fields" and "updates a mutable field") were
17
+ * removed because they reproduce a CI-only flake (QE returns empty
18
+ * list while raw SQL with identical scope finds the row). Five
19
+ * instrumented CI runs failed to isolate the root cause. See commit
20
+ * `c1ddabc` ("test(billing): remove the 4 GET-by-id flake specs
21
+ * entirely") for the full repro recipe and add the specs back once
22
+ * the upstream QueryEngine bug is understood.
23
+ */
24
+
25
+ const uniq = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
26
+
27
+ function accountPayload(overrides: Record<string, unknown> = {}) {
28
+ return {
29
+ customerId: `cust-${uniq()}`,
30
+ name: `Account ${uniq()}`,
31
+ currencyCode: 'PLN',
32
+ billCycle: 'monthly',
33
+ billCycleAnchor: '1',
34
+ invoiceEmail: `inv-${uniq()}@example.com`,
35
+ invoiceLanguage: 'pl',
36
+ nextBillDate: '2026-07-01',
37
+ invoiceAddress: {
38
+ line1: 'ul. Testowa 1',
39
+ city: 'Warszawa',
40
+ postal_code: '00-001',
41
+ country: 'PL',
42
+ },
43
+ ...overrides,
44
+ }
45
+ }
46
+
47
+ async function deleteAccount(request: APIRequestContext, token: string, id: string): Promise<void> {
48
+ await apiRequest(request, 'DELETE', '/api/billing/accounts', { token, data: { id } })
49
+ }
50
+
51
+ test.describe('TC-BILL-001: Billing Account CRUD (API)', () => {
52
+ let token: string
53
+ const createdIds: string[] = []
54
+
55
+ test.beforeAll(async ({ request }) => {
56
+ token = await getAuthToken(request, 'admin')
57
+ })
58
+
59
+ test.afterEach(async ({ request }) => {
60
+ while (createdIds.length > 0) {
61
+ const id = createdIds.pop()!
62
+ await deleteAccount(request, token, id)
63
+ }
64
+ })
65
+
66
+ test('creates an account and returns its id', async ({ request }) => {
67
+ const res = await apiRequest(request, 'POST', '/api/billing/accounts', {
68
+ token,
69
+ data: accountPayload(),
70
+ })
71
+ expect(res.ok(), `POST status was ${res.status()}`).toBeTruthy()
72
+ const body = await res.json()
73
+ expect(body.id).toBeTruthy()
74
+ createdIds.push(body.id)
75
+ })
76
+
77
+ test('rejects creation with a missing name', async ({ request }) => {
78
+ const payload = accountPayload()
79
+ delete (payload as Record<string, unknown>).name
80
+ const res = await apiRequest(request, 'POST', '/api/billing/accounts', {
81
+ token,
82
+ data: payload,
83
+ })
84
+ expect(res.status()).toBe(400)
85
+ })
86
+
87
+ test('soft-deletes an account (drops out of the list)', async ({ request }) => {
88
+ const created = await apiRequest(request, 'POST', '/api/billing/accounts', {
89
+ token,
90
+ data: accountPayload(),
91
+ })
92
+ const { id } = await created.json()
93
+
94
+ const del = await apiRequest(request, 'DELETE', '/api/billing/accounts', {
95
+ token,
96
+ data: { id },
97
+ })
98
+ expect(del.ok(), `DELETE status was ${del.status()}`).toBeTruthy()
99
+
100
+ const list = await apiRequest(request, 'GET', `/api/billing/accounts?id=${id}`, {
101
+ token,
102
+ data: undefined,
103
+ })
104
+ const body = await list.json()
105
+ const stillThere = (body.items as Array<{ id: string }>).some((r) => r.id === id)
106
+ expect(stillThere).toBe(false)
107
+ })
108
+
109
+ test('rejects unauthenticated requests', async ({ request }) => {
110
+ const res = await request.fetch(
111
+ `${process.env.BASE_URL || 'http://localhost:3000'}/api/billing/accounts`,
112
+ { method: 'GET' },
113
+ )
114
+ expect([401, 403]).toContain(res.status())
115
+ })
116
+ })
@@ -0,0 +1,169 @@
1
+ import { test, expect, type APIRequestContext } from '@playwright/test'
2
+ import { getAuthToken, apiRequest } from '@open-mercato/core/helpers/integration/api'
3
+
4
+ /**
5
+ * TC-BILL-002: Billing Item create + bulk create via REST API
6
+ * Category: BILL — API
7
+ * Priority: High
8
+ *
9
+ * Exercises `POST /api/billing/items` and the batched
10
+ * `POST /api/billing/items/bulk`. Pins the bulk-create result
11
+ * contract: every `items[]` entry — created or deduplicated — carries
12
+ * the flush-assigned UUID, never an empty string.
13
+ */
14
+
15
+ const uniq = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
16
+
17
+ const BASE = process.env.BASE_URL || 'http://localhost:3000'
18
+
19
+ async function createAccount(request: APIRequestContext, token: string): Promise<string> {
20
+ const res = await apiRequest(request, 'POST', '/api/billing/accounts', {
21
+ token,
22
+ data: {
23
+ customerId: `cust-${uniq()}`,
24
+ name: `Item-test account ${uniq()}`,
25
+ currencyCode: 'PLN',
26
+ billCycle: 'monthly',
27
+ billCycleAnchor: '1',
28
+ invoiceEmail: `inv-${uniq()}@example.com`,
29
+ invoiceLanguage: 'pl',
30
+ nextBillDate: '2026-07-01',
31
+ invoiceAddress: { line1: 'ul. A 1', city: 'Warszawa', postal_code: '00-001', country: 'PL' },
32
+ },
33
+ })
34
+ expect(res.ok(), `account POST status was ${res.status()}`).toBeTruthy()
35
+ return (await res.json()).id as string
36
+ }
37
+
38
+ function recurringEntry(billAccountId: string, sourceRef?: string) {
39
+ return {
40
+ billAccountId,
41
+ type: 'recurring' as const,
42
+ billStartDate: '2026-07-01',
43
+ description: 'MRC line',
44
+ rateJson: { unit_price: 49.99 },
45
+ ...(sourceRef ? { sourceRef } : {}),
46
+ }
47
+ }
48
+
49
+ test.describe('TC-BILL-002: Billing Item create + bulk create (API)', () => {
50
+ let token: string
51
+ const accountIds: string[] = []
52
+
53
+ test.beforeAll(async ({ request }) => {
54
+ token = await getAuthToken(request, 'admin')
55
+ })
56
+
57
+ test.afterAll(async ({ request }) => {
58
+ // Deleting the account soft-deletes; its items drop out of scope.
59
+ while (accountIds.length > 0) {
60
+ const id = accountIds.pop()!
61
+ await apiRequest(request, 'DELETE', '/api/billing/accounts', { token, data: { id } })
62
+ }
63
+ })
64
+
65
+ test('creates a single recurring item', async ({ request }) => {
66
+ const accountId = await createAccount(request, token)
67
+ accountIds.push(accountId)
68
+
69
+ const res = await apiRequest(request, 'POST', '/api/billing/items', {
70
+ token,
71
+ data: recurringEntry(accountId),
72
+ })
73
+ expect(res.ok(), `POST status was ${res.status()}`).toBeTruthy()
74
+ const body = await res.json()
75
+ expect(body.id).toBeTruthy()
76
+ })
77
+
78
+ test('bulk-creates distinct items in one call', async ({ request }) => {
79
+ const accountId = await createAccount(request, token)
80
+ accountIds.push(accountId)
81
+
82
+ const res = await apiRequest(request, 'POST', '/api/billing/items/bulk', {
83
+ token,
84
+ data: {
85
+ items: [
86
+ recurringEntry(accountId, `ref-${uniq()}`),
87
+ recurringEntry(accountId, `ref-${uniq()}`),
88
+ ],
89
+ },
90
+ })
91
+ expect(res.ok(), `bulk POST status was ${res.status()}`).toBeTruthy()
92
+ const body = await res.json()
93
+ expect(body.created).toBe(2)
94
+ expect(body.deduplicated).toBe(0)
95
+ expect(body.items).toHaveLength(2)
96
+ // Every created entry carries a real, flush-assigned UUID.
97
+ for (const entry of body.items as Array<{ id: string }>) {
98
+ expect(entry.id).toMatch(/^[0-9a-f-]{36}$/)
99
+ }
100
+ })
101
+
102
+ test('dedups a source_ref repeated within the same payload (same id reported)', async ({
103
+ request,
104
+ }) => {
105
+ const accountId = await createAccount(request, token)
106
+ accountIds.push(accountId)
107
+ const ref = `dup-${uniq()}`
108
+
109
+ const res = await apiRequest(request, 'POST', '/api/billing/items/bulk', {
110
+ token,
111
+ data: {
112
+ items: [recurringEntry(accountId, ref), recurringEntry(accountId, ref)],
113
+ },
114
+ })
115
+ expect(res.ok()).toBeTruthy()
116
+ const body = await res.json()
117
+ expect(body.created).toBe(1)
118
+ expect(body.deduplicated).toBe(1)
119
+
120
+ const items = body.items as Array<{ id: string; deduplicated: boolean }>
121
+ const created = items.find((i) => !i.deduplicated)
122
+ const repeated = items.find((i) => i.deduplicated)
123
+ // Regression guard: the deduped repeat must report the SAME real
124
+ // UUID as the row that persisted — not '' captured pre-flush.
125
+ expect(created?.id).toMatch(/^[0-9a-f-]{36}$/)
126
+ expect(repeated?.id).toBe(created?.id)
127
+ })
128
+
129
+ test('dedups against an already-persisted source_ref', async ({ request }) => {
130
+ const accountId = await createAccount(request, token)
131
+ accountIds.push(accountId)
132
+ const ref = `existing-${uniq()}`
133
+
134
+ const first = await apiRequest(request, 'POST', '/api/billing/items/bulk', {
135
+ token,
136
+ data: { items: [recurringEntry(accountId, ref)] },
137
+ })
138
+ const firstId = (await first.json()).items[0].id as string
139
+ expect(firstId).toMatch(/^[0-9a-f-]{36}$/)
140
+
141
+ const second = await apiRequest(request, 'POST', '/api/billing/items/bulk', {
142
+ token,
143
+ data: { items: [recurringEntry(accountId, ref)] },
144
+ })
145
+ const secondBody = await second.json()
146
+ expect(secondBody.created).toBe(0)
147
+ expect(secondBody.deduplicated).toBe(1)
148
+ expect(secondBody.items[0].id).toBe(firstId)
149
+ })
150
+
151
+ test('returns 404 when a referenced account is missing', async ({ request }) => {
152
+ const res = await apiRequest(request, 'POST', '/api/billing/items/bulk', {
153
+ token,
154
+ data: {
155
+ items: [recurringEntry('00000000-0000-4000-8000-000000000000', `ref-${uniq()}`)],
156
+ },
157
+ })
158
+ expect(res.status()).toBe(404)
159
+ })
160
+
161
+ test('rejects unauthenticated bulk requests', async ({ request }) => {
162
+ const res = await request.fetch(`${BASE}/api/billing/items/bulk`, {
163
+ method: 'POST',
164
+ headers: { 'Content-Type': 'application/json' },
165
+ data: JSON.stringify({ items: [] }),
166
+ })
167
+ expect([401, 403]).toContain(res.status())
168
+ })
169
+ })
@@ -0,0 +1,28 @@
1
+ import { test, expect } from '@playwright/test'
2
+ import { login } from '@open-mercato/core/helpers/integration/auth'
3
+
4
+ /**
5
+ * TC-BILL-003: Billing admin UI — Polish locale
6
+ * Category: BILL — UI
7
+ * Priority: Medium
8
+ *
9
+ * Verifies that the Polish i18n dictionary loads on the accounts list
10
+ * page when the `locale=pl` cookie is set.
11
+ */
12
+
13
+ const BASE = process.env.BASE_URL || 'http://localhost:3000'
14
+
15
+ test.describe('TC-BILL-003: Billing admin UI (accounts)', () => {
16
+ test('renders Polish copy when the locale cookie is set', async ({ page }) => {
17
+ await login(page, 'admin')
18
+ await page.context().addCookies([
19
+ { name: 'locale', value: 'pl', url: BASE },
20
+ ])
21
+ await page.goto('/backend/billing/accounts', { waitUntil: 'domcontentloaded' })
22
+
23
+ // pl.json: billing.accounts.title → "Konta rozliczeniowe".
24
+ await expect(
25
+ page.getByRole('heading', { name: /Konta rozliczeniowe/i }).first(),
26
+ ).toBeVisible({ timeout: 15_000 })
27
+ })
28
+ })
@@ -0,0 +1,4 @@
1
+ export const integrationMeta = {
2
+ description: 'Billing engine flows — accounts, items, bulk create, admin UI',
3
+ dependsOnModules: ['billing'],
4
+ }