mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 04:08:11 +00:00
First passenger and back office portal commit
This commit is contained in:
@@ -73,10 +73,13 @@ export class SearchService {
|
||||
const legDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt;
|
||||
const legArrivalAt = destStop.plannedArrivalAt ?? schedule.arrivalAt;
|
||||
|
||||
// Fetch fares for all seat classes from fare engine in one call
|
||||
const faresByClass = await this.fareEngine
|
||||
.calculateAllForSchedule(schedule.id, dto.nationality)
|
||||
.catch(() => []);
|
||||
// Fetch fares for all seat classes - need to pass the SEARCH origin/destination, not schedule terminals
|
||||
const faresByClass = await this.calculateFaresForSegment(
|
||||
schedule,
|
||||
dto.originStationId,
|
||||
dto.destinationStationId,
|
||||
dto.nationality,
|
||||
);
|
||||
|
||||
results.push({
|
||||
scheduleId: schedule.id,
|
||||
@@ -240,6 +243,113 @@ export class SearchService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate fares for a specific segment of a schedule
|
||||
*/
|
||||
private async calculateFaresForSegment(
|
||||
schedule: any,
|
||||
originStationId: string,
|
||||
destinationStationId: string,
|
||||
nationality?: string,
|
||||
): Promise<Array<{ seatClassName: string; baseFareMinor: number }>> {
|
||||
// Get seat classes that are actually assigned to this schedule via coaches
|
||||
const assignedSeatClassIds: string[] = Array.from(
|
||||
new Set(
|
||||
schedule.coachAssignments.map((a: any) => a.coach.seatClass.id as string)
|
||||
)
|
||||
);
|
||||
|
||||
// Get only the seat classes that are assigned to this schedule
|
||||
const seatClasses = await this.prisma.seatClass.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
id: { in: assignedSeatClassIds }
|
||||
},
|
||||
orderBy: { basePrice: 'asc' },
|
||||
});
|
||||
|
||||
// If no coaches assigned, return empty array
|
||||
if (seatClasses.length === 0) {
|
||||
console.log(`No seat classes assigned to schedule ${schedule.id}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// If schedule has a route, use route-based calculation
|
||||
if (schedule.routeId) {
|
||||
const results = await Promise.all(
|
||||
seatClasses.map(async (sc) => {
|
||||
try {
|
||||
const fare = await this.fareEngine.calculate({
|
||||
routeId: schedule.routeId,
|
||||
originStationId,
|
||||
destinationStationId,
|
||||
seatClassId: sc.id,
|
||||
nationality,
|
||||
});
|
||||
return {
|
||||
seatClassName: fare.seatClassName,
|
||||
baseFareMinor: fare.baseFarePerPassengerMinor,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to calculate fare for ${sc.name}:`, (error as Error).message);
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const validResults = results.filter((r): r is { seatClassName: string; baseFareMinor: number } => r !== null);
|
||||
if (validResults.length > 0) {
|
||||
return validResults;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Try to get fares from FareRule table
|
||||
const originStation = await this.prisma.station.findUnique({ where: { id: originStationId } });
|
||||
const destStation = await this.prisma.station.findUnique({ where: { id: destinationStationId } });
|
||||
|
||||
if (originStation && destStation) {
|
||||
const segmentRoute = `${originStation.code}-${destStation.code}`;
|
||||
const now = new Date();
|
||||
|
||||
const fareRules = await this.prisma.fareRule.findMany({
|
||||
where: {
|
||||
route: segmentRoute,
|
||||
seatClassId: { in: assignedSeatClassIds },
|
||||
validFrom: { lte: now },
|
||||
OR: [
|
||||
{ validUntil: null },
|
||||
{ validUntil: { gte: now } },
|
||||
],
|
||||
},
|
||||
include: { seatClass: true },
|
||||
});
|
||||
|
||||
if (fareRules.length > 0) {
|
||||
console.log(`Found ${fareRules.length} fare rules for segment ${segmentRoute}`);
|
||||
return fareRules.map(rule => ({
|
||||
seatClassName: rule.seatClass.name,
|
||||
baseFareMinor: rule.baseFareMinor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: Return default fares only for assigned seat classes
|
||||
console.log(`No fares found, using defaults for ${originStationId} to ${destinationStationId}`);
|
||||
return seatClasses.map(sc => ({
|
||||
seatClassName: sc.name,
|
||||
baseFareMinor: this.getDefaultFareForClass(sc.name),
|
||||
}));
|
||||
}
|
||||
|
||||
private getDefaultFareForClass(className: string): number {
|
||||
const defaults: Record<string, number> = {
|
||||
'Economy Regular': 35000,
|
||||
'Economy Bed': 49000,
|
||||
'VIP Bed': 63000,
|
||||
};
|
||||
return defaults[className] ?? 35000;
|
||||
}
|
||||
|
||||
private defaultFare(seatClassName: string): number {
|
||||
const fares: Record<string, number> = {
|
||||
'Economy Regular': 45000,
|
||||
@@ -249,6 +359,47 @@ export class SearchService {
|
||||
return fares[seatClassName] ?? 45000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback method to get fares from FareRule table when fare engine fails
|
||||
*/
|
||||
private async getFallbackFares(
|
||||
scheduleId: string,
|
||||
originCode: string,
|
||||
destCode: string,
|
||||
): Promise<Array<{ seatClassName: string; baseFareMinor: number }>> {
|
||||
const segmentRoute = `${originCode}-${destCode}`;
|
||||
const now = new Date();
|
||||
|
||||
// Try to find fare rules for this segment
|
||||
const fareRules = await this.prisma.fareRule.findMany({
|
||||
where: {
|
||||
route: segmentRoute,
|
||||
validFrom: { lte: now },
|
||||
OR: [
|
||||
{ validUntil: null },
|
||||
{ validUntil: { gte: now } },
|
||||
],
|
||||
},
|
||||
include: { seatClass: true },
|
||||
});
|
||||
|
||||
if (fareRules.length > 0) {
|
||||
console.log(`Found ${fareRules.length} fare rules for segment ${segmentRoute}`);
|
||||
return fareRules.map(rule => ({
|
||||
seatClassName: rule.seatClass.name,
|
||||
baseFareMinor: rule.baseFareMinor,
|
||||
}));
|
||||
}
|
||||
|
||||
// If no segment-specific rules, return default fares
|
||||
console.log(`No fare rules found for ${segmentRoute}, using defaults`);
|
||||
return [
|
||||
{ seatClassName: 'Economy Regular', baseFareMinor: 35000 },
|
||||
{ seatClassName: 'Economy Bed', baseFareMinor: 49000 },
|
||||
{ seatClassName: 'VIP Bed', baseFareMinor: 63000 },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the best matching fare rule based on specificity:
|
||||
* 1. schedule+segment+nationality
|
||||
|
||||
Reference in New Issue
Block a user