feat(templateMatcher): Убран хардкод РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК при формировании шаблонов; добавлен метод для поиска аттрибутов ЭК по значению Code.
This commit is contained in:
@@ -3,9 +3,20 @@ using Microsoft.Extensions.Logging;
|
|||||||
using PARR.Core.Repositories.Interfaces.Job;
|
using PARR.Core.Repositories.Interfaces.Job;
|
||||||
using PARR.Domain.Entities.Base.History;
|
using PARR.Domain.Entities.Base.History;
|
||||||
using PARR.Domain.Entities.Job;
|
using PARR.Domain.Entities.Job;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PARR.Core.Common.Helpers;
|
namespace PARR.Core.Common.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис для синхронизации свойств Job внутри JobGroup.
|
||||||
|
/// Эталоном считается Job с максимальным MaxValueRelationships.
|
||||||
|
/// Синхронизируются: WorkName, маски, IsParentRelationships, AutoControl, UnitFilters.
|
||||||
|
/// НЕ синхронизируются: Id, Name, MinValueRelationships, MaxValueRelationships, TnkId.
|
||||||
|
/// </summary>
|
||||||
public class JobGroupUnitFilterSynchronizeHelper
|
public class JobGroupUnitFilterSynchronizeHelper
|
||||||
{
|
{
|
||||||
private readonly IJobRepository jobRepository;
|
private readonly IJobRepository jobRepository;
|
||||||
@@ -25,12 +36,13 @@ public class JobGroupUnitFilterSynchronizeHelper
|
|||||||
.ThenInclude(uf => uf.FieldFilters)
|
.ThenInclude(uf => uf.FieldFilters)
|
||||||
.Include(j => j.UnitFilters)
|
.Include(j => j.UnitFilters)
|
||||||
.ThenInclude(uf => uf.RelationshipFilters)
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.Include(j => j.AutoControl)
|
||||||
.Where(j => j.GroupId == jobGroupId)
|
.Where(j => j.GroupId == jobGroupId)
|
||||||
.ToListAsync(ct);
|
.ToListAsync(ct);
|
||||||
|
|
||||||
if (jobs == null || jobs.Count < 2)
|
if (jobs == null || jobs.Count < 2)
|
||||||
{
|
{
|
||||||
logger.LogDebug("JobGroup {JobGroupId} содержит менее двух Job. Синхронизация фильтров не требуется.", jobGroupId);
|
logger.LogDebug("JobGroup {JobGroupId} содержит менее двух Job. Синхронизация не требуется.", jobGroupId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +53,7 @@ public class JobGroupUnitFilterSynchronizeHelper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Начало синхронизации фильтров для JobGroup {JobGroupId}. Эталонный Job: {ReferenceJobId}.", jobGroupId, referenceJob.Id);
|
logger.LogInformation("Начало синхронизации для JobGroup {JobGroupId}. Эталонный Job: {ReferenceJobId}.", jobGroupId, referenceJob.Id);
|
||||||
|
|
||||||
foreach (var job in jobs)
|
foreach (var job in jobs)
|
||||||
{
|
{
|
||||||
@@ -49,31 +61,84 @@ public class JobGroupUnitFilterSynchronizeHelper
|
|||||||
|
|
||||||
if (job.Id == referenceJob.Id) continue;
|
if (job.Id == referenceJob.Id) continue;
|
||||||
|
|
||||||
if (!AreUnitFiltersIdentical(job.UnitFilters, referenceJob.UnitFilters))
|
if (!AreJobsIdentical(job, referenceJob))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Фильтры Job {JobId} отличаются от эталона. Запуск обновления.", job.Id);
|
logger.LogDebug("Свойства Job {JobId} отличаются от эталона. Запуск обновления.", job.Id);
|
||||||
await ApplyFilterCorrectionAsync(job.Id, referenceJob.UnitFilters ?? Enumerable.Empty<JobUnitFilter>(), initiator, ct);
|
await ApplyJobCorrectionAsync(job.Id, referenceJob, initiator, ct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Фильтры Job {JobId} идентичны эталону. Пропуск.", job.Id);
|
logger.LogDebug("Свойства Job {JobId} идентичны эталону. Пропуск.", job.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Синхронизация фильтров для JobGroup {JobGroupId} завершена.", jobGroupId);
|
logger.LogInformation("Синхронизация для JobGroup {JobGroupId} завершена.", jobGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyFilterCorrectionAsync(Guid jobId, IEnumerable<JobUnitFilter> referenceFilters, HistoryInitiator initiator, CancellationToken ct)
|
private bool AreJobsIdentical(Job current, Job reference)
|
||||||
|
{
|
||||||
|
// Сравнение скалярных полей (кроме Id, Name, Min/MaxValueRelationships, TnkId)
|
||||||
|
if (current.WorkName != reference.WorkName) return false;
|
||||||
|
if (current.IsParentRelationships != reference.IsParentRelationships) return false;
|
||||||
|
if (current.TemplateNameMask != reference.TemplateNameMask) return false;
|
||||||
|
if (current.WorkGroupMask != reference.WorkGroupMask) return false;
|
||||||
|
if (current.ResponseAreaMask != reference.ResponseAreaMask) return false;
|
||||||
|
|
||||||
|
// Сравнение AutoControl
|
||||||
|
if (current.AutoControl == null && reference.AutoControl != null) return false;
|
||||||
|
if (current.AutoControl != null && reference.AutoControl == null) return false;
|
||||||
|
if (current.AutoControl != null && reference.AutoControl != null)
|
||||||
|
{
|
||||||
|
if (current.AutoControl.InitUsedTemplateState != reference.AutoControl.InitUsedTemplateState) return false;
|
||||||
|
if (current.AutoControl.InitUsedScheduleState != reference.AutoControl.InitUsedScheduleState) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сравнение фильтров
|
||||||
|
if (!AreUnitFiltersIdentical(current.UnitFilters, reference.UnitFilters)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyJobCorrectionAsync(Guid jobId, Job referenceJob, HistoryInitiator initiator, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var job = await jobRepository.Get()
|
var job = await jobRepository.Get()
|
||||||
.Include(j => j.UnitFilters)
|
.Include(j => j.UnitFilters)
|
||||||
.ThenInclude(uf => uf.FieldFilters)
|
.ThenInclude(uf => uf.FieldFilters)
|
||||||
.Include(j => j.UnitFilters)
|
.Include(j => j.UnitFilters)
|
||||||
.ThenInclude(uf => uf.RelationshipFilters)
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.Include(j => j.AutoControl)
|
||||||
.FirstOrDefaultAsync(j => j.Id == jobId, ct);
|
.FirstOrDefaultAsync(j => j.Id == jobId, ct);
|
||||||
|
|
||||||
if (job == null) return;
|
if (job == null) return;
|
||||||
|
|
||||||
|
// 1. Обновляем скалярные поля
|
||||||
|
job.WorkName = referenceJob.WorkName;
|
||||||
|
job.IsParentRelationships = referenceJob.IsParentRelationships;
|
||||||
|
job.TemplateNameMask = referenceJob.TemplateNameMask;
|
||||||
|
job.WorkGroupMask = referenceJob.WorkGroupMask;
|
||||||
|
job.ResponseAreaMask = referenceJob.ResponseAreaMask;
|
||||||
|
job.DateModified = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// 2. Обновляем AutoControl
|
||||||
|
if (referenceJob.AutoControl != null)
|
||||||
|
{
|
||||||
|
if (job.AutoControl == null)
|
||||||
|
{
|
||||||
|
job.AutoControl = new JobAutoControl
|
||||||
|
{
|
||||||
|
JobId = job.Id,
|
||||||
|
InitUsedTemplateState = referenceJob.AutoControl.InitUsedTemplateState,
|
||||||
|
InitUsedScheduleState = referenceJob.AutoControl.InitUsedScheduleState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.AutoControl.InitUsedTemplateState = referenceJob.AutoControl.InitUsedTemplateState;
|
||||||
|
job.AutoControl.InitUsedScheduleState = referenceJob.AutoControl.InitUsedScheduleState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Обновляем UnitFilters
|
||||||
if (job.UnitFilters == null)
|
if (job.UnitFilters == null)
|
||||||
{
|
{
|
||||||
job.UnitFilters = new List<JobUnitFilter>();
|
job.UnitFilters = new List<JobUnitFilter>();
|
||||||
@@ -83,14 +148,14 @@ public class JobGroupUnitFilterSynchronizeHelper
|
|||||||
job.UnitFilters.Clear();
|
job.UnitFilters.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
var clonedFilters = CloneFilters(referenceFilters, job.Id);
|
var clonedFilters = CloneFilters(referenceJob.UnitFilters ?? Enumerable.Empty<JobUnitFilter>(), job.Id);
|
||||||
foreach (var filter in clonedFilters)
|
foreach (var filter in clonedFilters)
|
||||||
{
|
{
|
||||||
job.UnitFilters!.Add(filter);
|
job.UnitFilters!.Add(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
await jobRepository.CommitAsync(initiator);
|
await jobRepository.CommitAsync(initiator);
|
||||||
logger.LogInformation("Фильтры Job {JobId} успешно обновлены.", jobId);
|
logger.LogInformation("Job {JobId} успешно синхронизирован с эталоном.", jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AreUnitFiltersIdentical(IEnumerable<JobUnitFilter>? current, IEnumerable<JobUnitFilter>? reference)
|
private bool AreUnitFiltersIdentical(IEnumerable<JobUnitFilter>? current, IEnumerable<JobUnitFilter>? reference)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace PARR.Core.Repositories.Interfaces.Unit
|
|||||||
{
|
{
|
||||||
public interface IUnitFieldRepository : IBaseRepository<UnitField>
|
public interface IUnitFieldRepository : IBaseRepository<UnitField>
|
||||||
{
|
{
|
||||||
Task<UnitField?> GetByAihitNameAsync(string name);
|
Task<UnitField?> GetByCodeAsync(string code, CancellationToken ct = default);
|
||||||
|
Task<UnitField?> GetByAihitNameAsync(string aihitName, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,24 @@ namespace PARR.DAL.Repositories.Unit
|
|||||||
internal class UnitFieldRepository : BaseRepository<UnitField>, IUnitFieldRepository
|
internal class UnitFieldRepository : BaseRepository<UnitField>, IUnitFieldRepository
|
||||||
{
|
{
|
||||||
public UnitFieldRepository(DataContext dataContext, ILogger<UnitFieldRepository> logger) : base(logger, dataContext) { }
|
public UnitFieldRepository(DataContext dataContext, ILogger<UnitFieldRepository> logger) : base(logger, dataContext) { }
|
||||||
|
public async Task<UnitField?> GetByCodeAsync(string code, CancellationToken ct = default)
|
||||||
public async Task<UnitField?> GetByAihitNameAsync(string name)
|
|
||||||
{
|
{
|
||||||
return await EntitySet.FirstOrDefaultAsync(uf => uf.AihitName.ToLower().Trim() == name.ToLower().Trim());
|
if (string.IsNullOrWhiteSpace(code))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return await Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(f => f.Code != null && EF.Functions.ILike(f.Code, code), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UnitField?> GetByAihitNameAsync(string aihitName, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(aihitName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return await Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(f => EF.Functions.ILike(f.AihitName, aihitName), ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
PARR.Domain/Constants/UnitFieldCodes.cs
Normal file
15
PARR.Domain/Constants/UnitFieldCodes.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace PARR.Domain.Constants
|
||||||
|
{
|
||||||
|
public class UnitFieldCodes
|
||||||
|
{
|
||||||
|
public const string IP = "ip";
|
||||||
|
public const string IsActive = "isActive";
|
||||||
|
public const string ResponseArea = "responseArea";
|
||||||
|
public const string WorkGroupResponseArea = "workGroupResponseArea";
|
||||||
|
public const string OS = "os";
|
||||||
|
public const string Responsible = "responsible";
|
||||||
|
public const string Tag = "tag";
|
||||||
|
public const string WorkGroup = "workGroup";
|
||||||
|
public const string EkStatus = "ekStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using PARR.Core.Repositories.Interfaces.Unit;
|
using PARR.Core.Repositories.Interfaces.Unit;
|
||||||
|
using PARR.Core.Services.Shortcodes;
|
||||||
|
using PARR.Domain.Constants;
|
||||||
|
using PARR.Domain.Entities;
|
||||||
using PARR.Domain.Entities.Job;
|
using PARR.Domain.Entities.Job;
|
||||||
using PARR.TemplateMatcher.Models;
|
using PARR.TemplateMatcher.Models;
|
||||||
using PARR.TemplateMatcher.Services.Interfaces;
|
using PARR.TemplateMatcher.Services.Interfaces;
|
||||||
@@ -12,15 +15,19 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
private readonly ILogger<GroupedTemplateBuilder> logger;
|
private readonly ILogger<GroupedTemplateBuilder> logger;
|
||||||
private readonly IUnitInValueRepository unitInValueRepository;
|
private readonly IUnitInValueRepository unitInValueRepository;
|
||||||
private readonly IUnitFieldRepository unitFieldRepository;
|
private readonly IUnitFieldRepository unitFieldRepository;
|
||||||
|
private readonly IShortcodesService shortcodesService;
|
||||||
|
|
||||||
public GroupedTemplateBuilder(
|
public GroupedTemplateBuilder(
|
||||||
ILogger<GroupedTemplateBuilder> logger,
|
ILogger<GroupedTemplateBuilder> logger,
|
||||||
IUnitInValueRepository unitInValueRepository,
|
IUnitInValueRepository unitInValueRepository,
|
||||||
IUnitFieldRepository unitFieldRepository)
|
IUnitFieldRepository unitFieldRepository,
|
||||||
|
IShortcodesService shortcodesService
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.unitInValueRepository = unitInValueRepository;
|
this.unitInValueRepository = unitInValueRepository;
|
||||||
this.unitFieldRepository = unitFieldRepository;
|
this.unitFieldRepository = unitFieldRepository;
|
||||||
|
this.shortcodesService = shortcodesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<GroupedTemplateGroup>> BuildAsync(
|
public async Task<List<GroupedTemplateGroup>> BuildAsync(
|
||||||
@@ -34,20 +41,18 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
if (!initialReverseMapping.Any())
|
if (!initialReverseMapping.Any())
|
||||||
return new List<GroupedTemplateGroup>();
|
return new List<GroupedTemplateGroup>();
|
||||||
|
|
||||||
// 1. Определяем поле для внутренней группировки
|
// 1. Определяем стратегию внутренней группировки
|
||||||
var isGroupByResponsible = jobGroup.IsGroupByResponsible == true;
|
// Если IsGroupByResponsible != true, используем WorkGroupMask через ShortcodesService
|
||||||
var innerGroupingFieldName = isGroupByResponsible ? "ОТВЕТСТВЕННЫЙ_ЗА_ЭК" : "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК";
|
bool useWorkGroupMask = jobGroup.IsGroupByResponsible != true;
|
||||||
|
|
||||||
var innerGroupingField = await unitFieldRepository.GetByAihitNameAsync(innerGroupingFieldName)
|
logger.LogDebug("Стратегия внутренней группировки: {Strategy}",
|
||||||
?? throw new InvalidOperationException($"Поле '{innerGroupingFieldName}' не найдено.");
|
useWorkGroupMask ? "WorkGroupMask" : "Поле 'Ответственный за ЭК'");
|
||||||
|
|
||||||
var innerGroupingFieldId = innerGroupingField.Id;
|
|
||||||
var groupingFieldId = jobGroup.GroupingUnitFieldId!.Value;
|
|
||||||
|
|
||||||
// 2. Собираем все исходные UnitId
|
// 2. Собираем все исходные UnitId
|
||||||
var allSourceUnitIds = initialReverseMapping.Values.SelectMany(ids => ids).Distinct().ToList();
|
var allSourceUnitIds = initialReverseMapping.Values.SelectMany(ids => ids).Distinct().ToList();
|
||||||
|
|
||||||
// 3. Загружаем UnitInValue для трансформации
|
// 3. Загружаем UnitInValue для трансформации по полю группировки из настроек JobGroup
|
||||||
|
var groupingFieldId = jobGroup.GroupingUnitFieldId!.Value;
|
||||||
var relevantUnitInValues = await unitInValueRepository.Get()
|
var relevantUnitInValues = await unitInValueRepository.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(uiv => allSourceUnitIds.Contains(uiv.UnitId) && uiv.FieldId == groupingFieldId)
|
.Where(uiv => allSourceUnitIds.Contains(uiv.UnitId) && uiv.FieldId == groupingFieldId)
|
||||||
@@ -58,9 +63,7 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
|
|
||||||
var uivLookup = relevantUnitInValues
|
var uivLookup = relevantUnitInValues
|
||||||
.GroupBy(x => x.UnitId)
|
.GroupBy(x => x.UnitId)
|
||||||
.ToDictionary(
|
.ToDictionary(g => g.Key, g => g.Select(x => x.ValueId).ToList());
|
||||||
g => g.Key,
|
|
||||||
g => g.Select(x => x.ValueId).ToList());
|
|
||||||
|
|
||||||
// 4. Трансформируем в reverseMapping с парами
|
// 4. Трансформируем в reverseMapping с парами
|
||||||
var reverseMapping = new Dictionary<Guid, List<(Guid UnitId, Guid UnitFieldValueId)>>();
|
var reverseMapping = new Dictionary<Guid, List<(Guid UnitId, Guid UnitFieldValueId)>>();
|
||||||
@@ -83,7 +86,7 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
var uniqueEntries = entries
|
var uniqueEntries = entries
|
||||||
.GroupBy(e => (e.UnitId, e.UnitFieldValueId))
|
.GroupBy(e => (e.UnitId, e.UnitFieldValueId))
|
||||||
.Select(g => g.First())
|
.Select(g => g.First())
|
||||||
.OrderBy(e => e.UnitId) // ИСПРАВЛЕНО: стабильная сортировка
|
.OrderBy(e => e.UnitId)
|
||||||
.ThenBy(e => e.UnitFieldValueId)
|
.ThenBy(e => e.UnitFieldValueId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -94,7 +97,50 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
if (!reverseMapping.Any())
|
if (!reverseMapping.Any())
|
||||||
return new List<GroupedTemplateGroup>();
|
return new List<GroupedTemplateGroup>();
|
||||||
|
|
||||||
// 5. Загружаем значения для внутренней группировки
|
// 5. Определяем значения для внутренней группировки
|
||||||
|
Dictionary<Guid, string> unitIdToGroupingValueMap;
|
||||||
|
|
||||||
|
if (useWorkGroupMask)
|
||||||
|
{
|
||||||
|
// Используем ShortcodesService для получения финальных значений WorkGroupMask
|
||||||
|
unitIdToGroupingValueMap = new Dictionary<Guid, string>();
|
||||||
|
|
||||||
|
foreach (var potentialUnitId in reverseMapping.Keys)
|
||||||
|
{
|
||||||
|
var relatedUnitIds = reverseMapping[potentialUnitId].Select(e => e.UnitId).Distinct().ToList();
|
||||||
|
|
||||||
|
foreach (var relatedUnitId in relatedUnitIds)
|
||||||
|
{
|
||||||
|
// Создаем временный Template для применения шорткодов
|
||||||
|
var tempTemplate = new Template
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "temp",
|
||||||
|
JobId = maxJob.Id,
|
||||||
|
UnitId = potentialUnitId, // Родительский юнит шаблона
|
||||||
|
Job = maxJob,
|
||||||
|
UnitsInTemplate = new List<UnitsInTemplate>
|
||||||
|
{
|
||||||
|
new UnitsInTemplate { UnitId = relatedUnitId, UnitFieldValueId = Guid.Empty }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var workGroupValue = await shortcodesService.ApplyShortcodesAsync(
|
||||||
|
maxJob.WorkGroupMask,
|
||||||
|
tempTemplate,
|
||||||
|
nameof(GroupedTemplateBuilder));
|
||||||
|
|
||||||
|
unitIdToGroupingValueMap[relatedUnitId] = workGroupValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Логика разделения по ответственному за ЭК через служебный код поля
|
||||||
|
var innerGroupingField = await unitFieldRepository.GetByCodeAsync(UnitFieldCodes.Responsible, ct)
|
||||||
|
?? throw new InvalidOperationException($"Поле с кодом '{UnitFieldCodes.Responsible}' не найдено в справочнике UnitField.");
|
||||||
|
|
||||||
|
var innerGroupingFieldId = innerGroupingField.Id;
|
||||||
var allUnitsInTemplatePairs = reverseMapping.Values.SelectMany(list => list).ToList();
|
var allUnitsInTemplatePairs = reverseMapping.Values.SelectMany(list => list).ToList();
|
||||||
var allUnitIdsForInnerGrouping = allUnitsInTemplatePairs.Select(e => e.UnitId).Distinct().ToList();
|
var allUnitIdsForInnerGrouping = allUnitsInTemplatePairs.Select(e => e.UnitId).Distinct().ToList();
|
||||||
|
|
||||||
@@ -103,9 +149,10 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
new HashSet<Guid> { innerGroupingFieldId },
|
new HashSet<Guid> { innerGroupingFieldId },
|
||||||
ct);
|
ct);
|
||||||
|
|
||||||
var unitIdToInnerGroupingValueMap = innerGroupingValues
|
unitIdToGroupingValueMap = innerGroupingValues
|
||||||
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
.Where(uv => uv.Value != null && uv.Value.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
||||||
.ToDictionary(uv => uv.UnitId, uv => uv.Value!.Value);
|
.ToDictionary(uv => uv.UnitId, uv => uv.Value!.Value!);
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Формируем итоговую структуру
|
// 6. Формируем итоговую структуру
|
||||||
var templateGroups = new List<GroupedTemplateGroup>();
|
var templateGroups = new List<GroupedTemplateGroup>();
|
||||||
@@ -117,7 +164,7 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder
|
|||||||
var unitsInTemplateForThisPotentialUnitId = kvp.Value;
|
var unitsInTemplateForThisPotentialUnitId = kvp.Value;
|
||||||
|
|
||||||
var innerGroupedUnits = unitsInTemplateForThisPotentialUnitId
|
var innerGroupedUnits = unitsInTemplateForThisPotentialUnitId
|
||||||
.GroupBy(entry => unitIdToInnerGroupingValueMap.GetValueOrDefault(entry.UnitId, "Нет данных"))
|
.GroupBy(entry => unitIdToGroupingValueMap.GetValueOrDefault(entry.UnitId, "Нет данных"))
|
||||||
.OrderBy(g => g.Key, StringComparer.Ordinal)
|
.OrderBy(g => g.Key, StringComparer.Ordinal)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user