|
|
|
|
@@ -8,7 +8,6 @@ import { PriorityConfig } from "../modules/rule-engine/entities/priority-config.
|
|
|
|
|
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 { Route } from "../modules/routes/entities/route.entity";
|
|
|
|
|
import { RouteMilestone } from "../modules/routes/entities/route-milestone.entity";
|
|
|
|
|
@@ -40,15 +39,13 @@ export class PricingDataSeeder {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
// Clear booking cargo modifiers up front — they reference rate snapshots
|
|
|
|
|
// that get recomputed when bookings are repriced.
|
|
|
|
|
await manager.getRepository(BookingCargoModifier).createQueryBuilder().delete().execute();
|
|
|
|
|
|
|
|
|
|
await this.seedSurchargeTypes(manager, ratesByType);
|
|
|
|
|
const cargoTypesForRates = await manager.getRepository(CargoType).find();
|
|
|
|
|
const cargoForRatesByCode = new Map(cargoTypesForRates.map((c) => [c.code, c]));
|
|
|
|
|
await this.seedRates(rRepo, ctByCode, cargoForRatesByCode);
|
|
|
|
|
|
|
|
|
|
const yards = await yRepo.find();
|
|
|
|
|
const yardByCode = new Map(yards.map((y) => [y.code, y]));
|
|
|
|
|
@@ -422,135 +419,43 @@ private async seedWeightLimits(wlRepo: any, ctRepo: any): Promise<void> {
|
|
|
|
|
private async seedRates(
|
|
|
|
|
rRepo: any,
|
|
|
|
|
ctByCode: Map<string, any>,
|
|
|
|
|
cargoByCode: Map<string, any>,
|
|
|
|
|
): Promise<Rate[]> {
|
|
|
|
|
const effectiveFrom = new Date("2026-01-01");
|
|
|
|
|
const now = new Date();
|
|
|
|
|
// await rRepo.createQueryBuilder().delete().execute();
|
|
|
|
|
|
|
|
|
|
// Each rate is self-describing: `appliesTo` + `trigger` decide how the
|
|
|
|
|
// engine uses it. trigger=ALWAYS → base freight; anything else → a
|
|
|
|
|
// surcharge that stacks additively when the booking matches.
|
|
|
|
|
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_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_IMPORT",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 1000,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "CONTAINER_EXPORT",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 750,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "INTERCITY_CONTAINER",
|
|
|
|
|
containerTypeId: ctByCode.get("20FT")!.id,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 350,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "INTERCITY_CONTAINER",
|
|
|
|
|
containerTypeId: ctByCode.get("40FT")!.id,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 550,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "INTERCITY_CONTAINER",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 400,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "INTERCITY_BULK",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 35,
|
|
|
|
|
rateUnit: "PER_TON",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "BULK_IMPORT",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 50,
|
|
|
|
|
rateUnit: "PER_TON",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "BULK_EXPORT",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 40,
|
|
|
|
|
rateUnit: "PER_TON",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "OVERWEIGHT_PER_TON",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 25,
|
|
|
|
|
rateUnit: "PER_TON",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "HAZARD_SURCHARGE",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 150,
|
|
|
|
|
rateUnit: "FLAT",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "REEFER_SURCHARGE",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 200,
|
|
|
|
|
rateUnit: "FLAT",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "DOUBLE_HANDLING",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 100,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
rateType: "LASHING",
|
|
|
|
|
containerTypeId: null,
|
|
|
|
|
currency: "USD",
|
|
|
|
|
rateValue: 50,
|
|
|
|
|
rateUnit: "PER_CONTAINER",
|
|
|
|
|
},
|
|
|
|
|
// ── Container base freight ──────────────────────────────────────────
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_IMPORT", tradeDirection: "IMPORT", containerTypeId: ctByCode.get("20FT")!.id, rateValue: 800, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_IMPORT", tradeDirection: "IMPORT", containerTypeId: ctByCode.get("40FT")!.id, rateValue: 1200, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_EXPORT", tradeDirection: "EXPORT", containerTypeId: ctByCode.get("20FT")!.id, rateValue: 600, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_EXPORT", tradeDirection: "EXPORT", containerTypeId: ctByCode.get("40FT")!.id, rateValue: 900, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_IMPORT", tradeDirection: "IMPORT", containerTypeId: null, rateValue: 1000, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "CONTAINER", trigger: "ALWAYS", rateType: "CONTAINER_EXPORT", tradeDirection: "EXPORT", containerTypeId: null, rateValue: 750, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
// ── Intercity base freight ──────────────────────────────────────────
|
|
|
|
|
{ appliesTo: "INTERCITY", trigger: "ALWAYS", rateType: "INTERCITY_CONTAINER", containerTypeId: ctByCode.get("20FT")!.id, rateValue: 350, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "INTERCITY", trigger: "ALWAYS", rateType: "INTERCITY_CONTAINER", containerTypeId: ctByCode.get("40FT")!.id, rateValue: 550, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "INTERCITY", trigger: "ALWAYS", rateType: "INTERCITY_CONTAINER", containerTypeId: null, rateValue: 400, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "INTERCITY", trigger: "ALWAYS", rateType: "INTERCITY_BULK", rateValue: 35, rateUnit: "PER_TON" },
|
|
|
|
|
// ── Bulk base freight (by leaf cargo type where known) ──────────────
|
|
|
|
|
{ appliesTo: "BULK", trigger: "ALWAYS", rateType: "BULK_IMPORT", tradeDirection: "IMPORT", cargoTypeId: cargoByCode.get("GRAIN")?.id ?? null, rateValue: 50, rateUnit: "PER_TON" },
|
|
|
|
|
{ appliesTo: "BULK", trigger: "ALWAYS", rateType: "BULK_EXPORT", tradeDirection: "EXPORT", cargoTypeId: cargoByCode.get("GRAIN")?.id ?? null, rateValue: 40, rateUnit: "PER_TON" },
|
|
|
|
|
{ appliesTo: "BULK", trigger: "ALWAYS", rateType: "BULK_IMPORT", tradeDirection: "IMPORT", cargoTypeId: null, rateValue: 50, rateUnit: "PER_TON" },
|
|
|
|
|
{ appliesTo: "BULK", trigger: "ALWAYS", rateType: "BULK_EXPORT", tradeDirection: "EXPORT", cargoTypeId: null, rateValue: 40, rateUnit: "PER_TON" },
|
|
|
|
|
// ── Surcharges (trigger-based) ──────────────────────────────────────
|
|
|
|
|
{ appliesTo: "OTHER", trigger: "OVERWEIGHT", rateType: "OVERWEIGHT_PER_TON", rateValue: 25, rateUnit: "PER_TON" },
|
|
|
|
|
{ appliesTo: "OTHER", trigger: "HAZARDOUS", rateType: "HAZARD_SURCHARGE", rateValue: 150, rateUnit: "FLAT" },
|
|
|
|
|
{ appliesTo: "OTHER", trigger: "REEFER", rateType: "REEFER_SURCHARGE", rateValue: 200, rateUnit: "FLAT" },
|
|
|
|
|
{ appliesTo: "OTHER", trigger: "SHIPPING_LINE", rateType: "DOUBLE_HANDLING", rateValue: 100, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
{ appliesTo: "OTHER", trigger: "CONSOLIDATION", rateType: "LASHING", rateValue: 50, rateUnit: "PER_CONTAINER" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const entities = rateData.map((d) =>
|
|
|
|
|
rRepo.create({
|
|
|
|
|
currency: "USD",
|
|
|
|
|
...d,
|
|
|
|
|
status: "LIVE",
|
|
|
|
|
proposedByStaffId: STAFF_USER_ID,
|
|
|
|
|
@@ -562,66 +467,6 @@ private async seedWeightLimits(wlRepo: any, ctRepo: any): Promise<void> {
|
|
|
|
|
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 reeferRateUsd = findRate("REEFER_SURCHARGE", "USD");
|
|
|
|
|
const overweightRateUsd = findRate("OVERWEIGHT_PER_TON", "USD");
|
|
|
|
|
const shipLineRateUsd = findRate("DOUBLE_HANDLING", "USD");
|
|
|
|
|
const consolidRateUsd = findRate("LASHING", "USD");
|
|
|
|
|
|
|
|
|
|
await surRepo.createQueryBuilder().delete().execute();
|
|
|
|
|
await surRepo.save([
|
|
|
|
|
surRepo.create({
|
|
|
|
|
code: "HAZARDOUS_CARGO",
|
|
|
|
|
label: "Hazardous Cargo",
|
|
|
|
|
triggerCondition: "CARGO_FLAG_HAZARDOUS",
|
|
|
|
|
rateId: hazardRateUsd?.id,
|
|
|
|
|
isActive: true,
|
|
|
|
|
}),
|
|
|
|
|
surRepo.create({
|
|
|
|
|
code: "REEFER_CARGO",
|
|
|
|
|
label: "Reefer Cargo",
|
|
|
|
|
triggerCondition: "CARGO_FLAG_REEFER",
|
|
|
|
|
rateId: reeferRateUsd?.id,
|
|
|
|
|
isActive: true,
|
|
|
|
|
}),
|
|
|
|
|
surRepo.create({
|
|
|
|
|
code: "OVERWEIGHT_CARGO",
|
|
|
|
|
label: "Overweight Cargo",
|
|
|
|
|
triggerCondition: "VGM_EXCEEDS_LIMIT",
|
|
|
|
|
rateId: overweightRateUsd?.id,
|
|
|
|
|
isActive: true,
|
|
|
|
|
}),
|
|
|
|
|
surRepo.create({
|
|
|
|
|
code: "SHIPPING_LINE_FEE",
|
|
|
|
|
label: "Shipping Line Fee",
|
|
|
|
|
triggerCondition: "SHIPPING_LINE_MAPPED",
|
|
|
|
|
rateId: shipLineRateUsd?.id,
|
|
|
|
|
isActive: true,
|
|
|
|
|
}),
|
|
|
|
|
surRepo.create({
|
|
|
|
|
code: "CONSOLIDATION_FEE",
|
|
|
|
|
label: "Consolidation Fee",
|
|
|
|
|
triggerCondition: "CONSOLIDATION_ENABLED",
|
|
|
|
|
rateId: consolidRateUsd?.id,
|
|
|
|
|
isActive: true,
|
|
|
|
|
}),
|
|
|
|
|
]);
|
|
|
|
|
this.logger.log("Seeded surcharge types");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async seedDraftBookings(
|
|
|
|
|
ctByCode: Map<string, any>,
|
|
|
|
|
yardByCode: Map<string, any>,
|
|
|
|
|
|