import { api as client } from '../auth/http'; import { unwrap } from '@/utils/endpoint'; import { URL_CONSTANTS } from '@/constants/URLS'; import type { BatchBoardSchedule, BatchBoardScheduleDetail, BookableSchedule, AssignBookingsPayload, CompositionRemovalEntry, CompositionUnassignedBooking, UnassignedBookingsResponse, CreateTrainSchedulePayload, EligibleContainerBookingsResponse, FreightType, LocomotiveRecord, PinWagonsPayload, RecordCheckpointPayload, TrainScheduleDetail, TrainScheduleFilters, TrainScheduleListItem, TrainSchedulePreviewPayload, TrainSchedulePreviewResponse, TrainSchedulingGlobalRules, TrainTrackResponse, WagonAllocationAttemptResult, YardOption, } from '@/types/trainScheduling'; interface BookingReferenceDataResponse { yard?: Array; } const pathsFor = (freightType?: FreightType) => freightType === "BULK" ? URL_CONSTANTS.TRAIN_SCHEDULING.BULK : URL_CONSTANTS.TRAIN_SCHEDULING.CONTAINER; export const trainSchedulingService = { getEligibleBookings: async ( filters?: TrainScheduleFilters, freightType?: FreightType, ): Promise => { 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( useUnified ? URL_CONSTANTS.TRAIN_SCHEDULING.ELIGIBLE_BOOKINGS : pathsFor(freightType).ELIGIBLE_BOOKINGS, { params: { ...filters } }, ); return unwrap(response.data); }, preview: async ( payload: TrainSchedulePreviewPayload, freightType?: FreightType, ): Promise => { const useUnified = !freightType || freightType === "MIXED"; const response = await client.post( useUnified ? URL_CONSTANTS.TRAIN_SCHEDULING.PREVIEW : pathsFor(freightType).PREVIEW, payload, ); return unwrap(response.data); }, createSchedule: async ( payload: CreateTrainSchedulePayload, freightType?: FreightType, ): Promise => { const response = await client.post( pathsFor(freightType).SCHEDULES, payload, ); return unwrap(response.data); }, listSchedules: async ( freightType: FreightType = "CONTAINER", ): Promise => { const response = await client.get( pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULES, ); return unwrap(response.data); }, getBatchBoard: async (): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD, ); return unwrap(response.data); }, getBatchBoardDetail: async (scheduleId: string): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.BATCH_BOARD_DETAIL(scheduleId), ); return unwrap(response.data); }, getBookableSchedules: async ( originYardId?: string, destinationYardId?: string, ): Promise => { const response = await client.get( 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 => { const response = await client.get<{ days: string[] }>( URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_DAYS, { params: { originYardId, destinationYardId } }, ); return unwrap(response.data).days; }, runBatch: async (scheduleId: string): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.RUN_BATCH(scheduleId), {}, ); return unwrap(response.data); }, runAllocation: async (scheduleId: string): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.RUN_ALLOCATION(scheduleId), {}, ); return unwrap(response.data); }, setBookingWindow: async ( scheduleId: string, status: "OPEN" | "CLOSED", ): Promise => { const response = await client.patch( URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WINDOW(scheduleId), { status }, ); return unwrap(response.data); }, markBookingPaid: async (bookingId: string): Promise => { await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.MARK_BOOKING_PAID(bookingId), {}); }, expireBooking: async (bookingId: string): Promise => { await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.EXPIRE_BOOKING(bookingId), {}); }, moveBookingSchedule: async ( bookingId: string, trainScheduleId: string, ): Promise => { await client.post(URL_CONSTANTS.TRAIN_SCHEDULING.MOVE_BOOKING_SCHEDULE(bookingId), { trainScheduleId, }); }, getScheduleById: async ( id: string, freightType?: FreightType, ): Promise => { const response = await client.get( pathsFor(freightType === "MIXED" ? undefined : freightType).SCHEDULE_BY_ID(id), ); return unwrap(response.data); }, assignUnassignedBooking: async ( scheduleId: string, bookingId: string, ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.ASSIGN_UNASSIGNED_BOOKING(scheduleId), { bookingId }, ); return unwrap(response.data); }, assignBookings: async ( scheduleId: string, payload: AssignBookingsPayload, freightType?: FreightType, ): Promise => { const useUnified = !freightType || freightType === "MIXED"; const response = await client.post( 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 => { const response = await client.delete( URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGN_BOOKING(scheduleId, bookingId), ); return unwrap(response.data); }, pinWagons: async ( scheduleId: string, payload: PinWagonsPayload, ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.PIN_WAGONS(scheduleId), payload, ); return unwrap(response.data); }, finalizeSchedule: async (scheduleId: string): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.FINALIZE(scheduleId), {}, ); return unwrap(response.data); }, dispatchSchedule: async (scheduleId: string): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.DISPATCH(scheduleId), {}, ); return unwrap(response.data); }, getTrack: async (scheduleId: string): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId), ); return unwrap(response.data); }, recordCheckpoint: async ( scheduleId: string, payload: RecordCheckpointPayload, ): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.CHECKPOINTS(scheduleId), payload, ); return unwrap(response.data); }, arriveSchedule: async (scheduleId: string): Promise => { const response = await client.post( URL_CONSTANTS.TRAIN_SCHEDULING.ARRIVE(scheduleId), {}, ); return unwrap(response.data); }, cancelSchedule: async ( id: string, freightType: FreightType = "CONTAINER", ): Promise => { const response = await client.post( pathsFor(freightType === "MIXED" ? undefined : freightType).CANCEL_SCHEDULE(id), {}, ); return unwrap(response.data); }, getAvailableLocomotives: async (routeId?: string): Promise => { if (routeId) { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.AVAILABLE_LOCOMOTIVES, { params: { routeId } }, ); return unwrap(response.data); } const response = await client.get(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 => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES, ); return unwrap(response.data); }, updateGlobalRules: async ( payload: Partial< Pick< TrainSchedulingGlobalRules, | "maxTrainLengthMeters" | "maxTrainWeightTons" | "maxWagonsPerTrain" | "max20ftContainerWeightTons" | "max20ftPairWeightDiffTons" > >, ): Promise => { const response = await client.patch( URL_CONSTANTS.TRAIN_SCHEDULING.GLOBAL_RULES, payload, ); return unwrap(response.data); }, getStations: async (): Promise => { const response = await client.get( 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 => { const response = await client.delete( 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); }, getUnassignedBookings: async ( scheduleId: string, ): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.UNASSIGNED_BOOKINGS(scheduleId), ); return unwrap(response.data); }, getCompositionRemovals: async ( scheduleId: string, ): Promise => { const response = await client.get( URL_CONSTANTS.TRAIN_SCHEDULING.COMPOSITION_REMOVALS(scheduleId), ); return unwrap(response.data); }, };