import type { Freight } from '@edr/types'; import { api as apiClient } from '../auth/http'; import { URL_CONSTANTS } from '@/constants/URLS'; import type { AllocationCriteria, AllocationPreviewResult, AllocationRule, ArrivalQueueItem, AutoLoadResult, AutoUnloadResult, FeePreview, FeeRule, InspectionAttachment, InspectionReport, InspectionReportPayload, SaveAllocationRulePayload, SaveFeeRulePayload, WarehouseFeeInvoice, WarehouseInvoiceFilter, PayInvoicePayload, InitiateWarehouseInvoicePaymentPayload, WarehouseInvoicePaymentResponse, BookingScheduleView, InventoryFilter, InventoryInquiryFilter, InventoryInquiryResult, InventoryMovement, LoadableWagon, LoadInventoryPayload, MoveInventoryPayload, StoreInventoryPayload, ReceiveInventoryPayload, ReleaseOrderPayload, DeliverInventoryPayload, EligibleBooking, BulkReceivePayload, BulkReceiveResult, LoadPassedExportResult, BulkInspectPayload, BulkInspectResult, ReadyToLoadRow, BulkDispatchResult, AutoUnloadExportDjiboutiResult, ExportTrain, ExportTrainItem, ImportTrain, ImportTrainItem, ImportUnloadedItem, AutoUnloadArrivedResult, ReserveInventoryPayload, SaveWarehousePayload, SaveYardPayload, SaveZonePayload, Warehouse, WarehouseActivityLog, WarehouseDashboard, WarehouseFacility, WarehouseFilter, WarehouseInventoryItem, WarehouseLoading, WarehouseYard, WarehouseZone, } from '@/types/warehouse'; export type ContainerItemStage = 'PENDING' | 'RECEIVED' | 'GRN' | 'LOADED' | 'LEFT' | 'DELIVERED'; export interface ContainerItem { containerNumber: string; goods: string | null; stage: ContainerItemStage; grnNumber: string | null; truckAssignmentId: string | null; truckPlate: string | null; truckArrived: boolean; truckLeft: boolean; bookingReference: string | null; contractId: string | null; hasLastMile: boolean; handoverSigned: boolean; } /** A pre-dispatch EXPORT train that has inventory waiting to be loaded. */ export interface LoadableTrain { scheduleId: string; trainNumber: string | null; origin: string | null; destination: string | null; status: string; departureTime: string | null; readyCount: number; loadedCount: number; } /** A container/cargo inventory item assigned to a train, with its allocated wagon. */ export interface TrainLoadableItem { id: string; bookingId: string | null; bookingReference: string | null; customerName: string | null; containerNumber: string | null; cargoType: string | null; weight: number | null; grnNumber: string | null; inspectionStatus: string | null; status: string; wagonId: string | null; wagonNumber: string | null; sequenceNo: number | null; loadable: boolean; } export interface TrainLoadResult { loadedCount: number; skippedCount: number; results: { inventoryId: string; status: string; reason?: string }[]; } const cleanParams = (params: object) => Object.fromEntries( Object.entries(params).filter(([, value]) => value !== undefined && value !== '' && value !== null), ); export const warehouseService = { /** Customer self-haul trucks assigned to a booking (portal multi-truck). */ getCustomerTrucks: async (bookingId: string): Promise => { const { data } = await apiClient.get(`/bookings/${bookingId}/customer-trucks`); return data?.data ?? data ?? []; }, /** Per-container/bulk items of a booking with lifecycle stage + refs. */ getContainerItems: async (bookingId: string): Promise => { const { data } = await apiClient.get( `/warehouse-inventory/bookings/${bookingId}/container-items`, ); return data?.data ?? data ?? []; }, /** Ask the customer to sign the booking's handover (creates one if none, then notifies). */ requestHandoverSignature: async ( bookingId: string, ): Promise<{ notified: boolean; reference: string | null; alreadySigned: boolean }> => { const { data } = await apiClient.post( `/warehouse-inventory/bookings/${bookingId}/request-handover-signature`, ); return data?.data ?? data; }, /** A booking's containers with VGM cargo weight (tonnes) for exit weighing. */ getContainerWeights: async ( bookingId: string, ): Promise> => { const { data } = await apiClient.get( `/warehouse-inventory/bookings/${bookingId}/container-weights`, ); return data?.data ?? data ?? []; }, /** Booking container numbers not yet loaded onto any truck. */ getLoadableContainers: async (bookingId: string): Promise => { const { data } = await apiClient.get( `/bookings/${bookingId}/customer-trucks/loadable-containers`, ); return data?.data ?? data ?? []; }, /** Truck_dispatch: load selected containers onto a truck (after arrival). */ loadTruck: async ( bookingId: string, assignmentId: string, containerNumbers: string[], ): Promise => { const { data } = await apiClient.post( `/bookings/${bookingId}/customer-trucks/${assignmentId}/load`, { containerNumbers }, ); return data?.data ?? data ?? []; }, // ── Load to Train ───────────────────────────────────────────────────────── /** Pre-dispatch EXPORT trains with inventory waiting to be loaded. */ getLoadableTrains: async (): Promise => { const { data } = await apiClient.get('/warehouse-inventory/loadable-trains'); return data?.data ?? data ?? []; }, /** Container/cargo items assigned to a train, with allocated wagon + stage. */ getTrainLoadableItems: async (scheduleId: string): Promise => { const { data } = await apiClient.get( `/warehouse-inventory/train/${scheduleId}/loadable-items`, ); return data?.data ?? data ?? []; }, /** Load selected inventory items onto their allocated wagons for a train. */ loadItemsOntoTrain: async ( scheduleId: string, inventoryIds: string[], ): Promise => { const { data } = await apiClient.post( `/warehouse-inventory/train/${scheduleId}/load`, { inventoryIds }, ); return data?.data ?? data ?? { loadedCount: 0, skippedCount: 0, results: [] }; }, // ── Warehouses ────────────────────────────────────────────────────────── list: (filter?: WarehouseFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSES.BASE, { params: cleanParams(filter ?? {}), }), dashboard: () => apiClient.get(URL_CONSTANTS.WAREHOUSES.DASHBOARD), getDashboardSummary: (_filter?: InventoryFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSES.DASHBOARD), getById: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSES.BY_ID(id)), create: (payload: SaveWarehousePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSES.BASE, payload), update: (id: string, payload: Partial) => apiClient.patch(URL_CONSTANTS.WAREHOUSES.BY_ID(id), payload), listFacilities: () => apiClient.get(URL_CONSTANTS.RULE_ENGINE.YARDS), // ── Yards ──────────────────────────────────────────────────────────────── listYards: (warehouseId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSES.YARDS(warehouseId)), listAllYards: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_YARDS.BASE), createYard: (warehouseId: string, payload: SaveYardPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSES.YARDS(warehouseId), payload), getYard: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_YARDS.BY_ID(id)), updateYard: (id: string, payload: Partial) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_YARDS.BY_ID(id), payload), // ── Zones ────────────────────────────────────────────────────────────── listZones: (yardId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_YARDS.ZONES(yardId)), listAllZones: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_ZONES.BASE), createZone: (yardId: string, payload: SaveZonePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_YARDS.ZONES(yardId), payload), getZone: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_ZONES.BY_ID(id)), updateZone: (id: string, payload: Partial) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_ZONES.BY_ID(id), payload), // ── Inventory ────────────────────────────────────────────────────────── listInventory: (filter?: InventoryFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.BASE, { params: cleanParams(filter ?? {}), }), receiveInventory: (payload: ReceiveInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.RECEIVE, payload), listReadyForLoading: (filter?: InventoryFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.READY_FOR_LOADING, { params: cleanParams(filter ?? {}), }), inquiry: (filter: InventoryInquiryFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.INQUIRY, { params: cleanParams(filter ?? {}), }), // ── Lifecycle (Batch 2) ────────────────────────────────────────────────── store: (id: string, payload?: StoreInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.STORE(id), payload), reserve: (payload: ReserveInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.RESERVE, payload), markReadyForLoading: (id: string) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.MARK_READY(id)), load: (id: string, payload: LoadInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOAD(id), payload), dispatch: (id: string) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_INVENTORY.DISPATCH(id)), // ── Import branch (READY_FOR_PICKUP → DELIVERED) ───────────────────────── markReadyForPickup: (id: string) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.MARK_READY_PICKUP(id)), release: (id: string, payload: ReleaseOrderPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.RELEASE(id), payload), downloadReleaseDocument: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.RELEASE_DOCUMENT(id), { responseType: 'blob', }), downloadGrnDocument: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.GRN_DOCUMENT(id), { responseType: 'blob', }), downloadHandoverDocument: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.HANDOVER_DOCUMENT(id), { responseType: 'blob', }), /** Per-truck exit paper PDF (containers loaded on one customer truck). */ downloadTruckExitPaper: (assignmentId: string) => apiClient.get(`/warehouse-inventory/customer-truck-exit-paper/${assignmentId}`, { responseType: 'blob', }), deliver: (id: string, payload: DeliverInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.DELIVER(id), payload), // ── Receive (Import/Export bulk) ───────────────────────────────────────── eligibleBookings: (direction?: 'IMPORT' | 'EXPORT') => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.ELIGIBLE_BOOKINGS(direction)), receiveBulk: (payload: BulkReceivePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.RECEIVE_BULK, payload), loadPassedExport: () => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOAD_PASSED_EXPORT, {}), bulkMarkInspected: (payload: BulkInspectPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.BULK_MARK_INSPECTED, payload), receivedExport: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.RECEIVED_EXPORT), readyToLoadExport: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.READY_TO_LOAD_EXPORT), loadedExport: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOADED_EXPORT), bulkDispatchExport: (inventoryIds: string[]) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.BULK_DISPATCH_EXPORT, { inventoryIds, }), importArriveQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_ARRIVE_QUEUE), importTrainItems: (scheduleId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_TRAIN_ITEMS(scheduleId)), autoUnloadArrivedBookings: (payload: { scheduleId: string; warehouseId?: string; assignments?: { bookingId: string; warehouseId: string; yardId: string; zoneId: string }[]; }) => apiClient.post( URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_AUTO_UNLOAD_ARRIVED, payload, ), importUnloadedQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_UNLOADED_QUEUE), importPickupReadyQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.IMPORT_PICKUP_READY_QUEUE), exportDjiboutiArrivalQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.EXPORT_DJIBOUTI_ARRIVAL_QUEUE), exportDjiboutiTrainItems: (scheduleId: string) => apiClient.get( URL_CONSTANTS.WAREHOUSE_INVENTORY.EXPORT_DJIBOUTI_TRAIN_ITEMS(scheduleId), ), autoUnloadExportAtDjibouti: (scheduleId: string) => apiClient.post( URL_CONSTANTS.WAREHOUSE_INVENTORY.EXPORT_AUTO_UNLOAD_AT_DJIBOUTI, { scheduleId }, ), move: (id: string, payload: MoveInventoryPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.MOVE(id), payload), movements: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.MOVEMENTS(id)), activity: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.ACTIVITY(id)), // ── Loading (Batch 3) ───────────────────────────────────────────────────── loadableWagons: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOADABLE_WAGONS), bookingSchedule: (bookingId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.BOOKING_SCHEDULE(bookingId)), inventoryLoadings: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOADINGS(id)), loadings: (params?: { bookingId?: string; wagonId?: string }) => apiClient.get(URL_CONSTANTS.WAREHOUSE_LOADINGS.BASE, { params: cleanParams(params ?? {}), }), // ── Batch 4.5: Arrival / Unload / Load automation ────────────────────────── arrivalQueue: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.ARRIVAL_QUEUE), autoUnloadArrived: () => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.AUTO_UNLOAD_ARRIVED), autoLoadReady: () => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVENTORY.AUTO_LOAD_READY), unloadBooking: (bookingId: string, payload?: Record) => apiClient.post( URL_CONSTANTS.WAREHOUSE_INVENTORY.UNLOAD_BOOKING(bookingId), payload ?? {}, ), // ── Batch 4.5: Inspection reports ────────────────────────────────────────── listInspectionReports: (inventoryId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVENTORY.INSPECTION_REPORTS(inventoryId)), createInspectionReport: (inventoryId: string, payload: InspectionReportPayload) => apiClient.post( URL_CONSTANTS.WAREHOUSE_INVENTORY.INSPECTION_REPORTS(inventoryId), payload, ), getInspectionReport: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INSPECTION.BY_ID(id)), uploadInspectionAttachments: (id: string, files: File[]) => { const form = new FormData(); files.forEach((file) => form.append('files', file)); return apiClient.post( URL_CONSTANTS.WAREHOUSE_INSPECTION.ATTACHMENTS(id), form, { headers: { 'Content-Type': 'multipart/form-data' } }, ); }, // ── Batch 5: Allocation + Fee rules / previews ───────────────────────────── listAllocationRules: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_RULES.ALLOCATION), createAllocationRule: (payload: SaveAllocationRulePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_RULES.ALLOCATION, payload), updateAllocationRule: (id: string, payload: Partial) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_RULES.ALLOCATION_BY_ID(id), payload), deleteAllocationRule: (id: string) => apiClient.delete(URL_CONSTANTS.WAREHOUSE_RULES.ALLOCATION_BY_ID(id)), previewAllocation: (criteria: AllocationCriteria) => apiClient.post(URL_CONSTANTS.WAREHOUSE_RULES.ALLOCATION_PREVIEW, criteria), listFeeRules: () => apiClient.get(URL_CONSTANTS.WAREHOUSE_RULES.FEES), createFeeRule: (payload: SaveFeeRulePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_RULES.FEES, payload), updateFeeRule: (id: string, payload: Partial) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_RULES.FEES_BY_ID(id), payload), deleteFeeRule: (id: string) => apiClient.delete(URL_CONSTANTS.WAREHOUSE_RULES.FEES_BY_ID(id)), feePreview: (inventoryId: string, billingCurrency?: 'ETB' | 'USD') => apiClient.get(URL_CONSTANTS.WAREHOUSE_RULES.FEE_PREVIEW(inventoryId), { params: cleanParams({ billingCurrency }), }), // ── Batch 6: Warehouse fee invoices ──────────────────────────────────────── listInvoices: (filter?: WarehouseInvoiceFilter) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.BASE, { params: cleanParams(filter ?? {}), }), getInvoice: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.BY_ID(id)), downloadInvoiceDocument: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.DOCUMENT(id), { responseType: 'blob', }), downloadInvoiceReceipt: (id: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.RECEIPT(id), { responseType: 'blob', }), invoicesForInventory: (inventoryId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.FOR_INVENTORY(inventoryId)), invoicesForBooking: (bookingId: string) => apiClient.get(URL_CONSTANTS.WAREHOUSE_INVOICES.FOR_BOOKING(bookingId)), generateInvoice: (inventoryId: string, confirmZero = false, billingCurrency?: 'ETB' | 'USD') => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVOICES.GENERATE(inventoryId), { confirmZero, billingCurrency, }), cancelInvoice: (id: string) => apiClient.patch(URL_CONSTANTS.WAREHOUSE_INVOICES.CANCEL(id)), payInvoice: (id: string, payload: PayInvoicePayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVOICES.PAY(id), payload), payInvoiceOnline: (id: string, payload: InitiateWarehouseInvoicePaymentPayload) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVOICES.PAY_ONLINE(id), payload), gateClearance: (inventoryId: string) => apiClient.post(URL_CONSTANTS.WAREHOUSE_INVOICES.GATE_CLEARANCE(inventoryId), {}), };