Adding seat blocking revenue loss dashboard

This commit is contained in:
Mulu Mehari
2026-08-02 23:08:15 +03:00
parent ec4bf8a5ab
commit f0295f401a
32 changed files with 4008 additions and 59 deletions

View File

@@ -442,13 +442,47 @@ export class SearchService {
where: { active: true, stops: { some: { stationId: originStationId } } },
select: { stops: { select: { stationId: true, sequence: true } } },
});
return candidateRoutes.some((r) => {
const onRouteDefinition = candidateRoutes.some((r) => {
const o = r.stops.find((s) => s.stationId === originStationId);
const d = r.stops.find((s) => s.stationId === destinationStationId);
return !!o && !!d && o.sequence < d.sequence;
});
if (onRouteDefinition) return true;
// Fallback: a schedule whose own stop times connect the pair in order.
//
// A return leg is modelled by reusing the outbound Route while laying its TripStopTimes
// in the opposite order (see test/fixtures/seed-ui.ts). The RouteStop check above cannot
// see that — it only knows A→B→C — so it reports "no route" for C→A even though
// searchSchedules finds and sells that trip, because searchSchedules resolves
// connectivity from TripStopTime.sequence, exactly like the availability loop below.
// Without this fallback the endpoint contradicts the search it is meant to preview, and
// the portal would disable the date picker for a pair that is genuinely bookable.
const schedules = await this.prisma.trainSchedule.findMany({
where: {
AND: [
{ stopTimes: { some: { stationId: originStationId } } },
{ stopTimes: { some: { stationId: destinationStationId } } },
],
},
select: {
stopTimes: {
where: { stationId: { in: [originStationId, destinationStationId] } },
select: { stationId: true, sequence: true },
},
},
take: this.ROUTE_EXISTS_SCHEDULE_SCAN_LIMIT,
});
return schedules.some((s) => {
const o = s.stopTimes.find((st) => st.stationId === originStationId);
const d = s.stopTimes.find((st) => st.stationId === destinationStationId);
return !!o && !!d && o.sequence < d.sequence;
});
}
/** Bounds the stop-time fallback scan — connectivity is a yes/no, not a survey. */
private readonly ROUTE_EXISTS_SCHEDULE_SCAN_LIMIT = 200;
/** Status/package/coach bookability only — ignores date, cutoff, and seat-level availability. */
private isBookableSchedule(s: { status: string; isPackageOnly: boolean; coachAssignments: { id: string }[] }): boolean {
return (