import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseUUIDPipe, Patch, Post, Put, Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { CurrentUser } from '@edr/api-common'; import { FleetManage, FleetView } from '../../common/booking-guards'; import type { AuthUserPayload } from '../../common/resolve-auth-user-id'; import { resolveAuthUserId } from '../../common/resolve-auth-user-id'; import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry'; import { AssignTrainWagonsDto } from './dto/assign-train-wagons.dto'; import { BuildTrainDto } from './dto/build-train.dto'; import { ListBuiltTrainsQueryDto } from './dto/list-built-trains-query.dto'; import { ReorderTrainWagonsDto } from './dto/reorder-train-wagons.dto'; import { SendWagonToMaintenanceDto } from './dto/send-wagon-to-maintenance.dto'; import { UpdateTrainDetailsDto } from './dto/update-train-details.dto'; import { UpdateTrainLocomotivesDto } from './dto/update-train-locomotives.dto'; import { UpdateTrainYardDto } from './dto/update-train-yard.dto'; import { SetTrainWagonsYardDto } from './dto/set-train-wagons-yard.dto'; import { TrainBuilderService } from './train-builder.service'; @ApiTags('train-builder') @ApiBearerAuth() @Controller('train-builder') // Class gate lists every key its routes use: Nest runs class AND method // guards, so a key missing here would deny before the route's own key runs. @FleetView([ FREIGHT_PERMS.trains.view, FREIGHT_PERMS.trains.create, FREIGHT_PERMS.trains.update, FREIGHT_PERMS.trains.assignWagons, FREIGHT_PERMS.trains.delete, FREIGHT_PERMS.trains.changeLocomotives, FREIGHT_PERMS.trains.changeYard, FREIGHT_PERMS.trains.changeWagonYard, FREIGHT_PERMS.trains.toggleActive, FREIGHT_PERMS.trains.disband, ]) export class TrainBuilderController { constructor(private readonly trainBuilderService: TrainBuilderService) {} @Post() @FleetManage(FREIGHT_PERMS.trains.create) @ApiOperation({ summary: 'Build a train: code + yard + 2+ locomotives (+ optional wagons)' }) build(@Body() dto: BuildTrainDto) { return this.trainBuilderService.buildTrain(dto); } @Get() @ApiOperation({ summary: 'Paginated built trains with composition summary' }) list(@Query() query: ListBuiltTrainsQueryDto) { return this.trainBuilderService.listBuilt(query); } // Must be declared before @Get(':id') so the path isn't captured as an id. @Get('used-train-numbers') @ApiOperation({ summary: 'Import/export run numbers already claimed by existing trains', }) usedTrainNumbers() { return this.trainBuilderService.usedTrainNumbers(); } @Get(':id') @ApiOperation({ summary: 'Full train composition: locomotives, ordered wagons, totals vs. limits' }) composition(@Param('id', ParseUUIDPipe) id: string) { return this.trainBuilderService.getComposition(id); } @Put(':id/locomotives') @FleetManage(FREIGHT_PERMS.trains.changeLocomotives) @ApiOperation({ summary: 'Replace the locomotive set (minimum 1, same yard)' }) setLocomotives( @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateTrainLocomotivesDto, ) { return this.trainBuilderService.setLocomotives(id, dto); } @Patch(':id/details') @FleetManage(FREIGHT_PERMS.trains.update) @ApiOperation({ summary: "Edit the train's name and fixed import/export run numbers", }) updateDetails( @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateTrainDetailsDto, ) { return this.trainBuilderService.updateDetails(id, dto); } @Patch(':id/yard') @FleetManage(FREIGHT_PERMS.trains.changeYard) @ApiOperation({ summary: 'Relocate the train — its locomotives and wagons move to the new yard with it', }) setYard(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateTrainYardDto) { return this.trainBuilderService.setYard(id, dto.currentYardId); } @Patch(':id/wagons/:wagonId/yard') @FleetManage(FREIGHT_PERMS.trains.changeWagonYard) @ApiOperation({ summary: 'Move one coupled wagon to another yard — refused while any live schedule has the wagon allocated', }) setWagonYard( @Param('id', ParseUUIDPipe) id: string, @Param('wagonId', ParseUUIDPipe) wagonId: string, @Body() dto: UpdateTrainYardDto, @CurrentUser() user: AuthUserPayload, ) { return this.trainBuilderService.setWagonYard( id, wagonId, dto.currentYardId, resolveAuthUserId(user), ); } @Patch(':id/wagons/yard') @FleetManage(FREIGHT_PERMS.trains.changeWagonYard) @ApiOperation({ summary: 'Move several coupled wagons to another yard in one transaction — refused outright if any is allocated to a live schedule', }) setWagonsYard( @Param('id', ParseUUIDPipe) id: string, @Body() dto: SetTrainWagonsYardDto, @CurrentUser() user: AuthUserPayload, ) { return this.trainBuilderService.setWagonsYard( id, dto.wagonIds, dto.currentYardId, resolveAuthUserId(user), ); } @Post(':id/wagons') @FleetManage(FREIGHT_PERMS.trains.assignWagons) @ApiOperation({ summary: "Append AVAILABLE wagons from the train's yard to the consist" }) assignWagons( @Param('id', ParseUUIDPipe) id: string, @Body() dto: AssignTrainWagonsDto, @CurrentUser() user: AuthUserPayload, ) { return this.trainBuilderService.assignWagons(id, dto, resolveAuthUserId(user)); } @Delete(':id/wagons/:wagonId') @FleetManage(FREIGHT_PERMS.trains.assignWagons) @ApiOperation({ summary: 'Detach one wagon from the consist' }) removeWagon( @Param('id', ParseUUIDPipe) id: string, @Param('wagonId', ParseUUIDPipe) wagonId: string, @CurrentUser() user: AuthUserPayload, ) { return this.trainBuilderService.removeWagon(id, wagonId, resolveAuthUserId(user)); } @Post(':id/wagons/:wagonId/maintenance') @FleetManage(FREIGHT_PERMS.trains.assignWagons) @ApiOperation({ summary: 'Detach one wagon and move it to MAINTENANCE status' }) sendWagonToMaintenance( @Param('id', ParseUUIDPipe) id: string, @Param('wagonId', ParseUUIDPipe) wagonId: string, @CurrentUser() user: AuthUserPayload, @Body() dto?: SendWagonToMaintenanceDto, ) { return this.trainBuilderService.sendWagonToMaintenance( id, wagonId, resolveAuthUserId(user), dto?.note, ); } @Post(':id/reorder-wagons') @FleetManage(FREIGHT_PERMS.trains.assignWagons) @ApiOperation({ summary: 'Persist a drag-reorder of the full consist' }) reorderWagons(@Param('id', ParseUUIDPipe) id: string, @Body() dto: ReorderTrainWagonsDto) { return this.trainBuilderService.reorderWagons(id, dto); } @Post(':id/deactivate') @FleetManage(FREIGHT_PERMS.trains.toggleActive) @ApiOperation({ summary: 'Deactivate the train (park it) — only allowed with no active schedule', }) deactivate(@Param('id', ParseUUIDPipe) id: string) { return this.trainBuilderService.deactivate(id); } @Post(':id/activate') @FleetManage(FREIGHT_PERMS.trains.toggleActive) @ApiOperation({ summary: 'Reactivate a deactivated train back to AVAILABLE' }) activate(@Param('id', ParseUUIDPipe) id: string) { return this.trainBuilderService.activate(id); } @Delete(':id') @FleetManage(FREIGHT_PERMS.trains.disband) @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: 'Disband the train (release wagons and locomotives)' }) disband(@Param('id', ParseUUIDPipe) id: string) { return this.trainBuilderService.disband(id); } }