/** * SEGMENT-BASED SEAT RESERVATION EXAMPLE * * Demonstrates the complete flow for booking Addis Ababa → Dire Dawa * on the Addis Ababa → Djibouti route with segment-based seat management. * * Route: Addis Ababa (seq:1) → Adama (seq:2) → Awash (seq:3) → Dire Dawa (seq:4) → Aysha (seq:5) → Djibouti (seq:6) * Booking: Addis Ababa → Dire Dawa (segments: 1→2, 2→3, 3→4) */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function exampleBookingFlow() { console.log('=== SEGMENT-BASED BOOKING FLOW ===\n'); const scheduleId = 'schedule_add_dji_001'; const passengerId = 'passenger_kelemu'; const seatIds = ['seat_coach_a_1a', 'seat_coach_a_1b']; const originStationId = 'st_ADD'; const destinationStationId = 'st_DRE'; try { console.log('1. Checking seat availability...'); const segments = await getJourneySegments(scheduleId, originStationId, destinationStationId); console.log('Journey segments:', segments.map(s => `${s.fromName} → ${s.toName}`)); console.log('\n2. Holding seats...'); const holdResult = await holdSeatsTransaction(scheduleId, seatIds, passengerId, originStationId, destinationStationId); console.log('Hold created:', holdResult); console.log('\n3. Processing payment...'); await new Promise(resolve => setTimeout(resolve, 5000)); console.log('\n4. Confirming booking...'); const bookingId = 'booking_' + Date.now(); const confirmResult = await confirmBookingTransaction(holdResult.holdId, bookingId, segments); console.log('Booking confirmed:', confirmResult); console.log('\n5. Simulating trip progress...'); await simulateTripProgress(scheduleId, segments); } catch (error) { console.error('Booking flow error:', error); } } async function getJourneySegments(scheduleId: string, originStationId: string, destinationStationId: string) { const stopTimes = await prisma.tripStopTime.findMany({ where: { scheduleId }, include: { station: true }, orderBy: { sequence: 'asc' }, }); const originStop = stopTimes.find(st => st.stationId === originStationId); const destinationStop = stopTimes.find(st => st.stationId === destinationStationId); if (!originStop || !destinationStop || originStop.sequence >= destinationStop.sequence) { throw new Error('Invalid origin/destination'); } const segments = []; for (let i = originStop.sequence; i < destinationStop.sequence; i++) { const fromStop = stopTimes.find(st => st.sequence === i); const toStop = stopTimes.find(st => st.sequence === i + 1); if (fromStop && toStop) { segments.push({ fromStationId: fromStop.stationId, toStationId: toStop.stationId, fromSequence: fromStop.sequence, toSequence: toStop.sequence, fromName: fromStop.station.name, toName: toStop.station.name, }); } } return segments; } async function holdSeatsTransaction(scheduleId: string, seatIds: string[], passengerId: string, originStationId: string, destinationStationId: string) { return prisma.$transaction(async (tx) => { console.log(' → Starting seat hold transaction...'); const seats = await tx.seat.findMany({ where: { id: { in: seatIds } }, include: { coach: true } }); if (seats.length !== seatIds.length) throw new Error('Some seats not found'); for (const seat of seats) { if (seat.status !== 'AVAILABLE') { throw new Error(`Seat ${seat.seatNumber} is not available (status: ${seat.status})`); } } const expiresAt = new Date(Date.now() + 10 * 60 * 1000); const seatHold = await tx.seatHold.create({ data: { scheduleId, seatIds, passengerId, expiresAt }, }); await tx.seat.updateMany({ where: { id: { in: seatIds } }, data: { status: 'HELD', heldUntil: expiresAt } }); console.log(' → Seats held successfully'); return { holdId: seatHold.id, expiresAt, seats: seatIds.length }; }); } async function confirmBookingTransaction(holdId: string, bookingId: string, segments: any[]) { return prisma.$transaction(async (tx) => { console.log(' → Starting booking confirmation transaction...'); const hold = await tx.seatHold.findUnique({ where: { id: holdId } }); if (!hold || hold.expiresAt < new Date()) throw new Error('Hold expired or not found'); const booking = await tx.booking.create({ data: { id: bookingId, bookingRef: 'BK' + Date.now().toString().slice(-6), passengerId: hold.passengerId, scheduleId: hold.scheduleId, status: 'CONFIRMED', totalMinor: 45000, currency: 'ETB', }, }); const journey = await tx.journey.create({ data: { passengerId: hold.passengerId, status: 'CONFIRMED', totalMinor: 45000, currency: 'ETB' }, }); for (const seatId of hold.seatIds) { for (let i = 0; i < segments.length; i++) { await tx.journeySegment.create({ data: { journeyId: journey.id, scheduleId: hold.scheduleId, segmentOrder: i + 1, seatId, departureStationId: segments[i].fromStationId, arrivalStationId: segments[i].toStationId, }, }); } } for (const seatId of hold.seatIds) { await tx.bookingSeat.create({ data: { bookingId, seatId, passengerName: 'Kelemu Ketsela' } }); } await tx.seat.updateMany({ where: { id: { in: hold.seatIds } }, data: { status: 'BOOKED', heldUntil: null } }); await tx.seatHold.delete({ where: { id: holdId } }); console.log(' → Booking confirmed successfully'); return { bookingId, bookingRef: booking.bookingRef, confirmedSeats: hold.seatIds.length, segments: segments.length }; }); } async function simulateTripProgress(scheduleId: string, bookedSegments: any[]) { console.log(' → Simulating trip progress...'); for (const segment of bookedSegments) { console.log(` → Train approaching ${segment.toName}...`); await prisma.tripLiveStatus.upsert({ where: { scheduleId }, update: { currentLocationLabel: segment.toName, progressPercent: Math.round((segment.toSequence / 4) * 100) }, create: { scheduleId, state: 'EN_ROUTE', currentLocationLabel: segment.toName, progressPercent: Math.round((segment.toSequence / 4) * 100), delayMinutes: 0, }, }); if (segment.toName === 'Dire Dawa') { console.log(' → Passengers reached destination, releasing seats...'); await releaseSeatsAtStation(scheduleId, segment.toStationId); } await new Promise(resolve => setTimeout(resolve, 2000)); } } async function releaseSeatsAtStation(scheduleId: string, stationId: string) { return prisma.$transaction(async (tx) => { const completedSegments = await tx.journeySegment.findMany({ where: { scheduleId, arrivalStationId: stationId }, include: { journey: { include: { journeySegments: { where: { scheduleId } } } } }, }); const seatsToRelease: string[] = []; for (const segment of completedSegments) { const passengerSegments = segment.journey.journeySegments.filter((js: any) => js.seatId === segment.seatId); const maxOrder = Math.max(...passengerSegments.map((js: any) => js.segmentOrder)); if (segment.segmentOrder === maxOrder) seatsToRelease.push(segment.seatId!); } if (seatsToRelease.length > 0) { await tx.seat.updateMany({ where: { id: { in: seatsToRelease } }, data: { status: 'AVAILABLE' } }); console.log(` → Released ${seatsToRelease.length} seats at station`); } return seatsToRelease; }); } async function checkOverlappingReservations(tx: any, scheduleId: string, seatId: string, segments: any[]) { const activeHolds = await tx.seatHold.findMany({ where: { scheduleId, seatIds: { has: seatId }, expiresAt: { gt: new Date() } }, }); const activeBookings = await tx.journeySegment.findMany({ where: { scheduleId, seatId, journey: { status: { in: ['PENDING_PAYMENT', 'CONFIRMED'] } }, }, }); return [...activeHolds, ...activeBookings]; } if (require.main === module) { exampleBookingFlow() .then(() => console.log('\n=== EXAMPLES COMPLETED ===')) .catch(console.error) .finally(() => prisma.$disconnect()); } export { exampleBookingFlow, getJourneySegments, holdSeatsTransaction, confirmBookingTransaction, simulateTripProgress, releaseSeatsAtStation, checkOverlappingReservations, };