mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
merge conflict
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { DataSource, In } from 'typeorm';
|
||||
|
||||
import { BookingContainer } from '../modules/bookings/entities/booking-container.entity';
|
||||
import { Booking } from '../modules/bookings/entities/booking.entity';
|
||||
import {
|
||||
Company,
|
||||
CompanyStatus,
|
||||
CompanyType,
|
||||
} from '../modules/companies/entities/company.entity';
|
||||
import { FirstMile } from '../modules/first-mile/entities/first-mile.entity';
|
||||
import { LastMile } from '../modules/last-mile/entities/last-mile.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';
|
||||
|
||||
const SERVICE_TYPE_CODE = 'RAIL_CONTAINER_FIRST_LAST';
|
||||
const COMPANY_TIN = 'FLMDEMO001';
|
||||
const COMPANY_EMAIL = 'first-last-mile-demo@edr.local';
|
||||
|
||||
const YARDS = [
|
||||
{ code: 'DJIBOUTI', label: 'Djibouti', country: 'Djibouti', displayOrder: 1 },
|
||||
{ code: 'ADDIS_ABABA', label: 'Addis Ababa', country: 'Ethiopia', displayOrder: 2 },
|
||||
];
|
||||
|
||||
const CONTAINER_TYPES = [
|
||||
{ code: '20FT', label: '20FT', sizeFt: 20 },
|
||||
{ code: '40FT', label: '40FT', sizeFt: 40 },
|
||||
];
|
||||
|
||||
const DEMO_BOOKINGS = [
|
||||
{
|
||||
reference: 'DEMO-IMP-FLM-001',
|
||||
tradeDirection: 'IMPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 8,
|
||||
totalWeightTons: 224,
|
||||
originCode: 'DJIBOUTI',
|
||||
destinationCode: 'ADDIS_ABABA',
|
||||
scheduledDate: '2026-07-01T08:00:00.000Z',
|
||||
firstMilePickupAddress: 'Doraleh Container Terminal, Djibouti',
|
||||
firstMilePickupLat: 11.5881,
|
||||
firstMilePickupLng: 43.1372,
|
||||
lastMileDeliveryAddress: 'Akaki Industrial Zone, Addis Ababa',
|
||||
lastMileDeliveryLat: 8.8808,
|
||||
lastMileDeliveryLng: 38.7876,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-IMP-FLM-002',
|
||||
tradeDirection: 'IMPORT',
|
||||
containerCode: '20FT',
|
||||
quantity: 12,
|
||||
totalWeightTons: 240,
|
||||
originCode: 'DJIBOUTI',
|
||||
destinationCode: 'ADDIS_ABABA',
|
||||
scheduledDate: '2026-07-02T08:00:00.000Z',
|
||||
firstMilePickupAddress: 'PK12 Dry Port, Djibouti',
|
||||
firstMilePickupLat: 11.5536,
|
||||
firstMilePickupLng: 43.1103,
|
||||
lastMileDeliveryAddress: 'Kality Logistics Hub, Addis Ababa',
|
||||
lastMileDeliveryLat: 8.9137,
|
||||
lastMileDeliveryLng: 38.7815,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-IMP-FLM-003',
|
||||
tradeDirection: 'IMPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 6,
|
||||
totalWeightTons: 180,
|
||||
originCode: 'DJIBOUTI',
|
||||
destinationCode: 'ADDIS_ABABA',
|
||||
scheduledDate: '2026-07-03T08:00:00.000Z',
|
||||
firstMilePickupAddress: 'Djibouti Free Zone Yard',
|
||||
firstMilePickupLat: 11.5947,
|
||||
firstMilePickupLng: 43.1471,
|
||||
lastMileDeliveryAddress: 'Bole Lemi Industrial Park, Addis Ababa',
|
||||
lastMileDeliveryLat: 8.9806,
|
||||
lastMileDeliveryLng: 38.8736,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-IMP-FLM-004',
|
||||
tradeDirection: 'IMPORT',
|
||||
containerCode: '20FT',
|
||||
quantity: 10,
|
||||
totalWeightTons: 210,
|
||||
originCode: 'DJIBOUTI',
|
||||
destinationCode: 'ADDIS_ABABA',
|
||||
scheduledDate: '2026-07-04T08:00:00.000Z',
|
||||
firstMilePickupAddress: 'Doraleh Multipurpose Port, Djibouti',
|
||||
firstMilePickupLat: 11.6062,
|
||||
firstMilePickupLng: 43.1219,
|
||||
lastMileDeliveryAddress: 'Addis Ababa Freight Terminal',
|
||||
lastMileDeliveryLat: 9.0101,
|
||||
lastMileDeliveryLng: 38.7619,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-IMP-FLM-005',
|
||||
tradeDirection: 'IMPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 5,
|
||||
totalWeightTons: 150,
|
||||
originCode: 'DJIBOUTI',
|
||||
destinationCode: 'ADDIS_ABABA',
|
||||
scheduledDate: '2026-07-05T08:00:00.000Z',
|
||||
firstMilePickupAddress: 'Djibouti Port Gate 3',
|
||||
firstMilePickupLat: 11.5999,
|
||||
firstMilePickupLng: 43.1344,
|
||||
lastMileDeliveryAddress: 'Sebeta Distribution Center',
|
||||
lastMileDeliveryLat: 8.9169,
|
||||
lastMileDeliveryLng: 38.6177,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-EXP-FLM-001',
|
||||
tradeDirection: 'EXPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 7,
|
||||
totalWeightTons: 196,
|
||||
originCode: 'ADDIS_ABABA',
|
||||
destinationCode: 'DJIBOUTI',
|
||||
scheduledDate: '2026-07-01T10:00:00.000Z',
|
||||
firstMilePickupAddress: 'Bole Lemi Industrial Park, Addis Ababa',
|
||||
firstMilePickupLat: 8.9806,
|
||||
firstMilePickupLng: 38.8736,
|
||||
lastMileDeliveryAddress: 'Doraleh Container Terminal, Djibouti',
|
||||
lastMileDeliveryLat: 11.5881,
|
||||
lastMileDeliveryLng: 43.1372,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-EXP-FLM-002',
|
||||
tradeDirection: 'EXPORT',
|
||||
containerCode: '20FT',
|
||||
quantity: 11,
|
||||
totalWeightTons: 220,
|
||||
originCode: 'ADDIS_ABABA',
|
||||
destinationCode: 'DJIBOUTI',
|
||||
scheduledDate: '2026-07-02T10:00:00.000Z',
|
||||
firstMilePickupAddress: 'Akaki Industrial Zone, Addis Ababa',
|
||||
firstMilePickupLat: 8.8808,
|
||||
firstMilePickupLng: 38.7876,
|
||||
lastMileDeliveryAddress: 'Djibouti Free Zone Yard',
|
||||
lastMileDeliveryLat: 11.5947,
|
||||
lastMileDeliveryLng: 43.1471,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-EXP-FLM-003',
|
||||
tradeDirection: 'EXPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 4,
|
||||
totalWeightTons: 128,
|
||||
originCode: 'ADDIS_ABABA',
|
||||
destinationCode: 'DJIBOUTI',
|
||||
scheduledDate: '2026-07-03T10:00:00.000Z',
|
||||
firstMilePickupAddress: 'Kality Logistics Hub, Addis Ababa',
|
||||
firstMilePickupLat: 8.9137,
|
||||
firstMilePickupLng: 38.7815,
|
||||
lastMileDeliveryAddress: 'Doraleh Multipurpose Port, Djibouti',
|
||||
lastMileDeliveryLat: 11.6062,
|
||||
lastMileDeliveryLng: 43.1219,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-EXP-FLM-004',
|
||||
tradeDirection: 'EXPORT',
|
||||
containerCode: '20FT',
|
||||
quantity: 9,
|
||||
totalWeightTons: 180,
|
||||
originCode: 'ADDIS_ABABA',
|
||||
destinationCode: 'DJIBOUTI',
|
||||
scheduledDate: '2026-07-04T10:00:00.000Z',
|
||||
firstMilePickupAddress: 'Addis Ababa Freight Terminal',
|
||||
firstMilePickupLat: 9.0101,
|
||||
firstMilePickupLng: 38.7619,
|
||||
lastMileDeliveryAddress: 'PK12 Dry Port, Djibouti',
|
||||
lastMileDeliveryLat: 11.5536,
|
||||
lastMileDeliveryLng: 43.1103,
|
||||
},
|
||||
{
|
||||
reference: 'DEMO-EXP-FLM-005',
|
||||
tradeDirection: 'EXPORT',
|
||||
containerCode: '40FT',
|
||||
quantity: 6,
|
||||
totalWeightTons: 168,
|
||||
originCode: 'ADDIS_ABABA',
|
||||
destinationCode: 'DJIBOUTI',
|
||||
scheduledDate: '2026-07-05T10:00:00.000Z',
|
||||
firstMilePickupAddress: 'Sebeta Distribution Center',
|
||||
firstMilePickupLat: 8.9169,
|
||||
firstMilePickupLng: 38.6177,
|
||||
lastMileDeliveryAddress: 'Djibouti Port Gate 3',
|
||||
lastMileDeliveryLat: 11.5999,
|
||||
lastMileDeliveryLng: 43.1344,
|
||||
},
|
||||
] as const;
|
||||
|
||||
@Injectable()
|
||||
export class ApprovedFirstLastMileDemoBookingsSeeder {
|
||||
private readonly logger = new Logger(ApprovedFirstLastMileDemoBookingsSeeder.name);
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async run() {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await manager.getRepository(Yard).upsert(
|
||||
YARDS.map((yard) => ({ ...yard, isActive: true })),
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
await manager.getRepository(ServiceType).upsert(
|
||||
{
|
||||
code: SERVICE_TYPE_CODE,
|
||||
serviceName: 'Rail Container with First and Last Mile',
|
||||
description: 'Demo service type for approved first/last-mile bookings',
|
||||
canBeBookedAlone: true,
|
||||
includesFirstMile: true,
|
||||
includesLastMile: true,
|
||||
includesCustoms: false,
|
||||
priorityBonusPoints: 0,
|
||||
isActive: true,
|
||||
displayOrder: 10,
|
||||
},
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
await manager.getRepository(ContainerType).upsert(
|
||||
CONTAINER_TYPES.map((containerType, index) => ({
|
||||
...containerType,
|
||||
wagonsPerUnit: 1,
|
||||
isReefer: false,
|
||||
isOpenTop: false,
|
||||
isActive: true,
|
||||
displayOrder: index + 1,
|
||||
})),
|
||||
{ conflictPaths: { code: true } },
|
||||
);
|
||||
|
||||
await manager.getRepository(Company).upsert(
|
||||
{
|
||||
name: 'First and Last Mile Demo Customer',
|
||||
type: CompanyType.Customer,
|
||||
status: CompanyStatus.Active,
|
||||
tin: COMPANY_TIN,
|
||||
vatNumber: COMPANY_TIN,
|
||||
fanNumber: 'FLM0000000000001',
|
||||
country: 'Ethiopia',
|
||||
address: 'Addis Ababa',
|
||||
phone: '251900000101',
|
||||
email: COMPANY_EMAIL,
|
||||
website: null,
|
||||
contactPersonName: 'First Last Mile Demo',
|
||||
contactPersonPhone: '251900000101',
|
||||
generalManagerName: 'Demo Manager',
|
||||
generalManagerEmail: COMPANY_EMAIL,
|
||||
generalManagerPhone: '251900000101',
|
||||
},
|
||||
{ conflictPaths: { tin: true } },
|
||||
);
|
||||
|
||||
const [serviceType, company, yards, containerTypes] = await Promise.all([
|
||||
manager.getRepository(ServiceType).findOneByOrFail({ code: SERVICE_TYPE_CODE }),
|
||||
manager.getRepository(Company).findOneByOrFail({ tin: COMPANY_TIN }),
|
||||
manager.getRepository(Yard).find(),
|
||||
manager.getRepository(ContainerType).find(),
|
||||
]);
|
||||
|
||||
const yardByCode = new Map(yards.map((yard) => [yard.code, yard]));
|
||||
const containerTypeByCode = new Map(
|
||||
containerTypes.map((containerType) => [containerType.code, containerType]),
|
||||
);
|
||||
|
||||
for (const demoBooking of DEMO_BOOKINGS) {
|
||||
const origin = yardByCode.get(demoBooking.originCode);
|
||||
const destination = yardByCode.get(demoBooking.destinationCode);
|
||||
const containerType = containerTypeByCode.get(demoBooking.containerCode);
|
||||
|
||||
if (!origin || !destination || !containerType) {
|
||||
throw new Error(`approved_first_last_mile_demo_dependency_missing:${demoBooking.reference}`);
|
||||
}
|
||||
|
||||
const wagonsRequired =
|
||||
Number(demoBooking.quantity) * Number(containerType.wagonsPerUnit ?? 1);
|
||||
const vgmPerUnitTons = demoBooking.totalWeightTons / demoBooking.quantity;
|
||||
|
||||
await manager.getRepository(Booking).upsert(
|
||||
{
|
||||
reference: demoBooking.reference,
|
||||
companyId: company.id,
|
||||
status: 'APPROVED',
|
||||
scheduledDate: new Date(demoBooking.scheduledDate),
|
||||
estimatedShipmentDate: new Date(demoBooking.scheduledDate),
|
||||
totalAmount: demoBooking.totalWeightTons * 25,
|
||||
paymentStatus: 'PENDING',
|
||||
contractType: 'NEW',
|
||||
serviceTypeId: serviceType.id,
|
||||
firstMilePickupAddress: demoBooking.firstMilePickupAddress,
|
||||
firstMilePickupLat: demoBooking.firstMilePickupLat,
|
||||
firstMilePickupLng: demoBooking.firstMilePickupLng,
|
||||
lastMileDeliveryAddress: demoBooking.lastMileDeliveryAddress,
|
||||
lastMileDeliveryLat: demoBooking.lastMileDeliveryLat,
|
||||
lastMileDeliveryLng: demoBooking.lastMileDeliveryLng,
|
||||
equipmentReturn: 'WITHOUT_RETURN',
|
||||
originYardId: origin.id,
|
||||
destinationYardId: destination.id,
|
||||
tradeDirection: demoBooking.tradeDirection,
|
||||
freightType: 'CONTAINER',
|
||||
cargoTypeId: null,
|
||||
cargoFreeText: 'Demo container cargo',
|
||||
shippingLineId: null,
|
||||
cargoTotalWeightVgm: demoBooking.totalWeightTons,
|
||||
isHazardous: false,
|
||||
isReefer: false,
|
||||
paymentCurrency: 'ETB',
|
||||
approvedByStaffAt: new Date(),
|
||||
priorityScore: 20,
|
||||
wagonsRequired,
|
||||
schedulingStatus: 'NOT_SCHEDULED',
|
||||
versionNumber: 1,
|
||||
},
|
||||
{ conflictPaths: { reference: true } },
|
||||
);
|
||||
|
||||
const booking = await manager.getRepository(Booking).findOneByOrFail({
|
||||
reference: demoBooking.reference,
|
||||
});
|
||||
|
||||
await manager.getRepository(BookingContainer).delete({ bookingId: booking.id });
|
||||
await manager.getRepository(BookingContainer).insert({
|
||||
id: randomUUID(),
|
||||
bookingId: booking.id,
|
||||
containerTypeId: containerType.id,
|
||||
quantity: demoBooking.quantity,
|
||||
vgmPerUnitTons,
|
||||
totalVgmTons: demoBooking.totalWeightTons,
|
||||
wagonsRequired,
|
||||
weightLimitRuleId: null,
|
||||
isOverweight: vgmPerUnitTons > 35,
|
||||
overweightExcessTons: vgmPerUnitTons > 35 ? vgmPerUnitTons - 35 : null,
|
||||
});
|
||||
}
|
||||
|
||||
const bookings = await manager.getRepository(Booking).find({
|
||||
where: { reference: In(DEMO_BOOKINGS.map((booking) => booking.reference)) },
|
||||
select: { id: true },
|
||||
});
|
||||
const bookingIds = bookings.map((booking) => booking.id);
|
||||
|
||||
await manager.getRepository(FirstMile).delete({ bookingId: In(bookingIds) });
|
||||
await manager.getRepository(LastMile).delete({ bookingId: In(bookingIds) });
|
||||
|
||||
await manager.getRepository(FirstMile).insert(
|
||||
bookingIds.map((bookingId) => ({
|
||||
bookingId,
|
||||
status: 'READY_TO_TRANSIT',
|
||||
advancedPayment: 0,
|
||||
remainingPayment: 0,
|
||||
estimatedKm: 18,
|
||||
exactKm: null,
|
||||
vehicleId: null,
|
||||
})),
|
||||
);
|
||||
|
||||
await manager.getRepository(LastMile).insert(
|
||||
bookingIds.map((bookingId) => ({
|
||||
bookingId,
|
||||
status: 'READY_TO_TRANSIT',
|
||||
advancedPayment: 0,
|
||||
remainingPayment: 0,
|
||||
estimatedKm: 22,
|
||||
exactKm: null,
|
||||
vehicleId: null,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.log('Seeded 5 import and 5 export approved bookings with first/last-mile legs');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { Booking } from '../modules/bookings/entities/booking.entity';
|
||||
import { Locomotive } from '../modules/locomotives/entities/locomotive.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';
|
||||
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';
|
||||
|
||||
const SEED_FLAG = 'SEED_EXPORT_DJIBOUTI_INTERCHANGE_DEMO';
|
||||
|
||||
const DEMO_TRAINS = [
|
||||
{
|
||||
trainNumber: 'ICD-DEMO-EXP-DJ-01',
|
||||
bookingRefs: ['ICD-DEMO-EXP-001', 'ICD-DEMO-EXP-002', 'ICD-DEMO-EXP-003'],
|
||||
arrivalOffsetHours: 1,
|
||||
},
|
||||
{
|
||||
trainNumber: 'ICD-DEMO-EXP-DJ-02',
|
||||
bookingRefs: ['ICD-DEMO-EXP-004', 'ICD-DEMO-EXP-005', 'ICD-DEMO-EXP-006'],
|
||||
arrivalOffsetHours: 2,
|
||||
},
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class ExportDjiboutiInterchangeDemoSeeder {
|
||||
private readonly logger = new Logger(ExportDjiboutiInterchangeDemoSeeder.name);
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
|
||||
this.logger.log(`Skipping export Djibouti interchange demo seed because ${SEED_FLAG} is not enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const yardRepo = this.dataSource.getRepository(Yard);
|
||||
const serviceTypeRepo = this.dataSource.getRepository(ServiceType);
|
||||
const cargoTypeRepo = this.dataSource.getRepository(CargoType);
|
||||
const warehouseRepo = this.dataSource.getRepository(Warehouse);
|
||||
const warehouseYardRepo = this.dataSource.getRepository(WarehouseYard);
|
||||
const warehouseZoneRepo = this.dataSource.getRepository(WarehouseZone);
|
||||
const bookingRepo = this.dataSource.getRepository(Booking);
|
||||
const inventoryRepo = this.dataSource.getRepository(WarehouseInventory);
|
||||
const locomotiveRepo = this.dataSource.getRepository(Locomotive);
|
||||
const trainSetRepo = this.dataSource.getRepository(TrainSet);
|
||||
const scheduleRepo = this.dataSource.getRepository(TrainSchedule);
|
||||
const scheduleBookingRepo = this.dataSource.getRepository(TrainScheduleBooking);
|
||||
|
||||
const originYard =
|
||||
(await yardRepo.findOne({ where: { code: 'MOJO' } })) ??
|
||||
(await yardRepo.findOne({ where: { country: 'Ethiopia' } }));
|
||||
const destinationYard =
|
||||
(await yardRepo.findOne({ where: { code: 'DJIB_PORT' } })) ??
|
||||
(await yardRepo.findOne({ where: { code: 'DJIBOUTI' } })) ??
|
||||
(await yardRepo.findOne({ where: { country: 'Djibouti' } }));
|
||||
const serviceType =
|
||||
(await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ??
|
||||
(await serviceTypeRepo.findOne({ where: { isActive: true } }));
|
||||
const cargoType = await cargoTypeRepo.findOne({ where: { isActive: true } });
|
||||
const warehouse = await warehouseRepo.findOne({ where: { code: 'INDODE_OPEN' } });
|
||||
const warehouseYard = warehouse
|
||||
? await warehouseYardRepo.findOne({ where: { warehouseId: warehouse.id } })
|
||||
: null;
|
||||
const warehouseZone = warehouseYard
|
||||
? await warehouseZoneRepo.findOne({ where: { yardId: warehouseYard.id } })
|
||||
: null;
|
||||
|
||||
const missing = [
|
||||
!originYard ? 'Ethiopian origin yard' : '',
|
||||
!destinationYard ? 'Djibouti destination yard' : '',
|
||||
!serviceType ? 'service type' : '',
|
||||
!warehouse ? 'INDODE_OPEN warehouse' : '',
|
||||
!warehouseYard ? 'warehouse yard' : '',
|
||||
!warehouseZone ? 'warehouse zone' : '',
|
||||
].filter(Boolean);
|
||||
|
||||
if (missing.length) {
|
||||
this.logger.warn(`Cannot seed export Djibouti interchange demo, missing: ${missing.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const locomotive =
|
||||
(await locomotiveRepo.findOne({ where: { code: 'ICD-DEMO-LOCO' } })) ??
|
||||
(await locomotiveRepo.save(
|
||||
locomotiveRepo.create({
|
||||
code: 'ICD-DEMO-LOCO',
|
||||
name: 'Interchange Demo Locomotive',
|
||||
maxPullWeightTons: 4000,
|
||||
}),
|
||||
));
|
||||
|
||||
const now = Date.now();
|
||||
let seeded = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const [trainIndex, demo] of DEMO_TRAINS.entries()) {
|
||||
const existingSchedule = await scheduleRepo.findOne({ where: { trainNumber: demo.trainNumber } });
|
||||
if (existingSchedule) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const arrival = new Date(now - demo.arrivalOffsetHours * 60 * 60 * 1000);
|
||||
const departure = new Date(arrival.getTime() - 5 * 60 * 60 * 1000);
|
||||
|
||||
const trainSet = await trainSetRepo.save(
|
||||
trainSetRepo.create({
|
||||
locomotiveId: locomotive.id,
|
||||
totalWeightTons: 700 + trainIndex * 80,
|
||||
totalLengthMeters: 360 + trainIndex * 20,
|
||||
wagonCount: 12 + trainIndex,
|
||||
status: 'COMPLETED',
|
||||
}),
|
||||
);
|
||||
|
||||
const schedule = await scheduleRepo.save(
|
||||
scheduleRepo.create({
|
||||
trainSetId: trainSet.id,
|
||||
originStationId: originYard!.id,
|
||||
destinationStationId: destinationYard!.id,
|
||||
scheduledDepartureDate: departure,
|
||||
scheduledArrivalDate: arrival,
|
||||
actualArrivalAt: arrival,
|
||||
status: 'ARRIVED' as TrainSchedule['status'],
|
||||
trainNumber: demo.trainNumber,
|
||||
direction: 'EXPORT',
|
||||
}),
|
||||
);
|
||||
|
||||
for (const [bookingIndex, reference] of demo.bookingRefs.entries()) {
|
||||
const weight = 5200 + trainIndex * 600 + bookingIndex * 800;
|
||||
const booking = await bookingRepo.save(
|
||||
bookingRepo.create({
|
||||
reference,
|
||||
originYardId: originYard!.id,
|
||||
destinationYardId: destinationYard!.id,
|
||||
serviceTypeId: serviceType!.id,
|
||||
status: 'IN_TRANSIT',
|
||||
paymentStatus: 'PAID',
|
||||
scheduledDate: new Date(),
|
||||
contractType: 'SPOT',
|
||||
equipmentReturn: 'TERMINAL',
|
||||
paymentCurrency: 'ETB',
|
||||
totalAmount: 0,
|
||||
isGovernment: false,
|
||||
tradeDirection: 'EXPORT',
|
||||
freightType: bookingIndex % 2 === 0 ? 'CONTAINER' : 'BULK',
|
||||
cargoTypeId: cargoType?.id ?? null,
|
||||
cargoFreeText: cargoType
|
||||
? null
|
||||
: `Export Djibouti interchange demo cargo ${trainIndex + 1}-${bookingIndex + 1}`,
|
||||
cargoTotalWeightVgm: weight,
|
||||
}),
|
||||
);
|
||||
|
||||
await inventoryRepo.save(
|
||||
inventoryRepo.create({
|
||||
warehouseId: warehouse!.id,
|
||||
yardId: warehouseYard!.id,
|
||||
zoneId: warehouseZone!.id,
|
||||
bookingId: booking.id,
|
||||
quantity: 1,
|
||||
weight,
|
||||
status: 'DISPATCHED',
|
||||
inspectionStatus: 'PASSED',
|
||||
arrivedAt: new Date(departure.getTime() + 2 * 60 * 60 * 1000),
|
||||
inspectedAt: new Date(departure.getTime() + 3 * 60 * 60 * 1000),
|
||||
readyForLoadingAt: new Date(departure.getTime() + 4 * 60 * 60 * 1000),
|
||||
loadedAt: new Date(departure.getTime() + 4.5 * 60 * 60 * 1000),
|
||||
dispatchedAt: departure,
|
||||
notes: '[ICD-DEMO] Eligible for Djibouti export unload and interchange generation',
|
||||
}),
|
||||
);
|
||||
|
||||
await scheduleBookingRepo.save(
|
||||
scheduleBookingRepo.create({
|
||||
trainScheduleId: schedule.id,
|
||||
bookingId: booking.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
seeded += 1;
|
||||
}
|
||||
|
||||
this.logger.log(`Export Djibouti interchange demo seed complete: ${seeded} train(s) seeded, ${skipped} skipped`);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`ExportDjiboutiInterchangeDemoSeeder failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user