mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +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:
@@ -17,6 +17,14 @@ import {
|
||||
import { GeneratePriceResponseDto, PriceLineItemDto } from './dto/generate-price-response.dto';
|
||||
import { Booking } from './entities/booking.entity';
|
||||
import { assertBookingStatus } from './booking-status.util';
|
||||
import { ContainerValidationService } from './container-validation.service';
|
||||
|
||||
export interface OverweightLine {
|
||||
containerTypeCode: string;
|
||||
totalVgmTons: number;
|
||||
maxAllowedTons: number;
|
||||
excessTons: number;
|
||||
}
|
||||
|
||||
export interface ComputedPriceResult {
|
||||
lineItems: PriceLineItemDto[];
|
||||
@@ -27,6 +35,7 @@ export interface ComputedPriceResult {
|
||||
priorityScore: number;
|
||||
warnings: string[];
|
||||
hardBlocked: string[];
|
||||
overweightLines: OverweightLine[];
|
||||
}
|
||||
|
||||
type StoredPricingBreakdown = {
|
||||
@@ -67,6 +76,7 @@ export class BookingPricingService {
|
||||
private readonly containerTypesService: ContainerTypesService,
|
||||
private readonly ratesService: RatesService,
|
||||
private readonly exchangeService: ExchangeService,
|
||||
private readonly containerValidationService: ContainerValidationService,
|
||||
) {}
|
||||
|
||||
async generatePrice(bookingId: string): Promise<GeneratePriceResponseDto> {
|
||||
@@ -94,12 +104,19 @@ export class BookingPricingService {
|
||||
},
|
||||
} as never);
|
||||
|
||||
// 20ft weight-pairing preview: surfaced now so the customer sees the problem
|
||||
// (and the overweight warning + surcharge) at the confirm step, before submit.
|
||||
// Submit re-runs this and HARD-BLOCKS on a non-empty result.
|
||||
const pairing = await this.containerValidationService.validate20ftPairing(booking);
|
||||
|
||||
return {
|
||||
bookingId,
|
||||
totalAmount: computed.totalAmount,
|
||||
currency: computed.currency,
|
||||
lineItems: computed.lineItems,
|
||||
warnings: computed.warnings,
|
||||
overweightLines: computed.overweightLines,
|
||||
pairingErrors: pairing.map((p) => p.message),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,6 +186,35 @@ export class BookingPricingService {
|
||||
if (rate) usedRatesMap.set(rate.id, rate);
|
||||
}
|
||||
|
||||
// Overweight detail for the customer: map the engine's per-line results back
|
||||
// to the booking's container lines (same order) for code + weights. maxAllowed
|
||||
// is derived from the line total minus the excess the engine computed.
|
||||
const overweightLines: OverweightLine[] = [];
|
||||
const containerLines = (booking.bookingContainers ?? []).filter(
|
||||
(bc) => bc.containerTypeId != null,
|
||||
);
|
||||
for (let i = 0; i < ruleResult.containerWeightResults.length; i++) {
|
||||
const wr = ruleResult.containerWeightResults[i];
|
||||
if (!wr?.isOverweight) continue;
|
||||
const line = containerLines[i];
|
||||
const totalVgmTons = Number(line?.totalVgmTons ?? 0);
|
||||
const excessTons = Number(wr.overweightExcessTons ?? 0);
|
||||
let code = line?.containerSize ?? '';
|
||||
if (line?.containerTypeId) {
|
||||
try {
|
||||
code = (await this.containerTypesService.findById(line.containerTypeId)).code;
|
||||
} catch {
|
||||
// fall back to the container size label
|
||||
}
|
||||
}
|
||||
overweightLines.push({
|
||||
containerTypeCode: code,
|
||||
totalVgmTons,
|
||||
maxAllowedTons: Math.max(0, totalVgmTons - excessTons),
|
||||
excessTons,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
lineItems,
|
||||
totalAmount: total,
|
||||
@@ -178,6 +224,7 @@ export class BookingPricingService {
|
||||
priorityScore: ruleResult.priorityScore,
|
||||
warnings: ruleResult.warnings,
|
||||
hardBlocked: ruleResult.hardBlocked,
|
||||
overweightLines,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user