@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,52 @@
|
|
|
1
|
+
import { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Apply a 409 optimistic-lock `currentUpdatedAt` into a mutable token holder so
|
|
5
|
+
* ASN complete retries send a fresh If-Match.
|
|
6
|
+
* Returns the refreshed token, or null when the response is not a lock conflict.
|
|
7
|
+
*/
|
|
8
|
+
export function applyAsnCompleteLockTokenFromConflict(
|
|
9
|
+
tokenHolder: { current: string | null },
|
|
10
|
+
response: { status: number; body: unknown },
|
|
11
|
+
): string | null {
|
|
12
|
+
const conflict = extractOptimisticLockConflict({
|
|
13
|
+
status: response.status,
|
|
14
|
+
body: response.body,
|
|
15
|
+
})
|
|
16
|
+
if (!conflict?.currentUpdatedAt) return null
|
|
17
|
+
tokenHolder.current = conflict.currentUpdatedAt
|
|
18
|
+
return conflict.currentUpdatedAt
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Prefer a mutable ASN lock ref (updated on receive success) over the async
|
|
23
|
+
* query `updated_at`, so a second receive before header refetch does not send
|
|
24
|
+
* a stale If-Match.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveAsnReceiveLockToken(
|
|
27
|
+
tokenHolder: { current: string | null },
|
|
28
|
+
asnUpdatedAt?: string | null,
|
|
29
|
+
): string | null {
|
|
30
|
+
if (typeof tokenHolder.current === 'string' && tokenHolder.current.trim()) {
|
|
31
|
+
return tokenHolder.current.trim()
|
|
32
|
+
}
|
|
33
|
+
if (typeof asnUpdatedAt === 'string' && asnUpdatedAt.trim()) {
|
|
34
|
+
return asnUpdatedAt.trim()
|
|
35
|
+
}
|
|
36
|
+
return asnUpdatedAt ?? null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Store ASN `updated_at` from a successful receive so the next openReceive /
|
|
41
|
+
* query-cache seed uses a fresh If-Match without waiting on refetch.
|
|
42
|
+
*/
|
|
43
|
+
export function applyAsnReceiveLockTokenFromSuccess(
|
|
44
|
+
tokenHolder: { current: string | null },
|
|
45
|
+
asnUpdatedAt?: string | null,
|
|
46
|
+
): string | null {
|
|
47
|
+
if (typeof asnUpdatedAt !== 'string') return null
|
|
48
|
+
const trimmed = asnUpdatedAt.trim()
|
|
49
|
+
if (!trimmed) return null
|
|
50
|
+
tokenHolder.current = trimmed
|
|
51
|
+
return trimmed
|
|
52
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
2
|
+
|
|
3
|
+
/** API max pageSize (AGENTS.md); ASN detail loops until short page. */
|
|
4
|
+
export const ASN_RECEIVING_LINES_PAGE_SIZE = 100
|
|
5
|
+
|
|
6
|
+
export type AsnReceivingLineRow = {
|
|
7
|
+
id: string
|
|
8
|
+
asn_id?: string | null
|
|
9
|
+
catalog_variant_id?: string | null
|
|
10
|
+
expected_qty?: string | number | null
|
|
11
|
+
received_qty?: string | number | null
|
|
12
|
+
lot_number?: string | null
|
|
13
|
+
serial_numbers?: string[] | null
|
|
14
|
+
qc_status?: string | null
|
|
15
|
+
target_staging_location_id?: string | null
|
|
16
|
+
rejection_reason?: string | null
|
|
17
|
+
updated_at?: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type PagedResponse<T> = {
|
|
21
|
+
items: T[]
|
|
22
|
+
total: number
|
|
23
|
+
totalPages: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LoadAllAsnReceivingLinesResult =
|
|
27
|
+
| { ok: true; items: AsnReceivingLineRow[] }
|
|
28
|
+
| { ok: false; response: Response }
|
|
29
|
+
|
|
30
|
+
type FetchPage = (
|
|
31
|
+
page: number,
|
|
32
|
+
pageSize: number,
|
|
33
|
+
) => Promise<{
|
|
34
|
+
ok: boolean
|
|
35
|
+
response: Response
|
|
36
|
+
result?: PagedResponse<AsnReceivingLineRow> | null
|
|
37
|
+
}>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load every receiving line for an ASN detail console.
|
|
41
|
+
* Short-page termination only — never use `totalPages` (OM_LIST_COUNT_CAP).
|
|
42
|
+
*/
|
|
43
|
+
export async function loadAllAsnReceivingLines(
|
|
44
|
+
asnId: string,
|
|
45
|
+
options?: {
|
|
46
|
+
pageSize?: number
|
|
47
|
+
fetchPage?: FetchPage
|
|
48
|
+
},
|
|
49
|
+
): Promise<LoadAllAsnReceivingLinesResult> {
|
|
50
|
+
const pageSize = Math.min(
|
|
51
|
+
Math.max(1, options?.pageSize ?? ASN_RECEIVING_LINES_PAGE_SIZE),
|
|
52
|
+
ASN_RECEIVING_LINES_PAGE_SIZE,
|
|
53
|
+
)
|
|
54
|
+
const fetchPage: FetchPage =
|
|
55
|
+
options?.fetchPage ??
|
|
56
|
+
(async (page, size) => {
|
|
57
|
+
const call = await apiCall<PagedResponse<AsnReceivingLineRow>>(
|
|
58
|
+
`/api/wms/receiving-lines?asnId=${encodeURIComponent(asnId)}&page=${page}&pageSize=${size}`,
|
|
59
|
+
)
|
|
60
|
+
return {
|
|
61
|
+
ok: call.ok,
|
|
62
|
+
response: call.response,
|
|
63
|
+
result: call.result ?? null,
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const items: AsnReceivingLineRow[] = []
|
|
68
|
+
let page = 1
|
|
69
|
+
// Short-page termination: `totalPages` derives from `total`, which is a
|
|
70
|
+
// display value that can be capped (OM_LIST_COUNT_CAP) — never a loop bound.
|
|
71
|
+
for (;;) {
|
|
72
|
+
const call = await fetchPage(page, pageSize)
|
|
73
|
+
if (!call.ok) {
|
|
74
|
+
return { ok: false, response: call.response }
|
|
75
|
+
}
|
|
76
|
+
const batch = call.result?.items ?? []
|
|
77
|
+
items.push(...batch)
|
|
78
|
+
if (batch.length < pageSize) break
|
|
79
|
+
page += 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { ok: true, items }
|
|
83
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'
|
|
2
|
+
import { hasAsnReceiptActivity, isAsnCloseable } from '../../lib/asnReceiving'
|
|
3
|
+
|
|
4
|
+
export type AsnStatus = 'draft' | 'in_transit' | 'received' | 'closed'
|
|
5
|
+
export type ReceivingLineQcStatus = 'pending' | 'passed' | 'failed'
|
|
6
|
+
export type PutawayTaskStatus = 'open' | 'in_progress' | 'done' | 'cancelled'
|
|
7
|
+
|
|
8
|
+
export type AsnCompleteGateLine = {
|
|
9
|
+
expected_qty?: string | number | null
|
|
10
|
+
received_qty?: string | number | null
|
|
11
|
+
qc_status?: string | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function asnStatusVariant(status: string | null | undefined): StatusBadgeVariant {
|
|
15
|
+
switch (status) {
|
|
16
|
+
case 'in_transit':
|
|
17
|
+
return 'info'
|
|
18
|
+
case 'received':
|
|
19
|
+
return 'success'
|
|
20
|
+
case 'closed':
|
|
21
|
+
return 'neutral'
|
|
22
|
+
case 'draft':
|
|
23
|
+
default:
|
|
24
|
+
return 'neutral'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function qcStatusVariant(status: string | null | undefined): StatusBadgeVariant {
|
|
29
|
+
switch (status) {
|
|
30
|
+
case 'passed':
|
|
31
|
+
return 'success'
|
|
32
|
+
case 'failed':
|
|
33
|
+
return 'error'
|
|
34
|
+
case 'pending':
|
|
35
|
+
default:
|
|
36
|
+
return 'warning'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function putawayStatusVariant(status: string | null | undefined): StatusBadgeVariant {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case 'in_progress':
|
|
43
|
+
return 'warning'
|
|
44
|
+
case 'done':
|
|
45
|
+
return 'success'
|
|
46
|
+
case 'cancelled':
|
|
47
|
+
return 'neutral'
|
|
48
|
+
case 'open':
|
|
49
|
+
default:
|
|
50
|
+
return 'info'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatAgingLabel(createdAt: string | null | undefined, nowMs = Date.now()): string | null {
|
|
55
|
+
if (!createdAt) return null
|
|
56
|
+
const createdMs = new Date(createdAt).getTime()
|
|
57
|
+
if (Number.isNaN(createdMs)) return null
|
|
58
|
+
const minutes = Math.max(0, Math.floor((nowMs - createdMs) / 60_000))
|
|
59
|
+
if (minutes < 60) return `${minutes}m`
|
|
60
|
+
const hours = Math.floor(minutes / 60)
|
|
61
|
+
if (hours < 48) return `${hours}h`
|
|
62
|
+
const days = Math.floor(hours / 24)
|
|
63
|
+
return `${days}d`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Map putaway queue column ids to list API `sortField` values. Aging is derived from created_at. */
|
|
67
|
+
export function mapPutawayQueueSortField(columnId: string): string {
|
|
68
|
+
return columnId === 'aging' ? 'createdAt' : columnId
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Map UI sort direction to list API `sortDir`.
|
|
73
|
+
* Aging is age-since-created: UI "desc" (oldest first) is createdAt ascending,
|
|
74
|
+
* and UI "asc" (newest first) is createdAt descending.
|
|
75
|
+
*/
|
|
76
|
+
export function mapPutawayQueueSortDir(columnId: string, desc: boolean): 'asc' | 'desc' {
|
|
77
|
+
const dir = desc ? 'desc' : 'asc'
|
|
78
|
+
if (columnId === 'aging') {
|
|
79
|
+
return desc ? 'asc' : 'desc'
|
|
80
|
+
}
|
|
81
|
+
return dir
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function lineHasDiscrepancy(
|
|
85
|
+
expectedQty: string | number | null | undefined,
|
|
86
|
+
receivedQty: string | number | null | undefined,
|
|
87
|
+
): boolean {
|
|
88
|
+
const expected = Number(expectedQty ?? 0)
|
|
89
|
+
const received = Number(receivedQty ?? 0)
|
|
90
|
+
if (!Number.isFinite(expected) || !Number.isFinite(received)) return false
|
|
91
|
+
return received !== expected && received > 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* UI visibility for putaway Complete (and scan putaway complete CTAs).
|
|
96
|
+
* Matches what callers can actually invoke successfully:
|
|
97
|
+
* - HTTP floor on complete + scan/putaway requires `wms.adjust_inventory`
|
|
98
|
+
* - Command then allows manage (any task) or adjust + assignee match
|
|
99
|
+
* Manage-only without adjust must not see Complete (would 403 on the route).
|
|
100
|
+
*/
|
|
101
|
+
export function canShowPutawayCompleteAction(input: {
|
|
102
|
+
canManagePutaway: boolean
|
|
103
|
+
canAdjustInventory: boolean
|
|
104
|
+
isAssignee: boolean
|
|
105
|
+
}): boolean {
|
|
106
|
+
if (!input.canAdjustInventory) return false
|
|
107
|
+
return input.canManagePutaway || input.isAssignee
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toCloseableLines(lines: AsnCompleteGateLine[]) {
|
|
111
|
+
return lines.map((line) => ({
|
|
112
|
+
expectedQty: line.expected_qty ?? 0,
|
|
113
|
+
receivedQty: line.received_qty ?? 0,
|
|
114
|
+
qcStatus: line.qc_status,
|
|
115
|
+
}))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Complete ASN affordance aligned with `wms.asns.close` / `isAsnCloseable`:
|
|
120
|
+
* - Show when manage + open ASN + any receipt activity (empty / untouched lines hide Complete)
|
|
121
|
+
* - Enable default submit only when QC-passed accepted qty meets expected on every line
|
|
122
|
+
* - When short / QC-fail, expose close-when-short and enable submit only after that opt-in
|
|
123
|
+
* (avoids enabling a default Complete that always 409s as invalid_receipt_state)
|
|
124
|
+
* - Header-only / zero-activity ASNs stay non-closeable (isAsnCloseable requires receipt activity)
|
|
125
|
+
*/
|
|
126
|
+
export function resolveAsnCompleteGate(input: {
|
|
127
|
+
canManageAsn: boolean
|
|
128
|
+
asnStatus: string | null | undefined
|
|
129
|
+
lines: AsnCompleteGateLine[]
|
|
130
|
+
closeWhenShort: boolean
|
|
131
|
+
}): {
|
|
132
|
+
canShowComplete: boolean
|
|
133
|
+
canSubmitComplete: boolean
|
|
134
|
+
showCloseWhenShort: boolean
|
|
135
|
+
} {
|
|
136
|
+
const closeableLines = toCloseableLines(input.lines)
|
|
137
|
+
const canReceiveMore = input.asnStatus === 'draft' || input.asnStatus === 'in_transit'
|
|
138
|
+
const canShowComplete =
|
|
139
|
+
input.canManageAsn && canReceiveMore && hasAsnReceiptActivity(closeableLines)
|
|
140
|
+
if (!canShowComplete) {
|
|
141
|
+
return {
|
|
142
|
+
canShowComplete: false,
|
|
143
|
+
canSubmitComplete: false,
|
|
144
|
+
showCloseWhenShort: false,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fullyCloseable = isAsnCloseable(closeableLines, false)
|
|
149
|
+
return {
|
|
150
|
+
canShowComplete: true,
|
|
151
|
+
canSubmitComplete: isAsnCloseable(closeableLines, input.closeWhenShort),
|
|
152
|
+
showCloseWhenShort: !fullyCloseable,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -405,6 +405,26 @@ export async function loadBinLocationOptions(
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
export async function loadStagingLocationOptions(
|
|
409
|
+
warehouseId: string,
|
|
410
|
+
query?: string,
|
|
411
|
+
): Promise<CrudFieldOption[]> {
|
|
412
|
+
if (!warehouseId) return []
|
|
413
|
+
try {
|
|
414
|
+
const [staging, dock] = await Promise.all([
|
|
415
|
+
loadLocationPage(warehouseId, { type: 'staging', search: query }),
|
|
416
|
+
loadLocationPage(warehouseId, { type: 'dock', search: query }),
|
|
417
|
+
])
|
|
418
|
+
const byId = new Map<string, CrudFieldOption>()
|
|
419
|
+
for (const option of mapLocationOptions([...(staging.items ?? []), ...(dock.items ?? [])])) {
|
|
420
|
+
byId.set(option.value, option)
|
|
421
|
+
}
|
|
422
|
+
return Array.from(byId.values())
|
|
423
|
+
} catch {
|
|
424
|
+
return []
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
408
428
|
export async function loadZoneOptions(
|
|
409
429
|
warehouseId: string,
|
|
410
430
|
query?: string,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Apply a 409 optimistic-lock `currentUpdatedAt` into a mutable token map so
|
|
5
|
+
* putaway queue assign/start/cancel retries send a fresh If-Match.
|
|
6
|
+
* Returns the refreshed token, or null when the response is not a lock conflict.
|
|
7
|
+
*/
|
|
8
|
+
export function applyPutawayLockTokenFromConflict(
|
|
9
|
+
tokens: Record<string, string>,
|
|
10
|
+
taskId: string,
|
|
11
|
+
response: { status: number; body: unknown },
|
|
12
|
+
): string | null {
|
|
13
|
+
const conflict = extractOptimisticLockConflict({
|
|
14
|
+
status: response.status,
|
|
15
|
+
body: response.body,
|
|
16
|
+
})
|
|
17
|
+
if (!conflict?.currentUpdatedAt) return null
|
|
18
|
+
tokens[taskId] = conflict.currentUpdatedAt
|
|
19
|
+
return conflict.currentUpdatedAt
|
|
20
|
+
}
|
|
@@ -35,6 +35,8 @@ export function useWmsInventoryMutationAccess() {
|
|
|
35
35
|
'wms.adjust_inventory',
|
|
36
36
|
'wms.manage_inventory',
|
|
37
37
|
'wms.receive_inventory',
|
|
38
|
+
'wms.manage_asn',
|
|
39
|
+
'wms.manage_putaway',
|
|
38
40
|
'wms.cycle_count',
|
|
39
41
|
'wms.import',
|
|
40
42
|
'wms.manage_reservations',
|
|
@@ -75,6 +77,8 @@ export function useWmsInventoryMutationAccess() {
|
|
|
75
77
|
canAdjust: hasFeature(granted, 'wms.adjust_inventory'),
|
|
76
78
|
canManage: hasFeature(granted, 'wms.manage_inventory'),
|
|
77
79
|
canReceive: hasFeature(granted, 'wms.receive_inventory'),
|
|
80
|
+
canManageAsn: hasFeature(granted, 'wms.manage_asn'),
|
|
81
|
+
canManagePutaway: hasFeature(granted, 'wms.manage_putaway'),
|
|
78
82
|
canCycleCount: hasFeature(granted, 'wms.cycle_count'),
|
|
79
83
|
canImport: hasFeature(granted, 'wms.import'),
|
|
80
84
|
canMove: hasFeature(granted, 'wms.adjust_inventory'),
|
|
@@ -5,6 +5,8 @@ import type { FeatureTogglesService } from '@open-mercato/core/modules/feature_t
|
|
|
5
5
|
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
6
6
|
import { E } from '#generated/entities.ids.generated'
|
|
7
7
|
import { InventoryBalance, InventoryReservation, ProductInventoryProfile, SalesOrderWarehouseAssignment, Warehouse } from './entities'
|
|
8
|
+
import { formatCatalogVariantLabel } from '../lib/inventoryDisplayUi'
|
|
9
|
+
import { isBalanceLocationReservable } from '../lib/inventoryPolicy'
|
|
8
10
|
import { resolvePrimaryWarehouseId } from '../lib/primaryWarehousePolicy'
|
|
9
11
|
import {
|
|
10
12
|
WMS_ENRICHER_CACHE_TTL_MS,
|
|
@@ -28,7 +30,21 @@ type SalesOrderLineRow = {
|
|
|
28
30
|
type CatalogVariantRow = {
|
|
29
31
|
id?: string
|
|
30
32
|
product_id?: string | null
|
|
33
|
+
name?: string | null
|
|
34
|
+
sku?: string | null
|
|
31
35
|
}
|
|
36
|
+
|
|
37
|
+
type ReservationSummaryItem = {
|
|
38
|
+
id: string
|
|
39
|
+
reservationLabel: string
|
|
40
|
+
catalogVariantId: string
|
|
41
|
+
warehouseId: string | null
|
|
42
|
+
warehouseName: string | null
|
|
43
|
+
quantity: string
|
|
44
|
+
variantName: string | null
|
|
45
|
+
variantSku: string | null
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
type ReservationStatus = 'unreserved' | 'partially_reserved' | 'fully_reserved'
|
|
33
49
|
type ReorderState = 'no_profile' | 'healthy' | 'below_reorder_point' | 'below_safety_stock'
|
|
34
50
|
|
|
@@ -60,6 +76,11 @@ type ReorderStatus = {
|
|
|
60
76
|
safetyStock: string
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
type InboundSummary = {
|
|
80
|
+
openAsnCount: number
|
|
81
|
+
nextExpectedAt: string | null
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
type SalesOrderWmsEnrichment = {
|
|
64
85
|
_wms: {
|
|
65
86
|
assignedWarehouseId: string | null
|
|
@@ -73,7 +94,9 @@ type SalesOrderWmsEnrichment = {
|
|
|
73
94
|
reservationSummary: {
|
|
74
95
|
status: ReservationStatus
|
|
75
96
|
reservationIds: string[]
|
|
97
|
+
reservations: ReservationSummaryItem[]
|
|
76
98
|
}
|
|
99
|
+
inboundSummary: InboundSummary
|
|
77
100
|
}
|
|
78
101
|
}
|
|
79
102
|
|
|
@@ -90,6 +113,11 @@ type Scope = { organizationId: string; tenantId: string }
|
|
|
90
113
|
type BalanceAggregate = { onHand: number; reserved: number; allocated: number; available: number }
|
|
91
114
|
|
|
92
115
|
import { resolveWmsIntegrationToggleEnabled } from '../lib/wmsIntegrationToggles'
|
|
116
|
+
const EMPTY_INBOUND_SUMMARY: InboundSummary = {
|
|
117
|
+
openAsnCount: 0,
|
|
118
|
+
nextExpectedAt: null,
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
const EMPTY_ENRICHMENT: SalesOrderWmsEnrichment = {
|
|
94
122
|
_wms: {
|
|
95
123
|
assignedWarehouseId: null,
|
|
@@ -99,7 +127,9 @@ const EMPTY_ENRICHMENT: SalesOrderWmsEnrichment = {
|
|
|
99
127
|
reservationSummary: {
|
|
100
128
|
status: 'unreserved',
|
|
101
129
|
reservationIds: [],
|
|
130
|
+
reservations: [],
|
|
102
131
|
},
|
|
132
|
+
inboundSummary: EMPTY_INBOUND_SUMMARY,
|
|
103
133
|
},
|
|
104
134
|
}
|
|
105
135
|
|
|
@@ -167,6 +197,55 @@ function resolveReservationStatus(requiredQuantity: number, reservedQuantity: nu
|
|
|
167
197
|
return 'fully_reserved'
|
|
168
198
|
}
|
|
169
199
|
|
|
200
|
+
function buildReservationLabel(input: {
|
|
201
|
+
id: string
|
|
202
|
+
variantName: string | null
|
|
203
|
+
variantSku: string | null
|
|
204
|
+
warehouseName: string | null
|
|
205
|
+
quantity: string
|
|
206
|
+
}): string {
|
|
207
|
+
const variantLabel = formatCatalogVariantLabel({
|
|
208
|
+
variant_name: input.variantName,
|
|
209
|
+
variant_sku: input.variantSku,
|
|
210
|
+
})
|
|
211
|
+
const segments: string[] = []
|
|
212
|
+
if (variantLabel && variantLabel !== '—') segments.push(variantLabel)
|
|
213
|
+
const warehouseName = input.warehouseName?.trim()
|
|
214
|
+
if (warehouseName) segments.push(warehouseName)
|
|
215
|
+
const quantity = formatQuantity(Number(input.quantity))
|
|
216
|
+
if (quantity !== '0') segments.push(quantity)
|
|
217
|
+
return segments.length > 0 ? segments.join(' · ') : input.id
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toReservationSummaryItem(
|
|
221
|
+
reservation: InventoryReservation,
|
|
222
|
+
warehouseNamesById: Map<string, string>,
|
|
223
|
+
variantLabelsById: Map<string, { name: string | null; sku: string | null }>,
|
|
224
|
+
): ReservationSummaryItem {
|
|
225
|
+
const warehouseId = extractWarehouseId(reservation)
|
|
226
|
+
const warehouseName = warehouseId ? (warehouseNamesById.get(warehouseId) ?? null) : null
|
|
227
|
+
const variantMeta = variantLabelsById.get(reservation.catalogVariantId)
|
|
228
|
+
const variantName = variantMeta?.name ?? null
|
|
229
|
+
const variantSku = variantMeta?.sku ?? null
|
|
230
|
+
const quantity = formatQuantity(Number(reservation.quantity))
|
|
231
|
+
return {
|
|
232
|
+
id: reservation.id,
|
|
233
|
+
reservationLabel: buildReservationLabel({
|
|
234
|
+
id: reservation.id,
|
|
235
|
+
variantName,
|
|
236
|
+
variantSku,
|
|
237
|
+
warehouseName,
|
|
238
|
+
quantity,
|
|
239
|
+
}),
|
|
240
|
+
catalogVariantId: reservation.catalogVariantId,
|
|
241
|
+
warehouseId,
|
|
242
|
+
warehouseName,
|
|
243
|
+
quantity,
|
|
244
|
+
variantName,
|
|
245
|
+
variantSku,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
170
249
|
function resolveReorderStatus(
|
|
171
250
|
availableQuantity: number,
|
|
172
251
|
profile: ProductInventoryProfile | null | undefined,
|
|
@@ -204,6 +283,10 @@ function buildBalanceSummaryByVariant(
|
|
|
204
283
|
const byVariant = new Map<string, BalanceAggregate>()
|
|
205
284
|
|
|
206
285
|
for (const balance of balances) {
|
|
286
|
+
// Match reservation / sales-automation: staging/dock are inbound holding
|
|
287
|
+
// and must not inflate widget "Available" (or sellable on-hand aggregates).
|
|
288
|
+
if (!isBalanceLocationReservable(balance)) continue
|
|
289
|
+
|
|
207
290
|
const current = byVariant.get(balance.catalogVariantId) ?? {
|
|
208
291
|
onHand: 0,
|
|
209
292
|
reserved: 0,
|
|
@@ -254,6 +337,31 @@ async function isSalesOrderInventoryEnabled(context: EnricherContext): Promise<b
|
|
|
254
337
|
}
|
|
255
338
|
}
|
|
256
339
|
|
|
340
|
+
async function loadInboundSummary(em: EntityManager, scope: Scope): Promise<InboundSummary> {
|
|
341
|
+
// Aggregate only — never load all open ASN rows (backlog can blow the enricher timeout).
|
|
342
|
+
const rows = await em.getConnection().execute<
|
|
343
|
+
Array<{ open_asn_count: string | number; next_expected_at: Date | string | null }>
|
|
344
|
+
>(
|
|
345
|
+
`select count(*)::int as open_asn_count,
|
|
346
|
+
min(expected_at) as next_expected_at
|
|
347
|
+
from wms_asns
|
|
348
|
+
where organization_id = ?
|
|
349
|
+
and tenant_id = ?
|
|
350
|
+
and deleted_at is null
|
|
351
|
+
and status in ('draft', 'in_transit')`,
|
|
352
|
+
[scope.organizationId, scope.tenantId],
|
|
353
|
+
)
|
|
354
|
+
const row = rows[0]
|
|
355
|
+
const openAsnCount = Number(row?.open_asn_count ?? 0)
|
|
356
|
+
const nextExpected = row?.next_expected_at
|
|
357
|
+
return {
|
|
358
|
+
openAsnCount: Number.isFinite(openAsnCount) ? openAsnCount : 0,
|
|
359
|
+
nextExpectedAt: nextExpected
|
|
360
|
+
? (nextExpected instanceof Date ? nextExpected : new Date(nextExpected)).toISOString()
|
|
361
|
+
: null,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
257
365
|
async function loadExplicitAssignments(
|
|
258
366
|
em: EntityManager,
|
|
259
367
|
orderIds: string[],
|
|
@@ -352,7 +460,8 @@ async function loadBalances(
|
|
|
352
460
|
tenantId: scope.tenantId,
|
|
353
461
|
deletedAt: null,
|
|
354
462
|
},
|
|
355
|
-
|
|
463
|
+
// Populate location so isBalanceLocationReservable can exclude staging/dock.
|
|
464
|
+
{ populate: ['location'] },
|
|
356
465
|
scope,
|
|
357
466
|
)
|
|
358
467
|
}
|
|
@@ -376,6 +485,32 @@ async function loadCatalogVariants(
|
|
|
376
485
|
return result.items
|
|
377
486
|
}
|
|
378
487
|
|
|
488
|
+
async function loadCatalogVariantLabelsByIds(
|
|
489
|
+
context: EnricherContext,
|
|
490
|
+
variantIds: string[],
|
|
491
|
+
scope: Scope,
|
|
492
|
+
): Promise<Map<string, { name: string | null; sku: string | null }>> {
|
|
493
|
+
if (variantIds.length === 0) return new Map()
|
|
494
|
+
const container = context.container as { resolve: (name: string) => unknown }
|
|
495
|
+
const queryEngine = container.resolve('queryEngine') as QueryEngine
|
|
496
|
+
const result = await queryEngine.query<CatalogVariantRow>(E.catalog.catalog_product_variant, {
|
|
497
|
+
tenantId: scope.tenantId,
|
|
498
|
+
organizationId: scope.organizationId,
|
|
499
|
+
filters: { id: { $in: variantIds } },
|
|
500
|
+
fields: ['id', 'name', 'sku'],
|
|
501
|
+
page: { page: 1, pageSize: Math.max(variantIds.length, 1) },
|
|
502
|
+
})
|
|
503
|
+
const labels = new Map<string, { name: string | null; sku: string | null }>()
|
|
504
|
+
for (const row of result.items) {
|
|
505
|
+
if (typeof row.id !== 'string' || row.id.length === 0) continue
|
|
506
|
+
labels.set(row.id, {
|
|
507
|
+
name: typeof row.name === 'string' ? row.name : null,
|
|
508
|
+
sku: typeof row.sku === 'string' ? row.sku : null,
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
return labels
|
|
512
|
+
}
|
|
513
|
+
|
|
379
514
|
async function loadProductInventoryProfiles(
|
|
380
515
|
em: EntityManager,
|
|
381
516
|
productIds: string[],
|
|
@@ -462,12 +597,16 @@ const salesOrderInventoryEnricher: ResponseEnricher<SalesOrderRecord, SalesOrder
|
|
|
462
597
|
),
|
|
463
598
|
)
|
|
464
599
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
600
|
+
// inboundSummary is best-effort (raw ASN SQL); isolate so a missing table /
|
|
601
|
+
// migration lag cannot reject the whole Promise.all and drop stock/reservation.
|
|
602
|
+
const [reservations, balances, primaryWarehouseId, explicitAssignments, inboundSummary] =
|
|
603
|
+
await Promise.all([
|
|
604
|
+
loadReservations(em, orderIds, scope),
|
|
605
|
+
loadBalances(em, variantIds, scope),
|
|
606
|
+
resolvePrimaryWarehouseId(em, scope),
|
|
607
|
+
loadExplicitAssignments(em, orderIds, scope),
|
|
608
|
+
loadInboundSummary(em, scope).catch(() => EMPTY_INBOUND_SUMMARY),
|
|
609
|
+
])
|
|
471
610
|
|
|
472
611
|
const explicitWarehouseIdsByOrder = new Map<string, string>()
|
|
473
612
|
for (const assignment of explicitAssignments) {
|
|
@@ -491,14 +630,31 @@ const salesOrderInventoryEnricher: ResponseEnricher<SalesOrderRecord, SalesOrder
|
|
|
491
630
|
const warehouses = await loadWarehousesByIds(em, allRelevantWarehouseIds, scope)
|
|
492
631
|
const warehouseNamesById = new Map(warehouses.map((w) => [w.id, w.name]))
|
|
493
632
|
|
|
633
|
+
const activeReservationVariantIds = Array.from(
|
|
634
|
+
new Set(
|
|
635
|
+
reservations
|
|
636
|
+
.filter((reservation) => reservation.status === 'active')
|
|
637
|
+
.map((reservation) => reservation.catalogVariantId)
|
|
638
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0),
|
|
639
|
+
),
|
|
640
|
+
)
|
|
641
|
+
const variantLabelsById = await loadCatalogVariantLabelsByIds(
|
|
642
|
+
context,
|
|
643
|
+
activeReservationVariantIds,
|
|
644
|
+
scope,
|
|
645
|
+
)
|
|
646
|
+
|
|
494
647
|
const variantAvailability = new Map<string, number>()
|
|
495
648
|
const variantReserved = new Map<string, number>()
|
|
496
649
|
for (const balance of balances) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
650
|
+
// Staging/dock must not count toward Available (same rule as reserve path).
|
|
651
|
+
if (isBalanceLocationReservable(balance)) {
|
|
652
|
+
addToMap(
|
|
653
|
+
variantAvailability,
|
|
654
|
+
balance.catalogVariantId,
|
|
655
|
+
Number(balance.quantityOnHand) - Number(balance.quantityReserved) - Number(balance.quantityAllocated),
|
|
656
|
+
)
|
|
657
|
+
}
|
|
502
658
|
addToMap(variantReserved, balance.catalogVariantId, balance.quantityReserved)
|
|
503
659
|
}
|
|
504
660
|
|
|
@@ -564,6 +720,10 @@ const salesOrderInventoryEnricher: ResponseEnricher<SalesOrderRecord, SalesOrder
|
|
|
564
720
|
? (warehouseNamesById.get(assignedWarehouseId) ?? null)
|
|
565
721
|
: null
|
|
566
722
|
|
|
723
|
+
const reservationItems = reservationsForOrder.map((reservation) =>
|
|
724
|
+
toReservationSummaryItem(reservation, warehouseNamesById, variantLabelsById),
|
|
725
|
+
)
|
|
726
|
+
|
|
567
727
|
return {
|
|
568
728
|
...record,
|
|
569
729
|
_wms: {
|
|
@@ -580,8 +740,10 @@ const salesOrderInventoryEnricher: ResponseEnricher<SalesOrderRecord, SalesOrder
|
|
|
580
740
|
requiredBySalesOrder.get(orderId) ?? 0,
|
|
581
741
|
reservedBySalesOrder.get(orderId) ?? 0,
|
|
582
742
|
),
|
|
583
|
-
reservationIds:
|
|
743
|
+
reservationIds: reservationItems.map((item) => item.id),
|
|
744
|
+
reservations: reservationItems,
|
|
584
745
|
},
|
|
746
|
+
inboundSummary,
|
|
585
747
|
},
|
|
586
748
|
}
|
|
587
749
|
})
|