Import operation gate pass

This commit is contained in:
hagiye
2026-06-27 21:04:27 +03:00
parent 3441ee4a0a
commit fee4365b70
37 changed files with 3084 additions and 116 deletions

View File

@@ -0,0 +1,28 @@
import 'reflect-metadata';
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve(__dirname, '../../.env') });
import { NestFactory } from '@nestjs/core';
import { AppModule } from '../app.module';
import { ApprovedFirstLastMileDemoBookingsSeeder } from '../seed/approved-first-lastmile-demo-bookings.seeder';
async function main() {
const app = await NestFactory.createApplicationContext(AppModule, {
logger: ['error', 'warn', 'log'],
});
try {
const seeder = app.get(ApprovedFirstLastMileDemoBookingsSeeder);
await seeder.run();
console.log('Approved first/last-mile demo bookings seeded.');
} finally {
await app.close();
}
}
main().catch((err) => {
console.error('Approved first/last-mile demo booking seed failed:', err);
process.exit(1);
});

View File

@@ -0,0 +1,219 @@
import 'reflect-metadata';
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve(__dirname, '../../.env') });
import { NestFactory } from '@nestjs/core';
import { DataSource } from 'typeorm';
import { AppModule } from '../app.module';
import { Booking } from '../modules/bookings/entities/booking.entity';
import { Locomotive } from '../modules/locomotives/entities/locomotive.entity';
import { ImportDjiboutiOperation } from '../modules/train-scheduling/entities/import-djibouti-operation.entity';
import { CargoType } from '../modules/rule-engine/entities/cargo-type.entity';
import { ServiceType } from '../modules/rule-engine/entities/service-type.entity';
import { Yard } from '../modules/rule-engine/entities/yard.entity';
import { TrainSchedule } from '../modules/train-schedules/entities/train-schedule.entity';
import { TrainScheduleBooking } from '../modules/train-schedules/entities/train-schedule-booking.entity';
import { TrainSet } from '../modules/train-sets/entities/train-set.entity';
const DEMO_TRAINS = [
{
trainNumber: 'IMP-DJB-NAGAD-01',
bookingRefs: ['IMP-DJB-NGD-001', 'IMP-DJB-NGD-002', 'IMP-DJB-NGD-003'],
departureOffsetHours: 4,
},
{
trainNumber: 'IMP-DJB-NAGAD-02',
bookingRefs: ['IMP-DJB-NGD-004', 'IMP-DJB-NGD-005', 'IMP-DJB-NGD-006'],
departureOffsetHours: 8,
},
];
function addHours(date: Date, hours: number): Date {
return new Date(date.getTime() + hours * 60 * 60 * 1000);
}
async function main() {
const app = await NestFactory.createApplicationContext(AppModule, {
logger: ['error', 'warn', 'log'],
});
try {
const dataSource = app.get(DataSource);
const yardRepo = dataSource.getRepository(Yard);
const serviceTypeRepo = dataSource.getRepository(ServiceType);
const cargoTypeRepo = dataSource.getRepository(CargoType);
const locomotiveRepo = dataSource.getRepository(Locomotive);
const trainSetRepo = dataSource.getRepository(TrainSet);
const scheduleRepo = dataSource.getRepository(TrainSchedule);
const bookingRepo = dataSource.getRepository(Booking);
const scheduleBookingRepo = dataSource.getRepository(TrainScheduleBooking);
const importOperationRepo = dataSource.getRepository(ImportDjiboutiOperation);
const originYard =
(await yardRepo.findOne({ where: { code: 'NAGAD' } })) ??
(await yardRepo.findOne({ where: { country: 'Djibouti' } }));
const destinationYard =
(await yardRepo.findOne({ where: { code: 'INDODE' } })) ??
(await yardRepo.save(
yardRepo.create({
code: 'INDODE',
label: 'Indode Dry Port',
country: 'Ethiopia',
isActive: true,
displayOrder: 6,
}),
));
const serviceType =
(await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ??
(await serviceTypeRepo.findOne({ where: { isActive: true } }));
const cargoType = await cargoTypeRepo.findOne({ where: { isActive: true } });
const missing = [
!originYard ? 'NAGAD/Djibouti origin yard' : '',
!destinationYard ? 'INDODE destination yard' : '',
!serviceType ? 'service type' : '',
].filter(Boolean);
if (missing.length) {
throw new Error(`Cannot seed Djibouti-side import demo, missing: ${missing.join(', ')}`);
}
const locomotive =
(await locomotiveRepo.findOne({ where: { code: 'ICD-DEMO-IMP-LOCO' } })) ??
(await locomotiveRepo.save(
locomotiveRepo.create({
code: 'ICD-DEMO-IMP-LOCO',
name: 'Djibouti Import Demo Locomotive',
locomotiveType: 'DIESEL',
maxPullWeightTons: 4200,
maxTrainLengthMeters: 760,
status: 'IMPORT_READY',
currentYardId: originYard!.id,
}),
));
const now = new Date();
const seededSchedules: TrainSchedule[] = [];
for (const [trainIndex, demo] of DEMO_TRAINS.entries()) {
const existingSchedule = await scheduleRepo.findOne({
where: { trainNumber: demo.trainNumber },
});
if (existingSchedule) {
console.log(`Djibouti-side import demo already seeded: ${demo.trainNumber}`);
console.log(`Schedule ID: ${existingSchedule.id}`);
seededSchedules.push(existingSchedule);
continue;
}
const departure = addHours(now, demo.departureOffsetHours);
const arrival = addHours(departure, 12);
const trainSet = await trainSetRepo.save(
trainSetRepo.create({
locomotiveId: locomotive.id,
totalWeightTons: 900 + trainIndex * 120,
totalLengthMeters: 430 + trainIndex * 25,
wagonCount: 18 + trainIndex * 2,
status: 'ASSIGNED',
}),
);
const schedule = await scheduleRepo.save(
scheduleRepo.create({
trainSetId: trainSet.id,
originStationId: originYard!.id,
destinationStationId: destinationYard!.id,
scheduledDepartureDate: departure,
scheduledArrivalDate: arrival,
status: 'SCHEDULED' as TrainSchedule['status'],
trainNumber: demo.trainNumber,
direction: 'IMPORT',
maxWagons: 53,
bookingWindowStatus: 'CLOSED',
}),
);
for (const [bookingIndex, reference] of demo.bookingRefs.entries()) {
const weight = 6800 + trainIndex * 900 + bookingIndex * 750;
const booking = await bookingRepo.save(
bookingRepo.create({
reference,
originYardId: originYard!.id,
destinationYardId: destinationYard!.id,
serviceTypeId: serviceType!.id,
status: 'PAID',
paymentStatus: 'PAID',
scheduledDate: departure,
contractType: 'SPOT',
equipmentReturn: 'TERMINAL',
paymentCurrency: 'ETB',
totalAmount: 0,
isGovernment: false,
tradeDirection: 'IMPORT',
freightType: bookingIndex % 2 === 0 ? 'CONTAINER' : 'BULK',
cargoTypeId: cargoType?.id ?? null,
cargoFreeText: cargoType
? null
: `Djibouti import demo cargo ${trainIndex + 1}-${bookingIndex + 1}`,
cargoTotalWeightVgm: weight,
priorityScore: 80 - trainIndex * 5 - bookingIndex,
trainScheduleId: schedule.id,
schedulingStatus: 'SCHEDULED',
scheduledAt: now,
}),
);
await scheduleBookingRepo.save(
scheduleBookingRepo.create({
trainScheduleId: schedule.id,
bookingId: booking.id,
}),
);
}
await importOperationRepo.save(
importOperationRepo.create({
trainScheduleId: schedule.id,
documents: {
DELIVERY_ORDER: {
reference: `DO-${demo.trainNumber}`,
uploadedAt: now.toISOString(),
uploadedBy: 'Demo Seeder',
notes: 'Demo delivery order for Djibouti-side import flow',
},
RAILWAY_BILL: {
reference: `RB-${demo.trainNumber}`,
uploadedAt: now.toISOString(),
uploadedBy: 'Demo Seeder',
notes: 'Demo railway bill for Djibouti-side import flow',
},
},
performedBy: 'Demo Seeder',
notes: '[ICD-DEMO] Nagad to Indode import train for Djibouti-side flow',
}),
);
seededSchedules.push(schedule);
}
console.log('Djibouti-side import demo seeded.');
console.log(`Corridor: ${originYard!.code} -> ${destinationYard!.code}`);
for (const schedule of seededSchedules) {
console.log(`Train number: ${schedule.trainNumber}`);
console.log(`Schedule ID: ${schedule.id}`);
console.log(`Backoffice URL: /dashboard/operations/train-scheduling-v2/${schedule.id}`);
}
} finally {
await app.close();
}
}
main().catch((error) => {
console.error('Djibouti-side import demo seed failed:', error);
process.exit(1);
});