158 lines
5.9 KiB
C#
158 lines
5.9 KiB
C#
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<PagedResponse<RideResponse>> 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<RideResponse>(page, pageSize, total, items.Select(ToResponse).ToList());
|
|
}
|
|
|
|
public async Task<RideResponse?> GetByIdAsync(Guid id)
|
|
{
|
|
var ride = await rideRepo.GetByIdAsync(id);
|
|
return ride is null ? null : ToResponse(ride);
|
|
}
|
|
|
|
public async Task<RideResponse> 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<RideResponse> 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<RideResponse> 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<RideResponse> 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);
|
|
}
|