Merge branch 'alpha' of github.com:Tria-plc/edr-platform into alpha

This commit is contained in:
Stephanos A
2026-07-15 00:40:08 +03:00
4 changed files with 102 additions and 27 deletions

View File

@@ -398,10 +398,24 @@ export class SearchService {
this.calculateFaresForSegment(schedule, originStationId, destinationStationId, nationality),
]);
// Compute per-class availability using the pre-computed free seat set
// Compute per-class availability using the pre-computed free seat set. A coach type
// has separate seat classes per nationality tier (e.g. "VIP Bed Upper (Local)" AND
// "VIP Bed Upper (Intl)" on the same coach) — filter to the searching passenger's own
// nationality first, otherwise a name-based `.find()` across both tiers would credit
// all availability to whichever tier happens to come first in the query result,
// leaving the other tier's class permanently at 0 ("Fully booked") even when seats
// are actually free. Matched via the class's own bedPosition field (case-insensitive:
// Seat.bedPosition is lowercase, SeatClass.bedPosition is uppercase) rather than a
// name substring, since that's an exact, unambiguous signal.
const nationalityUpper = (nationality ?? '').toUpperCase();
const resolvedNationalityType = (nationalityUpper === 'ETHIOPIAN' || nationalityUpper === 'DJIBOUTIAN')
? 'LOCAL' : 'INTERNATIONAL';
const availabilityByClass: Record<string, number> = {};
for (const assignment of schedule.coachAssignments) {
const seatClassNames = assignment.coach.coachType?.seatClasses?.map((sc: any) => sc.name) || ['Standard'];
const seatClasses = (assignment.coach.coachType?.seatClasses ?? []).filter(
(sc: any) => !sc.nationalityType || sc.nationalityType === resolvedNationalityType,
);
const isBedCoach = assignment.coach.seats.some((s: any) => s.bedPosition);
if (isBedCoach) {
@@ -412,8 +426,8 @@ export class SearchService {
if (freeSeats.has(seat.id)) count++;
}
if (count > 0) {
const matchingClass = seatClassNames.find((n: string) => n.toLowerCase().includes(bedPosition));
if (matchingClass) availabilityByClass[matchingClass] = (availabilityByClass[matchingClass] ?? 0) + count;
const matchingClass = seatClasses.find((sc: any) => sc.bedPosition?.toLowerCase() === bedPosition);
if (matchingClass) availabilityByClass[matchingClass.name] = (availabilityByClass[matchingClass.name] ?? 0) + count;
}
}
} else {
@@ -422,11 +436,12 @@ export class SearchService {
if (seat.status === 'BLOCKED' || !seat.seatNumber?.trim()) continue;
if (freeSeats.has(seat.id)) available++;
}
for (const name of seatClassNames) availabilityByClass[name] = (availabilityByClass[name] ?? 0) + available;
const names = seatClasses.length > 0 ? seatClasses.map((sc: any) => sc.name) : ['Standard'];
for (const name of names) availabilityByClass[name] = (availabilityByClass[name] ?? 0) + available;
}
}
const coachTypes = this.buildCoachTypeDetails(schedule, faresByClass, nationality);
const coachTypes = this.buildCoachTypeDetails(schedule, faresByClass, nationality, availabilityByClass);
const legDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt;
const legArrivalAt = destStop.plannedArrivalAt ?? schedule.arrivalAt;
@@ -748,12 +763,13 @@ export class SearchService {
schedule: ScheduleWithIncludes,
faresByClass: Array<{ seatClassName: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number }>,
nationality?: string,
availabilityByClass: Record<string, number> = {},
): Array<{
coachTypeId: string;
coachTypeName: string;
coachTypeCode: string;
coachId: string;
classes: Array<{ name: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number }>;
classes: Array<{ name: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number; available: number }>;
}> {
const coachTypeMap = new Map<
string,
@@ -795,9 +811,10 @@ export class SearchService {
baseFareMinor: fareInfo.baseFareMinor,
displayCurrency: fareInfo.displayCurrency,
displayAmountMinor: fareInfo.displayAmountMinor,
available: availabilityByClass[className] ?? 0,
};
})
.filter((c): c is { name: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number } => c !== null)
.filter((c): c is { name: string; baseFareMinor: number; displayCurrency: Currency; displayAmountMinor: number; available: number } => c !== null)
.sort((a, b) => a.baseFareMinor - b.baseFareMinor);
if (classes.length === 0) continue;