mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
910 lines
32 KiB
TypeScript
910 lines
32 KiB
TypeScript
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
|
|
import type { Response } from "express";
|
|
import type { AuthUserPayload } from "../../common/resolve-auth-user-id";
|
|
import { resolveAuthUserId } from "../../common/resolve-auth-user-id";
|
|
|
|
import {
|
|
Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, Query, Res,
|
|
} from "@nestjs/common";
|
|
import { CurrentUser } from "@edr/api-common";
|
|
import {
|
|
BookingDocReviewAlert,
|
|
TrainSchedulingCancel,
|
|
TrainSchedulingCreate,
|
|
TrainSchedulingReschedule,
|
|
TrainSchedulingRulesManage,
|
|
TrainSchedulingUpdate,
|
|
TrainSchedulingView,
|
|
} from "../../common/booking-guards";
|
|
import { AcceptIntercityBookingsDto } from "./dto/accept-intercity-bookings.dto";
|
|
import { AssignBookingsDto } from "./dto/assign-bookings.dto";
|
|
import { AssignUnassignedBookingDto } from "./dto/assign-unassigned-booking.dto";
|
|
import { SwitchGovernmentBookingDto } from "./dto/switch-government-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 { MoveWagonLoadDto } from "./dto/move-wagon-load.dto";
|
|
import { UpdateContainerItemDto } from "./dto/update-container-item.dto";
|
|
import { UpdateImportLoadingStatusDto } from "./dto/update-import-loading-status.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 {
|
|
ImportDjiboutiActionDto,
|
|
UploadImportDjiboutiDocumentDto,
|
|
} from "./dto/import-djibouti-operation.dto";
|
|
import { AvailableLocomotivesQueryDto } from "./dto/available-locomotives-query.dto";
|
|
import { AdjustScheduleConsistDto } from "./dto/adjust-schedule-consist.dto";
|
|
import { AvailableTrainsQueryDto } from "./dto/available-trains-query.dto";
|
|
import { BatchBoardQueryDto } from "./dto/batch-board-query.dto";
|
|
import { BookableSchedulesQueryDto } from "./dto/bookable-schedules-query.dto";
|
|
import { ListTrainSchedulesQueryDto } from "./dto/list-train-schedules-query.dto";
|
|
import { AvailableDaysQueryDto } from "./dto/available-days-query.dto";
|
|
import { AvailableDaysForCargoQueryDto } from "./dto/available-days-for-cargo-query.dto";
|
|
import { UpdateTrainSchedulingGlobalRulesDto } from "./dto/update-train-scheduling-global-rules.dto";
|
|
import { UpdateScheduleWindowRuleDto } from "./dto/update-schedule-window-rule.dto";
|
|
import { UpdateScheduleDateDto } from "./dto/update-schedule-date.dto";
|
|
import { MaintenanceRescheduleDto } from "./dto/maintenance-reschedule.dto";
|
|
import { TrainSchedulingService } from "./train-scheduling.service";
|
|
import { BookingBatchService } from "./booking-batch.service";
|
|
import { BookingJourneyService } from "./booking-journey.service";
|
|
import { BookingWindowService } from "./booking-window.service";
|
|
import { IntercityService } from "./intercity.service";
|
|
import { BillingService } from "../billing/billing.service";
|
|
|
|
@ApiTags("train-scheduling")
|
|
@ApiBearerAuth()
|
|
@Controller("train-scheduling")
|
|
export class TrainSchedulingController {
|
|
constructor(
|
|
private readonly trainSchedulingService: TrainSchedulingService,
|
|
private readonly bookingBatchService: BookingBatchService,
|
|
private readonly bookingWindowService: BookingWindowService,
|
|
private readonly intercityService: IntercityService,
|
|
private readonly bookingJourneyService: BookingJourneyService,
|
|
private readonly billingService: BillingService,
|
|
) { }
|
|
|
|
@Get("my-booking-windows")
|
|
@ApiOperation({
|
|
summary:
|
|
"Upcoming/open booking windows announced to the signed-in customer (all window-engine schedules; their own contract lanes carry a Book-now target)",
|
|
})
|
|
async getMyBookingWindows(@CurrentUser() user: AuthUserPayload) {
|
|
// Every customer sees announced windows; companyId (when resolvable) just
|
|
// enriches lanes they hold a contract on so "Book now" can target it.
|
|
const companyId = await this.billingService.resolveCompanyId(
|
|
resolveAuthUserId(user),
|
|
);
|
|
return this.trainSchedulingService.getBookingWindowsForCompany(companyId);
|
|
}
|
|
|
|
@Get("contracts/:contractId/booking-windows")
|
|
@ApiOperation({
|
|
summary:
|
|
"Upcoming/open booking windows on a contract's routes — gates the booking form for customer + Ethiopian GL",
|
|
})
|
|
getContractBookingWindows(
|
|
@Param("contractId", ParseUUIDPipe) contractId: string,
|
|
) {
|
|
return this.trainSchedulingService.getBookingWindowsForContract(contractId);
|
|
}
|
|
|
|
@Get("booking-windows")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"All announced booking windows across lanes (import cycle + export FCFS), for staff dashboards",
|
|
})
|
|
listBookingWindows() {
|
|
return this.trainSchedulingService.listAllBookingWindows();
|
|
}
|
|
|
|
@Get("global-rules")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({ summary: "Get global train scheduling rules (singleton)" })
|
|
getGlobalRules() {
|
|
return this.trainSchedulingService.getTrainSchedulingGlobalRules();
|
|
}
|
|
|
|
@Patch("global-rules")
|
|
@TrainSchedulingRulesManage()
|
|
@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: paginated import schedules (all statuses) with bookings grouped by state",
|
|
})
|
|
getBatchBoard(@Query() query: BatchBoardQueryDto) {
|
|
return this.bookingBatchService.getBatchBoard(query);
|
|
}
|
|
|
|
@Get("batch-board/:scheduleId")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Batch board detail for one schedule: its booking window, in-window bookings and pending-contract bucket",
|
|
})
|
|
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("available-trains")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"List built trains (Train Builder) schedulable on a route, annotated with yard position and future runs",
|
|
})
|
|
getAvailableTrains(@Query() query: AvailableTrainsQueryDto) {
|
|
return this.trainSchedulingService.getAvailableTrainsForRoute(
|
|
query.routeId,
|
|
);
|
|
}
|
|
|
|
@Get("schedules/:id/consist")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Built-train consist snapshot for a schedule: gross weight/length vs locomotive limits (incl. tolerance), trimmable + addable wagons, adjustment history",
|
|
})
|
|
getScheduleConsist(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.getScheduleConsist(id);
|
|
}
|
|
|
|
@Post("schedules/:id/adjust-consist")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Permanently trim free wagons off / couple yard wagons onto the schedule's built train (weight & length limits incl. tolerance enforced, every change logged)",
|
|
})
|
|
adjustScheduleConsist(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: AdjustScheduleConsistDto,
|
|
@CurrentUser() user: AuthUserPayload,
|
|
) {
|
|
return this.trainSchedulingService.adjustScheduleConsist(
|
|
id,
|
|
dto,
|
|
resolveAuthUserId(user),
|
|
);
|
|
}
|
|
|
|
@Get("schedules/:id/history")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Unified change history for a schedule: wagon consist adjustments (add/remove/switch, with the stop they happened at) merged with booking composition removals, newest first",
|
|
})
|
|
getScheduleHistory(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.getScheduleHistory(id);
|
|
}
|
|
|
|
@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("available-days")
|
|
// No staff guard: customers hit this while creating a booking to find which
|
|
// DAYS have a departure on their route. Day-level pooling — no capacity is
|
|
// returned, only the list of bookable days.
|
|
@ApiOperation({
|
|
summary: "Distinct days with an OPEN same-route departure (day-level pool)",
|
|
})
|
|
getAvailableDays(@Query() query: AvailableDaysQueryDto) {
|
|
return this.trainSchedulingService.getAvailableDays(
|
|
query.originYardId,
|
|
query.destinationYardId,
|
|
);
|
|
}
|
|
|
|
@Get("available-days-for-cargo")
|
|
// No staff guard: customers (portal) and GL (backoffice) both hit this while
|
|
// creating a booking to find which DAYS are feasible for THIS cargo — i.e. the
|
|
// route has a train with remaining capacity AND enough matching-type wagons.
|
|
@ApiOperation({
|
|
summary: "Days bookable for a specific cargo (wagon + train capacity aware)",
|
|
})
|
|
getAvailableDaysForCargo(@Query() query: AvailableDaysForCargoQueryDto) {
|
|
return this.trainSchedulingService.getAvailableDaysForCargo({
|
|
originYardId: query.originYardId,
|
|
destinationYardId: query.destinationYardId,
|
|
freightType: query.freightType,
|
|
cargoTypeId: query.cargoTypeId,
|
|
cargoTypeCode: query.cargoTypeCode,
|
|
totalWeightTons: query.totalWeightTons,
|
|
containers: query.containers,
|
|
containerTypeIds: query.containerTypeIds,
|
|
});
|
|
}
|
|
|
|
@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")
|
|
@TrainSchedulingCreate()
|
|
@ApiOperation({ summary: "Create a container train schedule" })
|
|
createContainerTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
|
|
return this.trainSchedulingService.createContainerTrainSchedule(dto);
|
|
}
|
|
|
|
@Post("bulk/schedules")
|
|
@TrainSchedulingCreate()
|
|
@ApiOperation({ summary: "Create a bulk train schedule" })
|
|
createBulkTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
|
|
return this.trainSchedulingService.createContainerTrainSchedule(dto);
|
|
}
|
|
|
|
@Post("schedules/:id/assign-bookings")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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);
|
|
}
|
|
|
|
@Post("schedules/:id/wagons/:wagonId/move-load")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Move a wagon's whole load to another wagon (empty → move/repin, loaded → swap loads)",
|
|
})
|
|
moveWagonLoad(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("wagonId", ParseUUIDPipe) wagonId: string,
|
|
@Body() dto: MoveWagonLoadDto,
|
|
) {
|
|
return this.trainSchedulingService.moveWagonLoad(id, wagonId, 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")
|
|
@TrainSchedulingUpdate()
|
|
@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,
|
|
);
|
|
}
|
|
|
|
@Post("schedules/:id/switch-government-booking")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Switch out commercial bookings to allocate a government booking in their place",
|
|
})
|
|
switchGovernmentBooking(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: SwitchGovernmentBookingDto,
|
|
@CurrentUser() user: AuthUserPayload,
|
|
) {
|
|
return this.trainSchedulingService.switchGovernmentBooking(
|
|
id,
|
|
dto.governmentBookingId,
|
|
dto.removeBookingIds,
|
|
resolveAuthUserId(user),
|
|
);
|
|
}
|
|
|
|
@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);
|
|
}
|
|
|
|
@Get("schedules/:id/import-loading-bookings")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary: "List import bookings eligible for loading confirmation on this schedule",
|
|
})
|
|
getImportLoadingBookings(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.getImportLoadingBookings(id);
|
|
}
|
|
|
|
@Patch("schedules/:id/import-loading-status")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Mark import bookings loaded/unloaded on this schedule (tracking only, does not affect dispatch)",
|
|
})
|
|
updateImportLoadingStatus(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateImportLoadingStatusDto,
|
|
) {
|
|
return this.trainSchedulingService.updateImportLoadingStatus(id, dto);
|
|
}
|
|
|
|
@Patch("schedules/:id/loading-status")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Mark bookings loaded/unloaded on this schedule (any direction, pre-dispatch only)",
|
|
})
|
|
setBookingLoadingStatus(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateImportLoadingStatusDto,
|
|
) {
|
|
return this.trainSchedulingService.setBookingLoadingStatus(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/pin-wagons")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Finalize a draft train schedule" })
|
|
finalizeSchedule(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.finalizeSchedule(id);
|
|
}
|
|
|
|
@Post("schedules/:id/dispatch")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Dispatch a scheduled train" })
|
|
dispatchSchedule(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.dispatchSchedule(id);
|
|
}
|
|
|
|
@Get("intercity/bookings")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Every intercity booking with its ride-along state, both yards' facility status, and where its train is",
|
|
})
|
|
listIntercityBookings() {
|
|
return this.intercityService.listBookings();
|
|
}
|
|
|
|
@Get("schedules/:id/intercity-candidates")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Waiting intercity bookings this train could carry (corridor on route) + remaining wagon/weight/length capacity",
|
|
})
|
|
getIntercityCandidates(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.intercityService.listCandidates(id);
|
|
}
|
|
|
|
@Post("schedules/:id/intercity/accept")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Accept intercity bookings onto this train (opens their pay window; capacity re-checked per booking)",
|
|
})
|
|
acceptIntercityBookings(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: AcceptIntercityBookingsDto,
|
|
) {
|
|
return this.intercityService.acceptBookings(id, dto.bookingIds);
|
|
}
|
|
|
|
@Get("schedules/:id/yard-work")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({
|
|
summary:
|
|
"Per-yard operator worklist: which bookings board/alight at each stop, with journey state",
|
|
})
|
|
getYardWork(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.bookingJourneyService.listYardWork(id);
|
|
}
|
|
|
|
@Post("schedules/:id/bookings/:bookingId/load")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Confirm a booking's cargo loaded at its origin yard (any direction; train must be at that yard)",
|
|
})
|
|
loadScheduleBooking(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("bookingId", ParseUUIDPipe) bookingId: string,
|
|
) {
|
|
return this.bookingJourneyService.loadBooking(id, bookingId);
|
|
}
|
|
|
|
@Post("schedules/:id/bookings/:bookingId/unload")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Confirm a booking's cargo unloaded at its destination yard — per-booking arrival, may precede the train's final arrival",
|
|
})
|
|
unloadScheduleBooking(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("bookingId", ParseUUIDPipe) bookingId: string,
|
|
) {
|
|
return this.bookingJourneyService.unloadBooking(id, bookingId);
|
|
}
|
|
|
|
@Post("schedules/:id/intercity/:bookingId/load")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary: "Confirm intercity cargo loaded (train must be at the booking's origin yard)",
|
|
})
|
|
loadIntercityBooking(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("bookingId", ParseUUIDPipe) bookingId: string,
|
|
) {
|
|
return this.intercityService.loadBooking(id, bookingId);
|
|
}
|
|
|
|
@Post("schedules/:id/intercity/:bookingId/unload")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Confirm intercity cargo unloaded at the booking's destination yard (completes the booking)",
|
|
})
|
|
unloadIntercityBooking(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Param("bookingId", ParseUUIDPipe) bookingId: string,
|
|
) {
|
|
return this.intercityService.unloadBooking(id, bookingId);
|
|
}
|
|
|
|
@Get("schedules/:id/import-djibouti")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({ summary: "Batch 7 import Djibouti gatepass/loading status" })
|
|
getImportDjiboutiOperation(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.getImportDjiboutiOperation(id);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/documents")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Upload/check an import Djibouti-side document" })
|
|
uploadImportDjiboutiDocument(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UploadImportDjiboutiDocumentDto,
|
|
) {
|
|
return this.trainSchedulingService.uploadImportDjiboutiDocument(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/gatepass-granted")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Mark import Djibouti gatepass permission granted" })
|
|
grantImportDjiboutiGatepass(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.grantImportDjiboutiGatepass(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/ready-for-loading")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Mark import train ready for loading at Djibouti" })
|
|
markImportReadyForLoading(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.markImportReadyForLoading(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/loaded-on-train")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Confirm import cargo loaded on train at Djibouti" })
|
|
confirmImportLoadedOnTrain(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.confirmImportLoadedOnTrain(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/confirm-loading")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Confirm cargo loaded on the train (any direction; unblocks import-Djibouti dispatch)",
|
|
})
|
|
confirmScheduleLoading(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.confirmScheduleLoading(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/depart")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Depart loaded import train from Djibouti" })
|
|
departImportFromDjibouti(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.departImportFromDjibouti(id, dto);
|
|
}
|
|
|
|
@Post("schedules/:id/import-djibouti/load-list")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({ summary: "Generate import load list / marshalling document summary" })
|
|
generateImportLoadList(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: ImportDjiboutiActionDto,
|
|
) {
|
|
return this.trainSchedulingService.generateImportLoadList(id, dto);
|
|
}
|
|
|
|
@Get("schedules/:id/import-djibouti/load-list/document")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({ summary: "Download printable import load list / marshalling PDF" })
|
|
async importLoadListDocument(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Res() res: Response,
|
|
) {
|
|
const { filename, buffer } = await this.trainSchedulingService.importLoadListDocument(id);
|
|
res.setHeader("Content-Type", "application/pdf");
|
|
res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
|
|
res.setHeader("Content-Length", buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
@Get("schedules/:id/export/load-list/document")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({ summary: "Download printable export marshalling / load list PDF" })
|
|
async exportLoadListDocument(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Res() res: Response,
|
|
) {
|
|
const { filename, buffer } = await this.trainSchedulingService.exportLoadListDocument(id);
|
|
res.setHeader("Content-Type", "application/pdf");
|
|
res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
|
|
res.setHeader("Content-Length", buffer.length);
|
|
return res.send(buffer);
|
|
}
|
|
|
|
// ---- batch / booking-window staff actions ----
|
|
|
|
@Post("schedules/:id/run-batch")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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);
|
|
}
|
|
|
|
@Patch("schedules/:id/window-rule")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Override the booking-window rule for one schedule (open/close hour, duration, doc-review, payment, lead days) — only before the window opens",
|
|
})
|
|
async updateScheduleWindowRule(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateScheduleWindowRuleDto,
|
|
) {
|
|
await this.trainSchedulingService.updateScheduleWindowRule(id, dto);
|
|
return this.trainSchedulingService.getContainerTrainScheduleById(id);
|
|
}
|
|
|
|
@Patch("schedules/:id/schedule-date")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Reschedule a train's departure date — only before the booking window opens, and only if the new date still leaves room for the booking lead window",
|
|
})
|
|
async updateScheduleDate(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateScheduleDateDto,
|
|
) {
|
|
await this.trainSchedulingService.updateScheduleDate(id, dto);
|
|
return this.trainSchedulingService.getContainerTrainScheduleById(id);
|
|
}
|
|
|
|
@Post("schedules/:id/maintenance")
|
|
@TrainSchedulingReschedule()
|
|
@ApiOperation({
|
|
summary:
|
|
"Maintenance reschedule: move the train to a new departure with every allocated booking aboard — links, wagons and window settings unchanged",
|
|
})
|
|
async maintenanceReschedule(
|
|
@Param("id", ParseUUIDPipe) id: string,
|
|
@Body() dto: MaintenanceRescheduleDto,
|
|
) {
|
|
await this.trainSchedulingService.maintenanceReschedule(id, dto);
|
|
return this.trainSchedulingService.getContainerTrainScheduleById(id);
|
|
}
|
|
|
|
@Get("doc-review-alert")
|
|
// Dedicated permission, not scheduling or bookings:view — the alarm is meant
|
|
// for the position types that actually decide operation requests.
|
|
@BookingDocReviewAlert()
|
|
@ApiOperation({
|
|
summary:
|
|
"Nearest document-review deadline that still has un-accepted booking requests behind it (null when there is none) — drives the backoffice header countdown",
|
|
})
|
|
async getDocReviewAlert() {
|
|
return this.bookingWindowService.getDocReviewAlert();
|
|
}
|
|
|
|
@Post("schedules/:id/doc-review-complete")
|
|
@TrainSchedulingUpdate()
|
|
@ApiOperation({
|
|
summary:
|
|
"Staff finished document review early — run the batch/payment phase now (applies to the whole route-day group)",
|
|
})
|
|
async completeDocReview(@Param("id", ParseUUIDPipe) id: string) {
|
|
await this.bookingWindowService.completeDocReview(id);
|
|
return this.bookingBatchService.getBatchBoardDetail(id);
|
|
}
|
|
|
|
@Post("bookings/:bookingId/mark-paid")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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")
|
|
@TrainSchedulingUpdate()
|
|
@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 (paginated)" })
|
|
getContainerTrainSchedules(@Query() query: ListTrainSchedulesQueryDto) {
|
|
return this.trainSchedulingService.getContainerTrainSchedules(query);
|
|
}
|
|
|
|
@Get("bulk/schedules")
|
|
@TrainSchedulingView()
|
|
@ApiOperation({ summary: "List bulk train schedules (paginated)" })
|
|
getBulkTrainSchedules(@Query() query: ListTrainSchedulesQueryDto) {
|
|
return this.trainSchedulingService.getContainerTrainSchedules(query);
|
|
}
|
|
|
|
@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")
|
|
@TrainSchedulingCancel()
|
|
@ApiOperation({ summary: "Cancel container train schedule" })
|
|
cancelTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.cancelTrainSchedule(id);
|
|
}
|
|
|
|
@Post('bulk/schedules/:id/cancel')
|
|
@TrainSchedulingCancel()
|
|
@ApiOperation({ summary: "Cancel bulk train schedule" })
|
|
cancelBulkTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
|
|
return this.trainSchedulingService.cancelTrainSchedule(id);
|
|
}
|
|
}
|