PartnerScopeHelper.cs 6.88 KB
using FoodLabeling.Application.Services.DbModels;
using FoodLabeling.Domain.Entities;
using SqlSugar;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// 合作伙伴(公司)数据范围:管理员可见全部;非管理员仅可见其 <c>userlocation</c> 绑定门店所属公司。
/// </summary>
public static class PartnerScopeHelper
{
    /// <summary>
    /// <see cref="IsUnrestricted"/> 为 true 时不限制;
    /// 否则仅 <see cref="AllowedPartnerIds"/> 中的公司 Id(<c>fl_partner.Id</c>)可见;无绑定时为空列表。
    /// </summary>
    public sealed class PartnerScopeFilter
    {
        public bool IsUnrestricted { get; init; }

        public IReadOnlyList<string> AllowedPartnerIds { get; init; } = Array.Empty<string>();
    }

    /// <summary>
    /// 解析当前登录用户可查询的合作伙伴 Id 范围。
    /// </summary>
    public static async Task<PartnerScopeFilter> ResolvePartnerScopeAsync(
        ICurrentUser currentUser,
        ISqlSugarDbContext dbContext)
    {
        if (ReportsRoleHelper.IsAdminRole(currentUser))
        {
            return new PartnerScopeFilter { IsUnrestricted = true };
        }

        if (currentUser.Id is null)
        {
            return new PartnerScopeFilter();
        }

        var userId = currentUser.Id.Value.ToString();
        var locationIds = await dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>()
            .Where(x => !x.IsDeleted && x.UserId == userId)
            .Select(x => x.LocationId)
            .Distinct()
            .ToListAsync();

        if (locationIds.Count == 0)
        {
            return new PartnerScopeFilter();
        }

        var partnerKeys = await dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>()
            .Where(x => !x.IsDeleted && locationIds.Contains(x.Id.ToString()))
            .Where(x => x.Partner != null && x.Partner != string.Empty)
            .Select(x => x.Partner!)
            .Distinct()
            .ToListAsync();

        if (partnerKeys.Count == 0)
        {
            return new PartnerScopeFilter();
        }

        var allowedIds = await dbContext.SqlSugarClient.Queryable<FlPartnerDbEntity>()
            .Where(x => !x.IsDeleted)
            .Where(x => partnerKeys.Contains(x.PartnerName) || partnerKeys.Contains(x.Id))
            .Select(x => x.Id)
            .Distinct()
            .ToListAsync();

        return new PartnerScopeFilter { AllowedPartnerIds = allowedIds };
    }

    /// <summary>
    /// 将数据范围应用到合作伙伴查询(须在其它 Where 之前或之后均可,建议在 IsDeleted 之后)。
    /// </summary>
    public static ISugarQueryable<FlPartnerDbEntity> ApplyPartnerScope(
        ISugarQueryable<FlPartnerDbEntity> query,
        PartnerScopeFilter scope)
    {
        if (scope.IsUnrestricted)
        {
            return query;
        }

        var ids = scope.AllowedPartnerIds;
        if (ids.Count == 0)
        {
            return query.Where(_ => false);
        }

        return query.Where(x => ids.Contains(x.Id));
    }

    /// <summary>
    /// 组织(fl_group)数据范围:管理员可见全部;非管理员仅可见其 <c>userlocation</c> 绑定门店对应的 Region(公司 + 组织名与 location 一致)。
    /// </summary>
    public sealed class GroupScopeFilter
    {
        public bool IsUnrestricted { get; init; }

        public IReadOnlyList<string> AllowedGroupIds { get; init; } = Array.Empty<string>();
    }

    /// <summary>
    /// 解析当前登录用户可查询的组织 Id 范围(与 Reports 按 location.Partner + location.GroupName 对齐)。
    /// </summary>
    public static async Task<GroupScopeFilter> ResolveGroupScopeAsync(
        ICurrentUser currentUser,
        ISqlSugarDbContext dbContext)
    {
        if (ReportsRoleHelper.IsAdminRole(currentUser))
        {
            return new GroupScopeFilter { IsUnrestricted = true };
        }

        if (currentUser.Id is null)
        {
            return new GroupScopeFilter();
        }

        var userId = currentUser.Id.Value.ToString();
        var locationRows = await dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>()
            .InnerJoin<LocationAggregateRoot>((ul, loc) =>
                !loc.IsDeleted && ul.LocationId == loc.Id.ToString())
            .Where(ul => !ul.IsDeleted && ul.UserId == userId)
            .Select((ul, loc) => new { loc.Partner, loc.GroupName })
            .ToListAsync();

        if (locationRows.Count == 0)
        {
            return new GroupScopeFilter();
        }

        var pairs = locationRows
            .Where(x => !string.IsNullOrWhiteSpace(x.GroupName))
            .Select(x => (Partner: (x.Partner ?? string.Empty).Trim(), GroupName: x.GroupName!.Trim()))
            .Where(x => !string.IsNullOrEmpty(x.GroupName) && !string.IsNullOrEmpty(x.Partner))
            .Distinct()
            .ToList();

        if (pairs.Count == 0)
        {
            return new GroupScopeFilter();
        }

        var partnerKeys = pairs.Select(x => x.Partner).Distinct().ToList();
        var partners = await dbContext.SqlSugarClient.Queryable<FlPartnerDbEntity>()
            .Where(x => !x.IsDeleted)
            .Where(x => partnerKeys.Contains(x.PartnerName) || partnerKeys.Contains(x.Id))
            .Select(x => new { x.Id, x.PartnerName })
            .ToListAsync();

        var allowedGroupIds = new HashSet<string>(StringComparer.Ordinal);
        foreach (var pair in pairs)
        {
            var partnerId = partners
                .FirstOrDefault(p =>
                    string.Equals(p.PartnerName, pair.Partner, StringComparison.Ordinal) ||
                    string.Equals(p.Id, pair.Partner, StringComparison.Ordinal))
                ?.Id;
            if (string.IsNullOrEmpty(partnerId))
            {
                continue;
            }

            var ids = await dbContext.SqlSugarClient.Queryable<FlGroupDbEntity>()
                .Where(g => !g.IsDeleted && g.PartnerId == partnerId && g.GroupName == pair.GroupName)
                .Select(g => g.Id)
                .ToListAsync();
            foreach (var id in ids)
            {
                allowedGroupIds.Add(id);
            }
        }

        return new GroupScopeFilter { AllowedGroupIds = allowedGroupIds.ToList() };
    }

    /// <summary>
    /// 将组织数据范围应用到 fl_group 联表查询。
    /// </summary>
    public static ISugarQueryable<FlGroupDbEntity, FlPartnerDbEntity> ApplyGroupScope(
        ISugarQueryable<FlGroupDbEntity, FlPartnerDbEntity> query,
        GroupScopeFilter scope)
    {
        if (scope.IsUnrestricted)
        {
            return query;
        }

        var ids = scope.AllowedGroupIds;
        if (ids.Count == 0)
        {
            return query.Where((g, p) => false);
        }

        return query.Where((g, p) => ids.Contains(g.Id));
    }
}