feat(dal): TemplateDistributorV2 - если в периоде распределения нет рабочих дней, то период может расшириться еще 2 раза
This commit is contained in:
@@ -19,6 +19,7 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Метод проверяет, актуален ли текущий NextRun шаблона, если не актуален, возвращает актуальное значение
|
/// Метод проверяет, актуален ли текущий NextRun шаблона, если не актуален, возвращает актуальное значение
|
||||||
|
/// При проверке загружаются шаблоны из БД! Если хотим рассчитывать nextRun для НЕСКОЛЬКИХ шаблонов, то следует рассчитать nextRun для одного шаблона, сохранить в БД, затем рассчитать для следующего
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startDate"></param>
|
/// <param name="startDate"></param>
|
||||||
/// <param name="duration"></param>
|
/// <param name="duration"></param>
|
||||||
@@ -38,14 +39,5 @@ 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>
|
|
||||||
/// <param name="start"></param>
|
|
||||||
/// <param name="end"></param>
|
|
||||||
/// <param name="excludeWeekends"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,19 +20,27 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int offsetYearsIfErrorCalcNextRun = 5;
|
private const int offsetYearsIfErrorCalcNextRun = 5;
|
||||||
|
|
||||||
public TemplateDistributorV2(
|
/// <summary>
|
||||||
ILogger<TemplateDistributorV2> logger,
|
/// Максимальное количество попыток расширения периода при отсутствии рабочих дней
|
||||||
IWeekendDayService weekendDayService
|
/// </summary>
|
||||||
)
|
private const int maxPeriodExpansions = 2;
|
||||||
|
|
||||||
|
public TemplateDistributorV2(ILogger<TemplateDistributorV2> logger, IWeekendDayService weekendDayService)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
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)
|
public async Task<TemplateNextRunResultDto> GetNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew)
|
||||||
{
|
{
|
||||||
|
// Валидация входных параметров
|
||||||
|
if (duration <= 0)
|
||||||
|
throw new ArgumentException($"Duration must be positive, got {duration}", nameof(duration));
|
||||||
|
|
||||||
|
if (allTemplates == null)
|
||||||
|
throw new ArgumentNullException(nameof(allTemplates));
|
||||||
|
|
||||||
logger.LogInformation("Начинаем проверку/расчёт NextRun для шаблона {templateId}, nextRun={nextRun}. isNew={isNew}, offset={offset}, referenceDate={referenceDate}", targetTemplate.Id, targetTemplate.NextRun, isNew, offset, referenceDate);
|
logger.LogInformation("Начинаем проверку/расчёт NextRun для шаблона {templateId}, nextRun={nextRun}. isNew={isNew}, offset={offset}, referenceDate={referenceDate}", targetTemplate.Id, targetTemplate.NextRun, isNew, offset, referenceDate);
|
||||||
|
|
||||||
// 1. Определяем целевой часовой пояс
|
// 1. Определяем целевой часовой пояс
|
||||||
@@ -48,13 +56,15 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
|
|
||||||
logger.LogInformation("Период распределения: {_startDate} - {_endDate} (в поясе {offset})", _startDate, _endDate, targetOffset);
|
logger.LogInformation("Период распределения: {_startDate} - {_endDate} (в поясе {offset})", _startDate, _endDate, targetOffset);
|
||||||
|
|
||||||
// 3. Получаем список рабочих дней в периоде
|
// 3. Получаем список рабочих дней с расширением периода при необходимости
|
||||||
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
|
var (expandedEndDate, workDays) = await GetWorkDaysWithExpansionAsync(_startDate, duration, excludeWeekends);
|
||||||
|
|
||||||
|
// Обновляем _endDate на расширенный период для дальнейших расчётов
|
||||||
|
_endDate = expandedEndDate;
|
||||||
|
|
||||||
if (workDays.Count == 0)
|
if (workDays.Count == 0)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Нет рабочих дней в периоде {_startDate} - {_endDate} (пояс {offset}). Возвращаем ошибку со смещением.", _startDate, _endDate, targetOffset);
|
logger.LogError("Не удалось найти рабочие дни даже после {maxAttempts} попыток расширения периода [{start}, {end}]. Возвращаем ошибку со смещением.", maxPeriodExpansions, _startDate, _endDate);
|
||||||
|
|
||||||
return FillTemplateResultWithOffsetError(targetTemplate, _startDate, referenceDateInTargetTz, targetOffset);
|
return FillTemplateResultWithOffsetError(targetTemplate, _startDate, referenceDateInTargetTz, targetOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +290,14 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// <returns></returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
|
// Валидация входных параметров
|
||||||
|
if (duration <= 0)
|
||||||
|
throw new ArgumentException($"Duration must be positive, got {duration}", nameof(duration));
|
||||||
|
|
||||||
|
if (allTemplates == null)
|
||||||
|
throw new ArgumentNullException(nameof(allTemplates));
|
||||||
|
|
||||||
|
|
||||||
// 1. Определяем целевой часовой пояс
|
// 1. Определяем целевой часовой пояс
|
||||||
var targetOffset = offset ?? TimeSpan.Zero;
|
var targetOffset = offset ?? TimeSpan.Zero;
|
||||||
|
|
||||||
@@ -292,26 +310,26 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
var _startDate = GetStartPeriodDate(startDate, referenceDateInTargetTz, targetOffset);
|
var _startDate = GetStartPeriodDate(startDate, referenceDateInTargetTz, targetOffset);
|
||||||
var _endDate = GetEndPeriodDate(_startDate, duration);
|
var _endDate = GetEndPeriodDate(_startDate, duration);
|
||||||
|
|
||||||
logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDate: {referenceDateInTargetTz} в целевом поясе: {targetOffset}. Исключить выходные и праздники: {excludeWeekends}.", allTemplates.Count, duration, _startDate, _endDate, referenceDateInTargetTz, targetOffset, excludeWeekends);
|
logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDateInTargetTz: {referenceDateInTargetTz} в целевом поясе: {targetOffset} ReferenceDate UTC: {referenceDate}. Исключить выходные и праздники: {excludeWeekends}.", allTemplates.Count, duration, _startDate, _endDate, referenceDateInTargetTz, targetOffset, referenceDate, excludeWeekends);
|
||||||
|
|
||||||
// 4. Получаем рабочие дни (календарные даты, не зависят от пояса)
|
// 4. Получаем рабочие дни с расширением периода при необходимости
|
||||||
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
|
var (expandedEndDate, workDays) = await GetWorkDaysWithExpansionAsync(_startDate, duration, excludeWeekends);
|
||||||
|
|
||||||
|
// Обновляем _endDate на расширенный период для дальнейших расчётов
|
||||||
|
_endDate = expandedEndDate;
|
||||||
|
|
||||||
if (workDays.Count == 0)
|
if (workDays.Count == 0)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Нет доступных рабочих дней в диапазоне {_startDate} - {_endDate} (пояс {offset}). Возвращаем шаблоны со смещением {years} лет.", _startDate, _endDate, targetOffset, offsetYearsIfErrorCalcNextRun);
|
logger.LogError("Не удалось найти рабочие дни даже после {maxAttempts} попыток расширения периода [{start}, {end}]. Возвращаем шаблоны со смещением {years} лет.", maxPeriodExpansions, _startDate, _endDate, offsetYearsIfErrorCalcNextRun);
|
||||||
|
|
||||||
// смещаем все nextRun вперед
|
|
||||||
var errorResult = allTemplates.Select(t =>
|
var errorResult = allTemplates.Select(t =>
|
||||||
FillTemplateResultWithOffsetError(t, _startDate, referenceDateInTargetTz, targetOffset)
|
FillTemplateResultWithOffsetError(t, _startDate, referenceDateInTargetTz, targetOffset)
|
||||||
).OrderBy(t => t.NextRun).ToList();
|
).OrderBy(t => t.NextRun).ToList();
|
||||||
|
|
||||||
logger.LogInformation("Распределение завершено. Возвращаем {Count} записей. NextRun не изменился у {oldCount} шт. Обновлен NextRun у {changedCount} шт.", errorResult.Count, errorResult.Count(t => t.NextRun == t.NextRunOld), errorResult.Count(t => t.NextRun != t.NextRunOld));
|
logger.LogInformation("Распределение завершено с ошибкой. Возвращаем {Count} записей.", errorResult.Count);
|
||||||
|
|
||||||
return errorResult;
|
return errorResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 5. Строим план распределения
|
// 5. Строим план распределения
|
||||||
// строим план, дни на которые должны быть назанчены шаблоны
|
// строим план, дни на которые должны быть назанчены шаблоны
|
||||||
var plan = GetDistributionPlan(allTemplates.Count, workDays);
|
var plan = GetDistributionPlan(allTemplates.Count, workDays);
|
||||||
@@ -355,8 +373,8 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 8. Определяем, какие шаблоны нужно оставить, а какие — переназначить
|
|
||||||
|
|
||||||
|
#region 8. Определяем, какие шаблоны нужно оставить, а какие — переназначить
|
||||||
var templatesToKeep = new List<TemplateNextRunResultDto>();
|
var templatesToKeep = new List<TemplateNextRunResultDto>();
|
||||||
var templatesToReassign = new List<TemplateNextRunDto>();
|
var templatesToReassign = new List<TemplateNextRunDto>();
|
||||||
|
|
||||||
@@ -381,33 +399,117 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
|
|
||||||
if (currentCount <= expectedCount)
|
if (currentCount <= expectedCount)
|
||||||
{
|
{
|
||||||
// Все шаблоны в этом дне можно оставить
|
// Все шаблоны в этом дне можно оставить, но нужно проверить/скорректировать время
|
||||||
foreach (var template in templates)
|
foreach (var template in templates)
|
||||||
{
|
{
|
||||||
// КОНВЕРТИРУЕМ В UTC ПЕРЕД СОХРАНЕНИЕМ!
|
// Конвертируем текущий NextRun в целевой пояс для проверки времени
|
||||||
templatesToKeep.Add(new TemplateNextRunResultDto
|
var nextRunInTargetTz = template.NextRun.Value.ToOffset(targetOffset);
|
||||||
(
|
var nextRunDate = DateOnly.FromDateTime(nextRunInTargetTz.Date);
|
||||||
|
|
||||||
|
// Проверяем, совпадает ли время с referenceDate
|
||||||
|
var hasMatchingTime = nextRunInTargetTz.Hour == referenceDateInTargetTz.Hour &&
|
||||||
|
nextRunInTargetTz.Minute == referenceDateInTargetTz.Minute &&
|
||||||
|
nextRunInTargetTz.Second == referenceDateInTargetTz.Second;
|
||||||
|
|
||||||
|
DateTimeOffset newNextRun;
|
||||||
|
|
||||||
|
if (hasMatchingTime)
|
||||||
|
{
|
||||||
|
// Время совпадает — оставляем как есть (только конвертируем в чистый UTC)
|
||||||
|
newNextRun = template.NextRun.Value.ToOffset(TimeSpan.Zero);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} оставлен без изменений (время совпадает): {NextRun} (UTC)",
|
||||||
|
template.Id, newNextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Время НЕ совпадает — корректируем ТОЛЬКО время, сохраняя ту же дату
|
||||||
|
var correctedNextRunInTargetTz = new DateTimeOffset(
|
||||||
|
nextRunDate.Year, nextRunDate.Month, nextRunDate.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
// Проверяем, что скорректированный момент ещё в будущем
|
||||||
|
if (correctedNextRunInTargetTz >= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
newNextRun = correctedNextRunInTargetTz.ToOffset(TimeSpan.Zero);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} оставлен на дате {date}, время скорректировано с {oldTime} на {newTime} (UTC): {NextRun}",
|
||||||
|
template.Id, nextRunDate,
|
||||||
|
$"{nextRunInTargetTz.Hour:D2}:{nextRunInTargetTz.Minute:D2}:{nextRunInTargetTz.Second:D2}",
|
||||||
|
$"{referenceDateInTargetTz.Hour:D2}:{referenceDateInTargetTz.Minute:D2}:{referenceDateInTargetTz.Second:D2}",
|
||||||
|
newNextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Скорректированное время уже прошло — переназначаем шаблон
|
||||||
|
templatesToReassign.Add(template);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} переназначается, т.к. скорректированное время {correctedTime} уже прошло",
|
||||||
|
template.Id, correctedNextRunInTargetTz);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templatesToKeep.Add(new TemplateNextRunResultDto(
|
||||||
Id: template.Id,
|
Id: template.Id,
|
||||||
NextRun: template.NextRun.Value.ToOffset(TimeSpan.Zero), // ← В UTC!
|
NextRun: newNextRun,
|
||||||
NextRunOld: template.NextRun
|
NextRunOld: template.NextRun
|
||||||
));
|
));
|
||||||
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun} (UTC), т.к. нагрузка в день не превышена.", template.Id, template.NextRun.Value.ToOffset(TimeSpan.Zero));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Оставляем только expectedCount штук, остальные — переназначить
|
// Оставляем только expectedCount штук, остальные — переназначить
|
||||||
|
// (для оставляемых тоже нужно проверить/скорректировать время)
|
||||||
for (int i = 0; i < expectedCount; i++)
|
for (int i = 0; i < expectedCount; i++)
|
||||||
{
|
{
|
||||||
var template = templates[i];
|
var template = templates[i];
|
||||||
// КОНВЕРТИРУЕМ В UTC ПЕРЕД СОХРАНЕНИЕМ!
|
|
||||||
templatesToKeep.Add(new TemplateNextRunResultDto
|
// Конвертируем текущий NextRun в целевой пояс для проверки времени
|
||||||
(
|
var nextRunInTargetTz = template.NextRun.Value.ToOffset(targetOffset);
|
||||||
|
var nextRunDate = DateOnly.FromDateTime(nextRunInTargetTz.Date);
|
||||||
|
|
||||||
|
var hasMatchingTime = nextRunInTargetTz.Hour == referenceDateInTargetTz.Hour &&
|
||||||
|
nextRunInTargetTz.Minute == referenceDateInTargetTz.Minute &&
|
||||||
|
nextRunInTargetTz.Second == referenceDateInTargetTz.Second;
|
||||||
|
|
||||||
|
DateTimeOffset newNextRun;
|
||||||
|
|
||||||
|
if (hasMatchingTime)
|
||||||
|
{
|
||||||
|
newNextRun = template.NextRun.Value.ToOffset(TimeSpan.Zero);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} (часть перегрузки) оставлен без изменений (время совпадает): {NextRun} (UTC)",
|
||||||
|
template.Id, newNextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var correctedNextRunInTargetTz = new DateTimeOffset(
|
||||||
|
nextRunDate.Year, nextRunDate.Month, nextRunDate.Day,
|
||||||
|
referenceDateInTargetTz.Hour,
|
||||||
|
referenceDateInTargetTz.Minute,
|
||||||
|
referenceDateInTargetTz.Second,
|
||||||
|
targetOffset);
|
||||||
|
|
||||||
|
if (correctedNextRunInTargetTz >= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
newNextRun = correctedNextRunInTargetTz.ToOffset(TimeSpan.Zero);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} (часть перегрузки) оставлен на дате {date}, время скорректировано: {NextRun}",
|
||||||
|
template.Id, nextRunDate, newNextRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
templatesToReassign.Add(template);
|
||||||
|
logger.LogDebug("Шаблон {TemplateId} (часть перегрузки) переназначается, т.к. скорректированное время уже прошло",
|
||||||
|
template.Id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templatesToKeep.Add(new TemplateNextRunResultDto(
|
||||||
Id: template.Id,
|
Id: template.Id,
|
||||||
NextRun: template.NextRun.Value.ToOffset(TimeSpan.Zero), // ← В UTC!
|
NextRun: newNextRun,
|
||||||
NextRunOld: template.NextRun
|
NextRunOld: template.NextRun
|
||||||
));
|
));
|
||||||
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun} (UTC), т.к. нагрузка в день не превышена.", template.Id, template.NextRun.Value.ToOffset(TimeSpan.Zero));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = expectedCount; i < currentCount; i++)
|
for (int i = expectedCount; i < currentCount; i++)
|
||||||
@@ -418,9 +520,9 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region 9. Добавляем шаблоны, у которых не было даты — тоже нужно распределить
|
#region 9. Добавляем шаблоны, у которых не было даты — тоже нужно распределить
|
||||||
|
|
||||||
foreach (var template in allTemplates)
|
foreach (var template in allTemplates)
|
||||||
@@ -777,16 +879,7 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// Если момент времени уже прошёл, ищет следующий рабочий день в будущем с учётом нагрузки.
|
/// Если момент времени уже прошёл, ищет следующий рабочий день в будущем с учётом нагрузки.
|
||||||
/// В случае ошибки (нет будущих рабочих дней) возвращает ошибку через out-параметр.
|
/// В случае ошибки (нет будущих рабочих дней) возвращает ошибку через out-параметр.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private DateTimeOffset? AdjustNextRunToFuture(
|
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)
|
||||||
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;
|
errorResult = null;
|
||||||
|
|
||||||
@@ -1103,7 +1196,7 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
/// <param name="end"></param>
|
/// <param name="end"></param>
|
||||||
/// <param name="excludeWeekends">Исключить выходные и праздники</param>
|
/// <param name="excludeWeekends">Исключить выходные и праздники</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
|
private async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
|
||||||
{
|
{
|
||||||
// Получаем список выходных/праздничных дней
|
// Получаем список выходных/праздничных дней
|
||||||
var weekends = new HashSet<DateOnly>();
|
var weekends = new HashSet<DateOnly>();
|
||||||
@@ -1130,5 +1223,44 @@ namespace PARR.DAL.NextRunServices.Subservices
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает рабочие дни с автоматическим расширением периода при их отсутствии.
|
||||||
|
/// Расширяет период до maxPeriodExpansions раз: _startDate + duration * N (N = 1, 2, 3)
|
||||||
|
/// </summary>
|
||||||
|
private async Task<(DateOnly endDate, List<DateOnly> workDays)> GetWorkDaysWithExpansionAsync(DateOnly startDate, int duration, bool excludeWeekends)
|
||||||
|
{
|
||||||
|
// Попытка 0: исходный период [_startDate, _startDate + duration - 1]
|
||||||
|
var currentEndDate = startDate.AddDays(duration - 1);
|
||||||
|
var workDays = await GetWorkDaysAsync(startDate, currentEndDate, excludeWeekends);
|
||||||
|
|
||||||
|
if (workDays.Count > 0)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Найдено {count} рабочих дней в исходном периоде [{start}, {end}]", workDays.Count, startDate, currentEndDate);
|
||||||
|
return (currentEndDate, workDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Попытки расширения 1..maxPeriodExpansions
|
||||||
|
for (int attempt = 1; attempt <= maxPeriodExpansions; attempt++)
|
||||||
|
{
|
||||||
|
//расширяем период относительно ПРЕДЫДУЩЕГО конца
|
||||||
|
currentEndDate = currentEndDate.AddDays(duration);
|
||||||
|
|
||||||
|
logger.LogWarning("Попытка {attempt}/{maxAttempts} расширения периода: нет рабочих дней в [{start}, {end}]. Расширяем до {newEnd}.", attempt, maxPeriodExpansions, startDate, startDate.AddDays(duration - 1), currentEndDate);
|
||||||
|
|
||||||
|
workDays = await GetWorkDaysAsync(startDate, currentEndDate, excludeWeekends);
|
||||||
|
|
||||||
|
if (workDays.Count > 0)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Рабочие дни найдены после {attempt} попытки расширения. Период: [{start}, {end}], кол-во рабочих дней: {count}", attempt, startDate, currentEndDate, workDays.Count);
|
||||||
|
return (currentEndDate, workDays);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Все попытки исчерпаны — рабочих дней нет
|
||||||
|
logger.LogError("После {maxAttempts} попыток расширения рабочие дни не найдены в периоде [{start}, {end}]. Возвращаем ошибку.", maxPeriodExpansions, startDate, currentEndDate);
|
||||||
|
|
||||||
|
return (currentEndDate, new List<DateOnly>()); // Пустой список для обработки ошибки вызывающим кодом
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using PARR.DAL.DomainServices.Shortcodes;
|
||||||
using PARR.DAL.DomainServices.Shortcodes;
|
|
||||||
using PARR.DAL.NextRunServices;
|
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;
|
|
||||||
|
|
||||||
namespace PARR.Test.NextRun
|
namespace PARR.Test.NextRun
|
||||||
{
|
{
|
||||||
@@ -36,22 +33,22 @@ namespace PARR.Test.NextRun
|
|||||||
|
|
||||||
public async Task Test()
|
public async Task Test()
|
||||||
{
|
{
|
||||||
var duration = 10;
|
var duration = 2;
|
||||||
var offset = TimeSpan.FromHours(10);
|
var offset = TimeSpan.FromHours(3);
|
||||||
|
|
||||||
var startDate = new DateOnly(2026, 2, 10);
|
var startDate = new DateOnly(2026, 2, 10);
|
||||||
var referenceDate = new DateTimeOffset(2026, 2, 12, 23, 0, 0, TimeSpan.Zero);
|
var referenceDate = new DateTimeOffset(2026, 2, 13, 23, 0, 0, TimeSpan.Zero);
|
||||||
|
|
||||||
var templates = GetTemplates();
|
var templates = GetTemplates();
|
||||||
var templatesEmpty = new List<TemplateNextRunDto>();
|
var templatesEmpty = new List<TemplateNextRunDto>();
|
||||||
var templatesToHandeler = templates; // templatesEmpty
|
var templatesToHandeler = templatesEmpty; // templatesEmpty // templates
|
||||||
|
|
||||||
// распределить шаблоны
|
// распределить шаблоны
|
||||||
//var distributedTemplates = await templateDistributorV2.DistributeTemplatesAsync(startDate, duration, referenceDate, offset, templates, true);
|
var distributedTemplates = await templateDistributorV2.DistributeTemplatesAsync(startDate, duration, referenceDate, offset, templates, true);
|
||||||
|
|
||||||
// получить актуальный nextRun
|
// получить актуальный nextRun
|
||||||
var targetTemplate = new TemplateNextRunDto(Guid.Parse("DA151719-2742-4260-BFFE-012B61591053"), new DateTimeOffset(2026, 2, 14, 23, 30, 0, TimeSpan.Zero));
|
var targetTemplate = new TemplateNextRunDto(Guid.Parse("DA151719-2742-4260-BFFE-012B61591053"), new DateTimeOffset(2026, 2, 15, 23, 30, 0, TimeSpan.Zero));
|
||||||
var validNextRun = await templateDistributorV2.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesToHandeler, true, isNew: false);
|
//var validNextRun = await templateDistributorV2.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesToHandeler, 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-ГВЦ");
|
||||||
@@ -169,7 +166,7 @@ namespace PARR.Test.NextRun
|
|||||||
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("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("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("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(2026, 2, 13, 4, 0, 0, TimeSpan.Zero)));
|
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("DE7241F1-5C62-4164-A3DC-99B946350E10"), NextRun: new DateTimeOffset(2026, 2, 16, 23, 30, 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("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("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("3259B408-48A3-4995-AF9A-097477ECAB81"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
|
||||||
|
|||||||
Reference in New Issue
Block a user