feat(dal, templateMatcher): UnitFilterService теперь возвращает модель Dto а не только Id

This commit is contained in:
Mikhail Kuznetsov
2026-01-21 18:25:09 +10:00
parent eb5bed5614
commit 555b946daa
15 changed files with 1105 additions and 968 deletions

View File

@@ -6,6 +6,7 @@ using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.UnitFilterService;
using PARR.DAL.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Models.Unit;
@@ -170,15 +171,18 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships);
// 3. Использовать фильтры maxJob для получения expectedUnitIds
var expectedUnitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(maxJob.Id);
if (expectedUnitIds == null || !expectedUnitIds.Any())
// 3. Использовать фильтры maxJob для получения отфильтрованных юнитов
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id);
if (filteredUnits == null || !filteredUnits.Any())
{
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов");
return;
}
// Извлекаем ID юнитов для последующих операций
var expectedUnitIds = filteredUnits.Select(u => u.Id).ToList();
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
if (!jobGroup.GroupingUnitFieldId.HasValue)
{
@@ -188,15 +192,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
}
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
var unitIdsWithValidGroupingFieldSet = await unitService.Get()
.AsNoTracking()
.Where(u => expectedUnitIds.Contains(u.Id))
.Where(u => u.UnitValues.Any(uv =>
uv.FieldId == groupingFieldId &&
uv.Value != null &&
!string.IsNullOrEmpty(uv.Value.Value)))
// Фильтруем юниты по GroupingUnitFieldId используя данные из DTO
var unitIdsWithValidGroupingFieldSet = filteredUnits
.Where(u => u.Values.Any(v =>
v.FieldId == groupingFieldId &&
!string.IsNullOrEmpty(v.Value)))
.Select(u => u.Id)
.ToListAsync();
.ToList();
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
@@ -208,15 +211,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
}
// Аналогично для фильтрации по "РАБОЧАЯ_ГР_ОТВ_ЗАК"
var unitIdsWithValidWorkGroupFieldSet = await unitService.Get()
.AsNoTracking()
.Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id))
.Where(u => u.UnitValues.Any(uv =>
uv.FieldId == workGroupFieldId &&
uv.Value != null &&
relationshipGroupValueIds.Contains(uv.Value.Id)))
var unitIdsWithValidWorkGroupFieldSet = filteredUnits
.Where(u => u.Values.Any(v =>
v.FieldId == workGroupFieldId &&
relationshipGroupValueIds.Contains(v.FieldId) &&
!string.IsNullOrEmpty(v.Value)))
.Select(u => u.Id)
.ToListAsync();
.ToList();
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
@@ -233,6 +234,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию...");
// Используем unitIdsWithValidGroupingFieldSet для получения связей
var potentialUnitInUnitLinks = await unitInUnitService.Get()
.AsNoTracking()
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
@@ -240,30 +243,26 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
// Подготавливаем данные для фильтрации связей
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
var parentUnitValues = await unitInValueService.Get()
.AsNoTracking()
.Include(uv => uv.Field)
.Include(uv => uv.Value)
.Where(uv => allParentIds.Contains(uv.UnitId))
.ToListAsync();
// Фильтруем юниты для получения родительских и дочерних значений
var parentUnits = filteredUnits.Where(u => allParentIds.Contains(u.Id)).ToList();
var childUnits = filteredUnits.Where(u => allChildIds.Contains(u.Id)).ToList();
var childUnitValues = await unitInValueService.Get()
.AsNoTracking()
.Include(uv => uv.Field)
.Include(uv => uv.Value)
.Where(uv => allChildIds.Contains(uv.UnitId))
.ToListAsync();
// Создаем словари значений для родителей и детей
var parentValuesMap = parentUnits
.ToDictionary(
u => u.Id,
u => u.Values.ToDictionary(v => v.FieldId, v => v.Value)
);
var parentValuesMap = parentUnitValues
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
var childValuesMap = childUnitValues
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
var childValuesMap = childUnits
.ToDictionary(
u => u.Id,
u => u.Values.ToDictionary(v => v.FieldId, v => v.Value)
);
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
@@ -273,15 +272,37 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
bool linkMatchesAllFilters = true;
foreach (var rf in relationshipFilters)
{
var valuesToCheck = rf.IsParent ? parentValuesMap.GetValueOrDefault(link.ParentUnitId, new List<UnitInValue>()) : childValuesMap.GetValueOrDefault(link.ChildUnitId, new List<UnitInValue>());
bool filterMatch = valuesToCheck.Any(uv =>
uv.FieldId == rf.FieldId &&
uv.Value != null &&
uv.Value.Value != null &&
uv.Value.Value.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase)
);
bool filterMatch;
if (rf.IsParent)
{
// Родительские значения
if (parentValuesMap.TryGetValue(link.ParentUnitId, out var parentValues) &&
parentValues.TryGetValue(rf.FieldId, out var parentValue) &&
parentValue != null)
{
filterMatch = parentValue.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase);
}
else
{
filterMatch = false;
}
}
else
{
// Дочерние значения
if (childValuesMap.TryGetValue(link.ChildUnitId, out var childValues) &&
childValues.TryGetValue(rf.FieldId, out var childValue) &&
childValue != null)
{
filterMatch = childValue.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase);
}
else
{
filterMatch = false;
}
}
// Учитываем инверсию фильтра
if (rf.IsInverse)
filterMatch = !filterMatch;
@@ -350,9 +371,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.Distinct()
.ToList();
var unitNamesMap = await unitService.Get()
// Создаем словарь имен для конфликтующих ключей из отфильтрованных юнитов
var unitNamesMap = filteredUnits
.Where(u => conflictKeys.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.Name ?? string.Empty);
.ToDictionary(u => u.Id, u => u.Name);
foreach (var conflictedUnitEntry in unitToKeys.Where(kvp => kvp.Value.Count > 1))
{
@@ -404,10 +426,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
var childUnitNameMap = await unitService.Get()
.AsNoTracking()
// Получаем имена юнитов из отфильтрованных данных
var childUnitNameMap = filteredUnits
.Where(u => childUnitIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.Name);
.ToDictionary(u => u.Id, u => u.Name);
var sortedChildUnitIds = childUnitIds
.OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString()))
@@ -454,10 +476,11 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
// === 2. Сравниваем детерминированно с сортировкой по имени ===
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
var unitNamesForSort = await unitService.Get()
.AsNoTracking()
// Получаем имена юнитов из отфильтрованных данных
var unitNamesForSort = filteredUnits
.Where(u => allUnitIdsForSort.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
.ToDictionary(u => u.Id, u => u.Name);
var sortedCurrentUnitIds = currentUnitIds
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
@@ -566,10 +589,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{
var relationshipUnitId = kvp.Key;
var childUnitIds = kvp.Value;
var childUnitNameMapForDeactivate = await unitService.Get()
.AsNoTracking()
// Получаем имена юнитов из отфильтрованных данных
var childUnitNameMapForDeactivate = filteredUnits
.Where(u => childUnitIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.Name);
.ToDictionary(u => u.Id, u => u.Name);
var sortedChildUnitIdsForDeactivate = childUnitIds
.OrderBy(id => childUnitNameMapForDeactivate.GetValueOrDefault(id, id.ToString()))
.ToList();

View File

@@ -8,6 +8,7 @@ using PARR.DAL.Cache.Models;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.UnitFilterService;
using PARR.DAL.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces;
@@ -137,8 +138,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
return;
}
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
if (unitIds == null || !unitIds.Any())
// === Получение отфильтрованных юнитов с полной информацией ===
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(jobId);
if (filteredUnits == null || !filteredUnits.Any())
{
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
@@ -162,6 +164,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
return;
}
// Извлекаем ID юнитов для последующих операций
var unitIds = filteredUnits.Select(u => u.Id).ToList();
#if DEBUG
// Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
@@ -198,26 +203,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
.Where(t => !unitIds.Contains(t.UnitId))
.ToList();
// === Подсчёт операций ===
int toCreate = newUnitIds.Count;
int toUpdate = 0;
int toDeactivate = unusedTemplates.Count;
// Подсчёт обновлений по имени
foreach (var template in existingUsedTemplates)
{
if (unitIds.Contains(template.UnitId))
{
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
toUpdate++;
}
}
await UpdateMatchingStatusAsync(jobId, $"Осталось: создать={toCreate}, обновить={toUpdate}, деактивировать={toDeactivate}");
// === Обработка новых юнитов ===
int created = 0, updated = 0, deactivated = 0;
foreach (var unitId in newUnitIds)
{
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
@@ -262,8 +247,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
await CreateSimpleTemplateAsync(jobId, unitId, initiator);
}
created++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
// === Обработка существующих шаблонов (проверка имени) ===
@@ -295,8 +278,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
updated++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
}
}
@@ -306,8 +287,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
deactivated++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
// === Успешное завершение ===
@@ -372,8 +351,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
return;
}
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
if (unitIds == null || !unitIds.Any())
// === Получение отфильтрованных юнитов с полной информацией ===
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(jobId);
if (filteredUnits == null || !filteredUnits.Any())
{
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
await UpdateMatchingStatusAsync(jobId, "Нет Unit'ов — обновление не требуется");
@@ -381,6 +361,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
return;
}
// Извлекаем ID юнитов для последующих операций
var unitIds = filteredUnits.Select(u => u.Id).ToList();
#if DEBUG
// Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))