@open-mercato/core 0.6.7-develop.6661.1.0043ed1d03 → 0.6.7-develop.6670.1.4efa7961c6
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/inventory_balance/index.js +21 -0
- package/dist/generated/entities/inventory_balance/index.js.map +7 -0
- package/dist/generated/entities/inventory_lot/index.js +19 -0
- package/dist/generated/entities/inventory_lot/index.js.map +7 -0
- package/dist/generated/entities/inventory_movement/index.js +35 -0
- package/dist/generated/entities/inventory_movement/index.js.map +7 -0
- package/dist/generated/entities/inventory_reservation/index.js +23 -0
- package/dist/generated/entities/inventory_reservation/index.js.map +7 -0
- package/dist/generated/entities/product_inventory_profile/index.js +21 -0
- package/dist/generated/entities/product_inventory_profile/index.js.map +7 -0
- package/dist/generated/entities/sales_order_warehouse_assignment/index.js +11 -0
- package/dist/generated/entities/sales_order_warehouse_assignment/index.js.map +7 -0
- package/dist/generated/entities/warehouse/index.js +25 -0
- package/dist/generated/entities/warehouse/index.js.map +7 -0
- package/dist/generated/entities/warehouse_location/index.js +21 -0
- package/dist/generated/entities/warehouse_location/index.js.map +7 -0
- package/dist/generated/entities/warehouse_zone/index.js +11 -0
- package/dist/generated/entities/warehouse_zone/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +12 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +98 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/backend/roles/page.js +12 -4
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +1 -0
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +16 -17
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +3 -0
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/api/variants/route.js +3 -0
- package/dist/modules/catalog/api/variants/route.js.map +2 -2
- package/dist/modules/customer_accounts/migrations/Migration20260724120000_customer_accounts.js +56 -0
- package/dist/modules/customer_accounts/migrations/Migration20260724120000_customer_accounts.js.map +7 -0
- package/dist/modules/directory/api/organizations/route.js +1 -1
- package/dist/modules/directory/api/organizations/route.js.map +1 -1
- package/dist/modules/entities/api/definitions.js +2 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/lib/helpers.js +1 -0
- package/dist/modules/entities/lib/helpers.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +6 -6
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +1 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +7 -2
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +2 -1
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +51 -2
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/events.js +2 -0
- package/dist/modules/sales/events.js.map +2 -2
- package/dist/modules/sales/lib/publicQuoteTenantScope.js +24 -0
- package/dist/modules/sales/lib/publicQuoteTenantScope.js.map +7 -0
- package/dist/modules/sales/widgets/injection/order-items-context.js +11 -0
- package/dist/modules/sales/widgets/injection/order-items-context.js.map +7 -0
- package/dist/modules/wms/acl.js +18 -0
- package/dist/modules/wms/acl.js.map +7 -0
- package/dist/modules/wms/analytics.js +11 -0
- package/dist/modules/wms/analytics.js.map +7 -0
- package/dist/modules/wms/api/dashboard/operational/route.js +80 -0
- package/dist/modules/wms/api/dashboard/operational/route.js.map +7 -0
- package/dist/modules/wms/api/interceptors.js +90 -0
- package/dist/modules/wms/api/interceptors.js.map +7 -0
- package/dist/modules/wms/api/inventory/adjust/route.js +51 -0
- package/dist/modules/wms/api/inventory/adjust/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/allocate/route.js +51 -0
- package/dist/modules/wms/api/inventory/allocate/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/balances/route.js +172 -0
- package/dist/modules/wms/api/inventory/balances/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/cycle-count/route.js +52 -0
- package/dist/modules/wms/api/inventory/cycle-count/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/helpers.js +113 -0
- package/dist/modules/wms/api/inventory/helpers.js.map +7 -0
- package/dist/modules/wms/api/inventory/import/apply/route.js +46 -0
- package/dist/modules/wms/api/inventory/import/apply/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/import/helpers.js +195 -0
- package/dist/modules/wms/api/inventory/import/helpers.js.map +7 -0
- package/dist/modules/wms/api/inventory/import/template/route.js +32 -0
- package/dist/modules/wms/api/inventory/import/template/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/import/validate/route.js +54 -0
- package/dist/modules/wms/api/inventory/import/validate/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/move/route.js +51 -0
- package/dist/modules/wms/api/inventory/move/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/movements/route.js +164 -0
- package/dist/modules/wms/api/inventory/movements/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/receive/route.js +50 -0
- package/dist/modules/wms/api/inventory/receive/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/release/route.js +44 -0
- package/dist/modules/wms/api/inventory/release/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/reservations/route.js +128 -0
- package/dist/modules/wms/api/inventory/reservations/route.js.map +7 -0
- package/dist/modules/wms/api/inventory/reserve/route.js +59 -0
- package/dist/modules/wms/api/inventory/reserve/route.js.map +7 -0
- package/dist/modules/wms/api/inventory-profiles/route.js +178 -0
- package/dist/modules/wms/api/inventory-profiles/route.js.map +7 -0
- package/dist/modules/wms/api/listEnrichers.js +181 -0
- package/dist/modules/wms/api/listEnrichers.js.map +7 -0
- package/dist/modules/wms/api/listSearch.js +77 -0
- package/dist/modules/wms/api/listSearch.js.map +7 -0
- package/dist/modules/wms/api/locations/route.js +172 -0
- package/dist/modules/wms/api/locations/route.js.map +7 -0
- package/dist/modules/wms/api/lots/route.js +204 -0
- package/dist/modules/wms/api/lots/route.js.map +7 -0
- package/dist/modules/wms/api/openapi.js +27 -0
- package/dist/modules/wms/api/openapi.js.map +7 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js +54 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js.map +7 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +279 -0
- package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +7 -0
- package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js +56 -0
- package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js.map +7 -0
- package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js +43 -0
- package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js.map +7 -0
- package/dist/modules/wms/api/warehouses/route.js +181 -0
- package/dist/modules/wms/api/warehouses/route.js.map +7 -0
- package/dist/modules/wms/api/zones/route.js +159 -0
- package/dist/modules/wms/api/zones/route.js.map +7 -0
- package/dist/modules/wms/backend/config/wms/page.js +9 -0
- package/dist/modules/wms/backend/config/wms/page.js.map +7 -0
- package/dist/modules/wms/backend/config/wms/page.meta.js +18 -0
- package/dist/modules/wms/backend/config/wms/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/page.js +9 -0
- package/dist/modules/wms/backend/page.js.map +7 -0
- package/dist/modules/wms/backend/page.meta.js +16 -0
- package/dist/modules/wms/backend/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/inventory/page.js +9 -0
- package/dist/modules/wms/backend/wms/inventory/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/inventory/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/inventory/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/location/[id]/page.js +9 -0
- package/dist/modules/wms/backend/wms/location/[id]/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/location/[id]/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/location/[id]/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/locations/page.js +10 -0
- package/dist/modules/wms/backend/wms/locations/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/locations/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/locations/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/lot/[id]/page.js +9 -0
- package/dist/modules/wms/backend/wms/lot/[id]/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/lots/page.js +9 -0
- package/dist/modules/wms/backend/wms/lots/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/lots/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/lots/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/movements/page.js +22 -0
- package/dist/modules/wms/backend/wms/movements/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/movements/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/movements/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/reservations/page.js +23 -0
- package/dist/modules/wms/backend/wms/reservations/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/reservations/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/reservations/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/sku/[id]/page.js +9 -0
- package/dist/modules/wms/backend/wms/sku/[id]/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/warehouses/page.js +10 -0
- package/dist/modules/wms/backend/wms/warehouses/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/warehouses/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/warehouses/page.meta.js.map +7 -0
- package/dist/modules/wms/backend/wms/zones/page.js +10 -0
- package/dist/modules/wms/backend/wms/zones/page.js.map +7 -0
- package/dist/modules/wms/backend/wms/zones/page.meta.js +18 -0
- package/dist/modules/wms/backend/wms/zones/page.meta.js.map +7 -0
- package/dist/modules/wms/ce.js +80 -0
- package/dist/modules/wms/ce.js.map +7 -0
- package/dist/modules/wms/cli.js +83 -0
- package/dist/modules/wms/cli.js.map +7 -0
- package/dist/modules/wms/commands/configuration.js +1519 -0
- package/dist/modules/wms/commands/configuration.js.map +7 -0
- package/dist/modules/wms/commands/index.js +4 -0
- package/dist/modules/wms/commands/index.js.map +7 -0
- package/dist/modules/wms/commands/inventory-actions.js +1539 -0
- package/dist/modules/wms/commands/inventory-actions.js.map +7 -0
- package/dist/modules/wms/commands/sales-order-assignment.js +277 -0
- package/dist/modules/wms/commands/sales-order-assignment.js.map +7 -0
- package/dist/modules/wms/commands/shared.js +82 -0
- package/dist/modules/wms/commands/shared.js.map +7 -0
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js +755 -0
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js +269 -0
- package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/CycleCountWizardDialog.js +1783 -0
- package/dist/modules/wms/components/backend/CycleCountWizardDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ImportInventoryDialog.js +766 -0
- package/dist/modules/wms/components/backend/ImportInventoryDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/InventoryOperationsSection.js +119 -0
- package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +7 -0
- package/dist/modules/wms/components/backend/LocationEditDialog.js +140 -0
- package/dist/modules/wms/components/backend/LocationEditDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/MoveInventoryDialog.js +791 -0
- package/dist/modules/wms/components/backend/MoveInventoryDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +656 -0
- package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ReleaseReservationDialog.js +344 -0
- package/dist/modules/wms/components/backend/ReleaseReservationDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/ReserveInventoryDialog.js +475 -0
- package/dist/modules/wms/components/backend/ReserveInventoryDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js +1112 -0
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +908 -0
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +1015 -0
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js +1049 -0
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsLotsListPage.js +297 -0
- package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +757 -0
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +975 -0
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +7 -0
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +710 -0
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +7 -0
- package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +79 -0
- package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +7 -0
- package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js +24 -0
- package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js.map +7 -0
- package/dist/modules/wms/components/backend/wmsLookupLoaders.js +55 -0
- package/dist/modules/wms/components/backend/wmsLookupLoaders.js.map +7 -0
- package/dist/modules/wms/data/enrichers.js +501 -0
- package/dist/modules/wms/data/enrichers.js.map +7 -0
- package/dist/modules/wms/data/entities.js +467 -0
- package/dist/modules/wms/data/entities.js.map +7 -0
- package/dist/modules/wms/data/validators.js +399 -0
- package/dist/modules/wms/data/validators.js.map +7 -0
- package/dist/modules/wms/di.js +27 -0
- package/dist/modules/wms/di.js.map +7 -0
- package/dist/modules/wms/events.js +42 -0
- package/dist/modules/wms/events.js.map +7 -0
- package/dist/modules/wms/index.js +17 -0
- package/dist/modules/wms/index.js.map +7 -0
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js +54 -0
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js.map +7 -0
- package/dist/modules/wms/lib/catalogInventoryProfileSync.js +144 -0
- package/dist/modules/wms/lib/catalogInventoryProfileSync.js.map +7 -0
- package/dist/modules/wms/lib/expiry.js +29 -0
- package/dist/modules/wms/lib/expiry.js.map +7 -0
- package/dist/modules/wms/lib/flashMutationError.js +51 -0
- package/dist/modules/wms/lib/flashMutationError.js.map +7 -0
- package/dist/modules/wms/lib/inventoryDisplayUi.js +188 -0
- package/dist/modules/wms/lib/inventoryDisplayUi.js.map +7 -0
- package/dist/modules/wms/lib/inventoryIdempotency.js +37 -0
- package/dist/modules/wms/lib/inventoryIdempotency.js.map +7 -0
- package/dist/modules/wms/lib/inventoryImportCsv.js +173 -0
- package/dist/modules/wms/lib/inventoryImportCsv.js.map +7 -0
- package/dist/modules/wms/lib/inventoryImportService.js +468 -0
- package/dist/modules/wms/lib/inventoryImportService.js.map +7 -0
- package/dist/modules/wms/lib/inventoryMutationUi.js +25 -0
- package/dist/modules/wms/lib/inventoryMutationUi.js.map +7 -0
- package/dist/modules/wms/lib/inventoryPolicy.js +61 -0
- package/dist/modules/wms/lib/inventoryPolicy.js.map +7 -0
- package/dist/modules/wms/lib/inventoryReconciliation.js +193 -0
- package/dist/modules/wms/lib/inventoryReconciliation.js.map +7 -0
- package/dist/modules/wms/lib/inventoryTrackingValidation.js +13 -0
- package/dist/modules/wms/lib/inventoryTrackingValidation.js.map +7 -0
- package/dist/modules/wms/lib/loadOperationalDashboard.js +580 -0
- package/dist/modules/wms/lib/loadOperationalDashboard.js.map +7 -0
- package/dist/modules/wms/lib/lowStockBalanceFilter.js +49 -0
- package/dist/modules/wms/lib/lowStockBalanceFilter.js.map +7 -0
- package/dist/modules/wms/lib/primaryWarehousePolicy.js +32 -0
- package/dist/modules/wms/lib/primaryWarehousePolicy.js.map +7 -0
- package/dist/modules/wms/lib/roleFeatures.js +30 -0
- package/dist/modules/wms/lib/roleFeatures.js.map +7 -0
- package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +291 -0
- package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +7 -0
- package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js +54 -0
- package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js.map +7 -0
- package/dist/modules/wms/lib/wmsIntegrationToggles.js +71 -0
- package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260428110546.js +78 -0
- package/dist/modules/wms/migrations/Migration20260428110546.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260527120000.js +15 -0
- package/dist/modules/wms/migrations/Migration20260527120000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260527140000.js +37 -0
- package/dist/modules/wms/migrations/Migration20260527140000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260613120000.js +23 -0
- package/dist/modules/wms/migrations/Migration20260613120000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260616090000.js +13 -0
- package/dist/modules/wms/migrations/Migration20260616090000.js.map +7 -0
- package/dist/modules/wms/migrations/Migration20260707180000.js +15 -0
- package/dist/modules/wms/migrations/Migration20260707180000.js.map +7 -0
- package/dist/modules/wms/notifications.client.js +18 -0
- package/dist/modules/wms/notifications.client.js.map +7 -0
- package/dist/modules/wms/notifications.js +53 -0
- package/dist/modules/wms/notifications.js.map +7 -0
- package/dist/modules/wms/search.js +196 -0
- package/dist/modules/wms/search.js.map +7 -0
- package/dist/modules/wms/setup.js +30 -0
- package/dist/modules/wms/setup.js.map +7 -0
- package/dist/modules/wms/subscribers/low-stock-notification.js +40 -0
- package/dist/modules/wms/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/wms/subscribers/reservation-shortfall-notification.js +69 -0
- package/dist/modules/wms/subscribers/reservation-shortfall-notification.js.map +7 -0
- package/dist/modules/wms/subscribers/sales-order-cancelled-release.js +14 -0
- package/dist/modules/wms/subscribers/sales-order-cancelled-release.js.map +7 -0
- package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js +14 -0
- package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js.map +7 -0
- package/dist/modules/wms/translations.js +10 -0
- package/dist/modules/wms/translations.js.map +7 -0
- package/dist/modules/wms/vector.js +9 -0
- package/dist/modules/wms/vector.js.map +7 -0
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js +200 -0
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js.map +7 -0
- package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js +104 -0
- package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js.map +7 -0
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +389 -0
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +7 -0
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js +16 -0
- package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js.map +7 -0
- package/dist/modules/wms/widgets/injection-table.js +36 -0
- package/dist/modules/wms/widgets/injection-table.js.map +7 -0
- package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js +150 -0
- package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js.map +7 -0
- package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js +186 -0
- package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js.map +7 -0
- package/dist/modules/wms/widgets/notifications/index.js +7 -0
- package/dist/modules/wms/widgets/notifications/index.js.map +7 -0
- package/generated/entities/inventory_balance/index.ts +9 -0
- package/generated/entities/inventory_lot/index.ts +8 -0
- package/generated/entities/inventory_movement/index.ts +16 -0
- package/generated/entities/inventory_reservation/index.ts +10 -0
- package/generated/entities/product_inventory_profile/index.ts +9 -0
- package/generated/entities/sales_order_warehouse_assignment/index.ts +4 -0
- package/generated/entities/warehouse/index.ts +11 -0
- package/generated/entities/warehouse_location/index.ts +9 -0
- package/generated/entities/warehouse_zone/index.ts +4 -0
- package/generated/entities.ids.generated.ts +12 -0
- package/generated/entity-fields-registry.ts +98 -0
- package/package.json +7 -7
- package/src/modules/auth/backend/roles/page.tsx +13 -4
- 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/pl.json +2 -0
- package/src/modules/auth/lib/backendChrome.tsx +1 -0
- package/src/modules/auth/services/rbacService.ts +25 -21
- package/src/modules/catalog/api/products/route.ts +3 -0
- package/src/modules/catalog/api/variants/route.ts +3 -0
- package/src/modules/customer_accounts/migrations/Migration20260724120000_customer_accounts.ts +96 -0
- package/src/modules/directory/api/organizations/route.ts +1 -1
- package/src/modules/entities/api/definitions.ts +2 -0
- package/src/modules/entities/lib/helpers.ts +1 -0
- package/src/modules/query_index/lib/engine.ts +18 -6
- package/src/modules/sales/api/documents/factory.ts +1 -0
- package/src/modules/sales/api/quotes/accept/route.ts +9 -2
- package/src/modules/sales/api/quotes/public/[token]/route.ts +2 -1
- package/src/modules/sales/commands/documents.ts +71 -0
- package/src/modules/sales/components/documents/ItemsSection.tsx +58 -0
- package/src/modules/sales/events.ts +2 -0
- package/src/modules/sales/lib/publicQuoteTenantScope.ts +49 -0
- package/src/modules/sales/widgets/injection/order-items-context.ts +15 -0
- package/src/modules/wms/acl.ts +14 -0
- package/src/modules/wms/analytics.ts +9 -0
- package/src/modules/wms/api/dashboard/operational/route.ts +83 -0
- package/src/modules/wms/api/interceptors.ts +108 -0
- package/src/modules/wms/api/inventory/adjust/route.ts +50 -0
- package/src/modules/wms/api/inventory/allocate/route.ts +50 -0
- package/src/modules/wms/api/inventory/balances/route.ts +178 -0
- package/src/modules/wms/api/inventory/cycle-count/route.ts +51 -0
- package/src/modules/wms/api/inventory/helpers.ts +128 -0
- package/src/modules/wms/api/inventory/import/apply/route.ts +47 -0
- package/src/modules/wms/api/inventory/import/helpers.ts +223 -0
- package/src/modules/wms/api/inventory/import/template/route.ts +31 -0
- package/src/modules/wms/api/inventory/import/validate/route.ts +56 -0
- package/src/modules/wms/api/inventory/move/route.ts +50 -0
- package/src/modules/wms/api/inventory/movements/route.ts +164 -0
- package/src/modules/wms/api/inventory/receive/route.ts +49 -0
- package/src/modules/wms/api/inventory/release/route.ts +43 -0
- package/src/modules/wms/api/inventory/reservations/route.ts +128 -0
- package/src/modules/wms/api/inventory/reserve/route.ts +59 -0
- package/src/modules/wms/api/inventory-profiles/route.ts +177 -0
- package/src/modules/wms/api/listEnrichers.ts +312 -0
- package/src/modules/wms/api/listSearch.ts +121 -0
- package/src/modules/wms/api/locations/route.ts +171 -0
- package/src/modules/wms/api/lots/route.ts +205 -0
- package/src/modules/wms/api/openapi.ts +28 -0
- package/src/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.ts +58 -0
- package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +306 -0
- package/src/modules/wms/api/sales-orders/assign-warehouse/route.ts +57 -0
- package/src/modules/wms/api/sales-orders/unassign-warehouse/route.ts +43 -0
- package/src/modules/wms/api/warehouses/route.ts +181 -0
- package/src/modules/wms/api/zones/route.ts +158 -0
- package/src/modules/wms/backend/config/wms/page.meta.ts +14 -0
- package/src/modules/wms/backend/config/wms/page.tsx +5 -0
- package/src/modules/wms/backend/page.meta.ts +12 -0
- package/src/modules/wms/backend/page.tsx +5 -0
- package/src/modules/wms/backend/wms/inventory/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/inventory/page.tsx +5 -0
- package/src/modules/wms/backend/wms/location/[id]/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/location/[id]/page.tsx +5 -0
- package/src/modules/wms/backend/wms/locations/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/locations/page.tsx +12 -0
- package/src/modules/wms/backend/wms/lot/[id]/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/lot/[id]/page.tsx +5 -0
- package/src/modules/wms/backend/wms/lots/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/lots/page.tsx +5 -0
- package/src/modules/wms/backend/wms/movements/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/movements/page.tsx +23 -0
- package/src/modules/wms/backend/wms/reservations/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/reservations/page.tsx +24 -0
- package/src/modules/wms/backend/wms/sku/[id]/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/sku/[id]/page.tsx +5 -0
- package/src/modules/wms/backend/wms/warehouses/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/warehouses/page.tsx +12 -0
- package/src/modules/wms/backend/wms/zones/page.meta.ts +14 -0
- package/src/modules/wms/backend/wms/zones/page.tsx +12 -0
- package/src/modules/wms/ce.ts +76 -0
- package/src/modules/wms/cli.ts +96 -0
- package/src/modules/wms/commands/configuration.ts +1844 -0
- package/src/modules/wms/commands/index.ts +3 -0
- package/src/modules/wms/commands/inventory-actions.ts +1866 -0
- package/src/modules/wms/commands/sales-order-assignment.ts +346 -0
- package/src/modules/wms/commands/shared.ts +90 -0
- package/src/modules/wms/components/backend/AdjustInventoryDialog.tsx +820 -0
- package/src/modules/wms/components/backend/ChangeLotStatusDialog.tsx +312 -0
- package/src/modules/wms/components/backend/CycleCountWizardDialog.tsx +2007 -0
- package/src/modules/wms/components/backend/ImportInventoryDialog.tsx +1018 -0
- package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +159 -0
- package/src/modules/wms/components/backend/LocationEditDialog.tsx +185 -0
- package/src/modules/wms/components/backend/MoveInventoryDialog.tsx +907 -0
- package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +695 -0
- package/src/modules/wms/components/backend/ReleaseReservationDialog.tsx +424 -0
- package/src/modules/wms/components/backend/ReserveInventoryDialog.tsx +511 -0
- package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +1340 -0
- package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +1132 -0
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +1310 -0
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +1355 -0
- package/src/modules/wms/components/backend/WmsLotsListPage.tsx +359 -0
- package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +958 -0
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +1245 -0
- package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +960 -0
- package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +89 -0
- package/src/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.ts +32 -0
- package/src/modules/wms/components/backend/wmsLookupLoaders.ts +78 -0
- package/src/modules/wms/data/enrichers.ts +718 -0
- package/src/modules/wms/data/entities.ts +436 -0
- package/src/modules/wms/data/validators.ts +463 -0
- package/src/modules/wms/di.ts +25 -0
- package/src/modules/wms/events.ts +41 -0
- package/src/modules/wms/i18n/de.json +1051 -0
- package/src/modules/wms/i18n/en.json +1051 -0
- package/src/modules/wms/i18n/es.json +1051 -0
- package/src/modules/wms/i18n/pl.json +1051 -0
- package/src/modules/wms/index.ts +15 -0
- package/src/modules/wms/lib/catalogInventoryProfileIntent.ts +65 -0
- package/src/modules/wms/lib/catalogInventoryProfileSync.ts +199 -0
- package/src/modules/wms/lib/expiry.ts +30 -0
- package/src/modules/wms/lib/flashMutationError.ts +74 -0
- package/src/modules/wms/lib/inventoryDisplayUi.ts +249 -0
- package/src/modules/wms/lib/inventoryIdempotency.ts +54 -0
- package/src/modules/wms/lib/inventoryImportCsv.ts +212 -0
- package/src/modules/wms/lib/inventoryImportService.ts +629 -0
- package/src/modules/wms/lib/inventoryMutationUi.ts +21 -0
- package/src/modules/wms/lib/inventoryPolicy.ts +111 -0
- package/src/modules/wms/lib/inventoryReconciliation.ts +263 -0
- package/src/modules/wms/lib/inventoryTrackingValidation.ts +23 -0
- package/src/modules/wms/lib/loadOperationalDashboard.ts +776 -0
- package/src/modules/wms/lib/lowStockBalanceFilter.ts +74 -0
- package/src/modules/wms/lib/primaryWarehousePolicy.ts +46 -0
- package/src/modules/wms/lib/roleFeatures.ts +32 -0
- package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +390 -0
- package/src/modules/wms/lib/salesOrderWarehouseAssignment.ts +78 -0
- package/src/modules/wms/lib/wmsIntegrationToggles.ts +81 -0
- package/src/modules/wms/migrations/.snapshot-open-mercato.json +2880 -0
- package/src/modules/wms/migrations/Migration20260428110546.ts +91 -0
- package/src/modules/wms/migrations/Migration20260527120000.ts +15 -0
- package/src/modules/wms/migrations/Migration20260527140000.ts +35 -0
- package/src/modules/wms/migrations/Migration20260613120000.ts +21 -0
- package/src/modules/wms/migrations/Migration20260616090000.ts +11 -0
- package/src/modules/wms/migrations/Migration20260707180000.ts +13 -0
- package/src/modules/wms/notifications.client.ts +18 -0
- package/src/modules/wms/notifications.ts +51 -0
- package/src/modules/wms/search.ts +230 -0
- package/src/modules/wms/setup.ts +29 -0
- package/src/modules/wms/subscribers/low-stock-notification.ts +54 -0
- package/src/modules/wms/subscribers/reservation-shortfall-notification.ts +98 -0
- package/src/modules/wms/subscribers/sales-order-cancelled-release.ts +21 -0
- package/src/modules/wms/subscribers/sales-order-confirmed-reserve.ts +21 -0
- package/src/modules/wms/translations.ts +6 -0
- package/src/modules/wms/vector.ts +7 -0
- package/src/modules/wms/widgets/injection/catalog-inventory-profile/widget.ts +238 -0
- package/src/modules/wms/widgets/injection/order-items-stock-column/widget.tsx +153 -0
- package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +476 -0
- package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.ts +15 -0
- package/src/modules/wms/widgets/injection-table.ts +34 -0
- package/src/modules/wms/widgets/notifications/WmsLowStockRenderer.tsx +153 -0
- package/src/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.tsx +194 -0
- package/src/modules/wms/widgets/notifications/index.ts +2 -0
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import type { CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'
|
|
2
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
3
|
+
import {
|
|
4
|
+
buildQuery,
|
|
5
|
+
loadCatalogVariantOptions,
|
|
6
|
+
loadWarehouseOptions,
|
|
7
|
+
} from './wmsLookupLoaders'
|
|
8
|
+
|
|
9
|
+
export { buildQuery, loadCatalogVariantOptions, loadWarehouseOptions }
|
|
10
|
+
|
|
11
|
+
type PagedResponse<T> = {
|
|
12
|
+
items: T[]
|
|
13
|
+
total: number
|
|
14
|
+
totalPages: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type InventoryLotListRow = {
|
|
18
|
+
id?: string | null
|
|
19
|
+
lot_number?: string | null
|
|
20
|
+
expires_at?: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function loadCrudOptionsByIds<T>(
|
|
24
|
+
endpoint: string,
|
|
25
|
+
ids: string[],
|
|
26
|
+
mapItem: (item: T) => CrudFieldOption | null,
|
|
27
|
+
): Promise<CrudFieldOption[]> {
|
|
28
|
+
const normalizedIds = [...new Set(ids.map((id) => id.trim()).filter(Boolean))]
|
|
29
|
+
if (normalizedIds.length === 0) return []
|
|
30
|
+
const params = buildQuery({
|
|
31
|
+
page: 1,
|
|
32
|
+
pageSize: normalizedIds.length,
|
|
33
|
+
ids: normalizedIds.join(','),
|
|
34
|
+
})
|
|
35
|
+
const call = await apiCall<PagedResponse<T>>(`${endpoint}?${params}`)
|
|
36
|
+
if (!call.ok) return []
|
|
37
|
+
return (call.result?.items ?? [])
|
|
38
|
+
.map(mapItem)
|
|
39
|
+
.filter((option): option is CrudFieldOption => option !== null)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function resolveCatalogVariantLabel(catalogVariantId: string): Promise<string | null> {
|
|
43
|
+
const id = catalogVariantId.trim()
|
|
44
|
+
if (!id) return null
|
|
45
|
+
const params = buildQuery({ page: 1, pageSize: 1, id })
|
|
46
|
+
const call = await apiCall<PagedResponse<{ id?: string | null; name?: string | null; sku?: string | null }>>(
|
|
47
|
+
`/api/catalog/variants?${params}`,
|
|
48
|
+
)
|
|
49
|
+
if (!call.ok) return null
|
|
50
|
+
const item = call.result?.items?.[0]
|
|
51
|
+
if (!item) return null
|
|
52
|
+
return item.sku?.trim() || item.name?.trim() || id
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function resolveWarehouseLabel(warehouseId: string): Promise<string | null> {
|
|
56
|
+
const [option] = await loadCrudOptionsByIds<{ id?: string | null; name?: string | null; code?: string | null }>(
|
|
57
|
+
'/api/wms/warehouses',
|
|
58
|
+
[warehouseId],
|
|
59
|
+
(item) => {
|
|
60
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
61
|
+
if (!value) return null
|
|
62
|
+
return { value, label: item.name || item.code || value }
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
return option?.label ?? null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function resolveLocationLabel(locationId: string): Promise<string | null> {
|
|
69
|
+
const [option] = await loadCrudOptionsByIds<{ id?: string | null; code?: string | null }>(
|
|
70
|
+
'/api/wms/locations',
|
|
71
|
+
[locationId],
|
|
72
|
+
(item) => {
|
|
73
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
74
|
+
if (!value) return null
|
|
75
|
+
return { value, label: item.code || value }
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
return option?.label ?? null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function resolveLotLabel(lotId: string): Promise<string | null> {
|
|
82
|
+
const [option] = await loadCrudOptionsByIds<InventoryLotListRow>(
|
|
83
|
+
'/api/wms/lots',
|
|
84
|
+
[lotId],
|
|
85
|
+
(item) => {
|
|
86
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
87
|
+
if (!value) return null
|
|
88
|
+
const lotNumber = item.lot_number?.trim() || value
|
|
89
|
+
const expiresAt = item.expires_at?.trim()
|
|
90
|
+
const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
|
|
91
|
+
return { value, label }
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
return option?.label ?? null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function resolveLotNumberFromId(lotId: string): Promise<string | null> {
|
|
98
|
+
const [option] = await loadCrudOptionsByIds<InventoryLotListRow>(
|
|
99
|
+
'/api/wms/lots',
|
|
100
|
+
[lotId],
|
|
101
|
+
(item) => {
|
|
102
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
103
|
+
const lotNumber = item.lot_number?.trim()
|
|
104
|
+
if (!value || !lotNumber) return null
|
|
105
|
+
return { value, label: lotNumber }
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
return option?.label ?? null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function resolveCatalogVariantSku(catalogVariantId: string): Promise<string | null> {
|
|
112
|
+
const id = catalogVariantId.trim()
|
|
113
|
+
if (!id) return null
|
|
114
|
+
const params = buildQuery({ page: 1, pageSize: 1, id })
|
|
115
|
+
const call = await apiCall<PagedResponse<{ id?: string | null; sku?: string | null }>>(
|
|
116
|
+
`/api/catalog/variants?${params}`,
|
|
117
|
+
)
|
|
118
|
+
if (!call.ok) return null
|
|
119
|
+
const sku = call.result?.items?.[0]?.sku?.trim()
|
|
120
|
+
return sku || null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type InventoryProfileListRow = {
|
|
124
|
+
track_lot?: boolean | null
|
|
125
|
+
track_serial?: boolean | null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function loadInventoryProfileForVariant(
|
|
129
|
+
catalogVariantId: string,
|
|
130
|
+
): Promise<InventoryProfileListRow | null> {
|
|
131
|
+
const variantId = catalogVariantId.trim()
|
|
132
|
+
if (!variantId) return null
|
|
133
|
+
const params = buildQuery({ page: 1, pageSize: 1, catalogVariantId: variantId })
|
|
134
|
+
const call = await apiCall<PagedResponse<InventoryProfileListRow>>(
|
|
135
|
+
`/api/wms/inventory-profiles?${params}`,
|
|
136
|
+
)
|
|
137
|
+
if (!call.ok) return null
|
|
138
|
+
return call.result?.items?.[0] ?? null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function findLotIdByNumber(
|
|
142
|
+
catalogVariantId: string,
|
|
143
|
+
lotNumber: string,
|
|
144
|
+
): Promise<string | null> {
|
|
145
|
+
const variantId = catalogVariantId.trim()
|
|
146
|
+
const normalizedLotNumber = lotNumber.trim()
|
|
147
|
+
if (!variantId || !normalizedLotNumber) return null
|
|
148
|
+
|
|
149
|
+
const params = buildQuery({
|
|
150
|
+
page: 1,
|
|
151
|
+
pageSize: 20,
|
|
152
|
+
catalogVariantId: variantId,
|
|
153
|
+
search: normalizedLotNumber,
|
|
154
|
+
})
|
|
155
|
+
const call = await apiCall<PagedResponse<InventoryLotListRow>>(`/api/wms/lots?${params}`)
|
|
156
|
+
if (!call.ok) return null
|
|
157
|
+
|
|
158
|
+
const match = (call.result?.items ?? []).find(
|
|
159
|
+
(item) => item.lot_number?.trim().toLowerCase() === normalizedLotNumber.toLowerCase(),
|
|
160
|
+
)
|
|
161
|
+
return typeof match?.id === 'string' ? match.id : null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class InventoryLotMutationError extends Error {
|
|
165
|
+
constructor(message = 'Failed to resolve inventory lot.') {
|
|
166
|
+
super(message)
|
|
167
|
+
this.name = 'InventoryLotMutationError'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function ensureLotIdForInventoryMutation(input: {
|
|
172
|
+
catalogVariantId: string
|
|
173
|
+
lotNumber: string
|
|
174
|
+
organizationId: string
|
|
175
|
+
tenantId: string
|
|
176
|
+
}): Promise<string> {
|
|
177
|
+
const lotNumber = input.lotNumber.trim()
|
|
178
|
+
if (!lotNumber) {
|
|
179
|
+
throw new InventoryLotMutationError('Lot number is required.')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const existingId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
|
|
183
|
+
if (existingId) return existingId
|
|
184
|
+
|
|
185
|
+
const sku = await resolveCatalogVariantSku(input.catalogVariantId)
|
|
186
|
+
if (!sku) {
|
|
187
|
+
throw new InventoryLotMutationError('Could not resolve SKU for the selected variant.')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const call = await apiCall<{ id?: string | null }>('/api/wms/lots', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { 'content-type': 'application/json' },
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
organizationId: input.organizationId,
|
|
195
|
+
tenantId: input.tenantId,
|
|
196
|
+
catalogVariantId: input.catalogVariantId,
|
|
197
|
+
sku,
|
|
198
|
+
lotNumber,
|
|
199
|
+
status: 'available',
|
|
200
|
+
}),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if (!call.ok) {
|
|
204
|
+
const racedId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
|
|
205
|
+
if (racedId) return racedId
|
|
206
|
+
throw new InventoryLotMutationError('Failed to create inventory lot.')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const createdId = call.result?.id?.trim()
|
|
210
|
+
if (createdId) return createdId
|
|
211
|
+
|
|
212
|
+
const resolvedId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
|
|
213
|
+
if (resolvedId) return resolvedId
|
|
214
|
+
|
|
215
|
+
throw new InventoryLotMutationError('Failed to resolve created inventory lot.')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type LocationListRow = {
|
|
219
|
+
id?: string | null
|
|
220
|
+
code?: string | null
|
|
221
|
+
type?: string | null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
type ZoneListRow = {
|
|
225
|
+
id?: string | null
|
|
226
|
+
name?: string | null
|
|
227
|
+
code?: string | null
|
|
228
|
+
warehouse_id?: string | null
|
|
229
|
+
warehouseId?: string | null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export type ZoneCrudFieldOption = CrudFieldOption & {
|
|
233
|
+
warehouseId?: string
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function readZoneWarehouseId(item: ZoneListRow): string | null {
|
|
237
|
+
if (typeof item.warehouse_id === 'string' && item.warehouse_id.trim()) {
|
|
238
|
+
return item.warehouse_id.trim()
|
|
239
|
+
}
|
|
240
|
+
if (typeof item.warehouseId === 'string' && item.warehouseId.trim()) {
|
|
241
|
+
return item.warehouseId.trim()
|
|
242
|
+
}
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type AuthUserListRow = {
|
|
247
|
+
id?: string | null
|
|
248
|
+
email?: string | null
|
|
249
|
+
roles?: string[] | null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type AssigneeOptionsResult = {
|
|
253
|
+
options: CrudFieldOption[]
|
|
254
|
+
canListUsers: boolean
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type BalanceLookupErrorCode = 'LOOKUP_FAILED' | 'LOT_REQUIRED' | 'LOT_NOT_FOUND'
|
|
258
|
+
|
|
259
|
+
export class BalanceLookupError extends Error {
|
|
260
|
+
code: BalanceLookupErrorCode
|
|
261
|
+
/** Lot IDs present at the ambiguous location (set when `code === 'LOT_REQUIRED'`). */
|
|
262
|
+
candidateLotIds: string[]
|
|
263
|
+
|
|
264
|
+
constructor(
|
|
265
|
+
message = 'Failed to load inventory balance.',
|
|
266
|
+
code: BalanceLookupErrorCode = 'LOOKUP_FAILED',
|
|
267
|
+
candidateLotIds: string[] = [],
|
|
268
|
+
) {
|
|
269
|
+
super(message)
|
|
270
|
+
this.name = 'BalanceLookupError'
|
|
271
|
+
this.code = code
|
|
272
|
+
this.candidateLotIds = candidateLotIds
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export class ScopeEstimateError extends Error {
|
|
277
|
+
constructor(message = 'Failed to estimate cycle count scope.') {
|
|
278
|
+
super(message)
|
|
279
|
+
this.name = 'ScopeEstimateError'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function compareLocationCodes(left: string, right: string): number {
|
|
284
|
+
return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isLocationCodeWithinRange(
|
|
288
|
+
code: string,
|
|
289
|
+
fromCode?: string | null,
|
|
290
|
+
toCode?: string | null,
|
|
291
|
+
): boolean {
|
|
292
|
+
const from = fromCode?.trim()
|
|
293
|
+
const to = toCode?.trim()
|
|
294
|
+
if (from && compareLocationCodes(code, from) < 0) return false
|
|
295
|
+
if (to && compareLocationCodes(code, to) > 0) return false
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function formatCycleCountZoneLabel(
|
|
300
|
+
baseLabel: string,
|
|
301
|
+
stats?: { expectedSkus: number; binCount: number },
|
|
302
|
+
): string {
|
|
303
|
+
const trimmed = baseLabel.trim()
|
|
304
|
+
if (!trimmed || !stats) return trimmed
|
|
305
|
+
return `${trimmed} · ${stats.expectedSkus} SKUs · ${stats.binCount} bins`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function mapLocationOptions(items: LocationListRow[]): CrudFieldOption[] {
|
|
309
|
+
return items
|
|
310
|
+
.map((item) => {
|
|
311
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
312
|
+
if (!value) return null
|
|
313
|
+
const label = item.code || value
|
|
314
|
+
return { value, label }
|
|
315
|
+
})
|
|
316
|
+
.filter((option): option is CrudFieldOption => option !== null)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function loadAllLocations(
|
|
320
|
+
warehouseId: string,
|
|
321
|
+
filters: { type?: string; search?: string },
|
|
322
|
+
): Promise<LocationListRow[]> {
|
|
323
|
+
const items: LocationListRow[] = []
|
|
324
|
+
let page = 1
|
|
325
|
+
let totalPages = 1
|
|
326
|
+
|
|
327
|
+
while (page <= totalPages && page <= 10) {
|
|
328
|
+
const params = buildQuery({
|
|
329
|
+
page,
|
|
330
|
+
pageSize: 100,
|
|
331
|
+
warehouseId,
|
|
332
|
+
type: filters.type,
|
|
333
|
+
search: filters.search?.trim() || undefined,
|
|
334
|
+
})
|
|
335
|
+
const call = await apiCall<PagedResponse<LocationListRow>>(`/api/wms/locations?${params}`)
|
|
336
|
+
if (!call.ok) break
|
|
337
|
+
items.push(...(call.result?.items ?? []))
|
|
338
|
+
totalPages = call.result?.totalPages ?? 1
|
|
339
|
+
page += 1
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return items
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function filterLocationsByCodeRange(
|
|
346
|
+
locations: LocationListRow[],
|
|
347
|
+
fromCode?: string | null,
|
|
348
|
+
toCode?: string | null,
|
|
349
|
+
): LocationListRow[] {
|
|
350
|
+
const from = fromCode?.trim()
|
|
351
|
+
const to = toCode?.trim()
|
|
352
|
+
if (!from && !to) return locations
|
|
353
|
+
return locations.filter((location) => {
|
|
354
|
+
const code = location.code?.trim()
|
|
355
|
+
if (!code) return false
|
|
356
|
+
return isLocationCodeWithinRange(code, from, to)
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function loadLocationPage(
|
|
361
|
+
warehouseId: string,
|
|
362
|
+
filters: { type?: string; search?: string; page?: number; pageSize?: number },
|
|
363
|
+
): Promise<PagedResponse<LocationListRow>> {
|
|
364
|
+
const params = buildQuery({
|
|
365
|
+
page: filters.page ?? 1,
|
|
366
|
+
pageSize: filters.pageSize ?? 50,
|
|
367
|
+
warehouseId,
|
|
368
|
+
type: filters.type,
|
|
369
|
+
search: filters.search?.trim() || undefined,
|
|
370
|
+
})
|
|
371
|
+
const call = await apiCall<PagedResponse<LocationListRow>>(`/api/wms/locations?${params}`)
|
|
372
|
+
if (!call.ok) {
|
|
373
|
+
throw new BalanceLookupError('Failed to load warehouse locations.')
|
|
374
|
+
}
|
|
375
|
+
return call.result ?? { items: [], total: 0, totalPages: 0 }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function loadLocationOptions(
|
|
379
|
+
warehouseId: string,
|
|
380
|
+
query?: string,
|
|
381
|
+
): Promise<CrudFieldOption[]> {
|
|
382
|
+
if (!warehouseId) return []
|
|
383
|
+
try {
|
|
384
|
+
const page = await loadLocationPage(warehouseId, { search: query })
|
|
385
|
+
return mapLocationOptions(page.items ?? [])
|
|
386
|
+
} catch {
|
|
387
|
+
return []
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export async function loadBinLocationOptions(
|
|
392
|
+
warehouseId: string,
|
|
393
|
+
query?: string,
|
|
394
|
+
): Promise<CrudFieldOption[]> {
|
|
395
|
+
if (!warehouseId) return []
|
|
396
|
+
try {
|
|
397
|
+
const page = await loadLocationPage(warehouseId, { type: 'bin', search: query })
|
|
398
|
+
return mapLocationOptions(page.items ?? [])
|
|
399
|
+
} catch {
|
|
400
|
+
return []
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function loadZoneOptions(
|
|
405
|
+
warehouseId: string,
|
|
406
|
+
query?: string,
|
|
407
|
+
): Promise<ZoneCrudFieldOption[]> {
|
|
408
|
+
const scopedWarehouseId = warehouseId.trim()
|
|
409
|
+
if (!scopedWarehouseId) return []
|
|
410
|
+
const params = buildQuery({
|
|
411
|
+
page: 1,
|
|
412
|
+
pageSize: 50,
|
|
413
|
+
warehouseId: scopedWarehouseId,
|
|
414
|
+
search: query?.trim() || undefined,
|
|
415
|
+
})
|
|
416
|
+
const call = await apiCall<PagedResponse<ZoneListRow>>(`/api/wms/zones?${params}`)
|
|
417
|
+
if (!call.ok) return []
|
|
418
|
+
return (call.result?.items ?? [])
|
|
419
|
+
.map((item) => {
|
|
420
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
421
|
+
if (!value) return null
|
|
422
|
+
const label = item.name?.trim() || item.code?.trim() || value
|
|
423
|
+
const zoneWarehouseId = readZoneWarehouseId(item)
|
|
424
|
+
return {
|
|
425
|
+
value,
|
|
426
|
+
label,
|
|
427
|
+
...(zoneWarehouseId ? { warehouseId: zoneWarehouseId } : {}),
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
.filter((option): option is ZoneCrudFieldOption => option !== null)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export async function resolveZoneLabel(zoneId: string): Promise<string | null> {
|
|
434
|
+
const [option] = await loadCrudOptionsByIds<ZoneListRow>(
|
|
435
|
+
'/api/wms/zones',
|
|
436
|
+
[zoneId],
|
|
437
|
+
(item) => {
|
|
438
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
439
|
+
if (!value) return null
|
|
440
|
+
return { value, label: item.name?.trim() || item.code?.trim() || value }
|
|
441
|
+
},
|
|
442
|
+
)
|
|
443
|
+
return option?.label ?? null
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function resolveZoneWarehouseId(zoneId: string): Promise<string | null> {
|
|
447
|
+
const id = zoneId.trim()
|
|
448
|
+
if (!id) return null
|
|
449
|
+
const [option] = await loadCrudOptionsByIds<ZoneListRow>(
|
|
450
|
+
'/api/wms/zones',
|
|
451
|
+
[id],
|
|
452
|
+
(item) => {
|
|
453
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
454
|
+
const warehouseId = readZoneWarehouseId(item)
|
|
455
|
+
if (!value || !warehouseId) return null
|
|
456
|
+
return { value, label: warehouseId }
|
|
457
|
+
},
|
|
458
|
+
)
|
|
459
|
+
return option?.label ?? null
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export async function loadAssigneeOptions(
|
|
463
|
+
query?: string,
|
|
464
|
+
fallback?: { userId: string; label: string },
|
|
465
|
+
): Promise<AssigneeOptionsResult> {
|
|
466
|
+
const fallbackUserId = fallback?.userId.trim()
|
|
467
|
+
const fallbackLabel = fallback?.label.trim()
|
|
468
|
+
const fallbackOption =
|
|
469
|
+
fallbackUserId && fallbackLabel
|
|
470
|
+
? [{ value: fallbackUserId, label: fallbackLabel }]
|
|
471
|
+
: []
|
|
472
|
+
|
|
473
|
+
const params = buildQuery({
|
|
474
|
+
page: 1,
|
|
475
|
+
pageSize: 50,
|
|
476
|
+
search: query?.trim() || undefined,
|
|
477
|
+
})
|
|
478
|
+
const call = await apiCall<PagedResponse<AuthUserListRow>>(`/api/auth/users?${params}`)
|
|
479
|
+
if (!call.ok) {
|
|
480
|
+
return {
|
|
481
|
+
options: fallbackOption,
|
|
482
|
+
canListUsers: false,
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const options = (call.result?.items ?? [])
|
|
487
|
+
.map((item) => {
|
|
488
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
489
|
+
if (!value) return null
|
|
490
|
+
const email = item.email?.trim() || value
|
|
491
|
+
const role = item.roles?.find((entry) => entry.trim().length > 0)?.trim()
|
|
492
|
+
const label = role ? `${email} (${role})` : email
|
|
493
|
+
return { value, label }
|
|
494
|
+
})
|
|
495
|
+
.filter((option): option is CrudFieldOption => option !== null)
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
options,
|
|
499
|
+
canListUsers: true,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export async function resolveAssigneeLabel(userId: string): Promise<string | null> {
|
|
504
|
+
const params = buildQuery({ page: 1, pageSize: 1, id: userId.trim() })
|
|
505
|
+
const call = await apiCall<PagedResponse<AuthUserListRow>>(`/api/auth/users?${params}`)
|
|
506
|
+
if (!call.ok) return null
|
|
507
|
+
const item = call.result?.items?.[0]
|
|
508
|
+
if (!item?.id) return null
|
|
509
|
+
const email = item.email?.trim() || item.id
|
|
510
|
+
const role = item.roles?.find((entry) => entry.trim().length > 0)?.trim()
|
|
511
|
+
return role ? `${email} (${role})` : email
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export async function fetchCycleCountScopeEstimate(input: {
|
|
515
|
+
warehouseId: string
|
|
516
|
+
fromLocationId?: string | null
|
|
517
|
+
toLocationId?: string | null
|
|
518
|
+
}): Promise<{ expectedSkus: number; binCount: number }> {
|
|
519
|
+
const warehouseId = input.warehouseId.trim()
|
|
520
|
+
if (!warehouseId) return { expectedSkus: 0, binCount: 0 }
|
|
521
|
+
|
|
522
|
+
const bins = await loadAllLocations(warehouseId, { type: 'bin' })
|
|
523
|
+
const locationById = new Map(
|
|
524
|
+
bins
|
|
525
|
+
.map((location) => {
|
|
526
|
+
const id = location.id?.trim()
|
|
527
|
+
return id ? [id, location] as const : null
|
|
528
|
+
})
|
|
529
|
+
.filter((entry): entry is readonly [string, LocationListRow] => entry !== null),
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
const fromCode = input.fromLocationId ? locationById.get(input.fromLocationId.trim())?.code : undefined
|
|
533
|
+
const toCode = input.toLocationId ? locationById.get(input.toLocationId.trim())?.code : undefined
|
|
534
|
+
const scopedBins = filterLocationsByCodeRange(bins, fromCode, toCode)
|
|
535
|
+
const scopedBinIds = new Set(
|
|
536
|
+
scopedBins.map((location) => location.id?.trim()).filter((id): id is string => Boolean(id)),
|
|
537
|
+
)
|
|
538
|
+
const binCount = scopedBinIds.size
|
|
539
|
+
|
|
540
|
+
const variantIds = new Set<string>()
|
|
541
|
+
let page = 1
|
|
542
|
+
let totalPages = 1
|
|
543
|
+
|
|
544
|
+
while (page <= totalPages && page <= 10) {
|
|
545
|
+
const params = buildQuery({
|
|
546
|
+
page,
|
|
547
|
+
pageSize: 100,
|
|
548
|
+
warehouseId,
|
|
549
|
+
})
|
|
550
|
+
const call = await apiCall<PagedResponse<{ catalog_variant_id?: string | null; location_id?: string | null; quantity_on_hand?: string | number | null }>>(
|
|
551
|
+
`/api/wms/inventory/balances?${params}`,
|
|
552
|
+
)
|
|
553
|
+
if (!call.ok) {
|
|
554
|
+
throw new ScopeEstimateError()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
for (const row of call.result?.items ?? []) {
|
|
558
|
+
const locationId = row.location_id?.trim()
|
|
559
|
+
const variantId = row.catalog_variant_id?.trim()
|
|
560
|
+
const onHand = Number(row.quantity_on_hand ?? 0)
|
|
561
|
+
if (!locationId || !variantId || !Number.isFinite(onHand) || onHand <= 0) continue
|
|
562
|
+
if (scopedBinIds.size > 0 && !scopedBinIds.has(locationId)) continue
|
|
563
|
+
variantIds.add(variantId)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
totalPages = call.result?.totalPages ?? 1
|
|
567
|
+
page += 1
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return { expectedSkus: variantIds.size, binCount }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export type InventoryBalanceLookupRow = {
|
|
574
|
+
lot_id?: string | null
|
|
575
|
+
quantity_on_hand?: string | number | null
|
|
576
|
+
quantity_available?: number | null
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function normalizeBalanceLotId(value: string | null | undefined): string | null {
|
|
580
|
+
const trimmed = value?.trim()
|
|
581
|
+
return trimmed || null
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function readBalanceAvailable(row: InventoryBalanceLookupRow): number {
|
|
585
|
+
const available = Number(row.quantity_available ?? row.quantity_on_hand ?? 0)
|
|
586
|
+
return Number.isFinite(available) ? available : 0
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function selectBalanceLookupRow(
|
|
590
|
+
items: InventoryBalanceLookupRow[],
|
|
591
|
+
lotId?: string | null,
|
|
592
|
+
): InventoryBalanceLookupRow | null {
|
|
593
|
+
if (items.length === 0) return null
|
|
594
|
+
if (items.length === 1) return items[0]
|
|
595
|
+
|
|
596
|
+
const normalizedLotId = normalizeBalanceLotId(lotId)
|
|
597
|
+
if (normalizedLotId) {
|
|
598
|
+
const exactMatch = items.find(
|
|
599
|
+
(row) => normalizeBalanceLotId(row.lot_id) === normalizedLotId,
|
|
600
|
+
)
|
|
601
|
+
if (exactMatch) return exactMatch
|
|
602
|
+
throw new BalanceLookupError('No balance bucket matches the selected lot.', 'LOT_NOT_FOUND')
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const withoutLot = items.filter((row) => !normalizeBalanceLotId(row.lot_id))
|
|
606
|
+
if (withoutLot.length === 1) return withoutLot[0]
|
|
607
|
+
|
|
608
|
+
const candidateLotIds = [
|
|
609
|
+
...new Set(
|
|
610
|
+
items
|
|
611
|
+
.map((row) => normalizeBalanceLotId(row.lot_id))
|
|
612
|
+
.filter((id): id is string => Boolean(id)),
|
|
613
|
+
),
|
|
614
|
+
]
|
|
615
|
+
throw new BalanceLookupError(
|
|
616
|
+
'Multiple balance buckets match this location; specify a lot.',
|
|
617
|
+
'LOT_REQUIRED',
|
|
618
|
+
candidateLotIds,
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export async function fetchBalanceAvailable(input: {
|
|
623
|
+
warehouseId: string
|
|
624
|
+
locationId: string
|
|
625
|
+
catalogVariantId: string
|
|
626
|
+
lotId?: string | null
|
|
627
|
+
}): Promise<number> {
|
|
628
|
+
const params = buildQuery({
|
|
629
|
+
page: 1,
|
|
630
|
+
pageSize: 20,
|
|
631
|
+
warehouseId: input.warehouseId,
|
|
632
|
+
locationId: input.locationId,
|
|
633
|
+
catalogVariantId: input.catalogVariantId,
|
|
634
|
+
lotId: input.lotId?.trim() || undefined,
|
|
635
|
+
})
|
|
636
|
+
const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
|
|
637
|
+
`/api/wms/inventory/balances?${params}`,
|
|
638
|
+
)
|
|
639
|
+
if (!call.ok) {
|
|
640
|
+
throw new BalanceLookupError()
|
|
641
|
+
}
|
|
642
|
+
const row = selectBalanceLookupRow(call.result?.items ?? [], input.lotId)
|
|
643
|
+
if (!row) return 0
|
|
644
|
+
return readBalanceAvailable(row)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function mapInventoryLotListRowToOption(item: InventoryLotListRow): CrudFieldOption | null {
|
|
648
|
+
const value = typeof item.id === 'string' ? item.id : null
|
|
649
|
+
if (!value) return null
|
|
650
|
+
const lotNumber = item.lot_number?.trim() || value
|
|
651
|
+
const expiresAt = item.expires_at?.trim()
|
|
652
|
+
const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
|
|
653
|
+
return { value, label }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function mapInventoryLotListRowToLotNumberOption(item: InventoryLotListRow): CrudFieldOption | null {
|
|
657
|
+
const lotNumber = item.lot_number?.trim()
|
|
658
|
+
if (!lotNumber) return null
|
|
659
|
+
const expiresAt = item.expires_at?.trim()
|
|
660
|
+
const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
|
|
661
|
+
return { value: lotNumber, label }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async function loadInventoryLotListOptions(
|
|
665
|
+
catalogVariantId: string,
|
|
666
|
+
query: string | undefined,
|
|
667
|
+
mapItem: (item: InventoryLotListRow) => CrudFieldOption | null,
|
|
668
|
+
): Promise<CrudFieldOption[]> {
|
|
669
|
+
if (!catalogVariantId) return []
|
|
670
|
+
const params = buildQuery({
|
|
671
|
+
page: 1,
|
|
672
|
+
pageSize: 50,
|
|
673
|
+
catalogVariantId,
|
|
674
|
+
status: 'available',
|
|
675
|
+
search: query?.trim() || undefined,
|
|
676
|
+
})
|
|
677
|
+
const call = await apiCall<PagedResponse<InventoryLotListRow>>(`/api/wms/lots?${params}`)
|
|
678
|
+
if (!call.ok) return []
|
|
679
|
+
return (call.result?.items ?? [])
|
|
680
|
+
.map(mapItem)
|
|
681
|
+
.filter((option): option is CrudFieldOption => option !== null)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export async function loadLotOptions(
|
|
685
|
+
catalogVariantId: string,
|
|
686
|
+
query?: string,
|
|
687
|
+
): Promise<CrudFieldOption[]> {
|
|
688
|
+
return loadInventoryLotListOptions(catalogVariantId, query, mapInventoryLotListRowToOption)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Resolve display options for known lot IDs (e.g. candidates from a LOT_REQUIRED balance lookup). */
|
|
692
|
+
export async function loadLotOptionsByIds(lotIds: string[]): Promise<CrudFieldOption[]> {
|
|
693
|
+
return loadCrudOptionsByIds<InventoryLotListRow>(
|
|
694
|
+
'/api/wms/lots',
|
|
695
|
+
lotIds,
|
|
696
|
+
mapInventoryLotListRowToOption,
|
|
697
|
+
)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Scopes lot suggestions to the lots actually present in the given warehouse
|
|
701
|
+
// location's balance buckets, instead of every lot ever created for the
|
|
702
|
+
// variant. This is what backs the LOT_REQUIRED disambiguation flow: when a
|
|
703
|
+
// location holds multiple lot-bearing balances for the same variant, the
|
|
704
|
+
// candidates offered here are exactly the ones `selectBalanceLookupRow` would
|
|
705
|
+
// otherwise reject as ambiguous.
|
|
706
|
+
export async function loadLotOptionsForBalanceLocation(input: {
|
|
707
|
+
warehouseId: string
|
|
708
|
+
locationId: string
|
|
709
|
+
catalogVariantId: string
|
|
710
|
+
query?: string
|
|
711
|
+
}): Promise<CrudFieldOption[]> {
|
|
712
|
+
const warehouseId = input.warehouseId.trim()
|
|
713
|
+
const locationId = input.locationId.trim()
|
|
714
|
+
const catalogVariantId = input.catalogVariantId.trim()
|
|
715
|
+
if (!warehouseId || !locationId || !catalogVariantId) return []
|
|
716
|
+
|
|
717
|
+
const params = buildQuery({
|
|
718
|
+
page: 1,
|
|
719
|
+
pageSize: 50,
|
|
720
|
+
warehouseId,
|
|
721
|
+
locationId,
|
|
722
|
+
catalogVariantId,
|
|
723
|
+
})
|
|
724
|
+
const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
|
|
725
|
+
`/api/wms/inventory/balances?${params}`,
|
|
726
|
+
)
|
|
727
|
+
if (!call.ok) return []
|
|
728
|
+
|
|
729
|
+
const lotIds = [
|
|
730
|
+
...new Set(
|
|
731
|
+
(call.result?.items ?? [])
|
|
732
|
+
.map((row) => normalizeBalanceLotId(row.lot_id))
|
|
733
|
+
.filter((id): id is string => Boolean(id)),
|
|
734
|
+
),
|
|
735
|
+
]
|
|
736
|
+
if (lotIds.length === 0) return []
|
|
737
|
+
|
|
738
|
+
const options = await loadCrudOptionsByIds<InventoryLotListRow>(
|
|
739
|
+
'/api/wms/lots',
|
|
740
|
+
lotIds,
|
|
741
|
+
mapInventoryLotListRowToOption,
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
const query = input.query?.trim().toLowerCase()
|
|
745
|
+
if (!query) return options
|
|
746
|
+
return options.filter((option) => option.label.toLowerCase().includes(query))
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export async function loadLotNumberOptions(
|
|
750
|
+
catalogVariantId: string,
|
|
751
|
+
query?: string,
|
|
752
|
+
): Promise<CrudFieldOption[]> {
|
|
753
|
+
return loadInventoryLotListOptions(catalogVariantId, query, mapInventoryLotListRowToLotNumberOption)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export class ScopeQueueError extends Error {
|
|
757
|
+
constructor(message = 'Failed to build cycle count scope queue.') {
|
|
758
|
+
super(message)
|
|
759
|
+
this.name = 'ScopeQueueError'
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export type ScopeQueueItem = {
|
|
764
|
+
locationId: string
|
|
765
|
+
locationCode: string
|
|
766
|
+
catalogVariantId: string
|
|
767
|
+
lotId: string | null
|
|
768
|
+
expectedOnHand: number
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export async function buildCycleCountScopeQueue(input: {
|
|
772
|
+
warehouseId: string
|
|
773
|
+
fromLocationId?: string | null
|
|
774
|
+
toLocationId?: string | null
|
|
775
|
+
}): Promise<ScopeQueueItem[]> {
|
|
776
|
+
const warehouseId = input.warehouseId.trim()
|
|
777
|
+
if (!warehouseId) return []
|
|
778
|
+
|
|
779
|
+
const bins = await loadAllLocations(warehouseId, { type: 'bin' })
|
|
780
|
+
const binById = new Map(
|
|
781
|
+
bins
|
|
782
|
+
.map((b) => {
|
|
783
|
+
const id = b.id?.trim()
|
|
784
|
+
return id ? ([id, b] as const) : null
|
|
785
|
+
})
|
|
786
|
+
.filter((entry): entry is readonly [string, LocationListRow] => entry !== null),
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
const fromCode = input.fromLocationId?.trim()
|
|
790
|
+
? binById.get(input.fromLocationId.trim())?.code
|
|
791
|
+
: undefined
|
|
792
|
+
const toCode = input.toLocationId?.trim()
|
|
793
|
+
? binById.get(input.toLocationId.trim())?.code
|
|
794
|
+
: undefined
|
|
795
|
+
|
|
796
|
+
const scopedBins = filterLocationsByCodeRange(bins, fromCode, toCode)
|
|
797
|
+
const scopedBinIds = new Set(
|
|
798
|
+
scopedBins.map((b) => b.id?.trim()).filter((id): id is string => Boolean(id)),
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
if (scopedBinIds.size === 0) return []
|
|
802
|
+
|
|
803
|
+
type BalanceRow = {
|
|
804
|
+
catalog_variant_id?: string | null
|
|
805
|
+
location_id?: string | null
|
|
806
|
+
lot_id?: string | null
|
|
807
|
+
quantity_on_hand?: string | number | null
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const items: ScopeQueueItem[] = []
|
|
811
|
+
let page = 1
|
|
812
|
+
let totalPages = 1
|
|
813
|
+
|
|
814
|
+
while (page <= totalPages && page <= 20) {
|
|
815
|
+
const params = buildQuery({ page, pageSize: 100, warehouseId })
|
|
816
|
+
const call = await apiCall<PagedResponse<BalanceRow>>(
|
|
817
|
+
`/api/wms/inventory/balances?${params}`,
|
|
818
|
+
)
|
|
819
|
+
if (!call.ok) throw new ScopeQueueError()
|
|
820
|
+
|
|
821
|
+
for (const row of call.result?.items ?? []) {
|
|
822
|
+
const locationId = row.location_id?.trim()
|
|
823
|
+
const variantId = row.catalog_variant_id?.trim()
|
|
824
|
+
const lotId = row.lot_id?.trim() || null
|
|
825
|
+
const onHand = Number(row.quantity_on_hand ?? 0)
|
|
826
|
+
|
|
827
|
+
if (!locationId || !variantId) continue
|
|
828
|
+
if (!scopedBinIds.has(locationId)) continue
|
|
829
|
+
if (!Number.isFinite(onHand) || onHand <= 0) continue
|
|
830
|
+
|
|
831
|
+
const bin = binById.get(locationId)
|
|
832
|
+
items.push({
|
|
833
|
+
locationId,
|
|
834
|
+
locationCode: bin?.code?.trim() ?? locationId,
|
|
835
|
+
catalogVariantId: variantId,
|
|
836
|
+
lotId,
|
|
837
|
+
expectedOnHand: onHand,
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
totalPages = call.result?.totalPages ?? 1
|
|
842
|
+
page += 1
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
items.sort((a, b) => {
|
|
846
|
+
const cmp = a.locationCode.localeCompare(b.locationCode, undefined, {
|
|
847
|
+
numeric: true,
|
|
848
|
+
sensitivity: 'base',
|
|
849
|
+
})
|
|
850
|
+
return cmp !== 0 ? cmp : a.catalogVariantId.localeCompare(b.catalogVariantId)
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
return items
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export async function fetchVariantReorderPoint(catalogVariantId: string): Promise<number> {
|
|
857
|
+
if (!catalogVariantId) return 0
|
|
858
|
+
const params = buildQuery({
|
|
859
|
+
page: 1,
|
|
860
|
+
pageSize: 1,
|
|
861
|
+
catalogVariantId,
|
|
862
|
+
})
|
|
863
|
+
const call = await apiCall<PagedResponse<{ reorder_point?: string | number | null }>>(
|
|
864
|
+
`/api/wms/inventory-profiles?${params}`,
|
|
865
|
+
)
|
|
866
|
+
if (!call.ok) return 0
|
|
867
|
+
const row = call.result?.items?.[0]
|
|
868
|
+
if (!row) return 0
|
|
869
|
+
const reorderPoint = Number(row.reorder_point ?? 0)
|
|
870
|
+
return Number.isFinite(reorderPoint) ? reorderPoint : 0
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export type LocationCapacitySnapshot = {
|
|
874
|
+
capacityUnits: number | null
|
|
875
|
+
totalOnHand: number
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async function fetchLocationCapacityUnits(locationId: string): Promise<number | null> {
|
|
879
|
+
const id = locationId.trim()
|
|
880
|
+
if (!id) return null
|
|
881
|
+
const params = buildQuery({ page: 1, pageSize: 1, ids: id })
|
|
882
|
+
const call = await apiCall<PagedResponse<{ id?: string | null; capacity_units?: string | number | null }>>(
|
|
883
|
+
`/api/wms/locations?${params}`,
|
|
884
|
+
)
|
|
885
|
+
if (!call.ok) return null
|
|
886
|
+
const raw = call.result?.items?.[0]?.capacity_units
|
|
887
|
+
if (raw == null) return null
|
|
888
|
+
const value = Number(raw)
|
|
889
|
+
return Number.isFinite(value) ? value : null
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const LOCATION_TOTAL_ON_HAND_PAGE_SIZE = 100
|
|
893
|
+
const LOCATION_TOTAL_ON_HAND_MAX_PAGES = 50
|
|
894
|
+
|
|
895
|
+
async function fetchLocationTotalOnHand(input: {
|
|
896
|
+
warehouseId: string
|
|
897
|
+
locationId: string
|
|
898
|
+
}): Promise<number> {
|
|
899
|
+
const locationId = input.locationId.trim()
|
|
900
|
+
if (!locationId) return 0
|
|
901
|
+
let sum = 0
|
|
902
|
+
let page = 1
|
|
903
|
+
let totalPages = 1
|
|
904
|
+
do {
|
|
905
|
+
const params = buildQuery({
|
|
906
|
+
page,
|
|
907
|
+
pageSize: LOCATION_TOTAL_ON_HAND_PAGE_SIZE,
|
|
908
|
+
warehouseId: input.warehouseId,
|
|
909
|
+
locationId,
|
|
910
|
+
})
|
|
911
|
+
const call = await apiCall<PagedResponse<{ quantity_on_hand?: string | number | null }>>(
|
|
912
|
+
`/api/wms/inventory/balances?${params}`,
|
|
913
|
+
)
|
|
914
|
+
if (!call.ok) break
|
|
915
|
+
for (const row of call.result?.items ?? []) {
|
|
916
|
+
const value = Number(row.quantity_on_hand ?? 0)
|
|
917
|
+
if (Number.isFinite(value)) sum += value
|
|
918
|
+
}
|
|
919
|
+
totalPages = call.result?.totalPages ?? 1
|
|
920
|
+
page += 1
|
|
921
|
+
} while (page <= totalPages && page <= LOCATION_TOTAL_ON_HAND_MAX_PAGES)
|
|
922
|
+
return sum
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export async function fetchLocationCapacitySnapshot(input: {
|
|
926
|
+
warehouseId: string
|
|
927
|
+
locationId: string
|
|
928
|
+
}): Promise<LocationCapacitySnapshot> {
|
|
929
|
+
const [capacityUnits, totalOnHand] = await Promise.all([
|
|
930
|
+
fetchLocationCapacityUnits(input.locationId),
|
|
931
|
+
fetchLocationTotalOnHand(input),
|
|
932
|
+
])
|
|
933
|
+
return { capacityUnits, totalOnHand }
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export async function fetchBalanceOnHand(input: {
|
|
937
|
+
warehouseId: string
|
|
938
|
+
locationId: string
|
|
939
|
+
catalogVariantId: string
|
|
940
|
+
lotId?: string | null
|
|
941
|
+
}): Promise<number> {
|
|
942
|
+
const params = buildQuery({
|
|
943
|
+
page: 1,
|
|
944
|
+
pageSize: 20,
|
|
945
|
+
warehouseId: input.warehouseId,
|
|
946
|
+
locationId: input.locationId,
|
|
947
|
+
catalogVariantId: input.catalogVariantId,
|
|
948
|
+
lotId: input.lotId?.trim() || undefined,
|
|
949
|
+
})
|
|
950
|
+
const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
|
|
951
|
+
`/api/wms/inventory/balances?${params}`,
|
|
952
|
+
)
|
|
953
|
+
if (!call.ok) {
|
|
954
|
+
throw new BalanceLookupError()
|
|
955
|
+
}
|
|
956
|
+
const row = selectBalanceLookupRow(call.result?.items ?? [], input.lotId)
|
|
957
|
+
if (!row) return 0
|
|
958
|
+
const onHand = Number(row.quantity_on_hand ?? 0)
|
|
959
|
+
return Number.isFinite(onHand) ? onHand : 0
|
|
960
|
+
}
|