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 811ddd36b..ade7402a9 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 @@ -5,6 +5,7 @@ import { BookingsRepository } from '../bookings/bookings.repository'; import { DriversService } from '../drivers/drivers.service'; import { SmsClientService } from '../notifications/sms-client.service'; import { VehiclesService } from '../vehicles/vehicles.service'; +import { VehicleAvailability } from '../vehicles/entities/vehicle.entity'; import { CreateLastMileDto } from './dto/create-last-mile.dto'; import { UpdateLastMileDto } from './dto/update-last-mile.dto'; import { LastMile, LastMileStatus } from './entities/last-mile.entity'; @@ -182,6 +183,7 @@ export class LastMileService { }); 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, @@ -240,6 +242,14 @@ export class LastMileService { // Audit the mile↔vehicle (re)assignment on both vehicle and driver lines. const bookingRef = await this.resolveBookingRef(existing); if (dto.vehicleId !== undefined && dto.vehicleId !== existing.vehicleId) { + // Keep vehicle availability in sync: new vehicle goes BUSY, replaced one + // is freed if no other active trip still holds it. + if (dto.vehicleId) { + await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY); + } + if (existing.vehicleId) { + await this.vehiclesService.releaseIfUnused([existing.vehicleId]); + } if (existing.vehicleId) { const info = await this.vehicleInfo(existing.vehicleId); await this.history.record({ @@ -292,9 +302,32 @@ export class LastMileService { }); } + // Delivery finished — free the vehicles this trip was holding. + if (dto.status === 'DELIVERED' && existing.status !== 'DELIVERED') { + await this.releaseVehicles(updated); + } + return updated; } + /** + * Free every vehicle held by this record (direct assignment + container + * allocations), unless still in use by another active trip. + */ + private async releaseVehicles(record: LastMile): Promise { + const recordAllocations = await this.dataSource.manager.find( + LastMileContainerAllocation, + { where: { lastMileId: record.id } }, + ); + const vehicleIds = recordAllocations + .map((a) => a.vehicleId) + .filter((id): id is string => Boolean(id)); + if (record.vehicleId) { + vehicleIds.push(record.vehicleId); + } + await this.vehiclesService.releaseIfUnused(vehicleIds); + } + private async notifyDriverAssignment(vehicleId: string, record: LastMile): Promise { try { const vehicle = await this.vehiclesService.findById(vehicleId); @@ -379,9 +412,20 @@ export class LastMileService { } }); + // Keep vehicle availability in sync: newly-allocated cars go BUSY, cars no + // longer on any of these containers are freed if unused elsewhere. + const vehicleIds = new Set(allocations.map((a) => a.vehicleId)); + await Promise.all( + [...vehicleIds].map((id) => + this.vehiclesService.setAvailability(id, VehicleAvailability.BUSY), + ), + ); + await this.vehiclesService.releaseIfUnused( + 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 vehicleIds = new Set(allocations.map((a) => a.vehicleId)); const prevSet = new Set(previousVehicleIds); const bookingRef = await this.resolveBookingRef(lastMile); for (const vehicleId of vehicleIds) {