feat(warehouses): gate double-handling fee on a per-booking Yes/No

Double handling billed every import with a matching rule. Add
bookings.double_handling (+ set_at/by), charge only when Yes, expose a
PATCH endpoint (import-only, locked after invoicing, audited) and Yes/No
items in the inventory row menu.
This commit is contained in:
Hagernesh
2026-07-25 10:44:41 +00:00
parent 43b053bf71
commit 6983645347
10 changed files with 280 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ import {
} from '@mantine/core';
import {
ArrowRightLeft,
Check,
ChevronDown,
ChevronRight,
ClipboardCheck,
@@ -29,6 +30,7 @@ import {
FileText,
History,
Info,
Layers,
MapPin,
MoreHorizontal,
PackageCheck,
@@ -2721,6 +2723,40 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
</Menu.Item>
)}
<Menu.Item onClick={() => setInspectId(r.id)}>Inspect / report</Menu.Item>
{/* Double handling is decided once the goods are off
the wagon (every row here is unloaded) — Yes is
what makes the fee rule bill this booking. */}
<Menu.Divider />
<Menu.Label>
Double handling {' '}
{r.doubleHandling == null ? 'not set' : r.doubleHandling ? 'Yes' : 'No'}
</Menu.Label>
<Menu.Item
leftSection={
r.doubleHandling === true ? <Check size={14} /> : <Layers size={14} />
}
disabled={!r.bookingId || r.doubleHandling === true}
onClick={() =>
runRowAction(r, 'Double handling: Yes — fee rule applies', () =>
warehouseService.setDoubleHandling(r.bookingId as string, true),
)
}
>
Yes apply fee
</Menu.Item>
<Menu.Item
leftSection={
r.doubleHandling === false ? <Check size={14} /> : <Layers size={14} />
}
disabled={!r.bookingId || r.doubleHandling === false}
onClick={() =>
runRowAction(r, 'Double handling: No', () =>
warehouseService.setDoubleHandling(r.bookingId as string, false),
)
}
>
No
</Menu.Item>
<Menu.Divider />
<Menu.Item leftSection={<PackageCheck size={14} />} onClick={() => setFeeItem(toInventoryItem(r))}>
Storage / fee preview

View File

@@ -334,6 +334,13 @@ export const warehouseService = {
deliver: (id: string, payload: DeliverInventoryPayload) =>
apiClient.post<WarehouseInventoryItem>(URL_CONSTANTS.WAREHOUSE_INVENTORY.DELIVER(id), payload),
/** Post-unloading Yes/No — Yes makes the double-handling fee rule bill this booking. */
setDoubleHandling: (bookingId: string, doubleHandling: boolean) =>
apiClient.patch<{ bookingId: string; doubleHandling: boolean; setAt: string }>(
`/warehouse-inventory/bookings/${bookingId}/double-handling`,
{ doubleHandling },
),
// ── Receive (Import/Export bulk) ─────────────────────────────────────────
eligibleBookings: (direction?: 'IMPORT' | 'EXPORT') =>
apiClient.get<EligibleBooking[]>(URL_CONSTANTS.WAREHOUSE_INVENTORY.ELIGIBLE_BOOKINGS(direction)),

View File

@@ -603,6 +603,8 @@ export interface ImportUnloadedItem {
customerTruckContainerNumber: string | null;
customerTruckAssignedAt: string | null;
hasAssignedTruck: boolean;
/** Post-unloading Yes/No; null = not recorded yet (no double-handling charge). */
doubleHandling: boolean | null;
currentStatus: string;
releaseDate: string | null;
releaseOrderReference: string | null;