using FoodLabeling.Application.Services.DbModels;
using SqlSugar;
using Volo.Abp;
namespace FoodLabeling.Application.Helpers;
///
/// 标签适用 Region(fl_group.Id)范围:保存、展示、列表筛选与 App 门店校验。
///
public static class LabelRegionScopeHelper
{
public const string AppliedTypeAll = "ALL";
public const string AppliedTypeSpecified = "SPECIFIED";
public const string AllRegionsDisplay = "All Regions";
private const string EmptyDisplay = "无";
public sealed class LabelRegionScopeData
{
public string AppliedRegionType { get; init; } = AppliedTypeSpecified;
public string Region { get; init; } = string.Empty;
public List RegionIds { get; init; } = new();
}
///
/// 读取标签绑定的 Region Id 列表。
///
public static async Task> LoadRegionIdsAsync(ISqlSugarClient db, string labelId)
{
var lid = labelId?.Trim();
if (string.IsNullOrWhiteSpace(lid))
{
return new List();
}
return await db.Queryable()
.Where(x => x.LabelId == lid)
.Select(x => x.GroupId)
.ToListAsync();
}
///
/// 批量读取标签 Region Id(按 LabelId 分组)。
///
public static async Task>> LoadRegionIdsMapAsync(
ISqlSugarClient db,
IReadOnlyList labelIds)
{
var ids = LocationScopeBindingHelper.NormalizeIds(labelIds);
var result = ids.ToDictionary(x => x, _ => new List(), StringComparer.Ordinal);
if (ids.Count == 0)
{
return result;
}
var rows = await db.Queryable()
.Where(x => ids.Contains(x.LabelId))
.ToListAsync();
foreach (var row in rows)
{
if (string.IsNullOrWhiteSpace(row.LabelId) || string.IsNullOrWhiteSpace(row.GroupId))
{
continue;
}
if (!result.TryGetValue(row.LabelId, out var list))
{
list = new List();
result[row.LabelId] = list;
}
if (!list.Contains(row.GroupId, StringComparer.Ordinal))
{
list.Add(row.GroupId.Trim());
}
}
foreach (var key in result.Keys.ToList())
{
result[key] = result[key].OrderBy(x => x, StringComparer.Ordinal).ToList();
}
return result;
}
///
/// 先删后插保存标签 Region 绑定;ALL 时仅删除关联行。
///
public static async Task SaveRegionIdsAsync(
ISqlSugarClient db,
string labelId,
string appliedRegionType,
IReadOnlyList regionIds,
string? creatorId,
DateTime now,
Func idGenerator)
{
var lid = labelId?.Trim();
if (string.IsNullOrWhiteSpace(lid))
{
return;
}
await db.Deleteable()
.Where(x => x.LabelId == lid)
.ExecuteCommandAsync();
if (!string.Equals(appliedRegionType, AppliedTypeSpecified, StringComparison.OrdinalIgnoreCase))
{
return;
}
var groupIds = LocationScopeBindingHelper.NormalizeIds(regionIds);
if (groupIds.Count == 0)
{
return;
}
var rows = groupIds.Select(gid => new FlLabelRegionDbEntity
{
Id = idGenerator(),
LabelId = lid,
GroupId = gid,
CreationTime = now,
CreatorId = creatorId
}).ToList();
await db.Insertable(rows).ExecuteCommandAsync();
}
///
/// 解析保存入参:ALL/SPECIFIED、Region Id 列表与唯一 location.Id。
///
public static async Task<(string AppliedRegionType, List RegionIds, string LocationId)> ResolveScopeForSaveAsync(
ISqlSugarClient db,
string? appliedRegionType,
IReadOnlyList? regionIds,
IReadOnlyList? groupIds,
string? locationId,
IReadOnlyList? locationIds,
IReadOnlyList? partnerIds)
{
var normalizedRegionIds = MergeRegionIds(regionIds, groupIds);
var explicitLocationId = locationId?.Trim();
var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds);
var type = (appliedRegionType ?? AppliedTypeSpecified).Trim().ToUpperInvariant();
if (normalizedRegionIds.Count > 0)
{
type = AppliedTypeSpecified;
}
if (normalizedRegionIds.Count == 0 &&
(!string.IsNullOrWhiteSpace(explicitLocationId) || explicitLocationIds.Count > 0))
{
var locIdsForRegion = new List();
if (!string.IsNullOrWhiteSpace(explicitLocationId))
{
locIdsForRegion.Add(explicitLocationId);
}
locIdsForRegion.AddRange(explicitLocationIds);
normalizedRegionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
db, locIdsForRegion);
if (normalizedRegionIds.Count > 0)
{
type = AppliedTypeSpecified;
}
}
if (type != AppliedTypeAll && type != AppliedTypeSpecified)
{
throw new UserFriendlyException("适用Region范围不合法(ALL/SPECIFIED)");
}
if (type == AppliedTypeAll)
{
var resolvedLoc = explicitLocationId;
if (string.IsNullOrWhiteSpace(resolvedLoc) && explicitLocationIds.Count == 1)
{
resolvedLoc = explicitLocationIds[0];
}
else if (string.IsNullOrWhiteSpace(resolvedLoc) && explicitLocationIds.Count > 1)
{
throw new UserFriendlyException("全选 Region 时请显式指定 locationId,或仅选择一个门店");
}
if (!string.IsNullOrWhiteSpace(resolvedLoc))
{
await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(
db, new List { resolvedLoc });
}
return (AppliedTypeAll, new List(), resolvedLoc ?? string.Empty);
}
if (normalizedRegionIds.Count == 0 &&
string.IsNullOrWhiteSpace(explicitLocationId) &&
explicitLocationIds.Count == 0)
{
throw new UserFriendlyException("请选择适用 Region");
}
await ValidateGroupIdsExistAsync(db, normalizedRegionIds);
var mergedLocations = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
db, partnerIds, normalizedRegionIds, explicitLocationIds);
if (!string.IsNullOrWhiteSpace(explicitLocationId))
{
if (mergedLocations.Count > 0 &&
!mergedLocations.Contains(explicitLocationId, StringComparer.Ordinal))
{
throw new UserFriendlyException("所选门店不在指定 Region 范围内");
}
await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(
db, new List { explicitLocationId });
return (AppliedTypeSpecified, normalizedRegionIds, explicitLocationId);
}
if (mergedLocations.Count == 0)
{
throw new UserFriendlyException("指定适用 Region 时,至少需要匹配到一个有效门店");
}
return (AppliedTypeSpecified, normalizedRegionIds, mergedLocations[0]);
}
///
/// 列表筛选:按 Region Id 过滤,命中 fl_label_region 或 AppliedRegionType=ALL。
///
public static ISugarQueryable ApplyGroupIdListFilter(
ISugarQueryable query,
string groupId)
{
var gid = groupId?.Trim();
if (string.IsNullOrWhiteSpace(gid))
{
return query;
}
return query.Where(l =>
l.AppliedRegionType == AppliedTypeAll ||
SqlFunc.Subqueryable()
.Where(lr => lr.LabelId == l.Id && lr.GroupId == gid)
.Any());
}
///
/// App 树/预览:判断标签是否适用于指定门店(ALL 或 Region 命中当前门店所属 Region)。
///
public static async Task LabelAppliesToLocationAsync(
ISqlSugarClient db,
FlLabelDbEntity label,
string locationId)
{
if (label is null)
{
return false;
}
if (string.Equals(label.AppliedRegionType, AppliedTypeAll, StringComparison.OrdinalIgnoreCase))
{
return true;
}
var locId = locationId?.Trim();
if (string.IsNullOrWhiteSpace(locId))
{
return false;
}
var groupIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
db, new List { locId });
if (groupIds.Count == 0)
{
return false;
}
var regionLinks = await db.Queryable()
.Where(x => x.LabelId == label.Id)
.Select(x => x.GroupId)
.ToListAsync();
return regionLinks.Any(gid => groupIds.Contains(gid, StringComparer.Ordinal));
}
///
/// 构建列表/详情 Region 展示字段。
///
public static async Task> BuildRegionScopeMapAsync(
ISqlSugarClient db,
IReadOnlyList<(string Id, string AppliedRegionType)> labels)
{
var result = new Dictionary(StringComparer.Ordinal);
if (labels.Count == 0)
{
return result;
}
foreach (var item in labels.Where(x =>
string.Equals(x.AppliedRegionType, AppliedTypeAll, StringComparison.OrdinalIgnoreCase)))
{
result[item.Id] = new LabelRegionScopeData
{
AppliedRegionType = AppliedTypeAll,
Region = AllRegionsDisplay,
RegionIds = new List()
};
}
var specifiedIds = labels
.Where(x => string.Equals(x.AppliedRegionType, AppliedTypeSpecified, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Id)
.ToList();
if (specifiedIds.Count == 0)
{
return result;
}
var regionMap = await LoadRegionIdsMapAsync(db, specifiedIds);
var allGroupIds = regionMap.Values.SelectMany(x => x).Distinct(StringComparer.Ordinal).ToList();
var groupNameMap = new Dictionary(StringComparer.Ordinal);
if (allGroupIds.Count > 0)
{
var groups = await db.Queryable()
.Where(x => !x.IsDeleted && allGroupIds.Contains(x.Id))
.ToListAsync();
foreach (var g in groups)
{
if (!string.IsNullOrWhiteSpace(g.Id))
{
groupNameMap[g.Id] = g.GroupName?.Trim() ?? EmptyDisplay;
}
}
}
foreach (var labelId in specifiedIds)
{
var regionIds = regionMap.TryGetValue(labelId, out var ids)
? ids
: new List();
if (regionIds.Count == 0)
{
result[labelId] = new LabelRegionScopeData
{
AppliedRegionType = AppliedTypeSpecified,
Region = EmptyDisplay,
RegionIds = new List()
};
continue;
}
var names = regionIds
.Select(id => groupNameMap.TryGetValue(id, out var name) ? name : id)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
.ToList();
result[labelId] = new LabelRegionScopeData
{
AppliedRegionType = AppliedTypeSpecified,
Region = names.Count > 0 ? string.Join(", ", names) : EmptyDisplay,
RegionIds = regionIds
};
}
return result;
}
private static List MergeRegionIds(
IReadOnlyList? regionIds,
IReadOnlyList? groupIds)
{
var merged = new HashSet(StringComparer.Ordinal);
foreach (var id in LocationScopeBindingHelper.NormalizeIds(regionIds))
{
merged.Add(id);
}
foreach (var id in LocationScopeBindingHelper.NormalizeIds(groupIds))
{
merged.Add(id);
}
return merged.OrderBy(x => x, StringComparer.Ordinal).ToList();
}
private static async Task ValidateGroupIdsExistAsync(ISqlSugarClient db, IReadOnlyList groupIds)
{
var ids = LocationScopeBindingHelper.NormalizeIds(groupIds);
if (ids.Count == 0)
{
return;
}
var existCount = await db.Queryable()
.Where(x => !x.IsDeleted && ids.Contains(x.Id))
.CountAsync();
if (existCount != ids.Count)
{
throw new UserFriendlyException("Region 不存在");
}
}
}