Truck detention on lastmile

This commit is contained in:
Hagernesh
2026-07-07 08:45:51 +00:00
parent 24fa6ab83b
commit 392038852e
15 changed files with 543 additions and 11 deletions

View File

@@ -58,6 +58,7 @@ import { vehiclesService } from "@/services/vehicles.service";
import { driversService, type Driver } from "@/services/drivers.service";
import { ratesService } from "@/services/rates.service";
import { ReleaseOrderModal, type ReleaseOrderTruckPrefill } from "@/components/warehouses/ReleaseOrderModal";
import { TruckDetentionModal } from "@/components/operations/TruckDetentionModal";
import { LastMileStepper, type LastMileStepState } from "@/components/operations/LastMileSteps";
const formatPrice = (amount: number | string | null | undefined, currency = "ETB") =>
@@ -544,6 +545,7 @@ const LastMilePage = () => {
const [tripSlipVehicleId, setTripSlipVehicleId] = useState<string | null>(null);
const [tripSlipSelectOpen, setTripSlipSelectOpen] = useState(false);
const [activeId, setActiveId] = useState<string | null>(null);
const [detentionRecord, setDetentionRecord] = useState<LastMileRecord | null>(null);
// Multi-vehicle assign: one row per truck — vehicle + the container it carries.
const [vehicleRows, setVehicleRows] = useState<
Array<{ vehicleId: string | null; containerNumber: string }>
@@ -1360,6 +1362,16 @@ const LastMilePage = () => {
>
{row.original.invoice ? "Invoice generated" : "Generate Invoice"}
</Menu.Item>
{/* Truck detention: set/adjust arrival & return times, preview the
per-truck-per-day charge, and generate its invoice. Available
once the vehicle is en route/delivered (clock has a start). */}
<Menu.Item
leftSection={<Receipt size={15} />}
disabled={!pastTransit}
onClick={() => setDetentionRecord(row.original)}
>
Truck detention
</Menu.Item>
{canPrint && (
<Menu.Item
leftSection={<Printer size={15} />}
@@ -2019,6 +2031,12 @@ const LastMilePage = () => {
item={releaseItem}
truckPrefill={releaseTruckPrefill}
/>
<TruckDetentionModal
opened={Boolean(detentionRecord)}
onClose={() => setDetentionRecord(null)}
record={detentionRecord}
/>
</Stack>
);
};

View File

@@ -373,6 +373,7 @@ function FeeRules() {
cargoTypeCode: '',
containerType: '',
freeDays: 3,
freeHours: 3,
ratePerDay: 0,
tiers: [] as Array<{ fromDay: number; toDay: number | null; ratePerDay: number }>,
currency: 'USD',
@@ -385,6 +386,9 @@ function FeeRules() {
// Double handling is a flat per-unit charge (basis × rate), not day-based:
// no free days, no progressive tiers.
const isDoubleHandling = form.ruleType === 'DOUBLE_HANDLING_FEE';
// Truck detention: per truck per day after an HOURS-based grace (default 3h),
// with day tiers. Uses "free hours" instead of "free days".
const isTruckDetention = form.ruleType === 'TRUCK_DETENTION_FEE';
const resetForm = () =>
setForm({
@@ -396,6 +400,7 @@ function FeeRules() {
cargoTypeCode: '',
containerType: '',
freeDays: 3,
freeHours: 3,
ratePerDay: 0,
tiers: [],
currency: 'USD',
@@ -459,11 +464,12 @@ function FeeRules() {
tradeDirection: clean(form.tradeDirection) ?? null,
cargoTypeCode: isBulkRule ? (clean(form.cargoTypeCode) ?? null) : null,
containerType: isContainerRule ? (clean(form.containerType) ?? null) : null,
// Double handling: flat basis × rate — no free days, no tiers.
freeDays: isDoubleHandling ? 0 : form.freeDays,
// Double handling: flat basis × rate. Truck detention: HOURS-based grace.
freeDays: isDoubleHandling || isTruckDetention ? 0 : form.freeDays,
ratePerDay: form.ratePerDay,
currency: form.currency || 'USD',
...(isDoubleHandling ? { basis: form.basis } : {}),
...(isTruckDetention ? { freeHours: form.freeHours } : {}),
...(!isDoubleHandling && tiers.length ? { tiers } : {}),
};
@@ -668,6 +674,14 @@ function FeeRules() {
}
allowDeselect={false}
/>
) : isTruckDetention ? (
<NumberInput
label="Free hours"
description="Grace before detention accrues"
min={0}
value={form.freeHours}
onChange={(value) => setForm((f) => ({ ...f, freeHours: numberValue(value) }))}
/>
) : (
<NumberInput
label="Free days"