@open-mercato/core 0.10.1-develop.7709.1.046124599c → 0.10.1-develop.7714.1.9dbb8200d8
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/asn/index.js +19 -0
- package/dist/generated/entities/asn/index.js.map +7 -0
- package/dist/generated/entities/putaway_task/index.js +25 -0
- package/dist/generated/entities/putaway_task/index.js.map +7 -0
- package/dist/generated/entities/receiving_line/index.js +21 -0
- package/dist/generated/entities/receiving_line/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +4 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +34 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js +22 -3
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +2 -2
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +40 -11
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +13 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
- package/dist/modules/wms/acl.js +2 -0
- package/dist/modules/wms/acl.js.map +2 -2
- package/dist/modules/wms/api/asns/[id]/complete/route.js +61 -0
- package/dist/modules/wms/api/asns/[id]/complete/route.js.map +7 -0
- package/dist/modules/wms/api/asns/[id]/receive/route.js +68 -0
- package/dist/modules/wms/api/asns/[id]/receive/route.js.map +7 -0
- package/dist/modules/wms/api/asns/route.js +197 -0
- package/dist/modules/wms/api/asns/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/helpers.js +42 -34
- package/dist/modules/wms/api/inventory/helpers.js.map +2 -2
- package/dist/modules/wms/api/inventory/receive/route.js +1 -1
- package/dist/modules/wms/api/inventory/receive/route.js.map +2 -2
- package/dist/modules/wms/api/listEnrichers.js +23 -0
- package/dist/modules/wms/api/listEnrichers.js.map +2 -2
- package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js +56 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js.map +7 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js +56 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js.map +7 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js +65 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js.map +7 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js +56 -0
- package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js.map +7 -0
- package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js +51 -0
- package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js.map +7 -0
- package/dist/modules/wms/api/putaway-tasks/route.js +192 -0
- package/dist/modules/wms/api/putaway-tasks/route.js.map +7 -0
- package/dist/modules/wms/api/receiving-lines/route.js +180 -0
- package/dist/modules/wms/api/receiving-lines/route.js.map +7 -0
- package/dist/modules/wms/api/scan/putaway/route.js +104 -0
- package/dist/modules/wms/api/scan/putaway/route.js.map +7 -0
- package/dist/modules/wms/api/scan/receive/route.js +109 -0
- package/dist/modules/wms/api/scan/receive/route.js.map +7 -0
- package/dist/modules/wms/api/scan/resolve-location/route.js +89 -0
- package/dist/modules/wms/api/scan/resolve-location/route.js.map +7 -0
- package/dist/modules/wms/api/scan/resolve-lot/route.js +89 -0
- package/dist/modules/wms/api/scan/resolve-lot/route.js.map +7 -0
- package/dist/modules/wms/backend/wms/asns/[id]/page.js +9 -0
- package/dist/modules/wms/backend/wms/asns/[id]/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/asns/page.js +9 -0
- package/dist/modules/wms/backend/wms/asns/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/asns/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/asns/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/putaway/page.js +9 -0
- package/dist/modules/wms/backend/wms/putaway/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/putaway/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/putaway/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/receiving/page.js +8 -0
- package/dist/modules/wms/backend/wms/receiving/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/receiving/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/receiving/page.meta.js.map +7 -0
- package/dist/modules/wms/ce.js +27 -0
- package/dist/modules/wms/ce.js.map +2 -2
- package/dist/modules/wms/commands/asn.js +1746 -0
- package/dist/modules/wms/commands/asn.js.map +7 -0
- package/dist/modules/wms/commands/index.js +2 -0
- package/dist/modules/wms/commands/index.js.map +2 -2
- package/dist/modules/wms/commands/inventory-actions.js +347 -243
- package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
- package/dist/modules/wms/commands/putaway.js +945 -0
- package/dist/modules/wms/commands/putaway.js.map +7 -0
- package/dist/modules/wms/commands/shared.js +51 -0
- package/dist/modules/wms/commands/shared.js.map +2 -2
- package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js +278 -0
- package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js +327 -0
- package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/CreateAsnDialog.js +404 -0
- package/dist/modules/wms/components/backend/CreateAsnDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/InventoryOperationsSection.js +16 -3
- package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +2 -2
- package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js +476 -0
- package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +2 -2
- package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsAsnDetailPage.js +524 -0
- package/dist/modules/wms/components/backend/WmsAsnDetailPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsAsnsListPage.js +339 -0
- package/dist/modules/wms/components/backend/WmsAsnsListPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +5 -6
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js +5 -6
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +14 -10
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js +598 -0
- package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +5 -6
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
- package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js +32 -0
- package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js.map +7 -0
- package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js +36 -0
- package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js.map +7 -0
- package/dist/modules/wms/components/backend/inboundStatusUi.js +106 -0
- package/dist/modules/wms/components/backend/inboundStatusUi.js.map +7 -0
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +17 -0
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
- package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js +14 -0
- package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js.map +7 -0
- package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +4 -0
- package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +2 -2
- package/dist/modules/wms/data/enrichers.js +117 -12
- package/dist/modules/wms/data/enrichers.js.map +2 -2
- package/dist/modules/wms/data/entities.js +150 -1
- package/dist/modules/wms/data/entities.js.map +2 -2
- package/dist/modules/wms/data/validators.js +214 -0
- package/dist/modules/wms/data/validators.js.map +2 -2
- package/dist/modules/wms/events.js +12 -1
- package/dist/modules/wms/events.js.map +2 -2
- package/dist/modules/wms/extension-points.js +3 -1
- package/dist/modules/wms/extension-points.js.map +2 -2
- package/dist/modules/wms/lib/asnReceiving.js +124 -0
- package/dist/modules/wms/lib/asnReceiving.js.map +7 -0
- package/dist/modules/wms/lib/asnVendor.js +52 -0
- package/dist/modules/wms/lib/asnVendor.js.map +7 -0
- package/dist/modules/wms/lib/flashMutationError.js +4 -0
- package/dist/modules/wms/lib/flashMutationError.js.map +2 -2
- package/dist/modules/wms/lib/inventoryDisplayUi.js +24 -0
- package/dist/modules/wms/lib/inventoryDisplayUi.js.map +2 -2
- package/dist/modules/wms/lib/inventoryPolicy.js +17 -0
- package/dist/modules/wms/lib/inventoryPolicy.js.map +2 -2
- package/dist/modules/wms/lib/loadOperationalDashboard.js +67 -1
- package/dist/modules/wms/lib/loadOperationalDashboard.js.map +2 -2
- package/dist/modules/wms/lib/putaway.js +141 -0
- package/dist/modules/wms/lib/putaway.js.map +7 -0
- package/dist/modules/wms/lib/roleFeatures.js +3 -1
- package/dist/modules/wms/lib/roleFeatures.js.map +2 -2
- package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +99 -1
- package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +2 -2
- package/dist/modules/wms/lib/scanResolve.js +147 -0
- package/dist/modules/wms/lib/scanResolve.js.map +7 -0
- package/dist/modules/wms/lib/wmsIntegrationToggles.js +1 -1
- package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +2 -2
- package/dist/modules/wms/migrations/Migration20260812120000.js +43 -0
- package/dist/modules/wms/migrations/Migration20260812120000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260812130000.js +30 -0
- package/dist/modules/wms/migrations/Migration20260812130000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260822120000.js +17 -0
- package/dist/modules/wms/migrations/Migration20260822120000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260822130000.js +17 -0
- package/dist/modules/wms/migrations/Migration20260822130000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20261029120000.js +13 -0
- package/dist/modules/wms/migrations/Migration20261029120000.js.map +7 -0
- package/dist/modules/wms/search.js +85 -0
- package/dist/modules/wms/search.js.map +2 -2
- package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js +14 -0
- package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js.map +7 -0
- package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js +12 -0
- package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js.map +7 -0
- package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js +163 -0
- package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js.map +7 -0
- package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js +14 -0
- package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js.map +7 -0
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +37 -8
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +2 -2
- package/generated/entities/asn/index.ts +8 -0
- package/generated/entities/putaway_task/index.ts +11 -0
- package/generated/entities/receiving_line/index.ts +9 -0
- package/generated/entities.ids.generated.ts +4 -1
- package/generated/entity-fields-registry.ts +34 -0
- package/package.json +7 -7
- package/src/modules/auth/i18n/de.json +2 -0
- package/src/modules/auth/i18n/en.json +2 -0
- package/src/modules/auth/i18n/es.json +2 -0
- package/src/modules/auth/i18n/ko.json +2 -0
- package/src/modules/auth/i18n/pl.json +2 -0
- package/src/modules/communication_channels/i18n/de.json +2 -0
- package/src/modules/communication_channels/i18n/en.json +2 -0
- package/src/modules/communication_channels/i18n/es.json +2 -0
- package/src/modules/communication_channels/i18n/ko.json +2 -0
- package/src/modules/communication_channels/i18n/pl.json +2 -0
- package/src/modules/communication_channels/lib/sanitize-channel-html.ts +23 -3
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +44 -12
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +16 -2
- package/src/modules/wms/acl.ts +2 -0
- package/src/modules/wms/api/asns/[id]/complete/route.ts +62 -0
- package/src/modules/wms/api/asns/[id]/receive/route.ts +74 -0
- package/src/modules/wms/api/asns/route.ts +200 -0
- package/src/modules/wms/api/inventory/helpers.ts +57 -34
- package/src/modules/wms/api/inventory/receive/route.ts +2 -1
- package/src/modules/wms/api/listEnrichers.ts +38 -0
- package/src/modules/wms/api/putaway-tasks/[id]/assign/route.ts +56 -0
- package/src/modules/wms/api/putaway-tasks/[id]/cancel/route.ts +56 -0
- package/src/modules/wms/api/putaway-tasks/[id]/complete/route.ts +66 -0
- package/src/modules/wms/api/putaway-tasks/[id]/start/route.ts +56 -0
- package/src/modules/wms/api/putaway-tasks/create-from-balance/route.ts +50 -0
- package/src/modules/wms/api/putaway-tasks/route.ts +199 -0
- package/src/modules/wms/api/receiving-lines/route.ts +180 -0
- package/src/modules/wms/api/scan/putaway/route.ts +108 -0
- package/src/modules/wms/api/scan/receive/route.ts +118 -0
- package/src/modules/wms/api/scan/resolve-location/route.ts +92 -0
- package/src/modules/wms/api/scan/resolve-lot/route.ts +92 -0
- package/src/modules/wms/backend/wms/asns/[id]/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/asns/[id]/page.tsx +5 -0
- package/src/modules/wms/backend/wms/asns/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/asns/page.tsx +5 -0
- package/src/modules/wms/backend/wms/putaway/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/putaway/page.tsx +5 -0
- package/src/modules/wms/backend/wms/receiving/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/receiving/page.tsx +5 -0
- package/src/modules/wms/ce.ts +27 -0
- package/src/modules/wms/commands/asn.ts +2122 -0
- package/src/modules/wms/commands/index.ts +2 -0
- package/src/modules/wms/commands/inventory-actions.ts +413 -243
- package/src/modules/wms/commands/putaway.ts +1169 -0
- package/src/modules/wms/commands/shared.ts +51 -0
- package/src/modules/wms/components/backend/AssignPutawayTaskDialog.tsx +328 -0
- package/src/modules/wms/components/backend/CompletePutawayTaskDialog.tsx +366 -0
- package/src/modules/wms/components/backend/CreateAsnDialog.tsx +427 -0
- package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +20 -2
- package/src/modules/wms/components/backend/ReceiveAsnLineDialog.tsx +535 -0
- package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +2 -2
- package/src/modules/wms/components/backend/WmsAsnDetailPage.tsx +647 -0
- package/src/modules/wms/components/backend/WmsAsnsListPage.tsx +407 -0
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -5
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -5
- package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +12 -8
- package/src/modules/wms/components/backend/WmsPutawayQueuePage.tsx +677 -0
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -5
- package/src/modules/wms/components/backend/asnCompleteOptimisticLock.ts +52 -0
- package/src/modules/wms/components/backend/asnReceivingLinesLoader.ts +83 -0
- package/src/modules/wms/components/backend/inboundStatusUi.ts +154 -0
- package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +20 -0
- package/src/modules/wms/components/backend/putawayQueueOptimisticLock.ts +20 -0
- package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +4 -0
- package/src/modules/wms/data/enrichers.ts +175 -13
- package/src/modules/wms/data/entities.ts +155 -0
- package/src/modules/wms/data/validators.ts +256 -0
- package/src/modules/wms/events.ts +11 -0
- package/src/modules/wms/extension-points.ts +2 -0
- package/src/modules/wms/i18n/de.json +205 -3
- package/src/modules/wms/i18n/en.json +205 -3
- package/src/modules/wms/i18n/es.json +205 -3
- package/src/modules/wms/i18n/ko.json +205 -3
- package/src/modules/wms/i18n/pl.json +205 -3
- package/src/modules/wms/lib/asnReceiving.ts +226 -0
- package/src/modules/wms/lib/asnVendor.ts +80 -0
- package/src/modules/wms/lib/flashMutationError.ts +4 -0
- package/src/modules/wms/lib/inventoryDisplayUi.ts +46 -0
- package/src/modules/wms/lib/inventoryPolicy.ts +31 -1
- package/src/modules/wms/lib/loadOperationalDashboard.ts +89 -1
- package/src/modules/wms/lib/putaway.ts +210 -0
- package/src/modules/wms/lib/roleFeatures.ts +2 -0
- package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +150 -1
- package/src/modules/wms/lib/scanResolve.ts +185 -0
- package/src/modules/wms/lib/wmsIntegrationToggles.ts +2 -1
- package/src/modules/wms/migrations/.snapshot-open-mercato.json +1000 -0
- package/src/modules/wms/migrations/Migration20260812120000.ts +43 -0
- package/src/modules/wms/migrations/Migration20260812130000.ts +28 -0
- package/src/modules/wms/migrations/Migration20260822120000.ts +17 -0
- package/src/modules/wms/migrations/Migration20260822130000.ts +15 -0
- package/src/modules/wms/migrations/Migration20261029120000.ts +11 -0
- package/src/modules/wms/search.ts +95 -0
- package/src/modules/wms/subscribers/asn-line-received-reevaluate-reservations.ts +21 -0
- package/src/modules/wms/subscribers/invalidate-enricher-cache-asn.ts +12 -0
- package/src/modules/wms/subscribers/procurement-goods-receipt-created.ts +238 -0
- package/src/modules/wms/subscribers/putaway-completed-reevaluate-reservations.ts +21 -0
- package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +63 -13
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
4
|
+
import { asnCloseSchema } from '../../../../data/validators'
|
|
5
|
+
import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
POST: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const paramsSchema = z.object({
|
|
12
|
+
id: z.string().uuid(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export async function POST(request: Request, routeContext: { params: { id: string } }) {
|
|
16
|
+
const parsedParams = paramsSchema.parse(routeContext.params)
|
|
17
|
+
const body = await readJsonSafe<Record<string, unknown>>(request, {})
|
|
18
|
+
const scopedRequest = new Request(request.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
body: JSON.stringify({ ...body, id: parsedParams.id }),
|
|
22
|
+
})
|
|
23
|
+
return executeWmsCustomPostRoute({
|
|
24
|
+
request: scopedRequest,
|
|
25
|
+
routePath: `wms/asns/${parsedParams.id}/complete`,
|
|
26
|
+
inputSchema: asnCloseSchema,
|
|
27
|
+
commandId: 'wms.asns.close',
|
|
28
|
+
describeResource: (input) => ({
|
|
29
|
+
resourceKind: 'wms.asn',
|
|
30
|
+
resourceId: input.id,
|
|
31
|
+
}),
|
|
32
|
+
mapSuccess: (result: { status: string }) => ({
|
|
33
|
+
ok: true,
|
|
34
|
+
status: result.status,
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const successSchema = z.object({
|
|
40
|
+
ok: z.literal(true),
|
|
41
|
+
status: z.enum(['draft', 'in_transit', 'received', 'closed']),
|
|
42
|
+
})
|
|
43
|
+
const errorSchema = z.object({ error: z.string() })
|
|
44
|
+
|
|
45
|
+
export const openApi: OpenApiRouteDoc = {
|
|
46
|
+
tag: 'WMS',
|
|
47
|
+
summary: 'Complete ASN',
|
|
48
|
+
methods: {
|
|
49
|
+
POST: {
|
|
50
|
+
summary: 'Complete ASN',
|
|
51
|
+
description:
|
|
52
|
+
'Marks an ASN as received when it has receipt activity (at least one line with receivedQty > 0) and lines are complete (QC-passed accepted qty meets expected), or when closeWhenShort is true for short/QC-fail receipts after some receipt activity. Header-only ASNs and zero-receipt lines are rejected even with closeWhenShort.',
|
|
53
|
+
requestBody: { contentType: 'application/json', schema: asnCloseSchema },
|
|
54
|
+
responses: [{ status: 200, description: 'ASN completed', schema: successSchema }],
|
|
55
|
+
errors: [
|
|
56
|
+
{ status: 400, description: 'Validation failed', schema: errorSchema },
|
|
57
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
58
|
+
{ status: 409, description: 'Invalid receipt state', schema: errorSchema },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
4
|
+
import { asnReceiveLineSchema } from '../../../../data/validators'
|
|
5
|
+
import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
POST: { requireAuth: true, requireFeatures: ['wms.receive_inventory'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const paramsSchema = z.object({
|
|
12
|
+
id: z.string().uuid(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export async function POST(request: Request, routeContext: { params: { id: string } }) {
|
|
16
|
+
const parsedParams = paramsSchema.parse(routeContext.params)
|
|
17
|
+
const body = await readJsonSafe<Record<string, unknown>>(request, {})
|
|
18
|
+
const scopedRequest = new Request(request.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
body: JSON.stringify({ ...body, asnId: parsedParams.id }),
|
|
22
|
+
})
|
|
23
|
+
return executeWmsCustomPostRoute({
|
|
24
|
+
request: scopedRequest,
|
|
25
|
+
routePath: `wms/asns/${parsedParams.id}/receive`,
|
|
26
|
+
inputSchema: asnReceiveLineSchema,
|
|
27
|
+
commandId: 'wms.asns.receive-line',
|
|
28
|
+
describeResource: (input) => ({
|
|
29
|
+
resourceKind: 'wms.asn',
|
|
30
|
+
resourceId: input.asnId,
|
|
31
|
+
}),
|
|
32
|
+
mapSuccess: (result: {
|
|
33
|
+
movementIds: string[]
|
|
34
|
+
putawayTaskIds: string[]
|
|
35
|
+
receivedQty?: number
|
|
36
|
+
asnUpdatedAt?: string
|
|
37
|
+
}) => ({
|
|
38
|
+
ok: true,
|
|
39
|
+
movementIds: result.movementIds,
|
|
40
|
+
putawayTaskIds: result.putawayTaskIds,
|
|
41
|
+
receivedQty: result.receivedQty,
|
|
42
|
+
asnUpdatedAt: result.asnUpdatedAt,
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const successSchema = z.object({
|
|
48
|
+
ok: z.literal(true),
|
|
49
|
+
movementIds: z.array(z.string().uuid()),
|
|
50
|
+
putawayTaskIds: z.array(z.string().uuid()),
|
|
51
|
+
receivedQty: z.number().optional(),
|
|
52
|
+
asnUpdatedAt: z.string().datetime().optional(),
|
|
53
|
+
})
|
|
54
|
+
const errorSchema = z.object({ error: z.string() })
|
|
55
|
+
|
|
56
|
+
export const openApi: OpenApiRouteDoc = {
|
|
57
|
+
tag: 'WMS',
|
|
58
|
+
summary: 'Receive ASN line',
|
|
59
|
+
methods: {
|
|
60
|
+
POST: {
|
|
61
|
+
summary: 'Receive ASN line',
|
|
62
|
+
description:
|
|
63
|
+
'Receives a line against an ASN with QC outcome. QC pass writes receipt stock at staging/dock; QC fail records audit without balance increase. Both require absolute targetReceivedQty for retry-safe attempts (optional idempotencyKey is an extra stabilizer).',
|
|
64
|
+
requestBody: { contentType: 'application/json', schema: asnReceiveLineSchema },
|
|
65
|
+
responses: [{ status: 200, description: 'ASN line received', schema: successSchema }],
|
|
66
|
+
errors: [
|
|
67
|
+
{ status: 400, description: 'Validation failed', schema: errorSchema },
|
|
68
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
69
|
+
{ status: 409, description: 'Invalid receipt state', schema: errorSchema },
|
|
70
|
+
{ status: 422, description: 'Tracking or QC transition invalid', schema: errorSchema },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
3
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
4
|
+
import { parseScopedCommandInput, resolveCrudRecordId } from '@open-mercato/shared/lib/api/scoped'
|
|
5
|
+
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
6
|
+
import { E } from '#generated/entities.ids.generated'
|
|
7
|
+
import { Asn } from '../../data/entities'
|
|
8
|
+
import { asnCreatePublicSchema, asnUpdateSchema } from '../../data/validators'
|
|
9
|
+
import { buildAsnStatusFilter } from '../../lib/asnReceiving'
|
|
10
|
+
import { attachVendorLabelsToListItems, attachWarehouseLabelsToListItems } from '../listEnrichers'
|
|
11
|
+
import { createPagedListResponseSchema, createWmsCrudOpenApi, defaultOkResponseSchema } from '../openapi'
|
|
12
|
+
|
|
13
|
+
const F = {
|
|
14
|
+
id: 'id',
|
|
15
|
+
organization_id: 'organization_id',
|
|
16
|
+
tenant_id: 'tenant_id',
|
|
17
|
+
warehouse_id: 'warehouse_id',
|
|
18
|
+
vendor_id: 'vendor_id',
|
|
19
|
+
status: 'status',
|
|
20
|
+
expected_at: 'expected_at',
|
|
21
|
+
reference_number: 'reference_number',
|
|
22
|
+
notes: 'notes',
|
|
23
|
+
created_at: 'created_at',
|
|
24
|
+
updated_at: 'updated_at',
|
|
25
|
+
} as const
|
|
26
|
+
|
|
27
|
+
const routeMetadata = {
|
|
28
|
+
GET: { requireAuth: true, requireFeatures: ['wms.view'] },
|
|
29
|
+
POST: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
|
|
30
|
+
PUT: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
|
|
31
|
+
DELETE: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const metadata = routeMetadata
|
|
35
|
+
|
|
36
|
+
const rawBodySchema = z.object({}).passthrough()
|
|
37
|
+
|
|
38
|
+
const listSchema = z.object({
|
|
39
|
+
page: z.coerce.number().min(1).default(1),
|
|
40
|
+
pageSize: z.coerce.number().min(1).max(100).default(25),
|
|
41
|
+
search: z.string().optional(),
|
|
42
|
+
ids: z.string().optional(),
|
|
43
|
+
warehouseId: z.string().uuid().optional(),
|
|
44
|
+
vendorId: z.string().uuid().optional(),
|
|
45
|
+
// Single status or comma-separated (e.g. draft,in_transit for open receiving queue).
|
|
46
|
+
status: z.string().optional(),
|
|
47
|
+
sortField: z.string().optional(),
|
|
48
|
+
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
49
|
+
}).passthrough()
|
|
50
|
+
|
|
51
|
+
const crud = makeCrudRoute({
|
|
52
|
+
metadata: routeMetadata,
|
|
53
|
+
orm: {
|
|
54
|
+
entity: Asn,
|
|
55
|
+
idField: 'id',
|
|
56
|
+
orgField: 'organizationId',
|
|
57
|
+
tenantField: 'tenantId',
|
|
58
|
+
softDeleteField: 'deletedAt',
|
|
59
|
+
},
|
|
60
|
+
indexer: { entityType: E.wms.asn },
|
|
61
|
+
list: {
|
|
62
|
+
schema: listSchema,
|
|
63
|
+
disableListCache: true,
|
|
64
|
+
entityId: E.wms.asn,
|
|
65
|
+
fields: [
|
|
66
|
+
F.id,
|
|
67
|
+
F.organization_id,
|
|
68
|
+
F.tenant_id,
|
|
69
|
+
F.warehouse_id,
|
|
70
|
+
F.vendor_id,
|
|
71
|
+
F.status,
|
|
72
|
+
F.expected_at,
|
|
73
|
+
F.reference_number,
|
|
74
|
+
F.notes,
|
|
75
|
+
F.created_at,
|
|
76
|
+
F.updated_at,
|
|
77
|
+
],
|
|
78
|
+
sortFieldMap: {
|
|
79
|
+
status: F.status,
|
|
80
|
+
expectedAt: F.expected_at,
|
|
81
|
+
createdAt: F.created_at,
|
|
82
|
+
updatedAt: F.updated_at,
|
|
83
|
+
referenceNumber: F.reference_number,
|
|
84
|
+
},
|
|
85
|
+
buildFilters: async (query) => {
|
|
86
|
+
const filters: Record<string, unknown> = {}
|
|
87
|
+
if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
|
|
88
|
+
filters[F.id] = {
|
|
89
|
+
$in: query.ids.split(',').map((value) => value.trim()).filter((value) => value.length > 0),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (typeof query.warehouseId === 'string' && query.warehouseId.length > 0) {
|
|
93
|
+
filters[F.warehouse_id] = { $eq: query.warehouseId }
|
|
94
|
+
}
|
|
95
|
+
if (typeof query.vendorId === 'string' && query.vendorId.length > 0) {
|
|
96
|
+
filters[F.vendor_id] = { $eq: query.vendorId }
|
|
97
|
+
}
|
|
98
|
+
if (typeof query.status === 'string' && query.status.length > 0) {
|
|
99
|
+
const statusFilter = buildAsnStatusFilter(query.status)
|
|
100
|
+
if (statusFilter) filters[F.status] = statusFilter
|
|
101
|
+
}
|
|
102
|
+
const term = query.search?.trim()
|
|
103
|
+
if (term) {
|
|
104
|
+
const like = `%${escapeLikePattern(term)}%`
|
|
105
|
+
filters.$or = [
|
|
106
|
+
{ [F.reference_number]: { $ilike: like } },
|
|
107
|
+
{ [F.notes]: { $ilike: like } },
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
return filters
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
hooks: {
|
|
114
|
+
afterList: async (payload, ctx) => {
|
|
115
|
+
await Promise.all([
|
|
116
|
+
attachWarehouseLabelsToListItems(payload, ctx),
|
|
117
|
+
attachVendorLabelsToListItems(payload, ctx),
|
|
118
|
+
])
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
actions: {
|
|
122
|
+
create: {
|
|
123
|
+
commandId: 'wms.asns.create',
|
|
124
|
+
schema: rawBodySchema,
|
|
125
|
+
mapInput: async ({ raw, ctx }) => {
|
|
126
|
+
const { translate } = await resolveTranslations()
|
|
127
|
+
// Public create strips sourceKey — procurement/system sets it via command with auth: null.
|
|
128
|
+
return parseScopedCommandInput(asnCreatePublicSchema, raw ?? {}, ctx, translate)
|
|
129
|
+
},
|
|
130
|
+
response: ({ result }) => ({
|
|
131
|
+
id: result?.asnId ?? null,
|
|
132
|
+
lineIds: result?.lineIds ?? [],
|
|
133
|
+
}),
|
|
134
|
+
status: 201,
|
|
135
|
+
},
|
|
136
|
+
update: {
|
|
137
|
+
commandId: 'wms.asns.update',
|
|
138
|
+
schema: rawBodySchema,
|
|
139
|
+
mapInput: async ({ raw, ctx }) => {
|
|
140
|
+
const { translate } = await resolveTranslations()
|
|
141
|
+
return parseScopedCommandInput(asnUpdateSchema, raw ?? {}, ctx, translate)
|
|
142
|
+
},
|
|
143
|
+
response: () => ({ ok: true }),
|
|
144
|
+
},
|
|
145
|
+
delete: {
|
|
146
|
+
commandId: 'wms.asns.delete',
|
|
147
|
+
schema: rawBodySchema,
|
|
148
|
+
mapInput: async ({ parsed, ctx }) => {
|
|
149
|
+
const { translate } = await resolveTranslations()
|
|
150
|
+
return { id: resolveCrudRecordId(parsed, ctx, translate) }
|
|
151
|
+
},
|
|
152
|
+
response: () => ({ ok: true }),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
export const GET = crud.GET
|
|
158
|
+
export const POST = crud.POST
|
|
159
|
+
export const PUT = crud.PUT
|
|
160
|
+
export const DELETE = crud.DELETE
|
|
161
|
+
|
|
162
|
+
const asnListItemSchema = z.object({
|
|
163
|
+
id: z.string().uuid().nullable().optional(),
|
|
164
|
+
organization_id: z.string().uuid().nullable().optional(),
|
|
165
|
+
tenant_id: z.string().uuid().nullable().optional(),
|
|
166
|
+
warehouse_id: z.string().uuid().nullable().optional(),
|
|
167
|
+
warehouse_name: z.string().nullable().optional(),
|
|
168
|
+
warehouse_code: z.string().nullable().optional(),
|
|
169
|
+
vendor_id: z.string().uuid().nullable().optional(),
|
|
170
|
+
vendor_name: z.string().nullable().optional(),
|
|
171
|
+
status: z.string().nullable().optional(),
|
|
172
|
+
expected_at: z.string().nullable().optional(),
|
|
173
|
+
reference_number: z.string().nullable().optional(),
|
|
174
|
+
notes: z.string().nullable().optional(),
|
|
175
|
+
created_at: z.string().nullable().optional(),
|
|
176
|
+
updated_at: z.string().nullable().optional(),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
export const openApi = createWmsCrudOpenApi({
|
|
180
|
+
resourceName: 'ASN',
|
|
181
|
+
pluralName: 'ASNs',
|
|
182
|
+
querySchema: listSchema,
|
|
183
|
+
listResponseSchema: createPagedListResponseSchema(asnListItemSchema),
|
|
184
|
+
create: {
|
|
185
|
+
schema: asnCreatePublicSchema,
|
|
186
|
+
description:
|
|
187
|
+
'Creates an ASN with optional expected receiving lines. `sourceKey` is server-only (procurement/system path) and is not accepted on this public API.',
|
|
188
|
+
},
|
|
189
|
+
update: {
|
|
190
|
+
schema: asnUpdateSchema,
|
|
191
|
+
responseSchema: defaultOkResponseSchema,
|
|
192
|
+
description:
|
|
193
|
+
'Updates an ASN by id while status is draft or in_transit and no receiving line has receipt/QC activity. Received/closed ASNs, or ASNs with line receipt/QC activity, return 409 invalid_receipt_state (status demotion and warehouse/header changes are not allowed). Status may only be set to draft or in_transit; received/closed are set via receive/complete flows.',
|
|
194
|
+
},
|
|
195
|
+
del: {
|
|
196
|
+
schema: z.object({ id: z.string().uuid() }),
|
|
197
|
+
responseSchema: defaultOkResponseSchema,
|
|
198
|
+
description: 'Soft-deletes an ASN by id.',
|
|
199
|
+
},
|
|
200
|
+
})
|
|
@@ -5,13 +5,11 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
6
|
import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
|
|
7
7
|
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
8
|
+
import { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'
|
|
8
9
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
9
10
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
10
11
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
11
|
-
import {
|
|
12
|
-
runCrudMutationGuardAfterSuccess,
|
|
13
|
-
validateCrudMutationGuard,
|
|
14
|
-
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
12
|
+
import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
|
|
15
13
|
import { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'
|
|
16
14
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
17
15
|
|
|
@@ -31,6 +29,27 @@ type ExecuteWmsCustomPostRouteOptions<TInput, TResult> = {
|
|
|
31
29
|
mapSuccess: (result: TResult) => Record<string, unknown>
|
|
32
30
|
}
|
|
33
31
|
|
|
32
|
+
function resolveBodyOrganizationId(body: Record<string, unknown>): string | null {
|
|
33
|
+
const raw = body.organizationId
|
|
34
|
+
if (typeof raw !== 'string') return null
|
|
35
|
+
const trimmed = raw.trim()
|
|
36
|
+
return trimmed.length > 0 ? trimmed : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Shared POST helper for WMS custom write routes.
|
|
41
|
+
*
|
|
42
|
+
* Organization scoping:
|
|
43
|
+
* - When the body includes `organizationId`, pass it through and validate with
|
|
44
|
+
* `ensureOrganizationScope` (allowed org ids / Pattern C). Do not silently
|
|
45
|
+
* overwrite with the session-selected org — that mis-routes multi-org writes.
|
|
46
|
+
* - When the body omits `organizationId`, default to the session selected/auth org.
|
|
47
|
+
* - `tenantId` is always taken from auth (never from the body).
|
|
48
|
+
*
|
|
49
|
+
* Intentional session-only exception: barcode scan resolve/receive/putaway routes
|
|
50
|
+
* force session org onto the request body *before* calling this helper so resolve
|
|
51
|
+
* lookups cannot probe another org. That pre-scope policy stays on those routes.
|
|
52
|
+
*/
|
|
34
53
|
export async function executeWmsCustomPostRoute<TInput, TResult>(
|
|
35
54
|
options: ExecuteWmsCustomPostRouteOptions<TInput, TResult>,
|
|
36
55
|
) {
|
|
@@ -54,41 +73,45 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
|
|
|
54
73
|
organizationIds: organizationScope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
55
74
|
request: options.request,
|
|
56
75
|
}
|
|
57
|
-
const body = await readJsonSafe<Record<string, unknown>>(options.request, {})
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
76
|
+
const body = (await readJsonSafe<Record<string, unknown>>(options.request, {})) ?? {}
|
|
77
|
+
const sessionOrganizationId = ctx.selectedOrganizationId ?? auth.orgId ?? null
|
|
78
|
+
const organizationId = resolveBodyOrganizationId(body) ?? sessionOrganizationId
|
|
79
|
+
if (!organizationId) {
|
|
80
|
+
throw new CrudHttpError(400, { error: 'organization_scope_required' })
|
|
81
|
+
}
|
|
82
|
+
ensureOrganizationScope(ctx, organizationId)
|
|
83
|
+
const scopedBody = {
|
|
84
|
+
...body,
|
|
61
85
|
tenantId: auth.tenantId,
|
|
62
|
-
organizationId
|
|
63
|
-
userId: auth.sub,
|
|
64
|
-
resourceKind: resource.resourceKind,
|
|
65
|
-
resourceId: resource.resourceId,
|
|
66
|
-
operation: 'custom',
|
|
67
|
-
requestMethod: options.request.method,
|
|
68
|
-
requestHeaders: options.request.headers,
|
|
69
|
-
mutationPayload: parsed as Record<string, unknown>,
|
|
70
|
-
})
|
|
71
|
-
if (guardResult && !guardResult.ok) {
|
|
72
|
-
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
86
|
+
organizationId,
|
|
73
87
|
}
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await runCrudMutationGuardAfterSuccess(container, {
|
|
81
|
-
tenantId: auth.tenantId,
|
|
82
|
-
organizationId: ctx.selectedOrganizationId,
|
|
88
|
+
const parsed = options.inputSchema.parse(scopedBody)
|
|
89
|
+
const resource = options.describeResource(parsed)
|
|
90
|
+
const guardResult = await runRouteMutationGuards({
|
|
91
|
+
container,
|
|
92
|
+
req: options.request,
|
|
93
|
+
auth: {
|
|
83
94
|
userId: auth.sub,
|
|
95
|
+
tenantId: auth.tenantId,
|
|
96
|
+
organizationId,
|
|
97
|
+
},
|
|
98
|
+
input: {
|
|
84
99
|
resourceKind: resource.resourceKind,
|
|
85
100
|
resourceId: resource.resourceId,
|
|
86
101
|
operation: 'custom',
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
mutationPayload: parsed as Record<string, unknown>,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
if (!guardResult.ok) {
|
|
106
|
+
return NextResponse.json(guardResult.errorBody, { status: guardResult.errorStatus })
|
|
91
107
|
}
|
|
108
|
+
const commandInput = (guardResult.modifiedPayload as TInput | undefined) ?? parsed
|
|
109
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
110
|
+
const execution = await commandBus.execute<TInput, TResult>(options.commandId, {
|
|
111
|
+
input: commandInput,
|
|
112
|
+
ctx,
|
|
113
|
+
})
|
|
114
|
+
await guardResult.runAfterSuccess()
|
|
92
115
|
const responseBody = options.mapSuccess(execution.result)
|
|
93
116
|
const intercepted = await runCustomRouteAfterInterceptors({
|
|
94
117
|
routePath: options.routePath,
|
|
@@ -96,7 +119,7 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
|
|
|
96
119
|
request: {
|
|
97
120
|
method: 'POST',
|
|
98
121
|
url: options.request.url,
|
|
99
|
-
body:
|
|
122
|
+
body: commandInput as Record<string, unknown>,
|
|
100
123
|
headers: Object.fromEntries(options.request.headers.entries()),
|
|
101
124
|
},
|
|
102
125
|
response: {
|
|
@@ -108,7 +131,7 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
|
|
|
108
131
|
em: container.resolve('em'),
|
|
109
132
|
container,
|
|
110
133
|
userId: auth.sub,
|
|
111
|
-
organizationId
|
|
134
|
+
organizationId,
|
|
112
135
|
tenantId: auth.tenantId,
|
|
113
136
|
},
|
|
114
137
|
})
|
|
@@ -36,7 +36,8 @@ export const openApi: OpenApiRouteDoc = {
|
|
|
36
36
|
methods: {
|
|
37
37
|
POST: {
|
|
38
38
|
summary: 'Receive inventory',
|
|
39
|
-
description:
|
|
39
|
+
description:
|
|
40
|
+
'Records inbound inventory receipt and appends a receipt movement ledger row. When `organizationId` is present it must be in the caller organization scope; otherwise the session-selected organization is used.',
|
|
40
41
|
requestBody: { contentType: 'application/json', schema: inventoryReceiveSchema },
|
|
41
42
|
responses: [{ status: 200, description: 'Inventory received', schema: successSchema }],
|
|
42
43
|
errors: [
|
|
@@ -266,6 +266,44 @@ export async function attachInventoryProfileCatalogLabelsToListItems(
|
|
|
266
266
|
])
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// Customers (ASN vendors)
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
type CustomerLookupRow = LookupRow & { display_name?: string | null }
|
|
274
|
+
|
|
275
|
+
const CUSTOMER_FIELDS = ['id', 'display_name']
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Decorate list items with `vendor_name` when they carry a `vendor_id` column
|
|
279
|
+
* referencing `customers.customer_entity` by UUID only (no ORM cross-module
|
|
280
|
+
* relations). Mutates `payload.items` in place.
|
|
281
|
+
*/
|
|
282
|
+
export async function attachVendorLabelsToListItems(
|
|
283
|
+
payload: AnyListPayload,
|
|
284
|
+
ctx: CrudCtx,
|
|
285
|
+
): Promise<void> {
|
|
286
|
+
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
287
|
+
if (items.length === 0) return
|
|
288
|
+
const ids = uniqueIdsFromKeys(items, ['vendor_id'])
|
|
289
|
+
if (ids.length === 0) return
|
|
290
|
+
const map = (await batchLoadById(
|
|
291
|
+
ctx,
|
|
292
|
+
E.customers.customer_entity,
|
|
293
|
+
ids,
|
|
294
|
+
CUSTOMER_FIELDS,
|
|
295
|
+
'vendor',
|
|
296
|
+
)) as Map<string, CustomerLookupRow>
|
|
297
|
+
if (map.size === 0) return
|
|
298
|
+
for (const item of items) {
|
|
299
|
+
const id = item?.vendor_id
|
|
300
|
+
if (typeof id !== 'string' || !id) continue
|
|
301
|
+
const row = map.get(id)
|
|
302
|
+
if (!row) continue
|
|
303
|
+
decorateOnce(item, 'vendor_name', row.display_name ?? null)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
269
307
|
// ---------------------------------------------------------------------------
|
|
270
308
|
// Reservation sources
|
|
271
309
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
4
|
+
import { putawayTaskAssignSchema } from '../../../../data/validators'
|
|
5
|
+
import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
POST: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const paramsSchema = z.object({
|
|
12
|
+
id: z.string().uuid(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export async function POST(request: Request, routeContext: { params: { id: string } }) {
|
|
16
|
+
const parsedParams = paramsSchema.parse(routeContext.params)
|
|
17
|
+
const body = await readJsonSafe<Record<string, unknown>>(request, {})
|
|
18
|
+
const scopedRequest = new Request(request.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
body: JSON.stringify({ ...body, id: parsedParams.id }),
|
|
22
|
+
})
|
|
23
|
+
return executeWmsCustomPostRoute({
|
|
24
|
+
request: scopedRequest,
|
|
25
|
+
routePath: `wms/putaway-tasks/${parsedParams.id}/assign`,
|
|
26
|
+
inputSchema: putawayTaskAssignSchema,
|
|
27
|
+
commandId: 'wms.putaway-tasks.assign',
|
|
28
|
+
describeResource: (input) => ({
|
|
29
|
+
resourceKind: 'wms.putawayTask',
|
|
30
|
+
resourceId: input.id,
|
|
31
|
+
}),
|
|
32
|
+
mapSuccess: () => ({ ok: true }),
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
37
|
+
const errorSchema = z.object({ error: z.string() })
|
|
38
|
+
|
|
39
|
+
export const openApi: OpenApiRouteDoc = {
|
|
40
|
+
tag: 'WMS',
|
|
41
|
+
summary: 'Assign putaway task',
|
|
42
|
+
methods: {
|
|
43
|
+
POST: {
|
|
44
|
+
summary: 'Assign putaway task',
|
|
45
|
+
description: 'Assigns a putaway task to a user.',
|
|
46
|
+
requestBody: { contentType: 'application/json', schema: putawayTaskAssignSchema },
|
|
47
|
+
responses: [{ status: 200, description: 'Putaway task assigned', schema: successSchema }],
|
|
48
|
+
errors: [
|
|
49
|
+
{ status: 400, description: 'Validation failed', schema: errorSchema },
|
|
50
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
51
|
+
{ status: 403, description: 'Forbidden', schema: errorSchema },
|
|
52
|
+
{ status: 409, description: 'Invalid putaway state', schema: errorSchema },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
4
|
+
import { putawayTaskCancelSchema } from '../../../../data/validators'
|
|
5
|
+
import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
POST: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const paramsSchema = z.object({
|
|
12
|
+
id: z.string().uuid(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export async function POST(request: Request, routeContext: { params: { id: string } }) {
|
|
16
|
+
const parsedParams = paramsSchema.parse(routeContext.params)
|
|
17
|
+
const body = await readJsonSafe<Record<string, unknown>>(request, {})
|
|
18
|
+
const scopedRequest = new Request(request.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
body: JSON.stringify({ ...body, id: parsedParams.id }),
|
|
22
|
+
})
|
|
23
|
+
return executeWmsCustomPostRoute({
|
|
24
|
+
request: scopedRequest,
|
|
25
|
+
routePath: `wms/putaway-tasks/${parsedParams.id}/cancel`,
|
|
26
|
+
inputSchema: putawayTaskCancelSchema,
|
|
27
|
+
commandId: 'wms.putaway-tasks.cancel',
|
|
28
|
+
describeResource: (input) => ({
|
|
29
|
+
resourceKind: 'wms.putawayTask',
|
|
30
|
+
resourceId: input.id,
|
|
31
|
+
}),
|
|
32
|
+
mapSuccess: () => ({ ok: true }),
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const successSchema = z.object({ ok: z.literal(true) })
|
|
37
|
+
const errorSchema = z.object({ error: z.string() })
|
|
38
|
+
|
|
39
|
+
export const openApi: OpenApiRouteDoc = {
|
|
40
|
+
tag: 'WMS',
|
|
41
|
+
summary: 'Cancel putaway task',
|
|
42
|
+
methods: {
|
|
43
|
+
POST: {
|
|
44
|
+
summary: 'Cancel putaway task',
|
|
45
|
+
description: 'Cancels an open or in-progress putaway task without moving stock.',
|
|
46
|
+
requestBody: { contentType: 'application/json', schema: putawayTaskCancelSchema },
|
|
47
|
+
responses: [{ status: 200, description: 'Putaway task cancelled', schema: successSchema }],
|
|
48
|
+
errors: [
|
|
49
|
+
{ status: 400, description: 'Validation failed', schema: errorSchema },
|
|
50
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
51
|
+
{ status: 403, description: 'Forbidden', schema: errorSchema },
|
|
52
|
+
{ status: 409, description: 'Invalid putaway state', schema: errorSchema },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|