fix(warehouses): detention groups by canonical truck type

Join truck_types via vehicles.truck_type_id (normalized legacy
vehicle_type only as fallback) so type renames can't unmatch detention
rules and FK-less vehicles keep billing.
This commit is contained in:
Hagernesh
2026-07-23 13:40:14 +00:00
parent 227a561e89
commit cf8a2e928d
41 changed files with 1495 additions and 109 deletions

View File

@@ -8,6 +8,7 @@ import {
Group,
Loader,
MultiSelect,
NumberInput,
Select,
SimpleGrid,
Stack,
@@ -79,6 +80,8 @@ export function CustomerTruckAssignmentCard({
const [driverName, setDriverName] = useState("");
const [truckType, setTruckType] = useState("");
const [containers, setContainers] = useState<string[]>([]);
const [plannedTons, setPlannedTons] = useState<number | "">("");
const [plannedQty, setPlannedQty] = useState<number | "">("");
const [editingId, setEditingId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [bulkModalOpen, setBulkModalOpen] = useState(false);
@@ -105,15 +108,23 @@ export function CustomerTruckAssignmentCard({
setDriverName("");
setTruckType("");
setContainers([]);
setPlannedTons("");
setPlannedQty("");
setEditingId(null);
setError(null);
};
const startEdit = (t: Freight.ICustomerTruck) => {
const planned = t as Freight.ICustomerTruck & {
plannedTons?: number | string | null;
plannedQuantity?: number | null;
};
setPlateNumber(t.plateNumber ?? "");
setDriverName(t.driverName ?? "");
setTruckType(t.truckType ?? "");
setContainers((t.containers ?? []).map((c) => c.containerNumber));
setPlannedTons(planned.plannedTons != null ? Number(planned.plannedTons) : "");
setPlannedQty(planned.plannedQuantity != null ? Number(planned.plannedQuantity) : "");
setEditingId(t.id);
setError(null);
};
@@ -129,6 +140,12 @@ export function CustomerTruckAssignmentCard({
driverName: driverName.trim(),
truckType: truckType.trim(),
containerNumbers: isBulk ? [] : containers,
...(isBulk
? {
plannedTons: plannedTons === "" ? undefined : Number(plannedTons),
plannedQuantity: plannedQty === "" ? undefined : Number(plannedQty),
}
: {}),
};
return editingId
? customerTrucksService.update(booking.id, editingId, payload)
@@ -170,6 +187,10 @@ export function CustomerTruckAssignmentCard({
setError("Select 1 or 2 container numbers for this truck.");
return;
}
if (isBulk && plannedTons === "") {
setError("Enter the tonnes this truck will haul.");
return;
}
setError(null);
addMutation.mutate();
};
@@ -324,6 +345,40 @@ export function CustomerTruckAssignmentCard({
nothingFoundMessage="No unassigned containers"
/>
)}
{isBulk && (
<NumberInput
label="Tonnes to load"
description={(() => {
const total = Number(booking.cargoTotalWeightVgm) || 0;
const assigned = trucks
.filter((t) => t.id !== editingId)
.reduce((s, t) => {
const x = t as Freight.ICustomerTruck & {
netWeightTons?: number | string | null;
plannedTons?: number | string | null;
};
return s + (Number(x.netWeightTons ?? x.plannedTons) || 0);
}, 0);
const remaining = Math.max(0, Math.round((total - assigned) * 1000) / 1000);
return total > 0
? `${assigned} t of ${total} t already on trucks · ${remaining} t remaining`
: "Tonnage this truck hauls";
})()}
required
min={0}
value={plannedTons}
onChange={(v) => setPlannedTons(v === "" ? "" : Number(v))}
/>
)}
{isBulk && (
<NumberInput
label="Items quantity (pcs)"
description="Optional piece count on this truck"
min={0}
value={plannedQty}
onChange={(v) => setPlannedQty(v === "" ? "" : Number(v))}
/>
)}
</SimpleGrid>
<Group justify="flex-end">
{editingId && (