diff --git a/PARR.AIHITMainLoader/Models/PtkData.cs b/PARR.AIHITMainLoader/Models/PtkData.cs index 5c47de4a..8f3c1e74 100644 --- a/PARR.AIHITMainLoader/Models/PtkData.cs +++ b/PARR.AIHITMainLoader/Models/PtkData.cs @@ -94,6 +94,12 @@ namespace PARR.AIHITMainLoader.Models [Column("НЕУНИКАЛЬНЫЙ_ЭК")] public string? IsNotUnique { get; set; } + [Column("Холодный резерв")] + public string? ColdReserve { get; set; } + + [Column("ДОПОЛНИТЕЛЬНАЯ_ИНФОРМАЦИЯ")] + public string? AdditionalInformation { get; set; } + public AihitMainDataMq ToMainData() { @@ -128,7 +134,9 @@ namespace PARR.AIHITMainLoader.Models {"Тип сервера инфраструктуры", InfrastructureServerType }, {"Тип сервера мониторинга", MonitoringServerType }, {"ОС", OSType }, - {"ЗО_РГ", WorkGroupResponseArea } + {"ЗО_РГ", WorkGroupResponseArea }, + {"Холодный резерв", ColdReserve }, + {"ДОПОЛНИТЕЛЬНАЯ_ИНФОРМАЦИЯ", AdditionalInformation }, } }; } diff --git a/PARR.AIHITMainLoader/Models/StoData.cs b/PARR.AIHITMainLoader/Models/StoData.cs index 62f8ac96..0244ea68 100644 --- a/PARR.AIHITMainLoader/Models/StoData.cs +++ b/PARR.AIHITMainLoader/Models/StoData.cs @@ -59,10 +59,9 @@ namespace PARR.AIHITMainLoader.Models public int IsNotUnique { get; set; } [Column("ЗО_РГ")] - public string? WorkGroupResponseArea { get; set; } - [Column("ДОПОЛНИТЕЛЬНАЯ_ИНФОРМАЦИЯ")] + [Column("ДОПОЛНИТЕЛЬНАЯ_ИНФОРМАЦИЯ")] public string? AdditionalInformation { get; set; } [Column("Управляемое_оборудование")] diff --git a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs index a60c1296..0b166fde 100644 --- a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs +++ b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using PARR.DAL.CacheServices; using PARR.DAL.Contracts; using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.Models.Job; @@ -15,6 +16,7 @@ namespace PARR.DAL.DomainServices.Implementations private readonly IJobService jobService; private readonly IUnitService unitService; private readonly IUnitInUnitService unitInUnitService; + private readonly IRedisCacheService cacheService; private readonly IUnitInValueService unitInValueService; public UnitFilterService( @@ -22,12 +24,15 @@ namespace PARR.DAL.DomainServices.Implementations IJobService jobService, IUnitService unitService, IUnitInUnitService unitInUnitService, - IUnitInValueService unitInValueService) + IUnitInValueService unitInValueService, + IRedisCacheService cacheService + ) { this.logger = logger; this.jobService = jobService; this.unitService = unitService; this.unitInUnitService = unitInUnitService; + this.cacheService = cacheService; this.unitInValueService = unitInValueService; } @@ -61,6 +66,15 @@ namespace PARR.DAL.DomainServices.Implementations public List Children { get; set; } = new(); } + + public class CachedUnitIds + { + public List UnitIds { get; set; } = new(); + public DateTimeOffset Timestamp { get; set; } + public string? Source { get; set; } = "UnitFilterService"; + public int Version { get; set; } = 1; + } + #endregion public async Task?> GetUnitsIdByJobFilterAsync(Guid jobId, int? takeCount = null) @@ -100,10 +114,7 @@ namespace PARR.DAL.DomainServices.Implementations logger.LogDebug("Применяем фильтр #{Index} (Id={FilterId})", filterNumber, filter.Id); // 1️ Найти ID юнитов по UnitFilter (Name LIKE) - var initialUnitIds = await unitService.Get().AsNoTracking() - .Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter)) - .Select(u => u.Id) - .ToListAsync(); + var initialUnitIds = await GetUnitIdsFromCacheOrDbAsync(filter); logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count); @@ -516,6 +527,46 @@ namespace PARR.DAL.DomainServices.Implementations }); } + private async Task> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter) + { + var cacheKey = $"uf_ids_{ComputeHash(filter.UnitFilter)}"; + + var cachedData = await cacheService.GetCachedDataAsync(cacheKey); + if (cachedData != null) + { + logger.LogDebug("Кэш попал для UnitFilter '{Name}': {Count} юнитов", filter.UnitFilter, cachedData.UnitIds.Count); + return cachedData.UnitIds; + } + + logger.LogDebug("Кэш промахнут для UnitFilter '{Name}'. Запрашиваем из БД.", filter.UnitFilter); + + var initialUnitIds = await unitService.Get().AsNoTracking() + .Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter)) + .Select(u => u.Id) + .ToListAsync(); + + logger.LogDebug("Загружено {Count} юнитов из БД для UnitFilter '{Name}'", initialUnitIds.Count, filter.UnitFilter); + + var toCache = new CachedUnitIds + { + UnitIds = initialUnitIds, + Timestamp = DateTimeOffset.UtcNow, + Source = GetType().Name, + Version = 1 + }; + + await cacheService.SetCachedDataAsync(cacheKey, toCache, TimeSpan.FromHours(1)); + + return initialUnitIds; + } + + 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); + } + #endregion } } \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs index f1afe839..1a6f7721 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs @@ -41,6 +41,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService; private readonly IUnitFieldService unitFieldService; + private readonly IJobService jobService; public GroupedTemplateSynchronizer( ILogger logger, @@ -56,7 +57,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer IShortcodesService shortcodesService, IEsppScheduleTransformService esppScheduleTransformService, IUnitRegionalEkPtkGroupService regionalEkPtkGroupService, - IUnitFieldService unitFieldService + IUnitFieldService unitFieldService, + IJobService jobService ) { this.logger = logger; @@ -73,6 +75,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer this.esppScheduleTransformService = esppScheduleTransformService; this.regionalEkPtkGroupService = regionalEkPtkGroupService; this.unitFieldService = unitFieldService; + this.jobService = jobService; } public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) @@ -105,7 +108,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var jobsInGroup = jobGroup.Jobs.ToList(); - // --- НОВАЯ ЛОГИКА: Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- + // --- Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"); if (workGroupField == null) { @@ -120,6 +123,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count); + + // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) + var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); + + var unusedJob = await jobService.Get() + .AsNoTracking() + .Include(j => j.Tnk) + .Include(j => j.Group) + .ThenInclude(g => g.GroupType) + .FirstOrDefaultAsync(j => j.Id == unusedJobId); + // 2. Найти Job с максимальным MaxValueRelationships var maxJob = jobsInGroup .Where(j => j.MaxValueRelationships.HasValue) @@ -387,7 +401,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Связанный юнит {RegionalUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count); // Для каждой подгруппы: - for (int i = 1; i <= childUnitGroups.Count; i++) + for (int i = 0; i < childUnitGroups.Count; i++) { var subGroup = childUnitGroups[i]; var subGroupSize = subGroup.Count; @@ -401,7 +415,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var existingTemplatesForRelationship = await templateService.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) - .Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i) + .Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used) .ToListAsync(); var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault(); @@ -416,7 +430,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer { logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); - // ✅ Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) + // Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase)) { @@ -434,7 +448,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule, LastRun = existingTemplateForSubGroup.LastRun, NextRun = nextRun, - Index = i, + Index = i , StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, UnitsInTemplate = subGroup @@ -539,7 +553,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer if (!expectedTemplateKeys.Contains(key)) { logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index); - await DeactivateTemplateAsync(existingTemplate, existingTemplate.JobId, initiator); + await DeactivateTemplateAsync(existingTemplate, existingTemplate.JobId, unusedJob, initiator); } } @@ -644,10 +658,11 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer private async Task DeactivateTemplateAsync( Template template, Guid jobId, + Job unusedJob, HistoryInitiator initiator) { if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) - return true; // уже в обработке + return true; if (template.StatusTypeId == TemplateStatusTypeEnum.Unused) { @@ -667,17 +682,26 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return false; } + if (unusedJob == null) + { + logger.LogError("Job для деактивированных шаблонов не найден."); + return false; + } + + // Вычисляем имя шаблона с новым Job + var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); + var updateRequest = new TemplateUpdaterMq { TemplateId = template.Id, - JobId = jobId, + JobId = unusedJob.Id, UnitId = template.UnitId, - Name = GetTemplateNameForUnused(template.Name), + Name = GetTemplateNameForUnused(expectedName), IsActiveTemplate = DefaultUnusedTemplateState, IsActiveSchedule = DefaultUnusedScheduleState, LastRun = template.LastRun, NextRun = template.NextRun, - Index = template.Index, + Index = null, StatusTypeId = TemplateStatusTypeEnum.Unused, Initiator = initiator, UnitsInTemplate = new List() @@ -717,7 +741,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var templateForShortcodes = new TemplateForShortcodes { Id = Guid.Empty, // Не используется в подстановке, но нужен для структуры - Index = index, + Index = index+1, JobId = targetJob.Id, UnitId = unitId, Job = new JobForShortcodes diff --git a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs index a27bb146..0c10d4f0 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs @@ -69,6 +69,16 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { logger.LogDebug("Начало синхронизации шаблонов для JobId {JobId}", jobId); + // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) + var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); + + var unusedJob = await jobService.Get() + .AsNoTracking() + .Include(j => j.Tnk) + .Include(j => j.Group) + .ThenInclude(g => g.GroupType) + .FirstOrDefaultAsync(j => j.Id == unusedJobId); + var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet(); logger.LogDebug("JobId {JobId}: найдено {Count} UnitId по фильтрам.", jobId, expectedUnitIds.Count); @@ -106,7 +116,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer foreach (var template in existingTemplates) { - await DeactivateTemplateAsync(template, jobId, initiator); + await DeactivateTemplateAsync(template, jobId, unusedJob, initiator); } } else @@ -125,14 +135,14 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer foreach (var template in templatesToDeactivate) { - await DeactivateTemplateAsync(template, jobId, initiator); + await DeactivateTemplateAsync(template, jobId, unusedJob, initiator); } // Перечитываем шаблоны после деактивации existingTemplates = await templateService.Get() .AsNoTracking() - .Include(t=>t.Job) - .ThenInclude(t=>t.Tnk) + .Include(t => t.Job) + .ThenInclude(t => t.Tnk) .Where(t => t.JobId == jobId) .ToListAsync(); @@ -202,6 +212,16 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { logger.LogDebug("Начало обновления шаблонов для JobId {JobId}", jobId); + // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) + var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); + + var unusedJob = await jobService.Get() + .AsNoTracking() + .Include(j => j.Tnk) + .Include(j => j.Group) + .ThenInclude(g => g.GroupType) + .FirstOrDefaultAsync(j => j.Id == unusedJobId); + var existingTemplates = await templateService.Get() .AsNoTracking() .Where(t => t.JobId == jobId) @@ -237,7 +257,18 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer targetStatus = TemplateStatusTypeEnum.Unused; targetIsActiveTemplate = DefaultUnusedTemplateState; targetIsActiveSchedule = DefaultUnusedScheduleState; - expectedName = GetTemplateNameForUnused(expectedName); + + // ✅ Вычисляем имя шаблона с новым unusedJob + if (unusedJob != null) + { + expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); + } + else + { + logger.LogError("Job для деактивированных шаблонов не найден."); + expectedName = GetTemplateNameForUnused(template.Name); + } + logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", template.Id, template.UnitId); } @@ -270,7 +301,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer var updateRequest = new TemplateUpdaterMq { TemplateId = template.Id, - JobId = jobId, + JobId = unitStillInFilter ? jobId : unusedJob?.Id ?? jobId, // ✅ Используем unusedJob.Id, если деактивация UnitId = template.UnitId, Name = expectedName, IsActiveTemplate = targetIsActiveTemplate, @@ -293,6 +324,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer private async Task DeactivateTemplateAsync( Template template, Guid jobId, + Job unusedJob, HistoryInitiator initiator) { if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) @@ -316,12 +348,21 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer return false; } + if (unusedJob == null) + { + logger.LogError("Job для деактивированных шаблонов не найден."); + return false; + } + + // ✅ Вычисляем имя шаблона с новым Job + var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); + var updateRequest = new TemplateUpdaterMq { TemplateId = template.Id, - JobId = jobId, + JobId = unusedJob.Id, // ✅ Используем unusedJob.Id UnitId = template.UnitId, - Name = GetTemplateNameForUnused(template.Name), + Name = GetTemplateNameForUnused(expectedName), // ✅ Используем новое имя IsActiveTemplate = DefaultUnusedTemplateState, IsActiveSchedule = DefaultUnusedScheduleState, LastRun = template.LastRun, @@ -434,7 +475,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { return await jobService.Get() .AsNoTracking() - .Include(j=>j.Tnk) + .Include(j => j.Tnk) .Include(j => j.Group) .ThenInclude(j => j.GroupType) .Include(j => j.AutoControl) @@ -453,8 +494,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer var templateForShortcodes = new TemplateForShortcodes { Id = Guid.Empty, // шаблон ещё не создан - Index = null, - JobId = job.Id, + Index = null, + JobId = job.Id, UnitId = unitId, Job = new JobForShortcodes { diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs b/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs index 9ff39107..bf37afb5 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs @@ -34,7 +34,6 @@ internal class TemplateReuser : ITemplateReuser try { var unusedCandidates = await templateService.Get() - .AsNoTracking() .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused) .OrderBy(t => t.DateModified ?? t.DateCreated) .Take(UnusedCandidateBatchSize)