import { api as apiClient } from "../auth/http"; // --------------------------------------------------------------------------- // Types — mirror the freight API's train-builder responses // --------------------------------------------------------------------------- export type BuiltTrainStatus = | "AVAILABLE" | "SCHEDULED" | "IN_SERVICE" | "UNDER_MAINTENANCE" | "OUT_OF_SERVICE"; export interface YardRefLite { id: string; code: string; label: string; } export type TradeDirection = "IMPORT" | "EXPORT" | "DOMESTIC"; /** The one active (DRAFT/SCHEDULED/DISPATCHED) schedule surfaced per built train. */ export interface ActiveScheduleRef { id: string; status: string; reference: string | null; direction: TradeDirection | null; trainNumber: string | null; } export interface BuiltTrainSummary { id: string; code: string; trainName: string | null; status: BuiltTrainStatus; /** Fixed IMPORT (even) run number typed at build time. */ importTrainNumber: string | null; /** Fixed EXPORT (odd) run number typed at build time. */ exportTrainNumber: string | null; activeSchedule: ActiveScheduleRef | null; createdAt: string; currentYard: YardRefLite | null; locomotives: Array<{ id: string; code: string; name: string | null }>; wagonCount: number; totalTareTons: number; totalLengthMeters: number; maxPullWeightTons: number; } export interface TrainCompositionLocomotive { id: string; code: string; name: string | null; locomotiveType: "DIESEL" | "ELECTRIC"; status: string; sequenceNo: number; role: "LEAD" | "ASSIST"; currentYardId: string | null; currentYard: YardRefLite | null; maxPullWeightTons: number; maxTrainLengthMeters: number; } export interface TrainCompositionWagon { id: string; wagonNumber: string; sequenceNumber: number | null; status: string; wagonType: { id: string; code: string; name: string; capacityTons: number; tareWeightTons: number; lengthMeters: number; } | null; } export interface TrainCompositionTotals { wagonCount: number; totalTareTons: number; /** Informational only — building never checks against full capacity. */ totalCapacityTons: number; totalLengthMeters: number; maxPullWeightTons: number; maxTrainLengthMeters: number; /** Cargo the locomotives can still haul once pulling the empty consist. */ payloadCapacityTons: number; /** Share of the haul limit consumed by the empty wagons alone. */ tareUtilizationPct: number | null; lengthUtilizationPct: number | null; } export interface TrainComposition { id: string; code: string; trainName: string | null; status: BuiltTrainStatus; importTrainNumber: string | null; exportTrainNumber: string | null; notes: string | null; createdAt: string; currentYard: YardRefLite | null; locomotives: TrainCompositionLocomotive[]; wagons: TrainCompositionWagon[]; totals: TrainCompositionTotals; activeSchedules: ActiveScheduleRef[]; editable: boolean; } export interface BuiltTrainListFilters { page?: number; pageSize?: number; search?: string; status?: BuiltTrainStatus; currentYardId?: string; sortBy?: "code" | "trainName" | "status" | "createdAt"; sortOrder?: "ASC" | "DESC"; } export interface BuiltTrainListResponse { items: BuiltTrainSummary[]; meta: { total: number; page: number; pageSize: number; totalPages: number; }; } export interface BuildTrainPayload { /** EXPORT run number — odd, unique across trains (e.g. 8001). */ exportTrainNumber: string; /** IMPORT run number — even, unique across trains (e.g. 8002). */ importTrainNumber: string; currentYardId: string; locomotiveIds: string[]; wagonIds?: string[]; trainName?: string; notes?: string; } /** Edit a built train's display identity; omitted fields keep their value. */ export interface UpdateTrainDetailsPayload { /** Empty string clears the name. */ trainName?: string; importTrainNumber?: string; exportTrainNumber?: string; } /** Built train annotated for the schedule-creation picker. */ export interface AvailableTrain { id: string; code: string; trainName: string | null; status: BuiltTrainStatus; importTrainNumber: string | null; exportTrainNumber: string | null; currentYardId: string | null; currentYard: YardRefLite | null; locomotives: Array<{ id: string; code: string; name: string | null }>; wagonCount: number; totalTareTons: number; totalLengthMeters: number; maxPullWeightTons: number; atOriginYard: boolean; futureScheduleCount: number; } // --------------------------------------------------------------------------- // Service // --------------------------------------------------------------------------- const BASE = "/train-builder"; const toQuery = (filters: BuiltTrainListFilters = {}) => { const params = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { params.set(key, String(value)); } }); const qs = params.toString(); return qs ? `?${qs}` : ""; }; // --------------------------------------------------------------------------- // Schedule consist adjustment (train-bound schedules) // --------------------------------------------------------------------------- export interface ConsistWagonRef { id: string; wagonNumber: string; sequenceNumber: number | null; wagonType: { id: string; code: string; tareWeightTons: number; capacityTons: number; lengthMeters: number; } | null; } export interface ScheduleConsist { schedule: { id: string; reference: string | null; status: string }; train: { id: string; code: string; trainName: string | null; currentYardId: string | null; }; limits: { maxPullWeightTons: number; overageToleranceTons: number; pullCapTons: number; maxTrainLengthMeters: number; overageToleranceMeters: number; lengthCapMeters: number; }; totals: { wagonCount: number; cargoTons: number; consistTareTons: number; grossTons: number; consistLengthMeters: number; }; wagons: Array; addableWagons: ConsistWagonRef[]; adjustments: Array<{ id: string; action: "ADD" | "REMOVE"; wagonId: string; wagonNumber: string; adjustedByUserId: string | null; occurredAt: string; }>; editable: boolean; } export interface AdjustConsistPayload { addWagonIds?: string[]; removeWagonIds?: string[]; } export const trainBuilderService = { list: (filters: BuiltTrainListFilters = {}) => apiClient.get(`${BASE}${toQuery(filters)}`), getComposition: (id: string) => apiClient.get(`${BASE}/${id}`), build: (payload: BuildTrainPayload) => apiClient.post(BASE, payload), setLocomotives: (id: string, locomotiveIds: string[]) => apiClient.put(`${BASE}/${id}/locomotives`, { locomotiveIds }), /** Edit the train's name and fixed import/export run numbers. */ updateDetails: (id: string, payload: UpdateTrainDetailsPayload) => apiClient.patch(`${BASE}/${id}/details`, payload), /** Relocate the train — coupled locomotives and wagons move with it. */ setYard: (id: string, currentYardId: string) => apiClient.patch(`${BASE}/${id}/yard`, { currentYardId }), assignWagons: (id: string, wagonIds: string[]) => apiClient.post(`${BASE}/${id}/wagons`, { wagonIds }), removeWagon: (id: string, wagonId: string) => apiClient.delete(`${BASE}/${id}/wagons/${wagonId}`), /** Detach a wagon and move it to MAINTENANCE status. */ sendWagonToMaintenance: (id: string, wagonId: string) => apiClient.post(`${BASE}/${id}/wagons/${wagonId}/maintenance`), reorderWagons: (id: string, wagonIds: string[]) => apiClient.post(`${BASE}/${id}/reorder-wagons`, { wagonIds }), disband: (id: string) => apiClient.delete(`${BASE}/${id}`), /** Built trains schedulable on a route (train-scheduling picker). */ availableTrains: (routeId: string) => apiClient.get(`/train-scheduling/available-trains`, { params: { routeId }, }), /** Consist snapshot for a train-bound schedule (adjust-consist UI). */ scheduleConsist: (scheduleId: string) => apiClient.get(`/train-scheduling/schedules/${scheduleId}/consist`), /** Permanently trim/add wagons on the schedule's built train. */ adjustConsist: (scheduleId: string, payload: AdjustConsistPayload) => apiClient.post( `/train-scheduling/schedules/${scheduleId}/adjust-consist`, payload, ), };