mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 10:10:57 +00:00
623 lines
24 KiB
TypeScript
623 lines
24 KiB
TypeScript
import 'reflect-metadata';
|
|
import { config } from 'dotenv';
|
|
import { resolve } from 'path';
|
|
import { WagonStatus } from '@edr/types';
|
|
import { In } from 'typeorm';
|
|
|
|
config({ path: resolve(__dirname, '../../.env') });
|
|
|
|
import { AppDataSource } from '../data-source';
|
|
import { BookingContainer } from '../modules/bookings/entities/booking-container.entity';
|
|
import { Booking } from '../modules/bookings/entities/booking.entity';
|
|
import { CompanyProfile, ProfileStatus, ProfileType } from '../modules/companies/entities/company-profile.entity';
|
|
import { Company, CompanyKind, CompanyStatus, CompanyType } from '../modules/companies/entities/company.entity';
|
|
import { Container } from '../modules/container-management/entities/container.entity';
|
|
import { Locomotive } from '../modules/locomotives/entities/locomotive.entity';
|
|
import { ContainerType } from '../modules/rule-engine/entities/container-type.entity';
|
|
import { ServiceType } from '../modules/rule-engine/entities/service-type.entity';
|
|
import { Yard } from '../modules/rule-engine/entities/yard.entity';
|
|
import { ImportDjiboutiOperation } from '../modules/train-scheduling/entities/import-djibouti-operation.entity';
|
|
import { TrainScheduleBooking } from '../modules/train-schedules/entities/train-schedule-booking.entity';
|
|
import { WagonAllocationContainerItem } from '../modules/train-schedules/entities/wagon-allocation-container-item.entity';
|
|
import { WagonBookingAllocation } from '../modules/train-schedules/entities/wagon-booking-allocation.entity';
|
|
import { TrainSchedule } from '../modules/train-schedules/entities/train-schedule.entity';
|
|
import { TrainSetWagon } from '../modules/train-sets/entities/train-set-wagon.entity';
|
|
import { TrainSet } from '../modules/train-sets/entities/train-set.entity';
|
|
import { WagonType } from '../modules/wagon-types/entities/wagon-type.entity';
|
|
import { Wagon } from '../modules/wagons/entities/wagon.entity';
|
|
import { WarehouseInventory } from '../modules/warehouses/entities/warehouse-inventory.entity';
|
|
import { WarehouseYard } from '../modules/warehouses/entities/warehouse-yard.entity';
|
|
import { WarehouseZone } from '../modules/warehouses/entities/warehouse-zone.entity';
|
|
import { Warehouse } from '../modules/warehouses/entities/warehouse.entity';
|
|
|
|
type Direction = 'IMPORT' | 'EXPORT';
|
|
type TrainStatus = 'SCHEDULED' | 'ARRIVED';
|
|
|
|
interface ScenarioTrain {
|
|
trainNumber: string;
|
|
direction: Direction;
|
|
status: TrainStatus;
|
|
departureOffsetHours: number;
|
|
arrivalOffsetHours: number;
|
|
bookings: Array<{
|
|
reference: string;
|
|
mileVariant: 'FIRST_MILE' | 'LAST_MILE' | 'TERMINAL';
|
|
containerNumber: string;
|
|
weightTons: number;
|
|
}>;
|
|
}
|
|
|
|
const SCENARIOS: ScenarioTrain[] = [
|
|
{
|
|
trainNumber: 'GP-IMP-ARR-01',
|
|
direction: 'IMPORT',
|
|
status: 'ARRIVED',
|
|
departureOffsetHours: -18,
|
|
arrivalOffsetHours: -6,
|
|
bookings: [
|
|
{ reference: 'GP-IMP-ARR-LM-001', mileVariant: 'LAST_MILE', containerNumber: 'GPIM0000001', weightTons: 22 },
|
|
{ reference: 'GP-IMP-ARR-TM-002', mileVariant: 'TERMINAL', containerNumber: 'GPIM0000002', weightTons: 24 },
|
|
],
|
|
},
|
|
{
|
|
trainNumber: 'GP-IMP-NARR-01',
|
|
direction: 'IMPORT',
|
|
status: 'SCHEDULED',
|
|
departureOffsetHours: 6,
|
|
arrivalOffsetHours: 18,
|
|
bookings: [
|
|
{ reference: 'GP-IMP-NARR-LM-001', mileVariant: 'LAST_MILE', containerNumber: 'GPIM0000003', weightTons: 21 },
|
|
{ reference: 'GP-IMP-NARR-TM-002', mileVariant: 'TERMINAL', containerNumber: 'GPIM0000004', weightTons: 23 },
|
|
],
|
|
},
|
|
{
|
|
trainNumber: 'GP-EXP-ARR-01',
|
|
direction: 'EXPORT',
|
|
status: 'ARRIVED',
|
|
departureOffsetHours: -16,
|
|
arrivalOffsetHours: -4,
|
|
bookings: [
|
|
{ reference: 'GP-EXP-ARR-FM-001', mileVariant: 'FIRST_MILE', containerNumber: 'GPEX0000001', weightTons: 20 },
|
|
{ reference: 'GP-EXP-ARR-TM-002', mileVariant: 'TERMINAL', containerNumber: 'GPEX0000002', weightTons: 22 },
|
|
],
|
|
},
|
|
{
|
|
trainNumber: 'GP-EXP-NARR-01',
|
|
direction: 'EXPORT',
|
|
status: 'SCHEDULED',
|
|
departureOffsetHours: 8,
|
|
arrivalOffsetHours: 20,
|
|
bookings: [
|
|
{ reference: 'GP-EXP-NARR-FM-001', mileVariant: 'FIRST_MILE', containerNumber: 'GPEX0000003', weightTons: 19 },
|
|
{ reference: 'GP-EXP-NARR-TM-002', mileVariant: 'TERMINAL', containerNumber: 'GPEX0000004', weightTons: 21 },
|
|
],
|
|
},
|
|
];
|
|
|
|
const addHours = (date: Date, hours: number): Date => new Date(date.getTime() + hours * 60 * 60 * 1000);
|
|
|
|
async function main() {
|
|
const dataSource = await AppDataSource.initialize();
|
|
|
|
try {
|
|
const seeded = await dataSource.transaction(async (manager) => {
|
|
if (await isAlreadySeeded(manager)) {
|
|
return null;
|
|
}
|
|
const refs = await ensureReferences(manager);
|
|
const now = new Date();
|
|
const result: Array<{ trainNumber: string; bookings: string[] }> = [];
|
|
|
|
for (const scenario of SCENARIOS) {
|
|
const schedule = await seedScenarioTrain(manager, scenario, refs, now);
|
|
result.push({
|
|
trainNumber: schedule.trainNumber ?? scenario.trainNumber,
|
|
bookings: scenario.bookings.map((booking) => booking.reference),
|
|
});
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
console.log('Gate-pass train scenario seed complete.');
|
|
if (seeded) {
|
|
for (const row of seeded) {
|
|
console.log(`${row.trainNumber}: ${row.bookings.join(', ')}`);
|
|
}
|
|
} else {
|
|
console.log('Gate-pass train scenarios already seeded; nothing changed.');
|
|
}
|
|
} finally {
|
|
await dataSource.destroy();
|
|
}
|
|
}
|
|
|
|
async function isAlreadySeeded(manager: any): Promise<boolean> {
|
|
const scheduleRepo = manager.getRepository(TrainSchedule);
|
|
const bookingRepo = manager.getRepository(Booking);
|
|
const trainNumbers = SCENARIOS.map((scenario) => scenario.trainNumber);
|
|
const bookingRefs = SCENARIOS.flatMap((scenario) => scenario.bookings.map((booking) => booking.reference));
|
|
|
|
const [scheduleCount, bookingCount] = await Promise.all([
|
|
scheduleRepo.count({ where: { trainNumber: In(trainNumbers) } }),
|
|
bookingRepo.count({ where: { reference: In(bookingRefs) } }),
|
|
]);
|
|
|
|
return scheduleCount === trainNumbers.length && bookingCount === bookingRefs.length;
|
|
}
|
|
|
|
async function ensureReferences(manager: any) {
|
|
const yardRepo = manager.getRepository(Yard);
|
|
const serviceTypeRepo = manager.getRepository(ServiceType);
|
|
const containerTypeRepo = manager.getRepository(ContainerType);
|
|
const wagonTypeRepo = manager.getRepository(WagonType);
|
|
const companyRepo = manager.getRepository(Company);
|
|
const profileRepo = manager.getRepository(CompanyProfile);
|
|
const warehouseRepo = manager.getRepository(Warehouse);
|
|
const warehouseYardRepo = manager.getRepository(WarehouseYard);
|
|
const warehouseZoneRepo = manager.getRepository(WarehouseZone);
|
|
|
|
const djiboutiYard =
|
|
(await yardRepo.findOne({ where: { code: 'NAGAD' } })) ??
|
|
(await yardRepo.findOne({ where: { code: 'DJIB_PORT' } })) ??
|
|
(await yardRepo.findOne({ where: { country: 'Djibouti' } })) ??
|
|
(await yardRepo.save(
|
|
yardRepo.create({
|
|
code: 'NAGAD',
|
|
label: 'Nagad Port',
|
|
country: 'Djibouti',
|
|
isActive: true,
|
|
displayOrder: 90,
|
|
}),
|
|
));
|
|
|
|
const ethiopiaYard =
|
|
(await yardRepo.findOne({ where: { code: 'INDODE' } })) ??
|
|
(await yardRepo.findOne({ where: { code: 'MOJO' } })) ??
|
|
(await yardRepo.findOne({ where: { country: 'Ethiopia' } })) ??
|
|
(await yardRepo.save(
|
|
yardRepo.create({
|
|
code: 'INDODE',
|
|
label: 'Indode Dry Port',
|
|
country: 'Ethiopia',
|
|
isActive: true,
|
|
displayOrder: 91,
|
|
}),
|
|
));
|
|
|
|
const serviceType =
|
|
(await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ??
|
|
(await serviceTypeRepo.save(
|
|
serviceTypeRepo.create({
|
|
code: 'RAIL_CONTAINER',
|
|
serviceName: 'Rail Container Service',
|
|
description: 'Rail container service for gate-pass scenario seed',
|
|
canBeBookedAlone: true,
|
|
includesFirstMile: false,
|
|
includesLastMile: false,
|
|
includesCustoms: false,
|
|
isActive: true,
|
|
displayOrder: 1,
|
|
}),
|
|
));
|
|
|
|
const containerType =
|
|
(await containerTypeRepo.findOne({ where: { code: '40FT' } })) ??
|
|
(await containerTypeRepo.findOne({ where: { isActive: true } })) ??
|
|
(await containerTypeRepo.save(
|
|
containerTypeRepo.create({
|
|
code: '40FT',
|
|
label: '40FT',
|
|
sizeFt: 40,
|
|
isReefer: false,
|
|
isOpenTop: false,
|
|
isActive: true,
|
|
displayOrder: 1,
|
|
}),
|
|
));
|
|
|
|
const wagonType =
|
|
(await wagonTypeRepo.findOne({ where: { code: 'GP-FLAT' } })) ??
|
|
(await wagonTypeRepo.findOne({ where: { supportsContainer: true } })) ??
|
|
(await wagonTypeRepo.findOne({ where: { isActive: true } })) ??
|
|
(await wagonTypeRepo.save(
|
|
wagonTypeRepo.create({
|
|
code: 'GP-FLAT',
|
|
name: 'Gate Pass Demo Flat Wagon',
|
|
capacityTons: 70,
|
|
lengthMeters: 14,
|
|
supportedLoadTypes: ['CONTAINER'],
|
|
isActive: true,
|
|
equatedLengthM: 14,
|
|
tareWeightTons: 20,
|
|
supportsContainer: true,
|
|
maxContainerGrossT: 70,
|
|
}),
|
|
));
|
|
|
|
const company =
|
|
(await companyRepo.findOne({ where: { tin: 'GTPASS001' } })) ??
|
|
(await companyRepo.save(
|
|
companyRepo.create({
|
|
name: 'Gate Pass Scenario Customer',
|
|
type: CompanyType.Customer,
|
|
kind: CompanyKind.Commercial,
|
|
status: CompanyStatus.Active,
|
|
tin: 'GTPASS001',
|
|
vatNumber: 'GTPASS001',
|
|
fanNumber: 'GTPASS0000001',
|
|
country: 'Ethiopia',
|
|
address: 'Indode Dry Port',
|
|
phone: '251900000555',
|
|
email: 'gate-pass-scenarios@edr.local',
|
|
contactPersonName: 'Gate Pass Tester',
|
|
contactPersonPhone: '251900000555',
|
|
}),
|
|
));
|
|
|
|
const importerProfile = await ensureProfile(profileRepo, company.id, ProfileType.importer, 'GP-IMP');
|
|
const exporterProfile = await ensureProfile(profileRepo, company.id, ProfileType.exporter, 'GP-EXP');
|
|
|
|
const warehouse =
|
|
(await warehouseRepo.findOne({ where: { code: 'INDODE_OPEN' } })) ??
|
|
(await warehouseRepo.findOne({ where: {} }));
|
|
if (!warehouse) {
|
|
throw new Error('No warehouse found. Run the Indode/warehouse seed before gate-pass scenarios.');
|
|
}
|
|
const warehouseYard = await warehouseYardRepo.findOne({ where: { warehouseId: warehouse.id } });
|
|
if (!warehouseYard) {
|
|
throw new Error(`No warehouse yard found for ${warehouse.code ?? warehouse.id}.`);
|
|
}
|
|
const warehouseZone = await warehouseZoneRepo.findOne({ where: { yardId: warehouseYard.id } });
|
|
if (!warehouseZone) {
|
|
throw new Error(`No warehouse zone found for yard ${warehouseYard.id}.`);
|
|
}
|
|
|
|
return {
|
|
djiboutiYard,
|
|
ethiopiaYard,
|
|
serviceType,
|
|
containerType,
|
|
wagonType,
|
|
company,
|
|
importerProfile,
|
|
exporterProfile,
|
|
warehouse,
|
|
warehouseYard,
|
|
warehouseZone,
|
|
};
|
|
}
|
|
|
|
async function ensureProfile(repo: any, companyId: string, type: ProfileType, reference: string): Promise<CompanyProfile> {
|
|
const existing = await repo.findOne({ where: { companyId, type } });
|
|
if (existing) return existing;
|
|
return repo.save(
|
|
repo.create({
|
|
companyId,
|
|
type,
|
|
reference,
|
|
status: ProfileStatus.Active,
|
|
businessLicense: `${reference}-LICENSE`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function seedScenarioTrain(manager: any, scenario: ScenarioTrain, refs: Awaited<ReturnType<typeof ensureReferences>>, now: Date) {
|
|
const locomotiveRepo = manager.getRepository(Locomotive);
|
|
const trainSetRepo = manager.getRepository(TrainSet);
|
|
const scheduleRepo = manager.getRepository(TrainSchedule);
|
|
const trainSetWagonRepo = manager.getRepository(TrainSetWagon);
|
|
const wagonRepo = manager.getRepository(Wagon);
|
|
|
|
const departure = addHours(now, scenario.departureOffsetHours);
|
|
const arrival = addHours(now, scenario.arrivalOffsetHours);
|
|
const isArrived = scenario.status === 'ARRIVED';
|
|
const originYard = scenario.direction === 'IMPORT' ? refs.djiboutiYard : refs.ethiopiaYard;
|
|
const destinationYard = scenario.direction === 'IMPORT' ? refs.ethiopiaYard : refs.djiboutiYard;
|
|
const totalWeightTons = scenario.bookings.reduce((sum, booking) => sum + booking.weightTons, 0);
|
|
|
|
const locomotive =
|
|
(await locomotiveRepo.findOne({ where: { code: 'GP-DEMO-LOCO' } })) ??
|
|
(await locomotiveRepo.save(
|
|
locomotiveRepo.create({
|
|
code: 'GP-DEMO-LOCO',
|
|
name: 'Gate Pass Scenario Locomotive',
|
|
locomotiveType: 'DIESEL',
|
|
maxPullWeightTons: 4200,
|
|
maxTrainLengthMeters: 760,
|
|
status: 'AVAILABLE',
|
|
currentYardId: originYard.id,
|
|
}),
|
|
));
|
|
|
|
let schedule = await scheduleRepo.findOne({ where: { trainNumber: scenario.trainNumber } });
|
|
let trainSet: TrainSet | null = schedule?.trainSetId
|
|
? await trainSetRepo.findOne({ where: { id: schedule.trainSetId } })
|
|
: null;
|
|
|
|
if (!trainSet) {
|
|
trainSet = await trainSetRepo.save(
|
|
trainSetRepo.create({
|
|
locomotiveId: locomotive.id,
|
|
totalWeightTons,
|
|
totalLengthMeters: scenario.bookings.length * 14,
|
|
wagonCount: scenario.bookings.length,
|
|
status: isArrived ? 'COMPLETED' : 'ASSIGNED',
|
|
}),
|
|
);
|
|
} else {
|
|
await trainSetRepo.update(trainSet.id, {
|
|
locomotiveId: locomotive.id,
|
|
totalWeightTons,
|
|
totalLengthMeters: scenario.bookings.length * 14,
|
|
wagonCount: scenario.bookings.length,
|
|
status: isArrived ? 'COMPLETED' : 'ASSIGNED',
|
|
});
|
|
}
|
|
if (!trainSet) {
|
|
throw new Error(`Could not create train set for ${scenario.trainNumber}`);
|
|
}
|
|
const trainSetId = trainSet.id;
|
|
|
|
if (!schedule) {
|
|
schedule = scheduleRepo.create({ trainNumber: scenario.trainNumber });
|
|
}
|
|
Object.assign(schedule, {
|
|
trainSetId,
|
|
originStationId: originYard.id,
|
|
destinationStationId: destinationYard.id,
|
|
scheduledDepartureDate: departure,
|
|
scheduledArrivalDate: arrival,
|
|
actualDepartureAt: isArrived ? departure : null,
|
|
actualArrivalAt: isArrived ? arrival : null,
|
|
status: scenario.status,
|
|
direction: scenario.direction,
|
|
maxWagons: 53,
|
|
bookingWindowStatus: 'CLOSED',
|
|
});
|
|
schedule = await scheduleRepo.save(schedule);
|
|
|
|
for (const [index, bookingSpec] of scenario.bookings.entries()) {
|
|
const sequenceNo = index + 1;
|
|
const wagon = await ensureWagon(manager, scenario, sequenceNo, refs.wagonType.id, originYard.id, schedule.id);
|
|
const trainSetWagon = await ensureTrainSetWagon(
|
|
trainSetWagonRepo,
|
|
trainSetId,
|
|
refs.wagonType.id,
|
|
wagon.id,
|
|
sequenceNo,
|
|
bookingSpec.weightTons,
|
|
isArrived,
|
|
);
|
|
await wagonRepo.update(wagon.id, { trainSetWagonId: trainSetWagon.id });
|
|
|
|
const booking = await ensureBooking(manager, scenario, bookingSpec, refs, departure, now, schedule.id);
|
|
const bookingContainer = await ensureBookingContainer(manager, booking.id, refs.containerType.id, bookingSpec);
|
|
const allocation = await ensureAllocation(manager, trainSetWagon.id, booking.id, bookingSpec.weightTons, isArrived, now);
|
|
const container = await ensureContainer(manager, booking.id, bookingContainer.id, allocation.id, wagon.id, sequenceNo, bookingSpec, refs.containerType.id, isArrived);
|
|
await ensureContainerItem(manager, allocation.id, bookingContainer.id, container.id, refs.containerType.id, sequenceNo, bookingSpec);
|
|
await ensureScheduleBooking(manager, schedule.id, booking.id);
|
|
if (scenario.direction === 'EXPORT') {
|
|
await ensureExportInventory(manager, refs, booking.id, container.id, bookingSpec.weightTons, isArrived, now);
|
|
}
|
|
}
|
|
|
|
if (scenario.direction === 'IMPORT') {
|
|
await ensureImportOperation(manager, schedule.id, scenario, departure, isArrived);
|
|
}
|
|
|
|
return schedule;
|
|
}
|
|
|
|
async function ensureWagon(manager: any, scenario: ScenarioTrain, sequenceNo: number, wagonTypeId: string, yardId: string, scheduleId: string): Promise<Wagon> {
|
|
const repo = manager.getRepository(Wagon);
|
|
const wagonNumber = `${scenario.trainNumber}-W${String(sequenceNo).padStart(2, '0')}`;
|
|
const existing = await repo.findOne({ where: { wagonNumber } });
|
|
const values = {
|
|
wagonNumber,
|
|
wagonTypeId,
|
|
trainId: null,
|
|
sequenceNumber: sequenceNo,
|
|
status: WagonStatus.Assigned,
|
|
currentYardId: yardId,
|
|
currentTrainScheduleId: scheduleId,
|
|
notes: 'Gate-pass scenario seed wagon',
|
|
};
|
|
return repo.save(repo.create({ ...(existing ?? {}), ...values }));
|
|
}
|
|
|
|
async function ensureTrainSetWagon(repo: any, trainSetId: string, wagonTypeId: string, wagonId: string, sequenceNo: number, weightTons: number, isArrived: boolean): Promise<TrainSetWagon> {
|
|
const existing = await repo.findOne({ where: { trainSetId, sequenceNo } });
|
|
return repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
trainSetId,
|
|
wagonTypeId,
|
|
physicalWagonId: wagonId,
|
|
sequenceNo,
|
|
capacityTons: 70,
|
|
lengthMeters: 14,
|
|
assignedWeightTons: weightTons,
|
|
status: isArrived ? 'DEPARTED' : 'LOADED',
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureBooking(manager: any, scenario: ScenarioTrain, bookingSpec: ScenarioTrain['bookings'][number], refs: Awaited<ReturnType<typeof ensureReferences>>, departure: Date, now: Date, scheduleId: string): Promise<Booking> {
|
|
const repo = manager.getRepository(Booking);
|
|
const originYard = scenario.direction === 'IMPORT' ? refs.djiboutiYard : refs.ethiopiaYard;
|
|
const destinationYard = scenario.direction === 'IMPORT' ? refs.ethiopiaYard : refs.djiboutiYard;
|
|
const existing = await repo.findOne({ where: { reference: bookingSpec.reference } });
|
|
const profile = scenario.direction === 'IMPORT' ? refs.importerProfile : refs.exporterProfile;
|
|
const hasFirstMile = bookingSpec.mileVariant === 'FIRST_MILE';
|
|
const hasLastMile = bookingSpec.mileVariant === 'LAST_MILE';
|
|
|
|
return repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
reference: bookingSpec.reference,
|
|
companyId: refs.company.id,
|
|
companyProfileId: profile.id,
|
|
originYardId: originYard.id,
|
|
destinationYardId: destinationYard.id,
|
|
serviceTypeId: refs.serviceType.id,
|
|
status: scenario.status === 'ARRIVED' ? 'IN_TRANSIT' : 'PAID',
|
|
paymentStatus: 'PAID',
|
|
scheduledDate: departure,
|
|
estimatedShipmentDate: departure,
|
|
contractType: 'SPOT',
|
|
equipmentReturn: 'TERMINAL',
|
|
paymentCurrency: 'ETB',
|
|
totalAmount: 0,
|
|
isGovernment: false,
|
|
tradeDirection: scenario.direction,
|
|
freightType: 'CONTAINER',
|
|
cargoTypeId: null,
|
|
cargoFreeText: `${scenario.direction} gate-pass scenario ${bookingSpec.mileVariant.toLowerCase().replace('_', ' ')}`,
|
|
cargoTotalWeightVgm: bookingSpec.weightTons * 1000,
|
|
firstMilePickupAddress: hasFirstMile ? 'Customer factory pickup - Addis Ababa' : null,
|
|
firstMilePickupLat: hasFirstMile ? 9.03 : null,
|
|
firstMilePickupLng: hasFirstMile ? 38.74 : null,
|
|
lastMileDeliveryAddress: hasLastMile ? 'Customer warehouse delivery - Addis Ababa' : null,
|
|
lastMileDeliveryLat: hasLastMile ? 8.98 : null,
|
|
lastMileDeliveryLng: hasLastMile ? 38.8 : null,
|
|
trainScheduleId: scheduleId,
|
|
schedulingStatus: scenario.status === 'ARRIVED' ? 'DISPATCHED' : 'SCHEDULED',
|
|
scheduledAt: now,
|
|
wagonsRequired: 1,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureBookingContainer(manager: any, bookingId: string, containerTypeId: string, bookingSpec: ScenarioTrain['bookings'][number]): Promise<BookingContainer> {
|
|
const repo = manager.getRepository(BookingContainer);
|
|
const existing = await repo.findOne({ where: { bookingId } });
|
|
return repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
bookingId,
|
|
containerTypeId,
|
|
containerNumber: bookingSpec.containerNumber,
|
|
containerSize: '40',
|
|
quantity: 1,
|
|
hazardousQuantity: 0,
|
|
reeferQuantity: 0,
|
|
vgmPerUnitTons: bookingSpec.weightTons,
|
|
totalVgmTons: bookingSpec.weightTons,
|
|
wagonsRequired: 1,
|
|
weightLimitRuleId: null,
|
|
isOverweight: false,
|
|
overweightExcessTons: null,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureAllocation(manager: any, trainSetWagonId: string, bookingId: string, weightTons: number, isArrived: boolean, now: Date): Promise<WagonBookingAllocation> {
|
|
const repo = manager.getRepository(WagonBookingAllocation);
|
|
const existing = await repo.findOne({ where: { trainSetWagonId, bookingId } });
|
|
return repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
trainSetWagonId,
|
|
bookingId,
|
|
allocatedWeightTons: weightTons,
|
|
loadType: 'CONTAINER',
|
|
status: isArrived ? 'DEPARTED' : 'LOADED',
|
|
confirmedAt: now,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureContainer(manager: any, bookingId: string, bookingContainerId: string, allocationId: string, wagonId: string, position: number, bookingSpec: ScenarioTrain['bookings'][number], containerTypeId: string, isArrived: boolean): Promise<Container> {
|
|
const repo = manager.getRepository(Container);
|
|
const existing = await repo.findOne({ where: { containerNumber: bookingSpec.containerNumber } });
|
|
return repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
containerNumber: bookingSpec.containerNumber,
|
|
containerTypeId,
|
|
wagonId,
|
|
position,
|
|
tareWeight: 3800,
|
|
maxGrossWeight: 30480,
|
|
sealNumber: `SEAL-${bookingSpec.containerNumber}`,
|
|
status: isArrived ? 'IN_TRANSIT' : 'LOADED',
|
|
bookingId,
|
|
wagonBookingAllocationId: allocationId,
|
|
bookingContainerId,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureContainerItem(manager: any, allocationId: string, bookingContainerId: string, containerId: string, containerTypeId: string, position: number, bookingSpec: ScenarioTrain['bookings'][number]): Promise<void> {
|
|
const repo = manager.getRepository(WagonAllocationContainerItem);
|
|
await repo.delete({ wagonBookingAllocationId: allocationId });
|
|
await repo.save(
|
|
repo.create({
|
|
wagonBookingAllocationId: allocationId,
|
|
bookingContainerId,
|
|
containerId,
|
|
containerNumber: bookingSpec.containerNumber,
|
|
containerTypeId,
|
|
positionOnWagon: position,
|
|
sealNumber: `SEAL-${bookingSpec.containerNumber}`,
|
|
chassisNumber: `CHS-${bookingSpec.containerNumber}`,
|
|
grossWeightTons: bookingSpec.weightTons,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureScheduleBooking(manager: any, scheduleId: string, bookingId: string): Promise<void> {
|
|
const repo = manager.getRepository(TrainScheduleBooking);
|
|
const existing = await repo.findOne({ where: { bookingId } });
|
|
await repo.save(repo.create({ ...(existing ?? {}), trainScheduleId: scheduleId, bookingId }));
|
|
}
|
|
|
|
async function ensureExportInventory(manager: any, refs: Awaited<ReturnType<typeof ensureReferences>>, bookingId: string, containerId: string, weightTons: number, isArrived: boolean, now: Date): Promise<void> {
|
|
const repo = manager.getRepository(WarehouseInventory);
|
|
const existing = await repo.findOne({ where: { bookingId } });
|
|
await repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
warehouseId: refs.warehouse.id,
|
|
yardId: refs.warehouseYard.id,
|
|
zoneId: refs.warehouseZone.id,
|
|
bookingId,
|
|
containerId,
|
|
quantity: 1,
|
|
weight: weightTons * 1000,
|
|
status: 'LOADED',
|
|
inspectionStatus: 'PASSED',
|
|
arrivedAt: addHours(now, -24),
|
|
inspectedAt: addHours(now, -22),
|
|
readyForLoadingAt: addHours(now, -20),
|
|
loadedAt: isArrived ? addHours(now, -16) : null,
|
|
notes: '[GP-SCENARIO] Export train gate-pass scenario inventory',
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function ensureImportOperation(manager: any, scheduleId: string, scenario: ScenarioTrain, departure: Date, isArrived: boolean): Promise<void> {
|
|
const repo = manager.getRepository(ImportDjiboutiOperation);
|
|
const existing = await repo.findOne({ where: { trainScheduleId: scheduleId } });
|
|
await repo.save(
|
|
repo.create({
|
|
...(existing ?? {}),
|
|
trainScheduleId: scheduleId,
|
|
documents: existing?.documents ?? {},
|
|
gatepassGrantedAt: null,
|
|
readyForLoadingAt: null,
|
|
loadedOnTrainAt: null,
|
|
departedFromDjiboutiAt: isArrived ? departure : null,
|
|
loadListGeneratedAt: null,
|
|
performedBy: 'Gate Pass Scenario Seeder',
|
|
notes: `[GP-SCENARIO] ${scenario.trainNumber}; fill gate-pass dates during testing`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Gate-pass train scenario seed failed:', error);
|
|
process.exit(1);
|
|
});
|