@open-mercato/core 0.8.1-develop.7266.1.8e520bbe03 → 0.8.1-develop.7268.1.033c0546d5

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 (128) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities.ids.generated.js +0 -4
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +0 -21
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/customers/backend/customers/companies/create/page.js +5 -0
  7. package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
  8. package/dist/modules/wms/di.js +0 -3
  9. package/dist/modules/wms/di.js.map +2 -2
  10. package/generated/entities.ids.generated.ts +0 -4
  11. package/generated/entity-fields-registry.ts +0 -21
  12. package/package.json +7 -7
  13. package/src/modules/auth/i18n/de.json +0 -3
  14. package/src/modules/auth/i18n/en.json +0 -3
  15. package/src/modules/auth/i18n/es.json +0 -3
  16. package/src/modules/auth/i18n/ko.json +0 -3
  17. package/src/modules/auth/i18n/pl.json +0 -3
  18. package/src/modules/customer_accounts/AGENTS.md +1 -1
  19. package/src/modules/customers/backend/customers/companies/create/page.tsx +6 -0
  20. package/src/modules/wms/di.ts +0 -6
  21. package/dist/generated/entities/availability_policy/index.js +0 -41
  22. package/dist/generated/entities/availability_policy/index.js.map +0 -7
  23. package/dist/modules/availability/acl.js +0 -16
  24. package/dist/modules/availability/acl.js.map +0 -7
  25. package/dist/modules/availability/api/check/route.js +0 -95
  26. package/dist/modules/availability/api/check/route.js.map +0 -7
  27. package/dist/modules/availability/api/openapi.js +0 -27
  28. package/dist/modules/availability/api/openapi.js.map +0 -7
  29. package/dist/modules/availability/api/policies/resolve-preview/route.js +0 -58
  30. package/dist/modules/availability/api/policies/resolve-preview/route.js.map +0 -7
  31. package/dist/modules/availability/api/policies/route.js +0 -188
  32. package/dist/modules/availability/api/policies/route.js.map +0 -7
  33. package/dist/modules/availability/backend/availability/check/page.js +0 -185
  34. package/dist/modules/availability/backend/availability/check/page.js.map +0 -7
  35. package/dist/modules/availability/backend/availability/check/page.meta.js +0 -16
  36. package/dist/modules/availability/backend/availability/check/page.meta.js.map +0 -7
  37. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +0 -79
  38. package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +0 -7
  39. package/dist/modules/availability/backend/availability/policies/ScopeFields.js +0 -53
  40. package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +0 -7
  41. package/dist/modules/availability/backend/availability/policies/[id]/page.js +0 -152
  42. package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +0 -7
  43. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +0 -12
  44. package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +0 -7
  45. package/dist/modules/availability/backend/availability/policies/create/page.js +0 -94
  46. package/dist/modules/availability/backend/availability/policies/create/page.js.map +0 -7
  47. package/dist/modules/availability/backend/availability/policies/create/page.meta.js +0 -12
  48. package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +0 -7
  49. package/dist/modules/availability/backend/availability/policies/formGroups.js +0 -75
  50. package/dist/modules/availability/backend/availability/policies/formGroups.js.map +0 -7
  51. package/dist/modules/availability/backend/availability/policies/page.js +0 -176
  52. package/dist/modules/availability/backend/availability/policies/page.js.map +0 -7
  53. package/dist/modules/availability/backend/availability/policies/page.meta.js +0 -16
  54. package/dist/modules/availability/backend/availability/policies/page.meta.js.map +0 -7
  55. package/dist/modules/availability/commands/policies.js +0 -312
  56. package/dist/modules/availability/commands/policies.js.map +0 -7
  57. package/dist/modules/availability/commands/scope.js +0 -19
  58. package/dist/modules/availability/commands/scope.js.map +0 -7
  59. package/dist/modules/availability/data/entities.js +0 -95
  60. package/dist/modules/availability/data/entities.js.map +0 -7
  61. package/dist/modules/availability/data/validators.js +0 -53
  62. package/dist/modules/availability/data/validators.js.map +0 -7
  63. package/dist/modules/availability/di.js +0 -41
  64. package/dist/modules/availability/di.js.map +0 -7
  65. package/dist/modules/availability/events.js +0 -18
  66. package/dist/modules/availability/events.js.map +0 -7
  67. package/dist/modules/availability/index.js +0 -15
  68. package/dist/modules/availability/index.js.map +0 -7
  69. package/dist/modules/availability/lib/policyResolution.js +0 -155
  70. package/dist/modules/availability/lib/policyResolution.js.map +0 -7
  71. package/dist/modules/availability/lib/tryResolve.js +0 -11
  72. package/dist/modules/availability/lib/tryResolve.js.map +0 -7
  73. package/dist/modules/availability/migrations/Migration20260922095844_availability.js +0 -16
  74. package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +0 -7
  75. package/dist/modules/availability/setup.js +0 -12
  76. package/dist/modules/availability/setup.js.map +0 -7
  77. package/dist/modules/wms/lib/availabilityCache.js +0 -85
  78. package/dist/modules/wms/lib/availabilityCache.js.map +0 -7
  79. package/dist/modules/wms/lib/availabilityCalculation.js +0 -178
  80. package/dist/modules/wms/lib/availabilityCalculation.js.map +0 -7
  81. package/dist/modules/wms/lib/availabilityProvider.js +0 -14
  82. package/dist/modules/wms/lib/availabilityProvider.js.map +0 -7
  83. package/dist/modules/wms/lib/sqlInClause.js +0 -9
  84. package/dist/modules/wms/lib/sqlInClause.js.map +0 -7
  85. package/dist/modules/wms/lib/tryResolve.js +0 -11
  86. package/dist/modules/wms/lib/tryResolve.js.map +0 -7
  87. package/dist/modules/wms/subscribers/invalidate-availability-cache.js +0 -16
  88. package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +0 -7
  89. package/generated/entities/availability_policy/index.ts +0 -19
  90. package/src/modules/availability/acl.ts +0 -12
  91. package/src/modules/availability/api/check/route.ts +0 -114
  92. package/src/modules/availability/api/openapi.ts +0 -28
  93. package/src/modules/availability/api/policies/resolve-preview/route.ts +0 -67
  94. package/src/modules/availability/api/policies/route.ts +0 -224
  95. package/src/modules/availability/backend/availability/check/page.meta.ts +0 -12
  96. package/src/modules/availability/backend/availability/check/page.tsx +0 -223
  97. package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +0 -106
  98. package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +0 -55
  99. package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +0 -8
  100. package/src/modules/availability/backend/availability/policies/[id]/page.tsx +0 -182
  101. package/src/modules/availability/backend/availability/policies/create/page.meta.ts +0 -8
  102. package/src/modules/availability/backend/availability/policies/create/page.tsx +0 -105
  103. package/src/modules/availability/backend/availability/policies/formGroups.ts +0 -77
  104. package/src/modules/availability/backend/availability/policies/page.meta.ts +0 -12
  105. package/src/modules/availability/backend/availability/policies/page.tsx +0 -196
  106. package/src/modules/availability/commands/policies.ts +0 -358
  107. package/src/modules/availability/commands/scope.ts +0 -26
  108. package/src/modules/availability/data/entities.ts +0 -92
  109. package/src/modules/availability/data/validators.ts +0 -70
  110. package/src/modules/availability/di.ts +0 -54
  111. package/src/modules/availability/events.ts +0 -17
  112. package/src/modules/availability/i18n/de.json +0 -81
  113. package/src/modules/availability/i18n/en.json +0 -81
  114. package/src/modules/availability/i18n/es.json +0 -81
  115. package/src/modules/availability/i18n/ko.json +0 -81
  116. package/src/modules/availability/i18n/pl.json +0 -81
  117. package/src/modules/availability/index.ts +0 -13
  118. package/src/modules/availability/lib/policyResolution.ts +0 -264
  119. package/src/modules/availability/lib/tryResolve.ts +0 -8
  120. package/src/modules/availability/migrations/.snapshot-open-mercato.json +0 -375
  121. package/src/modules/availability/migrations/Migration20260922095844_availability.ts +0 -13
  122. package/src/modules/availability/setup.ts +0 -10
  123. package/src/modules/wms/lib/availabilityCache.ts +0 -136
  124. package/src/modules/wms/lib/availabilityCalculation.ts +0 -289
  125. package/src/modules/wms/lib/availabilityProvider.ts +0 -29
  126. package/src/modules/wms/lib/sqlInClause.ts +0 -21
  127. package/src/modules/wms/lib/tryResolve.ts +0 -8
  128. package/src/modules/wms/subscribers/invalidate-availability-cache.ts +0 -30
@@ -1,178 +0,0 @@
1
- import { availabilityItemKey } from "@open-mercato/shared/lib/availability";
2
- import { tryResolve } from "./tryResolve.js";
3
- import { buildSqlInClause } from "./sqlInClause.js";
4
- const OPEN_POLICY_DEFAULT = {
5
- isStockManaged: { value: true, policySourceId: null },
6
- allowBackorder: { value: false, policySourceId: null },
7
- backorderLeadTimeDays: { value: null, policySourceId: null },
8
- preorderReleaseAt: { value: null, policySourceId: null },
9
- lowStockThreshold: { value: null, policySourceId: null },
10
- isActive: { value: true, policySourceId: null }
11
- };
12
- async function loadBalanceAggregates(em, tenantId, organizationId, variantIds, locationIds) {
13
- const result = /* @__PURE__ */ new Map();
14
- if (variantIds.length === 0) return result;
15
- const variantClause = buildSqlInClause("b.catalog_variant_id", variantIds);
16
- const params = [organizationId, tenantId, ...variantClause.params];
17
- let locationClause = "";
18
- if (locationIds && locationIds.length > 0) {
19
- const locationIn = buildSqlInClause("b.location_id", locationIds);
20
- locationClause = ` and ${locationIn.sql}`;
21
- params.push(...locationIn.params);
22
- }
23
- const sql = `
24
- select b.catalog_variant_id,
25
- sum(coalesce(b.quantity_on_hand, 0) - coalesce(b.quantity_reserved, 0) - coalesce(b.quantity_allocated, 0)) as aggregate_available
26
- from wms_inventory_balances b
27
- where b.organization_id = ? and b.tenant_id = ? and ${variantClause.sql} and b.deleted_at is null
28
- ${locationClause}
29
- group by b.catalog_variant_id
30
- `;
31
- const rows = await em.getConnection().execute(sql, params);
32
- for (const row of rows) {
33
- result.set(row.catalog_variant_id, Number(row.aggregate_available ?? 0));
34
- }
35
- return result;
36
- }
37
- async function loadProfiles(em, tenantId, organizationId, variantIds, productIds) {
38
- if (variantIds.length === 0 && productIds.length === 0) return [];
39
- const variantClause = buildSqlInClause("catalog_variant_id", variantIds);
40
- const productClause = buildSqlInClause("catalog_product_id", productIds);
41
- const sql = `
42
- select catalog_product_id, catalog_variant_id, safety_stock, reorder_point
43
- from wms_product_inventory_profiles
44
- where organization_id = ? and tenant_id = ? and deleted_at is null
45
- and (${variantClause.sql} or (${productClause.sql} and catalog_variant_id is null))
46
- `;
47
- return em.getConnection().execute(sql, [
48
- organizationId,
49
- tenantId,
50
- ...variantClause.params,
51
- ...productClause.params
52
- ]);
53
- }
54
- async function loadActiveVariantsForProducts(em, tenantId, organizationId, productIds) {
55
- if (productIds.length === 0) return [];
56
- const productClause = buildSqlInClause("product_id", productIds);
57
- const sql = `
58
- select id, product_id
59
- from catalog_product_variants
60
- where organization_id = ? and tenant_id = ? and ${productClause.sql} and is_active = true and deleted_at is null
61
- `;
62
- return em.getConnection().execute(sql, [organizationId, tenantId, ...productClause.params]);
63
- }
64
- function findProfile(profiles, productId, variantId) {
65
- if (variantId) {
66
- const variantRow = profiles.find((p) => p.catalog_variant_id === variantId);
67
- if (variantRow) return variantRow;
68
- }
69
- return profiles.find((p) => p.catalog_product_id === productId && !p.catalog_variant_id) ?? null;
70
- }
71
- function computeState(params) {
72
- if (!params.isActive) {
73
- return { state: "out_of_stock", canFulfil: false, leadTimeDays: null, releaseAt: null };
74
- }
75
- if (params.preorderReleaseAt && params.preorderReleaseAt.getTime() > Date.now()) {
76
- return { state: "preorder", canFulfil: true, leadTimeDays: null, releaseAt: params.preorderReleaseAt.toISOString() };
77
- }
78
- if (params.sellable >= params.requested) {
79
- const state = params.lowStockThreshold != null && params.sellable <= params.lowStockThreshold ? "low_stock" : "in_stock";
80
- return { state, canFulfil: true, leadTimeDays: null, releaseAt: null };
81
- }
82
- if (params.allowBackorder) {
83
- return { state: "backorder", canFulfil: true, leadTimeDays: params.backorderLeadTimeDays, releaseAt: null };
84
- }
85
- return { state: "out_of_stock", canFulfil: false, leadTimeDays: null, releaseAt: null };
86
- }
87
- async function computeAvailability(em, container, query) {
88
- const byItem = {};
89
- if (query.items.length === 0) return { byItem };
90
- const variantItems = query.items.filter((item) => !!item.catalogVariantId);
91
- const productItems = query.items.filter((item) => !item.catalogVariantId);
92
- const productItemIds = Array.from(new Set(productItems.map((item) => item.catalogProductId)));
93
- const rollupVariantRows = await loadActiveVariantsForProducts(em, query.tenantId, query.organizationId, productItemIds);
94
- const variantsByProduct = /* @__PURE__ */ new Map();
95
- for (const row of rollupVariantRows) {
96
- const list = variantsByProduct.get(row.product_id) ?? [];
97
- list.push(row.id);
98
- variantsByProduct.set(row.product_id, list);
99
- }
100
- const allVariantIds = Array.from(
101
- /* @__PURE__ */ new Set([
102
- ...variantItems.map((item) => item.catalogVariantId),
103
- ...rollupVariantRows.map((row) => row.id)
104
- ])
105
- );
106
- const allProductIds = Array.from(new Set(query.items.map((item) => item.catalogProductId)));
107
- const [balances, profiles] = await Promise.all([
108
- loadBalanceAggregates(em, query.tenantId, query.organizationId, allVariantIds, query.locationIds),
109
- loadProfiles(em, query.tenantId, query.organizationId, allVariantIds, allProductIds)
110
- ]);
111
- const policyService = tryResolve(container, "policyResolutionService");
112
- const policyScopes = query.items.map((item) => ({
113
- tenantId: query.tenantId,
114
- organizationId: query.organizationId,
115
- storeId: query.storeId ?? null,
116
- productId: item.catalogProductId,
117
- variantId: item.catalogVariantId ?? null
118
- }));
119
- const resolvedPolicies = policyService ? await policyService.resolveMany(em, policyScopes) : policyScopes.map(() => OPEN_POLICY_DEFAULT);
120
- function sellableFor(variantId, productId) {
121
- const aggregate = balances.get(variantId) ?? 0;
122
- const profile = findProfile(profiles, productId, variantId);
123
- const safetyStock = profile ? Number(profile.safety_stock ?? 0) : 0;
124
- return Math.max(0, aggregate - safetyStock);
125
- }
126
- query.items.forEach((item, index) => {
127
- const policy = resolvedPolicies[index] ?? OPEN_POLICY_DEFAULT;
128
- const key = availabilityItemKey(item);
129
- if (!policy.isStockManaged.value) {
130
- byItem[key] = {
131
- state: "not_tracked",
132
- availableQuantity: null,
133
- canFulfil: true,
134
- leadTimeDays: null,
135
- releaseAt: null,
136
- isAuthoritative: true,
137
- policySourceId: policy.isStockManaged.policySourceId
138
- };
139
- return;
140
- }
141
- let sellable;
142
- let reorderPoint = null;
143
- if (item.catalogVariantId) {
144
- sellable = sellableFor(item.catalogVariantId, item.catalogProductId);
145
- const profile = findProfile(profiles, item.catalogProductId, item.catalogVariantId);
146
- reorderPoint = profile && profile.reorder_point != null ? Number(profile.reorder_point) : null;
147
- } else {
148
- const variantIds = variantsByProduct.get(item.catalogProductId) ?? [];
149
- sellable = variantIds.reduce((sum, variantId) => sum + sellableFor(variantId, item.catalogProductId), 0);
150
- const profile = findProfile(profiles, item.catalogProductId, null);
151
- reorderPoint = profile && profile.reorder_point != null ? Number(profile.reorder_point) : null;
152
- }
153
- const effectiveLowStockThreshold = policy.lowStockThreshold.value ?? reorderPoint;
154
- const computed = computeState({
155
- sellable,
156
- requested: item.quantity,
157
- lowStockThreshold: effectiveLowStockThreshold,
158
- allowBackorder: policy.allowBackorder.value,
159
- backorderLeadTimeDays: policy.backorderLeadTimeDays.value,
160
- preorderReleaseAt: policy.preorderReleaseAt.value,
161
- isActive: policy.isActive.value
162
- });
163
- byItem[key] = {
164
- state: computed.state,
165
- availableQuantity: sellable,
166
- canFulfil: computed.canFulfil,
167
- leadTimeDays: computed.leadTimeDays,
168
- releaseAt: computed.releaseAt,
169
- isAuthoritative: true,
170
- policySourceId: policy.isStockManaged.policySourceId
171
- };
172
- });
173
- return { byItem };
174
- }
175
- export {
176
- computeAvailability
177
- };
178
- //# sourceMappingURL=availabilityCalculation.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/wms/lib/availabilityCalculation.ts"],
4
- "sourcesContent": ["/**\n * `wms`'s `AvailabilityProvider` computation \u2014 batched sellable-quantity\n * aggregation, safety-stock handling, low-stock thresholds, and product\n * rollup.\n *\n * @see .ai/specs/2026-08-14-availability-contract.md \u00A74.2\n *\n * Batching contract (R4): exactly one balance aggregation, one profile\n * lookup, one policy lookup, and (only when the batch has product-level\n * items) one variant-rollup lookup \u2014 never per item.\n *\n * Safety-stock contract (R1): `sellable = max(0, aggregate_available -\n * safety_stock)`, with `safety_stock` subtracted ONCE per variant, after\n * aggregating every in-scope location \u2014 never once per balance row.\n */\n\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { availabilityItemKey } from '@open-mercato/shared/lib/availability'\nimport type { AvailabilityItemResult, AvailabilityQuery, AvailabilityResult, AvailabilityState } from '@open-mercato/shared/lib/availability'\nimport { tryResolve } from './tryResolve'\nimport { buildSqlInClause } from './sqlInClause'\n\ntype Resolver = { resolve: <T = unknown>(name: string) => T }\n\n/**\n * Narrow, locally-declared duck type for `availability`'s `PolicyResolutionService`\n * (soft-resolved, never statically imported \u2014 `wms` never hard-requires `availability`,\n * \u00A73.1). Structurally identical to the real service's relevant surface.\n */\ntype PolicyField<T> = { value: T; policySourceId: string | null }\ntype ResolvedPolicyOverlay = {\n isStockManaged: PolicyField<boolean>\n allowBackorder: PolicyField<boolean>\n backorderLeadTimeDays: PolicyField<number | null>\n preorderReleaseAt: PolicyField<Date | null>\n lowStockThreshold: PolicyField<number | null>\n isActive: PolicyField<boolean>\n}\ntype PolicyResolutionScopeLike = {\n tenantId: string\n organizationId: string\n storeId?: string | null\n productId: string\n variantId?: string | null\n}\ntype PolicyResolutionServiceLike = {\n resolveMany(em: EntityManager, scopes: PolicyResolutionScopeLike[]): Promise<ResolvedPolicyOverlay[]>\n}\n\nconst OPEN_POLICY_DEFAULT: ResolvedPolicyOverlay = {\n isStockManaged: { value: true, policySourceId: null },\n allowBackorder: { value: false, policySourceId: null },\n backorderLeadTimeDays: { value: null, policySourceId: null },\n preorderReleaseAt: { value: null, policySourceId: null },\n lowStockThreshold: { value: null, policySourceId: null },\n isActive: { value: true, policySourceId: null },\n}\n\ntype BalanceAggregateRow = { catalog_variant_id: string; aggregate_available: string | number | null }\ntype ProfileRow = {\n catalog_product_id: string\n catalog_variant_id: string | null\n safety_stock: string | number | null\n reorder_point: string | number | null\n}\ntype VariantRow = { id: string; product_id: string }\n\nasync function loadBalanceAggregates(\n em: EntityManager,\n tenantId: string,\n organizationId: string,\n variantIds: string[],\n locationIds?: string[] | null,\n): Promise<Map<string, number>> {\n const result = new Map<string, number>()\n if (variantIds.length === 0) return result\n\n const variantClause = buildSqlInClause('b.catalog_variant_id', variantIds)\n const params: unknown[] = [organizationId, tenantId, ...variantClause.params]\n let locationClause = ''\n if (locationIds && locationIds.length > 0) {\n const locationIn = buildSqlInClause('b.location_id', locationIds)\n locationClause = ` and ${locationIn.sql}`\n params.push(...locationIn.params)\n }\n\n const sql = `\n select b.catalog_variant_id,\n sum(coalesce(b.quantity_on_hand, 0) - coalesce(b.quantity_reserved, 0) - coalesce(b.quantity_allocated, 0)) as aggregate_available\n from wms_inventory_balances b\n where b.organization_id = ? and b.tenant_id = ? and ${variantClause.sql} and b.deleted_at is null\n ${locationClause}\n group by b.catalog_variant_id\n `\n const rows = await em.getConnection().execute<BalanceAggregateRow[]>(sql, params)\n for (const row of rows) {\n result.set(row.catalog_variant_id, Number(row.aggregate_available ?? 0))\n }\n return result\n}\n\nasync function loadProfiles(\n em: EntityManager,\n tenantId: string,\n organizationId: string,\n variantIds: string[],\n productIds: string[],\n): Promise<ProfileRow[]> {\n if (variantIds.length === 0 && productIds.length === 0) return []\n const variantClause = buildSqlInClause('catalog_variant_id', variantIds)\n const productClause = buildSqlInClause('catalog_product_id', productIds)\n const sql = `\n select catalog_product_id, catalog_variant_id, safety_stock, reorder_point\n from wms_product_inventory_profiles\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and (${variantClause.sql} or (${productClause.sql} and catalog_variant_id is null))\n `\n return em.getConnection().execute<ProfileRow[]>(sql, [\n organizationId,\n tenantId,\n ...variantClause.params,\n ...productClause.params,\n ])\n}\n\nasync function loadActiveVariantsForProducts(\n em: EntityManager,\n tenantId: string,\n organizationId: string,\n productIds: string[],\n): Promise<VariantRow[]> {\n if (productIds.length === 0) return []\n const productClause = buildSqlInClause('product_id', productIds)\n const sql = `\n select id, product_id\n from catalog_product_variants\n where organization_id = ? and tenant_id = ? and ${productClause.sql} and is_active = true and deleted_at is null\n `\n return em.getConnection().execute<VariantRow[]>(sql, [organizationId, tenantId, ...productClause.params])\n}\n\nfunction findProfile(profiles: ProfileRow[], productId: string, variantId: string | null): ProfileRow | null {\n if (variantId) {\n const variantRow = profiles.find((p) => p.catalog_variant_id === variantId)\n if (variantRow) return variantRow\n }\n return profiles.find((p) => p.catalog_product_id === productId && !p.catalog_variant_id) ?? null\n}\n\nfunction computeState(params: {\n sellable: number\n requested: number\n lowStockThreshold: number | null\n allowBackorder: boolean\n backorderLeadTimeDays: number | null\n preorderReleaseAt: Date | null\n isActive: boolean\n}): { state: AvailabilityState; canFulfil: boolean; leadTimeDays: number | null; releaseAt: string | null } {\n if (!params.isActive) {\n return { state: 'out_of_stock', canFulfil: false, leadTimeDays: null, releaseAt: null }\n }\n if (params.preorderReleaseAt && params.preorderReleaseAt.getTime() > Date.now()) {\n return { state: 'preorder', canFulfil: true, leadTimeDays: null, releaseAt: params.preorderReleaseAt.toISOString() }\n }\n if (params.sellable >= params.requested) {\n const state: AvailabilityState =\n params.lowStockThreshold != null && params.sellable <= params.lowStockThreshold ? 'low_stock' : 'in_stock'\n return { state, canFulfil: true, leadTimeDays: null, releaseAt: null }\n }\n if (params.allowBackorder) {\n return { state: 'backorder', canFulfil: true, leadTimeDays: params.backorderLeadTimeDays, releaseAt: null }\n }\n return { state: 'out_of_stock', canFulfil: false, leadTimeDays: null, releaseAt: null }\n}\n\n/**\n * Computes availability for a batch of items \u2014 always live, never cached\n * (the cache wrapper lives in `availabilityCache.ts`, Phase 2 Step 3.2).\n */\nexport async function computeAvailability(\n em: EntityManager,\n container: Resolver,\n query: AvailabilityQuery,\n): Promise<AvailabilityResult> {\n const byItem: AvailabilityResult['byItem'] = {}\n if (query.items.length === 0) return { byItem }\n\n const variantItems = query.items.filter((item) => !!item.catalogVariantId)\n const productItems = query.items.filter((item) => !item.catalogVariantId)\n const productItemIds = Array.from(new Set(productItems.map((item) => item.catalogProductId)))\n\n // One batched lookup of active variants for every product-level item (R4).\n const rollupVariantRows = await loadActiveVariantsForProducts(em, query.tenantId, query.organizationId, productItemIds)\n const variantsByProduct = new Map<string, string[]>()\n for (const row of rollupVariantRows) {\n const list = variantsByProduct.get(row.product_id) ?? []\n list.push(row.id)\n variantsByProduct.set(row.product_id, list)\n }\n\n const allVariantIds = Array.from(\n new Set([\n ...variantItems.map((item) => item.catalogVariantId as string),\n ...rollupVariantRows.map((row) => row.id),\n ]),\n )\n const allProductIds = Array.from(new Set(query.items.map((item) => item.catalogProductId)))\n\n // One balance aggregation and one profile lookup for the whole batch (R4).\n const [balances, profiles] = await Promise.all([\n loadBalanceAggregates(em, query.tenantId, query.organizationId, allVariantIds, query.locationIds),\n loadProfiles(em, query.tenantId, query.organizationId, allVariantIds, allProductIds),\n ])\n\n // One batched policy resolution for the whole batch (R4).\n const policyService = tryResolve<PolicyResolutionServiceLike>(container, 'policyResolutionService')\n const policyScopes: PolicyResolutionScopeLike[] = query.items.map((item) => ({\n tenantId: query.tenantId,\n organizationId: query.organizationId,\n storeId: query.storeId ?? null,\n productId: item.catalogProductId,\n variantId: item.catalogVariantId ?? null,\n }))\n const resolvedPolicies = policyService\n ? await policyService.resolveMany(em, policyScopes)\n : policyScopes.map(() => OPEN_POLICY_DEFAULT)\n\n function sellableFor(variantId: string, productId: string): number {\n const aggregate = balances.get(variantId) ?? 0\n const profile = findProfile(profiles, productId, variantId)\n const safetyStock = profile ? Number(profile.safety_stock ?? 0) : 0\n return Math.max(0, aggregate - safetyStock)\n }\n\n query.items.forEach((item, index) => {\n const policy = resolvedPolicies[index] ?? OPEN_POLICY_DEFAULT\n const key = availabilityItemKey(item)\n\n if (!policy.isStockManaged.value) {\n byItem[key] = {\n state: 'not_tracked',\n availableQuantity: null,\n canFulfil: true,\n leadTimeDays: null,\n releaseAt: null,\n isAuthoritative: true,\n policySourceId: policy.isStockManaged.policySourceId,\n }\n return\n }\n\n let sellable: number\n let reorderPoint: number | null = null\n if (item.catalogVariantId) {\n sellable = sellableFor(item.catalogVariantId, item.catalogProductId)\n const profile = findProfile(profiles, item.catalogProductId, item.catalogVariantId)\n reorderPoint = profile && profile.reorder_point != null ? Number(profile.reorder_point) : null\n } else {\n const variantIds = variantsByProduct.get(item.catalogProductId) ?? []\n sellable = variantIds.reduce((sum, variantId) => sum + sellableFor(variantId, item.catalogProductId), 0)\n const profile = findProfile(profiles, item.catalogProductId, null)\n reorderPoint = profile && profile.reorder_point != null ? Number(profile.reorder_point) : null\n }\n\n const effectiveLowStockThreshold = policy.lowStockThreshold.value ?? reorderPoint\n\n const computed = computeState({\n sellable,\n requested: item.quantity,\n lowStockThreshold: effectiveLowStockThreshold,\n allowBackorder: policy.allowBackorder.value,\n backorderLeadTimeDays: policy.backorderLeadTimeDays.value,\n preorderReleaseAt: policy.preorderReleaseAt.value,\n isActive: policy.isActive.value,\n })\n\n byItem[key] = {\n state: computed.state,\n availableQuantity: sellable,\n canFulfil: computed.canFulfil,\n leadTimeDays: computed.leadTimeDays,\n releaseAt: computed.releaseAt,\n isAuthoritative: true,\n policySourceId: policy.isStockManaged.policySourceId,\n }\n })\n\n return { byItem }\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,14 +0,0 @@
1
- import { computeAvailabilityCached } from "./availabilityCache.js";
2
- function createWmsAvailabilityProvider(container) {
3
- return {
4
- id: "wms",
5
- async getAvailability(query) {
6
- const em = container.resolve("em").fork();
7
- return computeAvailabilityCached(em, container, query);
8
- }
9
- };
10
- }
11
- export {
12
- createWmsAvailabilityProvider
13
- };
14
- //# sourceMappingURL=availabilityProvider.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/wms/lib/availabilityProvider.ts"],
4
- "sourcesContent": ["/**\n * `wms`'s `AvailabilityProvider` registration (`id: 'wms'`).\n *\n * @see .ai/specs/2026-08-14-availability-contract.md \u00A73.1, \u00A74.2\n *\n * Registered from `wms/di.ts`'s `register(container)`, which \u2014 like every\n * module DI registrar \u2014 runs once per `createRequestContainer()` call\n * (`packages/shared/src/lib/di/container.ts`), not once at process boot.\n * `availabilityProviderRegistry.register()` is idempotent replace-by-id, so\n * each request's registration simply re-points the shared registry entry at\n * a closure over that request's own container \u2014 the same pattern this\n * module already uses for its plain entity-class registrations in `di.ts`.\n */\n\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AvailabilityProvider, AvailabilityQuery, AvailabilityResult } from '@open-mercato/shared/lib/availability'\nimport { computeAvailabilityCached } from './availabilityCache'\n\ntype Resolver = { resolve: <T = unknown>(name: string) => T }\n\nexport function createWmsAvailabilityProvider(container: Resolver): AvailabilityProvider {\n return {\n id: 'wms',\n async getAvailability(query: AvailabilityQuery): Promise<AvailabilityResult> {\n const em = (container.resolve('em') as EntityManager).fork()\n return computeAvailabilityCached(em, container, query)\n },\n }\n}\n"],
5
- "mappings": "AAgBA,SAAS,iCAAiC;AAInC,SAAS,8BAA8B,WAA2C;AACvF,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,gBAAgB,OAAuD;AAC3E,YAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,aAAO,0BAA0B,IAAI,WAAW,KAAK;AAAA,IACvD;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,9 +0,0 @@
1
- function buildSqlInClause(column, values) {
2
- if (values.length === 0) return { sql: `${column} IN (NULL)`, params: [] };
3
- const placeholders = values.map(() => "?").join(", ");
4
- return { sql: `${column} IN (${placeholders})`, params: [...values] };
5
- }
6
- export {
7
- buildSqlInClause
8
- };
9
- //# sourceMappingURL=sqlInClause.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/wms/lib/sqlInClause.ts"],
4
- "sourcesContent": ["/**\n * Renders an id list into a raw-SQL `IN` clause with one placeholder per member.\n *\n * MikroORM does not bind parameters at the driver level: `AbstractSqlConnection.execute`\n * calls `platform.formatQuery`, which interpolates each value through\n * `BasePostgreSqlPlatform.escape`, and that renders a JavaScript array as a bare\n * comma-separated list. `column = ANY(?)` therefore reaches PostgreSQL as\n * `= ANY('a', 'b')` \u2014 or, for a single-element array, as the bare scalar, which fails\n * with `malformed array literal`. Same fix as the dashboards aggregation builder (#4669)\n * and the staff time-tracking raw queries: one placeholder per member.\n *\n * An empty list yields `IN (NULL)` rather than a dropped clause: a caller that narrowed\n * to nothing must select nothing, never silently widen to every row.\n */\nexport type SqlInClause = { sql: string; params: unknown[] }\n\nexport function buildSqlInClause(column: string, values: readonly string[]): SqlInClause {\n if (values.length === 0) return { sql: `${column} IN (NULL)`, params: [] }\n const placeholders = values.map(() => '?').join(', ')\n return { sql: `${column} IN (${placeholders})`, params: [...values] }\n}\n"],
5
- "mappings": "AAgBO,SAAS,iBAAiB,QAAgB,QAAwC;AACvF,MAAI,OAAO,WAAW,EAAG,QAAO,EAAE,KAAK,GAAG,MAAM,cAAc,QAAQ,CAAC,EAAE;AACzE,QAAM,eAAe,OAAO,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACpD,SAAO,EAAE,KAAK,GAAG,MAAM,QAAQ,YAAY,KAAK,QAAQ,CAAC,GAAG,MAAM,EAAE;AACtE;",
6
- "names": []
7
- }
@@ -1,11 +0,0 @@
1
- function tryResolve(resolver, name) {
2
- try {
3
- return resolver.resolve(name);
4
- } catch {
5
- return void 0;
6
- }
7
- }
8
- export {
9
- tryResolve
10
- };
11
- //# sourceMappingURL=tryResolve.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/wms/lib/tryResolve.ts"],
4
- "sourcesContent": ["/** Per-module local soft-resolve helper \u2014 see `packages/core/AGENTS.md` \u00A7 Cross-Module Coupling. */\nexport function tryResolve<T>(resolver: { resolve: <R = unknown>(name: string) => R }, name: string): T | undefined {\n try {\n return resolver.resolve<T>(name)\n } catch {\n return undefined\n }\n}\n"],
5
- "mappings": "AACO,SAAS,WAAc,UAAyD,MAA6B;AAClH,MAAI;AACF,WAAO,SAAS,QAAW,IAAI;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
- "names": []
7
- }
@@ -1,16 +0,0 @@
1
- import { invalidateWmsAvailabilityCache } from "../lib/availabilityCache.js";
2
- const metadata = {
3
- event: "wms.inventory_balance.*",
4
- persistent: false,
5
- id: "wms:invalidate-availability-cache"
6
- };
7
- async function handle(payload, ctx) {
8
- const data = payload ?? {};
9
- const tenantId = typeof data.tenantId === "string" && data.tenantId.length > 0 ? data.tenantId : ctx.tenantId ?? null;
10
- await invalidateWmsAvailabilityCache(ctx, tenantId);
11
- }
12
- export {
13
- handle as default,
14
- metadata
15
- };
16
- //# sourceMappingURL=invalidate-availability-cache.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/wms/subscribers/invalidate-availability-cache.ts"],
4
- "sourcesContent": ["// Invalidates the wms AvailabilityProvider's read-through cache\n// (`availabilityCache.ts`) whenever a balance changes. Ephemeral\n// (`persistent: false`) \u2014 cache invalidation is a real-time housekeeping\n// concern, not something that needs retry/audit semantics; the cache's own\n// 60s TTL is the backstop if an event is ever lost.\n//\n// `wms.*` does NOT match `wms.inventory_balance.created` (single-segment\n// matcher) \u2014 `wms.inventory_balance.*` matches all three CRUD actions\n// (created/updated/deleted) in one subscriber, mirroring\n// `invalidate-enricher-cache-balance.ts`.\n\nimport { invalidateWmsAvailabilityCache } from '../lib/availabilityCache'\n\nexport const metadata = {\n event: 'wms.inventory_balance.*',\n persistent: false,\n id: 'wms:invalidate-availability-cache',\n}\n\ntype SubscriberContext = {\n resolve: <T = unknown>(name: string) => T\n tenantId?: string | null\n}\n\nexport default async function handle(payload: unknown, ctx: SubscriberContext): Promise<void> {\n const data = (payload ?? {}) as Record<string, unknown>\n const tenantId =\n typeof data.tenantId === 'string' && data.tenantId.length > 0 ? data.tenantId : ctx.tenantId ?? null\n await invalidateWmsAvailabilityCache(ctx, tenantId)\n}\n"],
5
- "mappings": "AAWA,SAAS,sCAAsC;AAExC,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAOA,eAAO,OAA8B,SAAkB,KAAuC;AAC5F,QAAM,OAAQ,WAAW,CAAC;AAC1B,QAAM,WACJ,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,IAAI,KAAK,WAAW,IAAI,YAAY;AAClG,QAAM,+BAA+B,KAAK,QAAQ;AACpD;",
6
- "names": []
7
- }
@@ -1,19 +0,0 @@
1
- export const id = "id";
2
- export const organization_id = "organization_id";
3
- export const tenant_id = "tenant_id";
4
- export const store_id = "store_id";
5
- export const product_id = "product_id";
6
- export const variant_id = "variant_id";
7
- export const is_stock_managed = "is_stock_managed";
8
- export const allow_backorder = "allow_backorder";
9
- export const backorder_lead_time_days = "backorder_lead_time_days";
10
- export const preorder_release_at = "preorder_release_at";
11
- export const low_stock_threshold = "low_stock_threshold";
12
- export const min_order_quantity = "min_order_quantity";
13
- export const max_order_quantity = "max_order_quantity";
14
- export const quantity_increment = "quantity_increment";
15
- export const hide_when_out_of_stock = "hide_when_out_of_stock";
16
- export const is_active = "is_active";
17
- export const created_at = "created_at";
18
- export const updated_at = "updated_at";
19
- export const deleted_at = "deleted_at";
@@ -1,12 +0,0 @@
1
- export const features = [
2
- { id: 'availability.policies.view', title: 'View availability policies', module: 'availability' },
3
- {
4
- id: 'availability.policies.manage',
5
- title: 'Manage availability policies',
6
- module: 'availability',
7
- dependsOn: ['availability.policies.view'],
8
- },
9
- { id: 'availability.check', title: 'Run availability checks', module: 'availability' },
10
- ]
11
-
12
- export default features
@@ -1,114 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { z } from 'zod'
3
- import type { EntityManager } from '@mikro-orm/postgresql'
4
- import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
- import { resolveActiveOrganizationId, organizationScopeRequiredResponse } from '@open-mercato/shared/lib/auth/organizationScope'
6
- import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
7
- import { resolveAvailability, availabilityItemKey } from '@open-mercato/shared/lib/availability'
8
- import type { AvailabilityModuleConfigReader, AvailabilityQuery } from '@open-mercato/shared/lib/availability'
9
- import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
- import { tryResolve } from '../../lib/tryResolve'
11
- import type { PolicyResolutionService } from '../../lib/policyResolution'
12
-
13
- /**
14
- * `POST /api/availability/check` — admin/debug reproduction of what a buyer saw,
15
- * including the per-field `policySourceId` trace. No public storefront endpoints
16
- * ship in this module (§8).
17
- */
18
- const routeMetadata = {
19
- POST: { requireAuth: true, requireFeatures: ['availability.check'] },
20
- }
21
-
22
- export const metadata = routeMetadata
23
-
24
- export const openApi = {
25
- tags: ['Availability'],
26
- summary: 'Reproduce the availability state and policy trace a buyer would see for one item.',
27
- description: 'Admin/debug tool. Mirrors resolveAvailability() and includes the per-field policySourceId trace.',
28
- }
29
-
30
- const checkSchema = z.object({
31
- productId: z.uuid(),
32
- variantId: z.uuid().nullable().optional(),
33
- quantity: z.coerce.number().int().min(1).default(1),
34
- storeId: z.uuid().nullable().optional(),
35
- })
36
-
37
- export async function POST(req: Request) {
38
- const { translate } = await resolveTranslations()
39
- const auth = await getAuthFromRequest(req)
40
- if (!auth || !auth.tenantId) {
41
- return NextResponse.json({ error: translate('availability.errors.unauthorized', 'Unauthorized') }, { status: 401 })
42
- }
43
-
44
- const organizationId = resolveActiveOrganizationId(auth)
45
- if (!organizationId) return organizationScopeRequiredResponse()
46
-
47
- const body = await req.json().catch(() => ({}))
48
- const parsed = checkSchema.safeParse(body)
49
- if (!parsed.success) {
50
- return NextResponse.json(
51
- { error: translate('availability.errors.invalidCheckRequest', 'Invalid request') },
52
- { status: 400 },
53
- )
54
- }
55
- const { productId, variantId, quantity, storeId } = parsed.data
56
- const tenantId = auth.tenantId
57
-
58
- const container = await createRequestContainer()
59
- const em = container.resolve('em') as EntityManager
60
-
61
- // Soft-resolved product/variant existence check — degrades gracefully when
62
- // `catalog` is ejected (Phase 1 gate: coherent behaviour without it).
63
- const CatalogProduct = tryResolve<new () => unknown>(container, 'CatalogProduct')
64
- if (CatalogProduct) {
65
- const product = await em.findOne(CatalogProduct as any, { id: productId, organizationId, tenantId, deletedAt: null })
66
- if (!product) {
67
- return NextResponse.json(
68
- { error: translate('availability.errors.productNotFound', 'No such product') },
69
- { status: 404 },
70
- )
71
- }
72
- if (variantId) {
73
- const CatalogProductVariant = tryResolve<new () => unknown>(container, 'CatalogProductVariant')
74
- if (CatalogProductVariant) {
75
- const variant = await em.findOne(CatalogProductVariant as any, {
76
- id: variantId,
77
- organizationId,
78
- tenantId,
79
- deletedAt: null,
80
- })
81
- if (!variant) {
82
- return NextResponse.json(
83
- { error: translate('availability.errors.variantNotFound', 'No such variant') },
84
- { status: 404 },
85
- )
86
- }
87
- }
88
- }
89
- }
90
-
91
- const query: AvailabilityQuery = {
92
- tenantId,
93
- organizationId,
94
- storeId: storeId ?? null,
95
- items: [{ catalogProductId: productId, catalogVariantId: variantId ?? null, quantity }],
96
- }
97
-
98
- const moduleConfig = tryResolve<AvailabilityModuleConfigReader>(container, 'moduleConfigService')
99
- const result = await resolveAvailability(query, moduleConfig ? { moduleConfig } : undefined)
100
- const item = result.byItem[availabilityItemKey({ catalogProductId: productId, catalogVariantId: variantId ?? null })] ?? null
101
-
102
- const policyResolutionService = tryResolve<PolicyResolutionService>(container, 'policyResolutionService')
103
- const policyTrace = policyResolutionService
104
- ? await policyResolutionService.resolve(em, {
105
- tenantId,
106
- organizationId,
107
- storeId: storeId ?? null,
108
- productId,
109
- variantId: variantId ?? null,
110
- })
111
- : null
112
-
113
- return NextResponse.json({ availability: item, policyTrace })
114
- }
@@ -1,28 +0,0 @@
1
- import { type ZodTypeAny } from 'zod'
2
- import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
- import {
4
- createCrudOpenApiFactory,
5
- createPagedListResponseSchema as createSharedPagedListResponseSchema,
6
- defaultCreateResponseSchema as sharedDefaultCreateResponseSchema,
7
- defaultOkResponseSchema as sharedDefaultOkResponseSchema,
8
- type CrudOpenApiOptions,
9
- } from '@open-mercato/shared/lib/openapi/crud'
10
-
11
- export const defaultCreateResponseSchema = sharedDefaultCreateResponseSchema
12
- export const defaultOkResponseSchema = sharedDefaultOkResponseSchema
13
-
14
- export function createPagedListResponseSchema(itemSchema: ZodTypeAny) {
15
- return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })
16
- }
17
-
18
- const buildAvailabilityCrudOpenApi = createCrudOpenApiFactory({
19
- defaultTag: 'Availability',
20
- defaultCreateResponseSchema,
21
- defaultOkResponseSchema,
22
- makeListDescription: ({ pluralLower }) =>
23
- `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`,
24
- })
25
-
26
- export function createAvailabilityCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {
27
- return buildAvailabilityCrudOpenApi(options)
28
- }
@@ -1,67 +0,0 @@
1
- import { NextResponse } from 'next/server'
2
- import { z } from 'zod'
3
- import type { EntityManager } from '@mikro-orm/postgresql'
4
- import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
- import { resolveActiveOrganizationId, organizationScopeRequiredResponse } from '@open-mercato/shared/lib/auth/organizationScope'
6
- import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
7
- import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
8
- import { createPolicyResolutionService } from '../../../lib/policyResolution'
9
-
10
- /**
11
- * `GET /api/availability/policies/resolve-preview` — the currently PERSISTED
12
- * resolution chain for a (product, variant, store) target, used by the policy
13
- * edit/create form to show "which level currently decides each field" (US-A2)
14
- * before the admin saves an override.
15
- */
16
- const routeMetadata = {
17
- GET: { requireAuth: true, requireFeatures: ['availability.policies.view'] },
18
- }
19
-
20
- export const metadata = routeMetadata
21
-
22
- export const openApi = {
23
- tags: ['Availability'],
24
- summary: 'Preview the currently persisted policy resolution chain for a product/variant/store target.',
25
- }
26
-
27
- const querySchema = z.object({
28
- productId: z.uuid(),
29
- variantId: z.uuid().nullable().optional(),
30
- storeId: z.uuid().nullable().optional(),
31
- })
32
-
33
- export async function GET(req: Request) {
34
- const { translate } = await resolveTranslations()
35
- const auth = await getAuthFromRequest(req)
36
- if (!auth || !auth.tenantId) {
37
- return NextResponse.json({ error: translate('availability.errors.unauthorized', 'Unauthorized') }, { status: 401 })
38
- }
39
- const organizationId = resolveActiveOrganizationId(auth)
40
- if (!organizationId) return organizationScopeRequiredResponse()
41
-
42
- const url = new URL(req.url)
43
- const parsed = querySchema.safeParse({
44
- productId: url.searchParams.get('productId') ?? undefined,
45
- variantId: url.searchParams.get('variantId') ?? undefined,
46
- storeId: url.searchParams.get('storeId') ?? undefined,
47
- })
48
- if (!parsed.success) {
49
- return NextResponse.json(
50
- { error: translate('availability.errors.invalidPreviewRequest', 'Invalid request') },
51
- { status: 400 },
52
- )
53
- }
54
-
55
- const container = await createRequestContainer()
56
- const em = container.resolve('em') as EntityManager
57
- const service = createPolicyResolutionService(container)
58
- const trace = await service.resolve(em, {
59
- tenantId: auth.tenantId,
60
- organizationId,
61
- storeId: parsed.data.storeId ?? null,
62
- productId: parsed.data.productId,
63
- variantId: parsed.data.variantId ?? null,
64
- })
65
-
66
- return NextResponse.json({ policyTrace: trace })
67
- }