using Distribution.Data; using Microsoft.EntityFrameworkCore; namespace Distribution.Features.Region; public class RegionRepository(AppDBContext context) : IRegionRepository { public async Task GetByIdAsync(Guid id) => await context.Regions.FindAsync(id); public async Task> GetAllAsync() => await context.Regions.ToListAsync(); // Includes soft-deleted regions: the unique index on Code doesn't know about IsActive. public async Task> GetAllCodesAsync() => await context.Regions.Select(r => r.Code).ToListAsync(); public async Task CodeExistsAsync(string code, Guid? excludeId = null) => await context.Regions.AnyAsync(r => r.Code == code && (excludeId == null || r.Id != excludeId)); public async Task GetUserWithRoleAsync(Guid userId) => await context.Users.Include(u => u.Role).FirstOrDefaultAsync(u => u.Id == userId); public async Task> GetManagerScopesAsync(Guid regionId) => await context.UserRegionScopes .Include(s => s.User).ThenInclude(u => u.Role) .Where(s => s.FK_RegionId == regionId && s.User.IsActive && s.User.Role.Name == "RegionalManager") .ToListAsync(); public async Task GetRegionScopeAsync(Guid userId, Guid regionId) => await context.UserRegionScopes.FirstOrDefaultAsync(s => s.FK_UserId == userId && s.FK_RegionId == regionId); public async Task AddRegionScopeAsync(Models.UserRegionScope scope) => await context.UserRegionScopes.AddAsync(scope); // Scope rows are hard-deleted (composite key user+region), same as UserRepository.ReplaceRegionScopesAsync. public void RemoveRegionScopes(IEnumerable scopes) => context.UserRegionScopes.RemoveRange(scopes); public async Task AddAsync(Models.Region entity) => await context.Regions.AddAsync(entity); public void Update(Models.Region entity) => context.Regions.Update(entity); public void Remove(Models.Region entity) { entity.IsActive = false; context.Regions.Update(entity); } // Note: no IsActive filter on any RegionalTarget lookup below — this entity is hard-deleted // (see RemoveTarget), so IsActive is always true for rows that still exist. Querying without // the filter also means a pre-existing orphaned row (e.g. soft-deleted before this fix // shipped) is found and reused/cleaned up here instead of silently blocking a re-add. public async Task GetTargetAsync(Guid regionId, string period) => await context.RegionalTargets.FirstOrDefaultAsync(t => t.FK_RegionId == regionId && t.Period == period); public async Task AddTargetAsync(Models.RegionalTarget target) => await context.RegionalTargets.AddAsync(target); public void UpdateTarget(Models.RegionalTarget target) => context.RegionalTargets.Update(target); public async Task> GetAllTargetsAsync() => await context.RegionalTargets.ToListAsync(); public async Task GetTargetByIdAsync(Guid id) => await context.RegionalTargets.FirstOrDefaultAsync(t => t.Id == id); // Hard delete, not the usual IsActive soft-delete: RegionalTarget has a unique index on // (FK_RegionId, Period) at the DB level, which doesn't know about IsActive — soft-deleting // and then re-adding a target for the same region+period would violate that constraint. public void RemoveTarget(Models.RegionalTarget target) => context.RegionalTargets.Remove(target); // No IsActive filter — see GetTargetAsync's comment; same reasoning for RegionalBudget. public async Task GetBudgetAsync(Guid regionId, string period) => await context.RegionalBudgets.FirstOrDefaultAsync(b => b.FK_RegionId == regionId && b.Period == period); public async Task AddBudgetAsync(Models.RegionalBudget budget) => await context.RegionalBudgets.AddAsync(budget); public void UpdateBudget(Models.RegionalBudget budget) => context.RegionalBudgets.Update(budget); public async Task> GetAllBudgetsAsync() => await context.RegionalBudgets.ToListAsync(); public async Task GetBudgetByIdAsync(Guid id) => await context.RegionalBudgets.FirstOrDefaultAsync(b => b.Id == id); // Hard delete — see RemoveTarget's comment; same unique-index-vs-soft-delete conflict // applies to RegionalBudget's (FK_RegionId, Period) unique index. public void RemoveBudget(Models.RegionalBudget budget) => context.RegionalBudgets.Remove(budget); // No IsActive filter — see GetTargetAsync's comment; same reasoning for RegionAgencyMap. public async Task GetAgencyMapAsync(Guid regionId, Guid agencyUserId) => await context.RegionAgencyMaps.FirstOrDefaultAsync(m => m.FK_RegionId == regionId && m.FK_AgencyUserId == agencyUserId); public async Task AddAgencyMapAsync(Models.RegionAgencyMap map) => await context.RegionAgencyMaps.AddAsync(map); public void UpdateAgencyMap(Models.RegionAgencyMap map) => context.RegionAgencyMaps.Update(map); public async Task> GetAgencyMapsForRegionAsync(Guid regionId) => await context.RegionAgencyMaps.Where(m => m.FK_RegionId == regionId).ToListAsync(); public async Task> GetAllAgencyMapsAsync() => await context.RegionAgencyMaps.ToListAsync(); public async Task GetAgencyMapByIdAsync(Guid id) => await context.RegionAgencyMaps.FirstOrDefaultAsync(m => m.Id == id); // Hard delete — see RemoveTarget's comment; same conflict applies to RegionAgencyMap's // (FK_RegionId, FK_AgencyUserId) unique index. public void RemoveAgencyMap(Models.RegionAgencyMap map) => context.RegionAgencyMaps.Remove(map); // No IsActive filter — see GetTargetAsync's comment; same reasoning for RegionalTaxRule. public async Task GetTaxRuleAsync(Guid regionId) => await context.RegionalTaxRules.FirstOrDefaultAsync(r => r.FK_RegionId == regionId); public async Task AddTaxRuleAsync(Models.RegionalTaxRule rule) => await context.RegionalTaxRules.AddAsync(rule); public void UpdateTaxRule(Models.RegionalTaxRule rule) => context.RegionalTaxRules.Update(rule); public async Task> GetAllTaxRulesAsync() => await context.RegionalTaxRules.ToListAsync(); public async Task GetTaxRuleByIdAsync(Guid id) => await context.RegionalTaxRules.FirstOrDefaultAsync(r => r.Id == id); // Hard delete — see RemoveTarget's comment; same conflict applies to RegionalTaxRule's // unique index on FK_RegionId alone (one tax rule per region). public void RemoveTaxRule(Models.RegionalTaxRule rule) => context.RegionalTaxRules.Remove(rule); public async Task GetUserRoleLevelAsync(Guid userId) => await context.Users .Where(u => u.Id == userId && u.IsActive) .Select(u => (int?)u.Role.Level) .FirstOrDefaultAsync(); public async Task> GetRoutesAsync(Guid regionId) => await context.Routes.Where(r => r.FK_RegionId == regionId && r.IsActive).ToListAsync(); public async Task> GetStoresByRouteIdsAsync(List routeIds) => await context.Stores.Where(s => routeIds.Contains(s.FK_RouteId) && s.IsActive).ToListAsync(); public async Task> GetOrdersForDashboardAsync(Guid regionId, DateTime periodStart, DateTime periodEndExclusive) => await (from o in context.ShopPOs join s in context.Stores on o.FK_StoreId equals s.Id join r in context.Routes on s.FK_RouteId equals r.Id where r.FK_RegionId == regionId && o.IsActive && s.IsActive && r.IsActive && o.CreatedAt >= periodStart && o.CreatedAt < periodEndExclusive select o).ToListAsync(); }