using Distribution.Features.Stock; using Distribution.Features.DriverComplianceConfig; using Distribution.Features.DriverProfile; using Distribution.Infra.UoW; using Distribution.Models; namespace Distribution.Features.Ride; public class RideService( IRideRepository rideRepo, IDriverProfileRepository driverRepo, IDriverComplianceConfigRepository complianceRepo, StockMover mover, IUnitOfWork uow) { public async Task> GetAllAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize) { var total = await rideRepo.GetCountAsync(status, driverProfileId, vehicleId); var items = await rideRepo.GetAllAsync(status, driverProfileId, vehicleId, page, pageSize); return new PagedResponse(page, pageSize, total, items.Select(ToResponse).ToList()); } public async Task GetByIdAsync(Guid id) { var ride = await rideRepo.GetByIdAsync(id); return ride is null ? null : ToResponse(ride); } public async Task CreateAsync(CreateRideRequest request, CancellationToken ct = default) { if (await rideRepo.HasOpenRideForVehicleAsync(request.VehicleId)) throw new InvalidOperationException("Vehicle already has an open ride"); await CheckDriverComplianceAsync(request.DriverProfileId); var ride = new DeliveryRun { FK_VehicleId = request.VehicleId, FK_DriverProfileId = request.DriverProfileId, Status = RideStatus.Open, Meterstart = request.Meterstart, StartedAt = DateTime.Now }; await uow.BeginAsync(ct); try { await rideRepo.AddAsync(ride); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } var saved = await rideRepo.GetByIdAsync(ride.Id); return ToResponse(saved!); } public async Task AssignDriverAsync(Guid rideId, Guid driverProfileId, CancellationToken ct = default) { var ride = await rideRepo.GetByIdAsync(rideId) ?? throw new KeyNotFoundException("Ride not found"); if (ride.Status != RideStatus.Open) throw new InvalidOperationException("Driver can only be changed while the ride is Open"); var driver = await driverRepo.GetByIdAsync(driverProfileId); if (driver is null || !driver.IsActive) throw new InvalidOperationException("Driver profile not found or inactive"); ride.FK_DriverProfileId = driverProfileId; await uow.BeginAsync(ct); try { rideRepo.Update(ride); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } var saved = await rideRepo.GetByIdAsync(rideId); return ToResponse(saved!); } public async Task CloseAsync(Guid rideId, CloseRideRequest request, Guid userId, CancellationToken ct = default) { var ride = await rideRepo.GetByIdAsync(rideId) ?? throw new KeyNotFoundException("Ride not found"); if (ride.Status != RideStatus.Open) throw new InvalidOperationException($"Cannot close a ride in {ride.Status} status"); if (request.Meterend < ride.Meterstart) throw new InvalidOperationException($"Meter end ({request.Meterend}) cannot be less than meter start ({ride.Meterstart})"); ride.Status = RideStatus.Closed; ride.ClosedAt = DateTime.Now; ride.Meterend = request.Meterend; // Whatever wasn't delivered goes back to the warehouse it was loaded from. await uow.BeginAsync(ct); try { await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideClosed"); rideRepo.Update(ride); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(ride); } public async Task CancelAsync(Guid rideId, CancelRideRequest request, Guid userId, CancellationToken ct = default) { var ride = await rideRepo.GetByIdAsync(rideId) ?? throw new KeyNotFoundException("Ride not found"); if (ride.Status != RideStatus.Open) throw new InvalidOperationException($"Cannot cancel a ride in {ride.Status} status"); ride.Status = RideStatus.Cancelled; ride.CancelTypeCode = request.TypeCode; ride.CancelReason = request.Reason; await uow.BeginAsync(ct); try { await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideCancelled"); rideRepo.Update(ride); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(ride); } private async Task CheckDriverComplianceAsync(Guid driverProfileId) { var driver = await driverRepo.GetByIdAsync(driverProfileId) ?? throw new InvalidOperationException("Driver profile not found"); if (!driver.IsActive) throw new InvalidOperationException("Driver profile is inactive"); var config = await complianceRepo.GetAsync(); if (config is null) return; var today = DateTime.Now.Date; if (config.EnforceLicenseExpiry && driver.LicenseExpiry.Date <= today) throw new InvalidOperationException("Driver license has expired"); if (config.EnforceMedicalClearance && driver.MedicalClearanceExpiry.Date <= today) throw new InvalidOperationException("Driver medical clearance has expired"); } private static RideResponse ToResponse(DeliveryRun r) => new(r.Id, r.Status, r.FK_VehicleId, r.Vehicle?.NumberPlate ?? string.Empty, r.FK_DriverProfileId, r.DriverProfile?.User?.FullName ?? string.Empty, r.Meterstart, r.Meterend, r.StartedAt, r.ClosedAt, r.CancelTypeCode, r.CancelReason, r.CreatedAt); }