mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- Implemented pagination in ScheduleHistoryPanel to manage large history entries. - Updated API to support pagination parameters for schedule history. - Enhanced ConsolidationApprovalsPage with tabbed navigation and pagination for approval rows. - Introduced new types for paginated responses in bookings and train scheduling services. - Added a database migration to create an index on wagon_booking_allocations for performance improvements.
922 lines
28 KiB
TypeScript
922 lines
28 KiB
TypeScript
import type { Freight } from "@edr/types";
|
|
import { api as client } from "../auth/http";
|
|
import { unwrap } from "@/utils/endpoint";
|
|
import { URL_CONSTANTS } from "@/constants/URLS";
|
|
import type {
|
|
AllocationCandidates,
|
|
BatchBoardFilters,
|
|
BatchBoardListResponse,
|
|
BatchBoardScheduleDetail,
|
|
BookableSchedule,
|
|
BookingWindow,
|
|
AssignBookingsPayload,
|
|
BookingLoadResult,
|
|
BookingUnloadResult,
|
|
CompositionRemovalEntry,
|
|
DocReviewAlert,
|
|
UnassignedBookingsResponse,
|
|
CreateTrainSchedulePayload,
|
|
EligibleContainerBookingsResponse,
|
|
FreightType,
|
|
ImportDjiboutiActionPayload,
|
|
ImportDjiboutiLoadList,
|
|
ImportDjiboutiOperation,
|
|
ImportLoadingBookingsResponse,
|
|
IntercityAcceptResult,
|
|
IntercityCandidatesResult,
|
|
LoadingStatus,
|
|
LocomotiveRecord,
|
|
PinWagonsPayload,
|
|
RecordCheckpointPayload,
|
|
UpdateCheckpointPayload,
|
|
DispatchSchedulePayload,
|
|
StaffBookingWindow,
|
|
ScheduleMergePreview,
|
|
TrainScheduleDetail,
|
|
UpdateScheduleWindowRulePayload,
|
|
TrainScheduleFilters,
|
|
TrainScheduleListFilters,
|
|
TrainScheduleListResponse,
|
|
TrainSchedulePreviewPayload,
|
|
TrainSchedulePreviewResponse,
|
|
TrainSchedulingGlobalRules,
|
|
TrainTrackResponse,
|
|
UploadImportDjiboutiDocumentPayload,
|
|
WagonAllocationAttemptResult,
|
|
YardOption,
|
|
YardWorkResult,
|
|
} from "@/types/trainScheduling";
|
|
|
|
interface BookingReferenceDataResponse {
|
|
yard?: Array<YardOption & { label?: string }>;
|
|
}
|
|
|
|
/** Lightweight polling snapshot — refetch the full detail only when this changes. */
|
|
export interface SchedulePhaseSnapshot {
|
|
status: string;
|
|
bookingWindowStatus: string | null;
|
|
windowPhase: string | null;
|
|
windowOpensAt: string | null;
|
|
windowClosesAt: string | null;
|
|
docReviewEndsAt: string | null;
|
|
paymentPhaseEndsAt: string | null;
|
|
updatedAt: string;
|
|
}
|
|
|
|
const pathsFor = (freightType?: FreightType) =>
|
|
freightType === "BULK"
|
|
? URL_CONSTANTS.TRAIN_SCHEDULING.BULK
|
|
: URL_CONSTANTS.TRAIN_SCHEDULING.CONTAINER;
|
|
|
|
export const trainSchedulingService = {
|
|
getSchedulePhase: async (id: string): Promise<SchedulePhaseSnapshot> => {
|
|
const response = await client.get<SchedulePhaseSnapshot>(
|
|
`/train-scheduling/schedules/${id}/phase`,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
getEligibleBookings: async (
|
|
filters?: TrainScheduleFilters,
|
|
freightType?: FreightType,
|
|
): Promise<EligibleContainerBookingsResponse> => {
|
|
const useUnified = !freightType || freightType === "MIXED";
|
|
// The container/bulk endpoints already encode freight type in the path, and their
|
|
// query DTOs reject an extra `freightType` param — so only pass the station filters.
|
|
const response = await client.get<EligibleContainerBookingsResponse>(
|
|
useUnified
|
|
? URL_CONSTANTS.TRAIN_SCHEDULING.ELIGIBLE_BOOKINGS
|
|
: pathsFor(freightType).ELIGIBLE_BOOKINGS,
|
|
{ params: { ...filters } },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
preview: async (
|
|
payload: TrainSchedulePreviewPayload,
|
|
freightType?: FreightType,
|
|
): Promise<TrainSchedulePreviewResponse> => {
|
|
const useUnified = !freightType || freightType === "MIXED";
|
|
const response = await client.post<TrainSchedulePreviewResponse>(
|
|
useUnified
|
|
? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW
|
|
: pathsFor(freightType).PREVIEW,
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
createSchedule: async (
|
|
payload: CreateTrainSchedulePayload,
|
|
freightType?: FreightType,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
pathsFor(freightType).SCHEDULES,
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
listSchedules: async (
|
|
freightType: FreightType = "CONTAINER",
|
|
filters: TrainScheduleListFilters = {},
|
|
): Promise<TrainScheduleListResponse> => {
|
|
const params: Record<string, string | number> = {};
|
|
if (filters.page) params.page = filters.page;
|
|
if (filters.pageSize) params.pageSize = filters.pageSize;
|
|
if (filters.search?.trim()) params.search = filters.search.trim();
|
|
if (filters.status) params.status = filters.status;
|
|
if (filters.freightType) params.freightType = filters.freightType;
|
|
if (filters.originStationId) params.originStationId = filters.originStationId;
|
|
if (filters.destinationStationId)
|
|
params.destinationStationId = filters.destinationStationId;
|
|
if (filters.sortBy) params.sortBy = filters.sortBy;
|
|
if (filters.sortOrder) params.sortOrder = filters.sortOrder;
|
|
const response = await client.get<TrainScheduleListResponse>(
|
|
pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULES,
|
|
{ params },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getBatchBoard: async (
|
|
filters: BatchBoardFilters = {},
|
|
): Promise<BatchBoardListResponse> => {
|
|
const params: Record<string, string | number> = {};
|
|
if (filters.page) params.page = filters.page;
|
|
if (filters.pageSize) params.pageSize = filters.pageSize;
|
|
if (filters.statuses?.length) params.statuses = filters.statuses.join(",");
|
|
if (filters.bookingWindowStatus)
|
|
params.bookingWindowStatus = filters.bookingWindowStatus;
|
|
if (filters.search?.trim()) params.search = filters.search.trim();
|
|
if (filters.departureFrom) params.departureFrom = filters.departureFrom;
|
|
if (filters.departureTo) params.departureTo = filters.departureTo;
|
|
if (filters.createdFrom) params.createdFrom = filters.createdFrom;
|
|
if (filters.createdTo) params.createdTo = filters.createdTo;
|
|
if (filters.sortBy) params.sortBy = filters.sortBy;
|
|
if (filters.sortOrder) params.sortOrder = filters.sortOrder;
|
|
const response = await client.get<BatchBoardListResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD,
|
|
{ params },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getBatchBoardDetail: async (
|
|
scheduleId: string,
|
|
): Promise<BatchBoardScheduleDetail> => {
|
|
const response = await client.get<BatchBoardScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD_DETAIL(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/**
|
|
* Booking windows for every route/schedule of a contract. A window with
|
|
* `isOpenNow === true` means GL may create a booking right now for that route.
|
|
*/
|
|
getContractBookingWindows: async (
|
|
contractId: string,
|
|
): Promise<BookingWindow[]> => {
|
|
const response = await client.get<BookingWindow[]>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.CONTRACT_BOOKING_WINDOWS(contractId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getBookableSchedules: async (
|
|
originYardId?: string,
|
|
destinationYardId?: string,
|
|
): Promise<BookableSchedule[]> => {
|
|
const response = await client.get<BookableSchedule[]>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKABLE_SCHEDULES,
|
|
{ params: { originYardId, destinationYardId } },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/**
|
|
* Day-level pool: the days that have an OPEN departure on the route. Staff pick
|
|
* a day; the batch engine assigns the train. No capacity is returned.
|
|
*/
|
|
getAvailableDays: async (
|
|
originYardId?: string,
|
|
destinationYardId?: string,
|
|
): Promise<string[]> => {
|
|
const response = await client.get<{ days: string[] }>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS,
|
|
{ params: { originYardId, destinationYardId } },
|
|
);
|
|
return unwrap(response.data).days;
|
|
},
|
|
|
|
// Cargo-aware day pool (matching wagons + open train capacity). `containers`
|
|
// is serialized as a JSON string param (the server parses it).
|
|
// Export train picker for a booking's shipment day; cargo params cover bare
|
|
// instances whose cargo only exists on the form so far.
|
|
getExportTrains: async (
|
|
bookingId: string,
|
|
date: string,
|
|
cargo?: { containerSizes?: string[]; cargoTypeCode?: string; wagons?: number },
|
|
): Promise<Freight.ExportTrainOption[]> => {
|
|
const response = await client.get<Freight.ExportTrainOption[]>(
|
|
`/bookings/${bookingId}/export-trains`,
|
|
{
|
|
params: {
|
|
date,
|
|
...(cargo?.containerSizes?.length
|
|
? { containerSizes: cargo.containerSizes.join(",") }
|
|
: {}),
|
|
...(cargo?.cargoTypeCode ? { cargoTypeCode: cargo.cargoTypeCode } : {}),
|
|
...(cargo?.wagons ? { wagons: cargo.wagons } : {}),
|
|
},
|
|
},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getAvailableDaysForCargo: async (
|
|
query: Freight.AvailableDaysForCargoQuery,
|
|
): Promise<string[]> => {
|
|
const { containers, ...rest } = query;
|
|
const response = await client.get<{ days: string[] }>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS_FOR_CARGO,
|
|
{
|
|
params: {
|
|
...rest,
|
|
...(containers ? { containers: JSON.stringify(containers) } : {}),
|
|
},
|
|
},
|
|
);
|
|
return unwrap(response.data).days;
|
|
},
|
|
|
|
runBatch: async (scheduleId: string): Promise<BatchBoardScheduleDetail> => {
|
|
const response = await client.post<BatchBoardScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.RUN_BATCH(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/**
|
|
* Staff finished reviewing documents early — runs the batch immediately
|
|
* for the schedule's whole route-day group.
|
|
*/
|
|
completeDocReview: async (
|
|
scheduleId: string,
|
|
): Promise<BatchBoardScheduleDetail> => {
|
|
const response = await client.post<BatchBoardScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.DOC_REVIEW_COMPLETE(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
runAllocation: async (
|
|
scheduleId: string,
|
|
): Promise<WagonAllocationAttemptResult> => {
|
|
const response = await client.post<WagonAllocationAttemptResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.RUN_ALLOCATION(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
setBookingWindow: async (
|
|
scheduleId: string,
|
|
status: "OPEN" | "CLOSED",
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.patch<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOW(scheduleId),
|
|
{ status },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
updateScheduleWindowRule: async (
|
|
scheduleId: string,
|
|
payload: UpdateScheduleWindowRulePayload,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.patch<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.WINDOW_RULE(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
updateScheduleDate: async (
|
|
scheduleId: string,
|
|
scheduleDate: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.patch<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.SCHEDULE_DATE(scheduleId),
|
|
{ scheduleDate },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/** What a merge would do — drives the confirmation modal. Read-only. */
|
|
previewScheduleMerge: async (
|
|
scheduleId: string,
|
|
targetTrainId: string,
|
|
): Promise<ScheduleMergePreview> => {
|
|
const response = await client.get<ScheduleMergePreview>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MERGE_PREVIEW(scheduleId, targetTrainId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
/** Merge another train into this schedule. This schedule always survives. */
|
|
mergeScheduleTrain: async (
|
|
scheduleId: string,
|
|
payload: { targetTrainId: string; reason?: string },
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MERGE_TRAIN(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
markBookingPaid: async (bookingId: string): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId),
|
|
{},
|
|
);
|
|
},
|
|
|
|
expireBooking: async (bookingId: string): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId),
|
|
{},
|
|
);
|
|
},
|
|
|
|
moveBookingSchedule: async (
|
|
bookingId: string,
|
|
trainScheduleId: string,
|
|
): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId),
|
|
{
|
|
trainScheduleId,
|
|
},
|
|
);
|
|
},
|
|
|
|
getAllocationCandidates: async (
|
|
bookingId: string,
|
|
): Promise<AllocationCandidates> => {
|
|
const response = await client.get<AllocationCandidates>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.ALLOCATION_CANDIDATES(bookingId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
allocatePaidBooking: async (
|
|
bookingId: string,
|
|
trainScheduleId: string,
|
|
): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.ALLOCATE_BOOKING(bookingId),
|
|
{ trainScheduleId },
|
|
);
|
|
},
|
|
|
|
getScheduleById: async (
|
|
id: string,
|
|
freightType?: FreightType,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.get<TrainScheduleDetail>(
|
|
pathsFor(
|
|
freightType === "MIXED" ? undefined : freightType,
|
|
).SCHEDULE_BY_ID(id),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
assignUnassignedBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_UNASSIGNED_BOOKING(scheduleId),
|
|
{ bookingId },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
assignBookings: async (
|
|
scheduleId: string,
|
|
payload: AssignBookingsPayload,
|
|
freightType?: FreightType,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const useUnified = !freightType || freightType === "MIXED";
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
useUnified
|
|
? URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_BOOKINGS(scheduleId)
|
|
: pathsFor(freightType).ASSIGN_BOOKINGS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
unassignBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.delete<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGN_BOOKING(scheduleId, bookingId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
switchGovernmentBooking: async (
|
|
scheduleId: string,
|
|
governmentBookingId: string,
|
|
removeBookingIds: string[],
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.SWITCH_GOVERNMENT_BOOKING(scheduleId),
|
|
{ governmentBookingId, removeBookingIds },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
pinWagons: async (
|
|
scheduleId: string,
|
|
payload: PinWagonsPayload,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.PIN_WAGONS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
finalizeSchedule: async (
|
|
scheduleId: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.FINALIZE(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getYardWork: async (scheduleId: string): Promise<YardWorkResult> => {
|
|
const response = await client.get<YardWorkResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.YARD_WORK(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
loadScheduleBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<BookingLoadResult> => {
|
|
const response = await client.post<BookingLoadResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_LOAD(scheduleId, bookingId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
unloadScheduleBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<BookingUnloadResult> => {
|
|
const response = await client.post<BookingUnloadResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_UNLOAD(scheduleId, bookingId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
listIntercityBookings: async (): Promise<
|
|
import("@/types/trainScheduling").IntercityRideAlongRow[]
|
|
> => {
|
|
const response = await client.get<
|
|
import("@/types/trainScheduling").IntercityRideAlongRow[]
|
|
>(URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_BOOKINGS);
|
|
return response.data ?? [];
|
|
},
|
|
|
|
getIntercityCandidates: async (
|
|
scheduleId: string,
|
|
): Promise<IntercityCandidatesResult> => {
|
|
const response = await client.get<IntercityCandidatesResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_CANDIDATES(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
acceptIntercityBookings: async (
|
|
scheduleId: string,
|
|
bookingIds: string[],
|
|
): Promise<IntercityAcceptResult> => {
|
|
const response = await client.post<IntercityAcceptResult>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_ACCEPT(scheduleId),
|
|
{ bookingIds },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
loadIntercityBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_LOAD(scheduleId, bookingId),
|
|
{},
|
|
);
|
|
},
|
|
|
|
unloadIntercityBooking: async (
|
|
scheduleId: string,
|
|
bookingId: string,
|
|
): Promise<void> => {
|
|
await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_UNLOAD(scheduleId, bookingId),
|
|
{},
|
|
);
|
|
},
|
|
|
|
dispatchSchedule: async (
|
|
scheduleId: string,
|
|
payload: DispatchSchedulePayload = {},
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.DISPATCH(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getImportLoadingBookings: async (
|
|
scheduleId: string,
|
|
): Promise<ImportLoadingBookingsResponse> => {
|
|
const response = await client.get<ImportLoadingBookingsResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_LOADING_BOOKINGS(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
updateImportLoadingStatus: async (
|
|
scheduleId: string,
|
|
payload: { bookingIds: string[]; loadingStatus: LoadingStatus },
|
|
): Promise<ImportLoadingBookingsResponse> => {
|
|
const response = await client.patch<ImportLoadingBookingsResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_LOADING_STATUS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
setLoadingStatus: async (
|
|
scheduleId: string,
|
|
payload: { bookingIds: string[]; loadingStatus: LoadingStatus },
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.patch<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.LOADING_STATUS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
confirmLoading: async (
|
|
scheduleId: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.CONFIRM_LOADING(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getImportDjiboutiOperation: async (
|
|
scheduleId: string,
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.get<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
uploadImportDjiboutiDocument: async (
|
|
scheduleId: string,
|
|
payload: UploadImportDjiboutiDocumentPayload,
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.post<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_DOCUMENTS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
grantImportDjiboutiGatepass: async (
|
|
scheduleId: string,
|
|
payload: ImportDjiboutiActionPayload = {},
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.post<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_GATEPASS_GRANTED(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
markImportDjiboutiReadyForLoading: async (
|
|
scheduleId: string,
|
|
payload: ImportDjiboutiActionPayload = {},
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.post<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_READY_FOR_LOADING(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
confirmImportDjiboutiLoadedOnTrain: async (
|
|
scheduleId: string,
|
|
payload: ImportDjiboutiActionPayload = {},
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.post<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOADED_ON_TRAIN(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
departImportFromDjibouti: async (
|
|
scheduleId: string,
|
|
payload: ImportDjiboutiActionPayload = {},
|
|
): Promise<ImportDjiboutiOperation> => {
|
|
const response = await client.post<ImportDjiboutiOperation>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_DEPART(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
generateImportDjiboutiLoadList: async (
|
|
scheduleId: string,
|
|
payload: ImportDjiboutiActionPayload = {},
|
|
): Promise<ImportDjiboutiLoadList> => {
|
|
const response = await client.post<ImportDjiboutiLoadList>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOAD_LIST(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
downloadImportDjiboutiLoadListDocument: async (
|
|
scheduleId: string,
|
|
): Promise<Blob> => {
|
|
const response = await client.get(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.IMPORT_DJIBOUTI_LOAD_LIST_DOCUMENT(scheduleId),
|
|
{ responseType: "blob" },
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
downloadExportLoadListDocument: async (
|
|
scheduleId: string,
|
|
): Promise<Blob> => {
|
|
const response = await client.get(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.EXPORT_LOAD_LIST_DOCUMENT(scheduleId),
|
|
{ responseType: "blob" },
|
|
);
|
|
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),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
recordCheckpoint: async (
|
|
scheduleId: string,
|
|
payload: RecordCheckpointPayload,
|
|
): Promise<TrainTrackResponse> => {
|
|
const response = await client.post<TrainTrackResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
updateCheckpoint: async (
|
|
scheduleId: string,
|
|
sequenceNo: number,
|
|
payload: UpdateCheckpointPayload,
|
|
): Promise<TrainTrackResponse> => {
|
|
const response = await client.patch<TrainTrackResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINT(scheduleId, sequenceNo),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
arriveSchedule: async (scheduleId: string): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.ARRIVE(scheduleId),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
cancelSchedule: async (
|
|
id: string,
|
|
freightType: FreightType = "CONTAINER",
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
pathsFor(
|
|
freightType === "MIXED" ? undefined : freightType,
|
|
).CANCEL_SCHEDULE(id),
|
|
{},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getAvailableLocomotives: async (
|
|
routeId?: string,
|
|
): Promise<LocomotiveRecord[]> => {
|
|
if (routeId) {
|
|
const response = await client.get<LocomotiveRecord[]>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_LOCOMOTIVES,
|
|
{ params: { routeId } },
|
|
);
|
|
return unwrap(response.data);
|
|
}
|
|
const response = await client.get<LocomotiveRecord[]>(
|
|
URL_CONSTANTS.LOCOMOTIVES.BASE,
|
|
{
|
|
params: { status: "AVAILABLE" },
|
|
},
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
previewReschedule: async (
|
|
scheduleId: string,
|
|
payload: {
|
|
incomingBookingIds: string[];
|
|
trigger: string;
|
|
reason?: string;
|
|
newDepartureDate?: string;
|
|
},
|
|
) => {
|
|
const response = await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_PREVIEW(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
executeReschedule: async (
|
|
scheduleId: string,
|
|
payload: {
|
|
incomingBookingIds: string[];
|
|
trigger: string;
|
|
reason?: string;
|
|
newDepartureDate?: string;
|
|
finalBookingIds: string[];
|
|
displacedBookingIds: string[];
|
|
},
|
|
) => {
|
|
const response = await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.RESCHEDULE_EXECUTE(scheduleId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
maintenanceReschedule: async (
|
|
scheduleId: string,
|
|
payload: {
|
|
incomingBookingIds: string[];
|
|
newDepartureDate: string;
|
|
reason?: string;
|
|
},
|
|
) => {
|
|
const response = await client.post(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MAINTENANCE(scheduleId),
|
|
{ ...payload, trigger: "TRAIN_MAINTENANCE" },
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getGlobalRules: async (): Promise<TrainSchedulingGlobalRules> => {
|
|
const response = await client.get<TrainSchedulingGlobalRules>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getAllBookingWindows: async (): Promise<StaffBookingWindow[]> => {
|
|
const response = await client.get<StaffBookingWindow[]>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOWS,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getDocReviewAlert: async (): Promise<DocReviewAlert | null> => {
|
|
const response = await client.get<DocReviewAlert | null>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.DOC_REVIEW_ALERT,
|
|
);
|
|
// "No alert" comes back as null — which Nest sends as an empty body, so
|
|
// coerce anything falsy to null (react-query rejects undefined).
|
|
return unwrap(response.data) || null;
|
|
},
|
|
|
|
updateGlobalRules: async (
|
|
payload: Partial<Omit<TrainSchedulingGlobalRules, "id">>,
|
|
): Promise<TrainSchedulingGlobalRules> => {
|
|
const response = await client.patch<TrainSchedulingGlobalRules>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES,
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getStations: async (): Promise<YardOption[]> => {
|
|
const response = await client.get<BookingReferenceDataResponse>(
|
|
URL_CONSTANTS.BOOKINGS.REFERENCE_DATA,
|
|
);
|
|
const data = unwrap(response.data);
|
|
return (data.yard ?? []).map((yard) => ({
|
|
id: yard.id,
|
|
name: yard.name ?? yard.label ?? yard.code,
|
|
code: yard.code,
|
|
country: yard.country,
|
|
}));
|
|
},
|
|
|
|
removeWagonSlot: async (
|
|
scheduleId: string,
|
|
wagonId: string,
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.delete<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.REMOVE_WAGON_SLOT(scheduleId, wagonId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
updateContainerItem: async (
|
|
scheduleId: string,
|
|
itemId: string,
|
|
payload: { containerNumber: string | null },
|
|
): Promise<{ id: string; containerNumber: string | null }> => {
|
|
const response = await client.patch<{
|
|
id: string;
|
|
containerNumber: string | null;
|
|
}>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.UPDATE_CONTAINER_ITEM(scheduleId, itemId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
moveWagonLoad: async (
|
|
scheduleId: string,
|
|
wagonId: string,
|
|
payload: { targetWagonId: string },
|
|
): Promise<TrainScheduleDetail> => {
|
|
const response = await client.post<TrainScheduleDetail>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_WAGON_LOAD(scheduleId, wagonId),
|
|
payload,
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getUnassignedBookings: async (
|
|
scheduleId: string,
|
|
): Promise<UnassignedBookingsResponse> => {
|
|
const response = await client.get<UnassignedBookingsResponse>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGNED_BOOKINGS(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
|
|
getCompositionRemovals: async (
|
|
scheduleId: string,
|
|
): Promise<CompositionRemovalEntry[]> => {
|
|
const response = await client.get<CompositionRemovalEntry[]>(
|
|
URL_CONSTANTS.TRAIN_SCHEDULING.COMPOSITION_REMOVALS(scheduleId),
|
|
);
|
|
return unwrap(response.data);
|
|
},
|
|
};
|