feat(dal): ITemplateDistributorV2 - метод получения актуального nextRun
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
namespace PARR.DAL.NextRunServices.Subservices
|
namespace PARR.DAL.NextRunServices.Subservices
|
||||||
{
|
{
|
||||||
|
//todo: public->internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сервис трансформации расписания ЕСПП в дату/расписание
|
/// Сервис трансформации расписания ЕСПП в дату/расписание
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IEsppScheduleTransformService
|
public interface IEsppScheduleTransformService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить следующую дату по jobGroupId
|
/// Получить следующую дату по jobGroupId
|
||||||
|
|||||||
@@ -16,7 +16,21 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// <param name="excludeWeekends"></param>
|
/// <param name="excludeWeekends"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, List<TemplateNextRunDto> allTemplates, bool excludeWeekends);
|
Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, List<TemplateNextRunDto> allTemplates, bool excludeWeekends);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Метод проверяет, актуален ли текущий NextRun шаблона, если не актуален, возвращает актуальное значение
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="duration"></param>
|
||||||
|
/// <param name="referenceDate"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <param name="targetTemplate"></param>
|
||||||
|
/// <param name="allTemplates"></param>
|
||||||
|
/// <param name="excludeWeekends"></param>
|
||||||
|
/// <param name="isNew"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<TemplateNextRunResultDto> GetNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить список выходных дней
|
/// Получить список выходных дней
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -24,7 +38,7 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// <param name="endDate"></param>
|
/// <param name="endDate"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate);
|
Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить список рабочих дней
|
/// Получить список рабочих дней
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -29,6 +29,255 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
this.weekendDayService = weekendDayService;
|
this.weekendDayService = weekendDayService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<TemplateNextRunResultDto> GetNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Начинаем проверку/расчёт NextRun для шаблона {templateId}, nextRun={nextRun}. isNew={isNew}, offset={offset}, referenceDate={referenceDate}", targetTemplate.Id, targetTemplate.NextRun, isNew, offset, referenceDate);
|
||||||
|
|
||||||
|
// 1. Определяем целевой часовой пояс
|
||||||
|
var targetOffset = offset ?? TimeSpan.Zero;
|
||||||
|
var referenceDateInTargetTz = referenceDate.ToOffset(targetOffset);
|
||||||
|
|
||||||
|
logger.LogDebug("Целевой часовой пояс: {offset}. ReferenceDate в целевом поясе: {refDate}", targetOffset, referenceDateInTargetTz);
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Рассчитываем период распределения в целевом поясе
|
||||||
|
var _startDate = GetStartPeriodDate(startDate, referenceDateInTargetTz, targetOffset);
|
||||||
|
var _endDate = GetEndPeriodDate(_startDate, duration);
|
||||||
|
|
||||||
|
logger.LogInformation("Период распределения: {_startDate} - {_endDate} (в поясе {offset})", _startDate, _endDate, targetOffset);
|
||||||
|
|
||||||
|
// 3. Получаем список рабочих дней в периоде
|
||||||
|
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
|
||||||
|
|
||||||
|
if (workDays.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Нет рабочих дней в периоде {_startDate} - {_endDate} (пояс {offset}). Возвращаем ошибку со смещением.", _startDate, _endDate, targetOffset);
|
||||||
|
|
||||||
|
return FillTemplateResultWithOffsetError(targetTemplate, _startDate, referenceDateInTargetTz, targetOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 4. Строим план нагрузки и рассчитываем текущую нагрузку
|
||||||
|
|
||||||
|
logger.LogDebug("Строим план распределения для шаблона {templateId}. Всего шаблонов: {totalTemplates}", targetTemplate.Id, allTemplates.Count);
|
||||||
|
|
||||||
|
// Исключаем текущий шаблон из расчёта
|
||||||
|
var templatesForPlan = allTemplates.Where(t => t.Id != targetTemplate.Id).ToList();
|
||||||
|
|
||||||
|
logger.LogDebug("Исключили текущий шаблон из расчёта. Осталось {count} шаблонов для плана", templatesForPlan.Count);
|
||||||
|
|
||||||
|
// Определяем тип плана (обычный или зеркальный)
|
||||||
|
bool useMirrorPlan = false;
|
||||||
|
|
||||||
|
// Определяем тип плана на основе контекста
|
||||||
|
if (!targetTemplate.NextRun.HasValue)
|
||||||
|
{
|
||||||
|
// Нет текущего NextRun — тип плана зависит от isNew
|
||||||
|
useMirrorPlan = !isNew;
|
||||||
|
logger.LogDebug("Нет текущего NextRun. Тип плана: {planType} (isNew={isNew})", useMirrorPlan ? "зеркальный" : "обычный", isNew);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Есть текущий NextRun — проверяем, в периоде ли он
|
||||||
|
var _nextRunInTargetTz = targetTemplate.NextRun.Value.ToOffset(targetOffset);
|
||||||
|
var _nextRunDate = DateOnly.FromDateTime(_nextRunInTargetTz.Date);
|
||||||
|
|
||||||
|
bool _isInPeriod = _nextRunDate >= _startDate && _nextRunDate <= _endDate;
|
||||||
|
bool _isWorkDay = workDays.Contains(_nextRunDate);
|
||||||
|
|
||||||
|
if (_isInPeriod && !_isWorkDay)
|
||||||
|
{
|
||||||
|
// В периоде, но нерабочий день — обычный план
|
||||||
|
useMirrorPlan = false;
|
||||||
|
logger.LogDebug("NextRun в периоде, но на нерабочем дне. Используем обычный план.");
|
||||||
|
}
|
||||||
|
else if (!_isInPeriod)
|
||||||
|
{
|
||||||
|
// Не в периоде — тип плана зависит от isNew
|
||||||
|
useMirrorPlan = !isNew;
|
||||||
|
logger.LogDebug("NextRun вне периода. Тип плана: {planType} (isNew={isNew})", useMirrorPlan ? "зеркальный" : "обычный", isNew);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// В периоде и рабочий день — будем проверять актуальность
|
||||||
|
useMirrorPlan = false;
|
||||||
|
logger.LogDebug("NextRun в периоде и на рабочем дне. Используем обычный план для проверки.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Строим план распределения
|
||||||
|
List<DateOnly> plan;
|
||||||
|
if (useMirrorPlan)
|
||||||
|
{
|
||||||
|
// Зеркальный план
|
||||||
|
plan = GetDistributionPlan(templatesForPlan.Count + 1, workDays, mirror: true);
|
||||||
|
logger.LogDebug("Построен зеркальный план для {count} шаблонов", templatesForPlan.Count + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Обычный план
|
||||||
|
plan = GetDistributionPlan(templatesForPlan.Count + 1, workDays);
|
||||||
|
logger.LogDebug("Построен обычный план для {count} шаблонов", templatesForPlan.Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Считаем ожидаемую нагрузку из плана
|
||||||
|
var expectedDailyLoad = plan.GroupBy(d => d).ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
|
||||||
|
logger.LogDebug("Ожидаемая нагрузка по плану: {loadCount} дней", expectedDailyLoad.Count);
|
||||||
|
foreach (var kvp in expectedDailyLoad.OrderBy(x => x.Key))
|
||||||
|
logger.LogDebug(" {date}: ожидаемо {count} шаблонов", kvp.Key, kvp.Value);
|
||||||
|
|
||||||
|
// Считаем текущую нагрузку (только шаблоны внутри периода)
|
||||||
|
var currentDailyLoad = BuildCurrentLoad(templatesForPlan, workDays, _startDate, _endDate, targetOffset);
|
||||||
|
|
||||||
|
logger.LogDebug("Текущая нагрузка (без учёта текущего шаблона): {loadCount} дней", currentDailyLoad.Count);
|
||||||
|
foreach (var kvp in currentDailyLoad.OrderBy(x => x.Key))
|
||||||
|
logger.LogDebug(" {date}: текущая {current}, ожидаемая {expected}", kvp.Key, kvp.Value, expectedDailyLoad.GetValueOrDefault(kvp.Key, 0));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// 5. Случай: нет текущего NextRun
|
||||||
|
if (!targetTemplate.NextRun.HasValue)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Шаблон {templateId} не имеет текущего NextRun. Ищем новое место...", targetTemplate.Id);
|
||||||
|
|
||||||
|
return HandleNoNextRun(
|
||||||
|
targetTemplate,
|
||||||
|
workDays,
|
||||||
|
currentDailyLoad,
|
||||||
|
expectedDailyLoad,
|
||||||
|
referenceDateInTargetTz,
|
||||||
|
targetOffset,
|
||||||
|
isNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 6. Случай: есть текущий NextRun — проверяем актуальность
|
||||||
|
|
||||||
|
logger.LogDebug("Шаблон {templateId} имеет текущий NextRun: {nextRun}. Проверяем актуальность...", targetTemplate.Id, targetTemplate.NextRun.Value);
|
||||||
|
|
||||||
|
var nextRunInTargetTz = targetTemplate.NextRun.Value.ToOffset(targetOffset);
|
||||||
|
var nextRunDate = DateOnly.FromDateTime(nextRunInTargetTz.Date);
|
||||||
|
|
||||||
|
logger.LogDebug("NextRun в целевом поясе {offset}: {nextRunTarget} (дата: {date})", targetOffset, nextRunInTargetTz, nextRunDate);
|
||||||
|
|
||||||
|
// Проверка 1: В периоде?
|
||||||
|
var isInPeriod = nextRunDate >= _startDate && nextRunDate <= _endDate;
|
||||||
|
logger.LogDebug("Проверка 1 (в периоде): {result} ({date} ∈ [{start}, {end}])", isInPeriod, nextRunDate, _startDate, _endDate);
|
||||||
|
|
||||||
|
// Проверка 2: Рабочий день?
|
||||||
|
var isWorkDay = workDays.Contains(nextRunDate);
|
||||||
|
logger.LogDebug("Проверка 2 (рабочий день): {result}", isWorkDay ? "да" : "нет");
|
||||||
|
|
||||||
|
// Проверка 3: В будущем?
|
||||||
|
var nextRunMoment = new DateTimeOffset(
|
||||||
|
nextRunDate.Year, nextRunDate.Month, nextRunDate.Day,
|
||||||
|
nextRunInTargetTz.Hour, nextRunInTargetTz.Minute, nextRunInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
var isFuture = nextRunMoment >= DateTimeOffset.UtcNow;
|
||||||
|
logger.LogDebug("Проверка 3 (в будущем): {result} ({moment} >= {now})", isFuture, nextRunMoment, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
// Проверка 4: Время совпадает с referenceDate?
|
||||||
|
var hasMatchingTime = nextRunInTargetTz.Hour == referenceDateInTargetTz.Hour &&
|
||||||
|
nextRunInTargetTz.Minute == referenceDateInTargetTz.Minute &&
|
||||||
|
nextRunInTargetTz.Second == referenceDateInTargetTz.Second;
|
||||||
|
logger.LogDebug("Проверка 4 (время совпадает): {result} (NextRun: {nextRunHour}:{nextRunMin}:{nextRunSec} vs Reference: {refHour}:{refMin}:{refSec})",
|
||||||
|
hasMatchingTime,
|
||||||
|
nextRunInTargetTz.Hour, nextRunInTargetTz.Minute, nextRunInTargetTz.Second,
|
||||||
|
referenceDateInTargetTz.Hour, referenceDateInTargetTz.Minute, referenceDateInTargetTz.Second);
|
||||||
|
|
||||||
|
// СЛУЧАЙ А: Дата актуальна, но время не совпадает → корректируем ТОЛЬКО время
|
||||||
|
if (isInPeriod && isWorkDay && isFuture && !hasMatchingTime)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Дата NextRun шаблона {templateId} актуальна, но время не совпадает. Корректируем время с {oldTime} на {newTime} (пояс {offset}).",
|
||||||
|
targetTemplate.Id,
|
||||||
|
$"{nextRunInTargetTz.Hour:D2}:{nextRunInTargetTz.Minute:D2}:{nextRunInTargetTz.Second:D2}",
|
||||||
|
$"{referenceDateInTargetTz.Hour:D2}:{referenceDateInTargetTz.Minute:D2}:{referenceDateInTargetTz.Second:D2}",
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
// Сохраняем ту же дату, но меняем время на время из referenceDate
|
||||||
|
var correctedNextRunInTargetTz = new DateTimeOffset(
|
||||||
|
nextRunDate.Year, nextRunDate.Month, nextRunDate.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
//скорректированный момент ещё в будущем?
|
||||||
|
if (correctedNextRunInTargetTz >= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
// Время актуально — возвращаем скорректированный NextRun
|
||||||
|
var correctedNextRunUtc = correctedNextRunInTargetTz.ToOffset(TimeSpan.Zero);
|
||||||
|
|
||||||
|
logger.LogInformation("Скорректированный NextRun для шаблона {templateId}: {nextRunTarget} (пояс {offset}) → {nextRunUtc} (UTC). NextRunOld: {nextRunOld}",
|
||||||
|
targetTemplate.Id, correctedNextRunInTargetTz, targetOffset, correctedNextRunUtc, targetTemplate.NextRun);
|
||||||
|
|
||||||
|
return new TemplateNextRunResultDto(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: correctedNextRunUtc,
|
||||||
|
NextRunOld: targetTemplate.NextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Скорректированное время уже прошло — считаем дату НЕАКТУАЛЬНОЙ
|
||||||
|
logger.LogDebug("Скорректированное время {correctedTime} уже прошло (сейчас {now}). Считаем дату неактуальной и передаём в основную логику поиска.",
|
||||||
|
correctedNextRunInTargetTz, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
// Помечаем дату как неактуальную по времени
|
||||||
|
isFuture = false;
|
||||||
|
// Дальнейшая логика сама вызовет HandleOutdatedNextRun с правильными параметрами
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// СЛУЧАЙ Б: Все проверки пройдены (включая время) → возвращаем без изменений
|
||||||
|
if (isInPeriod && isWorkDay && isFuture && hasMatchingTime)
|
||||||
|
{
|
||||||
|
var nextRunUtc = targetTemplate.NextRun.Value.ToOffset(TimeSpan.Zero);
|
||||||
|
logger.LogInformation("NextRun шаблона {templateId} полностью актуален. Возвращаем без изменений: {nextRunUtc} (UTC)",
|
||||||
|
targetTemplate.Id, nextRunUtc);
|
||||||
|
|
||||||
|
return new TemplateNextRunResultDto(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: nextRunUtc,
|
||||||
|
NextRunOld: targetTemplate.NextRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
// СЛУЧАЙ В: Дата неактуальна (не в периоде / нерабочий день / уже прошло) → ищем новое место
|
||||||
|
logger.LogDebug("NextRun шаблона {templateId} неактуален по дате (в периоде={inPeriod}, рабочий={workDay}, будущее={future}). Ищем новое место...",
|
||||||
|
targetTemplate.Id, isInPeriod, isWorkDay, isFuture);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// 7. NextRun неактуален — ищем новое место
|
||||||
|
logger.LogDebug("NextRun шаблона {templateId} неактуален (в периоде={inPeriod}, рабочий={workDay}, будущее={future}). Ищем новое место...", targetTemplate.Id, isInPeriod, isWorkDay, isFuture);
|
||||||
|
|
||||||
|
return HandleOutdatedNextRun(
|
||||||
|
targetTemplate,
|
||||||
|
nextRunDate,
|
||||||
|
isInPeriod,
|
||||||
|
isWorkDay,
|
||||||
|
isNew,
|
||||||
|
workDays,
|
||||||
|
currentDailyLoad,
|
||||||
|
expectedDailyLoad,
|
||||||
|
referenceDateInTargetTz,
|
||||||
|
targetOffset,
|
||||||
|
_startDate,
|
||||||
|
_endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Распределить шаблоны
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startDate"></param>
|
||||||
|
/// <param name="duration"></param>
|
||||||
|
/// <param name="referenceDate"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <param name="allTemplates"></param>
|
||||||
|
/// <param name="excludeWeekends"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, List<TemplateNextRunDto> allTemplates, bool excludeWeekends)
|
public async Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, List<TemplateNextRunDto> allTemplates, bool excludeWeekends)
|
||||||
{
|
{
|
||||||
// 1. Определяем целевой часовой пояс
|
// 1. Определяем целевой часовой пояс
|
||||||
@@ -65,7 +314,7 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
|
|
||||||
// 5. Строим план распределения
|
// 5. Строим план распределения
|
||||||
// строим план, дни на которые должны быть назанчены шаблоны
|
// строим план, дни на которые должны быть назанчены шаблоны
|
||||||
var plan = GetPlan(allTemplates.Count, workDays);
|
var plan = GetDistributionPlan(allTemplates.Count, workDays);
|
||||||
// Подсчитываем, сколько работ должно быть на каждый день
|
// Подсчитываем, сколько работ должно быть на каждый день
|
||||||
var expectedDailyLoad = plan.GroupBy(d => d)
|
var expectedDailyLoad = plan.GroupBy(d => d)
|
||||||
.ToDictionary(g => g.Key, g => g.Count());
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
@@ -233,6 +482,383 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает случай, когда у шаблона нет текущего NextRun.
|
||||||
|
/// </summary>
|
||||||
|
private TemplateNextRunResultDto HandleNoNextRun(TemplateNextRunDto targetTemplate, List<DateOnly> workDays, Dictionary<DateOnly, int> currentDailyLoad, Dictionary<DateOnly, int> expectedDailyLoad, DateTimeOffset referenceDateInTargetTz, TimeSpan targetOffset, bool isNew)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Обработка случая 'нет текущего NextRun' для шаблона {templateId} (isNew={isNew})", targetTemplate.Id, isNew);
|
||||||
|
|
||||||
|
DateOnly? foundDay = null;
|
||||||
|
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
// Ищем от начала периода к концу
|
||||||
|
logger.LogDebug("isNew=true: ищем от начала периода к концу");
|
||||||
|
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
logger.LogDebug("Нашли первый не перегруженный день: {day}", day);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundDay.HasValue)
|
||||||
|
{
|
||||||
|
// Не нашли свободного места — берём первый день периода
|
||||||
|
foundDay = workDays.First();
|
||||||
|
logger.LogWarning("Все дни перегружены. Берём первый день периода: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ищем от конца периода к началу
|
||||||
|
logger.LogDebug("isNew=false: ищем от конца периода к началу");
|
||||||
|
|
||||||
|
foreach (var day in workDays.AsEnumerable().Reverse())
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
logger.LogDebug("Нашли первый не перегруженный день: {day}", day);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundDay.HasValue)
|
||||||
|
{
|
||||||
|
// Не нашли свободного места — берём последний день периода
|
||||||
|
foundDay = workDays.Last();
|
||||||
|
logger.LogWarning("Все дни перегружены. Берём последний день периода: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем результат
|
||||||
|
var nextRunInTargetTz = new DateTimeOffset(
|
||||||
|
foundDay.Value.Year, foundDay.Value.Month, foundDay.Value.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Корректируем найденный день, чтобы момент времени был в будущем
|
||||||
|
DateTimeOffset? adjustedNextRun;
|
||||||
|
TemplateNextRunResultDto? errorResult = null;
|
||||||
|
|
||||||
|
adjustedNextRun = AdjustNextRunToFuture(
|
||||||
|
foundDay.Value,
|
||||||
|
workDays,
|
||||||
|
currentDailyLoad,
|
||||||
|
expectedDailyLoad,
|
||||||
|
referenceDateInTargetTz,
|
||||||
|
targetOffset,
|
||||||
|
workDays.First(), // startDateForError для случая ошибки
|
||||||
|
targetTemplate,
|
||||||
|
out errorResult);
|
||||||
|
|
||||||
|
if (errorResult != null)
|
||||||
|
return errorResult;
|
||||||
|
|
||||||
|
//var nextRunUtc = nextRunInTargetTz.ToOffset(TimeSpan.Zero);
|
||||||
|
var nextRunUtc = adjustedNextRun.Value.ToOffset(TimeSpan.Zero);
|
||||||
|
|
||||||
|
logger.LogInformation("Найдено новое место для шаблона {templateId}: {nextRunTarget} (пояс {offset}) → {nextRunUtc} (UTC)", targetTemplate.Id, nextRunInTargetTz, targetOffset, nextRunUtc);
|
||||||
|
|
||||||
|
return new TemplateNextRunResultDto(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: nextRunUtc,
|
||||||
|
NextRunOld: targetTemplate.NextRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает случай, когда текущий NextRun неактуален.
|
||||||
|
/// </summary>
|
||||||
|
private TemplateNextRunResultDto HandleOutdatedNextRun(TemplateNextRunDto targetTemplate, DateOnly nextRunDate, bool isInPeriod, bool isWorkDay, bool isNew, List<DateOnly> workDays, Dictionary<DateOnly, int> currentDailyLoad, Dictionary<DateOnly, int> expectedDailyLoad, DateTimeOffset referenceDateInTargetTz, TimeSpan targetOffset, DateOnly periodStart, DateOnly periodEnd)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Обработка случая 'неактуальный NextRun' для шаблона {templateId}", targetTemplate.Id);
|
||||||
|
|
||||||
|
DateOnly? foundDay = null;
|
||||||
|
|
||||||
|
if (isInPeriod && !isWorkDay)
|
||||||
|
{
|
||||||
|
// СЦЕНАРИЙ А: В периоде, но нерабочий день
|
||||||
|
logger.LogDebug("Сценарий А: в периоде ({date}), но нерабочий день. Ищем слева...", nextRunDate);
|
||||||
|
|
||||||
|
// Берём все рабочие дни СТРОГО ЛЕВЕЕ исходной даты
|
||||||
|
var leftDays = workDays
|
||||||
|
.Where(d => d < nextRunDate)
|
||||||
|
.OrderByDescending(d => d) // Ближайший к исходной дате — первый
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (leftDays.Any())
|
||||||
|
{
|
||||||
|
logger.LogDebug("Слева найдено {count} рабочих дней. Ищем первый не перегруженный...", leftDays.Count);
|
||||||
|
|
||||||
|
// Ищем первый не перегруженный
|
||||||
|
foreach (var day in leftDays)
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
logger.LogDebug("Нашли слева не перегруженный день: {day}", day);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если не нашли — берём ближайший слева
|
||||||
|
if (!foundDay.HasValue)
|
||||||
|
{
|
||||||
|
foundDay = leftDays.First();
|
||||||
|
logger.LogWarning("Все дни слева перегружены. Берём ближайший: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Слева нет рабочих дней — берём ближайший справа
|
||||||
|
var rightDays = workDays
|
||||||
|
.Where(d => d > nextRunDate)
|
||||||
|
.OrderBy(d => d) // Ближайший к исходной дате — первый
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (rightDays.Any())
|
||||||
|
{
|
||||||
|
foundDay = rightDays.First();
|
||||||
|
logger.LogDebug("Слева нет рабочих дней. Берём ближайший справа: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Совсем нет рабочих дней — ошибка
|
||||||
|
logger.LogError("Нет рабочих дней ни слева, ни справа от {date}. Возвращаем ошибку.", nextRunDate);
|
||||||
|
return FillTemplateResultWithOffsetError(targetTemplate, periodStart, referenceDateInTargetTz, targetOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isInPeriod)
|
||||||
|
{
|
||||||
|
// СЦЕНАРИЙ Б: Не в периоде
|
||||||
|
logger.LogDebug("Сценарий Б: не в периоде ({date}). Ищем в зависимости от isNew...", nextRunDate);
|
||||||
|
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
// Ищем от начала периода к концу (обычный план)
|
||||||
|
logger.LogDebug("isNew=true: ищем от начала периода к концу");
|
||||||
|
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
logger.LogDebug("Нашли первый не перегруженный день: {day}", day);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundDay.HasValue)
|
||||||
|
{
|
||||||
|
foundDay = workDays.First();
|
||||||
|
logger.LogWarning("Все дни перегружены. Берём первый день периода: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ищем от конца периода к началу (зеркальный план)
|
||||||
|
logger.LogDebug("isNew=false: ищем от конца периода к началу");
|
||||||
|
|
||||||
|
foreach (var day in workDays.AsEnumerable().Reverse())
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
logger.LogDebug("Нашли первый не перегруженный день: {day}", day);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundDay.HasValue)
|
||||||
|
{
|
||||||
|
foundDay = workDays.Last();
|
||||||
|
logger.LogWarning("Все дни перегружены. Берём последний день периода: {day}", foundDay.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Другие случаи (например, время уже прошло)
|
||||||
|
logger.LogDebug("NextRun неактуален по другим причинам. Используем логику в зависимости от isNew...");
|
||||||
|
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
foreach (var day in workDays)
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundDay ??= workDays.First();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var day in workDays.AsEnumerable().Reverse())
|
||||||
|
{
|
||||||
|
if (currentDailyLoad[day] < expectedDailyLoad.GetValueOrDefault(day, 0))
|
||||||
|
{
|
||||||
|
foundDay = day;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundDay ??= workDays.Last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем результат
|
||||||
|
var nextRunInTargetTz = new DateTimeOffset(
|
||||||
|
foundDay.Value.Year, foundDay.Value.Month, foundDay.Value.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
// Корректируем найденный день, чтобы момент времени был в будущем
|
||||||
|
DateTimeOffset? adjustedNextRun;
|
||||||
|
TemplateNextRunResultDto? errorResult = null;
|
||||||
|
|
||||||
|
adjustedNextRun = AdjustNextRunToFuture(
|
||||||
|
foundDay.Value,
|
||||||
|
workDays,
|
||||||
|
currentDailyLoad,
|
||||||
|
expectedDailyLoad,
|
||||||
|
referenceDateInTargetTz,
|
||||||
|
targetOffset,
|
||||||
|
periodStart, // startDateForError для случая ошибки
|
||||||
|
targetTemplate,
|
||||||
|
out errorResult);
|
||||||
|
|
||||||
|
if (errorResult != null)
|
||||||
|
return errorResult;
|
||||||
|
|
||||||
|
|
||||||
|
//var nextRunUtc = nextRunInTargetTz.ToOffset(TimeSpan.Zero);
|
||||||
|
var nextRunUtc = adjustedNextRun.Value.ToOffset(TimeSpan.Zero);
|
||||||
|
|
||||||
|
logger.LogInformation("Найдено новое место для шаблона {templateId}: {nextRunTarget} (пояс {offset}) → {nextRunUtc} (UTC). NextRunOld: {nextRunOld}", targetTemplate.Id, nextRunInTargetTz, targetOffset, nextRunUtc, targetTemplate.NextRun);
|
||||||
|
|
||||||
|
return new TemplateNextRunResultDto(
|
||||||
|
Id: targetTemplate.Id,
|
||||||
|
NextRun: nextRunUtc,
|
||||||
|
NextRunOld: targetTemplate.NextRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Корректирует найденный день, чтобы момент времени был в будущем.
|
||||||
|
/// Если момент времени уже прошёл, ищет следующий рабочий день в будущем с учётом нагрузки.
|
||||||
|
/// В случае ошибки (нет будущих рабочих дней) возвращает ошибку через out-параметр.
|
||||||
|
/// </summary>
|
||||||
|
private DateTimeOffset? AdjustNextRunToFuture(
|
||||||
|
DateOnly foundDay,
|
||||||
|
List<DateOnly> workDays,
|
||||||
|
Dictionary<DateOnly, int> currentDailyLoad,
|
||||||
|
Dictionary<DateOnly, int> expectedDailyLoad,
|
||||||
|
DateTimeOffset referenceDateInTargetTz,
|
||||||
|
TimeSpan targetOffset,
|
||||||
|
DateOnly startDateForError,
|
||||||
|
TemplateNextRunDto targetTemplate,
|
||||||
|
out TemplateNextRunResultDto? errorResult)
|
||||||
|
{
|
||||||
|
errorResult = null;
|
||||||
|
|
||||||
|
// Создаём момент времени для найденного дня
|
||||||
|
var nextRunInTargetTz = new DateTimeOffset(
|
||||||
|
foundDay.Year, foundDay.Month, foundDay.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
// ФИНАЛЬНАЯ ПРОВЕРКА: момент времени должен быть в будущем
|
||||||
|
if (nextRunInTargetTz < DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Найденный момент времени {nextRun} уже прошёл (сейчас {now}). Ищем следующий рабочий день в будущем.",
|
||||||
|
nextRunInTargetTz, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
// Берём все рабочие дни СТРОГО ПОСЛЕ найденного дня
|
||||||
|
var futureWorkDays = workDays.Where(d => d > foundDay).ToList();
|
||||||
|
|
||||||
|
if (futureWorkDays.Any())
|
||||||
|
{
|
||||||
|
// Ищем первый день с допустимой нагрузкой (текущая < ожидаемая)
|
||||||
|
var notOverloadedDays = futureWorkDays
|
||||||
|
.Where(d => currentDailyLoad[d] < expectedDailyLoad.GetValueOrDefault(d, 0))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var futureDay = notOverloadedDays.Any()
|
||||||
|
? notOverloadedDays.First()
|
||||||
|
: futureWorkDays.First();
|
||||||
|
|
||||||
|
logger.LogDebug("Выбран следующий рабочий день в будущем: {day}", futureDay);
|
||||||
|
|
||||||
|
// Пересоздаём момент времени с новой датой
|
||||||
|
nextRunInTargetTz = new DateTimeOffset(
|
||||||
|
futureDay.Year, futureDay.Month, futureDay.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Нет будущих рабочих дней — ошибка
|
||||||
|
logger.LogError("Нет будущих рабочих дней после {date} в периоде. Возвращаем ошибку со смещением.",
|
||||||
|
foundDay);
|
||||||
|
|
||||||
|
errorResult = FillTemplateResultWithOffsetError(targetTemplate, startDateForError, referenceDateInTargetTz, targetOffset);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Момент времени {nextRun} актуален (в будущем). Коррекция не требуется.", nextRunInTargetTz);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRunInTargetTz;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Строит текущую нагрузку на дни, учитывая ТОЛЬКО шаблоны внутри периода.
|
||||||
|
/// Шаблоны вне периода игнорируются.
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<DateOnly, int> BuildCurrentLoad(List<TemplateNextRunDto> templates, List<DateOnly> workDays, DateOnly periodStart, DateOnly periodEnd, TimeSpan targetOffset)
|
||||||
|
{
|
||||||
|
var load = workDays.ToDictionary(day => day, _ => 0);
|
||||||
|
|
||||||
|
foreach (var template in templates)
|
||||||
|
{
|
||||||
|
if (!template.NextRun.HasValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Конвертируем в целевой пояс для определения даты
|
||||||
|
var templateDate = DateOnly.FromDateTime(template.NextRun.Value.ToOffset(targetOffset).Date);
|
||||||
|
|
||||||
|
// Учитываем ТОЛЬКО шаблоны внутри периода
|
||||||
|
if (templateDate >= periodStart && templateDate <= periodEnd)
|
||||||
|
{
|
||||||
|
if (load.ContainsKey(templateDate))
|
||||||
|
load[templateDate]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return load;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Заполнить TemplateNextRunResultDto, если нет рабочих дней со смещением несколько лет
|
/// Заполнить TemplateNextRunResultDto, если нет рабочих дней со смещением несколько лет
|
||||||
@@ -302,42 +928,41 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить план: дни, на которые должны быть назначены шаблоны
|
/// Получить план распределения шаблонов по рабочим дням.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="totalWorks"></param>
|
/// <param name="totalWorks">Общее количество шаблонов для распределения</param>
|
||||||
/// <param name="workDays"></param>
|
/// <param name="workDays">Список рабочих дней в периоде</param>
|
||||||
/// <returns></returns>
|
/// <param name="mirror">Если true — строит зеркальный план (справа налево от конца периода), иначе обычный (слева направо от начала периода)</param>
|
||||||
private List<DateOnly> GetPlan(int totalWorks, List<DateOnly> workDays)
|
/// <returns>Список дат, на которые должны быть назначены шаблоны</returns>
|
||||||
|
private List<DateOnly> GetDistributionPlan(int totalWorks, List<DateOnly> workDays, bool mirror = false)
|
||||||
{
|
{
|
||||||
var totalDays = workDays.Count();
|
var totalDays = workDays.Count;
|
||||||
|
var interval = CalculateDistributionInterval(totalDays, totalWorks);
|
||||||
var interval = GetInterval(totalDays, totalWorks);
|
|
||||||
|
|
||||||
var plan = new List<DateOnly>();
|
var plan = new List<DateOnly>();
|
||||||
|
|
||||||
for (int i = 0; i < totalWorks; i++)
|
for (int i = 0; i < totalWorks; i++)
|
||||||
{
|
{
|
||||||
#region Вынести в метод
|
// Базовый индекс для обычного плана (слева → направо)
|
||||||
// Вычисляем индекс дня, на который нужно назначить i-ю работу
|
var baseIndex = (int)Math.Floor(i * interval);
|
||||||
//var dayIndex = (int)Math.Round(i * interval);
|
|
||||||
var dayIndex = (int)Math.Floor(i * interval);
|
|
||||||
|
|
||||||
// Защита от возможного выхода за границы массива из-за округления
|
// Применяем зеркалирование, если нужно
|
||||||
if (dayIndex >= totalDays)
|
var index = mirror
|
||||||
dayIndex = totalDays - 1;
|
? totalDays - 1 - baseIndex // Зеркальный индекс: от конца периода
|
||||||
#endregion
|
: baseIndex; // Обычный индекс: от начала периода
|
||||||
|
|
||||||
var date = workDays[dayIndex];
|
// Защита от выхода за границы массива
|
||||||
plan.Add(date);
|
index = Math.Max(0, Math.Min(index, totalDays - 1));
|
||||||
|
|
||||||
|
plan.Add(workDays[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("--- План распределения ---");
|
// Логгирование
|
||||||
foreach (var item in plan.GroupBy(t => t))
|
var planType = mirror ? "Зеркальный" : "Обычный";
|
||||||
|
logger.LogDebug("--- {planType} план распределения ({totalWorks} шаблонов, {totalDays} раб. дней) ---", planType, totalWorks, totalDays);
|
||||||
|
|
||||||
|
foreach (var item in plan.GroupBy(t => t).OrderBy(g => g.Key))
|
||||||
logger.LogDebug("Дата: {date}, кол-во работ: {workCount}", item.Key, item.Count());
|
logger.LogDebug("Дата: {date}, кол-во работ: {workCount}", item.Key, item.Count());
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
@@ -350,11 +975,11 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// <param name="totalDays">Кол-во рабочих дней</param>
|
/// <param name="totalDays">Кол-во рабочих дней</param>
|
||||||
/// <param name="totalWorks">Кол-во шаблонов</param>
|
/// <param name="totalWorks">Кол-во шаблонов</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private double GetInterval(int totalDays, int totalWorks)
|
private double CalculateDistributionInterval(int totalDays, int totalWorks)
|
||||||
{
|
{
|
||||||
var interval = (double)totalDays / totalWorks;
|
var interval = (double)totalDays / totalWorks;
|
||||||
|
|
||||||
logger.LogDebug("Интевал между шаблонами: {interval}", interval);
|
logger.LogDebug("Интервал распределения: {interval} (дней на шаблон)", interval);
|
||||||
|
|
||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
@@ -490,6 +1115,5 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using PARR.DAL.NextRunServices;
|
|||||||
using PARR.DAL.NextRunServices.Models;
|
using PARR.DAL.NextRunServices.Models;
|
||||||
using PARR.DAL.NextRunServices.Subservices;
|
using PARR.DAL.NextRunServices.Subservices;
|
||||||
using PARR.DAL.Services.Interfaces;
|
using PARR.DAL.Services.Interfaces;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PARR.Test.NextRun
|
namespace PARR.Test.NextRun
|
||||||
@@ -39,11 +40,17 @@ namespace PARR.Test.NextRun
|
|||||||
var offset = TimeSpan.FromHours(10);
|
var offset = TimeSpan.FromHours(10);
|
||||||
|
|
||||||
var startDate = new DateOnly(2026, 2, 10);
|
var startDate = new DateOnly(2026, 2, 10);
|
||||||
var referenceDate = new DateTimeOffset(2026, 2, 10, 4, 0, 0, TimeSpan.Zero);
|
var referenceDate = new DateTimeOffset(2026, 2, 10, 6, 0, 0, TimeSpan.Zero);
|
||||||
|
|
||||||
var templates = GetTemplates();
|
var templates = GetTemplates();
|
||||||
|
var templatesEmpty = new List<TemplateNextRunDto>();
|
||||||
|
|
||||||
var distributedTemplates = await templateDistributorV2.DistributeTemplatesAsync(startDate, duration, referenceDate, offset, templates, true);
|
// распределить шаблоны
|
||||||
|
//var distributedTemplates = await templateDistributorV2.DistributeTemplatesAsync(startDate, duration, referenceDate, offset, templates, true);
|
||||||
|
|
||||||
|
// получить актуальный nextRun
|
||||||
|
var targetTemplate = new TemplateNextRunDto(Guid.Parse("DA151719-2742-4260-BFFE-012B61591053"), new DateTimeOffset(2026, 2, 14, 5, 0, 0, TimeSpan.Zero));
|
||||||
|
var validNextRun = await templateDistributorV2.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesEmpty, true, isNew: false);
|
||||||
|
|
||||||
|
|
||||||
//var template = await templateService.Get().Include(t => t.Job).ThenInclude(t => t.Group).FirstAsync(t => t.Name == "ЭИТИ-ПАРР_ЦКИТ-ГВЦ_СХД ТО-1_СХД-AERODISK-432-2-4-U26-EN4SAG022-ГВЦ");
|
//var template = await templateService.Get().Include(t => t.Job).ThenInclude(t => t.Group).FirstAsync(t => t.Name == "ЭИТИ-ПАРР_ЦКИТ-ГВЦ_СХД ТО-1_СХД-AERODISK-432-2-4-U26-EN4SAG022-ГВЦ");
|
||||||
|
|||||||
Reference in New Issue
Block a user