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);
}
}