Segment based seat assignement added

This commit is contained in:
Roba Boru
2026-05-27 15:11:23 +03:00
parent 3c6bd724f2
commit 2ceb993c60
10 changed files with 630 additions and 290 deletions

View File

@@ -1,132 +1,51 @@
import { Controller, Post, Get, Body, Query, Param } from '@nestjs/common';
import { Controller, Post, Get, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { EnhancedSeatsService } from './enhanced-seats.service';
import { HoldSeatsDto, ConfirmBookingDto, SeatAvailabilityDto, ReleaseSeatsDto } from './segments.dto';
import { ConfirmBookingDto, SeatAvailabilityDto, ReleaseSeatsDto } from './segments.dto';
@ApiTags('Segment-based Seats')
@Controller('segments/seats')
export class SegmentSeatsController {
constructor(private enhancedSeatsService: EnhancedSeatsService) {}
@Post('hold')
@ApiOperation({
summary: 'Hold seats for specific journey segments',
description: 'Reserve seats for a partial journey (e.g., Addis Ababa → Dire Dawa) with 10-minute expiry'
})
@ApiResponse({
status: 201,
description: 'Seats held successfully',
schema: {
example: {
holdId: 'hold_123',
expiresAt: '2024-01-15T10:10:00Z',
segments: [
{ fromName: 'Addis Ababa', toName: 'Adama', fromSequence: 0, toSequence: 1 },
{ fromName: 'Adama', toName: 'Awash', fromSequence: 1, toSequence: 2 },
{ fromName: 'Awash', toName: 'Dire Dawa', fromSequence: 2, toSequence: 3 }
],
seats: ['seat_1', 'seat_2']
}
}
})
@ApiResponse({ status: 409, description: 'Seats not available for requested segments' })
async holdSeats(@Body() dto: HoldSeatsDto) {
return this.enhancedSeatsService.holdSeats({
scheduleId: dto.scheduleId,
seatIds: dto.seatIds,
passengerId: dto.passengerId,
originStationId: dto.originStationId,
destinationStationId: dto.destinationStationId,
fareQuoteId: dto.fareQuoteId,
});
}
@Post('confirm')
@ApiOperation({
summary: 'Confirm booking and convert hold to reservation',
description: 'Convert seat hold to confirmed booking after payment success'
@ApiOperation({
summary: 'Confirm booking convert hold to reservation',
description: 'Call after payment succeeds. Converts the SeatHold (created via POST /seats/hold) into JourneySegment records scoped to the passenger\'s leg.',
})
@ApiResponse({
status: 200,
description: 'Booking confirmed successfully',
schema: {
example: {
bookingId: 'booking_123',
confirmedSeats: ['seat_1', 'seat_2'],
segments: [
{ fromName: 'Addis Ababa', toName: 'Adama' },
{ fromName: 'Adama', toName: 'Awash' },
{ fromName: 'Awash', toName: 'Dire Dawa' }
]
}
}
})
@ApiResponse({ status: 400, description: 'Hold expired or not found' })
async confirmBooking(@Body() dto: ConfirmBookingDto) {
@ApiResponse({ status: 200, description: 'Booking confirmed, JourneySegments created for the held leg' })
@ApiResponse({ status: 400, description: 'Hold expired or booking not found' })
confirmBooking(@Body() dto: ConfirmBookingDto) {
return this.enhancedSeatsService.confirmBooking(dto);
}
@Post('release')
@ApiOperation({
summary: 'Release seats when train reaches station',
description: 'Automatically release seats for passengers who have reached their destination'
@ApiOperation({
summary: 'Release seats when train reaches a station',
description: 'Called by the live tracking system when the train departs a station. Frees seats for passengers whose journey ended at that station.',
})
@ApiResponse({
status: 200,
description: 'Seats released successfully',
schema: {
example: {
releasedSeats: ['seat_1', 'seat_2'],
stationId: 'st_DRE'
}
}
})
async releaseSeats(@Body() dto: ReleaseSeatsDto) {
@ApiResponse({ status: 200, description: 'Seats released for passengers who reached their destination' })
releaseSeats(@Body() dto: ReleaseSeatsDto) {
return this.enhancedSeatsService.releaseSeats(dto.scheduleId, dto.currentStationId);
}
@Get('availability')
@ApiOperation({
summary: 'Check seat availability for journey segments',
description: 'Get available seats for a specific origin-destination pair'
@ApiOperation({
summary: 'Get available seats for a specific leg',
description: 'Returns seats that have no overlapping reservation for the requested origindestination leg. A seat booked A→B is shown as available for B→D.',
})
@ApiResponse({
status: 200,
description: 'Seat availability retrieved',
schema: {
example: {
segments: [
{ fromName: 'Addis Ababa', toName: 'Adama', fromSequence: 0, toSequence: 1 },
{ fromName: 'Adama', toName: 'Awash', fromSequence: 1, toSequence: 2 }
],
availableSeats: [
{ id: 'seat_1', label: '1A', coach: 'A', seatClass: 'Economy Regular', row: 1, col: 'A' },
{ id: 'seat_2', label: '1B', coach: 'A', seatClass: 'Economy Regular', row: 1, col: 'B' }
],
totalAvailable: 2
}
}
})
async getSeatAvailability(@Query() dto: SeatAvailabilityDto) {
@ApiResponse({ status: 200, description: 'Available seats with coach, seat class, row, col, window/aisle/bed flags' })
getSeatAvailability(@Query() dto: SeatAvailabilityDto) {
return this.enhancedSeatsService.getSeatAvailability(dto.scheduleId, dto.originStationId, dto.destinationStationId);
}
@Post('expire-holds')
@ApiOperation({
summary: 'Expire old seat holds (background job)',
description: 'Release seats from expired holds and make them available'
@ApiOperation({
summary: 'Expire stale seat holds (background job)',
description: 'Removes holds past their expiry time. Called by the scheduler every minute.',
})
@ApiResponse({
status: 200,
description: 'Expired holds processed',
schema: {
example: {
expiredHolds: 5,
releasedSeats: ['seat_1', 'seat_2', 'seat_3']
}
}
})
async expireHolds() {
@ApiResponse({ status: 200, description: 'Expired holds removed' })
expireHolds() {
return this.enhancedSeatsService.expireHolds();
}
}