From daee3eac0ead4ea7d63e0b31de72bfc49fdc98b2 Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Fri, 14 Aug 2026 08:17:35 +0000 Subject: [PATCH] feat(warehouses): pick export train when allocating empty return to wagon Advancing an empty container return to WAGON_ALLOCATED opened no dialog, so the wagon_allocation_reference column stayed null and nobody knew which departure carried the empty. Now the action opens a picker of scheduled EXPORT trains (DRAFT/SCHEDULED) and records the selection. --- .../pages/warehouses/ContainerReturnsPage.tsx | 170 +++++++++++++++++- 1 file changed, 163 insertions(+), 7 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx index f8468181c..11bf69334 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/ContainerReturnsPage.tsx @@ -37,6 +37,7 @@ import type { EmptyContainerReturn, EmptyContainerReturnStatus, } from "@/types/importOperations"; +import type { TrainScheduleListItem } from "@/types/trainScheduling"; import { formatDateTime } from "@/lib/format"; type ReturnType = "all" | "edr" | "customer"; @@ -100,6 +101,7 @@ export default function ContainerReturnsPage() { const [standaloneModalOpen, setStandaloneModalOpen] = useState(false); const [activeKey, setActiveKey] = useState(null); const [historyRow, setHistoryRow] = useState(null); + const [allocateRow, setAllocateRow] = useState(null); const { data: unloadedQueue = [], isLoading: queueLoading } = useQuery({ queryKey: ["import-unloaded-queue"], @@ -306,15 +308,25 @@ export default function ContainerReturnsPage() { }); const advanceStatusMutation = useMutation({ - mutationFn: (id: string) => { + mutationFn: ({ + id, + wagonAllocationReference, + }: { + id: string; + wagonAllocationReference?: string; + }) => { const current = returnedContainers.find((r: any) => r.id === id); const nextIndex = RETURN_STATUS_ORDER.indexOf(current?.status ?? "RETURNED") + 1; const status = RETURN_STATUS_ORDER[nextIndex] ?? "COMPLETED"; - return importOperationsService.updateEmptyReturnStatus(id, { status }); + return importOperationsService.updateEmptyReturnStatus(id, { + status, + wagonAllocationReference, + }); }, onSuccess: () => { toast({ title: "Return status updated" }); qc.invalidateQueries({ queryKey: ["empty-container-returns"] }); + setAllocateRow(null); }, onError: (error: any) => { toast({ @@ -381,9 +393,16 @@ export default function ContainerReturnsPage() { id: "status", header: "Status", cell: ({ row }) => ( - - {RETURN_STATUS_LABEL[row.original.status] ?? row.original.status} - + + + {RETURN_STATUS_LABEL[row.original.status] ?? row.original.status} + + {row.original.wagonAllocationReference && ( + + Train {row.original.wagonAllocationReference} + + )} + ), }, { @@ -406,8 +425,15 @@ export default function ContainerReturnsPage() { @@ -629,6 +655,19 @@ export default function ContainerReturnsPage() { loading={createReturnsMutation.isPending} /> + setAllocateRow(null)} + onSubmit={(reference) => + allocateRow && + advanceStatusMutation.mutate({ + id: allocateRow.id, + wagonAllocationReference: reference, + }) + } + loading={advanceStatusMutation.isPending} + /> + setHistoryRow(null)} title="Status History" size="sm"> {historyRow && ( @@ -649,6 +688,123 @@ export default function ContainerReturnsPage() { ); } +/** + * Empties ride an EXPORT departure back to Djibouti, so wagon allocation picks + * from the export schedules that have not left yet (DRAFT/SCHEDULED). The pick + * is recorded as the return's `wagonAllocationReference`. + */ +function ExportTrainAllocationModal({ + row, + onClose, + onSubmit, + loading, +}: { + row: EmptyContainerReturn | null; + onClose: () => void; + onSubmit: (reference: string) => void; + loading: boolean; +}) { + const [scheduleId, setScheduleId] = useState(null); + + const schedulesQuery = useQuery( + api.trainScheduling.scheduleList.queryOptions({ + input: { filters: { pageSize: 100, sortBy: "scheduledDepartureDate", sortOrder: "ASC" } }, + enabled: Boolean(row), + }), + ); + + const exportTrains = useMemo( + () => + ((schedulesQuery.data?.items ?? []) as TrainScheduleListItem[]).filter( + (s) => s.direction === "EXPORT" && (s.status === "DRAFT" || s.status === "SCHEDULED"), + ), + [schedulesQuery.data], + ); + + const referenceOf = (train: TrainScheduleListItem) => + train.trainNumber || train.reference || train.id; + + return ( + + {row && ( + + {row.containerNumber} + + {schedulesQuery.isLoading ? ( + + + + ) : exportTrains.length === 0 ? ( + No export train is scheduled — create one in Train Scheduling. + ) : ( + + + + + Train + Departure + Route + Wagons + Status + + + + {exportTrains.map((train) => ( + setScheduleId(train.id)} + style={{ cursor: "pointer" }} + > + + setScheduleId(train.id)} + /> + + + + {referenceOf(train)} + + + + {train.scheduleDate ? new Date(train.scheduleDate).toLocaleDateString() : "—"} + + + {train.origin ?? "—"} → {train.destination ?? "—"} + + + {train.wagonsUsed ?? 0}/{train.wagonCount} + + + {train.status} + + + ))} + +
+ )} + + + + + +
+ )} +
+ ); +} + interface ContainerReturnModalProps { opened: boolean; onClose: () => void;