mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
changes export flow
This commit is contained in:
@@ -86,6 +86,13 @@ export default function BuildTrainModal({ opened, onClose, onBuilt }: BuildTrain
|
||||
}, [opened]);
|
||||
|
||||
const handleBuild = async () => {
|
||||
if (!trainName.trim()) {
|
||||
toast({
|
||||
title: "Enter the vogue number",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!yardId || locomotiveIds.length < 1) {
|
||||
toast({
|
||||
title: "Pick a yard and couple at least one locomotive",
|
||||
@@ -108,7 +115,7 @@ export default function BuildTrainModal({ opened, onClose, onBuilt }: BuildTrain
|
||||
importTrainNumber: importTrainNumber.trim(),
|
||||
currentYardId: yardId,
|
||||
locomotiveIds,
|
||||
...(trainName.trim() ? { trainName: trainName.trim() } : {}),
|
||||
trainName: trainName.trim(),
|
||||
...(notes.trim() ? { notes: notes.trim() } : {}),
|
||||
});
|
||||
toast({ title: `Train ${composition.code} built` });
|
||||
@@ -143,11 +150,12 @@ export default function BuildTrainModal({ opened, onClose, onBuilt }: BuildTrain
|
||||
wagons are attached on the next screen.
|
||||
</Text>
|
||||
<TextInput
|
||||
label="Name (optional)"
|
||||
placeholder="e.g. Fertilizer block"
|
||||
label="Vogue number"
|
||||
placeholder="Enter vogue number"
|
||||
value={trainName}
|
||||
onChange={(e) => setTrainName(e.currentTarget.value)}
|
||||
maxLength={100}
|
||||
required
|
||||
/>
|
||||
<Group grow>
|
||||
{/* Fixed by the import run — derived, never typed. */}
|
||||
|
||||
@@ -363,14 +363,17 @@ export default function BookingWindowSettingsModal({
|
||||
/>
|
||||
<DurationField
|
||||
label="Payment window"
|
||||
description="Time a selected customer has to pay"
|
||||
description={
|
||||
isExport
|
||||
? "Time an export customer has to pay before the reserved wagons are released — overrides the global export payment window for THIS train only"
|
||||
: "Time a selected customer has to pay"
|
||||
}
|
||||
value={form.paymentWindowMinutes}
|
||||
nativeUnit="minutes"
|
||||
onChange={(v) =>
|
||||
setForm((f) => f && { ...f, paymentWindowMinutes: v })
|
||||
}
|
||||
min={1}
|
||||
disabled={isExport}
|
||||
/>
|
||||
</Group>
|
||||
{!isExport ? (
|
||||
|
||||
@@ -163,6 +163,7 @@ const WAGON_STATUS_OPTIONS = [
|
||||
{ label: "Assigned", value: Freight.WagonStatus.Assigned },
|
||||
{ label: "Maintenance", value: Freight.WagonStatus.Maintenance },
|
||||
{ label: "Detained", value: Freight.WagonStatus.Detained },
|
||||
{ label: "Out of service", value: Freight.WagonStatus.OutOfService },
|
||||
];
|
||||
|
||||
// Statuses staff may set BY HAND on the create/edit form. ASSIGNED is omitted
|
||||
|
||||
@@ -66,6 +66,7 @@ export default function TrainSchedulingGlobalRulesPage() {
|
||||
"windowDurationHours",
|
||||
"docReviewMinutes",
|
||||
"paymentWindowMinutes",
|
||||
"exportPaymentWindowMinutes",
|
||||
];
|
||||
const payload: Partial<Record<keyof TrainSchedulingGlobalRules, number>> = {};
|
||||
for (const key of fields) {
|
||||
@@ -209,8 +210,8 @@ export default function TrainSchedulingGlobalRulesPage() {
|
||||
disabled={loading}
|
||||
/>
|
||||
<DurationField
|
||||
label="Payment window"
|
||||
description="Time a selected customer has to pay before the slot expires"
|
||||
label="Import payment window"
|
||||
description="Time an import/domestic customer has to pay before the reserved slot expires"
|
||||
value={form.paymentWindowMinutes ?? ""}
|
||||
nativeUnit="minutes"
|
||||
onChange={(value) =>
|
||||
@@ -219,6 +220,17 @@ export default function TrainSchedulingGlobalRulesPage() {
|
||||
min={1}
|
||||
disabled={loading}
|
||||
/>
|
||||
<DurationField
|
||||
label="Export payment window"
|
||||
description="Time an export customer has to pay before the reserved wagons are released"
|
||||
value={form.exportPaymentWindowMinutes ?? ""}
|
||||
nativeUnit="minutes"
|
||||
onChange={(value) =>
|
||||
setForm((current) => ({ ...current, exportPaymentWindowMinutes: value }))
|
||||
}
|
||||
min={1}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -119,7 +119,10 @@ export interface TrainSchedulingGlobalRules {
|
||||
windowCloseHour: number;
|
||||
windowDurationHours: number;
|
||||
docReviewMinutes: number;
|
||||
/** Import/domestic customer pay window, minutes. */
|
||||
paymentWindowMinutes: number;
|
||||
/** Export customer pay window, minutes — tuned separately from import. */
|
||||
exportPaymentWindowMinutes: number;
|
||||
/** Minutes before departure the import window closes; null = close at departure. */
|
||||
importCloseOffsetMinutes: number | null;
|
||||
/** Minutes before departure the export window closes; null = close at departure. */
|
||||
|
||||
@@ -149,6 +149,10 @@ function mapBookingToFormValues(
|
||||
destinationYard: yardIdFromBooking(booking.destinationYard, referenceData),
|
||||
cargoType: booking.freightType === "BULK" ? "bulk" : "container",
|
||||
cargoWeight: String(booking.cargoTotalWeightVgm ?? ""),
|
||||
bulkTotalWeightTons:
|
||||
booking.bulkTotalWeightTons != null
|
||||
? String(Number(booking.bulkTotalWeightTons))
|
||||
: "",
|
||||
isHazardous: booking.isHazardous ?? false,
|
||||
isRefrigerated: booking.isRefrigerated ?? false,
|
||||
bulkHazardousQty: String(Number(booking.bulkHazardousQuantity ?? 0)),
|
||||
@@ -455,6 +459,15 @@ export default function EditBookingPage() {
|
||||
: "IMPORT",
|
||||
cargoTypeId: data.cargoType === "container" ? undefined : cargoTypeId,
|
||||
cargoTotalWeightVgm: totalWeight,
|
||||
// Break-bulk (PER_ITEM commodity): actual tonnage alongside the item
|
||||
// count, so wagon allocation can size indivisible items per wagon.
|
||||
...(data.cargoType === "bulk" &&
|
||||
referenceData?.cargo_type
|
||||
?.flatMap((g) => g.children ?? [])
|
||||
.find((c) => c.id === cargoTypeId)?.unit_of_measure === "PER_ITEM" &&
|
||||
Number(data.bulkTotalWeightTons) > 0
|
||||
? { bulkTotalWeightTons: Number(data.bulkTotalWeightTons) }
|
||||
: {}),
|
||||
// Containers: booking-level flags are the OR of the per-container switches;
|
||||
// bulk uses the route-step toggles.
|
||||
isHazardous:
|
||||
|
||||
@@ -519,6 +519,11 @@ export default function NewBookingPage() {
|
||||
// Day-level pool: the customer picks only a day (scheduledDate); the batch
|
||||
// engine assigns the train, so no trainScheduleId is sent.
|
||||
cargoTotalWeightVgm: totalWeight,
|
||||
// Break-bulk: the actual tonnage travels alongside the item count so
|
||||
// wagon allocation can size indivisible items per wagon.
|
||||
...(data.cargoType === "bulk" && isPerItem && Number(data.bulkTotalWeightTons) > 0
|
||||
? { bulkTotalWeightTons: Number(data.bulkTotalWeightTons) }
|
||||
: {}),
|
||||
// Booking-level flags drive the HAZARD / REEFER surcharge triggers. For
|
||||
// containers they're the OR of the per-container switches; for bulk they
|
||||
// come from the cargo-step toggles.
|
||||
|
||||
@@ -120,7 +120,7 @@ function BookingActionModalBody({
|
||||
leftSection={<CheckCircle2 size={16} />}
|
||||
onClick={handleProceed}
|
||||
loading={flow.proceedMutation.isPending}
|
||||
disabled={!flow.scheduledDate}
|
||||
disabled={!flow.scheduledDate || flow.requiresTrainSelection}
|
||||
>
|
||||
Proceed to operation
|
||||
</Button>
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useFileViewer } from "@/hooks/useFileViewer";
|
||||
import { bookingDocNoun } from "./bookingNextAction";
|
||||
import { OperationDatePicker } from "./OperationDatePicker";
|
||||
import { DayAvailabilityHint } from "./DayAvailabilityHint";
|
||||
import { ExportTrainPicker } from "./ExportTrainPicker";
|
||||
import type { ClearanceFlowController } from "./useClearanceFlow";
|
||||
|
||||
const BORDER = "#E6ECF2";
|
||||
@@ -71,6 +72,11 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
|
||||
setAdHocFile,
|
||||
scheduledDate,
|
||||
setScheduledDate,
|
||||
isExportRail,
|
||||
exportTrains,
|
||||
exportTrainsLoading,
|
||||
selectedTrainId,
|
||||
setSelectedTrainId,
|
||||
uploadMutation,
|
||||
proceedMutation,
|
||||
} = flow;
|
||||
@@ -224,9 +230,9 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
|
||||
Choose your shipment day
|
||||
</Text>
|
||||
<Text fz="12px" c="dimmed" mb="sm">
|
||||
Only days with a scheduled departure that can carry your cargo type
|
||||
can be selected. The operations team assigns the specific train for
|
||||
that day.
|
||||
{isExportRail
|
||||
? "Only days with a scheduled departure that can carry your cargo type can be selected. Pick the train you want for that day below."
|
||||
: "Only days with a scheduled departure that can carry your cargo type can be selected. The operations team assigns the specific train for that day."}
|
||||
</Text>
|
||||
<OperationDatePicker
|
||||
originYardId={booking.originYard?.id}
|
||||
@@ -235,13 +241,21 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
|
||||
value={scheduledDate}
|
||||
onChange={setScheduledDate}
|
||||
/>
|
||||
{scheduledDate && (
|
||||
{scheduledDate && !isExportRail && (
|
||||
<DayAvailabilityHint
|
||||
bookingId={booking.id}
|
||||
date={scheduledDate}
|
||||
tradeDirection={booking.tradeDirection}
|
||||
/>
|
||||
)}
|
||||
{scheduledDate && isExportRail && (
|
||||
<ExportTrainPicker
|
||||
options={exportTrains}
|
||||
loading={exportTrainsLoading}
|
||||
value={selectedTrainId}
|
||||
onChange={setSelectedTrainId}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Badge, Box, Group, Loader, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
import { CheckCircle2, TrainFront } from "lucide-react";
|
||||
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
const BORDER = "#E6ECF2";
|
||||
const SELECTED = "#0E7A5F";
|
||||
|
||||
function departureLabel(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString("en-GB", {
|
||||
weekday: "short",
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZone: "Africa/Addis_Ababa",
|
||||
});
|
||||
}
|
||||
|
||||
function closesLabel(iso: string | null): string | null {
|
||||
if (!iso) return null;
|
||||
return new Date(iso).toLocaleString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZone: "Africa/Addis_Ababa",
|
||||
});
|
||||
}
|
||||
|
||||
export interface ExportTrainPickerProps {
|
||||
options: Freight.ExportTrainOption[];
|
||||
loading: boolean;
|
||||
value: string;
|
||||
onChange: (scheduleId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export shipment-day train picker: one card per export train that day, with
|
||||
* live free-wagon space per wagon type for THIS booking's cargo. Full or
|
||||
* not-yet-open trains render disabled — the pick locks the booking onto that
|
||||
* train when the operation request is submitted.
|
||||
*/
|
||||
export function ExportTrainPicker({
|
||||
options,
|
||||
loading,
|
||||
value,
|
||||
onChange,
|
||||
}: ExportTrainPickerProps) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Group gap="xs" mt="sm">
|
||||
<Loader size="xs" />
|
||||
<Text fz="12px" c="dimmed">
|
||||
Checking trains for this day…
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
if (!options.length) return null;
|
||||
|
||||
return (
|
||||
<Box mt="sm">
|
||||
<Text fz="13px" fw={700} c="#10202F" mb={6}>
|
||||
Choose your train
|
||||
</Text>
|
||||
<Stack gap="xs">
|
||||
{options.map((option) => {
|
||||
const bookable = option.isOpen && option.fits;
|
||||
const selected = value === option.scheduleId;
|
||||
const closes = closesLabel(option.bookingClosesAt);
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={option.scheduleId}
|
||||
onClick={() => bookable && onChange(option.scheduleId)}
|
||||
disabled={!bookable}
|
||||
style={{
|
||||
border: `1.5px solid ${selected ? SELECTED : BORDER}`,
|
||||
borderRadius: 10,
|
||||
padding: "10px 12px",
|
||||
opacity: bookable ? 1 : 0.55,
|
||||
cursor: bookable ? "pointer" : "not-allowed",
|
||||
background: selected ? "#F2FAF7" : "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||
<Group gap="xs" align="flex-start" wrap="nowrap">
|
||||
<TrainFront size={16} color={selected ? SELECTED : "#5B6B7A"} />
|
||||
<Box>
|
||||
<Text fz="13px" fw={600} c="#10202F">
|
||||
Departs {departureLabel(option.departure)} EAT
|
||||
</Text>
|
||||
<Text fz="12px" c="dimmed">
|
||||
{option.freeWagons} wagon{option.freeWagons === 1 ? "" : "s"} free
|
||||
for your cargo · you need {option.neededWagons}
|
||||
{closes ? ` · booking closes ${closes} EAT` : ""}
|
||||
</Text>
|
||||
<Group gap={6} mt={4}>
|
||||
{option.byWagonType.map((t) => (
|
||||
<Badge
|
||||
key={t.wagonTypeId ?? "default"}
|
||||
size="sm"
|
||||
variant="light"
|
||||
color={t.freeWagons > 0 ? "teal" : "gray"}
|
||||
>
|
||||
{t.code ?? t.name ?? "Wagon"}: {t.freeWagons} free
|
||||
</Badge>
|
||||
))}
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
{selected ? (
|
||||
<CheckCircle2 size={18} color={SELECTED} />
|
||||
) : !option.isOpen ? (
|
||||
<Badge size="sm" color="gray" variant="light">
|
||||
Not open
|
||||
</Badge>
|
||||
) : !option.fits ? (
|
||||
<Badge size="sm" color="red" variant="light">
|
||||
Too little space
|
||||
</Badge>
|
||||
) : null}
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,39 @@ export function useClearanceFlow(booking: Freight.IBooking) {
|
||||
const [pending, setPending] = useState<Record<string, File>>({});
|
||||
const [adHoc, setAdHoc] = useState<AdHocDoc[]>([]);
|
||||
// Binding shipment day chosen for the operation request (yyyy-MM-dd).
|
||||
const [scheduledDate, setScheduledDate] = useState<string>("");
|
||||
const [scheduledDate, setScheduledDateState] = useState<string>("");
|
||||
// Export rail only: the specific train picked for that day.
|
||||
const [selectedTrainId, setSelectedTrainId] = useState<string>("");
|
||||
|
||||
// Mirrors the API's isRoadService: road/truck services dispatch a truck and
|
||||
// never pick a train. IBooking.serviceType is a string code.
|
||||
const serviceCode = String(booking.serviceType ?? "").toUpperCase();
|
||||
const isRoad = serviceCode.startsWith("ROAD") || serviceCode.startsWith("TRUCK");
|
||||
const isExportRail = booking.tradeDirection === "EXPORT" && !isRoad;
|
||||
|
||||
// A new day invalidates the old train pick.
|
||||
const setScheduledDate = (date: string) => {
|
||||
setScheduledDateState(date);
|
||||
setSelectedTrainId("");
|
||||
};
|
||||
|
||||
const exportTrainsQuery = useQuery(
|
||||
api.bookings.getExportTrains.queryOptions({
|
||||
input: { bookingId: booking.id, date: scheduledDate },
|
||||
enabled: isExportRail && Boolean(scheduledDate),
|
||||
}),
|
||||
);
|
||||
const exportTrains = useMemo(
|
||||
() => (isExportRail ? (exportTrainsQuery.data ?? []) : []),
|
||||
[isExportRail, exportTrainsQuery.data],
|
||||
);
|
||||
// Export must ride the train the customer picked — block proceed until a
|
||||
// bookable train is chosen (when none is bookable, proceed stays allowed so
|
||||
// the API can answer with the real capacity error).
|
||||
const requiresTrainSelection =
|
||||
isExportRail &&
|
||||
exportTrains.some((t) => t.isOpen && t.fits) &&
|
||||
!selectedTrainId;
|
||||
|
||||
const refresh = () => {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -146,9 +178,14 @@ export function useClearanceFlow(booking: Freight.IBooking) {
|
||||
};
|
||||
|
||||
const proceedToOperation = (opts?: { onSuccess?: () => void }) => {
|
||||
if (!scheduledDate) return;
|
||||
if (!scheduledDate || requiresTrainSelection) return;
|
||||
proceedMutation.mutate(
|
||||
{ id: booking.id, scheduledDate },
|
||||
{
|
||||
id: booking.id,
|
||||
scheduledDate,
|
||||
trainScheduleId:
|
||||
isExportRail && selectedTrainId ? selectedTrainId : undefined,
|
||||
},
|
||||
{ onSuccess: opts?.onSuccess },
|
||||
);
|
||||
};
|
||||
@@ -179,6 +216,13 @@ export function useClearanceFlow(booking: Freight.IBooking) {
|
||||
// schedule
|
||||
scheduledDate,
|
||||
setScheduledDate,
|
||||
// export train pick
|
||||
isExportRail,
|
||||
exportTrains,
|
||||
exportTrainsLoading: exportTrainsQuery.isLoading,
|
||||
selectedTrainId,
|
||||
setSelectedTrainId,
|
||||
requiresTrainSelection,
|
||||
// mutations
|
||||
uploadMutation,
|
||||
proceedMutation,
|
||||
|
||||
@@ -164,6 +164,10 @@ export const bookingFormSchema = z
|
||||
scheduledDate: z.string().default(""),
|
||||
cargoType: z.enum(["container", "bulk"], "Select a cargo type."),
|
||||
cargoWeight: z.string(),
|
||||
// Break-bulk (PER_ITEM commodities) only: actual total weight in tons —
|
||||
// cargoWeight then carries the item count. Empty for PER_TON bulk and
|
||||
// container cargo.
|
||||
bulkTotalWeightTons: z.string().default(""),
|
||||
cargoTypePath: z.array(z.string()).default([]),
|
||||
cargoFreeText: z.string(),
|
||||
isHazardous: z.boolean(),
|
||||
@@ -239,6 +243,16 @@ export const bookingFormSchema = z
|
||||
},
|
||||
{ message: "Enter a quantity greater than 0.", path: ["cargoWeight"] },
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
// Filled only for PER_ITEM commodities (the field is hidden otherwise);
|
||||
// when present it must be a positive tonnage.
|
||||
if (data.cargoType !== "bulk" || !data.bulkTotalWeightTons) return true;
|
||||
const tons = Number(data.bulkTotalWeightTons);
|
||||
return !Number.isNaN(tons) && tons > 0;
|
||||
},
|
||||
{ message: "Enter a total weight greater than 0.", path: ["bulkTotalWeightTons"] },
|
||||
)
|
||||
.refine(
|
||||
(data) => !(data.cargoType === "container" && data.containers.length === 0),
|
||||
{ message: "Add at least one container.", path: ["containers"] },
|
||||
@@ -381,6 +395,7 @@ export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
|
||||
extraRoutes: [],
|
||||
scheduledDate: "",
|
||||
cargoWeight: "",
|
||||
bulkTotalWeightTons: "",
|
||||
cargoTypePath: [],
|
||||
cargoFreeText: "",
|
||||
isHazardous: false,
|
||||
|
||||
@@ -350,6 +350,32 @@ export function Step5CargoDetails({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Break-bulk: item count alone can't size wagons — indivisible items
|
||||
pack by weight, so the actual total tonnage is captured too. */}
|
||||
{selectedCommodity && !isGeneralContract && isPerItem && (
|
||||
<Controller
|
||||
name="bulkTotalWeightTons"
|
||||
control={form.control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
id="bulkTotalWeightTons"
|
||||
type="number"
|
||||
onKeyDown={blockNegative}
|
||||
label="Total weight (Tons) *"
|
||||
placeholder="e.g. 800"
|
||||
leftSection={<Weight className="h-4 w-4" />}
|
||||
error={fieldState.error?.message}
|
||||
description="Actual total weight of all items — used to work out how many items fit one wagon."
|
||||
radius={10}
|
||||
styles={fieldStyles}
|
||||
min={0}
|
||||
step={0.01}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Cargo handling — how much of the cargo is hazardous / refrigerated,
|
||||
in the SAME unit as the quantity above (tons or items). Shown once a
|
||||
commodity is chosen so the unit is known; general contracts handle
|
||||
|
||||
@@ -385,10 +385,11 @@ export default function ContractDetailPage() {
|
||||
// slot, so the action is "none" and no booking button is shown.
|
||||
const canBookShipment =
|
||||
bookingAction.kind === "book" || bookingAction.kind === "rebook";
|
||||
const canRequestShipment = bookingAction.kind === "request";
|
||||
// TODO: bulk contracts pause here for now — remove isContainer gate once bulk flow resumes.
|
||||
const canRequestShipment = bookingAction.kind === "request" && isContainer;
|
||||
// Self-clearance import/export (ONE_TIME or GENERAL): one-click bare booking
|
||||
// instance — the per-booking clearance runs first, so no window gate here.
|
||||
const canInitiateBooking = bookingAction.kind === "initiate";
|
||||
const canInitiateBooking = bookingAction.kind === "initiate" && isContainer;
|
||||
|
||||
return (
|
||||
<Box style={{ padding: "28px 32px 40px" }}>
|
||||
|
||||
@@ -407,10 +407,10 @@ export const api = {
|
||||
),
|
||||
|
||||
proceedToOperation: endpoint<
|
||||
{ id: string; scheduledDate: string },
|
||||
{ id: string; scheduledDate: string; trainScheduleId?: string },
|
||||
Freight.IBooking
|
||||
>("bookings", "proceedToOperation", ({ id, scheduledDate }) =>
|
||||
bookingsService.proceedToOperation(id, scheduledDate),
|
||||
>("bookings", "proceedToOperation", ({ id, scheduledDate, trainScheduleId }) =>
|
||||
bookingsService.proceedToOperation(id, scheduledDate, trainScheduleId),
|
||||
),
|
||||
|
||||
checkPayment: endpoint<{ orderId: string }, { status: string }>(
|
||||
@@ -468,6 +468,13 @@ export const api = {
|
||||
bookingsService.getDayAvailability(bookingId, date),
|
||||
),
|
||||
|
||||
getExportTrains: endpoint<
|
||||
{ bookingId: string; date: string },
|
||||
Freight.ExportTrainOption[]
|
||||
>("train-scheduling", "exportTrains", ({ bookingId, date }) =>
|
||||
bookingsService.getExportTrains(bookingId, date),
|
||||
),
|
||||
|
||||
getMyBookingWindows: endpoint<void, MyBookingWindow[]>(
|
||||
"train-scheduling",
|
||||
"myBookingWindows",
|
||||
|
||||
@@ -378,10 +378,11 @@ export const bookingsService = {
|
||||
proceedToOperation: async (
|
||||
id: string,
|
||||
scheduledDate: string,
|
||||
trainScheduleId?: string,
|
||||
): Promise<Freight.IBooking> => {
|
||||
const { data } = await client.post(
|
||||
`/api/bookings/${id}/clearance/proceed`,
|
||||
{ scheduledDate },
|
||||
{ scheduledDate, ...(trainScheduleId ? { trainScheduleId } : {}) },
|
||||
);
|
||||
return data.data;
|
||||
},
|
||||
@@ -508,6 +509,18 @@ export const bookingsService = {
|
||||
return (data.data as Freight.AvailableDaysResponse).days;
|
||||
},
|
||||
|
||||
// Export train picker: the day's export trains with per-wagon-type free space.
|
||||
getExportTrains: async (
|
||||
bookingId: string,
|
||||
date: string,
|
||||
): Promise<Freight.ExportTrainOption[]> => {
|
||||
const { data } = await client.get(
|
||||
`/api/bookings/${bookingId}/export-trains`,
|
||||
{ params: { date } },
|
||||
);
|
||||
return data.data as Freight.ExportTrainOption[];
|
||||
},
|
||||
|
||||
// Advisory free-wagon count for a shipment day (planning hint, not enforced).
|
||||
getDayAvailability: async (
|
||||
bookingId: string,
|
||||
|
||||
Reference in New Issue
Block a user