mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-05 03:43:39 +00:00
Implement intercity document handling and rejection notes for contracts
This commit is contained in:
@@ -137,7 +137,7 @@ export class BookingPricingService {
|
||||
const lineItems: PriceLineItemDto[] = [];
|
||||
let total = 0;
|
||||
|
||||
const { lineItems: baseLines, usedRates: baseRates } =
|
||||
const { lineItems: baseLines, usedRates: baseRates, warnings: baseWarnings } =
|
||||
await this.computeBaseRailLinesWithRates(booking, evalInput, frozenRates);
|
||||
for (const line of baseLines) {
|
||||
lineItems.push(line);
|
||||
@@ -247,7 +247,7 @@ export class BookingPricingService {
|
||||
usedRates: [...usedRatesMap.values()],
|
||||
appliedModifiers: ruleResult.appliedModifiers,
|
||||
priorityScore: ruleResult.priorityScore,
|
||||
warnings: ruleResult.warnings,
|
||||
warnings: [...ruleResult.warnings, ...baseWarnings],
|
||||
hardBlocked: ruleResult.hardBlocked,
|
||||
overweightLines,
|
||||
};
|
||||
@@ -454,7 +454,7 @@ export class BookingPricingService {
|
||||
booking: Booking,
|
||||
evalInput: BookingEvaluationInput,
|
||||
frozenRates: Map<string, ContractRateSnapshot> | null = null,
|
||||
): Promise<{ lineItems: PriceLineItemDto[]; usedRates: Rate[] }> {
|
||||
): Promise<{ lineItems: PriceLineItemDto[]; usedRates: Rate[]; warnings: string[] }> {
|
||||
const liveRates = await this.ratesService.findLiveRates();
|
||||
const paymentCurrency = booking.paymentCurrency;
|
||||
const isEtbBooking = paymentCurrency === 'ETB';
|
||||
@@ -476,6 +476,7 @@ export class BookingPricingService {
|
||||
|
||||
const lines: PriceLineItemDto[] = [];
|
||||
const usedRatesMap = new Map<string, Rate>();
|
||||
const warnings: string[] = [];
|
||||
const wagonCount = await this.resolveWagonCount(booking);
|
||||
|
||||
for (const container of evalInput.containers) {
|
||||
@@ -487,47 +488,63 @@ export class BookingPricingService {
|
||||
booking.originYardId,
|
||||
booking.destinationYardId,
|
||||
);
|
||||
if (!rate) continue;
|
||||
|
||||
usedRatesMap.set(rate.id, rate);
|
||||
const unitUsd = Number(rate.rateValue);
|
||||
// H15: frozen contract rate for this container size, when present — its
|
||||
// unitPrice is already in the booking currency (no USD→currency convert).
|
||||
// It also stands on its own: a contract line prices off the agreed rate
|
||||
// even when nobody configured a live rate for this leg + type yet.
|
||||
const frozen = await this.frozenRateForContainer(
|
||||
frozenRates,
|
||||
container.containerTypeId,
|
||||
paymentCurrency,
|
||||
);
|
||||
const label = await this.containerTypeLabel(container.containerTypeId);
|
||||
if (!rate && !frozen) {
|
||||
// Never price this line off another container type's (or another
|
||||
// route's) rate — an unpriced line with a warning is recoverable; a
|
||||
// silently mischarged one is not.
|
||||
warnings.push(
|
||||
`No ${rateType} rate is configured for ${label} on this route — ` +
|
||||
'the line was not priced.',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const rateUnit = rate?.rateUnit ?? 'PER_CONTAINER';
|
||||
let amount: number;
|
||||
let unitAmount: number;
|
||||
if (frozen) {
|
||||
unitAmount = Number(frozen.unitPrice);
|
||||
amount = this.amountForUnit(
|
||||
rate.rateUnit,
|
||||
rateUnit,
|
||||
unitAmount,
|
||||
container.quantity,
|
||||
wagonCount,
|
||||
);
|
||||
} else {
|
||||
const usdAmount = this.amountForRate(rate, container.quantity, wagonCount);
|
||||
const unitUsd = Number(rate!.rateValue);
|
||||
const usdAmount = this.amountForRate(rate!, container.quantity, wagonCount);
|
||||
amount = isEtbBooking ? Math.round(usdAmount * usdToEtb) : usdAmount;
|
||||
unitAmount = isEtbBooking ? Math.round(unitUsd * usdToEtb) : unitUsd;
|
||||
}
|
||||
const label = await this.containerTypeLabel(container.containerTypeId);
|
||||
if (rate) usedRatesMap.set(rate.id, rate);
|
||||
lines.push({
|
||||
code: rateType,
|
||||
description: `${label} rail freight`,
|
||||
amount,
|
||||
unitAmount,
|
||||
unit: rate.rateUnit,
|
||||
quantity: this.effectiveUnitQuantity(rate.rateUnit, container.quantity, wagonCount),
|
||||
unit: rateUnit,
|
||||
quantity: this.effectiveUnitQuantity(rateUnit, container.quantity, wagonCount),
|
||||
currency: paymentCurrency,
|
||||
});
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
if (lines.length === 0 && evalInput.containers.length === 0) {
|
||||
// Bulk (and any booking with no container lines) still has to price off a
|
||||
// rate configured for this leg — never one belonging to another route.
|
||||
// Container bookings never reach this fallback: their lines price per
|
||||
// container type above or stay unpriced with a warning — falling back to
|
||||
// a corridor rate of a DIFFERENT container type billed once (qty 1) is
|
||||
// how a 38-container booking was invoiced 40 USD instead of 1900.
|
||||
const fallback = liveRates.find(
|
||||
(r) =>
|
||||
r.rateType === rateType &&
|
||||
@@ -573,7 +590,7 @@ export class BookingPricingService {
|
||||
}
|
||||
}
|
||||
|
||||
return { lineItems: lines, usedRates: [...usedRatesMap.values()] };
|
||||
return { lineItems: lines, usedRates: [...usedRatesMap.values()], warnings };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user