LocationScopeBindingHelper.cs 13 KB
using FoodLabeling.Application.Services.DbModels;
using FoodLabeling.Domain.Entities;
using SqlSugar;
using Volo.Abp;
using Yi.Framework.SqlSugarCore.Abstractions;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// Region(<c>fl_group.Id</c>)与 Location(<c>location.Id</c>)范围解析:Region 落库对应 <c>location.GroupName</c>。
/// </summary>
public static class LocationScopeBindingHelper
{
    /// <summary>
    /// 列表筛选:门店 Id 优先,否则 Region(groupId),否则 Company(partnerId);均未传返回 null。
    /// </summary>
    public static async Task<List<string>?> ResolveFilteredLocationIdsForListAsync(
        ISqlSugarClient db,
        string? partnerId,
        string? groupId,
        string? locationId)
    {
        var locId = locationId?.Trim();
        if (!string.IsNullOrWhiteSpace(locId))
        {
            if (!Guid.TryParse(locId, out var locationGuid))
            {
                return new List<string>();
            }

            var exists = await db.Queryable<LocationAggregateRoot>()
                .AnyAsync(x => !x.IsDeleted && x.Id == locationGuid);
            return exists ? new List<string> { locId } : new List<string>();
        }

        var gid = groupId?.Trim();
        var pid = partnerId?.Trim();

        if (string.IsNullOrWhiteSpace(pid) && string.IsNullOrWhiteSpace(gid))
        {
            return null;
        }

        var q = db.Queryable<LocationAggregateRoot>().Where(x => !x.IsDeleted);

        if (!string.IsNullOrWhiteSpace(gid))
        {
            var g = await db.Queryable<FlGroupDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == gid);
            if (g is null)
            {
                return new List<string>();
            }

            var gName = g.GroupName?.Trim() ?? string.Empty;
            var partner = await db.Queryable<FlPartnerDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == g.PartnerId);
            var pName = partner?.PartnerName?.Trim() ?? string.Empty;
            q = q.Where(x => x.GroupName == gName && x.Partner == pName);
        }
        else if (!string.IsNullOrWhiteSpace(pid))
        {
            var partner = await db.Queryable<FlPartnerDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == pid);
            if (partner is null)
            {
                return new List<string>();
            }

            var pName = partner.PartnerName?.Trim() ?? string.Empty;
            q = q.Where(x => x.Partner == pName);
        }

        return await q.Select(x => SqlFunc.ToString(x.Id)).ToListAsync();
    }

    /// <summary>
    /// 列表筛选:按门店 Id 优先,否则按 Region(groupId)解析门店主键;均未传返回 null。
    /// </summary>
    public static async Task<List<string>?> ResolveScopedLocationIdsAsync(
        ISqlSugarClient db,
        string? groupId,
        string? locationId)
    {
        var locId = locationId?.Trim();
        if (!string.IsNullOrWhiteSpace(locId))
        {
            if (!Guid.TryParse(locId, out var locationGuid))
            {
                return new List<string>();
            }

            var exists = await db.Queryable<LocationAggregateRoot>()
                .AnyAsync(x => !x.IsDeleted && x.Id == locationGuid);
            return exists ? new List<string> { locId } : new List<string>();
        }

        var gid = groupId?.Trim();
        if (string.IsNullOrWhiteSpace(gid))
        {
            return null;
        }

        return await ResolveLocationIdsFromGroupIdsAsync(db, new List<string> { gid });
    }

    /// <summary>
    /// 将 Company(partnerId)、Region(groupIds)与显式 locationIds 合并为去重后的门店 Id 列表。
    /// </summary>
    public static Task<List<string>> MergeToLocationIdsAsync(
        ISqlSugarClient db,
        string? partnerId,
        IReadOnlyList<string>? groupIds,
        IReadOnlyList<string>? locationIds) =>
        MergeToLocationIdsAsync(db,
            string.IsNullOrWhiteSpace(partnerId) ? null : new[] { partnerId.Trim() },
            groupIds,
            locationIds);

    /// <summary>
    /// 将 Company(partnerIds)、Region(groupIds)与显式 locationIds 合并为去重后的门店 Id 列表。
    /// </summary>
    public static async Task<List<string>> MergeToLocationIdsAsync(
        ISqlSugarClient db,
        IReadOnlyList<string>? partnerIds,
        IReadOnlyList<string>? groupIds,
        IReadOnlyList<string>? locationIds)
    {
        var merged = new HashSet<string>(StringComparer.Ordinal);

        var fromPartner = await ResolveLocationIdsFromPartnerIdsAsync(db, partnerIds);
        foreach (var id in fromPartner)
        {
            merged.Add(id);
        }

        var fromGroups = await ResolveLocationIdsFromGroupIdsAsync(db, groupIds);
        foreach (var id in fromGroups)
        {
            merged.Add(id);
        }

        foreach (var id in NormalizeIds(locationIds))
        {
            merged.Add(id);
        }

        return merged.ToList();
    }

    /// <summary>
    /// 根据已绑定门店反推适用的 Company Id(<c>fl_partner.Id</c>)。
    /// </summary>
    public static async Task<List<string>> ResolvePartnerIdsFromLocationIdsAsync(
        ISqlSugarClient db,
        IReadOnlyList<string> locationIds)
    {
        var ids = NormalizeIds(locationIds);
        if (ids.Count == 0)
        {
            return new List<string>();
        }

        var guidList = ids.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
        if (guidList.Count == 0)
        {
            return new List<string>();
        }

        var locs = await db.Queryable<LocationAggregateRoot>()
            .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
            .ToListAsync();
        if (locs.Count == 0)
        {
            return new List<string>();
        }

        var partnerNames = locs
            .Select(x => x.Partner?.Trim())
            .Where(x => !string.IsNullOrEmpty(x))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();
        if (partnerNames.Count == 0)
        {
            return new List<string>();
        }

        var partners = await db.Queryable<FlPartnerDbEntity>()
            .Where(x => !x.IsDeleted)
            .ToListAsync();

        var partnerIdSet = new HashSet<string>(StringComparer.Ordinal);
        foreach (var name in partnerNames)
        {
            var match = partners.FirstOrDefault(p =>
                string.Equals(p.PartnerName?.Trim(), name, StringComparison.OrdinalIgnoreCase) ||
                string.Equals(p.Id, name, StringComparison.OrdinalIgnoreCase));
            if (match is not null && !string.IsNullOrWhiteSpace(match.Id))
            {
                partnerIdSet.Add(match.Id.Trim());
            }
        }

        return partnerIdSet.OrderBy(x => x, StringComparer.Ordinal).ToList();
    }

    /// <summary>
    /// 解析 Company(<c>fl_partner.Id</c>)下全部未删除门店 Id。
    /// </summary>
    public static async Task<List<string>> ResolveLocationIdsFromPartnerIdsAsync(
        ISqlSugarClient db,
        IReadOnlyList<string>? partnerIds)
    {
        var ids = NormalizeIds(partnerIds);
        if (ids.Count == 0)
        {
            return new List<string>();
        }

        var result = new HashSet<string>(StringComparer.Ordinal);
        foreach (var pid in ids)
        {
            var partner = await db.Queryable<FlPartnerDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == pid);
            if (partner is null)
            {
                continue;
            }

            var pName = partner.PartnerName?.Trim() ?? string.Empty;
            if (string.IsNullOrEmpty(pName))
            {
                continue;
            }

            var locIds = await db.Queryable<LocationAggregateRoot>()
                .Where(x => !x.IsDeleted && x.Partner == pName)
                .Select(x => SqlFunc.ToString(x.Id))
                .ToListAsync();
            foreach (var lid in locIds.Where(x => !string.IsNullOrWhiteSpace(x)))
            {
                result.Add(lid.Trim());
            }
        }

        return result.ToList();
    }

    /// <summary>
    /// 根据已绑定门店反推适用的 Region Id(<c>fl_group.Id</c>)。
    /// </summary>
    public static async Task<List<string>> ResolveGroupIdsFromLocationIdsAsync(
        ISqlSugarClient db,
        IReadOnlyList<string> locationIds)
    {
        var ids = NormalizeIds(locationIds);
        if (ids.Count == 0)
        {
            return new List<string>();
        }

        var guidList = ids.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
        if (guidList.Count == 0)
        {
            return new List<string>();
        }

        var locs = await db.Queryable<LocationAggregateRoot>()
            .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
            .ToListAsync();
        if (locs.Count == 0)
        {
            return new List<string>();
        }

        var partners = await db.Queryable<FlPartnerDbEntity>()
            .Where(x => !x.IsDeleted)
            .ToListAsync();
        var partnerNameToId = partners
            .Where(x => !string.IsNullOrWhiteSpace(x.PartnerName))
            .GroupBy(x => x.PartnerName!.Trim(), StringComparer.OrdinalIgnoreCase)
            .ToDictionary(g => g.Key, g => g.First().Id, StringComparer.OrdinalIgnoreCase);

        var groups = await db.Queryable<FlGroupDbEntity>()
            .Where(x => !x.IsDeleted)
            .ToListAsync();

        var groupIdSet = new HashSet<string>(StringComparer.Ordinal);
        foreach (var loc in locs)
        {
            var gName = loc.GroupName?.Trim();
            var pName = loc.Partner?.Trim();
            if (string.IsNullOrEmpty(gName) || string.IsNullOrEmpty(pName))
            {
                continue;
            }

            if (!partnerNameToId.TryGetValue(pName, out var partnerId))
            {
                continue;
            }

            var match = groups.FirstOrDefault(g =>
                g.PartnerId == partnerId &&
                string.Equals(g.GroupName?.Trim(), gName, StringComparison.OrdinalIgnoreCase));
            if (match is not null && !string.IsNullOrWhiteSpace(match.Id))
            {
                groupIdSet.Add(match.Id.Trim());
            }
        }

        return groupIdSet.OrderBy(x => x, StringComparer.Ordinal).ToList();
    }

    /// <summary>
    /// 解析 Region(<c>fl_group.Id</c>)下全部未删除门店 Id。
    /// </summary>
    public static async Task<List<string>> ResolveLocationIdsFromGroupIdsAsync(
        ISqlSugarClient db,
        IReadOnlyList<string>? groupIds)
    {
        var ids = NormalizeIds(groupIds);
        if (ids.Count == 0)
        {
            return new List<string>();
        }

        var result = new HashSet<string>(StringComparer.Ordinal);
        foreach (var gid in ids)
        {
            var g = await db.Queryable<FlGroupDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == gid);
            if (g is null)
            {
                continue;
            }

            var partner = await db.Queryable<FlPartnerDbEntity>()
                .FirstAsync(x => !x.IsDeleted && x.Id == g.PartnerId);
            var pName = partner?.PartnerName?.Trim() ?? string.Empty;
            var gName = g.GroupName?.Trim() ?? string.Empty;
            if (string.IsNullOrEmpty(pName) || string.IsNullOrEmpty(gName))
            {
                continue;
            }

            var locIds = await db.Queryable<LocationAggregateRoot>()
                .Where(x => !x.IsDeleted && x.Partner == pName && x.GroupName == gName)
                .Select(x => SqlFunc.ToString(x.Id))
                .ToListAsync();
            foreach (var lid in locIds.Where(x => !string.IsNullOrWhiteSpace(x)))
            {
                result.Add(lid.Trim());
            }
        }

        return result.ToList();
    }

    /// <summary>
    /// 校验合并后的门店 Id 均存在。
    /// </summary>
    public static async Task ValidateLocationIdsExistAsync(ISqlSugarClient db, IReadOnlyList<string> locationIds)
    {
        var ids = NormalizeIds(locationIds);
        if (ids.Count == 0)
        {
            return;
        }

        foreach (var id in ids)
        {
            if (!Guid.TryParse(id, out _))
            {
                throw new UserFriendlyException("门店Id格式不正确");
            }
        }

        var guidList = ids.Select(Guid.Parse).ToList();
        var existCount = await db.Queryable<LocationAggregateRoot>()
            .Where(x => !x.IsDeleted && guidList.Contains(x.Id))
            .CountAsync();
        if (existCount != ids.Count)
        {
            throw new UserFriendlyException("门店不存在");
        }
    }

    public static List<string> NormalizeIds(IReadOnlyList<string>? raw)
    {
        return raw?
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Select(x => x.Trim())
            .Distinct(StringComparer.Ordinal)
            .ToList() ?? new List<string>();
    }
}