Added optimization for search result and fix sea map

This commit is contained in:
Roba Boru
2026-06-30 16:45:51 +03:00
parent 35a2a200d6
commit 6e81090f8c
4 changed files with 229 additions and 173 deletions

View File

@@ -146,6 +146,96 @@ export class SegmentsService {
return true;
}
/**
* Batch availability check for multiple seats on a single schedule.
* Replaces N×isSeatFreeForLeg calls with 2 queries total.
* Returns a Set of seat IDs that are free for [reqFrom, reqTo).
*/
async getFreeSeatIds(
scheduleId: string,
seatIds: string[],
stopTimesForSeqLookup: ReadonlyArray<{ stationId: string; sequence: number }>,
reqFrom: number,
reqTo: number,
): Promise<Set<string>> {
if (seatIds.length === 0) return new Set();
const seqOf = (stationId: string) =>
stopTimesForSeqLookup.find(s => s.stationId === stationId)?.sequence;
const seatIdSet = new Set(seatIds);
const now = new Date();
const [allHolds, bookedLegs] = await Promise.all([
this.prisma.seatHold.findMany({
where: { scheduleId, expiresAt: { gt: now } },
select: { seatIds: true, createdBy: true },
}),
this.prisma.journeySegment.findMany({
where: {
scheduleId,
seatId: { in: seatIds },
journey: { status: { in: ['CONFIRMED', 'PENDING_PAYMENT'] } },
},
select: { seatId: true, journeyId: true, departureStationId: true, arrivalStationId: true },
}),
]);
// Determine which seats are blocked by active holds
const holdBlockedSeats = new Set<string>();
for (const hold of allHolds) {
let holdFrom: number | undefined;
let holdTo: number | undefined;
try {
if (hold.createdBy) {
const meta = JSON.parse(hold.createdBy as string);
holdFrom = seqOf(meta.originStationId);
holdTo = seqOf(meta.destinationStationId);
}
} catch { /* ignore */ }
for (const sid of hold.seatIds) {
if (!seatIdSet.has(sid)) continue;
// Conservative block if leg can't be resolved; otherwise check overlap
if (holdFrom === undefined || holdTo === undefined || (holdFrom < reqTo && reqFrom < holdTo)) {
holdBlockedSeats.add(sid);
}
}
}
// Build full journey ranges per seat (group multi-leg journeys)
const journeyRangesBySeat = new Map<string, Map<string, { from: number; to: number }>>();
for (const leg of bookedLegs) {
if (!leg.seatId || !leg.journeyId || !leg.departureStationId || !leg.arrivalStationId) continue;
const depSeq = seqOf(leg.departureStationId);
const arrSeq = seqOf(leg.arrivalStationId);
if (depSeq === undefined || arrSeq === undefined) continue;
let rangeMap = journeyRangesBySeat.get(leg.seatId);
if (!rangeMap) { rangeMap = new Map(); journeyRangesBySeat.set(leg.seatId, rangeMap); }
const existing = rangeMap.get(leg.journeyId);
rangeMap.set(leg.journeyId, existing
? { from: Math.min(existing.from, depSeq), to: Math.max(existing.to, arrSeq) }
: { from: depSeq, to: arrSeq });
}
const freeSeats = new Set<string>();
for (const seatId of seatIds) {
if (holdBlockedSeats.has(seatId)) continue;
let blocked = false;
const rangeMap = journeyRangesBySeat.get(seatId);
if (rangeMap) {
for (const { from, to } of rangeMap.values()) {
if (from < reqTo && reqFrom < to) { blocked = true; break; }
}
}
if (!blocked) freeSeats.add(seatId);
}
return freeSeats;
}
/** Legacy wrapper used by EnhancedSeatsService.getOverlappingReservations */
async getOverlappingReservations(
scheduleId: string,