@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,69 @@
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.js";
7
+ import { billingInvoiceAddLineSchema } from "../../../data/validators.js";
8
+ import { createBillingCrudOpenApi } from "../../openapi.js";
9
+ const routeMetadata = {
10
+ POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
11
+ };
12
+ const metadata = routeMetadata;
13
+ const addLineResponseSchema = z.object({
14
+ invoiceId: z.string().uuid(),
15
+ invoiceLineId: z.string().uuid(),
16
+ auditId: z.string().uuid()
17
+ });
18
+ const rawBodySchema = z.object({}).passthrough();
19
+ const crud = makeCrudRoute({
20
+ metadata: routeMetadata,
21
+ orm: {
22
+ entity: SalesInvoice,
23
+ idField: "id",
24
+ orgField: "organizationId",
25
+ tenantField: "tenantId",
26
+ softDeleteField: "deletedAt"
27
+ },
28
+ list: {
29
+ schema: z.object({}).passthrough(),
30
+ entityId: billingEntityIds.draftInvoiceEdit,
31
+ fields: ["id"],
32
+ buildFilters: async () => ({})
33
+ },
34
+ actions: {
35
+ create: {
36
+ commandId: "billing.invoices.add_draft_line",
37
+ schema: rawBodySchema,
38
+ mapInput: async ({ raw, ctx }) => {
39
+ const { translate } = await resolveTranslations();
40
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate);
41
+ return billingInvoiceAddLineSchema.parse(scoped);
42
+ },
43
+ response: ({ result }) => ({
44
+ invoiceId: result?.invoiceId ?? null,
45
+ invoiceLineId: result?.invoiceLineId ?? null,
46
+ auditId: result?.auditId ?? null
47
+ }),
48
+ status: 201
49
+ }
50
+ }
51
+ });
52
+ const POST = crud.POST;
53
+ const openApi = createBillingCrudOpenApi({
54
+ resourceName: "BillingInvoiceLineAdd",
55
+ pluralName: "BillingInvoiceLineAdds",
56
+ querySchema: z.object({}).passthrough(),
57
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
58
+ create: {
59
+ schema: billingInvoiceAddLineSchema,
60
+ responseSchema: addLineResponseSchema,
61
+ description: "Appends a new line to a draft invoice (e.g. operator adds a one-off adjustment). Writes a `DraftInvoiceEdit` audit row (action=`line_added`, `before_json=null`). Recomputes invoice totals. Refuses non-draft invoices."
62
+ }
63
+ });
64
+ export {
65
+ POST,
66
+ metadata,
67
+ openApi
68
+ };
69
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/api/invoices/add-line/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoiceAddLineSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst addLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.add_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceAddLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 201,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineAdd',\n pluralName: 'BillingInvoiceLineAdds',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceAddLineSchema,\n responseSchema: addLineResponseSchema,\n description:\n 'Appends a new line to a draft invoice (e.g. operator adds a ' +\n 'one-off adjustment). Writes a `DraftInvoiceEdit` audit row ' +\n '(action=`line_added`, `before_json=null`). Recomputes invoice ' +\n 'totals. Refuses non-draft invoices.',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAEzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,4BAA4B,MAAM,MAAM;AAAA,MACjD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAIJ;AACF,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,78 @@
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.js";
7
+ import {
8
+ billingInvoiceAddLineSchema,
9
+ billingInvoiceEditLineSchema,
10
+ billingInvoiceRemoveLineSchema
11
+ } from "../../../data/validators.js";
12
+ import { createBillingCrudOpenApi } from "../../openapi.js";
13
+ const routeMetadata = {
14
+ POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
15
+ };
16
+ const metadata = routeMetadata;
17
+ const editLineResponseSchema = z.object({
18
+ invoiceId: z.string().uuid(),
19
+ invoiceLineId: z.string().uuid(),
20
+ auditId: z.string().uuid()
21
+ });
22
+ const rawBodySchema = z.object({}).passthrough();
23
+ const crud = makeCrudRoute({
24
+ metadata: routeMetadata,
25
+ orm: {
26
+ entity: SalesInvoice,
27
+ idField: "id",
28
+ orgField: "organizationId",
29
+ tenantField: "tenantId",
30
+ softDeleteField: "deletedAt"
31
+ },
32
+ list: {
33
+ schema: z.object({}).passthrough(),
34
+ entityId: billingEntityIds.draftInvoiceEdit,
35
+ fields: ["id"],
36
+ buildFilters: async () => ({})
37
+ },
38
+ actions: {
39
+ create: {
40
+ commandId: "billing.invoices.edit_draft_line",
41
+ schema: rawBodySchema,
42
+ mapInput: async ({ raw, ctx }) => {
43
+ const { translate } = await resolveTranslations();
44
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate);
45
+ return billingInvoiceEditLineSchema.parse(scoped);
46
+ },
47
+ response: ({ result }) => ({
48
+ invoiceId: result?.invoiceId ?? null,
49
+ invoiceLineId: result?.invoiceLineId ?? null,
50
+ auditId: result?.auditId ?? null
51
+ }),
52
+ status: 200
53
+ }
54
+ }
55
+ });
56
+ const POST = crud.POST;
57
+ const openApi = createBillingCrudOpenApi({
58
+ resourceName: "BillingInvoiceLineEdit",
59
+ pluralName: "BillingInvoiceLineEdits",
60
+ querySchema: z.object({}).passthrough(),
61
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
62
+ create: {
63
+ schema: billingInvoiceEditLineSchema,
64
+ responseSchema: editLineResponseSchema,
65
+ description: "Edits one line of a draft invoice (description, quantity, unit_price, or explicit `totalNetAmount` override). Writes a `DraftInvoiceEdit` audit row with before+after snapshots and recomputes invoice totals. Refuses non-draft invoices with HTTP 409 + `billing.invoice.not_draft`."
66
+ }
67
+ });
68
+ const _phaseSchemaRefs = {
69
+ add: billingInvoiceAddLineSchema,
70
+ remove: billingInvoiceRemoveLineSchema
71
+ };
72
+ export {
73
+ POST,
74
+ _phaseSchemaRefs,
75
+ metadata,
76
+ openApi
77
+ };
78
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/api/invoices/edit-line/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport {\n billingInvoiceAddLineSchema,\n billingInvoiceEditLineSchema,\n billingInvoiceRemoveLineSchema,\n} from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/invoices/edit-line` \u2014 edits one line of a draft\n * invoice. Refuses non-drafts (409 / `billing.invoice.not_draft`).\n * Writes a `DraftInvoiceEdit` audit row per spec. Recomputes invoice\n * totals from the surviving lines after the edit.\n *\n * Operator surface: route is gated by `billing.invoice.edit_draft`.\n * Future Phase 4c admin UI calls into the same command via this route.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst editLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.edit_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceEditLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineEdit',\n pluralName: 'BillingInvoiceLineEdits',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceEditLineSchema,\n responseSchema: editLineResponseSchema,\n description:\n 'Edits one line of a draft invoice (description, quantity, ' +\n \"unit_price, or explicit `totalNetAmount` override). Writes a \" +\n '`DraftInvoiceEdit` audit row with before+after snapshots and ' +\n 'recomputes invoice totals. Refuses non-draft invoices with ' +\n 'HTTP 409 + `billing.invoice.not_draft`.',\n },\n})\n\n// Re-export the add/remove route schemas so the OpenAPI bundle keeps\n// them discoverable from a single import surface \u2014 the dedicated\n// routes (sibling folders) own the actual handlers.\nexport const _phaseSchemaRefs = {\n add: billingInvoiceAddLineSchema,\n remove: billingInvoiceRemoveLineSchema,\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AAYzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,6BAA6B,MAAM,MAAM;AAAA,MAClD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;AAKM,MAAM,mBAAmB;AAAA,EAC9B,KAAK;AAAA,EACL,QAAQ;AACV;",
6
+ "names": []
7
+ }
@@ -0,0 +1,74 @@
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.js";
7
+ import { billingInvoicePostSchema } from "../../../data/validators.js";
8
+ import { createBillingCrudOpenApi } from "../../openapi.js";
9
+ const routeMetadata = {
10
+ POST: { requireAuth: true, requireFeatures: ["billing.invoice.post"] }
11
+ };
12
+ const metadata = routeMetadata;
13
+ const postResponseSchema = z.object({
14
+ invoiceId: z.string().uuid(),
15
+ invoiceNumber: z.string(),
16
+ status: z.literal("posted"),
17
+ lineCount: z.number().int().nonnegative()
18
+ });
19
+ const rawBodySchema = z.object({}).passthrough();
20
+ const crud = makeCrudRoute({
21
+ metadata: routeMetadata,
22
+ orm: {
23
+ entity: SalesInvoice,
24
+ idField: "id",
25
+ orgField: "organizationId",
26
+ tenantField: "tenantId",
27
+ softDeleteField: "deletedAt"
28
+ },
29
+ list: {
30
+ schema: z.object({}).passthrough(),
31
+ // `entityId` is technically a core/sales concept, but the factory
32
+ // only needs it as an identifier — using a billing-namespaced one
33
+ // keeps the registry tidy (no surprise list endpoints).
34
+ entityId: billingEntityIds.draftInvoiceEdit,
35
+ fields: ["id"],
36
+ buildFilters: async () => ({})
37
+ },
38
+ actions: {
39
+ create: {
40
+ commandId: "billing.invoices.post",
41
+ schema: rawBodySchema,
42
+ mapInput: async ({ raw, ctx }) => {
43
+ const { translate } = await resolveTranslations();
44
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate);
45
+ return billingInvoicePostSchema.parse(scoped);
46
+ },
47
+ response: ({ result }) => ({
48
+ invoiceId: result?.invoiceId ?? null,
49
+ invoiceNumber: result?.invoiceNumber ?? null,
50
+ status: result?.status ?? null,
51
+ lineCount: result?.lineCount ?? 0
52
+ }),
53
+ status: 200
54
+ }
55
+ }
56
+ });
57
+ const POST = crud.POST;
58
+ const openApi = createBillingCrudOpenApi({
59
+ resourceName: "BillingInvoicePost",
60
+ pluralName: "BillingInvoicePosts",
61
+ querySchema: z.object({}).passthrough(),
62
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
63
+ create: {
64
+ schema: billingInvoicePostSchema,
65
+ responseSchema: postResponseSchema,
66
+ description: "Flips a billing-drafted `core/sales` invoice from `draft` to `posted`. Refuses non-draft invoices (409 / `billing.invoice.not_draft`) and test-mode invoices (409 / `billing.invoice.test_run`). Fires `billing.invoice.posted` + one `billing.invoice.line_posted` per line. The invoice number is NOT re-assigned (set at draft create \u2014 see Phase 2 deviation)."
67
+ }
68
+ });
69
+ export {
70
+ POST,
71
+ metadata,
72
+ openApi
73
+ };
74
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/api/invoices/post/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoicePostSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/invoices/post`\n *\n * Operator action: flip a billing-drafted invoice from `draft` \u2192\n * `posted`. The command refuses non-draft inputs (409 with\n * `billing.invoice.not_draft`) and test-mode invoices (409 with\n * `billing.invoice.test_run`). The invoice number is NOT re-assigned\n * (it was set at draft create \u2014 see Phase 2 deviation note in the\n * spec).\n *\n * Implemented as a create-only `makeCrudRoute` so it inherits the same\n * RBAC + OpenAPI plumbing as the rest of the billing surface. The\n * `entity` is `SalesInvoice` only because the factory requires one;\n * the GET half is never exposed on this URL.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.post'] },\n}\n\nexport const metadata = routeMetadata\n\nconst postResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceNumber: z.string(),\n status: z.literal('posted'),\n lineCount: z.number().int().nonnegative(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n // `entityId` is technically a core/sales concept, but the factory\n // only needs it as an identifier \u2014 using a billing-namespaced one\n // keeps the registry tidy (no surprise list endpoints).\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.post',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoicePostSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceNumber: result?.invoiceNumber ?? null,\n status: result?.status ?? null,\n lineCount: result?.lineCount ?? 0,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoicePost',\n pluralName: 'BillingInvoicePosts',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoicePostSchema,\n responseSchema: postResponseSchema,\n description:\n 'Flips a billing-drafted `core/sales` invoice from `draft` to ' +\n '`posted`. Refuses non-draft invoices (409 / ' +\n '`billing.invoice.not_draft`) and test-mode invoices (409 / ' +\n '`billing.invoice.test_run`). Fires `billing.invoice.posted` + ' +\n 'one `billing.invoice.line_posted` per line. The invoice number ' +\n 'is NOT re-assigned (set at draft create \u2014 see Phase 2 deviation).',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,gCAAgC;AACzC,SAAS,gCAAgC;AAkBzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACvE;AAEO,MAAM,WAAW;AAExB,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO;AAAA,EACxB,QAAQ,EAAE,QAAQ,QAAQ;AAAA,EAC1B,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAC1C,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA;AAAA;AAAA;AAAA,IAIjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,yBAAyB,MAAM,MAAM;AAAA,MAC9C;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,QAAQ,QAAQ,UAAU;AAAA,QAC1B,WAAW,QAAQ,aAAa;AAAA,MAClC;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAMJ;AACF,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
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.js";
7
+ import { billingInvoiceRemoveLineSchema } from "../../../data/validators.js";
8
+ import { createBillingCrudOpenApi } from "../../openapi.js";
9
+ const routeMetadata = {
10
+ POST: { requireAuth: true, requireFeatures: ["billing.invoice.edit_draft"] }
11
+ };
12
+ const metadata = routeMetadata;
13
+ const removeLineResponseSchema = z.object({
14
+ invoiceId: z.string().uuid(),
15
+ invoiceLineId: z.string().uuid(),
16
+ auditId: z.string().uuid()
17
+ });
18
+ const rawBodySchema = z.object({}).passthrough();
19
+ const crud = makeCrudRoute({
20
+ metadata: routeMetadata,
21
+ orm: {
22
+ entity: SalesInvoice,
23
+ idField: "id",
24
+ orgField: "organizationId",
25
+ tenantField: "tenantId",
26
+ softDeleteField: "deletedAt"
27
+ },
28
+ list: {
29
+ schema: z.object({}).passthrough(),
30
+ entityId: billingEntityIds.draftInvoiceEdit,
31
+ fields: ["id"],
32
+ buildFilters: async () => ({})
33
+ },
34
+ actions: {
35
+ create: {
36
+ commandId: "billing.invoices.remove_draft_line",
37
+ schema: rawBodySchema,
38
+ mapInput: async ({ raw, ctx }) => {
39
+ const { translate } = await resolveTranslations();
40
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate);
41
+ return billingInvoiceRemoveLineSchema.parse(scoped);
42
+ },
43
+ response: ({ result }) => ({
44
+ invoiceId: result?.invoiceId ?? null,
45
+ invoiceLineId: result?.invoiceLineId ?? null,
46
+ auditId: result?.auditId ?? null
47
+ }),
48
+ status: 200
49
+ }
50
+ }
51
+ });
52
+ const POST = crud.POST;
53
+ const openApi = createBillingCrudOpenApi({
54
+ resourceName: "BillingInvoiceLineRemove",
55
+ pluralName: "BillingInvoiceLineRemoves",
56
+ querySchema: z.object({}).passthrough(),
57
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
58
+ create: {
59
+ schema: billingInvoiceRemoveLineSchema,
60
+ responseSchema: removeLineResponseSchema,
61
+ description: "Removes one line from a draft invoice (hard delete \u2014 the line never existed as far as future readers are concerned). Writes a `DraftInvoiceEdit` audit row (action=`line_removed`, `after_json=null`) carrying the before-snapshot for forensics. Recomputes invoice totals. Refuses non-draft invoices."
62
+ }
63
+ });
64
+ export {
65
+ POST,
66
+ metadata,
67
+ openApi
68
+ };
69
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/api/invoices/remove-line/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesInvoice } from '@open-mercato/core/modules/sales/data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingInvoiceRemoveLineSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.invoice.edit_draft'] },\n}\n\nexport const metadata = routeMetadata\n\nconst removeLineResponseSchema = z.object({\n invoiceId: z.string().uuid(),\n invoiceLineId: z.string().uuid(),\n auditId: z.string().uuid(),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesInvoice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.draftInvoiceEdit,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.invoices.remove_draft_line',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingInvoiceRemoveLineSchema.parse(scoped)\n },\n response: ({ result }) => ({\n invoiceId: result?.invoiceId ?? null,\n invoiceLineId: result?.invoiceLineId ?? null,\n auditId: result?.auditId ?? null,\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingInvoiceLineRemove',\n pluralName: 'BillingInvoiceLineRemoves',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingInvoiceRemoveLineSchema,\n responseSchema: removeLineResponseSchema,\n description:\n 'Removes one line from a draft invoice (hard delete \u2014 the line ' +\n 'never existed as far as future readers are concerned). Writes ' +\n 'a `DraftInvoiceEdit` audit row (action=`line_removed`, ' +\n '`after_json=null`) carrying the before-snapshot for forensics. ' +\n 'Recomputes invoice totals. Refuses non-draft invoices.',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,sCAAsC;AAC/C,SAAS,gCAAgC;AAEzC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC7E;AAEO,MAAM,WAAW;AAExB,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,OAAO,EAAE,KAAK;AAAA,EAC3B,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,+BAA+B,MAAM,MAAM;AAAA,MACpD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,WAAW,QAAQ,aAAa;AAAA,QAChC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,109 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ const metadata = {
6
+ GET: { requireAuth: true, requireFeatures: ["billing.invoice.view"] }
7
+ };
8
+ const querySchema = z.object({
9
+ page: z.coerce.number().int().min(1).default(1),
10
+ pageSize: z.coerce.number().int().min(1).max(100).default(50),
11
+ status: z.enum(["draft", "posted", "paid", "void"]).optional(),
12
+ billRunId: z.string().uuid().optional(),
13
+ billAccountId: z.string().uuid().optional(),
14
+ testRun: z.coerce.boolean().optional(),
15
+ search: z.string().optional()
16
+ }).passthrough();
17
+ async function GET(req) {
18
+ try {
19
+ const container = await createRequestContainer();
20
+ const auth = await getAuthFromRequest(req);
21
+ if (!auth || !auth.tenantId) {
22
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
23
+ }
24
+ const organizationId = auth.orgId ?? null;
25
+ if (!organizationId) {
26
+ return NextResponse.json(
27
+ { error: "Organization scope required" },
28
+ { status: 403 }
29
+ );
30
+ }
31
+ const url = new URL(req.url);
32
+ const query = querySchema.parse(
33
+ Object.fromEntries(url.searchParams.entries())
34
+ );
35
+ const em = container.resolve("em").fork();
36
+ const whereClauses = [
37
+ "tenant_id = ?",
38
+ "organization_id = ?",
39
+ "deleted_at IS NULL",
40
+ "jsonb_exists(metadata, 'bill_run_id')"
41
+ ];
42
+ const params = [auth.tenantId, organizationId];
43
+ if (query.status) {
44
+ whereClauses.push("status = ?");
45
+ params.push(query.status);
46
+ }
47
+ if (query.billRunId) {
48
+ whereClauses.push("metadata->>'bill_run_id' = ?");
49
+ params.push(query.billRunId);
50
+ }
51
+ if (query.billAccountId) {
52
+ whereClauses.push("metadata->>'bill_account_id' = ?");
53
+ params.push(query.billAccountId);
54
+ }
55
+ if (typeof query.testRun === "boolean") {
56
+ if (query.testRun) {
57
+ whereClauses.push("metadata->>'test_run' = 'true'");
58
+ } else {
59
+ whereClauses.push("(metadata->>'test_run' IS NULL OR metadata->>'test_run' != 'true')");
60
+ }
61
+ }
62
+ if (typeof query.search === "string" && query.search.trim()) {
63
+ whereClauses.push("invoice_number ILIKE ?");
64
+ params.push(`%${query.search.trim().replace(/[%_]/g, (m) => `\\${m}`)}%`);
65
+ }
66
+ const whereSql = whereClauses.join(" AND ");
67
+ const countRows = await em.execute(
68
+ `SELECT COUNT(*)::int AS total FROM sales_invoices WHERE ${whereSql}`,
69
+ params
70
+ );
71
+ const total = countRows?.[0]?.total ?? 0;
72
+ const offset = (query.page - 1) * query.pageSize;
73
+ const itemRows = await em.execute(
74
+ `SELECT id, invoice_number, status, currency_code, issue_date, due_date,
75
+ subtotal_net_amount, grand_total_net_amount, grand_total_gross_amount,
76
+ paid_total_amount, outstanding_amount, metadata, created_at, updated_at
77
+ FROM sales_invoices
78
+ WHERE ${whereSql}
79
+ ORDER BY created_at DESC
80
+ LIMIT ? OFFSET ?`,
81
+ [...params, query.pageSize, offset]
82
+ );
83
+ return NextResponse.json({
84
+ items: itemRows,
85
+ total,
86
+ page: query.page,
87
+ pageSize: query.pageSize,
88
+ totalPages: total === 0 ? 1 : Math.ceil(total / query.pageSize)
89
+ });
90
+ } catch (err) {
91
+ console.error("[billing/invoices.GET]", err);
92
+ return NextResponse.json({ error: "Internal error" }, { status: 500 });
93
+ }
94
+ }
95
+ const openApi = {
96
+ methods: {
97
+ GET: {
98
+ tags: ["Billing"],
99
+ summary: "List billing-managed invoices",
100
+ description: "Returns a paginated list of `core/sales` invoices that the billing engine emitted (matched via `metadata->>'bill_run_id'` presence). Supports status / billRunId / billAccountId / testRun / invoice_number search filters."
101
+ }
102
+ }
103
+ };
104
+ export {
105
+ GET,
106
+ metadata,
107
+ openApi
108
+ };
109
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/billing/api/invoices/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\n\n/**\n * `GET /api/billing/invoices`\n *\n * Lists `core/sales` invoices that the billing engine emitted (i.e.\n * carry `metadata.bill_run_id`). Hand-rolled rather than wrapped via\n * `makeCrudRoute` because the filter predicate is a JSONB\n * `metadata->>'bill_run_id' IS NOT NULL` check that doesn't map\n * cleanly onto the factory's `where`-shaped filter API, and the\n * entity itself is owned by `core/sales` \u2014 keeping the surface\n * read-only and hand-rolled avoids surprising the cross-module\n * reviewer.\n *\n * Filters:\n * - `status` \u2014 draft / posted / paid / void\n * - `billRunId` \u2014 exact match on `metadata->>'bill_run_id'`\n * - `billAccountId` \u2014 exact match on `metadata->>'bill_account_id'`\n * - `testRun=true|false` \u2014 flips the `metadata->>'test_run'` filter\n * - `search` \u2014 substring on invoice_number\n *\n * Sort: started_at DESC (most recent first). Pagination: standard\n * page / pageSize (capped at 100).\n */\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['billing.invoice.view'] },\n}\n\nconst querySchema = z\n .object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(50),\n status: z.enum(['draft', 'posted', 'paid', 'void']).optional(),\n billRunId: z.string().uuid().optional(),\n billAccountId: z.string().uuid().optional(),\n testRun: z.coerce.boolean().optional(),\n search: z.string().optional(),\n })\n .passthrough()\n\ntype BillingInvoiceRow = {\n id: string\n invoice_number: string\n status: string | null\n currency_code: string\n issue_date: string | null\n due_date: string | null\n subtotal_net_amount: string\n grand_total_net_amount: string\n grand_total_gross_amount: string\n paid_total_amount: string\n outstanding_amount: string\n metadata: Record<string, unknown> | null\n created_at: string\n updated_at: string\n}\n\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const organizationId = (auth as { orgId?: string }).orgId ?? null\n if (!organizationId) {\n return NextResponse.json(\n { error: 'Organization scope required' },\n { status: 403 },\n )\n }\n\n const url = new URL(req.url)\n const query = querySchema.parse(\n Object.fromEntries(url.searchParams.entries()),\n )\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n // Build the predicate. `jsonb_exists(metadata, 'bill_run_id')` is\n // the function form of the JSONB `?` \"key exists\" operator \u2014 the\n // operator form cannot be used here because `em.execute` treats a\n // literal `?` as a positional parameter placeholder. Keeps us\n // narrow to billing-managed invoices, never bleeding into invoices\n // written by other modules.\n const whereClauses: string[] = [\n 'tenant_id = ?',\n 'organization_id = ?',\n 'deleted_at IS NULL',\n \"jsonb_exists(metadata, 'bill_run_id')\",\n ]\n const params: unknown[] = [auth.tenantId, organizationId]\n\n if (query.status) {\n whereClauses.push('status = ?')\n params.push(query.status)\n }\n if (query.billRunId) {\n whereClauses.push(\"metadata->>'bill_run_id' = ?\")\n params.push(query.billRunId)\n }\n if (query.billAccountId) {\n whereClauses.push(\"metadata->>'bill_account_id' = ?\")\n params.push(query.billAccountId)\n }\n if (typeof query.testRun === 'boolean') {\n if (query.testRun) {\n whereClauses.push(\"metadata->>'test_run' = 'true'\")\n } else {\n whereClauses.push(\"(metadata->>'test_run' IS NULL OR metadata->>'test_run' != 'true')\")\n }\n }\n if (typeof query.search === 'string' && query.search.trim()) {\n whereClauses.push('invoice_number ILIKE ?')\n params.push(`%${query.search.trim().replace(/[%_]/g, (m) => `\\\\${m}`)}%`)\n }\n\n const whereSql = whereClauses.join(' AND ')\n const countRows = (await em.execute(\n `SELECT COUNT(*)::int AS total FROM sales_invoices WHERE ${whereSql}`,\n params,\n )) as unknown as Array<{ total: number }>\n const total = countRows?.[0]?.total ?? 0\n\n const offset = (query.page - 1) * query.pageSize\n const itemRows = (await em.execute(\n `SELECT id, invoice_number, status, currency_code, issue_date, due_date,\n subtotal_net_amount, grand_total_net_amount, grand_total_gross_amount,\n paid_total_amount, outstanding_amount, metadata, created_at, updated_at\n FROM sales_invoices\n WHERE ${whereSql}\n ORDER BY created_at DESC\n LIMIT ? OFFSET ?`,\n [...params, query.pageSize, offset],\n )) as unknown as BillingInvoiceRow[]\n\n return NextResponse.json({\n items: itemRows,\n total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: total === 0 ? 1 : Math.ceil(total / query.pageSize),\n })\n } catch (err) {\n console.error('[billing/invoices.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n tags: ['Billing'],\n summary: 'List billing-managed invoices',\n description:\n 'Returns a paginated list of `core/sales` invoices that the ' +\n 'billing engine emitted (matched via `metadata->>\\'bill_run_id\\'` ' +\n 'presence). Supports status / billRunId / billAccountId / testRun ' +\n '/ invoice_number search filters.',\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAyB5B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,MAAM,cAAc,EACjB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EAC5D,QAAQ,EAAE,KAAK,CAAC,SAAS,UAAU,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,EAC7D,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACtC,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC1C,SAAS,EAAE,OAAO,QAAQ,EAAE,SAAS;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,SAAS;AAC9B,CAAC,EACA,YAAY;AAmBf,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,UAAM,iBAAkB,KAA4B,SAAS;AAC7D,QAAI,CAAC,gBAAgB;AACnB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,8BAA8B;AAAA,QACvC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,YAAY;AAAA,MACxB,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC;AAAA,IAC/C;AAEA,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAQ3D,UAAM,eAAyB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,SAAoB,CAAC,KAAK,UAAU,cAAc;AAExD,QAAI,MAAM,QAAQ;AAChB,mBAAa,KAAK,YAAY;AAC9B,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B;AACA,QAAI,MAAM,WAAW;AACnB,mBAAa,KAAK,8BAA8B;AAChD,aAAO,KAAK,MAAM,SAAS;AAAA,IAC7B;AACA,QAAI,MAAM,eAAe;AACvB,mBAAa,KAAK,kCAAkC;AACpD,aAAO,KAAK,MAAM,aAAa;AAAA,IACjC;AACA,QAAI,OAAO,MAAM,YAAY,WAAW;AACtC,UAAI,MAAM,SAAS;AACjB,qBAAa,KAAK,gCAAgC;AAAA,MACpD,OAAO;AACL,qBAAa,KAAK,oEAAoE;AAAA,MACxF;AAAA,IACF;AACA,QAAI,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,KAAK,GAAG;AAC3D,mBAAa,KAAK,wBAAwB;AAC1C,aAAO,KAAK,IAAI,MAAM,OAAO,KAAK,EAAE,QAAQ,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC,GAAG;AAAA,IAC1E;AAEA,UAAM,WAAW,aAAa,KAAK,OAAO;AAC1C,UAAM,YAAa,MAAM,GAAG;AAAA,MAC1B,2DAA2D,QAAQ;AAAA,MACnE;AAAA,IACF;AACA,UAAM,QAAQ,YAAY,CAAC,GAAG,SAAS;AAEvC,UAAM,UAAU,MAAM,OAAO,KAAK,MAAM;AACxC,UAAM,WAAY,MAAM,GAAG;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA,eAIS,QAAQ;AAAA;AAAA;AAAA,MAGjB,CAAC,GAAG,QAAQ,MAAM,UAAU,MAAM;AAAA,IACpC;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO;AAAA,MACP;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,YAAY,UAAU,IAAI,IAAI,KAAK,KAAK,QAAQ,MAAM,QAAQ;AAAA,IAChE,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,0BAA0B,GAAG;AAC3C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,IACP,KAAK;AAAA,MACH,MAAM,CAAC,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,aACE;AAAA,IAIJ;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,74 @@
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 { BillingItem } from "../../../data/entities.js";
6
+ import { billingEntityIds } from "../../../data/entityIds.js";
7
+ import { billingItemBulkCreateSchema } from "../../../data/validators.js";
8
+ import { createBillingCrudOpenApi } from "../../openapi.js";
9
+ const routeMetadata = {
10
+ POST: { requireAuth: true, requireFeatures: ["billing.item.manage"] }
11
+ };
12
+ const metadata = routeMetadata;
13
+ const bulkResultEntrySchema = z.object({
14
+ sourceRef: z.string().nullable(),
15
+ id: z.string().uuid(),
16
+ deduplicated: z.boolean()
17
+ });
18
+ const bulkResponseSchema = z.object({
19
+ created: z.number().int().nonnegative(),
20
+ deduplicated: z.number().int().nonnegative(),
21
+ items: z.array(bulkResultEntrySchema)
22
+ });
23
+ const rawBodySchema = z.object({}).passthrough();
24
+ const crud = makeCrudRoute({
25
+ metadata: routeMetadata,
26
+ orm: {
27
+ entity: BillingItem,
28
+ idField: "id",
29
+ orgField: "organizationId",
30
+ tenantField: "tenantId",
31
+ softDeleteField: "deletedAt"
32
+ },
33
+ list: {
34
+ schema: z.object({}).passthrough(),
35
+ entityId: billingEntityIds.item,
36
+ fields: ["id"],
37
+ buildFilters: async () => ({})
38
+ },
39
+ actions: {
40
+ create: {
41
+ commandId: "billing.items.bulk_create",
42
+ schema: rawBodySchema,
43
+ mapInput: async ({ raw, ctx }) => {
44
+ const { translate } = await resolveTranslations();
45
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate);
46
+ return billingItemBulkCreateSchema.parse(scoped);
47
+ },
48
+ response: ({ result }) => ({
49
+ created: result?.created ?? 0,
50
+ deduplicated: result?.deduplicated ?? 0,
51
+ items: result?.items ?? []
52
+ }),
53
+ status: 200
54
+ }
55
+ }
56
+ });
57
+ const POST = crud.POST;
58
+ const openApi = createBillingCrudOpenApi({
59
+ resourceName: "BillingItemBulkCreate",
60
+ pluralName: "BillingItemBulkCreates",
61
+ querySchema: z.object({}).passthrough(),
62
+ listResponseSchema: z.object({ items: z.array(z.unknown()) }),
63
+ create: {
64
+ schema: billingItemBulkCreateSchema,
65
+ responseSchema: bulkResponseSchema,
66
+ description: "Batch-creates up to 500 Billing Items under one scope envelope. Idempotent per `sourceRef` (one batched existence check). Returns `{ created, deduplicated, items: [{ sourceRef, id, deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported in the body, never an error."
67
+ }
68
+ });
69
+ export {
70
+ POST,
71
+ metadata,
72
+ openApi
73
+ };
74
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/api/items/bulk/route.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { BillingItem } from '../../../data/entities'\nimport { billingEntityIds } from '../../../data/entityIds'\nimport { billingItemBulkCreateSchema } from '../../../data/validators'\nimport { createBillingCrudOpenApi } from '../../openapi'\n\n/**\n * `POST /api/billing/items/bulk` \u2014 batch-create Billing Items.\n *\n * One scope envelope, up to 500 item payloads. Idempotent per\n * `source_ref` with a *single* batched existence check, then a\n * single flush. The CPQ connector's subscription-activated /\n * -amended / -renewed subscribers are the primary callers \u2014 bulk\n * collapses what used to be one `billing.items.create` round-trip\n * per charge.\n */\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['billing.item.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst bulkResultEntrySchema = z.object({\n sourceRef: z.string().nullable(),\n id: z.string().uuid(),\n deduplicated: z.boolean(),\n})\n\nconst bulkResponseSchema = z.object({\n created: z.number().int().nonnegative(),\n deduplicated: z.number().int().nonnegative(),\n items: z.array(bulkResultEntrySchema),\n})\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: BillingItem,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n list: {\n schema: z.object({}).passthrough(),\n entityId: billingEntityIds.item,\n fields: ['id'],\n buildFilters: async () => ({}),\n },\n actions: {\n create: {\n commandId: 'billing.items.bulk_create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n return billingItemBulkCreateSchema.parse(scoped)\n },\n response: ({ result }) => ({\n created: result?.created ?? 0,\n deduplicated: result?.deduplicated ?? 0,\n items: result?.items ?? [],\n }),\n status: 200,\n },\n },\n})\n\nexport const POST = crud.POST\n\nexport const openApi = createBillingCrudOpenApi({\n resourceName: 'BillingItemBulkCreate',\n pluralName: 'BillingItemBulkCreates',\n querySchema: z.object({}).passthrough(),\n listResponseSchema: z.object({ items: z.array(z.unknown()) }),\n create: {\n schema: billingItemBulkCreateSchema,\n responseSchema: bulkResponseSchema,\n description:\n 'Batch-creates up to 500 Billing Items under one scope envelope. ' +\n 'Idempotent per `sourceRef` (one batched existence check). ' +\n 'Returns `{ created, deduplicated, items: [{ sourceRef, id, ' +\n 'deduplicated }] }`. Always HTTP 200 \u2014 duplicates are reported ' +\n 'in the body, never an error.',\n },\n})\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,gCAAgC;AAazC,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACtE;AAEO,MAAM,WAAW;AAExB,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,cAAc,EAAE,QAAQ;AAC1B,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,OAAO,EAAE,MAAM,qBAAqB;AACtC,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,OAAO,cAAc;AAAA,EACzB,UAAU;AAAA,EACV,KAAK;AAAA,IACH,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,EACnB;AAAA,EACA,MAAM;AAAA,IACJ,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,IACjC,UAAU,iBAAiB;AAAA,IAC3B,QAAQ,CAAC,IAAI;AAAA,IACb,cAAc,aAAa,CAAC;AAAA,EAC9B;AAAA,EACA,SAAS;AAAA,IACP,QAAQ;AAAA,MACN,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAM;AAChC,cAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,cAAM,SAAS,kBAAkB,OAAO,CAAC,GAAG,KAAK,SAAS;AAC1D,eAAO,4BAA4B,MAAM,MAAM;AAAA,MACjD;AAAA,MACA,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,QACzB,SAAS,QAAQ,WAAW;AAAA,QAC5B,cAAc,QAAQ,gBAAgB;AAAA,QACtC,OAAO,QAAQ,SAAS,CAAC;AAAA,MAC3B;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AACF,CAAC;AAEM,MAAM,OAAO,KAAK;AAElB,MAAM,UAAU,yBAAyB;AAAA,EAC9C,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAAA,EACtC,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,EAC5D,QAAQ;AAAA,IACN,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,aACE;AAAA,EAKJ;AACF,CAAC;",
6
+ "names": []
7
+ }