Merge branch 'distributor' into dev
This commit is contained in:
12
PARR.API.sln
12
PARR.API.sln
@@ -88,10 +88,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.AIHITRelationshipsSync
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateGeneratorWorker", "PARR.TemplateGeneratorWorker\PARR.TemplateGeneratorWorker.csproj", "{17245D0B-3290-4C96-B5B1-4B0711BABAF2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateGeneratorWorker", "PARR.TemplateGeneratorWorker\PARR.TemplateGeneratorWorker.csproj", "{17245D0B-3290-4C96-B5B1-4B0711BABAF2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateTaskGenerator", "PARR.TemplateTaskGenerator\PARR.TemplateTaskGenerator.csproj", "{D4C51090-CE06-4158-9A0E-961744872411}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateTaskGeneratorWorker", "PARR.TemplateTaskGeneratorWorker\PARR.TemplateTaskGeneratorWorker.csproj", "{93249642-DF7D-41A1-87C0-6E3BAECB43EC}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateMatcher", "PARR.TemplateMatcher\PARR.TemplateMatcher.csproj", "{EADC9A11-426A-41E9-98B4-010295412FF5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateMatcher", "PARR.TemplateMatcher\PARR.TemplateMatcher.csproj", "{EADC9A11-426A-41E9-98B4-010295412FF5}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateMatcherWorker", "PARR.TemplateMatcherWorker\PARR.TemplateMatcherWorker.csproj", "{DA6B2A17-6E64-4E2C-8D4C-E278BEE94E03}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TemplateMatcherWorker", "PARR.TemplateMatcherWorker\PARR.TemplateMatcherWorker.csproj", "{DA6B2A17-6E64-4E2C-8D4C-E278BEE94E03}"
|
||||||
@@ -260,14 +256,6 @@ Global
|
|||||||
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{17245D0B-3290-4C96-B5B1-4B0711BABAF2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{D4C51090-CE06-4158-9A0E-961744872411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D4C51090-CE06-4158-9A0E-961744872411}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D4C51090-CE06-4158-9A0E-961744872411}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D4C51090-CE06-4158-9A0E-961744872411}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{93249642-DF7D-41A1-87C0-6E3BAECB43EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{93249642-DF7D-41A1-87C0-6E3BAECB43EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{93249642-DF7D-41A1-87C0-6E3BAECB43EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{93249642-DF7D-41A1-87C0-6E3BAECB43EC}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{EADC9A11-426A-41E9-98B4-010295412FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{EADC9A11-426A-41E9-98B4-010295412FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{EADC9A11-426A-41E9-98B4-010295412FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EADC9A11-426A-41E9-98B4-010295412FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{EADC9A11-426A-41E9-98B4-010295412FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EADC9A11-426A-41E9-98B4-010295412FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
30
PARR.DAL/NextRunServices/INextRunService.cs
Normal file
30
PARR.DAL/NextRunServices/INextRunService.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace PARR.DAL.NextRunServices
|
||||||
|
{
|
||||||
|
public interface INextRunService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получить список TemplateId, NextRun по jobGroupId с автораспределением
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jobGroupId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<List<(Guid TemplateId, DateTimeOffset NextRun)>> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить NextRun по jobGroupId с расписанием ЕСПП
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jobGroupId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<DateTimeOffset> GetNextRunForJobGroupWithEsppSchedulleAsync(Guid jobGroupId);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить NextRun по id шаблона
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="templateId"></param>
|
||||||
|
/// <param name="isNew">Новый шаблон или шаблон переведенный из unused</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<DateTimeOffset> GetNextRunForTemplate(Guid templateId, bool isNew);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
14
PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs
Normal file
14
PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace PARR.DAL.NextRunServices.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Модель шаблона, для расчета NextRun
|
||||||
|
/// </summary>
|
||||||
|
public record TemplateNextRunDto(Guid Id, DateTimeOffset? NextRun);
|
||||||
|
|
||||||
|
//public class TemplateNextRunDto
|
||||||
|
//{
|
||||||
|
// public Guid Id { get; set; }
|
||||||
|
|
||||||
|
// public DateTimeOffset? NextRun { get; set; }
|
||||||
|
//}
|
||||||
|
}
|
||||||
16
PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs
Normal file
16
PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace PARR.DAL.NextRunServices.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Модель, результат распределения шаблона
|
||||||
|
/// </summary>
|
||||||
|
public record TemplateNextRunResultDto(Guid Id, DateTimeOffset NextRun, DateTimeOffset? NextRunOld);
|
||||||
|
|
||||||
|
//public class TemplateNextRunResultDto
|
||||||
|
//{
|
||||||
|
// public Guid Id { get; set; }
|
||||||
|
|
||||||
|
// public DateTimeOffset NextRun { get; set; }
|
||||||
|
|
||||||
|
// public DateTimeOffset? NextRunOld { get; set; }
|
||||||
|
//}
|
||||||
|
}
|
||||||
85
PARR.DAL/NextRunServices/NextRunService.cs
Normal file
85
PARR.DAL/NextRunServices/NextRunService.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.DAL.NextRunServices.Subservices;
|
||||||
|
using PARR.DAL.Services.Interfaces;
|
||||||
|
using PARR.DAL.Services.Interfaces.Job;
|
||||||
|
using PARR.DAL.TransformServices;
|
||||||
|
|
||||||
|
namespace PARR.DAL.NextRunServices
|
||||||
|
{
|
||||||
|
internal class NextRunService : INextRunService
|
||||||
|
{
|
||||||
|
private readonly ILogger<NextRunService> logger;
|
||||||
|
private readonly ITemplateService templateService;
|
||||||
|
private readonly IJobGroupService jobGroupService;
|
||||||
|
private readonly IEsppScheduleTransformService esppScheduleTransformService;
|
||||||
|
private readonly ITemplateDistributor templateDistributor;
|
||||||
|
|
||||||
|
public NextRunService(
|
||||||
|
ILogger<NextRunService> logger,
|
||||||
|
ITemplateService templateService,
|
||||||
|
IJobGroupService jobGroupService,
|
||||||
|
IEsppScheduleTransformService esppScheduleTransformService,
|
||||||
|
ITemplateDistributor templateDistributor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.templateService = templateService;
|
||||||
|
this.jobGroupService = jobGroupService;
|
||||||
|
this.esppScheduleTransformService = esppScheduleTransformService;
|
||||||
|
this.templateDistributor = templateDistributor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<(Guid TemplateId, DateTimeOffset NextRun)>> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId)
|
||||||
|
{
|
||||||
|
//TODO:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
//var ditributedTemplateList = await templateDistributor.DistributeTemplatesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<DateTimeOffset> GetNextRunForJobGroupWithEsppSchedulleAsync(Guid jobGroupId)
|
||||||
|
{
|
||||||
|
var jobGroup = await jobGroupService.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Id == jobGroupId);
|
||||||
|
|
||||||
|
if (jobGroup == null)
|
||||||
|
{
|
||||||
|
logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId);
|
||||||
|
throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Берем из обычного расписания ЕСПП
|
||||||
|
return await esppScheduleTransformService.GetNextDateAsync(jobGroupId, jobGroup.ReferenceDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<DateTimeOffset> GetNextRunForTemplate(Guid templateId, bool isNew)
|
||||||
|
{
|
||||||
|
var template = await templateService.Get()
|
||||||
|
.Include(t => t.Job).ThenInclude(t => t.Group)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == templateId);
|
||||||
|
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
logger.LogError("Не найдена шаблон с Id: {templateId}.", templateId);
|
||||||
|
throw new ArgumentNullException(nameof(templateId), $"Не найдена шаблон с Id: {templateId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.Job!.Group!.IsAutoDistributionEnabled == true)
|
||||||
|
{
|
||||||
|
// включено автораспределение
|
||||||
|
//TODO: !!!!!!!!!!!!!!!! добавить метод рассчета с учетом распределения
|
||||||
|
throw new NotImplementedException();
|
||||||
|
//return await templateDistributor.GetValidNextRunForTemplateAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// считаем как ЕСПП
|
||||||
|
return await esppScheduleTransformService.GetNextDateAsync(template.Job.Group.Id, template.Job.Group.ReferenceDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs
Normal file
37
PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using PARR.DAL.NextRunServices.Models;
|
||||||
|
|
||||||
|
namespace PARR.DAL.NextRunServices.Subservices
|
||||||
|
{
|
||||||
|
//todo: public -> internal!!!
|
||||||
|
public interface ITemplateDistributor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Распределяет все шаблоны на указанный период, учитывая план нагрузки.
|
||||||
|
/// Возвращает список всех шаблонов с обновлёнными датами выполнения.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate">Дата начала распределения</param>
|
||||||
|
/// <param name="duration">Длительность распределения, дней</param>
|
||||||
|
/// <param name="referenceDate">ReferenceDate</param>
|
||||||
|
/// <param name="allTemplates">Спиоск шаблонов для распределения</param>
|
||||||
|
/// <param name="excludeWeekends">Исключать выходные из распределения</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает актуальную дату выполнения для одного шаблона, учитывая план нагрузки.
|
||||||
|
/// Возвращает обновлённый шаблон с новой датой выполнения.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="duration"></param>
|
||||||
|
/// <param name="referenceDate"></param>
|
||||||
|
/// <param name="targetTemplate"></param>
|
||||||
|
/// <param name="allTemplates"></param>
|
||||||
|
/// <param name="excludeWeekends"></param>
|
||||||
|
/// <param name="isNew">Новый шаблон</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<TemplateNextRunResultDto> GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
593
PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs
Normal file
593
PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs
Normal file
@@ -0,0 +1,593 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.DAL.NextRunServices.Models;
|
||||||
|
using PARR.DAL.Services.Interfaces;
|
||||||
|
|
||||||
|
namespace PARR.DAL.NextRunServices.Subservices
|
||||||
|
{
|
||||||
|
internal class TemplateDistributor : ITemplateDistributor
|
||||||
|
{
|
||||||
|
private readonly ILogger<TemplateDistributor> logger;
|
||||||
|
private readonly IWeekendDayService weekendDayService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Смещение nextRun на несколько лет вперед, если не смог его рассчитать
|
||||||
|
/// </summary>
|
||||||
|
private const int offsetYearsIfErrorCalcNextRun = 5;
|
||||||
|
|
||||||
|
public TemplateDistributor(
|
||||||
|
ILogger<TemplateDistributor> logger,
|
||||||
|
IWeekendDayService weekendDayService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.weekendDayService = weekendDayService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<TemplateNextRunResultDto> GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew)
|
||||||
|
{
|
||||||
|
//todo: allTemplates - пока не используется. Будет использоваться когда каждый раз будем строить план
|
||||||
|
|
||||||
|
// Пока работает так:
|
||||||
|
// Получаем список рабочих дней, если работа попадает в эти дни, то считаем дату валидной
|
||||||
|
// Если нет, то перемещаем на последний день периода распределения или первый в зависимости от параметра isNew
|
||||||
|
|
||||||
|
var _startDate = GetStartPeriodDate(startDate, referenceDate);
|
||||||
|
var _endDate = GetEndPeriodDate(_startDate, duration);
|
||||||
|
|
||||||
|
logger.LogDebug("Получаю актуальный nextRun для шаблона {templateId}. Текущий nextRun: {nextRun}. Период {duration} дней, с {_startDate} по {_endDate}. Исключить выходные и праздники: {excludeWeekends}. Для нового шаблона: {isNew}", targetTemplate.Id, targetTemplate.NextRun, duration, _startDate, _endDate, excludeWeekends, isNew);
|
||||||
|
|
||||||
|
// Получаем список рабочих дней (исключая выходные и праздники)
|
||||||
|
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
|
||||||
|
|
||||||
|
if (workDays.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Нет доступных рабочих дней в диапазоне {_startDate} - {_endDate}. Возвращаем шаблон со смещением: {offsetYearsIfErrorCalcNextRun} лет.", _startDate, _endDate, offsetYearsIfErrorCalcNextRun);
|
||||||
|
|
||||||
|
// смещаем nextRun вперед
|
||||||
|
var result = FillTemplateResultWithOffsetError(targetTemplate, _startDate, referenceDate);
|
||||||
|
|
||||||
|
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// если isNew, то берем первый день периода
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
var result = new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: FindNextAvailableDateIgnoreLoad(workDays, referenceDate, true), //CreateDateTimeOffset(workDays.First(), referenceDate),
|
||||||
|
NextRunOld: targetTemplate.NextRun
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. Это было создание нового, поместили его в начало периода. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// есть ли NextRun и попадает ли он на рабочий день
|
||||||
|
// проверяем чтоб было не меньше чем UtcNow
|
||||||
|
//TODO: тут еще сделать проверку нагрузки на день
|
||||||
|
if (targetTemplate.NextRun.HasValue && workDays.Contains(DateOnly.FromDateTime(targetTemplate.NextRun.Value.Date)) && targetTemplate.NextRun.Value > DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
var result = new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: CreateDateTimeOffset(targetTemplate.NextRun.Value, referenceDate),
|
||||||
|
NextRunOld: targetTemplate.NextRun
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun актуальный, возвращаем его. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: Временный костыль, перемещаем на конец периода
|
||||||
|
|
||||||
|
var endPeriodResult = new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: FindNextAvailableDateIgnoreLoad(workDays, referenceDate, false),
|
||||||
|
NextRunOld: targetTemplate.NextRun
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun перемещен на конец периода. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", endPeriodResult.Id, endPeriodResult.NextRun, endPeriodResult.NextRunOld);
|
||||||
|
|
||||||
|
return endPeriodResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends)
|
||||||
|
{
|
||||||
|
var _startDate = GetStartPeriodDate(startDate, referenceDate);
|
||||||
|
var _endDate = GetEndPeriodDate(_startDate, duration);
|
||||||
|
|
||||||
|
logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDate: {referenceDate}. Исключить выходные и праздники: {excludeWeekends}.", allTemplates.Count, duration, _startDate, _endDate, referenceDate, excludeWeekends);
|
||||||
|
|
||||||
|
// Получаем список рабочих дней (исключая выходные и праздники)
|
||||||
|
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
|
||||||
|
|
||||||
|
if (workDays.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Нет доступных рабочих дней в диапазоне {_startDate} - {_endDate}. Возвращаем список шаблонов со смещением: {offsetYearsIfErrorCalcNextRun} лет.", _startDate, _endDate, offsetYearsIfErrorCalcNextRun);
|
||||||
|
|
||||||
|
// смещаем все nextRun вперед
|
||||||
|
var result = allTemplates.Select(t =>
|
||||||
|
FillTemplateResultWithOffsetError(t, _startDate, referenceDate)
|
||||||
|
).OrderBy(t => t.NextRun).ToList();
|
||||||
|
|
||||||
|
logger.LogInformation("Распределение завершено. Возвращаем {Count} записей. NextRun не изменился у {oldCount} шт. Обновлен NextRun у {changedCount} шт.", result.Count, result.Count(t => t.NextRun == t.NextRunOld), result.Count(t => t.NextRun != t.NextRunOld));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalWorks = allTemplates.Count;
|
||||||
|
|
||||||
|
// интервал между шаблонами
|
||||||
|
var interval = GetInterval(workDays.Count, totalWorks);
|
||||||
|
|
||||||
|
// строим план, дни на которые должны быть назанчены шаблоны
|
||||||
|
var plan = GetPlan(totalWorks, workDays);
|
||||||
|
|
||||||
|
|
||||||
|
// Подсчитываем, сколько работ должно быть на каждый день
|
||||||
|
var expectedDailyLoad = new Dictionary<DateOnly, int>();
|
||||||
|
|
||||||
|
foreach (var day in plan)
|
||||||
|
expectedDailyLoad[day] = expectedDailyLoad.GetValueOrDefault(day, 0) + 1;
|
||||||
|
|
||||||
|
#region Группируем шаблоны по дням, на которые они уже назначены
|
||||||
|
|
||||||
|
var templatesByDay = new Dictionary<DateOnly, List<TemplateNextRunDto>>();
|
||||||
|
|
||||||
|
foreach (var template in allTemplates)
|
||||||
|
{
|
||||||
|
if (template.NextRun.HasValue)
|
||||||
|
{
|
||||||
|
var day = DateOnly.FromDateTime(template.NextRun.Value.Date);
|
||||||
|
if (!templatesByDay.ContainsKey(day))
|
||||||
|
{
|
||||||
|
templatesByDay[day] = new List<TemplateNextRunDto>();
|
||||||
|
}
|
||||||
|
templatesByDay[day].Add(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Подсчитываем текущую нагрузку на каждый день
|
||||||
|
|
||||||
|
var currentDailyLoad = new Dictionary<DateOnly, int>();
|
||||||
|
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
currentDailyLoad[day] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in templatesByDay)
|
||||||
|
{
|
||||||
|
var day = kvp.Key;
|
||||||
|
var count = kvp.Value.Count;
|
||||||
|
currentDailyLoad[day] = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Определяем, какие шаблоны нужно оставить, а какие — переназначить
|
||||||
|
|
||||||
|
var templatesToKeep = new List<TemplateNextRunResultDto>();
|
||||||
|
var templatesToReassign = new List<TemplateNextRunDto>();
|
||||||
|
|
||||||
|
foreach (var kvp in templatesByDay)
|
||||||
|
{
|
||||||
|
var day = kvp.Key;
|
||||||
|
var templates = kvp.Value;
|
||||||
|
|
||||||
|
// Если день — нерабочий, то все шаблоны нужно переназначить
|
||||||
|
if (!workDays.Contains(day))
|
||||||
|
{
|
||||||
|
foreach (var template in templates)
|
||||||
|
{
|
||||||
|
templatesToReassign.Add(template);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} назначен на нерабочий день {NextRun} — будет переназначен.", template.Id, template.NextRun);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedCount = expectedDailyLoad.GetValueOrDefault(day, 0);
|
||||||
|
var currentCount = templates.Count;
|
||||||
|
|
||||||
|
if (currentCount <= expectedCount)
|
||||||
|
{
|
||||||
|
// Все шаблоны в этом дне можно оставить
|
||||||
|
foreach (var template in templates)
|
||||||
|
{
|
||||||
|
templatesToKeep.Add(new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: template.Id,
|
||||||
|
NextRun: template.NextRun.Value,
|
||||||
|
NextRunOld: template.NextRun
|
||||||
|
));
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Оставляем только expectedCount штук, остальные — переназначить
|
||||||
|
for (int i = 0; i < expectedCount; i++)
|
||||||
|
{
|
||||||
|
var template = templates[i];
|
||||||
|
templatesToKeep.Add(new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: template.Id,
|
||||||
|
NextRun: template.NextRun.Value,
|
||||||
|
NextRunOld: template.NextRun
|
||||||
|
));
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = expectedCount; i < currentCount; i++)
|
||||||
|
{
|
||||||
|
var template = templates[i];
|
||||||
|
templatesToReassign.Add(template);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} переназначается, т.к. нагрузка в день превышена.", template.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Добавляем шаблоны, у которых не было даты — тоже нужно распределить
|
||||||
|
|
||||||
|
foreach (var template in allTemplates)
|
||||||
|
{
|
||||||
|
if (!template.NextRun.HasValue)
|
||||||
|
{
|
||||||
|
templatesToReassign.Add(template);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} не имел даты — будет распределён.", template.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Распределяем шаблоны, которые нужно переназначить, учитывая нагрузку
|
||||||
|
|
||||||
|
var updatedTemplates = new List<TemplateNextRunResultDto>(templatesToKeep);
|
||||||
|
|
||||||
|
foreach (var template in templatesToReassign)
|
||||||
|
{
|
||||||
|
var nextRun = FindNextAvailableDateWithLoad(currentDailyLoad, workDays, referenceDate, expectedDailyLoad);
|
||||||
|
if (nextRun.HasValue)
|
||||||
|
{
|
||||||
|
var day = DateOnly.FromDateTime(nextRun.Value.Date);
|
||||||
|
currentDailyLoad[day]++;
|
||||||
|
|
||||||
|
updatedTemplates.Add(new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: template.Id,
|
||||||
|
NextRun: nextRun.Value,
|
||||||
|
NextRunOld: template.NextRun
|
||||||
|
));
|
||||||
|
|
||||||
|
logger.LogDebug("Шаблону {TemplateId} назначен NextRun: {NextRun}, NextRunOld: {NextRunOld}", template.Id, nextRun, template.NextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogWarning("Не удалось назначить дату для шаблона {TemplateId}", template.Id);
|
||||||
|
|
||||||
|
// Возвращаем с ошибкой
|
||||||
|
updatedTemplates.Add(new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: template.Id,
|
||||||
|
NextRun: CreateDateTimeOffset(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
|
||||||
|
NextRunOld: template.NextRun
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
updatedTemplates = updatedTemplates.OrderBy(t => t.NextRun).ToList();
|
||||||
|
|
||||||
|
logger.LogDebug("--- Результат распределения ---");
|
||||||
|
foreach (var item in updatedTemplates)
|
||||||
|
logger.LogDebug("Шаблон {templateId}, NextRun: {NextRun}, NextRunOld: {NextRunOld}", item.Id, item.NextRun, item.NextRunOld);
|
||||||
|
|
||||||
|
logger.LogInformation("Распределение завершено. Возвращаем {Count} записей. NextRun не изменился у {oldCount} шт. Обновлен NextRun у {changedCount} шт.", updatedTemplates.Count, updatedTemplates.Count(t => t.NextRun == t.NextRunOld), updatedTemplates.Count(t => t.NextRun != t.NextRunOld));
|
||||||
|
|
||||||
|
return updatedTemplates;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Заполнить TemplateNextRunResultDto, если нет рабочих дней со смещением
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="template"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private TemplateNextRunResultDto FillTemplateResultWithOffsetError(TemplateNextRunDto template, DateOnly startDate, DateTimeOffset referenceDate)
|
||||||
|
{
|
||||||
|
var result = new TemplateNextRunResultDto
|
||||||
|
(
|
||||||
|
Id: template.Id,
|
||||||
|
//NextRun = template.NextRun.HasValue
|
||||||
|
// // если есть nextRun, то прибавляем к нему offsetYearsIfErrorCalcNextRun
|
||||||
|
// ? CreateNextRun(template.NextRun.Value.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate)
|
||||||
|
// // если нет nextRun, берем сегодня и прибавляем offsetYearsIfErrorCalcNextRun
|
||||||
|
// : CreateNextRun(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
|
||||||
|
NextRun: CreateDateTimeOffset(startDate.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
|
||||||
|
NextRunOld: template.NextRun
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.LogDebug("Нет рабочих дней, сдвинул nextRun у шаблона {Id} на {offsetYearsIfErrorCalcNextRun} лет вперед, nextRun: {NextRun}, nextRunOld: {NextRunOld}", result.Id, offsetYearsIfErrorCalcNextRun, result.NextRun, result.NextRunOld);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает NextRun не учитывая нагрузку на день
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workDays"></param>
|
||||||
|
/// <param name="referenseDate"></param>
|
||||||
|
/// <param name="isSearchFirst">Искать с начала = true, false - искать с конца</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateTimeOffset FindNextAvailableDateIgnoreLoad(List<DateOnly> workDays, DateTimeOffset referenseDate, bool isSearchFirst)
|
||||||
|
{
|
||||||
|
// возможно это временная реализация метода, используется для GetValidNextRunForTemplateAsync
|
||||||
|
|
||||||
|
if (!isSearchFirst)
|
||||||
|
{
|
||||||
|
// берем первый рабочий день с конца и возвращаем его
|
||||||
|
var lastDate = CreateDateTimeOffset(workDays.Last(), referenseDate);
|
||||||
|
logger.LogDebug("Берем первый рабочий день с конца. Сгенерированный NextRun: {lastDate}", lastDate);
|
||||||
|
return lastDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// будем искать сначала
|
||||||
|
// берем первый день, и смотрим не прошло ли уже время
|
||||||
|
var firstDate = CreateDateTimeOffset(workDays.First(), referenseDate);
|
||||||
|
if (firstDate > DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Берем первый день с начала. Сгенерированный NextRun: {firstDate}", firstDate);
|
||||||
|
return firstDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// тогда берем второй день
|
||||||
|
if (workDays.Count > 1)
|
||||||
|
{
|
||||||
|
var secondDate = CreateDateTimeOffset(workDays.ElementAt(1), referenseDate);
|
||||||
|
logger.LogDebug("Берем второй день сначала, так как у первого дня уже время прошло. Сгенерированный NextRun: {secondDate}", secondDate);
|
||||||
|
return secondDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogWarning("Вернули nextRun: {firstDate} из первого дня распределения, хотя время уже просрочено. Всего рабочих дней: {workDays}", firstDate, workDays.Count);
|
||||||
|
// если вдруг список рабочих дней был не >1, то тогда берем первый и вренем его. Так никогда не должно быть
|
||||||
|
return firstDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Находит следующий день с доступной нагрузкой, учитывая expectedDailyLoad
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentDailyLoad"></param>
|
||||||
|
/// <param name="workDays"></param>
|
||||||
|
/// <param name="referenceDate"></param>
|
||||||
|
/// <param name="expectedDailyLoad"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateTimeOffset? FindNextAvailableDateWithLoad(Dictionary<DateOnly, int> currentDailyLoad, List<DateOnly> workDays, DateTimeOffset referenceDate, Dictionary<DateOnly, int> expectedDailyLoad)
|
||||||
|
{
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
var expected = expectedDailyLoad.GetValueOrDefault(day, 0);
|
||||||
|
var current = currentDailyLoad.GetValueOrDefault(day, 0);
|
||||||
|
|
||||||
|
if (current < expected)
|
||||||
|
{
|
||||||
|
return CreateDateTimeOffset(day, referenceDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если нет дня с доступной нагрузкой — ищем день с минимальной текущей нагрузкой, но не превышающий план
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
var expected = expectedDailyLoad.GetValueOrDefault(day, 0);
|
||||||
|
var current = currentDailyLoad.GetValueOrDefault(day, 0);
|
||||||
|
|
||||||
|
if (current < expected)
|
||||||
|
{
|
||||||
|
return CreateDateTimeOffset(day, referenceDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если все дни уже загружены по плану — ищем день с минимальной нагрузкой
|
||||||
|
var minLoad = currentDailyLoad.Values.Min();
|
||||||
|
var minDay = currentDailyLoad.First(x => x.Value == minLoad).Key;
|
||||||
|
|
||||||
|
return CreateDateTimeOffset(minDay, referenceDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить план: дни, на которые должны быть назначены шаблоны
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="totalWorks"></param>
|
||||||
|
/// <param name="workDays"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<DateOnly> GetPlan(int totalWorks, List<DateOnly> workDays)
|
||||||
|
{
|
||||||
|
var totalDays = workDays.Count();
|
||||||
|
|
||||||
|
var interval = GetInterval(totalDays, totalWorks);
|
||||||
|
|
||||||
|
var plan = new List<DateOnly>();
|
||||||
|
|
||||||
|
for (int i = 0; i < totalWorks; i++)
|
||||||
|
{
|
||||||
|
#region Вынести в метод
|
||||||
|
// Вычисляем индекс дня, на который нужно назначить i-ю работу
|
||||||
|
//var dayIndex = (int)Math.Round(i * interval);
|
||||||
|
var dayIndex = (int)Math.Floor(i * interval);
|
||||||
|
|
||||||
|
// Защита от возможного выхода за границы массива из-за округления
|
||||||
|
if (dayIndex >= totalDays)
|
||||||
|
dayIndex = totalDays - 1;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var date = workDays[dayIndex];
|
||||||
|
plan.Add(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("--- План распределения ---");
|
||||||
|
foreach (var item in plan.GroupBy(t => t))
|
||||||
|
logger.LogDebug("Дата: {date}, кол-во работ: {workCount}", item.Key, item.Count());
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Рассчитать интервал между шаблонами
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="totalDays">Кол-во рабочих дней</param>
|
||||||
|
/// <param name="totalWorks">Кол-во шаблонов</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private double GetInterval(int totalDays, int totalWorks)
|
||||||
|
{
|
||||||
|
var interval = (double)totalDays / totalWorks;
|
||||||
|
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создать DateTimeOffset (из DateTimeOffset + refDate)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">Откуда взять дату</param>
|
||||||
|
/// <param name="refDate">Откуда взять время</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateTimeOffset CreateDateTimeOffset(DateTimeOffset date, DateTimeOffset refDate)
|
||||||
|
{
|
||||||
|
return CreateDateTimeOffset(DateOnly.FromDateTime(date.Date), refDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создать DateTimeOffset (из DateOnly + refDate)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date"></param>
|
||||||
|
/// <param name="refDate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateTimeOffset CreateDateTimeOffset(DateOnly date, DateTimeOffset refDate)
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(date.Year, date.Month, date.Day, refDate.Hour, refDate.Minute, refDate.Second, TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить дату начала распределения относительно StartDate, ReferenceDate и текущей даты
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="referenceDate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateOnly GetStartPeriodDate(DateOnly startDate, DateTimeOffset referenceDate)
|
||||||
|
{
|
||||||
|
// дата начала не может быть раньше referenceDate
|
||||||
|
// и не может быть раньше чем сегодня
|
||||||
|
|
||||||
|
var dateStart = startDate;
|
||||||
|
|
||||||
|
// если dateStart < чем refDate, то берем refDate
|
||||||
|
if (dateStart < DateOnly.FromDateTime(referenceDate.Date))
|
||||||
|
dateStart = DateOnly.FromDateTime(referenceDate.Date);
|
||||||
|
|
||||||
|
// если dateStart < чем текущая дата, то берем текущую дату
|
||||||
|
if (dateStart < DateOnly.FromDateTime(DateTime.UtcNow.Date))
|
||||||
|
dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
|
||||||
|
|
||||||
|
// если текущее время > referenceDate, то прибавляем 1 день
|
||||||
|
var dateTimeStart = CreateDateTimeOffset(dateStart, referenceDate);
|
||||||
|
if (dateTimeStart < DateTimeOffset.UtcNow)
|
||||||
|
dateStart = dateStart.AddDays(1);
|
||||||
|
|
||||||
|
return dateStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить дату конца периода распределения
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="duration"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateOnly GetEndPeriodDate(DateOnly startDate, int duration)
|
||||||
|
{
|
||||||
|
// duration — количество дней, включая startDate
|
||||||
|
// поэтому вычитаем 1, чтобы получить сдвиг
|
||||||
|
|
||||||
|
return startDate.AddDays(duration - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить список выходных дней
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="endDate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
|
||||||
|
{
|
||||||
|
var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
|
||||||
|
|
||||||
|
logger.LogDebug("Найдено не рабочих дней: {Count}", weekends.Count);
|
||||||
|
foreach (var weekend in weekends)
|
||||||
|
logger.LogDebug("Не рабочий день: {weekend}", weekend);
|
||||||
|
|
||||||
|
return weekends.ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает список рабочих дней в диапазоне, исключая выходные и праздники.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start"></param>
|
||||||
|
/// <param name="end"></param>
|
||||||
|
/// <param name="excludeWeekends">Исключить выходные и праздники</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
|
||||||
|
{
|
||||||
|
// Получаем список выходных/праздничных дней
|
||||||
|
var weekends = new HashSet<DateOnly>();
|
||||||
|
if (excludeWeekends)
|
||||||
|
weekends = await GetWeekendsAsync(start, end);
|
||||||
|
|
||||||
|
var result = new List<DateOnly>();
|
||||||
|
var date = start;
|
||||||
|
|
||||||
|
while (date <= end)
|
||||||
|
{
|
||||||
|
var dayOfWeek = date.DayOfWeek;
|
||||||
|
if (!weekends.Contains(date))
|
||||||
|
{
|
||||||
|
result.Add(date);
|
||||||
|
}
|
||||||
|
date = date.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("Найдено рабочих дней: {Count}", result.Count);
|
||||||
|
foreach (var workDay in result)
|
||||||
|
logger.LogDebug("Рабочий день: {workDay}", workDay);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ using PARR.DAL.DomainServices.Implementations;
|
|||||||
using PARR.DAL.DomainServices.Interfaces;
|
using PARR.DAL.DomainServices.Interfaces;
|
||||||
using PARR.DAL.DomainServices.Shortcodes;
|
using PARR.DAL.DomainServices.Shortcodes;
|
||||||
using PARR.DAL.InfluxDbServices;
|
using PARR.DAL.InfluxDbServices;
|
||||||
|
using PARR.DAL.NextRunServices;
|
||||||
|
using PARR.DAL.NextRunServices.Subservices;
|
||||||
using PARR.DAL.Services.Implementation;
|
using PARR.DAL.Services.Implementation;
|
||||||
using PARR.DAL.Services.Implementations;
|
using PARR.DAL.Services.Implementations;
|
||||||
using PARR.DAL.Services.Implementations.Job;
|
using PARR.DAL.Services.Implementations.Job;
|
||||||
@@ -20,6 +22,7 @@ using PARR.DAL.Services.Interfaces.Schedule;
|
|||||||
using PARR.DAL.Services.Interfaces.Unit;
|
using PARR.DAL.Services.Interfaces.Unit;
|
||||||
using PARR.DAL.Settings;
|
using PARR.DAL.Settings;
|
||||||
using PARR.DAL.TransformServices;
|
using PARR.DAL.TransformServices;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace PARR.DAL
|
namespace PARR.DAL
|
||||||
{
|
{
|
||||||
@@ -135,6 +138,14 @@ namespace PARR.DAL
|
|||||||
services.AddTransient<IEsppScheduleTransformService, EsppScheduleTransformService>();
|
services.AddTransient<IEsppScheduleTransformService, EsppScheduleTransformService>();
|
||||||
services.AddTransient<INextRunModifierService, NextRunModifierService>();
|
services.AddTransient<INextRunModifierService, NextRunModifierService>();
|
||||||
|
|
||||||
|
#region NextRun Services
|
||||||
|
|
||||||
|
services.AddTransient<INextRunService, NextRunService>();
|
||||||
|
services.AddTransient<ITemplateDistributor, TemplateDistributor>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region DomainServces
|
#region DomainServces
|
||||||
services.AddTransient<IShortcodesService, ShortcodesService>();
|
services.AddTransient<IShortcodesService, ShortcodesService>();
|
||||||
services.AddTransient<IUnitFilterService, UnitFilterService>();
|
services.AddTransient<IUnitFilterService, UnitFilterService>();
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace PARR.DAL.Services.Implementations
|
|||||||
|
|
||||||
public IQueryable<DateOnly> GetWeekends(DateOnly start, DateOnly end)
|
public IQueryable<DateOnly> GetWeekends(DateOnly start, DateOnly end)
|
||||||
{
|
{
|
||||||
return EntitySet.Where(t => t.Date >= start && t.Date <= end).Select(t => t.Date);
|
return EntitySet.Where(t => t.Date >= start && t.Date <= end).AsNoTracking().Select(t => t.Date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ namespace PARR.TemplateTaskGenerator
|
|||||||
{
|
{
|
||||||
var mqRequest = new TemplateGeneratorMq
|
var mqRequest = new TemplateGeneratorMq
|
||||||
{
|
{
|
||||||
|
UnitsInTemplate=new List<Guid>(),
|
||||||
JobId = jobId,
|
JobId = jobId,
|
||||||
UnitId = unitId,
|
UnitId = unitId,
|
||||||
HistoryInitiator = new HistoryInitiator { InitiatorComment = "Запрос на создание шаблона", InitiatorParrComponentId = ParrComponentsEnum.TemplateTaskGenerator },
|
HistoryInitiator = new HistoryInitiator { InitiatorComment = "Запрос на создание шаблона", InitiatorParrComponentId = ParrComponentsEnum.TemplateTaskGenerator },
|
||||||
|
|||||||
124
PARR.Test/NextRun/NextRunTest.cs
Normal file
124
PARR.Test/NextRun/NextRunTest.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using PARR.DAL.NextRunServices.Models;
|
||||||
|
using PARR.DAL.NextRunServices.Subservices;
|
||||||
|
|
||||||
|
namespace PARR.Test.NextRun
|
||||||
|
{
|
||||||
|
internal class NextRunTest
|
||||||
|
{
|
||||||
|
private readonly ITemplateDistributor templateDistributor;
|
||||||
|
|
||||||
|
public NextRunTest(ITemplateDistributor templateDistributor)
|
||||||
|
{
|
||||||
|
this.templateDistributor = templateDistributor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int periodDays = 10;
|
||||||
|
DateTimeOffset referenceDate = new DateTimeOffset(2025, 1, 20, 5, 0, 0, TimeSpan.Zero);
|
||||||
|
|
||||||
|
public async Task Test()
|
||||||
|
{
|
||||||
|
await DistributeTemplatesAsync();
|
||||||
|
//await GetValidNextRunForTemplateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить актуальный NextRun
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task GetValidNextRunForTemplateAsync()
|
||||||
|
{
|
||||||
|
// исключать выходные и праздники
|
||||||
|
var excludeWeekends = true;
|
||||||
|
bool isNew = false;
|
||||||
|
|
||||||
|
var targetTemplate = new TemplateNextRunDto
|
||||||
|
(
|
||||||
|
Id: Guid.Parse("11F047E6-3631-4676-8959-FC75FF6BC963"),
|
||||||
|
//NextRun = new DateTimeOffset(2026, 1, 20, 4, 0, 0, TimeSpan.Zero)
|
||||||
|
NextRun: null
|
||||||
|
);
|
||||||
|
|
||||||
|
var template = await templateDistributor.GetValidNextRunForTemplateAsync(GetDateStart(), periodDays, referenceDate, targetTemplate, new List<TemplateNextRunDto>(), excludeWeekends, isNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// распределить шаблоны
|
||||||
|
/// </summary>
|
||||||
|
private async Task DistributeTemplatesAsync()
|
||||||
|
{
|
||||||
|
// исключать выходные и праздники
|
||||||
|
var excludeWeekends = true;
|
||||||
|
|
||||||
|
var templates = await templateDistributor.DistributeTemplatesAsync(GetDateStart(), periodDays, referenceDate, GetTemplates(), excludeWeekends);
|
||||||
|
|
||||||
|
//await GetValidNextRunForTemplateAsync(templates);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// получить nextRun
|
||||||
|
/// </summary>
|
||||||
|
private async Task GetValidNextRunForTemplateAsync(List<TemplateNextRunDto>? templates = null)
|
||||||
|
{
|
||||||
|
//var templateList = templates ?? GetTemplates();
|
||||||
|
|
||||||
|
//var targetTemplate = new TemplateNextRunDto { Id= Guid.Parse("4C2F61E9-7B47-4049-A881-9200D71303F7"), NextRun= new DateTimeOffset(2025, 12, 1, 3, 0, 0, TimeSpan.Zero) };
|
||||||
|
|
||||||
|
//var distribTemplates = await templateDistributor.GetValidNextRunForTemplateAsync(GetDateStart(), GetDateEnd(), referenceDate, targetTemplate, templateList);
|
||||||
|
|
||||||
|
//// еще раз
|
||||||
|
//templateList.Add(targetTemplate);
|
||||||
|
//var targetTemplate2 = new TemplateNextRunDto { Id = Guid.Parse("4C2F61E9-7B47-4049-A881-9200D71303F7"), NextRun = new DateTimeOffset(2025, 12, 1, 3, 0, 0, TimeSpan.Zero) };
|
||||||
|
//var distribTemplates2 = await templateDistributor.GetValidNextRunForTemplateAsync(GetDateStart(), GetDateEnd(), referenceDate, targetTemplate2, templateList);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private DateOnly GetDateStart()
|
||||||
|
{
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.UtcNow.Date);
|
||||||
|
|
||||||
|
var refDate = DateOnly.FromDateTime(referenceDate.Date);
|
||||||
|
|
||||||
|
return today < refDate ? refDate : today;
|
||||||
|
}
|
||||||
|
|
||||||
|
//private DateOnly GetDateEnd()
|
||||||
|
//{
|
||||||
|
// return GetDateStart().AddDays(periodDays);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
private List<TemplateNextRunDto> GetTemplates()
|
||||||
|
{
|
||||||
|
var templates = new List<TemplateNextRunDto>();
|
||||||
|
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("55F047E6-3631-4676-8959-FC75FF6BC963"), NextRun: new DateTimeOffset(2027, 1, 15, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("DD15E9F6-AAD4-4FE6-9441-7E7FF67781A3"), NextRun: new DateTimeOffset(2025, 12, 24, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("78EE4A94-DBEE-41D1-B51A-CE3BD5AEE4D4"), NextRun: new DateTimeOffset(2025, 12, 29, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("B397F1FE-DF2B-4B50-B8D6-2DABECD3D5A1"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("619D9621-2B45-4108-AF54-CF96862A8E9B"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("D74D831C-D435-46BF-9B5F-A24884AAA7DC"), NextRun: new DateTimeOffset(2025, 12, 25, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("DE7241F1-5C62-4164-A3DC-99B946350E10"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("94A7375E-D1BD-49B7-9186-D26E85F1CF74"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("B3681F5F-10A5-425C-BBEA-EA2B85FE0967"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("3259B408-48A3-4995-AF9A-097477ECAB81"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("25F53A28-D657-4B9A-82F3-CE9AF4D53385"), NextRun: new DateTimeOffset(2025, 12, 30, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("F75F6B1A-875F-4CB2-98C8-6721FD06C18C"), NextRun = new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero) });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("2C06C63D-BE92-4312-98FB-0547E74B8A33"), NextRun = new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero) });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("B22D314E-9F86-49BB-826F-20919EC55A7F"), NextRun = new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero) });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("061CB58A-B350-456F-8E02-C937D488D6FC"), NextRun = null });
|
||||||
|
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("C51E2C3F-655D-4395-BF9D-D6D64DA314D9"), NextRun = null });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("C9A77DEF-0FB8-4903-87B7-16B8C3EC97A4"), NextRun = null });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("EC703575-B38F-4A7F-9010-BBB3F92EDEA9"), NextRun = null });
|
||||||
|
//templates.Add(new TemplateNextRunDto { Id = Guid.Parse("188181C3-AACD-4205-BCB6-7414966CB33B"), NextRun = null });
|
||||||
|
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using PARR.BLL;
|
|
||||||
using PARR.EsppApi;
|
|
||||||
using PARR.DAL;
|
|
||||||
using PARR.Test;
|
|
||||||
using Serilog;
|
|
||||||
using PARR.TemplateDistributor;
|
|
||||||
using Elastic.CommonSchema.Serilog;
|
using Elastic.CommonSchema.Serilog;
|
||||||
|
using PARR.BLL;
|
||||||
|
using PARR.DAL;
|
||||||
|
using PARR.EsppApi;
|
||||||
|
using PARR.TemplateDistributor;
|
||||||
|
using PARR.Test;
|
||||||
|
using PARR.Test.NextRun;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
/*IHost host = Host.CreateDefaultBuilder(args)
|
/*IHost host = Host.CreateDefaultBuilder(args)
|
||||||
.ConfigureServices((hostContext, services) =>
|
.ConfigureServices((hostContext, services) =>
|
||||||
@@ -48,6 +49,8 @@ builder.Services.InstallBllServices(builder.Configuration);
|
|||||||
builder.Configuration.AddDalConfigurations(builder.Services);
|
builder.Configuration.AddDalConfigurations(builder.Services);
|
||||||
builder.Services.AddDallSettings(builder.Configuration);
|
builder.Services.AddDallSettings(builder.Configuration);
|
||||||
|
|
||||||
|
builder.Services.AddTransient<NextRunTest>();
|
||||||
|
|
||||||
builder.Services.AddHostedService<Worker>();
|
builder.Services.AddHostedService<Worker>();
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using PARR.DAL.Services.Interfaces;
|
|||||||
using PARR.EsppApi;
|
using PARR.EsppApi;
|
||||||
using PARR.EsppApi.Constants;
|
using PARR.EsppApi.Constants;
|
||||||
using PARR.EsppApi.Models.Query;
|
using PARR.EsppApi.Models.Query;
|
||||||
|
using PARR.Test.NextRun;
|
||||||
|
|
||||||
namespace PARR.Test
|
namespace PARR.Test
|
||||||
{
|
{
|
||||||
@@ -34,6 +35,19 @@ namespace PARR.Test
|
|||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#region NextRunTest
|
||||||
|
|
||||||
|
using (var scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var service = scope.ServiceProvider.GetService<NextRunTest>();
|
||||||
|
|
||||||
|
await service!.Test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region test
|
#region test
|
||||||
//using (var scope = serviceProvider.CreateScope())
|
//using (var scope = serviceProvider.CreateScope())
|
||||||
//{
|
//{
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="docker-compose.template-matcher.yml" />
|
<None Include="docker-compose.template-matcher.yml" />
|
||||||
<None Include="docker-compose.template-task-generator.yml" />
|
|
||||||
<None Include="docker-compose.template-generator.yml" />
|
<None Include="docker-compose.template-generator.yml" />
|
||||||
<None Include="docker-compose.aihit-loader.yml" />
|
<None Include="docker-compose.aihit-loader.yml" />
|
||||||
<None Include="docker-compose.aihit-relationships-syncer.yml" />
|
<None Include="docker-compose.aihit-relationships-syncer.yml" />
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
version: '3.4'
|
|
||||||
|
|
||||||
#PARR TEMPLATE TASK GENERATOR
|
|
||||||
services:
|
|
||||||
parr-template-task-generator:
|
|
||||||
image: harbor.dvgd.rzd/parr/parr-template-task-generator:${tag-latest}
|
|
||||||
environment:
|
|
||||||
- ASPNETCORE_ENVIRONMENT=Production
|
|
||||||
- TZ=Europe/Moscow
|
|
||||||
logging:
|
|
||||||
driver: fluentd
|
|
||||||
options:
|
|
||||||
fluentd-address: dvgd-efk-01.dvgd.oao.rzd:24224
|
|
||||||
fluentd-retry-wait: '10s'
|
|
||||||
fluentd-max-retries: '30'
|
|
||||||
fluentd-async: 'true'
|
|
||||||
fluentd-buffer-limit: '52428800'
|
|
||||||
tag: parr.template-task-generator.serilog
|
|
||||||
deploy:
|
|
||||||
replicas: 1
|
|
||||||
networks:
|
|
||||||
parr-network:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
parr-network:
|
|
||||||
driver: overlay
|
|
||||||
external: true
|
|
||||||
Reference in New Issue
Block a user