Merge branch 'dev' of gitlab.dvgd.oao.rzd:devptk/parr/parr_api into dev
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
{
|
||||
internal class UnitFilterServiceOptions
|
||||
{
|
||||
public int LoadBatchSize { get; set; } = 200;
|
||||
public int LoadBatchSize { get; set; } = 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var job = await LoadJobWithFiltersAsync(jobId);
|
||||
var job = await LoadJobWithFiltersAsync(jobId, cancellationToken);
|
||||
return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
{
|
||||
return await jobService
|
||||
.Get().AsNoTracking()
|
||||
.AsSplitQuery() // ← Избегаем предупреждения EF Core о множественных коллекциях
|
||||
.AsSplitQuery()
|
||||
.Include(j => j.UnitFilters).ThenInclude(uf => uf.FieldFilters).ThenInclude(ff => ff.UnitField)
|
||||
.Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters).ThenInclude(rf => rf.UnitField)
|
||||
.Include(j => j.Group).ThenInclude(g => g!.GroupType)
|
||||
@@ -392,48 +392,89 @@ internal class UnitFilterService : IUnitFilterService
|
||||
result.Count, resultContexts.Count);
|
||||
|
||||
#if DEBUG
|
||||
foreach (var context in result)
|
||||
foreach (var context in result.Take(10))
|
||||
{
|
||||
var u = await unitService.GetAsync(context.UnitId);
|
||||
|
||||
if (u == null)
|
||||
continue;
|
||||
|
||||
logger.LogDebug("Найден ЭК {UnitName}", u.Name);
|
||||
logger.LogDebug("📦 Найден ЭК {UnitName} (Id={UnitId})", u.Name, u.Id);
|
||||
|
||||
// Получаем все FieldId из фильтров для отображения значений
|
||||
var parentFieldIds = relationshipFilters.Where(rf => rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
var childFieldIds = relationshipFilters.Where(rf => !rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
|
||||
// === РОДИТЕЛИ ===
|
||||
if (context.ValidParentIds.Any())
|
||||
{
|
||||
logger.LogDebug("\t📌 Фильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
||||
|
||||
var parents = context.ValidParentIds;
|
||||
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", parents.Count);
|
||||
|
||||
foreach (var parent in context.ValidParentIds)
|
||||
foreach (var parentId in context.ValidParentIds)
|
||||
{
|
||||
var p = await unitService.GetAsync(parent);
|
||||
var p = await unitService.GetAsync(parentId);
|
||||
logger.LogDebug("\t\t👤 {ParentName} (Id={ParentId})", p?.Name, parentId);
|
||||
|
||||
logger.LogDebug("\t\t {ParentName};", p?.Name);
|
||||
// Выводим значения только для полей из RelationshipFilters
|
||||
if (parentFieldIds.Any())
|
||||
{
|
||||
var parentValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(
|
||||
new[] { parentId },
|
||||
parentFieldIds
|
||||
);
|
||||
|
||||
foreach (var pv in parentValues)
|
||||
{
|
||||
var field = pv.Field;
|
||||
var value = pv.Value?.Value;
|
||||
logger.LogDebug("\t\t 🔹 {FieldName} = {FieldValue}",
|
||||
field?.AihitName ?? $"FieldId={pv.FieldId}",
|
||||
value ?? "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parentRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\t Родительские фильтры заданы, но подходящих родителей не найдено");
|
||||
}
|
||||
|
||||
// === ДЕТИ ===
|
||||
if (context.ValidChildIds.Any())
|
||||
{
|
||||
logger.LogDebug("\t Фильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
||||
|
||||
var children = context.ValidChildIds;
|
||||
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} детей:", children.Count);
|
||||
|
||||
foreach (var child in children)
|
||||
foreach (var childId in context.ValidChildIds)
|
||||
{
|
||||
var c = await unitService.GetAsync(child);
|
||||
var c = await unitService.GetAsync(childId);
|
||||
logger.LogDebug("\t\t {ChildName} (Id={ChildId})", c?.Name, childId);
|
||||
|
||||
logger.LogDebug("\t\t {ChildName};", c?.Name);
|
||||
// Выводим значения только для полей из RelationshipFilters
|
||||
if (childFieldIds.Any())
|
||||
{
|
||||
var childValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(
|
||||
new[] { childId },
|
||||
childFieldIds
|
||||
);
|
||||
|
||||
foreach (var cv in childValues)
|
||||
{
|
||||
var field = cv.Field;
|
||||
var value = cv.Value?.Value;
|
||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||
field?.AihitName ?? $"FieldId={cv.FieldId}",
|
||||
value ?? "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\t Дочерние фильтры заданы, но подходящих детей не найдено");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -696,7 +737,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
/// Оценивает, проходит ли юнит фильтр связей
|
||||
/// </summary>
|
||||
private bool EvaluateFilter(
|
||||
List<Guid> unitRelations, // Все связи юнита (родители или дети)
|
||||
List<Guid> unitRelations, // Все связи юнита
|
||||
List<Guid> matchingRelations, // Связи, которые соответствуют маске
|
||||
bool isFullMatch, // Полное совпадение (All) или хотя бы одно (Any)
|
||||
bool isInverse, // Обратный фильтр
|
||||
@@ -714,29 +755,33 @@ internal class UnitFilterService : IUnitFilterService
|
||||
return isInverse;
|
||||
}
|
||||
|
||||
// Основная логика
|
||||
// Основная логика — только для определения, проходит ли юнит
|
||||
if (isFullMatch)
|
||||
{
|
||||
// Все связи должны соответствовать
|
||||
// ВСЕ связи должны соответствовать
|
||||
bool allMatch = matchingCount == totalCount;
|
||||
|
||||
if (isInverse)
|
||||
//Все связи не соответствуют фильтру
|
||||
return matchingCount == 0;
|
||||
return !allMatch; // НЕ все связи соответствуют
|
||||
else
|
||||
//Все связи соответствуют фильтру
|
||||
return allMatch;
|
||||
return allMatch; // ВСЕ связи соответствуют
|
||||
}
|
||||
else
|
||||
{
|
||||
// Хотя бы одна связь должна соответствовать
|
||||
return matchingCount > 0;
|
||||
// ХОТЯ БЫ ОДНА связь должна соответствовать
|
||||
bool anyMatch = matchingCount > 0;
|
||||
|
||||
if (isInverse)
|
||||
return !anyMatch; // НИ ОДНА связь не соответствует
|
||||
else
|
||||
return anyMatch; // ХОТЯ БЫ ОДНА связь соответствует
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Применяет фильтры к связям (родителям или детям)
|
||||
/// Связь должна пройти ВСЕ фильтры направления
|
||||
/// </summary>
|
||||
private async Task ApplyRelationshipFiltersAsync(
|
||||
List<Guid> unitIds,
|
||||
@@ -774,7 +819,10 @@ internal class UnitFilterService : IUnitFilterService
|
||||
var linksBySource = allLinks.GroupBy(l => l.SourceId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
||||
|
||||
// Обрабатываем каждый фильтр
|
||||
// Находим связи, которые проходят ВСЕ фильтры одновременно
|
||||
// Для каждого TargetId считаем, сколько фильтров он прошёл
|
||||
var targetFilterPassCount = new Dictionary<Guid, int>();
|
||||
|
||||
foreach (var relFilter in relFilters)
|
||||
{
|
||||
var valueMask = relFilter.ValueMask?.Trim();
|
||||
@@ -799,7 +847,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
isParentDirection ? "Parent" : "Child",
|
||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||
|
||||
// Находим целевые юниты, которые соответствуют фильтру
|
||||
// Находим целевые юниты, которые соответствуют текущему фильтру
|
||||
var matchingTargetIds = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allTargetIds.Contains(u.Id))
|
||||
@@ -813,26 +861,55 @@ internal class UnitFilterService : IUnitFilterService
|
||||
matchingTargetIds.Count,
|
||||
isParentDirection ? "родителей" : "детей");
|
||||
|
||||
// Применяем логику к каждому юниту
|
||||
// Считаем, сколько фильтров прошёл каждый TargetId
|
||||
foreach (var targetId in matchingTargetIds)
|
||||
{
|
||||
if (!targetFilterPassCount.ContainsKey(targetId))
|
||||
targetFilterPassCount[targetId] = 0;
|
||||
targetFilterPassCount[targetId]++;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Оставляем только те связи, которые прошли ВСЕ фильтры
|
||||
int requiredFiltersCount = relFilters.Count(r => !string.IsNullOrWhiteSpace(r.ValueMask?.Trim()));
|
||||
var validTargetIds = targetFilterPassCount
|
||||
.Where(kvp => kvp.Value == requiredFiltersCount)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
logger.LogDebug(" Найдено {ValidCount} {TargetType}, прошедших ВСЕ {RequiredCount} фильтров",
|
||||
validTargetIds.Count,
|
||||
isParentDirection ? "родителей" : "детей",
|
||||
requiredFiltersCount);
|
||||
|
||||
// Список юнитов, которые не прошли фильтры (для удаления)
|
||||
var unitsToRemove = new HashSet<Guid>();
|
||||
|
||||
// Применяем к каждому юниту
|
||||
foreach (var context in resultContexts.Values)
|
||||
{
|
||||
if (unitsToRemove.Contains(context.UnitId))
|
||||
continue;
|
||||
|
||||
var unitTargets = linksBySource.GetValueOrDefault(context.UnitId, new List<Guid>());
|
||||
bool hasTargets = unitTargets.Any();
|
||||
|
||||
// Находим валидные связи (прошли ВСЕ фильтры)
|
||||
var validTargets = unitTargets.Intersect(validTargetIds).ToList();
|
||||
bool hasValidTargets = validTargets.Any();
|
||||
|
||||
// Оцениваем, проходит ли юнит фильтр
|
||||
bool passesFilter = EvaluateFilter(
|
||||
unitTargets,
|
||||
matchingTargetIds,
|
||||
relFilter.IsFullMatch,
|
||||
relFilter.IsInverse,
|
||||
validTargets, // Только связи, прошедшие ВСЕ фильтры
|
||||
validTargets, // matchingRelations = validTargets (все валидные)
|
||||
relFilters.All(rf => rf.IsFullMatch), // IsFullMatch для всех фильтров
|
||||
relFilters.Any(rf => rf.IsInverse), // IsInverse для всех фильтров
|
||||
hasTargets
|
||||
);
|
||||
|
||||
if (passesFilter)
|
||||
if (passesFilter && hasValidTargets)
|
||||
{
|
||||
var validTargets = relFilter.IsInverse
|
||||
? unitTargets.Except(matchingTargetIds)
|
||||
: unitTargets.Intersect(matchingTargetIds);
|
||||
|
||||
// Сохраняем только валидные связи
|
||||
if (isParentDirection)
|
||||
context.ValidParentIds.UnionWith(validTargets);
|
||||
else
|
||||
@@ -840,17 +917,21 @@ internal class UnitFilterService : IUnitFilterService
|
||||
}
|
||||
else
|
||||
{
|
||||
// Юнит не прошёл фильтр — очищаем его связи
|
||||
if (isParentDirection)
|
||||
context.ValidParentIds.Clear();
|
||||
else
|
||||
context.ValidChildIds.Clear();
|
||||
}
|
||||
// Юнит не прошёл фильтр — помечаем на удаление
|
||||
unitsToRemove.Add(context.UnitId);
|
||||
}
|
||||
}
|
||||
|
||||
var totalAdded = resultContexts.Sum(c =>
|
||||
isParentDirection ? c.Value.ValidParentIds.Count : c.Value.ValidChildIds.Count);
|
||||
// Удаляем юниты, которые не прошли фильтры
|
||||
foreach (var unitId in unitsToRemove)
|
||||
{
|
||||
resultContexts.Remove(unitId);
|
||||
}
|
||||
|
||||
logger.LogDebug(" Удалено {RemovedCount} юнитов, не прошедших фильтры", unitsToRemove.Count);
|
||||
|
||||
var totalAdded = resultContexts.Values.Sum(c =>
|
||||
isParentDirection ? c.ValidParentIds.Count : c.ValidChildIds.Count);
|
||||
|
||||
logger.LogDebug(" Добавлено {Total} {TargetType} связей для {UnitCount} юнитов",
|
||||
totalAdded,
|
||||
|
||||
Reference in New Issue
Block a user