Files
edr-platform/apps/edr-freight-api/src/seed/demo-bookings.seeder.ts
2026-06-06 16:23:48 +03:00

344 lines
10 KiB
TypeScript

import { Injectable, Logger } from "@nestjs/common";
import { randomUUID } from "crypto";
import { DataSource } 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 { Locomotive } from "../modules/locomotives/entities/locomotive.entity";
import { ServiceType } from "../modules/rule-engine/entities/service-type.entity";
import { Yard } from "../modules/rule-engine/entities/yard.entity";
import { WagonType } from "../modules/wagon-types/entities/wagon-type.entity";
import { ContainerType } from "../modules/rule-engine/entities/container-type.entity";
const SEED_FLAG = "SEED_DEMO_BOOKINGS";
const SERVICE_TYPE_CODE = "RAIL_CONTAINER";
const COMPANY_EMAIL = "train-scheduling-demo@edr.local";
const COMPANY_TIN = "1234567890";
const YARDS = [
{ code: "DJIBOUTI", label: "Djibouti", country: "Djibouti", displayOrder: 1 },
{
code: "ADDIS_ABABA",
label: "Addis Ababa",
country: "Ethiopia",
displayOrder: 2,
},
{
code: "DIRE_DAWA",
label: "Dire Dawa",
country: "Ethiopia",
displayOrder: 3,
},
];
const CONTAINER_TYPES = [
{ code: "20FT", label: "20FT", sizeFt: 20 },
{ code: "40FT", label: "40FT", sizeFt: 40 },
];
const DEMO_BOOKINGS = [
{
reference: "BKG-CONT-001",
containerCode: "40FT",
quantity: 20,
totalWeightTons: 500,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-002",
containerCode: "20FT",
quantity: 10,
totalWeightTons: 300,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-003",
containerCode: "40FT",
quantity: 15,
totalWeightTons: 450,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-007",
containerCode: "20FT",
quantity: 6,
totalWeightTons: 180,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-008",
containerCode: "40FT",
quantity: 4,
totalWeightTons: 120,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-009",
containerCode: "20FT",
quantity: 5,
totalWeightTons: 110,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-004",
containerCode: "40FT",
quantity: 12,
totalWeightTons: 360,
originCode: "ADDIS_ABABA",
destinationCode: "DIRE_DAWA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-005",
containerCode: "20FT",
quantity: 8,
totalWeightTons: 160,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-21T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
{
reference: "BKG-CONT-006",
containerCode: "40FT",
quantity: 80,
totalWeightTons: 3600,
originCode: "DJIBOUTI",
destinationCode: "ADDIS_ABABA",
scheduledDate: "2026-06-20T08:00:00.000Z",
status: "PAID",
paymentStatus: "PAID",
},
];
@Injectable()
export class DemoBookingsSeeder {
private readonly logger = new Logger(DemoBookingsSeeder.name);
constructor(private readonly dataSource: DataSource) { }
async run() {
const shouldSeed = process.env[SEED_FLAG]?.trim().toLowerCase() === "true";
if (!shouldSeed) {
this.logger.log(
`Skipping demo booking seed because ${SEED_FLAG} is not enabled`,
);
return;
}
await this.dataSource.transaction(async (manager) => {
await manager.getRepository(WagonType).upsert(
{
code: "NW5",
name: "Flat Wagon",
capacityTons: 70,
lengthMeters: 14,
maxWagonsPerTrain: 53,
supportedLoadTypes: ["CONTAINER"],
isActive: true,
},
{ conflictPaths: { code: true } },
);
await manager.getRepository(Locomotive).upsert(
[
{
code: "LOC-001",
name: "Demo Locomotive 1",
locomotiveType: 'ELECTRIC',
maxPullWeightTons: 3500,
maxTrainLengthMeters: 760,
status: "AVAILABLE",
},
{
code: "LOC-002",
name: "Demo Locomotive 2",
locomotiveType: 'DIESEL',
maxPullWeightTons: 2500,
maxTrainLengthMeters: 760,
status: "AVAILABLE",
},
],
{ conflictPaths: { code: true } },
);
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 Service",
description: "Temporary service type for train scheduling demos",
canBeBookedAlone: true,
includesFirstMile: false,
includesLastMile: false,
includesCustoms: false,
priorityBonusPoints: 0,
isActive: true,
displayOrder: 1,
},
{ 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: "Train Scheduling Demo Customer",
type: CompanyType.Customer,
status: CompanyStatus.Active,
tin: COMPANY_TIN,
vatNumber: "1234567890",
fanNumber: "1234567890123456",
country: "Ethiopia",
address: "Demo Address",
phone: "251900000001",
email: COMPANY_EMAIL,
website: null,
contactPersonName: "Train Scheduling",
contactPersonPhone: "251900000001",
generalManagerName: "Demo Manager",
generalManagerEmail: COMPANY_EMAIL,
generalManagerPhone: "251900000001",
},
{ 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(
`demo_booking_seed_dependency_missing:${demoBooking.reference}`,
);
}
const vgmPerUnitTons =
demoBooking.totalWeightTons / demoBooking.quantity;
await manager.getRepository(Booking).upsert(
{
reference: demoBooking.reference,
companyId: company.id,
status: demoBooking.status,
scheduledDate: new Date(demoBooking.scheduledDate),
totalAmount: 0,
paymentStatus: demoBooking.paymentStatus,
contractType: "NEW",
serviceTypeId: serviceType.id,
equipmentReturn: "WITHOUT_RETURN",
originYardId: origin.id,
destinationYardId: destination.id,
tradeDirection: "IMPORT",
freightType: "CONTAINER",
cargoTypeId: null,
cargoFreeText: null,
shippingLineId: null,
cargoTotalWeightVgm: demoBooking.totalWeightTons,
isHazardous: false,
paymentCurrency: "USD",
allowConsolidation: false,
priorityScore: 0,
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: Math.ceil(demoBooking.totalWeightTons / 70),
weightLimitRuleId: null,
isOverweight: demoBooking.totalWeightTons > 70,
overweightExcessTons:
demoBooking.totalWeightTons > 70
? demoBooking.totalWeightTons - 70
: null,
});
}
});
this.logger.log("Seeded demo train scheduling data");
}
}