using FoodLabeling.Application.Services.DbModels; using SqlSugar; namespace FoodLabeling.Application.Helpers; /// /// 「适用全部 Region/Location/Company」:保存为 ALL(不写关联快照),查询时动态包含后续新增的 Region/Location。 /// public static class AllScopeBindingHelper { public const string ScopeAll = "ALL"; public const string ScopeSpecified = "SPECIFIED"; public const string AllRegionsDisplay = FoodLabelingDisplayConsts.AllRegion; public const string AllLocationsDisplay = FoodLabelingDisplayConsts.AllLocation; public const string AllCompaniesDisplay = "All Companies"; /// 选中 Id 是否覆盖当前上下文中全部可选项(用于将「全选」规范为 ALL)。 public static bool IsFullIdSelection(IReadOnlyList selected, IReadOnlyList universe) { if (universe.Count == 0) { return selected.Count == 0; } var universeSet = new HashSet( universe.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()), StringComparer.OrdinalIgnoreCase); var selectedSet = new HashSet( selected.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()), StringComparer.OrdinalIgnoreCase); if (selectedSet.Count != universeSet.Count) { return false; } return selectedSet.SetEquals(universeSet); } /// /// 解析维度类型:显式 ALL、空数组 ALL、或 Id 列表全选时视为 ALL。 /// 入参显式 时保留 SPECIFIED 并落库全部 Id,不因「当前上下文全选」折叠为 ALL。 /// public static string ResolveDimensionType( string? declaredType, IReadOnlyList ids, bool hasArrayInPayload, bool isFullSelection) { if (IsDeclaredSpecified(declaredType)) { return ScopeSpecified; } if (isFullSelection || (ids.Count == 0 && IsDeclaredAll(declaredType))) { return ScopeAll; } if (ids.Count > 0) { return ScopeSpecified; } if (hasArrayInPayload && IsDeclaredAll(declaredType)) { return ScopeAll; } var normalized = (declaredType ?? ScopeAll).Trim().ToUpperInvariant(); return normalized == ScopeSpecified ? ScopeSpecified : ScopeAll; } public static bool IsDeclaredAll(string? type) => string.Equals((type ?? ScopeAll).Trim(), ScopeAll, StringComparison.OrdinalIgnoreCase); public static bool IsDeclaredSpecified(string? type) => string.Equals((type ?? string.Empty).Trim(), ScopeSpecified, StringComparison.OrdinalIgnoreCase); /// 全部 Company(fl_partner.Id)。 public static async Task> ResolveAllPartnerIdsAsync(ISqlSugarClient db) { var rows = await db.Queryable() .Where(x => !x.IsDeleted) .Select(x => x.Id) .ToListAsync(); return LocationScopeBindingHelper.NormalizeIds(rows); } /// 全部 Region;可按 Company 限定。 public static async Task> ResolveAllRegionIdsAsync( ISqlSugarClient db, IReadOnlyList? partnerIds) { var query = db.Queryable().Where(x => !x.IsDeleted); var pids = LocationScopeBindingHelper.NormalizeIds(partnerIds); if (pids.Count > 0) { query = query.Where(x => pids.Contains(x.PartnerId)); } var rows = await query.Select(x => x.Id).ToListAsync(); return LocationScopeBindingHelper.NormalizeIds(rows); } /// 全部门店;可按 Company / Region 限定。 public static async Task> ResolveAllLocationIdsAsync( ISqlSugarClient db, IReadOnlyList? partnerIds, IReadOnlyList? regionIds) { var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync( db, partnerIds, regionIds, null); return LocationScopeBindingHelper.NormalizeIds(merged); } /// Company 维度是否应存为 ALL(含「勾选了全部 Company」)。 public static async Task<(string Type, List Ids)> NormalizePartnerScopeAsync( ISqlSugarClient db, string? declaredType, IReadOnlyList? partnerIds, IReadOnlyList? companyIds, bool hasArrayInPayload) { var ids = LabelEntityPartnerScopeHelper.NormalizePartnerIds(partnerIds, companyIds); var allPartners = await ResolveAllPartnerIdsAsync(db); // 勾选当前全部 Company(含前端仍传 SPECIFIED + 全量 Id)→ 归档 ALL,后续新增 Company 动态适用 var isFull = ids.Count > 0 && IsFullIdSelection(ids, allPartners); var type = ResolveDimensionType(declaredType, ids, hasArrayInPayload, isFull); return (type, type == ScopeSpecified ? ids : new List()); } /// Region 维度是否应存为 ALL。 public static async Task<(string Type, List Ids)> NormalizeRegionScopeAsync( ISqlSugarClient db, string? declaredType, IReadOnlyList regionIds, bool hasArrayInPayload, IReadOnlyList? partnerIdsForContext) { var ids = LocationScopeBindingHelper.NormalizeIds(regionIds); var allRegions = await ResolveAllRegionIdsAsync(db, partnerIdsForContext); var isFull = ids.Count > 0 && IsFullIdSelection(ids, allRegions); var type = ResolveDimensionType(declaredType, ids, hasArrayInPayload, isFull); return (type, type == ScopeSpecified ? ids : new List()); } /// Location 维度是否应存为 ALL。 public static async Task<(string Type, List Ids)> NormalizeLocationScopeAsync( ISqlSugarClient db, string? declaredType, IReadOnlyList locationIds, bool hasArrayInPayload, IReadOnlyList? partnerIdsForContext, IReadOnlyList? regionIdsForContext) { var ids = LocationScopeBindingHelper.NormalizeIds(locationIds); var allLocations = await ResolveAllLocationIdsAsync(db, partnerIdsForContext, regionIdsForContext); var isFull = ids.Count > 0 && IsFullIdSelection(ids, allLocations); var type = ResolveDimensionType(declaredType, ids, hasArrayInPayload, isFull); return (type, type == ScopeSpecified ? ids : new List()); } /// /// 由 PartnerId / Region / Location 反推 Company 上下文,用于判断「Select All」是否覆盖该公司全部门店。 /// public static async Task?> ResolvePartnerContextFromScopeAsync( ISqlSugarClient db, string? partnerId, IReadOnlyList? regionOrGroupIds, IReadOnlyList? locationIds) { if (!string.IsNullOrWhiteSpace(partnerId)) { return new List { partnerId.Trim() }; } var regionIds = LocationScopeBindingHelper.NormalizeIds(regionOrGroupIds); if (regionIds.Count > 0) { var rows = await db.Queryable() .Where(g => !g.IsDeleted && regionIds.Contains(g.Id)) .Select(g => g.PartnerId) .ToListAsync(); var normalized = LocationScopeBindingHelper.NormalizeIds(rows); return normalized.Count > 0 ? normalized : null; } var locIds = LocationScopeBindingHelper.NormalizeIds(locationIds); if (locIds.Count == 0) { return null; } var fromLoc = await LocationScopeBindingHelper.ResolvePartnerIdsFromLocationIdsAsync(db, locIds); var fromLocNormalized = LocationScopeBindingHelper.NormalizeIds(fromLoc); return fromLocNormalized.Count > 0 ? fromLocNormalized : null; } /// /// 标签类型/分类/多选项/产品分类:Region+Location 合并后是否应视为 ALL。 /// public static async Task ShouldTreatMergedLocationScopeAsAllAsync( ISqlSugarClient db, string? declaredAvailabilityType, IReadOnlyList? regionIds, IReadOnlyList? locationIds, bool hasScopeArrays, IReadOnlyList? partnerIdsForContext) { if (IsDeclaredAll(declaredAvailabilityType) && LocationScopeBindingHelper.NormalizeIds(regionIds).Count == 0 && LocationScopeBindingHelper.NormalizeIds(locationIds).Count == 0) { return true; } if (hasScopeArrays && LocationScopeBindingHelper.NormalizeIds(regionIds).Count == 0 && LocationScopeBindingHelper.NormalizeIds(locationIds).Count == 0 && IsDeclaredAll(declaredAvailabilityType)) { return true; } // 前端 Select All 常传 SPECIFIED + 当前全量 Id:覆盖 Company 上下文全部门店时归档 ALL var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync( db, (IReadOnlyList?)null, regionIds, locationIds); if (merged.Count == 0) { return false; } var partnerContext = LocationScopeBindingHelper.NormalizeIds(partnerIdsForContext); if (partnerContext.Count == 0) { partnerContext = await ResolvePartnerContextFromScopeAsync(db, null, regionIds, locationIds) ?? new List(); } var allLocations = await ResolveAllLocationIdsAsync( db, partnerContext.Count > 0 ? partnerContext : null, null); return IsFullIdSelection(merged, allLocations); } /// 产品门店范围是否应存为 ALL(不写 fl_location_product 快照)。 public static async Task ShouldTreatProductLocationScopeAsAllAsync( ISqlSugarClient db, string? declaredAvailabilityType, string? partnerId, IReadOnlyList? groupIds, IReadOnlyList? locationIds, bool hasScopePayload) { if (IsDeclaredAll(declaredAvailabilityType) && string.IsNullOrWhiteSpace(partnerId) && LocationScopeBindingHelper.NormalizeIds(groupIds).Count == 0 && LocationScopeBindingHelper.NormalizeIds(locationIds).Count == 0) { return true; } if (!hasScopePayload) { return false; } var merged = await LocationScopeBindingHelper.MergeToLocationIdsAsync( db, partnerId, groupIds, locationIds); if (merged.Count == 0) { return false; } // 未传 partnerId 时从 Region/门店反推 Company,避免用「全系统门店」误判导致无法归档 ALL var partnerContext = await ResolvePartnerContextFromScopeAsync( db, partnerId, groupIds, locationIds); var allLocations = await ResolveAllLocationIdsAsync(db, partnerContext, null); return IsFullIdSelection(merged, allLocations); } /// /// 详情回显:AvailabilityType/Applied*=ALL 时不落快照,展开为当前可见 Company/Region/Location 全集(对齐 Label)。 /// 为 null 表示管理员全量;非 null 为可见门店范围。 /// public static async Task<(List PartnerIds, List RegionIds, List LocationIds)> ResolveDisplayIdsForAllScopeAsync( ISqlSugarClient db, IReadOnlyList? scopedLocationIds, IReadOnlyList? preferredPartnerIds = null) { var preferred = LocationScopeBindingHelper.NormalizeIds(preferredPartnerIds); if (scopedLocationIds is null) { var partners = preferred.Count > 0 ? preferred : await ResolveAllPartnerIdsAsync(db); var partnerContext = partners.Count > 0 ? partners : null; var regions = await ResolveAllRegionIdsAsync(db, partnerContext); var locations = await ResolveAllLocationIdsAsync(db, partnerContext, null); return (partners, regions, locations); } var locs = LocationScopeBindingHelper.NormalizeIds(scopedLocationIds); var regionIds = await LocationScopeBindingHelper.ResolveGroupIdsFromLocationIdsAsync(db, locs); var partnerIds = preferred.Count > 0 ? preferred : await LocationScopeBindingHelper.ResolvePartnerIdsFromLocationIdsAsync(db, locs); return (partnerIds, regionIds, locs); } }