mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
feat(train-scheduling): auto-place intercity cargo on wagons freed mid-corridor
Intercity bookings ride the wagons freed by earlier unloads (e.g. import containers uncoupled at Dire Dawa). loadBooking now auto-allocates a DOMESTIC booking onto on-train slots whose cargo has all departed — greedy in consist order by capacity, container numbers copied for the marshalling tally. Falls back to unallocated load when nothing is free.
This commit is contained in:
@@ -416,6 +416,8 @@ export const URL_CONSTANTS = {
|
||||
`/train-scheduling/schedules/${id}/import-djibouti/load-list/document`,
|
||||
EXPORT_LOAD_LIST_DOCUMENT: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/export/load-list/document`,
|
||||
INTERCITY_MARSHALLING_DOCUMENT: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/intercity/marshalling/document`,
|
||||
CHECKPOINTS: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/checkpoints`,
|
||||
ARRIVE: (id: string) => `/train-scheduling/schedules/${id}/arrive`,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ArrowLeft,
|
||||
CalendarClock,
|
||||
CheckCircle2,
|
||||
FileText,
|
||||
Flag,
|
||||
MapPin,
|
||||
Navigation,
|
||||
@@ -40,6 +41,8 @@ import { freightBrand } from "@/theme/freight-brand";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/services/api";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { openPdfBlob } from "@/components/warehouses/pdf";
|
||||
import { trainSchedulingService } from "@/services/trainScheduling.service";
|
||||
|
||||
const parseError = (error: unknown, fallback: string) => {
|
||||
if (isAxiosError(error)) {
|
||||
@@ -160,6 +163,31 @@ export default function TrainScheduleTrackPage() {
|
||||
enabled: Boolean(scheduleId) && trackQuery.data?.status === "DISPATCHED",
|
||||
}),
|
||||
);
|
||||
// Marshalling 2: the current on-board list, reprinted after station work.
|
||||
const intercityMarshalling = useMutation({
|
||||
mutationFn: () =>
|
||||
trainSchedulingService.downloadIntercityMarshallingDocument(scheduleId ?? ""),
|
||||
});
|
||||
const openIntercityMarshalling = async () => {
|
||||
const pdfWindow = window.open("", "_blank");
|
||||
try {
|
||||
const blob = await intercityMarshalling.mutateAsync();
|
||||
const opened = openPdfBlob(blob, `intercity-marshalling-${scheduleId}.pdf`, pdfWindow);
|
||||
toast({
|
||||
title: "Intercity marshalling ready",
|
||||
description: opened
|
||||
? "The PDF opened in a browser tab for printing or saving."
|
||||
: "The browser blocked the preview tab, so the PDF was downloaded.",
|
||||
});
|
||||
} catch (error) {
|
||||
pdfWindow?.close();
|
||||
toast({
|
||||
title: "Could not open intercity marshalling document",
|
||||
description: parseError(error, "Please try again"),
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
const [yardModal, setYardModal] = useState<{
|
||||
station: TrackStation;
|
||||
isFinal: boolean;
|
||||
@@ -249,17 +277,32 @@ export default function TrainScheduleTrackPage() {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/dashboard/operations/train-scheduling-v2/${scheduleId}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="compact-sm"
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
w="fit-content"
|
||||
>
|
||||
Back to schedule
|
||||
</Button>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/dashboard/operations/train-scheduling-v2/${scheduleId}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="compact-sm"
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
w="fit-content"
|
||||
>
|
||||
Back to schedule
|
||||
</Button>
|
||||
{inTransit || arrived ? (
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
radius="lg"
|
||||
size="compact-sm"
|
||||
leftSection={<FileText size={16} />}
|
||||
loading={intercityMarshalling.isPending}
|
||||
onClick={() => void openIntercityMarshalling()}
|
||||
>
|
||||
Intercity Marshalling
|
||||
</Button>
|
||||
) : null}
|
||||
</Group>
|
||||
|
||||
{/* ── Hero: gradient wash, route + a bold progress ring woven together ── */}
|
||||
<Paper
|
||||
|
||||
@@ -208,10 +208,12 @@ export default function TrainScheduleV2DetailPage() {
|
||||
const switchGov = useMutation(api.trainScheduling.switchGovernmentBooking.mutationOptions());
|
||||
const dispatch = useMutation(api.trainScheduling.dispatchSchedule.mutationOptions());
|
||||
const downloadMarshalling = useMutation({
|
||||
mutationFn: ({ id, direction }: { id: string; direction?: string | null }) =>
|
||||
direction === "EXPORT"
|
||||
? trainSchedulingService.downloadExportLoadListDocument(id)
|
||||
: trainSchedulingService.downloadImportDjiboutiLoadListDocument(id),
|
||||
mutationFn: ({ id, direction, variant }: { id: string; direction?: string | null; variant?: "INTERCITY" }) =>
|
||||
variant === "INTERCITY"
|
||||
? trainSchedulingService.downloadIntercityMarshallingDocument(id)
|
||||
: direction === "EXPORT"
|
||||
? trainSchedulingService.downloadExportLoadListDocument(id)
|
||||
: trainSchedulingService.downloadImportDjiboutiLoadListDocument(id),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -426,14 +428,21 @@ export default function TrainScheduleV2DetailPage() {
|
||||
title?: string;
|
||||
successDescription?: string;
|
||||
errorTitle?: string;
|
||||
variant?: "INTERCITY";
|
||||
}) => {
|
||||
const pdfWindow = window.open("", "_blank");
|
||||
try {
|
||||
const blob = await downloadMarshalling.mutateAsync({
|
||||
id: scheduleId,
|
||||
direction: schedule.direction,
|
||||
variant: options?.variant,
|
||||
});
|
||||
const prefix = schedule.direction === "EXPORT" ? "export-marshalling" : "import-marshalling";
|
||||
const prefix =
|
||||
options?.variant === "INTERCITY"
|
||||
? "intercity-marshalling"
|
||||
: schedule.direction === "EXPORT"
|
||||
? "export-marshalling"
|
||||
: "import-marshalling";
|
||||
const filename = `${prefix}-${schedule.trainNumber ?? scheduleId}.pdf`;
|
||||
const opened = openPdfBlob(blob, filename, pdfWindow);
|
||||
toast({
|
||||
@@ -973,6 +982,24 @@ export default function TrainScheduleV2DetailPage() {
|
||||
Marshalling PDF
|
||||
</Button>
|
||||
) : null}
|
||||
{["DISPATCHED", "ARRIVED"].includes(schedule.status) ? (
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
radius="lg"
|
||||
size="sm"
|
||||
leftSection={<FileText size={16} />}
|
||||
loading={downloadMarshalling.isPending}
|
||||
onClick={() =>
|
||||
void openMarshallingDocument({
|
||||
title: "Intercity marshalling ready",
|
||||
variant: "INTERCITY",
|
||||
})
|
||||
}
|
||||
>
|
||||
Intercity Marshalling
|
||||
</Button>
|
||||
) : null}
|
||||
{["DISPATCHED", "ARRIVED"].includes(schedule.status) ? (
|
||||
<Button
|
||||
component={Link}
|
||||
|
||||
@@ -624,6 +624,16 @@ export const trainSchedulingService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
downloadIntercityMarshallingDocument: async (
|
||||
scheduleId: string,
|
||||
): Promise<Blob> => {
|
||||
const response = await client.get(
|
||||
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_MARSHALLING_DOCUMENT(scheduleId),
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTrack: async (scheduleId: string): Promise<TrainTrackResponse> => {
|
||||
const response = await client.get<TrainTrackResponse>(
|
||||
URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
|
||||
|
||||
Reference in New Issue
Block a user