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 Ababa–Djibouti 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> = { 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)`, ); } }