mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Round trip journeys, feedback items, backoffice documentation
This commit is contained in:
@@ -4,7 +4,6 @@ import { randomUUID as uuidv4 } from 'crypto';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const EDR_ROUTE_ID = uuidv4();
|
||||
const TRAIN_ID = uuidv4();
|
||||
|
||||
async function seedSystemUsers() {
|
||||
@@ -24,6 +23,10 @@ async function seedSystemUsers() {
|
||||
phone: '+251900000000',
|
||||
passwordHash: adminHash,
|
||||
role: 'ADMIN',
|
||||
gender: 'Male',
|
||||
dateOfBirth: new Date('1980-05-20'),
|
||||
nationality: 'Ethiopian',
|
||||
nationalId: 'ET123456789',
|
||||
},
|
||||
});
|
||||
console.log(' ✅ Admin: admin@edr-platform.com / admin123');
|
||||
@@ -39,6 +42,9 @@ async function seedSystemUsers() {
|
||||
role: 'PASSENGER',
|
||||
nationality: 'Ethiopian',
|
||||
faydaVerified: true,
|
||||
gender: 'Male',
|
||||
dateOfBirth: new Date('1990-03-15'),
|
||||
nationalId: 'ET987654321',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -49,7 +55,7 @@ async function seedSystemUsers() {
|
||||
data: { passengerId: passengerRecord.id, pointsBalance: 1500, lifetimePoints: 3000, tier: 'SILVER' },
|
||||
});
|
||||
await prisma.walletAccount.create({
|
||||
data: { passengerId: passengerRecord.id, balanceMinor: 50000 },
|
||||
data: { passengerId: passengerRecord.id, balanceMinor: 500 },
|
||||
});
|
||||
}
|
||||
await prisma.userPreferences.upsert({
|
||||
@@ -68,6 +74,8 @@ async function seedSystemUsers() {
|
||||
phone: '+251911111111',
|
||||
passwordHash: agentHash,
|
||||
role: 'AGENT',
|
||||
gender: 'Female',
|
||||
dateOfBirth: new Date('1992-07-22'),
|
||||
},
|
||||
});
|
||||
await prisma.agent.upsert({
|
||||
@@ -86,6 +94,8 @@ async function seedSystemUsers() {
|
||||
phone: '+251922222222',
|
||||
passwordHash: supervisorHash,
|
||||
role: 'SUPERVISOR',
|
||||
gender: 'Male',
|
||||
dateOfBirth: new Date('1985-11-10'),
|
||||
},
|
||||
});
|
||||
console.log(' ✅ Supervisor: supervisor@edr-platform.com / supervisor123');
|
||||
@@ -99,6 +109,8 @@ async function seedSystemUsers() {
|
||||
phone: '+251933333333',
|
||||
passwordHash: staffHash,
|
||||
role: 'STAFF',
|
||||
gender: 'Female',
|
||||
dateOfBirth: new Date('1995-09-08'),
|
||||
},
|
||||
});
|
||||
console.log(' ✅ Staff: staff@edr-platform.com / staff123');
|
||||
@@ -107,21 +119,21 @@ async function seedSystemUsers() {
|
||||
async function seedStations() {
|
||||
console.log('\n📍 Seeding 15 stations (Ethio-Djibouti Railway)...');
|
||||
const stations = [
|
||||
{ code: 'SBT', name: 'Sebeta', city: 'Sebeta', countryCode: 'ET', lat: 8.9520, lng: 38.6150 },
|
||||
{ code: 'LEB', name: 'Lebu', city: 'Lebu', countryCode: 'ET', lat: 8.8890, lng: 38.5320 },
|
||||
{ code: 'BSH', name: 'Bishoftu', city: 'Bishoftu', countryCode: 'ET', lat: 8.7650, lng: 39.0240 },
|
||||
{ code: 'MOJ', name: 'Mojo', city: 'Mojo', countryCode: 'ET', lat: 8.6780, lng: 39.2130 },
|
||||
{ code: 'ADM', name: 'Adama', city: 'Adama', countryCode: 'ET', lat: 8.5420, lng: 39.2780 },
|
||||
{ code: 'MTE', name: 'Metehara', city: 'Metehara', countryCode: 'ET', lat: 8.7890, lng: 39.8920 },
|
||||
{ code: 'MIS', name: 'Mieso', city: 'Mieso', countryCode: 'ET', lat: 8.9120, lng: 40.3450 },
|
||||
{ code: 'BIK', name: 'Bike', city: 'Bike', countryCode: 'ET', lat: 9.1230, lng: 40.8670 },
|
||||
{ code: 'DRE', name: 'Dire Dawa', city: 'Dire Dawa', countryCode: 'ET', lat: 9.5915, lng: 41.8578 },
|
||||
{ code: 'ADG', name: 'Adigala', city: 'Adigala', countryCode: 'ET', lat: 9.7340, lng: 42.2150 },
|
||||
{ code: 'AYS', name: 'Aysha', city: 'Aysha', countryCode: 'ET', lat: 10.0120, lng: 42.5670 },
|
||||
{ code: 'DAW', name: 'Dawanle', city: 'Dawanle', countryCode: 'ET', lat: 10.2340, lng: 42.8340 },
|
||||
{ code: 'ALS', name: 'Alisabieh', city: 'Alisabieh', countryCode: 'DJ', lat: 10.8950, lng: 42.9560 },
|
||||
{ code: 'HOL', name: 'Holhol', city: 'Holhol', countryCode: 'DJ', lat: 11.1230, lng: 43.0450 },
|
||||
{ code: 'NAG', name: 'Nagad', city: 'Nagad', countryCode: 'DJ', lat: 11.3780, lng: 43.1200 },
|
||||
{ code: 'SBT', name: 'Sebeta', city: 'Sebeta', countryCode: 'ET', lat: 8.9520, lng: 38.6150, sequence: 1 },
|
||||
{ code: 'LEB', name: 'Lebu', city: 'Lebu', countryCode: 'ET', lat: 8.8890, lng: 38.5320, sequence: 2 },
|
||||
{ code: 'BSH', name: 'Bishoftu', city: 'Bishoftu', countryCode: 'ET', lat: 8.7650, lng: 39.0240, sequence: 3 },
|
||||
{ code: 'MOJ', name: 'Mojo', city: 'Mojo', countryCode: 'ET', lat: 8.6780, lng: 39.2130, sequence: 4 },
|
||||
{ code: 'ADM', name: 'Adama', city: 'Adama', countryCode: 'ET', lat: 8.5420, lng: 39.2780, sequence: 5 },
|
||||
{ code: 'MTE', name: 'Metehara', city: 'Metehara', countryCode: 'ET', lat: 8.7890, lng: 39.8920, sequence: 6 },
|
||||
{ code: 'MIS', name: 'Mieso', city: 'Mieso', countryCode: 'ET', lat: 8.9120, lng: 40.3450, sequence: 7 },
|
||||
{ code: 'BIK', name: 'Bike', city: 'Bike', countryCode: 'ET', lat: 9.1230, lng: 40.8670, sequence: 8 },
|
||||
{ code: 'DRE', name: 'Dire Dawa', city: 'Dire Dawa', countryCode: 'ET', lat: 9.5915, lng: 41.8578, sequence: 9 },
|
||||
{ code: 'ADG', name: 'Adigala', city: 'Adigala', countryCode: 'ET', lat: 9.7340, lng: 42.2150, sequence: 10 },
|
||||
{ code: 'AYS', name: 'Aysha', city: 'Aysha', countryCode: 'ET', lat: 10.0120, lng: 42.5670, sequence: 11 },
|
||||
{ code: 'DAW', name: 'Dawanle', city: 'Dawanle', countryCode: 'ET', lat: 10.2340, lng: 42.8340, sequence: 12 },
|
||||
{ code: 'ALS', name: 'Alisabieh', city: 'Alisabieh', countryCode: 'DJ', lat: 10.8950, lng: 42.9560, sequence: 13 },
|
||||
{ code: 'HOL', name: 'Holhol', city: 'Holhol', countryCode: 'DJ', lat: 11.1230, lng: 43.0450, sequence: 14 },
|
||||
{ code: 'NAG', name: 'Nagad', city: 'Nagad', countryCode: 'DJ', lat: 11.3780, lng: 43.1200, sequence: 15 },
|
||||
];
|
||||
|
||||
for (const station of stations) {
|
||||
@@ -142,8 +154,8 @@ async function seedCoachTypesAndClasses() {
|
||||
console.log('\n🚂 Seeding coach types and seat classes...');
|
||||
const coachTypes = [
|
||||
{ code: 'HSC', name: 'Hard Seat Coach', type: 'Economy Regular' },
|
||||
{ code: 'HBC', name: 'Hard Bed Coach', type: 'Economy Bed' },
|
||||
{ code: 'SBC', name: 'Soft Bed Coach', type: 'VIP Bed' },
|
||||
{ code: 'HBC', name: 'Hard Berth Coach', type: 'Economy Bed' },
|
||||
{ code: 'SBC', name: 'Soft Berth Coach', type: 'VIP Bed' },
|
||||
];
|
||||
|
||||
for (const ct of coachTypes) {
|
||||
@@ -155,12 +167,12 @@ async function seedCoachTypesAndClasses() {
|
||||
}
|
||||
|
||||
const seatClasses = [
|
||||
{ name: 'VIP Bed Lower', coachCode: 'SBC', baseFareMinor: 900 },
|
||||
{ name: 'VIP Bed Upper', coachCode: 'SBC', baseFareMinor: 800 },
|
||||
{ name: 'Economy Bed Upper', coachCode: 'HBC', baseFareMinor: 600 },
|
||||
{ name: 'Economy Bed Middle', coachCode: 'HBC', baseFareMinor: 550 },
|
||||
{ name: 'Economy Bed Lower', coachCode: 'HBC', baseFareMinor: 500 },
|
||||
{ name: 'Economy Regular', coachCode: 'HSC', baseFareMinor: 250 },
|
||||
{ name: 'VIP Bed Lower', coachCode: 'SBC', baseFareMinor: 900, premiumMinor: 50, insuranceFeeMinor: 25 },
|
||||
{ name: 'VIP Bed Upper', coachCode: 'SBC', baseFareMinor: 800, premiumMinor: 45, insuranceFeeMinor: 20 },
|
||||
{ name: 'Economy Bed Upper', coachCode: 'HBC', baseFareMinor: 600, premiumMinor: 30, insuranceFeeMinor: 15 },
|
||||
{ name: 'Economy Bed Middle', coachCode: 'HBC', baseFareMinor: 550, premiumMinor: 28, insuranceFeeMinor: 14 },
|
||||
{ name: 'Economy Bed Lower', coachCode: 'HBC', baseFareMinor: 500, premiumMinor: 25, insuranceFeeMinor: 12 },
|
||||
{ name: 'Economy Regular', coachCode: 'HSC', baseFareMinor: 250, premiumMinor: 12, insuranceFeeMinor: 6 },
|
||||
];
|
||||
|
||||
for (const sc of seatClasses) {
|
||||
@@ -168,7 +180,7 @@ async function seedCoachTypesAndClasses() {
|
||||
await prisma.seatClass.upsert({
|
||||
where: { coachTypeId_name: { coachTypeId: ct!.id, name: sc.name } },
|
||||
update: {},
|
||||
create: { coachTypeId: ct!.id, name: sc.name, baseFareMinor: sc.baseFareMinor },
|
||||
create: { coachTypeId: ct!.id, name: sc.name, baseFareMinor: sc.baseFareMinor, premiumMinor: sc.premiumMinor, insuranceFeeMinor: sc.insuranceFeeMinor },
|
||||
});
|
||||
}
|
||||
console.log(` ✅ ${coachTypes.length} coach types, ${seatClasses.length} seat classes created`);
|
||||
@@ -176,14 +188,12 @@ async function seedCoachTypesAndClasses() {
|
||||
|
||||
async function seedRoute() {
|
||||
console.log('\n🛣️ Seeding route and stops...');
|
||||
const firstStation = await prisma.station.findUnique({ where: { code: 'SBT' } });
|
||||
const lastStation = await prisma.station.findUnique({ where: { code: 'NAG' } });
|
||||
|
||||
const route = await prisma.route.upsert({
|
||||
where: { code: 'EDR-101' },
|
||||
where: { code: 'Route-101' },
|
||||
update: {},
|
||||
create: {
|
||||
code: 'EDR-101',
|
||||
code: 'Route-101',
|
||||
name: 'Sebeta - Dire Dawa',
|
||||
description: 'Outbound local route from Sebeta to Dire Dawa',
|
||||
effectiveFrom: new Date('2026-01-01'),
|
||||
@@ -193,15 +203,40 @@ async function seedRoute() {
|
||||
});
|
||||
|
||||
const stationCodes = ['SBT', 'LEB', 'BSH', 'MOJ', 'ADM', 'MTE', 'MIS', 'BIK', 'DRE'];
|
||||
const routeDistancesKm = [0, 11.5, 67.2, 89.9, 106.7, 180.2, 231.6, 293.6, 413.0];
|
||||
for (let i = 0; i < stationCodes.length; i++) {
|
||||
const station = await prisma.station.findUnique({ where: { code: stationCodes[i] } });
|
||||
await prisma.routeStop.upsert({
|
||||
where: { routeId_sequence: { routeId: route.id, sequence: i + 1 } },
|
||||
update: {},
|
||||
create: { routeId: route.id, stationId: station!.id, sequence: i + 1, distanceKm: i * 85 },
|
||||
create: { routeId: route.id, stationId: station!.id, sequence: i + 1, distanceKm: routeDistancesKm[i] },
|
||||
});
|
||||
}
|
||||
console.log(` ✅ Route with ${stationCodes.length} stops created`);
|
||||
|
||||
const returnRoute = await prisma.route.upsert({
|
||||
where: { code: 'Route-102' },
|
||||
update: {},
|
||||
create: {
|
||||
code: 'Route-102',
|
||||
name: 'Dire Dawa - Sebeta',
|
||||
description: 'Inbound local route from Dire Dawa to Sebeta',
|
||||
effectiveFrom: new Date('2026-01-01'),
|
||||
effectiveUntil: new Date('2034-12-31'),
|
||||
active: true,
|
||||
},
|
||||
});
|
||||
|
||||
const returnStationCodes = ['DRE', 'BIK', 'MIS', 'MTE', 'ADM', 'MOJ', 'BSH', 'LEB', 'SBT'];
|
||||
const returnRouteDistancesKm = [0, 119.4, 181.4, 232.8, 306.3, 323.1, 345.8, 401.5, 413.0];
|
||||
for (let i = 0; i < returnStationCodes.length; i++) {
|
||||
const station = await prisma.station.findUnique({ where: { code: returnStationCodes[i] } });
|
||||
await prisma.routeStop.upsert({
|
||||
where: { routeId_sequence: { routeId: returnRoute!.id, sequence: i + 1 } },
|
||||
update: {},
|
||||
create: { routeId: returnRoute!.id, stationId: station!.id, sequence: i + 1, distanceKm: returnRouteDistancesKm[i] },
|
||||
});
|
||||
}
|
||||
console.log(` ✅ Route with ${returnStationCodes.length} stops created`);
|
||||
}
|
||||
|
||||
async function seedCoaches() {
|
||||
@@ -211,9 +246,9 @@ async function seedCoaches() {
|
||||
const vipBedCoachType = await prisma.coachType.findUnique({ where: { id: 'SBC' } });
|
||||
|
||||
const coaches = [
|
||||
{ number: 'HSC-0001', coachTypeId: ecoCoachType!.id, arrangement: '3+2', capacity: 40 },
|
||||
{ number: 'HBC-0001', coachTypeId: ecoBedCoachType!.id, arrangement: '3+0', capacity: 66 },
|
||||
{ number: 'SBC-0001', coachTypeId: vipBedCoachType!.id, arrangement: '2+0', capacity: 120 },
|
||||
{ number: 'HSC-0001', coachTypeId: ecoCoachType!.id, arrangement: '3+2', capacity: 128, sequence: 1 },
|
||||
{ number: 'HBC-0001', coachTypeId: ecoBedCoachType!.id, arrangement: '3+0', capacity: 66, sequence: 2 },
|
||||
{ number: 'SBC-0001', coachTypeId: vipBedCoachType!.id, arrangement: '2+0', capacity: 40, sequence: 3 },
|
||||
];
|
||||
|
||||
let totalSeats = 0;
|
||||
@@ -230,19 +265,23 @@ async function seedCoaches() {
|
||||
// FK violation once BookingSeat/SeatBlock/TicketSeat rows reference them.
|
||||
let seatIndex = 1;
|
||||
for (let row = 1; row <= Math.ceil(coach.capacity / 2); row++) {
|
||||
for (const col of ['A', 'B', 'C', 'D']) {
|
||||
for (const col of ['A', 'B', 'C', 'D', 'E']) {
|
||||
if (seatIndex > coach.capacity) break;
|
||||
let bedPosition: string | null = null;
|
||||
if (c.coachTypeId === ecoBedCoachType!.id || c.coachTypeId === vipBedCoachType!.id) {
|
||||
if (c.coachTypeId === ecoBedCoachType!.id) {
|
||||
// Economy Bed: 3-row cycle (upper, middle, lower)
|
||||
if (row % 3 === 1) bedPosition = 'upper';
|
||||
else if (row % 3 === 2) bedPosition = 'middle';
|
||||
else bedPosition = 'lower';
|
||||
} else if (c.coachTypeId === vipBedCoachType!.id) {
|
||||
// VIP Bed: 2-row cycle (upper, lower)
|
||||
bedPosition = row % 2 === 1 ? 'upper' : 'lower';
|
||||
}
|
||||
|
||||
const seatData = {
|
||||
seatNumber: seatIndex.toString(),
|
||||
isWindow: col === 'A' || col === 'D',
|
||||
isAisle: col === 'B' || col === 'C',
|
||||
isWindow: col === 'A' || col === 'E',
|
||||
isAisle: col === 'B' || col === 'C' || col === 'D',
|
||||
bedPosition,
|
||||
};
|
||||
|
||||
@@ -264,67 +303,102 @@ async function seedTrips() {
|
||||
const train = await prisma.train.upsert({
|
||||
where: { number: 'EDR-001' },
|
||||
update: {},
|
||||
create: { id: TRAIN_ID, number: 'EDR-001', name: 'Djibouti Express' },
|
||||
create: { id: TRAIN_ID, number: 'EDR-001', name: 'Express Service' },
|
||||
});
|
||||
|
||||
const route = await prisma.route.findUnique({ where: { code: 'EDR-101' } });
|
||||
const route = await prisma.route.findUnique({ where: { code: 'Route-101' } });
|
||||
const returnRoute = await prisma.route.findUnique({ where: { code: 'Route-102' } });
|
||||
const firstStation = await prisma.station.findUnique({ where: { code: 'SBT' } });
|
||||
const lastStation = await prisma.station.findUnique({ where: { code: 'DRE' } });
|
||||
const firstReturnStation = await prisma.station.findUnique({ where: { code: 'DRE' } });
|
||||
const lastReturnStation = await prisma.station.findUnique({ where: { code: 'SBT' } });
|
||||
const coaches = await prisma.coach.findMany();
|
||||
|
||||
const now = new Date();
|
||||
const schedules = [];
|
||||
const tomorrow = new Date(now);
|
||||
tomorrow.setDate(now.getDate() + 1);
|
||||
|
||||
for (let d = 0; d < 30; d++) {
|
||||
const schedules = [];
|
||||
|
||||
for (let d = 0; d < 5; d++) {
|
||||
const tripDate = new Date(now);
|
||||
tripDate.setDate(tripDate.getDate() + d);
|
||||
tripDate.setHours(8, 0, 0, 0);
|
||||
|
||||
const departureAt = new Date(tripDate);
|
||||
const arrivalAt = new Date(departureAt.getTime() + 4 * 24 * 60 * 60 * 1000);
|
||||
|
||||
tripDate.setHours(20, 30, 0, 0);
|
||||
schedules.push({
|
||||
trainId: train.id,
|
||||
routeId: route!.id,
|
||||
originStationId: firstStation!.id,
|
||||
destinationStationId: lastStation!.id,
|
||||
departureAt,
|
||||
arrivalAt,
|
||||
durationMinutes: 4 * 24 * 60,
|
||||
stopsCount: 15,
|
||||
departureAt: new Date(tripDate),
|
||||
arrivalAt: new Date(tripDate), // patched below
|
||||
durationMinutes: 0, // patched below
|
||||
stopsCount: 9,
|
||||
});
|
||||
}
|
||||
|
||||
const createdSchedules = await Promise.all(
|
||||
schedules.map(s => prisma.trainSchedule.create({ data: s }))
|
||||
);
|
||||
|
||||
// Create TripStopTimes for each schedule
|
||||
const routeStops = await prisma.routeStop.findMany({
|
||||
where: { routeId: route!.id },
|
||||
orderBy: { sequence: 'asc' },
|
||||
include: { route: true },
|
||||
for (let d = 0; d < 5; d++) {
|
||||
const returnTripDate = new Date(tomorrow);
|
||||
returnTripDate.setDate(returnTripDate.getDate() + d);
|
||||
returnTripDate.setHours(20, 0, 0, 0);
|
||||
schedules.push({
|
||||
trainId: train.id,
|
||||
routeId: returnRoute!.id,
|
||||
originStationId: firstReturnStation!.id,
|
||||
destinationStationId: lastReturnStation!.id,
|
||||
departureAt: new Date(returnTripDate),
|
||||
arrivalAt: new Date(returnTripDate), // patched below
|
||||
durationMinutes: 0, // patched below
|
||||
stopsCount: 9,
|
||||
});
|
||||
}
|
||||
|
||||
// Load route stops for both routes upfront
|
||||
const routeStopsMap = new Map<string, { stationId: string; sequence: number; distanceKm: number }[]>();
|
||||
for (const r of [route!, returnRoute!]) {
|
||||
const stops = await prisma.routeStop.findMany({
|
||||
where: { routeId: r.id },
|
||||
orderBy: { sequence: 'asc' },
|
||||
});
|
||||
routeStopsMap.set(r.id, stops.map(s => ({ stationId: s.stationId, sequence: s.sequence, distanceKm: s.distanceKm! })));
|
||||
}
|
||||
|
||||
// Compute duration from total route distance at 60 km/h
|
||||
function routeDuration(stops: { distanceKm: number }[]): number {
|
||||
const totalKm = stops[stops.length - 1].distanceKm - stops[0].distanceKm;
|
||||
return Math.ceil(totalKm / 60 * 60);
|
||||
}
|
||||
|
||||
// Patch arrivalAt and durationMinutes using distance-based timing
|
||||
const patchedSchedules = schedules.map(s => {
|
||||
const stops = routeStopsMap.get(s.routeId!)!;
|
||||
const durationMinutes = routeDuration(stops);
|
||||
return { ...s, durationMinutes, arrivalAt: new Date(s.departureAt.getTime() + durationMinutes * 60_000) };
|
||||
});
|
||||
|
||||
const createdSchedules = await Promise.all(
|
||||
patchedSchedules.map(s => prisma.trainSchedule.create({ data: s }))
|
||||
);
|
||||
|
||||
// Create TripStopTimes using cumulative distanceKm at 60 km/h
|
||||
for (const schedule of createdSchedules) {
|
||||
const stopTimes = [];
|
||||
for (const routeStop of routeStops) {
|
||||
const minutesFromStart = (routeStop.sequence - 1) * 480; // 8 hours per stop
|
||||
const stops = routeStopsMap.get(schedule.routeId!)!;
|
||||
const originKm = stops[0].distanceKm;
|
||||
const stopTimes = stops.map(stop => {
|
||||
const minutesFromStart = Math.ceil((stop.distanceKm - originKm) / 60 * 60);
|
||||
const plannedDepartureAt = new Date(schedule.departureAt.getTime() + minutesFromStart * 60_000);
|
||||
const plannedArrivalAt = new Date(plannedDepartureAt.getTime() + 30 * 60_000); // 30 min stop
|
||||
|
||||
stopTimes.push({
|
||||
const plannedArrivalAt = new Date(plannedDepartureAt.getTime() - 5 * 60_000); // 5 min dwell
|
||||
return {
|
||||
scheduleId: schedule.id,
|
||||
stationId: routeStop.stationId,
|
||||
sequence: routeStop.sequence,
|
||||
stationId: stop.stationId,
|
||||
sequence: stop.sequence,
|
||||
plannedArrivalAt,
|
||||
plannedDepartureAt,
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
stopTimes.map(st => prisma.tripStopTime.create({ data: st }))
|
||||
);
|
||||
};
|
||||
});
|
||||
// First stop: arrival = departure (no dwell at origin)
|
||||
stopTimes[0].plannedArrivalAt = stopTimes[0].plannedDepartureAt;
|
||||
|
||||
await Promise.all(stopTimes.map(st => prisma.tripStopTime.create({ data: st })));
|
||||
}
|
||||
|
||||
const coachAssignments = [];
|
||||
@@ -355,7 +429,7 @@ async function seedTrips() {
|
||||
|
||||
async function seedFareRules() {
|
||||
console.log('\n💰 Seeding fare rules...');
|
||||
const route = await prisma.route.findUnique({ where: { code: 'EDR-101' } });
|
||||
const route = await prisma.route.findUnique({ where: { code: 'Route-101' } });
|
||||
const seatClasses = await prisma.seatClass.findMany();
|
||||
const validFrom = new Date('2024-01-01');
|
||||
|
||||
@@ -374,7 +448,7 @@ async function seedFareRules() {
|
||||
seatClassId: sc.id,
|
||||
passengerCategory: 'CHILD' as const,
|
||||
baseFareMinor: Math.floor(sc.baseFareMinor * 0.5),
|
||||
discountPercent: 50,
|
||||
discountPercent: 10,
|
||||
currency: 'ETB',
|
||||
validFrom,
|
||||
});
|
||||
@@ -436,13 +510,50 @@ async function seedPaymentMethods() {
|
||||
console.log(` ✅ ${methods.length} payment methods created`);
|
||||
}
|
||||
|
||||
async function seedSegmentFares() {
|
||||
console.log('\n📍 Seeding segment fare rules...');
|
||||
const route = await prisma.route.findUnique({
|
||||
where: { code: 'Route-101' },
|
||||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||||
});
|
||||
const seatClasses = await prisma.seatClass.findMany();
|
||||
const validFrom = new Date('2024-01-01');
|
||||
|
||||
if (route && route.stops.length > 2) {
|
||||
for (const sc of seatClasses) {
|
||||
await prisma.segmentFareRule.create({
|
||||
data: {
|
||||
routeId: route.id,
|
||||
seatClassId: sc.id,
|
||||
originStopSequence: 1,
|
||||
destinationStopSequence: 3,
|
||||
baseFareMinor: Math.floor(sc.baseFareMinor * 0.4),
|
||||
validFrom,
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
await prisma.segmentFareRule.create({
|
||||
data: {
|
||||
routeId: route.id,
|
||||
seatClassId: sc.id,
|
||||
originStopSequence: 5,
|
||||
destinationStopSequence: 9,
|
||||
baseFareMinor: Math.floor(sc.baseFareMinor * 0.6),
|
||||
validFrom,
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
console.log(` ✅ ${seatClasses.length * 2} segment fare rules created`);
|
||||
}
|
||||
}
|
||||
|
||||
async function seedNotificationTemplates() {
|
||||
console.log('\n🔔 Seeding notification templates...');
|
||||
const templates = [
|
||||
{ id: uuidv4(), code: 'BOOKING_CONFIRMED', channel: 'EMAIL', subject: 'Booking Confirmed', bodyTemplate: 'Your booking {{bookingRef}} is confirmed' },
|
||||
{ id: uuidv4(), code: 'PAYMENT_RECEIVED', channel: 'SMS', bodyTemplate: 'Payment received for {{bookingRef}}' },
|
||||
{ id: uuidv4(), code: 'TRIP_DEPARTURE', channel: 'PUSH', bodyTemplate: 'Your trip departs in {{minutes}} minutes' },
|
||||
{ id: uuidv4(), code: 'TRIP_DELAY', channel: 'EMAIL', subject: 'Trip Delayed', bodyTemplate: 'Your trip is delayed by {{delayMinutes}} minutes' },
|
||||
{ id: uuidv4(), code: 'BOOKING_CONFIRMED', channel: 'EMAIL', subject: 'Booking Confirmed', bodyTemplate: 'Your booking {{bookingRef}} is confirmed for {{date}}' },
|
||||
{ id: uuidv4(), code: 'PAYMENT_RECEIVED', channel: 'SMS', bodyTemplate: 'Payment ETB {{amount}} received for {{bookingRef}}' },
|
||||
{ id: uuidv4(), code: 'TRIP_DEPARTURE', channel: 'PUSH', bodyTemplate: 'Your trip {{route}} departs in {{minutes}} minutes' },
|
||||
{ id: uuidv4(), code: 'TRIP_DELAY', channel: 'EMAIL', subject: 'Trip Delayed', bodyTemplate: 'Your trip {{route}} is delayed by {{delayMinutes}} minutes' },
|
||||
{ id: uuidv4(), code: 'PROMOTION', channel: 'PUSH', bodyTemplate: 'Get {{percentOff}}% off on {{route}}' },
|
||||
];
|
||||
|
||||
@@ -476,13 +587,13 @@ async function seedMenuAndFood() {
|
||||
const sandwichId = uuidv4();
|
||||
|
||||
await prisma.menuItem.create({
|
||||
data: { id: coffeeId, scheduleId: schedule.id, categoryId: beverages.id, name: 'Ethiopian Coffee', priceMinor: 5000 },
|
||||
data: { id: coffeeId, scheduleId: schedule.id, categoryId: beverages.id, name: 'Ethiopian Coffee', priceMinor: 50 },
|
||||
}).catch(() => {}); // ignore if exists
|
||||
await prisma.menuItem.create({
|
||||
data: { id: juiceId, scheduleId: schedule.id, categoryId: beverages.id, name: 'Fresh Juice', priceMinor: 3500 },
|
||||
data: { id: juiceId, scheduleId: schedule.id, categoryId: beverages.id, name: 'Fresh Juice', priceMinor: 35 },
|
||||
}).catch(() => {}); // ignore if exists
|
||||
await prisma.menuItem.create({
|
||||
data: { id: sandwichId, scheduleId: schedule.id, categoryId: snacks.id, name: 'Sandwich', priceMinor: 8000 },
|
||||
data: { id: sandwichId, scheduleId: schedule.id, categoryId: snacks.id, name: 'Sandwich', priceMinor: 80 },
|
||||
}).catch(() => {}); // ignore if exists
|
||||
}
|
||||
console.log(` ✅ Menu categories and items created`);
|
||||
@@ -493,7 +604,7 @@ async function seedPromotions() {
|
||||
const promos = [
|
||||
{ id: uuidv4(), title: 'Early Bird Discount', code: 'EARLY20', percentOff: 20, validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) },
|
||||
{ id: uuidv4(), title: 'Student Discount', code: 'STUDENT15', percentOff: 15, validUntil: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000) },
|
||||
{ id: uuidv4(), title: 'Group Booking', code: 'GROUP10', amountOffMinor: 10000, validUntil: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) },
|
||||
{ id: uuidv4(), title: 'Group Booking', code: 'GROUP10', amountOffMinor: 100, validUntil: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) },
|
||||
];
|
||||
|
||||
for (const p of promos) {
|
||||
@@ -583,6 +694,7 @@ async function main() {
|
||||
['promotions', seedPromotions],
|
||||
['FAQ', seedFAQ],
|
||||
['fraud rules', seedFraudRules],
|
||||
['segment fares', seedSegmentFares],
|
||||
];
|
||||
|
||||
let failed = 0;
|
||||
|
||||
Reference in New Issue
Block a user