diff --git a/PARR.TemplateMatcher/Constants/UnusedTemplateConstants.cs b/PARR.TemplateMatcher/Constants/UnusedTemplateConstants.cs
index 2e33d272..3fd053d8 100644
--- a/PARR.TemplateMatcher/Constants/UnusedTemplateConstants.cs
+++ b/PARR.TemplateMatcher/Constants/UnusedTemplateConstants.cs
@@ -21,5 +21,15 @@
/// Значение тега, обозначающее неиспользуемый юнит.
///
public const string NotUsedTagValue = "ПАРР-НЕИСП";
+
+ ///
+ /// Состояние шаблонов по-умолчанию
+ ///
+ public const bool DefaultUnusedTemplateState = false;
+
+ ///
+ /// Состояние расписаний по-умолчанию
+ ///
+ public const bool DefaultUnusedScheduleState = false;
}
}
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/BuildGroupsStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/BuildGroupsStage.cs
index 4e585e0c..157eaf15 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/BuildGroupsStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/BuildGroupsStage.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Logging;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class BuildGroupsStage : IGroupedSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/DeactivateTemplatesStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/DeactivateTemplatesStage.cs
index d944fa2b..566de49b 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/DeactivateTemplatesStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/DeactivateTemplatesStage.cs
@@ -2,15 +2,15 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Domain.Enums;
-using PARR.TemplateMatcher.Services.GroupedSync;
using PARR.TemplateMatcher.Services.Interfaces;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class DeactivateTemplatesStage : IGroupedSyncWriteStage
{
private readonly ITemplateRepository _templateRepository;
private readonly ITemplateDeactivator _deactivator;
+ private readonly IUnusedTemplateTargetUnitResolver _targetUnitResolver;
private readonly ILogger _logger;
public string StageName => "Деактивация шаблонов";
@@ -18,16 +18,22 @@ internal class DeactivateTemplatesStage : IGroupedSyncWriteStage
public DeactivateTemplatesStage(
ITemplateRepository templateRepository,
ITemplateDeactivator deactivator,
+ IUnusedTemplateTargetUnitResolver targetUnitResolver,
ILogger logger)
{
_templateRepository = templateRepository;
_deactivator = deactivator;
+ _targetUnitResolver = targetUnitResolver;
_logger = logger;
}
- public async Task ExecuteAsync(GroupedSyncContext context, CancellationToken ct = default)
+ public async Task ExecuteAsync(
+ GroupedSyncContext context,
+ CancellationToken ct = default)
{
- var allJobIds = context.JobsInGroup.Select(j => j.Id).ToHashSet();
+ var allJobIds = context.JobsInGroup
+ .Select(j => j.Id)
+ .ToHashSet();
var existingTemplates = await _templateRepository.Get()
.AsNoTracking()
@@ -39,45 +45,76 @@ internal class DeactivateTemplatesStage : IGroupedSyncWriteStage
&& t.Job!.GroupId == context.JobGroupId)
.ToListAsync(ct);
- int deactivated = 0;
- foreach (var template in existingTemplates)
+ var staleTemplates = existingTemplates
+ .Where(t => !context.ExpectedTemplateKeys.Contains(
+ (t.JobId, t.UnitId, t.Index ?? -1)))
+ .ToList();
+
+ if (staleTemplates.Count == 0)
{
- var key = (template.JobId, template.UnitId, template.Index ?? -1);
- if (!context.ExpectedTemplateKeys.Contains(key))
+ _logger.LogDebug(
+ "JobGroup '{JobGroupName}' ({JobGroupId}): шаблонов для деактивации нет.",
+ context.JobGroupName,
+ context.JobGroupId);
+
+ return context;
+ }
+
+ var targetUnits = await _targetUnitResolver.ResolveAsync(staleTemplates, ct);
+
+ int deactivated = 0;
+
+ foreach (var template in staleTemplates)
+ {
+ var unitLogName = context.UnitNames.TryGetValue(template.UnitId, out var unitName)
+ ? $"'{unitName}' ({template.UnitId})"
+ : template.Unit != null
+ ? $"'{template.Unit.Name}' ({template.UnitId})"
+ : $"({template.UnitId})";
+
+ _logger.LogInformation(
+ "JobGroup '{JobGroupName}' ({JobGroupId}): деактивация шаблона '{TemplateName}' ({TemplateId}), Job '{JobName}' ({JobId}), Unit {Unit}",
+ context.JobGroupName,
+ context.JobGroupId,
+ template.Name,
+ template.Id,
+ template.Job?.Name ?? string.Empty,
+ template.JobId,
+ unitLogName);
+
+ if (!targetUnits.TryGetValue(template.Id, out var targetUnit))
{
- var unitLogName = context.UnitNames.TryGetValue(template.UnitId, out var unitName)
- ? $"'{unitName}' ({template.UnitId})"
- : template.Unit != null
- ? $"'{template.Unit.Name}' ({template.UnitId})"
- : $"({template.UnitId})";
+ _logger.LogError(
+ "Не удалось определить целевой Unit для деактивации шаблона {TemplateId}.",
+ template.Id);
- _logger.LogInformation(
- "JobGroup '{JobGroupName}' ({JobGroupId}): деактивация шаблона '{TemplateName}' ({TemplateId}), Job '{JobName}' ({JobId}), Unit {Unit}",
- context.JobGroupName, context.JobGroupId,
- template.Name, template.Id,
- template.Job?.Name ?? string.Empty, template.JobId,
- unitLogName);
+ continue;
+ }
- var success = await _deactivator.DeactivateTemplateAsync(template, context.Initiator);
+ var success = await _deactivator.DeactivateTemplateAsync(
+ template,
+ targetUnit,
+ context.Initiator,
+ ct);
- if (success)
- {
- deactivated++;
- }
- else
- {
- _logger.LogError(
- "JobGroup '{JobGroupName}' ({JobGroupId}): не удалось деактивировать шаблон '{TemplateName}' ({TemplateId}).",
- context.JobGroupName,
- context.JobGroupId,
- template.Name,
- template.Id);
- }
+ if (success)
+ {
+ deactivated++;
+ }
+ else
+ {
+ _logger.LogError(
+ "Не удалось деактивировать шаблон {TemplateId}.",
+ template.Id);
}
}
- _logger.LogDebug("JobGroup '{JobGroupName}' ({JobGroupId}): деактивировано {Count} шаблонов из {Total}",
- context.JobGroupName, context.JobGroupId, deactivated, existingTemplates.Count);
+ _logger.LogDebug(
+ "JobGroup '{JobGroupName}' ({JobGroupId}): деактивировано {Count} шаблонов из {Total}",
+ context.JobGroupName,
+ context.JobGroupId,
+ deactivated,
+ staleTemplates.Count);
return context;
}
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/FilterUnitsStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/FilterUnitsStage.cs
index 023f6f52..c60d421c 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/FilterUnitsStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/FilterUnitsStage.cs
@@ -1,8 +1,7 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Services.UnitFilterService;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class FilterUnitsStage : IGroupedSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/GroupFilterStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/GroupFilterStage.cs
index e81489e2..2d28408e 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/GroupFilterStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/GroupFilterStage.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Logging;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class GroupFilterStage : IGroupedSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/GroupedSyncContext.cs b/PARR.TemplateMatcher/Services/GroupedSync/GroupedSyncContext.cs
index 25b3f081..103eb13d 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/GroupedSyncContext.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/GroupedSyncContext.cs
@@ -4,7 +4,7 @@ using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Entities.JobGroupEntities;
using PARR.TemplateMatcher.Models;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
///
/// Контекст групповой синхронизации. Передаётся между этапами.
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/IGroupedSyncStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/IGroupedSyncStage.cs
index 279a5fd0..eb5a210b 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/IGroupedSyncStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/IGroupedSyncStage.cs
@@ -1,6 +1,4 @@
-using PARR.TemplateMatcher.Services.Implementations.GroupedSync;
-
-namespace PARR.TemplateMatcher.Services.GroupedSync
+namespace PARR.TemplateMatcher.Services.GroupedSync
{
///
/// Этап групповой синхронизации, который только читает данные.
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/LoadJobGroupStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/LoadJobGroupStage.cs
index 08dcc868..6c494545 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/LoadJobGroupStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/LoadJobGroupStage.cs
@@ -2,9 +2,8 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
using PARR.TemplateMatcher.Exceptions;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class LoadJobGroupStage : IGroupedSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/ProcessGroupsStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/ProcessGroupsStage.cs
index c106b13d..2698d7c2 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/ProcessGroupsStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/ProcessGroupsStage.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Logging;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class ProcessGroupsStage : IGroupedSyncWriteStage
{
diff --git a/PARR.TemplateMatcher/Services/GroupedSync/ResolveConflictsStage.cs b/PARR.TemplateMatcher/Services/GroupedSync/ResolveConflictsStage.cs
index c960f66e..7d1de3aa 100644
--- a/PARR.TemplateMatcher/Services/GroupedSync/ResolveConflictsStage.cs
+++ b/PARR.TemplateMatcher/Services/GroupedSync/ResolveConflictsStage.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.Logging;
-using PARR.TemplateMatcher.Services.GroupedSync;
-namespace PARR.TemplateMatcher.Services.Implementations.GroupedSync;
+namespace PARR.TemplateMatcher.Services.GroupedSync;
internal class ResolveConflictsStage : IGroupedSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs
index 3318a46c..0d719ded 100644
--- a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs
+++ b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs
@@ -5,7 +5,6 @@ using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
using PARR.TemplateMatcher.Exceptions;
using PARR.TemplateMatcher.Services.GroupedSync;
-using PARR.TemplateMatcher.Services.Implementations.GroupedSync;
using PARR.TemplateMatcher.Services.Interfaces;
using System.Diagnostics;
diff --git a/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs b/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs
index d1974096..645e5d81 100644
--- a/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs
+++ b/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs
@@ -3,10 +3,10 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories;
-using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Entities.JobEntities;
+using PARR.Domain.Entities.Unit;
using PARR.Domain.Enums;
using PARR.Domain.Settings;
using PARR.TemplateMatcher.Services.Interfaces;
@@ -16,106 +16,113 @@ namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateDeactivator : ITemplateDeactivator
{
- private const bool DefaultUnusedTemplateState = false;
- private const bool DefaultUnusedScheduleState = false;
+ private readonly ILogger _logger;
+ private readonly ITemplateRepository _templateRepository;
+ private readonly IJobRepository _jobRepository;
+ private readonly ITemplateMqPublisher _templateMqPublisher;
+ private readonly IUnusedTemplateMessageFactory _unusedTemplateMessageFactory;
+ private readonly SettingsFromDb _settingsFromDb;
+ private readonly IOptions _templateSettings;
- private readonly ILogger logger;
- private readonly ITemplateRepository templateService;
- private readonly IJobRepository jobService;
- private readonly ITemplateNameNormalizer namenormalizer;
- private readonly ITemplateMqPublisher templateMqPublisher;
- private readonly SettingsFromDb settingsFromDb;
- private readonly IOptions templateSettings;
+ private Job? _unusedJob;
public TemplateDeactivator(
ILogger logger,
- ITemplateRepository templateService,
- IJobRepository jobService,
- ITemplateNameNormalizer namenormalizer,
+ ITemplateRepository templateRepository,
+ IJobRepository jobRepository,
ITemplateMqPublisher templateMqPublisher,
+ IUnusedTemplateMessageFactory unusedTemplateMessageFactory,
SettingsFromDb settingsFromDb,
- IOptions templateSettings
- )
+ IOptions templateSettings)
{
- this.logger = logger;
- this.templateService = templateService;
- this.jobService = jobService;
- this.namenormalizer = namenormalizer;
- this.templateMqPublisher = templateMqPublisher;
- this.settingsFromDb = settingsFromDb;
- this.templateSettings = templateSettings;
+ _logger = logger;
+ _templateRepository = templateRepository;
+ _jobRepository = jobRepository;
+ _templateMqPublisher = templateMqPublisher;
+ _unusedTemplateMessageFactory = unusedTemplateMessageFactory;
+ _settingsFromDb = settingsFromDb;
+ _templateSettings = templateSettings;
}
- public async Task DeactivateTemplateAsync(Template template, HistoryInitiator initiator)
+ public async Task DeactivateTemplateAsync(
+ Template template,
+ Unit targetUnit,
+ HistoryInitiator initiator,
+ CancellationToken ct = default)
{
- if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
- return true;
-
if (template.StatusTypeId == TemplateStatusTypeEnum.Unused)
{
- logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id);
+ _logger.LogDebug(
+ "Шаблон {TemplateId} уже находится в статусе Unused. Пропускаем деактивацию.",
+ template.Id);
+
return true;
}
- logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.",
- template.Id, template.UnitId);
+ if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
+ {
+ _logger.LogDebug(
+ "Шаблон {TemplateId} уже находится в статусе Updating. Пропускаем повторную деактивацию.",
+ template.Id);
+
+ return true;
+ }
+
+ var unusedJob = await GetUnusedJobAsync(ct);
+
+ if (unusedJob == null)
+ {
+ _logger.LogError(
+ "Job для неиспользуемых шаблонов не найден. Шаблон {TemplateId} не деактивирован.",
+ template.Id);
+
+ return false;
+ }
+
+ _logger.LogInformation(
+ "Деактивация шаблона {TemplateId}. Текущий UnitId: {CurrentUnitId}, целевой UnitId: {TargetUnitId}.",
+ template.Id,
+ template.UnitId,
+ targetUnit.Id);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
- if (!await templateService.CommitAsync(initiator))
+ if (!await _templateRepository.CommitAsync(initiator))
{
- logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
+ _logger.LogError(
+ "Не удалось перевести шаблон {TemplateId} в статус Updating.",
+ template.Id);
+
return false;
}
- var unusedJob = await jobService.Get()
+ var updateRequest = await _unusedTemplateMessageFactory.CreateAsync(
+ template,
+ unusedJob,
+ targetUnit,
+ initiator,
+ ct);
+
+ await _templateMqPublisher.PublishUpdateAsync(updateRequest);
+
+ return true;
+ }
+
+ private async Task GetUnusedJobAsync(CancellationToken ct)
+ {
+ if (_unusedJob != null)
+ return _unusedJob;
+
+ _unusedJob = await _jobRepository.Get()
.AsNoTracking()
.Include(j => j.Tnk)
.Include(j => j.Group)
.ThenInclude(g => g!.GroupType)
- .FirstOrDefaultAsync(j => j.Id == settingsFromDb.JobIdForUnusedTemplates);
+ .FirstOrDefaultAsync(
+ j => j.Id == _settingsFromDb.JobIdForUnusedTemplates,
+ ct);
-
- if (unusedJob == null)
- {
- logger.LogError("Job для деактивированных шаблонов не найден.");
- return false;
- }
-
- unusedJob.TemplateNameMask = templateSettings.Value.UnusedTemplateNameMask;
-
- // === Создаём временный Template для нормализации имени ===
- var tempTemplateForName = new Template
- {
- Id = template.Id,
- Name = template.Name,
- JobId = unusedJob.Id,
- UnitId = template.UnitId,
- Index = template.Index,
- Job = unusedJob,
- Unit = template.Unit,
- UnitsInTemplate = new List()
- };
-
- var expectedName = await namenormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
-
- var updateRequest = new TemplateUpdaterMessage
- {
- TemplateId = template.Id,
- JobId = unusedJob.Id,
- UnitId = template.UnitId,
- Name = expectedName,
- IsActiveTemplate = DefaultUnusedTemplateState,
- IsActiveSchedule = DefaultUnusedScheduleState,
- IsNew = false,
- Index = template.Index,
- StatusTypeId = TemplateStatusTypeEnum.Unused,
- Initiator = initiator,
- UnitsInTemplate = new List()
- };
-
- await templateMqPublisher.PublishUpdateAsync(updateRequest);
- return true;
+ return _unusedJob;
}
}
\ No newline at end of file
diff --git a/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateMessageFactory.cs b/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateMessageFactory.cs
new file mode 100644
index 00000000..8b9cf462
--- /dev/null
+++ b/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateMessageFactory.cs
@@ -0,0 +1,83 @@
+using Microsoft.Extensions.Options;
+using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
+using PARR.Domain.Entities;
+using PARR.Domain.Entities.Base.History;
+using PARR.Domain.Entities.JobEntities;
+using PARR.Domain.Entities.Unit;
+using PARR.Domain.Enums;
+using PARR.TemplateMatcher.Constants;
+using PARR.TemplateMatcher.Services.Interfaces;
+using PARR.TemplateMatcher.Settings;
+
+namespace PARR.TemplateMatcher.Services.Implementations;
+
+internal class UnusedTemplateMessageFactory : IUnusedTemplateMessageFactory
+{
+ private readonly ITemplateNameNormalizer _templateNameNormalizer;
+ private readonly IOptions _templateSettings;
+
+ public UnusedTemplateMessageFactory(
+ ITemplateNameNormalizer templateNameNormalizer,
+ IOptions templateSettings)
+ {
+ _templateNameNormalizer = templateNameNormalizer;
+ _templateSettings = templateSettings;
+ }
+
+ public async Task CreateAsync(
+ Template template,
+ Job unusedJob,
+ Unit targetUnit,
+ HistoryInitiator initiator,
+ CancellationToken ct = default)
+ {
+ var jobForName = new Job
+ {
+ Id = unusedJob.Id,
+ Name = unusedJob.Name,
+ WorkName = unusedJob.WorkName,
+ MinValueRelationships = unusedJob.MinValueRelationships,
+ MaxValueRelationships = unusedJob.MaxValueRelationships,
+ IsParentRelationships = unusedJob.IsParentRelationships,
+ TemplateNameMask = _templateSettings.Value.UnusedTemplateNameMask,
+ WorkGroupMask = unusedJob.WorkGroupMask,
+ ResponseAreaMask = unusedJob.ResponseAreaMask,
+ TnkId = unusedJob.TnkId,
+ GroupId = unusedJob.GroupId,
+ Group = unusedJob.Group,
+ Tnk = unusedJob.Tnk,
+ UnitFilters = unusedJob.UnitFilters,
+ Templates = unusedJob.Templates,
+ AutoControl = unusedJob.AutoControl
+ };
+
+ var templateForName = new Template
+ {
+ Id = template.Id,
+ Name = template.Name,
+ JobId = unusedJob.Id,
+ UnitId = targetUnit.Id,
+ Index = null,
+ Job = jobForName,
+ Unit = targetUnit,
+ UnitsInTemplate = new List()
+ };
+
+ var expectedName = await _templateNameNormalizer.GetNormalizedTemplateNameAsync(templateForName);
+
+ return new TemplateUpdaterMessage
+ {
+ TemplateId = template.Id,
+ JobId = unusedJob.Id,
+ UnitId = targetUnit.Id,
+ Name = expectedName,
+ IsActiveTemplate = UnusedTemplateConstants.DefaultUnusedTemplateState,
+ IsActiveSchedule = UnusedTemplateConstants.DefaultUnusedScheduleState,
+ IsNew = false,
+ Index = null,
+ StatusTypeId = TemplateStatusTypeEnum.Unused,
+ Initiator = initiator,
+ UnitsInTemplate = new List()
+ };
+ }
+}
\ No newline at end of file
diff --git a/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateTargetUnitResolver.cs b/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateTargetUnitResolver.cs
new file mode 100644
index 00000000..d6744506
--- /dev/null
+++ b/PARR.TemplateMatcher/Services/Implementations/UnusedTemplateTargetUnitResolver.cs
@@ -0,0 +1,209 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using PARR.Core.Repositories.Interfaces.Unit;
+using PARR.Domain.Entities;
+using PARR.Domain.Entities.Unit;
+using PARR.TemplateMatcher.Constants;
+using PARR.TemplateMatcher.Services.Interfaces;
+
+namespace PARR.TemplateMatcher.Services.Implementations;
+
+internal class UnusedTemplateTargetUnitResolver : IUnusedTemplateTargetUnitResolver
+{
+ private readonly IUnitFieldRepository _unitFieldRepository;
+ private readonly IUnitInValueRepository _unitInValueRepository;
+ private readonly IUnitRepository _unitRepository;
+ private readonly ILogger _logger;
+
+ public UnusedTemplateTargetUnitResolver(
+ IUnitFieldRepository unitFieldRepository,
+ IUnitInValueRepository unitInValueRepository,
+ IUnitRepository unitRepository,
+ ILogger logger)
+ {
+ _unitFieldRepository = unitFieldRepository;
+ _unitInValueRepository = unitInValueRepository;
+ _unitRepository = unitRepository;
+ _logger = logger;
+ }
+
+ public async Task> ResolveAsync(
+ IReadOnlyCollection templates,
+ CancellationToken ct = default)
+ {
+ var result = new Dictionary();
+
+ foreach (var template in templates.Where(t => t.Unit != null))
+ {
+ result[template.Id] = template.Unit!;
+ }
+
+ if (templates.Count == 0)
+ return result;
+
+ var responsableAreaField = await _unitFieldRepository.GetByAihitNameAsync(
+ UnusedTemplateConstants.ResponsibilityAreaFieldName,
+ ct);
+
+ var tagField = await _unitFieldRepository.GetByAihitNameAsync(
+ UnusedTemplateConstants.ParrTagFieldName,
+ ct);
+
+ if (responsableAreaField == null || tagField == null)
+ {
+ _logger.LogWarning(
+ "Не найдены поля '{ResponsibilityAreaField}' или '{TagField}'. Целевой Unit для неиспользуемых шаблонов будет выбран как текущий.",
+ UnusedTemplateConstants.ResponsibilityAreaFieldName,
+ UnusedTemplateConstants.ParrTagFieldName);
+
+ await AddFallbackUnitsAsync(templates, result, ct);
+
+ return result;
+ }
+
+ var targetTagValueId = await _unitInValueRepository.Get()
+ .AsNoTracking()
+ .Where(uiv => uiv.FieldId == tagField.Id
+ && uiv.Value != null
+ && uiv.Value.Value == UnusedTemplateConstants.NotUsedTagValue)
+ .Select(uiv => uiv.ValueId)
+ .FirstOrDefaultAsync(ct);
+
+ if (targetTagValueId == Guid.Empty)
+ {
+ _logger.LogWarning(
+ "Значение '{TagValue}' для поля '{FieldName}' не найдено. Целевой Unit для неиспользуемых шаблонов будет выбран как текущий.",
+ UnusedTemplateConstants.NotUsedTagValue,
+ UnusedTemplateConstants.ParrTagFieldName);
+
+ await AddFallbackUnitsAsync(templates, result, ct);
+
+ return result;
+ }
+
+ var unitIds = templates
+ .Select(t => t.UnitId)
+ .Distinct()
+ .ToList();
+
+ var unitResponsableAreaValues = await _unitInValueRepository.GetByUnitIdsAndFieldIdsAsync(
+ unitIds,
+ new List { responsableAreaField.Id },
+ ct);
+
+ var unitToResponsableAreaValueMap = unitResponsableAreaValues
+ .Where(uiv => uiv.ValueId != Guid.Empty)
+ .ToDictionary(uiv => uiv.UnitId, uiv => uiv.ValueId);
+
+ if (unitToResponsableAreaValueMap.Count == 0)
+ {
+ _logger.LogDebug(
+ "Для юнитов не найдены значения поля '{FieldName}'. Целевой Unit для неиспользуемых шаблонов будет выбран как текущий.",
+ UnusedTemplateConstants.ResponsibilityAreaFieldName);
+
+ await AddFallbackUnitsAsync(templates, result, ct);
+
+ return result;
+ }
+
+ var distinctResponsableAreaValues = unitToResponsableAreaValueMap.Values
+ .Distinct()
+ .ToList();
+
+ var matches = await _unitInValueRepository.Get()
+ .AsNoTracking()
+ .Where(uiv => uiv.FieldId == responsableAreaField.Id
+ && distinctResponsableAreaValues.Contains(uiv.ValueId))
+ .Join(
+ _unitInValueRepository.Get()
+ .AsNoTracking()
+ .Where(t => t.FieldId == tagField.Id && t.ValueId == targetTagValueId),
+ responsableArea => responsableArea.UnitId,
+ tag => tag.UnitId,
+ (responsableArea, tag) => new
+ {
+ responsableArea.ValueId,
+ responsableArea.UnitId
+ })
+ .ToListAsync(ct);
+
+ var responsableAreaToTargetUnitMap = matches
+ .GroupBy(x => x.ValueId)
+ .ToDictionary(g => g.Key, g => g.First().UnitId);
+
+ var templatesToReload = new Dictionary();
+
+ foreach (var template in templates)
+ {
+ if (!unitToResponsableAreaValueMap.TryGetValue(template.UnitId, out var responsableAreaValueId))
+ continue;
+
+ if (!responsableAreaToTargetUnitMap.TryGetValue(responsableAreaValueId, out var targetUnitId))
+ continue;
+
+ if (targetUnitId == Guid.Empty || targetUnitId == template.UnitId)
+ continue;
+
+ templatesToReload[template.Id] = targetUnitId;
+ }
+
+ if (templatesToReload.Count > 0)
+ {
+ var targetUnitIds = templatesToReload.Values
+ .Distinct()
+ .ToList();
+
+ var targetUnits = await _unitRepository.Get()
+ .AsNoTracking()
+ .Where(u => targetUnitIds.Contains(u.Id))
+ .ToDictionaryAsync(u => u.Id, ct);
+
+ foreach (var kvp in templatesToReload)
+ {
+ if (targetUnits.TryGetValue(kvp.Value, out var targetUnit))
+ {
+ result[kvp.Key] = targetUnit;
+ }
+ else
+ {
+ _logger.LogWarning(
+ "Не удалось загрузить целевой Unit {TargetUnitId} для шаблона {TemplateId}.",
+ kvp.Value,
+ kvp.Key);
+ }
+ }
+ }
+
+ await AddFallbackUnitsAsync(templates, result, ct);
+
+ return result;
+ }
+
+ private async Task AddFallbackUnitsAsync(
+ IReadOnlyCollection templates,
+ Dictionary result,
+ CancellationToken ct)
+ {
+ var missingUnitIds = templates
+ .Where(t => !result.ContainsKey(t.Id) && t.UnitId != Guid.Empty)
+ .Select(t => t.UnitId)
+ .Distinct()
+ .ToList();
+
+ if (missingUnitIds.Count == 0)
+ return;
+
+ var fallbackUnits = await _unitRepository.Get()
+ .AsNoTracking()
+ .Where(u => missingUnitIds.Contains(u.Id))
+ .ToDictionaryAsync(u => u.Id, ct);
+
+ foreach (var template in templates.Where(t => !result.ContainsKey(t.Id) && t.UnitId != Guid.Empty))
+ {
+ if (fallbackUnits.TryGetValue(template.UnitId, out var fallbackUnit))
+ {
+ result[template.Id] = fallbackUnit;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs b/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs
index afe8c1f6..80e4b6da 100644
--- a/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs
+++ b/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs
@@ -1,10 +1,15 @@
using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History;
+using PARR.Domain.Entities.Unit;
namespace PARR.TemplateMatcher.Services.Interfaces
{
public interface ITemplateDeactivator
{
- Task DeactivateTemplateAsync(Template template, HistoryInitiator initiator);
+ Task DeactivateTemplateAsync(
+ Template template,
+ Unit targetUnit,
+ HistoryInitiator initiator,
+ CancellationToken ct = default);
}
}
diff --git a/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateMessageFactory.cs b/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateMessageFactory.cs
new file mode 100644
index 00000000..553075e6
--- /dev/null
+++ b/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateMessageFactory.cs
@@ -0,0 +1,18 @@
+using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
+using PARR.Domain.Entities;
+using PARR.Domain.Entities.Base.History;
+using PARR.Domain.Entities.JobEntities;
+using PARR.Domain.Entities.Unit;
+
+namespace PARR.TemplateMatcher.Services.Interfaces
+{
+ internal interface IUnusedTemplateMessageFactory
+ {
+ Task CreateAsync(
+ Template template,
+ Job unusedJob,
+ Unit targetUnit,
+ HistoryInitiator initiator,
+ CancellationToken ct = default);
+ }
+}
diff --git a/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateTargetUnitResolver.cs b/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateTargetUnitResolver.cs
new file mode 100644
index 00000000..ecb0e5d7
--- /dev/null
+++ b/PARR.TemplateMatcher/Services/Interfaces/IUnusedTemplateTargetUnitResolver.cs
@@ -0,0 +1,12 @@
+using PARR.Domain.Entities;
+using PARR.Domain.Entities.Unit;
+
+namespace PARR.TemplateMatcher.Services.Interfaces
+{
+ internal interface IUnusedTemplateTargetUnitResolver
+ {
+ Task> ResolveAsync(
+ IReadOnlyCollection templates,
+ CancellationToken ct = default);
+ }
+}
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/AllocateTemplatesStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/AllocateTemplatesStage.cs
index 03e95a48..a029c7e6 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/AllocateTemplatesStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/AllocateTemplatesStage.cs
@@ -1,10 +1,10 @@
using Microsoft.Extensions.Logging;
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.TemplateMatcher.Models;
+using PARR.TemplateMatcher.Services.Implementations;
using PARR.TemplateMatcher.Services.Interfaces;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class AllocateTemplatesStage : ISimpleSyncWriteStage
{
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/AnalyzeChangesStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/AnalyzeChangesStage.cs
index cd10b47e..3b24907a 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/AnalyzeChangesStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/AnalyzeChangesStage.cs
@@ -3,9 +3,8 @@ using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Domain.Enums;
using PARR.TemplateMatcher.Services.Interfaces;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class AnalyzeChangesStage : ISimpleSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/DeactivateTemplatesStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/DeactivateTemplatesStage.cs
index 4dbc573a..94c894db 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/DeactivateTemplatesStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/DeactivateTemplatesStage.cs
@@ -1,33 +1,81 @@
using Microsoft.Extensions.Logging;
using PARR.TemplateMatcher.Services.Interfaces;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class DeactivateTemplatesStage : ISimpleSyncWriteStage
{
private readonly ITemplateDeactivator _deactivator;
+ private readonly IUnusedTemplateTargetUnitResolver _targetUnitResolver;
private readonly ILogger _logger;
public string StageName => "Деактивация шаблонов";
- public DeactivateTemplatesStage(ITemplateDeactivator deactivator, ILogger logger)
+ public DeactivateTemplatesStage(
+ ITemplateDeactivator deactivator,
+ IUnusedTemplateTargetUnitResolver targetUnitResolver,
+ ILogger logger)
{
_deactivator = deactivator;
+ _targetUnitResolver = targetUnitResolver;
_logger = logger;
}
- public async Task ExecuteAsync(SimpleSyncContext context, CancellationToken ct = default)
+ public async Task ExecuteAsync(
+ SimpleSyncContext context,
+ CancellationToken ct = default)
{
+ if (!context.UnusedTemplates.Any())
+ return context;
+
+ var targetUnits = await _targetUnitResolver.ResolveAsync(context.UnusedTemplates, ct);
+
+ int deactivated = 0;
+
foreach (var template in context.UnusedTemplates)
{
- _logger.LogInformation("Job '{JobName}' ({JobId}): деактивация шаблона '{TemplateName}' ({TemplateId}), Unit {Unit}",
- context.JobName, context.JobId, template.Name, template.Id,
+ _logger.LogInformation(
+ "Job '{JobName}' ({JobId}): деактивация шаблона '{TemplateName}' ({TemplateId}), Unit {Unit}",
+ context.JobName,
+ context.JobId,
+ template.Name,
+ template.Id,
context.FormatUnit(template.UnitId));
- await _deactivator.DeactivateTemplateAsync(template, context.Initiator);
+ if (!targetUnits.TryGetValue(template.Id, out var targetUnit))
+ {
+ _logger.LogError(
+ "Не удалось определить целевой Unit для деактивации шаблона {TemplateId}.",
+ template.Id);
+
+ continue;
+ }
+
+ var success = await _deactivator.DeactivateTemplateAsync(
+ template,
+ targetUnit,
+ context.Initiator,
+ ct);
+
+ if (success)
+ {
+ deactivated++;
+ }
+ else
+ {
+ _logger.LogError(
+ "Не удалось деактивировать шаблон {TemplateId}.",
+ template.Id);
+ }
}
+ _logger.LogDebug(
+ "Job '{JobName}' ({JobId}): деактивировано {Count} шаблонов из {Total}.",
+ context.JobName,
+ context.JobId,
+ deactivated,
+ context.UnusedTemplates.Count);
+
return context;
}
}
\ No newline at end of file
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/FilterUnitsStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/FilterUnitsStage.cs
index 6df32154..90f00d1b 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/FilterUnitsStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/FilterUnitsStage.cs
@@ -1,8 +1,7 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Services.UnitFilterService;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class FilterUnitsStage : ISimpleSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/LoadJobStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/LoadJobStage.cs
index 6692c7fe..c6657630 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/LoadJobStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/LoadJobStage.cs
@@ -1,9 +1,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces.JobRepositories;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class LoadJobStage : ISimpleSyncStage
{
diff --git a/PARR.TemplateMatcher/Services/SimpleSync/UpdateNamesStage.cs b/PARR.TemplateMatcher/Services/SimpleSync/UpdateNamesStage.cs
index 2fd61575..0f64d7a6 100644
--- a/PARR.TemplateMatcher/Services/SimpleSync/UpdateNamesStage.cs
+++ b/PARR.TemplateMatcher/Services/SimpleSync/UpdateNamesStage.cs
@@ -2,9 +2,8 @@
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.Domain.Enums;
using PARR.TemplateMatcher.Services.Interfaces;
-using PARR.TemplateMatcher.Services.SimpleSync;
-namespace PARR.TemplateMatcher.Services.Implementations.SimpleSync;
+namespace PARR.TemplateMatcher.Services.SimpleSync;
internal class UpdateNamesStage : ISimpleSyncWriteStage
{
diff --git a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs
index a5bb5b86..50034e22 100644
--- a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs
+++ b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs
@@ -35,27 +35,27 @@ namespace PARR.TemplateMatcher
services.AddScoped();
// Simple pipeline: Read-этапы
- services.AddScoped(); //1
- services.AddScoped(); //2
- services.AddScoped(); //3
+ services.AddScoped(); //1
+ services.AddScoped(); //2
+ services.AddScoped(); //3
// Simple pipeline: Write-этапы
- services.AddScoped(); //4
- services.AddScoped(); //5
- services.AddScoped(); //6
+ services.AddScoped(); //4
+ services.AddScoped(); //5
+ services.AddScoped(); //6
services.AddScoped();
// Grouped pipeline: Read-этапы
- services.AddScoped(); //1
- services.AddScoped(); //2
- services.AddScoped(); //3
- services.AddScoped(); //4
- services.AddScoped(); //5
+ services.AddScoped(); //1
+ services.AddScoped(); //2
+ services.AddScoped(); //3
+ services.AddScoped(); //4
+ services.AddScoped(); //5
// Grouped pipeline: Write-этапы
- services.AddScoped(); //6
- services.AddScoped();//7
+ services.AddScoped(); //6
+ services.AddScoped();//7
services.AddScoped();
@@ -81,6 +81,8 @@ namespace PARR.TemplateMatcher
//Сервис для неактуальных шаблонов
services.AddScoped();
+ services.AddTransient();
+ services.AddScoped();
}
public static IConfigurationBuilder AddTemplateMatcherConfigurations(this IConfigurationBuilder builder, IServiceCollection services)
diff --git a/PARR.Test/PARR.Test.csproj b/PARR.Test/PARR.Test.csproj
index 9d6a0fff..4ea8c673 100644
--- a/PARR.Test/PARR.Test.csproj
+++ b/PARR.Test/PARR.Test.csproj
@@ -25,4 +25,8 @@
+
+
+
+
diff --git a/PARR.Test/Worker.cs b/PARR.Test/Worker.cs
index 4abf4521..62d9c327 100644
--- a/PARR.Test/Worker.cs
+++ b/PARR.Test/Worker.cs
@@ -1,8 +1,6 @@
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
-using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Domain.Entities.Base.History;
-using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Enums;
using PARR.EsppApi;
using PARR.EsppApi.Constants;
@@ -43,12 +41,19 @@ namespace PARR.Test
//var bbb = aaa.ToOffset(new TimeSpan(3, 0, 0));
- await TemplateMatcherTest();
+ //await TemplateMatcherTest();
+ await using var scope = serviceProvider.CreateAsyncScope();
+ //var unitFilterService = scope.ServiceProvider.GetRequiredService();
+ //var t = await unitFilterService.GetRelatedUnitNamesAsync(Guid.Parse("4c35d6c3-f07d-4910-98af-b88247d22247"), Guid.Parse("4d4a1503-3a56-4e2a-bf92-a615c91c14ef"));
+ //t.ForEach(unit =>
+ //{
+ // _logger.LogInformation(unit);
+ //});
- //await NextRunTest();
-
+ //await GroupedSyncTest();
+ await SimpleSyncTest();
#region test
//using (var scope = serviceProvider.CreateScope())
@@ -164,7 +169,6 @@ namespace PARR.Test
}
#region SimpleSyncTest
-
private async Task SimpleSyncTest()
{
await using var scope = serviceProvider.CreateAsyncScope();
@@ -182,7 +186,7 @@ namespace PARR.Test
{
//Guid.Parse("cfb0d2dd-192a-4ed5-a29c-25f0abf61895"),//11010
//Guid.Parse("79976eaf-be7b-42a0-a4e3-a89d7f68a18e"),//17
- Guid.Parse("055ac0cc-10f7-4f96-baf6-9c8145b1332a")//318
+ Guid.Parse("edcabe55-7f89-45ae-b946-d136b8594c53")//48
};
if (!jobIds.Any())
@@ -207,7 +211,8 @@ namespace PARR.Test
InitiatorIp = "127.0.0.1",
InitiatorParrComponentId = ParrComponentsEnum.Master,
InitiatorComment = "Тестовая синхронизация SimpleTemplateSynchronizer"
- });
+ }, dryRun: false
+ );
sw.Stop();
passed++;
@@ -236,7 +241,7 @@ namespace PARR.Test
var templateMatcher = scope.ServiceProvider.GetRequiredService();
var groupRepository = scope.ServiceProvider.GetRequiredService();
- await templateMatcher.SyncTemplatesForJob(Guid.Parse("055ac0cc-10f7-4f96-baf6-9c8145b1332a"),
+ await templateMatcher.SyncTemplatesForJobGroup(Guid.Parse("9ed70f1e-69f6-4559-9139-b4947cce8d54"),
new HistoryInitiator
{
InitiatorIp = "10.99.246.156",
@@ -267,6 +272,56 @@ namespace PARR.Test
return;
}
#endregion
+ #region GroupedSyncTest
+ private async Task GroupedSyncTest()
+ {
+ _logger.LogInformation("=== DryRun-тестирование GroupedTemplateSynchronizer ===");
+
+ await using var scope = serviceProvider.CreateAsyncScope();
+
+ var synchronizers = scope.ServiceProvider.GetRequiredService>();
+ var groupedSync = synchronizers.FirstOrDefault(s => s is GroupedTemplateSynchronizer);
+
+ if (groupedSync == null)
+ {
+ _logger.LogError("[FAIL] GroupedTemplateSynchronizer не найден в DI");
+ return;
+ }
+
+ // Подставьте ID нужных JobGroup для тестирования
+ var jobGroupIds = new List
+ {
+ Guid.Parse("fb8678d0-aa8b-457d-8ea8-6452040c0692"), // Анализ производительности БД
+ // Добавьте другие ID для проверки
+ };
+
+ foreach (var jobGroupId in jobGroupIds)
+ {
+ _logger.LogInformation("--- DryRun: JobGroup {JobGroupId} ---", jobGroupId);
+
+ try
+ {
+ await groupedSync.SyncTemplatesForJobGroupAsync(
+ jobGroupId,
+ new HistoryInitiator
+ {
+ InitiatorIp = "127.0.0.1",
+ InitiatorParrComponentId = ParrComponentsEnum.Master,
+ InitiatorComment = "DryRun-тест"
+ },
+ dryRun: true // Ключевой флаг
+ );
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "[FAIL] DryRun для JobGroup {JobGroupId}", jobGroupId);
+ }
+
+ _logger.LogInformation("--- DryRun завершён ---");
+ _logger.LogInformation("");
+ }
+ }
+ #endregion
private async Task NextRunTest()
{