@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
2
|
import {
|
|
3
|
+
Asn,
|
|
3
4
|
InventoryBalance,
|
|
4
5
|
InventoryLot,
|
|
5
6
|
InventoryMovement,
|
|
@@ -14,6 +15,7 @@ const TREND_DAYS = 7;
|
|
|
14
15
|
const MONTHLY_TREND_MONTHS = 6;
|
|
15
16
|
const ACTIVITY_LIMIT = 10;
|
|
16
17
|
const EXPIRY_CARD_LIMIT = 5;
|
|
18
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17
19
|
class OperationalDashboardWarehouseNotFoundError extends Error {
|
|
18
20
|
constructor(warehouseId) {
|
|
19
21
|
super(`Warehouse ${warehouseId} not found`);
|
|
@@ -184,6 +186,51 @@ async function loadVariantSkus(em, scope, variantIds) {
|
|
|
184
186
|
}
|
|
185
187
|
return map;
|
|
186
188
|
}
|
|
189
|
+
function readMovementMetadataRecord(metadata) {
|
|
190
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return null;
|
|
191
|
+
return metadata;
|
|
192
|
+
}
|
|
193
|
+
function extractActivityMovementSource(metadata) {
|
|
194
|
+
const record = readMovementMetadataRecord(metadata);
|
|
195
|
+
const source = record?.source;
|
|
196
|
+
return typeof source === "string" && source.trim() ? source.trim() : null;
|
|
197
|
+
}
|
|
198
|
+
function extractActivityAsnId(metadata) {
|
|
199
|
+
const record = readMovementMetadataRecord(metadata);
|
|
200
|
+
const asnId = record?.asnId;
|
|
201
|
+
if (typeof asnId !== "string") return null;
|
|
202
|
+
const trimmed = asnId.trim();
|
|
203
|
+
return UUID_PATTERN.test(trimmed) ? trimmed : null;
|
|
204
|
+
}
|
|
205
|
+
function extractActivityAsnReferenceNumber(metadata) {
|
|
206
|
+
const record = readMovementMetadataRecord(metadata);
|
|
207
|
+
const value = record?.asnReferenceNumber;
|
|
208
|
+
if (typeof value !== "string") return null;
|
|
209
|
+
const trimmed = value.trim();
|
|
210
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
211
|
+
}
|
|
212
|
+
async function loadAsnReferenceNumbers(em, scope, asnIds) {
|
|
213
|
+
if (asnIds.length === 0) return /* @__PURE__ */ new Map();
|
|
214
|
+
const decryptionScope = resolveDecryptionScope(scope);
|
|
215
|
+
const asns = await findWithDecryption(
|
|
216
|
+
em,
|
|
217
|
+
Asn,
|
|
218
|
+
{
|
|
219
|
+
id: { $in: asnIds },
|
|
220
|
+
organizationId: scope.organizationId,
|
|
221
|
+
tenantId: scope.tenantId,
|
|
222
|
+
deletedAt: null
|
|
223
|
+
},
|
|
224
|
+
void 0,
|
|
225
|
+
decryptionScope
|
|
226
|
+
);
|
|
227
|
+
const map = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const asn of asns) {
|
|
229
|
+
const label = asn.referenceNumber?.trim();
|
|
230
|
+
if (label) map.set(asn.id, label);
|
|
231
|
+
}
|
|
232
|
+
return map;
|
|
233
|
+
}
|
|
187
234
|
async function loadVariantIdsForProductLevelProfiles(em, scope, profiles) {
|
|
188
235
|
const productIds = Array.from(
|
|
189
236
|
new Set(
|
|
@@ -566,8 +613,22 @@ async function loadOperationalDashboard(em, scope) {
|
|
|
566
613
|
];
|
|
567
614
|
const variantIds = Array.from(new Set(activityMovements.map((movement) => movement.catalogVariantId)));
|
|
568
615
|
const variantSkus = await loadVariantSkus(em, scope, variantIds);
|
|
616
|
+
const asnIdsNeedingLookup = Array.from(
|
|
617
|
+
new Set(
|
|
618
|
+
activityMovements.map((movement) => {
|
|
619
|
+
if (extractActivityAsnReferenceNumber(movement.metadata)) return null;
|
|
620
|
+
return extractActivityAsnId(movement.metadata);
|
|
621
|
+
}).filter((id) => Boolean(id))
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
const asnReferenceNumbers = await loadAsnReferenceNumbers(em, scope, asnIdsNeedingLookup);
|
|
569
625
|
const recentActivity = activityMovements.map((movement) => {
|
|
570
626
|
const warehouseLabel = resolveWarehouseLabel(movement.warehouse, movement.warehouse.id);
|
|
627
|
+
const source = extractActivityMovementSource(movement.metadata);
|
|
628
|
+
const metadataAsnReference = extractActivityAsnReferenceNumber(movement.metadata);
|
|
629
|
+
const asnId = extractActivityAsnId(movement.metadata);
|
|
630
|
+
const referenceLabel = metadataAsnReference ?? (asnId ? asnReferenceNumbers.get(asnId) ?? null : null);
|
|
631
|
+
const reasonCode = movement.reasonCode?.trim() || (source === "asn_receive" ? "asn_receive" : null);
|
|
571
632
|
return {
|
|
572
633
|
id: movement.id,
|
|
573
634
|
movementType: movement.type,
|
|
@@ -576,8 +637,10 @@ async function loadOperationalDashboard(em, scope) {
|
|
|
576
637
|
variantId: movement.catalogVariantId,
|
|
577
638
|
referenceType: movement.referenceType ?? null,
|
|
578
639
|
referenceId: movement.referenceId ?? null,
|
|
640
|
+
referenceLabel,
|
|
579
641
|
reason: movement.reason ?? null,
|
|
580
|
-
reasonCode
|
|
642
|
+
reasonCode,
|
|
643
|
+
source,
|
|
581
644
|
locationLabel: resolveLocationLabel(
|
|
582
645
|
warehouseLabel,
|
|
583
646
|
movement.locationFrom?.code,
|
|
@@ -607,6 +670,9 @@ export {
|
|
|
607
670
|
buildDailyBuckets,
|
|
608
671
|
buildExpiryLotRows,
|
|
609
672
|
computeLowStockCounts,
|
|
673
|
+
extractActivityAsnId,
|
|
674
|
+
extractActivityAsnReferenceNumber,
|
|
675
|
+
extractActivityMovementSource,
|
|
610
676
|
loadOperationalDashboard,
|
|
611
677
|
mapDailyCountsToSparkline,
|
|
612
678
|
resolveEffectiveProfilesByVariant,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/wms/lib/loadOperationalDashboard.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n InventoryBalance,\n InventoryLot,\n InventoryMovement,\n InventoryReservation,\n ProductInventoryProfile,\n Warehouse,\n} from '../data/entities'\nimport { evaluateLowStock } from './inventoryPolicy'\nimport { addUtcDays, EXPIRING_SOON_DAYS, startOfUtcDay } from './expiry'\n\nconst AGING_RESERVATION_DAYS = 7\nconst TREND_DAYS = 7\nconst MONTHLY_TREND_MONTHS = 6\nconst ACTIVITY_LIMIT = 10\nconst EXPIRY_CARD_LIMIT = 5\n\nexport type OperationalDashboardScope = {\n organizationId: string\n tenantId: string\n warehouseId?: string | null\n}\n\nexport type OperationalDashboardKpiId =\n | 'lowStock'\n | 'reorderCritical'\n | 'expiringSoon'\n | 'pastDue'\n | 'agingReservations'\n | 'todaysMoves'\n\nexport type OperationalDashboardKpi = {\n id: OperationalDashboardKpiId\n count: number\n deltaSinceYesterday: number | null\n sparkline: number[]\n}\n\nexport type OperationalDashboardTrendPoint = {\n month: string\n receive: number\n allocate: number\n}\n\nexport type OperationalDashboardActivityRow = {\n id: string\n movementType: string\n quantity: number\n variantSku: string | null\n variantId: string\n referenceType: string | null\n referenceId: string | null\n reason: string | null\n reasonCode: string | null\n locationLabel: string\n performedAt: string\n}\n\nexport type OperationalDashboardExpiryLotRow = {\n id: string\n lotNumber: string\n sku: string\n catalogVariantId: string\n expiresAt: string\n availableQuantity: number\n status: string\n updatedAt: string | null\n category: 'expiringSoon' | 'pastDue'\n}\n\nexport type OperationalDashboardPayload = {\n lastUpdatedAt: string\n warehouseId: string | null\n kpis: OperationalDashboardKpi[]\n expiryLots: OperationalDashboardExpiryLotRow[]\n monthlyTrends: OperationalDashboardTrendPoint[]\n recentActivity: OperationalDashboardActivityRow[]\n}\n\nexport class OperationalDashboardWarehouseNotFoundError extends Error {\n readonly warehouseId: string\n\n constructor(warehouseId: string) {\n super(`Warehouse ${warehouseId} not found`)\n this.name = 'OperationalDashboardWarehouseNotFoundError'\n this.warehouseId = warehouseId\n }\n}\n\ntype DecryptionScope = {\n tenantId: string\n organizationId: string\n}\n\ntype DailyCountRow = {\n day: Date | string\n count: string | number\n}\n\ntype MonthlyTrendRow = {\n month_start: Date | string\n type: string\n count: string | number\n}\n\nexport function toOperationalDashboardNumber(value: string | number | null | undefined): number {\n if (typeof value === 'number') return value\n if (typeof value === 'string' && value.trim().length > 0) return Number(value)\n return 0\n}\n\nexport { addUtcDays, startOfUtcDay } from './expiry'\n\nfunction formatMonthKey(date: Date): string {\n return date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' })\n}\n\nexport function buildDailyBuckets(days: number, now: Date): Date[] {\n const today = startOfUtcDay(now)\n const buckets: Date[] = []\n for (let offset = days - 1; offset >= 0; offset -= 1) {\n buckets.push(addUtcDays(today, -offset))\n }\n return buckets\n}\n\nexport function mapDailyCountsToSparkline(\n dayBuckets: Date[],\n rows: Array<{ day: Date | string; count: string | number }>,\n): number[] {\n const counts = new Map<number, number>()\n for (const bucket of dayBuckets) {\n counts.set(bucket.getTime(), 0)\n }\n for (const row of rows) {\n const day = startOfUtcDay(row.day instanceof Date ? row.day : new Date(row.day)).getTime()\n if (counts.has(day)) {\n counts.set(day, toOperationalDashboardNumber(row.count))\n }\n }\n return dayBuckets.map((bucket) => counts.get(bucket.getTime()) ?? 0)\n}\n\nexport function resolveLotAvailableQuantity(\n lotId: string,\n balances: InventoryBalance[],\n warehouseId?: string | null,\n): number {\n let total = 0\n for (const balance of balances) {\n if (balance.lot?.id !== lotId) continue\n if (warehouseId && balance.warehouse.id !== warehouseId) continue\n const available =\n toOperationalDashboardNumber(balance.quantityOnHand)\n - toOperationalDashboardNumber(balance.quantityReserved)\n - toOperationalDashboardNumber(balance.quantityAllocated)\n if (available > 0) total += available\n }\n return total\n}\n\nexport function buildExpiryLotRows(\n expiringSoonLots: InventoryLot[],\n pastDueLots: InventoryLot[],\n balances: InventoryBalance[],\n warehouseId?: string | null,\n limit = EXPIRY_CARD_LIMIT,\n): OperationalDashboardExpiryLotRow[] {\n const expiringRows = expiringSoonLots\n .map((lot) => ({\n lot,\n availableQuantity: resolveLotAvailableQuantity(lot.id, balances, warehouseId),\n }))\n .filter(({ availableQuantity }) => availableQuantity > 0)\n .sort((left, right) => {\n const leftTime = left.lot.expiresAt?.getTime() ?? 0\n const rightTime = right.lot.expiresAt?.getTime() ?? 0\n return leftTime - rightTime\n })\n .slice(0, limit)\n .map(({ lot, availableQuantity }) => ({\n id: lot.id,\n lotNumber: lot.lotNumber,\n sku: lot.sku,\n catalogVariantId: lot.catalogVariantId,\n expiresAt: lot.expiresAt!.toISOString(),\n availableQuantity,\n status: lot.status,\n updatedAt: lot.updatedAt?.toISOString() ?? null,\n category: 'expiringSoon' as const,\n }))\n\n const pastDueRows = pastDueLots\n .map((lot) => ({\n lot,\n availableQuantity: resolveLotAvailableQuantity(lot.id, balances, warehouseId),\n }))\n .filter(({ availableQuantity }) => availableQuantity > 0)\n .sort((left, right) => {\n const leftTime = left.lot.expiresAt?.getTime() ?? 0\n const rightTime = right.lot.expiresAt?.getTime() ?? 0\n return leftTime - rightTime\n })\n .slice(0, limit)\n .map(({ lot, availableQuantity }) => ({\n id: lot.id,\n lotNumber: lot.lotNumber,\n sku: lot.sku,\n catalogVariantId: lot.catalogVariantId,\n expiresAt: lot.expiresAt!.toISOString(),\n availableQuantity,\n status: lot.status,\n updatedAt: lot.updatedAt?.toISOString() ?? null,\n category: 'pastDue' as const,\n }))\n\n return [...expiringRows, ...pastDueRows]\n}\n\nexport function resolveEffectiveProfilesByVariant(\n profiles: ProductInventoryProfile[],\n variantIdsByProduct: Map<string, string[]> = new Map(),\n): Map<string, ProductInventoryProfile> {\n const effective = new Map<string, ProductInventoryProfile>()\n for (const profile of profiles) {\n const variantId = profile.catalogVariantId\n if (variantId && !effective.has(variantId)) effective.set(variantId, profile)\n }\n for (const profile of profiles) {\n if (profile.catalogVariantId) continue\n for (const variantId of variantIdsByProduct.get(profile.catalogProductId) ?? []) {\n if (!effective.has(variantId)) effective.set(variantId, profile)\n }\n }\n return effective\n}\n\nexport function computeLowStockCounts(\n profiles: ProductInventoryProfile[],\n balances: InventoryBalance[],\n warehouseId?: string | null,\n variantIdsByProduct?: Map<string, string[]>,\n): { lowStockCount: number; reorderCriticalCount: number } {\n const availableByVariantWarehouse = new Map<string, number>()\n for (const balance of balances) {\n const onHand = toOperationalDashboardNumber(balance.quantityOnHand)\n const reserved = toOperationalDashboardNumber(balance.quantityReserved)\n const allocated = toOperationalDashboardNumber(balance.quantityAllocated)\n const available = onHand - reserved - allocated\n const key = `${balance.catalogVariantId}::${balance.warehouse.id}`\n availableByVariantWarehouse.set(key, (availableByVariantWarehouse.get(key) ?? 0) + available)\n }\n\n let lowStockCount = 0\n let reorderCriticalCount = 0\n const seenLowStockKeys = new Set<string>()\n\n for (const [variantId, profile] of resolveEffectiveProfilesByVariant(profiles, variantIdsByProduct)) {\n const reorderPoint = toOperationalDashboardNumber(profile.reorderPoint)\n const safetyStock = toOperationalDashboardNumber(profile.safetyStock)\n if (reorderPoint <= 0 && safetyStock <= 0) continue\n\n const warehouseIds = warehouseId\n ? [warehouseId]\n : Array.from(new Set(balances.map((balance) => balance.warehouse.id)))\n\n for (const scopedWarehouseId of warehouseIds) {\n const key = `${variantId}::${scopedWarehouseId}`\n const available = availableByVariantWarehouse.get(key) ?? 0\n const evaluation = evaluateLowStock(\n { reorderPoint: profile.reorderPoint, safetyStock: profile.safetyStock },\n available,\n )\n if (!evaluation) continue\n if (seenLowStockKeys.has(key)) continue\n seenLowStockKeys.add(key)\n lowStockCount += 1\n if (evaluation.state === 'below_safety_stock') {\n reorderCriticalCount += 1\n }\n }\n }\n\n return { lowStockCount, reorderCriticalCount }\n}\n\nfunction resolveWarehouseLabel(\n warehouse: Warehouse | undefined,\n warehouseId: string,\n): string {\n return warehouse?.code?.trim() || warehouse?.name?.trim() || warehouseId\n}\n\nfunction resolveLocationLabel(\n warehouseLabel: string,\n fromCode?: string | null,\n toCode?: string | null,\n): string {\n if (fromCode && toCode) return `${fromCode} \u2192 ${toCode}`\n if (toCode) return `${warehouseLabel} \u00B7 ${toCode}`\n if (fromCode) return `${warehouseLabel} \u00B7 ${fromCode}`\n return warehouseLabel\n}\n\nfunction resolveDecryptionScope(scope: OperationalDashboardScope): DecryptionScope {\n return {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n}\n\nfunction appendWarehouseFilter(\n sql: string,\n params: unknown[],\n warehouseId?: string | null,\n): string {\n if (!warehouseId) return sql\n params.push(warehouseId)\n return `${sql} and warehouse_id = ?`\n}\n\nasync function loadVariantSkus(\n em: EntityManager,\n scope: OperationalDashboardScope,\n variantIds: string[],\n): Promise<Map<string, string>> {\n if (variantIds.length === 0) return new Map()\n const rows = await em.getConnection().execute<Array<{ id: string; sku: string | null }>>(\n `select id, sku from catalog_product_variants\n where organization_id = ? and tenant_id = ? and id in (${variantIds.map(() => '?').join(', ')})\n and deleted_at is null`,\n [scope.organizationId, scope.tenantId, ...variantIds],\n )\n const map = new Map<string, string>()\n for (const row of rows) {\n if (row.id && row.sku) map.set(row.id, row.sku)\n }\n return map\n}\n\nasync function loadVariantIdsForProductLevelProfiles(\n em: EntityManager,\n scope: OperationalDashboardScope,\n profiles: ProductInventoryProfile[],\n): Promise<Map<string, string[]>> {\n const productIds = Array.from(\n new Set(\n profiles\n .filter((profile) => !profile.catalogVariantId && profile.catalogProductId)\n .map((profile) => profile.catalogProductId),\n ),\n )\n if (productIds.length === 0) return new Map()\n const rows = await em.getConnection().execute<Array<{ id: string; product_id: string }>>(\n `select id, product_id from catalog_product_variants\n where organization_id = ? and tenant_id = ? and product_id in (${productIds.map(() => '?').join(', ')})\n and is_active = true and deleted_at is null`,\n [scope.organizationId, scope.tenantId, ...productIds],\n )\n const map = new Map<string, string[]>()\n for (const row of rows) {\n if (!row.id || !row.product_id) continue\n const variantIds = map.get(row.product_id) ?? []\n variantIds.push(row.id)\n map.set(row.product_id, variantIds)\n }\n return map\n}\n\nasync function countMovementsBetween(\n em: EntityManager,\n scope: OperationalDashboardScope,\n start: Date,\n end: Date,\n options?: { types?: string[]; quantitySign?: 'negative' },\n): Promise<number> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, start, end]\n let sql = `\n select count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ? and performed_at < ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n if (options?.types?.length) {\n sql += ` and type in (${options.types.map(() => '?').join(', ')})`\n params.push(...options.types)\n }\n if (options?.quantitySign === 'negative') {\n sql += ' and quantity < 0'\n }\n const rows = await em.getConnection().execute<Array<{ count: string | number }>>(sql, params)\n return toOperationalDashboardNumber(rows[0]?.count)\n}\n\nasync function loadMovementDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n start: Date,\n options?: { types?: string[]; quantitySign?: 'negative' },\n): Promise<DailyCountRow[]> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, start]\n let sql = `\n select date_trunc('day', performed_at) as day, count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n if (options?.types?.length) {\n sql += ` and type in (${options.types.map(() => '?').join(', ')})`\n params.push(...options.types)\n }\n if (options?.quantitySign === 'negative') {\n sql += ' and quantity < 0'\n }\n sql += ' group by 1 order by 1'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadMonthlyMovementTrends(\n em: EntityManager,\n scope: OperationalDashboardScope,\n monthlyStart: Date,\n now: Date,\n): Promise<OperationalDashboardTrendPoint[]> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, monthlyStart]\n let sql = `\n select date_trunc('month', performed_at) as month_start, type, count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n sql += ' group by 1, 2 order by 1'\n\n const rows = await em.getConnection().execute<MonthlyTrendRow[]>(sql, params)\n const monthlyTrendMap = new Map<string, { receive: number; allocate: number }>()\n for (let index = 0; index < MONTHLY_TREND_MONTHS; index += 1) {\n const monthDate = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (MONTHLY_TREND_MONTHS - 1 - index), 1),\n )\n monthlyTrendMap.set(formatMonthKey(monthDate), { receive: 0, allocate: 0 })\n }\n\n for (const row of rows) {\n const monthDate = row.month_start instanceof Date ? row.month_start : new Date(row.month_start)\n const key = formatMonthKey(monthDate)\n const bucket = monthlyTrendMap.get(key)\n if (!bucket) continue\n if (row.type === 'receipt' || row.type === 'return_receive') {\n bucket.receive += toOperationalDashboardNumber(row.count)\n }\n if (row.type === 'pick' || row.type === 'pack') {\n bucket.allocate += toOperationalDashboardNumber(row.count)\n }\n }\n\n return Array.from(monthlyTrendMap.entries()).map(([month, values]) => ({\n month,\n receive: values.receive,\n allocate: values.allocate,\n }))\n}\n\nasync function loadExpiringSoonDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const trendStart = dayBuckets[0]!\n const trendEnd = dayBuckets[dayBuckets.length - 1]!\n const params: unknown[] = [\n trendStart,\n trendEnd,\n scope.organizationId,\n scope.tenantId,\n ]\n let sql = `\n select bucket.day as day, count(distinct l.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_lots l\n on l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at >= bucket.day\n and l.expires_at <= bucket.day + interval '${EXPIRING_SOON_DAYS} days'\n `\n if (scope.warehouseId) {\n sql += `\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and b.warehouse_id = ?\n `\n params.push(scope.warehouseId)\n }\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadPastDueLotCount(\n em: EntityManager,\n scope: OperationalDashboardScope,\n todayStart: Date,\n): Promise<number> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, todayStart]\n let sql = `\n select count(distinct l.id)::int as count\n from wms_inventory_lots l\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and (\n coalesce(b.quantity_on_hand, 0)\n - coalesce(b.quantity_reserved, 0)\n - coalesce(b.quantity_allocated, 0)\n ) > 0\n where l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at < ?\n `\n if (scope.warehouseId) {\n sql += ' and b.warehouse_id = ?'\n params.push(scope.warehouseId)\n }\n const rows = await em.getConnection().execute<Array<{ count: string | number }>>(sql, params)\n return toOperationalDashboardNumber(rows[0]?.count)\n}\n\nasync function loadPastDueDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const params: unknown[] = [\n dayBuckets[0]!,\n dayBuckets[dayBuckets.length - 1]!,\n scope.organizationId,\n scope.tenantId,\n ]\n let sql = `\n select bucket.day as day, count(distinct l.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_lots l\n on l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at < bucket.day\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and (\n coalesce(b.quantity_on_hand, 0)\n - coalesce(b.quantity_reserved, 0)\n - coalesce(b.quantity_allocated, 0)\n ) > 0\n `\n if (scope.warehouseId) {\n sql += ' and b.warehouse_id = ?'\n params.push(scope.warehouseId)\n }\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadAgingReservationsDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const params: unknown[] = [\n dayBuckets[0]!,\n dayBuckets[dayBuckets.length - 1]!,\n scope.organizationId,\n scope.tenantId,\n String(AGING_RESERVATION_DAYS),\n ]\n let sql = `\n select bucket.day as day, count(r.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_reservations r\n on r.organization_id = ?\n and r.tenant_id = ?\n and r.deleted_at is null\n and r.status = 'active'\n and r.created_at <= bucket.day - (?::int * interval '1 day')\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nexport async function loadOperationalDashboard(\n em: EntityManager,\n scope: OperationalDashboardScope,\n): Promise<OperationalDashboardPayload> {\n const now = new Date()\n const todayStart = startOfUtcDay(now)\n const tomorrowStart = addUtcDays(todayStart, 1)\n const yesterdayStart = addUtcDays(todayStart, -1)\n const agingCutoff = addUtcDays(todayStart, -AGING_RESERVATION_DAYS)\n const expiringCutoff = addUtcDays(todayStart, EXPIRING_SOON_DAYS)\n const trendStart = addUtcDays(todayStart, -(TREND_DAYS - 1))\n const dayBuckets = buildDailyBuckets(TREND_DAYS, now)\n const decryptionScope = resolveDecryptionScope(scope)\n\n const baseScope = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null as Date | null,\n }\n\n if (scope.warehouseId) {\n const warehouse = await findOneWithDecryption(\n em,\n Warehouse,\n { id: scope.warehouseId, ...baseScope },\n undefined,\n decryptionScope,\n )\n if (!warehouse) {\n throw new OperationalDashboardWarehouseNotFoundError(scope.warehouseId)\n }\n }\n\n const warehouseFilter = scope.warehouseId ? { warehouse: scope.warehouseId } : {}\n\n const [profiles, balances, expiringSoonLots, pastDueLots, reservations, activityMovements] = await Promise.all([\n findWithDecryption(em, ProductInventoryProfile, baseScope, undefined, decryptionScope),\n findWithDecryption(\n em,\n InventoryBalance,\n { ...baseScope, ...warehouseFilter },\n { populate: ['warehouse', 'location', 'lot'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryLot,\n {\n ...baseScope,\n status: 'available',\n expiresAt: { $ne: null, $gte: todayStart, $lte: expiringCutoff },\n },\n { orderBy: { expiresAt: 'ASC' }, limit: 100 },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryLot,\n {\n ...baseScope,\n status: 'available',\n expiresAt: { $ne: null, $lt: todayStart },\n },\n { orderBy: { expiresAt: 'ASC' }, limit: 100 },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryReservation,\n {\n ...baseScope,\n ...warehouseFilter,\n status: 'active',\n createdAt: { $lte: agingCutoff },\n },\n undefined,\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryMovement,\n { ...baseScope, ...warehouseFilter },\n {\n populate: ['warehouse', 'locationFrom', 'locationTo'],\n orderBy: { performedAt: 'DESC' },\n limit: ACTIVITY_LIMIT,\n },\n decryptionScope,\n ),\n ])\n\n const variantIdsByProduct = await loadVariantIdsForProductLevelProfiles(em, scope, profiles)\n const { lowStockCount, reorderCriticalCount } = computeLowStockCounts(\n profiles,\n balances,\n scope.warehouseId,\n variantIdsByProduct,\n )\n\n const expiringSoonCount = expiringSoonLots.filter(\n (lot) => resolveLotAvailableQuantity(lot.id, balances, scope.warehouseId) > 0,\n ).length\n\n const agingReservationsCount = reservations.length\n\n const [\n todaysMoveCount,\n yesterdaysMoveCount,\n pastDueCount,\n lowStockSparklineRows,\n reorderCriticalSparklineRows,\n expiringSoonSparklineRows,\n pastDueSparklineRows,\n agingReservationsSparklineRows,\n todaysMovesSparklineRows,\n monthlyTrends,\n ] = await Promise.all([\n countMovementsBetween(em, scope, todayStart, tomorrowStart),\n countMovementsBetween(em, scope, yesterdayStart, todayStart),\n loadPastDueLotCount(em, scope, todayStart),\n loadMovementDailyCounts(em, scope, trendStart, { types: ['adjust'], quantitySign: 'negative' }),\n loadMovementDailyCounts(em, scope, trendStart, { types: ['adjust'], quantitySign: 'negative' }),\n loadExpiringSoonDailyCounts(em, scope, dayBuckets),\n loadPastDueDailyCounts(em, scope, dayBuckets),\n loadAgingReservationsDailyCounts(em, scope, dayBuckets),\n loadMovementDailyCounts(em, scope, trendStart),\n loadMonthlyMovementTrends(\n em,\n scope,\n new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (MONTHLY_TREND_MONTHS - 1), 1)),\n now,\n ),\n ])\n\n const kpis: OperationalDashboardKpi[] = [\n {\n id: 'lowStock',\n count: lowStockCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, lowStockSparklineRows),\n },\n {\n id: 'reorderCritical',\n count: reorderCriticalCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, reorderCriticalSparklineRows),\n },\n {\n id: 'expiringSoon',\n count: expiringSoonCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, expiringSoonSparklineRows),\n },\n {\n id: 'pastDue',\n count: pastDueCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, pastDueSparklineRows),\n },\n {\n id: 'agingReservations',\n count: agingReservationsCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, agingReservationsSparklineRows),\n },\n {\n id: 'todaysMoves',\n count: todaysMoveCount,\n deltaSinceYesterday: todaysMoveCount - yesterdaysMoveCount,\n sparkline: mapDailyCountsToSparkline(dayBuckets, todaysMovesSparklineRows),\n },\n ]\n\n const variantIds = Array.from(new Set(activityMovements.map((movement) => movement.catalogVariantId)))\n const variantSkus = await loadVariantSkus(em, scope, variantIds)\n\n const recentActivity: OperationalDashboardActivityRow[] = activityMovements.map((movement) => {\n const warehouseLabel = resolveWarehouseLabel(movement.warehouse, movement.warehouse.id)\n return {\n id: movement.id,\n movementType: movement.type,\n quantity: toOperationalDashboardNumber(movement.quantity),\n variantSku: variantSkus.get(movement.catalogVariantId) ?? null,\n variantId: movement.catalogVariantId,\n referenceType: movement.referenceType ?? null,\n referenceId: movement.referenceId ?? null,\n reason: movement.reason ?? null,\n reasonCode: movement.reasonCode ?? null,\n locationLabel: resolveLocationLabel(\n warehouseLabel,\n movement.locationFrom?.code,\n movement.locationTo?.code,\n ),\n performedAt: movement.performedAt.toISOString(),\n }\n })\n\n const expiryLots = buildExpiryLotRows(\n expiringSoonLots,\n pastDueLots,\n balances,\n scope.warehouseId,\n )\n\n return {\n lastUpdatedAt: now.toISOString(),\n warehouseId: scope.warehouseId ?? null,\n kpis,\n expiryLots,\n monthlyTrends,\n recentActivity,\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n Asn,\n InventoryBalance,\n InventoryLot,\n InventoryMovement,\n InventoryReservation,\n ProductInventoryProfile,\n Warehouse,\n} from '../data/entities'\nimport { evaluateLowStock } from './inventoryPolicy'\nimport { addUtcDays, EXPIRING_SOON_DAYS, startOfUtcDay } from './expiry'\n\nconst AGING_RESERVATION_DAYS = 7\nconst TREND_DAYS = 7\nconst MONTHLY_TREND_MONTHS = 6\nconst ACTIVITY_LIMIT = 10\nconst EXPIRY_CARD_LIMIT = 5\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport type OperationalDashboardScope = {\n organizationId: string\n tenantId: string\n warehouseId?: string | null\n}\n\nexport type OperationalDashboardKpiId =\n | 'lowStock'\n | 'reorderCritical'\n | 'expiringSoon'\n | 'pastDue'\n | 'agingReservations'\n | 'todaysMoves'\n\nexport type OperationalDashboardKpi = {\n id: OperationalDashboardKpiId\n count: number\n deltaSinceYesterday: number | null\n sparkline: number[]\n}\n\nexport type OperationalDashboardTrendPoint = {\n month: string\n receive: number\n allocate: number\n}\n\nexport type OperationalDashboardActivityRow = {\n id: string\n movementType: string\n quantity: number\n variantSku: string | null\n variantId: string\n referenceType: string | null\n referenceId: string | null\n /** Human-readable reference (e.g. ASN reference number) \u2014 never the idempotency UUID. */\n referenceLabel: string | null\n reason: string | null\n reasonCode: string | null\n /** Movement metadata `source` (e.g. `asn_receive`) for activity label recovery. */\n source: string | null\n locationLabel: string\n performedAt: string\n}\n\nexport type OperationalDashboardExpiryLotRow = {\n id: string\n lotNumber: string\n sku: string\n catalogVariantId: string\n expiresAt: string\n availableQuantity: number\n status: string\n updatedAt: string | null\n category: 'expiringSoon' | 'pastDue'\n}\n\nexport type OperationalDashboardPayload = {\n lastUpdatedAt: string\n warehouseId: string | null\n kpis: OperationalDashboardKpi[]\n expiryLots: OperationalDashboardExpiryLotRow[]\n monthlyTrends: OperationalDashboardTrendPoint[]\n recentActivity: OperationalDashboardActivityRow[]\n}\n\nexport class OperationalDashboardWarehouseNotFoundError extends Error {\n readonly warehouseId: string\n\n constructor(warehouseId: string) {\n super(`Warehouse ${warehouseId} not found`)\n this.name = 'OperationalDashboardWarehouseNotFoundError'\n this.warehouseId = warehouseId\n }\n}\n\ntype DecryptionScope = {\n tenantId: string\n organizationId: string\n}\n\ntype DailyCountRow = {\n day: Date | string\n count: string | number\n}\n\ntype MonthlyTrendRow = {\n month_start: Date | string\n type: string\n count: string | number\n}\n\nexport function toOperationalDashboardNumber(value: string | number | null | undefined): number {\n if (typeof value === 'number') return value\n if (typeof value === 'string' && value.trim().length > 0) return Number(value)\n return 0\n}\n\nexport { addUtcDays, startOfUtcDay } from './expiry'\n\nfunction formatMonthKey(date: Date): string {\n return date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' })\n}\n\nexport function buildDailyBuckets(days: number, now: Date): Date[] {\n const today = startOfUtcDay(now)\n const buckets: Date[] = []\n for (let offset = days - 1; offset >= 0; offset -= 1) {\n buckets.push(addUtcDays(today, -offset))\n }\n return buckets\n}\n\nexport function mapDailyCountsToSparkline(\n dayBuckets: Date[],\n rows: Array<{ day: Date | string; count: string | number }>,\n): number[] {\n const counts = new Map<number, number>()\n for (const bucket of dayBuckets) {\n counts.set(bucket.getTime(), 0)\n }\n for (const row of rows) {\n const day = startOfUtcDay(row.day instanceof Date ? row.day : new Date(row.day)).getTime()\n if (counts.has(day)) {\n counts.set(day, toOperationalDashboardNumber(row.count))\n }\n }\n return dayBuckets.map((bucket) => counts.get(bucket.getTime()) ?? 0)\n}\n\nexport function resolveLotAvailableQuantity(\n lotId: string,\n balances: InventoryBalance[],\n warehouseId?: string | null,\n): number {\n let total = 0\n for (const balance of balances) {\n if (balance.lot?.id !== lotId) continue\n if (warehouseId && balance.warehouse.id !== warehouseId) continue\n const available =\n toOperationalDashboardNumber(balance.quantityOnHand)\n - toOperationalDashboardNumber(balance.quantityReserved)\n - toOperationalDashboardNumber(balance.quantityAllocated)\n if (available > 0) total += available\n }\n return total\n}\n\nexport function buildExpiryLotRows(\n expiringSoonLots: InventoryLot[],\n pastDueLots: InventoryLot[],\n balances: InventoryBalance[],\n warehouseId?: string | null,\n limit = EXPIRY_CARD_LIMIT,\n): OperationalDashboardExpiryLotRow[] {\n const expiringRows = expiringSoonLots\n .map((lot) => ({\n lot,\n availableQuantity: resolveLotAvailableQuantity(lot.id, balances, warehouseId),\n }))\n .filter(({ availableQuantity }) => availableQuantity > 0)\n .sort((left, right) => {\n const leftTime = left.lot.expiresAt?.getTime() ?? 0\n const rightTime = right.lot.expiresAt?.getTime() ?? 0\n return leftTime - rightTime\n })\n .slice(0, limit)\n .map(({ lot, availableQuantity }) => ({\n id: lot.id,\n lotNumber: lot.lotNumber,\n sku: lot.sku,\n catalogVariantId: lot.catalogVariantId,\n expiresAt: lot.expiresAt!.toISOString(),\n availableQuantity,\n status: lot.status,\n updatedAt: lot.updatedAt?.toISOString() ?? null,\n category: 'expiringSoon' as const,\n }))\n\n const pastDueRows = pastDueLots\n .map((lot) => ({\n lot,\n availableQuantity: resolveLotAvailableQuantity(lot.id, balances, warehouseId),\n }))\n .filter(({ availableQuantity }) => availableQuantity > 0)\n .sort((left, right) => {\n const leftTime = left.lot.expiresAt?.getTime() ?? 0\n const rightTime = right.lot.expiresAt?.getTime() ?? 0\n return leftTime - rightTime\n })\n .slice(0, limit)\n .map(({ lot, availableQuantity }) => ({\n id: lot.id,\n lotNumber: lot.lotNumber,\n sku: lot.sku,\n catalogVariantId: lot.catalogVariantId,\n expiresAt: lot.expiresAt!.toISOString(),\n availableQuantity,\n status: lot.status,\n updatedAt: lot.updatedAt?.toISOString() ?? null,\n category: 'pastDue' as const,\n }))\n\n return [...expiringRows, ...pastDueRows]\n}\n\nexport function resolveEffectiveProfilesByVariant(\n profiles: ProductInventoryProfile[],\n variantIdsByProduct: Map<string, string[]> = new Map(),\n): Map<string, ProductInventoryProfile> {\n const effective = new Map<string, ProductInventoryProfile>()\n for (const profile of profiles) {\n const variantId = profile.catalogVariantId\n if (variantId && !effective.has(variantId)) effective.set(variantId, profile)\n }\n for (const profile of profiles) {\n if (profile.catalogVariantId) continue\n for (const variantId of variantIdsByProduct.get(profile.catalogProductId) ?? []) {\n if (!effective.has(variantId)) effective.set(variantId, profile)\n }\n }\n return effective\n}\n\nexport function computeLowStockCounts(\n profiles: ProductInventoryProfile[],\n balances: InventoryBalance[],\n warehouseId?: string | null,\n variantIdsByProduct?: Map<string, string[]>,\n): { lowStockCount: number; reorderCriticalCount: number } {\n const availableByVariantWarehouse = new Map<string, number>()\n for (const balance of balances) {\n const onHand = toOperationalDashboardNumber(balance.quantityOnHand)\n const reserved = toOperationalDashboardNumber(balance.quantityReserved)\n const allocated = toOperationalDashboardNumber(balance.quantityAllocated)\n const available = onHand - reserved - allocated\n const key = `${balance.catalogVariantId}::${balance.warehouse.id}`\n availableByVariantWarehouse.set(key, (availableByVariantWarehouse.get(key) ?? 0) + available)\n }\n\n let lowStockCount = 0\n let reorderCriticalCount = 0\n const seenLowStockKeys = new Set<string>()\n\n for (const [variantId, profile] of resolveEffectiveProfilesByVariant(profiles, variantIdsByProduct)) {\n const reorderPoint = toOperationalDashboardNumber(profile.reorderPoint)\n const safetyStock = toOperationalDashboardNumber(profile.safetyStock)\n if (reorderPoint <= 0 && safetyStock <= 0) continue\n\n const warehouseIds = warehouseId\n ? [warehouseId]\n : Array.from(new Set(balances.map((balance) => balance.warehouse.id)))\n\n for (const scopedWarehouseId of warehouseIds) {\n const key = `${variantId}::${scopedWarehouseId}`\n const available = availableByVariantWarehouse.get(key) ?? 0\n const evaluation = evaluateLowStock(\n { reorderPoint: profile.reorderPoint, safetyStock: profile.safetyStock },\n available,\n )\n if (!evaluation) continue\n if (seenLowStockKeys.has(key)) continue\n seenLowStockKeys.add(key)\n lowStockCount += 1\n if (evaluation.state === 'below_safety_stock') {\n reorderCriticalCount += 1\n }\n }\n }\n\n return { lowStockCount, reorderCriticalCount }\n}\n\nfunction resolveWarehouseLabel(\n warehouse: Warehouse | undefined,\n warehouseId: string,\n): string {\n return warehouse?.code?.trim() || warehouse?.name?.trim() || warehouseId\n}\n\nfunction resolveLocationLabel(\n warehouseLabel: string,\n fromCode?: string | null,\n toCode?: string | null,\n): string {\n if (fromCode && toCode) return `${fromCode} \u2192 ${toCode}`\n if (toCode) return `${warehouseLabel} \u00B7 ${toCode}`\n if (fromCode) return `${warehouseLabel} \u00B7 ${fromCode}`\n return warehouseLabel\n}\n\nfunction resolveDecryptionScope(scope: OperationalDashboardScope): DecryptionScope {\n return {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n}\n\nfunction appendWarehouseFilter(\n sql: string,\n params: unknown[],\n warehouseId?: string | null,\n): string {\n if (!warehouseId) return sql\n params.push(warehouseId)\n return `${sql} and warehouse_id = ?`\n}\n\nasync function loadVariantSkus(\n em: EntityManager,\n scope: OperationalDashboardScope,\n variantIds: string[],\n): Promise<Map<string, string>> {\n if (variantIds.length === 0) return new Map()\n const rows = await em.getConnection().execute<Array<{ id: string; sku: string | null }>>(\n `select id, sku from catalog_product_variants\n where organization_id = ? and tenant_id = ? and id in (${variantIds.map(() => '?').join(', ')})\n and deleted_at is null`,\n [scope.organizationId, scope.tenantId, ...variantIds],\n )\n const map = new Map<string, string>()\n for (const row of rows) {\n if (row.id && row.sku) map.set(row.id, row.sku)\n }\n return map\n}\n\nfunction readMovementMetadataRecord(\n metadata: InventoryMovement['metadata'],\n): Record<string, unknown> | null {\n if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return null\n return metadata as Record<string, unknown>\n}\n\nexport function extractActivityMovementSource(\n metadata: InventoryMovement['metadata'],\n): string | null {\n const record = readMovementMetadataRecord(metadata)\n const source = record?.source\n return typeof source === 'string' && source.trim() ? source.trim() : null\n}\n\nexport function extractActivityAsnId(metadata: InventoryMovement['metadata']): string | null {\n const record = readMovementMetadataRecord(metadata)\n const asnId = record?.asnId\n if (typeof asnId !== 'string') return null\n const trimmed = asnId.trim()\n return UUID_PATTERN.test(trimmed) ? trimmed : null\n}\n\nexport function extractActivityAsnReferenceNumber(\n metadata: InventoryMovement['metadata'],\n): string | null {\n const record = readMovementMetadataRecord(metadata)\n const value = record?.asnReferenceNumber\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nasync function loadAsnReferenceNumbers(\n em: EntityManager,\n scope: OperationalDashboardScope,\n asnIds: string[],\n): Promise<Map<string, string>> {\n if (asnIds.length === 0) return new Map()\n const decryptionScope = resolveDecryptionScope(scope)\n const asns = await findWithDecryption(\n em,\n Asn,\n {\n id: { $in: asnIds },\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n decryptionScope,\n )\n const map = new Map<string, string>()\n for (const asn of asns) {\n const label = asn.referenceNumber?.trim()\n if (label) map.set(asn.id, label)\n }\n return map\n}\n\nasync function loadVariantIdsForProductLevelProfiles(\n em: EntityManager,\n scope: OperationalDashboardScope,\n profiles: ProductInventoryProfile[],\n): Promise<Map<string, string[]>> {\n const productIds = Array.from(\n new Set(\n profiles\n .filter((profile) => !profile.catalogVariantId && profile.catalogProductId)\n .map((profile) => profile.catalogProductId),\n ),\n )\n if (productIds.length === 0) return new Map()\n const rows = await em.getConnection().execute<Array<{ id: string; product_id: string }>>(\n `select id, product_id from catalog_product_variants\n where organization_id = ? and tenant_id = ? and product_id in (${productIds.map(() => '?').join(', ')})\n and is_active = true and deleted_at is null`,\n [scope.organizationId, scope.tenantId, ...productIds],\n )\n const map = new Map<string, string[]>()\n for (const row of rows) {\n if (!row.id || !row.product_id) continue\n const variantIds = map.get(row.product_id) ?? []\n variantIds.push(row.id)\n map.set(row.product_id, variantIds)\n }\n return map\n}\n\nasync function countMovementsBetween(\n em: EntityManager,\n scope: OperationalDashboardScope,\n start: Date,\n end: Date,\n options?: { types?: string[]; quantitySign?: 'negative' },\n): Promise<number> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, start, end]\n let sql = `\n select count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ? and performed_at < ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n if (options?.types?.length) {\n sql += ` and type in (${options.types.map(() => '?').join(', ')})`\n params.push(...options.types)\n }\n if (options?.quantitySign === 'negative') {\n sql += ' and quantity < 0'\n }\n const rows = await em.getConnection().execute<Array<{ count: string | number }>>(sql, params)\n return toOperationalDashboardNumber(rows[0]?.count)\n}\n\nasync function loadMovementDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n start: Date,\n options?: { types?: string[]; quantitySign?: 'negative' },\n): Promise<DailyCountRow[]> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, start]\n let sql = `\n select date_trunc('day', performed_at) as day, count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n if (options?.types?.length) {\n sql += ` and type in (${options.types.map(() => '?').join(', ')})`\n params.push(...options.types)\n }\n if (options?.quantitySign === 'negative') {\n sql += ' and quantity < 0'\n }\n sql += ' group by 1 order by 1'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadMonthlyMovementTrends(\n em: EntityManager,\n scope: OperationalDashboardScope,\n monthlyStart: Date,\n now: Date,\n): Promise<OperationalDashboardTrendPoint[]> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, monthlyStart]\n let sql = `\n select date_trunc('month', performed_at) as month_start, type, count(*)::int as count\n from wms_inventory_movements\n where organization_id = ? and tenant_id = ? and deleted_at is null\n and performed_at >= ?\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n sql += ' group by 1, 2 order by 1'\n\n const rows = await em.getConnection().execute<MonthlyTrendRow[]>(sql, params)\n const monthlyTrendMap = new Map<string, { receive: number; allocate: number }>()\n for (let index = 0; index < MONTHLY_TREND_MONTHS; index += 1) {\n const monthDate = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (MONTHLY_TREND_MONTHS - 1 - index), 1),\n )\n monthlyTrendMap.set(formatMonthKey(monthDate), { receive: 0, allocate: 0 })\n }\n\n for (const row of rows) {\n const monthDate = row.month_start instanceof Date ? row.month_start : new Date(row.month_start)\n const key = formatMonthKey(monthDate)\n const bucket = monthlyTrendMap.get(key)\n if (!bucket) continue\n if (row.type === 'receipt' || row.type === 'return_receive') {\n bucket.receive += toOperationalDashboardNumber(row.count)\n }\n if (row.type === 'pick' || row.type === 'pack') {\n bucket.allocate += toOperationalDashboardNumber(row.count)\n }\n }\n\n return Array.from(monthlyTrendMap.entries()).map(([month, values]) => ({\n month,\n receive: values.receive,\n allocate: values.allocate,\n }))\n}\n\nasync function loadExpiringSoonDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const trendStart = dayBuckets[0]!\n const trendEnd = dayBuckets[dayBuckets.length - 1]!\n const params: unknown[] = [\n trendStart,\n trendEnd,\n scope.organizationId,\n scope.tenantId,\n ]\n let sql = `\n select bucket.day as day, count(distinct l.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_lots l\n on l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at >= bucket.day\n and l.expires_at <= bucket.day + interval '${EXPIRING_SOON_DAYS} days'\n `\n if (scope.warehouseId) {\n sql += `\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and b.warehouse_id = ?\n `\n params.push(scope.warehouseId)\n }\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadPastDueLotCount(\n em: EntityManager,\n scope: OperationalDashboardScope,\n todayStart: Date,\n): Promise<number> {\n const params: unknown[] = [scope.organizationId, scope.tenantId, todayStart]\n let sql = `\n select count(distinct l.id)::int as count\n from wms_inventory_lots l\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and (\n coalesce(b.quantity_on_hand, 0)\n - coalesce(b.quantity_reserved, 0)\n - coalesce(b.quantity_allocated, 0)\n ) > 0\n where l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at < ?\n `\n if (scope.warehouseId) {\n sql += ' and b.warehouse_id = ?'\n params.push(scope.warehouseId)\n }\n const rows = await em.getConnection().execute<Array<{ count: string | number }>>(sql, params)\n return toOperationalDashboardNumber(rows[0]?.count)\n}\n\nasync function loadPastDueDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const params: unknown[] = [\n dayBuckets[0]!,\n dayBuckets[dayBuckets.length - 1]!,\n scope.organizationId,\n scope.tenantId,\n ]\n let sql = `\n select bucket.day as day, count(distinct l.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_lots l\n on l.organization_id = ?\n and l.tenant_id = ?\n and l.deleted_at is null\n and l.status = 'available'\n and l.expires_at is not null\n and l.expires_at < bucket.day\n join wms_inventory_balances b\n on b.lot_id = l.id\n and b.deleted_at is null\n and (\n coalesce(b.quantity_on_hand, 0)\n - coalesce(b.quantity_reserved, 0)\n - coalesce(b.quantity_allocated, 0)\n ) > 0\n `\n if (scope.warehouseId) {\n sql += ' and b.warehouse_id = ?'\n params.push(scope.warehouseId)\n }\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nasync function loadAgingReservationsDailyCounts(\n em: EntityManager,\n scope: OperationalDashboardScope,\n dayBuckets: Date[],\n): Promise<DailyCountRow[]> {\n if (dayBuckets.length === 0) return []\n const params: unknown[] = [\n dayBuckets[0]!,\n dayBuckets[dayBuckets.length - 1]!,\n scope.organizationId,\n scope.tenantId,\n String(AGING_RESERVATION_DAYS),\n ]\n let sql = `\n select bucket.day as day, count(r.id)::int as count\n from (\n select generate_series(?::timestamptz, ?::timestamptz, interval '1 day') as day\n ) bucket\n join wms_inventory_reservations r\n on r.organization_id = ?\n and r.tenant_id = ?\n and r.deleted_at is null\n and r.status = 'active'\n and r.created_at <= bucket.day - (?::int * interval '1 day')\n `\n sql = appendWarehouseFilter(sql, params, scope.warehouseId)\n sql += ' group by bucket.day order by bucket.day'\n return em.getConnection().execute<DailyCountRow[]>(sql, params)\n}\n\nexport async function loadOperationalDashboard(\n em: EntityManager,\n scope: OperationalDashboardScope,\n): Promise<OperationalDashboardPayload> {\n const now = new Date()\n const todayStart = startOfUtcDay(now)\n const tomorrowStart = addUtcDays(todayStart, 1)\n const yesterdayStart = addUtcDays(todayStart, -1)\n const agingCutoff = addUtcDays(todayStart, -AGING_RESERVATION_DAYS)\n const expiringCutoff = addUtcDays(todayStart, EXPIRING_SOON_DAYS)\n const trendStart = addUtcDays(todayStart, -(TREND_DAYS - 1))\n const dayBuckets = buildDailyBuckets(TREND_DAYS, now)\n const decryptionScope = resolveDecryptionScope(scope)\n\n const baseScope = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null as Date | null,\n }\n\n if (scope.warehouseId) {\n const warehouse = await findOneWithDecryption(\n em,\n Warehouse,\n { id: scope.warehouseId, ...baseScope },\n undefined,\n decryptionScope,\n )\n if (!warehouse) {\n throw new OperationalDashboardWarehouseNotFoundError(scope.warehouseId)\n }\n }\n\n const warehouseFilter = scope.warehouseId ? { warehouse: scope.warehouseId } : {}\n\n const [profiles, balances, expiringSoonLots, pastDueLots, reservations, activityMovements] = await Promise.all([\n findWithDecryption(em, ProductInventoryProfile, baseScope, undefined, decryptionScope),\n findWithDecryption(\n em,\n InventoryBalance,\n { ...baseScope, ...warehouseFilter },\n { populate: ['warehouse', 'location', 'lot'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryLot,\n {\n ...baseScope,\n status: 'available',\n expiresAt: { $ne: null, $gte: todayStart, $lte: expiringCutoff },\n },\n { orderBy: { expiresAt: 'ASC' }, limit: 100 },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryLot,\n {\n ...baseScope,\n status: 'available',\n expiresAt: { $ne: null, $lt: todayStart },\n },\n { orderBy: { expiresAt: 'ASC' }, limit: 100 },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryReservation,\n {\n ...baseScope,\n ...warehouseFilter,\n status: 'active',\n createdAt: { $lte: agingCutoff },\n },\n undefined,\n decryptionScope,\n ),\n findWithDecryption(\n em,\n InventoryMovement,\n { ...baseScope, ...warehouseFilter },\n {\n populate: ['warehouse', 'locationFrom', 'locationTo'],\n orderBy: { performedAt: 'DESC' },\n limit: ACTIVITY_LIMIT,\n },\n decryptionScope,\n ),\n ])\n\n const variantIdsByProduct = await loadVariantIdsForProductLevelProfiles(em, scope, profiles)\n const { lowStockCount, reorderCriticalCount } = computeLowStockCounts(\n profiles,\n balances,\n scope.warehouseId,\n variantIdsByProduct,\n )\n\n const expiringSoonCount = expiringSoonLots.filter(\n (lot) => resolveLotAvailableQuantity(lot.id, balances, scope.warehouseId) > 0,\n ).length\n\n const agingReservationsCount = reservations.length\n\n const [\n todaysMoveCount,\n yesterdaysMoveCount,\n pastDueCount,\n lowStockSparklineRows,\n reorderCriticalSparklineRows,\n expiringSoonSparklineRows,\n pastDueSparklineRows,\n agingReservationsSparklineRows,\n todaysMovesSparklineRows,\n monthlyTrends,\n ] = await Promise.all([\n countMovementsBetween(em, scope, todayStart, tomorrowStart),\n countMovementsBetween(em, scope, yesterdayStart, todayStart),\n loadPastDueLotCount(em, scope, todayStart),\n loadMovementDailyCounts(em, scope, trendStart, { types: ['adjust'], quantitySign: 'negative' }),\n loadMovementDailyCounts(em, scope, trendStart, { types: ['adjust'], quantitySign: 'negative' }),\n loadExpiringSoonDailyCounts(em, scope, dayBuckets),\n loadPastDueDailyCounts(em, scope, dayBuckets),\n loadAgingReservationsDailyCounts(em, scope, dayBuckets),\n loadMovementDailyCounts(em, scope, trendStart),\n loadMonthlyMovementTrends(\n em,\n scope,\n new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - (MONTHLY_TREND_MONTHS - 1), 1)),\n now,\n ),\n ])\n\n const kpis: OperationalDashboardKpi[] = [\n {\n id: 'lowStock',\n count: lowStockCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, lowStockSparklineRows),\n },\n {\n id: 'reorderCritical',\n count: reorderCriticalCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, reorderCriticalSparklineRows),\n },\n {\n id: 'expiringSoon',\n count: expiringSoonCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, expiringSoonSparklineRows),\n },\n {\n id: 'pastDue',\n count: pastDueCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, pastDueSparklineRows),\n },\n {\n id: 'agingReservations',\n count: agingReservationsCount,\n deltaSinceYesterday: null,\n sparkline: mapDailyCountsToSparkline(dayBuckets, agingReservationsSparklineRows),\n },\n {\n id: 'todaysMoves',\n count: todaysMoveCount,\n deltaSinceYesterday: todaysMoveCount - yesterdaysMoveCount,\n sparkline: mapDailyCountsToSparkline(dayBuckets, todaysMovesSparklineRows),\n },\n ]\n\n const variantIds = Array.from(new Set(activityMovements.map((movement) => movement.catalogVariantId)))\n const variantSkus = await loadVariantSkus(em, scope, variantIds)\n\n const asnIdsNeedingLookup = Array.from(\n new Set(\n activityMovements\n .map((movement) => {\n if (extractActivityAsnReferenceNumber(movement.metadata)) return null\n return extractActivityAsnId(movement.metadata)\n })\n .filter((id): id is string => Boolean(id)),\n ),\n )\n const asnReferenceNumbers = await loadAsnReferenceNumbers(em, scope, asnIdsNeedingLookup)\n\n const recentActivity: OperationalDashboardActivityRow[] = activityMovements.map((movement) => {\n const warehouseLabel = resolveWarehouseLabel(movement.warehouse, movement.warehouse.id)\n const source = extractActivityMovementSource(movement.metadata)\n const metadataAsnReference = extractActivityAsnReferenceNumber(movement.metadata)\n const asnId = extractActivityAsnId(movement.metadata)\n const referenceLabel =\n metadataAsnReference ?? (asnId ? asnReferenceNumbers.get(asnId) ?? null : null)\n const reasonCode =\n movement.reasonCode?.trim() || (source === 'asn_receive' ? 'asn_receive' : null)\n return {\n id: movement.id,\n movementType: movement.type,\n quantity: toOperationalDashboardNumber(movement.quantity),\n variantSku: variantSkus.get(movement.catalogVariantId) ?? null,\n variantId: movement.catalogVariantId,\n referenceType: movement.referenceType ?? null,\n referenceId: movement.referenceId ?? null,\n referenceLabel,\n reason: movement.reason ?? null,\n reasonCode,\n source,\n locationLabel: resolveLocationLabel(\n warehouseLabel,\n movement.locationFrom?.code,\n movement.locationTo?.code,\n ),\n performedAt: movement.performedAt.toISOString(),\n }\n })\n\n const expiryLots = buildExpiryLotRows(\n expiringSoonLots,\n pastDueLots,\n balances,\n scope.warehouseId,\n )\n\n return {\n lastUpdatedAt: now.toISOString(),\n warehouseId: scope.warehouseId ?? null,\n kpis,\n expiryLots,\n monthlyTrends,\n recentActivity,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["addUtcDays", "startOfUtcDay"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
|
+
import {
|
|
5
|
+
InventoryMovement
|
|
6
|
+
} from "../data/entities.js";
|
|
7
|
+
const TERMINAL = /* @__PURE__ */ new Set(["done", "cancelled"]);
|
|
8
|
+
const FORBIDDEN_PUTAWAY_TARGET_TYPES = /* @__PURE__ */ new Set(["staging", "dock"]);
|
|
9
|
+
const PUTAWAY_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
10
|
+
"open",
|
|
11
|
+
"in_progress",
|
|
12
|
+
"done",
|
|
13
|
+
"cancelled"
|
|
14
|
+
]);
|
|
15
|
+
function buildPutawayTaskStatusFilter(statusQuery) {
|
|
16
|
+
if (typeof statusQuery !== "string" || statusQuery.trim().length === 0) return void 0;
|
|
17
|
+
const statuses = [
|
|
18
|
+
...new Set(
|
|
19
|
+
statusQuery.split(",").map((value) => value.trim()).filter(
|
|
20
|
+
(value) => PUTAWAY_TASK_STATUSES.has(value)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
];
|
|
24
|
+
if (statuses.length === 0) return void 0;
|
|
25
|
+
if (statuses.length === 1) return { $eq: statuses[0] };
|
|
26
|
+
return { $in: statuses };
|
|
27
|
+
}
|
|
28
|
+
function assertPutawayAssignable(status) {
|
|
29
|
+
if (TERMINAL.has(status)) {
|
|
30
|
+
throw new CrudHttpError(409, { error: "invalid_putaway_state" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function assertPutawayStartable(status) {
|
|
34
|
+
if (status !== "open" && status !== "in_progress") {
|
|
35
|
+
throw new CrudHttpError(409, { error: "invalid_putaway_state" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function assertPutawayCompletable(status) {
|
|
39
|
+
if (status !== "open" && status !== "in_progress") {
|
|
40
|
+
throw new CrudHttpError(409, { error: "invalid_putaway_state" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function assertPutawayCancellable(status) {
|
|
44
|
+
if (TERMINAL.has(status)) {
|
|
45
|
+
throw new CrudHttpError(409, { error: "invalid_putaway_state" });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function assertPutawayDeletable(status) {
|
|
49
|
+
if (status === "cancelled") return;
|
|
50
|
+
if (status === "done") {
|
|
51
|
+
throw new CrudHttpError(409, { error: "putaway_delete_done_forbidden" });
|
|
52
|
+
}
|
|
53
|
+
throw new CrudHttpError(409, { error: "putaway_delete_requires_terminal_status" });
|
|
54
|
+
}
|
|
55
|
+
function assertPutawayLifecycleFieldsForbidden(rawInput) {
|
|
56
|
+
if (!rawInput || typeof rawInput !== "object") return;
|
|
57
|
+
const payload = rawInput;
|
|
58
|
+
if ("status" in payload || "assignedTo" in payload || "assigned_to" in payload) {
|
|
59
|
+
throw new CrudHttpError(422, { error: "lifecycle_field_forbidden" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function assertPutawayCompleteAuthorized(input) {
|
|
63
|
+
if (input.canManagePutaway) return;
|
|
64
|
+
if (input.canAdjustInventory && typeof input.actorUserId === "string" && input.actorUserId.length > 0 && input.assignedTo === input.actorUserId) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
68
|
+
}
|
|
69
|
+
function assertPutawayConfirmedQuantity(taskQuantity, confirmedQuantity) {
|
|
70
|
+
if (!(confirmedQuantity > 0) || !Number.isFinite(confirmedQuantity)) {
|
|
71
|
+
throw new CrudHttpError(422, { error: "invalid_putaway_quantity" });
|
|
72
|
+
}
|
|
73
|
+
if (confirmedQuantity > taskQuantity + 1e-6) {
|
|
74
|
+
throw new CrudHttpError(422, { error: "putaway_quantity_exceeds_task" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function putawayResidualQuantity(taskQuantity, confirmedQuantity) {
|
|
78
|
+
const residual = taskQuantity - confirmedQuantity;
|
|
79
|
+
return residual > 1e-6 ? residual : 0;
|
|
80
|
+
}
|
|
81
|
+
function computeUncommittedPutawaySourceQuantity(input) {
|
|
82
|
+
const available = input.quantityOnHand - input.quantityReserved - input.quantityAllocated;
|
|
83
|
+
const committed = input.openPutawayQuantities.reduce((sum, qty) => sum + qty, 0);
|
|
84
|
+
return available - committed;
|
|
85
|
+
}
|
|
86
|
+
function hasUncommittedPutawaySourceQuantity(remainingAvailable, requestedQuantity) {
|
|
87
|
+
return requestedQuantity <= remainingAvailable + 1e-6;
|
|
88
|
+
}
|
|
89
|
+
function selectCoveringOpenPutawayTask(tasks, requestedQuantity) {
|
|
90
|
+
if (!(requestedQuantity > 1e-6) || tasks.length === 0) return null;
|
|
91
|
+
let best = null;
|
|
92
|
+
for (const task of tasks) {
|
|
93
|
+
if (task.quantity + 1e-6 < requestedQuantity) continue;
|
|
94
|
+
if (!best || task.quantity < best.quantity) best = task;
|
|
95
|
+
}
|
|
96
|
+
return best;
|
|
97
|
+
}
|
|
98
|
+
function buildPutawayCompleteReferenceId(taskId) {
|
|
99
|
+
const hex = createHash("sha256").update(`wms:putaway-complete:${taskId}`).digest("hex");
|
|
100
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
101
|
+
}
|
|
102
|
+
function assertPutawayTargetLocationType(type) {
|
|
103
|
+
if (FORBIDDEN_PUTAWAY_TARGET_TYPES.has(type)) {
|
|
104
|
+
throw new CrudHttpError(422, { error: "invalid_putaway_target_location" });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function resolveLotIdFromMovement(em, ctx, movementId) {
|
|
108
|
+
const tenantId = ctx.auth?.tenantId ?? null;
|
|
109
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
110
|
+
if (!tenantId || !organizationId) return null;
|
|
111
|
+
const movement = await findOneWithDecryption(
|
|
112
|
+
em,
|
|
113
|
+
InventoryMovement,
|
|
114
|
+
{ id: movementId, deletedAt: null },
|
|
115
|
+
void 0,
|
|
116
|
+
{ tenantId, organizationId }
|
|
117
|
+
);
|
|
118
|
+
if (!movement) return null;
|
|
119
|
+
const lotRaw = movement.lot ?? null;
|
|
120
|
+
if (!lotRaw) return null;
|
|
121
|
+
return typeof lotRaw === "string" ? lotRaw : lotRaw.id;
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
assertPutawayAssignable,
|
|
125
|
+
assertPutawayCancellable,
|
|
126
|
+
assertPutawayCompletable,
|
|
127
|
+
assertPutawayCompleteAuthorized,
|
|
128
|
+
assertPutawayConfirmedQuantity,
|
|
129
|
+
assertPutawayDeletable,
|
|
130
|
+
assertPutawayLifecycleFieldsForbidden,
|
|
131
|
+
assertPutawayStartable,
|
|
132
|
+
assertPutawayTargetLocationType,
|
|
133
|
+
buildPutawayCompleteReferenceId,
|
|
134
|
+
buildPutawayTaskStatusFilter,
|
|
135
|
+
computeUncommittedPutawaySourceQuantity,
|
|
136
|
+
hasUncommittedPutawaySourceQuantity,
|
|
137
|
+
putawayResidualQuantity,
|
|
138
|
+
resolveLotIdFromMovement,
|
|
139
|
+
selectCoveringOpenPutawayTask
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=putaway.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/wms/lib/putaway.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n InventoryMovement,\n type PutawayTaskStatus,\n type WarehouseLocationType,\n} from '../data/entities'\n\nconst TERMINAL: ReadonlySet<PutawayTaskStatus> = new Set(['done', 'cancelled'])\nconst FORBIDDEN_PUTAWAY_TARGET_TYPES: ReadonlySet<WarehouseLocationType> = new Set(['staging', 'dock'])\nconst PUTAWAY_TASK_STATUSES: ReadonlySet<PutawayTaskStatus> = new Set([\n 'open',\n 'in_progress',\n 'done',\n 'cancelled',\n])\n\n/**\n * Parse list `status` query: single value or comma-separated (e.g. `open,in_progress`).\n * Returns `$eq` / `$in` filter fragment, or undefined when empty/invalid.\n */\nexport function buildPutawayTaskStatusFilter(\n statusQuery: string | undefined,\n): { $eq: PutawayTaskStatus } | { $in: PutawayTaskStatus[] } | undefined {\n if (typeof statusQuery !== 'string' || statusQuery.trim().length === 0) return undefined\n const statuses = [\n ...new Set(\n statusQuery\n .split(',')\n .map((value) => value.trim())\n .filter((value): value is PutawayTaskStatus =>\n PUTAWAY_TASK_STATUSES.has(value as PutawayTaskStatus),\n ),\n ),\n ]\n if (statuses.length === 0) return undefined\n if (statuses.length === 1) return { $eq: statuses[0]! }\n return { $in: statuses }\n}\n\nexport function assertPutawayAssignable(status: PutawayTaskStatus): void {\n if (TERMINAL.has(status)) {\n throw new CrudHttpError(409, { error: 'invalid_putaway_state' })\n }\n}\n\nexport function assertPutawayStartable(status: PutawayTaskStatus): void {\n if (status !== 'open' && status !== 'in_progress') {\n throw new CrudHttpError(409, { error: 'invalid_putaway_state' })\n }\n}\n\nexport function assertPutawayCompletable(status: PutawayTaskStatus): void {\n if (status !== 'open' && status !== 'in_progress') {\n throw new CrudHttpError(409, { error: 'invalid_putaway_state' })\n }\n}\n\nexport function assertPutawayCancellable(status: PutawayTaskStatus): void {\n if (TERMINAL.has(status)) {\n throw new CrudHttpError(409, { error: 'invalid_putaway_state' })\n }\n}\n\n/**\n * Soft-delete is archival cleanup for cancelled tasks only.\n * Done is permanent history: soft-deleting it would drop the row from the\n * partial unique index (`deleted_at IS NULL`) while leaving `putaway_key`,\n * letting idempotent ASN receive recreate an open putaway for already-moved stock.\n * Cancel first (clears the key); refuse open/in_progress/done delete.\n */\nexport function assertPutawayDeletable(status: PutawayTaskStatus): void {\n if (status === 'cancelled') return\n if (status === 'done') {\n throw new CrudHttpError(409, { error: 'putaway_delete_done_forbidden' })\n }\n throw new CrudHttpError(409, { error: 'putaway_delete_requires_terminal_status' })\n}\n\n/** Reject lifecycle fields on putaway-task CRUD before zod strips them. */\nexport function assertPutawayLifecycleFieldsForbidden(rawInput: unknown): void {\n if (!rawInput || typeof rawInput !== 'object') return\n const payload = rawInput as Record<string, unknown>\n // Status and assignee changes go through dedicated lifecycle endpoints.\n if ('status' in payload || 'assignedTo' in payload || 'assigned_to' in payload) {\n throw new CrudHttpError(422, { error: 'lifecycle_field_forbidden' })\n }\n}\n\n/**\n * Complete authorization: managers may complete any task; operators with\n * `wms.adjust_inventory` may complete only when the task is assigned to them.\n */\nexport function assertPutawayCompleteAuthorized(input: {\n canManagePutaway: boolean\n canAdjustInventory: boolean\n actorUserId: string | null | undefined\n assignedTo: string | null | undefined\n}): void {\n if (input.canManagePutaway) return\n if (\n input.canAdjustInventory\n && typeof input.actorUserId === 'string'\n && input.actorUserId.length > 0\n && input.assignedTo === input.actorUserId\n ) {\n return\n }\n throw new CrudHttpError(403, { error: 'Forbidden' })\n}\n\n/** Reject completing with more than the open task quantity. */\nexport function assertPutawayConfirmedQuantity(\n taskQuantity: number,\n confirmedQuantity: number,\n): void {\n if (!(confirmedQuantity > 0) || !Number.isFinite(confirmedQuantity)) {\n throw new CrudHttpError(422, { error: 'invalid_putaway_quantity' })\n }\n if (confirmedQuantity > taskQuantity + 0.000001) {\n throw new CrudHttpError(422, { error: 'putaway_quantity_exceeds_task' })\n }\n}\n\nexport function putawayResidualQuantity(taskQuantity: number, confirmedQuantity: number): number {\n const residual = taskQuantity - confirmedQuantity\n return residual > 0.000001 ? residual : 0\n}\n\n/**\n * Staging floor for new putaway: on-hand minus reserved/allocated minus open\n * and in-progress putaway commitments (optionally excluding one task being updated).\n */\nexport function computeUncommittedPutawaySourceQuantity(input: {\n quantityOnHand: number\n quantityReserved: number\n quantityAllocated: number\n openPutawayQuantities: number[]\n}): number {\n const available =\n input.quantityOnHand - input.quantityReserved - input.quantityAllocated\n const committed = input.openPutawayQuantities.reduce((sum, qty) => sum + qty, 0)\n return available - committed\n}\n\n/** True when requested qty fits the uncommitted staging floor (epsilon-tolerant). */\nexport function hasUncommittedPutawaySourceQuantity(\n remainingAvailable: number,\n requestedQuantity: number,\n): boolean {\n return requestedQuantity <= remainingAvailable + 0.000001\n}\n\n/**\n * Prefer an open/in_progress peer whose quantity covers the receipt when\n * recreate would oversubscribe staging stock already queued elsewhere.\n */\nexport function selectCoveringOpenPutawayTask<T extends { quantity: number }>(\n tasks: T[],\n requestedQuantity: number,\n): T | null {\n if (!(requestedQuantity > 0.000001) || tasks.length === 0) return null\n let best: T | null = null\n for (const task of tasks) {\n if (task.quantity + 0.000001 < requestedQuantity) continue\n if (!best || task.quantity < best.quantity) best = task\n }\n return best\n}\n\n/**\n * Stable movement reference for putaway complete \u2014 task id only (not confirmed qty).\n * Retries with the same or different confirmedQuantity must not open a second\n * stock move under a different idempotency signature; quantity conflicts are\n * detected by looking up this reference before moving.\n */\nexport function buildPutawayCompleteReferenceId(taskId: string): string {\n const hex = createHash('sha256').update(`wms:putaway-complete:${taskId}`).digest('hex')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`\n}\n\nexport function assertPutawayTargetLocationType(type: WarehouseLocationType): void {\n if (FORBIDDEN_PUTAWAY_TARGET_TYPES.has(type)) {\n throw new CrudHttpError(422, { error: 'invalid_putaway_target_location' })\n }\n}\n\nexport async function resolveLotIdFromMovement(\n em: EntityManager,\n ctx: CommandRuntimeContext,\n movementId: string,\n): Promise<string | null> {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!tenantId || !organizationId) return null\n const movement = await findOneWithDecryption(\n em,\n InventoryMovement,\n { id: movementId, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n if (!movement) return null\n const lotRaw = movement.lot ?? null\n if (!lotRaw) return null\n return typeof lotRaw === 'string' ? lotRaw : lotRaw.id\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -12,7 +12,9 @@ const WMS_MANAGE_FEATURES = [
|
|
|
12
12
|
"wms.manage_zones",
|
|
13
13
|
"wms.manage_locations",
|
|
14
14
|
"wms.manage_inventory",
|
|
15
|
-
"wms.manage_reservations"
|
|
15
|
+
"wms.manage_reservations",
|
|
16
|
+
"wms.manage_asn",
|
|
17
|
+
"wms.manage_putaway"
|
|
16
18
|
];
|
|
17
19
|
const WMS_SUPERVISOR_FEATURES = [
|
|
18
20
|
...WMS_OPERATOR_FEATURES,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/wms/lib/roleFeatures.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Reserved tenant role names seeded by WMS `setup.ts` via `ensureRoles`.\n * Other modules MUST NOT reuse `operator` or `supervisor` without coordinating ACL grants and i18n labels.\n */\nexport const WMS_OPERATOR_ROLE = 'operator' as const\nexport const WMS_SUPERVISOR_ROLE = 'supervisor' as const\n\nexport const WMS_CUSTOM_ROLE_NAMES = [WMS_OPERATOR_ROLE, WMS_SUPERVISOR_ROLE] as const\n\nexport const WMS_OPERATOR_FEATURES = [\n 'wms.view',\n 'wms.adjust_inventory',\n 'wms.receive_inventory',\n 'wms.cycle_count',\n] as const\n\nexport const WMS_MANAGE_FEATURES = [\n 'wms.manage_warehouses',\n 'wms.manage_zones',\n 'wms.manage_locations',\n 'wms.manage_inventory',\n 'wms.manage_reservations',\n] as const\n\nexport const WMS_SUPERVISOR_FEATURES = [\n ...WMS_OPERATOR_FEATURES,\n 'wms.import',\n ...WMS_MANAGE_FEATURES,\n] as const\n\nexport type WmsOperatorFeature = (typeof WMS_OPERATOR_FEATURES)[number]\nexport type WmsSupervisorFeature = (typeof WMS_SUPERVISOR_FEATURES)[number]\n"],
|
|
5
|
-
"mappings": "AAIO,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAE5B,MAAM,wBAAwB,CAAC,mBAAmB,mBAAmB;AAErE,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,0BAA0B;AAAA,EACrC,GAAG;AAAA,EACH;AAAA,EACA,GAAG;AACL;",
|
|
4
|
+
"sourcesContent": ["/**\n * Reserved tenant role names seeded by WMS `setup.ts` via `ensureRoles`.\n * Other modules MUST NOT reuse `operator` or `supervisor` without coordinating ACL grants and i18n labels.\n */\nexport const WMS_OPERATOR_ROLE = 'operator' as const\nexport const WMS_SUPERVISOR_ROLE = 'supervisor' as const\n\nexport const WMS_CUSTOM_ROLE_NAMES = [WMS_OPERATOR_ROLE, WMS_SUPERVISOR_ROLE] as const\n\nexport const WMS_OPERATOR_FEATURES = [\n 'wms.view',\n 'wms.adjust_inventory',\n 'wms.receive_inventory',\n 'wms.cycle_count',\n] as const\n\nexport const WMS_MANAGE_FEATURES = [\n 'wms.manage_warehouses',\n 'wms.manage_zones',\n 'wms.manage_locations',\n 'wms.manage_inventory',\n 'wms.manage_reservations',\n 'wms.manage_asn',\n 'wms.manage_putaway',\n] as const\n\nexport const WMS_SUPERVISOR_FEATURES = [\n ...WMS_OPERATOR_FEATURES,\n 'wms.import',\n ...WMS_MANAGE_FEATURES,\n] as const\n\nexport type WmsOperatorFeature = (typeof WMS_OPERATOR_FEATURES)[number]\nexport type WmsSupervisorFeature = (typeof WMS_SUPERVISOR_FEATURES)[number]\n"],
|
|
5
|
+
"mappings": "AAIO,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAE5B,MAAM,wBAAwB,CAAC,mBAAmB,mBAAmB;AAErE,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,0BAA0B;AAAA,EACrC,GAAG;AAAA,EACH;AAAA,EACA,GAAG;AACL;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|