feat: enhance train scheduling and contract management features

- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage.
- Implemented API endpoints for recording station work and managing wagon detach requests.
- Updated contract templates to include Ethiopian customs handling options.
- Enhanced shipment forms to collect customs clearing agent details for without-customs bookings.
- Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts.
- Improved validation for customs clearing agent information in shipment forms.
- Updated various components and services to accommodate new features and ensure data integrity.
This commit is contained in:
Marshal
2026-08-25 21:44:21 +00:00
parent d5a5085d6d
commit b926a3116e
67 changed files with 2998 additions and 255 deletions

View File

@@ -799,6 +799,17 @@ export function ScheduleWorkspacePanel({
{group.rows.map((b) => {
const ref = b.reference ?? b.id.slice(0, 8);
const journey = journeyById.get(b.id);
// Server gates load/unload on the yard's started work
// window (Start loading/unloading buttons) — mirror it.
const loadWindowStarted = Boolean(
b.originYardId &&
schedule.stationWorkLogs?.[b.originYardId]?.loading?.startedAt,
);
const unloadWindowStarted = Boolean(
b.destinationYardId &&
schedule.stationWorkLogs?.[b.destinationYardId]?.unloading
?.startedAt,
);
const riding = b.status === "IN_TRANSIT";
const done = ["ARRIVED", "COMPLETED", "DELIVERED"].includes(
b.status ?? "",
@@ -845,7 +856,9 @@ export function ScheduleWorkspacePanel({
label={
!canLoad
? "You don't have permission to load cargo"
: boardHere
: boardHere && !loadWindowStarted
? `Start loading at ${group.label} first`
: boardHere
? `Load cargo onto the train at ${group.label}`
: passed
? `Train already passed ${group.label} — this cargo missed its stop`
@@ -860,7 +873,7 @@ export function ScheduleWorkspacePanel({
variant="filled"
color="edr-green"
radius="md"
disabled={!boardHere || !canLoad}
disabled={!boardHere || !canLoad || !loadWindowStarted}
leftSection={<PackageCheck size={13} />}
loading={
loadJourney.isPending &&
@@ -877,9 +890,11 @@ export function ScheduleWorkspacePanel({
{showTruckToTrain ? (
<Tooltip
label={
canLoad
? "Customer truck loaded straight onto the wagon — no warehouse receipt, no GRN. Sets direct truck-to-train handover and loads."
: "You don't have permission to load cargo"
!canLoad
? "You don't have permission to load cargo"
: !loadWindowStarted
? `Start loading at ${group.label} first`
: "Customer truck loaded straight onto the wagon — no warehouse receipt, no GRN. Sets direct truck-to-train handover and loads."
}
withArrow
>
@@ -888,7 +903,7 @@ export function ScheduleWorkspacePanel({
variant="light"
color="blue"
radius="md"
disabled={!canLoad}
disabled={!canLoad || !loadWindowStarted}
leftSection={<Truck size={13} />}
loading={truckToTrainPending === b.id}
onClick={() =>
@@ -908,9 +923,11 @@ export function ScheduleWorkspacePanel({
label={
!canUnload
? "You don't have permission to unload cargo"
: alightHere
? "Unload at this yard — stamps the booking's arrival"
: "Unloads when the train reaches its destination yard"
: alightHere && !unloadWindowStarted
? "Start unloading at this yard first"
: alightHere
? "Unload at this yard — stamps the booking's arrival"
: "Unloads when the train reaches its destination yard"
}
withArrow
>
@@ -919,7 +936,7 @@ export function ScheduleWorkspacePanel({
variant="light"
color="orange"
radius="md"
disabled={!alightHere || !canUnload}
disabled={!alightHere || !canUnload || !unloadWindowStarted}
leftSection={<PackageOpen size={13} />}
loading={
unloadJourney.isPending &&