mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 22:18:12 +00:00
Multi-truck self-haul had no list anywhere on the warehouse side. The ops dashboard counted trucks on site and offered no way to open the list, and the inventory table showed a blank plate on exactly the bookings that have several trucks: it read booking.customer_truck_plate_number, which multi-truck self-haul leaves null because plates live in customer_truck_assignments. Booking BK-2026-000033 has a truck and a driver on file and displayed neither. Adds a Trucks on Site page listing every truck that has arrived and not yet departed, across bookings, with plate, driver, booking, customer, containers and dwell time. It covers both haulage paths because the gate does — a customer's own truck and an EDR last-mile truck reach the same barrier — and flags anything sitting over four hours. It lives under Warehouse Management rather than Imports or Exports, since the yard is not per-direction. The inventory queries now read plates and drivers from the assignments and keep the booking columns as the fallback for single-truck bookings written before that table existed. Both new statements were EXPLAIN-validated against the live schema; the plate fix returns the data that was previously null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
667 lines
24 KiB
TypeScript
667 lines
24 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
|
import { warehouseService } from '@/services/warehouse.service';
|
|
import type {
|
|
InspectionReportPayload,
|
|
SaveAllocationRulePayload,
|
|
SaveFeeRulePayload,
|
|
WarehouseInvoiceFilter,
|
|
PayInvoicePayload,
|
|
InventoryFilter,
|
|
InventoryInquiryFilter,
|
|
LoadInventoryPayload,
|
|
MoveInventoryPayload,
|
|
ReceiveInventoryPayload,
|
|
ReleaseOrderPayload,
|
|
DeliverInventoryPayload,
|
|
BulkReceivePayload,
|
|
BulkInspectPayload,
|
|
ReserveInventoryPayload,
|
|
SaveWarehousePayload,
|
|
SaveYardPayload,
|
|
SaveZonePayload,
|
|
WarehouseFilter,
|
|
} from '@/types/warehouse';
|
|
|
|
export const warehouseKeys = {
|
|
all: ['warehouses'] as const,
|
|
list: (filter?: WarehouseFilter) => ['warehouses', 'list', filter ?? {}] as const,
|
|
facilities: () => ['warehouses', 'facilities'] as const,
|
|
detail: (id: string) => ['warehouses', 'detail', id] as const,
|
|
yards: (warehouseId: string) => ['warehouses', warehouseId, 'yards'] as const,
|
|
allYards: () => ['warehouse-yards', 'all'] as const,
|
|
zones: (yardId: string) => ['warehouse-yards', yardId, 'zones'] as const,
|
|
allZones: () => ['warehouse-zones', 'all'] as const,
|
|
inventory: (filter?: InventoryFilter) => ['warehouse-inventory', 'list', filter ?? {}] as const,
|
|
dashboardSummary: (filter?: InventoryFilter) => ['warehouse-dashboard', 'summary', filter ?? {}] as const,
|
|
inquiry: (filter: InventoryInquiryFilter) => ['warehouse-inventory', 'inquiry', filter] as const,
|
|
};
|
|
|
|
// ── Warehouses ─────────────────────────────────────────────────────────────
|
|
|
|
export function useWarehouses(filter?: WarehouseFilter) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.list(filter),
|
|
queryFn: () => warehouseService.list(filter).then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useWarehouse(id?: string) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.detail(id ?? ''),
|
|
queryFn: () => warehouseService.getById(id as string).then((r) => r.data),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
export function useWarehouseFacilities() {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.facilities(),
|
|
queryFn: () => warehouseService.listFacilities().then((r) => r.data.items),
|
|
});
|
|
}
|
|
|
|
export function useCreateWarehouse() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (payload: SaveWarehousePayload) => warehouseService.create(payload),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: warehouseKeys.all }),
|
|
});
|
|
}
|
|
|
|
export function useUpdateWarehouse() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveWarehousePayload> }) =>
|
|
warehouseService.update(id, payload),
|
|
onSuccess: (_, { id }) => {
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.detail(id) });
|
|
},
|
|
});
|
|
}
|
|
|
|
// ── Yards ────────────────────────────────────────────────────────────────
|
|
|
|
export function useWarehouseYards(warehouseId?: string) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.yards(warehouseId ?? ''),
|
|
queryFn: () => warehouseService.listYards(warehouseId as string).then((r) => r.data),
|
|
enabled: Boolean(warehouseId),
|
|
});
|
|
}
|
|
|
|
export function useAllWarehouseYards() {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.allYards(),
|
|
queryFn: () => warehouseService.listAllYards().then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useCreateYard() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ warehouseId, payload }: { warehouseId: string; payload: SaveYardPayload }) =>
|
|
warehouseService.createYard(warehouseId, payload),
|
|
onSuccess: (_, { warehouseId }) => {
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.yards(warehouseId) });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.detail(warehouseId) });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateYard() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveYardPayload> }) =>
|
|
warehouseService.updateYard(id, payload),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: warehouseKeys.all }),
|
|
});
|
|
}
|
|
|
|
// ── Zones ──────────────────────────────────────────────────────────────────
|
|
|
|
export function useWarehouseZones(yardId?: string) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.zones(yardId ?? ''),
|
|
queryFn: () => warehouseService.listZones(yardId as string).then((r) => r.data),
|
|
enabled: Boolean(yardId),
|
|
});
|
|
}
|
|
|
|
export function useAllWarehouseZones() {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.allZones(),
|
|
queryFn: () => warehouseService.listAllZones().then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
/** Live per-zone occupancy for the heatmap (optionally scoped to one yard). */
|
|
export function useZoneOccupancy(yardId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-zones', 'occupancy', yardId ?? 'all'],
|
|
queryFn: () => warehouseService.zoneOccupancy(yardId).then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** At-a-glance warehouse ops counters for the KPI strip. */
|
|
export function useWarehouseOpsStats() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'ops-stats'],
|
|
queryFn: () => warehouseService.opsStats().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** Trucks in the yard right now — refreshes with the rest of the ops widgets. */
|
|
export function useTrucksOnSite() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'trucks-on-site'],
|
|
queryFn: () => warehouseService.trucksOnSite().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** How often the live warehouse dashboard widgets auto-refresh (ms). */
|
|
export const DASHBOARD_REFETCH_MS = 60_000;
|
|
|
|
/** Server-side received-vs-dispatched throughput time series. */
|
|
export function useWarehouseThroughput(granularity: 'week' | 'month' | 'year') {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'throughput', granularity],
|
|
queryFn: () => warehouseService.throughput(granularity).then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** Dwell time of in-warehouse items (average + aging buckets). */
|
|
export function useWarehouseDwellStats() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'dwell-stats'],
|
|
queryFn: () => warehouseService.dwellStats().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** Average stage cycle times over recently dispatched items. */
|
|
export function useWarehouseCycleStats() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'cycle-stats'],
|
|
queryFn: () => warehouseService.cycleStats().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** Gate / dock throughput (cleared today, turnaround, hourly clearances). */
|
|
export function useWarehouseGateStats() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'gate-stats'],
|
|
queryFn: () => warehouseService.gateStats().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** On-time dispatch rate (left before storage free-days expired). */
|
|
export function useOnTimeDispatch() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-fees', 'on-time-dispatch'],
|
|
queryFn: () => warehouseService.onTimeDispatch().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
/** Live per-item fee accrual (storage/demurrage) with alerts. */
|
|
export function useAccrualDashboard(billingCurrency?: 'ETB' | 'USD') {
|
|
return useQuery({
|
|
queryKey: ['warehouse-fees', 'accrual-dashboard', billingCurrency ?? 'USD'],
|
|
queryFn: () => warehouseService.accrualDashboard(billingCurrency).then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
export function useCreateZone() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ yardId, payload }: { yardId: string; payload: SaveZonePayload }) =>
|
|
warehouseService.createZone(yardId, payload),
|
|
onSuccess: (_, { yardId }) => qc.invalidateQueries({ queryKey: warehouseKeys.zones(yardId) }),
|
|
});
|
|
}
|
|
|
|
export function useUpdateZone() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveZonePayload> }) =>
|
|
warehouseService.updateZone(id, payload),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ['warehouse-yards'] }),
|
|
});
|
|
}
|
|
|
|
// ── Inventory ──────────────────────────────────────────────────────────────
|
|
|
|
export function useWarehouseInventory(filter?: InventoryFilter) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.inventory(filter),
|
|
queryFn: () => warehouseService.listInventory(filter).then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useWarehouseDashboardSummary(filter?: InventoryFilter) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.dashboardSummary(filter),
|
|
queryFn: () => warehouseService.getDashboardSummary(filter).then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useReceiveInventory() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (payload: ReceiveInventoryPayload) => warehouseService.receiveInventory(payload),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
qc.invalidateQueries({ queryKey: ['warehouse-dashboard'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
function useInventoryMutation<TArgs>(fn: (args: TArgs) => Promise<unknown>) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: fn,
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
qc.invalidateQueries({ queryKey: ['warehouse-loadings'] });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
},
|
|
});
|
|
}
|
|
|
|
export const useStoreInventory = () => useInventoryMutation((id: string) => warehouseService.store(id));
|
|
export const useReserveInventory = () =>
|
|
useInventoryMutation((payload: ReserveInventoryPayload) => warehouseService.reserve(payload));
|
|
export const useMarkReadyForLoading = () =>
|
|
useInventoryMutation((id: string) => warehouseService.markReadyForLoading(id));
|
|
export const useLoadInventory = () =>
|
|
useInventoryMutation((args: { id: string; payload: LoadInventoryPayload }) =>
|
|
warehouseService.load(args.id, args.payload),
|
|
);
|
|
export const useDispatchInventory = () => useInventoryMutation((id: string) => warehouseService.dispatch(id));
|
|
export const useMoveInventory = () =>
|
|
useInventoryMutation((args: { id: string; payload: MoveInventoryPayload }) =>
|
|
warehouseService.move(args.id, args.payload),
|
|
);
|
|
|
|
// ── Import branch (READY_FOR_PICKUP → DELIVERED) ───────────────────────────
|
|
export const useMarkReadyForPickup = () =>
|
|
useInventoryMutation((id: string) => warehouseService.markReadyForPickup(id));
|
|
export const useReleaseInventory = () =>
|
|
useInventoryMutation((args: { id: string; payload: ReleaseOrderPayload }) =>
|
|
warehouseService.release(args.id, args.payload),
|
|
);
|
|
export const useDeliverInventory = () =>
|
|
useInventoryMutation((args: { id: string; payload: DeliverInventoryPayload }) =>
|
|
warehouseService.deliver(args.id, args.payload),
|
|
);
|
|
|
|
// ── Receive (Import/Export bulk) ───────────────────────────────────────────
|
|
/**
|
|
* All not-yet-received PAID bookings, classified IMPORT/EXPORT by route, in one call.
|
|
* Both Receive tabs share this single query (same key) — only one HTTP request fires —
|
|
* then filter client-side by direction.
|
|
*/
|
|
export function useEligibleBookings(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'eligible-bookings'],
|
|
queryFn: () => warehouseService.eligibleBookings().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
export const useBulkReceive = () =>
|
|
useInventoryMutation((payload: BulkReceivePayload) => warehouseService.receiveBulk(payload));
|
|
export const useBulkMarkInspected = () =>
|
|
useInventoryMutation((payload: BulkInspectPayload) => warehouseService.bulkMarkInspected(payload));
|
|
|
|
export function useReadyToLoadExport(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'ready-to-load-export'],
|
|
queryFn: () => warehouseService.readyToLoadExport().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
export function useLoadedExport(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'loaded-export'],
|
|
queryFn: () => warehouseService.loadedExport().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
export const useBulkDispatchExport = () =>
|
|
useInventoryMutation((inventoryIds: string[]) => warehouseService.bulkDispatchExport(inventoryIds));
|
|
|
|
/** Arrived IMPORT trains (route-derived). Read-only. */
|
|
export function useImportArriveQueue(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'import-arrive-queue'],
|
|
queryFn: () => warehouseService.importArriveQueue().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
/** Assigned bookings/items for an arrived import train. Read-only. */
|
|
export function useImportTrainItems(scheduleId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'import-train-items', scheduleId],
|
|
queryFn: () => warehouseService.importTrainItems(scheduleId as string).then((r) => r.data),
|
|
enabled: Boolean(scheduleId),
|
|
});
|
|
}
|
|
|
|
/** Unload all eligible assigned bookings of an ARRIVED import train (→ UNLOADED). */
|
|
export const useAutoUnloadArrivedBookings = () =>
|
|
useInventoryMutation((payload: {
|
|
scheduleId: string;
|
|
warehouseId?: string;
|
|
assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[];
|
|
}) =>
|
|
warehouseService.autoUnloadArrivedBookings(payload),
|
|
);
|
|
|
|
/** Arrived EXPORT trains at Djibouti-side ports. Read-only. */
|
|
export function useExportDjiboutiArrivalQueue(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'export-djibouti-arrival-queue'],
|
|
queryFn: () => warehouseService.exportDjiboutiArrivalQueue().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
/** Assigned export bookings/items for a Djibouti-side arrived export train. Read-only. */
|
|
export function useExportDjiboutiTrainItems(scheduleId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'export-djibouti-train-items', scheduleId],
|
|
queryFn: () => warehouseService.exportDjiboutiTrainItems(scheduleId as string).then((r) => r.data),
|
|
enabled: Boolean(scheduleId),
|
|
});
|
|
}
|
|
|
|
/** Unload eligible export items assigned to an arrived Djibouti-side train. */
|
|
export const useAutoUnloadExportAtDjibouti = () => {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (scheduleId: string) => warehouseService.autoUnloadExportAtDjibouti(scheduleId),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
qc.invalidateQueries({ queryKey: ['warehouse-loadings'] });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
qc.invalidateQueries({ queryKey: ['interchange-documents'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/** IMPORT inventory in the Unloaded Queue (UNLOADED / destination inspection). Read-only. */
|
|
export function useImportUnloadedQueue(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'import-unloaded-queue'],
|
|
queryFn: () => warehouseService.importUnloadedQueue().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
/** IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch. Read-only. */
|
|
export function useImportPickupReadyQueue(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'import-pickup-ready-queue'],
|
|
queryFn: () => warehouseService.importPickupReadyQueue().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
// ── Loading (Batch 3) ────────────────────────────────────────────────────────
|
|
|
|
export function useLoadableWagons(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['warehouse', 'loadable-wagons'],
|
|
queryFn: () => warehouseService.loadableWagons().then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
export function useWarehouseLoadings(params?: { bookingId?: string; wagonId?: string }) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-loadings', params ?? {}],
|
|
queryFn: () => warehouseService.loadings(params).then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useBookingSchedule(bookingId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse', 'booking-schedule', bookingId ?? ''],
|
|
queryFn: () => warehouseService.bookingSchedule(bookingId as string).then((r) => r.data),
|
|
enabled: Boolean(bookingId),
|
|
});
|
|
}
|
|
|
|
export function useInventoryMovements(id?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', id, 'movements'],
|
|
queryFn: () => warehouseService.movements(id as string).then((r) => r.data),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
export function useInventoryActivity(id?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', id, 'activity'],
|
|
queryFn: () => warehouseService.activity(id as string).then((r) => r.data),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
export function useWarehouseDashboard() {
|
|
return useQuery({
|
|
queryKey: ['warehouses', 'dashboard'],
|
|
queryFn: () => warehouseService.dashboard().then((r) => r.data),
|
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
|
});
|
|
}
|
|
|
|
export function useInventoryInquiry(filter: InventoryInquiryFilter, enabled = true) {
|
|
return useQuery({
|
|
queryKey: warehouseKeys.inquiry(filter),
|
|
queryFn: () => warehouseService.inquiry(filter).then((r) => r.data),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
// ── Batch 4.5: Arrival / Unload / Inspection ────────────────────────────────
|
|
|
|
export function useArrivalQueue() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', 'arrival-queue'],
|
|
queryFn: () => warehouseService.arrivalQueue().then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
function useArrivalInvalidation() {
|
|
const qc = useQueryClient();
|
|
return () => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
};
|
|
}
|
|
|
|
export function useAutoUnloadArrived() {
|
|
const onSuccess = useArrivalInvalidation();
|
|
return useMutation({ mutationFn: () => warehouseService.autoUnloadArrived(), onSuccess });
|
|
}
|
|
|
|
export function useAutoLoadReady() {
|
|
const onSuccess = useArrivalInvalidation();
|
|
return useMutation({ mutationFn: () => warehouseService.autoLoadReady(), onSuccess });
|
|
}
|
|
|
|
export function useUnloadBooking() {
|
|
const onSuccess = useArrivalInvalidation();
|
|
return useMutation({
|
|
mutationFn: (args: { bookingId: string; payload?: Record<string, unknown> }) =>
|
|
warehouseService.unloadBooking(args.bookingId, args.payload),
|
|
onSuccess,
|
|
});
|
|
}
|
|
|
|
export function useInspectionReports(inventoryId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', inventoryId, 'inspection-reports'],
|
|
queryFn: () => warehouseService.listInspectionReports(inventoryId as string).then((r) => r.data),
|
|
enabled: Boolean(inventoryId),
|
|
});
|
|
}
|
|
|
|
export function useCreateInspectionReport() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ inventoryId, payload }: { inventoryId: string; payload: InspectionReportPayload }) =>
|
|
warehouseService.createInspectionReport(inventoryId, payload).then((r) => r.data),
|
|
onSuccess: (_, { inventoryId }) => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory', inventoryId, 'inspection-reports'] });
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUploadInspectionAttachments() {
|
|
return useMutation({
|
|
mutationFn: ({ reportId, files }: { reportId: string; files: File[] }) =>
|
|
warehouseService.uploadInspectionAttachments(reportId, files),
|
|
});
|
|
}
|
|
|
|
// ── Batch 5: Allocation + Fee rules / preview ───────────────────────────────
|
|
|
|
export function useAllocationRules() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-allocation-rules'],
|
|
queryFn: () => warehouseService.listAllocationRules().then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useFeeRules() {
|
|
return useQuery({
|
|
queryKey: ['warehouse-fee-rules'],
|
|
queryFn: () => warehouseService.listFeeRules().then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
function useRuleMutation<TArgs>(fn: (args: TArgs) => Promise<unknown>, keys: string[]) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: fn,
|
|
onSuccess: () => keys.forEach((k) => qc.invalidateQueries({ queryKey: [k] })),
|
|
});
|
|
}
|
|
|
|
export const useCreateAllocationRule = () =>
|
|
useRuleMutation(
|
|
(payload: SaveAllocationRulePayload) => warehouseService.createAllocationRule(payload),
|
|
['warehouse-allocation-rules'],
|
|
);
|
|
export const useUpdateAllocationRule = () =>
|
|
useRuleMutation(
|
|
(args: { id: string; payload: Partial<SaveAllocationRulePayload> }) =>
|
|
warehouseService.updateAllocationRule(args.id, args.payload),
|
|
['warehouse-allocation-rules'],
|
|
);
|
|
export const useDeleteAllocationRule = () =>
|
|
useRuleMutation((id: string) => warehouseService.deleteAllocationRule(id), ['warehouse-allocation-rules']);
|
|
|
|
export const useCreateFeeRule = () =>
|
|
useRuleMutation((payload: SaveFeeRulePayload) => warehouseService.createFeeRule(payload), ['warehouse-fee-rules']);
|
|
export const useUpdateFeeRule = () =>
|
|
useRuleMutation(
|
|
(args: { id: string; payload: Partial<SaveFeeRulePayload> }) =>
|
|
warehouseService.updateFeeRule(args.id, args.payload),
|
|
['warehouse-fee-rules'],
|
|
);
|
|
export const useDeleteFeeRule = () =>
|
|
useRuleMutation((id: string) => warehouseService.deleteFeeRule(id), ['warehouse-fee-rules']);
|
|
|
|
export function useFeePreview(inventoryId?: string, billingCurrency: 'ETB' | 'USD' = 'USD') {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', inventoryId, 'fee-preview', billingCurrency],
|
|
queryFn: () => warehouseService.feePreview(inventoryId as string, billingCurrency).then((r) => r.data),
|
|
enabled: Boolean(inventoryId),
|
|
});
|
|
}
|
|
|
|
// ── Batch 6: Warehouse fee invoices ─────────────────────────────────────────
|
|
|
|
export function useWarehouseInvoices(filter?: WarehouseInvoiceFilter) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-fee-invoices', filter ?? {}],
|
|
queryFn: () => warehouseService.listInvoices(filter).then((r) => r.data),
|
|
});
|
|
}
|
|
|
|
export function useWarehouseInvoice(id?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-fee-invoices', 'detail', id],
|
|
queryFn: () => warehouseService.getInvoice(id as string).then((r) => r.data),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
export function useInvoicesForInventory(inventoryId?: string) {
|
|
return useQuery({
|
|
queryKey: ['warehouse-inventory', inventoryId, 'fee-invoices'],
|
|
queryFn: () => warehouseService.invoicesForInventory(inventoryId as string).then((r) => r.data),
|
|
enabled: Boolean(inventoryId),
|
|
});
|
|
}
|
|
|
|
function useInvoiceInvalidation() {
|
|
const qc = useQueryClient();
|
|
return () => {
|
|
qc.invalidateQueries({ queryKey: ['warehouse-fee-invoices'] });
|
|
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
};
|
|
}
|
|
|
|
export function useGenerateInvoice() {
|
|
const onSuccess = useInvoiceInvalidation();
|
|
return useMutation({
|
|
mutationFn: ({
|
|
inventoryId,
|
|
confirmZero,
|
|
billingCurrency,
|
|
}: {
|
|
inventoryId: string;
|
|
confirmZero?: boolean;
|
|
billingCurrency?: 'ETB' | 'USD';
|
|
}) => warehouseService.generateInvoice(inventoryId, confirmZero, billingCurrency).then((r) => r.data),
|
|
onSuccess,
|
|
});
|
|
}
|
|
|
|
export function useCancelInvoice() {
|
|
const onSuccess = useInvoiceInvalidation();
|
|
return useMutation({ mutationFn: (id: string) => warehouseService.cancelInvoice(id), onSuccess });
|
|
}
|
|
|
|
export function usePayInvoice() {
|
|
const onSuccess = useInvoiceInvalidation();
|
|
return useMutation({
|
|
mutationFn: ({ id, payload }: { id: string; payload: PayInvoicePayload }) =>
|
|
warehouseService.payInvoice(id, payload),
|
|
onSuccess,
|
|
});
|
|
}
|
|
|
|
export function useGateClearance() {
|
|
const onSuccess = useInvoiceInvalidation();
|
|
return useMutation({ mutationFn: (inventoryId: string) => warehouseService.gateClearance(inventoryId), onSuccess });
|
|
}
|