mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
Merge pull request #1068 from Tria-plc/freight_feature/usermanagement
Add a minimal payment mock server for end-to-end testing
This commit is contained in:
@@ -290,6 +290,7 @@ export class ContractBookingService {
|
|||||||
isHazardous: this.resolveShipmentHandlingFlag(contract, dto, 'hazardousQuantity'),
|
isHazardous: this.resolveShipmentHandlingFlag(contract, dto, 'hazardousQuantity'),
|
||||||
isReefer: this.resolveShipmentHandlingFlag(contract, dto, 'reeferQuantity'),
|
isReefer: this.resolveShipmentHandlingFlag(contract, dto, 'reeferQuantity'),
|
||||||
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
||||||
|
bulkTotalWeightTons: this.resolveBulkWeightTons(dto),
|
||||||
firstMilePickupAddress: contract.firstMilePickupAddress ?? null,
|
firstMilePickupAddress: contract.firstMilePickupAddress ?? null,
|
||||||
firstMilePickupLat: contract.firstMilePickupLat ?? null,
|
firstMilePickupLat: contract.firstMilePickupLat ?? null,
|
||||||
firstMilePickupLng: contract.firstMilePickupLng ?? null,
|
firstMilePickupLng: contract.firstMilePickupLng ?? null,
|
||||||
@@ -773,6 +774,7 @@ export class ContractBookingService {
|
|||||||
cargoTypeId: this.resolveCargoTypeId(contract, dto),
|
cargoTypeId: this.resolveCargoTypeId(contract, dto),
|
||||||
cargoFreeText: dto.cargoFreeText?.trim() || null,
|
cargoFreeText: dto.cargoFreeText?.trim() || null,
|
||||||
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
||||||
|
bulkTotalWeightTons: this.resolveBulkWeightTons(dto),
|
||||||
equipmentReturn: this.resolveShipmentEquipmentReturn(contract, dto),
|
equipmentReturn: this.resolveShipmentEquipmentReturn(contract, dto),
|
||||||
// Completion is where the cargo — and therefore the price — is fixed, so
|
// Completion is where the cargo — and therefore the price — is fixed, so
|
||||||
// it is also where the billing currency is chosen. A bare instance was
|
// it is also where the billing currency is chosen. A bare instance was
|
||||||
@@ -1253,6 +1255,7 @@ export class ContractBookingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
probe.cargoTotalWeightVgm = this.resolveBulkTons(dto);
|
probe.cargoTotalWeightVgm = this.resolveBulkTons(dto);
|
||||||
|
probe.bulkTotalWeightTons = this.resolveBulkWeightTons(dto);
|
||||||
const cargoTypeId = this.resolveCargoTypeId(contract, dto);
|
const cargoTypeId = this.resolveCargoTypeId(contract, dto);
|
||||||
probe.cargoTypeId = cargoTypeId;
|
probe.cargoTypeId = cargoTypeId;
|
||||||
if (cargoTypeId) {
|
if (cargoTypeId) {
|
||||||
@@ -1297,11 +1300,7 @@ export class ContractBookingService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requested =
|
const requested = this.resolveBulkTons(dto);
|
||||||
(dto.bulkLines ?? []).reduce(
|
|
||||||
(sum, b) => sum + (b.cargoWeightTons ?? b.itemCount ?? 0),
|
|
||||||
0,
|
|
||||||
) || this.resolveBulkTons(dto) || 0;
|
|
||||||
const remaining = outstanding.bulk?.outstanding ?? 0;
|
const remaining = outstanding.bulk?.outstanding ?? 0;
|
||||||
// 0.001 t tolerance absorbs the 3-decimal rounding applied to split weights.
|
// 0.001 t tolerance absorbs the 3-decimal rounding applied to split weights.
|
||||||
if (Math.abs(requested - remaining) > 0.001) {
|
if (Math.abs(requested - remaining) > 0.001) {
|
||||||
@@ -1344,8 +1343,10 @@ export class ContractBookingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// PER_ITEM contracts are capped in items, so the item count is the
|
||||||
|
// consumption figure — tonnage is only wagon-sizing data.
|
||||||
const requested =
|
const requested =
|
||||||
(lines.bulk?.cargoWeightTons ?? lines.bulk?.itemCount ?? 0) || 0;
|
Number(lines.bulk?.itemCount ?? lines.bulk?.cargoWeightTons ?? 0) || 0;
|
||||||
const cap = capacity.find((c) => c.cap != null);
|
const cap = capacity.find((c) => c.cap != null);
|
||||||
if (cap && cap.remaining != null && requested > cap.remaining) {
|
if (cap && cap.remaining != null && requested > cap.remaining) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -1373,11 +1374,7 @@ export class ContractBookingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const requested =
|
const requested = this.resolveBulkTons(dto);
|
||||||
(dto.bulkLines ?? []).reduce(
|
|
||||||
(sum, b) => sum + (b.cargoWeightTons ?? b.itemCount ?? 0),
|
|
||||||
0,
|
|
||||||
) || this.resolveBulkTons(dto) || 0;
|
|
||||||
const cap = capacity.find((c) => c.cap != null);
|
const cap = capacity.find((c) => c.cap != null);
|
||||||
if (cap && cap.remaining != null && requested > cap.remaining) {
|
if (cap && cap.remaining != null && requested > cap.remaining) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -1641,11 +1638,27 @@ export class ContractBookingService {
|
|||||||
private resolveBulkTons(dto: CreateBookingUnderContractDto): number {
|
private resolveBulkTons(dto: CreateBookingUnderContractDto): number {
|
||||||
if (!dto.bulkLines?.length) return 0;
|
if (!dto.bulkLines?.length) return 0;
|
||||||
return dto.bulkLines.reduce(
|
return dto.bulkLines.reduce(
|
||||||
(sum, l) => sum + Number(l.cargoWeightTons ?? l.itemCount ?? 0),
|
(sum, l) => sum + Number(l.itemCount ?? l.cargoWeightTons ?? 0),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Real tonnage of a PER_ITEM (break-bulk) booking, kept alongside the item
|
||||||
|
* count `cargoTotalWeightVgm` holds. Both are needed: the item count prices
|
||||||
|
* the booking, the tonnage sizes the wagons (`bulkItemWagonsRequired` derives
|
||||||
|
* per-item weight from tonnage ÷ items). Null for PER_TON bulk, where
|
||||||
|
* `cargoTotalWeightVgm` already IS the tonnage.
|
||||||
|
*/
|
||||||
|
private resolveBulkWeightTons(
|
||||||
|
dto: CreateBookingUnderContractDto,
|
||||||
|
): number | null {
|
||||||
|
const lines = dto.bulkLines ?? [];
|
||||||
|
if (!lines.some((l) => Number(l.itemCount) > 0)) return null;
|
||||||
|
const tons = lines.reduce((sum, l) => sum + Number(l.cargoWeightTons ?? 0), 0);
|
||||||
|
return tons > 0 ? tons : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-line handling counts. Each physical container carries its own hazardous
|
* Per-line handling counts. Each physical container carries its own hazardous
|
||||||
* / reefer / return switch (entered next to its VGM), so the count is however
|
* / reefer / return switch (entered next to its VGM), so the count is however
|
||||||
@@ -1961,6 +1974,7 @@ export class ContractBookingService {
|
|||||||
originYardId: route?.originYardId ?? null,
|
originYardId: route?.originYardId ?? null,
|
||||||
destinationYardId: route?.destinationYardId ?? null,
|
destinationYardId: route?.destinationYardId ?? null,
|
||||||
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
cargoTotalWeightVgm: this.resolveBulkTons(dto),
|
||||||
|
bulkTotalWeightTons: this.resolveBulkWeightTons(dto),
|
||||||
firstMilePickupAddress: contract.firstMilePickupAddress ?? null,
|
firstMilePickupAddress: contract.firstMilePickupAddress ?? null,
|
||||||
lastMileDeliveryAddress: contract.lastMileDeliveryAddress ?? null,
|
lastMileDeliveryAddress: contract.lastMileDeliveryAddress ?? null,
|
||||||
bookingContainers: resolved.map(({ line, ct, totalVgmTons }) =>
|
bookingContainers: resolved.map(({ line, ct, totalVgmTons }) =>
|
||||||
|
|||||||
@@ -182,18 +182,18 @@ const FreightDashboardHeader = ({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={<FileSignature size={15} />}
|
||||||
|
onClick={() => navigate("/dashboard/profile#signature")}
|
||||||
|
>
|
||||||
|
Signature & Stamp
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<User size={15} />}
|
leftSection={<User size={15} />}
|
||||||
onClick={() => navigate("/dashboard/profile")}
|
onClick={() => navigate("/dashboard/profile")}
|
||||||
>
|
>
|
||||||
Profile
|
Profile
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
|
||||||
leftSection={<FileSignature size={15} />}
|
|
||||||
onClick={() => navigate("/dashboard/profile#signature")}
|
|
||||||
>
|
|
||||||
My signature
|
|
||||||
</Menu.Item>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<LogOut size={15} />}
|
leftSection={<LogOut size={15} />}
|
||||||
|
|||||||
@@ -548,18 +548,18 @@ export function AppLayout({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={<FileSignature size={15} />}
|
||||||
|
onClick={() => navigate("/signature")}
|
||||||
|
>
|
||||||
|
Signature & Stamp
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<User size={15} />}
|
leftSection={<User size={15} />}
|
||||||
onClick={() => navigate("/profile")}
|
onClick={() => navigate("/profile")}
|
||||||
>
|
>
|
||||||
Profile
|
Profile
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
|
||||||
leftSection={<FileSignature size={15} />}
|
|
||||||
onClick={() => navigate("/signature")}
|
|
||||||
>
|
|
||||||
My signature
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<Settings size={15} />}
|
leftSection={<Settings size={15} />}
|
||||||
onClick={() => navigate("/settings")}
|
onClick={() => navigate("/settings")}
|
||||||
|
|||||||
@@ -992,8 +992,9 @@ function ScheduleStep({
|
|||||||
?.cargoTypeCode ?? undefined,
|
?.cargoTypeCode ?? undefined,
|
||||||
totalWeightTons: tons,
|
totalWeightTons: tons,
|
||||||
};
|
};
|
||||||
// itemCount is referenced so the query refreshes when a PER_ITEM cargo
|
// Tonnage is the sizing input the day-feasibility endpoint takes, and
|
||||||
// amount changes (weight is the sizing input the backend uses).
|
// PER_ITEM cargo now captures it too — itemCount stays in the deps so the
|
||||||
|
// query still refreshes when only the item count changes.
|
||||||
}, [
|
}, [
|
||||||
route,
|
route,
|
||||||
isContainer,
|
isContainer,
|
||||||
@@ -1162,6 +1163,9 @@ function CargoStep({
|
|||||||
contract: Freight.IContract;
|
contract: Freight.IContract;
|
||||||
}) {
|
}) {
|
||||||
const isContainer = contract.freightType === "CONTAINER";
|
const isContainer = contract.freightType === "CONTAINER";
|
||||||
|
// Break-bulk (PER_ITEM) cargo needs BOTH the item count (which prices it) and
|
||||||
|
// the total tonnage (which sizes the wagons); PER_TON needs tonnage only.
|
||||||
|
const isPerItem = bulkUnitOfMeasure(contract) === "PER_ITEM";
|
||||||
// Sizes enabled by the contract scope.
|
// Sizes enabled by the contract scope.
|
||||||
const sizes = useMemo(
|
const sizes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -1193,8 +1197,7 @@ function CargoStep({
|
|||||||
// still needs its container number.
|
// still needs its container number.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!remainderMode || isContainer) return;
|
if (!remainderMode || isContainer) return;
|
||||||
const field =
|
const field = isPerItem ? "itemCount" : "cargoWeightTons";
|
||||||
bulkUnitOfMeasure(contract) === "PER_ITEM" ? "itemCount" : "cargoWeightTons";
|
|
||||||
if (!form.getValues(field)) {
|
if (!form.getValues(field)) {
|
||||||
form.setValue(field, String(remainderLines[0].remaining), {
|
form.setValue(field, String(remainderLines[0].remaining), {
|
||||||
shouldValidate: false,
|
shouldValidate: false,
|
||||||
@@ -1478,6 +1481,27 @@ function CargoStep({
|
|||||||
<Stack gap={14}>
|
<Stack gap={14}>
|
||||||
{remainderNotice}
|
{remainderNotice}
|
||||||
<ContractCapacityNotice contractId={contract.id} isContainer={false} />
|
<ContractCapacityNotice contractId={contract.id} isContainer={false} />
|
||||||
|
{isPerItem && (
|
||||||
|
<Controller
|
||||||
|
name="itemCount"
|
||||||
|
control={form.control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<TextInput
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onKeyDown={blockNegative}
|
||||||
|
label="Number of items *"
|
||||||
|
description="This contract is priced per item — the item count sets the price."
|
||||||
|
placeholder="e.g. 500"
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
error={fieldState.error?.message}
|
||||||
|
radius={10}
|
||||||
|
styles={fieldStyles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Controller
|
<Controller
|
||||||
name="cargoWeightTons"
|
name="cargoWeightTons"
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -1486,7 +1510,12 @@ function CargoStep({
|
|||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
onKeyDown={blockNegative}
|
onKeyDown={blockNegative}
|
||||||
label="Quantity (tons)"
|
label="Total weight (tons) *"
|
||||||
|
description={
|
||||||
|
isPerItem
|
||||||
|
? "Combined weight of all the items — used to work out how many wagons the shipment needs."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
placeholder="e.g. 1200"
|
placeholder="e.g. 1200"
|
||||||
min={0}
|
min={0}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
@@ -1496,24 +1525,6 @@ function CargoStep({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
|
||||||
name="itemCount"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field, fieldState }) => (
|
|
||||||
<TextInput
|
|
||||||
{...field}
|
|
||||||
type="number"
|
|
||||||
onKeyDown={blockNegative}
|
|
||||||
label="Item count (if applicable)"
|
|
||||||
placeholder="e.g. 500"
|
|
||||||
min={0}
|
|
||||||
step={1}
|
|
||||||
error={fieldState.error?.message}
|
|
||||||
radius={10}
|
|
||||||
styles={fieldStyles}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{contract.isHazardous && (
|
{contract.isHazardous && (
|
||||||
<Controller
|
<Controller
|
||||||
name="bulkHazardousQuantity"
|
name="bulkHazardousQuantity"
|
||||||
|
|||||||
@@ -221,6 +221,20 @@ export function createShipmentFormSchema(ctx: ShipmentValidationContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PER_ITEM cargo needs BOTH figures: the item count prices the booking,
|
||||||
|
// the total weight sizes the wagons (per-item weight = tons ÷ items).
|
||||||
|
// PER_TON needs only the tonnage, which `bulkCap` already covers.
|
||||||
|
if (isPerItem) {
|
||||||
|
const tons = Number(data.cargoWeightTons || 0);
|
||||||
|
if (Number.isNaN(tons) || tons <= 0) {
|
||||||
|
refineCtx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
path: ["cargoWeightTons"],
|
||||||
|
message: "Enter the total weight in tons.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const boundBulkPortion = (
|
const boundBulkPortion = (
|
||||||
on: boolean,
|
on: boolean,
|
||||||
raw: string,
|
raw: string,
|
||||||
|
|||||||
@@ -148,3 +148,62 @@ describe("computeShipmentTotal — with_return surcharge", () => {
|
|||||||
expect(t.total).toBe(200 + 60);
|
expect(t.total).toBe(200 + 60);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// PER_ITEM (break-bulk) cargo captures BOTH an item count and a total tonnage:
|
||||||
|
// the item count prices the booking, the tonnage sizes the wagons. Before this,
|
||||||
|
// the estimate took `cargoWeightTons || itemCount` and so billed a per_item rate
|
||||||
|
// against the tonnage as soon as both fields were filled.
|
||||||
|
describe("computeShipmentTotal — PER_ITEM bulk", () => {
|
||||||
|
const perItemContract = contract({
|
||||||
|
freightType: "BULK",
|
||||||
|
isHazardous: false,
|
||||||
|
isReefer: false,
|
||||||
|
equipmentReturn: "NO_RETURN",
|
||||||
|
pricingBreakdown: {
|
||||||
|
currency: "ETB",
|
||||||
|
lineItems: [
|
||||||
|
{ label: "Break-bulk freight", unit: "per_item", unitPrice: 50 },
|
||||||
|
{
|
||||||
|
label: "Customs clearance",
|
||||||
|
unit: "per_item",
|
||||||
|
unitPrice: 5,
|
||||||
|
isClearance: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as unknown as Partial<Freight.IContract>);
|
||||||
|
|
||||||
|
it("bills a per_item rate on the item count, not the tonnage", () => {
|
||||||
|
const t = computeShipmentTotal(
|
||||||
|
perItemContract,
|
||||||
|
values({ containers: [], itemCount: "400", cargoWeightTons: "800" }),
|
||||||
|
);
|
||||||
|
expect(line(t, "Break-bulk freight")).toMatchObject({
|
||||||
|
quantity: 400,
|
||||||
|
amount: 20000,
|
||||||
|
});
|
||||||
|
expect(line(t, "Customs clearance")).toMatchObject({
|
||||||
|
quantity: 400,
|
||||||
|
amount: 2000,
|
||||||
|
});
|
||||||
|
expect(t.total).toBe(22000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("still bills a per_ton rate on the tonnage", () => {
|
||||||
|
const perTon = contract({
|
||||||
|
freightType: "BULK",
|
||||||
|
isHazardous: false,
|
||||||
|
isReefer: false,
|
||||||
|
equipmentReturn: "NO_RETURN",
|
||||||
|
pricingBreakdown: {
|
||||||
|
currency: "ETB",
|
||||||
|
lineItems: [{ label: "Bulk freight", unit: "per_ton", unitPrice: 10 }],
|
||||||
|
},
|
||||||
|
} as unknown as Partial<Freight.IContract>);
|
||||||
|
const t = computeShipmentTotal(
|
||||||
|
perTon,
|
||||||
|
values({ containers: [], cargoWeightTons: "800", itemCount: "" }),
|
||||||
|
);
|
||||||
|
expect(line(t, "Bulk freight")).toMatchObject({ quantity: 800, amount: 8000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,6 +15,18 @@ export interface ShipmentTotal {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quantity a bulk rate bills, in ITS OWN unit. PER_ITEM cargo carries both
|
||||||
|
* figures — the item count prices the booking, the tonnage sizes the wagons —
|
||||||
|
* so a per_item rate must bill items even though tonnage is also filled in.
|
||||||
|
* PER_TON cargo has no item count and falls back the other way for legacy rows.
|
||||||
|
*/
|
||||||
|
function bulkQtyForUnit(values: ShipmentFormValues, unit: string): number {
|
||||||
|
return unit === "per_item"
|
||||||
|
? Number(values.itemCount || 0)
|
||||||
|
: Number(values.cargoWeightTons || values.itemCount || 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the booking total client-side from the contract's frozen unit rates ×
|
* Compute the booking total client-side from the contract's frozen unit rates ×
|
||||||
* the quantities the customer enters (doc §9.2). This is an estimate shown in
|
* the quantities the customer enters (doc §9.2). This is an estimate shown in
|
||||||
@@ -115,7 +127,6 @@ export function computeShipmentTotal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const qty = Number(values.cargoWeightTons || values.itemCount || 0);
|
|
||||||
const rate =
|
const rate =
|
||||||
rateFor(
|
rateFor(
|
||||||
(i) =>
|
(i) =>
|
||||||
@@ -123,6 +134,7 @@ export function computeShipmentTotal(
|
|||||||
!i.isClearance &&
|
!i.isClearance &&
|
||||||
!i.conditionalOn,
|
!i.conditionalOn,
|
||||||
) ?? items[0];
|
) ?? items[0];
|
||||||
|
const qty = rate ? bulkQtyForUnit(values, rate.unit) : 0;
|
||||||
if (rate && qty > 0) {
|
if (rate && qty > 0) {
|
||||||
lines.push({
|
lines.push({
|
||||||
label: rate.label,
|
label: rate.label,
|
||||||
@@ -166,14 +178,14 @@ export function computeShipmentTotal(
|
|||||||
// real pricing.
|
// real pricing.
|
||||||
const lashing = items.find((i) => i.conditionalOn === "has_lashing");
|
const lashing = items.find((i) => i.conditionalOn === "has_lashing");
|
||||||
if (lashing && (lashing.unit === "per_ton" || lashing.unit === "per_item")) {
|
if (lashing && (lashing.unit === "per_ton" || lashing.unit === "per_item")) {
|
||||||
const tons = Number(values.cargoWeightTons || values.itemCount || 0);
|
const qty = bulkQtyForUnit(values, lashing.unit);
|
||||||
if (tons > 0) {
|
if (qty > 0) {
|
||||||
lines.push({
|
lines.push({
|
||||||
label: lashing.label,
|
label: lashing.label,
|
||||||
unitPrice: lashing.unitPrice,
|
unitPrice: lashing.unitPrice,
|
||||||
unit: lashing.unit,
|
unit: lashing.unit,
|
||||||
quantity: tons,
|
quantity: qty,
|
||||||
amount: lashing.unitPrice * tons,
|
amount: lashing.unitPrice * qty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +205,7 @@ export function computeShipmentTotal(
|
|||||||
? Math.ceil(boxes * (cl.containerSize === "40ft" ? 1 : 0.5))
|
? Math.ceil(boxes * (cl.containerSize === "40ft" ? 1 : 0.5))
|
||||||
: boxes;
|
: boxes;
|
||||||
} else if (cl.unit === "per_ton" || cl.unit === "per_item") {
|
} else if (cl.unit === "per_ton" || cl.unit === "per_item") {
|
||||||
qty = Number(values.cargoWeightTons || values.itemCount || 0);
|
qty = bulkQtyForUnit(values, cl.unit);
|
||||||
} else if (cl.unit === "flat") {
|
} else if (cl.unit === "flat") {
|
||||||
qty = 1;
|
qty = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user