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,请刷新后重试"); } } }