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 43f02442a..20048c94d 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 @@ -550,6 +550,34 @@ export class FirstMileService { previousVehicleIds.filter((id) => !vehicleIds.has(id)), ); + // History: one event per vehicle actually added or removed by this + // multi-car (re)allocation, so reassignments show on every timeline. + const prevSet = new Set(previousVehicleIds); + const bookingRef = await this.resolveBookingRef(firstMile); + for (const vehicleId of vehicleIds) { + if (prevSet.has(vehicleId)) continue; + const info = await this.vehicleInfo(vehicleId); + await this.history.record({ + eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, + vehicleId, + firstMileId, + driverId: info.driverId, + label: firstMile.status, + metadata: { mile: 'FIRST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName }, + }); + } + for (const vehicleId of previousVehicleIds) { + if (vehicleIds.has(vehicleId)) continue; + const info = await this.vehicleInfo(vehicleId); + await this.history.record({ + eventType: FleetEventType.MILE_VEHICLE_RELEASED, + vehicleId, + firstMileId, + driverId: info.driverId, + metadata: { mile: 'FIRST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName }, + }); + } + return { success: true, allocated: allocations.length, 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 1e952b1ce..811ddd36b 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 @@ -1,5 +1,5 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { DataSource, FindOptionsWhere } from 'typeorm'; +import { DataSource, FindOptionsWhere, In } from 'typeorm'; import { BookingsRepository } from '../bookings/bookings.repository'; import { DriversService } from '../drivers/drivers.service'; @@ -348,6 +348,21 @@ export class LastMileService { throw new NotFoundException(`Last-mile record ${lastMileId} not found`); } + // Capture the vehicles currently on these containers so a reallocation can + // be diffed into assigned/released history events below. + const previousAllocations = await this.dataSource.manager.find( + LastMileContainerAllocation, + { + where: { + lastMileId, + containerId: In(allocations.map((a) => a.containerId)), + }, + }, + ); + const previousVehicleIds = previousAllocations + .map((a) => a.vehicleId) + .filter((id): id is string => Boolean(id)); + await this.dataSource.transaction(async (manager) => { for (const allocation of allocations) { await manager.delete(LastMileContainerAllocation, { @@ -364,6 +379,35 @@ export class LastMileService { } }); + // History: one event per vehicle actually added or removed by this + // multi-car (re)allocation, so reassignments show on every timeline. + const vehicleIds = new Set(allocations.map((a) => a.vehicleId)); + const prevSet = new Set(previousVehicleIds); + const bookingRef = await this.resolveBookingRef(lastMile); + for (const vehicleId of vehicleIds) { + if (prevSet.has(vehicleId)) continue; + const info = await this.vehicleInfo(vehicleId); + await this.history.record({ + eventType: FleetEventType.MILE_VEHICLE_ASSIGNED, + vehicleId, + lastMileId, + driverId: info.driverId, + label: lastMile.status, + metadata: { mile: 'LAST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName }, + }); + } + for (const vehicleId of previousVehicleIds) { + if (vehicleIds.has(vehicleId)) continue; + const info = await this.vehicleInfo(vehicleId); + await this.history.record({ + eventType: FleetEventType.MILE_VEHICLE_RELEASED, + vehicleId, + lastMileId, + driverId: info.driverId, + metadata: { mile: 'LAST', bookingRef, vehiclePlate: info.plate, driverName: info.driverName }, + }); + } + return { success: true, allocated: allocations.length,