From 76ca9f874d68f37a8a870343873d20f921e54be8 Mon Sep 17 00:00:00 2001 From: "Kalana B. Thilakarathna" Date: Mon, 31 Aug 2026 12:14:30 +0530 Subject: [PATCH] ride reconcilation with driver profile update --- Distribution/Data/AppDBContext.cs | 20 + .../DriverComplianceConfigDTOs.cs | 12 - .../DriverComplianceConfigRepository.cs | 16 - .../DriverComplianceConfigService.cs | 62 - .../IDriverComplianceConfigRepository.cs | 8 - .../DriverProfile/DriverProfileController.cs | 6 +- .../DriverProfile/DriverProfileDTOs.cs | 11 + .../DriverProfile/DriverProfileRepository.cs | 9 + .../DriverProfile/DriverProfileService.cs | 50 + .../DriverProfile/IDriverProfileRepository.cs | 4 + .../IReconciliationRepository.cs | 21 + .../ReconciliationController.cs | 73 + .../Reconciliation/ReconciliationDTOs.cs | 19 + .../ReconciliationRepository.cs | 51 + .../Reconciliation/ReconciliationService.cs | 396 +++ Distribution/Features/Ride/RideService.cs | 8 +- ...30055526_AddRideReconciliation.Designer.cs | 2828 +++++++++++++++++ .../20260830055526_AddRideReconciliation.cs | 167 + .../Migrations/AppDBContextModelSnapshot.cs | 185 ++ Distribution/Models/Enums.cs | 4 +- Distribution/Models/RideCloseSummary.cs | 21 + Distribution/Models/RideItemCount.cs | 17 + Distribution/Models/RideVariance.cs | 30 + Distribution/Program.cs | 6 +- 24 files changed, 3912 insertions(+), 112 deletions(-) delete mode 100644 Distribution/Features/DriverComplianceConfig/DriverComplianceConfigDTOs.cs delete mode 100644 Distribution/Features/DriverComplianceConfig/DriverComplianceConfigRepository.cs delete mode 100644 Distribution/Features/DriverComplianceConfig/DriverComplianceConfigService.cs delete mode 100644 Distribution/Features/DriverComplianceConfig/IDriverComplianceConfigRepository.cs create mode 100644 Distribution/Features/Reconciliation/IReconciliationRepository.cs create mode 100644 Distribution/Features/Reconciliation/ReconciliationController.cs create mode 100644 Distribution/Features/Reconciliation/ReconciliationDTOs.cs create mode 100644 Distribution/Features/Reconciliation/ReconciliationRepository.cs create mode 100644 Distribution/Features/Reconciliation/ReconciliationService.cs create mode 100644 Distribution/Migrations/20260830055526_AddRideReconciliation.Designer.cs create mode 100644 Distribution/Migrations/20260830055526_AddRideReconciliation.cs create mode 100644 Distribution/Models/RideCloseSummary.cs create mode 100644 Distribution/Models/RideItemCount.cs create mode 100644 Distribution/Models/RideVariance.cs diff --git a/Distribution/Data/AppDBContext.cs b/Distribution/Data/AppDBContext.cs index 79a4e39..ebb8ea6 100644 --- a/Distribution/Data/AppDBContext.cs +++ b/Distribution/Data/AppDBContext.cs @@ -60,6 +60,9 @@ public class AppDBContext(DbContextOptions options) : DbContext(op public DbSet RegionalBudgets { get; set; } public DbSet RegionAgencyMaps { get; set; } public DbSet RegionalTaxRules { get; set; } + public DbSet RideItemCounts { get; set; } + public DbSet RideVariances { get; set; } + public DbSet RideCloseSummaries { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -399,6 +402,23 @@ public class AppDBContext(DbContextOptions options) : DbContext(op .HasIndex(r => r.FK_RegionId) .IsUnique(); + modelBuilder.Entity() + .HasOne(v => v.ResolvedBy) + .WithMany() + .HasForeignKey(v => v.FK_ResolvedByUserId) + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(false); + + modelBuilder.Entity() + .HasIndex(s => s.FK_RideId) + .IsUnique(); + + modelBuilder.Entity() + .HasOne(s => s.ClosedByUser) + .WithMany() + .HasForeignKey(s => s.FK_ClosedByUserId) + .OnDelete(DeleteBehavior.Restrict); + Seed.RoleAndPermissionSeed.Apply(modelBuilder); } } diff --git a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigDTOs.cs b/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigDTOs.cs deleted file mode 100644 index 393729d..0000000 --- a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigDTOs.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Distribution.Features.DriverComplianceConfig; - -public record DriverComplianceConfigResponse( - Guid Id, - bool EnforceLicenseExpiry, - bool EnforceMedicalClearance, - DateTime UpdatedAt, - Guid UpdatedBy); - -public record UpdateDriverComplianceConfigRequest( - bool EnforceLicenseExpiry, - bool EnforceMedicalClearance); diff --git a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigRepository.cs b/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigRepository.cs deleted file mode 100644 index e3bdd91..0000000 --- a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigRepository.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Distribution.Data; -using Microsoft.EntityFrameworkCore; - -namespace Distribution.Features.DriverComplianceConfig; - -public class DriverComplianceConfigRepository(AppDBContext context) : IDriverComplianceConfigRepository -{ - public async Task GetAsync() => - await context.DriverComplianceConfigs.FirstOrDefaultAsync(); - - public async Task AddAsync(Models.DriverComplianceConfig config) => - await context.DriverComplianceConfigs.AddAsync(config); - - public void Update(Models.DriverComplianceConfig config) => - context.DriverComplianceConfigs.Update(config); -} diff --git a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigService.cs b/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigService.cs deleted file mode 100644 index bb541ee..0000000 --- a/Distribution/Features/DriverComplianceConfig/DriverComplianceConfigService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Distribution.Infra.UoW; - -namespace Distribution.Features.DriverComplianceConfig; - -public class DriverComplianceConfigService(IDriverComplianceConfigRepository repo, IUnitOfWork uow) -{ - public async Task GetAsync() - { - var config = await repo.GetAsync(); - return config is null - ? null - : new DriverComplianceConfigResponse( - config.Id, - config.EnforceLicenseExpiry, - config.EnforceMedicalClearance, - config.UpdatedAt, - config.UpdatedBy); - } - - public async Task UpsertAsync(UpdateDriverComplianceConfigRequest request, Guid updatedBy, CancellationToken ct = default) - { - var config = await repo.GetAsync(); - - await uow.BeginAsync(ct); - try - { - if (config is null) - { - config = new global::Distribution.Models.DriverComplianceConfig - { - EnforceLicenseExpiry = request.EnforceLicenseExpiry, - EnforceMedicalClearance = request.EnforceMedicalClearance, - UpdatedBy = updatedBy, - UpdatedAt = DateTime.Now - }; - await repo.AddAsync(config); - } - else - { - config.EnforceLicenseExpiry = request.EnforceLicenseExpiry; - config.EnforceMedicalClearance = request.EnforceMedicalClearance; - config.UpdatedBy = updatedBy; - config.UpdatedAt = DateTime.Now; - repo.Update(config); - } - - await uow.CommitAsync(ct); - } - catch - { - await uow.RollbackAsync(ct); - throw; - } - - return new DriverComplianceConfigResponse( - config.Id, - config.EnforceLicenseExpiry, - config.EnforceMedicalClearance, - config.UpdatedAt, - config.UpdatedBy); - } -} diff --git a/Distribution/Features/DriverComplianceConfig/IDriverComplianceConfigRepository.cs b/Distribution/Features/DriverComplianceConfig/IDriverComplianceConfigRepository.cs deleted file mode 100644 index b7d16f8..0000000 --- a/Distribution/Features/DriverComplianceConfig/IDriverComplianceConfigRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Distribution.Features.DriverComplianceConfig; - -public interface IDriverComplianceConfigRepository -{ - Task GetAsync(); - Task AddAsync(Models.DriverComplianceConfig config); - void Update(Models.DriverComplianceConfig config); -} diff --git a/Distribution/Features/DriverProfile/DriverProfileController.cs b/Distribution/Features/DriverProfile/DriverProfileController.cs index d22743d..7d0c218 100644 --- a/Distribution/Features/DriverProfile/DriverProfileController.cs +++ b/Distribution/Features/DriverProfile/DriverProfileController.cs @@ -1,4 +1,3 @@ -using Distribution.Features.DriverComplianceConfig; using Distribution.Infra.Authorization; using Microsoft.AspNetCore.Mvc; @@ -8,7 +7,6 @@ namespace Distribution.Features.DriverProfile; [Route("api/driver-profiles")] public class DriverProfileController( DriverProfileService service, - DriverComplianceConfigService complianceService, ICurrentUserService currentUser) : ControllerBase { [HttpGet] @@ -60,7 +58,7 @@ public class DriverProfileController( [HttpGet("compliance")] public async Task GetCompliance() { - var result = await complianceService.GetAsync(); + var result = await service.GetComplianceConfigAsync(); return result is null ? NoContent() : Ok(result); } @@ -68,7 +66,7 @@ public class DriverProfileController( public async Task UpsertCompliance(UpdateDriverComplianceConfigRequest request, CancellationToken ct) { var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); - var result = await complianceService.UpsertAsync(request, userId, ct); + var result = await service.UpsertComplianceConfigAsync(request, userId, ct); return Ok(result); } } diff --git a/Distribution/Features/DriverProfile/DriverProfileDTOs.cs b/Distribution/Features/DriverProfile/DriverProfileDTOs.cs index ba059f2..d73161d 100644 --- a/Distribution/Features/DriverProfile/DriverProfileDTOs.cs +++ b/Distribution/Features/DriverProfile/DriverProfileDTOs.cs @@ -20,3 +20,14 @@ public record UpdateDriverProfileRequest( string LicenseNumber, DateTime LicenseExpiry, DateTime MedicalClearanceExpiry); + +public record DriverComplianceConfigResponse( + Guid Id, + bool EnforceLicenseExpiry, + bool EnforceMedicalClearance, + DateTime UpdatedAt, + Guid UpdatedBy); + +public record UpdateDriverComplianceConfigRequest( + bool EnforceLicenseExpiry, + bool EnforceMedicalClearance); diff --git a/Distribution/Features/DriverProfile/DriverProfileRepository.cs b/Distribution/Features/DriverProfile/DriverProfileRepository.cs index 43350a2..bf922f9 100644 --- a/Distribution/Features/DriverProfile/DriverProfileRepository.cs +++ b/Distribution/Features/DriverProfile/DriverProfileRepository.cs @@ -37,4 +37,13 @@ public class DriverProfileRepository(AppDBContext context) : IDriverProfileRepos profile.IsActive = false; context.DriverProfiles.Update(profile); } + + public async Task GetComplianceConfigAsync() => + await context.DriverComplianceConfigs.FirstOrDefaultAsync(); + + public async Task AddComplianceConfigAsync(global::Distribution.Models.DriverComplianceConfig config) => + await context.DriverComplianceConfigs.AddAsync(config); + + public void UpdateComplianceConfig(global::Distribution.Models.DriverComplianceConfig config) => + context.DriverComplianceConfigs.Update(config); } diff --git a/Distribution/Features/DriverProfile/DriverProfileService.cs b/Distribution/Features/DriverProfile/DriverProfileService.cs index a4bc473..b56e071 100644 --- a/Distribution/Features/DriverProfile/DriverProfileService.cs +++ b/Distribution/Features/DriverProfile/DriverProfileService.cs @@ -98,6 +98,56 @@ public class DriverProfileService(IDriverProfileRepository repo, IUnitOfWork uow return true; } + public async Task GetComplianceConfigAsync() + { + var config = await repo.GetComplianceConfigAsync(); + return config is null ? null + : new DriverComplianceConfigResponse( + config.Id, config.EnforceLicenseExpiry, config.EnforceMedicalClearance, + config.UpdatedAt, config.UpdatedBy); + } + + public async Task UpsertComplianceConfigAsync( + UpdateDriverComplianceConfigRequest request, Guid updatedBy, CancellationToken ct = default) + { + var config = await repo.GetComplianceConfigAsync(); + + await uow.BeginAsync(ct); + try + { + if (config is null) + { + config = new global::Distribution.Models.DriverComplianceConfig + { + EnforceLicenseExpiry = request.EnforceLicenseExpiry, + EnforceMedicalClearance = request.EnforceMedicalClearance, + UpdatedBy = updatedBy, + UpdatedAt = DateTime.Now + }; + await repo.AddComplianceConfigAsync(config); + } + else + { + config.EnforceLicenseExpiry = request.EnforceLicenseExpiry; + config.EnforceMedicalClearance = request.EnforceMedicalClearance; + config.UpdatedBy = updatedBy; + config.UpdatedAt = DateTime.Now; + repo.UpdateComplianceConfig(config); + } + + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + + return new DriverComplianceConfigResponse( + config.Id, config.EnforceLicenseExpiry, config.EnforceMedicalClearance, + config.UpdatedAt, config.UpdatedBy); + } + private static DriverProfileResponse ToResponse(global::Distribution.Models.DriverProfile p) => new(p.Id, p.FK_UserId, p.User?.FullName ?? string.Empty, p.LicenseNumber, p.LicenseExpiry, p.MedicalClearanceExpiry, diff --git a/Distribution/Features/DriverProfile/IDriverProfileRepository.cs b/Distribution/Features/DriverProfile/IDriverProfileRepository.cs index 2132552..30e94ea 100644 --- a/Distribution/Features/DriverProfile/IDriverProfileRepository.cs +++ b/Distribution/Features/DriverProfile/IDriverProfileRepository.cs @@ -9,4 +9,8 @@ public interface IDriverProfileRepository Task AddAsync(global::Distribution.Models.DriverProfile profile); void Update(global::Distribution.Models.DriverProfile profile); void Deactivate(global::Distribution.Models.DriverProfile profile); + + Task GetComplianceConfigAsync(); + Task AddComplianceConfigAsync(global::Distribution.Models.DriverComplianceConfig config); + void UpdateComplianceConfig(global::Distribution.Models.DriverComplianceConfig config); } diff --git a/Distribution/Features/Reconciliation/IReconciliationRepository.cs b/Distribution/Features/Reconciliation/IReconciliationRepository.cs new file mode 100644 index 0000000..3d5616f --- /dev/null +++ b/Distribution/Features/Reconciliation/IReconciliationRepository.cs @@ -0,0 +1,21 @@ +using Distribution.Models; + +namespace Distribution.Features.Reconciliation; + +public interface IReconciliationRepository +{ + Task AddItemCountAsync(RideItemCount count); + Task> GetItemCountsByRideAsync(Guid rideId); + + Task AddVarianceAsync(RideVariance variance); + Task> GetVariancesByRideAsync(Guid rideId); + Task GetVarianceByIdAsync(Guid id); + void UpdateVariance(RideVariance variance); + Task HasPendingVariancesAsync(Guid rideId); + + Task AddCloseSummaryAsync(RideCloseSummary summary); + Task GetCloseSummaryByRideAsync(Guid rideId); + + /// Sum of Cash payments on sales belonging to the given ride. + Task GetCashCollectedByRideAsync(Guid rideId); +} diff --git a/Distribution/Features/Reconciliation/ReconciliationController.cs b/Distribution/Features/Reconciliation/ReconciliationController.cs new file mode 100644 index 0000000..e81bdca --- /dev/null +++ b/Distribution/Features/Reconciliation/ReconciliationController.cs @@ -0,0 +1,73 @@ +using Distribution.Infra.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.Reconciliation; + +[ApiController] +[Route("api/rides/{rideId:guid}")] +public class ReconciliationController(ReconciliationService service, ICurrentUserService currentUser) : ControllerBase +{ + [HttpPost("cycle-count")] + public async Task SubmitCycleCount(Guid rideId, SubmitCycleCountRequest request, CancellationToken ct) + { + var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + try + { + var result = await service.SubmitCycleCountAsync(rideId, request, userId, ct); + return Ok(result); + } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpGet("variances")] + public async Task GetVariances(Guid rideId) + { + try + { + var result = await service.GetVariancesAsync(rideId); + return Ok(result); + } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("variances/{id:guid}/approve")] + public async Task ApproveVariance(Guid rideId, Guid id, ResolveVarianceRequest request, CancellationToken ct) + { + var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + try + { + var result = await service.ApproveVarianceAsync(id, request, userId, ct); + return Ok(result); + } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("variances/{id:guid}/reject")] + public async Task RejectVariance(Guid rideId, Guid id, ResolveVarianceRequest request, CancellationToken ct) + { + var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + try + { + var result = await service.RejectVarianceAsync(id, request, userId, ct); + return Ok(result); + } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } + + [HttpPost("close-ride")] + public async Task CloseRide(Guid rideId, CancellationToken ct) + { + var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + try + { + var result = await service.CloseRideAsync(rideId, userId, ct); + return Ok(result); + } + catch (KeyNotFoundException) { return NotFound(); } + catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } + } +} diff --git a/Distribution/Features/Reconciliation/ReconciliationDTOs.cs b/Distribution/Features/Reconciliation/ReconciliationDTOs.cs new file mode 100644 index 0000000..468e5ab --- /dev/null +++ b/Distribution/Features/Reconciliation/ReconciliationDTOs.cs @@ -0,0 +1,19 @@ +using Distribution.Models; + +namespace Distribution.Features.Reconciliation; + +public record CycleCountItemLine(Guid ItemId, decimal PhysicalQuantity); +public record SubmitCycleCountRequest(List Items, decimal CashOnHand); + +public record RideVarianceResponse( + Guid Id, Guid RideId, Guid? ItemId, string? ItemName, + bool IsCash, decimal ExpectedQuantity, decimal ActualQuantity, decimal VarianceQuantity, + string? ReasonCode, VarianceStatus Status, Guid? ResolvedByUserId, DateTime? ResolvedAt); + +public record ResolveVarianceRequest(string ReasonCode); + +public record CycleCountResult(bool HasVariances, List Variances); + +public record RideCloseSummaryResponse( + Guid Id, Guid RideId, decimal ExpectedCash, decimal ActualCash, decimal CashVariance, + Guid ClosedByUserId, DateTime ClosedAt); diff --git a/Distribution/Features/Reconciliation/ReconciliationRepository.cs b/Distribution/Features/Reconciliation/ReconciliationRepository.cs new file mode 100644 index 0000000..708730c --- /dev/null +++ b/Distribution/Features/Reconciliation/ReconciliationRepository.cs @@ -0,0 +1,51 @@ +using Distribution.Data; +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.Reconciliation; + +public class ReconciliationRepository(AppDBContext db) : IReconciliationRepository +{ + public async Task AddItemCountAsync(RideItemCount count) + => await db.RideItemCounts.AddAsync(count); + + public async Task> GetItemCountsByRideAsync(Guid rideId) + => await db.RideItemCounts + .Where(c => c.FK_RideId == rideId) + .ToListAsync(); + + public async Task AddVarianceAsync(RideVariance variance) + => await db.RideVariances.AddAsync(variance); + + public async Task> GetVariancesByRideAsync(Guid rideId) + => await db.RideVariances + .Include(v => v.Item) + .Include(v => v.ResolvedBy) + .Where(v => v.FK_RideId == rideId) + .ToListAsync(); + + public async Task GetVarianceByIdAsync(Guid id) + => await db.RideVariances + .Include(v => v.Item) + .Include(v => v.ResolvedBy) + .FirstOrDefaultAsync(v => v.Id == id); + + public void UpdateVariance(RideVariance variance) + => db.RideVariances.Update(variance); + + public async Task HasPendingVariancesAsync(Guid rideId) + => await db.RideVariances + .AnyAsync(v => v.FK_RideId == rideId && v.Status == VarianceStatus.Pending); + + public async Task AddCloseSummaryAsync(RideCloseSummary summary) + => await db.RideCloseSummaries.AddAsync(summary); + + public async Task GetCloseSummaryByRideAsync(Guid rideId) + => await db.RideCloseSummaries + .FirstOrDefaultAsync(s => s.FK_RideId == rideId); + + public async Task GetCashCollectedByRideAsync(Guid rideId) + => await db.Payments + .Where(p => p.Sale.FK_RideId == rideId && p.Method == PaymentMethod.Cash) + .SumAsync(p => p.Amount); +} diff --git a/Distribution/Features/Reconciliation/ReconciliationService.cs b/Distribution/Features/Reconciliation/ReconciliationService.cs new file mode 100644 index 0000000..2fbb522 --- /dev/null +++ b/Distribution/Features/Reconciliation/ReconciliationService.cs @@ -0,0 +1,396 @@ +using Distribution.Features.Ride; +using Distribution.Features.Stock; +using Distribution.Infra.UoW; +using Distribution.Models; + +namespace Distribution.Features.Reconciliation; + +public class ReconciliationService( + IReconciliationRepository reconciliationRepo, + IRideRepository rideRepo, + IStockRepository stockRepo, + IUnitOfWork uow) +{ + public async Task SubmitCycleCountAsync( + Guid rideId, SubmitCycleCountRequest request, Guid userId, CancellationToken ct = default) + { + var ride = await rideRepo.GetByIdAsync(rideId) + ?? throw new KeyNotFoundException("Ride not found"); + + if (ride.Status != RideStatus.Active) + throw new InvalidOperationException($"Cannot submit cycle count for a ride in {ride.Status} status"); + + var existingCounts = await reconciliationRepo.GetItemCountsByRideAsync(rideId); + if (existingCounts.Count > 0) + throw new InvalidOperationException("Cycle count has already been submitted for this ride"); + + // Build submitted counts lookup + var submittedCounts = request.Items.ToDictionary(i => i.ItemId, i => i.PhysicalQuantity); + + // Save all item count records + var itemCounts = request.Items.Select(line => new RideItemCount + { + FK_RideId = rideId, + FK_ItemId = line.ItemId, + PhysicalQuantity = line.PhysicalQuantity + }).ToList(); + + // Get current vehicle stock balances + var vehicleBalances = await stockRepo.GetBalancesByLocationAsync(ride.FK_VehicleId, LocationType.Vehicle); + + var variances = new List(); + + // Compare each stock balance against the physical count + foreach (var balance in vehicleBalances) + { + var physicalQty = submittedCounts.TryGetValue(balance.FK_ItemId, out var qty) ? qty : 0m; + + if (balance.Quantity != physicalQty) + { + variances.Add(new RideVariance + { + FK_RideId = rideId, + FK_ItemId = balance.FK_ItemId, + IsCash = false, + ExpectedQuantity = balance.Quantity, + ActualQuantity = physicalQty, + VarianceQuantity = balance.Quantity - physicalQty, + Status = VarianceStatus.Pending + }); + } + } + + // Cash variance check + var expectedCash = await reconciliationRepo.GetCashCollectedByRideAsync(rideId); + if (expectedCash != request.CashOnHand) + { + variances.Add(new RideVariance + { + FK_RideId = rideId, + FK_ItemId = null, + IsCash = true, + ExpectedQuantity = expectedCash, + ActualQuantity = request.CashOnHand, + VarianceQuantity = expectedCash - request.CashOnHand, + Status = VarianceStatus.Pending + }); + } + + ride.Status = RideStatus.PendingClose; + + await uow.BeginAsync(ct); + try + { + foreach (var count in itemCounts) + await reconciliationRepo.AddItemCountAsync(count); + + foreach (var variance in variances) + await reconciliationRepo.AddVarianceAsync(variance); + + rideRepo.Update(ride); + + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + + var varianceResponses = variances.Select(ToVarianceResponse).ToList(); + return new CycleCountResult(varianceResponses.Count > 0, varianceResponses); + } + + public async Task> GetVariancesAsync(Guid rideId) + { + var variances = await reconciliationRepo.GetVariancesByRideAsync(rideId); + return variances.Select(ToVarianceResponse).ToList(); + } + + public async Task ApproveVarianceAsync( + Guid varianceId, ResolveVarianceRequest request, Guid userId, CancellationToken ct = default) + { + var variance = await reconciliationRepo.GetVarianceByIdAsync(varianceId) + ?? throw new KeyNotFoundException("Variance not found"); + + if (variance.Status != VarianceStatus.Pending) + throw new InvalidOperationException($"Variance is not in Pending status"); + + var ride = await rideRepo.GetByIdAsync(variance.FK_RideId) + ?? throw new KeyNotFoundException("Ride not found"); + + if (ride.Status != RideStatus.PendingClose) + throw new InvalidOperationException($"Ride is not in PendingClose status"); + + StockMovement? movement = null; + StockBalance? balance = null; + + if (!variance.IsCash) + { + // adjustmentQty = -variance.VarianceQuantity + // positive variance = short → debit vehicle (reduce stock) + // negative variance = surplus → credit vehicle (add stock) + var adjustmentQty = -variance.VarianceQuantity; + + balance = await stockRepo.GetBalanceAsync(variance.FK_ItemId!.Value, ride.FK_VehicleId); + + if (adjustmentQty < 0) + { + // Debit vehicle: FromLocationId=vehicleId, Quantity=abs(adjustmentQty) + var absQty = Math.Abs(adjustmentQty); + movement = new StockMovement + { + FK_ItemId = variance.FK_ItemId!.Value, + FromLocationId = ride.FK_VehicleId, + FromLocationType = LocationType.Vehicle, + ToLocationId = null, + ToLocationType = null, + Quantity = absQty, + Type = MovementType.Adjustment, + ReasonCode = request.ReasonCode, + ReferenceId = variance.Id, + CreatedAt = DateTime.Now, + CreatedBy = userId + }; + + if (balance is not null) + { + balance.Quantity -= absQty; + balance.UpdatedAt = DateTime.Now; + } + } + else if (adjustmentQty > 0) + { + // Credit vehicle: ToLocationId=vehicleId, Quantity=adjustmentQty + movement = new StockMovement + { + FK_ItemId = variance.FK_ItemId!.Value, + FromLocationId = null, + FromLocationType = null, + ToLocationId = ride.FK_VehicleId, + ToLocationType = LocationType.Vehicle, + Quantity = adjustmentQty, + Type = MovementType.Adjustment, + ReasonCode = request.ReasonCode, + ReferenceId = variance.Id, + CreatedAt = DateTime.Now, + CreatedBy = userId + }; + + if (balance is not null) + { + balance.Quantity += adjustmentQty; + balance.UpdatedAt = DateTime.Now; + } + else + { + balance = new StockBalance + { + FK_ItemId = variance.FK_ItemId!.Value, + LocationId = ride.FK_VehicleId, + LocationType = LocationType.Vehicle, + Quantity = adjustmentQty, + UpdatedAt = DateTime.Now + }; + } + } + } + + variance.Status = VarianceStatus.Approved; + variance.ReasonCode = request.ReasonCode; + variance.FK_ResolvedByUserId = userId; + variance.ResolvedAt = DateTime.Now; + + await uow.BeginAsync(ct); + try + { + reconciliationRepo.UpdateVariance(variance); + + if (movement is not null) + await stockRepo.AddMovementAsync(movement); + + if (balance is not null) + stockRepo.UpdateBalance(balance); + + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + + return ToVarianceResponse(variance); + } + + public async Task RejectVarianceAsync( + Guid varianceId, ResolveVarianceRequest request, Guid userId, CancellationToken ct = default) + { + var variance = await reconciliationRepo.GetVarianceByIdAsync(varianceId) + ?? throw new KeyNotFoundException("Variance not found"); + + if (variance.Status != VarianceStatus.Pending) + throw new InvalidOperationException($"Variance is not in Pending status"); + + var ride = await rideRepo.GetByIdAsync(variance.FK_RideId) + ?? throw new KeyNotFoundException("Ride not found"); + + if (ride.Status != RideStatus.PendingClose) + throw new InvalidOperationException($"Ride is not in PendingClose status"); + + StockMovement? movement = null; + StockBalance? balance = null; + + if (!variance.IsCash) + { + var adjustmentQty = -variance.VarianceQuantity; + + balance = await stockRepo.GetBalanceAsync(variance.FK_ItemId!.Value, ride.FK_VehicleId); + + if (adjustmentQty < 0) + { + var absQty = Math.Abs(adjustmentQty); + movement = new StockMovement + { + FK_ItemId = variance.FK_ItemId!.Value, + FromLocationId = ride.FK_VehicleId, + FromLocationType = LocationType.Vehicle, + ToLocationId = null, + ToLocationType = null, + Quantity = absQty, + Type = MovementType.Adjustment, + ReasonCode = request.ReasonCode, + ReferenceId = variance.Id, + CreatedAt = DateTime.Now, + CreatedBy = userId + }; + + if (balance is not null) + { + balance.Quantity -= absQty; + balance.UpdatedAt = DateTime.Now; + } + } + else if (adjustmentQty > 0) + { + movement = new StockMovement + { + FK_ItemId = variance.FK_ItemId!.Value, + FromLocationId = null, + FromLocationType = null, + ToLocationId = ride.FK_VehicleId, + ToLocationType = LocationType.Vehicle, + Quantity = adjustmentQty, + Type = MovementType.Adjustment, + ReasonCode = request.ReasonCode, + ReferenceId = variance.Id, + CreatedAt = DateTime.Now, + CreatedBy = userId + }; + + if (balance is not null) + { + balance.Quantity += adjustmentQty; + balance.UpdatedAt = DateTime.Now; + } + else + { + balance = new StockBalance + { + FK_ItemId = variance.FK_ItemId!.Value, + LocationId = ride.FK_VehicleId, + LocationType = LocationType.Vehicle, + Quantity = adjustmentQty, + UpdatedAt = DateTime.Now + }; + } + } + } + + variance.Status = VarianceStatus.Rejected; + variance.ReasonCode = request.ReasonCode; + variance.FK_ResolvedByUserId = userId; + variance.ResolvedAt = DateTime.Now; + + await uow.BeginAsync(ct); + try + { + reconciliationRepo.UpdateVariance(variance); + + if (movement is not null) + await stockRepo.AddMovementAsync(movement); + + if (balance is not null) + stockRepo.UpdateBalance(balance); + + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + + return ToVarianceResponse(variance); + } + + public async Task CloseRideAsync( + Guid rideId, Guid userId, CancellationToken ct = default) + { + var ride = await rideRepo.GetByIdAsync(rideId) + ?? throw new KeyNotFoundException("Ride not found"); + + if (ride.Status != RideStatus.PendingClose) + throw new InvalidOperationException($"Cannot close a ride in {ride.Status} status"); + + if (await reconciliationRepo.HasPendingVariancesAsync(rideId)) + throw new InvalidOperationException("All variances must be resolved before the ride can be closed"); + + var allVariances = await reconciliationRepo.GetVariancesByRideAsync(rideId); + var cashVariance = allVariances.FirstOrDefault(v => v.IsCash); + + var expectedCash = cashVariance?.ExpectedQuantity ?? 0m; + var actualCash = cashVariance?.ActualQuantity ?? 0m; + var cashVarianceAmount = cashVariance?.VarianceQuantity ?? 0m; + + var summary = new RideCloseSummary + { + FK_RideId = rideId, + ExpectedCash = expectedCash, + ActualCash = actualCash, + CashVariance = cashVarianceAmount, + FK_ClosedByUserId = userId, + ClosedAt = DateTime.Now + }; + + ride.Status = RideStatus.Closed; + ride.ClosedAt = DateTime.Now; + + await uow.BeginAsync(ct); + try + { + await reconciliationRepo.AddCloseSummaryAsync(summary); + rideRepo.Update(ride); + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + + return new RideCloseSummaryResponse( + summary.Id, + summary.FK_RideId, + summary.ExpectedCash, + summary.ActualCash, + summary.CashVariance, + summary.FK_ClosedByUserId, + summary.ClosedAt); + } + + private static RideVarianceResponse ToVarianceResponse(RideVariance v) => + new(v.Id, v.FK_RideId, v.FK_ItemId, v.Item?.Name, + v.IsCash, v.ExpectedQuantity, v.ActualQuantity, v.VarianceQuantity, + v.ReasonCode, v.Status, v.FK_ResolvedByUserId, v.ResolvedAt); +} diff --git a/Distribution/Features/Ride/RideService.cs b/Distribution/Features/Ride/RideService.cs index df20d44..cedf599 100644 --- a/Distribution/Features/Ride/RideService.cs +++ b/Distribution/Features/Ride/RideService.cs @@ -1,5 +1,4 @@ using Distribution.Features.Stock; -using Distribution.Features.DriverComplianceConfig; using Distribution.Features.DriverProfile; using Distribution.Infra.UoW; using Distribution.Models; @@ -9,7 +8,6 @@ namespace Distribution.Features.Ride; public class RideService( IRideRepository rideRepo, IDriverProfileRepository driverRepo, - IDriverComplianceConfigRepository complianceRepo, IRideCancelRequestRepository cancelRepo, Features.Vehicle.IVehicleRepository vehicleRepo, IStockRepository stockRepo, @@ -138,11 +136,9 @@ public class RideService( var ride = await rideRepo.GetByIdAsync(rideId) ?? throw new KeyNotFoundException("Ride not found"); - if (ride.Status != RideStatus.Active) + if (ride.Status != RideStatus.PendingClose) throw new InvalidOperationException($"Cannot close a ride in {ride.Status} status"); - await EnsureVehicleStockClearedAsync(ride.FK_VehicleId); - ride.Status = RideStatus.Closed; ride.ClosedAt = DateTime.Now; ride.Meterend = request.Meterend; @@ -319,7 +315,7 @@ public class RideService( if (!driver.IsActive) throw new InvalidOperationException("Driver profile is inactive"); - var config = await complianceRepo.GetAsync(); + var config = await driverRepo.GetComplianceConfigAsync(); if (config is null) return; var today = DateTime.Now.Date; diff --git a/Distribution/Migrations/20260830055526_AddRideReconciliation.Designer.cs b/Distribution/Migrations/20260830055526_AddRideReconciliation.Designer.cs new file mode 100644 index 0000000..c9f3d31 --- /dev/null +++ b/Distribution/Migrations/20260830055526_AddRideReconciliation.Designer.cs @@ -0,0 +1,2828 @@ +// +using System; +using Distribution.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Distribution.Migrations +{ + [DbContext(typeof(AppDBContext))] + [Migration("20260830055526_AddRideReconciliation")] + partial class AddRideReconciliation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Distribution.Models.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Brands"); + }); + + modelBuilder.Entity("Distribution.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomerReturn") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("SettledAt") + .HasColumnType("datetime(6)"); + + b.Property("SettledByUserId") + .HasColumnType("char(36)"); + + b.Property("SettlementAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("SettledByUserId"); + + b.ToTable("Claims"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("CreditNotes"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActivatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_DriverProfileId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("FK_SourceWarehouseId") + .HasColumnType("char(36)"); + + b.Property("FK_VehicleId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Meterend") + .HasColumnType("int"); + + b.Property("Meterstart") + .HasColumnType("int"); + + b.Property("RideType") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_DriverProfileId"); + + b.HasIndex("FK_RouteId"); + + b.HasIndex("FK_SourceWarehouseId"); + + b.HasIndex("FK_VehicleId"); + + b.ToTable("DeliveryRuns"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("FK_OrderId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_OrderId"); + + b.HasIndex("FK_RideId"); + + b.ToTable("DeliveryTasks"); + }); + + modelBuilder.Entity("Distribution.Models.DriverComplianceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("EnforceLicenseExpiry") + .HasColumnType("tinyint(1)"); + + b.Property("EnforceMedicalClearance") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("DriverComplianceConfigs"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LicenseExpiry") + .HasColumnType("datetime(6)"); + + b.Property("LicenseNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MedicalClearanceExpiry") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_UserId") + .IsUnique(); + + b.ToTable("DriverProfiles"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("GoodsReceipts"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcceptedQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RejectedQuantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderItemId"); + + b.ToTable("GoodsReceiptItems"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CostPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalErpItemId") + .HasColumnType("int"); + + b.Property("FK_BaseUomId") + .HasColumnType("char(36)"); + + b.Property("FK_BrandId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_SubCategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SalePrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SyncSource") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_BaseUomId"); + + b.HasIndex("FK_BrandId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_SubCategoryId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DispatchConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("DispatchConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_SourceWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceiptConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReceiptConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_SourceWarehouseId"); + + b.ToTable("LoadingSheets"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("QuantityDispatched") + .HasColumnType("decimal(65,30)"); + + b.Property("QuantityReceived") + .HasColumnType("decimal(65,30)"); + + b.Property("QuantityResolved") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_LoadingSheetId"); + + b.ToTable("LoadingSheetItems"); + }); + + modelBuilder.Entity("Distribution.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfilledAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("OrderDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleId") + .IsUnique(); + + b.HasIndex("FK_StoreId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Distribution.Models.OrderItem", b => + { + b.Property("FK_OrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityFulfilled") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("FK_OrderId", "FK_ItemId"); + + b.HasIndex("FK_ItemId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CollectedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollectedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("Distribution.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("PriceLists"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PriceListId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MinPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Price") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PriceListId", "FK_ItemId") + .IsUnique(); + + b.ToTable("PriceListItems"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("PurchaseOrders"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelledQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCancelled") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("ReceivedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseReturns"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseReturnId"); + + b.ToTable("PurchaseReturnItems"); + }); + + modelBuilder.Entity("Distribution.Models.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Regions"); + }); + + modelBuilder.Entity("Distribution.Models.RideCancelRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApprovedBy") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RequestedAt") + .HasColumnType("datetime(6)"); + + b.Property("RequestedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TypeCode") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.ToTable("RideCancelRequests"); + }); + + modelBuilder.Entity("Distribution.Models.RideCloseSummary", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActualCash") + .HasColumnType("decimal(65,30)"); + + b.Property("CashVariance") + .HasColumnType("decimal(65,30)"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpectedCash") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_ClosedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ClosedByUserId"); + + b.HasIndex("FK_RideId") + .IsUnique(); + + b.ToTable("RideCloseSummaries"); + }); + + modelBuilder.Entity("Distribution.Models.RideItemCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PhysicalQuantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_RideId"); + + b.ToTable("RideItemCounts"); + }); + + modelBuilder.Entity("Distribution.Models.RideVariance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActualQuantity") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpectedQuantity") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCash") + .HasColumnType("tinyint(1)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("VarianceQuantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ResolvedByUserId"); + + b.HasIndex("FK_RideId"); + + b.ToTable("RideVariances"); + }); + + modelBuilder.Entity("Distribution.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 10, + Name = "SalesRep" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 20, + Name = "Supervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 30, + Name = "AreaManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 40, + Name = "AreaSupervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 50, + Name = "RegionalManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 60, + Name = "RegionalAgency" + }); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FK_PermissionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_RoleId", "FK_PermissionId"); + + b.HasIndex("FK_PermissionId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndPoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Length") + .HasColumnType("decimal(65,30)"); + + b.Property("RouteName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartPoint") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("Routes"); + }); + + modelBuilder.Entity("Distribution.Models.Sale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfillImmediately") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("SaleDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("Sales"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_OrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Mode") + .HasColumnType("int"); + + b.Property("TotalCreditAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_OrderId"); + + b.ToTable("SaleReturns"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("SaleReturnItems"); + }); + + modelBuilder.Entity("Distribution.Models.SaleRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("SaleRoutes"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgentLimitExempt") + .HasColumnType("tinyint(1)"); + + b.Property("BaselineExpectedSales") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeFrom") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeTo") + .HasColumnType("datetime(6)"); + + b.Property("EligibleSegment") + .HasColumnType("longtext"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("FK_TriggerItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegionId") + .HasColumnType("char(36)"); + + b.Property("RuleConfig") + .HasColumnType("longtext"); + + b.Property("RuleType") + .HasColumnType("int"); + + b.Property("TriggerQty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_TriggerItemId"); + + b.HasIndex("RegionId"); + + b.ToTable("Schemes"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("Qty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SchemeId", "FK_ItemId") + .IsUnique(); + + b.ToTable("SchemeBundleComponents"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_OrderId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("FieldInvoiceId") + .HasColumnType("char(36)"); + + b.Property("FreeItemCostValue") + .HasColumnType("decimal(65,30)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("RedeemedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_OrderId"); + + b.HasIndex("FK_SchemeId"); + + b.ToTable("SchemeRedemptions"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("LocationId") + .HasColumnType("char(36)"); + + b.Property("LocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "LocationId") + .IsUnique(); + + b.ToTable("StockBalances"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FromLocationId") + .HasColumnType("char(36)"); + + b.Property("FromLocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ReferenceId") + .HasColumnType("char(36)"); + + b.Property("ToLocationId") + .HasColumnType("char(36)"); + + b.Property("ToLocationType") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.ToTable("StockMovements"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("KycStatus") + .HasColumnType("int"); + + b.Property("Latitude") + .HasColumnType("decimal(65,30)"); + + b.Property("Longitude") + .HasColumnType("decimal(65,30)"); + + b.Property("OneTimeAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("OutstandingBalance") + .HasColumnType("decimal(65,30)"); + + b.Property("OwnerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Segment") + .HasColumnType("longtext"); + + b.Property("StoreAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoreName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("Stores"); + }); + + modelBuilder.Entity("Distribution.Models.StoreKycDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocumentType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FileReference") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("StoreKycDocuments"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.ToTable("SubCategories"); + }); + + modelBuilder.Entity("Distribution.Models.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PerformanceScore") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EvidenceUrl") + .HasColumnType("longtext"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedByUserId") + .HasColumnType("char(36)"); + + b.Property("ResolutionAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("ResolutionNotes") + .HasColumnType("longtext"); + + b.Property("ResolutionType") + .HasColumnType("int"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("RaisedByUserId"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("SupplierClaims"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierClaimId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssueType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SupplierClaimId"); + + b.ToTable("SupplierClaimItems"); + }); + + modelBuilder.Entity("Distribution.Models.Uom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Uoms"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_FromUomId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ToUomId") + .HasColumnType("char(36)"); + + b.Property("Factor") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FromUomId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ToUomId"); + + b.ToTable("UomConversions"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ReportsToUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_ReportsToUserId"); + + b.HasIndex("FK_RoleId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RegionId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("UserRegionScopes"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RouteId"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("UserRouteScopes"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_StoreId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("UserStoreScopes"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NumberPlate") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DefaultWarehouseId"); + + b.ToTable("Vehicles"); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Location") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VehicleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("VehicleId"); + + b.ToTable("Warehouses"); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "SettledByUser") + .WithMany() + .HasForeignKey("SettledByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AgencyUser"); + + b.Navigation("SettledByUser"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany() + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.HasOne("Distribution.Models.DriverProfile", "DriverProfile") + .WithMany() + .HasForeignKey("FK_DriverProfileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Warehouse", "SourceWarehouse") + .WithMany() + .HasForeignKey("FK_SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("FK_VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DriverProfile"); + + b.Navigation("Route"); + + b.Navigation("SourceWarehouse"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.HasOne("Distribution.Models.Order", "Order") + .WithMany() + .HasForeignKey("FK_OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany("Items") + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrderItem", "PurchaseOrderItem") + .WithMany() + .HasForeignKey("FK_PurchaseOrderItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrderItem"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.HasOne("Distribution.Models.Uom", "BaseUom") + .WithMany() + .HasForeignKey("FK_BaseUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Brand", "Brand") + .WithMany() + .HasForeignKey("FK_BrandId"); + + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("FK_SubCategoryId"); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("SubCategory"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Warehouse", "SourceWarehouse") + .WithMany() + .HasForeignKey("FK_SourceWarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + + b.Navigation("SourceWarehouse"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Items") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("LoadingSheet"); + }); + + modelBuilder.Entity("Distribution.Models.Order", b => + { + b.HasOne("Distribution.Models.Sale", "Sale") + .WithMany() + .HasForeignKey("FK_SaleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Sale"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.OrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("FK_OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.HasOne("Distribution.Models.Sale", "Sale") + .WithMany() + .HasForeignKey("FK_SaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Sale"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.PriceList", "PriceList") + .WithMany("Items") + .HasForeignKey("FK_PriceListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceList"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseReturn", "PurchaseReturn") + .WithMany("Items") + .HasForeignKey("FK_PurchaseReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseReturn"); + }); + + modelBuilder.Entity("Distribution.Models.RideCancelRequest", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RideCloseSummary", b => + { + b.HasOne("Distribution.Models.User", "ClosedByUser") + .WithMany() + .HasForeignKey("FK_ClosedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClosedByUser"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RideItemCount", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RideVariance", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId"); + + b.HasOne("Distribution.Models.User", "ResolvedBy") + .WithMany() + .HasForeignKey("FK_ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ResolvedBy"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.HasOne("Distribution.Models.Permission", "Permission") + .WithMany() + .HasForeignKey("FK_PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.Sale", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("Ride"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Order", "Order") + .WithMany() + .HasForeignKey("FK_OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany("Items") + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "TriggerItem") + .WithMany() + .HasForeignKey("FK_TriggerItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("RegionId"); + + b.Navigation("FreeItem"); + + b.Navigation("Region"); + + b.Navigation("TriggerItem"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("BundleComponents") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Order", "Order") + .WithMany() + .HasForeignKey("FK_OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("Redemptions") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FreeItem"); + + b.Navigation("Order"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Distribution.Models.StoreKycDocument", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "RaisedByUser") + .WithMany() + .HasForeignKey("RaisedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("RaisedByUser"); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierClaim", "SupplierClaim") + .WithMany("Items") + .HasForeignKey("FK_SupplierClaimId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SupplierClaim"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.HasOne("Distribution.Models.Uom", "FromUom") + .WithMany() + .HasForeignKey("FK_FromUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany("Conversions") + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Uom", "ToUom") + .WithMany() + .HasForeignKey("FK_ToUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.HasOne("Distribution.Models.User", "ReportsTo") + .WithMany() + .HasForeignKey("FK_ReportsToUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportsTo"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.HasOne("Distribution.Models.Warehouse", null) + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("VehicleId"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Navigation("Conversions"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Order", b => + { + b.Navigation("OrderItems"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Navigation("BundleComponents"); + + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Distribution/Migrations/20260830055526_AddRideReconciliation.cs b/Distribution/Migrations/20260830055526_AddRideReconciliation.cs new file mode 100644 index 0000000..142ad6e --- /dev/null +++ b/Distribution/Migrations/20260830055526_AddRideReconciliation.cs @@ -0,0 +1,167 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Distribution.Migrations +{ + /// + public partial class AddRideReconciliation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RideCloseSummaries", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_RideId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ExpectedCash = table.Column(type: "decimal(65,30)", nullable: false), + ActualCash = table.Column(type: "decimal(65,30)", nullable: false), + CashVariance = table.Column(type: "decimal(65,30)", nullable: false), + FK_ClosedByUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ClosedAt = table.Column(type: "datetime(6)", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RideCloseSummaries", x => x.Id); + table.ForeignKey( + name: "FK_RideCloseSummaries_DeliveryRuns_FK_RideId", + column: x => x.FK_RideId, + principalTable: "DeliveryRuns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RideCloseSummaries_Users_FK_ClosedByUserId", + column: x => x.FK_ClosedByUserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "RideItemCounts", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_RideId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_ItemId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + PhysicalQuantity = table.Column(type: "decimal(65,30)", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RideItemCounts", x => x.Id); + table.ForeignKey( + name: "FK_RideItemCounts_DeliveryRuns_FK_RideId", + column: x => x.FK_RideId, + principalTable: "DeliveryRuns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RideItemCounts_Items_FK_ItemId", + column: x => x.FK_ItemId, + principalTable: "Items", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "RideVariances", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_RideId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + FK_ItemId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + IsCash = table.Column(type: "tinyint(1)", nullable: false), + ExpectedQuantity = table.Column(type: "decimal(65,30)", nullable: false), + ActualQuantity = table.Column(type: "decimal(65,30)", nullable: false), + VarianceQuantity = table.Column(type: "decimal(65,30)", nullable: false), + ReasonCode = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column(type: "int", nullable: false), + FK_ResolvedByUserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + ResolvedAt = table.Column(type: "datetime(6)", nullable: true), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsActive = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RideVariances", x => x.Id); + table.ForeignKey( + name: "FK_RideVariances_DeliveryRuns_FK_RideId", + column: x => x.FK_RideId, + principalTable: "DeliveryRuns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RideVariances_Items_FK_ItemId", + column: x => x.FK_ItemId, + principalTable: "Items", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_RideVariances_Users_FK_ResolvedByUserId", + column: x => x.FK_ResolvedByUserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_RideCloseSummaries_FK_ClosedByUserId", + table: "RideCloseSummaries", + column: "FK_ClosedByUserId"); + + migrationBuilder.CreateIndex( + name: "IX_RideCloseSummaries_FK_RideId", + table: "RideCloseSummaries", + column: "FK_RideId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RideItemCounts_FK_ItemId", + table: "RideItemCounts", + column: "FK_ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_RideItemCounts_FK_RideId", + table: "RideItemCounts", + column: "FK_RideId"); + + migrationBuilder.CreateIndex( + name: "IX_RideVariances_FK_ItemId", + table: "RideVariances", + column: "FK_ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_RideVariances_FK_ResolvedByUserId", + table: "RideVariances", + column: "FK_ResolvedByUserId"); + + migrationBuilder.CreateIndex( + name: "IX_RideVariances_FK_RideId", + table: "RideVariances", + column: "FK_RideId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RideCloseSummaries"); + + migrationBuilder.DropTable( + name: "RideItemCounts"); + + migrationBuilder.DropTable( + name: "RideVariances"); + } + } +} diff --git a/Distribution/Migrations/AppDBContextModelSnapshot.cs b/Distribution/Migrations/AppDBContextModelSnapshot.cs index da94a53..96b38e5 100644 --- a/Distribution/Migrations/AppDBContextModelSnapshot.cs +++ b/Distribution/Migrations/AppDBContextModelSnapshot.cs @@ -1004,6 +1004,129 @@ namespace Distribution.Migrations b.ToTable("RideCancelRequests"); }); + modelBuilder.Entity("Distribution.Models.RideCloseSummary", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActualCash") + .HasColumnType("decimal(65,30)"); + + b.Property("CashVariance") + .HasColumnType("decimal(65,30)"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpectedCash") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_ClosedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ClosedByUserId"); + + b.HasIndex("FK_RideId") + .IsUnique(); + + b.ToTable("RideCloseSummaries"); + }); + + modelBuilder.Entity("Distribution.Models.RideItemCount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PhysicalQuantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_RideId"); + + b.ToTable("RideItemCounts"); + }); + + modelBuilder.Entity("Distribution.Models.RideVariance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActualQuantity") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpectedQuantity") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCash") + .HasColumnType("tinyint(1)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("VarianceQuantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ResolvedByUserId"); + + b.HasIndex("FK_RideId"); + + b.ToTable("RideVariances"); + }); + modelBuilder.Entity("Distribution.Models.Role", b => { b.Property("Id") @@ -2363,6 +2486,68 @@ namespace Distribution.Migrations b.Navigation("Ride"); }); + modelBuilder.Entity("Distribution.Models.RideCloseSummary", b => + { + b.HasOne("Distribution.Models.User", "ClosedByUser") + .WithMany() + .HasForeignKey("FK_ClosedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClosedByUser"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RideItemCount", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.RideVariance", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId"); + + b.HasOne("Distribution.Models.User", "ResolvedBy") + .WithMany() + .HasForeignKey("FK_ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ResolvedBy"); + + b.Navigation("Ride"); + }); + modelBuilder.Entity("Distribution.Models.RolePermission", b => { b.HasOne("Distribution.Models.Permission", "Permission") diff --git a/Distribution/Models/Enums.cs b/Distribution/Models/Enums.cs index 588af42..232e959 100644 --- a/Distribution/Models/Enums.cs +++ b/Distribution/Models/Enums.cs @@ -11,7 +11,9 @@ namespace Distribution.Models public enum RideType { Delivery, Sale } - public enum RideStatus { Draft, Open, Active, CancelRequested, Cancelled, Closed } + public enum RideStatus { Draft, Open, Active, PendingClose, CancelRequested, Cancelled, Closed } + + public enum VarianceStatus { Pending, Approved, Rejected } public enum CancelRequestType { VehicleBreakdown, DriverUnavailable, RouteIssue, Other } diff --git a/Distribution/Models/RideCloseSummary.cs b/Distribution/Models/RideCloseSummary.cs new file mode 100644 index 0000000..a16bba4 --- /dev/null +++ b/Distribution/Models/RideCloseSummary.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class RideCloseSummary : Template + { + public Guid FK_RideId { get; set; } + [ForeignKey("FK_RideId")] + public DeliveryRun Ride { get; set; } + + public decimal ExpectedCash { get; set; } + public decimal ActualCash { get; set; } + public decimal CashVariance { get; set; } + + public Guid FK_ClosedByUserId { get; set; } + [ForeignKey("FK_ClosedByUserId")] + public User ClosedByUser { get; set; } + + public DateTime ClosedAt { get; set; } + } +} diff --git a/Distribution/Models/RideItemCount.cs b/Distribution/Models/RideItemCount.cs new file mode 100644 index 0000000..fca9367 --- /dev/null +++ b/Distribution/Models/RideItemCount.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class RideItemCount : Template + { + public Guid FK_RideId { get; set; } + [ForeignKey("FK_RideId")] + public DeliveryRun Ride { get; set; } + + public Guid FK_ItemId { get; set; } + [ForeignKey("FK_ItemId")] + public Item Item { get; set; } + + public decimal PhysicalQuantity { get; set; } + } +} diff --git a/Distribution/Models/RideVariance.cs b/Distribution/Models/RideVariance.cs new file mode 100644 index 0000000..429faba --- /dev/null +++ b/Distribution/Models/RideVariance.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class RideVariance : Template + { + public Guid FK_RideId { get; set; } + [ForeignKey("FK_RideId")] + public DeliveryRun Ride { get; set; } + + public Guid? FK_ItemId { get; set; } + [ForeignKey("FK_ItemId")] + public Item? Item { get; set; } + + public bool IsCash { get; set; } + + public decimal ExpectedQuantity { get; set; } + public decimal ActualQuantity { get; set; } + public decimal VarianceQuantity { get; set; } + + public string? ReasonCode { get; set; } + public VarianceStatus Status { get; set; } = VarianceStatus.Pending; + + public Guid? FK_ResolvedByUserId { get; set; } + [ForeignKey("FK_ResolvedByUserId")] + public User? ResolvedBy { get; set; } + + public DateTime? ResolvedAt { get; set; } + } +} diff --git a/Distribution/Program.cs b/Distribution/Program.cs index 9d15715..68afd7b 100644 --- a/Distribution/Program.cs +++ b/Distribution/Program.cs @@ -5,7 +5,6 @@ using Distribution.Features.Brand; using Distribution.Features.Category; using Distribution.Features.DeliveryRide; using Distribution.Features.Payment; -using Distribution.Features.DriverComplianceConfig; using Distribution.Features.DriverProfile; using Distribution.Features.Grn; using Distribution.Features.Item; @@ -30,6 +29,7 @@ using Distribution.Features.Sync; using Distribution.Features.Uom; using Distribution.Features.User; using Distribution.Features.Vehicle; +using Distribution.Features.Reconciliation; using Distribution.Features.Stock; using Distribution.Features.Warehouse; using Distribution.Infra.Authorization; @@ -62,7 +62,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -78,6 +77,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -99,7 +99,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -111,6 +110,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped();