import type { RateAppliesTo, RateTrigger, RateUnit } from './rate.entity'; /** * Which rate units make sense for a given rate shape. The weighting basis is * driven by the *type* of thing being billed — a container leg bills per * container, bulk freight per ton, an intercity move can be per-km, a * cancellation is a flat/per-invoice fee, and overweight is always per excess * ton. This keeps the rate table dynamic yet non-conflicting: the admin can * only pick a unit the pricing engine knows how to apply. * * Returned lists are ordered with the most natural/default unit first. */ export function allowedRateUnits(input: { appliesTo: RateAppliesTo; trigger: RateTrigger; }): RateUnit[] { const { appliesTo, trigger } = input; // Surcharges (Applies to = Other) are governed by their trigger. if (appliesTo === 'OTHER') { switch (trigger) { case 'OVERWEIGHT': // Overweight always bills the excess tonnage — per ton, nothing else. return ['PER_TON']; case 'REEFER': case 'HAZARDOUS': // Scale with the freight shape: per container for boxes, per ton for bulk. return ['PER_CONTAINER', 'PER_TON']; case 'DEMURRAGE': return ['PER_CONTAINER', 'PER_TON']; case 'WITH_RETURN': // Container-only empty-return service — bills per returned container. return ['PER_CONTAINER', 'FLAT']; case 'CANCELLATION': return ['FLAT', 'PER_INVOICE']; case 'CUSTOMS_CLEARANCE': // Flat per clearance (ONE_TIME contract) / per shipment request (GENERAL). return ['FLAT']; case 'CONSOLIDATION': return ['PER_CONTAINER', 'FLAT']; case 'SHIPPING_LINE': case 'PIL_EXTRA_FEE': return ['PER_CONTAINER', 'FLAT']; default: return ['FLAT', 'PER_TON', 'PER_CONTAINER']; } } // Base freight + first/last mile scale with the cargo type. switch (appliesTo) { case 'CONTAINER': return ['PER_CONTAINER', 'PER_WAGON']; case 'BULK': return ['PER_TON', 'PER_WAGON']; case 'INTERCITY': return ['PER_CONTAINER', 'PER_TON', 'PER_WAGON', 'PER_KM']; case 'FIRST_MILE': case 'LAST_MILE': return ['PER_CONTAINER', 'PER_TON', 'PER_KM', 'FLAT']; default: return ['FLAT']; } } /** The default (first / most natural) unit for a rate shape. */ export function defaultRateUnit(input: { appliesTo: RateAppliesTo; trigger: RateTrigger }): RateUnit { return allowedRateUnits(input)[0]; } /** True when `unit` is a valid weighting basis for the given rate shape. */ export function isRateUnitAllowed(input: { appliesTo: RateAppliesTo; trigger: RateTrigger; unit: RateUnit; }): boolean { return allowedRateUnits(input).includes(input.unit); }