mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
- Added ContainerValidationService to handle 20ft weight-pairing logic. - Introduced validate20ftWeightPairing utility function to check weight differences. - Updated BookingPricingService to include overweight line details and pairing errors in price response. - Enhanced BookingTransitionService to reject submissions with unpairable 20ft containers. - Created ShipmentValidation interface for pre-submit validation of container contracts. - Integrated shipment validation into the contract booking process, providing warnings for overweight containers and hard blocks for pairing errors. - Updated front-end components to display validation results and prevent submission when errors are present.
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
/**
|
|
* Booking-time 20ft weight-pairing rule.
|
|
*
|
|
* A container wagon holds two 20ft containers (2 TEU). When two 20ft ride the
|
|
* same wagon their gross-weight difference must not exceed `maxPairDiffTons`
|
|
* (global rule `max20ftPairWeightDiffTons`, default 10t) so the wagon load stays
|
|
* balanced. 40ft containers occupy a whole wagon alone and never pair.
|
|
*
|
|
* At booking time the customer enters every 20ft container's weight but not its
|
|
* wagon slot, so we auto-pair: sort the 20ft weights ascending and pair adjacent
|
|
* (0-1, 2-3, …). Adjacent pairing minimises the diff of every pair, so if ANY
|
|
* valid pairing exists this one finds it — a violation here means no balanced
|
|
* pairing is possible and the booking must be blocked. An odd leftover 20ft is
|
|
* fine: it has no partner in this booking and flows to consolidation.
|
|
*/
|
|
|
|
export interface Container20ftUnit {
|
|
/** Human label for messages, e.g. the container number. */
|
|
label: string;
|
|
grossWeightTons: number;
|
|
}
|
|
|
|
export interface PairingViolation {
|
|
message: string;
|
|
/** The two container labels whose pairing exceeds the diff cap. */
|
|
labels: [string, string];
|
|
diffTons: number;
|
|
}
|
|
|
|
const round2 = (n: number): number => Math.round(n * 100) / 100;
|
|
|
|
/**
|
|
* Validate that the given 20ft units can all be paired onto wagons within the
|
|
* weight-difference cap. Returns one violation per over-cap adjacent pair (empty
|
|
* when every wagon pair is balanced or there is nothing to pair). A single
|
|
* leftover unit (odd count) is not a violation.
|
|
*/
|
|
export function validate20ftWeightPairing(
|
|
units: Container20ftUnit[],
|
|
maxPairDiffTons: number,
|
|
): PairingViolation[] {
|
|
if (units.length < 2 || maxPairDiffTons == null) return [];
|
|
|
|
// Ascending by weight: adjacent pairs have the smallest possible diffs.
|
|
const sorted = [...units].sort((a, b) => a.grossWeightTons - b.grossWeightTons);
|
|
const violations: PairingViolation[] = [];
|
|
|
|
for (let i = 0; i + 1 < sorted.length; i += 2) {
|
|
const a = sorted[i];
|
|
const b = sorted[i + 1];
|
|
const diff = Math.abs(a.grossWeightTons - b.grossWeightTons);
|
|
if (diff > maxPairDiffTons) {
|
|
violations.push({
|
|
message:
|
|
`20ft containers ${a.label} (${round2(a.grossWeightTons)}T) and ` +
|
|
`${b.label} (${round2(b.grossWeightTons)}T) cannot share a wagon: ` +
|
|
`weight difference ${round2(diff)}T exceeds the ${maxPairDiffTons}T limit.`,
|
|
labels: [a.label, b.label],
|
|
diffTons: round2(diff),
|
|
});
|
|
}
|
|
}
|
|
return violations;
|
|
}
|