Files
parr_api/PARR.DAL/NextRunServices/Subservices/TemplateDistributor_old.cs

345 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<TemplateDistributor_old> logger;
private readonly IWeekendDayService weekendDayService;
public TemplateDistributor_old(
ILogger<TemplateDistributor_old> logger,
IWeekendDayService weekendDayService
)
{
this.logger = logger;
this.weekendDayService = weekendDayService;
}
/// <summary>
/// Распределяет все шаблоны на указанный период, учитывая план нагрузки.
/// Возвращает список всех шаблонов с обновлёнными датами выполнения.
/// </summary>
public async Task<List<TemplateNextRunDto>> DistributeTemplatesAsync(
DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, List<TemplateNextRunDto> 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<TemplateNextRunDto>();
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;
}
/// <summary>
/// Возвращает актуальную дату выполнения для одного шаблона, учитывая план нагрузки.
/// Возвращает обновлённый шаблон с новой датой выполнения.
/// </summary>
public async Task<TemplateNextRunDto> GetValidNextRunForTemplateAsync(
DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> 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;
}
/// <summary>
/// Строит план работ на основе списка шаблонов
/// </summary>
private async Task<List<DateOnly>> BuildPlanAsync(
DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, List<TemplateNextRunDto> templates, HashSet<DateOnly> weekends, List<DateOnly> workDays)
{
var totalWorks = templates.Count;
var totalDays = workDays.Count;
if (totalDays == 0) return new List<DateOnly>();
var interval = (double)totalDays / totalWorks;
var plan = new List<DateOnly>();
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();
}
/// <summary>
/// Ищет следующую дату в плане, которая не занята
/// </summary>
private DateOnly? FindNextAvailableDateNotInPlan(List<DateOnly> workDays, DateOnly afterDate, List<DateOnly> plan)
{
var planSet = new HashSet<DateOnly>(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;
}
/// <summary>
/// Ищет следующую доступную дату после указанной, если не находит — ищет назад.
/// </summary>
private DateOnly? FindNextAvailableDate(List<DateOnly> 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;
}
/// <summary>
/// Возвращает список рабочих дней в диапазоне, исключая выходные и праздники.
/// </summary>
private List<DateOnly> GetWorkDays(DateOnly start, DateOnly end, HashSet<DateOnly> weekends)
{
var result = new List<DateOnly>();
var date = start;
while (date <= end)
{
var dayOfWeek = date.DayOfWeek;
if (!weekends.Contains(date))
{
result.Add(date);
}
date = date.AddDays(1);
}
return result;
}
/// <summary>
/// Получить список выходных дней
/// </summary>
private async Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
{
var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
return weekends.ToHashSet();
}
}
}