Added Checkin duration configuration

This commit is contained in:
Roba Boru
2026-07-17 22:12:54 +03:00
parent 0c584cce88
commit 738b7df5cf
14 changed files with 297 additions and 132 deletions

View File

@@ -4,10 +4,9 @@ import { SearchService } from './search.service';
import { CurrencyModule } from '../currency/currency.module';
import { FareEngineModule } from '../fare-engine/fare-engine.module';
import { SegmentsModule } from '../segments/segments.module';
import { SystemConfigModule } from '../system-config/system-config.module';
@Module({
imports: [CurrencyModule, FareEngineModule, SegmentsModule, SystemConfigModule],
imports: [CurrencyModule, FareEngineModule, SegmentsModule],
controllers: [SearchController],
providers: [SearchService],
exports: [SearchService],

View File

@@ -6,7 +6,6 @@ import { FareEngineService } from '../fare-engine/fare-engine.service';
import { SegmentsService } from '../segments/segments.service';
import { resolveCurrencyFromNationality } from '../fare-engine/fare-engine.dto';
import { Currency } from '@prisma/client';
import { SystemConfigService, CONFIG_KEYS } from '../system-config/system-config.service';
const POINTS_TO_MINOR = 10;
@@ -20,6 +19,7 @@ type ScheduleWithIncludes = {
train: any;
originStation: any;
destinationStation: any;
route: { checkinMinutesBefore: number; stops: Array<{ stationId: string; checkinMinutesBefore: number | null }> } | null;
stopTimes: Array<{ stationId: string; sequence: number; plannedArrivalAt: Date | null; plannedDepartureAt: Date | null; station: any }>;
coachAssignments: Array<{ coach: { id: string; seats: any[]; coachType: { id: string; name: string; code: string; seatClasses: any[] } | null } }>;
};
@@ -28,6 +28,7 @@ const SCHEDULE_INCLUDE = {
train: true,
originStation: true,
destinationStation: true,
route: { select: { checkinMinutesBefore: true, stops: { select: { stationId: true, checkinMinutesBefore: true } } } },
stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } },
coachAssignments: {
include: { coach: { include: { seats: true, coachType: { include: { seatClasses: true } } } } },
@@ -41,13 +42,8 @@ export class SearchService {
private currencyService: CurrencyService,
private fareEngine: FareEngineService,
private segmentsService: SegmentsService,
private systemConfig: SystemConfigService,
) {}
private async getCutoffHours(): Promise<number> {
return this.systemConfig.getNumber(CONFIG_KEYS.HOLD_CUTOFF_HOURS_BEFORE_DEPARTURE);
}
async searchTrips(dto: SearchTripsDto) {
const [direct, transit] = await Promise.all([
this.searchSchedules(
@@ -218,10 +214,11 @@ export class SearchService {
const now = new Date();
const totalPassengers = adultCount + (childCount ?? 0);
const cutoffHours = await this.getCutoffHours();
const cutoffThreshold = new Date(now.getTime() + cutoffHours * 60 * 60 * 1000);
// Use now as the lower bound for today so we don't fetch schedules that have
// already fully departed. The per-segment cutoff check in buildScheduleResult
// handles the exact check using each stop's own plannedDepartureAt.
const isToday = now.getFullYear() === y && now.getMonth() === m - 1 && now.getDate() === d;
const earliest = isToday ? cutoffThreshold : date;
const earliest = isToday ? now : date;
const schedules = await this.prisma.trainSchedule.findMany({
where: {
@@ -284,12 +281,9 @@ export class SearchService {
}),
]);
const cutoffHours = await this.getCutoffHours();
const cutoffThreshold = new Date(Date.now() + cutoffHours * 60 * 60 * 1000);
const results: any[] = [];
for (const leg1 of (leg1Schedules as ScheduleWithIncludes[]).filter(s => new Date(s.departureAt) > cutoffThreshold)) {
for (const leg1 of leg1Schedules as ScheduleWithIncludes[]) {
const originStop = leg1.stopTimes.find(s => s.stationId === originStationId);
if (!originStop) continue;
@@ -376,6 +370,16 @@ export class SearchService {
const destStop = schedule.stopTimes.find(s => s.stationId === destinationStationId);
if (!originStop || !destStop || originStop.sequence >= destStop.sequence) return null;
// Segment-level cutoff: use the origin stop's planned departure, not the
// schedule's overall departureAt (which is station A's time). This lets
// B→D remain bookable even after A→D closes.
// Cutoff resolution: stop-level override → route default → 30 min fallback.
const now = new Date();
const segmentDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt;
const routeStop = schedule.route?.stops?.find(s => s.stationId === originStationId);
const checkinMinutes = routeStop?.checkinMinutesBefore ?? schedule.route?.checkinMinutesBefore ?? 30;
if (segmentDepartureAt.getTime() - now.getTime() <= checkinMinutes * 60 * 1000) return null;
// Collect all valid seat IDs upfront for a single batch availability check
const allValidSeatIds = schedule.coachAssignments.flatMap(a =>
a.coach.seats