fix agent issues

This commit is contained in:
marshal
2026-09-08 13:57:20 +00:00
parent 7af3ded7d7
commit 52f604e738
4 changed files with 94 additions and 16 deletions

View File

@@ -220,6 +220,15 @@ export class GlOperationsService {
wagonAllocated,
departedAt: departedAt ? new Date(departedAt).toISOString() : null,
arrivedAt: arrivedAt ? new Date(arrivedAt).toISOString() : null,
// The train's own actuals, kept separate from the booking's journey
// above: a UI can say where the TRAIN is without implying this booking
// has been unloaded (which is what the clearance gates key on).
scheduleDepartedAt: schedule?.actualDepartureAt
? new Date(schedule.actualDepartureAt).toISOString()
: null,
scheduleArrivedAt: schedule?.actualArrivalAt
? new Date(schedule.actualArrivalAt).toISOString()
: null,
};
}

View File

@@ -44,10 +44,27 @@ function isMilestoneDone(ms: MilestoneRow[] | undefined, code: string): boolean
const formatStamp = (value: string): string => new Date(value).toLocaleString();
/**
* When the TRAIN physically left / reached its terminal, as opposed to when
* THIS booking was loaded and unloaded. The booking's own stamps gate the
* clearance steps that follow (T1), but the train leg is about the train, so
* a booking still waiting to be unloaded must not make an arrived train read
* as in transit. Falls back to the booking's stamps on legacy rows that carry
* no schedule actuals.
*/
function trainDeparted(train: Freight.ClearanceView["train"] | undefined): string | null {
return train?.scheduleDepartedAt ?? train?.departedAt ?? null;
}
function trainArrived(train: Freight.ClearanceView["train"] | undefined): string | null {
return train?.scheduleArrivedAt ?? train?.arrivedAt ?? null;
}
function trainLegDescription(train: Freight.ClearanceView["train"] | undefined): string {
if (train?.arrivedAt) return `Arrived ${formatStamp(train.arrivedAt)}`;
if (train?.departedAt) return `Departed ${formatStamp(train.departedAt)} · in transit`;
return "Departure and arrival";
const arrived = trainArrived(train);
if (arrived) return `Arrived ${formatStamp(arrived)}`;
if (trainDeparted(train)) return "In transit — completes on arrival";
return "Arrival at Djibouti";
}
const OWNER_LABEL: Record<Owner, string> = {
@@ -260,16 +277,28 @@ export function ForwarderClearancePanel({
done: done("EXPORT_TRANSPORT_ISSUED"),
},
{
key: "train",
label: "Train to Djibouti",
key: "departed",
label: "Train departs to Djibouti",
description: trainDeparted(clearance.train)
? `Departed ${formatStamp(trainDeparted(clearance.train)!)}`
: "Departure from the loading yard",
owner: "system",
done: Boolean(trainDeparted(clearance.train) || trainArrived(clearance.train)),
},
{
key: "arrived",
label: "Train arrives at Djibouti",
description: trainLegDescription(clearance.train),
owner: "system",
done: Boolean(clearance.train?.arrivedAt),
done: Boolean(trainArrived(clearance.train)),
},
{
key: "t1close",
label: "Accept T1",
description: "The Djibouti agent closes once the train arrives",
description:
trainArrived(clearance.train) && !clearance.train?.arrivedAt
? "Unlocks once the cargo is unloaded at Djibouti"
: "The Djibouti agent closes once the train arrives",
owner: "djibouti",
done: Boolean(clearance.t1Closed),
},

View File

@@ -47,16 +47,31 @@ function isMilestoneDone(
const formatStamp = (value: string): string => new Date(value).toLocaleString();
/**
* The rail leg's own stamps, so a train that has left reads as in transit
* rather than untouched — the step only completes on arrival.
* The TRAIN's own actuals, as opposed to THIS booking's journey (loaded at,
* unloaded at its own destination). The rail leg is about the train, so an
* arrived train must not read as in transit merely because this booking has
* not been unloaded yet. Falls back to the booking's stamps on legacy rows
* that carry no schedule actuals.
*/
function trainDeparted(
train: Freight.ClearanceView["train"] | undefined,
): string | null {
return train?.scheduleDepartedAt ?? train?.departedAt ?? null;
}
function trainArrived(
train: Freight.ClearanceView["train"] | undefined,
): string | null {
return train?.scheduleArrivedAt ?? train?.arrivedAt ?? null;
}
function trainLegDescription(
train: Freight.ClearanceView["train"] | undefined,
): string {
if (train?.arrivedAt) return `Arrived ${formatStamp(train.arrivedAt)}`;
if (train?.departedAt)
return `Departed ${formatStamp(train.departedAt)} · in transit`;
return "Departure and arrival";
const arrived = trainArrived(train);
if (arrived) return `Arrived ${formatStamp(arrived)}`;
if (trainDeparted(train)) return "In transit — completes on arrival";
return "Arrival at Djibouti";
}
/**
@@ -238,13 +253,25 @@ export function TransitClearanceActionPanel({
done: bookingDone("EXPORT_TRANSPORT_ISSUED"),
},
{
label: "Train to Djibouti",
label: "Train departs to Djibouti",
description: trainDeparted(clearance?.train)
? `Departed ${formatStamp(trainDeparted(clearance?.train)!)}`
: "Departure from the loading yard",
done: Boolean(
trainDeparted(clearance?.train) || trainArrived(clearance?.train),
),
},
{
label: "Train arrives at Djibouti",
description: trainLegDescription(clearance?.train),
done: Boolean(clearance?.train?.arrivedAt),
done: Boolean(trainArrived(clearance?.train)),
},
{
label: "Accept T1",
description: "You close the T1 once the train arrives",
description:
trainArrived(clearance?.train) && !clearance?.train?.arrivedAt
? "Unlocks once the cargo is unloaded at Djibouti"
: "You close the T1 once the train arrives",
done: Boolean(clearance?.t1Closed),
},
{