import { PrismaClient, SeatKind } from '@prisma/client'; import * as bcrypt from 'bcrypt'; const prisma = new PrismaClient(); async function main() { console.log('🌱 Starting complete seed...\n'); // 1. STATIONS console.log('šŸ“ Seeding stations...'); const stationData = [ { code: 'SBT', name: 'Sebeta', city: 'Sebeta', countryCode: 'ET', lat: 8.9167, lng: 38.6167 }, { code: 'LBU', name: 'Labu', city: 'Labu', countryCode: 'ET', lat: 8.8500, lng: 38.7000 }, { code: 'IND', name: 'Indode', city: 'Indode', countryCode: 'ET', lat: 8.7800, lng: 38.8200 }, { code: 'BSH', name: 'Bishoftu', city: 'Bishoftu', countryCode: 'ET', lat: 8.7500, lng: 38.9833 }, { code: 'MJO', name: 'Mojo', city: 'Mojo', countryCode: 'ET', lat: 8.6000, lng: 39.1200 }, { code: 'ADM', name: 'Adama', city: 'Adama', countryCode: 'ET', lat: 8.5400, lng: 39.2675 }, { code: 'DDW', name: 'Diredawa', city: 'Diredawa', countryCode: 'ET', lat: 9.5931, lng: 41.8661 }, { code: 'NGD', name: 'Nagad', city: 'Nagad', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 }, ]; const stations = []; for (const s of stationData) { stations.push(await prisma.station.upsert({ where: { code: s.code }, update: {}, create: s })); } console.log(`āœ… ${stations.length} stations\n`); // 2. SEAT CLASSES console.log('šŸ’ŗ Seeding seat classes...'); const scEconomy = await prisma.seatClass.upsert({ where: { name: 'Economy Regular' }, update: {}, create: { name: 'Economy Regular', description: 'Standard economy', basePrice: 25000, isActive: true }, }); const scBed = await prisma.seatClass.upsert({ where: { name: 'Economy Bed' }, update: {}, create: { name: 'Economy Bed', description: 'Economy bed', basePrice: 35000, isActive: true }, }); console.log(`āœ… 2 seat classes\n`); // 3. ROUTES console.log('šŸ›¤ļø Seeding routes...'); const route1 = await prisma.route.upsert({ where: { code: 'SBT-NGD' }, update: {}, create: { code: 'SBT-NGD', name: 'Sebeta-Nagad Express', effectiveFrom: new Date('2026-01-01'), active: true }, }); await prisma.routeStop.createMany({ data: [ { routeId: route1.id, stationId: stations[0].id, sequence: 1, distanceKm: 0 }, { routeId: route1.id, stationId: stations[1].id, sequence: 2, distanceKm: 15 }, { routeId: route1.id, stationId: stations[2].id, sequence: 3, distanceKm: 28 }, { routeId: route1.id, stationId: stations[3].id, sequence: 4, distanceKm: 45 }, { routeId: route1.id, stationId: stations[4].id, sequence: 5, distanceKm: 73 }, { routeId: route1.id, stationId: stations[5].id, sequence: 6, distanceKm: 99 }, { routeId: route1.id, stationId: stations[6].id, sequence: 7, distanceKm: 378 }, { routeId: route1.id, stationId: stations[7].id, sequence: 8, distanceKm: 756 }, ], skipDuplicates: true, }); await prisma.routeFareRule.createMany({ data: [ { routeId: route1.id, seatClassId: scEconomy.id, passengerCategory: 'ADULT', baseFareMinor: 65000, validFrom: new Date('2026-01-01') }, { routeId: route1.id, seatClassId: scEconomy.id, passengerCategory: 'CHILD', baseFareMinor: 65000, validFrom: new Date('2026-01-01') }, { routeId: route1.id, seatClassId: scBed.id, passengerCategory: 'ADULT', baseFareMinor: 91000, validFrom: new Date('2026-01-01') }, { routeId: route1.id, seatClassId: scBed.id, passengerCategory: 'CHILD', baseFareMinor: 91000, validFrom: new Date('2026-01-01') }, ], skipDuplicates: true, }); console.log(`āœ… 1 route with stops and fares\n`); // 4. TRAINS console.log('šŸš‚ Seeding trains...'); const train = await prisma.train.upsert({ where: { number: '301' }, update: {}, create: { number: '301', name: 'Express 301', description: 'Main Express' }, }); console.log(`āœ… 1 train\n`); // 5. COACHES & SEATS console.log('🚃 Seeding coaches...'); const coach1 = await prisma.coach.upsert({ where: { coachNumber: 'C-A1' }, update: {}, create: { coachNumber: 'C-A1', label: 'A', seatClassId: scEconomy.id, mode: 'seat', totalUnits: 20 }, }); const existingSeats = await prisma.seat.count({ where: { coachId: coach1.id } }); if (existingSeats === 0) { const seats = []; for (let row = 1; row <= 5; row++) { for (const col of ['A', 'B', 'C', 'D']) { seats.push({ coachId: coach1.id, row, col, label: `${row}${col}`, seatNumber: `A${row}${col}`, kind: 'STANDARD' as SeatKind, }); } } await prisma.seat.createMany({ data: seats }); } console.log(`āœ… 1 coach with 20 seats\n`); // 6. SCHEDULE console.log('šŸ“… Seeding schedule...'); const existingSchedules = await prisma.trainSchedule.findMany({ where: { trainId: train.id }, select: { id: true } }); if (existingSchedules.length > 0) { const scheduleIds = existingSchedules.map(s => s.id); // Delete in correct order to avoid foreign key constraints await prisma.bookingSeat.deleteMany({ where: { booking: { scheduleId: { in: scheduleIds } } } }); await prisma.booking.deleteMany({ where: { scheduleId: { in: scheduleIds } } }); await prisma.fareRule.deleteMany({ where: { tripId: { in: scheduleIds } } }); await prisma.tripStopTime.deleteMany({ where: { scheduleId: { in: scheduleIds } } }); await prisma.coachAssignment.deleteMany({ where: { scheduleId: { in: scheduleIds } } }); await prisma.trainSchedule.deleteMany({ where: { trainId: train.id } }); } const schedule = await prisma.trainSchedule.create({ data: { trainId: train.id, routeId: route1.id, originStationId: stations[0].id, destinationStationId: stations[7].id, departureAt: new Date('2026-06-15T06:00:00Z'), arrivalAt: new Date('2026-06-15T22:00:00Z'), durationMinutes: 960, stopsCount: 8, }, }); await prisma.coachAssignment.create({ data: { scheduleId: schedule.id, coachId: coach1.id, positionNumber: 1 }, }); await prisma.tripStopTime.createMany({ data: [ { scheduleId: schedule.id, stationId: stations[0].id, sequence: 1, plannedDepartureAt: new Date('2026-06-15T06:00:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[1].id, sequence: 2, plannedArrivalAt: new Date('2026-06-15T07:00:00Z'), plannedDepartureAt: new Date('2026-06-15T07:05:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[2].id, sequence: 3, plannedArrivalAt: new Date('2026-06-15T08:00:00Z'), plannedDepartureAt: new Date('2026-06-15T08:05:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[3].id, sequence: 4, plannedArrivalAt: new Date('2026-06-15T09:00:00Z'), plannedDepartureAt: new Date('2026-06-15T09:10:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[4].id, sequence: 5, plannedArrivalAt: new Date('2026-06-15T10:00:00Z'), plannedDepartureAt: new Date('2026-06-15T10:10:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[5].id, sequence: 6, plannedArrivalAt: new Date('2026-06-15T11:00:00Z'), plannedDepartureAt: new Date('2026-06-15T11:15:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[6].id, sequence: 7, plannedArrivalAt: new Date('2026-06-15T15:00:00Z'), plannedDepartureAt: new Date('2026-06-15T15:20:00Z'), status: 'UPCOMING' }, { scheduleId: schedule.id, stationId: stations[7].id, sequence: 8, plannedArrivalAt: new Date('2026-06-15T22:00:00Z'), status: 'UPCOMING' }, ], }); console.log(`āœ… 1 schedule with stops\n`); // 7. USERS console.log('šŸ‘„ Seeding users...'); const adminHash = await bcrypt.hash('admin123', 10); const userHash = await bcrypt.hash('password123', 10); await prisma.user.upsert({ where: { email: 'admin@edr-platform.com' }, update: {}, create: { fullName: 'Admin', email: 'admin@edr-platform.com', phone: '+251900000000', passwordHash: adminHash, role: 'ADMIN' }, }); const user = await prisma.user.upsert({ where: { email: 'abebe@email.com' }, update: {}, create: { fullName: 'Abebe Kebede', email: 'abebe@email.com', phone: '+251912345678', passwordHash: userHash, nationality: 'Ethiopian' }, }); let passenger = await prisma.passenger.findUnique({ where: { userId: user.id } }); if (!passenger) { passenger = await prisma.passenger.create({ data: { userId: user.id } }); await prisma.loyaltyAccount.create({ data: { passengerId: passenger.id, pointsBalance: 1000, tier: 'BRONZE' } }); await prisma.walletAccount.create({ data: { passengerId: passenger.id, balanceMinor: 100000 } }); } console.log(`āœ… 2 users\n`); // 8. SUPPORTING DATA console.log('šŸ“¦ Seeding supporting data...'); await prisma.paymentMethod.upsert({ where: { type: 'TELEBIRR' }, update: {}, create: { type: 'TELEBIRR', displayName: 'Telebirr', region: 'ETHIOPIA', currency: 'ETB', enabled: true, sortOrder: 1 }, }); await prisma.currencyExchangeRate.deleteMany({}); await prisma.currencyExchangeRate.createMany({ data: [ { fromCurrency: 'ETB', toCurrency: 'ETB', rate: 1.0, effectiveDate: new Date() }, { fromCurrency: 'ETB', toCurrency: 'USD', rate: 0.018, effectiveDate: new Date() }, { fromCurrency: 'ETB', toCurrency: 'DJF', rate: 3.2, effectiveDate: new Date() }, ], }); console.log(`āœ… Payment methods and currencies\n`); console.log('āœ… SEED COMPLETE!\n'); console.log('šŸ“‹ Summary:'); console.log(' - 8 Stations'); console.log(' - 2 Seat Classes'); console.log(' - 1 Route with 8 stops'); console.log(' - 1 Train with 1 schedule'); console.log(' - 1 Coach with 20 seats'); console.log(' - 2 Users (Admin + Passenger)'); console.log('\nšŸ”‘ Credentials:'); console.log(' Admin: admin@edr-platform.com / admin123'); console.log(' User: abebe@email.com / password123'); } main() .catch((e) => { console.error('āŒ Error:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });