mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
Cut off time configuration
This commit is contained in:
@@ -14,6 +14,7 @@ import { Currency, PassengerCategory, IdDocumentType } from '@prisma/client';
|
||||
import { resolveCurrencyFromNationality } from '../fare-engine/fare-engine.dto';
|
||||
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
|
||||
import { AuditService } from '../../common/audit.service';
|
||||
import { resolveBookingSegment } from '../../common/utils/segment-resolver.utils';
|
||||
|
||||
function generateRef(): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
@@ -140,7 +141,7 @@ export class BookingsService {
|
||||
take: pageSize,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true, stopTimes: { include: { station: true } } } },
|
||||
paymentIntent: true,
|
||||
seats: { include: { seat: true } },
|
||||
priceTier: { select: { priceMinor: true } },
|
||||
@@ -148,31 +149,34 @@ export class BookingsService {
|
||||
}),
|
||||
this.prisma.booking.count({ where }),
|
||||
]);
|
||||
|
||||
|
||||
return {
|
||||
items: items.map(booking => ({
|
||||
id: booking.id,
|
||||
bookingRef: booking.bookingRef,
|
||||
status: booking.status,
|
||||
totalMinor: booking.displayTotalMinor ?? resolvePackageRoundTripTotal(booking, (booking as any).priceTier?.priceMinor, booking.adultCount, booking.childCount),
|
||||
currency: booking.displayCurrency,
|
||||
displayCurrency: booking.displayCurrency,
|
||||
displayTotalMinor: booking.displayTotalMinor,
|
||||
adultCount: booking.adultCount,
|
||||
childCount: booking.childCount,
|
||||
bookingType: booking.bookingType,
|
||||
returnLegStatus: (booking as any).returnLegStatus ?? null,
|
||||
createdAt: booking.createdAt,
|
||||
schedule: {
|
||||
train: booking.schedule.train,
|
||||
originStation: booking.schedule.originStation,
|
||||
destinationStation: booking.schedule.destinationStation,
|
||||
departureAt: booking.schedule.departureAt,
|
||||
arrivalAt: booking.schedule.arrivalAt,
|
||||
},
|
||||
paymentIntent: booking.paymentIntent,
|
||||
seatCount: booking.seats.length,
|
||||
})),
|
||||
items: items.map(booking => {
|
||||
const segment = resolveBookingSegment((booking as any).schedule, (booking as any).originStationId, (booking as any).destinationStationId);
|
||||
return {
|
||||
id: booking.id,
|
||||
bookingRef: booking.bookingRef,
|
||||
status: booking.status,
|
||||
totalMinor: booking.displayTotalMinor ?? resolvePackageRoundTripTotal(booking, (booking as any).priceTier?.priceMinor, booking.adultCount, booking.childCount),
|
||||
currency: booking.displayCurrency,
|
||||
displayCurrency: booking.displayCurrency,
|
||||
displayTotalMinor: booking.displayTotalMinor,
|
||||
adultCount: booking.adultCount,
|
||||
childCount: booking.childCount,
|
||||
bookingType: booking.bookingType,
|
||||
returnLegStatus: (booking as any).returnLegStatus ?? null,
|
||||
createdAt: booking.createdAt,
|
||||
schedule: {
|
||||
train: booking.schedule.train,
|
||||
originStation: segment.origin,
|
||||
destinationStation: segment.destination,
|
||||
departureAt: segment.departureAt,
|
||||
arrivalAt: segment.arrivalAt,
|
||||
},
|
||||
paymentIntent: booking.paymentIntent,
|
||||
seatCount: booking.seats.length,
|
||||
};
|
||||
}),
|
||||
meta: {
|
||||
page,
|
||||
pageSize,
|
||||
@@ -270,7 +274,7 @@ export class BookingsService {
|
||||
take: pageSize,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true, stopTimes: { include: { station: true } } } },
|
||||
paymentIntent: { select: { method: true, status: true, amountMinor: true, currency: true } },
|
||||
seats: { select: { id: true } },
|
||||
priceTier: { select: { priceMinor: true } },
|
||||
@@ -294,7 +298,9 @@ export class BookingsService {
|
||||
this.prisma.packageBooking.count({ where: pkgWhere }),
|
||||
]);
|
||||
|
||||
const mappedBookings = items.map(booking => ({
|
||||
const mappedBookings = items.map(booking => {
|
||||
const segment = resolveBookingSegment((booking as any).schedule, (booking as any).originStationId, (booking as any).destinationStationId);
|
||||
return {
|
||||
id: booking.id,
|
||||
bookingRef: booking.bookingRef,
|
||||
status: booking.status,
|
||||
@@ -309,14 +315,15 @@ export class BookingsService {
|
||||
createdAt: booking.createdAt,
|
||||
schedule: {
|
||||
train: booking.schedule.train,
|
||||
originStation: booking.schedule.originStation,
|
||||
destinationStation: booking.schedule.destinationStation,
|
||||
departureAt: booking.schedule.departureAt,
|
||||
arrivalAt: booking.schedule.arrivalAt,
|
||||
originStation: segment.origin,
|
||||
destinationStation: segment.destination,
|
||||
departureAt: segment.departureAt,
|
||||
arrivalAt: segment.arrivalAt,
|
||||
},
|
||||
payment: booking.paymentIntent ?? undefined,
|
||||
seatCount: booking.seats.length,
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
const mappedPkg = pkgItems.map((b: any) => ({
|
||||
id: b.id,
|
||||
@@ -397,7 +404,7 @@ export class BookingsService {
|
||||
take: pageSize,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true, stopTimes: { include: { station: true } } } },
|
||||
paymentIntent: true,
|
||||
seats: { include: { seat: true } },
|
||||
priceTier: { select: { priceMinor: true } },
|
||||
@@ -405,9 +412,11 @@ export class BookingsService {
|
||||
}),
|
||||
this.prisma.booking.count({ where }),
|
||||
]);
|
||||
|
||||
|
||||
return {
|
||||
items: items.map(booking => ({
|
||||
items: items.map(booking => {
|
||||
const segment = resolveBookingSegment((booking as any).schedule, (booking as any).originStationId, (booking as any).destinationStationId);
|
||||
return {
|
||||
id: booking.id,
|
||||
bookingRef: booking.bookingRef,
|
||||
status: booking.status,
|
||||
@@ -422,14 +431,15 @@ export class BookingsService {
|
||||
createdAt: booking.createdAt,
|
||||
schedule: {
|
||||
train: booking.schedule.train,
|
||||
originStation: booking.schedule.originStation,
|
||||
destinationStation: booking.schedule.destinationStation,
|
||||
departureAt: booking.schedule.departureAt,
|
||||
arrivalAt: booking.schedule.arrivalAt,
|
||||
originStation: segment.origin,
|
||||
destinationStation: segment.destination,
|
||||
departureAt: segment.departureAt,
|
||||
arrivalAt: segment.arrivalAt,
|
||||
},
|
||||
paymentIntent: booking.paymentIntent,
|
||||
seatCount: booking.seats.length,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
meta: {
|
||||
page,
|
||||
pageSize,
|
||||
@@ -532,7 +542,7 @@ export class BookingsService {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
passenger: { select: { id: true, iamUserId: true } },
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true, stopTimes: { include: { station: true } } } },
|
||||
paymentIntent: true,
|
||||
seats: { include: { seat: true } },
|
||||
priceTier: { select: { priceMinor: true } },
|
||||
@@ -554,9 +564,10 @@ export class BookingsService {
|
||||
const iam = booking.passenger?.iamUserId ? iamMap.get(booking.passenger.iamUserId) : undefined;
|
||||
const passengerDetails = booking.seats.map((s: any) => ({ name: s.passengerName, category: s.passengerCategory }));
|
||||
const uniquePassengers = Array.from(new Map(passengerDetails.map((p: any) => [p.name, p])).values());
|
||||
const segment = booking.schedule ? resolveBookingSegment(booking.schedule, booking.originStationId, booking.destinationStationId) : null;
|
||||
return {
|
||||
id: booking.id, bookingRef: booking.bookingRef, status: booking.status,
|
||||
totalMinor: booking.displayTotalMinor ?? resolvePackageRoundTripTotal(booking, booking.priceTier?.priceMinor, booking.adultCount, booking.childCount),
|
||||
totalMinor: booking.displayTotalMinor ?? resolvePackageRoundTripTotal(booking, booking.priceTier?.priceMinor, booking.adultCount, booking.childCount),
|
||||
currency: booking.displayCurrency,
|
||||
displayCurrency: booking.displayCurrency, displayTotalMinor: booking.displayTotalMinor,
|
||||
contactEmail: booking.contactEmail, contactPhone: booking.contactPhone,
|
||||
@@ -567,11 +578,11 @@ export class BookingsService {
|
||||
passenger: iam ? { fullName: iam.name?.en ?? iam.name?.am ?? null, email: iam.email, phone: iam.phone_number } : null,
|
||||
passengerNames: [...new Set(booking.seats.map((s: any) => s.passengerName))],
|
||||
passengers: uniquePassengers,
|
||||
schedule: booking.schedule ? {
|
||||
schedule: segment ? {
|
||||
train: booking.schedule.train,
|
||||
originStation: booking.schedule.originStation,
|
||||
destinationStation: booking.schedule.destinationStation,
|
||||
departureAt: booking.schedule.departureAt,
|
||||
originStation: segment.origin,
|
||||
destinationStation: segment.destination,
|
||||
departureAt: segment.departureAt,
|
||||
} : null,
|
||||
paymentIntent: booking.paymentIntent,
|
||||
seatCount: booking.seats.length,
|
||||
@@ -715,16 +726,15 @@ export class BookingsService {
|
||||
verifaydaVerified: s.verifaydaVerified,
|
||||
seat: s.seat ? { seatNumber: s.seat.seatNumber, coach: { number: s.seat.coach?.number ?? null } } : null,
|
||||
})),
|
||||
schedule: {
|
||||
train: booking.schedule.train,
|
||||
originStation: (booking as any).originStationId
|
||||
? ((booking.schedule as any).stopTimes?.find((s: any) => s.stationId === (booking as any).originStationId)?.station ?? booking.schedule.originStation)
|
||||
: booking.schedule.originStation,
|
||||
destinationStation: (booking as any).destinationStationId
|
||||
? ((booking.schedule as any).stopTimes?.find((s: any) => s.stationId === (booking as any).destinationStationId)?.station ?? booking.schedule.destinationStation)
|
||||
: booking.schedule.destinationStation,
|
||||
departureAt: booking.schedule.departureAt,
|
||||
},
|
||||
schedule: (() => {
|
||||
const segment = resolveBookingSegment(booking.schedule, (booking as any).originStationId, (booking as any).destinationStationId);
|
||||
return {
|
||||
train: booking.schedule.train,
|
||||
originStation: segment.origin,
|
||||
destinationStation: segment.destination,
|
||||
departureAt: segment.departureAt,
|
||||
};
|
||||
})(),
|
||||
paymentIntent: booking.paymentIntent,
|
||||
seatCount: booking.seats.length,
|
||||
};
|
||||
@@ -1850,35 +1860,6 @@ export class BookingsService {
|
||||
);
|
||||
}
|
||||
|
||||
// Resolves the passenger's actual boarding/alighting stations AND times for one leg
|
||||
// from originStationId/destinationStationId (set when the booking covers only part of
|
||||
// a longer multi-stop schedule, e.g. train runs A→D but the passenger booked B→D) via
|
||||
// the schedule's stopTimes, falling back to the schedule's own full-route endpoints/
|
||||
// times when there's no segment override (older records, or a booking that covers the
|
||||
// whole run). Station resolution mirrors notifications.service.ts's
|
||||
// resolveSegmentStations (already applied to SMS/email); the departureAt/arrivalAt
|
||||
// resolution mirrors search.service.ts's leg construction (originStop.plannedDepartureAt
|
||||
// / destStop.plannedArrivalAt) — this brings the booking API (voucher, detail page,
|
||||
// confirmation) to the same behavior search results already have, instead of always
|
||||
// showing the train's full-route span.
|
||||
private resolveSegmentStations(
|
||||
schedule: any,
|
||||
originStationId: string | null | undefined,
|
||||
destinationStationId: string | null | undefined,
|
||||
): { origin: any; destination: any; departureAt: any; arrivalAt: any } {
|
||||
const stopTimes: any[] = schedule?.stopTimes ?? [];
|
||||
const findStop = (stationId: string | null | undefined) =>
|
||||
stationId && stopTimes.length > 0 ? stopTimes.find((st: any) => st.stationId === stationId) : undefined;
|
||||
const originStop = findStop(originStationId);
|
||||
const destStop = findStop(destinationStationId);
|
||||
return {
|
||||
origin: originStop?.station ?? schedule?.originStation ?? null,
|
||||
destination: destStop?.station ?? schedule?.destinationStation ?? null,
|
||||
departureAt: originStop?.plannedDepartureAt ?? schedule?.departureAt ?? null,
|
||||
arrivalAt: destStop?.plannedArrivalAt ?? schedule?.arrivalAt ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async getByRef(bookingRefOrId: string) {
|
||||
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(bookingRefOrId);
|
||||
const booking = await this.prisma.booking.findUnique({
|
||||
@@ -1987,13 +1968,13 @@ export class BookingsService {
|
||||
if (refreshed) Object.assign(booking, refreshed);
|
||||
}
|
||||
|
||||
const outboundSegment = this.resolveSegmentStations(
|
||||
const outboundSegment = resolveBookingSegment(
|
||||
(booking as any).schedule,
|
||||
(booking as any).originStationId,
|
||||
(booking as any).destinationStationId,
|
||||
);
|
||||
const returnSegment = (booking as any).returnSchedule
|
||||
? this.resolveSegmentStations(
|
||||
? resolveBookingSegment(
|
||||
(booking as any).returnSchedule,
|
||||
(booking as any).returnOriginStationId,
|
||||
(booking as any).returnDestinationStationId,
|
||||
|
||||
@@ -8,9 +8,22 @@ import { FareEngineService } from '../fare-engine/fare-engine.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { CreateGuestBookingDto, SavedPassengerProfileDto } from './guest-booking.dto';
|
||||
import { Currency, PassengerCategory, IdDocumentType } from '@prisma/client';
|
||||
import { resolveCheckinCutoff } from '../../common/utils/checkin-cutoff.utils';
|
||||
|
||||
/** Booking cutoff: reject new bookings within this many ms of departure. */
|
||||
const BOOKING_CUTOFF_MS = 30 * 60 * 1000;
|
||||
/**
|
||||
* Throws if the given boarding stop's own configurable check-in cutoff (route/stop
|
||||
* checkinMinutesBefore, same mechanism the seat hold and search results already enforce) has
|
||||
* passed. Must be checked against the actual boarding stop, not the schedule's origin — a
|
||||
* downstream stop's cutoff is independent of how long ago the train left its origin.
|
||||
*/
|
||||
function assertWithinCheckinCutoff(schedule: any, stopTime: any, stationId: string | null | undefined): void {
|
||||
const { cutoffAt, checkinMinutes } = resolveCheckinCutoff(schedule, stopTime, stationId);
|
||||
if (Date.now() >= cutoffAt.getTime()) {
|
||||
throw new BadRequestException(
|
||||
`Bookings are not accepted within ${checkinMinutes} minute${checkinMinutes !== 1 ? 's' : ''} of departure`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function generateRef(): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
@@ -107,20 +120,22 @@ export class GuestBookingService {
|
||||
originStation: true,
|
||||
destinationStation: true,
|
||||
stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } },
|
||||
route: { include: { stops: true } },
|
||||
},
|
||||
});
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
|
||||
if (Date.now() >= schedule.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||
}
|
||||
|
||||
const originStop = schedule.stopTimes.find(s => s.stationId === dto.originStationId)
|
||||
?? (schedule.stopTimes.length === 0 ? { stationId: schedule.originStationId, sequence: 0, station: schedule.originStation } : undefined);
|
||||
const destStop = schedule.stopTimes.find(s => s.stationId === dto.destinationStationId)
|
||||
?? (schedule.stopTimes.length === 0 ? { stationId: schedule.destinationStationId, sequence: 1, station: schedule.destinationStation } : undefined);
|
||||
if (!originStop || !destStop) throw new NotFoundException('Origin or destination not found');
|
||||
|
||||
// Cut off relative to the passenger's actual boarding stop, using the same
|
||||
// configurable per-stop/route checkinMinutesBefore that already gated the seat hold
|
||||
// and the search result — not a separate, hardcoded 30 minutes off the train's origin.
|
||||
assertWithinCheckinCutoff(schedule, originStop, dto.originStationId);
|
||||
|
||||
const segmentRoute = `${originStop.station.code}-${destStop.station.code}`;
|
||||
const fullRoute = `${schedule.originStation.code}-${schedule.destinationStation.code}`;
|
||||
|
||||
@@ -391,7 +406,7 @@ export class GuestBookingService {
|
||||
const [outboundSchedule, returnSchedule] = await Promise.all([
|
||||
this.prisma.trainSchedule.findUnique({
|
||||
where: { id: dto.scheduleId },
|
||||
include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } },
|
||||
include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, route: { include: { stops: true } } },
|
||||
}),
|
||||
this.prisma.trainSchedule.findUnique({
|
||||
where: { id: dto.returnScheduleId },
|
||||
@@ -401,10 +416,6 @@ export class GuestBookingService {
|
||||
if (!outboundSchedule) throw new NotFoundException('Outbound schedule not found');
|
||||
if (!returnSchedule) throw new NotFoundException('Return schedule not found');
|
||||
|
||||
if (Date.now() >= outboundSchedule.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||
}
|
||||
|
||||
const synth = (sched: any, stationId: string, seq: number) => {
|
||||
const station = sched.originStationId === stationId ? sched.originStation : sched.destinationStation;
|
||||
return { stationId, sequence: seq, station };
|
||||
@@ -418,6 +429,10 @@ export class GuestBookingService {
|
||||
if (!outboundOriginStop || !outboundDestStop) throw new NotFoundException('Outbound origin or destination not found on schedule');
|
||||
if (!returnOriginStop || !returnDestStop) throw new NotFoundException('Return origin or destination not found on schedule');
|
||||
|
||||
// Cut off relative to the passenger's actual boarding stop, using the same configurable
|
||||
// per-stop/route checkinMinutesBefore that already gated the seat hold and search result.
|
||||
assertWithinCheckinCutoff(outboundSchedule, outboundOriginStop, dto.originStationId);
|
||||
|
||||
const outboundSegmentRoute = `${outboundOriginStop.station.code}-${outboundDestStop.station.code}`;
|
||||
const outboundFullRoute = `${outboundSchedule.originStation.code}-${outboundSchedule.destinationStation.code}`;
|
||||
const returnSegmentRoute = `${returnOriginStop.station.code}-${returnDestStop.station.code}`;
|
||||
@@ -690,7 +705,7 @@ export class GuestBookingService {
|
||||
const [leg1Schedule, leg2Schedule] = await Promise.all([
|
||||
this.prisma.trainSchedule.findUnique({
|
||||
where: { id: dto.scheduleId },
|
||||
include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } },
|
||||
include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, route: { include: { stops: true } } },
|
||||
}),
|
||||
this.prisma.trainSchedule.findUnique({
|
||||
where: { id: dto.leg2ScheduleId },
|
||||
@@ -700,10 +715,6 @@ export class GuestBookingService {
|
||||
if (!leg1Schedule) throw new NotFoundException('Leg-1 schedule not found');
|
||||
if (!leg2Schedule) throw new NotFoundException('Leg-2 schedule not found');
|
||||
|
||||
if (Date.now() >= leg1Schedule.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||
}
|
||||
|
||||
const leg1OriginStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.originStationId);
|
||||
const leg1DestStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||
const leg2OriginStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||
@@ -711,6 +722,10 @@ export class GuestBookingService {
|
||||
if (!leg1OriginStop || !leg1DestStop) throw new NotFoundException('Leg-1 origin or transit station not found on schedule');
|
||||
if (!leg2OriginStop || !leg2DestStop) throw new NotFoundException('Transit or leg-2 destination not found on leg-2 schedule');
|
||||
|
||||
// Cut off relative to the passenger's actual boarding stop, using the same configurable
|
||||
// per-stop/route checkinMinutesBefore that already gated the seat hold and search result.
|
||||
assertWithinCheckinCutoff(leg1Schedule, leg1OriginStop, dto.originStationId);
|
||||
|
||||
// Process passengers (verify identity once)
|
||||
const passengersData: any[] = [];
|
||||
let adultCount = 0, childCount = 0;
|
||||
@@ -894,7 +909,7 @@ export class GuestBookingService {
|
||||
if (!retL2Hold || retL2Hold.expiresAt < now) throw new BadRequestException('Return leg-2 hold expired');
|
||||
|
||||
const [obL1Sched, obL2Sched, retL1Sched, retL2Sched] = await Promise.all([
|
||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } }, route: { include: { stops: true } } } }),
|
||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.leg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnLeg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||
@@ -904,10 +919,6 @@ export class GuestBookingService {
|
||||
if (!retL1Sched) throw new NotFoundException('Return leg-1 schedule not found');
|
||||
if (!retL2Sched) throw new NotFoundException('Return leg-2 schedule not found');
|
||||
|
||||
if (Date.now() >= obL1Sched.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||
}
|
||||
|
||||
const obL1Origin = obL1Sched.stopTimes.find(s => s.stationId === dto.originStationId);
|
||||
const obL1Dest = obL1Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||
const obL2Origin = obL2Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||
@@ -921,6 +932,10 @@ export class GuestBookingService {
|
||||
if (!retL1Origin || !retL1Dest) throw new NotFoundException('Return leg-1: origin or transit stop not found');
|
||||
if (!retL2Origin || !retL2Dest) throw new NotFoundException('Return leg-2: transit or destination stop not found');
|
||||
|
||||
// Cut off relative to the passenger's actual boarding stop, using the same configurable
|
||||
// per-stop/route checkinMinutesBefore that already gated the seat hold and search result.
|
||||
assertWithinCheckinCutoff(obL1Sched, obL1Origin, dto.originStationId);
|
||||
|
||||
// Process passengers (verify once)
|
||||
const passengersData: any[] = [];
|
||||
let adultCount = 0, childCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user