Inactive
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
index 2e2cc3e85..5a4e67d0d 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
@@ -337,14 +337,6 @@ export default function NewBookingPage() {
throw new Error("Validation failed");
}
- const totalWeight =
- data.cargoType === "container"
- ? data.containers.reduce(
- (acc, c) => acc + Number(c.vgm || 0) * Number(c.qty || 0),
- 0,
- )
- : Number(data.cargoWeight || 0);
-
const cargoTree = referenceData?.cargo_type ?? [];
const containerGroups = referenceData?.containers ?? [];
@@ -363,6 +355,22 @@ export default function NewBookingPage() {
.flatMap((g) => g.children ?? [])
.find((c) => c.id === childId);
+ // Bulk amount lives in cargoTotalWeightVgm — tons (estimated) or a whole
+ // item count, depending on the commodity's unit_of_measure. Item counts are
+ // rounded since fractional items are meaningless. Container totals are the
+ // summed VGM of all container lines.
+ const isPerItem =
+ bulkChild?.unit_of_measure === Freight.CargoUnitOfMeasure.PerItem;
+ const totalWeight =
+ data.cargoType === "container"
+ ? data.containers.reduce(
+ (acc, c) => acc + Number(c.vgm || 0) * Number(c.qty || 0),
+ 0,
+ )
+ : isPerItem
+ ? Math.round(Number(data.cargoWeight || 0))
+ : Number(data.cargoWeight || 0);
+
const cargoTypeId = data.cargoType === "bulk" ? childId : undefined;
const cargoFreeText = bulkChild?.show_free_text_box
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
index 7a21ef3d0..b31a83174 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
@@ -87,10 +87,10 @@ export function Step5CargoDetails({
return group?.children?.find((c) => c.id === childId) ?? null;
}, [referenceData, parentId, childId]);
- // Unit of measure for bulk/break-bulk cargo: PER_ITEM → "Items", else "Tons".
- // Drives the weight/quantity label so customers enter the right unit.
+ // Unit of measure for bulk/break-bulk cargo: PER_ITEM → ask for a total item
+ // count; otherwise ask for estimated tons. Drives the amount field's label,
+ // icon, and step so customers enter the right unit.
const isPerItem = selectedCommodity?.unit_of_measure === "PER_ITEM";
- const bulkUnitLabel = isPerItem ? "Items" : "Tons";
const freightTypeGroups = useMemo(() => {
if (!referenceData?.cargo_type) return [];
@@ -204,41 +204,36 @@ export function Step5CargoDetails({
/>
- {/* Weight */}
-
- (
- }
- error={fieldState.error?.message}
- // Container total is auto-summed from the containers below.
- readOnly={cargoType === "container"}
- description={
- cargoType === "container"
- ? "Auto-calculated from the containers below."
- : undefined
- }
- radius={10}
- styles={fieldStyles}
- min={0}
- step={isPerItem ? 1 : 0.01}
- />
- )}
- />
-
+ {/* Containerised cargo: total weight is auto-summed from the containers
+ below, so we show it here up-front as a read-only running total. */}
+ {cargoType === "container" && (
+
+ (
+ }
+ error={fieldState.error?.message}
+ readOnly
+ description="Auto-calculated from the containers below."
+ radius={10}
+ styles={fieldStyles}
+ min={0}
+ step={0.01}
+ />
+ )}
+ />
+
+ )}
- {/* Bulk freight type */}
+ {/* Bulk freight type — pick the commodity FIRST so we know whether the
+ cargo is measured in tons or items before asking for the amount. */}
{cargoType === "bulk" && (
{freightTypeOptions.length > 0 ? (
@@ -269,8 +264,8 @@ export function Step5CargoDetails({
)}
@@ -292,6 +287,46 @@ export function Step5CargoDetails({
)}
/>
)}
+
+ {/* Amount — only once a commodity is chosen, so the unit (tons vs
+ items) is known. PER_TON asks for estimated tons to ship;
+ PER_ITEM asks for the total item count to import/export. */}
+ {selectedCommodity && (
+ (
+
+ ) : (
+
+ )
+ }
+ error={fieldState.error?.message}
+ description={
+ isPerItem
+ ? "Total count of items you plan to import or export."
+ : "Your best estimate of the total weight to ship, in tons."
+ }
+ radius={10}
+ styles={fieldStyles}
+ min={0}
+ step={isPerItem ? 1 : 0.01}
+ />
+ )}
+ />
+ )}
)}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
index 9cae7d510..5acbc0213 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step8-review.tsx
@@ -173,6 +173,13 @@ export function Step8Review({
// Documents are reused from onboarding (read-only) and attached on submit.
const onboardingDocsCount = onboardingDocs.length;
+ const selectedCommodity = (() => {
+ if (values.cargoType !== "bulk" || !referenceData) return null;
+ const path = values.cargoTypePath ?? [];
+ const group = referenceData.cargo_type.find((g) => g.id === path[0]);
+ return group?.children?.find((c) => c.id === path[1]) ?? null;
+ })();
+
const cargoValue = (() => {
if (values.cargoType === "container") return "Container freight";
if (!referenceData) return "";
@@ -183,6 +190,18 @@ export function Step8Review({
return child ? `${group.name} — ${child.name}` : group.name;
})();
+ // Bulk PER_ITEM cargo is a whole item count, not tons — label it accordingly.
+ const isPerItem = selectedCommodity?.unit_of_measure === "PER_ITEM";
+ const totalQuantityRow = isPerItem
+ ? {
+ label: "Total quantity",
+ value: totalVgm > 0 ? `${Math.round(totalVgm)} items` : "—",
+ }
+ : {
+ label: "Total VGM",
+ value: totalVgm > 0 ? `${totalVgm.toFixed(1)} tons` : "—",
+ };
+
const originYardName =
referenceData?.yard.find((y) => y.id === values.originYard)?.name ??
values.originYard;
@@ -333,8 +352,8 @@ export function Step8Review({
>
0 ? `${totalVgm.toFixed(1)} tons` : "—"}
+ label={totalQuantityRow.label}
+ value={totalQuantityRow.value}
/>
{values.cargoType === "container" && values.containers.length > 0 && (