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
}
};
}
}