feat(dal,): NextRunServiceV2 - метод получения nextRun для нового шаблона

This commit is contained in:
Mikhail Trubnikov
2026-02-16 12:23:24 +10:00
parent 89b6cdf731
commit 361338f433
5 changed files with 242 additions and 8 deletions

View File

@@ -12,5 +12,27 @@ namespace PARR.DAL.NextRunServices
/// <param name="templateStatusType">Статус шаблона, если не передать, берутся все шаблоны группы</param> /// <param name="templateStatusType">Статус шаблона, если не передать, берутся все шаблоны группы</param>
/// <returns>Список TemplateId с NextRun и NextRunOld</returns> /// <returns>Список TemplateId с NextRun и NextRunOld</returns>
Task<List<TemplateNextRunResultDto>?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId, TemplateStatusTypeEnum? templateStatusType); Task<List<TemplateNextRunResultDto>?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId, TemplateStatusTypeEnum? templateStatusType);
/// <summary>
/// Получить nextRun для вновь создаваемого шаблона, которого еще нет в БД
/// </summary>
/// <param name="jobGroupId">ИД группы работ</param>
/// <param name="workGroupName">Рабочая группа, не маска</param>
/// <param name="responseAreaName">Зона ответственности, не маска</param>
/// <returns></returns>
Task<DateTimeOffset> GetNextRunForNewTemplateAsync(Guid jobGroupId, string workGroupName, string responseAreaName);
/// <summary>
/// Получить nextRun по id существующего в БД шаблона
/// </summary>
/// <param name="templateId">ИД шаблона</param>
/// <param name="isNew">При перемещении шаблона в новую для него работу, ставить isNew=true</param>
/// <returns></returns>
Task<DateTimeOffset> GetNextRunForTemplateAsync(Guid templateId, bool isNew);
} }
} }

View File

@@ -153,7 +153,7 @@ namespace PARR.DAL.NextRunServices
public async Task<DateTimeOffset> GetNextRunForNewTemplateAsync(Guid jobGroupId, string responseArea) public async Task<DateTimeOffset> GetNextRunForNewTemplateAsync(Guid jobGroupId, string responseArea)
{ {
// определяю это автораспределение или нет, вызываю соответствующий рассчет // определяю это автораспределение или нет, вызываю соответствующий расчет
var jobGroup = await jobGroupService.Get() var jobGroup = await jobGroupService.Get()
.Include(t => t.DistributionConfig) .Include(t => t.DistributionConfig)

View File

@@ -115,7 +115,7 @@ namespace PARR.DAL.NextRunServices
logger.LogDebug("Нужно группировать по РГ"); logger.LogDebug("Нужно группировать по РГ");
// получаем для каждого шаблона РГ и сразу группируем по РГ // получаем для каждого шаблона РГ и сразу группируем по РГ
var templatesByWorkGroup = new Dictionary<string?, List<Template>>(); var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates) foreach (var template in allTemplates)
{ {
var workGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template); var workGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
@@ -199,7 +199,7 @@ namespace PARR.DAL.NextRunServices
logger.LogDebug("Использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)"); logger.LogDebug("Использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)");
// получаем для каждого шаблона ЗО и сразу группируем по ЗО // получаем для каждого шаблона ЗО и сразу группируем по ЗО
var templatesByResponseArea = new Dictionary<string?, List<Template>>(); var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates) foreach (var template in allTemplates)
{ {
var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template); var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
@@ -260,6 +260,214 @@ namespace PARR.DAL.NextRunServices
} }
public async Task<DateTimeOffset> GetNextRunForNewTemplateAsync(Guid jobGroupId, string workGroupName, string responseAreaName)
{
logger.LogDebug("Получить nextRun для вновь создаваемого шаблона. jobGroupId: {jobGroupId}, workGroupName: {workGroupName}, responseAreaName: {responseAreaName}",
jobGroupId, workGroupName, responseAreaName);
var jobGroup = await jobGroupService.Get()
.AsNoTracking()
.Include(t => t.DistributionConfig)
.ThenInclude(t => t.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == jobGroupId);
if (jobGroup == null)
{
logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId);
throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}");
}
if (jobGroup.IsAutoDistributionEnabled)
{
// автораспределение
logger.LogDebug("Расчет используя метод распределения работ");
if (jobGroup.DistributionConfig == null)
{
logger.LogError("Для группы работ {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", jobGroupId, nameof(JobGroupDistributionConfig));
throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {jobGroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}");
}
var startDate = GetDateStart();
var duration = GetDurationDays(jobGroup.DistributionConfig);
var excludeWeekends = jobGroup.DistributionConfig.IsExcludeWeekends;
var targetTemplate = new TemplateNextRunDto(Guid.Empty, null);
var templateStatusType = TemplateStatusTypeEnum.Used;
var allTemplatesQuery = templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.Where(t => t.StatusTypeId == templateStatusType && t.Job!.GroupId == jobGroupId);
// группировать по РГ
var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;
// использовать часовой пояс РГ
var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;
if (isGroupingByWorkGroup)
{
// группировать по РГ
logger.LogDebug("Нужно группировать по РГ");
// получаем для всех шаблонов РГ и группируем их по РГ
var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
var allTemplates = await allTemplatesQuery.ToListAsync();
foreach (var template in allTemplates)
{
var templateWorkGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
// Если даже workGroupName == null, ну и ладно, сгруппируем по null
if (templateWorkGroupName == null)
logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
template.Id, template.Job!.WorkGroupMask, templateWorkGroupName);
// Добавляем шаблон в группу
if (!templatesByWorkGroup.TryGetValue(templateWorkGroupName, out var templatesList))
{
templatesList = new List<Template>();
templatesByWorkGroup[templateWorkGroupName] = templatesList;
}
templatesList.Add(template);
}
//Берем шаблоны только нашей РГ workGroupName
templatesByWorkGroup.TryGetValue(workGroupName, out var templatesWithWorkGroup);
var templatesByWorkGroupDto = (templatesWithWorkGroup ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Шаблонов в РГ {workGroupName} - {count} шт.", workGroupName, templatesByWorkGroupDto.Count);
if (isResponseAreaTimeZone)
{
// использовать часовой пояс РГ
logger.LogDebug("Использовать часовой пояс РГ '{responseAreaName}'", responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");
var offset = GetOffsetForDistributionByResponseArea();
var referenceDate = GetReferenceDate(jobGroup, null);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
}
else
{
// не нужно группировать по РГ
logger.LogDebug("Не нужно группировать по РГ");
if (isResponseAreaTimeZone)
{
// использовать ЧАСОВОЙ ПОЯС ЗО
logger.LogDebug("Нужно использовать часовой пояс ЗО '{responseAreaName}'", responseAreaName);
// получить все шаблоны, сгруппировать их по ЗО, получить nextRun в рамках переданной ЗО
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
var allTemplates = await allTemplatesQuery.ToListAsync();
// получаем для каждого шаблона ЗО и сразу группируем по ЗО
var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
if (responseArea == null)
logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
template.Id, template.Job!.ResponseAreaMask, responseArea);
// Добавляем шаблон в группу
if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
{
templateList = new List<Template>();
templatesByResponseArea[responseArea] = templateList;
}
templateList.Add(template);
}
// из сгруппированных шаблонов, получаем существующие с нашей переданной responseAreaName
templatesByResponseArea.TryGetValue(responseAreaName, out var templatesWithResponseArea);
var templatesWithResponseAreaDto = (templatesWithResponseArea ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Существующих шаблонов в БД с responseAreaName: {responseAreaName} - {count} шт.", responseAreaName, templatesWithResponseAreaDto.Count);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesWithResponseAreaDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс ЗО
logger.LogDebug("Не нужно использовать часовой пояс ЗО.");
var referenceDate = GetReferenceDate(jobGroup, null);
var offset = GetOffsetForDistributionByResponseArea();
var allTemplates = await allTemplatesQuery
.Select(t => new TemplateNextRunDto(t.Id, t.NextRun))
.ToListAsync();
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, allTemplates, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
}
}
else
{
// расписание ЕСПП
logger.LogDebug("Расчет используя метод ЕСПП");
if (jobGroup.IsResponseAreaTimezone)
{
// использовать часовой пояс РГ
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
logger.LogDebug("Расчет nextRun используя метод ЕСПП, использую часовой пояс ЗО '{responseAreaName}, referenceDate: {referenceDate}, offset: {offset}'", responseAreaName, referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);
return nextRun;
}
else
{
// не использовать часовой пояс РГ
// так как это классическое расписание еспп, и не используется часовой пояс РГ, то нам нужно считать в часовом поясе УЗ ЕСПП, т.е. МСК
var referenceDate = GetReferenceDate(jobGroup, null);
var offset = GetOffsetForDistributionByResponseArea();
logger.LogDebug("Расчет nextRun используя метод ЕСПП, не использую часовой пояс ЗО, использую часовой пояс УЗ ЕСПП, referenceDate: {referenceDate}, offset: {offset}", referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);
return nextRun;
}
}
}
public async Task<DateTimeOffset> GetNextRunForTemplateAsync(Guid templateId, bool isNew)
{
logger.LogDebug("Получить nextRun для существующего шаблона, templateId: {templateId}, isNew: {isNew}", templateId, isNew);
throw new NotImplementedException();
}
/// <summary> /// <summary>
/// Получить продолжительность в днях /// Получить продолжительность в днях
/// </summary> /// </summary>
@@ -354,7 +562,6 @@ namespace PARR.DAL.NextRunServices
} }
/// <summary> /// <summary>
/// Получить referenceDate для расчета с учетом часового пояса ЗО или без учета /// Получить referenceDate для расчета с учетом часового пояса ЗО или без учета
/// </summary> /// </summary>
@@ -413,7 +620,5 @@ namespace PARR.DAL.NextRunServices
} }
} }
} }
} }

View File

@@ -68,7 +68,7 @@ namespace PARR.DAL.NextRunServices.Subservices
{ {
//offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК //offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК
logger.LogDebug("Начинаю рассчет nextRun по расписанию ЕСПП. Входные данные: date: {referenceDate}, offset: {offset}, esppSchedule: {esppSchedule}", referenceDate, offset, JsonSerializer.Serialize(esppSchedule)); logger.LogDebug("Начинаю рассчет nextRun по расписанию ЕСПП. Входные данные: date: {referenceDate}, offset: {offset}", referenceDate, offset);
var nextRun = referenceDate; var nextRun = referenceDate;

View File

@@ -39,7 +39,14 @@ namespace PARR.Test.NextRun
{ {
#region тестирование INextRunServiceV2 #region тестирование INextRunServiceV2
var distributeResult = await nextRunServiceV2.GetNextRunForJobGroupWithAutoDistributionAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"), TemplateStatusTypeEnum.Used); //рапсределить
//var distributeResult = await nextRunServiceV2.GetNextRunForJobGroupWithAutoDistributionAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"), TemplateStatusTypeEnum.Used);
// новый шаблон - автодистриб
//var nextRunForNewTemplateDistrib = await nextRunServiceV2.GetNextRunForNewTemplateAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"), workGroupName: "ЦКИТ-ВСИБ", responseAreaName: "92-ВСИБ");
// новый шаблон - еспп
//var nextRunForNewTemplateEspp = await nextRunServiceV2.GetNextRunForNewTemplateAsync(Guid.Parse("692531d6-4ef4-4a6d-99be-d2ef375d520f"), workGroupName: "ЦКИТ-ЗСИБ", responseAreaName: "83-ЗСИБ");
#endregion #endregion