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

@@ -3,11 +3,7 @@ import { Cron } from '@nestjs/schedule';
import { PrismaService } from '../../common/prisma.service';
import { SmsClientService } from '../notifications/sms-client.service';
import { CurrencyService } from '../currency/currency.service';
/** Maximum time (hours) a passenger has to pay after booking. */
const MAX_PAYMENT_HOURS = 2;
/** Minutes before departure: cutoff for new bookings and payment deadline. */
const CUTOFF_MINUTES = 30;
import { MAX_PAYMENT_HOURS, CUTOFF_MINUTES, computePaymentDeadline } from '../../common/utils/payment-deadline.utils';
// Retention windows
const OTP_RETENTION_HOURS = 1;
@@ -16,15 +12,6 @@ const AUDIT_LOG_RETENTION_DAYS = 365;
const WEBHOOK_EVENT_RETENTION_DAYS = 90;
const GATE_LOG_RETENTION_DAYS = 180;
/**
* payment_deadline = MIN(booking_time + 2h, departure_time - 30min)
*/
function computePaymentDeadline(createdAt: Date, departureAt: Date): Date {
const maxDeadline = new Date(createdAt.getTime() + MAX_PAYMENT_HOURS * 60 * 60 * 1000);
const cutoffDeadline = new Date(departureAt.getTime() - CUTOFF_MINUTES * 60 * 1000);
return maxDeadline < cutoffDeadline ? maxDeadline : cutoffDeadline;
}
function fmtTime(d: Date): string {
return d.toLocaleTimeString('en-GB', {
hour: '2-digit',
@@ -192,6 +179,7 @@ export class TasksService {
},
},
paymentIntent: { select: { method: true } },
seats: { select: { seatId: true } },
},
});
@@ -205,9 +193,21 @@ export class TasksService {
const paymentDeadline = computePaymentDeadline(createdAt, dep);
if (now < paymentDeadline) continue;
// 1. Release held seats (Journey rows are the occupancy source of truth)
// 1a. Release held seats (Journey rows are the occupancy source of truth once paid)
await this.prisma.journey.deleteMany({ where: { bookingId: booking.id } as any });
// 1b. Also release the SeatHold(s) covering this booking's seats — SeatsService
// extends these to the payment deadline when the booking is created, so without
// this they'd otherwise keep the seat locked for up to MAX_PAYMENT_HOURS even
// though the booking is now cancelled. Scoped to this booking's own schedule,
// since the same physical Seat row is reused across other recurring dates.
const seatIds = booking.seats.map(s => s.seatId);
if (seatIds.length > 0) {
await this.prisma.seatHold.deleteMany({
where: { scheduleId: booking.scheduleId, seatIds: { hasSome: seatIds } },
});
}
// 2. Audit record (no refund — payment was never completed)
await this.prisma.bookingCancellation.create({
data: {