Merge remote-tracking branch 'origin/dev' into Truckdetantion

This commit is contained in:
Hagernesh
2026-07-07 08:49:20 +00:00
252 changed files with 16873 additions and 1684 deletions

View File

@@ -593,6 +593,52 @@ export const api = {
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
intercityCandidates: endpoint<
{ scheduleId: string },
import("@/types/trainScheduling").IntercityCandidatesResult
>(
"train-scheduling",
"intercity-candidates",
({ scheduleId }) => trainSchedulingService.getIntercityCandidates(scheduleId),
({ scheduleId }) => ["train-scheduling", "intercity-candidates", scheduleId],
),
acceptIntercityBookings: endpoint<
{ scheduleId: string; bookingIds: string[] },
import("@/types/trainScheduling").IntercityAcceptResult
>(
"train-scheduling",
"intercity-accept",
({ scheduleId, bookingIds }) =>
trainSchedulingService.acceptIntercityBookings(scheduleId, bookingIds),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
loadIntercityBooking: endpoint<
{ scheduleId: string; bookingId: string },
void
>(
"train-scheduling",
"intercity-load",
({ scheduleId, bookingId }) =>
trainSchedulingService.loadIntercityBooking(scheduleId, bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
unloadIntercityBooking: endpoint<
{ scheduleId: string; bookingId: string },
void
>(
"train-scheduling",
"intercity-unload",
({ scheduleId, bookingId }) =>
trainSchedulingService.unloadIntercityBooking(scheduleId, bookingId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
cancelSchedule: endpoint<
{ id: string; freightType?: FreightType },
TrainScheduleDetail

View File

@@ -0,0 +1,73 @@
import { api as apiClient } from '../auth/http';
export type ComplianceType =
| 'INSPECTION'
| 'INSURANCE'
| 'ROADWORTHINESS'
| 'PERMIT'
| 'TAX';
export type ComplianceStatus = 'VALID' | 'EXPIRING' | 'EXPIRED';
export type AlertSeverity = 'OVERDUE' | 'DUE_SOON';
export interface ComplianceRecord {
id: string;
vehicleId: string;
vehicle?: {
id: string;
plateNumber?: string | null;
manufacturer?: string | null;
model?: string | null;
};
type: ComplianceType;
documentNumber?: string | null;
issuedDate?: string | null;
expiryDate: string;
status: ComplianceStatus;
notes?: string | null;
createdAt: string;
updatedAt: string;
}
export interface ComplianceAlert {
vehicleId: string;
vehiclePlate?: string;
kind: string;
label: string;
expiryDate: string;
daysUntil: number;
severity: AlertSeverity;
}
export interface ComplianceListFilters {
vehicleId?: string;
type?: ComplianceType;
}
export interface SaveCompliancePayload {
vehicleId: string;
type: ComplianceType;
documentNumber?: string;
issuedDate?: string;
expiryDate: string;
status?: ComplianceStatus;
notes?: string;
}
export const complianceService = {
list: (filters: ComplianceListFilters = {}) => {
const params = new URLSearchParams();
if (filters.vehicleId) params.set('vehicleId', filters.vehicleId);
if (filters.type) params.set('type', filters.type);
const qs = params.toString();
return apiClient.get<ComplianceRecord[]>(`/compliance${qs ? `?${qs}` : ''}`);
},
getAlerts: () => apiClient.get<ComplianceAlert[]>('/compliance/alerts'),
getById: (id: string) => apiClient.get<ComplianceRecord>(`/compliance/${id}`),
create: (data: SaveCompliancePayload) =>
apiClient.post<ComplianceRecord>('/compliance', data),
update: (id: string, data: Partial<SaveCompliancePayload>) =>
apiClient.patch<ComplianceRecord>(`/compliance/${id}`, data),
remove: (id: string) => apiClient.delete(`/compliance/${id}`),
};

View File

@@ -39,6 +39,17 @@ export type SaveDriverPayload = Omit<
'id' | 'createdAt' | 'updatedAt' | 'totalTrips' | 'rating'
>;
/** A stored driver document (code "driver_docs"). */
export interface DriverDocument {
id: string;
name: string;
url: string;
size: number;
mimeType: string;
code: string;
createdAt: string;
}
export const driversService = {
getAll: (filters: DriverListFilters = {}) => {
const params = new URLSearchParams();
@@ -59,4 +70,18 @@ export const driversService = {
update: (id: string, data: Partial<SaveDriverPayload>) =>
apiClient.patch(URL_CONSTANTS.DRIVERS.BY_ID(id), data),
delete: (id: string) => apiClient.delete(URL_CONSTANTS.DRIVERS.BY_ID(id)),
// ── Driver documents (upload area code "driver_docs") ──
listDocuments: (id: string) =>
apiClient.get<DriverDocument[]>(`${URL_CONSTANTS.DRIVERS.BY_ID(id)}/documents`),
uploadDocuments: (id: string, files: File[]) => {
const form = new FormData();
for (const f of files) form.append('files', f);
return apiClient.post<DriverDocument[]>(
`${URL_CONSTANTS.DRIVERS.BY_ID(id)}/documents`,
form,
);
},
removeDocument: (id: string, fileId: string) =>
apiClient.delete(`${URL_CONSTANTS.DRIVERS.BY_ID(id)}/documents/${fileId}`),
};

View File

@@ -47,6 +47,8 @@ export interface FirstMileVehicle {
trailerPlateNo?: string | null;
assignedDriverId?: string | null;
assignedDriverName?: string | null;
pricePerKm?: number | string | null;
currency?: string | null;
}
export interface FirstMileRecord {

View File

@@ -0,0 +1,48 @@
import { api } from "@/auth/http";
export interface GpsDevice {
id: string;
imei: string;
name?: string | null;
vehicleId?: string | null;
vehicle?: {
id: string;
plateNumber?: string;
code?: string | null;
manufacturer?: string;
model?: string;
} | null;
status: string;
online: boolean;
lastSeenAt?: string | null;
lastLat?: number | string | null;
lastLng?: number | string | null;
lastSpeed?: number | string | null;
lastCourse?: number | null;
lastFixAt?: string | null;
voltageLevel?: number | null;
gsmLevel?: number | null;
}
export interface GpsPosition {
id: string;
lat: number | string;
lng: number | string;
speed: number | string;
course: number;
satellites: number;
gpsTime: string;
alarm: number;
}
export const gpsTrackingService = {
latest: () => api.get<GpsDevice[]>("/gps/positions/latest"),
listDevices: () => api.get<GpsDevice[]>("/gps/devices"),
history: (vehicleId: string, limit = 200) =>
api.get<GpsPosition[]>(`/gps/positions/${vehicleId}/history?limit=${limit}`),
register: (data: { imei: string; name?: string; vehicleId?: string | null }) =>
api.post<GpsDevice>("/gps/devices", data),
update: (id: string, data: { name?: string; vehicleId?: string | null }) =>
api.patch<GpsDevice>(`/gps/devices/${id}`, data),
remove: (id: string) => api.delete<void>(`/gps/devices/${id}`),
};

View File

@@ -0,0 +1,87 @@
import { api } from '@/auth/http';
export type IncidentType = 'ACCIDENT' | 'BREAKDOWN' | 'TRAFFIC_VIOLATION' | 'THEFT' | 'OTHER';
export type IncidentSeverity = 'MINOR' | 'MODERATE' | 'MAJOR' | 'CRITICAL';
export type IncidentStatus =
| 'REPORTED'
| 'UNDER_REVIEW'
| 'CLAIM_FILED'
| 'RESOLVED'
| 'CLOSED';
export interface Incident {
id: string;
vehicleId?: string | null;
driverId?: string | null;
bookingId?: string | null;
type: IncidentType;
severity: IncidentSeverity;
occurredAt: string;
location?: string | null;
description: string;
/** API sends numeric as string; coerce with Number. */
damageEstimate?: number | string | null;
status: IncidentStatus;
insuranceClaimNumber?: string | null;
reportedBy?: string | null;
vehicle?: {
id: string;
plateNumber?: string | null;
registrationNumber?: string | null;
} | null;
driver?: {
id: string;
firstName?: string | null;
lastName?: string | null;
} | null;
createdAt: string;
updatedAt: string;
}
export interface IncidentFilters {
vehicleId?: string;
driverId?: string;
status?: IncidentStatus;
type?: IncidentType;
}
export interface DriverIncidentStats {
total: number;
byType: Record<string, number>;
lastIncidentAt: string | null;
}
export interface SaveIncidentPayload {
vehicleId?: string;
driverId?: string;
bookingId?: string;
type: IncidentType;
severity: IncidentSeverity;
occurredAt: string;
location?: string;
description: string;
damageEstimate?: number;
status?: IncidentStatus;
insuranceClaimNumber?: string;
reportedBy?: string;
}
export const incidentsService = {
getAll: (filters: IncidentFilters = {}) => {
const params = new URLSearchParams();
if (filters.vehicleId) params.set('vehicleId', filters.vehicleId);
if (filters.driverId) params.set('driverId', filters.driverId);
if (filters.status) params.set('status', filters.status);
if (filters.type) params.set('type', filters.type);
const qs = params.toString();
return api.get<Incident[]>(`/incidents${qs ? `?${qs}` : ''}`);
},
getByDriver: (driverId: string) => api.get<Incident[]>(`/incidents/driver/${driverId}`),
getDriverStats: (driverId: string) =>
api.get<DriverIncidentStats>(`/incidents/driver/${driverId}/stats`),
getById: (id: string) => api.get<Incident>(`/incidents/${id}`),
create: (data: SaveIncidentPayload) => api.post<Incident>('/incidents', data),
update: (id: string, data: Partial<SaveIncidentPayload>) =>
api.patch<Incident>(`/incidents/${id}`, data),
delete: (id: string) => api.delete(`/incidents/${id}`),
};

View File

@@ -48,6 +48,8 @@ export interface LastMileVehicle {
trailerPlateNo?: string | null;
assignedDriverId?: string | null;
assignedDriverName?: string | null;
pricePerKm?: number | string | null;
currency?: string | null;
}
export interface LastMileRecord {

View File

@@ -0,0 +1,130 @@
import { api } from '@/auth/http';
export type WorkOrderStatus = 'OPEN' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED';
export type WorkOrderPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
export interface WorkOrder {
id: string;
vehicleId: string;
title: string;
description?: string | null;
status: WorkOrderStatus;
priority: WorkOrderPriority;
assignedTo?: string | null;
openedAt: string;
closedAt?: string | null;
/** API sends numeric as string; coerce with Number. */
laborCost?: number | string | null;
partsCost?: number | string | null;
createdAt: string;
updatedAt: string;
}
export interface SaveWorkOrderPayload {
vehicleId: string;
title: string;
description?: string;
status?: WorkOrderStatus;
priority?: WorkOrderPriority;
assignedTo?: string;
openedAt?: string;
closedAt?: string;
laborCost?: number;
partsCost?: number;
}
export interface WorkOrderFilters {
vehicleId?: string;
status?: WorkOrderStatus;
}
export interface Part {
id: string;
name: string;
sku?: string | null;
category?: string | null;
quantityInStock: number;
reorderLevel: number;
/** API sends numeric as string; coerce with Number. */
unitCost?: number | string | null;
location?: string | null;
createdAt: string;
updatedAt: string;
}
export interface SavePartPayload {
name: string;
sku?: string;
category?: string;
quantityInStock?: number;
reorderLevel?: number;
unitCost?: number;
location?: string;
}
export interface PartFilters {
category?: string;
lowStock?: boolean;
}
export interface Warranty {
id: string;
vehicleId: string;
component: string;
provider?: string | null;
startDate?: string | null;
expiryDate: string;
coverageNotes?: string | null;
createdAt: string;
updatedAt: string;
}
export interface SaveWarrantyPayload {
vehicleId: string;
component: string;
provider?: string;
startDate?: string;
expiryDate: string;
coverageNotes?: string;
}
export const maintenanceDepthService = {
// Work Orders
getWorkOrders: (filters: WorkOrderFilters = {}) => {
const params = new URLSearchParams();
if (filters.vehicleId) params.set('vehicleId', filters.vehicleId);
if (filters.status) params.set('status', filters.status);
const qs = params.toString();
return api.get<WorkOrder[]>(`/maintenance/work-orders${qs ? `?${qs}` : ''}`);
},
getWorkOrder: (id: string) => api.get<WorkOrder>(`/maintenance/work-orders/${id}`),
createWorkOrder: (data: SaveWorkOrderPayload) =>
api.post<WorkOrder>('/maintenance/work-orders', data),
updateWorkOrder: (id: string, data: Partial<SaveWorkOrderPayload>) =>
api.patch<WorkOrder>(`/maintenance/work-orders/${id}`, data),
deleteWorkOrder: (id: string) => api.delete(`/maintenance/work-orders/${id}`),
// Parts / Tires
getParts: (filters: PartFilters = {}) => {
const params = new URLSearchParams();
if (filters.category) params.set('category', filters.category);
if (filters.lowStock) params.set('lowStock', 'true');
const qs = params.toString();
return api.get<Part[]>(`/maintenance/parts${qs ? `?${qs}` : ''}`);
},
createPart: (data: SavePartPayload) => api.post<Part>('/maintenance/parts', data),
updatePart: (id: string, data: Partial<SavePartPayload>) =>
api.patch<Part>(`/maintenance/parts/${id}`, data),
deletePart: (id: string) => api.delete(`/maintenance/parts/${id}`),
// Warranties
getWarranties: (vehicleId?: string) => {
const params = new URLSearchParams();
if (vehicleId) params.set('vehicleId', vehicleId);
const qs = params.toString();
return api.get<Warranty[]>(`/maintenance/warranties${qs ? `?${qs}` : ''}`);
},
createWarranty: (data: SaveWarrantyPayload) =>
api.post<Warranty>('/maintenance/warranties', data),
deleteWarranty: (id: string) => api.delete(`/maintenance/warranties/${id}`),
};

View File

@@ -0,0 +1,138 @@
import { api } from "@/auth/http";
export type VendorType = "DEALER" | "LEASING" | "PARTS" | "SERVICE" | "OTHER";
export type AcquisitionType = "PURCHASE" | "LEASE" | "RENTAL";
export type AcquisitionStatus = "ACTIVE" | "LEASE_EXPIRING" | "DISPOSED";
export type DisposalMethod = "SALE" | "SCRAP" | "RETURN_LEASE" | "TRADE_IN";
export interface Vendor {
id: string;
name: string;
type?: VendorType | null;
contactPerson?: string | null;
phone?: string | null;
email?: string | null;
address?: string | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface AssetAcquisition {
id: string;
vehicleId?: string | null;
vendorId?: string | null;
acquisitionType: AcquisitionType;
acquisitionDate: string;
cost?: number | null;
usefulLifeMonths?: number | null;
salvageValue?: number | null;
leaseStart?: string | null;
leaseEnd?: string | null;
monthlyPayment?: number | null;
status: AcquisitionStatus;
notes?: string | null;
vehicle?: {
id: string;
plateNumber?: string | null;
registrationNumber?: string | null;
manufacturer?: string | null;
model?: string | null;
} | null;
vendor?: Vendor | null;
createdAt: string;
updatedAt: string;
}
export interface AssetDisposal {
id: string;
vehicleId: string;
disposalDate: string;
method: DisposalMethod;
salePrice?: number | null;
buyer?: string | null;
notes?: string | null;
createdAt: string;
updatedAt: string;
}
export interface DepreciationResult {
method: "STRAIGHT_LINE";
cost: number;
salvageValue: number;
usefulLifeMonths: number;
monthsElapsed: number;
monthlyDepreciation: number;
bookValue: number;
}
export interface LifecycleResult {
vehicleId: string;
acquisition: AssetAcquisition | null;
disposal: AssetDisposal | null;
depreciation: DepreciationResult | null;
}
export interface CreateVendorPayload {
name: string;
type?: VendorType;
contactPerson?: string;
phone?: string;
email?: string;
address?: string;
isActive?: boolean;
}
export interface CreateAcquisitionPayload {
vehicleId?: string;
vendorId?: string;
acquisitionType: AcquisitionType;
acquisitionDate: string;
cost?: number;
usefulLifeMonths?: number;
salvageValue?: number;
leaseStart?: string;
leaseEnd?: string;
monthlyPayment?: number;
status?: AcquisitionStatus;
notes?: string;
}
export interface CreateDisposalPayload {
vehicleId: string;
disposalDate: string;
method: DisposalMethod;
salePrice?: number;
buyer?: string;
notes?: string;
}
export const procurementService = {
// Vendors
listVendors: () => api.get<Vendor[]>("/procurement/vendors"),
createVendor: (data: CreateVendorPayload) => api.post("/procurement/vendors", data),
updateVendor: (id: string, data: Partial<CreateVendorPayload>) =>
api.patch(`/procurement/vendors/${id}`, data),
deleteVendor: (id: string) => api.delete(`/procurement/vendors/${id}`),
// Acquisitions
listAcquisitions: (vehicleId?: string) =>
api.get<AssetAcquisition[]>(
`/procurement/acquisitions${vehicleId ? `?vehicleId=${vehicleId}` : ""}`,
),
getAcquisition: (id: string) => api.get<AssetAcquisition>(`/procurement/acquisitions/${id}`),
createAcquisition: (data: CreateAcquisitionPayload) =>
api.post("/procurement/acquisitions", data),
updateAcquisition: (id: string, data: Partial<CreateAcquisitionPayload>) =>
api.patch(`/procurement/acquisitions/${id}`, data),
deleteAcquisition: (id: string) => api.delete(`/procurement/acquisitions/${id}`),
// Disposals
listDisposals: () => api.get<AssetDisposal[]>("/procurement/disposals"),
createDisposal: (data: CreateDisposalPayload) => api.post("/procurement/disposals", data),
deleteDisposal: (id: string) => api.delete(`/procurement/disposals/${id}`),
// Lifecycle
lifecycle: (vehicleId: string) =>
api.get<LifecycleResult>(`/procurement/lifecycle/${vehicleId}`),
};

View File

@@ -4,6 +4,9 @@ import { URL_CONSTANTS } from '@/constants/URLS';
export type RouteStatus = 'AVAILABLE' | 'MAINTENANCE' | 'DAMAGED' | 'STOP_WORKING';
/** Frozen from yard countries: ET→DJ EXPORT, DJ→ET IMPORT, same country DOMESTIC (intercity, disabled). */
export type RouteDirection = 'IMPORT' | 'EXPORT' | 'DOMESTIC';
export interface YardRef {
id: string;
code: string;
@@ -23,6 +26,7 @@ export interface RouteMilestone {
export interface RouteRecord {
id: string;
status: RouteStatus;
direction?: RouteDirection;
originYardId: string;
destinationYardId: string;
originYard?: YardRef | null;

View File

@@ -17,6 +17,8 @@ import type {
ImportDjiboutiLoadList,
ImportDjiboutiOperation,
ImportLoadingBookingsResponse,
IntercityAcceptResult,
IntercityCandidatesResult,
LoadingStatus,
LocomotiveRecord,
PinWagonsPayload,
@@ -328,6 +330,46 @@ export const trainSchedulingService = {
return unwrap(response.data);
},
getIntercityCandidates: async (
scheduleId: string,
): Promise<IntercityCandidatesResult> => {
const response = await client.get<IntercityCandidatesResult>(
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_CANDIDATES(scheduleId),
);
return unwrap(response.data);
},
acceptIntercityBookings: async (
scheduleId: string,
bookingIds: string[],
): Promise<IntercityAcceptResult> => {
const response = await client.post<IntercityAcceptResult>(
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_ACCEPT(scheduleId),
{ bookingIds },
);
return unwrap(response.data);
},
loadIntercityBooking: async (
scheduleId: string,
bookingId: string,
): Promise<void> => {
await client.post(
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_LOAD(scheduleId, bookingId),
{},
);
},
unloadIntercityBooking: async (
scheduleId: string,
bookingId: string,
): Promise<void> => {
await client.post(
URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_UNLOAD(scheduleId, bookingId),
{},
);
},
dispatchSchedule: async (
scheduleId: string,
): Promise<TrainScheduleDetail> => {

View File

@@ -38,6 +38,9 @@ export interface Vehicle {
/** Odometer-derived distances (API sends numeric strings; coerce with Number). */
estimatedDistanceKm?: number | null;
actualDistanceKm?: number | null;
/** Haulage rate per km + its currency (ETB | USD). */
pricePerKm?: number | null;
currency?: string | null;
createdAt: string;
updatedAt: string;
}