mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 14:38:12 +00:00
add per-container handling options for hazardous, reefer, and return services
- Introduced new boolean fields (isHazardous, isReefer, isReturn) in UnitDraft and related interfaces to allow individual container handling options. - Updated emptyUnit function to initialize these new fields. - Modified GlCreateBookingForm to handle and display these options for each container. - Adjusted calculations for hazardous, reefer, and return quantities based on the new handling options. - Updated the schema for container units and booking container lines to include handling options. - Added migration to support the new return flag in the database. - Enhanced various components to reflect gross weight calculations, ensuring consistency across the application.
This commit is contained in:
@@ -40,7 +40,10 @@ import { ContractsRepository } from './contracts.repository';
|
||||
import { ClearanceFeeService } from './clearance-fee.service';
|
||||
import { ClearanceMilestoneService } from './clearance-milestone.service';
|
||||
import { ClearanceWorkflowService } from './clearance-workflow.service';
|
||||
import { CreateBookingUnderContractDto } from './dto/create-booking-under-contract.dto';
|
||||
import {
|
||||
CreateBookingContainerLineDto,
|
||||
CreateBookingUnderContractDto,
|
||||
} from './dto/create-booking-under-contract.dto';
|
||||
|
||||
/** Statuses that still occupy the single active-booking slot of a ONE_TIME contract. */
|
||||
const TERMINAL_BOOKING_STATUSES = ['EXPIRED', 'CANCELLED', 'COMPLETED', 'REJECTED'];
|
||||
@@ -283,8 +286,8 @@ export class ContractBookingService {
|
||||
tradeDirection: contract.tradeDirection,
|
||||
freightType,
|
||||
cargoTypeId: this.resolveCargoTypeId(contract, dto),
|
||||
isHazardous: contract.isHazardous,
|
||||
isReefer: contract.isReefer,
|
||||
isHazardous: this.resolveShipmentHandlingFlag(contract, dto, 'hazardousQuantity'),
|
||||
isReefer: this.resolveShipmentHandlingFlag(contract, dto, 'reeferQuantity'),
|
||||
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
||||
firstMilePickupAddress: contract.firstMilePickupAddress ?? null,
|
||||
firstMilePickupLat: contract.firstMilePickupLat ?? null,
|
||||
@@ -1451,6 +1454,53 @@ export class ContractBookingService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-line handling counts. Each physical container carries its own hazardous
|
||||
* / reefer / return switch (entered next to its VGM), so the count is however
|
||||
* many units opted in. Forms that predate per-unit switches send line-level
|
||||
* counts and no unit flags — those are honoured as-is.
|
||||
*/
|
||||
private handlingCounts(line: CreateBookingContainerLineDto): {
|
||||
hazardousQuantity: number;
|
||||
reeferQuantity: number;
|
||||
returnQuantity: number;
|
||||
} {
|
||||
const units = line.units ?? [];
|
||||
const flagged = units.some((u) => u.isHazardous || u.isReefer || u.isReturn);
|
||||
if (!flagged) {
|
||||
return {
|
||||
hazardousQuantity: Number(line.hazardousQuantity ?? 0),
|
||||
reeferQuantity: Number(line.reeferQuantity ?? 0),
|
||||
returnQuantity: Number(line.returnQuantity ?? 0),
|
||||
};
|
||||
}
|
||||
return {
|
||||
hazardousQuantity: units.filter((u) => u.isHazardous).length,
|
||||
reeferQuantity: units.filter((u) => u.isReefer).length,
|
||||
returnQuantity: units.filter((u) => u.isReturn).length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Booking-level hazardous / reefer flags. The CONTRACT gates the service; the
|
||||
* per-container opt-ins decide whether THIS shipment actually uses it. A
|
||||
* container contract that allows hazardous but a booking where nobody ticked
|
||||
* the switch is not a hazardous booking, and must not fire the surcharge.
|
||||
* Bulk keeps the contract flag — it has its own bulk*Quantity fields.
|
||||
*/
|
||||
private resolveShipmentHandlingFlag(
|
||||
contract: Contract,
|
||||
dto: CreateBookingUnderContractDto,
|
||||
field: 'hazardousQuantity' | 'reeferQuantity',
|
||||
): boolean {
|
||||
const gated = field === 'hazardousQuantity' ? contract.isHazardous : contract.isReefer;
|
||||
if (!gated) return false;
|
||||
if (contract.freightType !== 'CONTAINER') return true;
|
||||
const lines = dto.containers ?? [];
|
||||
if (!lines.length) return Boolean(gated);
|
||||
return lines.some((l) => this.handlingCounts(l)[field] > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the booking's equipment return from the per-line return quantities
|
||||
* (container freight). The CONTRACT gates the service — like hazardous:
|
||||
@@ -1470,7 +1520,7 @@ export class ContractBookingService {
|
||||
|
||||
const lines = dto.containers ?? [];
|
||||
for (const line of lines) {
|
||||
const qty = Number(line.returnQuantity ?? 0);
|
||||
const qty = this.handlingCounts(line).returnQuantity;
|
||||
if (qty === 0) continue;
|
||||
if (contract.equipmentReturn !== 'WITH_RETURN') {
|
||||
throw new BadRequestException(
|
||||
@@ -1486,7 +1536,7 @@ export class ContractBookingService {
|
||||
}
|
||||
|
||||
if (contract.equipmentReturn === 'WITH_RETURN') {
|
||||
const anyReturn = lines.some((l) => Number(l.returnQuantity ?? 0) > 0);
|
||||
const anyReturn = lines.some((l) => this.handlingCounts(l).returnQuantity > 0);
|
||||
return anyReturn ? 'WITH_RETURN' : 'WITHOUT_RETURN';
|
||||
}
|
||||
return legacy;
|
||||
@@ -1524,9 +1574,10 @@ export class ContractBookingService {
|
||||
);
|
||||
}
|
||||
|
||||
const counts = this.handlingCounts(line);
|
||||
const containerType = await this.resolveContainerTypeForSize(
|
||||
line.containerSize,
|
||||
contract.isReefer || (line.reeferQuantity ?? 0) > 0,
|
||||
contract.isReefer || counts.reeferQuantity > 0,
|
||||
);
|
||||
|
||||
const vgmPerUnit = line.units.length
|
||||
@@ -1540,12 +1591,10 @@ export class ContractBookingService {
|
||||
containerTypeId: containerType.id,
|
||||
containerSize: line.containerSize,
|
||||
quantity: line.quantity,
|
||||
hazardousQuantity: line.hazardousQuantity ?? 0,
|
||||
reeferQuantity: line.reeferQuantity ?? 0,
|
||||
hazardousQuantity: counts.hazardousQuantity,
|
||||
reeferQuantity: counts.reeferQuantity,
|
||||
returnQuantity:
|
||||
contract.equipmentReturn === 'WITH_RETURN'
|
||||
? (line.returnQuantity ?? 0)
|
||||
: 0,
|
||||
contract.equipmentReturn === 'WITH_RETURN' ? counts.returnQuantity : 0,
|
||||
vgmPerUnitTons: vgmPerUnit,
|
||||
totalVgmTons: totalVgm,
|
||||
wagonsRequired: Math.ceil(line.quantity * wagonsPerUnitForSize(containerType.sizeFt)),
|
||||
@@ -1564,6 +1613,8 @@ export class ContractBookingService {
|
||||
vgmTons: unit.vgmTons,
|
||||
isHazardous: unit.isHazardous ?? false,
|
||||
isReefer: unit.isReefer ?? false,
|
||||
isReturn:
|
||||
contract.equipmentReturn === 'WITH_RETURN' && (unit.isReturn ?? false),
|
||||
sortOrder: sortOrder++,
|
||||
}),
|
||||
);
|
||||
@@ -1664,8 +1715,8 @@ export class ContractBookingService {
|
||||
paymentCurrency: contract.paymentCurrency,
|
||||
serviceTypeId: contract.serviceTypeId,
|
||||
cargoTypeId: this.resolveCargoTypeId(contract, dto),
|
||||
isHazardous: contract.isHazardous,
|
||||
isReefer: contract.isReefer,
|
||||
isHazardous: this.resolveShipmentHandlingFlag(contract, dto, 'hazardousQuantity'),
|
||||
isReefer: this.resolveShipmentHandlingFlag(contract, dto, 'reeferQuantity'),
|
||||
equipmentReturn: this.resolveShipmentEquipmentReturn(contract, dto),
|
||||
isGovernment: contract.isGovernment,
|
||||
shippingLineId: null,
|
||||
@@ -1680,11 +1731,11 @@ export class ContractBookingService {
|
||||
containerTypeId: ct.id,
|
||||
containerSize: line.containerSize,
|
||||
quantity: line.quantity,
|
||||
hazardousQuantity: line.hazardousQuantity ?? 0,
|
||||
reeferQuantity: line.reeferQuantity ?? 0,
|
||||
hazardousQuantity: this.handlingCounts(line).hazardousQuantity,
|
||||
reeferQuantity: this.handlingCounts(line).reeferQuantity,
|
||||
returnQuantity:
|
||||
contract.equipmentReturn === 'WITH_RETURN'
|
||||
? (line.returnQuantity ?? 0)
|
||||
? this.handlingCounts(line).returnQuantity
|
||||
: 0,
|
||||
vgmPerUnitTons: line.units.length ? totalVgmTons / line.units.length : 0,
|
||||
totalVgmTons,
|
||||
|
||||
@@ -50,6 +50,15 @@ export class CreateContainerUnitDto {
|
||||
@IsBoolean()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
isReefer?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
default: false,
|
||||
description: 'This container ships back empty (equipment return).',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
isReturn?: boolean;
|
||||
}
|
||||
|
||||
export class CreateBookingContainerLineDto {
|
||||
|
||||
Reference in New Issue
Block a user