# Segment-Based Seat Reservation ## Overview This implementation introduces segment-based seat reservation and release logic for the Ethio-Djibouti Railway passenger booking system. It allows passengers to book partial journeys while ensuring optimal seat utilization through automatic release when passengers reach their destinations. ## Key Features - **Segment-based reservations**: Book seats for specific route segments (e.g., Addis Ababa → Dire Dawa) - **Automatic seat release**: Seats are released when passengers reach their destination - **Concurrency control**: Database transactions ensure consistency - **Real-time updates**: Event-driven notifications for seat availability changes - **Hold expiration**: Automatic cleanup of expired seat holds ## Route Example **Full Route**: Addis Ababa → Adama → Awash → Dire Dawa → Djibouti **Passenger Journey**: Addis Ababa → Dire Dawa - **Segments**: [Addis→Adama, Adama→Awash, Awash→Dire Dawa] - **Seat Status**: HELD → BOOKED → AVAILABLE (when reaching Dire Dawa) ## Database Schema Integration ### Core Tables Used ```sql -- Trip and route structure Trip, TripStopTime, Station -- Seat management Seat, SeatHold, BookingSeat, Booking -- Journey tracking JourneySegment (stores segment-to-seat mapping) -- Real-time progress TripLiveStatus (triggers seat releases) ``` ### Key Enums ```typescript enum SeatStatus { AVAILABLE = 'AVAILABLE', HELD = 'HELD', BOOKED = 'BOOKED', BLOCKED = 'BLOCKED' } ``` ## API Endpoints ### 1. Check Seat Availability ```http GET /segments/seats/availability?tripId=trip_001&originStationId=st_ADD&destinationStationId=st_DRE ``` **Response:** ```json { "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 } ], "availableSeats": [ { "id": "seat_1", "label": "1A", "coach": "A", "serviceClass": "ECONOMY" } ], "totalAvailable": 1 } ``` ### 2. Hold Seats ```http POST /segments/seats/hold ``` **Request:** ```json { "tripId": "trip_001", "seatIds": ["seat_1", "seat_2"], "passengerId": "passenger_123", "originStationId": "st_ADD", "destinationStationId": "st_DRE", "fareQuoteId": "quote_456" } ``` **Response:** ```json { "holdId": "hold_789", "expiresAt": "2024-01-15T10:10:00Z", "segments": [ { "fromName": "Addis Ababa", "toName": "Adama" }, { "fromName": "Adama", "toName": "Awash" }, { "fromName": "Awash", "toName": "Dire Dawa" } ], "seats": ["seat_1", "seat_2"] } ``` ### 3. Confirm Booking ```http POST /segments/seats/confirm ``` **Request:** ```json { "holdId": "hold_789", "bookingId": "booking_123" } ``` ### 4. Release Seats (Automatic) ```http POST /segments/seats/release ``` **Request:** ```json { "tripId": "trip_001", "currentStationId": "st_DRE" } ``` ## Database Transaction Flow ### 1. Seat Hold Transaction ```typescript async function holdSeatsTransaction(request: SeatHoldRequest) { return prisma.$transaction(async (tx) => { // 1. Validate seat availability const seats = await tx.seat.findMany({ where: { id: { in: request.seatIds } } }); // 2. Check for overlapping reservations for (const seatId of request.seatIds) { const overlaps = await checkOverlaps(tx, tripId, seatId, segments); if (overlaps.length > 0) throw new ConflictException(); } // 3. Create hold record const hold = await tx.seatHold.create({ data: { tripId: request.tripId, seatIds: request.seatIds, passengerId: request.passengerId, expiresAt: new Date(Date.now() + 10 * 60 * 1000) } }); // 4. Update seat status await tx.seat.updateMany({ where: { id: { in: request.seatIds } }, data: { status: 'HELD', heldUntil: hold.expiresAt } }); return hold; }); } ``` ### 2. Booking Confirmation Transaction ```typescript async function confirmBookingTransaction(holdId: string, bookingId: string) { return prisma.$transaction(async (tx) => { // 1. Validate hold const hold = await tx.seatHold.findUnique({ where: { id: holdId } }); if (!hold || hold.expiresAt < new Date()) { throw new BadRequestException('Hold expired'); } // 2. Create journey segments for (const seatId of hold.seatIds) { for (let i = 0; i < segments.length; i++) { await tx.journeySegment.create({ data: { journeyId: bookingId, tripId: hold.tripId, segmentOrder: i + 1, seatId, departureStationId: segments[i].fromStationId, arrivalStationId: segments[i].toStationId } }); } } // 3. Update seat status to BOOKED await tx.seat.updateMany({ where: { id: { in: hold.seatIds } }, data: { status: 'BOOKED', heldUntil: null } }); // 4. Delete hold await tx.seatHold.delete({ where: { id: holdId } }); return { bookingId, confirmedSeats: hold.seatIds }; }); } ``` ### 3. Seat Release Transaction ```typescript async function releaseSeatsTransaction(tripId: string, currentStationId: string) { return prisma.$transaction(async (tx) => { // 1. Find completed journey segments const completedSegments = await tx.journeySegment.findMany({ where: { tripId, arrivalStationId: currentStationId }, include: { journey: { include: { journeySegments: true } } } }); const seatsToRelease = []; // 2. Check if passenger's entire journey is complete for (const segment of completedSegments) { const allSegments = segment.journey.journeySegments .filter(js => js.seatId === segment.seatId); const maxOrder = Math.max(...allSegments.map(js => js.segmentOrder)); if (segment.segmentOrder === maxOrder) { seatsToRelease.push(segment.seatId); } } // 3. Release seats if (seatsToRelease.length > 0) { await tx.seat.updateMany({ where: { id: { in: seatsToRelease } }, data: { status: 'AVAILABLE' } }); } return { releasedSeats: seatsToRelease }; }); } ``` ## Real-Time Integration ### Trip Progress Updates ```typescript // When train reaches a station await tripProgressService.updateTripProgress(tripId, stationId, progressPercent); // Automatically triggers seat release this.eventEmitter.emit('trip.station.arrived', { tripId, stationId, stationName: 'Dire Dawa' }); ``` ### Event Listeners ```typescript @OnEvent('trip.station.arrived') async handleStationArrival(payload: { tripId: string, stationId: string }) { await this.enhancedSeatsService.releaseSeats(payload.tripId, payload.stationId); } @OnEvent('seats.released') async handleSeatsReleased(payload: { releasedSeats: string[] }) { // Notify waiting passengers about newly available seats this.notificationService.notifyAvailability(payload.releasedSeats); } ``` ## Background Jobs ### Hold Expiration (Every Minute) ```typescript @Cron(CronExpression.EVERY_MINUTE) async expireHolds() { const expired = await this.prisma.seatHold.findMany({ where: { expiresAt: { lt: new Date() } } }); // Release expired seats await this.prisma.seat.updateMany({ where: { id: { in: expiredSeatIds } }, data: { status: 'AVAILABLE', heldUntil: null } }); } ``` ## Usage Examples ### Complete Booking Flow ```typescript // 1. Check availability const availability = await segmentSeatsService.getSeatAvailability( 'trip_001', 'st_ADD', 'st_DRE' ); // 2. Hold seats (10-minute expiry) const hold = await segmentSeatsService.holdSeats({ tripId: 'trip_001', seatIds: ['seat_1'], passengerId: 'passenger_123', originStationId: 'st_ADD', destinationStationId: 'st_DRE' }); // 3. Process payment... await paymentService.processPayment(bookingId); // 4. Confirm booking const booking = await segmentSeatsService.confirmBooking({ holdId: hold.holdId, bookingId: 'booking_456' }); // 5. Seats automatically released when train reaches Dire Dawa ``` ## Error Handling - **Seat Conflicts**: `ConflictException` when seats overlap with existing reservations - **Expired Holds**: `BadRequestException` when trying to confirm expired holds - **Invalid Segments**: `BadRequestException` for invalid origin/destination combinations - **Transaction Rollback**: Automatic rollback on any failure within transactions ## Performance Considerations - **Indexing**: Ensure indexes on `tripId`, `seatId`, `stationId`, `expiresAt` - **Batch Operations**: Use `updateMany` for bulk seat status updates - **Event Queuing**: Consider message queues for high-volume seat release events - **Caching**: Cache frequently accessed route/station data ## Integration Notes 1. **Existing Booking System**: Extends current booking flow with segment awareness 2. **Payment Integration**: Hold expiry provides payment processing window 3. **Real-time Updates**: WebSocket notifications for seat availability changes 4. **Mobile Apps**: Push notifications when seats become available on preferred routes 5. **Analytics**: Track seat utilization patterns by segment for route optimization ## Testing Run the example booking flow: ```bash cd apps/edr-passenger-api npx ts-node src/modules/segments/booking-flow-example.ts ``` This demonstrates the complete segment-based reservation lifecycle with database transactions and real-time seat releases.