diff --git a/PARR.DAL/NextRunServices/INextRunService.cs b/PARR.DAL/NextRunServices/INextRunService.cs
index d62fd757..3800f9f9 100644
--- a/PARR.DAL/NextRunServices/INextRunService.cs
+++ b/PARR.DAL/NextRunServices/INextRunService.cs
@@ -22,8 +22,9 @@
/// Получить NextRun по id шаблона
///
///
+ /// Новый шаблон или шаблон переведенный из unused
///
- Task GetNextRunForTemplate(Guid templateId);
+ Task GetNextRunForTemplate(Guid templateId, bool isNew);
}
}
diff --git a/PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs b/PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs
index 839fe5c6..e2a498f6 100644
--- a/PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs
+++ b/PARR.DAL/NextRunServices/Models/TemplateNextRunDto.cs
@@ -3,10 +3,12 @@
///
/// Модель шаблона, для расчета NextRun
///
- public class TemplateNextRunDto
- {
- public Guid Id { get; set; }
+ public record TemplateNextRunDto(Guid Id, DateTimeOffset? NextRun);
- public DateTimeOffset? NextRun { get; set; }
- }
+ //public class TemplateNextRunDto
+ //{
+ // public Guid Id { get; set; }
+
+ // public DateTimeOffset? NextRun { get; set; }
+ //}
}
diff --git a/PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs b/PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs
index 3477327c..4852c7b4 100644
--- a/PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs
+++ b/PARR.DAL/NextRunServices/Models/TemplateNextRunResultDto.cs
@@ -3,12 +3,14 @@
///
/// Модель, результат распределения шаблона
///
- public class TemplateNextRunResultDto
- {
- public Guid Id { get; set; }
+ public record TemplateNextRunResultDto(Guid Id, DateTimeOffset NextRun, DateTimeOffset? NextRunOld);
- public DateTimeOffset NextRun { get; set; }
+ //public class TemplateNextRunResultDto
+ //{
+ // public Guid Id { get; set; }
- public DateTimeOffset? NextRunOld { get; set; }
- }
+ // public DateTimeOffset NextRun { get; set; }
+
+ // public DateTimeOffset? NextRunOld { get; set; }
+ //}
}
diff --git a/PARR.DAL/NextRunServices/NextRunService.cs b/PARR.DAL/NextRunServices/NextRunService.cs
index 796e6599..ff5348b7 100644
--- a/PARR.DAL/NextRunServices/NextRunService.cs
+++ b/PARR.DAL/NextRunServices/NextRunService.cs
@@ -1,6 +1,7 @@
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;
@@ -13,25 +14,29 @@ namespace PARR.DAL.NextRunServices
private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
+ private readonly ITemplateDistributor templateDistributor;
public NextRunService(
ILogger logger,
ITemplateService templateService,
IJobGroupService jobGroupService,
- IEsppScheduleTransformService esppScheduleTransformService
+ IEsppScheduleTransformService esppScheduleTransformService,
+ ITemplateDistributor templateDistributor
)
{
this.logger = logger;
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.esppScheduleTransformService = esppScheduleTransformService;
+ this.templateDistributor = templateDistributor;
}
- public Task> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId)
+ public async Task> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId)
{
//TODO:
throw new NotImplementedException();
+ //var ditributedTemplateList = await templateDistributor.DistributeTemplatesAsync();
}
@@ -50,7 +55,7 @@ namespace PARR.DAL.NextRunServices
}
- public async Task GetNextRunForTemplate(Guid templateId)
+ public async Task GetNextRunForTemplate(Guid templateId, bool isNew)
{
var template = await templateService.Get()
.Include(t => t.Job).ThenInclude(t => t.Group)
@@ -67,7 +72,8 @@ namespace PARR.DAL.NextRunServices
{
// включено автораспределение
//TODO: !!!!!!!!!!!!!!!! добавить метод рассчета с учетом распределения
- throw new NotImplementedException("добавить метод рассчета с учетом распределения");
+ throw new NotImplementedException();
+ //return await templateDistributor.GetValidNextRunForTemplateAsync();
}
else
{
diff --git a/PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs b/PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs
index 0936d6a9..8ef6474a 100644
--- a/PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs
+++ b/PARR.DAL/NextRunServices/Subservices/ITemplateDistributor.cs
@@ -17,10 +17,21 @@ namespace PARR.DAL.NextRunServices.Subservices
///
Task> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List allTemplates, bool excludeWeekends);
+
+
///
/// Возвращает актуальную дату выполнения для одного шаблона, учитывая план нагрузки.
/// Возвращает обновлённый шаблон с новой датой выполнения.
///
- Task GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List allTemplates, bool excludeWeekends);
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Новый шаблон
+ ///
+ Task GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List allTemplates, bool excludeWeekends, bool isNew);
}
}
+
diff --git a/PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs b/PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs
index bbcf2c4b..be239755 100644
--- a/PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs
+++ b/PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs
@@ -25,60 +25,100 @@ namespace PARR.DAL.NextRunServices.Subservices
}
- public Task GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List allTemplates, bool excludeWeekends)
+ public async Task GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List allTemplates, bool excludeWeekends, bool isNew)
{
- //TODO:
- throw new NotImplementedException();
+ //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> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List allTemplates, bool excludeWeekends)
{
var _startDate = GetStartPeriodDate(startDate, referenceDate);
var _endDate = GetEndPeriodDate(_startDate, duration);
- logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDate: {referenceDate}", allTemplates.Count, duration, _startDate, _endDate, referenceDate);
-
- // Получаем список выходных/праздничных дней
- var weekends = new HashSet();
- if (excludeWeekends)
- {
- weekends = await GetWeekendsAsync(_startDate, _endDate);
- logger.LogDebug("Найдено не рабочих дней: {Count}", weekends.Count);
-
- foreach (var weekend in weekends)
- logger.LogDebug("Не рабочий день: {weekend}", weekend);
- }
+ logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDate: {referenceDate}. Исключить выходные и праздники: {excludeWeekends}.", allTemplates.Count, duration, _startDate, _endDate, referenceDate, excludeWeekends);
// Получаем список рабочих дней (исключая выходные и праздники)
- var workDays = GetWorkDays(_startDate, _endDate, weekends);
-
- logger.LogDebug("Найдено рабочих дней: {Count}", workDays.Count);
-
- foreach (var workDay in workDays)
- logger.LogDebug("Рабочий день: {workDay}", workDay);
+ 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 => new TemplateNextRunResultDto
- {
- Id = t.Id,
- NextRun = t.NextRun.HasValue
- // если есть nextRun, то прибавляем к нему offsetYearsIfErrorCalcNextRun
- ? CreateNextRun(t.NextRun.Value.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate)
- // если нет nextRun, берем сегодня и прибавляем offsetYearsIfErrorCalcNextRun
- : CreateNextRun(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
- NextRunOld = t.NextRun
- }).OrderBy(t => t.NextRun).ToList();
-
- foreach (var item in result)
- logger.LogDebug("Нет рабочих дней, сдвинул nextRun у шаблона {Id}, nextRun: {NextRun}, nextRunOld: {NextRunOld}", item.Id, item.NextRun, item.NextRunOld);
+ 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));
@@ -86,13 +126,12 @@ namespace PARR.DAL.NextRunServices.Subservices
}
var totalWorks = allTemplates.Count;
- var totalDays = workDays.Count;
// интервал между шаблонами
- var interval = GetInterval(totalDays, totalWorks);
+ var interval = GetInterval(workDays.Count, totalWorks);
// строим план, дни на которые должны быть назанчены шаблоны
- var plan = GetPlan(totalWorks, totalDays, workDays);
+ var plan = GetPlan(totalWorks, workDays);
// Подсчитываем, сколько работ должно быть на каждый день
@@ -168,11 +207,11 @@ namespace PARR.DAL.NextRunServices.Subservices
foreach (var template in templates)
{
templatesToKeep.Add(new TemplateNextRunResultDto
- {
- Id = template.Id,
- NextRun = template.NextRun.Value,
- NextRunOld = template.NextRun
- });
+ (
+ Id: template.Id,
+ NextRun: template.NextRun.Value,
+ NextRunOld: template.NextRun
+ ));
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
}
}
@@ -183,11 +222,11 @@ namespace PARR.DAL.NextRunServices.Subservices
{
var template = templates[i];
templatesToKeep.Add(new TemplateNextRunResultDto
- {
- Id = template.Id,
- NextRun = template.NextRun.Value,
- NextRunOld = template.NextRun
- });
+ (
+ Id: template.Id,
+ NextRun: template.NextRun.Value,
+ NextRunOld: template.NextRun
+ ));
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
}
@@ -228,11 +267,11 @@ namespace PARR.DAL.NextRunServices.Subservices
currentDailyLoad[day]++;
updatedTemplates.Add(new TemplateNextRunResultDto
- {
- Id = template.Id,
- NextRun = nextRun.Value,
- NextRunOld = template.NextRun
- });
+ (
+ Id: template.Id,
+ NextRun: nextRun.Value,
+ NextRunOld: template.NextRun
+ ));
logger.LogDebug("Шаблону {TemplateId} назначен NextRun: {NextRun}, NextRunOld: {NextRunOld}", template.Id, nextRun, template.NextRun);
}
@@ -242,11 +281,11 @@ namespace PARR.DAL.NextRunServices.Subservices
// Возвращаем с ошибкой
updatedTemplates.Add(new TemplateNextRunResultDto
- {
- Id = template.Id,
- NextRun = CreateNextRun(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
- NextRunOld = template.NextRun
- });
+ (
+ Id: template.Id,
+ NextRun: CreateDateTimeOffset(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
+ NextRunOld: template.NextRun
+ ));
}
}
@@ -255,7 +294,7 @@ namespace PARR.DAL.NextRunServices.Subservices
updatedTemplates = updatedTemplates.OrderBy(t => t.NextRun).ToList();
- logger.LogDebug("--- Результат распределения: ---");
+ logger.LogDebug("--- Результат распределения ---");
foreach (var item in updatedTemplates)
logger.LogDebug("Шаблон {templateId}, NextRun: {NextRun}, NextRunOld: {NextRunOld}", item.Id, item.NextRun, item.NextRunOld);
@@ -265,6 +304,77 @@ namespace PARR.DAL.NextRunServices.Subservices
}
+ ///
+ /// Заполнить TemplateNextRunResultDto, если нет рабочих дней со смещением
+ ///
+ ///
+ ///
+ 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;
+ }
+
+
+
+ ///
+ /// Получает NextRun не учитывая нагрузку на день
+ ///
+ ///
+ ///
+ /// Искать с начала = true, false - искать с конца
+ ///
+ private DateTimeOffset FindNextAvailableDateIgnoreLoad(List 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;
+ }
+
+
+
+
///
/// Находит следующий день с доступной нагрузкой, учитывая expectedDailyLoad
///
@@ -282,7 +392,7 @@ namespace PARR.DAL.NextRunServices.Subservices
if (current < expected)
{
- return CreateNextRun(day, referenceDate);
+ return CreateDateTimeOffset(day, referenceDate);
}
}
@@ -294,7 +404,7 @@ namespace PARR.DAL.NextRunServices.Subservices
if (current < expected)
{
- return CreateNextRun(day, referenceDate);
+ return CreateDateTimeOffset(day, referenceDate);
}
}
@@ -302,7 +412,7 @@ namespace PARR.DAL.NextRunServices.Subservices
var minLoad = currentDailyLoad.Values.Min();
var minDay = currentDailyLoad.First(x => x.Value == minLoad).Key;
- return CreateNextRun(minDay, referenceDate);
+ return CreateDateTimeOffset(minDay, referenceDate);
}
@@ -311,11 +421,12 @@ namespace PARR.DAL.NextRunServices.Subservices
/// Получить план: дни, на которые должны быть назначены шаблоны
///
///
- ///
///
///
- private List GetPlan(int totalWorks, int totalDays, List workDays)
+ private List GetPlan(int totalWorks, List workDays)
{
+ var totalDays = workDays.Count();
+
var interval = GetInterval(totalDays, totalWorks);
var plan = new List();
@@ -336,8 +447,11 @@ namespace PARR.DAL.NextRunServices.Subservices
plan.Add(date);
}
+ logger.LogDebug("--- План распределения ---");
+ foreach (var item in plan.GroupBy(t => t))
+ logger.LogDebug("Дата: {date}, кол-во работ: {workCount}", item.Key, item.Count());
+
return plan;
- // return new HashSet(plan);
}
@@ -356,23 +470,23 @@ namespace PARR.DAL.NextRunServices.Subservices
///
- /// Создать NextRun (из DateTimeOffset + refDate)
+ /// Создать DateTimeOffset (из DateTimeOffset + refDate)
///
/// Откуда взять дату
/// Откуда взять время
///
- private DateTimeOffset CreateNextRun(DateTimeOffset date, DateTimeOffset refDate)
+ private DateTimeOffset CreateDateTimeOffset(DateTimeOffset date, DateTimeOffset refDate)
{
- return CreateNextRun(DateOnly.FromDateTime(date.Date), refDate);
+ return CreateDateTimeOffset(DateOnly.FromDateTime(date.Date), refDate);
}
///
- /// Создать NextRun (из DateOnly + refDate)
+ /// Создать DateTimeOffset (из DateOnly + refDate)
///
///
///
///
- private DateTimeOffset CreateNextRun(DateOnly date, DateTimeOffset refDate)
+ private DateTimeOffset CreateDateTimeOffset(DateOnly date, DateTimeOffset refDate)
{
return new DateTimeOffset(date.Year, date.Month, date.Day, refDate.Hour, refDate.Minute, refDate.Second, TimeSpan.Zero);
}
@@ -395,10 +509,15 @@ namespace PARR.DAL.NextRunServices.Subservices
if (dateStart < DateOnly.FromDateTime(referenceDate.Date))
dateStart = DateOnly.FromDateTime(referenceDate.Date);
- // если dateStart < чем текущая дата, то берем ьекущую дату
+ // если 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;
}
@@ -428,6 +547,10 @@ namespace PARR.DAL.NextRunServices.Subservices
{
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();
}
@@ -437,10 +560,15 @@ namespace PARR.DAL.NextRunServices.Subservices
///
///
///
- ///
+ /// Исключить выходные и праздники
///
- private List GetWorkDays(DateOnly start, DateOnly end, HashSet weekends)
+ private async Task> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
{
+ // Получаем список выходных/праздничных дней
+ var weekends = new HashSet();
+ if (excludeWeekends)
+ weekends = await GetWeekendsAsync(start, end);
+
var result = new List();
var date = start;
@@ -454,6 +582,10 @@ namespace PARR.DAL.NextRunServices.Subservices
date = date.AddDays(1);
}
+ logger.LogDebug("Найдено рабочих дней: {Count}", result.Count);
+ foreach (var workDay in result)
+ logger.LogDebug("Рабочий день: {workDay}", workDay);
+
return result;
}
diff --git a/PARR.DAL/NextRunServices/Subservices/TemplateDistributor_old.cs b/PARR.DAL/NextRunServices/Subservices/TemplateDistributor_old.cs
deleted file mode 100644
index 93c7ba2f..00000000
--- a/PARR.DAL/NextRunServices/Subservices/TemplateDistributor_old.cs
+++ /dev/null
@@ -1,345 +0,0 @@
-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_old //: ITemplateDistributor
- {
- private readonly ILogger logger;
- private readonly IWeekendDayService weekendDayService;
-
- public TemplateDistributor_old(
- ILogger logger,
- IWeekendDayService weekendDayService
- )
- {
- this.logger = logger;
- this.weekendDayService = weekendDayService;
- }
-
- ///
- /// Распределяет все шаблоны на указанный период, учитывая план нагрузки.
- /// Возвращает список всех шаблонов с обновлёнными датами выполнения.
- ///
- public async Task> DistributeTemplatesAsync(
- DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, List allTemplates)
- {
- logger.LogInformation("Начинаем распределение {Count} шаблонов с {StartDate} по {EndDate}", allTemplates.Count, startDate, endDate);
-
- // Получаем список выходных/праздничных дней
- var weekends = await GetWeekendsAsync(startDate, endDate);
-
- // Получаем список рабочих дней (исключая выходные и праздники)
- var workDays = GetWorkDays(startDate, endDate, weekends);
-
- logger.LogDebug("Найдено {Count} рабочих дней", workDays.Count);
-
- if (workDays.Count == 0)
- {
- logger.LogWarning("Нет доступных рабочих дней в диапазоне {StartDate} - {EndDate}", startDate, endDate);
- return allTemplates;
- }
-
- var totalWorks = allTemplates.Count;
- var totalDays = workDays.Count;
-
- //-------------
-
- // Вычисляем интервал между работами: сколько "дней" приходится на одну работу
- var interval = (double)totalDays / totalWorks;
-
- // Создаём новый список шаблонов с обновлёнными датами
- var updatedTemplates = new List();
-
- for (int i = 0; i < totalWorks; i++)
- {
- // Вычисляем индекс дня, на который нужно назначить i-ю работу
- var dayIndex = (int)Math.Round(i * interval);
-
- // Защита от возможного выхода за границы массива из-за округления
- if (dayIndex >= totalDays) dayIndex = totalDays - 1;
-
- var date = workDays[dayIndex];
-
- // Формируем дату выполнения: дата + время из referenceDate
- var nextRun = new DateTimeOffset(date.Year, date.Month, date.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
-
- //------------------
- // Проверяем, не раньше ли дата, чем referenceDate (не включая саму referenceDate)
- if (nextRun < referenceDate)
- {
- logger.LogWarning("Шаблон {TemplateId} имеет дату {NextRun}, которая раньше referenceDate {ReferenceDate}. Переносим на ближайший рабочий день после referenceDate (включая её).", allTemplates[i].Id, nextRun, referenceDate);
-
- // Ищем ближайший день после referenceDate (включая его)
- var newDate = FindNextAvailableDate(workDays, DateOnly.FromDateTime(referenceDate.DateTime), date);
- if (newDate.HasValue)
- {
- nextRun = new DateTimeOffset(newDate.Value.Year, newDate.Value.Month, newDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- }
- else
- {
- // Если не нашли — используем первый рабочий день
- logger.LogWarning("Не удалось найти дату после referenceDate для шаблона {TemplateId}. Используем первый рабочий день.", allTemplates[i].Id);
- var firstDay = workDays[0];
- nextRun = new DateTimeOffset(firstDay.Year, firstDay.Month, firstDay.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- }
- }
- //------------------
-
- var updatedTemplate = new TemplateNextRunDto
- {
- Id = allTemplates[i].Id,
- NextRun = nextRun
- };
-
- updatedTemplates.Add(updatedTemplate);
-
- logger.LogDebug("Шаблон {TemplateId} назначен на {NextRun}", updatedTemplate.Id, updatedTemplate.NextRun);
- }
-
- logger.LogInformation("Распределение завершено. Возвращаем {Count} записей.", updatedTemplates.Count);
-
- return updatedTemplates;
- }
-
- ///
- /// Возвращает актуальную дату выполнения для одного шаблона, учитывая план нагрузки.
- /// Возвращает обновлённый шаблон с новой датой выполнения.
- ///
- public async Task GetValidNextRunForTemplateAsync(
- DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List allTemplates)
- {
- logger.LogDebug("Запрос актуальной даты для шаблона {TemplateId}", targetTemplate.Id);
-
- // Получаем список выходных/праздничных дней
- var weekends = await GetWeekendsAsync(startDate, endDate);
-
- // Получаем список рабочих дней
- var workDays = GetWorkDays(startDate, endDate, weekends);
-
- logger.LogDebug("Найдено {Count} рабочих дней", workDays.Count);
-
- if (workDays.Count == 0)
- {
- logger.LogWarning("Нет доступных рабочих дней в диапазоне {StartDate} - {EndDate}", startDate, endDate);
-
- // Возвращаем дату, равную referenceDate, если не нашли рабочих дней
- logger.LogWarning("Используем referenceDate как дату выполнения для шаблона {TemplateId}", targetTemplate.Id);
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = referenceDate };
- }
-
- var now = DateTimeOffset.UtcNow;
-
- // Проверяем, устарела ли дата
- if (targetTemplate.NextRun.HasValue && targetTemplate.NextRun.Value < now)
- {
- logger.LogDebug("Дата шаблона {TemplateId} ({Date}) устарела", targetTemplate.Id, targetTemplate.NextRun);
-
- // Строим план на основе оставшихся шаблонов (без устаревшего)
- var remainingTemplates = allTemplates.Where(t => t.Id != targetTemplate.Id && t.NextRun.HasValue).ToList();
-
- // Вычисляем план для оставшихся
- var plan = await BuildPlanAsync(startDate, endDate, referenceDate, remainingTemplates, weekends, workDays);
-
- // Находим следующую дату в плане, не занятую
- var newDate = FindNextAvailableDateNotInPlan(workDays, DateOnly.FromDateTime(now.DateTime), plan);
- if (newDate.HasValue)
- {
- var nextRun = new DateTimeOffset(newDate.Value.Year, newDate.Value.Month, newDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- logger.LogDebug("Шаблон {TemplateId} перенесен на {NewDate}", targetTemplate.Id, nextRun);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- else
- {
- logger.LogWarning("Не удалось найти новую дату для устаревшего шаблона {TemplateId} по плану. Используем ближайшую дату.", targetTemplate.Id);
-
- // Используем ближайшую дату после now, не в плане
- var fallbackDate = FindNextAvailableDate(workDays, DateOnly.FromDateTime(now.DateTime), DateOnly.FromDateTime(now.DateTime));
- if (fallbackDate.HasValue)
- {
- var nextRun = new DateTimeOffset(fallbackDate.Value.Year, fallbackDate.Value.Month, fallbackDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- logger.LogDebug("Шаблон {TemplateId} перенесен на резервную дату {NewDate}", targetTemplate.Id, nextRun);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- else
- {
- logger.LogWarning("Не удалось найти резервную дату для шаблона {TemplateId}. Используем первую рабочую дату.", targetTemplate.Id);
-
- var firstDay = workDays[0];
- var nextRun = new DateTimeOffset(firstDay.Year, firstDay.Month, firstDay.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- }
- }
-
- // Если дата не устарела, но не соответствует плану — переносим
- var currentPlan = await BuildPlanAsync(startDate, endDate, referenceDate, allTemplates, weekends, workDays);
- var expectedDate = currentPlan.FirstOrDefault(d => d == DateOnly.FromDateTime(targetTemplate.NextRun.Value.DateTime));
- if (expectedDate == default)
- {
- logger.LogDebug("Дата шаблона {TemplateId} ({Date}) не соответствует плану. Переносим на ближайшую дату в плане.", targetTemplate.Id, targetTemplate.NextRun);
-
- var newDate = FindNextAvailableDateNotInPlan(workDays, DateOnly.FromDateTime(targetTemplate.NextRun.Value.DateTime), currentPlan);
- if (newDate.HasValue)
- {
- var nextRun = new DateTimeOffset(newDate.Value.Year, newDate.Value.Month, newDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- logger.LogDebug("Шаблон {TemplateId} перенесен на {NewDate}", targetTemplate.Id, nextRun);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- else
- {
- logger.LogWarning("Не удалось найти новую дату для шаблона {TemplateId} по плану. Используем ближайшую дату.", targetTemplate.Id);
-
- // Используем ближайшую дату, не в плане
- var fallbackDate = FindNextAvailableDate(workDays, DateOnly.FromDateTime(targetTemplate.NextRun.Value.DateTime), DateOnly.FromDateTime(targetTemplate.NextRun.Value.DateTime));
- if (fallbackDate.HasValue)
- {
- var nextRun = new DateTimeOffset(fallbackDate.Value.Year, fallbackDate.Value.Month, fallbackDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- logger.LogDebug("Шаблон {TemplateId} перенесен на резервную дату {NewDate}", targetTemplate.Id, nextRun);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- else
- {
- logger.LogWarning("Не удалось найти резервную дату для шаблона {TemplateId}. Используем первую рабочую дату.", targetTemplate.Id);
-
- var firstDay = workDays[0];
- var nextRun = new DateTimeOffset(firstDay.Year, firstDay.Month, firstDay.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- }
- }
-
- logger.LogDebug("Шаблон {TemplateId} имеет актуальную дату: {NextRun}", targetTemplate.Id, targetTemplate.NextRun);
-
- // Проверяем, не раньше ли дата, чем referenceDate (не включая саму referenceDate)
- if (targetTemplate.NextRun < referenceDate)
- {
- logger.LogWarning("Шаблон {TemplateId} имеет дату {NextRun}, которая раньше referenceDate {ReferenceDate}. Переносим на ближайшую дату.", targetTemplate.Id, targetTemplate.NextRun, referenceDate);
-
- var newDate = FindNextAvailableDate(workDays, DateOnly.FromDateTime(referenceDate.DateTime), DateOnly.FromDateTime(targetTemplate.NextRun.Value.DateTime));
- if (newDate.HasValue)
- {
- var nextRun = new DateTimeOffset(newDate.Value.Year, newDate.Value.Month, newDate.Value.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- logger.LogDebug("Шаблон {TemplateId} перенесен на {NewDate}", targetTemplate.Id, nextRun);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- else
- {
- logger.LogWarning("Не удалось найти дату после referenceDate для шаблона {TemplateId}. Используем первую рабочую дату.", targetTemplate.Id);
-
- var firstDay = workDays[0];
- var nextRun = new DateTimeOffset(firstDay.Year, firstDay.Month, firstDay.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
-
- return new TemplateNextRunDto { Id = targetTemplate.Id, NextRun = nextRun };
- }
- }
-
- return targetTemplate;
- }
-
- ///
- /// Строит план работ на основе списка шаблонов
- ///
- private async Task> BuildPlanAsync(
- DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, List templates, HashSet weekends, List workDays)
- {
- var totalWorks = templates.Count;
- var totalDays = workDays.Count;
-
- if (totalDays == 0) return new List();
-
- var interval = (double)totalDays / totalWorks;
- var plan = new List();
-
- for (int i = 0; i < totalWorks; i++)
- {
- var dayIndex = (int)Math.Round(i * interval);
- if (dayIndex >= totalDays) dayIndex = totalDays - 1;
-
- var date = workDays[dayIndex];
- plan.Add(date);
- }
-
- return plan.OrderBy(d => d).ToList();
- }
-
- ///
- /// Ищет следующую дату в плане, которая не занята
- ///
- private DateOnly? FindNextAvailableDateNotInPlan(List workDays, DateOnly afterDate, List plan)
- {
- var planSet = new HashSet(plan);
-
- // Ищем вперед
- foreach (var day in workDays)
- {
- if (day > afterDate && !planSet.Contains(day))
- {
- return day;
- }
- }
-
- // Ищем назад
- foreach (var day in workDays.AsEnumerable().Reverse())
- {
- if (day <= afterDate && !planSet.Contains(day))
- {
- return day;
- }
- }
-
- return null;
- }
-
- ///
- /// Ищет следующую доступную дату после указанной, если не находит — ищет назад.
- ///
- private DateOnly? FindNextAvailableDate(List workDays, DateOnly afterDate, DateOnly originalDate)
- {
- var future = workDays.FirstOrDefault(d => d >= afterDate);
- if (future != default) return future;
-
- var past = workDays.Where(t => t >= originalDate).OrderByDescending(t => t).FirstOrDefault();
- return past != default ? (DateOnly?)past : null;
- }
-
- ///
- /// Возвращает список рабочих дней в диапазоне, исключая выходные и праздники.
- ///
- private List GetWorkDays(DateOnly start, DateOnly end, HashSet weekends)
- {
- var result = new List();
- var date = start;
-
- while (date <= end)
- {
- var dayOfWeek = date.DayOfWeek;
- if (!weekends.Contains(date))
- {
- result.Add(date);
- }
- date = date.AddDays(1);
- }
-
- return result;
- }
-
- ///
- /// Получить список выходных дней
- ///
- private async Task> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
- {
- var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
- return weekends.ToHashSet();
- }
- }
-}
\ No newline at end of file
diff --git a/PARR.Test/NextRun/NextRunTest.cs b/PARR.Test/NextRun/NextRunTest.cs
index 5791f4a2..c815937f 100644
--- a/PARR.Test/NextRun/NextRunTest.cs
+++ b/PARR.Test/NextRun/NextRunTest.cs
@@ -11,15 +11,37 @@ namespace PARR.Test.NextRun
{
this.templateDistributor = templateDistributor;
}
-
+
int periodDays = 10;
- DateTimeOffset referenceDate = new DateTimeOffset(2025, 12, 1, 7, 0, 0, TimeSpan.Zero);
+ DateTimeOffset referenceDate = new DateTimeOffset(2025, 1, 20, 5, 0, 0, TimeSpan.Zero);
public async Task Test()
{
- await DistributeTemplatesAsync();
+ await DistributeTemplatesAsync();
+ //await GetValidNextRunForTemplateAsync();
}
+ ///
+ /// Получить актуальный NextRun
+ ///
+ ///
+ 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(), excludeWeekends, isNew);
+ }
+
+
///
/// распределить шаблоны
///
@@ -72,18 +94,18 @@ namespace PARR.Test.NextRun
{
var templates = new List();
- 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("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("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) });