Files
edr-platform/apps/edr-passenger-api/prisma/seed.ts

556 lines
19 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
const EDR_ROUTE_ID = 'route-edr-main';
const TRAIN_ID = 'train-001';
async function seedSystemUsers() {
console.log('👥 Seeding system users...');
const adminHash = await bcrypt.hash('admin123', 10);
const passengerHash = await bcrypt.hash('password123', 10);
const agentHash = await bcrypt.hash('agent123', 10);
const supervisorHash = await bcrypt.hash('supervisor123', 10);
const staffHash = await bcrypt.hash('staff123', 10);
const admin = await prisma.user.upsert({
where: { email: 'admin@edr-platform.com' },
update: { passwordHash: adminHash, role: 'ADMIN' },
create: {
fullName: 'System Admin',
email: 'admin@edr-platform.com',
phone: '+251900000000',
passwordHash: adminHash,
role: 'ADMIN',
},
});
console.log(' ✅ Admin: admin@edr-platform.com / admin123');
const passenger = await prisma.user.upsert({
where: { email: 'kelemu@email.com' },
update: { passwordHash: passengerHash },
create: {
fullName: 'Kelemu Kebede',
email: 'kelemu@email.com',
phone: '+251911234567',
passwordHash: passengerHash,
role: 'PASSENGER',
nationality: 'Ethiopian',
faydaVerified: true,
},
});
let passengerRecord = await prisma.passenger.findUnique({ where: { userId: passenger.id } });
if (!passengerRecord) {
passengerRecord = await prisma.passenger.create({ data: { userId: passenger.id } });
await prisma.loyaltyAccount.create({
data: { passengerId: passengerRecord.id, pointsBalance: 1500, lifetimePoints: 3000, tier: 'SILVER' },
});
await prisma.walletAccount.create({
data: { passengerId: passengerRecord.id, balanceMinor: 50000 },
});
}
await prisma.userPreferences.upsert({
where: { userId: passenger.id },
update: {},
create: { userId: passenger.id, language: 'en' },
});
console.log(' ✅ Passenger: kelemu@email.com / password123');
const agent = await prisma.user.upsert({
where: { email: 'agent@edr-platform.com' },
update: { passwordHash: agentHash, role: 'AGENT' },
create: {
fullName: 'Booking Agent',
email: 'agent@edr-platform.com',
phone: '+251911111111',
passwordHash: agentHash,
role: 'AGENT',
},
});
await prisma.agent.upsert({
where: { userId: agent.id },
update: {},
create: { userId: agent.id, agentCode: 'AG0001', commissionRate: 5 },
});
console.log(' ✅ Agent: agent@edr-platform.com / agent123');
const supervisor = await prisma.user.upsert({
where: { email: 'supervisor@edr-platform.com' },
update: { passwordHash: supervisorHash, role: 'SUPERVISOR' },
create: {
fullName: 'System Supervisor',
email: 'supervisor@edr-platform.com',
phone: '+251922222222',
passwordHash: supervisorHash,
role: 'SUPERVISOR',
},
});
console.log(' ✅ Supervisor: supervisor@edr-platform.com / supervisor123');
const staff = await prisma.user.upsert({
where: { email: 'staff@edr-platform.com' },
update: { passwordHash: staffHash, role: 'STAFF' },
create: {
fullName: 'Support Staff',
email: 'staff@edr-platform.com',
phone: '+251933333333',
passwordHash: staffHash,
role: 'STAFF',
},
});
console.log(' ✅ Staff: staff@edr-platform.com / staff123');
}
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 },
];
for (const station of stations) {
await prisma.station.upsert({
where: { code: station.code },
update: {},
create: {
id: `station-${station.code.toLowerCase()}`,
...station,
},
});
}
console.log(`${stations.length} stations created`);
return stations;
}
async function seedCoachTypesAndClasses() {
console.log('\n🚂 Seeding coach types and seat classes...');
const coachTypes = [
{ code: 'ECO', name: 'Economy', type: 'passenger' },
{ code: 'ECO_BED', name: 'Economy Bed', type: 'sleeper' },
{ code: 'VIP_BED', name: 'VIP Bed', type: 'sleeper' },
];
for (const ct of coachTypes) {
await prisma.coachType.upsert({
where: { code: ct.code },
update: {},
create: ct,
});
}
const seatClasses = [
{ name: 'ECONOMY_REGULAR', coachCode: 'ECO', baseFareMinor: 35000 },
{ name: 'ECONOMY_WINDOW', coachCode: 'ECO', baseFareMinor: 37000 },
{ name: 'ECONOMY_BED', coachCode: 'ECO_BED', baseFareMinor: 55000 },
{ name: 'VIP_BED', coachCode: 'VIP_BED', baseFareMinor: 85000 },
];
for (const sc of seatClasses) {
const ct = await prisma.coachType.findUnique({ where: { code: sc.coachCode } });
await prisma.seatClass.upsert({
where: { coachTypeId_name: { coachTypeId: ct!.id, name: sc.name } },
update: {},
create: { coachTypeId: ct!.id, name: sc.name, baseFareMinor: sc.baseFareMinor },
});
}
console.log(`${coachTypes.length} coach types, ${seatClasses.length} seat classes created`);
}
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-MAIN' },
update: {},
create: {
id: EDR_ROUTE_ID,
code: 'EDR-MAIN',
name: 'Ethio-Djibouti Railway Main Route',
description: 'Main route connecting Sebeta to Nagad',
effectiveFrom: new Date('2024-01-01'),
effectiveUntil: new Date('2034-12-31'),
active: true,
},
});
const stationCodes = ['SBT', 'LEB', 'BSH', 'MOJ', 'ADM', 'MTE', 'MIS', 'BIK', 'DRE', 'ADG', 'AYS', 'DAW', 'ALS', 'HOL', 'NAG'];
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 },
});
}
console.log(` ✅ Route with ${stationCodes.length} stops created`);
}
async function seedCoaches() {
console.log('\n🚃 Seeding coaches and seats...');
const ecoCoachType = await prisma.coachType.findUnique({ where: { code: 'ECO' } });
const ecoBedCoachType = await prisma.coachType.findUnique({ where: { code: 'ECO_BED' } });
const vipBedCoachType = await prisma.coachType.findUnique({ where: { code: 'VIP_BED' } });
const coaches = [
{ number: 'C-001', coachTypeId: ecoCoachType!.id, arrangement: '2+2', capacity: 48 },
{ number: 'C-002', coachTypeId: ecoCoachType!.id, arrangement: '2+2', capacity: 48 },
{ number: 'C-003', coachTypeId: ecoCoachType!.id, arrangement: '2+2', capacity: 48 },
{ number: 'C-004', coachTypeId: ecoBedCoachType!.id, arrangement: '2+2', capacity: 32 },
{ number: 'C-005', coachTypeId: ecoBedCoachType!.id, arrangement: '2+2', capacity: 32 },
{ number: 'C-006', coachTypeId: vipBedCoachType!.id, arrangement: '1+1', capacity: 16 },
];
let totalSeats = 0;
for (const coach of coaches) {
const c = await prisma.coach.upsert({
where: { number: coach.number },
update: {},
create: coach,
});
let seatIndex = 1;
for (let row = 1; row <= Math.ceil(coach.capacity / 2); row++) {
for (const col of ['A', 'B', 'C', 'D']) {
if (seatIndex <= coach.capacity) {
await prisma.seat.upsert({
where: { coachId_seatNumber: { coachId: c.id, seatNumber: seatIndex.toString() } },
update: {},
create: {
coachId: c.id,
seatNumber: seatIndex.toString(),
row,
col,
isWindow: col === 'A' || col === 'D',
isAisle: col === 'B' || col === 'C',
},
});
seatIndex++;
}
}
}
totalSeats += coach.capacity;
}
console.log(`${coaches.length} coaches with ${totalSeats} seats created`);
}
async function seedTrips() {
console.log('\n🚆 Seeding train service, schedule, and trips...');
const train = await prisma.train.upsert({
where: { number: 'EDR-001' },
update: {},
create: { id: TRAIN_ID, number: 'EDR-001', name: 'Djibouti Express' },
});
const route = await prisma.route.findUnique({ where: { code: 'EDR-MAIN' } });
const firstStation = await prisma.station.findUnique({ where: { code: 'SBT' } });
const lastStation = await prisma.station.findUnique({ where: { code: 'NAG' } });
const coaches = await prisma.coach.findMany();
const now = new Date();
const schedules = [];
// Bulk prepare schedule data
for (let d = 0; d < 30; 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);
schedules.push({
trainId: train.id,
routeId: route!.id,
originStationId: firstStation!.id,
destinationStationId: lastStation!.id,
departureAt,
arrivalAt,
durationMinutes: 4 * 24 * 60,
stopsCount: 15,
});
}
// Bulk create schedules
const createdSchedules = await Promise.all(
schedules.map(s => prisma.trainSchedule.create({ data: s }))
);
// Bulk create coach assignments and live status
const coachAssignments = [];
const liveStatuses = [];
for (const schedule of createdSchedules) {
for (let p = 0; p < coaches.length; p++) {
coachAssignments.push({
scheduleId: schedule.id,
coachId: coaches[p].id,
positionNumber: p + 1,
});
}
liveStatuses.push({
scheduleId: schedule.id,
state: 'scheduled',
progressPercent: 0,
});
}
await Promise.all([
...coachAssignments.map(ca => prisma.coachAssignment.create({ data: ca })),
...liveStatuses.map(ls => prisma.tripLiveStatus.create({ data: ls })),
]);
console.log(` ✅ Train with ${createdSchedules.length} upcoming trips created`);
}
async function seedFareRules() {
console.log('\n💰 Seeding fare rules...');
const route = await prisma.route.findUnique({ where: { code: 'EDR-MAIN' } });
const seatClasses = await prisma.seatClass.findMany();
const validFrom = new Date('2024-01-01');
const fareRules = [];
for (const sc of seatClasses) {
fareRules.push({
routeId: route!.id,
seatClassId: sc.id,
passengerCategory: 'ADULT',
baseFareMinor: sc.baseFareMinor,
currency: 'ETB',
validFrom,
});
fareRules.push({
routeId: route!.id,
seatClassId: sc.id,
passengerCategory: 'CHILD',
baseFareMinor: Math.floor(sc.baseFareMinor * 0.5),
discountPercent: 50,
currency: 'ETB',
validFrom,
});
}
await Promise.all(
fareRules.map(fr => prisma.routeFareRule.create({ data: fr }))
);
console.log(`${fareRules.length} fare rules for ADULT/CHILD categories created`);
}
async function seedCurrency() {
console.log('\n💱 Seeding currency exchange rates...');
const rates = [
{ from: 'ETB', to: 'DJF', rate: 3.25 },
{ from: 'ETB', to: 'USD', rate: 0.018 },
{ from: 'DJF', to: 'ETB', rate: 0.3077 },
{ from: 'USD', to: 'ETB', rate: 55.56 },
];
for (const r of rates) {
await prisma.currencyExchangeRate.upsert({
where: {
fromCurrency_toCurrency_effectiveDate: {
fromCurrency: r.from as any,
toCurrency: r.to as any,
effectiveDate: new Date('2024-01-01'),
},
},
update: { rate: r.rate },
create: {
fromCurrency: r.from as any,
toCurrency: r.to as any,
rate: r.rate,
effectiveDate: new Date('2024-01-01'),
},
});
}
console.log(` ✅ 4 currency exchange rates created`);
}
async function seedPaymentMethods() {
console.log('\n💳 Seeding payment methods...');
const methods = [
{ type: 'TELEBIRR', displayName: 'Telebirr', region: 'ETHIOPIA' },
{ type: 'CBE_BIRR', displayName: 'CBE Birr', region: 'ETHIOPIA' },
{ type: 'EBIRR', displayName: 'eBirr', region: 'ETHIOPIA' },
{ type: 'CARD', displayName: 'Credit/Debit Card', region: 'GLOBAL' },
{ type: 'WALLET', displayName: 'Wallet', region: 'GLOBAL' },
];
for (const m of methods) {
await prisma.paymentMethod.upsert({
where: { type: m.type as any },
update: {},
create: m,
});
}
console.log(`${methods.length} payment methods created`);
}
async function seedNotificationTemplates() {
console.log('\n🔔 Seeding notification templates...');
const templates = [
{ code: 'BOOKING_CONFIRMED', channel: 'EMAIL', subject: 'Booking Confirmed', bodyTemplate: 'Your booking {{bookingRef}} is confirmed' },
{ code: 'PAYMENT_RECEIVED', channel: 'SMS', bodyTemplate: 'Payment received for {{bookingRef}}' },
{ code: 'TRIP_DEPARTURE', channel: 'PUSH', bodyTemplate: 'Your trip departs in {{minutes}} minutes' },
{ code: 'TRIP_DELAY', channel: 'EMAIL', subject: 'Trip Delayed', bodyTemplate: 'Your trip is delayed by {{delayMinutes}} minutes' },
{ code: 'PROMOTION', channel: 'PUSH', bodyTemplate: 'Get {{percentOff}}% off on {{route}}' },
];
for (const t of templates) {
await prisma.notificationTemplate.upsert({
where: { code: t.code },
update: {},
create: t,
});
}
console.log(`${templates.length} notification templates created`);
}
async function seedMenuAndFood() {
console.log('\n🍽 Seeding menu categories and items...');
const beverages = await prisma.menuCategory.upsert({
where: { id: 'cat-beverages' },
update: {},
create: { id: 'cat-beverages', name: 'Beverages' },
});
const snacks = await prisma.menuCategory.upsert({
where: { id: 'cat-snacks' },
update: {},
create: { id: 'cat-snacks', name: 'Snacks' },
});
const schedule = await prisma.trainSchedule.findFirst();
if (schedule) {
await prisma.menuItem.upsert({
where: { id: 'menu-coffee' },
update: {},
create: { id: 'menu-coffee', scheduleId: schedule.id, categoryId: beverages.id, name: 'Ethiopian Coffee', priceMinor: 5000 },
});
await prisma.menuItem.upsert({
where: { id: 'menu-juice' },
update: {},
create: { id: 'menu-juice', scheduleId: schedule.id, categoryId: beverages.id, name: 'Fresh Juice', priceMinor: 3500 },
});
await prisma.menuItem.upsert({
where: { id: 'menu-sandwich' },
update: {},
create: { id: 'menu-sandwich', scheduleId: schedule.id, categoryId: snacks.id, name: 'Sandwich', priceMinor: 8000 },
});
}
console.log(` ✅ Menu categories and items created`);
}
async function seedPromotions() {
console.log('\n🎉 Seeding promotions...');
const promos = [
{ title: 'Early Bird Discount', code: 'EARLY20', percentOff: 20, validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) },
{ title: 'Student Discount', code: 'STUDENT15', percentOff: 15, validUntil: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000) },
{ title: 'Group Booking', code: 'GROUP10', amountOffMinor: 10000, validUntil: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) },
];
for (const p of promos) {
await prisma.promotion.upsert({
where: { code: p.code },
update: {},
create: p,
});
}
console.log(`${promos.length} promotions created`);
}
async function seedFAQ() {
console.log('\n❓ Seeding FAQ...');
const general = await prisma.faqCategory.upsert({
where: { id: 'faq-general' },
update: {},
create: { id: 'faq-general', title: 'General', iconKey: 'help' },
});
const booking = await prisma.faqCategory.upsert({
where: { id: 'faq-booking' },
update: {},
create: { id: 'faq-booking', title: 'Booking', iconKey: 'book' },
});
await prisma.faqArticle.upsert({
where: { id: 'faq-article-1' },
update: {},
create: { id: 'faq-article-1', categoryId: general.id, question: 'What is EDR?', answerMarkdown: 'Ethio-Djibouti Railway' },
});
await prisma.faqArticle.upsert({
where: { id: 'faq-article-2' },
update: {},
create: { id: 'faq-article-2', categoryId: booking.id, question: 'How to book?', answerMarkdown: 'Use the booking system' },
});
console.log(` ✅ FAQ categories and articles created`);
}
async function seedFraudRules() {
console.log('\n🔐 Seeding fraud detection rules...');
const rules = [
{ type: 'RAPID_BOOKINGS', threshold: 10, enabled: true },
{ type: 'HIGH_VALUE_BOOKING', threshold: 500000, enabled: true },
{ type: 'UNUSUAL_DEVICE', threshold: 0.8, enabled: true },
];
for (const r of rules) {
await prisma.fraudRule.upsert({
where: { type: r.type },
update: {},
create: r,
});
}
console.log(`${rules.length} fraud detection rules created`);
}
async function main() {
console.log('🌱 Comprehensive EDR Seed Starting...\n');
await seedSystemUsers();
await seedStations();
await seedCoachTypesAndClasses();
await seedRoute();
await seedCoaches();
await seedTrips();
await seedFareRules();
await seedCurrency();
await seedPaymentMethods();
await seedNotificationTemplates();
await seedMenuAndFood();
await seedPromotions();
await seedFAQ();
await seedFraudRules();
console.log('\n✅ Seed complete!\n');
console.log('🔑 System Users:');
console.log(' Admin: admin@edr-platform.com / admin123');
console.log(' Passenger: kelemu@email.com / password123');
console.log(' Agent: agent@edr-platform.com / agent123');
console.log(' Supervisor: supervisor@edr-platform.com / supervisor123');
console.log(' Staff: staff@edr-platform.com / staff123');
}
main()
.catch((e) => {
console.error('❌ Seed failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});