mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 22:55:02 +00:00
feat(rule-engine): add fuel surcharge rate trigger and cargo flag
This commit is contained in:
@@ -175,6 +175,9 @@ export class RuleEngineService {
|
||||
// matchesTrigger can fire the LASHING rate. Falls back to an explicit
|
||||
// input flag when no cargo type is set (e.g. container bookings).
|
||||
let hasLashing = input.hasLashing === true;
|
||||
// Fuel is likewise a cargo-type property (hasFuel), billed off the
|
||||
// lane-scoped FUEL rate — see fuelCharges.
|
||||
let hasFuel = false;
|
||||
if (input.cargoTypeId) {
|
||||
const cargoType = await this.cargoTypesRepo.findById(input.cargoTypeId);
|
||||
if (!cargoType) {
|
||||
@@ -186,6 +189,9 @@ export class RuleEngineService {
|
||||
if (cargoType.hasLashing) {
|
||||
hasLashing = true;
|
||||
}
|
||||
if (cargoType.hasFuel) {
|
||||
hasFuel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +334,9 @@ export class RuleEngineService {
|
||||
// Lashing is sold per cargo kind + container type — billed by the
|
||||
// kind-aware block below, never by this generic loop.
|
||||
if (rate.trigger === 'LASHING') continue;
|
||||
// Fuel is sold per lane + cargo type — billed by the route-matched
|
||||
// block below, never by this route-agnostic loop.
|
||||
if (rate.trigger === 'FUEL') continue;
|
||||
const triggered = this.matchesTrigger(rate.trigger, {
|
||||
isHazardous: input.isHazardous,
|
||||
hasReefer,
|
||||
@@ -442,6 +451,10 @@ export class RuleEngineService {
|
||||
appliedModifiers.push(...this.lashingCharges(input, liveRates));
|
||||
}
|
||||
|
||||
if (hasFuel) {
|
||||
appliedModifiers.push(...this.fuelCharges(input, liveRates));
|
||||
}
|
||||
|
||||
return {
|
||||
priorityScore,
|
||||
appliedModifiers,
|
||||
@@ -632,6 +645,50 @@ export class RuleEngineService {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fuel surcharge — fires when the booking's cargo type has hasFuel = true,
|
||||
* billed off the FUEL rate matching the booking's lane (trade direction +
|
||||
* origin + destination) and cargo type. PER_LITER bills baseLiters ×
|
||||
* rateValue once per booking; PER_WAGON bills the wagons the cargo occupies.
|
||||
* No matching lane rate simply bills nothing — same leniency as lashing.
|
||||
*/
|
||||
private fuelCharges(
|
||||
input: BookingEvaluationInput,
|
||||
liveRates: Rate[],
|
||||
): AppliedCargoModifier[] {
|
||||
const modifiers: AppliedCargoModifier[] = [];
|
||||
const rate = liveRates.find(
|
||||
(r) =>
|
||||
r.trigger === 'FUEL' &&
|
||||
r.currency === 'USD' &&
|
||||
r.tradeDirection === input.tradeDirection &&
|
||||
r.originYardId === input.originYardId &&
|
||||
r.destinationYardId === input.destinationYardId &&
|
||||
r.cargoTypeId === input.cargoTypeId,
|
||||
);
|
||||
if (!rate) return modifiers;
|
||||
|
||||
const rateValue = Number(rate.rateValue);
|
||||
const wagons = Math.max(
|
||||
0,
|
||||
Number(input.bulkWagons ?? 0) || Number(input.totalWagons ?? 0),
|
||||
);
|
||||
const billedQty =
|
||||
rate.rateUnit === 'PER_LITER' ? Number(rate.baseLiters ?? 0) : wagons;
|
||||
const amount = billedQty * rateValue;
|
||||
if (!(amount > 0)) return modifiers;
|
||||
modifiers.push({
|
||||
rateId: rate.id,
|
||||
surchargeCode: this.surchargeCode(rate),
|
||||
triggerValue: billedQty,
|
||||
calculatedAmount: amount,
|
||||
currency: rate.currency,
|
||||
unitPriceUsd: rateValue,
|
||||
billingUnit: rate.rateUnit,
|
||||
});
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages for container lines whose total weight exceeds the hard capacity
|
||||
* ceiling (weight_limit_rules.max_capacity_tons). Non-empty ⇒ the booking
|
||||
|
||||
Reference in New Issue
Block a user