using Distribution.Infra.Authorization; using Distribution.Infra.UoW; namespace Distribution.Features.Region; public class RegionService(IRegionRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess) { private const int RegionalManagerRoleLevel = 50; private const int RegionalAgencyRoleLevel = 60; private async Task CanManageRegionAsync(Guid regionId) { if (currentUser.RoleLevel >= RegionalAgencyRoleLevel) return true; if (currentUser.RoleLevel < RegionalManagerRoleLevel || currentUser.UserId is null) return false; return await scopeAccess.HasRegionScopeAsync(currentUser.UserId.Value, regionId); } public async Task> GetAllAsync() => (await repo.GetAllAsync()) .Where(r => r.IsActive) .Select(r => new RegionResponse(r.Id, r.Name, r.IsActive, r.CreatedAt)) .ToList(); public async Task GetByIdAsync(Guid id) { var region = await repo.GetByIdAsync(id); if (region is null || !region.IsActive) return null; return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt); } public async Task CreateAsync(CreateRegionRequest request, CancellationToken ct = default) { var region = new Models.Region { Name = request.Name }; await uow.BeginAsync(ct); try { await repo.AddAsync(region); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt); } public async Task UpdateAsync(Guid id, UpdateRegionRequest request, CancellationToken ct = default) { var region = await repo.GetByIdAsync(id); if (region is null || !region.IsActive) return null; region.Name = request.Name; await uow.BeginAsync(ct); try { repo.Update(region); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt); } public async Task DeleteAsync(Guid id, CancellationToken ct = default) { var region = await repo.GetByIdAsync(id); if (region is null || !region.IsActive) return false; await uow.BeginAsync(ct); try { repo.Remove(region); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return true; } public async Task SetTargetAsync(Guid regionId, string period, SetRegionalTargetRequest request, CancellationToken ct = default) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) return null; if (!await CanManageRegionAsync(regionId)) return null; var target = await repo.GetTargetAsync(regionId, period); await uow.BeginAsync(ct); try { if (target is null) { target = new Models.RegionalTarget { FK_RegionId = regionId, Period = period, TargetValue = request.TargetValue }; await repo.AddTargetAsync(target); } else { target.TargetValue = request.TargetValue; repo.UpdateTarget(target); } await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionalTargetResponse(regionId, period, target.TargetValue); } public async Task SetBudgetAsync(Guid regionId, string period, SetRegionalBudgetRequest request, CancellationToken ct = default) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) return null; if (!await CanManageRegionAsync(regionId)) return null; var budget = await repo.GetBudgetAsync(regionId, period); await uow.BeginAsync(ct); try { if (budget is null) { budget = new Models.RegionalBudget { FK_RegionId = regionId, Period = period, BudgetAmount = request.BudgetAmount }; await repo.AddBudgetAsync(budget); } else { budget.BudgetAmount = request.BudgetAmount; repo.UpdateBudget(budget); } await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionalBudgetResponse(regionId, period, budget.BudgetAmount); } public async Task MapAgencyAsync(Guid regionId, MapAgencyToRegionRequest request, CancellationToken ct = default) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) return null; if (!await CanManageRegionAsync(regionId)) return null; var agencyRoleLevel = await repo.GetUserRoleLevelAsync(request.AgencyUserId); if (agencyRoleLevel is null || agencyRoleLevel < RegionalAgencyRoleLevel) return null; var existing = await repo.GetAgencyMapAsync(regionId, request.AgencyUserId); if (existing is not null) return new RegionAgencyMapResponse(regionId, request.AgencyUserId); var map = new Models.RegionAgencyMap { FK_RegionId = regionId, FK_AgencyUserId = request.AgencyUserId }; await uow.BeginAsync(ct); try { await repo.AddAgencyMapAsync(map); await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionAgencyMapResponse(regionId, request.AgencyUserId); } public async Task SetTaxRuleAsync(Guid regionId, SetRegionalTaxRuleRequest request, CancellationToken ct = default) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) return null; if (!await CanManageRegionAsync(regionId)) return null; var rule = await repo.GetTaxRuleAsync(regionId); await uow.BeginAsync(ct); try { if (rule is null) { rule = new Models.RegionalTaxRule { FK_RegionId = regionId, RuleConfig = request.RuleConfig }; await repo.AddTaxRuleAsync(rule); } else { rule.RuleConfig = request.RuleConfig; repo.UpdateTaxRule(rule); } await uow.CommitAsync(ct); } catch { await uow.RollbackAsync(ct); throw; } return new RegionalTaxRuleResponse(regionId, rule.RuleConfig); } public async Task GetHierarchyAsync(Guid regionId) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) return null; var routes = await repo.GetRoutesAsync(regionId); var stores = await repo.GetStoresByRouteIdsAsync(routes.Select(r => r.Id).ToList()); var routeItems = routes .Select(r => new RegionHierarchyRouteItem( r.Id, r.RouteName, stores.Where(s => s.FK_RouteId == r.Id).Select(s => s.Id).ToList())) .ToList(); return new RegionHierarchyResponse(region.Id, region.Name, routeItems); } public async Task GetDashboardAsync(Guid regionId, string period) { var region = await repo.GetByIdAsync(regionId); if (region is null || !region.IsActive) 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 normalizedPeriod = periodStart.ToString("yyyy-MM"); var periodEndExclusive = periodStart.AddMonths(1); var target = await repo.GetTargetAsync(regionId, normalizedPeriod); var budget = await repo.GetBudgetAsync(regionId, normalizedPeriod); var orders = await repo.GetOrdersForDashboardAsync(regionId, periodStart, periodEndExclusive); return new RegionDashboardResponse( regionId, normalizedPeriod, target?.TargetValue, budget?.BudgetAmount, orders.Count, orders.Sum(o => o.TotalAmount), orders.Count == 0 ? null : orders.Max(o => o.OrderDate)); } }