mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 00:50:56 +00:00
239 lines
8.4 KiB
TypeScript
239 lines
8.4 KiB
TypeScript
/**
|
|
* 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,
|
|
};
|