mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
803 lines
23 KiB
TypeScript
803 lines
23 KiB
TypeScript
import { Injectable, Logger } from "@nestjs/common";
|
|
import { DataSource } from "typeorm";
|
|
|
|
import { BookingCargoModifier } from "../modules/bookings/entities/booking-cargo-modifier.entity";
|
|
import { CargoType } from "../modules/rule-engine/entities/cargo-type.entity";
|
|
import { ContainerType } from "../modules/rule-engine/entities/container-type.entity";
|
|
import { PriorityRule } from "../modules/rule-engine/entities/priority-rule.entity";
|
|
import { Rate } from "../modules/rule-engine/entities/rate.entity";
|
|
import { ServiceType } from "../modules/rule-engine/entities/service-type.entity";
|
|
import { ShippingLine } from "../modules/rule-engine/entities/shipping-line.entity";
|
|
import { SurchargeType } from "../modules/rule-engine/entities/surcharge-type.entity";
|
|
import { WeightLimitRule } from "../modules/rule-engine/entities/weight-limit-rule.entity";
|
|
import { Yard } from "../modules/rule-engine/entities/yard.entity";
|
|
|
|
const STAFF_USER_ID = "00000000-0000-0000-0000-000000000001";
|
|
const CEO_USER_ID = "00000000-0000-0000-0000-000000000002";
|
|
|
|
@Injectable()
|
|
export class PricingDataSeeder {
|
|
private readonly logger = new Logger(PricingDataSeeder.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) { }
|
|
|
|
async run(): Promise<void> {
|
|
await this.dataSource.transaction(async (manager) => {
|
|
const ctRepo = manager.getRepository(ContainerType);
|
|
const stRepo = manager.getRepository(ServiceType);
|
|
const yRepo = manager.getRepository(Yard);
|
|
const slRepo = manager.getRepository(ShippingLine);
|
|
const wlRepo = manager.getRepository(WeightLimitRule);
|
|
const prRepo = manager.getRepository(PriorityRule);
|
|
const rRepo = manager.getRepository(Rate);
|
|
|
|
await this.upsertReferenceData(manager, ctRepo, stRepo, yRepo, slRepo);
|
|
await this.seedWeightLimits(wlRepo, ctRepo);
|
|
await this.seedPriorityRules(prRepo);
|
|
const containerTypes = await ctRepo.find();
|
|
const ctByCode = new Map(containerTypes.map((ct) => [ct.code, ct]));
|
|
|
|
const rates = await this.seedRates(rRepo, ctByCode);
|
|
const ratesByType = new Map<string, Rate[]>();
|
|
for (const r of rates) {
|
|
const key = `${r.rateType}|${r.currency}|${r.containerTypeId ?? ""}`;
|
|
if (!ratesByType.has(key)) ratesByType.set(key, []);
|
|
ratesByType.get(key)!.push(r);
|
|
}
|
|
|
|
await this.seedSurchargeTypes(manager, ratesByType);
|
|
|
|
const yards = await yRepo.find();
|
|
const yardByCode = new Map(yards.map((y) => [y.code, y]));
|
|
const serviceTypes = await stRepo.find();
|
|
const stByCode = new Map(serviceTypes.map((st) => [st.code, st]));
|
|
const shippingLines = await slRepo.find();
|
|
const slByCode = new Map(shippingLines.map((sl) => [sl.code, sl]));
|
|
const cargoTypes = await manager.getRepository(CargoType).find();
|
|
const cargoByCode = new Map(cargoTypes.map((c) => [c.code, c]));
|
|
|
|
await this.seedDraftBookings(
|
|
ctByCode,
|
|
yardByCode,
|
|
stByCode,
|
|
slByCode,
|
|
cargoByCode,
|
|
);
|
|
});
|
|
|
|
this.logger.log("Seeded pricing data");
|
|
}
|
|
|
|
private async upsertReferenceData(
|
|
manager: any,
|
|
ctRepo: any,
|
|
stRepo: any,
|
|
yRepo: any,
|
|
slRepo: any,
|
|
): Promise<void> {
|
|
await yRepo.upsert(
|
|
[
|
|
{
|
|
code: "DJIBOUTI",
|
|
label: "Djibouti",
|
|
country: "Djibouti",
|
|
displayOrder: 1,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "ADDIS_ABABA",
|
|
label: "Addis Ababa",
|
|
country: "Ethiopia",
|
|
displayOrder: 2,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "DIRE_DAWA",
|
|
label: "Dire Dawa",
|
|
country: "Ethiopia",
|
|
displayOrder: 3,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "MODJO",
|
|
label: "Modjo",
|
|
country: "Ethiopia",
|
|
displayOrder: 4,
|
|
isActive: true,
|
|
},
|
|
],
|
|
{ conflictPaths: { code: true } },
|
|
);
|
|
|
|
await ctRepo.upsert(
|
|
[
|
|
{
|
|
code: "20FT",
|
|
label: "20FT Standard",
|
|
sizeFt: 20,
|
|
wagonsPerUnit: 1,
|
|
isReefer: false,
|
|
isOpenTop: false,
|
|
isActive: true,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
code: "40FT",
|
|
label: "40FT Standard",
|
|
sizeFt: 40,
|
|
wagonsPerUnit: 1,
|
|
isReefer: false,
|
|
isOpenTop: false,
|
|
isActive: true,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
code: "20FT_REEFER",
|
|
label: "20FT Reefer",
|
|
sizeFt: 20,
|
|
wagonsPerUnit: 1,
|
|
isReefer: true,
|
|
isOpenTop: false,
|
|
isActive: true,
|
|
displayOrder: 3,
|
|
},
|
|
{
|
|
code: "40FT_REEFER",
|
|
label: "40FT Reefer",
|
|
sizeFt: 40,
|
|
wagonsPerUnit: 1,
|
|
isReefer: true,
|
|
isOpenTop: false,
|
|
isActive: true,
|
|
displayOrder: 4,
|
|
},
|
|
],
|
|
{ conflictPaths: { code: true } },
|
|
);
|
|
|
|
await stRepo.upsert(
|
|
[
|
|
{
|
|
code: "RAIL_CONTAINER",
|
|
serviceName: "Rail Container Service",
|
|
description: "Standard rail container transport",
|
|
canBeBookedAlone: true,
|
|
includesFirstMile: false,
|
|
includesLastMile: false,
|
|
includesCustoms: false,
|
|
priorityBonusPoints: 0,
|
|
isActive: true,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
code: "RAIL_FORWARDING",
|
|
serviceName: "Rail Forwarding Service",
|
|
description: "Rail transport with first/last mile and customs",
|
|
canBeBookedAlone: true,
|
|
includesFirstMile: true,
|
|
includesLastMile: true,
|
|
includesCustoms: true,
|
|
priorityBonusPoints: 100,
|
|
isActive: true,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
code: "RAIL_BULK",
|
|
serviceName: "Rail Bulk Transport",
|
|
description: "Bulk commodity rail transport",
|
|
canBeBookedAlone: true,
|
|
includesFirstMile: false,
|
|
includesLastMile: false,
|
|
includesCustoms: false,
|
|
priorityBonusPoints: 50,
|
|
isActive: true,
|
|
displayOrder: 3,
|
|
},
|
|
],
|
|
{ conflictPaths: { code: true } },
|
|
);
|
|
|
|
await slRepo.upsert(
|
|
[
|
|
{
|
|
code: "MAERSK",
|
|
label: "Maersk Line",
|
|
mappedToCode: "MAERSK",
|
|
showExtraFeeNotice: true,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "MSC",
|
|
label: "MSC",
|
|
mappedToCode: "MSC",
|
|
showExtraFeeNotice: true,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "CMA_CGM",
|
|
label: "CMA CGM",
|
|
mappedToCode: "CMA_CGM",
|
|
showExtraFeeNotice: true,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "COSCO",
|
|
label: "COSCO Shipping",
|
|
mappedToCode: "COSCO",
|
|
showExtraFeeNotice: true,
|
|
isActive: true,
|
|
},
|
|
{
|
|
code: "OTHER",
|
|
label: "Other Line",
|
|
mappedToCode: null,
|
|
showExtraFeeNotice: false,
|
|
isActive: true,
|
|
},
|
|
],
|
|
{ conflictPaths: { code: true } },
|
|
);
|
|
|
|
await manager.getRepository(CargoType).upsert(
|
|
[
|
|
{
|
|
code: "GRAIN",
|
|
cargoTypeName: "Grain / Cereals",
|
|
requiresDirectorApproval: false,
|
|
isActive: true,
|
|
displayOrder: 1,
|
|
},
|
|
{
|
|
code: "FERTILIZER",
|
|
cargoTypeName: "Fertilizer",
|
|
requiresDirectorApproval: false,
|
|
isActive: true,
|
|
displayOrder: 2,
|
|
},
|
|
{
|
|
code: "CEMENT",
|
|
cargoTypeName: "Cement / Clinker",
|
|
requiresDirectorApproval: false,
|
|
isActive: true,
|
|
displayOrder: 3,
|
|
},
|
|
{
|
|
code: "STEEL",
|
|
cargoTypeName: "Steel / Rebar",
|
|
requiresDirectorApproval: true,
|
|
isActive: true,
|
|
displayOrder: 4,
|
|
},
|
|
{
|
|
code: "MACHINERY",
|
|
cargoTypeName: "Heavy Machinery",
|
|
requiresDirectorApproval: true,
|
|
isActive: true,
|
|
displayOrder: 5,
|
|
},
|
|
{
|
|
code: "OTHER_BULK",
|
|
cargoTypeName: "Other Bulk Cargo",
|
|
requiresDirectorApproval: false,
|
|
isActive: true,
|
|
displayOrder: 6,
|
|
},
|
|
],
|
|
{ conflictPaths: { code: true } },
|
|
);
|
|
}
|
|
|
|
private async seedWeightLimits(wlRepo: any, ctRepo: any): Promise<void> {
|
|
await wlRepo.createQueryBuilder().delete().execute();
|
|
const twenty = await ctRepo.findOneByOrFail({ code: "20FT" });
|
|
const forty = await ctRepo.findOneByOrFail({ code: "40FT" });
|
|
const base = new Date("2026-01-01");
|
|
await wlRepo.insert([
|
|
{
|
|
containerTypeId: twenty.id,
|
|
tradeDirection: "IMPORT",
|
|
maxVgmTons: 26,
|
|
effectiveFrom: base,
|
|
},
|
|
{
|
|
containerTypeId: twenty.id,
|
|
tradeDirection: "EXPORT",
|
|
maxVgmTons: 26,
|
|
effectiveFrom: base,
|
|
},
|
|
{
|
|
containerTypeId: forty.id,
|
|
tradeDirection: "IMPORT",
|
|
maxVgmTons: 28,
|
|
effectiveFrom: base,
|
|
},
|
|
{
|
|
containerTypeId: forty.id,
|
|
tradeDirection: "EXPORT",
|
|
maxVgmTons: 28,
|
|
effectiveFrom: base,
|
|
},
|
|
]);
|
|
this.logger.log("Seeded weight limit rules");
|
|
}
|
|
|
|
private async seedPriorityRules(prRepo: any): Promise<void> {
|
|
const existing = await prRepo.find({
|
|
where: [{ code: "USD_PRIORITY" }, { code: "STANDARD_PRIORITY" }],
|
|
});
|
|
for (const r of existing) {
|
|
await prRepo.remove(r);
|
|
}
|
|
await prRepo.save([
|
|
prRepo.create({
|
|
code: "USD_PRIORITY",
|
|
label: "USD Payment Priority",
|
|
score: 200,
|
|
conditionCurrency: "USD",
|
|
isActive: true,
|
|
}),
|
|
prRepo.create({
|
|
code: "STANDARD_PRIORITY",
|
|
label: "Standard Priority",
|
|
score: 50,
|
|
conditionCurrency: null,
|
|
isActive: true,
|
|
}),
|
|
]);
|
|
this.logger.log("Seeded priority rules");
|
|
}
|
|
|
|
private async seedRates(
|
|
rRepo: any,
|
|
ctByCode: Map<string, any>,
|
|
): Promise<Rate[]> {
|
|
const effectiveFrom = new Date("2026-01-01");
|
|
const now = new Date();
|
|
// await rRepo.createQueryBuilder().delete().execute();
|
|
|
|
const rateData = [
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
currency: "USD",
|
|
rateValue: 800,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
currency: "USD",
|
|
rateValue: 1200,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 45000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 67000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
currency: "USD",
|
|
rateValue: 600,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
currency: "USD",
|
|
rateValue: 900,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 34000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 50000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "INTERCITY_CONTAINER",
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 20000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "INTERCITY_CONTAINER",
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
currency: "ETB",
|
|
rateValue: 30000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 1000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_IMPORT",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 56000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 750,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "CONTAINER_EXPORT",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 42000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "INTERCITY_CONTAINER",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 25000,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "BULK_IMPORT",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 50,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "BULK_IMPORT",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 2800,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "BULK_EXPORT",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 40,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "BULK_EXPORT",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 2200,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "OVERWEIGHT_PER_TON",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 25,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "OVERWEIGHT_PER_TON",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 1400,
|
|
rateUnit: "PER_TON",
|
|
},
|
|
{
|
|
rateType: "HAZARD_SURCHARGE",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 150,
|
|
rateUnit: "FLAT",
|
|
},
|
|
{
|
|
rateType: "HAZARD_SURCHARGE",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 8500,
|
|
rateUnit: "FLAT",
|
|
},
|
|
{
|
|
rateType: "REEFER_SURCHARGE",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 200,
|
|
rateUnit: "FLAT",
|
|
},
|
|
{
|
|
rateType: "REEFER_SURCHARGE",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 11000,
|
|
rateUnit: "FLAT",
|
|
},
|
|
{
|
|
rateType: "DOUBLE_HANDLING",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 100,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "DOUBLE_HANDLING",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 5500,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "LASHING",
|
|
containerTypeId: null,
|
|
currency: "USD",
|
|
rateValue: 50,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
{
|
|
rateType: "LASHING",
|
|
containerTypeId: null,
|
|
currency: "ETB",
|
|
rateValue: 2800,
|
|
rateUnit: "PER_CONTAINER",
|
|
},
|
|
];
|
|
|
|
const entities = rateData.map((d) =>
|
|
rRepo.create({
|
|
...d,
|
|
status: "LIVE",
|
|
proposedByStaffId: STAFF_USER_ID,
|
|
approvedByCeoId: CEO_USER_ID,
|
|
approvedAt: now,
|
|
effectiveFrom,
|
|
}),
|
|
);
|
|
return rRepo.save(entities);
|
|
}
|
|
|
|
private async seedSurchargeTypes(
|
|
manager: any,
|
|
ratesByType: Map<string, Rate[]>,
|
|
): Promise<void> {
|
|
const surRepo = manager.getRepository(SurchargeType);
|
|
const bcmRepo = manager.getRepository(BookingCargoModifier);
|
|
await bcmRepo.createQueryBuilder().delete().execute();
|
|
const findRate = (rateType: string, currency: string) => {
|
|
const key = `${rateType}|${currency}|`;
|
|
const rates = ratesByType.get(key);
|
|
return rates?.[0];
|
|
};
|
|
|
|
const hazardRateUsd = findRate("HAZARD_SURCHARGE", "USD");
|
|
const hazardRateEtb = findRate("HAZARD_SURCHARGE", "ETB");
|
|
const reeferRateUsd = findRate("REEFER_SURCHARGE", "USD");
|
|
const reeferRateEtb = findRate("REEFER_SURCHARGE", "ETB");
|
|
const overweightRateUsd = findRate("OVERWEIGHT_PER_TON", "USD");
|
|
const overweightRateEtb = findRate("OVERWEIGHT_PER_TON", "ETB");
|
|
const shipLineRateUsd = findRate("DOUBLE_HANDLING", "USD");
|
|
const shipLineRateEtb = findRate("DOUBLE_HANDLING", "ETB");
|
|
const consolidRateUsd = findRate("LASHING", "USD");
|
|
const consolidRateEtb = findRate("LASHING", "ETB");
|
|
|
|
await surRepo.createQueryBuilder().delete().execute();
|
|
await surRepo.save([
|
|
surRepo.create({
|
|
code: "HAZARDOUS_CARGO",
|
|
label: "Hazardous Cargo",
|
|
triggerCondition: "CARGO_FLAG_HAZARDOUS",
|
|
rateId: hazardRateUsd?.id ?? hazardRateEtb?.id,
|
|
isActive: true,
|
|
}),
|
|
surRepo.create({
|
|
code: "REEFER_CARGO",
|
|
label: "Reefer Cargo",
|
|
triggerCondition: "CARGO_FLAG_REEFER",
|
|
rateId: reeferRateUsd?.id ?? reeferRateEtb?.id,
|
|
isActive: true,
|
|
}),
|
|
surRepo.create({
|
|
code: "OVERWEIGHT_CARGO",
|
|
label: "Overweight Cargo",
|
|
triggerCondition: "VGM_EXCEEDS_LIMIT",
|
|
rateId: overweightRateUsd?.id ?? overweightRateEtb?.id,
|
|
isActive: true,
|
|
}),
|
|
surRepo.create({
|
|
code: "SHIPPING_LINE_FEE",
|
|
label: "Shipping Line Fee",
|
|
triggerCondition: "SHIPPING_LINE_MAPPED",
|
|
rateId: shipLineRateUsd?.id ?? shipLineRateEtb?.id,
|
|
isActive: true,
|
|
}),
|
|
surRepo.create({
|
|
code: "CONSOLIDATION_FEE",
|
|
label: "Consolidation Fee",
|
|
triggerCondition: "CONSOLIDATION_ENABLED",
|
|
rateId: consolidRateUsd?.id ?? consolidRateEtb?.id,
|
|
isActive: true,
|
|
}),
|
|
]);
|
|
this.logger.log("Seeded surcharge types");
|
|
}
|
|
|
|
private async seedDraftBookings(
|
|
ctByCode: Map<string, any>,
|
|
yardByCode: Map<string, any>,
|
|
stByCode: Map<string, any>,
|
|
slByCode: Map<string, any>,
|
|
cargoByCode: Map<string, any>,
|
|
): Promise<void> {
|
|
const djibouti = yardByCode.get("DJIBOUTI")!;
|
|
const addis = yardByCode.get("ADDIS_ABABA")!;
|
|
const railContainer = stByCode.get("RAIL_CONTAINER")!;
|
|
const railBulk = stByCode.get("RAIL_BULK")!;
|
|
const maersk = slByCode.get("MAERSK")!;
|
|
const grain = cargoByCode.get("GRAIN")!;
|
|
const twenty = ctByCode.get("20FT")!;
|
|
const forty = ctByCode.get("40FT")!;
|
|
const twentyReefer = ctByCode.get("20FT_REEFER")!;
|
|
|
|
const drafts = [
|
|
{
|
|
reference: "BKG-PRICE-001",
|
|
description: "Standard 20FT container import — base rail only",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: false,
|
|
shippingLineId: null,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 250,
|
|
containers: [
|
|
{ containerTypeId: twenty.id, quantity: 10, vgmPerUnitTons: 25 },
|
|
],
|
|
expectedBaseRate: 800,
|
|
expectedSurcharges: [],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-002",
|
|
description: "40FT container import + hazardous surcharge",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: true,
|
|
allowConsolidation: false,
|
|
shippingLineId: null,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 135,
|
|
containers: [
|
|
{ containerTypeId: forty.id, quantity: 5, vgmPerUnitTons: 27 },
|
|
],
|
|
expectedBaseRate: 1200,
|
|
expectedSurcharges: ["HAZARDOUS_CARGO"],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-003",
|
|
description: "20FT container import + shipping line (ETB)",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "ETB",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: false,
|
|
shippingLineId: maersk.id,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 480,
|
|
containers: [
|
|
{ containerTypeId: twenty.id, quantity: 20, vgmPerUnitTons: 24 },
|
|
],
|
|
expectedBaseRate: 45000,
|
|
expectedSurcharges: ["SHIPPING_LINE_FEE"],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-004",
|
|
description: "40FT container import + consolidation (USD)",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: true,
|
|
shippingLineId: null,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 224,
|
|
containers: [
|
|
{ containerTypeId: forty.id, quantity: 8, vgmPerUnitTons: 28 },
|
|
],
|
|
expectedBaseRate: 1200,
|
|
expectedSurcharges: ["CONSOLIDATION_FEE"],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-005",
|
|
description: "Bulk import — grain",
|
|
freightType: "BULK" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railBulk.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: false,
|
|
shippingLineId: null,
|
|
cargoTypeId: grain.id,
|
|
cargoTotalWeightVgm: 500,
|
|
containers: [],
|
|
expectedBaseRate: 50,
|
|
expectedSurcharges: [],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-006",
|
|
description: "20FT reefer container import + reefer surcharge",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: false,
|
|
shippingLineId: null,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 75,
|
|
containers: [
|
|
{ containerTypeId: twentyReefer.id, quantity: 3, vgmPerUnitTons: 25 },
|
|
],
|
|
expectedBaseRate: 800,
|
|
expectedSurcharges: ["REEFER_CARGO"],
|
|
},
|
|
{
|
|
reference: "BKG-PRICE-007",
|
|
description: "20FT container import + overweight (30t > 26t limit)",
|
|
freightType: "CONTAINER" as const,
|
|
tradeDirection: "IMPORT",
|
|
paymentCurrency: "USD",
|
|
serviceTypeId: railContainer.id,
|
|
originYardId: djibouti.id,
|
|
destinationYardId: addis.id,
|
|
isHazardous: false,
|
|
allowConsolidation: false,
|
|
shippingLineId: null,
|
|
cargoTypeId: null,
|
|
cargoTotalWeightVgm: 300,
|
|
containers: [
|
|
{ containerTypeId: twenty.id, quantity: 10, vgmPerUnitTons: 30 },
|
|
],
|
|
expectedBaseRate: 800,
|
|
expectedSurcharges: ["OVERWEIGHT_CARGO"],
|
|
},
|
|
];
|
|
|
|
this.logger.log(`Seeded ${drafts.length} DRAFT bookings for pricing`);
|
|
}
|
|
}
|