mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 21:15:41 +00:00
feat: implement 20ft container weight-pairing validation
- 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.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user