Tour package booking, app release, new endpoints, more updates and fixes

This commit is contained in:
Stephanos A
2026-07-05 00:28:06 +03:00
parent 868639084c
commit 595be6e123
68 changed files with 2773 additions and 787 deletions

View File

@@ -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)

View File

@@ -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: {