mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 02:00:56 +00:00
226 lines
6.1 KiB
TypeScript
226 lines
6.1 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { PrismaService } from '../../common/prisma.service';
|
|
import { EnhancedSeatsService } from './enhanced-seats.service';
|
|
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
|
|
|
@Injectable()
|
|
export class TripProgressService {
|
|
constructor(
|
|
private prisma: PrismaService,
|
|
private enhancedSeatsService: EnhancedSeatsService,
|
|
private eventEmitter: EventEmitter2
|
|
) {}
|
|
|
|
/**
|
|
* Update trip progress and trigger seat releases
|
|
*/
|
|
async updateTripProgress(tripId: string, currentStationId: string, progressPercent: number) {
|
|
return this.prisma.$transaction(async (tx) => {
|
|
// 1. Update trip live status
|
|
await tx.tripLiveStatus.upsert({
|
|
where: { scheduleId: tripId },
|
|
update: {
|
|
currentLocationLabel: currentStationId,
|
|
progressPercent,
|
|
updatedAt: new Date()
|
|
},
|
|
create: {
|
|
scheduleId: tripId,
|
|
state: 'EN_ROUTE',
|
|
currentLocationLabel: currentStationId,
|
|
progressPercent,
|
|
delayMinutes: 0,
|
|
updatedAt: new Date()
|
|
}
|
|
});
|
|
|
|
// 2. Get station name for comparison
|
|
const station = await tx.station.findUnique({
|
|
where: { id: currentStationId }
|
|
});
|
|
|
|
if (station) {
|
|
// 3. Trigger seat release for passengers reaching destination
|
|
const releaseResult = await this.enhancedSeatsService.releaseSeats(tripId, currentStationId);
|
|
|
|
// 4. Emit progress update event
|
|
this.eventEmitter.emit('trip.progress.updated', {
|
|
tripId,
|
|
currentStation: station.name,
|
|
progressPercent,
|
|
releasedSeats: releaseResult.releasedSeats
|
|
});
|
|
|
|
return {
|
|
tripId,
|
|
currentStation: station.name,
|
|
progressPercent,
|
|
releasedSeats: releaseResult.releasedSeats.length,
|
|
updatedAt: new Date()
|
|
};
|
|
}
|
|
|
|
return { tripId, currentStation: currentStationId, progressPercent, releasedSeats: 0 };
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Simulate trip progress (for testing/demo)
|
|
*/
|
|
async simulateTripProgress(tripId: string) {
|
|
const trip = await this.prisma.trainSchedule.findUnique({
|
|
where: { id: tripId },
|
|
include: {
|
|
stopTimes: {
|
|
include: { station: true },
|
|
orderBy: { sequence: 'asc' }
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!trip) {
|
|
throw new Error('Trip not found');
|
|
}
|
|
|
|
// Simulate progress through each station
|
|
for (let i = 0; i < trip.stopTimes.length; i++) {
|
|
const stopTime = trip.stopTimes[i];
|
|
const progressPercent = Math.round((i / (trip.stopTimes.length - 1)) * 100);
|
|
|
|
await this.updateTripProgress(tripId, stopTime.stationId, progressPercent);
|
|
|
|
// Emit station arrival event
|
|
this.eventEmitter.emit('trip.station.arrived', {
|
|
tripId,
|
|
stationId: stopTime.stationId,
|
|
stationName: stopTime.station.name,
|
|
sequence: stopTime.sequence,
|
|
progressPercent
|
|
});
|
|
|
|
// Wait 30 seconds between stations (for demo)
|
|
await new Promise(resolve => setTimeout(resolve, 30000));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle trip completion
|
|
*/
|
|
@OnEvent('trip.completed')
|
|
async handleTripCompleted(payload: { tripId: string }) {
|
|
// Release all remaining seats for this trip
|
|
const trip = await this.prisma.trainSchedule.findUnique({
|
|
where: { id: payload.tripId },
|
|
include: {
|
|
coachAssignments: {
|
|
include: {
|
|
coach: {
|
|
include: {
|
|
seats: {
|
|
where: { status: 'BOOKED' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (trip) {
|
|
const bookedSeatIds = trip.coachAssignments.flatMap(assignment =>
|
|
assignment.coach.seats.map(seat => seat.id)
|
|
);
|
|
|
|
if (bookedSeatIds.length > 0) {
|
|
await this.prisma.seat.updateMany({
|
|
where: { id: { in: bookedSeatIds } },
|
|
data: { status: 'AVAILABLE' }
|
|
});
|
|
|
|
this.eventEmitter.emit('trip.seats.released', {
|
|
tripId: payload.tripId,
|
|
releasedSeats: bookedSeatIds
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Background job to expire holds every minute
|
|
*/
|
|
@Cron(CronExpression.EVERY_MINUTE)
|
|
async expireHoldsJob() {
|
|
try {
|
|
const result = await this.enhancedSeatsService.expireHolds();
|
|
if (result.expiredHolds > 0) {
|
|
console.log(`Expired ${result.expiredHolds} holds, released ${result.releasedSeats.length} seats`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error expiring holds:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current trip status with seat availability
|
|
*/
|
|
async getTripStatus(tripId: string) {
|
|
const trip = await this.prisma.trainSchedule.findUnique({
|
|
where: { id: tripId },
|
|
include: {
|
|
liveStatus: true,
|
|
stopTimes: {
|
|
include: { station: true },
|
|
orderBy: { sequence: 'asc' }
|
|
},
|
|
coachAssignments: {
|
|
include: {
|
|
coach: {
|
|
include: { seats: true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!trip) {
|
|
throw new Error('Trip not found');
|
|
}
|
|
|
|
const seatSummary = {
|
|
total: 0,
|
|
available: 0,
|
|
held: 0,
|
|
booked: 0,
|
|
blocked: 0
|
|
};
|
|
|
|
trip.coachAssignments.forEach(assignment => {
|
|
assignment.coach.seats.forEach(seat => {
|
|
seatSummary.total++;
|
|
const status = seat.status.toLowerCase() as keyof typeof seatSummary;
|
|
if (status in seatSummary) {
|
|
seatSummary[status]++;
|
|
}
|
|
});
|
|
});
|
|
|
|
return {
|
|
tripId,
|
|
status: trip.status,
|
|
currentLocation: trip.liveStatus?.currentLocationLabel,
|
|
progressPercent: trip.liveStatus?.progressPercent || 0,
|
|
delayMinutes: trip.liveStatus?.delayMinutes || 0,
|
|
stations: trip.stopTimes.map(st => ({
|
|
id: st.stationId,
|
|
name: st.station.name,
|
|
sequence: st.sequence,
|
|
plannedArrival: st.plannedArrivalAt,
|
|
plannedDeparture: st.plannedDepartureAt,
|
|
actualArrival: st.actualArrivalAt
|
|
})),
|
|
seatSummary,
|
|
lastUpdated: trip.liveStatus?.updatedAt
|
|
};
|
|
}
|
|
} |