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
|
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
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var job = await LoadJobWithFiltersAsync(jobId);
|
var job = await LoadJobWithFiltersAsync(jobId, cancellationToken);
|
||||||
return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
|
return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
{
|
{
|
||||||
return await jobService
|
return await jobService
|
||||||
.Get().AsNoTracking()
|
.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.FieldFilters).ThenInclude(ff => ff.UnitField)
|
||||||
.Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters).ThenInclude(rf => rf.UnitField)
|
.Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters).ThenInclude(rf => rf.UnitField)
|
||||||
.Include(j => j.Group).ThenInclude(g => g!.GroupType)
|
.Include(j => j.Group).ThenInclude(g => g!.GroupType)
|
||||||
@@ -392,48 +392,89 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
result.Count, resultContexts.Count);
|
result.Count, resultContexts.Count);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
foreach (var context in result)
|
foreach (var context in result.Take(10))
|
||||||
{
|
{
|
||||||
var u = await unitService.GetAsync(context.UnitId);
|
var u = await unitService.GetAsync(context.UnitId);
|
||||||
|
|
||||||
if (u == null)
|
if (u == null)
|
||||||
continue;
|
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())
|
if (context.ValidParentIds.Any())
|
||||||
{
|
{
|
||||||
|
logger.LogDebug("\t📌 Фильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
||||||
|
|
||||||
var parents = context.ValidParentIds;
|
foreach (var parentId in context.ValidParentIds)
|
||||||
|
|
||||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", parents.Count);
|
|
||||||
|
|
||||||
foreach (var parent 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())
|
if (context.ValidChildIds.Any())
|
||||||
{
|
{
|
||||||
|
logger.LogDebug("\t Фильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
||||||
|
|
||||||
var children = context.ValidChildIds;
|
foreach (var childId in context.ValidChildIds)
|
||||||
|
|
||||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} детей:", children.Count);
|
|
||||||
|
|
||||||
foreach (var child in children)
|
|
||||||
{
|
{
|
||||||
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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +737,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
/// Оценивает, проходит ли юнит фильтр связей
|
/// Оценивает, проходит ли юнит фильтр связей
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool EvaluateFilter(
|
private bool EvaluateFilter(
|
||||||
List<Guid> unitRelations, // Все связи юнита (родители или дети)
|
List<Guid> unitRelations, // Все связи юнита
|
||||||
List<Guid> matchingRelations, // Связи, которые соответствуют маске
|
List<Guid> matchingRelations, // Связи, которые соответствуют маске
|
||||||
bool isFullMatch, // Полное совпадение (All) или хотя бы одно (Any)
|
bool isFullMatch, // Полное совпадение (All) или хотя бы одно (Any)
|
||||||
bool isInverse, // Обратный фильтр
|
bool isInverse, // Обратный фильтр
|
||||||
@@ -714,29 +755,33 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
return isInverse;
|
return isInverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Основная логика
|
// Основная логика — только для определения, проходит ли юнит
|
||||||
if (isFullMatch)
|
if (isFullMatch)
|
||||||
{
|
{
|
||||||
// Все связи должны соответствовать
|
// ВСЕ связи должны соответствовать
|
||||||
bool allMatch = matchingCount == totalCount;
|
bool allMatch = matchingCount == totalCount;
|
||||||
|
|
||||||
if (isInverse)
|
if (isInverse)
|
||||||
//Все связи не соответствуют фильтру
|
return !allMatch; // НЕ все связи соответствуют
|
||||||
return matchingCount == 0;
|
|
||||||
else
|
else
|
||||||
//Все связи соответствуют фильтру
|
return allMatch; // ВСЕ связи соответствуют
|
||||||
return allMatch;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Хотя бы одна связь должна соответствовать
|
// ХОТЯ БЫ ОДНА связь должна соответствовать
|
||||||
return matchingCount > 0;
|
bool anyMatch = matchingCount > 0;
|
||||||
|
|
||||||
|
if (isInverse)
|
||||||
|
return !anyMatch; // НИ ОДНА связь не соответствует
|
||||||
|
else
|
||||||
|
return anyMatch; // ХОТЯ БЫ ОДНА связь соответствует
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Применяет фильтры к связям (родителям или детям)
|
/// Применяет фильтры к связям (родителям или детям)
|
||||||
|
/// Связь должна пройти ВСЕ фильтры направления
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ApplyRelationshipFiltersAsync(
|
private async Task ApplyRelationshipFiltersAsync(
|
||||||
List<Guid> unitIds,
|
List<Guid> unitIds,
|
||||||
@@ -774,7 +819,10 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
var linksBySource = allLinks.GroupBy(l => l.SourceId)
|
var linksBySource = allLinks.GroupBy(l => l.SourceId)
|
||||||
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
||||||
|
|
||||||
// Обрабатываем каждый фильтр
|
// Находим связи, которые проходят ВСЕ фильтры одновременно
|
||||||
|
// Для каждого TargetId считаем, сколько фильтров он прошёл
|
||||||
|
var targetFilterPassCount = new Dictionary<Guid, int>();
|
||||||
|
|
||||||
foreach (var relFilter in relFilters)
|
foreach (var relFilter in relFilters)
|
||||||
{
|
{
|
||||||
var valueMask = relFilter.ValueMask?.Trim();
|
var valueMask = relFilter.ValueMask?.Trim();
|
||||||
@@ -799,7 +847,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
isParentDirection ? "Parent" : "Child",
|
isParentDirection ? "Parent" : "Child",
|
||||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||||
|
|
||||||
// Находим целевые юниты, которые соответствуют фильтру
|
// Находим целевые юниты, которые соответствуют текущему фильтру
|
||||||
var matchingTargetIds = await unitService.Get()
|
var matchingTargetIds = await unitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(u => allTargetIds.Contains(u.Id))
|
.Where(u => allTargetIds.Contains(u.Id))
|
||||||
@@ -813,44 +861,77 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
matchingTargetIds.Count,
|
matchingTargetIds.Count,
|
||||||
isParentDirection ? "родителей" : "детей");
|
isParentDirection ? "родителей" : "детей");
|
||||||
|
|
||||||
// Применяем логику к каждому юниту
|
// Считаем, сколько фильтров прошёл каждый TargetId
|
||||||
foreach (var context in resultContexts.Values)
|
foreach (var targetId in matchingTargetIds)
|
||||||
{
|
{
|
||||||
var unitTargets = linksBySource.GetValueOrDefault(context.UnitId, new List<Guid>());
|
if (!targetFilterPassCount.ContainsKey(targetId))
|
||||||
bool hasTargets = unitTargets.Any();
|
targetFilterPassCount[targetId] = 0;
|
||||||
|
targetFilterPassCount[targetId]++;
|
||||||
bool passesFilter = EvaluateFilter(
|
|
||||||
unitTargets,
|
|
||||||
matchingTargetIds,
|
|
||||||
relFilter.IsFullMatch,
|
|
||||||
relFilter.IsInverse,
|
|
||||||
hasTargets
|
|
||||||
);
|
|
||||||
|
|
||||||
if (passesFilter)
|
|
||||||
{
|
|
||||||
var validTargets = relFilter.IsInverse
|
|
||||||
? unitTargets.Except(matchingTargetIds)
|
|
||||||
: unitTargets.Intersect(matchingTargetIds);
|
|
||||||
|
|
||||||
if (isParentDirection)
|
|
||||||
context.ValidParentIds.UnionWith(validTargets);
|
|
||||||
else
|
|
||||||
context.ValidChildIds.UnionWith(validTargets);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Юнит не прошёл фильтр — очищаем его связи
|
|
||||||
if (isParentDirection)
|
|
||||||
context.ValidParentIds.Clear();
|
|
||||||
else
|
|
||||||
context.ValidChildIds.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalAdded = resultContexts.Sum(c =>
|
// ✅ Оставляем только те связи, которые прошли ВСЕ фильтры
|
||||||
isParentDirection ? c.Value.ValidParentIds.Count : c.Value.ValidChildIds.Count);
|
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(
|
||||||
|
validTargets, // Только связи, прошедшие ВСЕ фильтры
|
||||||
|
validTargets, // matchingRelations = validTargets (все валидные)
|
||||||
|
relFilters.All(rf => rf.IsFullMatch), // IsFullMatch для всех фильтров
|
||||||
|
relFilters.Any(rf => rf.IsInverse), // IsInverse для всех фильтров
|
||||||
|
hasTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
if (passesFilter && hasValidTargets)
|
||||||
|
{
|
||||||
|
// Сохраняем только валидные связи
|
||||||
|
if (isParentDirection)
|
||||||
|
context.ValidParentIds.UnionWith(validTargets);
|
||||||
|
else
|
||||||
|
context.ValidChildIds.UnionWith(validTargets);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Юнит не прошёл фильтр — помечаем на удаление
|
||||||
|
unitsToRemove.Add(context.UnitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем юниты, которые не прошли фильтры
|
||||||
|
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} юнитов",
|
logger.LogDebug(" Добавлено {Total} {TargetType} связей для {UnitCount} юнитов",
|
||||||
totalAdded,
|
totalAdded,
|
||||||
|
|||||||
Reference in New Issue
Block a user