import { WagonTransferRequestStatus } from '@edr/types'; import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Query, } from '@nestjs/common'; import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { CurrentUser } from '@edr/api-common'; import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type'; import { FleetManage, FleetView, WagonTransferFulfill, WagonTransferHistoryAll, WagonTransferRequest, } from '../../common/booking-guards'; import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry'; import { BulkFulfillTransferRequestsDto } from './dto/bulk-fulfill-transfer-requests.dto'; import { CreateTransferRequestDto } from './dto/create-transfer-request.dto'; import { FulfillTransferRequestDto } from './dto/fulfill-transfer-request.dto'; import { WagonTransferRequestsService } from './wagon-transfer-requests.service'; /** * Two-person wagon-transfer queue. Requester (transfer_request perm) files a * count-only request; OCC (transfer_fulfill perm) picks the wagons and executes * the move. Separate top-level path so it never collides with `wagons/:id`. */ @ApiTags('wagon-transfer-requests') @Controller('wagon-transfer-requests') @FleetView(FREIGHT_PERMS.wagons.view) export class WagonTransferRequestsController { constructor(private readonly service: WagonTransferRequestsService) {} @Post() @WagonTransferRequest() @ApiOperation({ summary: 'File a count-only wagon-transfer request' }) create( @Body() dto: CreateTransferRequestDto, @CurrentUser() user: TCurrentUser, ) { return this.service.createRequest(dto, user?.id); } @Get() @ApiQuery({ name: 'status', required: false, enum: WagonTransferRequestStatus }) @ApiOperation({ summary: 'List transfer requests (OCC queue: status=PENDING)' }) list(@Query('status') status?: WagonTransferRequestStatus) { return this.service.listRequests(status); } // NOTE: static routes (`history`, `bulk-fulfill`) MUST stay above `@Get(':id')` // — Express matches in declaration order, so they would otherwise be captured // by the `:id` param route (and rejected by ParseUUIDPipe). @Post('bulk-fulfill') @WagonTransferFulfill() @ApiOperation({ summary: 'OCC: accept-and-execute a subset of pending requests (auto-picks available wagons; the rest stay PENDING)', }) bulkFulfill( @Body() dto: BulkFulfillTransferRequestsDto, @CurrentUser() user: TCurrentUser, ) { return this.service.bulkFulfill(dto.requestIds, user?.id); } // NOTE: the two `history` routes MUST stay above `@Get(':id')` — Express // matches in declaration order, so `/history` would otherwise be captured by // the `:id` param route (and rejected by ParseUUIDPipe). @Get('history') @ApiOperation({ summary: "Caller's own transfer history (requests filed/fulfilled + wagons moved)", }) myHistory(@CurrentUser() user: TCurrentUser) { // Never fall through to the all-staff view: getHistory(undefined) means // "everyone", so a missing caller id must return empty, not leak scope. if (!user?.id) return { requests: [], movements: [] }; return this.service.getHistory(user.id); } @Get('history/all') @WagonTransferHistoryAll() @ApiQuery({ name: 'userId', required: false }) @ApiOperation({ summary: "Admin: any/all staff's transfer history (optional ?userId filter)", }) allHistory(@Query('userId') userId?: string) { return this.service.getHistory(userId); } @Get(':id') @ApiOperation({ summary: 'Get one transfer request' }) findOne(@Param('id', ParseUUIDPipe) id: string) { return this.service.findById(id); } @Post(':id/fulfill') @WagonTransferFulfill() @ApiOperation({ summary: 'OCC: pick wagons and execute the transfer' }) fulfill( @Param('id', ParseUUIDPipe) id: string, @Body() dto: FulfillTransferRequestDto, @CurrentUser() user: TCurrentUser, ) { return this.service.fulfillRequest(id, dto, user?.id); } @Post(':id/cancel') @FleetManage(FREIGHT_PERMS.wagons.transferRequest) @ApiOperation({ summary: 'Withdraw a pending transfer request' }) cancel(@Param('id', ParseUUIDPipe) id: string) { return this.service.cancelRequest(id); } }