mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
270 lines
13 KiB
TypeScript
270 lines
13 KiB
TypeScript
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, ParseIntPipe } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
|
|
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
|
|
import { SchedulesService } from './schedules.service';
|
|
import { CreateScheduleDto, UpdateScheduleDto, CreateFareRuleDto, UpdateScheduleStatusDto, UpdateStopTimeDto, ListSchedulesDto, BulkCreateSchedulesDto, BulkSchedulesResponseDto, TripStatus, ApplyDelayDto } from './schedules.dto';
|
|
import { PassengerAdmin, PassengerStaff } from '../../common/passenger-guards';
|
|
import { PASSENGER_PERMS } from '../../seed/passenger-permissions.registry';
|
|
|
|
@ApiTags('Schedule')
|
|
@Controller('schedules')
|
|
export class SchedulesController {
|
|
constructor(private service: SchedulesService) {}
|
|
|
|
@Post('bulk-generate')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Bulk generate repetitive schedules' })
|
|
bulkGenerateSchedules(@Body() dto: BulkCreateSchedulesDto) {
|
|
return this.service.bulkGenerateSchedules(dto);
|
|
}
|
|
|
|
@Post()
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Create a train schedule from a route template' })
|
|
createSchedule(@Body() dto: CreateScheduleDto) { return this.service.createSchedule(dto); }
|
|
|
|
@Get()
|
|
@IsPublic()
|
|
@ApiOperation({ summary: 'List schedules with optional filters' })
|
|
@ApiQuery({ name: 'date', required: false })
|
|
@ApiQuery({ name: 'routeId', required: false })
|
|
@ApiQuery({ name: 'trainId', required: false })
|
|
@ApiQuery({ name: 'status', required: false, enum: TripStatus })
|
|
listSchedules(
|
|
@Query('date') date?: string,
|
|
@Query('routeId') routeId?: string,
|
|
@Query('trainId') trainId?: string,
|
|
@Query('status') status?: TripStatus,
|
|
) {
|
|
return this.service.listSchedules({ date, routeId, trainId, status });
|
|
}
|
|
|
|
// ===== SPECIFIC ROUTES (must come BEFORE generic :id routes) =====
|
|
|
|
@Post('fares')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Create a fare rule scoped to a schedule or route code' })
|
|
@ApiResponse({ status: 201, description: 'Fare rule created' })
|
|
createFareRule(@Body() dto: CreateFareRuleDto) { return this.service.createFareRule(dto); }
|
|
|
|
@Patch('fares/:id')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update a fare rule' })
|
|
@ApiParam({ name: 'id', description: 'FareRule UUID' })
|
|
@ApiResponse({ status: 200, description: 'Fare rule updated' })
|
|
updateFareRule(@Param('id') id: string, @Body() dto: Partial<CreateFareRuleDto>) {
|
|
return this.service.updateFareRule(id, dto);
|
|
}
|
|
|
|
@Delete('fares/:id')
|
|
@PassengerAdmin()
|
|
@ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Delete a fare rule' })
|
|
@ApiParam({ name: 'id', description: 'FareRule UUID' })
|
|
@ApiResponse({ status: 200, description: 'Fare rule deleted' })
|
|
deleteFareRule(@Param('id') id: string) { return this.service.deleteFareRule(id); }
|
|
|
|
@Post('segment-fares')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Create a segment fare rule' })
|
|
createSegmentFareRule(@Body() dto: any) { return this.service.createSegmentFareRule(dto); }
|
|
|
|
// Static sub-path MUST come before routes/:routeId/* to avoid :routeId swallowing 'fare-rules'
|
|
@Delete('routes/fare-rules/:id')
|
|
@PassengerAdmin()
|
|
@ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Delete a route-level fare override' })
|
|
@ApiParam({ name: 'id', description: 'RouteFareRule UUID' })
|
|
deleteRouteFareRule(@Param('id') id: string) {
|
|
return this.service.deleteRouteFareRule(id);
|
|
}
|
|
|
|
@Patch('routes/fare-rules/:id')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update a route-level fare override' })
|
|
@ApiParam({ name: 'id', description: 'RouteFareRule UUID' })
|
|
updateRouteFareRule(@Param('id') id: string, @Body() dto: any) {
|
|
return this.service.updateRouteFareRule(id, dto);
|
|
}
|
|
|
|
@Get('routes/:routeId/fare-rules')
|
|
@IsPublic()
|
|
@ApiOperation({ summary: 'List route-level fare overrides for a route' })
|
|
@ApiParam({ name: 'routeId', description: 'Route UUID' })
|
|
listRouteFareRules(@Param('routeId') routeId: string) {
|
|
return this.service.listRouteFareRules(routeId);
|
|
}
|
|
|
|
@Post('routes/:routeId/fare-rules')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Create a route-level fare override' })
|
|
@ApiParam({ name: 'routeId', description: 'Route UUID' })
|
|
createRouteFareRule(@Param('routeId') routeId: string, @Body() dto: any) {
|
|
return this.service.createRouteFareRule({ ...dto, routeId });
|
|
}
|
|
|
|
@Get('routes/:routeId/segment-fares')
|
|
@IsPublic()
|
|
@ApiOperation({ summary: 'List all segment fare rules for a route' })
|
|
@ApiParam({ name: 'routeId', description: 'Route UUID' })
|
|
getSegmentFares(@Param('routeId') routeId: string) { return this.service.getSegmentFares(routeId); }
|
|
|
|
@Patch('segment-fares/:id')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update a segment fare rule' })
|
|
@ApiParam({ name: 'id', description: 'SegmentFareRule UUID' })
|
|
updateSegmentFareRule(@Param('id') id: string, @Body() dto: any) { return this.service.updateSegmentFareRule(id, dto); }
|
|
|
|
@Delete('segment-fares/:id')
|
|
@PassengerAdmin()
|
|
@ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Delete a segment fare rule' })
|
|
@ApiParam({ name: 'id', description: 'SegmentFareRule UUID' })
|
|
deleteSegmentFareRule(@Param('id') id: string) { return this.service.deleteSegmentFareRule(id); }
|
|
|
|
// ===== PARAMETRIZED ROUTES (generic :id routes come AFTER specific routes) =====
|
|
|
|
@Get(':id')
|
|
@IsPublic()
|
|
@ApiOperation({ summary: 'Get schedule detail' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
getSchedule(@Param('id') id: string) { return this.service.getSchedule(id); }
|
|
|
|
@Patch(':id')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update a schedule (partial)' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
updateSchedule(@Param('id') id: string, @Body() dto: UpdateScheduleDto) {
|
|
return this.service.updateSchedulePartial(id, dto);
|
|
}
|
|
|
|
@Patch(':id/status')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update schedule status' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
updateStatus(@Param('id') id: string, @Body() dto: UpdateScheduleStatusDto) {
|
|
return this.service.updateScheduleStatus(id, dto);
|
|
}
|
|
|
|
@Delete(':id')
|
|
@PassengerAdmin()
|
|
@ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Delete a schedule' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
@ApiQuery({ name: 'cascade', required: false, type: Boolean })
|
|
deleteSchedule(@Param('id') id: string, @Query('cascade') cascade?: string) { return this.service.deleteSchedule(id, cascade === 'true'); }
|
|
|
|
@Get(':id/stops')
|
|
@IsPublic()
|
|
@ApiOperation({ summary: 'List all stops for a schedule' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
getStops(@Param('id') id: string) { return this.service.getStops(id); }
|
|
|
|
@Patch(':id/stops/:sequence')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Update a stop time' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
@ApiParam({ name: 'sequence', description: 'Stop sequence number' })
|
|
updateStop(
|
|
@Param('id') id: string,
|
|
@Param('sequence', ParseIntPipe) sequence: number,
|
|
@Body() dto: UpdateStopTimeDto,
|
|
) { return this.service.updateStop(id, sequence, dto); }
|
|
|
|
@Post(':id/delay')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({
|
|
summary: 'Report a delay — pushes every downstream stop\'s planned times (and check-in cutoffs) back by the same amount',
|
|
description: `Shifts plannedArrivalAt/plannedDepartureAt on every stop not yet BOARDED/COMPLETED (or from fromSequence
|
|
onward, if given) by delayMinutes. Since check-in cutoffs are derived directly from these planned
|
|
times, this is the only action needed for booking closure to reflect the delay — no separate cutoff
|
|
update. Also shifts the schedule's own departureAt/arrivalAt when the origin stop is included, and
|
|
records the accumulated delay on the schedule's live status. Does not change schedule/stop status.`,
|
|
})
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
@ApiResponse({ status: 200, description: 'Schedule with shifted stop times' })
|
|
applyDelay(@Param('id') id: string, @Body() dto: ApplyDelayDto) {
|
|
return this.service.applyDelay(id, dto);
|
|
}
|
|
|
|
@Put(':scheduleId/fares/:seatClassId')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({
|
|
summary: 'Override fare for a specific seat class on a schedule',
|
|
description: 'Upserts a schedule-scoped FareRule. Expires any existing active rule for the same schedule+seatClass and creates a new one.',
|
|
})
|
|
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
|
@ApiParam({ name: 'seatClassId', description: 'SeatClass UUID' })
|
|
@ApiResponse({ status: 200, description: 'Fare rule upserted' })
|
|
upsertScheduleFare(
|
|
@Param('scheduleId') scheduleId: string,
|
|
@Param('seatClassId') seatClassId: string,
|
|
@Body() dto: { baseFareMinor: number; validFrom?: string; validUntil?: string },
|
|
) {
|
|
return this.service.upsertScheduleFare(scheduleId, seatClassId, dto);
|
|
}
|
|
|
|
@Get(':scheduleId/fares/stored')
|
|
@ApiOperation({ summary: 'Get stored fare rules for a schedule' })
|
|
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
|
getStoredFares(@Param('scheduleId') scheduleId: string) {
|
|
return this.service.getFareRules(scheduleId);
|
|
}
|
|
|
|
@Get(':scheduleId/fares/all')
|
|
@ApiOperation({ summary: 'Get fares for all active seat classes from the fare engine' })
|
|
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
|
@ApiQuery({ name: 'nationality', required: false })
|
|
getAllFares(
|
|
@Param('scheduleId') scheduleId: string,
|
|
@Query('nationality') nationality?: string,
|
|
) {
|
|
return this.service.getAllFaresFromEngine(scheduleId, nationality);
|
|
}
|
|
|
|
@Get(':scheduleId/fares')
|
|
@ApiOperation({ summary: 'Get fare for a specific seat class from the fare engine' })
|
|
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
|
@ApiQuery({ name: 'seatClassId', required: true })
|
|
@ApiQuery({ name: 'nationality', required: false })
|
|
getFare(
|
|
@Param('scheduleId') scheduleId: string,
|
|
@Query('seatClassId') seatClassId: string,
|
|
@Query('nationality') nationality?: string,
|
|
) {
|
|
return this.service.getFareFromEngine(scheduleId, seatClassId, nationality);
|
|
}
|
|
|
|
@Post(':id/fares/sync')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Sync fares from fare engine' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
syncFares(@Param('id') id: string) { return this.service.syncFaresFromEngine(id); }
|
|
|
|
@Post(':id/coaches')
|
|
@PassengerStaff([PASSENGER_PERMS.schedules.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Assign coaches to a schedule' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
assignCoaches(
|
|
@Param('id') id: string,
|
|
@Body() dto: { coaches: Array<{ coachId: string; positionNumber: number }> },
|
|
) {
|
|
return this.service.assignCoaches(id, dto.coaches);
|
|
}
|
|
|
|
@Get(':id/coaches')
|
|
@ApiOperation({ summary: 'Get assigned coaches for a schedule' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
getAssignedCoaches(@Param('id') id: string) { return this.service.getAssignedCoaches(id); }
|
|
|
|
@Delete(':id/coaches/:coachId')
|
|
@PassengerAdmin()
|
|
@ApiBearerAuth('IAM-auth')
|
|
@ApiOperation({ summary: 'Remove a coach assignment' })
|
|
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
|
@ApiParam({ name: 'coachId', description: 'Coach UUID' })
|
|
removeCoachAssignment(@Param('id') id: string, @Param('coachId') coachId: string) {
|
|
return this.service.removeCoachAssignment(id, coachId);
|
|
}
|
|
}
|