Merge pull request #815 from Tria-plc/alpha

Fix blocked seat count on booking result
This commit is contained in:
robiman
2026-07-19 18:52:52 +03:00
committed by GitHub
3 changed files with 38 additions and 7 deletions

View File

@@ -390,8 +390,8 @@ export class SearchService {
// Exclude schedules with no seats at all
if (allValidSeatIds.length === 0) return null;
// Run availability batch and fare calculation in parallel
const [freeSeats, faresByClass] = await Promise.all([
// Run availability batch, fare calculation, and schedule-scoped seat blocks in parallel
const [freeSeatsRaw, faresByClass, scheduleBlocks] = await Promise.all([
this.segmentsService.getFreeSeatIds(
schedule.id,
allValidSeatIds,
@@ -400,7 +400,17 @@ export class SearchService {
destStop.sequence,
),
this.calculateFaresForSegment(schedule, originStationId, destinationStationId, nationality),
this.prisma.seatBlock.findMany({
where: {
scheduleId: schedule.id,
seatId: { in: allValidSeatIds },
},
select: { seatId: true },
}),
]);
const scheduleBlockedIds = new Set(scheduleBlocks.map((b: any) => b.seatId));
// Seats that are free from holds/bookings AND not schedule-blocked
const freeSeats = new Set([...freeSeatsRaw].filter(id => !scheduleBlockedIds.has(id)));
// Compute per-class availability using the pre-computed free seat set. A coach type
// has separate seat classes per nationality tier (e.g. "VIP Bed Upper (Local)" AND

View File

@@ -325,6 +325,18 @@ export class SeatsService {
throw new ConflictException(`Seat(s) ${blocked.map(s => s.seatNumber).join(', ')} are already taken`);
const seatLabelById = Object.fromEntries(seats.map(s => [s.id, s.seatNumber]));
// Schedule-scoped blocks prevent holding a seat on this specific schedule
// even if its global Seat.status is AVAILABLE.
const scheduleBlockRecords = await tx.seatBlock.findMany({
where: { scheduleId: dto.scheduleId, seatId: { in: seatIds } },
select: { seatId: true },
});
if (scheduleBlockRecords.length > 0) {
const blockedNums = scheduleBlockRecords.map(b => seatLabelById[b.seatId]).join(', ');
throw new ConflictException(`Seat(s) ${blockedNums} are blocked for this schedule`);
}
const stopTimes = await tx.tripStopTime.findMany({
where: { scheduleId: dto.scheduleId },
select: { stationId: true, sequence: true },
@@ -711,11 +723,18 @@ export class SeatsService {
const reqFrom = seqOf(schedule.originStationId) ?? 0;
const reqTo = seqOf(schedule.destinationStationId) ?? stopTimes.length;
const unavailable = await this.segmentsService.getSeatAvailabilityMap(
scheduleId, allSeatIds, stopTimes, reqFrom, reqTo,
);
const [unavailable, scheduleBlocks] = await Promise.all([
this.segmentsService.getSeatAvailabilityMap(
scheduleId, allSeatIds, stopTimes, reqFrom, reqTo,
),
this.prisma.seatBlock.findMany({
where: { scheduleId, seatId: { in: allSeatIds } },
select: { seatId: true },
}),
]);
const scheduleBlockedIds = new Set(scheduleBlocks.map(b => b.seatId));
const availableSeats = seats.filter(s => !unavailable.has(s.id));
const availableSeats = seats.filter(s => !unavailable.has(s.id) && !scheduleBlockedIds.has(s.id));
if (availableSeats.length < count) {
throw new ConflictException(`Only ${availableSeats.length} seats available, requested ${count}`);

View File

@@ -33,6 +33,8 @@ export default function PassengersReportPage() {
const [scheduleId, setScheduleId] = useState('');
const [tab, setTab] = useState<Tab>('occupancy');
const [listSearch, setListSearch] = useState('');
const [filterCoach, setFilterCoach] = useState('');
const [filterOrigin, setFilterOrigin] = useState('');
const { data: schedulesRaw, isLoading: loadingSchedules } = useQuery<ScheduleOption[]>({
queryKey: ['report-schedules'],
@@ -98,7 +100,7 @@ export default function PassengersReportPage() {
<select
className="input"
value={scheduleId}
onChange={e => { setScheduleId(e.target.value); setTab('occupancy'); setListSearch(''); }}
onChange={e => { setScheduleId(e.target.value); setTab('occupancy'); setListSearch(''); setFilterCoach(''); setFilterOrigin(''); }}
disabled={loadingSchedules}
>
<option value="">{loadingSchedules ? 'Loading schedules…' : 'Select a schedule…'}</option>