@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
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
4
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'
6
+ import { billingEntityIds } from '../../../data/entityIds'
7
+ import { billingWipeTestInvoicesSchema } from '../../../data/validators'
8
+ import { createBillingCrudOpenApi } from '../../openapi'
9
+
10
+ /**
11
+ * `POST /api/billing/test-invoices/wipe` — hard-delete invoices
12
+ * flagged `metadata.test_run=true` for this tenant. Optionally
13
+ * scoped to a `billRunId` so an operator can wipe a single test run
14
+ * without nuking other test data.
15
+ *
16
+ * Modelled as POST (not DELETE) because the body carries the scope
17
+ * fields and modern Next.js routers reject DELETE-with-body more
18
+ * consistently than they accept it; keeping POST keeps the surface
19
+ * uniform with the rest of the billing API.
20
+ *
21
+ * Gated by `billing.run.trigger` (test mode is part of the
22
+ * "operate Bill Runs" toolkit). Phase 4c admin UI exposes this
23
+ * behind a confirm dialog on the Bill Run detail page.
24
+ */
25
+
26
+ const routeMetadata = {
27
+ POST: { requireAuth: true, requireFeatures: ['billing.run.trigger'] },
28
+ }
29
+
30
+ export const metadata = routeMetadata
31
+
32
+ const wipeResponseSchema = z.object({
33
+ invoicesRemoved: z.number().int().nonnegative(),
34
+ })
35
+
36
+ const rawBodySchema = z.object({}).passthrough()
37
+
38
+ const crud = makeCrudRoute({
39
+ metadata: routeMetadata,
40
+ orm: {
41
+ entity: SalesInvoice,
42
+ idField: 'id',
43
+ orgField: 'organizationId',
44
+ tenantField: 'tenantId',
45
+ softDeleteField: 'deletedAt',
46
+ },
47
+ list: {
48
+ schema: z.object({}).passthrough(),
49
+ entityId: billingEntityIds.draftInvoiceEdit,
50
+ fields: ['id'],
51
+ buildFilters: async () => ({}),
52
+ },
53
+ actions: {
54
+ create: {
55
+ commandId: 'billing.invoices.wipe_test',
56
+ schema: rawBodySchema,
57
+ mapInput: async ({ raw, ctx }) => {
58
+ const { translate } = await resolveTranslations()
59
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate)
60
+ return billingWipeTestInvoicesSchema.parse(scoped)
61
+ },
62
+ response: ({ result }) => ({
63
+ invoicesRemoved: result?.invoicesRemoved ?? 0,
64
+ }),
65
+ status: 200,
66
+ },
67
+ },
68
+ })
69
+
70
+ export const POST = crud.POST
71
+
72
+ export const openApi = createBillingCrudOpenApi({
73
+ resourceName: 'BillingTestInvoiceWipe',
74
+ pluralName: 'BillingTestInvoiceWipes',
75
+ querySchema: z.object({}).passthrough(),
76
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
77
+ create: {
78
+ schema: billingWipeTestInvoicesSchema,
79
+ responseSchema: wipeResponseSchema,
80
+ description:
81
+ 'Hard-deletes test invoices (`metadata.test_run=true`) for the ' +
82
+ 'authenticated tenant. Pass `billRunId` to wipe only invoices ' +
83
+ 'from a specific test Bill Run. Returns the count removed. ' +
84
+ 'Test data should not pollute production history, so this is a ' +
85
+ 'hard delete, not a soft delete.',
86
+ },
87
+ })
@@ -0,0 +1,148 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
4
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { BillingAccountUsage } from '../../data/entities'
6
+ import { billingEntityIds } from '../../data/entityIds'
7
+ import {
8
+ billingUsageCreateSchema,
9
+ billingUsageListQuerySchema,
10
+ } from '../../data/validators'
11
+ import {
12
+ createBillingCrudOpenApi,
13
+ createPagedListResponseSchema,
14
+ } from '../openapi'
15
+
16
+ // ─── Auth / RBAC ─────────────────────────────────────────────────
17
+ //
18
+ // Usage is append-only — no update or delete. The narrow scope here
19
+ // lets us mint API keys for external metering systems with ONLY
20
+ // `billing.usage.ingest`, eliminating an entire blast radius.
21
+
22
+ const routeMetadata = {
23
+ GET: { requireAuth: true, requireFeatures: ['billing.usage.view'] },
24
+ POST: { requireAuth: true, requireFeatures: ['billing.usage.ingest'] },
25
+ }
26
+
27
+ export const metadata = routeMetadata
28
+
29
+ const billingUsageListItemSchema = z.object({
30
+ id: z.string().uuid(),
31
+ billAccountId: z.string().uuid(),
32
+ uomCode: z.string(),
33
+ quantity: z.string(), // numeric(18,4) → string
34
+ periodStart: z.string(),
35
+ periodEnd: z.string(),
36
+ lineDescription: z.string().nullable(),
37
+ sourceRef: z.string().nullable(),
38
+ ratedInBillRunId: z.string().uuid().nullable(),
39
+ isActive: z.boolean(),
40
+ organizationId: z.string().uuid(),
41
+ tenantId: z.string().uuid(),
42
+ createdAt: z.string(),
43
+ updatedAt: z.string(),
44
+ })
45
+
46
+ const usageCreateResponseSchema = z.object({
47
+ id: z.string().uuid().nullable(),
48
+ deduplicated: z.boolean(),
49
+ })
50
+
51
+ const rawBodySchema = z.object({}).passthrough()
52
+
53
+ const crud = makeCrudRoute({
54
+ metadata: routeMetadata,
55
+ orm: {
56
+ entity: BillingAccountUsage,
57
+ idField: 'id',
58
+ orgField: 'organizationId',
59
+ tenantField: 'tenantId',
60
+ softDeleteField: 'deletedAt',
61
+ },
62
+ list: {
63
+ schema: billingUsageListQuerySchema,
64
+ entityId: billingEntityIds.usage,
65
+ fields: [
66
+ 'id',
67
+ 'bill_account_id',
68
+ 'uom_code',
69
+ 'quantity',
70
+ 'period_start',
71
+ 'period_end',
72
+ 'line_description',
73
+ 'source_ref',
74
+ 'rated_in_bill_run_id',
75
+ 'is_active',
76
+ 'organization_id',
77
+ 'tenant_id',
78
+ 'created_at',
79
+ 'updated_at',
80
+ ],
81
+ sortFieldMap: {
82
+ createdAt: 'created_at',
83
+ periodStart: 'period_start',
84
+ periodEnd: 'period_end',
85
+ },
86
+ buildFilters: async (query) => {
87
+ const filters: Record<string, unknown> = {}
88
+ if (typeof query.billAccountId === 'string' && query.billAccountId) {
89
+ filters.bill_account_id = { $eq: query.billAccountId }
90
+ }
91
+ if (typeof query.uomCode === 'string' && query.uomCode) {
92
+ filters.uom_code = { $eq: query.uomCode }
93
+ }
94
+ if (typeof query.rated === 'boolean') {
95
+ filters.rated_in_bill_run_id = query.rated ? { $ne: null } : { $eq: null }
96
+ }
97
+ if (query.periodEndFrom instanceof Date) {
98
+ filters.period_end = Object.assign(
99
+ (filters.period_end as Record<string, unknown>) ?? {},
100
+ { $gte: query.periodEndFrom },
101
+ )
102
+ }
103
+ if (query.periodEndTo instanceof Date) {
104
+ filters.period_end = Object.assign(
105
+ (filters.period_end as Record<string, unknown>) ?? {},
106
+ { $lte: query.periodEndTo },
107
+ )
108
+ }
109
+ return filters
110
+ },
111
+ },
112
+ actions: {
113
+ create: {
114
+ commandId: 'billing.usage.create',
115
+ schema: rawBodySchema,
116
+ mapInput: async ({ raw, ctx }) => {
117
+ const { translate } = await resolveTranslations()
118
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate)
119
+ return billingUsageCreateSchema.parse(scoped)
120
+ },
121
+ response: ({ result }) => ({
122
+ id: result?.id ?? null,
123
+ deduplicated: Boolean(result?.deduplicated),
124
+ }),
125
+ status: 200,
126
+ },
127
+ // Usage has no update or delete by design — see file header.
128
+ },
129
+ })
130
+
131
+ const { POST } = crud
132
+ export { POST }
133
+ export const GET = crud.GET
134
+
135
+ export const openApi = createBillingCrudOpenApi({
136
+ resourceName: 'BillingUsageRecord',
137
+ pluralName: 'BillingUsageRecords',
138
+ querySchema: billingUsageListQuerySchema,
139
+ listResponseSchema: createPagedListResponseSchema(billingUsageListItemSchema),
140
+ create: {
141
+ schema: billingUsageCreateSchema,
142
+ responseSchema: usageCreateResponseSchema,
143
+ description:
144
+ 'Ingests a pre-aggregated usage record. Idempotent via `sourceRef`. ' +
145
+ 'Usage records are immutable once persisted — to correct a mis-aggregation, ' +
146
+ "post a `one_time` Billing Item credit (the spec's refund model).",
147
+ },
148
+ })
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { Building2 } from 'lucide-react'
3
+
4
+ const accountIcon = React.createElement(Building2, { size: 16 })
5
+
6
+ export const metadata = {
7
+ requireAuth: true,
8
+ requireFeatures: ['billing.account.view'],
9
+ pageTitle: 'Billing Account',
10
+ pageTitleKey: 'billing.accounts.detail.title',
11
+ pageGroup: 'Billing',
12
+ pageGroupKey: 'billing.nav.group',
13
+ navHidden: true,
14
+ icon: accountIcon,
15
+ breadcrumb: [
16
+ {
17
+ label: 'Billing Accounts',
18
+ labelKey: 'billing.accounts.title',
19
+ href: '/backend/billing/accounts',
20
+ },
21
+ { label: 'Account', labelKey: 'billing.accounts.detail.title' },
22
+ ],
23
+ } as const