Files
edr-platform/apps/edr-passenger-api/src/seed/segment-fare.seeder.ts
2026-07-05 18:14:42 +03:00

110 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../common/prisma.service';
const SEED_FLAG = 'SEED_SEGMENT_FARES';
/**
* Seeds SegmentFareRule rows for every origin→destination pair on the
* Addis AbabaDjibouti route across all active seat classes.
*
* Skip-if-loaded: uses Prisma upsert on the unique constraint
* (routeId, originStopSequence, destinationStopSequence, seatClassId, nationality).
* Re-running is safe — existing rows are updated in-place.
*/
@Injectable()
export class SegmentFareSeeder {
private readonly logger = new Logger(SegmentFareSeeder.name);
constructor(private readonly prisma: PrismaService) {}
async run() {
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
this.logger.log(`Skipping segment fare seed — set ${SEED_FLAG}=true to enable`);
return;
}
const route = await this.prisma.route.findFirst({
where: { active: true },
include: { stops: { orderBy: { sequence: 'asc' } } },
});
if (!route) {
this.logger.warn('No active route found — skipping segment fare seed');
return;
}
const seatClasses = await this.prisma.seatClass.findMany({
where: { isActive: true },
});
if (!seatClasses.length) {
this.logger.warn('No active seat classes found — skipping segment fare seed');
return;
}
const stops = route.stops;
const validFrom = new Date('2025-01-01T00:00:00.000Z');
// Rate table: ETB minor units per km, keyed by (nationalityType, bedPosition)
// null bedPosition = regular seat
const rateTable: Record<string, Record<string | 'null', number>> = {
LOCAL: { null: 3000, UPPER: 4000, MIDDLE: 5500, LOWER: 6000 },
INTERNATIONAL: { null: 6000, UPPER: 8000, MIDDLE: 11000, LOWER: 12000 },
};
let upserted = 0;
for (const seatClass of seatClasses) {
const natType = seatClass.nationalityType ?? 'LOCAL';
const bedPos = seatClass.bedPosition ?? 'null';
const ratePerKm = rateTable[natType]?.[bedPos] ?? rateTable['LOCAL']['null'];
for (let i = 0; i < stops.length - 1; i++) {
for (let j = i + 1; j < stops.length; j++) {
const origin = stops[i];
const dest = stops[j];
// Approximate distance: sum of per-stop distanceKm if available,
// otherwise fall back to sequence-gap × 50 km.
let distanceKm = 0;
for (let k = i; k < j; k++) {
distanceKm += stops[k + 1].distanceKm ?? 50;
}
const baseFareMinor = Math.round(distanceKm * ratePerKm);
await this.prisma.segmentFareRule.upsert({
where: {
routeId_originStopSequence_destinationStopSequence_seatClassId_nationality: {
routeId: route.id,
originStopSequence: origin.sequence,
destinationStopSequence: dest.sequence,
seatClassId: seatClass.id,
nationality: natType,
},
},
update: { baseFareMinor, validFrom },
create: {
routeId: route.id,
originStopSequence: origin.sequence,
destinationStopSequence: dest.sequence,
seatClassId: seatClass.id,
nationality: natType,
baseFareMinor,
currency: 'ETB',
validFrom,
},
});
upserted++;
}
}
}
this.logger.log(
`Segment fare seed complete — ${upserted} rules upserted ` +
`(${stops.length} stops × ${seatClasses.length} seat classes)`,
);
}
}