mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 01:18:18 +00:00
Tour package booking, app release, new endpoints, more updates and fixes
This commit is contained in:
@@ -28,6 +28,25 @@ import { IamGuard } from "../../common/iam-adapter";
|
||||
export class SeatsController {
|
||||
constructor(private service: SeatsService) {}
|
||||
|
||||
// ── Coach Availability ────────────────────────────────────────────────────
|
||||
@Get('coaches/:scheduleId')
|
||||
@SetMetadata('isPublic', true)
|
||||
@ApiOperation({
|
||||
summary: 'List coaches with remaining seat counts for a schedule',
|
||||
description: 'Returns each coach assigned to the schedule with total, available, held, and booked seat counts. Optionally scoped to a specific origin→destination leg.',
|
||||
})
|
||||
@ApiParam({ name: 'scheduleId', description: 'TrainSchedule UUID' })
|
||||
@ApiQuery({ name: 'originStationId', required: false, description: 'Scope availability to this origin station' })
|
||||
@ApiQuery({ name: 'destinationStationId', required: false, description: 'Scope availability to this destination station' })
|
||||
@ApiResponse({ status: 200, description: 'Coaches with seat availability counts' })
|
||||
getCoachesWithAvailability(
|
||||
@Param('scheduleId') scheduleId: string,
|
||||
@Query('originStationId') originStationId?: string,
|
||||
@Query('destinationStationId') destinationStationId?: string,
|
||||
) {
|
||||
return this.service.getCoachesWithAvailability(scheduleId, originStationId, destinationStationId);
|
||||
}
|
||||
|
||||
// ── Seat Map ──────────────────────────────────────────────────────────────
|
||||
@Get("seatmap/:scheduleId")
|
||||
@SetMetadata('isPublic', true)
|
||||
|
||||
@@ -367,7 +367,24 @@ export class SeatsService {
|
||||
where: { scheduleId: dto.scheduleId },
|
||||
select: { stationId: true, sequence: true },
|
||||
});
|
||||
const seqOf = (stationId: string) => stopTimes.find(s => s.stationId === stationId)?.sequence;
|
||||
|
||||
// When no stop times exist, fall back to the schedule's own origin/destination
|
||||
// with synthetic sequences so the hold can still be created.
|
||||
let effectiveStopTimes = stopTimes;
|
||||
if (stopTimes.length === 0) {
|
||||
const sched = await tx.trainSchedule.findUnique({
|
||||
where: { id: dto.scheduleId },
|
||||
select: { originStationId: true, destinationStationId: true },
|
||||
});
|
||||
if (sched) {
|
||||
effectiveStopTimes = [
|
||||
{ stationId: sched.originStationId, sequence: 0 },
|
||||
{ stationId: sched.destinationStationId, sequence: 1 },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const seqOf = (stationId: string) => effectiveStopTimes.find(s => s.stationId === stationId)?.sequence;
|
||||
const reqFrom = seqOf(dto.originStationId);
|
||||
const reqTo = seqOf(dto.destinationStationId);
|
||||
|
||||
@@ -604,6 +621,56 @@ export class SeatsService {
|
||||
await this.prisma.journey.deleteMany({ where: { bookingId } as any });
|
||||
}
|
||||
|
||||
async getCoachesWithAvailability(scheduleId: string, originStationId?: string, destinationStationId?: string) {
|
||||
const schedule = await this.prisma.trainSchedule.findUnique({
|
||||
where: { id: scheduleId },
|
||||
select: { originStationId: true, destinationStationId: true },
|
||||
});
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
|
||||
const assignments = await this.prisma.coachAssignment.findMany({
|
||||
where: { scheduleId },
|
||||
include: {
|
||||
coach: {
|
||||
include: {
|
||||
seats: { select: { id: true, status: true, seatNumber: true } },
|
||||
coachType: { include: { seatClasses: { select: { name: true } } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { positionNumber: 'asc' },
|
||||
});
|
||||
|
||||
const allSeatIds = assignments.flatMap(a => a.coach.seats.map(s => s.id));
|
||||
const effectiveStatuses = await this.resolveEffectiveStatuses(
|
||||
scheduleId,
|
||||
allSeatIds,
|
||||
originStationId ?? schedule.originStationId,
|
||||
destinationStationId ?? schedule.destinationStationId,
|
||||
);
|
||||
|
||||
return assignments.map(a => {
|
||||
const seats = a.coach.seats.filter(s => s.seatNumber && !s.seatNumber.startsWith('-'));
|
||||
const totalSeats = seats.length;
|
||||
const unavailable = seats.filter(s => {
|
||||
const status = effectiveStatuses.get(s.id) ?? s.status;
|
||||
return status === 'HELD' || status === 'BOOKED' || status === 'BLOCKED';
|
||||
}).length;
|
||||
|
||||
return {
|
||||
coachId: a.coach.id,
|
||||
coachNumber: a.coach.number,
|
||||
positionNumber: a.positionNumber,
|
||||
coachTypeName: a.coach.coachType?.name ?? '',
|
||||
seatClasses: a.coach.coachType?.seatClasses.map(sc => sc.name) ?? [],
|
||||
totalSeats,
|
||||
availableSeats: totalSeats - unavailable,
|
||||
heldSeats: seats.filter(s => (effectiveStatuses.get(s.id) ?? s.status) === 'HELD').length,
|
||||
bookedSeats: seats.filter(s => (effectiveStatuses.get(s.id) ?? s.status) === 'BOOKED').length,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async autoAssignSeats(scheduleId: string, count: number, seatClassName: string): Promise<string[]> {
|
||||
const seats = await this.prisma.seat.findMany({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user