2026-05-19 09:47:35 +10:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2026-07-03 11:17:48 +10:00
|
|
|
|
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
2026-05-19 09:47:35 +10:00
|
|
|
|
using PARR.Domain.Entities.Base.History;
|
2026-07-03 11:17:48 +10:00
|
|
|
|
using PARR.Domain.Entities.JobEntities;
|
2026-06-15 16:56:23 +10:00
|
|
|
|
using PARR.Domain.Entities.JobGroupEntities;
|
2026-05-19 09:47:35 +10:00
|
|
|
|
using PARR.Domain.Entities.Schedule;
|
|
|
|
|
|
|
|
|
|
|
|
namespace PARR.Core.Common.Helpers;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Сервис для полного клонирования JobGroup со всеми вложенными Job, фильтрами, AutoControl и EsppSchValues.
|
|
|
|
|
|
/// Шаблоны (Template) не клонируются. Идентификаторы генерируются стратегией EF Core.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class JobGroupCloneHelper
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IJobGroupRepository groupRepository;
|
|
|
|
|
|
private readonly ILogger<JobGroupCloneHelper> logger;
|
|
|
|
|
|
|
|
|
|
|
|
public JobGroupCloneHelper(
|
|
|
|
|
|
IJobGroupRepository groupRepository,
|
|
|
|
|
|
ILogger<JobGroupCloneHelper> logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.groupRepository = groupRepository;
|
|
|
|
|
|
this.logger = logger;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<Guid> CloneAsync(
|
|
|
|
|
|
Guid sourceJobGroupId,
|
|
|
|
|
|
string? newGroupName,
|
|
|
|
|
|
HistoryInitiator initiator,
|
|
|
|
|
|
CancellationToken ct = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sourceGroup = await groupRepository.Get()
|
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
|
.Include(g => g.Jobs)
|
|
|
|
|
|
.ThenInclude(j => j.UnitFilters)
|
|
|
|
|
|
.ThenInclude(uf => uf.FieldFilters)
|
|
|
|
|
|
.Include(g => g.Jobs)
|
|
|
|
|
|
.ThenInclude(j => j.UnitFilters)
|
|
|
|
|
|
.ThenInclude(uf => uf.RelationshipFilters)
|
|
|
|
|
|
.Include(g => g.Jobs)
|
|
|
|
|
|
.ThenInclude(j => j.AutoControl)
|
|
|
|
|
|
.Include(g => g.EsppSchValues)
|
|
|
|
|
|
.FirstOrDefaultAsync(g => g.Id == sourceJobGroupId, ct);
|
|
|
|
|
|
|
|
|
|
|
|
if (sourceGroup == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException($"JobGroup {sourceJobGroupId} not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("Начало клонирования JobGroup {JobGroupId}.", sourceJobGroupId);
|
|
|
|
|
|
|
|
|
|
|
|
var newGroup = MapJobGroup(sourceGroup, newGroupName);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var sourceJob in sourceGroup.Jobs ?? Enumerable.Empty<Job>())
|
|
|
|
|
|
{
|
|
|
|
|
|
var newJob = MapJob(sourceJob);
|
|
|
|
|
|
newGroup.Jobs.Add(newJob);
|
|
|
|
|
|
|
|
|
|
|
|
CloneJobFilters(sourceJob.UnitFilters, newJob);
|
|
|
|
|
|
|
|
|
|
|
|
if (sourceJob.AutoControl != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
newJob.AutoControl = MapAutoControl(sourceJob.AutoControl);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Клонирование значений расписания
|
|
|
|
|
|
foreach (var sourceSchValue in sourceGroup.EsppSchValues ?? Enumerable.Empty<EsppSchValue>())
|
|
|
|
|
|
{
|
|
|
|
|
|
newGroup.EsppSchValues.Add(new EsppSchValue
|
|
|
|
|
|
{
|
|
|
|
|
|
TypeValueId = sourceSchValue.TypeValueId,
|
|
|
|
|
|
TypeConfigId = sourceSchValue.TypeConfigId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var isSuccess = await groupRepository.CreateAsync(newGroup);
|
|
|
|
|
|
if (!isSuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("Не удалось сохранить клон JobGroup в базу данных.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await groupRepository.CommitAsync(initiator);
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("JobGroup успешно клонирована. Новый ID: {NewGroupId}.", newGroup.Id);
|
|
|
|
|
|
|
|
|
|
|
|
return newGroup.Id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private JobGroup MapJobGroup(JobGroup source, string? newName)
|
|
|
|
|
|
{
|
|
|
|
|
|
string finalName = string.IsNullOrWhiteSpace(newName)
|
|
|
|
|
|
? $"{source.GroupName?.TrimEnd()} КЛОН"
|
|
|
|
|
|
: newName.Trim();
|
|
|
|
|
|
|
|
|
|
|
|
return new JobGroup
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupName = finalName,
|
|
|
|
|
|
ShortDescription = source.ShortDescription,
|
|
|
|
|
|
FullDescription = source.FullDescription,
|
|
|
|
|
|
Solution = source.Solution,
|
|
|
|
|
|
TemplateDuration = source.TemplateDuration,
|
|
|
|
|
|
ReferenceDate = source.ReferenceDate,
|
|
|
|
|
|
UserTimeZoneOffsetMinutes = source.UserTimeZoneOffsetMinutes,
|
|
|
|
|
|
IsResponseAreaTimezone = source.IsResponseAreaTimezone,
|
|
|
|
|
|
IsAutoDistributionEnabled = source.IsAutoDistributionEnabled,
|
|
|
|
|
|
IsAgent = source.IsAgent,
|
|
|
|
|
|
AgentName = source.AgentName,
|
|
|
|
|
|
AgentTimeOutSec = source.AgentTimeOutSec,
|
|
|
|
|
|
AgentScript = source.AgentScript,
|
|
|
|
|
|
GroupTypeId = source.GroupTypeId,
|
|
|
|
|
|
GroupingUnitFieldId = source.GroupingUnitFieldId,
|
|
|
|
|
|
IsGroupByResponsible = source.IsGroupByResponsible,
|
|
|
|
|
|
ScheduleExcludeTypeId = source.ScheduleExcludeTypeId,
|
|
|
|
|
|
ScheduleExcludeTypeCalendarId = source.ScheduleExcludeTypeCalendarId,
|
|
|
|
|
|
DateCreated = DateTimeOffset.UtcNow,
|
|
|
|
|
|
Jobs = new HashSet<Job>(),
|
|
|
|
|
|
EsppSchValues = new HashSet<EsppSchValue>()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Job MapJob(Job source)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new Job
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = source.Name,
|
|
|
|
|
|
WorkName = source.WorkName,
|
|
|
|
|
|
MinValueRelationships = source.MinValueRelationships,
|
|
|
|
|
|
MaxValueRelationships = source.MaxValueRelationships,
|
|
|
|
|
|
IsParentRelationships = source.IsParentRelationships,
|
|
|
|
|
|
TemplateNameMask = source.TemplateNameMask,
|
|
|
|
|
|
WorkGroupMask = source.WorkGroupMask,
|
|
|
|
|
|
ResponseAreaMask = source.ResponseAreaMask,
|
|
|
|
|
|
TnkId = source.TnkId,
|
|
|
|
|
|
DateCreated = DateTimeOffset.UtcNow,
|
|
|
|
|
|
UnitFilters = new HashSet<JobUnitFilter>()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private JobAutoControl MapAutoControl(JobAutoControl source)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new JobAutoControl
|
|
|
|
|
|
{
|
|
|
|
|
|
InitUsedTemplateState = source.InitUsedTemplateState,
|
|
|
|
|
|
InitUsedScheduleState = source.InitUsedScheduleState
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CloneJobFilters(IEnumerable<JobUnitFilter>? sourceFilters, Job targetJob)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sourceFilters == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var sourceFilter in sourceFilters)
|
|
|
|
|
|
{
|
|
|
|
|
|
var newFilter = new JobUnitFilter
|
|
|
|
|
|
{
|
|
|
|
|
|
UnitFilter = sourceFilter.UnitFilter,
|
|
|
|
|
|
DateCreated = DateTimeOffset.UtcNow,
|
|
|
|
|
|
FieldFilters = sourceFilter.FieldFilters?.Select(f => new JobFieldFilter
|
|
|
|
|
|
{
|
|
|
|
|
|
FieldId = f.FieldId,
|
|
|
|
|
|
ValueMask = f.ValueMask,
|
|
|
|
|
|
IsInverse = f.IsInverse,
|
|
|
|
|
|
DateCreated = DateTimeOffset.UtcNow
|
|
|
|
|
|
}).ToList() ?? new List<JobFieldFilter>(),
|
|
|
|
|
|
RelationshipFilters = sourceFilter.RelationshipFilters?.Select(r => new JobRelationshipFilter
|
|
|
|
|
|
{
|
|
|
|
|
|
FieldId = r.FieldId,
|
|
|
|
|
|
ValueMask = r.ValueMask,
|
|
|
|
|
|
IsInverse = r.IsInverse,
|
|
|
|
|
|
IsParent = r.IsParent,
|
|
|
|
|
|
IsFullMatch = r.IsFullMatch
|
|
|
|
|
|
}).ToList() ?? new List<JobRelationshipFilter>()
|
|
|
|
|
|
};
|
|
|
|
|
|
targetJob.UnitFilters.Add(newFilter);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|