import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Query, } from "@nestjs/common"; import { CurrentUser } from "@edr/api-common"; import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; import type { AuthUserPayload } from "../../common/resolve-auth-user-id"; import { resolveAuthUserId } from "../../common/resolve-auth-user-id"; import { TrainSchedulingManage, TrainSchedulingView, } from "../../common/booking-guards"; import { AssignBookingsDto } from "./dto/assign-bookings.dto"; import { AssignUnassignedBookingDto } from "./dto/assign-unassigned-booking.dto"; import { CreateContainerTrainScheduleDto } from "./dto/create-container-train-schedule.dto"; import { GetEligibleBookingsDto } from "./dto/get-eligible-bookings.dto"; import { GetEligibleBulkBookingsDto } from "./dto/get-eligible-bulk-bookings.dto"; import { GetEligibleContainerBookingsDto } from "./dto/get-eligible-container-bookings.dto"; import { PinWagonsDto } from "./dto/pin-wagons.dto"; import { UpdateContainerItemDto } from "./dto/update-container-item.dto"; import { PreviewBulkTrainScheduleDto } from "./dto/preview-bulk-train-schedule.dto"; import { PreviewContainerTrainScheduleDto } from "./dto/preview-container-train-schedule.dto"; import { PreviewTrainScheduleDto } from "./dto/preview-train-schedule.dto"; import { RecordCheckpointDto } from "./dto/record-checkpoint.dto"; import { AvailableLocomotivesQueryDto } from "./dto/available-locomotives-query.dto"; import { BookableSchedulesQueryDto } from "./dto/bookable-schedules-query.dto"; import { UpdateTrainSchedulingGlobalRulesDto } from "./dto/update-train-scheduling-global-rules.dto"; import { TrainSchedulingService } from "./train-scheduling.service"; import { BookingBatchService } from "./booking-batch.service"; @ApiTags("train-scheduling") @ApiBearerAuth() @Controller("train-scheduling") export class TrainSchedulingController { constructor( private readonly trainSchedulingService: TrainSchedulingService, private readonly bookingBatchService: BookingBatchService, ) { } @Get("global-rules") @TrainSchedulingView() @ApiOperation({ summary: "Get global train scheduling rules (singleton)" }) getGlobalRules() { return this.trainSchedulingService.getTrainSchedulingGlobalRules(); } @Patch("global-rules") @TrainSchedulingManage() @ApiOperation({ summary: "Update global train scheduling rules (singleton)" }) updateGlobalRules(@Body() dto: UpdateTrainSchedulingGlobalRulesDto) { return this.trainSchedulingService.updateTrainSchedulingGlobalRules(dto); } @Get("eligible-bookings") @TrainSchedulingView() @ApiOperation({ summary: "List eligible bookings (container and/or bulk)" }) getEligibleBookings(@Query() query: GetEligibleBookingsDto) { return this.trainSchedulingService.getEligibleBookings(query); } @Get("batch-board") @TrainSchedulingView() @ApiOperation({ summary: "Batch monitoring board: schedules with bookings grouped by state", }) getBatchBoard() { return this.bookingBatchService.getBatchBoard(); } @Get("batch-board/:scheduleId") @TrainSchedulingView() @ApiOperation({ summary: "Batch board detail for one schedule with EAT 3h windows", }) getBatchBoardDetail(@Param("scheduleId", ParseUUIDPipe) scheduleId: string) { return this.bookingBatchService.getBatchBoardDetail(scheduleId); } @Get("available-locomotives") @TrainSchedulingView() @ApiOperation({ summary: "List AVAILABLE locomotives at the route origin yard", }) getAvailableLocomotives(@Query() query: AvailableLocomotivesQueryDto) { return this.trainSchedulingService.getAvailableLocomotivesForRoute( query.routeId, ); } @Get("bookable-schedules") // No staff guard: customers hit this while creating a booking to find OPEN // same-route schedules. Do not attach train_scheduling permissions here. @ApiOperation({ summary: "OPEN same-route schedules a new booking can target", }) getBookableSchedules(@Query() query: BookableSchedulesQueryDto) { return this.trainSchedulingService.getBookableSchedules( query.originYardId, query.destinationYardId, ); } @Get("container/eligible-bookings") @TrainSchedulingView() @ApiOperation({ summary: "List eligible container bookings" }) getEligibleContainerBookings( @Query() query: GetEligibleContainerBookingsDto, ) { return this.trainSchedulingService.getEligibleContainerBookings(query); } @Get("bulk/eligible-bookings") @TrainSchedulingView() @ApiOperation({ summary: "List eligible bulk bookings" }) getEligibleBulkBookings(@Query() query: GetEligibleBulkBookingsDto) { return this.trainSchedulingService.getEligibleBulkBookings(query); } @Post("preview") @TrainSchedulingView() @ApiOperation({ summary: "Preview a mixed-capable train schedule" }) previewTrainSchedule(@Body() dto: PreviewTrainScheduleDto) { return this.trainSchedulingService.previewTrainSchedule(dto); } @Post("container/preview") @TrainSchedulingView() @ApiOperation({ summary: "Preview a container train schedule" }) previewContainerTrainSchedule(@Body() dto: PreviewContainerTrainScheduleDto) { return this.trainSchedulingService.previewContainerTrainSchedule(dto); } @Post("bulk/preview") @TrainSchedulingView() @ApiOperation({ summary: "Preview a bulk train schedule" }) previewBulkTrainSchedule(@Body() dto: PreviewBulkTrainScheduleDto) { return this.trainSchedulingService.previewBulkTrainSchedule(dto); } @Post("container/schedules") @TrainSchedulingManage() @ApiOperation({ summary: "Create a container train schedule" }) createContainerTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) { return this.trainSchedulingService.createContainerTrainSchedule(dto); } @Post("bulk/schedules") @TrainSchedulingManage() @ApiOperation({ summary: "Create a bulk train schedule" }) createBulkTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) { return this.trainSchedulingService.createContainerTrainSchedule(dto); } @Post("schedules/:id/assign-bookings") @TrainSchedulingManage() @ApiOperation({ summary: "Assign bookings to a train schedule (mixed-capable)", }) assignBookings( @Param("id", ParseUUIDPipe) id: string, @Body() dto: AssignBookingsDto, ) { return this.trainSchedulingService.assignBookingsToSchedule(id, dto); } @Post("container/schedules/:id/assign-bookings") @TrainSchedulingManage() @ApiOperation({ summary: "Assign container bookings to a train schedule" }) assignContainerBookings( @Param("id", ParseUUIDPipe) id: string, @Body() dto: AssignBookingsDto, ) { return this.trainSchedulingService.assignBookingsToSchedule( id, dto, "CONTAINER", ); } @Post("bulk/schedules/:id/assign-bookings") @TrainSchedulingManage() @ApiOperation({ summary: "Assign bulk bookings to a train schedule" }) assignBulkBookings( @Param("id", ParseUUIDPipe) id: string, @Body() dto: AssignBookingsDto, ) { return this.trainSchedulingService.assignBookingsToSchedule( id, dto, "BULK", ); } @Delete("schedules/:id/bookings/:bookingId") @TrainSchedulingManage() @ApiOperation({ summary: "Unassign a booking from a train schedule" }) unassignBooking( @Param("id", ParseUUIDPipe) id: string, @Param("bookingId", ParseUUIDPipe) bookingId: string, @CurrentUser() user: AuthUserPayload, ) { return this.trainSchedulingService.unassignBooking( id, bookingId, resolveAuthUserId(user), ); } @Delete("schedules/:id/wagons/:trainSetWagonId") @TrainSchedulingManage() @ApiOperation({ summary: "Remove an empty wagon slot from a train" }) removeWagonSlot( @Param("id", ParseUUIDPipe) id: string, @Param("trainSetWagonId", ParseUUIDPipe) trainSetWagonId: string, ) { return this.trainSchedulingService.removeTrainSetWagonSlot( id, trainSetWagonId, ); } @Patch("schedules/:id/container-items/:itemId") @TrainSchedulingManage() @ApiOperation({ summary: "Update a container number on a wagon slot" }) updateContainerItem( @Param("id", ParseUUIDPipe) id: string, @Param("itemId", ParseUUIDPipe) itemId: string, @Body() dto: UpdateContainerItemDto, ) { return this.trainSchedulingService.updateContainerItem(id, itemId, dto); } @Get("schedules/:id/unassigned-bookings") @TrainSchedulingView() @ApiOperation({ summary: "Get unassigned bookings for a schedule" }) getUnassignedBookings(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.getUnassignedBookings(id); } @Post("schedules/:id/assign-unassigned-booking") @TrainSchedulingManage() @ApiOperation({ summary: "Assign one linked unallocated booking to wagons (preserves existing assignments)", }) assignUnassignedBooking( @Param("id", ParseUUIDPipe) id: string, @Body() dto: AssignUnassignedBookingDto, ) { return this.trainSchedulingService.assignUnassignedBookingToWagons( id, dto.bookingId, ); } @Get("schedules/:id/composition-removals") @TrainSchedulingView() @ApiOperation({ summary: "Get removal log for a schedule" }) getCompositionRemovals(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.getCompositionRemovals(id); } @Post("schedules/:id/pin-wagons") @TrainSchedulingManage() @ApiOperation({ summary: "Pin physical wagons to train set slots" }) pinWagons(@Param("id", ParseUUIDPipe) id: string, @Body() dto: PinWagonsDto) { return this.trainSchedulingService.pinWagons(id, dto); } @Post("schedules/:id/finalize") @TrainSchedulingManage() @ApiOperation({ summary: "Finalize a draft train schedule" }) finalizeSchedule(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.finalizeSchedule(id); } @Post("schedules/:id/dispatch") @TrainSchedulingManage() @ApiOperation({ summary: "Dispatch a scheduled train" }) dispatchSchedule(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.dispatchSchedule(id); } // ---- batch / booking-window staff actions ---- @Post("schedules/:id/run-batch") @TrainSchedulingManage() @ApiOperation({ summary: "Manually run the batch fill for a schedule" }) async runBatch(@Param("id", ParseUUIDPipe) id: string) { await this.bookingBatchService.fillSchedule(id); return this.bookingBatchService.getBatchBoardDetail(id); } @Post("schedules/:id/run-allocation") @TrainSchedulingManage() @ApiOperation({ summary: "Run wagon-level allocation for all eligible linked bookings", }) async runAllocation(@Param("id", ParseUUIDPipe) id: string) { return this.bookingBatchService.runWagonAllocation(id); } @Patch("schedules/:id/booking-window") @TrainSchedulingManage() @ApiOperation({ summary: "Open or close a schedule booking window" }) async setBookingWindow( @Param("id", ParseUUIDPipe) id: string, @Body("status") status: "OPEN" | "CLOSED", ) { await this.trainSchedulingService.setBookingWindow( id, status === "CLOSED" ? "CLOSED" : "OPEN", ); return this.trainSchedulingService.getContainerTrainScheduleById(id); } @Post("bookings/:bookingId/mark-paid") @TrainSchedulingManage() @ApiOperation({ summary: "Staff: mark a reserved booking paid and allocate it now", }) async markBookingPaid(@Param("bookingId", ParseUUIDPipe) bookingId: string) { await this.bookingBatchService.markPaid(bookingId); return { ok: true }; } @Post("bookings/:bookingId/expire") @TrainSchedulingManage() @ApiOperation({ summary: "Staff: expire a reservation and free its capacity", }) async expireBooking(@Param("bookingId", ParseUUIDPipe) bookingId: string) { await this.bookingBatchService.expireReservation(bookingId); return { ok: true }; } @Post("bookings/:bookingId/move-schedule") @TrainSchedulingManage() @ApiOperation({ summary: "Re-point a booking to another OPEN same-route schedule", }) async moveBookingSchedule( @Param("bookingId", ParseUUIDPipe) bookingId: string, @Body("trainScheduleId", ParseUUIDPipe) trainScheduleId: string, ) { await this.bookingBatchService.moveToSchedule(bookingId, trainScheduleId); return { ok: true }; } @Get("schedules/:id/checkpoints") @TrainSchedulingView() @ApiOperation({ summary: "Get the tracking corridor + logged checkpoints for a train", }) getScheduleCheckpoints(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.getScheduleCheckpoints(id); } @Post("schedules/:id/checkpoints") @TrainSchedulingManage() @ApiOperation({ summary: "Log the train passing a station (final station triggers arrival)", }) recordCheckpoint( @Param("id", ParseUUIDPipe) id: string, @Body() dto: RecordCheckpointDto, ) { return this.trainSchedulingService.recordCheckpoint(id, dto); } @Post("schedules/:id/arrive") @TrainSchedulingManage() @ApiOperation({ summary: "Mark a dispatched train arrived (move assets to destination yard, free assets)", }) arriveSchedule(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.arriveSchedule(id); } @Get("container/schedules") @TrainSchedulingView() @ApiOperation({ summary: "List container train schedules" }) getContainerTrainSchedules() { return this.trainSchedulingService.getContainerTrainSchedules(); } @Get("bulk/schedules") @TrainSchedulingView() @ApiOperation({ summary: "List bulk train schedules" }) getBulkTrainSchedules() { return this.trainSchedulingService.getContainerTrainSchedules(); } @Get("container/schedules/:id") @TrainSchedulingView() @ApiOperation({ summary: "Get container train schedule detail" }) getContainerTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.getContainerTrainScheduleById(id); } @Get("bulk/schedules/:id") @TrainSchedulingView() @ApiOperation({ summary: "Get bulk train schedule detail" }) getBulkTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.getContainerTrainScheduleById(id); } @Post("container/schedules/:id/cancel") @TrainSchedulingManage() @ApiOperation({ summary: "Cancel container train schedule" }) cancelTrainSchedule(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.cancelTrainSchedule(id); } @Post("bulk/schedules/:id/cancel") @TrainSchedulingManage() @ApiOperation({ summary: "Cancel bulk train schedule" }) cancelBulkTrainSchedule(@Param("id", ParseUUIDPipe) id: string) { return this.trainSchedulingService.cancelTrainSchedule(id); } }