using FoodLabeling.Application.Services.DbModels;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Guids;
namespace FoodLabeling.Application.Helpers;
///
/// 标签适用 Region(fl_group.Id)范围:支持全选(ALL)、单选/多选(SPECIFIED + fl_label_region)。
///
public static class LabelRegionScopeHelper
{
public const string AppliedRegionAll = "ALL";
public const string AppliedRegionSpecified = "SPECIFIED";
public const string AllRegionsDisplay = "All Regions";
public sealed class LabelRegionScopeSaveResult
{
public string AppliedRegionType { get; init; } = AppliedRegionSpecified;
public List RegionIds { get; init; } = new();
public string? LocationId { get; init; }
}
public sealed class LabelRegionScopeDisplay
{
public string Region { get; init; } = string.Empty;
public List RegionIds { get; init; } = new();
public List GroupIds { get; init; } = new();
}
///
/// 解析新增/编辑入参中的 Region 范围与落库门店 Id。
///
public static async Task ResolveScopeForSaveAsync(
ISqlSugarClient db,
string? appliedRegionType,
IReadOnlyList? regionIds,
IReadOnlyList? groupIds,
string? locationId,
IReadOnlyList? locationIds)
{
var mergedRegionIds = NormalizeRegionIds(regionIds, groupIds);
var type = (appliedRegionType ?? AppliedRegionAll).Trim().ToUpperInvariant();
var hasScopeArrays = regionIds is not null || groupIds is not null || locationIds is not null;
if (mergedRegionIds.Count > 0)
{
type = AppliedRegionSpecified;
}
else if (hasScopeArrays && string.Equals(type, AppliedRegionAll, StringComparison.OrdinalIgnoreCase))
{
type = AppliedRegionAll;
}
if (type != AppliedRegionAll && type != AppliedRegionSpecified)
{
throw new UserFriendlyException("适用 Region 范围不合法(ALL/SPECIFIED)");
}
if (string.Equals(type, AppliedRegionAll, StringComparison.OrdinalIgnoreCase))
{
var loc = locationId?.Trim();
if (!string.IsNullOrWhiteSpace(loc))
{
await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(db, new List { loc });
}
return new LabelRegionScopeSaveResult
{
AppliedRegionType = AppliedRegionAll,
RegionIds = new List(),
LocationId = string.IsNullOrWhiteSpace(loc) ? null : loc
};
}
if (mergedRegionIds.Count == 0 && string.IsNullOrWhiteSpace(locationId))
{
throw new UserFriendlyException("请选择适用 Region(regionIds),或指定门店 locationId");
}
await ValidateRegionIdsExistAsync(db, mergedRegionIds);
var explicitLocationId = locationId?.Trim();
var explicitLocationIds = LocationScopeBindingHelper.NormalizeIds(locationIds);
var mergedLocations = await LocationScopeBindingHelper.MergeToLocationIdsAsync(
db, (IReadOnlyList?)null, mergedRegionIds, explicitLocationIds);
string? resolvedLocationId;
if (!string.IsNullOrWhiteSpace(explicitLocationId))
{
if (mergedLocations.Count > 0 && !mergedLocations.Contains(explicitLocationId, StringComparer.Ordinal))
{
throw new UserFriendlyException("所选门店不在指定 Region 范围内");
}
await LocationScopeBindingHelper.ValidateLocationIdsExistAsync(db, new List { explicitLocationId });
resolvedLocationId = explicitLocationId;
}
else if (mergedLocations.Count == 0)
{
throw new UserFriendlyException("所选 Region 下无有效门店,请检查 Region 或补充 locationId");
}
else if (mergedLocations.Count == 1)
{
resolvedLocationId = mergedLocations[0];
}
else
{
resolvedLocationId = mergedLocations[0];
}
return new LabelRegionScopeSaveResult
{
AppliedRegionType = AppliedRegionSpecified,
RegionIds = mergedRegionIds,
LocationId = resolvedLocationId
};
}
public static async Task SaveLabelRegionsAsync(
ISqlSugarClient db,
IGuidGenerator guidGenerator,
string labelId,
string appliedRegionType,
IReadOnlyList regionIds,
string? currentUserId,
DateTime now)
{
var schema = await LabelRegionSchemaHelper.GetStatusAsync(db);
if (!schema.HasLabelRegionTable)
{
return;
}
await db.Deleteable()
.Where(x => x.LabelId == labelId)
.ExecuteCommandAsync();
if (!string.Equals(appliedRegionType, AppliedRegionSpecified, StringComparison.OrdinalIgnoreCase)
|| regionIds.Count == 0)
{
return;
}
var rows = regionIds.Select(gid => new FlLabelRegionDbEntity
{
Id = guidGenerator.Create().ToString(),
LabelId = labelId,
GroupId = gid,
CreationTime = now,
CreatorId = currentUserId
}).ToList();
await db.Insertable(rows).ExecuteCommandAsync();
}
public static async Task> GetRegionIdsForLabelAsync(ISqlSugarClient db, string labelId)
{
var schema = await LabelRegionSchemaHelper.GetStatusAsync(db);
if (!schema.HasLabelRegionTable)
{
return new List();
}
return LocationScopeBindingHelper.NormalizeIds(
await db.Queryable()
.Where(x => x.LabelId == labelId)
.Select(x => x.GroupId)
.ToListAsync());
}
public static async Task BuildScopeDisplayAsync(
ISqlSugarClient db,
string labelId,
string? appliedRegionType,
string? locationId)
{
if (string.Equals(appliedRegionType, AppliedRegionAll, StringComparison.OrdinalIgnoreCase))
{
return new LabelRegionScopeDisplay
{
Region = AllRegionsDisplay,
RegionIds = new List(),
GroupIds = new List()
};
}
var regionIds = await GetRegionIdsForLabelAsync(db, labelId);
if (regionIds.Count == 0 && !string.IsNullOrWhiteSpace(locationId))
{
regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
db, new List { locationId.Trim() });
}
if (regionIds.Count == 0)
{
return new LabelRegionScopeDisplay
{
Region = "无",
RegionIds = new List(),
GroupIds = new List()
};
}
var names = await db.Queryable()
.Where(g => !g.IsDeleted && regionIds.Contains(g.Id))
.OrderBy(g => g.GroupName)
.Select(g => g.GroupName)
.ToListAsync();
var regionText = names.Count > 0
? string.Join(", ", names.Where(n => !string.IsNullOrWhiteSpace(n)).Select(n => n!.Trim()).Distinct())
: "无";
return new LabelRegionScopeDisplay
{
Region = regionText,
RegionIds = regionIds,
GroupIds = regionIds
};
}
///
/// 列表/筛选:标签是否落在指定 Region(fl_group.Id)或关联门店范围内。
///
public static ISugarQueryable ApplyLabelRegionListFilter(
ISugarQueryable query,
string groupId,
List? scopedLocationIds,
LabelRegionSchemaHelper.LabelRegionSchemaStatus schema)
{
var gid = groupId.Trim();
if (scopedLocationIds is not { Count: > 0 })
{
return query.Where(_ => false);
}
if (!schema.HasLabelRegionTable && !schema.HasAppliedRegionTypeColumn)
{
return query.Where(l => scopedLocationIds.Contains(l.LocationId));
}
if (!schema.HasLabelRegionTable)
{
return query.Where(
"(AppliedRegionType = @all OR LocationId IN (@locs))",
new { all = AppliedRegionAll, locs = scopedLocationIds });
}
if (!schema.HasAppliedRegionTypeColumn)
{
return query.Where(l =>
SqlFunc.Subqueryable()
.Where(lr => lr.LabelId == l.Id && lr.GroupId == gid)
.Any() ||
scopedLocationIds.Contains(l.LocationId));
}
return query.Where(
"""
(AppliedRegionType = @all
OR EXISTS (SELECT 1 FROM fl_label_region lr WHERE lr.LabelId = fl_label.Id AND lr.GroupId = @gid)
OR LocationId IN (@locs))
""",
new { all = AppliedRegionAll, gid, locs = scopedLocationIds });
}
///
/// 解析门店所属的 Region Id(用于 App 树、打印校验)。
///
public static async Task> ResolveGroupIdsForLocationAsync(ISqlSugarClient db, string locationId)
{
return await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(
db, new List { locationId.Trim() });
}
///
/// 标签是否适用于指定门店(LocationId 命中、Region 关联或 ALL)。
///
///
/// 校验标签是否适用于当前门店(否则抛出友好异常)。
///
public static async Task EnsureLabelAppliesToLocationAsync(
ISqlSugarClient db,
string labelId,
string locationId)
{
var label = await db.Queryable()
.FirstAsync(x => !x.IsDeleted && x.Id == labelId);
var groupIds = await ResolveGroupIdsForLocationAsync(db, locationId);
if (!await LabelAppliesToLocationAsync(db, label, locationId, groupIds))
{
throw new UserFriendlyException("该标签不属于当前门店或未配置适用 Region");
}
}
public static async Task LabelAppliesToLocationAsync(
ISqlSugarClient db,
FlLabelDbEntity label,
string locationId,
List groupIdsForLocation)
{
var appliedType = await LabelRegionSchemaHelper.GetAppliedRegionTypeForLabelAsync(
db, label.Id, label.AppliedRegionType);
if (string.Equals(appliedType, AppliedRegionAll, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrWhiteSpace(label.LocationId) &&
string.Equals(label.LocationId.Trim(), locationId, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (groupIdsForLocation.Count == 0)
{
return false;
}
var schema = await LabelRegionSchemaHelper.GetStatusAsync(db);
if (!schema.HasLabelRegionTable)
{
return false;
}
return await db.Queryable()
.AnyAsync(lr => lr.LabelId == label.Id && groupIdsForLocation.Contains(lr.GroupId));
}
private static List NormalizeRegionIds(
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 ValidateRegionIdsExistAsync(ISqlSugarClient db, List regionIds)
{
if (regionIds.Count == 0)
{
return;
}
var count = await db.Queryable()
.Where(g => !g.IsDeleted && regionIds.Contains(g.Id))
.CountAsync();
if (count != regionIds.Count)
{
throw new UserFriendlyException("存在无效的 Region Id,请刷新后重试");
}
}
}