93f2f1bd3b
Major refactor replacing Sale/Order with ShopPO → Invoice → LoadingSheet flow. Removed legacy Sale/Order models and features; introduced ShopPO, ShopPOItem, Invoice, LoadingSheetOrder, and SysConfig models. Updated related entities, enums, repositories, and controllers. Added new migrations for schema changes and system config. Overhauled business logic and DI registrations. Updated documentation with migration plan.
154 lines
5.8 KiB
C#
154 lines
5.8 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,
|
|
IStockRepository stockRepo,
|
|
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, 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})");
|
|
|
|
await EnsureVehicleStockClearedAsync(ride.FK_VehicleId);
|
|
|
|
ride.Status = RideStatus.Closed;
|
|
ride.ClosedAt = DateTime.Now;
|
|
ride.Meterend = request.Meterend;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { 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, 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;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { 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 async Task EnsureVehicleStockClearedAsync(Guid vehicleId)
|
|
{
|
|
var balances = await stockRepo.GetBalancesByLocationAsync(vehicleId, LocationType.Vehicle);
|
|
if (balances.Any(b => b.Quantity > 0))
|
|
throw new InvalidOperationException("All vehicle stock must be returned before this ride can be completed");
|
|
}
|
|
|
|
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.CreatedAt);
|
|
}
|