using FoodLabeling.Application.Contracts.Dtos.AuthScope; using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; using FoodLabeling.Application.Services.DbModels; using FoodLabeling.Domain.Entities; using SqlSugar; using Volo.Abp; using Volo.Abp.Caching; using Volo.Abp.Users; using Yi.Framework.SqlSugarCore.Abstractions; namespace FoodLabeling.Application.Helpers; /// /// Company → Region → Location 级联选店查询(Web auth-scope 与 App us-app-auth 共用)。 /// public static class AuthScopeQueryHelper { public static void EnsureLoggedIn(ICurrentUser currentUser) { if (!currentUser.Id.HasValue) { throw new UserFriendlyException("用户未登录"); } } public static async Task> GetCompaniesAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext) { EnsureLoggedIn(currentUser); var scope = await PartnerScopeHelper.ResolvePartnerScopeAsync(currentUser, dbContext); var query = dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted); query = PartnerScopeHelper.ApplyPartnerScope(query, scope); return await query .OrderBy(x => x.PartnerName) .Select(x => new AuthScopeCompanyOptionDto { Id = x.Id, PartnerName = x.PartnerName ?? string.Empty, State = x.State }) .ToListAsync(); } public static async Task> GetRegionsAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, string partnerId) { EnsureLoggedIn(currentUser); var pid = RequireNonEmpty(partnerId, "公司标识不能为空"); await EnsurePartnerAccessibleAsync(currentUser, dbContext, pid); var groupScope = await PartnerScopeHelper.ResolveGroupScopeAsync(currentUser, dbContext); var query = dbContext.SqlSugarClient.Queryable() .Where(g => !g.IsDeleted && g.PartnerId == pid); if (!groupScope.IsUnrestricted) { var allowed = groupScope.AllowedGroupIds; query = allowed.Count == 0 ? query.Where(_ => false) : query.Where(g => allowed.Contains(g.Id)); } return await query .OrderBy(g => g.GroupName) .Select(g => new AuthScopeRegionOptionDto { Id = g.Id, GroupName = g.GroupName ?? string.Empty, PartnerId = g.PartnerId ?? string.Empty, State = g.State }) .ToListAsync(); } public static async Task> GetLocationsAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, string partnerId, string groupId) { EnsureLoggedIn(currentUser); var pid = RequireNonEmpty(partnerId, "公司标识不能为空"); var gid = RequireNonEmpty(groupId, "区域标识不能为空"); await EnsurePartnerAccessibleAsync(currentUser, dbContext, pid); var group = await dbContext.SqlSugarClient.Queryable() .FirstAsync(g => !g.IsDeleted && g.Id == gid && g.PartnerId == pid); if (group is null) { throw new UserFriendlyException("区域不存在或不属于所选公司"); } var groupScope = await PartnerScopeHelper.ResolveGroupScopeAsync(currentUser, dbContext); if (!groupScope.IsUnrestricted && !groupScope.AllowedGroupIds.Contains(gid)) { throw new UserFriendlyException("无权查看该区域下的门店"); } var partner = await dbContext.SqlSugarClient.Queryable() .FirstAsync(p => !p.IsDeleted && p.Id == pid); if (partner is null) { throw new UserFriendlyException("公司不存在"); } var partnerKeys = new List { partner.Id, partner.PartnerName ?? string.Empty } .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.Ordinal) .ToList(); var groupName = (group.GroupName ?? string.Empty).Trim(); var locQuery = dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .Where(x => x.GroupName == groupName) .Where(x => partnerKeys.Contains(x.Partner!)); var listScope = await LocationRegionScopeHelper.ResolveLocationListScopeAsync(currentUser, dbContext); locQuery = LocationRegionScopeHelper.ApplyLocationListScope(locQuery, listScope); var locations = (await locQuery.ToListAsync()) .OrderBy(x => x.OrderNum) .ThenBy(x => x.LocationName) .ToList(); return locations.Select(loc => new AuthScopeLocationOptionDto { Id = loc.Id.ToString(), LocationCode = loc.LocationCode ?? string.Empty, LocationName = loc.LocationName ?? string.Empty, FullAddress = BuildFullAddress(loc), State = loc.State, PartnerId = pid, GroupId = gid, GroupName = groupName }).ToList(); } public static async Task SelectLocationAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, IDistributedCache workingScopeCache, AuthScopeSelectLocationInputVo input) { EnsureLoggedIn(currentUser); var userId = currentUser.Id!.Value; var result = await ResolveAndValidateSelectionAsync(currentUser, dbContext, input); await EnsureUserMaySelectLocationAsync(currentUser, dbContext, result.Location.Id); await AdminWorkingScopeCacheHelper.SetAsync( workingScopeCache, userId, new AdminWorkingScopeCacheItem { LocationId = result.Location.Id, PartnerId = result.PartnerId, GroupId = result.GroupId }); return result; } public static async Task GetCurrentScopeAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, IDistributedCache workingScopeCache) { if (!currentUser.Id.HasValue) { return null; } var cached = await AdminWorkingScopeCacheHelper.GetAsync(workingScopeCache, currentUser.Id.Value); if (cached is null || string.IsNullOrWhiteSpace(cached.LocationId)) { return null; } try { return await BuildOutputFromLocationIdAsync( dbContext, cached.LocationId, cached.PartnerId, cached.GroupId); } catch (UserFriendlyException) { return null; } } public static string BuildFullAddress(LocationAggregateRoot loc) { var street = loc.Street?.Trim(); var city = loc.City?.Trim(); var state = loc.StateCode?.Trim(); var zip = loc.ZipCode?.Trim(); var line2Parts = new List(); if (!string.IsNullOrEmpty(city)) { line2Parts.Add(city); } if (!string.IsNullOrEmpty(state)) { line2Parts.Add(state); } var line2 = line2Parts.Count > 0 ? string.Join(", ", line2Parts) : string.Empty; if (!string.IsNullOrEmpty(zip)) { line2 = string.IsNullOrEmpty(line2) ? zip : $"{line2} {zip}"; } var segments = new List(); if (!string.IsNullOrEmpty(street)) { segments.Add(street); } if (!string.IsNullOrEmpty(line2)) { segments.Add(line2); } return segments.Count == 0 ? "无" : string.Join(", ", segments); } private static string RequireNonEmpty(string? value, string message) { var v = (value ?? string.Empty).Trim(); if (string.IsNullOrEmpty(v)) { throw new UserFriendlyException(message); } return v; } private static async Task EnsurePartnerAccessibleAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, string partnerId) { var scope = await PartnerScopeHelper.ResolvePartnerScopeAsync(currentUser, dbContext); if (scope.IsUnrestricted) { return; } if (!scope.AllowedPartnerIds.Contains(partnerId)) { throw new UserFriendlyException("无权查看该公司"); } } private static async Task EnsureUserMaySelectLocationAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, string locationId) { if (ReportsRoleHelper.IsAdminRole(currentUser)) { return; } var lid = locationId.Trim(); var userIdStr = currentUser.Id!.Value.ToString(); var bound = await dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted && x.UserId == userIdStr && x.LocationId == lid); if (!bound) { throw new UserFriendlyException("当前账号未绑定该门店,无法选择"); } } private static async Task ResolveAndValidateSelectionAsync( ICurrentUser currentUser, ISqlSugarDbContext dbContext, AuthScopeSelectLocationInputVo input) { var pid = RequireNonEmpty(input.PartnerId, "请选择公司"); var gid = RequireNonEmpty(input.GroupId, "请选择区域"); var lid = RequireNonEmpty(input.LocationId, "请选择门店"); if (!Guid.TryParse(lid, out var locationGuid)) { throw new UserFriendlyException("无效的门店标识"); } await EnsurePartnerAccessibleAsync(currentUser, dbContext, pid); var group = await dbContext.SqlSugarClient.Queryable() .FirstAsync(g => !g.IsDeleted && g.Id == gid && g.PartnerId == pid); if (group is null) { throw new UserFriendlyException("区域不存在或不属于所选公司"); } var partner = await dbContext.SqlSugarClient.Queryable() .FirstAsync(p => !p.IsDeleted && p.Id == pid); if (partner is null) { throw new UserFriendlyException("公司不存在"); } var loc = await dbContext.SqlSugarClient.Queryable() .FirstAsync(x => !x.IsDeleted && x.Id == locationGuid); if (loc is null) { throw new UserFriendlyException("门店不存在或已删除"); } var partnerKeys = new List { partner.Id, partner.PartnerName ?? string.Empty } .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.Ordinal) .ToList(); var groupName = (group.GroupName ?? string.Empty).Trim(); var locPartner = (loc.Partner ?? string.Empty).Trim(); var locGroup = (loc.GroupName ?? string.Empty).Trim(); if (!partnerKeys.Contains(locPartner, StringComparer.Ordinal) || !string.Equals(locGroup, groupName, StringComparison.Ordinal)) { throw new UserFriendlyException("门店与所选公司/区域不匹配"); } return new AuthScopeSelectLocationOutputDto { PartnerId = pid, PartnerName = partner.PartnerName ?? string.Empty, GroupId = gid, GroupName = groupName, Location = new UsAppBoundLocationDto { Id = loc.Id.ToString(), LocationCode = loc.LocationCode ?? string.Empty, LocationName = loc.LocationName ?? string.Empty, FullAddress = BuildFullAddress(loc), State = loc.State } }; } private static async Task BuildOutputFromLocationIdAsync( ISqlSugarDbContext dbContext, string locationId, string? partnerId, string? groupId) { if (!Guid.TryParse(locationId.Trim(), out var locationGuid)) { throw new UserFriendlyException("无效的门店标识"); } var loc = await dbContext.SqlSugarClient.Queryable() .FirstAsync(x => !x.IsDeleted && x.Id == locationGuid); if (loc is null) { throw new UserFriendlyException("门店不存在或已删除"); } var pid = (partnerId ?? string.Empty).Trim(); var gid = (groupId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(pid) || string.IsNullOrEmpty(gid)) { var partnerKeys = new List { loc.Partner ?? string.Empty } .Where(x => !string.IsNullOrWhiteSpace(x)) .ToList(); var partners = partnerKeys.Count == 0 ? new List() : await dbContext.SqlSugarClient.Queryable() .Where(p => !p.IsDeleted) .Where(p => partnerKeys.Contains(p.Id) || partnerKeys.Contains(p.PartnerName)) .ToListAsync(); var partner = partners.FirstOrDefault(); pid = partner?.Id ?? pid; if (string.IsNullOrEmpty(gid) && partner is not null && !string.IsNullOrWhiteSpace(loc.GroupName)) { var g = await dbContext.SqlSugarClient.Queryable() .FirstAsync(x => !x.IsDeleted && x.PartnerId == partner.Id && x.GroupName == loc.GroupName); gid = g?.Id ?? gid; } } var partnerEntity = string.IsNullOrEmpty(pid) ? null : await dbContext.SqlSugarClient.Queryable() .FirstAsync(p => !p.IsDeleted && p.Id == pid); var groupEntity = string.IsNullOrEmpty(gid) ? null : await dbContext.SqlSugarClient.Queryable() .FirstAsync(g => !g.IsDeleted && g.Id == gid); return new AuthScopeSelectLocationOutputDto { PartnerId = pid, PartnerName = partnerEntity?.PartnerName ?? string.Empty, GroupId = gid, GroupName = groupEntity?.GroupName ?? loc.GroupName ?? string.Empty, Location = new UsAppBoundLocationDto { Id = loc.Id.ToString(), LocationCode = loc.LocationCode ?? string.Empty, LocationName = loc.LocationName ?? string.Empty, FullAddress = BuildFullAddress(loc), State = loc.State } }; } }