@open-mercato/core 0.8.1-develop.7228.1.b7ea09d2d3 → 0.8.1-develop.7238.1.3abe669ea5

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 (173) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/availability_policy/index.js +41 -0
  3. package/dist/generated/entities/availability_policy/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +4 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +21 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/attachments/data/entities.js +3 -3
  9. package/dist/modules/attachments/data/entities.js.map +2 -2
  10. package/dist/modules/availability/acl.js +16 -0
  11. package/dist/modules/availability/acl.js.map +7 -0
  12. package/dist/modules/availability/api/check/route.js +95 -0
  13. package/dist/modules/availability/api/check/route.js.map +7 -0
  14. package/dist/modules/availability/api/openapi.js +27 -0
  15. package/dist/modules/availability/api/openapi.js.map +7 -0
  16. package/dist/modules/availability/api/policies/resolve-preview/route.js +58 -0
  17. package/dist/modules/availability/api/policies/resolve-preview/route.js.map +7 -0
  18. package/dist/modules/availability/api/policies/route.js +188 -0
  19. package/dist/modules/availability/api/policies/route.js.map +7 -0
  20. package/dist/modules/availability/backend/availability/check/page.js +185 -0
  21. package/dist/modules/availability/backend/availability/check/page.js.map +7 -0
  22. package/dist/modules/availability/backend/availability/check/page.meta.js +16 -0
  23. package/dist/modules/availability/backend/availability/check/page.meta.js.map +7 -0
  24. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +79 -0
  25. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +7 -0
  26. package/dist/modules/availability/backend/availability/policies/ScopeFields.js +53 -0
  27. package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +7 -0
  28. package/dist/modules/availability/backend/availability/policies/[id]/page.js +152 -0
  29. package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +7 -0
  30. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +12 -0
  31. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +7 -0
  32. package/dist/modules/availability/backend/availability/policies/create/page.js +94 -0
  33. package/dist/modules/availability/backend/availability/policies/create/page.js.map +7 -0
  34. package/dist/modules/availability/backend/availability/policies/create/page.meta.js +12 -0
  35. package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +7 -0
  36. package/dist/modules/availability/backend/availability/policies/formGroups.js +75 -0
  37. package/dist/modules/availability/backend/availability/policies/formGroups.js.map +7 -0
  38. package/dist/modules/availability/backend/availability/policies/page.js +176 -0
  39. package/dist/modules/availability/backend/availability/policies/page.js.map +7 -0
  40. package/dist/modules/availability/backend/availability/policies/page.meta.js +16 -0
  41. package/dist/modules/availability/backend/availability/policies/page.meta.js.map +7 -0
  42. package/dist/modules/availability/commands/policies.js +312 -0
  43. package/dist/modules/availability/commands/policies.js.map +7 -0
  44. package/dist/modules/availability/commands/scope.js +19 -0
  45. package/dist/modules/availability/commands/scope.js.map +7 -0
  46. package/dist/modules/availability/data/entities.js +95 -0
  47. package/dist/modules/availability/data/entities.js.map +7 -0
  48. package/dist/modules/availability/data/validators.js +53 -0
  49. package/dist/modules/availability/data/validators.js.map +7 -0
  50. package/dist/modules/availability/di.js +41 -0
  51. package/dist/modules/availability/di.js.map +7 -0
  52. package/dist/modules/availability/events.js +18 -0
  53. package/dist/modules/availability/events.js.map +7 -0
  54. package/dist/modules/availability/index.js +15 -0
  55. package/dist/modules/availability/index.js.map +7 -0
  56. package/dist/modules/availability/lib/policyResolution.js +155 -0
  57. package/dist/modules/availability/lib/policyResolution.js.map +7 -0
  58. package/dist/modules/availability/lib/tryResolve.js +11 -0
  59. package/dist/modules/availability/lib/tryResolve.js.map +7 -0
  60. package/dist/modules/availability/migrations/Migration20260922095844_availability.js +16 -0
  61. package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +7 -0
  62. package/dist/modules/availability/setup.js +12 -0
  63. package/dist/modules/availability/setup.js.map +7 -0
  64. package/dist/modules/entities/cli.js +33 -12
  65. package/dist/modules/entities/cli.js.map +2 -2
  66. package/dist/modules/inbox_ops/api/settings/route.js +7 -32
  67. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  68. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +2 -2
  69. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  70. package/dist/modules/inbox_ops/components/proposals/ActionCard.js +1 -1
  71. package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
  72. package/dist/modules/inbox_ops/lib/ensure-settings.js +47 -0
  73. package/dist/modules/inbox_ops/lib/ensure-settings.js.map +7 -0
  74. package/dist/modules/inbox_ops/lib/executionEngine.js.map +1 -1
  75. package/dist/modules/inbox_ops/lib/executionHelpers.js +0 -23
  76. package/dist/modules/inbox_ops/lib/executionHelpers.js.map +2 -2
  77. package/dist/modules/inbox_ops/lib/payloadEnrichment.js +0 -3
  78. package/dist/modules/inbox_ops/lib/payloadEnrichment.js.map +2 -2
  79. package/dist/modules/inbox_ops/setup.js +2 -21
  80. package/dist/modules/inbox_ops/setup.js.map +2 -2
  81. package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +1 -1
  82. package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -0
  83. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  84. package/dist/modules/sales/data/entities.js +3 -1
  85. package/dist/modules/sales/data/entities.js.map +2 -2
  86. package/dist/modules/sales/inbox-actions.js +0 -12
  87. package/dist/modules/sales/inbox-actions.js.map +2 -2
  88. package/dist/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.js +20 -0
  89. package/dist/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.js.map +7 -0
  90. package/dist/modules/wms/di.js +3 -0
  91. package/dist/modules/wms/di.js.map +2 -2
  92. package/dist/modules/wms/lib/availabilityCache.js +85 -0
  93. package/dist/modules/wms/lib/availabilityCache.js.map +7 -0
  94. package/dist/modules/wms/lib/availabilityCalculation.js +178 -0
  95. package/dist/modules/wms/lib/availabilityCalculation.js.map +7 -0
  96. package/dist/modules/wms/lib/availabilityProvider.js +14 -0
  97. package/dist/modules/wms/lib/availabilityProvider.js.map +7 -0
  98. package/dist/modules/wms/lib/sqlInClause.js +9 -0
  99. package/dist/modules/wms/lib/sqlInClause.js.map +7 -0
  100. package/dist/modules/wms/lib/tryResolve.js +11 -0
  101. package/dist/modules/wms/lib/tryResolve.js.map +7 -0
  102. package/dist/modules/wms/subscribers/invalidate-availability-cache.js +16 -0
  103. package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +7 -0
  104. package/generated/entities/availability_policy/index.ts +19 -0
  105. package/generated/entities.ids.generated.ts +4 -0
  106. package/generated/entity-fields-registry.ts +21 -0
  107. package/package.json +7 -7
  108. package/src/modules/attachments/data/entities.ts +5 -3
  109. package/src/modules/auth/i18n/de.json +3 -0
  110. package/src/modules/auth/i18n/en.json +3 -0
  111. package/src/modules/auth/i18n/es.json +3 -0
  112. package/src/modules/auth/i18n/ko.json +3 -0
  113. package/src/modules/auth/i18n/pl.json +3 -0
  114. package/src/modules/availability/acl.ts +12 -0
  115. package/src/modules/availability/api/check/route.ts +114 -0
  116. package/src/modules/availability/api/openapi.ts +28 -0
  117. package/src/modules/availability/api/policies/resolve-preview/route.ts +67 -0
  118. package/src/modules/availability/api/policies/route.ts +224 -0
  119. package/src/modules/availability/backend/availability/check/page.meta.ts +12 -0
  120. package/src/modules/availability/backend/availability/check/page.tsx +223 -0
  121. package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +106 -0
  122. package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +55 -0
  123. package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +8 -0
  124. package/src/modules/availability/backend/availability/policies/[id]/page.tsx +182 -0
  125. package/src/modules/availability/backend/availability/policies/create/page.meta.ts +8 -0
  126. package/src/modules/availability/backend/availability/policies/create/page.tsx +105 -0
  127. package/src/modules/availability/backend/availability/policies/formGroups.ts +77 -0
  128. package/src/modules/availability/backend/availability/policies/page.meta.ts +12 -0
  129. package/src/modules/availability/backend/availability/policies/page.tsx +196 -0
  130. package/src/modules/availability/commands/policies.ts +358 -0
  131. package/src/modules/availability/commands/scope.ts +26 -0
  132. package/src/modules/availability/data/entities.ts +92 -0
  133. package/src/modules/availability/data/validators.ts +70 -0
  134. package/src/modules/availability/di.ts +54 -0
  135. package/src/modules/availability/events.ts +17 -0
  136. package/src/modules/availability/i18n/de.json +81 -0
  137. package/src/modules/availability/i18n/en.json +81 -0
  138. package/src/modules/availability/i18n/es.json +81 -0
  139. package/src/modules/availability/i18n/ko.json +81 -0
  140. package/src/modules/availability/i18n/pl.json +81 -0
  141. package/src/modules/availability/index.ts +13 -0
  142. package/src/modules/availability/lib/policyResolution.ts +264 -0
  143. package/src/modules/availability/lib/tryResolve.ts +8 -0
  144. package/src/modules/availability/migrations/.snapshot-open-mercato.json +375 -0
  145. package/src/modules/availability/migrations/Migration20260922095844_availability.ts +13 -0
  146. package/src/modules/availability/setup.ts +10 -0
  147. package/src/modules/entities/cli.ts +53 -13
  148. package/src/modules/inbox_ops/api/settings/route.ts +9 -32
  149. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +2 -2
  150. package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +1 -1
  151. package/src/modules/inbox_ops/i18n/de.json +1 -1
  152. package/src/modules/inbox_ops/i18n/en.json +1 -1
  153. package/src/modules/inbox_ops/i18n/es.json +1 -1
  154. package/src/modules/inbox_ops/i18n/ko.json +1 -1
  155. package/src/modules/inbox_ops/i18n/pl.json +1 -1
  156. package/src/modules/inbox_ops/lib/ensure-settings.ts +77 -0
  157. package/src/modules/inbox_ops/lib/executionEngine.ts +1 -1
  158. package/src/modules/inbox_ops/lib/executionHelpers.ts +0 -27
  159. package/src/modules/inbox_ops/lib/payloadEnrichment.ts +1 -5
  160. package/src/modules/inbox_ops/setup.ts +2 -21
  161. package/src/modules/inbox_ops/subscribers/extractionWorker.ts +1 -1
  162. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +13 -0
  163. package/src/modules/sales/data/entities.ts +2 -0
  164. package/src/modules/sales/inbox-actions.ts +0 -20
  165. package/src/modules/sales/migrations/.snapshot-open-mercato.json +20 -0
  166. package/src/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.ts +74 -0
  167. package/src/modules/wms/di.ts +6 -0
  168. package/src/modules/wms/lib/availabilityCache.ts +136 -0
  169. package/src/modules/wms/lib/availabilityCalculation.ts +289 -0
  170. package/src/modules/wms/lib/availabilityProvider.ts +29 -0
  171. package/src/modules/wms/lib/sqlInClause.ts +21 -0
  172. package/src/modules/wms/lib/tryResolve.ts +8 -0
  173. package/src/modules/wms/subscribers/invalidate-availability-cache.ts +30 -0
@@ -0,0 +1,185 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
5
+ import { Input } from "@open-mercato/ui/primitives/input";
6
+ import { Label } from "@open-mercato/ui/primitives/label";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
9
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
10
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
11
+ const STATE_VARIANTS = {
12
+ in_stock: "success",
13
+ low_stock: "warning",
14
+ out_of_stock: "error",
15
+ backorder: "info",
16
+ preorder: "info",
17
+ not_tracked: "neutral"
18
+ };
19
+ const TRACE_FIELDS = [
20
+ "isStockManaged",
21
+ "allowBackorder",
22
+ "backorderLeadTimeDays",
23
+ "preorderReleaseAt",
24
+ "lowStockThreshold",
25
+ "minOrderQuantity",
26
+ "maxOrderQuantity",
27
+ "quantityIncrement",
28
+ "hideWhenOutOfStock",
29
+ "isActive"
30
+ ];
31
+ function formatTraceValue(value, t) {
32
+ if (value === null || value === void 0) return t("availability.policies.form.preview.notSet");
33
+ if (typeof value === "boolean") return value ? t("common.yes") : t("common.no");
34
+ return String(value);
35
+ }
36
+ function AvailabilityCheckPage() {
37
+ const t = useT();
38
+ const [productId, setProductId] = React.useState("");
39
+ const [variantId, setVariantId] = React.useState("");
40
+ const [storeId, setStoreId] = React.useState("");
41
+ const [quantity, setQuantity] = React.useState("1");
42
+ const [isRunning, setIsRunning] = React.useState(false);
43
+ const [error, setError] = React.useState(null);
44
+ const [result, setResult] = React.useState(null);
45
+ const runCheck = React.useCallback(async () => {
46
+ if (!productId.trim()) {
47
+ setError(t("availability.check.errors.productRequired"));
48
+ return;
49
+ }
50
+ setIsRunning(true);
51
+ setError(null);
52
+ setResult(null);
53
+ try {
54
+ const call = await apiCall(
55
+ "/api/availability/check",
56
+ {
57
+ method: "POST",
58
+ body: JSON.stringify({
59
+ productId: productId.trim(),
60
+ variantId: variantId.trim() || null,
61
+ storeId: storeId.trim() || null,
62
+ quantity: Number(quantity) > 0 ? Number(quantity) : 1
63
+ })
64
+ },
65
+ { fallback: null }
66
+ );
67
+ if (!call.ok) {
68
+ const errorPayload = call.result;
69
+ setError(typeof errorPayload?.error === "string" ? errorPayload.error : t("availability.check.errors.failed"));
70
+ return;
71
+ }
72
+ setResult(call.result ?? null);
73
+ } catch (e) {
74
+ setError(e instanceof Error ? e.message : t("availability.check.errors.failed"));
75
+ } finally {
76
+ setIsRunning(false);
77
+ }
78
+ }, [productId, variantId, storeId, quantity, t]);
79
+ const availability = result?.availability ?? null;
80
+ const policyTrace = result?.policyTrace ?? null;
81
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsxs(
82
+ "div",
83
+ {
84
+ className: "space-y-6",
85
+ onKeyDown: (event) => {
86
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
87
+ event.preventDefault();
88
+ void runCheck();
89
+ }
90
+ },
91
+ children: [
92
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: t("availability.check.title") }),
93
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 rounded-md border p-4 sm:grid-cols-4", children: [
94
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
95
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-check-product-id", children: t("availability.policies.form.field.productId") }),
96
+ /* @__PURE__ */ jsx(
97
+ Input,
98
+ {
99
+ id: "availability-check-product-id",
100
+ value: productId,
101
+ onChange: (e) => setProductId(e.target.value),
102
+ autoFocus: true
103
+ }
104
+ )
105
+ ] }),
106
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-check-variant-id", children: t("availability.policies.form.field.variantId") }),
108
+ /* @__PURE__ */ jsx(Input, { id: "availability-check-variant-id", value: variantId, onChange: (e) => setVariantId(e.target.value) })
109
+ ] }),
110
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
111
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-check-store-id", children: t("availability.policies.form.field.storeId") }),
112
+ /* @__PURE__ */ jsx(Input, { id: "availability-check-store-id", value: storeId, onChange: (e) => setStoreId(e.target.value) })
113
+ ] }),
114
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
115
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-check-quantity", children: t("availability.check.field.quantity") }),
116
+ /* @__PURE__ */ jsx(
117
+ Input,
118
+ {
119
+ id: "availability-check-quantity",
120
+ type: "number",
121
+ min: 1,
122
+ value: quantity,
123
+ onChange: (e) => setQuantity(e.target.value)
124
+ }
125
+ )
126
+ ] })
127
+ ] }),
128
+ /* @__PURE__ */ jsx(Button, { onClick: () => void runCheck(), disabled: isRunning, children: isRunning ? t("availability.check.action.running") : t("availability.check.action.run") }),
129
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-4 text-sm text-destructive", "data-testid": "availability-check-error", children: error }),
130
+ availability && /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-md border p-4", "data-testid": "availability-check-result", children: [
131
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
132
+ /* @__PURE__ */ jsx(StatusBadge, { variant: STATE_VARIANTS[availability.state], children: t(`availability.states.${availability.state}`) }),
133
+ !availability.isAuthoritative && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("availability.check.advisoryNote") })
134
+ ] }),
135
+ /* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-1 gap-2 sm:grid-cols-3", children: [
136
+ /* @__PURE__ */ jsxs("div", { children: [
137
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t("availability.check.result.availableQuantity") }),
138
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: availability.availableQuantity ?? t("availability.policies.form.preview.notSet") })
139
+ ] }),
140
+ /* @__PURE__ */ jsxs("div", { children: [
141
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t("availability.check.result.canFulfil") }),
142
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: availability.canFulfil ? t("common.yes") : t("common.no") })
143
+ ] }),
144
+ /* @__PURE__ */ jsxs("div", { children: [
145
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t("availability.check.result.policySourceId") }),
146
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: availability.policySourceId ?? t("availability.policies.form.preview.moduleDefault") })
147
+ ] }),
148
+ availability.leadTimeDays != null && /* @__PURE__ */ jsxs("div", { children: [
149
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t("availability.policies.form.field.backorderLeadTimeDays") }),
150
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: availability.leadTimeDays })
151
+ ] }),
152
+ availability.releaseAt && /* @__PURE__ */ jsxs("div", { children: [
153
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t("availability.policies.form.field.preorderReleaseAt") }),
154
+ /* @__PURE__ */ jsx("dd", { className: "text-sm", children: availability.releaseAt })
155
+ ] })
156
+ ] }),
157
+ availability.state === "not_tracked" && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("availability.check.notTrackedExplanation") })
158
+ ] }),
159
+ policyTrace && /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted p-4", "data-testid": "availability-check-policy-trace", children: [
160
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm font-medium", children: t("availability.policies.form.preview.title") }),
161
+ /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: TRACE_FIELDS.map((key) => {
162
+ const field = policyTrace[key];
163
+ const source = field?.policySourceId ?? t("availability.policies.form.preview.moduleDefault");
164
+ return /* @__PURE__ */ jsxs("div", { children: [
165
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t(`availability.policies.form.field.${key}`) }),
166
+ /* @__PURE__ */ jsxs("dd", { className: "text-xs", children: [
167
+ formatTraceValue(field?.value, t),
168
+ " ",
169
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
170
+ "(",
171
+ source,
172
+ ")"
173
+ ] })
174
+ ] })
175
+ ] }, key);
176
+ }) })
177
+ ] })
178
+ ]
179
+ }
180
+ ) }) });
181
+ }
182
+ export {
183
+ AvailabilityCheckPage as default
184
+ };
185
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/availability/backend/availability/check/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype AvailabilityState = 'in_stock' | 'low_stock' | 'out_of_stock' | 'backorder' | 'preorder' | 'not_tracked'\n\ntype AvailabilityItemResult = {\n state: AvailabilityState\n availableQuantity: number | null\n canFulfil: boolean\n leadTimeDays: number | null\n releaseAt: string | null\n isAuthoritative: boolean\n policySourceId: string | null\n}\n\ntype FieldTrace = { value: unknown; policySourceId: string | null }\ntype PolicyTrace = Record<string, FieldTrace>\n\ntype CheckResponse = {\n availability?: AvailabilityItemResult | null\n policyTrace?: PolicyTrace | null\n error?: string\n}\n\nconst STATE_VARIANTS: Record<AvailabilityState, StatusBadgeVariant> = {\n in_stock: 'success',\n low_stock: 'warning',\n out_of_stock: 'error',\n backorder: 'info',\n preorder: 'info',\n not_tracked: 'neutral',\n}\n\nconst TRACE_FIELDS = [\n 'isStockManaged',\n 'allowBackorder',\n 'backorderLeadTimeDays',\n 'preorderReleaseAt',\n 'lowStockThreshold',\n 'minOrderQuantity',\n 'maxOrderQuantity',\n 'quantityIncrement',\n 'hideWhenOutOfStock',\n 'isActive',\n]\n\nfunction formatTraceValue(value: unknown, t: (key: string) => string): string {\n if (value === null || value === undefined) return t('availability.policies.form.preview.notSet')\n if (typeof value === 'boolean') return value ? t('common.yes') : t('common.no')\n return String(value)\n}\n\nexport default function AvailabilityCheckPage() {\n const t = useT()\n const [productId, setProductId] = React.useState('')\n const [variantId, setVariantId] = React.useState('')\n const [storeId, setStoreId] = React.useState('')\n const [quantity, setQuantity] = React.useState('1')\n const [isRunning, setIsRunning] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [result, setResult] = React.useState<CheckResponse | null>(null)\n\n const runCheck = React.useCallback(async () => {\n if (!productId.trim()) {\n setError(t('availability.check.errors.productRequired'))\n return\n }\n setIsRunning(true)\n setError(null)\n setResult(null)\n try {\n const call = await apiCall<CheckResponse>(\n '/api/availability/check',\n {\n method: 'POST',\n body: JSON.stringify({\n productId: productId.trim(),\n variantId: variantId.trim() || null,\n storeId: storeId.trim() || null,\n quantity: Number(quantity) > 0 ? Number(quantity) : 1,\n }),\n },\n { fallback: null },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n setError(typeof errorPayload?.error === 'string' ? errorPayload.error : t('availability.check.errors.failed'))\n return\n }\n setResult(call.result ?? null)\n } catch (e) {\n setError(e instanceof Error ? e.message : t('availability.check.errors.failed'))\n } finally {\n setIsRunning(false)\n }\n }, [productId, variantId, storeId, quantity, t])\n\n const availability = result?.availability ?? null\n const policyTrace = result?.policyTrace ?? null\n\n return (\n <Page>\n <PageBody>\n <div\n className=\"space-y-6\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void runCheck()\n }\n }}\n >\n <h1 className=\"text-xl font-semibold\">{t('availability.check.title')}</h1>\n <div className=\"grid grid-cols-1 gap-4 rounded-md border p-4 sm:grid-cols-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-check-product-id\">{t('availability.policies.form.field.productId')}</Label>\n <Input\n id=\"availability-check-product-id\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n autoFocus\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-check-variant-id\">{t('availability.policies.form.field.variantId')}</Label>\n <Input id=\"availability-check-variant-id\" value={variantId} onChange={(e) => setVariantId(e.target.value)} />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-check-store-id\">{t('availability.policies.form.field.storeId')}</Label>\n <Input id=\"availability-check-store-id\" value={storeId} onChange={(e) => setStoreId(e.target.value)} />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-check-quantity\">{t('availability.check.field.quantity')}</Label>\n <Input\n id=\"availability-check-quantity\"\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => setQuantity(e.target.value)}\n />\n </div>\n </div>\n <Button onClick={() => void runCheck()} disabled={isRunning}>\n {isRunning ? t('availability.check.action.running') : t('availability.check.action.run')}\n </Button>\n\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-4 text-sm text-destructive\" data-testid=\"availability-check-error\">\n {error}\n </div>\n )}\n\n {availability && (\n <div className=\"space-y-4 rounded-md border p-4\" data-testid=\"availability-check-result\">\n <div className=\"flex items-center gap-3\">\n <StatusBadge variant={STATE_VARIANTS[availability.state]}>{t(`availability.states.${availability.state}`)}</StatusBadge>\n {!availability.isAuthoritative && (\n <span className=\"text-xs text-muted-foreground\">{t('availability.check.advisoryNote')}</span>\n )}\n </div>\n <dl className=\"grid grid-cols-1 gap-2 sm:grid-cols-3\">\n <div>\n <dt className=\"text-xs text-muted-foreground\">{t('availability.check.result.availableQuantity')}</dt>\n <dd className=\"text-sm\">{availability.availableQuantity ?? t('availability.policies.form.preview.notSet')}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-muted-foreground\">{t('availability.check.result.canFulfil')}</dt>\n <dd className=\"text-sm\">{availability.canFulfil ? t('common.yes') : t('common.no')}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-muted-foreground\">{t('availability.check.result.policySourceId')}</dt>\n <dd className=\"text-sm\">{availability.policySourceId ?? t('availability.policies.form.preview.moduleDefault')}</dd>\n </div>\n {availability.leadTimeDays != null && (\n <div>\n <dt className=\"text-xs text-muted-foreground\">{t('availability.policies.form.field.backorderLeadTimeDays')}</dt>\n <dd className=\"text-sm\">{availability.leadTimeDays}</dd>\n </div>\n )}\n {availability.releaseAt && (\n <div>\n <dt className=\"text-xs text-muted-foreground\">{t('availability.policies.form.field.preorderReleaseAt')}</dt>\n <dd className=\"text-sm\">{availability.releaseAt}</dd>\n </div>\n )}\n </dl>\n {availability.state === 'not_tracked' && (\n <p className=\"text-xs text-muted-foreground\">{t('availability.check.notTrackedExplanation')}</p>\n )}\n </div>\n )}\n\n {policyTrace && (\n <div className=\"rounded-md border bg-muted p-4\" data-testid=\"availability-check-policy-trace\">\n <div className=\"mb-2 text-sm font-medium\">{t('availability.policies.form.preview.title')}</div>\n <dl className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n {TRACE_FIELDS.map((key) => {\n const field = policyTrace[key]\n const source = field?.policySourceId ?? t('availability.policies.form.preview.moduleDefault')\n return (\n <div key={key}>\n <dt className=\"text-xs text-muted-foreground\">{t(`availability.policies.form.field.${key}`)}</dt>\n <dd className=\"text-xs\">\n {formatTraceValue(field?.value, t)} <span className=\"text-muted-foreground\">({source})</span>\n </dd>\n </div>\n )\n })}\n </dl>\n </div>\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ["availability.check"],
4
+ pageTitle: "Availability Check",
5
+ pageTitleKey: "availability.check.nav.title",
6
+ pageGroup: "Availability",
7
+ pageGroupKey: "availability.nav.group",
8
+ pageOrder: 20,
9
+ pageContext: "settings",
10
+ icon: "search",
11
+ breadcrumb: [{ label: "Availability Check", labelKey: "availability.check.nav.title" }]
12
+ };
13
+ export {
14
+ metadata
15
+ };
16
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/availability/backend/availability/check/page.meta.ts"],
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['availability.check'],\n pageTitle: 'Availability Check',\n pageTitleKey: 'availability.check.nav.title',\n pageGroup: 'Availability',\n pageGroupKey: 'availability.nav.group',\n pageOrder: 20,\n pageContext: 'settings' as const,\n icon: 'search',\n breadcrumb: [{ label: 'Availability Check', labelKey: 'availability.check.nav.title' }],\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,oBAAoB;AAAA,EACtC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,aAAa;AAAA,EACb,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,sBAAsB,UAAU,+BAA+B,CAAC;AACxF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ const TRACE_FIELDS = [
7
+ { key: "isStockManaged", labelKey: "availability.policies.form.field.isStockManaged" },
8
+ { key: "allowBackorder", labelKey: "availability.policies.form.field.allowBackorder" },
9
+ { key: "backorderLeadTimeDays", labelKey: "availability.policies.form.field.backorderLeadTimeDays" },
10
+ { key: "preorderReleaseAt", labelKey: "availability.policies.form.field.preorderReleaseAt" },
11
+ { key: "lowStockThreshold", labelKey: "availability.policies.form.field.lowStockThreshold" },
12
+ { key: "minOrderQuantity", labelKey: "availability.policies.form.field.minOrderQuantity" },
13
+ { key: "maxOrderQuantity", labelKey: "availability.policies.form.field.maxOrderQuantity" },
14
+ { key: "quantityIncrement", labelKey: "availability.policies.form.field.quantityIncrement" },
15
+ { key: "hideWhenOutOfStock", labelKey: "availability.policies.form.field.hideWhenOutOfStock" },
16
+ { key: "isActive", labelKey: "availability.policies.form.field.isActive" }
17
+ ];
18
+ function formatValue(value, t) {
19
+ if (value === null || value === void 0) return t("availability.policies.form.preview.notSet");
20
+ if (typeof value === "boolean") return value ? t("common.yes") : t("common.no");
21
+ return String(value);
22
+ }
23
+ function ResolutionPreviewPanel({
24
+ productId,
25
+ variantId,
26
+ storeId
27
+ }) {
28
+ const t = useT();
29
+ const [trace, setTrace] = React.useState(null);
30
+ const [isLoading, setIsLoading] = React.useState(false);
31
+ React.useEffect(() => {
32
+ if (!productId) {
33
+ setTrace(null);
34
+ return;
35
+ }
36
+ let cancelled = false;
37
+ const handle = setTimeout(() => {
38
+ setIsLoading(true);
39
+ const params = new URLSearchParams({ productId });
40
+ if (variantId) params.set("variantId", variantId);
41
+ if (storeId) params.set("storeId", storeId);
42
+ apiCall(`/api/availability/policies/resolve-preview?${params.toString()}`, void 0, { fallback: null }).then((call) => {
43
+ if (!cancelled && call.ok) setTrace(call.result?.policyTrace ?? null);
44
+ }).finally(() => {
45
+ if (!cancelled) setIsLoading(false);
46
+ });
47
+ }, 300);
48
+ return () => {
49
+ cancelled = true;
50
+ clearTimeout(handle);
51
+ };
52
+ }, [productId, variantId, storeId]);
53
+ if (!productId) {
54
+ return /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-muted p-4 text-sm text-muted-foreground", "data-testid": "availability-resolution-preview", children: t("availability.policies.form.preview.enterProduct") });
55
+ }
56
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted p-4 text-sm", "data-testid": "availability-resolution-preview", children: [
57
+ /* @__PURE__ */ jsx("div", { className: "mb-2 font-medium", children: t("availability.policies.form.preview.title") }),
58
+ isLoading && !trace ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: t("common.loading") }) : /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: TRACE_FIELDS.map(({ key, labelKey }) => {
59
+ const field = trace?.[key];
60
+ const source = field?.policySourceId ?? t("availability.policies.form.preview.moduleDefault");
61
+ return /* @__PURE__ */ jsxs("div", { children: [
62
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: t(labelKey) }),
63
+ /* @__PURE__ */ jsxs("dd", { className: "text-xs", children: [
64
+ formatValue(field?.value, t),
65
+ " ",
66
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
67
+ "(",
68
+ source,
69
+ ")"
70
+ ] })
71
+ ] })
72
+ ] }, key);
73
+ }) })
74
+ ] });
75
+ }
76
+ export {
77
+ ResolutionPreviewPanel
78
+ };
79
+ //# sourceMappingURL=ResolutionPreviewPanel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype FieldTrace = { value: unknown; policySourceId: string | null }\ntype PolicyTrace = Record<string, FieldTrace>\n\ntype ResponsePayload = { policyTrace?: PolicyTrace }\n\nconst TRACE_FIELDS: Array<{ key: string; labelKey: string }> = [\n { key: 'isStockManaged', labelKey: 'availability.policies.form.field.isStockManaged' },\n { key: 'allowBackorder', labelKey: 'availability.policies.form.field.allowBackorder' },\n { key: 'backorderLeadTimeDays', labelKey: 'availability.policies.form.field.backorderLeadTimeDays' },\n { key: 'preorderReleaseAt', labelKey: 'availability.policies.form.field.preorderReleaseAt' },\n { key: 'lowStockThreshold', labelKey: 'availability.policies.form.field.lowStockThreshold' },\n { key: 'minOrderQuantity', labelKey: 'availability.policies.form.field.minOrderQuantity' },\n { key: 'maxOrderQuantity', labelKey: 'availability.policies.form.field.maxOrderQuantity' },\n { key: 'quantityIncrement', labelKey: 'availability.policies.form.field.quantityIncrement' },\n { key: 'hideWhenOutOfStock', labelKey: 'availability.policies.form.field.hideWhenOutOfStock' },\n { key: 'isActive', labelKey: 'availability.policies.form.field.isActive' },\n]\n\nfunction formatValue(value: unknown, t: (key: string) => string): string {\n if (value === null || value === undefined) return t('availability.policies.form.preview.notSet')\n if (typeof value === 'boolean') return value ? t('common.yes') : t('common.no')\n return String(value)\n}\n\n/**\n * Live \"resolution-chain preview\" \u2014 US-A2. Shows the currently PERSISTED\n * resolution for the (product, variant, store) target the admin is editing,\n * refetched (debounced) whenever the target changes. Does not simulate the\n * in-progress, unsaved field edits \u2014 see PLAN.md \u00A7 Key design decisions.\n */\nexport function ResolutionPreviewPanel({\n productId,\n variantId,\n storeId,\n}: {\n productId: string\n variantId: string | null\n storeId: string | null\n}) {\n const t = useT()\n const [trace, setTrace] = React.useState<PolicyTrace | null>(null)\n const [isLoading, setIsLoading] = React.useState(false)\n\n React.useEffect(() => {\n if (!productId) {\n setTrace(null)\n return\n }\n let cancelled = false\n const handle = setTimeout(() => {\n setIsLoading(true)\n const params = new URLSearchParams({ productId })\n if (variantId) params.set('variantId', variantId)\n if (storeId) params.set('storeId', storeId)\n apiCall<ResponsePayload>(`/api/availability/policies/resolve-preview?${params.toString()}`, undefined, { fallback: null })\n .then((call) => {\n if (!cancelled && call.ok) setTrace(call.result?.policyTrace ?? null)\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false)\n })\n }, 300)\n return () => {\n cancelled = true\n clearTimeout(handle)\n }\n }, [productId, variantId, storeId])\n\n if (!productId) {\n return (\n <div className=\"rounded-md border bg-muted p-4 text-sm text-muted-foreground\" data-testid=\"availability-resolution-preview\">\n {t('availability.policies.form.preview.enterProduct')}\n </div>\n )\n }\n\n return (\n <div className=\"rounded-md border bg-muted p-4 text-sm\" data-testid=\"availability-resolution-preview\">\n <div className=\"mb-2 font-medium\">{t('availability.policies.form.preview.title')}</div>\n {isLoading && !trace ? (\n <div className=\"text-muted-foreground\">{t('common.loading')}</div>\n ) : (\n <dl className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n {TRACE_FIELDS.map(({ key, labelKey }) => {\n const field = trace?.[key]\n const source = field?.policySourceId ?? t('availability.policies.form.preview.moduleDefault')\n return (\n <div key={key}>\n <dt className=\"text-xs text-muted-foreground\">{t(labelKey)}</dt>\n <dd className=\"text-xs\">\n {formatValue(field?.value, t)}{' '}\n <span className=\"text-muted-foreground\">({source})</span>\n </dd>\n </div>\n )\n })}\n </dl>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Input } from "@open-mercato/ui/primitives/input";
4
+ import { Label } from "@open-mercato/ui/primitives/label";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ function ScopeFields({ value, onChange, disabled }) {
7
+ const t = useT();
8
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 rounded-md border p-4 sm:grid-cols-3", children: [
9
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
10
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-policy-product-id", children: t("availability.policies.form.field.productId") }),
11
+ /* @__PURE__ */ jsx(
12
+ Input,
13
+ {
14
+ id: "availability-policy-product-id",
15
+ value: value.productId,
16
+ disabled,
17
+ onChange: (e) => onChange({ ...value, productId: e.target.value.trim() }),
18
+ placeholder: t("availability.policies.form.field.productId.placeholder")
19
+ }
20
+ )
21
+ ] }),
22
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
23
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-policy-variant-id", children: t("availability.policies.form.field.variantId") }),
24
+ /* @__PURE__ */ jsx(
25
+ Input,
26
+ {
27
+ id: "availability-policy-variant-id",
28
+ value: value.variantId,
29
+ disabled,
30
+ onChange: (e) => onChange({ ...value, variantId: e.target.value.trim() }),
31
+ placeholder: t("availability.policies.form.field.variantId.placeholder")
32
+ }
33
+ )
34
+ ] }),
35
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
36
+ /* @__PURE__ */ jsx(Label, { htmlFor: "availability-policy-store-id", children: t("availability.policies.form.field.storeId") }),
37
+ /* @__PURE__ */ jsx(
38
+ Input,
39
+ {
40
+ id: "availability-policy-store-id",
41
+ value: value.storeId,
42
+ disabled,
43
+ onChange: (e) => onChange({ ...value, storeId: e.target.value.trim() }),
44
+ placeholder: t("availability.policies.form.field.storeId.placeholder")
45
+ }
46
+ )
47
+ ] })
48
+ ] });
49
+ }
50
+ export {
51
+ ScopeFields
52
+ };
53
+ //# sourceMappingURL=ScopeFields.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/availability/backend/availability/policies/ScopeFields.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type PolicyScope = {\n productId: string\n variantId: string\n storeId: string\n}\n\n/**\n * Product/variant/store target fields, kept as controlled local state\n * (outside `CrudForm`'s own field list) so a value change can drive the live\n * resolution-chain preview immediately \u2014 `CrudForm` does not expose a\n * live-values callback. Rendered via `CrudForm`'s `contentHeader` slot.\n */\nexport function ScopeFields({ value, onChange, disabled }: { value: PolicyScope; onChange: (next: PolicyScope) => void; disabled?: boolean }) {\n const t = useT()\n return (\n <div className=\"grid grid-cols-1 gap-4 rounded-md border p-4 sm:grid-cols-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-policy-product-id\">{t('availability.policies.form.field.productId')}</Label>\n <Input\n id=\"availability-policy-product-id\"\n value={value.productId}\n disabled={disabled}\n onChange={(e) => onChange({ ...value, productId: e.target.value.trim() })}\n placeholder={t('availability.policies.form.field.productId.placeholder')}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-policy-variant-id\">{t('availability.policies.form.field.variantId')}</Label>\n <Input\n id=\"availability-policy-variant-id\"\n value={value.variantId}\n disabled={disabled}\n onChange={(e) => onChange({ ...value, variantId: e.target.value.trim() })}\n placeholder={t('availability.policies.form.field.variantId.placeholder')}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"availability-policy-store-id\">{t('availability.policies.form.field.storeId')}</Label>\n <Input\n id=\"availability-policy-store-id\"\n value={value.storeId}\n disabled={disabled}\n onChange={(e) => onChange({ ...value, storeId: e.target.value.trim() })}\n placeholder={t('availability.policies.form.field.storeId.placeholder')}\n />\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAsBM,SACE,KADF;AApBN,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,YAAY;AAcd,SAAS,YAAY,EAAE,OAAO,UAAU,SAAS,GAAsF;AAC5I,QAAM,IAAI,KAAK;AACf,SACE,qBAAC,SAAI,WAAU,+DACb;AAAA,yBAAC,SAAI,WAAU,eACb;AAAA,0BAAC,SAAM,SAAQ,kCAAkC,YAAE,4CAA4C,GAAE;AAAA,MACjG;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,MAAM;AAAA,UACb;AAAA,UACA,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,WAAW,EAAE,OAAO,MAAM,KAAK,EAAE,CAAC;AAAA,UACxE,aAAa,EAAE,wDAAwD;AAAA;AAAA,MACzE;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,eACb;AAAA,0BAAC,SAAM,SAAQ,kCAAkC,YAAE,4CAA4C,GAAE;AAAA,MACjG;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,MAAM;AAAA,UACb;AAAA,UACA,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,WAAW,EAAE,OAAO,MAAM,KAAK,EAAE,CAAC;AAAA,UACxE,aAAa,EAAE,wDAAwD;AAAA;AAAA,MACzE;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,eACb;AAAA,0BAAC,SAAM,SAAQ,gCAAgC,YAAE,0CAA0C,GAAE;AAAA,MAC7F;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,MAAM;AAAA,UACb;AAAA,UACA,UAAU,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,SAAS,EAAE,OAAO,MAAM,KAAK,EAAE,CAAC;AAAA,UACtE,aAAa,EAAE,sDAAsD;AAAA;AAAA,MACvE;AAAA,OACF;AAAA,KACF;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,152 @@
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 { CrudForm } from "@open-mercato/ui/backend/CrudForm";
6
+ import { updateCrud, deleteCrud } from "@open-mercato/ui/backend/utils/crud";
7
+ import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
8
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
9
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
10
+ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
11
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
12
+ import { useBackendChrome } from "@open-mercato/ui/backend/BackendChromeProvider";
13
+ import { hasFeature } from "@open-mercato/shared/security/features";
14
+ import { ScopeFields } from "../ScopeFields.js";
15
+ import { ResolutionPreviewPanel } from "../ResolutionPreviewPanel.js";
16
+ import { buildPolicyFieldGroups } from "../formGroups.js";
17
+ function toNullableInt(value) {
18
+ if (value === null || value === void 0 || value === "") return null;
19
+ const parsed = typeof value === "number" ? value : parseInt(value, 10);
20
+ return Number.isFinite(parsed) ? parsed : null;
21
+ }
22
+ function AvailabilityPolicyEditPage({ params }) {
23
+ const t = useT();
24
+ const router = useRouter();
25
+ const id = params?.id ?? "";
26
+ const { payload } = useBackendChrome();
27
+ const canManage = hasFeature(payload?.grantedFeatures, "availability.policies.manage");
28
+ const [policy, setPolicy] = React.useState(null);
29
+ const [isLoading, setIsLoading] = React.useState(true);
30
+ const [error, setError] = React.useState(null);
31
+ const [scope, setScope] = React.useState({ productId: "", variantId: "", storeId: "" });
32
+ React.useEffect(() => {
33
+ if (!id) return;
34
+ let cancelled = false;
35
+ async function load() {
36
+ setIsLoading(true);
37
+ setError(null);
38
+ try {
39
+ const call = await apiCall(`/api/availability/policies?id=${encodeURIComponent(id)}`, void 0, {
40
+ fallback: { items: [] }
41
+ });
42
+ if (cancelled) return;
43
+ const record = call.ok ? call.result?.items?.[0] ?? null : null;
44
+ if (!record) {
45
+ setError(t("availability.policies.edit.notFound"));
46
+ return;
47
+ }
48
+ setPolicy(record);
49
+ setScope({ productId: record.productId ?? "", variantId: record.variantId ?? "", storeId: record.storeId ?? "" });
50
+ } catch (e) {
51
+ if (!cancelled) setError(e instanceof Error ? e.message : t("availability.policies.list.error.loadFailed"));
52
+ } finally {
53
+ if (!cancelled) setIsLoading(false);
54
+ }
55
+ }
56
+ load();
57
+ return () => {
58
+ cancelled = true;
59
+ };
60
+ }, [id, t]);
61
+ const groups = React.useMemo(() => buildPolicyFieldGroups(t), [t]);
62
+ if (isLoading) return /* @__PURE__ */ jsx(LoadingMessage, { label: t("common.loading") });
63
+ if (error || !policy) return /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("availability.policies.edit.notFound") });
64
+ return /* @__PURE__ */ jsx(
65
+ CrudForm,
66
+ {
67
+ title: t("availability.policies.edit.title"),
68
+ titleHeadingLevel: 1,
69
+ backHref: "/backend/availability/policies",
70
+ fields: [],
71
+ groups,
72
+ readOnly: !canManage,
73
+ submitLabel: t("availability.policies.form.action.save"),
74
+ cancelHref: "/backend/availability/policies",
75
+ optimisticLockUpdatedAt: policy.updatedAt ?? null,
76
+ initialValues: {
77
+ isStockManaged: policy.isStockManaged,
78
+ allowBackorder: policy.allowBackorder,
79
+ backorderLeadTimeDays: policy.backorderLeadTimeDays,
80
+ preorderReleaseAt: policy.preorderReleaseAt,
81
+ lowStockThreshold: policy.lowStockThreshold,
82
+ minOrderQuantity: policy.minOrderQuantity,
83
+ maxOrderQuantity: policy.maxOrderQuantity,
84
+ quantityIncrement: policy.quantityIncrement,
85
+ hideWhenOutOfStock: policy.hideWhenOutOfStock,
86
+ isActive: policy.isActive
87
+ },
88
+ contentHeader: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
89
+ /* @__PURE__ */ jsx(ScopeFields, { value: scope, onChange: setScope, disabled: !canManage }),
90
+ /* @__PURE__ */ jsx(
91
+ ResolutionPreviewPanel,
92
+ {
93
+ productId: scope.productId,
94
+ variantId: scope.variantId || null,
95
+ storeId: scope.storeId || null
96
+ }
97
+ )
98
+ ] }),
99
+ deleteVisible: canManage,
100
+ onDelete: async () => {
101
+ await deleteCrud("availability/policies", id);
102
+ flash(t("availability.policies.flash.deleted"), "success");
103
+ router.push("/backend/availability/policies");
104
+ },
105
+ onSubmit: async (values) => {
106
+ if (scope.variantId && !scope.productId) {
107
+ throw createCrudFormError(
108
+ t("availability.policies.errors.variantRequiresProduct"),
109
+ { productId: t("availability.policies.errors.variantRequiresProduct") }
110
+ );
111
+ }
112
+ if (values.allowBackorder && toNullableInt(values.backorderLeadTimeDays) == null) {
113
+ throw createCrudFormError(
114
+ t("availability.policies.errors.backorderRequiresLeadTime"),
115
+ { backorderLeadTimeDays: t("availability.policies.errors.backorderRequiresLeadTime") }
116
+ );
117
+ }
118
+ const min = toNullableInt(values.minOrderQuantity);
119
+ const max = toNullableInt(values.maxOrderQuantity);
120
+ if (min != null && max != null && max < min) {
121
+ throw createCrudFormError(
122
+ t("availability.policies.errors.maxBelowMin"),
123
+ { maxOrderQuantity: t("availability.policies.errors.maxBelowMin") }
124
+ );
125
+ }
126
+ const payloadBody = {
127
+ id,
128
+ productId: scope.productId || null,
129
+ variantId: scope.variantId || null,
130
+ storeId: scope.storeId || null,
131
+ isStockManaged: !!values.isStockManaged,
132
+ allowBackorder: !!values.allowBackorder,
133
+ backorderLeadTimeDays: toNullableInt(values.backorderLeadTimeDays),
134
+ preorderReleaseAt: values.preorderReleaseAt || null,
135
+ lowStockThreshold: toNullableInt(values.lowStockThreshold),
136
+ minOrderQuantity: min,
137
+ maxOrderQuantity: max,
138
+ quantityIncrement: toNullableInt(values.quantityIncrement),
139
+ hideWhenOutOfStock: !!values.hideWhenOutOfStock,
140
+ isActive: values.isActive !== false
141
+ };
142
+ await updateCrud("availability/policies", payloadBody);
143
+ flash(t("availability.policies.flash.updated"), "success");
144
+ router.push("/backend/availability/policies");
145
+ }
146
+ }
147
+ );
148
+ }
149
+ export {
150
+ AvailabilityPolicyEditPage as default
151
+ };
152
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/availability/backend/availability/policies/%5Bid%5D/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { ScopeFields, type PolicyScope } from '../ScopeFields'\nimport { ResolutionPreviewPanel } from '../ResolutionPreviewPanel'\nimport { buildPolicyFieldGroups } from '../formGroups'\n\ntype PolicyRecord = {\n id: string\n storeId: string | null\n productId: string | null\n variantId: string | null\n isStockManaged: boolean\n allowBackorder: boolean\n backorderLeadTimeDays: number | null\n preorderReleaseAt: string | null\n lowStockThreshold: number | null\n minOrderQuantity: number | null\n maxOrderQuantity: number | null\n quantityIncrement: number | null\n hideWhenOutOfStock: boolean\n isActive: boolean\n updatedAt: string | null\n}\n\ntype FormValues = {\n isStockManaged?: boolean\n allowBackorder?: boolean\n backorderLeadTimeDays?: number | string | null\n preorderReleaseAt?: string | null\n lowStockThreshold?: number | string | null\n minOrderQuantity?: number | string | null\n maxOrderQuantity?: number | string | null\n quantityIncrement?: number | string | null\n hideWhenOutOfStock?: boolean\n isActive?: boolean\n}\n\nfunction toNullableInt(value: number | string | null | undefined): number | null {\n if (value === null || value === undefined || value === '') return null\n const parsed = typeof value === 'number' ? value : parseInt(value, 10)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nexport default function AvailabilityPolicyEditPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const id = params?.id ?? ''\n const { payload } = useBackendChrome()\n const canManage = hasFeature(payload?.grantedFeatures, 'availability.policies.manage')\n\n const [policy, setPolicy] = React.useState<PolicyRecord | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [scope, setScope] = React.useState<PolicyScope>({ productId: '', variantId: '', storeId: '' })\n\n React.useEffect(() => {\n if (!id) return\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n try {\n const call = await apiCall<{ items: PolicyRecord[] }>(`/api/availability/policies?id=${encodeURIComponent(id)}`, undefined, {\n fallback: { items: [] },\n })\n if (cancelled) return\n const record = call.ok ? call.result?.items?.[0] ?? null : null\n if (!record) {\n setError(t('availability.policies.edit.notFound'))\n return\n }\n setPolicy(record)\n setScope({ productId: record.productId ?? '', variantId: record.variantId ?? '', storeId: record.storeId ?? '' })\n } catch (e) {\n if (!cancelled) setError(e instanceof Error ? e.message : t('availability.policies.list.error.loadFailed'))\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n const groups = React.useMemo(() => buildPolicyFieldGroups(t), [t])\n\n if (isLoading) return <LoadingMessage label={t('common.loading')} />\n if (error || !policy) return <ErrorMessage label={error ?? t('availability.policies.edit.notFound')} />\n\n return (\n <CrudForm<FormValues>\n title={t('availability.policies.edit.title')}\n titleHeadingLevel={1}\n backHref=\"/backend/availability/policies\"\n fields={[]}\n groups={groups}\n readOnly={!canManage}\n submitLabel={t('availability.policies.form.action.save')}\n cancelHref=\"/backend/availability/policies\"\n optimisticLockUpdatedAt={policy.updatedAt ?? null}\n initialValues={{\n isStockManaged: policy.isStockManaged,\n allowBackorder: policy.allowBackorder,\n backorderLeadTimeDays: policy.backorderLeadTimeDays,\n preorderReleaseAt: policy.preorderReleaseAt,\n lowStockThreshold: policy.lowStockThreshold,\n minOrderQuantity: policy.minOrderQuantity,\n maxOrderQuantity: policy.maxOrderQuantity,\n quantityIncrement: policy.quantityIncrement,\n hideWhenOutOfStock: policy.hideWhenOutOfStock,\n isActive: policy.isActive,\n }}\n contentHeader={(\n <div className=\"space-y-4\">\n <ScopeFields value={scope} onChange={setScope} disabled={!canManage} />\n <ResolutionPreviewPanel\n productId={scope.productId}\n variantId={scope.variantId || null}\n storeId={scope.storeId || null}\n />\n </div>\n )}\n deleteVisible={canManage}\n onDelete={async () => {\n await deleteCrud('availability/policies', id)\n flash(t('availability.policies.flash.deleted'), 'success')\n router.push('/backend/availability/policies')\n }}\n onSubmit={async (values) => {\n if (scope.variantId && !scope.productId) {\n throw createCrudFormError(\n t('availability.policies.errors.variantRequiresProduct'),\n { productId: t('availability.policies.errors.variantRequiresProduct') },\n )\n }\n if (values.allowBackorder && toNullableInt(values.backorderLeadTimeDays) == null) {\n throw createCrudFormError(\n t('availability.policies.errors.backorderRequiresLeadTime'),\n { backorderLeadTimeDays: t('availability.policies.errors.backorderRequiresLeadTime') },\n )\n }\n const min = toNullableInt(values.minOrderQuantity)\n const max = toNullableInt(values.maxOrderQuantity)\n if (min != null && max != null && max < min) {\n throw createCrudFormError(\n t('availability.policies.errors.maxBelowMin'),\n { maxOrderQuantity: t('availability.policies.errors.maxBelowMin') },\n )\n }\n\n const payloadBody = {\n id,\n productId: scope.productId || null,\n variantId: scope.variantId || null,\n storeId: scope.storeId || null,\n isStockManaged: !!values.isStockManaged,\n allowBackorder: !!values.allowBackorder,\n backorderLeadTimeDays: toNullableInt(values.backorderLeadTimeDays),\n preorderReleaseAt: values.preorderReleaseAt || null,\n lowStockThreshold: toNullableInt(values.lowStockThreshold),\n minOrderQuantity: min,\n maxOrderQuantity: max,\n quantityIncrement: toNullableInt(values.quantityIncrement),\n hideWhenOutOfStock: !!values.hideWhenOutOfStock,\n isActive: values.isActive !== false,\n }\n await updateCrud('availability/policies', payloadBody)\n flash(t('availability.policies.flash.updated'), 'success')\n router.push('/backend/availability/policies')\n }}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ["availability.policies.view"],
4
+ breadcrumb: [
5
+ { label: "Availability Policies", labelKey: "availability.policies.nav.title", href: "/backend/availability/policies" },
6
+ { label: "Edit", labelKey: "availability.policies.edit.title" }
7
+ ]
8
+ };
9
+ export {
10
+ metadata
11
+ };
12
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/availability/backend/availability/policies/%5Bid%5D/page.meta.ts"],
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['availability.policies.view'],\n breadcrumb: [\n { label: 'Availability Policies', labelKey: 'availability.policies.nav.title', href: '/backend/availability/policies' },\n { label: 'Edit', labelKey: 'availability.policies.edit.title' },\n ],\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,4BAA4B;AAAA,EAC9C,YAAY;AAAA,IACV,EAAE,OAAO,yBAAyB,UAAU,mCAAmC,MAAM,iCAAiC;AAAA,IACtH,EAAE,OAAO,QAAQ,UAAU,mCAAmC;AAAA,EAChE;AACF;",
6
+ "names": []
7
+ }