feat(templateMatcher): Убрана избыточная проверка связанных юнитов, теперь это выполняется в UnitFilter. Добавлена группировка по рабочей группе или ответственному за ЭК
This commit is contained in:
@@ -738,7 +738,8 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
|||||||
return NoContent;
|
return NoContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = $"gr_shcd_{jobGroupId:N}_{unitId:N}_{ComputeHash(fullShortcode)}";
|
//var cacheKey = $"gr_shcd_{jobGroupId:N}_{unitId:N}_{ComputeHash(fullShortcode)}";
|
||||||
|
var cacheKey = cacheService.GetKey(new[] { "grouped", "shortcode", $"{jobGroupId:N}", $"{unitId:N}" }, new[] { fullShortcode });
|
||||||
|
|
||||||
var cachedData = await cacheService.GetCachedDataAsync<CachedGroupedShortCode>(cacheKey).ConfigureAwait(false);
|
var cachedData = await cacheService.GetCachedDataAsync<CachedGroupedShortCode>(cacheKey).ConfigureAwait(false);
|
||||||
if (cachedData != null)
|
if (cachedData != null)
|
||||||
@@ -872,12 +873,5 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
|||||||
}
|
}
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ComputeHash(string input)
|
|
||||||
{
|
|
||||||
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
||||||
var hashedBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
|
|
||||||
return Convert.ToBase64String(hashedBytes).Replace('+', '-').Replace('/', '_').Substring(0, 16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,6 +566,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Формируем итоговый результат
|
/// Формируем итоговый результат
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -781,6 +782,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
return result.ToList();
|
return result.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#region вспомогательные методы
|
#region вспомогательные методы
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Применяет фильтры к связям (родителям или детям)
|
/// Применяет фильтры к связям (родителям или детям)
|
||||||
@@ -959,7 +961,6 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter, CancellationToken cancellationToken = default)
|
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// var cacheKey = cacheService.GetKey(new[] { "uf_ids", filter.UnitFilter }, isUseHash: true);
|
// var cacheKey = cacheService.GetKey(new[] { "uf_ids", filter.UnitFilter }, isUseHash: true);
|
||||||
@@ -991,7 +992,6 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обрабатывает маску LIKE для корректной работы с SQL
|
/// Обрабатывает маску LIKE для корректной работы с SQL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -109,61 +109,64 @@ namespace PARR.DAL.Services.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, HistoryInitiator initiator)
|
public async Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, Guid newJobId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Резервирую неиспользуемый шаблон с проверкой конфигураций роботов для UnitId: {UnitId}", newUnitId);
|
logger.LogDebug("Резервирую неиспользуемый шаблон с проверкой конфигураций роботов для UnitId: {UnitId}, JobId: {JobId}", newUnitId, newJobId);
|
||||||
|
|
||||||
var sql = @"
|
var sql = @"
|
||||||
UPDATE ""Templates""
|
UPDATE ""Templates""
|
||||||
SET ""StatusTypeId"" = @NewStatus,
|
SET ""StatusTypeId"" = @NewStatus,
|
||||||
""DateModified"" = @DateModified,
|
""DateModified"" = @DateModified,
|
||||||
""InitiatorIp"" = @InitiatorIp,
|
""UnitId"" = @NewUnitId,
|
||||||
""InitiatorParrComponentId"" = @InitiatorComponent,
|
""JobId"" = @NewJobId,
|
||||||
""InitiatorComment"" = @InitiatorComment
|
""InitiatorIp"" = @InitiatorIp,
|
||||||
WHERE ""Id"" = (
|
""InitiatorParrComponentId"" = @InitiatorComponent,
|
||||||
SELECT t.""Id""
|
""InitiatorComment"" = @InitiatorComment
|
||||||
FROM ""Templates"" t
|
WHERE ""Id"" = (
|
||||||
WHERE t.""StatusTypeId"" = @OldStatus
|
SELECT t.""Id""
|
||||||
AND t.""UnitId"" != @NewUnitId
|
FROM ""Templates"" t
|
||||||
AND EXISTS (
|
WHERE t.""StatusTypeId"" = @OldStatus
|
||||||
SELECT 1
|
AND t.""UnitId"" != @NewUnitId
|
||||||
FROM ""RobotConfigurations"" rc
|
AND EXISTS (
|
||||||
WHERE rc.""TemplateId"" = t.""Id""
|
SELECT 1
|
||||||
AND rc.""RobotCode"" = @RobotCode1
|
FROM ""RobotConfigurations"" rc
|
||||||
AND rc.""TaskStatusCode"" = @TaskStatus
|
WHERE rc.""TemplateId"" = t.""Id""
|
||||||
AND rc.""RobotStatusCode"" = @RobotStatus
|
AND rc.""RobotCode"" = @RobotCode1
|
||||||
)
|
AND rc.""TaskStatusCode"" = @TaskStatus
|
||||||
AND EXISTS (
|
AND rc.""RobotStatusCode"" = @RobotStatus
|
||||||
SELECT 1
|
)
|
||||||
FROM ""RobotConfigurations"" rc
|
AND EXISTS (
|
||||||
WHERE rc.""TemplateId"" = t.""Id""
|
SELECT 1
|
||||||
AND rc.""RobotCode"" = @RobotCode2
|
FROM ""RobotConfigurations"" rc
|
||||||
AND rc.""TaskStatusCode"" = @TaskStatus
|
WHERE rc.""TemplateId"" = t.""Id""
|
||||||
AND rc.""RobotStatusCode"" = @RobotStatus
|
AND rc.""RobotCode"" = @RobotCode2
|
||||||
)
|
AND rc.""TaskStatusCode"" = @TaskStatus
|
||||||
ORDER BY t.""DateCreated"" ASC
|
AND rc.""RobotStatusCode"" = @RobotStatus
|
||||||
LIMIT 1
|
)
|
||||||
)
|
ORDER BY t.""DateCreated"" ASC
|
||||||
RETURNING ""Id"";";
|
LIMIT 1
|
||||||
|
)
|
||||||
|
RETURNING ""Id"";";
|
||||||
|
|
||||||
var parameters = new[]
|
var parameters = new[]
|
||||||
{
|
{
|
||||||
new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating),
|
new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating),
|
||||||
new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow),
|
new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow),
|
||||||
new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value),
|
new NpgsqlParameter("@NewUnitId", newUnitId),
|
||||||
new NpgsqlParameter("@InitiatorComponent",
|
new NpgsqlParameter("@NewJobId", newJobId),
|
||||||
initiator.InitiatorParrComponentId.HasValue
|
new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value),
|
||||||
? (object)(int)initiator.InitiatorParrComponentId.Value
|
new NpgsqlParameter("@InitiatorComponent",
|
||||||
: DBNull.Value),
|
initiator.InitiatorParrComponentId.HasValue
|
||||||
new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value),
|
? (object)(int)initiator.InitiatorParrComponentId.Value
|
||||||
new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused),
|
: DBNull.Value),
|
||||||
new NpgsqlParameter("@NewUnitId", newUnitId),
|
new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value),
|
||||||
// Параметры для проверки конфигураций роботов
|
new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused),
|
||||||
new NpgsqlParameter("@RobotCode1", 1),
|
// Параметры для проверки конфигураций роботов
|
||||||
new NpgsqlParameter("@RobotCode2", 2),
|
new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder),
|
||||||
new NpgsqlParameter("@TaskStatus", 30),
|
new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder),
|
||||||
new NpgsqlParameter("@RobotStatus", 44)
|
new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok),
|
||||||
};
|
new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete)
|
||||||
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -175,19 +178,19 @@ namespace PARR.DAL.Services.Implementations
|
|||||||
|
|
||||||
if (reservedTemplateId != default(Guid))
|
if (reservedTemplateId != default(Guid))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Успешно зарезервирован шаблон с ID: {TemplateId} для UnitId: {UnitId}",
|
logger.LogInformation("Успешно зарезервирован шаблон с ID: {TemplateId} для UnitId: {UnitId}, JobId: {JobId}",
|
||||||
reservedTemplateId, newUnitId);
|
reservedTemplateId, newUnitId, newJobId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId} (не найдено подходящих конфигураций роботов)", newUnitId);
|
logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId}, JobId: {JobId} (не найдено подходящих конфигураций роботов)", newUnitId, newJobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reservedTemplateId;
|
return reservedTemplateId;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Ошибка при резервировании шаблона для UnitId: {UnitId}", newUnitId);
|
logger.LogError(ex, "Ошибка при резервировании шаблона для UnitId: {UnitId}, JobId: {JobId}", newUnitId, newJobId);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,18 @@ namespace PARR.DAL.Services.Interfaces
|
|||||||
{
|
{
|
||||||
Task<Template?> GetTemplateByNameAsync(string name);
|
Task<Template?> GetTemplateByNameAsync(string name);
|
||||||
|
|
||||||
Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, HistoryInitiator initiator);
|
/// <summary>
|
||||||
|
/// Резервирует неиспользуемый шаблон для указанного ЭК и работы.
|
||||||
|
/// Для корректного расчета NextRun назначает захваченному шаблону ЭК и работу
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newUnitId">Id ЭК.</param>
|
||||||
|
/// <param name="newJobId">Id работы.</param>
|
||||||
|
/// <param name="initiator">Данные инициатора операции.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Id зарезервированного шаблона или <see langword="null"/>,
|
||||||
|
/// если подходящий шаблон не найден.
|
||||||
|
/// </returns>
|
||||||
|
Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, Guid newJobId, HistoryInitiator initiator);
|
||||||
|
|
||||||
IQueryable<Template> GetWithIncludes();
|
IQueryable<Template> GetWithIncludes();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ using PARR.BLL.Services.Interfaces;
|
|||||||
using PARR.Common.Domain;
|
using PARR.Common.Domain;
|
||||||
using PARR.Constants;
|
using PARR.Constants;
|
||||||
using PARR.DAL.Cache.Models;
|
using PARR.DAL.Cache.Models;
|
||||||
|
|
||||||
using PARR.DAL.DomainServices.Interfaces;
|
using PARR.DAL.DomainServices.Interfaces;
|
||||||
|
using PARR.DAL.DomainServices.Shortcodes;
|
||||||
using PARR.DAL.DomainServices.UnitFilterService;
|
using PARR.DAL.DomainServices.UnitFilterService;
|
||||||
using PARR.DAL.Models;
|
using PARR.DAL.Models;
|
||||||
using PARR.DAL.Models.Job;
|
using PARR.DAL.Models.Job;
|
||||||
using PARR.DAL.Models.Unit;
|
|
||||||
using PARR.DAL.NextRunServices;
|
|
||||||
using PARR.DAL.Services.Interfaces;
|
using PARR.DAL.Services.Interfaces;
|
||||||
using PARR.DAL.Services.Interfaces.Job;
|
using PARR.DAL.Services.Interfaces.Job;
|
||||||
using PARR.DAL.Services.Interfaces.Unit;
|
using PARR.DAL.Services.Interfaces.Unit;
|
||||||
@@ -28,7 +26,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
private readonly ILogger<GroupedTemplateSynchronizer> logger;
|
private readonly ILogger<GroupedTemplateSynchronizer> logger;
|
||||||
private readonly IUnitFilterService unitFilterService;
|
private readonly IUnitFilterService unitFilterService;
|
||||||
private readonly IUnitInUnitService unitInUnitService;
|
|
||||||
private readonly IUnitInValueService unitInValueService;
|
private readonly IUnitInValueService unitInValueService;
|
||||||
private readonly IUnitService unitService;
|
private readonly IUnitService unitService;
|
||||||
private readonly MqSettings mqSettings;
|
private readonly MqSettings mqSettings;
|
||||||
@@ -42,12 +39,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||||
private readonly IMatchingStatusService matchingStatusService;
|
private readonly IMatchingStatusService matchingStatusService;
|
||||||
|
private readonly IShortcodesService shortcodesService;
|
||||||
|
|
||||||
//private readonly INextRunService nextRunService;
|
//private readonly INextRunService nextRunService;
|
||||||
|
|
||||||
public GroupedTemplateSynchronizer(
|
public GroupedTemplateSynchronizer(
|
||||||
ILogger<GroupedTemplateSynchronizer> logger,
|
ILogger<GroupedTemplateSynchronizer> logger,
|
||||||
IUnitFilterService unitFilterService,
|
IUnitFilterService unitFilterService,
|
||||||
IUnitInUnitService unitInUnitService,
|
|
||||||
IUnitInValueService unitInValueService,
|
IUnitInValueService unitInValueService,
|
||||||
IUnitService unitService,
|
IUnitService unitService,
|
||||||
MqSettings mqSettings,
|
MqSettings mqSettings,
|
||||||
@@ -60,13 +58,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
ITemplateDeactivator templateDeactivator,
|
ITemplateDeactivator templateDeactivator,
|
||||||
ITemplateNameNormalizer templateNameNormalizer,
|
ITemplateNameNormalizer templateNameNormalizer,
|
||||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||||
IMatchingStatusService matchingStatusService
|
IMatchingStatusService matchingStatusService,
|
||||||
|
IShortcodesService shortcodesService
|
||||||
//INextRunService nextRunService
|
//INextRunService nextRunService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.unitFilterService = unitFilterService;
|
this.unitFilterService = unitFilterService;
|
||||||
this.unitInUnitService = unitInUnitService;
|
|
||||||
this.unitInValueService = unitInValueService;
|
this.unitInValueService = unitInValueService;
|
||||||
this.unitService = unitService;
|
this.unitService = unitService;
|
||||||
this.mqSettings = mqSettings;
|
this.mqSettings = mqSettings;
|
||||||
@@ -80,6 +78,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
this.templateNameNormalizer = templateNameNormalizer;
|
this.templateNameNormalizer = templateNameNormalizer;
|
||||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||||
this.matchingStatusService = matchingStatusService;
|
this.matchingStatusService = matchingStatusService;
|
||||||
|
this.shortcodesService = shortcodesService;
|
||||||
//this.nextRunService = nextRunService;
|
//this.nextRunService = nextRunService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
||||||
@@ -119,12 +117,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
// 1. Получить JobGroup и связанные Job'ы
|
// 1. Получить JobGroup и связанные Job'ы
|
||||||
var jobGroup = await jobGroupService.Get()
|
var jobGroup = await jobGroupService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.AsSingleQuery()
|
||||||
.Include(jg => jg.GroupType)
|
.Include(jg => jg.GroupType)
|
||||||
.Include(jg => jg.Jobs)
|
.Include(jg => jg.Jobs)
|
||||||
.ThenInclude(j => j.AutoControl)
|
.ThenInclude(j => j.AutoControl)
|
||||||
.Include(jg => jg.Jobs)
|
.Include(jg => jg.Jobs)
|
||||||
.ThenInclude(j => j.UnitFilters)
|
.ThenInclude(j => j.UnitFilters)
|
||||||
.ThenInclude(uf => uf.RelationshipFilters)
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.ThenInclude(rf => rf.UnitField) // Подгрузим поля для фильтрации
|
||||||
.Include(jg => jg.Jobs)
|
.Include(jg => jg.Jobs)
|
||||||
.ThenInclude(jg => jg.Tnk)
|
.ThenInclude(jg => jg.Tnk)
|
||||||
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
|
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
|
||||||
@@ -138,20 +138,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
var jobsInGroup = jobGroup.Jobs.ToList();
|
var jobsInGroup = jobGroup.Jobs.ToList();
|
||||||
|
|
||||||
// --- Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" ---
|
|
||||||
var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
|
||||||
if (workGroupField == null)
|
|
||||||
{
|
|
||||||
logger.LogError("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено в справочнике полей. Синхронизация прервана.");
|
|
||||||
await UpdateMatchingStatusAsync(jobGroupId, "Ошибка: поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var workGroupFieldId = workGroupField.Id;
|
|
||||||
var relationshipGroupValueIds = regionalEkPtkGroupService.Get()
|
|
||||||
.Select(g => g.FieldValueId)
|
|
||||||
.ToList();
|
|
||||||
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", relationshipGroupValueIds.Count);
|
|
||||||
|
|
||||||
// 2. Найти Job с максимальным MaxValueRelationships
|
// 2. Найти Job с максимальным MaxValueRelationships
|
||||||
var maxJob = jobsInGroup
|
var maxJob = jobsInGroup
|
||||||
.Where(j => j.MaxValueRelationships.HasValue)
|
.Where(j => j.MaxValueRelationships.HasValue)
|
||||||
@@ -165,26 +151,25 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxJob.UnitFilters == null)
|
|
||||||
{
|
|
||||||
logger.LogWarning("Job {JobId} не содержит UnitFilters.", maxJob.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships);
|
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships);
|
||||||
|
|
||||||
// 3. Использовать фильтры maxJob для получения отфильтрованных юнитов
|
// 3. Использовать unitFilterService для получения отфильтрованных юнитов с их связями
|
||||||
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id);
|
// Это включает в себя все фильтры: UnitFilter, FieldFilter, RelationshipFilter, UmbrellaFilter
|
||||||
if (filteredUnits == null || !filteredUnits.Any())
|
logger.LogDebug("Получение отфильтрованных юнитов с их связями через UnitFilterService для Job {JobId}.", maxJob.Id);
|
||||||
|
var unitFilterResults = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id);
|
||||||
|
|
||||||
|
if (unitFilterResults == null || !unitFilterResults.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
|
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов с подходящими связями.", jobGroupId);
|
||||||
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов");
|
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов с подходящими связями");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Извлекаем ID юнитов для последующих операций
|
logger.LogDebug("Получено {Count} юнитов с подходящими связями через UnitFilterService.", unitFilterResults.Count());
|
||||||
var expectedUnitIds = filteredUnits.Select(u => u.Id).ToList();
|
|
||||||
|
|
||||||
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
|
// --- ФИЛЬТРАЦИЯ unitFilterResults (dto.Id) ---
|
||||||
|
|
||||||
|
// 4. Фильтрация unitFilterResults по GroupingUnitFieldId (проверяем dto.Id)
|
||||||
if (!jobGroup.GroupingUnitFieldId.HasValue)
|
if (!jobGroup.GroupingUnitFieldId.HasValue)
|
||||||
{
|
{
|
||||||
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
|
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
|
||||||
@@ -193,413 +178,335 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
}
|
}
|
||||||
|
|
||||||
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
||||||
|
logger.LogDebug("Фильтрация юнитов (UnitFilterResultDto.Id) по GroupingUnitFieldId (FieldId={FieldId}).", groupingFieldId);
|
||||||
|
|
||||||
// --- ОПТИМИЗАЦИЯ: Загрузка значений поля GroupingUnitFieldId отдельно ---
|
// Загрузим значения поля GroupingUnitFieldId для всех Id из unitFilterResults
|
||||||
logger.LogDebug("Загружаем значения поля GroupingUnitFieldId (FieldId={FieldId}) для {Count} юнитов.", groupingFieldId, expectedUnitIds.Count);
|
var allUnitFilterResultIds = unitFilterResults.Select(dto => dto.Id).ToList();
|
||||||
|
var groupingUnitValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitFilterResultIds, new HashSet<Guid> { groupingFieldId });
|
||||||
|
|
||||||
var groupingUnitValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(expectedUnitIds, new HashSet<Guid> { groupingFieldId });
|
// Найдем Id юнитов, у которых есть значение в GroupingUnitFieldId
|
||||||
|
var validUnitFilterResultIds = groupingUnitValues
|
||||||
var unitIdsWithValidGroupingFieldSet = groupingUnitValues
|
|
||||||
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
||||||
.Select(uv => uv.UnitId)
|
.Select(uv => uv.UnitId)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
// Оставляем только те UnitFilterResultDto, чей Id проходит фильтр
|
||||||
|
var filteredUnitFilterResultsByGrouping = unitFilterResults
|
||||||
|
.Where(dto => validUnitFilterResultIds.Contains(dto.Id))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
|
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} UnitFilterResultDto.", filteredUnitFilterResultsByGrouping.Count);
|
||||||
|
|
||||||
if (!unitIdsWithValidGroupingFieldSet.Any())
|
if (!filteredUnitFilterResultsByGrouping.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по GroupingUnitFieldId");
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по GroupingUnitFieldId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Аналогично для фильтрации по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" ---
|
// 5. Фильтрация unitFilterResults по РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (проверяем dto.Id)
|
||||||
// var workGroupFieldId = workGroupField.Id; // УЖЕ ОПРЕДЕЛЕНО РАНЕЕ (строка 157)
|
var workGroupFieldId = await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
||||||
// var relationshipGroupValueIds = regionalEkPtkGroupService.Get() // УДАЛИТЬ - УЖЕ ОПРЕДЕЛЕНО (строка 161)
|
|
||||||
|
|
||||||
logger.LogDebug("Загружаем значения поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' (FieldId={FieldId}) для {Count} юнитов.", workGroupFieldId, unitIdsWithValidGroupingFieldSet.Count);
|
logger.LogDebug("Фильтрация юнитов (UnitFilterResultDto.Id) по полю 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' (FieldId={FieldId}).", workGroupFieldId);
|
||||||
|
|
||||||
var workGroupValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(unitIdsWithValidGroupingFieldSet, new HashSet<Guid> { workGroupFieldId });
|
// Загрузим значения поля РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК для Id из filteredUnitFilterResultsByGrouping
|
||||||
|
var allFilteredUnitFilterResultIds = filteredUnitFilterResultsByGrouping.Select(dto => dto.Id).ToList();
|
||||||
|
var workGroupValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allFilteredUnitFilterResultIds, new HashSet<Guid> { workGroupFieldId });
|
||||||
|
|
||||||
var unitIdsWithValidWorkGroupFieldSet = workGroupValues
|
// Получим разрешенные значения из regionalEkPtkGroupService
|
||||||
.Where(uv => uv.Value != null && relationshipGroupValueIds.Contains(uv.Value.Id))
|
var allowedValueIds = regionalEkPtkGroupService.Get()
|
||||||
|
.Select(g => g.FieldValueId)
|
||||||
|
.ToHashSet();
|
||||||
|
logger.LogDebug("Найдено {Count} разрешенных значений для поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", allowedValueIds.Count);
|
||||||
|
|
||||||
|
// Найдем Id юнитов, у которых значение в РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК разрешено
|
||||||
|
var validUnitFilterResultIdsForWorkGroup = workGroupValues
|
||||||
|
.Where(uv => uv.Value != null && allowedValueIds.Contains(uv.Value.Id))
|
||||||
.Select(uv => uv.UnitId)
|
.Select(uv => uv.UnitId)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
// Оставляем только те UnitFilterResultDto, чей Id проходит фильтр
|
||||||
|
var finalFilteredUnitFilterResults = filteredUnitFilterResultsByGrouping
|
||||||
|
.Where(dto => validUnitFilterResultIdsForWorkGroup.Contains(dto.Id))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
|
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} UnitFilterResultDto.", finalFilteredUnitFilterResults.Count);
|
||||||
|
|
||||||
if (!unitIdsWithValidWorkGroupFieldSet.Any())
|
if (!finalFilteredUnitFilterResults.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'");
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ИСПОЛЬЗУЕМ ПОСЛЕДНИЙ РЕЗУЛЬТАТ ДАЛЬШЕ ---
|
// --- ПОСТРОЕНИЕ ОБРАТНОГО ОТОБРАЖЕНИЯ (после фильтрации) ---
|
||||||
var finalUnitIds = unitIdsWithValidWorkGroupFieldSet; // Более понятное имя
|
logger.LogDebug("Построение обратного отображения: связанные юниты -> юниты, связанные с ними (после фильтрации).");
|
||||||
|
var reverseMapping = new Dictionary<Guid, List<Guid>>();
|
||||||
// 5. Получить RelationshipFilters из maxJob
|
foreach (var dto in finalFilteredUnitFilterResults)
|
||||||
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
|
|
||||||
|
|
||||||
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию...");
|
|
||||||
var potentialUnitInUnitLinks = await unitInUnitService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(link => finalUnitIds.Contains(link.ParentUnitId) || finalUnitIds.Contains(link.ChildUnitId))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
|
|
||||||
|
|
||||||
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
|
|
||||||
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
|
|
||||||
|
|
||||||
// --- Оптимизация: Загрузка всех значений за ОДИН запрос ---
|
|
||||||
var allRelevantUnitIds = allParentIds.Concat(allChildIds).ToHashSet();
|
|
||||||
|
|
||||||
var allUnitValues = await unitInValueService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(uv => uv.Field)
|
|
||||||
.Include(uv => uv.Value)
|
|
||||||
.Where(uv => allRelevantUnitIds.Contains(uv.UnitId))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
// --- Создание карт значений из одного списка ---
|
|
||||||
var parentValuesMap = allUnitValues
|
|
||||||
.Where(uv => allParentIds.Contains(uv.UnitId))
|
|
||||||
.GroupBy(uv => uv.UnitId)
|
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
|
||||||
|
|
||||||
var childValuesMap = allUnitValues
|
|
||||||
.Where(uv => allChildIds.Contains(uv.UnitId))
|
|
||||||
.GroupBy(uv => uv.UnitId)
|
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
|
||||||
|
|
||||||
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
|
|
||||||
|
|
||||||
var filteredUnitInUnitLinks = new List<UnitInUnit>();
|
|
||||||
foreach (var link in potentialUnitInUnitLinks)
|
|
||||||
{
|
{
|
||||||
bool linkMatchesAllFilters = true;
|
List<Guid> relatedUnitIds;
|
||||||
foreach (var rf in relationshipFilters)
|
if (maxJob.IsParentRelationships == true)
|
||||||
{
|
{
|
||||||
var valuesToCheck = rf.IsParent ? parentValuesMap.GetValueOrDefault(link.ParentUnitId, new List<UnitInValue>()) : childValuesMap.GetValueOrDefault(link.ChildUnitId, new List<UnitInValue>());
|
// dto.Id - это ParentUnitId, связанные - ChildUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
|
||||||
|
// relatedUnitIds - это ChildUnitIds, которые станут UnitId шаблона
|
||||||
bool filterMatch = valuesToCheck.Any(uv =>
|
relatedUnitIds = dto.Children.Select(c => c.UnitId).ToList(); // <-- Исправлено
|
||||||
uv.FieldId == rf.FieldId &&
|
}
|
||||||
uv.Value != null &&
|
else
|
||||||
uv.Value.Value != null &&
|
{
|
||||||
uv.Value.Value.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase)
|
// dto.Id - это ChildUnitId, связанные - ParentUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
|
||||||
);
|
// relatedUnitIds - это ParentUnitIds, которые станут UnitId шаблона
|
||||||
|
relatedUnitIds = dto.Parents.Select(p => p.UnitId).ToList();
|
||||||
if (rf.IsInverse)
|
|
||||||
filterMatch = !filterMatch;
|
|
||||||
|
|
||||||
if (!filterMatch)
|
|
||||||
{
|
|
||||||
linkMatchesAllFilters = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkMatchesAllFilters)
|
// dto.Id - это юнит, который прошел фильтры, он будет в UnitsInTemplate
|
||||||
|
var unitInTemplateId = dto.Id;
|
||||||
|
|
||||||
|
foreach (var relatedUnitId in relatedUnitIds)
|
||||||
{
|
{
|
||||||
filteredUnitInUnitLinks.Add(link);
|
// relatedUnitId уже прошел все фильтры, т.к. dto.Id (его связанный юнит) прошел фильтры
|
||||||
|
if (!reverseMapping.ContainsKey(relatedUnitId))
|
||||||
|
{
|
||||||
|
reverseMapping[relatedUnitId] = new List<Guid>();
|
||||||
|
}
|
||||||
|
reverseMapping[relatedUnitId].Add(unitInTemplateId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
|
logger.LogDebug("Построено {Count} записей в обратном отображении.", reverseMapping.Count);
|
||||||
|
|
||||||
// --- Сгруппировать юниты ---
|
if (!reverseMapping.Any())
|
||||||
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
|
|
||||||
foreach (var link in filteredUnitInUnitLinks)
|
|
||||||
{
|
{
|
||||||
var parentUnitId = link.ParentUnitId;
|
logger.LogInformation("После построения обратного отображения в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||||
var childUnitId = link.ChildUnitId;
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после построения обратного отображения");
|
||||||
|
return;
|
||||||
if (finalUnitIds.Contains(parentUnitId))
|
|
||||||
{
|
|
||||||
if (!groupedRelationships.ContainsKey(childUnitId))
|
|
||||||
{
|
|
||||||
groupedRelationships[childUnitId] = new List<Guid>();
|
|
||||||
}
|
|
||||||
groupedRelationships[childUnitId].Add(parentUnitId);
|
|
||||||
}
|
|
||||||
else if (finalUnitIds.Contains(childUnitId))
|
|
||||||
{
|
|
||||||
if (!groupedRelationships.ContainsKey(parentUnitId))
|
|
||||||
{
|
|
||||||
groupedRelationships[parentUnitId] = new List<Guid>();
|
|
||||||
}
|
|
||||||
groupedRelationships[parentUnitId].Add(childUnitId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
|
// 6. Внутренняя группировка по ОТВЕТСТВЕННЫЙ_ЗА_ЭК / РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (для юнитов в UnitsInTemplate)
|
||||||
|
logger.LogDebug("Внутренняя группировка по полю (IsGroupByResponsible={IsGroupByResponsible}).", jobGroup.IsGroupByResponsible);
|
||||||
|
|
||||||
// --- Разрешение конфликта с детерминированной сортировкой ---
|
var groupingFieldIdForInnerGrouping = jobGroup.IsGroupByResponsible == true
|
||||||
var unitToKeys = new Dictionary<Guid, List<Guid>>();
|
? await GetFieldIdByAihitNameAsync("ОТВЕТСТВЕННЫЙ_ЗА_ЭК")
|
||||||
foreach (var kvp in groupedRelationships)
|
: await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
||||||
|
|
||||||
|
// Загрузим значения поля для *всех* юнитов, которые могут быть в UnitsInTemplate
|
||||||
|
// Это все юниты из всех списков в reverseMapping.Values
|
||||||
|
var allUnitsInTemplate = reverseMapping.Values.SelectMany(list => list).Distinct().ToList();
|
||||||
|
var innerGroupingValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitsInTemplate, new HashSet<Guid> { groupingFieldIdForInnerGrouping });
|
||||||
|
|
||||||
|
// Создадим маппинг UnitId (из UnitsInTemplate) -> значение поля для внутренней группировки
|
||||||
|
var unitInTemplateToInnerGroupingValueMap = innerGroupingValues
|
||||||
|
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
||||||
|
.ToDictionary(uv => uv.UnitId, uv => uv.Value.Value);
|
||||||
|
|
||||||
|
// 7. Основной цикл обработки: итерируемся по potentialUnitIds (UnitId шаблонов)
|
||||||
|
foreach (var kvpOuter in reverseMapping)
|
||||||
{
|
{
|
||||||
var key = kvp.Key;
|
var potentialUnitId = kvpOuter.Key;
|
||||||
var units = kvp.Value;
|
var unitsInTemplateForThisPotentialUnitId = kvpOuter.Value;
|
||||||
foreach (var unitId in units)
|
|
||||||
{
|
|
||||||
if (!unitToKeys.ContainsKey(unitId))
|
|
||||||
{
|
|
||||||
unitToKeys[unitId] = new List<Guid>();
|
|
||||||
}
|
|
||||||
unitToKeys[unitId].Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем имена всех конфликтующих ключей
|
logger.LogDebug("Обработка потенциального шаблона для UnitId {PotentialUnitId} с {Count} юнитами в UnitsInTemplate до внутренней группировки.", potentialUnitId, unitsInTemplateForThisPotentialUnitId.Count);
|
||||||
var conflictKeys = unitToKeys
|
|
||||||
.Where(kvp => kvp.Value.Count > 1)
|
|
||||||
.SelectMany(kvp => kvp.Value)
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var unitNamesMap = await unitService.Get()
|
// --- ВНУТРЕННЯЯ ГРУППИРОВКА ---
|
||||||
.Where(u => conflictKeys.Contains(u.Id))
|
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
|
||||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? string.Empty);
|
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
|
||||||
|
.GroupBy(unitId => unitInTemplateToInnerGroupingValueMap.GetValueOrDefault(unitId, "Нет данных")) // Используем маппинг
|
||||||
foreach (var conflictedUnitEntry in unitToKeys.Where(kvp => kvp.Value.Count > 1))
|
|
||||||
{
|
|
||||||
var unitId = conflictedUnitEntry.Key;
|
|
||||||
var keysForUnit = conflictedUnitEntry.Value;
|
|
||||||
|
|
||||||
// Сортируем по: 1) кол-во юнитов (убывание), 2) имя ключа (возрастание)
|
|
||||||
var sortedKeys = keysForUnit
|
|
||||||
.Select(key => (
|
|
||||||
key,
|
|
||||||
count: groupedRelationships[key].Count,
|
|
||||||
name: unitNamesMap.GetValueOrDefault(key, "")
|
|
||||||
))
|
|
||||||
.OrderByDescending(x => x.count)
|
|
||||||
.ThenBy(x => x.name)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var bestKey = sortedKeys.First().key;
|
logger.LogDebug("Для UnitId {PotentialUnitId}: сформировано {Count} внутренних групп UnitsInTemplate.", potentialUnitId, innerGroupedUnitsInTemplate.Count);
|
||||||
|
|
||||||
// Удаляем юнит из ВСЕХ групп, кроме лучшей
|
// 8. Цикл по внутренним группам UnitsInTemplate
|
||||||
foreach (var key in keysForUnit)
|
foreach (var innerGroup in innerGroupedUnitsInTemplate)
|
||||||
{
|
{
|
||||||
if (key != bestKey && groupedRelationships.ContainsKey(key))
|
var groupingValueName = innerGroup.Key; // Значение поля
|
||||||
|
var unitsInTemplateInInnerGroup = innerGroup.ToList(); // Список юнитов (UnitId), связанных с potentialUnitId и имеющих одно и то же значение поля
|
||||||
|
|
||||||
|
logger.LogDebug("Обработка внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId} с {Count} юнитами.", groupingValueName, potentialUnitId, unitsInTemplateInInnerGroup.Count);
|
||||||
|
|
||||||
|
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
|
||||||
|
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||||
|
var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup
|
||||||
|
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
||||||
|
.GroupBy(x => x.groupIndex)
|
||||||
|
.Select(g => g.Select(x => x.id).ToList())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
logger.LogDebug("Внутренняя группа '{GroupingValue}' для UnitId {PotentialUnitId}: разбит на {GroupCount} подгрупп UnitsInTemplate.", groupingValueName, potentialUnitId, unitsInTemplateSubGroups.Count);
|
||||||
|
|
||||||
|
// 9. Цикл по подгруппам UnitsInTemplate для создания/обновления шаблонов
|
||||||
|
for (int i = 1; i <= unitsInTemplateSubGroups.Count; i++) // Индекс начинается с 1
|
||||||
{
|
{
|
||||||
groupedRelationships[key].Remove(unitId);
|
var unitsInTemplateSubGroup = unitsInTemplateSubGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||||
}
|
var subGroupSize = unitsInTemplateSubGroup.Count;
|
||||||
}
|
logger.LogDebug("Обработка подгруппы {Index} внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId}, размер UnitsInTemplate {Size}.", i, groupingValueName, potentialUnitId, subGroupSize);
|
||||||
}
|
|
||||||
|
|
||||||
// Удаляем пустые группы
|
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||||
var emptyKeys = groupedRelationships
|
|
||||||
.Where(kvp => !kvp.Value.Any())
|
|
||||||
.Select(kvp => kvp.Key)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var key in emptyKeys)
|
var existingTemplatesForRelationship = await templateService.Get()
|
||||||
{
|
|
||||||
groupedRelationships.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
|
|
||||||
|
|
||||||
// === Основной цикл обработки ===
|
|
||||||
foreach (var kvp in groupedRelationships)
|
|
||||||
{
|
|
||||||
var relationshipUnitId = kvp.Key;
|
|
||||||
var childUnitIds = kvp.Value;
|
|
||||||
if (childUnitIds.Count == 0) continue;
|
|
||||||
|
|
||||||
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
|
|
||||||
|
|
||||||
var childUnitNameMap = await unitService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(u => childUnitIds.Contains(u.Id))
|
|
||||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
|
||||||
|
|
||||||
var sortedChildUnitIds = childUnitIds
|
|
||||||
.OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
|
||||||
var childUnitGroups = sortedChildUnitIds
|
|
||||||
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
|
||||||
.GroupBy(x => x.groupIndex)
|
|
||||||
.Select(g => g.Select(x => x.id).ToList())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
logger.LogDebug("Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count);
|
|
||||||
|
|
||||||
// Индекс начинается с 1
|
|
||||||
for (int i = 1; i <= childUnitGroups.Count; i++)
|
|
||||||
{
|
|
||||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
|
||||||
var subGroupSize = subGroup.Count;
|
|
||||||
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
|
||||||
|
|
||||||
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
|
||||||
|
|
||||||
var existingTemplatesForRelationship = await templateService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(t => t.Unit)
|
|
||||||
.Include(t => t.Job)
|
|
||||||
.ThenInclude(t => t!.Tnk)
|
|
||||||
.Include(t => t.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(t => t!.GroupType)
|
|
||||||
.Include(t => t.UnitsInTemplate)
|
|
||||||
.ThenInclude(uit => uit.Unit)
|
|
||||||
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
|
|
||||||
|
|
||||||
if (existingTemplateForSubGroup != null)
|
|
||||||
{
|
|
||||||
// === 1. Получаем текущие и новые ID юнитов ===
|
|
||||||
var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
|
||||||
var proposedUnitIds = subGroup.ToList();
|
|
||||||
|
|
||||||
// === 2. Сравниваем детерминированно с сортировкой по имени ===
|
|
||||||
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
|
|
||||||
var unitNamesForSort = await unitService.Get()
|
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
.Include(t => t.Unit)
|
||||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
.Include(t => t.Job)
|
||||||
|
.ThenInclude(t => t!.Tnk)
|
||||||
|
.Include(t => t.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(t => t!.GroupType)
|
||||||
|
.Include(t => t.UnitsInTemplate)
|
||||||
|
.ThenInclude(uit => uit.Unit)
|
||||||
|
.Where(t => t.JobId == targetJob.Id && t.UnitId == potentialUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
var sortedCurrentUnitIds = currentUnitIds
|
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
|
||||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var sortedProposedUnitIds = proposedUnitIds
|
if (existingTemplateForSubGroup != null)
|
||||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds);
|
|
||||||
|
|
||||||
if (unitsAreEqual)
|
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id);
|
// === 1. Получаем текущие и новые ID юнитов ===
|
||||||
existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
var proposedUnitIds = unitsInTemplateSubGroup.ToList();
|
||||||
|
|
||||||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
// === 2. Сравниваем детерминированно с сортировкой по имени ===
|
||||||
|
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
|
||||||
|
var unitNamesForSort = await unitService.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
||||||
|
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
||||||
|
|
||||||
|
var sortedCurrentUnitIds = currentUnitIds
|
||||||
|
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var sortedProposedUnitIds = proposedUnitIds
|
||||||
|
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds);
|
||||||
|
|
||||||
|
if (unitsAreEqual)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id);
|
||||||
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(existingTemplateForSubGroup.Id, false);
|
existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||||
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||||||
|
|
||||||
|
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
||||||
|
var updateRequest = new TemplateUpdaterMq
|
||||||
|
{
|
||||||
|
TemplateId = existingTemplateForSubGroup.Id,
|
||||||
|
JobId = targetJob.Id,
|
||||||
|
UnitId = potentialUnitId,
|
||||||
|
Name = expectedName,
|
||||||
|
IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate,
|
||||||
|
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
|
||||||
|
IsNew = false,
|
||||||
|
Index = i,
|
||||||
|
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||||
|
Initiator = initiator,
|
||||||
|
UnitsInTemplate = sortedProposedUnitIds
|
||||||
|
};
|
||||||
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id);
|
||||||
|
var newTargetJob = SelectTargetJob(jobsInGroup, unitsInTemplateSubGroup.Count, maxJob); // Размер - из подмножества
|
||||||
|
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
||||||
|
}
|
||||||
|
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator);
|
||||||
|
if (reusableTemplate != null)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, potentialUnitId, i);
|
||||||
|
|
||||||
|
var tempTemplateForName = new Template
|
||||||
|
{
|
||||||
|
Id = reusableTemplate.Id,
|
||||||
|
Name = reusableTemplate.Name,
|
||||||
|
JobId = targetJob.Id,
|
||||||
|
UnitId = potentialUnitId,
|
||||||
|
Index = i,
|
||||||
|
Job = targetJob,
|
||||||
|
Unit = reusableTemplate.Unit,
|
||||||
|
UnitsInTemplate = unitsInTemplateSubGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||||
|
|
||||||
var updateRequest = new TemplateUpdaterMq
|
var updateRequest = new TemplateUpdaterMq
|
||||||
{
|
{
|
||||||
TemplateId = existingTemplateForSubGroup.Id,
|
TemplateId = reusableTemplate.Id,
|
||||||
JobId = targetJob.Id,
|
JobId = targetJob.Id,
|
||||||
UnitId = relationshipUnitId,
|
UnitId = potentialUnitId,
|
||||||
Name = expectedName,
|
Name = expectedName,
|
||||||
IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate,
|
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
||||||
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
|
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
||||||
//LastRun = existingTemplateForSubGroup.LastRun,
|
|
||||||
//NextRun = nextRun,
|
|
||||||
IsNew = false,
|
|
||||||
Index = i,
|
|
||||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||||
Initiator = initiator,
|
Initiator = initiator,
|
||||||
UnitsInTemplate = sortedProposedUnitIds
|
IsNew = true,
|
||||||
|
Index = i,
|
||||||
|
UnitsInTemplate = unitsInTemplateSubGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}, с {Count} юнитами.", targetJob.Id, potentialUnitId, i, unitsInTemplateSubGroup.Count);
|
||||||
|
await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, i, initiator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id);
|
|
||||||
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
|
||||||
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
|
||||||
{
|
|
||||||
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
|
||||||
}
|
|
||||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, relationshipUnitId, initiator);
|
|
||||||
if (reusableTemplate != null)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
|
|
||||||
|
|
||||||
var tempTemplateForName = new Template
|
|
||||||
{
|
|
||||||
Id = reusableTemplate.Id,
|
|
||||||
Name = reusableTemplate.Name,
|
|
||||||
JobId = targetJob.Id,
|
|
||||||
UnitId = relationshipUnitId,
|
|
||||||
Index = i,
|
|
||||||
Job = targetJob,
|
|
||||||
Unit = reusableTemplate.Unit,
|
|
||||||
UnitsInTemplate = subGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
|
||||||
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(reusableTemplate.Id, true);
|
|
||||||
|
|
||||||
var updateRequest = new TemplateUpdaterMq
|
|
||||||
{
|
|
||||||
TemplateId = reusableTemplate.Id,
|
|
||||||
JobId = targetJob.Id,
|
|
||||||
UnitId = relationshipUnitId,
|
|
||||||
Name = expectedName,
|
|
||||||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
|
||||||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
|
||||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
|
||||||
Initiator = initiator,
|
|
||||||
//NextRun = nextRun,
|
|
||||||
IsNew = true,
|
|
||||||
Index = i,
|
|
||||||
UnitsInTemplate = subGroup
|
|
||||||
};
|
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count);
|
|
||||||
await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Деактивация ===
|
// === Деактивация ===
|
||||||
|
// Собираем ожидаемые ключи шаблонов на основе внутренне сгруппированных результатов
|
||||||
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
||||||
foreach (var kvp in groupedRelationships)
|
foreach (var kvpOuter in reverseMapping)
|
||||||
{
|
{
|
||||||
var relationshipUnitId = kvp.Key;
|
var potentialUnitId = kvpOuter.Key;
|
||||||
var childUnitIds = kvp.Value;
|
var unitsInTemplateForThisPotentialUnitId = kvpOuter.Value;
|
||||||
var childUnitNameMapForDeactivate = await unitService.Get()
|
|
||||||
.AsNoTracking()
|
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
|
||||||
.Where(u => childUnitIds.Contains(u.Id))
|
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
|
||||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
.GroupBy(unitId => unitInTemplateToInnerGroupingValueMap.GetValueOrDefault(unitId, "Нет данных"))
|
||||||
var sortedChildUnitIdsForDeactivate = childUnitIds
|
|
||||||
.OrderBy(id => childUnitNameMapForDeactivate.GetValueOrDefault(id, id.ToString()))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
|
||||||
var childUnitGroups = sortedChildUnitIdsForDeactivate
|
foreach (var innerGroup in innerGroupedUnitsInTemplate)
|
||||||
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
|
||||||
.GroupBy(x => x.groupIndex)
|
|
||||||
.Select(g => g.Select(x => x.id).ToList())
|
|
||||||
.ToList();
|
|
||||||
// Индекс начинается с 1
|
|
||||||
for (int i = 1; i <= childUnitGroups.Count; i++)
|
|
||||||
{
|
{
|
||||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
var groupingValueName = innerGroup.Key;
|
||||||
var subGroupSize = subGroup.Count;
|
var unitsInTemplateInInnerGroup = innerGroup.ToList();
|
||||||
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
|
||||||
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, relationshipUnitId, i));
|
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
|
||||||
|
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||||
|
var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup
|
||||||
|
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
||||||
|
.GroupBy(x => x.groupIndex)
|
||||||
|
.Select(g => g.Select(x => x.id).ToList())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Для каждой подгруппы UnitsInTemplate
|
||||||
|
for (int i = 1; i <= unitsInTemplateSubGroups.Count; i++) // Индекс начинается с 1
|
||||||
|
{
|
||||||
|
var unitsInTemplateSubGroup = unitsInTemplateSubGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||||
|
var subGroupSize = unitsInTemplateSubGroup.Count;
|
||||||
|
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||||
|
|
||||||
|
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, potentialUnitId, i)); // potentialUnitId - это UnitId шаблона
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем ВСЕ шаблоны для JobGroup (не только для текущих relationshipUnitIds)
|
// Получаем ВСЕ шаблоны для JobGroup (не только для текущих potentialUnitIds)
|
||||||
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
||||||
var allExistingTemplatesInGroup = await templateService.Get()
|
var allExistingTemplatesInGroup = await templateService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -724,8 +631,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
HistoryInitiator = initiator
|
HistoryInitiator = initiator
|
||||||
};
|
};
|
||||||
|
|
||||||
//var msg = JsonSerializer.Serialize(mqRequest);
|
|
||||||
//var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
|
|
||||||
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
|
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
@@ -749,4 +654,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
TimeSpan.FromMinutes(30)
|
TimeSpan.FromMinutes(30)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Вспомогательные методы
|
||||||
|
private async Task<Guid> GetFieldIdByAihitNameAsync(string fieldName)
|
||||||
|
{
|
||||||
|
var field = await unitFieldService.GetByAihitNameAsync(fieldName);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
logger.LogError("Поле '{FieldName}' не найдено в справочнике полей.", fieldName);
|
||||||
|
throw new InvalidOperationException($"Поле '{fieldName}' не найдено в справочнике полей.");
|
||||||
|
}
|
||||||
|
return field.Id;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ internal class TemplateReuser : ITemplateReuser
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Атомарно резервируем один шаблон через DAL
|
// Атомарно резервируем один шаблон через DAL
|
||||||
var templateId = await templateService.ReserveUnusedTemplateAsync(unitId, initiator);
|
var templateId = await templateService.ReserveUnusedTemplateAsync(unitId, jobId, initiator);
|
||||||
|
|
||||||
if (templateId == null)
|
if (templateId == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,19 @@ namespace PARR.TemplateMatcher.Services.Interfaces
|
|||||||
{
|
{
|
||||||
public interface ITemplateReuser
|
public interface ITemplateReuser
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Пытается переиспользовать неиспользуемый шаблон для указанной работы и ЭК.
|
||||||
|
/// При резервировании шаблона для правильного расчета NextRun Id работы
|
||||||
|
/// и ЭК устанавливается при захвате.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jobId">Id работы.</param>
|
||||||
|
/// <param name="unitId">Id ЭК.</param>
|
||||||
|
/// <param name="initiator">Данные инициатора операции.</param>
|
||||||
|
/// <param name="maxAttempts">Максимальное количество попыток (по умолчанию 3).</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Зарезервированный шаблон или <see langword="null"/>,
|
||||||
|
/// если не удалось получить шаблон после всех попыток.
|
||||||
|
/// </returns>
|
||||||
Task<Template?> TryReuseOneUnusedTemplateAsync(
|
Task<Template?> TryReuseOneUnusedTemplateAsync(
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
Guid unitId,
|
Guid unitId,
|
||||||
|
|||||||
Reference in New Issue
Block a user