@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,187 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
+ import { DataTable } from "@open-mercato/ui/backend/DataTable";
8
+ import { Tag } from "@open-mercato/ui/primitives/tag";
9
+ import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
10
+ function formatDate(value) {
11
+ if (!value) return "\u2014";
12
+ const parsed = new Date(value);
13
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString();
14
+ }
15
+ function statusVariant(status) {
16
+ switch (status) {
17
+ case "paid":
18
+ return "success";
19
+ case "posted":
20
+ return "warning";
21
+ case "void":
22
+ return "error";
23
+ case "draft":
24
+ default:
25
+ return "default";
26
+ }
27
+ }
28
+ function formatMoney(value, currency) {
29
+ const n = Number.parseFloat(value);
30
+ if (!Number.isFinite(n)) return value;
31
+ try {
32
+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(n);
33
+ } catch {
34
+ return `${value} ${currency}`;
35
+ }
36
+ }
37
+ function BillingInvoicesListPage() {
38
+ const t = useT();
39
+ const router = useRouter();
40
+ const [rows, setRows] = React.useState([]);
41
+ const [page, setPage] = React.useState(1);
42
+ const [pageSize] = React.useState(25);
43
+ const [filters, setFilters] = React.useState({});
44
+ const [search, setSearch] = React.useState("");
45
+ const [total, setTotal] = React.useState(0);
46
+ const [totalPages, setTotalPages] = React.useState(1);
47
+ const [loading, setLoading] = React.useState(true);
48
+ const filterDefs = React.useMemo(
49
+ () => [
50
+ {
51
+ id: "status",
52
+ label: t("billing.invoices.filters.status", "Status"),
53
+ type: "select",
54
+ options: [
55
+ { value: "draft", label: t("billing.invoices.status.draft", "Draft") },
56
+ { value: "posted", label: t("billing.invoices.status.posted", "Posted") },
57
+ { value: "paid", label: t("billing.invoices.status.paid", "Paid") },
58
+ { value: "void", label: t("billing.invoices.status.void", "Void") }
59
+ ]
60
+ },
61
+ {
62
+ id: "testRun",
63
+ label: t("billing.invoices.filters.test_run", "Test runs"),
64
+ type: "select",
65
+ options: [
66
+ { value: "true", label: t("billing.invoices.filters.test_run.only", "Only test") },
67
+ {
68
+ value: "false",
69
+ label: t("billing.invoices.filters.test_run.exclude", "Exclude test")
70
+ }
71
+ ]
72
+ }
73
+ ],
74
+ [t]
75
+ );
76
+ const loadRows = React.useCallback(async () => {
77
+ setLoading(true);
78
+ try {
79
+ const params = new URLSearchParams({
80
+ page: String(page),
81
+ pageSize: String(pageSize)
82
+ });
83
+ if (search.trim()) params.set("search", search.trim());
84
+ if (typeof filters.status === "string" && filters.status) {
85
+ params.set("status", filters.status);
86
+ }
87
+ if (filters.testRun === "true" || filters.testRun === "false") {
88
+ params.set("testRun", String(filters.testRun));
89
+ }
90
+ const result = await readApiResultOrThrow(
91
+ `/api/billing/invoices?${params.toString()}`
92
+ );
93
+ setRows(result.items ?? []);
94
+ setTotal(result.total ?? 0);
95
+ setTotalPages(result.totalPages ?? 1);
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ }, [filters.status, filters.testRun, page, pageSize, search]);
100
+ React.useEffect(() => {
101
+ void loadRows();
102
+ }, [loadRows]);
103
+ const columns = React.useMemo(
104
+ () => [
105
+ {
106
+ accessorKey: "invoice_number",
107
+ header: t("billing.invoices.columns.number", "Number"),
108
+ cell: ({ row }) => {
109
+ const isTest = row.original.metadata?.test_run === true;
110
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
111
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-primary", children: row.original.invoice_number }),
112
+ isTest ? /* @__PURE__ */ jsx(Tag, { variant: "warning", children: "TEST" }) : null
113
+ ] });
114
+ }
115
+ },
116
+ {
117
+ accessorKey: "status",
118
+ header: t("billing.invoices.columns.status", "Status"),
119
+ cell: ({ row }) => /* @__PURE__ */ jsx(Tag, { variant: statusVariant(row.original.status), children: row.original.status ?? "\u2014" })
120
+ },
121
+ {
122
+ id: "period",
123
+ header: t("billing.invoices.columns.period", "Period"),
124
+ cell: ({ row }) => {
125
+ const start = row.original.metadata?.bill_period_start;
126
+ const end = row.original.metadata?.bill_period_end;
127
+ if (!start || !end) return "\u2014";
128
+ return /* @__PURE__ */ jsxs("span", { className: "text-xs", children: [
129
+ start,
130
+ " \u2192 ",
131
+ end
132
+ ] });
133
+ }
134
+ },
135
+ {
136
+ id: "total",
137
+ header: t("billing.invoices.columns.total", "Total"),
138
+ cell: ({ row }) => formatMoney(row.original.grand_total_gross_amount, row.original.currency_code)
139
+ },
140
+ {
141
+ accessorKey: "outstanding_amount",
142
+ header: t("billing.invoices.columns.outstanding", "Outstanding"),
143
+ cell: ({ row }) => formatMoney(row.original.outstanding_amount, row.original.currency_code)
144
+ },
145
+ {
146
+ accessorKey: "issue_date",
147
+ header: t("billing.invoices.columns.issued", "Issued"),
148
+ cell: ({ row }) => formatDate(row.original.issue_date)
149
+ }
150
+ ],
151
+ [t]
152
+ );
153
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
154
+ DataTable,
155
+ {
156
+ title: t("billing.invoices.title", "Billing Invoices"),
157
+ refreshButton: {
158
+ label: t("billing.common.refresh", "Refresh"),
159
+ onRefresh: loadRows,
160
+ isRefreshing: loading
161
+ },
162
+ columnChooser: { auto: true },
163
+ perspective: { tableId: "billing-invoices" },
164
+ columns,
165
+ data: rows,
166
+ onRowClick: (row) => router.push(`/backend/billing/invoices/${row.id}`),
167
+ isLoading: loading,
168
+ pagination: {
169
+ page,
170
+ pageSize,
171
+ total,
172
+ totalPages,
173
+ onPageChange: setPage
174
+ },
175
+ filters: filterDefs,
176
+ filterValues: filters,
177
+ onFiltersApply: setFilters,
178
+ onFiltersClear: () => setFilters({}),
179
+ searchValue: search,
180
+ onSearchChange: setSearch
181
+ }
182
+ ) }) });
183
+ }
184
+ export {
185
+ BillingInvoicesListPage as default
186
+ };
187
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/backend/billing/invoices/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\n/**\n * Billing Invoices list \u2014 what the operator actually opens every\n * morning to \"review and post drafts\".\n *\n * Scope: only invoices the billing engine emitted (`metadata.bill_run_id`\n * set). Plain `core/sales` invoices (created by sales orders, etc.)\n * are NOT shown here \u2014 billing has its own surface so the operator's\n * review queue is small and focused.\n */\n\ntype BillingInvoiceRow = {\n id: string\n invoice_number: string\n status: 'draft' | 'posted' | 'paid' | 'void' | 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: {\n bill_run_id?: string\n bill_account_id?: string\n bill_period_start?: string\n bill_period_end?: string\n test_run?: boolean\n } | null\n created_at: string\n updated_at: string\n}\n\ntype ListResponse = {\n items: BillingInvoiceRow[]\n total: number\n totalPages: number\n}\n\nfunction formatDate(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString()\n}\n\nfunction statusVariant(\n status: BillingInvoiceRow['status'],\n): 'default' | 'success' | 'warning' | 'error' {\n switch (status) {\n case 'paid':\n return 'success'\n case 'posted':\n return 'warning'\n case 'void':\n return 'error'\n case 'draft':\n default:\n return 'default'\n }\n}\n\nfunction formatMoney(value: string, currency: string): string {\n const n = Number.parseFloat(value)\n if (!Number.isFinite(n)) return value\n try {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(n)\n } catch {\n return `${value} ${currency}`\n }\n}\n\nexport default function BillingInvoicesListPage() {\n const t = useT()\n const router = useRouter()\n const [rows, setRows] = React.useState<BillingInvoiceRow[]>([])\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(25)\n const [filters, setFilters] = React.useState<FilterValues>({})\n const [search, setSearch] = React.useState('')\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(true)\n\n const filterDefs = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('billing.invoices.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: 'draft', label: t('billing.invoices.status.draft', 'Draft') },\n { value: 'posted', label: t('billing.invoices.status.posted', 'Posted') },\n { value: 'paid', label: t('billing.invoices.status.paid', 'Paid') },\n { value: 'void', label: t('billing.invoices.status.void', 'Void') },\n ],\n },\n {\n id: 'testRun',\n label: t('billing.invoices.filters.test_run', 'Test runs'),\n type: 'select',\n options: [\n { value: 'true', label: t('billing.invoices.filters.test_run.only', 'Only test') },\n {\n value: 'false',\n label: t('billing.invoices.filters.test_run.exclude', 'Exclude test'),\n },\n ],\n },\n ],\n [t],\n )\n\n const loadRows = React.useCallback(async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(pageSize),\n })\n if (search.trim()) params.set('search', search.trim())\n if (typeof filters.status === 'string' && filters.status) {\n params.set('status', filters.status)\n }\n if (filters.testRun === 'true' || filters.testRun === 'false') {\n params.set('testRun', String(filters.testRun))\n }\n const result = await readApiResultOrThrow<ListResponse>(\n `/api/billing/invoices?${params.toString()}`,\n )\n setRows(result.items ?? [])\n setTotal(result.total ?? 0)\n setTotalPages(result.totalPages ?? 1)\n } finally {\n setLoading(false)\n }\n }, [filters.status, filters.testRun, page, pageSize, search])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows])\n\n const columns = React.useMemo<ColumnDef<BillingInvoiceRow>[]>(\n () => [\n {\n accessorKey: 'invoice_number',\n header: t('billing.invoices.columns.number', 'Number'),\n cell: ({ row }) => {\n const isTest = row.original.metadata?.test_run === true\n return (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-primary\">\n {row.original.invoice_number}\n </span>\n {isTest ? <Tag variant=\"warning\">TEST</Tag> : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'status',\n header: t('billing.invoices.columns.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={statusVariant(row.original.status)}>{row.original.status ?? '\u2014'}</Tag>\n ),\n },\n {\n id: 'period',\n header: t('billing.invoices.columns.period', 'Period'),\n cell: ({ row }) => {\n const start = row.original.metadata?.bill_period_start\n const end = row.original.metadata?.bill_period_end\n if (!start || !end) return '\u2014'\n return (\n <span className=\"text-xs\">\n {start} \u2192 {end}\n </span>\n )\n },\n },\n {\n id: 'total',\n header: t('billing.invoices.columns.total', 'Total'),\n cell: ({ row }) =>\n formatMoney(row.original.grand_total_gross_amount, row.original.currency_code),\n },\n {\n accessorKey: 'outstanding_amount',\n header: t('billing.invoices.columns.outstanding', 'Outstanding'),\n cell: ({ row }) =>\n formatMoney(row.original.outstanding_amount, row.original.currency_code),\n },\n {\n accessorKey: 'issue_date',\n header: t('billing.invoices.columns.issued', 'Issued'),\n cell: ({ row }) => formatDate(row.original.issue_date),\n },\n ],\n [t],\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('billing.invoices.title', 'Billing Invoices')}\n refreshButton={{\n label: t('billing.common.refresh', 'Refresh'),\n onRefresh: loadRows,\n isRefreshing: loading,\n }}\n columnChooser={{ auto: true }}\n perspective={{ tableId: 'billing-invoices' }}\n columns={columns}\n data={rows}\n onRowClick={(row) => router.push(`/backend/billing/invoices/${row.id}`)}\n isLoading={loading}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n filters={filterDefs}\n filterValues={filters}\n onFiltersApply={setFilters}\n onFiltersClear={() => setFilters({})}\n searchValue={search}\n onSearchChange={setSearch}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": ";AAgKY,SACE,KADF;AA/JZ,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAE1B,SAAS,YAAY;AAErB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,WAAW;AACpB,SAAS,4BAA4B;AAyCrC,SAAS,WAAW,OAA0C;AAC5D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,SAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,QAAQ,OAAO,mBAAmB;AAC5E;AAEA,SAAS,cACP,QAC6C;AAC7C,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,YAAY,OAAe,UAA0B;AAC5D,QAAM,IAAI,OAAO,WAAW,KAAK;AACjC,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,CAAC;AAAA,EACjF,QAAQ;AACN,WAAO,GAAG,KAAK,IAAI,QAAQ;AAAA,EAC7B;AACF;AAEe,SAAR,0BAA2C;AAChD,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAA8B,CAAC,CAAC;AAC9D,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,QAAQ,IAAI,MAAM,SAAS,EAAE;AACpC,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,CAAC;AAC7D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AAEjD,QAAM,aAAa,MAAM;AAAA,IACvB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,mCAAmC,QAAQ;AAAA,QACpD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,SAAS,OAAO,EAAE,iCAAiC,OAAO,EAAE;AAAA,UACrE,EAAE,OAAO,UAAU,OAAO,EAAE,kCAAkC,QAAQ,EAAE;AAAA,UACxE,EAAE,OAAO,QAAQ,OAAO,EAAE,gCAAgC,MAAM,EAAE;AAAA,UAClE,EAAE,OAAO,QAAQ,OAAO,EAAE,gCAAgC,MAAM,EAAE;AAAA,QACpE;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,qCAAqC,WAAW;AAAA,QACzD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,QAAQ,OAAO,EAAE,0CAA0C,WAAW,EAAE;AAAA,UACjF;AAAA,YACE,OAAO;AAAA,YACP,OAAO,EAAE,6CAA6C,cAAc;AAAA,UACtE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,WAAW,MAAM,YAAY,YAAY;AAC7C,eAAW,IAAI;AACf,QAAI;AACF,YAAM,SAAS,IAAI,gBAAgB;AAAA,QACjC,MAAM,OAAO,IAAI;AAAA,QACjB,UAAU,OAAO,QAAQ;AAAA,MAC3B,CAAC;AACD,UAAI,OAAO,KAAK,EAAG,QAAO,IAAI,UAAU,OAAO,KAAK,CAAC;AACrD,UAAI,OAAO,QAAQ,WAAW,YAAY,QAAQ,QAAQ;AACxD,eAAO,IAAI,UAAU,QAAQ,MAAM;AAAA,MACrC;AACA,UAAI,QAAQ,YAAY,UAAU,QAAQ,YAAY,SAAS;AAC7D,eAAO,IAAI,WAAW,OAAO,QAAQ,OAAO,CAAC;AAAA,MAC/C;AACA,YAAM,SAAS,MAAM;AAAA,QACnB,yBAAyB,OAAO,SAAS,CAAC;AAAA,MAC5C;AACA,cAAQ,OAAO,SAAS,CAAC,CAAC;AAC1B,eAAS,OAAO,SAAS,CAAC;AAC1B,oBAAc,OAAO,cAAc,CAAC;AAAA,IACtC,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,QAAQ,QAAQ,QAAQ,SAAS,MAAM,UAAU,MAAM,CAAC;AAE5D,QAAM,UAAU,MAAM;AACpB,SAAK,SAAS;AAAA,EAChB,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,mCAAmC,QAAQ;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,SAAS,IAAI,SAAS,UAAU,aAAa;AACnD,iBACE,qBAAC,SAAI,WAAU,2BACb;AAAA,gCAAC,UAAK,WAAU,oCACb,cAAI,SAAS,gBAChB;AAAA,YACC,SAAS,oBAAC,OAAI,SAAQ,WAAU,kBAAI,IAAS;AAAA,aAChD;AAAA,QAEJ;AAAA,MACF;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,mCAAmC,QAAQ;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,OAAI,SAAS,cAAc,IAAI,SAAS,MAAM,GAAI,cAAI,SAAS,UAAU,UAAI;AAAA,MAElF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,mCAAmC,QAAQ;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,QAAQ,IAAI,SAAS,UAAU;AACrC,gBAAM,MAAM,IAAI,SAAS,UAAU;AACnC,cAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,iBACE,qBAAC,UAAK,WAAU,WACb;AAAA;AAAA,YAAM;AAAA,YAAI;AAAA,aACb;AAAA,QAEJ;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,kCAAkC,OAAO;AAAA,QACnD,MAAM,CAAC,EAAE,IAAI,MACX,YAAY,IAAI,SAAS,0BAA0B,IAAI,SAAS,aAAa;AAAA,MACjF;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,wCAAwC,aAAa;AAAA,QAC/D,MAAM,CAAC,EAAE,IAAI,MACX,YAAY,IAAI,SAAS,oBAAoB,IAAI,SAAS,aAAa;AAAA,MAC3E;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,mCAAmC,QAAQ;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM,WAAW,IAAI,SAAS,UAAU;AAAA,MACvD;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,0BAA0B,kBAAkB;AAAA,MACrD,eAAe;AAAA,QACb,OAAO,EAAE,0BAA0B,SAAS;AAAA,QAC5C,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,MACA,eAAe,EAAE,MAAM,KAAK;AAAA,MAC5B,aAAa,EAAE,SAAS,mBAAmB;AAAA,MAC3C;AAAA,MACA,MAAM;AAAA,MACN,YAAY,CAAC,QAAQ,OAAO,KAAK,6BAA6B,IAAI,EAAE,EAAE;AAAA,MACtE,WAAW;AAAA,MACX,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc;AAAA,MAChB;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,gBAAgB,MAAM,WAAW,CAAC,CAAC;AAAA,MACnC,aAAa;AAAA,MACb,gBAAgB;AAAA;AAAA,EAClB,GACF,GACF;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { FileText } from "lucide-react";
3
+ const invoicesIcon = React.createElement(FileText, { size: 16 });
4
+ const metadata = {
5
+ requireAuth: true,
6
+ requireFeatures: ["billing.invoice.view"],
7
+ pageTitle: "Billing Invoices",
8
+ pageTitleKey: "billing.invoices.title",
9
+ pageGroup: "Billing",
10
+ pageGroupKey: "billing.nav.group",
11
+ pageOrder: 40,
12
+ icon: invoicesIcon,
13
+ breadcrumb: [{ label: "Billing Invoices", labelKey: "billing.invoices.title" }]
14
+ };
15
+ export {
16
+ metadata
17
+ };
18
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/billing/backend/billing/invoices/page.meta.ts"],
4
+ "sourcesContent": ["import React from 'react'\nimport { FileText } from 'lucide-react'\n\nconst invoicesIcon = React.createElement(FileText, { size: 16 })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['billing.invoice.view'],\n pageTitle: 'Billing Invoices',\n pageTitleKey: 'billing.invoices.title',\n pageGroup: 'Billing',\n pageGroupKey: 'billing.nav.group',\n pageOrder: 40,\n icon: invoicesIcon,\n breadcrumb: [{ label: 'Billing Invoices', labelKey: 'billing.invoices.title' }],\n} as const\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,gBAAgB;AAEzB,MAAM,eAAe,MAAM,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAExD,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,sBAAsB;AAAA,EACxC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,oBAAoB,UAAU,yBAAyB,CAAC;AAChF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,265 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { Pencil } from "lucide-react";
6
+ import { useParams, useRouter } from "next/navigation";
7
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import { Page, PageBody, PageHeader } from "@open-mercato/ui/backend/Page";
9
+ import { FormHeader } from "@open-mercato/ui/backend/forms";
10
+ import { Button } from "@open-mercato/ui/primitives/button";
11
+ import { Tag } from "@open-mercato/ui/primitives/tag";
12
+ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
13
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
14
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
15
+ import {
16
+ apiCallOrThrow,
17
+ readApiResultOrThrow
18
+ } from "@open-mercato/ui/backend/utils/apiCall";
19
+ import { normalizeCrudServerError } from "@open-mercato/ui/backend/utils/serverErrors";
20
+ import {
21
+ ItemForm
22
+ } from "../../../../components/ItemForm.js";
23
+ import { DetailCard, DetailField } from "../../../../components/DetailFields.js";
24
+ function formatDate(value) {
25
+ if (!value) return "\u2014";
26
+ const parsed = new Date(value);
27
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString();
28
+ }
29
+ function formatRate(row) {
30
+ const rate = row.rate_json ?? {};
31
+ if (row.type === "one_time" && typeof rate.amount === "number") {
32
+ return String(rate.amount);
33
+ }
34
+ if (row.type === "recurring" && typeof rate.unit_price === "number") {
35
+ return String(rate.unit_price);
36
+ }
37
+ if (row.type === "usage") {
38
+ if ("model" in rate && "tiers" in rate) {
39
+ return `tiered (${String(rate.model)})`;
40
+ }
41
+ if (typeof rate.unit_price === "number") {
42
+ return String(rate.unit_price);
43
+ }
44
+ }
45
+ return "\u2014";
46
+ }
47
+ function toFormValues(row) {
48
+ const rate = row.rate_json ?? {};
49
+ const out = {
50
+ billAccountId: row.bill_account_id,
51
+ type: row.type,
52
+ billStartDate: row.bill_start_date?.slice(0, 10) ?? "",
53
+ billEndDate: row.bill_end_date?.slice(0, 10) ?? "",
54
+ description: row.description,
55
+ uomCode: row.uom_code ?? "",
56
+ subscriptionId: row.subscription_id ?? "",
57
+ subscriptionItemId: row.subscription_item_id ?? "",
58
+ isActive: row.is_active
59
+ };
60
+ if (row.type === "one_time" && typeof rate.amount === "number") {
61
+ out.oneTimeAmount = String(rate.amount);
62
+ } else if (row.type === "recurring" && typeof rate.unit_price === "number") {
63
+ out.recurringUnitPrice = String(rate.unit_price);
64
+ } else if (row.type === "usage") {
65
+ if ("model" in rate && "tiers" in rate) {
66
+ out.usageMode = "tiered";
67
+ out.usageTieredJson = JSON.stringify(rate, null, 2);
68
+ } else if (typeof rate.unit_price === "number") {
69
+ out.usageMode = "simple";
70
+ out.usageUnitPrice = String(rate.unit_price);
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ function ItemView({ row }) {
76
+ const t = useT();
77
+ return /* @__PURE__ */ jsxs(DetailCard, { children: [
78
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.detail.field.account", "Account"), children: /* @__PURE__ */ jsx(
79
+ Link,
80
+ {
81
+ href: `/backend/billing/accounts/${row.bill_account_id}`,
82
+ className: "text-primary hover:underline",
83
+ children: row.bill_account_name ?? row.bill_account_id
84
+ }
85
+ ) }),
86
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.columns.type", "Type"), children: /* @__PURE__ */ jsx(Tag, { variant: "info", children: row.type }) }),
87
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.columns.start", "Start"), children: formatDate(row.bill_start_date) }),
88
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.columns.end", "End"), children: formatDate(row.bill_end_date) }),
89
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.form.rate.title", "Rate"), children: formatRate(row) }),
90
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.columns.uom", "UoM"), children: row.uom_code || "\u2014" }),
91
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.form.subscription_id", "Subscription ID"), children: row.subscription_id || "\u2014" }),
92
+ /* @__PURE__ */ jsx(
93
+ DetailField,
94
+ {
95
+ label: t("billing.items.form.subscription_item_id", "Subscription item ID"),
96
+ children: row.subscription_item_id || "\u2014"
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.detail.field.source_ref", "Source ref"), children: row.source_ref ? /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: row.source_ref }) : "\u2014" }),
100
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.detail.field.billed_to", "Billed through"), children: row.billed_to_date ? formatDate(row.billed_to_date) : "\u2014" }),
101
+ row.currency_mismatch ? /* @__PURE__ */ jsx(
102
+ DetailField,
103
+ {
104
+ label: t("billing.items.detail.currency_mismatch", "Currency mismatch"),
105
+ children: /* @__PURE__ */ jsx(Tag, { variant: "warning", children: t("billing.common.yes", "Yes") })
106
+ }
107
+ ) : null,
108
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.items.columns.status", "Status"), children: row.is_active ? /* @__PURE__ */ jsx(Tag, { variant: "success", children: t("billing.common.active", "Active") }) : /* @__PURE__ */ jsx(Tag, { variant: "default", children: t("billing.common.inactive", "Inactive") }) }),
109
+ /* @__PURE__ */ jsx(DetailField, { label: t("billing.common.id", "ID"), children: /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: row.id }) })
110
+ ] });
111
+ }
112
+ function BillingItemDetailPage(props) {
113
+ const t = useT();
114
+ const router = useRouter();
115
+ const urlParams = useParams();
116
+ const itemId = props.params?.id ?? urlParams?.id ?? "";
117
+ const [row, setRow] = React.useState(null);
118
+ const [loading, setLoading] = React.useState(true);
119
+ const [error, setError] = React.useState(null);
120
+ const [editing, setEditing] = React.useState(false);
121
+ const [saving, setSaving] = React.useState(false);
122
+ const [deleting, setDeleting] = React.useState(false);
123
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
124
+ const load = React.useCallback(async () => {
125
+ if (!itemId) return;
126
+ setLoading(true);
127
+ setError(null);
128
+ try {
129
+ const result = await readApiResultOrThrow(
130
+ `/api/billing/items?id=${itemId}&pageSize=1`
131
+ );
132
+ const found = result.items?.[0];
133
+ if (!found) {
134
+ setError(t("billing.items.detail.error.not_found", "Item not found"));
135
+ return;
136
+ }
137
+ setRow(found);
138
+ } catch (err) {
139
+ const { message } = normalizeCrudServerError(err);
140
+ setError(message || t("billing.items.detail.error.load", "Failed to load item"));
141
+ } finally {
142
+ setLoading(false);
143
+ }
144
+ }, [itemId, t]);
145
+ React.useEffect(() => {
146
+ void load();
147
+ }, [load]);
148
+ const handleSubmit = React.useCallback(
149
+ async (assembled) => {
150
+ if (!row) return;
151
+ setSaving(true);
152
+ try {
153
+ const payload = {
154
+ id: row.id,
155
+ billStartDate: assembled.billStartDate,
156
+ description: assembled.description,
157
+ rateJson: assembled.rateJson,
158
+ isActive: assembled.isActive ?? row.is_active
159
+ };
160
+ payload.billEndDate = assembled.billEndDate ?? null;
161
+ payload.uomCode = assembled.uomCode ?? null;
162
+ payload.subscriptionId = assembled.subscriptionId ?? null;
163
+ payload.subscriptionItemId = assembled.subscriptionItemId ?? null;
164
+ await apiCallOrThrow("/api/billing/items", {
165
+ method: "PUT",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify(payload)
168
+ });
169
+ flash(t("billing.items.detail.save.success", "Item updated"), "success");
170
+ setEditing(false);
171
+ void load();
172
+ } catch (err) {
173
+ const { message } = normalizeCrudServerError(err);
174
+ flash(message || t("billing.items.detail.save.error", "Save failed"), "error");
175
+ } finally {
176
+ setSaving(false);
177
+ }
178
+ },
179
+ [load, row, t]
180
+ );
181
+ const handleDelete = React.useCallback(async () => {
182
+ if (!row) return;
183
+ const ok = await confirm({
184
+ title: t("billing.items.detail.delete.confirm.title", "Delete item?"),
185
+ text: t(
186
+ "billing.items.detail.delete.confirm.text",
187
+ "Soft-deletes the item. Its row stays in the database for audit; the Bill Run engine will stop including it immediately."
188
+ ),
189
+ confirmText: t("billing.items.detail.delete.confirm.button", "Soft delete"),
190
+ variant: "destructive"
191
+ });
192
+ if (!ok) return;
193
+ setDeleting(true);
194
+ try {
195
+ await apiCallOrThrow("/api/billing/items", {
196
+ method: "DELETE",
197
+ headers: { "Content-Type": "application/json" },
198
+ body: JSON.stringify({ id: row.id })
199
+ });
200
+ flash(t("billing.items.detail.delete.success", "Item deleted"), "success");
201
+ router.push("/backend/billing/items");
202
+ } catch (err) {
203
+ const { message } = normalizeCrudServerError(err);
204
+ flash(message || t("billing.items.detail.delete.error", "Delete failed"), "error");
205
+ setDeleting(false);
206
+ }
207
+ }, [confirm, router, row, t]);
208
+ if (error) {
209
+ return /* @__PURE__ */ jsxs(Page, { children: [
210
+ /* @__PURE__ */ jsx(PageHeader, { title: t("billing.items.detail.title", "Billing Item") }),
211
+ /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error }) })
212
+ ] });
213
+ }
214
+ if (loading || !row) {
215
+ return /* @__PURE__ */ jsxs(Page, { children: [
216
+ /* @__PURE__ */ jsx(PageHeader, { title: t("billing.items.detail.title", "Billing Item") }),
217
+ /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("billing.items.detail.loading", "Loading item\u2026") }) })
218
+ ] });
219
+ }
220
+ return /* @__PURE__ */ jsxs(Page, { children: [
221
+ /* @__PURE__ */ jsx(
222
+ FormHeader,
223
+ {
224
+ mode: "detail",
225
+ backHref: "/backend/billing/items",
226
+ entityTypeLabel: t("billing.items.detail.title", "Billing Item"),
227
+ title: row.description,
228
+ statusBadge: row.is_active ? /* @__PURE__ */ jsx(Tag, { variant: "success", children: t("billing.common.active", "Active") }) : /* @__PURE__ */ jsx(Tag, { variant: "default", children: t("billing.common.inactive", "Inactive") }),
229
+ actionsContent: /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ !editing ? /* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => setEditing(true), children: [
231
+ /* @__PURE__ */ jsx(Pencil, { size: 16 }),
232
+ t("billing.common.edit", "Edit")
233
+ ] }) : null,
234
+ /* @__PURE__ */ jsx(
235
+ Button,
236
+ {
237
+ type: "button",
238
+ variant: "outline",
239
+ onClick: handleDelete,
240
+ disabled: deleting,
241
+ children: deleting ? t("billing.items.detail.delete.in_progress", "Deleting\u2026") : t("billing.items.detail.delete.action", "Soft delete")
242
+ }
243
+ )
244
+ ] })
245
+ }
246
+ ),
247
+ /* @__PURE__ */ jsxs(PageBody, { children: [
248
+ editing ? /* @__PURE__ */ jsx(
249
+ ItemForm,
250
+ {
251
+ mode: "edit",
252
+ initial: toFormValues(row),
253
+ submitting: saving,
254
+ onSubmit: handleSubmit,
255
+ onCancel: () => setEditing(false)
256
+ }
257
+ ) : /* @__PURE__ */ jsx(ItemView, { row }),
258
+ ConfirmDialogElement
259
+ ] })
260
+ ] });
261
+ }
262
+ export {
263
+ BillingItemDetailPage as default
264
+ };
265
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/billing/backend/billing/items/%5Bid%5D/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Pencil } from 'lucide-react'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n apiCallOrThrow,\n readApiResultOrThrow,\n} from '@open-mercato/ui/backend/utils/apiCall'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport {\n ItemForm,\n type AssembledItemPayload,\n type ItemFormValues,\n} from '../../../../components/ItemForm'\nimport { DetailCard, DetailField } from '../../../../components/DetailFields'\n\n/**\n * `/backend/billing/items/[id]` \u2014 Billing Item detail.\n *\n * Opens read-only (field grid). The `Edit` button swaps the grid for\n * the `ItemForm`; Save / Cancel return to the read-only view. Loads\n * via the list endpoint (`?id=<id>`), edits through `PUT`, soft-delete\n * via `DELETE` with a `ConfirmDialog` gate.\n *\n * Two read-only badges at the top: `currencyMismatch` and\n * `billedToDate` (Bill Run engine-managed columns the operator\n * cannot edit but needs to see for troubleshooting).\n */\n\n// API list rows are snake_case (see `api/items/route.ts` `fields`).\n// `bill_account_name` is resolved by the route's `afterList` hook.\ntype BillingItemRow = {\n id: string\n bill_account_id: string\n bill_account_name: string | null\n type: 'one_time' | 'recurring' | 'usage'\n bill_start_date: string\n bill_end_date: string | null\n description: string\n rate_json: Record<string, unknown> | null\n uom_code: string | null\n subscription_id: string | null\n subscription_item_id: string | null\n source_ref: string | null\n currency_mismatch: boolean\n billed_to_date: string | null\n is_active: boolean\n created_at: string\n updated_at: string\n}\n\ntype ListResponse = { items: BillingItemRow[] }\n\nfunction formatDate(value: string | null | undefined): string {\n if (!value) return '\u2014'\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString()\n}\n\nfunction formatRate(row: BillingItemRow): string {\n const rate = (row.rate_json ?? {}) as Record<string, unknown>\n if (row.type === 'one_time' && typeof rate.amount === 'number') {\n return String(rate.amount)\n }\n if (row.type === 'recurring' && typeof rate.unit_price === 'number') {\n return String(rate.unit_price)\n }\n if (row.type === 'usage') {\n if ('model' in rate && 'tiers' in rate) {\n return `tiered (${String(rate.model)})`\n }\n if (typeof rate.unit_price === 'number') {\n return String(rate.unit_price)\n }\n }\n return '\u2014'\n}\n\nfunction toFormValues(row: BillingItemRow): Partial<ItemFormValues> {\n const rate = (row.rate_json ?? {}) as Record<string, unknown>\n const out: Partial<ItemFormValues> = {\n billAccountId: row.bill_account_id,\n type: row.type,\n billStartDate: row.bill_start_date?.slice(0, 10) ?? '',\n billEndDate: row.bill_end_date?.slice(0, 10) ?? '',\n description: row.description,\n uomCode: row.uom_code ?? '',\n subscriptionId: row.subscription_id ?? '',\n subscriptionItemId: row.subscription_item_id ?? '',\n isActive: row.is_active,\n }\n if (row.type === 'one_time' && typeof rate.amount === 'number') {\n out.oneTimeAmount = String(rate.amount)\n } else if (row.type === 'recurring' && typeof rate.unit_price === 'number') {\n out.recurringUnitPrice = String(rate.unit_price)\n } else if (row.type === 'usage') {\n if ('model' in rate && 'tiers' in rate) {\n out.usageMode = 'tiered'\n out.usageTieredJson = JSON.stringify(rate, null, 2)\n } else if (typeof rate.unit_price === 'number') {\n out.usageMode = 'simple'\n out.usageUnitPrice = String(rate.unit_price)\n }\n }\n return out\n}\n\n// \u2500\u2500\u2500 Read-only view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ItemView({ row }: { row: BillingItemRow }) {\n const t = useT()\n return (\n <DetailCard>\n <DetailField label={t('billing.items.detail.field.account', 'Account')}>\n <Link\n href={`/backend/billing/accounts/${row.bill_account_id}`}\n className=\"text-primary hover:underline\"\n >\n {row.bill_account_name ?? row.bill_account_id}\n </Link>\n </DetailField>\n <DetailField label={t('billing.items.columns.type', 'Type')}>\n <Tag variant=\"info\">{row.type}</Tag>\n </DetailField>\n <DetailField label={t('billing.items.columns.start', 'Start')}>\n {formatDate(row.bill_start_date)}\n </DetailField>\n <DetailField label={t('billing.items.columns.end', 'End')}>\n {formatDate(row.bill_end_date)}\n </DetailField>\n <DetailField label={t('billing.items.form.rate.title', 'Rate')}>\n {formatRate(row)}\n </DetailField>\n <DetailField label={t('billing.items.columns.uom', 'UoM')}>\n {row.uom_code || '\u2014'}\n </DetailField>\n <DetailField label={t('billing.items.form.subscription_id', 'Subscription ID')}>\n {row.subscription_id || '\u2014'}\n </DetailField>\n <DetailField\n label={t('billing.items.form.subscription_item_id', 'Subscription item ID')}\n >\n {row.subscription_item_id || '\u2014'}\n </DetailField>\n <DetailField label={t('billing.items.detail.field.source_ref', 'Source ref')}>\n {row.source_ref ? (\n <span className=\"font-mono text-xs\">{row.source_ref}</span>\n ) : (\n '\u2014'\n )}\n </DetailField>\n <DetailField label={t('billing.items.detail.field.billed_to', 'Billed through')}>\n {row.billed_to_date ? formatDate(row.billed_to_date) : '\u2014'}\n </DetailField>\n {row.currency_mismatch ? (\n <DetailField\n label={t('billing.items.detail.currency_mismatch', 'Currency mismatch')}\n >\n <Tag variant=\"warning\">{t('billing.common.yes', 'Yes')}</Tag>\n </DetailField>\n ) : null}\n <DetailField label={t('billing.items.columns.status', 'Status')}>\n {row.is_active ? (\n <Tag variant=\"success\">{t('billing.common.active', 'Active')}</Tag>\n ) : (\n <Tag variant=\"default\">{t('billing.common.inactive', 'Inactive')}</Tag>\n )}\n </DetailField>\n <DetailField label={t('billing.common.id', 'ID')}>\n <span className=\"font-mono text-xs\">{row.id}</span>\n </DetailField>\n </DetailCard>\n )\n}\n\nexport default function BillingItemDetailPage(props: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n // OM serves backend pages through a catch-all route \u2014 the dynamic\n // `[id]` segment arrives as a page prop; `useParams()` is the fallback.\n const urlParams = useParams<{ id: string }>()\n const itemId = (props.params?.id ?? urlParams?.id ?? '') as string\n\n const [row, setRow] = React.useState<BillingItemRow | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [editing, setEditing] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [deleting, setDeleting] = React.useState(false)\n\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const load = React.useCallback(async () => {\n if (!itemId) return\n setLoading(true)\n setError(null)\n try {\n const result = await readApiResultOrThrow<ListResponse>(\n `/api/billing/items?id=${itemId}&pageSize=1`,\n )\n const found = result.items?.[0]\n if (!found) {\n setError(t('billing.items.detail.error.not_found', 'Item not found'))\n return\n }\n setRow(found)\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n setError(message || t('billing.items.detail.error.load', 'Failed to load item'))\n } finally {\n setLoading(false)\n }\n }, [itemId, t])\n\n React.useEffect(() => {\n void load()\n }, [load])\n\n const handleSubmit = React.useCallback(\n async (assembled: AssembledItemPayload) => {\n if (!row) return\n setSaving(true)\n try {\n const payload: Record<string, unknown> = {\n id: row.id,\n billStartDate: assembled.billStartDate,\n description: assembled.description,\n rateJson: assembled.rateJson,\n isActive: assembled.isActive ?? row.is_active,\n }\n // Optional fields \u2014 submit null when the operator cleared them\n // so the column actually resets (the schema validators accept\n // null for nullable columns).\n payload.billEndDate = assembled.billEndDate ?? null\n payload.uomCode = assembled.uomCode ?? null\n payload.subscriptionId = assembled.subscriptionId ?? null\n payload.subscriptionItemId = assembled.subscriptionItemId ?? null\n\n await apiCallOrThrow('/api/billing/items', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n flash(t('billing.items.detail.save.success', 'Item updated'), 'success')\n setEditing(false)\n void load()\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.items.detail.save.error', 'Save failed'), 'error')\n } finally {\n setSaving(false)\n }\n },\n [load, row, t],\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!row) return\n const ok = await confirm({\n title: t('billing.items.detail.delete.confirm.title', 'Delete item?'),\n text: t(\n 'billing.items.detail.delete.confirm.text',\n 'Soft-deletes the item. Its row stays in the database for audit; the Bill Run engine will stop including it immediately.',\n ),\n confirmText: t('billing.items.detail.delete.confirm.button', 'Soft delete'),\n variant: 'destructive',\n })\n if (!ok) return\n setDeleting(true)\n try {\n await apiCallOrThrow('/api/billing/items', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id }),\n })\n flash(t('billing.items.detail.delete.success', 'Item deleted'), 'success')\n router.push('/backend/billing/items')\n } catch (err) {\n const { message } = normalizeCrudServerError(err)\n flash(message || t('billing.items.detail.delete.error', 'Delete failed'), 'error')\n setDeleting(false)\n }\n }, [confirm, router, row, t])\n\n if (error) {\n return (\n <Page>\n <PageHeader title={t('billing.items.detail.title', 'Billing Item')} />\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n if (loading || !row) {\n return (\n <Page>\n <PageHeader title={t('billing.items.detail.title', 'Billing Item')} />\n <PageBody>\n <LoadingMessage label={t('billing.items.detail.loading', 'Loading item\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/billing/items\"\n entityTypeLabel={t('billing.items.detail.title', 'Billing Item')}\n title={row.description}\n statusBadge={\n row.is_active ? (\n <Tag variant=\"success\">{t('billing.common.active', 'Active')}</Tag>\n ) : (\n <Tag variant=\"default\">{t('billing.common.inactive', 'Inactive')}</Tag>\n )\n }\n actionsContent={\n <>\n {!editing ? (\n <Button type=\"button\" onClick={() => setEditing(true)}>\n <Pencil size={16} />\n {t('billing.common.edit', 'Edit')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleDelete}\n disabled={deleting}\n >\n {deleting\n ? t('billing.items.detail.delete.in_progress', 'Deleting\u2026')\n : t('billing.items.detail.delete.action', 'Soft delete')}\n </Button>\n </>\n }\n />\n <PageBody>\n {editing ? (\n <ItemForm\n mode=\"edit\"\n initial={toFormValues(row)}\n submitting={saving}\n onSubmit={handleSubmit}\n onCancel={() => setEditing(false)}\n />\n ) : (\n <ItemView row={row} />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { Receipt } from "lucide-react";
3
+ const itemIcon = React.createElement(Receipt, { size: 16 });
4
+ const metadata = {
5
+ requireAuth: true,
6
+ requireFeatures: ["billing.item.view"],
7
+ pageTitle: "Billing Item",
8
+ pageTitleKey: "billing.items.detail.title",
9
+ pageGroup: "Billing",
10
+ pageGroupKey: "billing.nav.group",
11
+ navHidden: true,
12
+ icon: itemIcon,
13
+ breadcrumb: [
14
+ {
15
+ label: "Billing Items",
16
+ labelKey: "billing.items.title",
17
+ href: "/backend/billing/items"
18
+ },
19
+ { label: "Item", labelKey: "billing.items.detail.title" }
20
+ ]
21
+ };
22
+ export {
23
+ metadata
24
+ };
25
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/billing/backend/billing/items/%5Bid%5D/page.meta.ts"],
4
+ "sourcesContent": ["import React from 'react'\nimport { Receipt } from 'lucide-react'\n\nconst itemIcon = React.createElement(Receipt, { size: 16 })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['billing.item.view'],\n pageTitle: 'Billing Item',\n pageTitleKey: 'billing.items.detail.title',\n pageGroup: 'Billing',\n pageGroupKey: 'billing.nav.group',\n navHidden: true,\n icon: itemIcon,\n breadcrumb: [\n {\n label: 'Billing Items',\n labelKey: 'billing.items.title',\n href: '/backend/billing/items',\n },\n { label: 'Item', labelKey: 'billing.items.detail.title' },\n ],\n} as const\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,eAAe;AAExB,MAAM,WAAW,MAAM,cAAc,SAAS,EAAE,MAAM,GAAG,CAAC;AAEnD,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,mBAAmB;AAAA,EACrC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY;AAAA,IACV;AAAA,MACE,OAAO;AAAA,MACP,UAAU;AAAA,MACV,MAAM;AAAA,IACR;AAAA,IACA,EAAE,OAAO,QAAQ,UAAU,6BAA6B;AAAA,EAC1D;AACF;",
6
+ "names": []
7
+ }