mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 11:55:42 +00:00
feat(train-scheduling): add direct truck-to-train load action beside Load
Export bookings whose cargo goes straight from customer truck to wagon never get a warehouse GRN, so the load gate (assertExportReceivedWithGrn) rejects them forever unless staff first flip exportHandoverMode on the booking detail page, then come back and retry Load. Add a Truck to Train button next to Load in the schedule workspace's on-train list, shown for EXPORT bookings at the boarding yard. One click sets exportHandoverMode=DIRECT_TO_TRAIN then loads the booking.
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
// Repeat, // used by the hidden Move (reassign) button
|
// Repeat, // used by the hidden Move (reassign) button
|
||||||
Train,
|
Train,
|
||||||
TrainFront,
|
TrainFront,
|
||||||
|
Truck,
|
||||||
Weight,
|
Weight,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -37,6 +38,7 @@ import { CountdownTimer } from "@edr/ui-common";
|
|||||||
|
|
||||||
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
||||||
import { api } from "@/services/api";
|
import { api } from "@/services/api";
|
||||||
|
import { bookingsService } from "@/services/bookings.service";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import type {
|
import type {
|
||||||
EligibleContainerBooking,
|
EligibleContainerBooking,
|
||||||
@@ -411,6 +413,31 @@ export function ScheduleWorkspacePanel({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Export cargo that skipped the warehouse (customer truck straight onto the
|
||||||
|
// wagon) has no GRN and never will — loadBooking's GRN gate would keep
|
||||||
|
// rejecting it forever. Setting DIRECT_TO_TRAIN tells that gate the carriage
|
||||||
|
// acceptance sheet is the handover document instead, then loads in one click.
|
||||||
|
const [truckToTrainPending, setTruckToTrainPending] = useState<string | null>(null);
|
||||||
|
const doTruckToTrain = (bookingId: string, ref: string) => {
|
||||||
|
setTruckToTrainPending(bookingId);
|
||||||
|
bookingsService
|
||||||
|
.setExportHandoverMode(bookingId, "DIRECT_TO_TRAIN")
|
||||||
|
.then(() => loadJourney.mutateAsync({ scheduleId: schedule.id, bookingId }))
|
||||||
|
.then(() => {
|
||||||
|
toast({ title: `${ref} loaded — direct truck-to-train handover` });
|
||||||
|
onChanged();
|
||||||
|
void yardWorkQuery.refetch();
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
toast({
|
||||||
|
title: "Could not load as direct truck-to-train",
|
||||||
|
description: apiErrorMessage(error, "Please try again."),
|
||||||
|
variant: "destructive",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.finally(() => setTruckToTrainPending(null));
|
||||||
|
};
|
||||||
|
|
||||||
const doUnload = (bookingId: string, ref: string) => {
|
const doUnload = (bookingId: string, ref: string) => {
|
||||||
unloadJourney
|
unloadJourney
|
||||||
.mutateAsync({ scheduleId: schedule.id, bookingId })
|
.mutateAsync({ scheduleId: schedule.id, bookingId })
|
||||||
@@ -708,6 +735,12 @@ export function ScheduleWorkspacePanel({
|
|||||||
const alightHere = trainAtYardId != null && b.destinationYardId === trainAtYardId;
|
const alightHere = trainAtYardId != null && b.destinationYardId === trainAtYardId;
|
||||||
const showLoad = canWork && !riding && !done && (journey?.canLoad ?? false);
|
const showLoad = canWork && !riding && !done && (journey?.canLoad ?? false);
|
||||||
const showUnload = canWork && riding && (journey?.canUnload ?? false);
|
const showUnload = canWork && riding && (journey?.canUnload ?? false);
|
||||||
|
const showTruckToTrain =
|
||||||
|
canWork &&
|
||||||
|
!riding &&
|
||||||
|
!done &&
|
||||||
|
boardHere &&
|
||||||
|
b.tradeDirection === "EXPORT";
|
||||||
return (
|
return (
|
||||||
<BookingCard
|
<BookingCard
|
||||||
key={b.id}
|
key={b.id}
|
||||||
@@ -764,6 +797,24 @@ export function ScheduleWorkspacePanel({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showTruckToTrain ? (
|
||||||
|
<Tooltip
|
||||||
|
label="Customer truck loaded straight onto the wagon — no warehouse receipt, no GRN. Sets direct truck-to-train handover and loads."
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="compact-sm"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<Truck size={13} />}
|
||||||
|
loading={truckToTrainPending === b.id}
|
||||||
|
onClick={() => doTruckToTrain(b.id, ref)}
|
||||||
|
>
|
||||||
|
Truck to Train
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
{showUnload ? (
|
{showUnload ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={
|
label={
|
||||||
|
|||||||
Reference in New Issue
Block a user