mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 18:55:42 +00:00
Luggage processing,, schedule times and tariff related updates
This commit is contained in:
@@ -154,43 +154,54 @@ export class SearchService {
|
||||
) {
|
||||
const [y, m, d] = dateStr.split('-').map(Number);
|
||||
const requestedDate = new Date(y, m - 1, d, 0, 0, 0, 0);
|
||||
|
||||
const now = new Date();
|
||||
const daysBefore = Math.min(7, Math.floor(requestedDate.getTime() / 86_400_000));
|
||||
const daysAfter = 14 - daysBefore;
|
||||
|
||||
const windowStart = new Date(requestedDate);
|
||||
windowStart.setDate(windowStart.getDate() - daysBefore);
|
||||
if (windowStart < now) windowStart.setTime(now.getTime());
|
||||
|
||||
const windowEnd = new Date(requestedDate);
|
||||
windowEnd.setDate(windowEnd.getDate() + daysAfter + 1);
|
||||
|
||||
const totalPassengers = adultCount + (childCount ?? 0);
|
||||
|
||||
const requestedNextDay = new Date(y, m - 1, d + 1, 0, 0, 0, 0);
|
||||
const now = new Date();
|
||||
const totalPassengers = adultCount + (childCount ?? 0);
|
||||
const NEEDED = 3;
|
||||
|
||||
const schedules = await this.prisma.trainSchedule.findMany({
|
||||
where: {
|
||||
status: 'SCHEDULED',
|
||||
isPackageOnly: false,
|
||||
OR: [
|
||||
{ departureAt: { gte: windowStart, lt: requestedDate } },
|
||||
{ departureAt: { gte: requestedNextDay < now ? now : requestedNextDay, lt: windowEnd } },
|
||||
],
|
||||
stopTimes: { some: { stationId: originStationId } },
|
||||
coachAssignments: { some: {} },
|
||||
},
|
||||
include: SCHEDULE_INCLUDE,
|
||||
orderBy: { departureAt: 'asc' },
|
||||
});
|
||||
const baseWhere = {
|
||||
status: 'SCHEDULED',
|
||||
isPackageOnly: false,
|
||||
stopTimes: { some: { stationId: originStationId } },
|
||||
coachAssignments: { some: {} },
|
||||
} as const;
|
||||
|
||||
const results = await Promise.all(
|
||||
schedules.map(schedule =>
|
||||
this.buildScheduleResult(schedule as any, originStationId, destinationStationId, totalPassengers, nationality)
|
||||
)
|
||||
);
|
||||
return results.filter((r): r is NonNullable<typeof r> => !!r && r.hasAvailability);
|
||||
// Fetch candidates before and after in parallel; take more than needed to
|
||||
// account for routes that don't serve the destination or have no availability.
|
||||
const FETCH_LIMIT = NEEDED * 5;
|
||||
|
||||
const [beforeCandidates, afterCandidates] = await Promise.all([
|
||||
this.prisma.trainSchedule.findMany({
|
||||
where: { ...baseWhere, departureAt: { gte: now < requestedDate ? now : new Date(0), lt: requestedDate } },
|
||||
include: SCHEDULE_INCLUDE,
|
||||
orderBy: { departureAt: 'desc' },
|
||||
take: FETCH_LIMIT,
|
||||
}),
|
||||
this.prisma.trainSchedule.findMany({
|
||||
where: { ...baseWhere, departureAt: { gte: requestedNextDay > now ? requestedNextDay : now } },
|
||||
include: SCHEDULE_INCLUDE,
|
||||
orderBy: { departureAt: 'asc' },
|
||||
take: FETCH_LIMIT,
|
||||
}),
|
||||
]);
|
||||
|
||||
const pickN = async (candidates: typeof beforeCandidates, limit: number) => {
|
||||
const out: NonNullable<Awaited<ReturnType<typeof this.buildScheduleResult>>>[] = [];
|
||||
for (const schedule of candidates) {
|
||||
if (out.length >= limit) break;
|
||||
const r = await this.buildScheduleResult(schedule as any, originStationId, destinationStationId, totalPassengers, nationality);
|
||||
if (r?.hasAvailability) out.push(r);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const [before, after] = await Promise.all([
|
||||
pickN(beforeCandidates, NEEDED),
|
||||
pickN(afterCandidates, NEEDED),
|
||||
]);
|
||||
|
||||
// before was fetched desc (closest first); reverse so result is chronological
|
||||
return [...before.reverse(), ...after];
|
||||
}
|
||||
|
||||
private async searchSchedules(
|
||||
@@ -415,7 +426,7 @@ export class SearchService {
|
||||
}
|
||||
}
|
||||
|
||||
const coachTypes = this.buildCoachTypeDetails(schedule, faresByClass);
|
||||
const coachTypes = this.buildCoachTypeDetails(schedule, faresByClass, nationality);
|
||||
const legDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt;
|
||||
const legArrivalAt = destStop.plannedArrivalAt ?? schedule.arrivalAt;
|
||||
|
||||
@@ -638,6 +649,10 @@ export class SearchService {
|
||||
): Promise<Array<{ seatClassName: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number }>> {
|
||||
const displayCurrency = resolveCurrencyFromNationality(nationality);
|
||||
|
||||
const nationalityUpper = (nationality ?? '').toUpperCase();
|
||||
const nationalityType = (nationalityUpper === 'ETHIOPIAN' || nationalityUpper === 'DJIBOUTIAN')
|
||||
? 'LOCAL' : 'INTERNATIONAL';
|
||||
|
||||
// Collect seat class IDs from the schedule include for the ID set,
|
||||
// but fetch fresh records from DB so updated baseFareMinor is always current
|
||||
const seatClassIdSet = new Set<string>();
|
||||
@@ -647,7 +662,14 @@ export class SearchService {
|
||||
}
|
||||
}
|
||||
const freshSeatClasses = await this.prisma.seatClass.findMany({
|
||||
where: { id: { in: Array.from(seatClassIdSet) }, isActive: true },
|
||||
where: {
|
||||
id: { in: Array.from(seatClassIdSet) },
|
||||
isActive: true,
|
||||
OR: [
|
||||
{ nationalityType: null },
|
||||
{ nationalityType: nationalityType },
|
||||
],
|
||||
},
|
||||
});
|
||||
const seatClassMap = new Map(freshSeatClasses.map(sc => [sc.id, sc]));
|
||||
const seatClasses = freshSeatClasses.sort((a, b) => a.baseFareMinor - b.baseFareMinor);
|
||||
@@ -725,6 +747,7 @@ export class SearchService {
|
||||
private buildCoachTypeDetails(
|
||||
schedule: ScheduleWithIncludes,
|
||||
faresByClass: Array<{ seatClassName: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number }>,
|
||||
nationality?: string,
|
||||
): Array<{
|
||||
coachTypeId: string;
|
||||
coachTypeName: string;
|
||||
@@ -749,8 +772,16 @@ export class SearchService {
|
||||
});
|
||||
}
|
||||
|
||||
const nationalityUpper = (nationality ?? '').toUpperCase();
|
||||
const resolvedNationalityType = (nationalityUpper === 'ETHIOPIAN' || nationalityUpper === 'DJIBOUTIAN')
|
||||
? 'LOCAL' : 'INTERNATIONAL';
|
||||
|
||||
const entry = coachTypeMap.get(coachType.id)!;
|
||||
coachType.seatClasses?.forEach((sc: any) => entry.classNames.add(sc.name));
|
||||
coachType.seatClasses?.forEach((sc: any) => {
|
||||
// Exclude classes that belong to the wrong nationality type
|
||||
if (sc.nationalityType && sc.nationalityType !== resolvedNationalityType) return;
|
||||
if (faresByClass.some(f => f.seatClassName === sc.name)) entry.classNames.add(sc.name);
|
||||
});
|
||||
}
|
||||
|
||||
const result = [];
|
||||
@@ -769,6 +800,7 @@ export class SearchService {
|
||||
.filter((c): c is { name: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number } => c !== null)
|
||||
.sort((a, b) => a.baseFareMinor - b.baseFareMinor);
|
||||
|
||||
if (classes.length === 0) continue;
|
||||
result.push({
|
||||
coachTypeId: coachType.id,
|
||||
coachTypeName: coachType.name,
|
||||
|
||||
Reference in New Issue
Block a user