From c643d30624825009a96d2f537f0c9fcfa39fc330 Mon Sep 17 00:00:00 2001 From: natib21 Date: Fri, 3 Jul 2026 15:47:12 +0000 Subject: [PATCH] fix --- .../modules/first-mile/first-mile.service.ts | 83 +++++++++++++++---- .../modules/last-mile/last-mile.service.ts | 60 ++++++++++---- .../src/modules/vehicles/vehicles.service.ts | 7 ++ .../components/fleet/FleetHistoryModal.tsx | 52 ++++++++---- 4 files changed, 157 insertions(+), 45 deletions(-) diff --git a/apps/edr-freight-api/src/modules/first-mile/first-mile.service.ts b/apps/edr-freight-api/src/modules/first-mile/first-mile.service.ts index 419b69969..43f02442a 100644 --- a/apps/edr-freight-api/src/modules/first-mile/first-mile.service.ts +++ b/apps/edr-freight-api/src/modules/first-mile/first-mile.service.ts @@ -48,13 +48,33 @@ export class FirstMileService { private readonly history: FleetHistoryService, ) { } - /** Resolve the driver currently assigned to a vehicle, for stamping mile - * events onto that driver's timeline. Best-effort — never throws. */ - private async resolveDriverId(vehicleId?: string | null): Promise { - if (!vehicleId) return null; + /** Resolve a vehicle's driver + human labels, for stamping mile events onto + * the driver's timeline and naming the vehicle. Best-effort — never throws. */ + private async vehicleInfo( + vehicleId?: string | null, + ): Promise<{ driverId: string | null; plate: string | null; driverName: string | null }> { + if (!vehicleId) return { driverId: null, plate: null, driverName: null }; try { - const vehicle = await this.vehiclesService.findById(vehicleId); - return vehicle.assignedDriverId ?? null; + const v = await this.vehiclesService.findById(vehicleId); + return { + driverId: v.assignedDriverId ?? null, + plate: v.plateNumber ?? v.code ?? null, + driverName: v.assignedDriverName ?? null, + }; + } catch { + return { driverId: null, plate: null, driverName: null }; + } + } + + /** Human booking reference for a first-mile record, for the history timeline. */ + private async resolveBookingRef(record: FirstMile): Promise { + const loaded = (record as FirstMile & { booking?: { reference?: string } }) + .booking?.reference; + if (loaded) return loaded; + if (!record.bookingId) return null; + try { + const b = await this.bookingsRepository.findById(record.bookingId); + return (b as { reference?: string } | null)?.reference ?? null; } catch { return null; } @@ -225,13 +245,19 @@ export class FirstMileService { if (dto.vehicleId) { await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY); + const info = await this.vehicleInfo(dto.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, vehicleId: dto.vehicleId, firstMileId: record.id, - driverId: await this.resolveDriverId(dto.vehicleId), + driverId: info.driverId, label: record.status, - metadata: { mile: 'FIRST' }, + metadata: { + mile: 'FIRST', + bookingRef: await this.resolveBookingRef(record), + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } @@ -302,23 +328,36 @@ export class FirstMileService { await this.vehiclesService.releaseIfUnused([existing.vehicleId]); } // Audit the mile↔vehicle (re)assignment on both vehicle and driver lines. + const bookingRef = await this.resolveBookingRef(existing); if (existing.vehicleId) { + const info = await this.vehicleInfo(existing.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_RELEASED, vehicleId: existing.vehicleId, firstMileId: id, - driverId: await this.resolveDriverId(existing.vehicleId), - metadata: { mile: 'FIRST' }, + driverId: info.driverId, + metadata: { + mile: 'FIRST', + bookingRef, + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } if (dto.vehicleId) { + const info = await this.vehicleInfo(dto.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, vehicleId: dto.vehicleId, firstMileId: id, - driverId: await this.resolveDriverId(dto.vehicleId), + driverId: info.driverId, label: updated.status, - metadata: { mile: 'FIRST' }, + metadata: { + mile: 'FIRST', + bookingRef, + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } } @@ -330,14 +369,20 @@ export class FirstMileService { if (dto.status !== undefined && dto.status !== existing.status) { const vehicleId = updated.vehicleId ?? existing.vehicleId ?? null; + const info = await this.vehicleInfo(vehicleId); await this.history.record({ eventType: FleetEventType.MILE_STATUS_CHANGED, firstMileId: id, vehicleId, - driverId: await this.resolveDriverId(vehicleId), + driverId: info.driverId, fromValue: existing.status, toValue: dto.status, - metadata: { mile: 'FIRST' }, + metadata: { + mile: 'FIRST', + bookingRef: await this.resolveBookingRef(existing), + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } @@ -359,14 +404,20 @@ export class FirstMileService { if (status !== existing.status) { const vehicleId = updated.vehicleId ?? existing.vehicleId ?? null; + const info = await this.vehicleInfo(vehicleId); await this.history.record({ eventType: FleetEventType.MILE_STATUS_CHANGED, firstMileId: id, vehicleId, - driverId: await this.resolveDriverId(vehicleId), + driverId: info.driverId, fromValue: existing.status, toValue: status, - metadata: { mile: 'FIRST' }, + metadata: { + mile: 'FIRST', + bookingRef: await this.resolveBookingRef(existing), + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } diff --git a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts index f5c9696e6..1e952b1ce 100644 --- a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts +++ b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts @@ -46,15 +46,21 @@ export class LastMileService { private readonly history: FleetHistoryService, ) {} - /** Resolve the driver currently assigned to a vehicle, for stamping mile - * events onto that driver's timeline. Best-effort — never throws. */ - private async resolveDriverId(vehicleId?: string | null): Promise { - if (!vehicleId) return null; + /** Resolve a vehicle's driver + human labels, for stamping mile events onto + * the driver's timeline and naming the vehicle. Best-effort — never throws. */ + private async vehicleInfo( + vehicleId?: string | null, + ): Promise<{ driverId: string | null; plate: string | null; driverName: string | null }> { + if (!vehicleId) return { driverId: null, plate: null, driverName: null }; try { - const vehicle = await this.vehiclesService.findById(vehicleId); - return vehicle.assignedDriverId ?? null; + const v = await this.vehiclesService.findById(vehicleId); + return { + driverId: v.assignedDriverId ?? null, + plate: v.plateNumber ?? v.code ?? null, + driverName: v.assignedDriverName ?? null, + }; } catch { - return null; + return { driverId: null, plate: null, driverName: null }; } } @@ -176,13 +182,19 @@ export class LastMileService { }); if (dto.vehicleId) { + const info = await this.vehicleInfo(dto.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, vehicleId: dto.vehicleId, lastMileId: record.id, - driverId: await this.resolveDriverId(dto.vehicleId), + driverId: info.driverId, label: record.status, - metadata: { mile: 'LAST', bookingRef: await this.resolveBookingRef(record) }, + metadata: { + mile: 'LAST', + bookingRef: await this.resolveBookingRef(record), + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } @@ -229,36 +241,54 @@ export class LastMileService { const bookingRef = await this.resolveBookingRef(existing); if (dto.vehicleId !== undefined && dto.vehicleId !== existing.vehicleId) { if (existing.vehicleId) { + const info = await this.vehicleInfo(existing.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_RELEASED, vehicleId: existing.vehicleId, lastMileId: id, - driverId: await this.resolveDriverId(existing.vehicleId), - metadata: { mile: 'LAST', bookingRef }, + driverId: info.driverId, + metadata: { + mile: 'LAST', + bookingRef, + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } if (dto.vehicleId) { + const info = await this.vehicleInfo(dto.vehicleId); await this.history.record({ eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, vehicleId: dto.vehicleId, lastMileId: id, - driverId: await this.resolveDriverId(dto.vehicleId), + driverId: info.driverId, label: updated.status, - metadata: { mile: 'LAST', bookingRef }, + metadata: { + mile: 'LAST', + bookingRef, + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } } if (dto.status !== undefined && dto.status !== existing.status) { const vehicleId = updated.vehicleId ?? existing.vehicleId ?? null; + const info = await this.vehicleInfo(vehicleId); await this.history.record({ eventType: FleetEventType.MILE_STATUS_CHANGED, lastMileId: id, vehicleId, - driverId: await this.resolveDriverId(vehicleId), + driverId: info.driverId, fromValue: existing.status, toValue: dto.status, - metadata: { mile: 'LAST', bookingRef }, + metadata: { + mile: 'LAST', + bookingRef, + vehiclePlate: info.plate, + driverName: info.driverName, + }, }); } diff --git a/apps/edr-freight-api/src/modules/vehicles/vehicles.service.ts b/apps/edr-freight-api/src/modules/vehicles/vehicles.service.ts index 345d4f896..25260e86f 100644 --- a/apps/edr-freight-api/src/modules/vehicles/vehicles.service.ts +++ b/apps/edr-freight-api/src/modules/vehicles/vehicles.service.ts @@ -51,6 +51,10 @@ export class VehiclesService { vehicleId: saved.id, driverId: saved.assignedDriverId, label: saved.assignedDriverName ?? null, + metadata: { + vehiclePlate: saved.plateNumber ?? saved.code ?? null, + driverName: saved.assignedDriverName ?? null, + }, }); } @@ -133,12 +137,14 @@ export class VehiclesService { dto.assignedDriverId !== undefined && dto.assignedDriverId !== prev.assignedDriverId ) { + const vehiclePlate = saved.plateNumber ?? saved.code ?? null; if (prev.assignedDriverId) { await this.history.record({ eventType: FleetEventType.DRIVER_UNASSIGNED, vehicleId: id, driverId: prev.assignedDriverId, label: prev.assignedDriverName ?? null, + metadata: { vehiclePlate, driverName: prev.assignedDriverName ?? null }, }); } if (saved.assignedDriverId) { @@ -147,6 +153,7 @@ export class VehiclesService { vehicleId: id, driverId: saved.assignedDriverId, label: saved.assignedDriverName ?? null, + metadata: { vehiclePlate, driverName: saved.assignedDriverName ?? null }, }); } } diff --git a/apps/edr-freight-web/backoffice/src/components/fleet/FleetHistoryModal.tsx b/apps/edr-freight-web/backoffice/src/components/fleet/FleetHistoryModal.tsx index 0e7a0e65b..43f2bce4f 100644 --- a/apps/edr-freight-web/backoffice/src/components/fleet/FleetHistoryModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/fleet/FleetHistoryModal.tsx @@ -41,11 +41,24 @@ const mileLabel = (e: FleetHistoryEvent) => const arrow = (from?: string | null, to?: string | null) => `${from ?? "—"} → ${to ?? "—"}`; +const metaStr = (e: FleetHistoryEvent, key: string) => { + const v = e.metadata?.[key]; + return typeof v === "string" && v ? v : null; +}; + function describe(e: FleetHistoryEvent, entity: "driver" | "vehicle") { - const bookingRef = - typeof e.metadata?.bookingRef === "string" ? e.metadata.bookingRef : null; - const withBooking = (rest?: string) => - [bookingRef ? `Booking ${bookingRef}` : "", rest ?? ""] + const vehiclePlate = metaStr(e, "vehiclePlate"); + const driverName = metaStr(e, "driverName") ?? (e.label || null); + const bookingRef = metaStr(e, "bookingRef"); + + // Compose the detail line with whatever the current view doesn't already + // know: on a driver's timeline show which vehicle; always show the booking. + const detail = (extra?: string) => + [ + entity === "driver" && vehiclePlate ? `Vehicle ${vehiclePlate}` : "", + bookingRef ? `Booking ${bookingRef}` : "", + extra ?? "", + ] .filter(Boolean) .join(" · "); @@ -65,20 +78,31 @@ function describe(e: FleetHistoryEvent, entity: "driver" | "vehicle") { case "DRIVER_ASSIGNED": return { icon: , - title: + title: entity === "vehicle" ? "Driver assigned" : "Assigned to vehicle", + text: entity === "vehicle" - ? `Driver assigned${e.label ? `: ${e.label}` : ""}` - : "Assigned to a vehicle", - text: "", + ? driverName + ? `Driver ${driverName}` + : "" + : vehiclePlate + ? `Vehicle ${vehiclePlate}` + : "", }; case "DRIVER_UNASSIGNED": return { icon: , title: entity === "vehicle" - ? `Driver unassigned${e.label ? `: ${e.label}` : ""}` - : "Unassigned from a vehicle", - text: "", + ? "Driver unassigned" + : "Unassigned from vehicle", + text: + entity === "vehicle" + ? driverName + ? `Driver ${driverName}` + : "" + : vehiclePlate + ? `Vehicle ${vehiclePlate}` + : "", }; case "VEHICLE_STATUS_CHANGED": return { @@ -96,19 +120,19 @@ function describe(e: FleetHistoryEvent, entity: "driver" | "vehicle") { return { icon: , title: `${mileLabel(e)}: vehicle assigned`, - text: withBooking(e.label ? `Status: ${e.label}` : ""), + text: detail(e.label ? `Status: ${e.label}` : ""), }; case "MILE_VEHICLE_RELEASED": return { icon: , title: `${mileLabel(e)}: vehicle released`, - text: withBooking(), + text: detail(), }; case "MILE_STATUS_CHANGED": return { icon: , title: `${mileLabel(e)} status`, - text: withBooking(arrow(e.fromValue, e.toValue)), + text: detail(arrow(e.fromValue, e.toValue)), }; default: return { icon: , title: e.eventType, text: "" };