mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
134 lines
7.5 KiB
TypeScript
134 lines
7.5 KiB
TypeScript
import { Body, Controller, Get, Param, Patch, Post, Query, ParseIntPipe, UseGuards } from '@nestjs/common';
|
||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
|
||
import { SchedulesService } from './schedules.service';
|
||
import { CreateScheduleDto, CreateFareRuleDto, UpdateScheduleStatusDto, UpdateStopTimeDto, ListSchedulesDto } from './schedules.dto';
|
||
import { JwtGuard } from '../../common/jwt.guard';
|
||
import { TripStatus } from '@prisma/client';
|
||
|
||
@ApiTags('Schedule')
|
||
@Controller('schedules')
|
||
export class SchedulesController {
|
||
constructor(private service: SchedulesService) {}
|
||
|
||
@Post()
|
||
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth')
|
||
@ApiOperation({
|
||
summary: 'Create a train schedule from a route template',
|
||
description: `Creates a schedule by referencing a Route (routeId).
|
||
Stops are automatically copied from the route's RouteStop definitions.
|
||
You supply the actual planned arrival/departure times per stop sequence.
|
||
Origin and destination are derived from the first and last route stop — no need to specify them manually.`,
|
||
})
|
||
@ApiResponse({ status: 201, description: 'Schedule created with stops copied from route template' })
|
||
@ApiResponse({ status: 400, description: 'Invalid times, inactive route, or missing planned times for some stops' })
|
||
@ApiResponse({ status: 404, description: 'Train or route not found' })
|
||
createSchedule(@Body() dto: CreateScheduleDto) { return this.service.createSchedule(dto); }
|
||
|
||
@Get()
|
||
@ApiOperation({ summary: 'List schedules with optional filters' })
|
||
@ApiQuery({ name: 'date', required: false, example: '2026-06-15', description: 'Departure date (YYYY-MM-DD). Returns all schedules departing on this calendar day.' })
|
||
@ApiQuery({ name: 'routeId', required: false, description: 'Filter by route UUID' })
|
||
@ApiQuery({ name: 'trainId', required: false, description: 'Filter by train UUID' })
|
||
@ApiQuery({ name: 'status', required: false, enum: TripStatus, description: 'Filter by schedule status' })
|
||
@ApiResponse({ status: 200, description: 'Array of schedules ordered by departureAt, each with train, origin/destination, stops, and booking/assignment counts' })
|
||
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 });
|
||
}
|
||
|
||
// Static routes before parameterised ones
|
||
@Post('fares')
|
||
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-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); }
|
||
|
||
@Get(':id')
|
||
@ApiOperation({ summary: 'Get schedule with train, coaches, seats, and stop timeline' })
|
||
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
||
@ApiResponse({ status: 200, description: 'Full schedule detail including route stops with station info' })
|
||
@ApiResponse({ status: 404, description: 'Schedule not found' })
|
||
getSchedule(@Param('id') id: string) { return this.service.getSchedule(id); }
|
||
|
||
@Patch(':id/status')
|
||
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth')
|
||
@ApiOperation({ summary: 'Update schedule status (SCHEDULED → BOARDING → EN_ROUTE → ARRIVED)' })
|
||
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
||
@ApiResponse({ status: 200, description: 'Status updated' })
|
||
@ApiResponse({ status: 404, description: 'Schedule not found' })
|
||
updateStatus(@Param('id') id: string, @Body() dto: UpdateScheduleStatusDto) {
|
||
return this.service.updateScheduleStatus(id, dto);
|
||
}
|
||
|
||
// ── Stop Times ─────────────────────────────────────────────────────────────
|
||
|
||
@Get(':id/stops')
|
||
@ApiOperation({ summary: 'List all stops for a schedule ordered by sequence' })
|
||
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
||
@ApiResponse({ status: 200, description: 'Ordered stop list with station details and planned/actual times' })
|
||
@ApiResponse({ status: 404, description: 'Schedule not found' })
|
||
getStops(@Param('id') id: string) { return this.service.getStops(id); }
|
||
|
||
@Patch(':id/stops/:sequence')
|
||
@UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth')
|
||
@ApiOperation({ summary: 'Update planned times or live status of a specific stop' })
|
||
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
||
@ApiParam({ name: 'sequence', description: 'Stop sequence number' })
|
||
@ApiResponse({ status: 200, description: 'Stop updated' })
|
||
@ApiResponse({ status: 404, description: 'Stop not found on schedule' })
|
||
updateStop(
|
||
@Param('id') id: string,
|
||
@Param('sequence', ParseIntPipe) sequence: number,
|
||
@Body() dto: UpdateStopTimeDto,
|
||
) { return this.service.updateStop(id, sequence, dto); }
|
||
|
||
// ── Fares ──────────────────────────────────────────────────────────────────
|
||
|
||
@Get(':scheduleId/fares')
|
||
@ApiOperation({ summary: 'Get fare for a schedule and seat class from the fare engine' })
|
||
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
||
@ApiQuery({ name: 'seatClassId', required: true, description: 'SeatClass UUID' })
|
||
@ApiQuery({ name: 'nationality', required: false, description: 'Passenger nationality — determines billing currency (Ethiopian→ETB, Djiboutian→DJF, other→USD)' })
|
||
@ApiResponse({ status: 200, description: 'Live fare breakdown from fare engine' })
|
||
@ApiResponse({ status: 400, description: 'Schedule has no route or missing distanceKm on stops' })
|
||
@ApiResponse({ status: 404, description: 'Schedule or seat class not found' })
|
||
getFare(
|
||
@Param('scheduleId') scheduleId: string,
|
||
@Query('seatClassId') seatClassId: string,
|
||
@Query('nationality') nationality?: string,
|
||
) {
|
||
return this.service.getFareFromEngine(scheduleId, seatClassId, nationality);
|
||
}
|
||
|
||
@Get(':scheduleId/fares/all')
|
||
@ApiOperation({ summary: 'Get fares for all active seat classes on a schedule' })
|
||
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
||
@ApiQuery({ name: 'nationality', required: false, description: 'Passenger nationality — determines billing currency' })
|
||
@ApiResponse({ status: 200, description: 'Array of fare breakdowns for every active seat class, ordered by price ascending' })
|
||
@ApiResponse({ status: 400, description: 'Schedule has no route or missing distanceKm on stops' })
|
||
@ApiResponse({ status: 404, description: 'Schedule not found' })
|
||
getAllFares(
|
||
@Param('scheduleId') scheduleId: string,
|
||
@Query('nationality') nationality?: string,
|
||
) {
|
||
return this.service.getAllFaresFromEngine(scheduleId, nationality);
|
||
}
|
||
|
||
@Post(':id/fares/sync')
|
||
@ApiOperation({
|
||
summary: 'Sync fares from fare engine',
|
||
description: 'Recalculates fares for all active seat classes using the fare engine (km × ratePerKm + tax) and upserts them as FareRule records scoped to this schedule. Previous active rules are expired.',
|
||
})
|
||
@ApiParam({ name: 'id', description: 'TrainSchedule UUID' })
|
||
@ApiResponse({ status: 201, description: 'Fares synced — returns count of synced rules and any errors' })
|
||
@ApiResponse({ status: 400, description: 'Schedule has no associated route or missing distanceKm on stops' })
|
||
@ApiResponse({ status: 404, description: 'Schedule not found' })
|
||
syncFares(@Param('id') id: string) {
|
||
return this.service.syncFaresFromEngine(id);
|
||
}
|
||
}
|