@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,20 +1,32 @@
|
|
|
1
1
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
2
2
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
3
4
|
import { E } from "../../../generated/entities.ids.generated.js";
|
|
4
5
|
import { InventoryBalance, InventoryReservation } from "../data/entities.js";
|
|
5
6
|
import { emitWmsEvent } from "../events.js";
|
|
6
7
|
import { loadExplicitWarehouseIdForOrder } from "./salesOrderWarehouseAssignment.js";
|
|
8
|
+
import { isBalanceLocationReservable } from "./inventoryPolicy.js";
|
|
7
9
|
import {
|
|
8
10
|
resolvePrimaryWarehouseId,
|
|
9
11
|
sortWarehouseAvailabilityForReservation
|
|
10
12
|
} from "./primaryWarehousePolicy.js";
|
|
11
13
|
import { resolveWmsIntegrationToggleEnabled } from "./wmsIntegrationToggles.js";
|
|
14
|
+
const logger = createLogger("wms");
|
|
12
15
|
function isInsufficientStockError(error) {
|
|
13
16
|
return error instanceof CrudHttpError && error.status === 409 && error.body?.error === "insufficient_stock";
|
|
14
17
|
}
|
|
15
18
|
function isBalanceIntegrityViolationError(error) {
|
|
16
19
|
return error instanceof CrudHttpError && error.status === 409 && error.body?.error === "balance_integrity_violation";
|
|
17
20
|
}
|
|
21
|
+
function isReservableOrderStatus(status, fulfillmentStatus) {
|
|
22
|
+
const normalized = (status ?? "").trim().toLowerCase();
|
|
23
|
+
if (normalized !== "confirmed") return false;
|
|
24
|
+
const fulfillment = (fulfillmentStatus ?? "").trim().toLowerCase();
|
|
25
|
+
if (fulfillment === "fulfilled" || fulfillment === "cancelled" || fulfillment === "canceled") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
18
30
|
function toNumber(value) {
|
|
19
31
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
20
32
|
if (typeof value === "string" && value.trim().length > 0) {
|
|
@@ -116,7 +128,7 @@ async function loadBalances(em, variantIds, scope) {
|
|
|
116
128
|
organizationId: scope.organizationId,
|
|
117
129
|
deletedAt: null
|
|
118
130
|
},
|
|
119
|
-
|
|
131
|
+
{ populate: ["location"] },
|
|
120
132
|
scope
|
|
121
133
|
);
|
|
122
134
|
}
|
|
@@ -146,6 +158,7 @@ function buildReservedQuantityByVariant(reservations) {
|
|
|
146
158
|
function buildWarehouseAvailability(balances) {
|
|
147
159
|
const byVariant = /* @__PURE__ */ new Map();
|
|
148
160
|
for (const balance of balances) {
|
|
161
|
+
if (!isBalanceLocationReservable(balance)) continue;
|
|
149
162
|
const warehouseId = getWarehouseId(balance);
|
|
150
163
|
if (!warehouseId) continue;
|
|
151
164
|
const available = toNumber(balance.quantityOnHand) - toNumber(balance.quantityReserved) - toNumber(balance.quantityAllocated);
|
|
@@ -257,6 +270,89 @@ async function reserveInventoryForConfirmedOrder(payload, ctx) {
|
|
|
257
270
|
});
|
|
258
271
|
}
|
|
259
272
|
}
|
|
273
|
+
const REEVAL_ORDER_PAGE_SIZE = 500;
|
|
274
|
+
async function reevaluateReservationsAfterStockIncrease(payload, ctx) {
|
|
275
|
+
if (!payload.catalogVariantId || !payload.tenantId || !payload.organizationId) return;
|
|
276
|
+
if (!await isInventoryAutomationEnabled(ctx, payload.tenantId)) return;
|
|
277
|
+
const scope = {
|
|
278
|
+
tenantId: payload.tenantId,
|
|
279
|
+
organizationId: payload.organizationId
|
|
280
|
+
};
|
|
281
|
+
let queryEngine;
|
|
282
|
+
try {
|
|
283
|
+
queryEngine = ctx.resolve("queryEngine");
|
|
284
|
+
} catch {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
let page = 1;
|
|
288
|
+
for (; ; ) {
|
|
289
|
+
let orderItems;
|
|
290
|
+
try {
|
|
291
|
+
const result = await queryEngine.query(E.sales.sales_order, {
|
|
292
|
+
tenantId: scope.tenantId,
|
|
293
|
+
organizationId: scope.organizationId,
|
|
294
|
+
filters: { status: { $eq: "confirmed" } },
|
|
295
|
+
fields: ["id", "status", "fulfillment_status"],
|
|
296
|
+
page: { page, pageSize: REEVAL_ORDER_PAGE_SIZE }
|
|
297
|
+
});
|
|
298
|
+
orderItems = result.items;
|
|
299
|
+
} catch {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (orderItems.length === 0) break;
|
|
303
|
+
const reservableOrderIds = orderItems.filter((order) => isReservableOrderStatus(order.status, order.fulfillment_status)).map((order) => order.id).filter((value) => typeof value === "string" && value.length > 0);
|
|
304
|
+
if (reservableOrderIds.length > 0) {
|
|
305
|
+
const matchingOrderIds = /* @__PURE__ */ new Set();
|
|
306
|
+
let linePage = 1;
|
|
307
|
+
try {
|
|
308
|
+
for (; ; ) {
|
|
309
|
+
const lines = await queryEngine.query(
|
|
310
|
+
E.sales.sales_order_line,
|
|
311
|
+
{
|
|
312
|
+
tenantId: scope.tenantId,
|
|
313
|
+
organizationId: scope.organizationId,
|
|
314
|
+
filters: {
|
|
315
|
+
product_variant_id: { $eq: payload.catalogVariantId },
|
|
316
|
+
order_id: { $in: reservableOrderIds }
|
|
317
|
+
},
|
|
318
|
+
fields: ["id", "order_id", "product_variant_id"],
|
|
319
|
+
page: { page: linePage, pageSize: REEVAL_ORDER_PAGE_SIZE }
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
for (const line of lines.items) {
|
|
323
|
+
if (typeof line.order_id === "string" && line.order_id.length > 0) {
|
|
324
|
+
matchingOrderIds.add(line.order_id);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (lines.items.length < REEVAL_ORDER_PAGE_SIZE) break;
|
|
328
|
+
linePage += 1;
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
logger.warn("Reservation re-eval line lookup failed; continuing next page", {
|
|
332
|
+
catalogVariantId: payload.catalogVariantId,
|
|
333
|
+
page,
|
|
334
|
+
orderCount: reservableOrderIds.length,
|
|
335
|
+
error: error instanceof Error ? error.message : String(error)
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
for (const orderId of matchingOrderIds) {
|
|
339
|
+
try {
|
|
340
|
+
await reserveInventoryForConfirmedOrder(
|
|
341
|
+
{
|
|
342
|
+
orderId,
|
|
343
|
+
tenantId: scope.tenantId,
|
|
344
|
+
organizationId: scope.organizationId
|
|
345
|
+
},
|
|
346
|
+
ctx
|
|
347
|
+
);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (orderItems.length < REEVAL_ORDER_PAGE_SIZE) break;
|
|
353
|
+
page += 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
260
356
|
async function releaseInventoryForCancelledOrder(payload, ctx) {
|
|
261
357
|
if (!payload.orderId || !payload.tenantId || !payload.organizationId) return;
|
|
262
358
|
if (!await isInventoryAutomationEnabled(ctx, payload.tenantId)) return;
|
|
@@ -299,7 +395,9 @@ async function releaseInventoryForCancelledOrder(payload, ctx) {
|
|
|
299
395
|
}
|
|
300
396
|
}
|
|
301
397
|
export {
|
|
398
|
+
isReservableOrderStatus,
|
|
302
399
|
isSalesOrderCancelled,
|
|
400
|
+
reevaluateReservationsAfterStockIncrease,
|
|
303
401
|
releaseInventoryForCancelledOrder,
|
|
304
402
|
reserveInventoryForConfirmedOrder
|
|
305
403
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/wms/lib/salesOrderInventoryAutomation.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { E } from '#generated/entities.ids.generated'\nimport { InventoryBalance, InventoryReservation } from '../data/entities'\nimport { emitWmsEvent } from '../events'\nimport { loadExplicitWarehouseIdForOrder } from './salesOrderWarehouseAssignment'\nimport {\n resolvePrimaryWarehouseId,\n sortWarehouseAvailabilityForReservation,\n type WarehouseAvailability,\n} from './primaryWarehousePolicy'\nimport { resolveWmsIntegrationToggleEnabled } from './wmsIntegrationToggles'\n\ntype EventContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype SalesOrderLifecyclePayload = {\n orderId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype Scope = {\n tenantId: string\n organizationId: string\n}\n\ntype ReservationShortfallLine = {\n catalogVariantId: string\n requiredQuantity: number\n reservedQuantity: number\n shortfallQuantity: number\n}\n\nfunction isInsufficientStockError(error: unknown): boolean {\n return error instanceof CrudHttpError && error.status === 409 && error.body?.error === 'insufficient_stock'\n}\n\nfunction isBalanceIntegrityViolationError(error: unknown): boolean {\n return error instanceof CrudHttpError && error.status === 409 && error.body?.error === 'balance_integrity_violation'\n}\n\ntype SalesOrderRow = {\n id?: string\n order_number?: string | null\n status?: string | null\n tenant_id?: string | null\n organization_id?: string | null\n}\n\ntype SalesOrderLineRow = {\n id?: string\n kind?: string | null\n product_variant_id?: string | null\n quantity?: string | number | null\n line_number?: number | null\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction getWarehouseId(balance: InventoryBalance): string | null {\n const relation = balance.warehouse as { id?: string } | string | undefined\n if (typeof relation === 'string') return relation\n return typeof relation?.id === 'string' ? relation.id : null\n}\n\nfunction buildCommandContext(ctx: EventContext, scope: Scope): CommandRuntimeContext {\n return {\n container: {\n resolve: ctx.resolve,\n } as CommandRuntimeContext['container'],\n auth: null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nasync function isInventoryAutomationEnabled(ctx: EventContext, tenantId: string): Promise<boolean> {\n try {\n const featureTogglesService = ctx.resolve<FeatureTogglesService>('featureTogglesService')\n const em = ctx.resolve<EntityManager>('em')\n return resolveWmsIntegrationToggleEnabled(\n featureTogglesService,\n em,\n 'wms_integration_sales_order_inventory',\n tenantId,\n )\n } catch {\n return true\n }\n}\n\nasync function loadOrderViaQueryEngine(\n ctx: EventContext,\n orderId: string,\n scope: Scope,\n): Promise<SalesOrderRow | null> {\n // Read sales orders/lines via QueryEngine instead of importing the sales ORM\n // entity classes \u2014 keeps the WMS module decoupled from sales internals.\n // Sales mutations also flush their CRUD side effects synchronously through\n // the command bus before this subscriber fires, so the query_index/base\n // table is consistent at this point.\n const queryEngine = ctx.resolve<QueryEngine>('queryEngine')\n const result = await queryEngine.query<SalesOrderRow>(E.sales.sales_order, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: { id: { $eq: orderId } },\n fields: ['id', 'order_number', 'status', 'tenant_id', 'organization_id'],\n page: { page: 1, pageSize: 1 },\n })\n return result.items[0] ?? null\n}\n\n// Mirrors the sales module's own cancelled-status check; WMS reads the status\n// through QueryEngine rather than importing sales internals.\nfunction isCancelledOrderStatus(status: unknown): boolean {\n if (typeof status !== 'string') return false\n const normalized = status.trim()\n return normalized === 'canceled' || normalized === 'cancelled'\n}\n\nexport async function isSalesOrderCancelled(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<boolean> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return false\n const order = await loadOrderViaQueryEngine(ctx, payload.orderId, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n })\n return isCancelledOrderStatus(order?.status)\n}\n\nasync function loadOrderLinesViaQueryEngine(\n ctx: EventContext,\n orderId: string,\n scope: Scope,\n): Promise<SalesOrderLineRow[]> {\n const queryEngine = ctx.resolve<QueryEngine>('queryEngine')\n const result = await queryEngine.query<SalesOrderLineRow>(E.sales.sales_order_line, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: { order_id: { $eq: orderId } },\n fields: ['id', 'kind', 'product_variant_id', 'quantity', 'line_number'],\n sort: [{ field: 'line_number', dir: 'asc' as never }],\n page: { page: 1, pageSize: 1000 },\n })\n return result.items\n}\n\nasync function loadActiveReservations(\n em: EntityManager,\n orderId: string,\n scope: Scope,\n): Promise<InventoryReservation[]> {\n return findWithDecryption(\n em,\n InventoryReservation,\n {\n sourceType: 'order',\n sourceId: orderId,\n status: 'active',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'asc' } },\n scope,\n )\n}\n\nasync function loadBalances(\n em: EntityManager,\n variantIds: string[],\n scope: Scope,\n): Promise<InventoryBalance[]> {\n if (variantIds.length === 0) return []\n return findWithDecryption(\n em,\n InventoryBalance,\n {\n catalogVariantId: { $in: variantIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n}\n\nfunction buildRequiredQuantityByVariant(lines: SalesOrderLineRow[]): Map<string, number> {\n const quantities = new Map<string, number>()\n for (const line of lines) {\n if (line.kind !== 'product' || !line.product_variant_id) continue\n const quantity = toNumber(line.quantity)\n if (quantity <= 0) continue\n quantities.set(\n line.product_variant_id,\n (quantities.get(line.product_variant_id) ?? 0) + quantity,\n )\n }\n return quantities\n}\n\nfunction buildReservedQuantityByVariant(reservations: InventoryReservation[]): Map<string, number> {\n const quantities = new Map<string, number>()\n for (const reservation of reservations) {\n quantities.set(\n reservation.catalogVariantId,\n (quantities.get(reservation.catalogVariantId) ?? 0) + toNumber(reservation.quantity),\n )\n }\n return quantities\n}\n\nfunction buildWarehouseAvailability(\n balances: InventoryBalance[],\n): Map<string, WarehouseAvailability[]> {\n const byVariant = new Map<string, Map<string, number>>()\n for (const balance of balances) {\n const warehouseId = getWarehouseId(balance)\n if (!warehouseId) continue\n const available =\n toNumber(balance.quantityOnHand) -\n toNumber(balance.quantityReserved) -\n toNumber(balance.quantityAllocated)\n if (available <= 0) continue\n\n const warehouseMap = byVariant.get(balance.catalogVariantId) ?? new Map<string, number>()\n warehouseMap.set(warehouseId, (warehouseMap.get(warehouseId) ?? 0) + available)\n byVariant.set(balance.catalogVariantId, warehouseMap)\n }\n\n const result = new Map<string, WarehouseAvailability[]>()\n for (const [variantId, warehouseMap] of byVariant.entries()) {\n result.set(\n variantId,\n Array.from(warehouseMap.entries()).map(([warehouseId, available]) => ({ warehouseId, available })),\n )\n }\n return result\n}\n\nexport async function reserveInventoryForConfirmedOrder(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<void> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return\n if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return\n\n const scope: Scope = {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n }\n const em = ctx.resolve<EntityManager>('em').fork()\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const commandCtx = buildCommandContext(ctx, scope)\n const order = await loadOrderViaQueryEngine(ctx, payload.orderId, scope)\n if (!order || typeof order.id !== 'string') return\n if (isCancelledOrderStatus(order.status)) return\n\n const orderId = order.id\n const orderNumber = typeof order.order_number === 'string' ? order.order_number : null\n\n const [lines, activeReservations] = await Promise.all([\n loadOrderLinesViaQueryEngine(ctx, orderId, scope),\n loadActiveReservations(em, orderId, scope),\n ])\n const requiredByVariant = buildRequiredQuantityByVariant(lines)\n if (requiredByVariant.size === 0) return\n\n const reservedByVariant = buildReservedQuantityByVariant(activeReservations)\n const balances = await loadBalances(em, Array.from(requiredByVariant.keys()), scope)\n const availabilityByVariant = buildWarehouseAvailability(balances)\n const [primaryWarehouseId, assignedWarehouseId] = await Promise.all([\n resolvePrimaryWarehouseId(em, scope),\n loadExplicitWarehouseIdForOrder(em, orderId, scope),\n ])\n const preferredWarehouseId = assignedWarehouseId ?? primaryWarehouseId\n const shortfalls: ReservationShortfallLine[] = []\n\n for (const [variantId, requiredQuantity] of requiredByVariant.entries()) {\n let remainingQuantity = requiredQuantity - (reservedByVariant.get(variantId) ?? 0)\n if (remainingQuantity <= 0) continue\n\n let variantBuckets = availabilityByVariant.get(variantId) ?? []\n if (assignedWarehouseId) {\n variantBuckets = variantBuckets.filter(\n (bucket) => bucket.warehouseId === assignedWarehouseId,\n )\n }\n\n const warehouseAvailability = sortWarehouseAvailabilityForReservation(\n variantBuckets,\n preferredWarehouseId,\n )\n let reservedForVariant = 0\n try {\n for (const bucket of warehouseAvailability) {\n if (remainingQuantity <= 0) break\n const reserveQuantity = Math.min(remainingQuantity, bucket.available)\n if (reserveQuantity <= 0) continue\n\n await commandBus.execute('wms.inventory.reserve', {\n input: {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n warehouseId: bucket.warehouseId,\n catalogVariantId: variantId,\n quantity: reserveQuantity,\n sourceType: 'order',\n sourceId: orderId,\n metadata: {\n automation: 'sales.order.confirmed',\n orderId,\n orderNumber,\n catalogVariantId: variantId,\n },\n },\n ctx: commandCtx,\n })\n\n remainingQuantity -= reserveQuantity\n reservedForVariant += reserveQuantity\n bucket.available -= reserveQuantity\n }\n } catch (error) {\n if (!isInsufficientStockError(error)) throw error\n }\n\n if (remainingQuantity > 0.000001) {\n shortfalls.push({\n catalogVariantId: variantId,\n requiredQuantity,\n reservedQuantity: reservedForVariant + (reservedByVariant.get(variantId) ?? 0),\n shortfallQuantity: remainingQuantity,\n })\n }\n }\n\n if (shortfalls.length > 0) {\n await emitWmsEvent('wms.inventory.reservation_shortfall', {\n id: orderId,\n orderId,\n orderNumber,\n shortfalls,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n }\n}\n\nexport async function releaseInventoryForCancelledOrder(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<void> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return\n if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return\n\n const scope: Scope = {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n }\n const em = ctx.resolve<EntityManager>('em').fork()\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const commandCtx = buildCommandContext(ctx, scope)\n const activeReservations = await loadActiveReservations(em, payload.orderId, scope)\n\n for (const reservation of activeReservations) {\n try {\n await commandBus.execute('wms.inventory.release', {\n input: {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n reservationId: reservation.id,\n reason: 'sales.order.cancelled',\n metadata: {\n automation: 'sales.order.cancelled',\n orderId: payload.orderId,\n },\n },\n ctx: commandCtx,\n })\n } catch (error) {\n if (!isBalanceIntegrityViolationError(error)) throw error\n void emitWmsEvent('wms.inventory.balance_drift', {\n id: reservation.id,\n balanceId: reservation.id,\n reservationId: reservation.id,\n field: 'quantityReserved',\n attemptedValue: null,\n clampedValue: null,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,qBAAqB;AAC9B,SAAS,0BAAiD;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { E } from '#generated/entities.ids.generated'\nimport { InventoryBalance, InventoryReservation } from '../data/entities'\nimport { emitWmsEvent } from '../events'\nimport { loadExplicitWarehouseIdForOrder } from './salesOrderWarehouseAssignment'\nimport { isBalanceLocationReservable } from './inventoryPolicy'\nimport {\n resolvePrimaryWarehouseId,\n sortWarehouseAvailabilityForReservation,\n type WarehouseAvailability,\n} from './primaryWarehousePolicy'\nimport { resolveWmsIntegrationToggleEnabled } from './wmsIntegrationToggles'\n\nconst logger = createLogger('wms')\n\ntype EventContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\ntype SalesOrderLifecyclePayload = {\n orderId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype Scope = {\n tenantId: string\n organizationId: string\n}\n\ntype ReservationShortfallLine = {\n catalogVariantId: string\n requiredQuantity: number\n reservedQuantity: number\n shortfallQuantity: number\n}\n\nfunction isInsufficientStockError(error: unknown): boolean {\n return error instanceof CrudHttpError && error.status === 409 && error.body?.error === 'insufficient_stock'\n}\n\nfunction isBalanceIntegrityViolationError(error: unknown): boolean {\n return error instanceof CrudHttpError && error.status === 409 && error.body?.error === 'balance_integrity_violation'\n}\n\ntype SalesOrderRow = {\n id?: string\n order_number?: string | null\n status?: string | null\n fulfillment_status?: string | null\n tenant_id?: string | null\n organization_id?: string | null\n}\n\n/** Only confirmed (non-fulfilled / non-cancelled) orders may receive re-reservations. */\nexport function isReservableOrderStatus(\n status?: string | null,\n fulfillmentStatus?: string | null,\n): boolean {\n const normalized = (status ?? '').trim().toLowerCase()\n if (normalized !== 'confirmed') return false\n const fulfillment = (fulfillmentStatus ?? '').trim().toLowerCase()\n if (\n fulfillment === 'fulfilled' ||\n fulfillment === 'cancelled' ||\n fulfillment === 'canceled'\n ) {\n return false\n }\n return true\n}\n\ntype SalesOrderLineRow = {\n id?: string\n kind?: string | null\n product_variant_id?: string | null\n quantity?: string | number | null\n line_number?: number | null\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction getWarehouseId(balance: InventoryBalance): string | null {\n const relation = balance.warehouse as { id?: string } | string | undefined\n if (typeof relation === 'string') return relation\n return typeof relation?.id === 'string' ? relation.id : null\n}\n\nfunction buildCommandContext(ctx: EventContext, scope: Scope): CommandRuntimeContext {\n return {\n container: {\n resolve: ctx.resolve,\n } as CommandRuntimeContext['container'],\n auth: null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nasync function isInventoryAutomationEnabled(ctx: EventContext, tenantId: string): Promise<boolean> {\n try {\n const featureTogglesService = ctx.resolve<FeatureTogglesService>('featureTogglesService')\n const em = ctx.resolve<EntityManager>('em')\n return resolveWmsIntegrationToggleEnabled(\n featureTogglesService,\n em,\n 'wms_integration_sales_order_inventory',\n tenantId,\n )\n } catch {\n return true\n }\n}\n\nasync function loadOrderViaQueryEngine(\n ctx: EventContext,\n orderId: string,\n scope: Scope,\n): Promise<SalesOrderRow | null> {\n // Read sales orders/lines via QueryEngine instead of importing the sales ORM\n // entity classes \u2014 keeps the WMS module decoupled from sales internals.\n // Sales mutations also flush their CRUD side effects synchronously through\n // the command bus before this subscriber fires, so the query_index/base\n // table is consistent at this point.\n const queryEngine = ctx.resolve<QueryEngine>('queryEngine')\n const result = await queryEngine.query<SalesOrderRow>(E.sales.sales_order, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: { id: { $eq: orderId } },\n fields: ['id', 'order_number', 'status', 'tenant_id', 'organization_id'],\n page: { page: 1, pageSize: 1 },\n })\n return result.items[0] ?? null\n}\n\n// Mirrors the sales module's own cancelled-status check; WMS reads the status\n// through QueryEngine rather than importing sales internals.\nfunction isCancelledOrderStatus(status: unknown): boolean {\n if (typeof status !== 'string') return false\n const normalized = status.trim()\n return normalized === 'canceled' || normalized === 'cancelled'\n}\n\nexport async function isSalesOrderCancelled(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<boolean> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return false\n const order = await loadOrderViaQueryEngine(ctx, payload.orderId, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n })\n return isCancelledOrderStatus(order?.status)\n}\n\nasync function loadOrderLinesViaQueryEngine(\n ctx: EventContext,\n orderId: string,\n scope: Scope,\n): Promise<SalesOrderLineRow[]> {\n const queryEngine = ctx.resolve<QueryEngine>('queryEngine')\n const result = await queryEngine.query<SalesOrderLineRow>(E.sales.sales_order_line, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: { order_id: { $eq: orderId } },\n fields: ['id', 'kind', 'product_variant_id', 'quantity', 'line_number'],\n sort: [{ field: 'line_number', dir: 'asc' as never }],\n page: { page: 1, pageSize: 1000 },\n })\n return result.items\n}\n\nasync function loadActiveReservations(\n em: EntityManager,\n orderId: string,\n scope: Scope,\n): Promise<InventoryReservation[]> {\n return findWithDecryption(\n em,\n InventoryReservation,\n {\n sourceType: 'order',\n sourceId: orderId,\n status: 'active',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'asc' } },\n scope,\n )\n}\n\nasync function loadBalances(\n em: EntityManager,\n variantIds: string[],\n scope: Scope,\n): Promise<InventoryBalance[]> {\n if (variantIds.length === 0) return []\n return findWithDecryption(\n em,\n InventoryBalance,\n {\n catalogVariantId: { $in: variantIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n { populate: ['location'] },\n scope,\n )\n}\n\nfunction buildRequiredQuantityByVariant(lines: SalesOrderLineRow[]): Map<string, number> {\n const quantities = new Map<string, number>()\n for (const line of lines) {\n if (line.kind !== 'product' || !line.product_variant_id) continue\n const quantity = toNumber(line.quantity)\n if (quantity <= 0) continue\n quantities.set(\n line.product_variant_id,\n (quantities.get(line.product_variant_id) ?? 0) + quantity,\n )\n }\n return quantities\n}\n\nfunction buildReservedQuantityByVariant(reservations: InventoryReservation[]): Map<string, number> {\n const quantities = new Map<string, number>()\n for (const reservation of reservations) {\n quantities.set(\n reservation.catalogVariantId,\n (quantities.get(reservation.catalogVariantId) ?? 0) + toNumber(reservation.quantity),\n )\n }\n return quantities\n}\n\nfunction buildWarehouseAvailability(\n balances: InventoryBalance[],\n): Map<string, WarehouseAvailability[]> {\n const byVariant = new Map<string, Map<string, number>>()\n for (const balance of balances) {\n // Staging/dock are inbound holding locations \u2014 availability for sales\n // reservation must ignore them so ASN receive re-eval does not claim\n // stock that putaway still needs to move into storage.\n if (!isBalanceLocationReservable(balance)) continue\n const warehouseId = getWarehouseId(balance)\n if (!warehouseId) continue\n const available =\n toNumber(balance.quantityOnHand) -\n toNumber(balance.quantityReserved) -\n toNumber(balance.quantityAllocated)\n if (available <= 0) continue\n\n const warehouseMap = byVariant.get(balance.catalogVariantId) ?? new Map<string, number>()\n warehouseMap.set(warehouseId, (warehouseMap.get(warehouseId) ?? 0) + available)\n byVariant.set(balance.catalogVariantId, warehouseMap)\n }\n\n const result = new Map<string, WarehouseAvailability[]>()\n for (const [variantId, warehouseMap] of byVariant.entries()) {\n result.set(\n variantId,\n Array.from(warehouseMap.entries()).map(([warehouseId, available]) => ({ warehouseId, available })),\n )\n }\n return result\n}\n\nexport async function reserveInventoryForConfirmedOrder(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<void> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return\n if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return\n\n const scope: Scope = {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n }\n const em = ctx.resolve<EntityManager>('em').fork()\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const commandCtx = buildCommandContext(ctx, scope)\n const order = await loadOrderViaQueryEngine(ctx, payload.orderId, scope)\n if (!order || typeof order.id !== 'string') return\n if (isCancelledOrderStatus(order.status)) return\n\n const orderId = order.id\n const orderNumber = typeof order.order_number === 'string' ? order.order_number : null\n\n const [lines, activeReservations] = await Promise.all([\n loadOrderLinesViaQueryEngine(ctx, orderId, scope),\n loadActiveReservations(em, orderId, scope),\n ])\n const requiredByVariant = buildRequiredQuantityByVariant(lines)\n if (requiredByVariant.size === 0) return\n\n const reservedByVariant = buildReservedQuantityByVariant(activeReservations)\n const balances = await loadBalances(em, Array.from(requiredByVariant.keys()), scope)\n const availabilityByVariant = buildWarehouseAvailability(balances)\n const [primaryWarehouseId, assignedWarehouseId] = await Promise.all([\n resolvePrimaryWarehouseId(em, scope),\n loadExplicitWarehouseIdForOrder(em, orderId, scope),\n ])\n const preferredWarehouseId = assignedWarehouseId ?? primaryWarehouseId\n const shortfalls: ReservationShortfallLine[] = []\n\n for (const [variantId, requiredQuantity] of requiredByVariant.entries()) {\n let remainingQuantity = requiredQuantity - (reservedByVariant.get(variantId) ?? 0)\n if (remainingQuantity <= 0) continue\n\n let variantBuckets = availabilityByVariant.get(variantId) ?? []\n if (assignedWarehouseId) {\n variantBuckets = variantBuckets.filter(\n (bucket) => bucket.warehouseId === assignedWarehouseId,\n )\n }\n\n const warehouseAvailability = sortWarehouseAvailabilityForReservation(\n variantBuckets,\n preferredWarehouseId,\n )\n let reservedForVariant = 0\n try {\n for (const bucket of warehouseAvailability) {\n if (remainingQuantity <= 0) break\n const reserveQuantity = Math.min(remainingQuantity, bucket.available)\n if (reserveQuantity <= 0) continue\n\n await commandBus.execute('wms.inventory.reserve', {\n input: {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n warehouseId: bucket.warehouseId,\n catalogVariantId: variantId,\n quantity: reserveQuantity,\n sourceType: 'order',\n sourceId: orderId,\n metadata: {\n automation: 'sales.order.confirmed',\n orderId,\n orderNumber,\n catalogVariantId: variantId,\n },\n },\n ctx: commandCtx,\n })\n\n remainingQuantity -= reserveQuantity\n reservedForVariant += reserveQuantity\n bucket.available -= reserveQuantity\n }\n } catch (error) {\n if (!isInsufficientStockError(error)) throw error\n }\n\n if (remainingQuantity > 0.000001) {\n shortfalls.push({\n catalogVariantId: variantId,\n requiredQuantity,\n reservedQuantity: reservedForVariant + (reservedByVariant.get(variantId) ?? 0),\n shortfallQuantity: remainingQuantity,\n })\n }\n }\n\n if (shortfalls.length > 0) {\n await emitWmsEvent('wms.inventory.reservation_shortfall', {\n id: orderId,\n orderId,\n orderNumber,\n shortfalls,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n }\n}\n\ntype StockIncreasePayload = {\n catalogVariantId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n}\n\n/**\n * Re-runs reservation automation for sales orders that include the received\n * catalog variant. Idempotent: reserveInventoryForConfirmedOrder only fills\n * remaining shortfall. No-ops when the sales integration toggle is off or\n * sales query peers are unavailable.\n *\n * Candidate discovery starts from currently reservable (confirmed,\n * non-terminal fulfillment) sales orders \u2014 not lifetime `sales_order_line`\n * history for the variant \u2014 then keeps only orders that still reference the\n * received catalog variant. That bounds work to the open order set.\n */\nconst REEVAL_ORDER_PAGE_SIZE = 500\n\nexport async function reevaluateReservationsAfterStockIncrease(\n payload: StockIncreasePayload,\n ctx: EventContext,\n): Promise<void> {\n if (!payload.catalogVariantId || !payload.tenantId || !payload.organizationId) return\n if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return\n\n const scope: Scope = {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n }\n\n let queryEngine: QueryEngine\n try {\n queryEngine = ctx.resolve<QueryEngine>('queryEngine')\n } catch {\n // Sales module / query engine absent or unavailable \u2014 degrade gracefully.\n return\n }\n\n // Page reservable orders, then intersect with lines for this variant.\n // Process each page immediately (bounded memory); reserveInventoryForConfirmedOrder is idempotent.\n let page = 1\n\n for (;;) {\n let orderItems: SalesOrderRow[]\n try {\n const result = await queryEngine.query<SalesOrderRow>(E.sales.sales_order, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: { status: { $eq: 'confirmed' } },\n fields: ['id', 'status', 'fulfillment_status'],\n page: { page, pageSize: REEVAL_ORDER_PAGE_SIZE },\n })\n orderItems = result.items\n } catch {\n return\n }\n\n if (orderItems.length === 0) break\n\n const reservableOrderIds = orderItems\n .filter((order) => isReservableOrderStatus(order.status, order.fulfillment_status))\n .map((order) => order.id)\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n\n if (reservableOrderIds.length > 0) {\n const matchingOrderIds = new Set<string>()\n let linePage = 1\n try {\n for (;;) {\n const lines = await queryEngine.query<SalesOrderLineRow & { order_id?: string | null }>(\n E.sales.sales_order_line,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n filters: {\n product_variant_id: { $eq: payload.catalogVariantId },\n order_id: { $in: reservableOrderIds },\n },\n fields: ['id', 'order_id', 'product_variant_id'],\n page: { page: linePage, pageSize: REEVAL_ORDER_PAGE_SIZE },\n },\n )\n for (const line of lines.items) {\n if (typeof line.order_id === 'string' && line.order_id.length > 0) {\n matchingOrderIds.add(line.order_id)\n }\n }\n if (lines.items.length < REEVAL_ORDER_PAGE_SIZE) break\n linePage += 1\n }\n } catch (error) {\n // Transient peer failure on one page must not abort later order pages.\n logger.warn('Reservation re-eval line lookup failed; continuing next page', {\n catalogVariantId: payload.catalogVariantId,\n page,\n orderCount: reservableOrderIds.length,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n for (const orderId of matchingOrderIds) {\n try {\n await reserveInventoryForConfirmedOrder(\n {\n orderId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n ctx,\n )\n } catch {\n // Keep processing remaining orders; a single peer failure must not abort the batch.\n }\n }\n }\n\n if (orderItems.length < REEVAL_ORDER_PAGE_SIZE) break\n page += 1\n }\n}\n\nexport async function releaseInventoryForCancelledOrder(\n payload: SalesOrderLifecyclePayload,\n ctx: EventContext,\n): Promise<void> {\n if (!payload.orderId || !payload.tenantId || !payload.organizationId) return\n if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return\n\n const scope: Scope = {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n }\n const em = ctx.resolve<EntityManager>('em').fork()\n const commandBus = ctx.resolve<CommandBus>('commandBus')\n const commandCtx = buildCommandContext(ctx, scope)\n const activeReservations = await loadActiveReservations(em, payload.orderId, scope)\n\n for (const reservation of activeReservations) {\n try {\n await commandBus.execute('wms.inventory.release', {\n input: {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n reservationId: reservation.id,\n reason: 'sales.order.cancelled',\n metadata: {\n automation: 'sales.order.cancelled',\n orderId: payload.orderId,\n },\n },\n ctx: commandCtx,\n })\n } catch (error) {\n if (!isBalanceIntegrityViolationError(error)) throw error\n void emitWmsEvent('wms.inventory.balance_drift', {\n id: reservation.id,\n balanceId: reservation.id,\n reservationId: reservation.id,\n field: 'quantityReserved',\n attemptedValue: null,\n clampedValue: null,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
import { Asn, InventoryLot, PutawayTask, WarehouseLocation } from "../data/entities.js";
|
|
4
|
+
function resolveWarehouseId(warehouse) {
|
|
5
|
+
if (typeof warehouse === "string") return warehouse;
|
|
6
|
+
return typeof warehouse?.id === "string" ? warehouse.id : null;
|
|
7
|
+
}
|
|
8
|
+
async function resolveLocationByCode(em, input) {
|
|
9
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
10
|
+
const location = await findOneWithDecryption(
|
|
11
|
+
em,
|
|
12
|
+
WarehouseLocation,
|
|
13
|
+
{
|
|
14
|
+
code: input.code.trim(),
|
|
15
|
+
warehouse: input.warehouseId,
|
|
16
|
+
organizationId: scope.organizationId,
|
|
17
|
+
tenantId: scope.tenantId,
|
|
18
|
+
deletedAt: null
|
|
19
|
+
},
|
|
20
|
+
void 0,
|
|
21
|
+
scope
|
|
22
|
+
);
|
|
23
|
+
if (!location) {
|
|
24
|
+
throw new CrudHttpError(404, { error: "not_found" });
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
locationId: location.id,
|
|
28
|
+
code: location.code,
|
|
29
|
+
type: location.type
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function resolveLotByNumber(em, input) {
|
|
33
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
34
|
+
const lot = await findOneWithDecryption(
|
|
35
|
+
em,
|
|
36
|
+
InventoryLot,
|
|
37
|
+
{
|
|
38
|
+
catalogVariantId: input.catalogVariantId,
|
|
39
|
+
lotNumber: input.lotNumber.trim(),
|
|
40
|
+
organizationId: scope.organizationId,
|
|
41
|
+
tenantId: scope.tenantId,
|
|
42
|
+
deletedAt: null
|
|
43
|
+
},
|
|
44
|
+
void 0,
|
|
45
|
+
scope
|
|
46
|
+
);
|
|
47
|
+
if (!lot) {
|
|
48
|
+
throw new CrudHttpError(404, { error: "not_found" });
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
lotId: lot.id,
|
|
52
|
+
lotNumber: lot.lotNumber,
|
|
53
|
+
expiresAt: lot.expiresAt ? lot.expiresAt.toISOString() : null
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function resolveScanReceiveCommandInput(em, input) {
|
|
57
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
58
|
+
const asn = await findOneWithDecryption(
|
|
59
|
+
em,
|
|
60
|
+
Asn,
|
|
61
|
+
{
|
|
62
|
+
id: input.asnId,
|
|
63
|
+
organizationId: scope.organizationId,
|
|
64
|
+
tenantId: scope.tenantId,
|
|
65
|
+
deletedAt: null
|
|
66
|
+
},
|
|
67
|
+
void 0,
|
|
68
|
+
scope
|
|
69
|
+
);
|
|
70
|
+
if (!asn) {
|
|
71
|
+
throw new CrudHttpError(404, { error: "not_found" });
|
|
72
|
+
}
|
|
73
|
+
const warehouseId = resolveWarehouseId(asn.warehouse);
|
|
74
|
+
if (!warehouseId) {
|
|
75
|
+
throw new CrudHttpError(422, { error: "invalid_asn_warehouse" });
|
|
76
|
+
}
|
|
77
|
+
const location = await resolveLocationByCode(em, {
|
|
78
|
+
organizationId: input.organizationId,
|
|
79
|
+
tenantId: input.tenantId,
|
|
80
|
+
warehouseId,
|
|
81
|
+
code: input.locationCode
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
organizationId: input.organizationId,
|
|
85
|
+
tenantId: input.tenantId,
|
|
86
|
+
asnId: input.asnId,
|
|
87
|
+
lineId: input.lineId,
|
|
88
|
+
receivedQty: input.receivedQty,
|
|
89
|
+
targetReceivedQty: input.targetReceivedQty,
|
|
90
|
+
idempotencyKey: input.idempotencyKey,
|
|
91
|
+
targetStagingLocationId: location.locationId,
|
|
92
|
+
lotNumber: input.lotNumber,
|
|
93
|
+
serialNumbers: input.serialNumbers,
|
|
94
|
+
qcStatus: input.qcStatus,
|
|
95
|
+
rejectionReason: input.rejectionReason,
|
|
96
|
+
performedBy: input.performedBy,
|
|
97
|
+
receivedAt: input.receivedAt,
|
|
98
|
+
metadata: input.metadata
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function resolveScanPutawayCommandInput(em, input) {
|
|
102
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
103
|
+
const task = await findOneWithDecryption(
|
|
104
|
+
em,
|
|
105
|
+
PutawayTask,
|
|
106
|
+
{
|
|
107
|
+
id: input.taskId,
|
|
108
|
+
organizationId: scope.organizationId,
|
|
109
|
+
tenantId: scope.tenantId,
|
|
110
|
+
deletedAt: null
|
|
111
|
+
},
|
|
112
|
+
void 0,
|
|
113
|
+
scope
|
|
114
|
+
);
|
|
115
|
+
if (!task) {
|
|
116
|
+
throw new CrudHttpError(404, { error: "not_found" });
|
|
117
|
+
}
|
|
118
|
+
const warehouseId = resolveWarehouseId(task.warehouse);
|
|
119
|
+
if (!warehouseId) {
|
|
120
|
+
throw new CrudHttpError(422, { error: "invalid_putaway_warehouse" });
|
|
121
|
+
}
|
|
122
|
+
const location = await resolveLocationByCode(em, {
|
|
123
|
+
organizationId: input.organizationId,
|
|
124
|
+
tenantId: input.tenantId,
|
|
125
|
+
warehouseId,
|
|
126
|
+
code: input.targetLocationCode
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
organizationId: input.organizationId,
|
|
130
|
+
tenantId: input.tenantId,
|
|
131
|
+
id: input.taskId,
|
|
132
|
+
confirmedQuantity: input.confirmedQuantity,
|
|
133
|
+
targetLocationId: location.locationId,
|
|
134
|
+
lotId: input.lotId,
|
|
135
|
+
performedBy: input.performedBy,
|
|
136
|
+
performedAt: input.performedAt,
|
|
137
|
+
reason: input.reason,
|
|
138
|
+
metadata: input.metadata
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
resolveLocationByCode,
|
|
143
|
+
resolveLotByNumber,
|
|
144
|
+
resolveScanPutawayCommandInput,
|
|
145
|
+
resolveScanReceiveCommandInput
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=scanResolve.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/wms/lib/scanResolve.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { Asn, InventoryLot, PutawayTask, WarehouseLocation } from '../data/entities'\nimport type {\n AsnReceiveLineInput,\n PutawayTaskCompleteInput,\n ScanPutawayInput,\n ScanReceiveInput,\n ScanResolveLocationInput,\n ScanResolveLotInput,\n} from '../data/validators'\n\ntype Scope = { organizationId: string; tenantId: string }\n\nexport type ResolvedScanLocation = {\n locationId: string\n code: string\n type: string\n}\n\nexport type ResolvedScanLot = {\n lotId: string\n lotNumber: string\n expiresAt: string | null\n}\n\nfunction resolveWarehouseId(warehouse: WarehouseLocation['warehouse'] | Asn['warehouse'] | PutawayTask['warehouse']): string | null {\n if (typeof warehouse === 'string') return warehouse\n return typeof warehouse?.id === 'string' ? warehouse.id : null\n}\n\nexport async function resolveLocationByCode(\n em: EntityManager,\n input: ScanResolveLocationInput,\n): Promise<ResolvedScanLocation> {\n const scope: Scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const location = await findOneWithDecryption(\n em,\n WarehouseLocation,\n {\n code: input.code.trim(),\n warehouse: input.warehouseId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!location) {\n throw new CrudHttpError(404, { error: 'not_found' })\n }\n return {\n locationId: location.id,\n code: location.code,\n type: location.type,\n }\n}\n\nexport async function resolveLotByNumber(\n em: EntityManager,\n input: ScanResolveLotInput,\n): Promise<ResolvedScanLot> {\n const scope: Scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const lot = await findOneWithDecryption(\n em,\n InventoryLot,\n {\n catalogVariantId: input.catalogVariantId,\n lotNumber: input.lotNumber.trim(),\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!lot) {\n throw new CrudHttpError(404, { error: 'not_found' })\n }\n return {\n lotId: lot.id,\n lotNumber: lot.lotNumber,\n expiresAt: lot.expiresAt ? lot.expiresAt.toISOString() : null,\n }\n}\n\nexport async function resolveScanReceiveCommandInput(\n em: EntityManager,\n input: ScanReceiveInput,\n): Promise<AsnReceiveLineInput> {\n const scope: Scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const asn = await findOneWithDecryption(\n em,\n Asn,\n {\n id: input.asnId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!asn) {\n throw new CrudHttpError(404, { error: 'not_found' })\n }\n const warehouseId = resolveWarehouseId(asn.warehouse)\n if (!warehouseId) {\n throw new CrudHttpError(422, { error: 'invalid_asn_warehouse' })\n }\n const location = await resolveLocationByCode(em, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n warehouseId,\n code: input.locationCode,\n })\n\n // Pass through client absolute target / optional idempotencyKey only.\n // Do NOT derive target as prior+delta here \u2014 that re-advances on retry after\n // success (outside the receive-line lock). Both QC outcomes require absolute\n // target via scanReceiveSchema / asnReceiveLineSchema.\n return {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n asnId: input.asnId,\n lineId: input.lineId,\n receivedQty: input.receivedQty,\n targetReceivedQty: input.targetReceivedQty,\n idempotencyKey: input.idempotencyKey,\n targetStagingLocationId: location.locationId,\n lotNumber: input.lotNumber,\n serialNumbers: input.serialNumbers,\n qcStatus: input.qcStatus,\n rejectionReason: input.rejectionReason,\n performedBy: input.performedBy,\n receivedAt: input.receivedAt,\n metadata: input.metadata,\n }\n}\n\nexport async function resolveScanPutawayCommandInput(\n em: EntityManager,\n input: ScanPutawayInput,\n): Promise<PutawayTaskCompleteInput> {\n const scope: Scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const task = await findOneWithDecryption(\n em,\n PutawayTask,\n {\n id: input.taskId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!task) {\n throw new CrudHttpError(404, { error: 'not_found' })\n }\n const warehouseId = resolveWarehouseId(task.warehouse)\n if (!warehouseId) {\n throw new CrudHttpError(422, { error: 'invalid_putaway_warehouse' })\n }\n const location = await resolveLocationByCode(em, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n warehouseId,\n code: input.targetLocationCode,\n })\n return {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n id: input.taskId,\n confirmedQuantity: input.confirmedQuantity,\n targetLocationId: location.locationId,\n lotId: input.lotId,\n performedBy: input.performedBy,\n performedAt: input.performedAt,\n reason: input.reason,\n metadata: input.metadata,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -20,7 +20,7 @@ const WMS_INTEGRATION_TOGGLES = [
|
|
|
20
20
|
{
|
|
21
21
|
identifier: "wms_integration_procurement_goods_receipt",
|
|
22
22
|
name: "Procurement Goods Receipt Bridge",
|
|
23
|
-
description: "
|
|
23
|
+
description: "When enabled, WMS creates/updates draft ASNs from procurement.goods_receipt.created events (no-op when procurement is absent).",
|
|
24
24
|
category: "wms",
|
|
25
25
|
type: "boolean",
|
|
26
26
|
defaultValue: false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/wms/lib/wmsIntegrationToggles.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'\nimport type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'\n\nexport const WMS_INTEGRATION_TOGGLES = [\n {\n identifier: 'wms_integration_sales_order_inventory',\n name: 'Sales Order Inventory Reservation',\n description: 'Allows WMS to reserve and release inventory from sales order lifecycle events.',\n category: 'wms',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'wms_integration_shipping_shipments',\n name: 'Shipping Shipment Coordination',\n description: 'Allows WMS to react to shipping shipment lifecycle events when shipment orchestration is enabled.',\n category: 'wms',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'wms_integration_procurement_goods_receipt',\n name: 'Procurement Goods Receipt Bridge',\n description
|
|
5
|
-
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,qBAAqB;AAGvB,MAAM,0BAA0B;AAAA,EACrC;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'\nimport type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'\n\nexport const WMS_INTEGRATION_TOGGLES = [\n {\n identifier: 'wms_integration_sales_order_inventory',\n name: 'Sales Order Inventory Reservation',\n description: 'Allows WMS to reserve and release inventory from sales order lifecycle events.',\n category: 'wms',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'wms_integration_shipping_shipments',\n name: 'Shipping Shipment Coordination',\n description: 'Allows WMS to react to shipping shipment lifecycle events when shipment orchestration is enabled.',\n category: 'wms',\n type: 'boolean' as const,\n defaultValue: true,\n },\n {\n identifier: 'wms_integration_procurement_goods_receipt',\n name: 'Procurement Goods Receipt Bridge',\n description:\n 'When enabled, WMS creates/updates draft ASNs from procurement.goods_receipt.created events (no-op when procurement is absent).',\n category: 'wms',\n type: 'boolean' as const,\n defaultValue: false,\n },\n] as const\n\nexport type WmsIntegrationToggleIdentifier = (typeof WMS_INTEGRATION_TOGGLES)[number]['identifier']\n\nexport function findWmsIntegrationToggleDefinition(identifier: string) {\n return WMS_INTEGRATION_TOGGLES.find((toggle) => toggle.identifier === identifier) ?? null\n}\n\nexport async function seedWmsIntegrationToggles(em: EntityManager): Promise<void> {\n for (const toggle of WMS_INTEGRATION_TOGGLES) {\n const existing = await findOneWithDecryption(em, FeatureToggle, {\n identifier: toggle.identifier,\n deletedAt: null,\n })\n if (existing) continue\n em.persist(\n em.create(FeatureToggle, {\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description,\n category: toggle.category,\n type: toggle.type,\n defaultValue: toggle.defaultValue,\n }),\n )\n }\n await em.flush()\n}\n\nexport async function resolveWmsIntegrationToggleEnabled(\n featureTogglesService: FeatureTogglesService,\n em: EntityManager,\n identifier: WmsIntegrationToggleIdentifier,\n tenantId: string,\n): Promise<boolean> {\n const definition = findWmsIntegrationToggleDefinition(identifier)\n const fallback = definition?.defaultValue ?? true\n\n const result = await featureTogglesService.getBoolConfig(identifier, tenantId)\n if (result.ok) return result.value\n\n if (result.error?.code !== 'MISSING_TOGGLE' || !definition) {\n return fallback\n }\n\n await seedWmsIntegrationToggles(em)\n await featureTogglesService.invalidateIsEnabledCacheByKey(identifier, tenantId)\n\n const retried = await featureTogglesService.getBoolConfig(identifier, tenantId)\n return retried.ok ? retried.value : fallback\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,qBAAqB;AAGvB,MAAM,0BAA0B;AAAA,EACrC;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,aACE;AAAA,IACF,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,mCAAmC,YAAoB;AACrE,SAAO,wBAAwB,KAAK,CAAC,WAAW,OAAO,eAAe,UAAU,KAAK;AACvF;AAEA,eAAsB,0BAA0B,IAAkC;AAChF,aAAW,UAAU,yBAAyB;AAC5C,UAAM,WAAW,MAAM,sBAAsB,IAAI,eAAe;AAAA,MAC9D,YAAY,OAAO;AAAA,MACnB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,SAAU;AACd,OAAG;AAAA,MACD,GAAG,OAAO,eAAe;AAAA,QACvB,YAAY,OAAO;AAAA,QACnB,MAAM,OAAO;AAAA,QACb,aAAa,OAAO;AAAA,QACpB,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,cAAc,OAAO;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AACA,QAAM,GAAG,MAAM;AACjB;AAEA,eAAsB,mCACpB,uBACA,IACA,YACA,UACkB;AAClB,QAAM,aAAa,mCAAmC,UAAU;AAChE,QAAM,WAAW,YAAY,gBAAgB;AAE7C,QAAM,SAAS,MAAM,sBAAsB,cAAc,YAAY,QAAQ;AAC7E,MAAI,OAAO,GAAI,QAAO,OAAO;AAE7B,MAAI,OAAO,OAAO,SAAS,oBAAoB,CAAC,YAAY;AAC1D,WAAO;AAAA,EACT;AAEA,QAAM,0BAA0B,EAAE;AAClC,QAAM,sBAAsB,8BAA8B,YAAY,QAAQ;AAE9E,QAAM,UAAU,MAAM,sBAAsB,cAAc,YAAY,QAAQ;AAC9E,SAAO,QAAQ,KAAK,QAAQ,QAAQ;AACtC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260812120000_wms extends Migration {
|
|
3
|
+
up() {
|
|
4
|
+
this.addSql(
|
|
5
|
+
`create table "wms_asns" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "warehouse_id" uuid not null, "vendor_id" uuid null, "status" text not null default 'draft', "expected_at" timestamptz not null, "reference_number" text null, "notes" text null, primary key ("id"));`
|
|
6
|
+
);
|
|
7
|
+
this.addSql(
|
|
8
|
+
`create index "wms_asns_org_warehouse_status_expected_idx" on "wms_asns" ("organization_id", "warehouse_id", "status", "expected_at");`
|
|
9
|
+
);
|
|
10
|
+
this.addSql(
|
|
11
|
+
`create index "wms_asns_org_vendor_expected_idx" on "wms_asns" ("organization_id", "vendor_id", "expected_at" desc) where deleted_at is null;`
|
|
12
|
+
);
|
|
13
|
+
this.addSql(`create index "wms_asns_org_tenant_idx" on "wms_asns" ("organization_id", "tenant_id");`);
|
|
14
|
+
this.addSql(
|
|
15
|
+
`create table "wms_receiving_lines" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "asn_id" uuid not null, "catalog_variant_id" uuid not null, "expected_qty" numeric(16,4) not null, "received_qty" numeric(16,4) not null default '0', "lot_number" text null, "serial_numbers" jsonb null, "qc_status" text not null default 'pending', "target_staging_location_id" uuid null, "rejection_reason" text null, primary key ("id"));`
|
|
16
|
+
);
|
|
17
|
+
this.addSql(
|
|
18
|
+
`create index "wms_receiving_lines_org_variant_qc_idx" on "wms_receiving_lines" ("organization_id", "catalog_variant_id", "qc_status");`
|
|
19
|
+
);
|
|
20
|
+
this.addSql(
|
|
21
|
+
`create index "wms_receiving_lines_org_asn_idx" on "wms_receiving_lines" ("organization_id", "asn_id");`
|
|
22
|
+
);
|
|
23
|
+
this.addSql(
|
|
24
|
+
`create index "wms_receiving_lines_org_tenant_idx" on "wms_receiving_lines" ("organization_id", "tenant_id");`
|
|
25
|
+
);
|
|
26
|
+
this.addSql(
|
|
27
|
+
`alter table "wms_asns" add constraint "wms_asns_warehouse_id_foreign" foreign key ("warehouse_id") references "wms_warehouses" ("id");`
|
|
28
|
+
);
|
|
29
|
+
this.addSql(
|
|
30
|
+
`alter table "wms_receiving_lines" add constraint "wms_receiving_lines_asn_id_foreign" foreign key ("asn_id") references "wms_asns" ("id");`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
down() {
|
|
34
|
+
this.addSql(`alter table "wms_receiving_lines" drop constraint if exists "wms_receiving_lines_asn_id_foreign";`);
|
|
35
|
+
this.addSql(`alter table "wms_asns" drop constraint if exists "wms_asns_warehouse_id_foreign";`);
|
|
36
|
+
this.addSql(`drop table if exists "wms_receiving_lines" cascade;`);
|
|
37
|
+
this.addSql(`drop table if exists "wms_asns" cascade;`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
Migration20260812120000_wms
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=Migration20260812120000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/wms/migrations/Migration20260812120000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nexport class Migration20260812120000_wms extends Migration {\n override up(): void | Promise<void> {\n this.addSql(\n `create table \"wms_asns\" (\"id\" uuid not null default gen_random_uuid(), \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"metadata\" jsonb null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, \"warehouse_id\" uuid not null, \"vendor_id\" uuid null, \"status\" text not null default 'draft', \"expected_at\" timestamptz not null, \"reference_number\" text null, \"notes\" text null, primary key (\"id\"));`,\n )\n this.addSql(\n `create index \"wms_asns_org_warehouse_status_expected_idx\" on \"wms_asns\" (\"organization_id\", \"warehouse_id\", \"status\", \"expected_at\");`,\n )\n this.addSql(\n `create index \"wms_asns_org_vendor_expected_idx\" on \"wms_asns\" (\"organization_id\", \"vendor_id\", \"expected_at\" desc) where deleted_at is null;`,\n )\n this.addSql(`create index \"wms_asns_org_tenant_idx\" on \"wms_asns\" (\"organization_id\", \"tenant_id\");`)\n\n this.addSql(\n `create table \"wms_receiving_lines\" (\"id\" uuid not null default gen_random_uuid(), \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"metadata\" jsonb null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, \"asn_id\" uuid not null, \"catalog_variant_id\" uuid not null, \"expected_qty\" numeric(16,4) not null, \"received_qty\" numeric(16,4) not null default '0', \"lot_number\" text null, \"serial_numbers\" jsonb null, \"qc_status\" text not null default 'pending', \"target_staging_location_id\" uuid null, \"rejection_reason\" text null, primary key (\"id\"));`,\n )\n this.addSql(\n `create index \"wms_receiving_lines_org_variant_qc_idx\" on \"wms_receiving_lines\" (\"organization_id\", \"catalog_variant_id\", \"qc_status\");`,\n )\n this.addSql(\n `create index \"wms_receiving_lines_org_asn_idx\" on \"wms_receiving_lines\" (\"organization_id\", \"asn_id\");`,\n )\n this.addSql(\n `create index \"wms_receiving_lines_org_tenant_idx\" on \"wms_receiving_lines\" (\"organization_id\", \"tenant_id\");`,\n )\n\n this.addSql(\n `alter table \"wms_asns\" add constraint \"wms_asns_warehouse_id_foreign\" foreign key (\"warehouse_id\") references \"wms_warehouses\" (\"id\");`,\n )\n this.addSql(\n `alter table \"wms_receiving_lines\" add constraint \"wms_receiving_lines_asn_id_foreign\" foreign key (\"asn_id\") references \"wms_asns\" (\"id\");`,\n )\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"wms_receiving_lines\" drop constraint if exists \"wms_receiving_lines_asn_id_foreign\";`)\n this.addSql(`alter table \"wms_asns\" drop constraint if exists \"wms_asns_warehouse_id_foreign\";`)\n this.addSql(`drop table if exists \"wms_receiving_lines\" cascade;`)\n this.addSql(`drop table if exists \"wms_asns\" cascade;`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,oCAAoC,UAAU;AAAA,EAChD,KAA2B;AAClC,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK,OAAO,wFAAwF;AAEpG,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AAEA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,mGAAmG;AAC/G,SAAK,OAAO,mFAAmF;AAC/F,SAAK,OAAO,qDAAqD;AACjE,SAAK,OAAO,0CAA0C;AAAA,EACxD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260812130000_wms extends Migration {
|
|
3
|
+
up() {
|
|
4
|
+
this.addSql(
|
|
5
|
+
`create table "wms_putaway_tasks" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "warehouse_id" uuid not null, "source_location_id" uuid not null, "target_location_id" uuid null, "catalog_variant_id" uuid not null, "lot_id" uuid null, "quantity" numeric(16,4) not null, "status" text not null default 'open', "assigned_to" uuid null, "priority" int not null default 5, primary key ("id"));`
|
|
6
|
+
);
|
|
7
|
+
this.addSql(
|
|
8
|
+
`create index "wms_putaway_tasks_org_warehouse_status_priority_idx" on "wms_putaway_tasks" ("organization_id", "warehouse_id", "status", "priority");`
|
|
9
|
+
);
|
|
10
|
+
this.addSql(
|
|
11
|
+
`create index "wms_putaway_tasks_org_assigned_status_idx" on "wms_putaway_tasks" ("organization_id", "assigned_to", "status");`
|
|
12
|
+
);
|
|
13
|
+
this.addSql(
|
|
14
|
+
`create index "wms_putaway_tasks_org_tenant_idx" on "wms_putaway_tasks" ("organization_id", "tenant_id");`
|
|
15
|
+
);
|
|
16
|
+
this.addSql(
|
|
17
|
+
`alter table "wms_putaway_tasks" add constraint "wms_putaway_tasks_warehouse_id_foreign" foreign key ("warehouse_id") references "wms_warehouses" ("id");`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
down() {
|
|
21
|
+
this.addSql(
|
|
22
|
+
`alter table "wms_putaway_tasks" drop constraint if exists "wms_putaway_tasks_warehouse_id_foreign";`
|
|
23
|
+
);
|
|
24
|
+
this.addSql(`drop table if exists "wms_putaway_tasks" cascade;`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
Migration20260812130000_wms
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=Migration20260812130000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/wms/migrations/Migration20260812130000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nexport class Migration20260812130000_wms extends Migration {\n override up(): void | Promise<void> {\n this.addSql(\n `create table \"wms_putaway_tasks\" (\"id\" uuid not null default gen_random_uuid(), \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"metadata\" jsonb null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, \"warehouse_id\" uuid not null, \"source_location_id\" uuid not null, \"target_location_id\" uuid null, \"catalog_variant_id\" uuid not null, \"lot_id\" uuid null, \"quantity\" numeric(16,4) not null, \"status\" text not null default 'open', \"assigned_to\" uuid null, \"priority\" int not null default 5, primary key (\"id\"));`,\n )\n this.addSql(\n `create index \"wms_putaway_tasks_org_warehouse_status_priority_idx\" on \"wms_putaway_tasks\" (\"organization_id\", \"warehouse_id\", \"status\", \"priority\");`,\n )\n this.addSql(\n `create index \"wms_putaway_tasks_org_assigned_status_idx\" on \"wms_putaway_tasks\" (\"organization_id\", \"assigned_to\", \"status\");`,\n )\n this.addSql(\n `create index \"wms_putaway_tasks_org_tenant_idx\" on \"wms_putaway_tasks\" (\"organization_id\", \"tenant_id\");`,\n )\n this.addSql(\n `alter table \"wms_putaway_tasks\" add constraint \"wms_putaway_tasks_warehouse_id_foreign\" foreign key (\"warehouse_id\") references \"wms_warehouses\" (\"id\");`,\n )\n }\n\n override down(): void | Promise<void> {\n this.addSql(\n `alter table \"wms_putaway_tasks\" drop constraint if exists \"wms_putaway_tasks_warehouse_id_foreign\";`,\n )\n this.addSql(`drop table if exists \"wms_putaway_tasks\" cascade;`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,oCAAoC,UAAU;AAAA,EAChD,KAA2B;AAClC,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAES,OAA6B;AACpC,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK,OAAO,mDAAmD;AAAA,EACjE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260822120000_wms extends Migration {
|
|
3
|
+
up() {
|
|
4
|
+
this.addSql(`alter table "wms_putaway_tasks" add column "putaway_key" text null;`);
|
|
5
|
+
this.addSql(
|
|
6
|
+
`create unique index "wms_putaway_tasks_org_putaway_key_unique_idx" on "wms_putaway_tasks" ("organization_id", "putaway_key") where putaway_key is not null and deleted_at is null and status <> 'cancelled';`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
down() {
|
|
10
|
+
this.addSql(`drop index if exists "wms_putaway_tasks_org_putaway_key_unique_idx";`);
|
|
11
|
+
this.addSql(`alter table "wms_putaway_tasks" drop column if exists "putaway_key";`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
Migration20260822120000_wms
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=Migration20260822120000.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/wms/migrations/Migration20260822120000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nexport class Migration20260822120000_wms extends Migration {\n override up(): void | Promise<void> {\n this.addSql(`alter table \"wms_putaway_tasks\" add column \"putaway_key\" text null;`)\n // Exclude cancelled so cancel-without-clearing-key (legacy rows) cannot block\n // ASN receive recreate; cancel also nulls putaway_key in the command path.\n this.addSql(\n `create unique index \"wms_putaway_tasks_org_putaway_key_unique_idx\" on \"wms_putaway_tasks\" (\"organization_id\", \"putaway_key\") where putaway_key is not null and deleted_at is null and status <> 'cancelled';`,\n )\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop index if exists \"wms_putaway_tasks_org_putaway_key_unique_idx\";`)\n this.addSql(`alter table \"wms_putaway_tasks\" drop column if exists \"putaway_key\";`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,oCAAoC,UAAU;AAAA,EAChD,KAA2B;AAClC,SAAK,OAAO,qEAAqE;AAGjF,SAAK;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,sEAAsE;AAClF,SAAK,OAAO,sEAAsE;AAAA,EACpF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|