@open-mercato/core 0.8.1-develop.7228.1.b7ea09d2d3 → 0.8.1-develop.7237.1.635faf2c7a
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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/availability_policy/index.js +41 -0
- package/dist/generated/entities/availability_policy/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +4 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +21 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/attachments/data/entities.js +3 -3
- package/dist/modules/attachments/data/entities.js.map +2 -2
- package/dist/modules/availability/acl.js +16 -0
- package/dist/modules/availability/acl.js.map +7 -0
- package/dist/modules/availability/api/check/route.js +95 -0
- package/dist/modules/availability/api/check/route.js.map +7 -0
- package/dist/modules/availability/api/openapi.js +27 -0
- package/dist/modules/availability/api/openapi.js.map +7 -0
- package/dist/modules/availability/api/policies/resolve-preview/route.js +58 -0
- package/dist/modules/availability/api/policies/resolve-preview/route.js.map +7 -0
- package/dist/modules/availability/api/policies/route.js +188 -0
- package/dist/modules/availability/api/policies/route.js.map +7 -0
- package/dist/modules/availability/backend/availability/check/page.js +185 -0
- package/dist/modules/availability/backend/availability/check/page.js.map +7 -0
- package/dist/modules/availability/backend/availability/check/page.meta.js +16 -0
- package/dist/modules/availability/backend/availability/check/page.meta.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +79 -0
- package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/ScopeFields.js +53 -0
- package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/[id]/page.js +152 -0
- package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +12 -0
- package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/create/page.js +94 -0
- package/dist/modules/availability/backend/availability/policies/create/page.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/create/page.meta.js +12 -0
- package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/formGroups.js +75 -0
- package/dist/modules/availability/backend/availability/policies/formGroups.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/page.js +176 -0
- package/dist/modules/availability/backend/availability/policies/page.js.map +7 -0
- package/dist/modules/availability/backend/availability/policies/page.meta.js +16 -0
- package/dist/modules/availability/backend/availability/policies/page.meta.js.map +7 -0
- package/dist/modules/availability/commands/policies.js +312 -0
- package/dist/modules/availability/commands/policies.js.map +7 -0
- package/dist/modules/availability/commands/scope.js +19 -0
- package/dist/modules/availability/commands/scope.js.map +7 -0
- package/dist/modules/availability/data/entities.js +95 -0
- package/dist/modules/availability/data/entities.js.map +7 -0
- package/dist/modules/availability/data/validators.js +53 -0
- package/dist/modules/availability/data/validators.js.map +7 -0
- package/dist/modules/availability/di.js +41 -0
- package/dist/modules/availability/di.js.map +7 -0
- package/dist/modules/availability/events.js +18 -0
- package/dist/modules/availability/events.js.map +7 -0
- package/dist/modules/availability/index.js +15 -0
- package/dist/modules/availability/index.js.map +7 -0
- package/dist/modules/availability/lib/policyResolution.js +155 -0
- package/dist/modules/availability/lib/policyResolution.js.map +7 -0
- package/dist/modules/availability/lib/tryResolve.js +11 -0
- package/dist/modules/availability/lib/tryResolve.js.map +7 -0
- package/dist/modules/availability/migrations/Migration20260922095844_availability.js +16 -0
- package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +7 -0
- package/dist/modules/availability/setup.js +12 -0
- package/dist/modules/availability/setup.js.map +7 -0
- package/dist/modules/entities/cli.js +33 -12
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +7 -32
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js +1 -1
- package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
- package/dist/modules/inbox_ops/lib/ensure-settings.js +47 -0
- package/dist/modules/inbox_ops/lib/ensure-settings.js.map +7 -0
- package/dist/modules/inbox_ops/lib/executionEngine.js.map +1 -1
- package/dist/modules/inbox_ops/lib/executionHelpers.js +0 -23
- package/dist/modules/inbox_ops/lib/executionHelpers.js.map +2 -2
- package/dist/modules/inbox_ops/lib/payloadEnrichment.js +0 -3
- package/dist/modules/inbox_ops/lib/payloadEnrichment.js.map +2 -2
- package/dist/modules/inbox_ops/setup.js +2 -21
- package/dist/modules/inbox_ops/setup.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +1 -1
- package/dist/modules/sales/data/entities.js +3 -1
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/inbox-actions.js +0 -12
- package/dist/modules/sales/inbox-actions.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.js +20 -0
- package/dist/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.js.map +7 -0
- package/dist/modules/wms/di.js +3 -0
- package/dist/modules/wms/di.js.map +2 -2
- package/dist/modules/wms/lib/availabilityCache.js +85 -0
- package/dist/modules/wms/lib/availabilityCache.js.map +7 -0
- package/dist/modules/wms/lib/availabilityCalculation.js +178 -0
- package/dist/modules/wms/lib/availabilityCalculation.js.map +7 -0
- package/dist/modules/wms/lib/availabilityProvider.js +14 -0
- package/dist/modules/wms/lib/availabilityProvider.js.map +7 -0
- package/dist/modules/wms/lib/sqlInClause.js +9 -0
- package/dist/modules/wms/lib/sqlInClause.js.map +7 -0
- package/dist/modules/wms/lib/tryResolve.js +11 -0
- package/dist/modules/wms/lib/tryResolve.js.map +7 -0
- package/dist/modules/wms/subscribers/invalidate-availability-cache.js +16 -0
- package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +7 -0
- package/generated/entities/availability_policy/index.ts +19 -0
- package/generated/entities.ids.generated.ts +4 -0
- package/generated/entity-fields-registry.ts +21 -0
- package/package.json +7 -7
- package/src/modules/attachments/data/entities.ts +5 -3
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/availability/acl.ts +12 -0
- package/src/modules/availability/api/check/route.ts +114 -0
- package/src/modules/availability/api/openapi.ts +28 -0
- package/src/modules/availability/api/policies/resolve-preview/route.ts +67 -0
- package/src/modules/availability/api/policies/route.ts +224 -0
- package/src/modules/availability/backend/availability/check/page.meta.ts +12 -0
- package/src/modules/availability/backend/availability/check/page.tsx +223 -0
- package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +106 -0
- package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +55 -0
- package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +8 -0
- package/src/modules/availability/backend/availability/policies/[id]/page.tsx +182 -0
- package/src/modules/availability/backend/availability/policies/create/page.meta.ts +8 -0
- package/src/modules/availability/backend/availability/policies/create/page.tsx +105 -0
- package/src/modules/availability/backend/availability/policies/formGroups.ts +77 -0
- package/src/modules/availability/backend/availability/policies/page.meta.ts +12 -0
- package/src/modules/availability/backend/availability/policies/page.tsx +196 -0
- package/src/modules/availability/commands/policies.ts +358 -0
- package/src/modules/availability/commands/scope.ts +26 -0
- package/src/modules/availability/data/entities.ts +92 -0
- package/src/modules/availability/data/validators.ts +70 -0
- package/src/modules/availability/di.ts +54 -0
- package/src/modules/availability/events.ts +17 -0
- package/src/modules/availability/i18n/de.json +81 -0
- package/src/modules/availability/i18n/en.json +81 -0
- package/src/modules/availability/i18n/es.json +81 -0
- package/src/modules/availability/i18n/ko.json +81 -0
- package/src/modules/availability/i18n/pl.json +81 -0
- package/src/modules/availability/index.ts +13 -0
- package/src/modules/availability/lib/policyResolution.ts +264 -0
- package/src/modules/availability/lib/tryResolve.ts +8 -0
- package/src/modules/availability/migrations/.snapshot-open-mercato.json +375 -0
- package/src/modules/availability/migrations/Migration20260922095844_availability.ts +13 -0
- package/src/modules/availability/setup.ts +10 -0
- package/src/modules/entities/cli.ts +53 -13
- package/src/modules/inbox_ops/api/settings/route.ts +9 -32
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +2 -2
- package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +1 -1
- package/src/modules/inbox_ops/i18n/de.json +1 -1
- package/src/modules/inbox_ops/i18n/en.json +1 -1
- package/src/modules/inbox_ops/i18n/es.json +1 -1
- package/src/modules/inbox_ops/i18n/ko.json +1 -1
- package/src/modules/inbox_ops/i18n/pl.json +1 -1
- package/src/modules/inbox_ops/lib/ensure-settings.ts +77 -0
- package/src/modules/inbox_ops/lib/executionEngine.ts +1 -1
- package/src/modules/inbox_ops/lib/executionHelpers.ts +0 -27
- package/src/modules/inbox_ops/lib/payloadEnrichment.ts +1 -5
- package/src/modules/inbox_ops/setup.ts +2 -21
- package/src/modules/inbox_ops/subscribers/extractionWorker.ts +1 -1
- package/src/modules/sales/data/entities.ts +2 -0
- package/src/modules/sales/inbox-actions.ts +0 -20
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +20 -0
- package/src/modules/sales/migrations/Migration20260916120000_sales_payment_allocation_order_idx.ts +74 -0
- package/src/modules/wms/di.ts +6 -0
- package/src/modules/wms/lib/availabilityCache.ts +136 -0
- package/src/modules/wms/lib/availabilityCalculation.ts +289 -0
- package/src/modules/wms/lib/availabilityProvider.ts +29 -0
- package/src/modules/wms/lib/sqlInClause.ts +21 -0
- package/src/modules/wms/lib/tryResolve.ts +8 -0
- package/src/modules/wms/subscribers/invalidate-availability-cache.ts +30 -0
|
@@ -0,0 +1,16 @@
|
|
|
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
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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";
|
|
@@ -14,6 +14,7 @@ export const M = {
|
|
|
14
14
|
"sales": "sales",
|
|
15
15
|
"warranty_claims": "warranty_claims",
|
|
16
16
|
"wms": "wms",
|
|
17
|
+
"availability": "availability",
|
|
17
18
|
"api_keys": "api_keys",
|
|
18
19
|
"devices": "devices",
|
|
19
20
|
"dictionaries": "dictionaries",
|
|
@@ -191,6 +192,9 @@ export const M = {
|
|
|
191
192
|
"sales_order_warehouse_assignment": "wms:sales_order_warehouse_assignment",
|
|
192
193
|
"inventory_movement": "wms:inventory_movement"
|
|
193
194
|
},
|
|
195
|
+
"availability": {
|
|
196
|
+
"availability_policy": "availability:availability_policy"
|
|
197
|
+
},
|
|
194
198
|
"api_keys": {
|
|
195
199
|
"api_key": "api_keys:api_key"
|
|
196
200
|
},
|
|
@@ -112,6 +112,27 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
112
112
|
"created_at": "created_at",
|
|
113
113
|
"updated_at": "updated_at"
|
|
114
114
|
},
|
|
115
|
+
"availability_policy": {
|
|
116
|
+
"id": "id",
|
|
117
|
+
"organization_id": "organization_id",
|
|
118
|
+
"tenant_id": "tenant_id",
|
|
119
|
+
"store_id": "store_id",
|
|
120
|
+
"product_id": "product_id",
|
|
121
|
+
"variant_id": "variant_id",
|
|
122
|
+
"is_stock_managed": "is_stock_managed",
|
|
123
|
+
"allow_backorder": "allow_backorder",
|
|
124
|
+
"backorder_lead_time_days": "backorder_lead_time_days",
|
|
125
|
+
"preorder_release_at": "preorder_release_at",
|
|
126
|
+
"low_stock_threshold": "low_stock_threshold",
|
|
127
|
+
"min_order_quantity": "min_order_quantity",
|
|
128
|
+
"max_order_quantity": "max_order_quantity",
|
|
129
|
+
"quantity_increment": "quantity_increment",
|
|
130
|
+
"hide_when_out_of_stock": "hide_when_out_of_stock",
|
|
131
|
+
"is_active": "is_active",
|
|
132
|
+
"created_at": "created_at",
|
|
133
|
+
"updated_at": "updated_at",
|
|
134
|
+
"deleted_at": "deleted_at"
|
|
135
|
+
},
|
|
115
136
|
"business_rule": {
|
|
116
137
|
"id": "id",
|
|
117
138
|
"rule_id": "rule_id",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.8.1-develop.
|
|
3
|
+
"version": "0.8.1-develop.7237.1.635faf2c7a",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.8.1-develop.
|
|
257
|
-
"@open-mercato/shared": "0.8.1-develop.
|
|
258
|
-
"@open-mercato/ui": "0.8.1-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.8.1-develop.7237.1.635faf2c7a",
|
|
257
|
+
"@open-mercato/shared": "0.8.1-develop.7237.1.635faf2c7a",
|
|
258
|
+
"@open-mercato/ui": "0.8.1-develop.7237.1.635faf2c7a",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.8.1-develop.
|
|
264
|
-
"@open-mercato/shared": "0.8.1-develop.
|
|
265
|
-
"@open-mercato/ui": "0.8.1-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.8.1-develop.7237.1.635faf2c7a",
|
|
264
|
+
"@open-mercato/shared": "0.8.1-develop.7237.1.635faf2c7a",
|
|
265
|
+
"@open-mercato/ui": "0.8.1-develop.7237.1.635faf2c7a",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^7.0.1",
|
|
268
268
|
"@testing-library/react": "^16.3.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OptionalProps } from '@mikro-orm/core'
|
|
1
|
+
import { BigIntType, OptionalProps } from '@mikro-orm/core'
|
|
2
2
|
import { Entity, Index, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
|
|
3
3
|
import { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'
|
|
4
4
|
|
|
@@ -121,10 +121,12 @@ export class AttachmentQuotaReservation {
|
|
|
121
121
|
@Property({ name: 'organization_id', type: 'uuid' })
|
|
122
122
|
organizationId!: string
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
/** Backed by a `bigint` column; hydrate as `number` (safe under Number.MAX_SAFE_INTEGER for attachment byte counts) instead of MikroORM's default JS `bigint`. */
|
|
125
|
+
@Property({ name: 'reserved_bytes', type: new BigIntType('number') })
|
|
125
126
|
reservedBytes!: number
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
/** Backed by a `bigint` column; hydrate as `number`, not MikroORM's default JS `bigint`. */
|
|
129
|
+
@Property({ name: 'actual_bytes', type: new BigIntType('number'), nullable: true })
|
|
128
130
|
actualBytes?: number | null
|
|
129
131
|
|
|
130
132
|
@Property({ type: 'text', default: 'reserved' })
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
+
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
+
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
+
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
65
68
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
66
69
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
67
70
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
+
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
+
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
+
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
65
68
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
66
69
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
67
70
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
+
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
+
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
+
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
65
68
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
66
69
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
67
70
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
+
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
+
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
+
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
65
68
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
66
69
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
67
70
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Usuwaj użytkowników",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edytuj użytkowników",
|
|
64
64
|
"auth.acl.features.auth.users.list": "Wyświetl listę użytkowników",
|
|
65
|
+
"auth.acl.features.availability.check": "Uruchamiaj sprawdzenia dostępności",
|
|
66
|
+
"auth.acl.features.availability.policies.manage": "Zarządzaj politykami dostępności",
|
|
67
|
+
"auth.acl.features.availability.policies.view": "Wyświetl polityki dostępności",
|
|
65
68
|
"auth.acl.features.business_rules.execute": "Uruchamiaj reguły biznesowe ręcznie",
|
|
66
69
|
"auth.acl.features.business_rules.manage": "Twórz, edytuj i usuwaj reguły biznesowe",
|
|
67
70
|
"auth.acl.features.business_rules.manage_sets": "Zarządzaj zestawami reguł",
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
4
|
+
import { AvailabilityPolicy } from '../../data/entities'
|
|
5
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
6
|
+
import type { FilterQuery } from '@mikro-orm/core'
|
|
7
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
8
|
+
import { resolveActiveOrganizationId, organizationScopeRequiredResponse } from '@open-mercato/shared/lib/auth/organizationScope'
|
|
9
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
10
|
+
import { availabilityPolicyCreateSchema, availabilityPolicyUpdateSchema } from '../../data/validators'
|
|
11
|
+
import {
|
|
12
|
+
createAvailabilityCrudOpenApi,
|
|
13
|
+
createPagedListResponseSchema,
|
|
14
|
+
defaultOkResponseSchema,
|
|
15
|
+
} from '../openapi'
|
|
16
|
+
|
|
17
|
+
const routeMetadata = {
|
|
18
|
+
GET: { requireAuth: true, requireFeatures: ['availability.policies.view'] },
|
|
19
|
+
POST: { requireAuth: true, requireFeatures: ['availability.policies.manage'] },
|
|
20
|
+
PUT: { requireAuth: true, requireFeatures: ['availability.policies.manage'] },
|
|
21
|
+
DELETE: { requireAuth: true, requireFeatures: ['availability.policies.manage'] },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const metadata = routeMetadata
|
|
25
|
+
|
|
26
|
+
const rawBodySchema = z.object({}).loose()
|
|
27
|
+
type CrudInput = Record<string, unknown>
|
|
28
|
+
|
|
29
|
+
const crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({
|
|
30
|
+
metadata: routeMetadata,
|
|
31
|
+
orm: {
|
|
32
|
+
entity: AvailabilityPolicy,
|
|
33
|
+
idField: 'id',
|
|
34
|
+
orgField: 'organizationId',
|
|
35
|
+
tenantField: 'tenantId',
|
|
36
|
+
softDeleteField: 'deletedAt',
|
|
37
|
+
},
|
|
38
|
+
events: {
|
|
39
|
+
module: 'availability',
|
|
40
|
+
entity: 'policy',
|
|
41
|
+
persistent: true,
|
|
42
|
+
},
|
|
43
|
+
actions: {
|
|
44
|
+
create: {
|
|
45
|
+
commandId: 'availability.policies.create',
|
|
46
|
+
schema: rawBodySchema,
|
|
47
|
+
mapInput: ({ parsed }) => parsed,
|
|
48
|
+
response: ({ result }) => ({ id: String(result.policyId) }),
|
|
49
|
+
status: 201,
|
|
50
|
+
},
|
|
51
|
+
update: {
|
|
52
|
+
commandId: 'availability.policies.update',
|
|
53
|
+
schema: rawBodySchema,
|
|
54
|
+
mapInput: ({ parsed }) => parsed,
|
|
55
|
+
response: () => ({ ok: true }),
|
|
56
|
+
},
|
|
57
|
+
delete: {
|
|
58
|
+
commandId: 'availability.policies.delete',
|
|
59
|
+
schema: rawBodySchema,
|
|
60
|
+
mapInput: ({ raw, ctx }) => ({
|
|
61
|
+
id: ((raw as Record<string, unknown>).query as Record<string, unknown> | undefined)?.id as string | undefined,
|
|
62
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? undefined,
|
|
63
|
+
tenantId: ctx.auth?.tenantId ?? undefined,
|
|
64
|
+
}),
|
|
65
|
+
response: () => ({ ok: true }),
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const listQuerySchema = z.object({
|
|
71
|
+
id: z.uuid().optional(),
|
|
72
|
+
page: z.coerce.number().min(1).default(1),
|
|
73
|
+
pageSize: z.coerce.number().min(1).max(100).default(50),
|
|
74
|
+
storeId: z.uuid().optional(),
|
|
75
|
+
productId: z.uuid().optional(),
|
|
76
|
+
variantId: z.uuid().optional(),
|
|
77
|
+
isActive: z.enum(['true', 'false']).optional(),
|
|
78
|
+
sortField: z.enum(['createdAt', 'updatedAt']).optional(),
|
|
79
|
+
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
80
|
+
}).loose()
|
|
81
|
+
|
|
82
|
+
type AvailabilityPolicyRow = {
|
|
83
|
+
id: string
|
|
84
|
+
organizationId: string
|
|
85
|
+
tenantId: string
|
|
86
|
+
storeId: string | null
|
|
87
|
+
productId: string | null
|
|
88
|
+
variantId: string | null
|
|
89
|
+
isStockManaged: boolean
|
|
90
|
+
allowBackorder: boolean
|
|
91
|
+
backorderLeadTimeDays: number | null
|
|
92
|
+
preorderReleaseAt: string | null
|
|
93
|
+
lowStockThreshold: number | null
|
|
94
|
+
minOrderQuantity: number | null
|
|
95
|
+
maxOrderQuantity: number | null
|
|
96
|
+
quantityIncrement: number | null
|
|
97
|
+
hideWhenOutOfStock: boolean
|
|
98
|
+
isActive: boolean
|
|
99
|
+
createdAt: string | null
|
|
100
|
+
updatedAt: string | null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const toRow = (policy: AvailabilityPolicy): AvailabilityPolicyRow => ({
|
|
104
|
+
id: String(policy.id),
|
|
105
|
+
organizationId: String(policy.organizationId),
|
|
106
|
+
tenantId: String(policy.tenantId),
|
|
107
|
+
storeId: policy.storeId ?? null,
|
|
108
|
+
productId: policy.productId ?? null,
|
|
109
|
+
variantId: policy.variantId ?? null,
|
|
110
|
+
isStockManaged: !!policy.isStockManaged,
|
|
111
|
+
allowBackorder: !!policy.allowBackorder,
|
|
112
|
+
backorderLeadTimeDays: policy.backorderLeadTimeDays ?? null,
|
|
113
|
+
preorderReleaseAt: policy.preorderReleaseAt ? policy.preorderReleaseAt.toISOString() : null,
|
|
114
|
+
lowStockThreshold: policy.lowStockThreshold ?? null,
|
|
115
|
+
minOrderQuantity: policy.minOrderQuantity ?? null,
|
|
116
|
+
maxOrderQuantity: policy.maxOrderQuantity ?? null,
|
|
117
|
+
quantityIncrement: policy.quantityIncrement ?? null,
|
|
118
|
+
hideWhenOutOfStock: !!policy.hideWhenOutOfStock,
|
|
119
|
+
isActive: !!policy.isActive,
|
|
120
|
+
createdAt: policy.createdAt ? policy.createdAt.toISOString() : null,
|
|
121
|
+
updatedAt: policy.updatedAt ? policy.updatedAt.toISOString() : null,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
export async function GET(req: Request) {
|
|
125
|
+
const auth = await getAuthFromRequest(req)
|
|
126
|
+
if (!auth || !auth.tenantId) {
|
|
127
|
+
return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 401 })
|
|
128
|
+
}
|
|
129
|
+
const organizationId = resolveActiveOrganizationId(auth)
|
|
130
|
+
// A superadmin with no organization selected legitimately sees every
|
|
131
|
+
// organization in the tenant (om_selected_org=__all__); anyone else with
|
|
132
|
+
// an unresolved scope gets the standard 400, never a 401 (that reads as an
|
|
133
|
+
// expired session to the client and redirect-loops through session
|
|
134
|
+
// refresh — see organizationScope.ts).
|
|
135
|
+
if (!organizationId && !auth.isSuperAdmin) return organizationScopeRequiredResponse()
|
|
136
|
+
|
|
137
|
+
const url = new URL(req.url)
|
|
138
|
+
const parsed = listQuerySchema.safeParse({
|
|
139
|
+
id: url.searchParams.get('id') ?? undefined,
|
|
140
|
+
page: url.searchParams.get('page') ?? undefined,
|
|
141
|
+
pageSize: url.searchParams.get('pageSize') ?? undefined,
|
|
142
|
+
storeId: url.searchParams.get('storeId') ?? undefined,
|
|
143
|
+
productId: url.searchParams.get('productId') ?? undefined,
|
|
144
|
+
variantId: url.searchParams.get('variantId') ?? undefined,
|
|
145
|
+
isActive: url.searchParams.get('isActive') ?? undefined,
|
|
146
|
+
sortField: url.searchParams.get('sortField') ?? undefined,
|
|
147
|
+
sortDir: url.searchParams.get('sortDir') ?? undefined,
|
|
148
|
+
})
|
|
149
|
+
if (!parsed.success) {
|
|
150
|
+
return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 400 })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const container = await createRequestContainer()
|
|
154
|
+
const em = container.resolve('em') as EntityManager
|
|
155
|
+
|
|
156
|
+
const { id, page, pageSize, storeId, productId, variantId, isActive, sortField, sortDir } = parsed.data
|
|
157
|
+
const filter: FilterQuery<AvailabilityPolicy> = {
|
|
158
|
+
tenantId: auth.tenantId,
|
|
159
|
+
deletedAt: null,
|
|
160
|
+
}
|
|
161
|
+
if (organizationId) filter.organizationId = organizationId
|
|
162
|
+
if (id) filter.id = id
|
|
163
|
+
if (storeId) filter.storeId = storeId
|
|
164
|
+
if (productId) filter.productId = productId
|
|
165
|
+
if (variantId) filter.variantId = variantId
|
|
166
|
+
if (isActive === 'true') filter.isActive = true
|
|
167
|
+
if (isActive === 'false') filter.isActive = false
|
|
168
|
+
|
|
169
|
+
const orderBy: Record<string, 'ASC' | 'DESC'> = {}
|
|
170
|
+
orderBy[sortField ?? 'createdAt'] = sortDir === 'asc' ? 'ASC' : 'DESC'
|
|
171
|
+
|
|
172
|
+
const offset = (page - 1) * pageSize
|
|
173
|
+
const [rows, total] = await em.findAndCount(AvailabilityPolicy, filter, { orderBy, limit: pageSize, offset })
|
|
174
|
+
const items = rows.map(toRow)
|
|
175
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize))
|
|
176
|
+
|
|
177
|
+
return NextResponse.json({ items, total, page, pageSize, totalPages })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const POST = crud.POST
|
|
181
|
+
export const PUT = crud.PUT
|
|
182
|
+
export const DELETE = crud.DELETE
|
|
183
|
+
|
|
184
|
+
const availabilityPolicyListItemSchema = z.object({
|
|
185
|
+
id: z.uuid(),
|
|
186
|
+
organizationId: z.uuid(),
|
|
187
|
+
tenantId: z.uuid(),
|
|
188
|
+
storeId: z.uuid().nullable(),
|
|
189
|
+
productId: z.uuid().nullable(),
|
|
190
|
+
variantId: z.uuid().nullable(),
|
|
191
|
+
isStockManaged: z.boolean(),
|
|
192
|
+
allowBackorder: z.boolean(),
|
|
193
|
+
backorderLeadTimeDays: z.number().nullable(),
|
|
194
|
+
preorderReleaseAt: z.string().nullable(),
|
|
195
|
+
lowStockThreshold: z.number().nullable(),
|
|
196
|
+
minOrderQuantity: z.number().nullable(),
|
|
197
|
+
maxOrderQuantity: z.number().nullable(),
|
|
198
|
+
quantityIncrement: z.number().nullable(),
|
|
199
|
+
hideWhenOutOfStock: z.boolean(),
|
|
200
|
+
isActive: z.boolean(),
|
|
201
|
+
createdAt: z.string().nullable(),
|
|
202
|
+
updatedAt: z.string().nullable(),
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
export const openApi = createAvailabilityCrudOpenApi({
|
|
206
|
+
resourceName: 'AvailabilityPolicy',
|
|
207
|
+
pluralName: 'AvailabilityPolicies',
|
|
208
|
+
querySchema: listQuerySchema,
|
|
209
|
+
listResponseSchema: createPagedListResponseSchema(availabilityPolicyListItemSchema),
|
|
210
|
+
create: {
|
|
211
|
+
schema: availabilityPolicyCreateSchema,
|
|
212
|
+
description: 'Creates a new availability policy.',
|
|
213
|
+
},
|
|
214
|
+
update: {
|
|
215
|
+
schema: availabilityPolicyUpdateSchema,
|
|
216
|
+
responseSchema: defaultOkResponseSchema,
|
|
217
|
+
description: 'Updates an existing availability policy by id.',
|
|
218
|
+
},
|
|
219
|
+
del: {
|
|
220
|
+
schema: z.object({ id: z.string().uuid() }),
|
|
221
|
+
responseSchema: defaultOkResponseSchema,
|
|
222
|
+
description: 'Deletes an availability policy by id.',
|
|
223
|
+
},
|
|
224
|
+
})
|