using Distribution.Features.Scheme; using Distribution.Infra.Authorization; using Distribution.Infra.UoW; using Distribution.Models; namespace Distribution.Features.Store; public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo) { private const int GlobalScopeRoleLevel = 50; private const int CreditApproverRoleLevel = 20; private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel; private async Task HasStoreScopeAsync(Guid storeId) { //if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope; return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId); } private static StoreResponse ToResponse(Models.Store s) => new(s.Id, s.FK_RouteId, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount, s.CreditLimit, s.OutstandingBalance, s.Segment, s.KycStatus, s.IsActive, s.CreatedAt); public async Task> GetAllAsync() { var stores = (await repo.GetAllAsync()).Where(s => s.IsActive).ToList(); if (!HasGlobalScope && currentUser.UserId is not null) { var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet(); stores = stores.Where(s => accessibleStoreIds.Contains(s.Id)).ToList(); } return stores.Select(ToResponse).ToList(); } public async Task GetByIdAsync(Guid id) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; //if (!await HasStoreScopeAsync(id)) return null; return ToResponse(store); } public async Task CreateAsync(CreateStoreRequest request, CancellationToken ct = default) { var store = new Models.Store { FK_RouteId = request.RouteId, OwnerName = request.OwnerName, StoreName = request.StoreName, StoreAddress = request.StoreAddress, Latitude = request.Latitude, Longitude = request.Longitude, OneTimeAmount = request.OneTimeAmount, CreditAmount = request.CreditAmount }; await uow.BeginAsync(ct); try { await repo.AddAsync(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task UpdateAsync(Guid id, UpdateStoreRequest request, CancellationToken ct = default) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; store.FK_RouteId = request.RouteId; store.OwnerName = request.OwnerName; store.StoreName = request.StoreName; store.StoreAddress = request.StoreAddress; store.Latitude = request.Latitude; store.Longitude = request.Longitude; store.OneTimeAmount = request.OneTimeAmount; store.CreditAmount = request.CreditAmount; await uow.BeginAsync(ct); try { repo.Update(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task DeleteAsync(Guid id, CancellationToken ct = default) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return false; if (!await HasStoreScopeAsync(id)) return false; await uow.BeginAsync(ct); try { repo.Remove(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return true; } public async Task UpdateCreditLimitAsync(Guid id, UpdateCreditLimitRequest request, CancellationToken ct = default) { if (currentUser.RoleLevel < CreditApproverRoleLevel) return null; var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; store.CreditLimit = request.NewLimit; await uow.BeginAsync(ct); try { repo.Update(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task UpdateOutstandingBalanceAsync(Guid id, UpdateOutstandingBalanceRequest request, CancellationToken ct = default) { if (currentUser.RoleLevel < CreditApproverRoleLevel) return null; var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; store.OutstandingBalance = request.NewBalance; await uow.BeginAsync(ct); try { repo.Update(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task GetOutstandingBalanceAsync(Guid id) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; return new OutstandingBalanceResponse(store.Id, store.CreditLimit, store.OutstandingBalance, store.CreditLimit - store.OutstandingBalance); } public async Task VerifyKycAsync(Guid id, VerifyKycRequest request, CancellationToken ct = default) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; store.KycStatus = request.Status; await uow.BeginAsync(ct); try { repo.Update(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task ClassifySegmentAsync(Guid id, ClassifySegmentRequest request, CancellationToken ct = default) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; store.Segment = request.Segment; await uow.BeginAsync(ct); try { repo.Update(store); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return ToResponse(store); } public async Task?> GetVisitHistoryAsync(Guid id) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; var visits = await routeRepo.GetVisitsByStoreIdAsync(id); return visits .OrderByDescending(v => v.CheckInAt) .Select(v => new StoreVisitHistoryItem(v.Id, v.CheckInAt, v.FK_UserId, v.CheckOutAt is null ? "In Progress" : "Completed")) .ToList(); } public async Task CheckSchemeEligibilityAsync(Guid id, Guid schemeId) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; var result = await schemeService.CheckStoreScopeEligibilityAsync(id, schemeId); if (result is null) return null; return new SchemeEligibilityResponse(result.Value.Eligible, result.Value.Reason); } public async Task GetDashboardAsync(Guid id, string period) { var store = await repo.GetByIdAsync(id); if (store is null || !store.IsActive) return null; if (!await HasStoreScopeAsync(id)) return null; if (!DateTime.TryParseExact(period, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var periodStart)) periodStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); var periodEndExclusive = periodStart.AddMonths(1); var orders = await repo.GetOrdersForDashboardAsync(id, periodStart, periodEndExclusive); return new StoreDashboardResponse( id, periodStart.ToString("yyyy-MM"), orders.Count, orders.Sum(o => o.TotalAmount), orders.Count == 0 ? null : orders.Max(o => o.OrderDate)); } }