Extend seat hold time if booking created

This commit is contained in:
Roba Boru
2026-07-15 09:15:49 +03:00
parent 7cca901913
commit 6a9227b0f6
3 changed files with 82 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import { Cron, CronExpression } from '@nestjs/schedule';
import { SegmentsService } from '../segments/segments.service';
import { SystemConfigService, CONFIG_KEYS } from '../system-config/system-config.service';
import { AuditService } from '../../common/audit.service';
import { computePaymentDeadline } from '../../common/utils/payment-deadline.utils';
@Injectable()
export class SeatsService {
@@ -625,7 +626,50 @@ export class SeatsService {
return { released: true, holdId };
}
async confirmSeats(_seatIds: string[]) {}
// Called right after a booking (PNR) is created, and again on successful payment.
// Extends the SeatHold(s) covering these seats to the booking's actual payment
// deadline — the same MIN(createdAt + 2h, departureAt - 30min) window TasksService
// uses to auto-cancel unpaid bookings — instead of leaving them on the original
// short seat-selection hold (5 min by default). Without this, the hold could expire
// while the customer was still on the payment page, and a second customer could
// hold/book the exact same seat out from under them.
async confirmSeats(seatIds: string[], now: Date = new Date()): Promise<void> {
if (seatIds.length === 0) return;
const holds = await this.prisma.seatHold.findMany({
where: { seatIds: { hasSome: seatIds } },
select: { id: true, scheduleId: true, expiresAt: true },
});
if (holds.length === 0) return;
const scheduleIds = Array.from(new Set(holds.map(h => h.scheduleId)));
const schedules = await this.prisma.trainSchedule.findMany({
where: { id: { in: scheduleIds } },
select: { id: true, departureAt: true },
});
const departureById = new Map(schedules.map(s => [s.id, s.departureAt]));
let extended = 0;
await Promise.all(
holds.map(async (hold) => {
const departureAt = departureById.get(hold.scheduleId);
if (!departureAt) return;
const deadline = computePaymentDeadline(now, departureAt);
// Only ever extend forward — never shorten a hold that's already valid longer
// than the payment deadline would give it (e.g. a second confirmSeats call on
// the same booking, or a hold that was already extended).
if (deadline <= hold.expiresAt) return;
await this.prisma.seatHold.update({ where: { id: hold.id }, data: { expiresAt: deadline } });
extended++;
}),
);
if (extended > 0) {
this.logger.log(
`Extended ${extended} seat hold(s) covering ${seatIds.length} seat(s) to their booking's payment deadline`,
);
}
}
// Delete the Journey (and its JourneySegments) scoped to this booking.
async releaseSeats(bookingId: string) {