diff --git a/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-inventory.entity.ts b/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-inventory.entity.ts index 1434c3653..a5d4e1eeb 100644 --- a/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-inventory.entity.ts +++ b/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-inventory.entity.ts @@ -37,7 +37,10 @@ export const WAREHOUSE_INVENTORY_TRANSITIONS: Record { + /** Shared query for the import queues — IMPORT inventory at the given statuses, inspection columns. */ + private async importQueueByStatuses(statuses: string[]): Promise { const rows: Array< ImportUnloadedRow & { originCountry: string | null; destinationCountry: string | null } > = await this.dataSource.query( @@ -743,8 +739,9 @@ export class WarehouseInventoryService { LEFT JOIN freight.train_schedule_bookings tsb ON tsb.booking_id = b.id AND tsb.deleted_at IS NULL LEFT JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id WHERE inv.deleted_at IS NULL - AND inv.status IN ('UNLOADED', 'DESTINATION_INSPECTION', 'UNDER_INSPECTION') + AND inv.status = ANY($1) ORDER BY inv.created_at DESC`, + [statuses], ); return rows @@ -755,6 +752,22 @@ export class WarehouseInventoryService { .map(({ originCountry: _oc, destinationCountry: _dc, ...rest }) => rest); } + /** + * Batch 9 — IMPORT inventory sitting in the Unloaded Queue (UNLOADED / destination-inspection + * states), with the columns the inspection screen needs. Read-only. + */ + importUnloadedQueue(): Promise { + return this.importQueueByStatuses(['UNLOADED', 'DESTINATION_INSPECTION', 'UNDER_INSPECTION']); + } + + /** + * Batch 10 — IMPORT inventory that passed inspection and is PICKUP_READY (READY_FOR_PICKUP), + * awaiting customer pickup / last mile / store / dispatch. Read-only. + */ + importPickupReadyQueue(): Promise { + return this.importQueueByStatuses(['READY_FOR_PICKUP']); + } + /** * Bulk-dispatch loaded EXPORT inventory. Reuses the single-item dispatch transition * (status LOADED → DISPATCHED, capacity freed, movement/activity logged). Items not diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx index 03a0adcf6..eab14ad63 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx @@ -32,6 +32,7 @@ import { useLoadedExport, useReadyToLoadExport, useReceiveInventory, + useWarehouseInventory, useWarehouseYards, useWarehouseZones, useWarehouses, @@ -50,6 +51,7 @@ import type { } from '@/types/warehouse'; import { BookingSelect } from './BookingSelect'; import { InspectionReportModal } from './InspectionReportModal'; +import { InventoryWorkbench } from './InventoryWorkbench'; import { extractErrorMessage, formatDate, formatNumber } from './options'; interface ReceiveInventoryModalProps { @@ -1023,6 +1025,37 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) { ); } +/** + * Import Dispatch Queue (Batch 10): inspected import items that are PICKUP_READY (READY_FOR_PICKUP), + * awaiting Customer Pickup (release → deliver), Store, or Dispatch. Reuses InventoryWorkbench so all + * existing actions + modals stay intact; Last Mile shows only when the booking requested door delivery. + * Nothing is stored automatically — Store is an explicit operator action. + */ +function ImportDispatchQueueTab({ enabled }: { enabled: boolean }) { + const { toast } = useToast(); + const { data: items = [], isLoading } = useWarehouseInventory( + enabled ? { status: 'READY_FOR_PICKUP' } : undefined, + ); + + return ( + + + {items.length} pickup-ready item{items.length !== 1 ? 's' : ''} + + + toast({ + title: 'Last mile delivery', + description: `Door delivery for ${it.booking?.reference ?? it.id} — handled in the next batch.`, + }) + } + /> + + ); +} + /** New bulk Receive: Import / Export tabs with eligible PAID bookings. */ function BulkReceiveModal({ opened, onClose, onReceived }: ReceiveInventoryModalProps) { const [location, setLocation] = useState({ warehouseId: '', yardId: '', zoneId: '' }); @@ -1064,9 +1097,7 @@ function BulkReceiveModal({ opened, onClose, onReceived }: ReceiveInventoryModal - - Dispatch Queue — coming in the next batch. - + diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx index 7156cf585..d4ce1a1d6 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx @@ -153,6 +153,30 @@ export function WarehouseInventoryTable({ {humanizeEnum(nextAction.replace(/-/g, '_'))} )} + {/* Batch 10 — a PICKUP_READY import item can also be stored or dispatched, + kept separate from the customer-pickup (release/deliver) next action. */} + {item.status === 'READY_FOR_PICKUP' && ( + <> + + + + )} {item.status !== 'DISPATCHED' && ( onMove(item)}> diff --git a/apps/edr-freight-web/backoffice/src/constants/URLS.ts b/apps/edr-freight-web/backoffice/src/constants/URLS.ts index c7a02c0c6..62c2a3949 100644 --- a/apps/edr-freight-web/backoffice/src/constants/URLS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/URLS.ts @@ -322,6 +322,7 @@ export const URL_CONSTANTS = { `/warehouse-inventory/import/trains/${scheduleId}/items`, IMPORT_AUTO_UNLOAD_ARRIVED: '/warehouse-inventory/import/auto-unload-arrived-bookings', IMPORT_UNLOADED_QUEUE: '/warehouse-inventory/import/unloaded-queue', + IMPORT_PICKUP_READY_QUEUE: '/warehouse-inventory/import/pickup-ready-queue', }, WAREHOUSE_LOADINGS: { diff --git a/apps/edr-freight-web/backoffice/src/hooks/useWarehouses.ts b/apps/edr-freight-web/backoffice/src/hooks/useWarehouses.ts index b7ff9b4e9..5e50c151a 100644 --- a/apps/edr-freight-web/backoffice/src/hooks/useWarehouses.ts +++ b/apps/edr-freight-web/backoffice/src/hooks/useWarehouses.ts @@ -258,6 +258,15 @@ export function useImportUnloadedQueue(enabled = true) { }); } +/** IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch. Read-only. */ +export function useImportPickupReadyQueue(enabled = true) { + return useQuery({ + queryKey: ['warehouse-inventory', 'import-pickup-ready-queue'], + queryFn: () => warehouseService.importPickupReadyQueue().then((r) => r.data), + enabled, + }); +} + // ── Loading (Batch 3) ──────────────────────────────────────────────────────── export function useLoadableWagons(enabled = true) { diff --git a/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts b/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts index f11a62fd9..082a447fa 100644 --- a/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/warehouse.service.ts @@ -155,6 +155,8 @@ export const warehouseService = { ), importUnloadedQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_UNLOADED_QUEUE), + importPickupReadyQueue: () => + apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_PICKUP_READY_QUEUE), move: (id: string, payload: MoveInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.MOVE(id), payload), movements: (id: string) =>