mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
changes
This commit is contained in:
@@ -194,17 +194,49 @@ export class ContractPricingService {
|
||||
contract.freightType === 'CONTAINER' &&
|
||||
contract.equipmentReturn === 'WITH_RETURN'
|
||||
) {
|
||||
const withReturn = liveRates.find(
|
||||
(r) => r.rateType === 'RETURN_SURCHARGE' && r.currency === 'USD',
|
||||
);
|
||||
if (withReturn && Number(withReturn.rateValue) > 0) {
|
||||
lineItems.push({
|
||||
code: 'RETURN_SURCHARGE',
|
||||
label: 'Empty container return',
|
||||
unit: toContractUnit(withReturn.rateUnit),
|
||||
unitPrice: convert(Number(withReturn.rateValue)),
|
||||
conditionalOn: 'with_return',
|
||||
// Return is sold per direction + route + container type (import-only) —
|
||||
// one display line per contract size that has a configured rate. A size
|
||||
// with no rate shows nothing here and hard-blocks at booking time.
|
||||
// ponytail: bookings bill the live route rate, not a frozen snapshot.
|
||||
const route = [...(contract.routes ?? [])].sort(
|
||||
(a, b) => a.sortOrder - b.sortOrder,
|
||||
)[0];
|
||||
const onLeg = route
|
||||
? liveRates.filter(
|
||||
(r) =>
|
||||
r.rateType === 'RETURN_SURCHARGE' &&
|
||||
r.currency === 'USD' &&
|
||||
r.tradeDirection === contract.tradeDirection &&
|
||||
r.originYardId === route.originYardId &&
|
||||
r.destinationYardId === route.destinationYardId,
|
||||
)
|
||||
: [];
|
||||
if (onLeg.length > 0) {
|
||||
const sizes = (contract.cargoScope ?? [])
|
||||
.map((c) => c.containerSize)
|
||||
.filter((s): s is string => !!s);
|
||||
const { items: containerTypes } = await this.containerTypesService.findAll({
|
||||
isActive: true,
|
||||
pageSize: 100,
|
||||
});
|
||||
for (const size of sizes) {
|
||||
const sizeFt = size === '40ft' ? 40 : 20;
|
||||
const matchedIds = new Set(
|
||||
containerTypes.filter((ct) => ct.sizeFt === sizeFt).map((ct) => ct.id),
|
||||
);
|
||||
const rate =
|
||||
onLeg.find((r) => r.containerTypeId && matchedIds.has(r.containerTypeId)) ??
|
||||
onLeg.find((r) => !r.containerTypeId);
|
||||
if (!rate || Number(rate.rateValue) <= 0) continue;
|
||||
lineItems.push({
|
||||
code: 'RETURN_SURCHARGE',
|
||||
label: `Empty container return (${size})`,
|
||||
unit: toContractUnit(rate.rateUnit),
|
||||
unitPrice: convert(Number(rate.rateValue)),
|
||||
containerSize: size,
|
||||
conditionalOn: 'with_return',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,12 +245,24 @@ export class ContractPricingService {
|
||||
// ONE_TIME, per shipment request for GENERAL. Excluded from booking totals.
|
||||
// A customs contract may not proceed without a configured live rate.
|
||||
if (contract.customsClearingEnabled) {
|
||||
const clearance = liveRates.find(
|
||||
(r) => r.rateType === 'CUSTOMS_CLEARANCE' && r.currency === 'USD',
|
||||
);
|
||||
// The fee is sold per direction + route — strict, no route-less fallback.
|
||||
// ponytail: multi-route contracts bill the first lane's fee; per-lane fees need per-route snapshots.
|
||||
const route = [...(contract.routes ?? [])].sort(
|
||||
(a, b) => a.sortOrder - b.sortOrder,
|
||||
)[0];
|
||||
const clearance = route
|
||||
? liveRates.find(
|
||||
(r) =>
|
||||
r.rateType === 'CUSTOMS_CLEARANCE' &&
|
||||
r.currency === 'USD' &&
|
||||
r.tradeDirection === contract.tradeDirection &&
|
||||
r.originYardId === route.originYardId &&
|
||||
r.destinationYardId === route.destinationYardId,
|
||||
)
|
||||
: undefined;
|
||||
if (!clearance || Number(clearance.rateValue) <= 0) {
|
||||
throw new UnprocessableEntityException(
|
||||
'No customs clearance service fee is configured. Ask the rates team to set a live CUSTOMS_CLEARANCE rate before submitting customs contracts.',
|
||||
'No customs clearance service fee is configured for this direction and route. Ask the rates team to set a live CUSTOMS_CLEARANCE rate for this origin → destination.',
|
||||
);
|
||||
}
|
||||
lineItems.push({
|
||||
|
||||
Reference in New Issue
Block a user