feat(dal,templateMatcher): UnitFilterService переписан, теперь при подсчете количества связей для групп работ зонтик учитываются relationshipFilter. В TemplateMatcher добавлено изменение имени Unused шаблонов в конце имени добавляется DateTimeOffset.UtcNow.ToUnixTimeSeconds() для уникальности неиспользуемых шаблонов. Актуализированы unit-тесты
This commit is contained in:
@@ -31,13 +31,45 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
this.unitInValueService = unitInValueService;
|
||||
}
|
||||
|
||||
#region вложенные классы
|
||||
|
||||
private class UnitInfoDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
}
|
||||
|
||||
private class UnitValueDto
|
||||
{
|
||||
public Guid FieldId { get; set; }
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
|
||||
private class RelatedUnitDto
|
||||
{
|
||||
public Guid UnitId { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public List<UnitValueDto> Values { get; set; } = new();
|
||||
}
|
||||
|
||||
private class UnitDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public List<UnitValueDto> Values { get; set; } = new();
|
||||
public List<RelatedUnitDto> Parents { get; set; } = new();
|
||||
public List<RelatedUnitDto> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(Guid jobId, int? takeCount = null)
|
||||
{
|
||||
var job = await jobService
|
||||
.Get().AsNoTracking()
|
||||
.Include(j => j.UnitFilters).ThenInclude(uf => uf.FieldFilters)
|
||||
.Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters)
|
||||
.Include(j => j.Group).ThenInclude(g => g.GroupType)
|
||||
.Include(j => j.Group).ThenInclude(g => g!.GroupType)
|
||||
.FirstOrDefaultAsync(j => j.Id == jobId);
|
||||
|
||||
return job == null ? null : await GetUnitsIdByJobFilterAsync(job, takeCount);
|
||||
@@ -45,14 +77,12 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
|
||||
public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(Job job, int? takeCount = null)
|
||||
{
|
||||
#region Проверка обязательных зависимостей
|
||||
if (job.Group == null)
|
||||
throw new ArgumentNullException(nameof(job.Group), $"Job {job.Id} не содержит Group");
|
||||
if (job.Group.GroupType == null)
|
||||
throw new ArgumentNullException(nameof(job.Group.GroupType), $"Job {job.Id} не содержит GroupType");
|
||||
if (job.UnitFilters == null || !job.UnitFilters.Any())
|
||||
throw new ArgumentNullException(nameof(job.UnitFilters), $"Job {job.Id} не содержит UnitFilters");
|
||||
#endregion
|
||||
|
||||
var maxCount = takeCount ?? int.MaxValue;
|
||||
var collectedIds = new HashSet<Guid>();
|
||||
@@ -69,30 +99,171 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
{
|
||||
logger.LogDebug("Применяем фильтр #{Index} (Id={FilterId})", filterNumber, filter.Id);
|
||||
|
||||
var query = unitService.Get().AsNoTracking()
|
||||
.Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter));
|
||||
// 1️ Найти ID юнитов по UnitFilter (Name LIKE)
|
||||
var initialUnitIds = await unitService.Get().AsNoTracking()
|
||||
.Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
|
||||
logger.LogDebug("Базовый фильтр по Name: {NameFilter}", filter.UnitFilter);
|
||||
logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count);
|
||||
|
||||
if (!initialUnitIds.Any())
|
||||
{
|
||||
logger.LogDebug("Фильтр #{Index}: 0 юнитов после UnitFilter. Пропускаем.", filterNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2️ Загрузить UnitValues для этих юнитов (только FieldId из FieldFilter)
|
||||
var fieldFilterFieldIds = filter.FieldFilters.Select(f => f.FieldId).ToHashSet();
|
||||
var unitValues = fieldFilterFieldIds.Any()
|
||||
? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(initialUnitIds, fieldFilterFieldIds)
|
||||
: new List<UnitInValue>();
|
||||
|
||||
// 3️ Загрузить связи
|
||||
var parentRelFilters = filter.RelationshipFilters.Where(rf => rf.IsParent).ToList();
|
||||
var childRelFilters = filter.RelationshipFilters.Where(rf => !rf.IsParent).ToList();
|
||||
|
||||
var parentLinks = parentRelFilters.Any()
|
||||
? await unitInUnitService.GetParentLinksByChildIdsAsync(initialUnitIds)
|
||||
: new List<UnitInUnit>();
|
||||
|
||||
var childLinks = childRelFilters.Any()
|
||||
? await unitInUnitService.GetChildLinksByParentIdsAsync(initialUnitIds)
|
||||
: new List<UnitInUnit>();
|
||||
|
||||
// 4️ ID родителей и детей
|
||||
var parentUnitIds = parentLinks.Select(l => l.ParentUnitId).ToHashSet();
|
||||
var childUnitIds = childLinks.Select(l => l.ChildUnitId).ToHashSet();
|
||||
|
||||
// 5️ Загрузить родительские и дочерние юниты (Id + Name)
|
||||
var parentUnits = parentUnitIds.Any()
|
||||
? await unitService.Get().AsNoTracking()
|
||||
.Where(u => parentUnitIds.Contains(u.Id))
|
||||
.Select(u => new UnitInfoDto { Id = u.Id, Name = u.Name })
|
||||
.ToListAsync()
|
||||
: new List<UnitInfoDto>();
|
||||
|
||||
var childUnits = childUnitIds.Any()
|
||||
? await unitService.Get().AsNoTracking()
|
||||
.Where(u => childUnitIds.Contains(u.Id))
|
||||
.Select(u => new UnitInfoDto { Id = u.Id, Name = u.Name })
|
||||
.ToListAsync()
|
||||
: new List<UnitInfoDto>();
|
||||
|
||||
// 6️ UnitValues для родителей и детей (только FieldId из RelationshipFilter)
|
||||
var relFilterFieldIds = filter.RelationshipFilters.Select(f => f.FieldId).ToHashSet();
|
||||
|
||||
var parentUnitValues = relFilterFieldIds.Any() && parentUnitIds.Any()
|
||||
? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(parentUnitIds, relFilterFieldIds)
|
||||
: new List<UnitInValue>();
|
||||
|
||||
var childUnitValues = relFilterFieldIds.Any() && childUnitIds.Any()
|
||||
? await unitInValueService.GetByUnitIdsAndFieldIdsAsync(childUnitIds, relFilterFieldIds)
|
||||
: new List<UnitInValue>();
|
||||
|
||||
// 7️ Сборка DTO
|
||||
var unitMap = initialUnitIds.ToDictionary(id => id, id => new UnitDto { Id = id });
|
||||
|
||||
// UnitValues → Unit
|
||||
foreach (var uv in unitValues)
|
||||
{
|
||||
if (unitMap.TryGetValue(uv.UnitId, out var dto))
|
||||
{
|
||||
dto.Values.Add(new UnitValueDto { FieldId = uv.FieldId, Value = uv.Value?.Value });
|
||||
}
|
||||
}
|
||||
|
||||
var parentIdMap = parentUnits.ToDictionary(u => u.Id, u => u.Name);
|
||||
var childIdMap = childUnits.ToDictionary(u => u.Id, u => u.Name);
|
||||
|
||||
// ParentLinks → Parent
|
||||
foreach (var link in parentLinks)
|
||||
{
|
||||
if (unitMap.TryGetValue(link.ChildUnitId, out var dto))
|
||||
{
|
||||
dto.Parents.Add(new RelatedUnitDto
|
||||
{
|
||||
UnitId = link.ParentUnitId,
|
||||
Name = parentIdMap.GetValueOrDefault(link.ParentUnitId, "")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ChildLinks → Child
|
||||
foreach (var link in childLinks)
|
||||
{
|
||||
if (unitMap.TryGetValue(link.ParentUnitId, out var dto))
|
||||
{
|
||||
dto.Children.Add(new RelatedUnitDto
|
||||
{
|
||||
UnitId = link.ChildUnitId,
|
||||
Name = childIdMap.GetValueOrDefault(link.ChildUnitId, "")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Привязка значений к родителям
|
||||
var parentValuesMap = parentUnitValues
|
||||
.GroupBy(v => v.UnitId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(v => new UnitValueDto { FieldId = v.FieldId, Value = v.Value?.Value }).ToList());
|
||||
|
||||
foreach (var dto in unitMap.Values)
|
||||
{
|
||||
foreach (var parent in dto.Parents)
|
||||
{
|
||||
if (parentValuesMap.TryGetValue(parent.UnitId, out var values))
|
||||
{
|
||||
parent.Values.AddRange(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Привязка значений к детям
|
||||
var childValuesMap = childUnitValues
|
||||
.GroupBy(v => v.UnitId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(v => new UnitValueDto { FieldId = v.FieldId, Value = v.Value?.Value }).ToList());
|
||||
|
||||
foreach (var dto in unitMap.Values)
|
||||
{
|
||||
foreach (var child in dto.Children)
|
||||
{
|
||||
if (childValuesMap.TryGetValue(child.UnitId, out var values))
|
||||
{
|
||||
child.Values.AddRange(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8️ Применение фильтров
|
||||
var candidateUnits = unitMap.Values.AsQueryable();
|
||||
|
||||
foreach (var fieldFilter in filter.FieldFilters)
|
||||
{
|
||||
query = ApplyFieldFilter(query, fieldFilter);
|
||||
candidateUnits = ApplyFieldFilter(candidateUnits, fieldFilter);
|
||||
}
|
||||
|
||||
logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateUnits.Count());
|
||||
|
||||
foreach (var relFilter in filter.RelationshipFilters)
|
||||
{
|
||||
query = ApplyRelationshipFilterToQuery(query, relFilter);
|
||||
candidateUnits = ApplyRelationshipFilterToQuery(candidateUnits, relFilter);
|
||||
}
|
||||
|
||||
logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateUnits.Count());
|
||||
|
||||
// 9️⃣ Umbrella-фильтр
|
||||
var finalUnits = candidateUnits.AsEnumerable();
|
||||
if (job.Group.GroupType.Code == JobGroupTypesEnum.Umbrella)
|
||||
{
|
||||
query = ApplyRelationshipCountFilter(query, job);
|
||||
finalUnits = ApplyRelationshipCountFilter(finalUnits, job, filter.RelationshipFilters);
|
||||
logger.LogDebug("После Umbrella-фильтра осталось {Count} юнитов", finalUnits.Count());
|
||||
}
|
||||
|
||||
var newIds = await query
|
||||
// 10 Взять ID
|
||||
var newIds = finalUnits
|
||||
.Select(u => u.Id)
|
||||
.Take(remaining)
|
||||
.ToListAsync();
|
||||
.ToList();
|
||||
|
||||
collectedIds.UnionWith(newIds);
|
||||
logger.LogDebug("Фильтр #{Index}: найдено {Count} Unit'ов. Всего: {Total}",
|
||||
@@ -133,11 +304,7 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
|
||||
foreach (var filter in job.UnitFilters)
|
||||
{
|
||||
if (!filter.RelationshipFilters.Any())
|
||||
{
|
||||
logger.LogDebug("UnitFilter.Id {FilterId} не содержит RelationshipFilters. Пропускаем.", filter.Id);
|
||||
continue;
|
||||
}
|
||||
if (!filter.RelationshipFilters.Any()) continue;
|
||||
|
||||
logger.LogDebug("Обработка UnitFilter.Id {FilterId}. Количество RelationshipFilters: {RelFilterCount}", filter.Id, filter.RelationshipFilters.Count());
|
||||
|
||||
@@ -147,11 +314,7 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
rf.UnitFilterId, rf.IsParent, rf.FieldId, rf.ValueMask);
|
||||
|
||||
var valueMask = rf.ValueMask?.Trim();
|
||||
if (string.IsNullOrEmpty(valueMask))
|
||||
{
|
||||
logger.LogDebug("ValueMask пуст. Пропускаем фильтр (UnitFilterId={UnitFilterId}).", rf.UnitFilterId);
|
||||
continue;
|
||||
}
|
||||
if (string.IsNullOrEmpty(valueMask)) continue;
|
||||
|
||||
List<UnitInUnit> relevantLinks;
|
||||
if (rf.IsParent)
|
||||
@@ -159,207 +322,176 @@ namespace PARR.DAL.DomainServices.Implementations
|
||||
else
|
||||
relevantLinks = await unitInUnitService.GetByParentIdAsync(unitId);
|
||||
|
||||
logger.LogDebug("Найдено {Count} связей через UnitInUnitService.", relevantLinks.Count);
|
||||
|
||||
// Получаем Id юнитов, которые участвуют в связях (дети или родители)
|
||||
var unitIdsToCheck = rf.IsParent
|
||||
? relevantLinks.Select(l => l.ParentUnitId).ToList()
|
||||
: relevantLinks.Select(l => l.ChildUnitId).ToList();
|
||||
|
||||
if (!unitIdsToCheck.Any())
|
||||
{
|
||||
logger.LogDebug("Нет юнитов для проверки значений.");
|
||||
continue;
|
||||
}
|
||||
if (!unitIdsToCheck.Any()) continue;
|
||||
|
||||
// Получаем значения полей для этих юнитов
|
||||
var unitValues = await unitInValueService.GetByUnitIdsAsync(unitIdsToCheck);
|
||||
|
||||
logger.LogDebug("Получено {Count} значений полей через UnitInValueService.", unitValues.Count);
|
||||
|
||||
// Фильтруем юниты, у которых есть нужное значение
|
||||
var matchingUnitIds = unitValues
|
||||
.Where(uv => uv.FieldId == rf.FieldId
|
||||
&& uv.Value != null
|
||||
&& uv.Value.Value != null
|
||||
&& uv.Value.Value.ToString()!.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
&& uv.Value?.Value != null
|
||||
&& uv.Value.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(uv => uv.UnitId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
logger.LogDebug("Найдено {Count} юнитов с подходящим значением поля.", matchingUnitIds.Count);
|
||||
|
||||
// Получаем имена этих юнитов через IUnitService
|
||||
if (matchingUnitIds.Any())
|
||||
{
|
||||
var matchingUnitNames = await unitService.Get().AsNoTracking()
|
||||
var names = await unitService.Get().AsNoTracking()
|
||||
.Where(u => matchingUnitIds.Contains(u.Id))
|
||||
.Select(u => u.Name)
|
||||
.ToListAsync();
|
||||
|
||||
logger.LogDebug("Найдены имена: [{Names}]", string.Join(", ", matchingUnitNames));
|
||||
result.UnionWith(matchingUnitNames);
|
||||
result.UnionWith(names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogDebug("Итоговый результат: [{Result}]", string.Join(", ", result));
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
#region Вспомогательные методы фильтрации
|
||||
#region вспомогательные методы
|
||||
|
||||
private IQueryable<Unit> ApplyFieldFilter(IQueryable<Unit> query, JobFieldFilter fieldFilter)
|
||||
private IQueryable<UnitDto> ApplyFieldFilter(IQueryable<UnitDto> query, JobFieldFilter fieldFilter)
|
||||
{
|
||||
var fieldId = fieldFilter.FieldId;
|
||||
var valueMask = fieldFilter.ValueMask?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(valueMask))
|
||||
return query;
|
||||
|
||||
logger.LogDebug("Фильтр по полю: FieldId={FieldId}, ValueMask={ValueMask}", fieldId, valueMask);
|
||||
logger.LogDebug("Применяем FieldFilterDto: FieldId={FieldId}, ValueMask={ValueMask}", fieldFilter.FieldId, valueMask);
|
||||
|
||||
return query.Where(unit =>
|
||||
unit.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
|
||||
return query.AsEnumerable().Where(dto =>
|
||||
dto.Values.Any(v =>
|
||||
v.FieldId == fieldFilter.FieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask)));
|
||||
(isStartsWith && isEndsWith ? v.Value.Contains(valueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isStartsWith ? v.Value.StartsWith(valueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isEndsWith ? v.Value.EndsWith(valueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
).AsQueryable();
|
||||
}
|
||||
|
||||
private IQueryable<Unit> ApplyRelationshipFilterToQuery(IQueryable<Unit> query, JobRelationshipFilter relFilter)
|
||||
private IQueryable<UnitDto> ApplyRelationshipFilterToQuery(IQueryable<UnitDto> query, JobRelationshipFilter relFilter)
|
||||
{
|
||||
var fieldId = relFilter.FieldId;
|
||||
var valueMask = relFilter.ValueMask ?? "";
|
||||
|
||||
var valueMask = relFilter.ValueMask?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
return query;
|
||||
|
||||
logger.LogDebug("Фильтр по связи: IsParent={IsParent}, IsInverse={IsInverse}, IsFullMatch={IsFullMatch}, FieldId={FieldId}, ValueMask={ValueMask}",
|
||||
relFilter.IsParent, relFilter.IsInverse, relFilter.IsFullMatch, fieldId, valueMask);
|
||||
logger.LogDebug("Применяем RelationshipFilterDto: IsParent={IsParent}, IsInverse={IsInverse}, IsFullMatch={IsFullMatch}, FieldId={FieldId}, ValueMask={ValueMask}",
|
||||
relFilter.IsParent, relFilter.IsInverse, relFilter.IsFullMatch, relFilter.FieldId, valueMask);
|
||||
|
||||
if (relFilter.IsParent)
|
||||
var isParent = relFilter.IsParent;
|
||||
var isInverse = relFilter.IsInverse;
|
||||
var isFullMatch = relFilter.IsFullMatch;
|
||||
var fieldId = relFilter.FieldId;
|
||||
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
|
||||
return query.AsEnumerable().Where(dto =>
|
||||
{
|
||||
if (relFilter.IsInverse)
|
||||
var links = isParent ? dto.Parents : dto.Children;
|
||||
|
||||
if (links == null || !links.Any())
|
||||
{
|
||||
if (relFilter.IsFullMatch)
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ParentUnits.Any() ||
|
||||
unit.ParentUnits.All(link =>
|
||||
!link.ParentUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ParentUnits.Any() ||
|
||||
unit.ParentUnits.Any(link =>
|
||||
!link.ParentUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
// Для IsFullMatch/IsInverse — "все подходят", т.е. true
|
||||
// Для остальных — false
|
||||
return isFullMatch && isInverse;
|
||||
}
|
||||
else
|
||||
|
||||
var hasMatchingLinks = links.Any(link =>
|
||||
{
|
||||
if (relFilter.IsFullMatch)
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ParentUnits.Any() ||
|
||||
unit.ParentUnits.All(link =>
|
||||
link.ParentUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return query.Where(unit =>
|
||||
unit.ParentUnits.Any(link =>
|
||||
link.ParentUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (relFilter.IsInverse)
|
||||
var hasMatch = link.Values.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
(isStartsWith && isEndsWith ? v.Value.Contains(valueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isStartsWith ? v.Value.StartsWith(valueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isEndsWith ? v.Value.EndsWith(valueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
);
|
||||
|
||||
return isInverse ? !hasMatch : hasMatch;
|
||||
});
|
||||
|
||||
if (isFullMatch)
|
||||
{
|
||||
if (relFilter.IsFullMatch)
|
||||
var allMatch = links.All(link =>
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ChildUnits.Any() ||
|
||||
unit.ChildUnits.All(link =>
|
||||
!link.ChildUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ChildUnits.Any() ||
|
||||
unit.ChildUnits.Any(link =>
|
||||
!link.ChildUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
var hasMatch = link.Values.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
(isStartsWith && isEndsWith ? v.Value.Contains(valueMask.Trim('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isStartsWith ? v.Value.StartsWith(valueMask.TrimEnd('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
isEndsWith ? v.Value.EndsWith(valueMask.TrimStart('%'), StringComparison.OrdinalIgnoreCase) :
|
||||
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
|
||||
);
|
||||
|
||||
return isInverse ? !hasMatch : hasMatch;
|
||||
});
|
||||
|
||||
return allMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (relFilter.IsFullMatch)
|
||||
{
|
||||
return query.Where(unit =>
|
||||
!unit.ChildUnits.Any() ||
|
||||
unit.ChildUnits.All(link =>
|
||||
link.ChildUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return query.Where(unit =>
|
||||
unit.ChildUnits.Any(link =>
|
||||
link.ChildUnit!.UnitValues.Any(v =>
|
||||
v.FieldId == fieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Value != null &&
|
||||
EF.Functions.Like(v.Value.Value, valueMask))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasMatchingLinks;
|
||||
}).AsQueryable();
|
||||
}
|
||||
|
||||
private IQueryable<Unit> ApplyRelationshipCountFilter(IQueryable<Unit> query, Job job)
|
||||
private IEnumerable<UnitDto> ApplyRelationshipCountFilter(
|
||||
IEnumerable<UnitDto> units,
|
||||
Job job,
|
||||
IEnumerable<JobRelationshipFilter> relationshipFilters)
|
||||
{
|
||||
int min = job.MinValueRelationships.GetValueOrDefault(0);
|
||||
int max = job.MaxValueRelationships.GetValueOrDefault(int.MaxValue);
|
||||
var min = job.MinValueRelationships ?? 0;
|
||||
var max = job.MaxValueRelationships ?? int.MaxValue;
|
||||
var isParentDirection = job.IsParentRelationships == true;
|
||||
|
||||
logger.LogDebug("Фильтр по количеству связей: Min={Min}, Max={Max}, IsParent={IsParent}", min, max, job.IsParentRelationships);
|
||||
if (min == 0 && max == int.MaxValue)
|
||||
return units;
|
||||
|
||||
if (job.IsParentRelationships == true)
|
||||
var activeFilters = relationshipFilters
|
||||
.Where(rf => rf.IsParent == isParentDirection && !string.IsNullOrWhiteSpace(rf.ValueMask))
|
||||
.ToList();
|
||||
|
||||
if (activeFilters.Count == 0)
|
||||
return units;
|
||||
|
||||
logger.LogDebug("RelationshipFilter: Min={Min}, Max={Max}, IsParent={IsParent}, Filters={Count}",
|
||||
min, max, isParentDirection, activeFilters.Count);
|
||||
|
||||
return units.Where(dto =>
|
||||
{
|
||||
return query.Where(unit => unit.ParentUnits.Count >= min && unit.ParentUnits.Count <= max);
|
||||
}
|
||||
else
|
||||
{
|
||||
return query.Where(unit => unit.ChildUnits.Count >= min && unit.ChildUnits.Count <= max);
|
||||
}
|
||||
var links = isParentDirection ? dto.Parents : dto.Children;
|
||||
|
||||
if (links == null || !links.Any())
|
||||
return min == 0;
|
||||
|
||||
int matchingCount = 0;
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
bool hasMatch = link.Values.Any(v =>
|
||||
activeFilters.Any(f =>
|
||||
v.FieldId == f.FieldId &&
|
||||
v.Value != null &&
|
||||
v.Value.Contains(f.ValueMask, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
|
||||
if (hasMatch)
|
||||
matchingCount++;
|
||||
|
||||
if (matchingCount > max)
|
||||
break;
|
||||
}
|
||||
|
||||
return matchingCount >= min && matchingCount <= max;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user