Files
parr_api/PARR.DAL/TransformServices/EsppScheduleTransformService.cs

514 lines
23 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.Extensions.Logging;
using PARR.Common;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.Extensions;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
namespace PARR.DAL.TransformServices
{
internal class EsppScheduleTransformService : IEsppScheduleTransformService
{
private readonly IEsppSchTypeConfigService esppSchTypeConfigService;
private readonly INextRunModifierService nextRunModifierService;
private readonly ILogger<EsppScheduleTransformService> logger;
private static readonly Dictionary<string, int> monthDict = new Dictionary<string, int>()
{
{ "Январь", 1 },
{ "Февраль", 2 },
{ "Март", 3 },
{ "Апрель", 4 },
{ "Май", 5 },
{ "Июнь", 6 },
{ "Июль", 7 },
{ "Август", 8 },
{ "Сентябрь", 9 },
{ "Октябрь", 10 },
{ "Ноябрь", 11 },
{ "Декабрь", 12 }
};
private static readonly Dictionary<string, int> orderDict = new Dictionary<string, int>()
{
{ "Первый", 1 },
{ "Второй", 2 },
{ "Третий", 3 },
{ "Четвертый", 4 },
{ "Последний", 5 }
};
private static readonly Dictionary<string, int> dwDict = new Dictionary<string, int>()
{
{"Понедельник", 1 },
{"Вторник", 2 },
{"Среда", 3 },
{"Четверг", 4 },
{"Пятница", 5 },
{"Суббота", 6 },
{"Воскресенье", 0 }
};
public EsppScheduleTransformService(
ILogger<EsppScheduleTransformService> logger,
IEsppSchTypeConfigService esppSchTypeConfigService,
INextRunModifierService nextRunModifierService
)
{
this.esppSchTypeConfigService = esppSchTypeConfigService;
this.nextRunModifierService = nextRunModifierService;
this.logger = logger;
}
public async Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate)
{
var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
return GetNextDate(esppSchedule, referenceDate);
}
public DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate)
{
var nextRun = referenceDate;
switch (esppSchedule.TypeSchedule.Id)
{
case (int)EsppSchTypeScheduleEnum.Regularly:
nextRun = GetNextDateRegularly(esppSchedule.Values, referenceDate);
break;
case (int)EsppSchTypeScheduleEnum.Weekly:
nextRun = GetNextDateWeekly(esppSchedule.Values, referenceDate);
break;
case (int)EsppSchTypeScheduleEnum.Monthly:
nextRun = GetNextDateMonthly(esppSchedule.Values, referenceDate);
break;
case (int)EsppSchTypeScheduleEnum.Monthly2:
nextRun = GetNextDateMonthly2(esppSchedule.Values, referenceDate);
break;
case (int)EsppSchTypeScheduleEnum.Annually:
nextRun = GetNextDateAnnually(esppSchedule.Values, referenceDate);
break;
case (int)EsppSchTypeScheduleEnum.Annually2:
nextRun = GetNextDateAnnually2(esppSchedule.Values, referenceDate);
break;
}
//возможно для автораспределения нужно nextRun = nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult();
// тут может быть 21 час по МСК, то это предыдущие сутки, нужно отнять 1 день
if (!DateResolver.IsCurrentDayRelativeMskTime(nextRun.Hour))
nextRun = nextRun.AddDays(-1);
return nextRun;
}
private List<DateTimeOffset> GetNextSchedule(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate)
{
// расписание на оставшееся на сегодня время. его так мало...
referenceDate = referenceDate.UtcDateTime;
var schedules = new List<DateTimeOffset>();
var curTime = referenceDate;
//Если ещё не произошло то добавляем расписание, возможно это новое AiW и он ещё ниразу не запускался.
//lastRun это будущее
if (referenceDate >= DateTimeOffset.UtcNow && referenceDate <= DateTimeOffset.UtcNow.EndOfDay())
schedules.Add(referenceDate);
while (curTime < DateTimeOffset.UtcNow.EndOfDay())
{
switch (esppSchedule.TypeSchedule.Id)
{
case (int)EsppSchTypeScheduleEnum.Regularly:
curTime = GetNextDateRegularly(esppSchedule.Values, curTime);
break;
case (int)EsppSchTypeScheduleEnum.Weekly:
curTime = GetNextDateWeekly(esppSchedule.Values, curTime);
break;
case (int)EsppSchTypeScheduleEnum.Monthly:
curTime = GetNextDateMonthly(esppSchedule.Values, curTime);
break;
case (int)EsppSchTypeScheduleEnum.Monthly2:
curTime = GetNextDateMonthly2(esppSchedule.Values, curTime);
break;
case (int)EsppSchTypeScheduleEnum.Annually:
curTime = GetNextDateAnnually(esppSchedule.Values, curTime);
break;
case (int)EsppSchTypeScheduleEnum.Annually2:
curTime = GetNextDateAnnually2(esppSchedule.Values, curTime);
break;
}
schedules.Add(curTime);
}
schedules.RemoveAll(s => s > DateTimeOffset.UtcNow.EndOfDay());
return schedules.OrderBy(t => t).ToList();
}
public async Task<List<DateTimeOffset>> GetNextScheduleAsync(Guid jobGroupId, DateTimeOffset referenceDate)
{
//TODO: тут не проверен переход через выходные дни!!! Переход не используется, так как тут не рассчитываем NextRun.
//В общем проверить, когда будем тестировать агента, что с датами все ок
var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
var nextSchedule = GetNextSchedule(esppSchedule, referenceDate);
return nextSchedule;
}
private async Task<EsppScheduleDto> GetEsppScheduleAsync(Guid jobGroupId)
{
var esppSchedule = await esppSchTypeConfigService.GetEsppScheduleDtoAsync(jobGroupId);
if (esppSchedule == null)
{
logger.LogError($"Не найдено расписание в БД, jobId: {jobGroupId}");
throw new Exception($"Не найдено расписание в БД, jobId: {jobGroupId}");
}
return esppSchedule;
}
private DateTimeOffset GetNextDateRegularly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
var regDict = new Dictionary<string, int>
{
{"Каждый час",1},
{"Каждые 2 часа",2},
{"Каждые 3 часа", 3},
{"Каждые 4 часа", 4},
{"Каждые 6 часов", 6},
{"Каждые 12 часов", 12},
{"Ежедневно", 24},
{"Каждые 72 часа", 72},
{"Каждые 96 часов", 96},
{"Каждые 2 недели", 336},
{"Каждые 60 дней", 1440},
{"Каждые 80 дней", 1920},
{"Каждые 90 дней", 2160},
{"Каждые полгода", 4380},
{"Каждые 1,5 года", 13140},
{"Каждые 3 года", 26280},
};
if (!regDict.TryGetValue(values[0].Value.Value, out int regNum))
{
logger.LogError("Неизвестное значение: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
var calcDay = referenceDate.AddHours(regNum);
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
if (calcDay < DateTimeOffset.UtcNow)
calcDay = GetNextDateRegularly(values, calcDay.AddHours(regNum));
return calcDay;
}
private DateTimeOffset GetNextDateWeekly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
if (!dwDict.TryGetValue(values[0].Value.Value, out int dwNum))
{
logger.LogError("Неизвестное значение дня недели: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
var calcDay = referenceDate;
if (calcDay < DateTimeOffset.UtcNow)
calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, DateTimeOffset.UtcNow.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
if (((int)calcDay.DayOfWeek) != dwNum)
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if (calcDay < DateTimeOffset.UtcNow)
calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
return calcDay;
}
private DateTimeOffset GetNextDateMonthly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
if (!int.TryParse(values[0].Value.Value, out int dayNum))
{
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
if (referenceDate < DateTimeOffset.UtcNow)
calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if (calcDay < DateTimeOffset.UtcNow)
calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
return calcDay;
}
private DateTimeOffset GetNextDateMonthly2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
{
logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum))
{
logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
}
var startDay = referenceDate < DateTimeOffset.UtcNow
? new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset)
: new DateTimeOffset(referenceDate.Year, referenceDate.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
var daysList = FindAllDaysInMonth(dwNum, startDay);
DateTimeOffset calcDay;
if (orderNum == 5) // Последний
{
calcDay = daysList.Last();
}
else
{
if (orderNum > daysList.Count)
{
logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
}
else
calcDay = daysList[orderNum - 1];
}
// Если дата <= текущей, переходим на следующий месяц
if (calcDay <= DateTimeOffset.UtcNow)
return GetNextDateMonthly2(values, startDay.AddMonths(1));
return calcDay;
}
private DateTimeOffset GetNextDateAnnually(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
if (!monthDict.TryGetValue(values[0].Value.Value, out int monthNum))
{
logger.LogError("Неизвестное значение номера месяца: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
if (!int.TryParse(values[1].Value.Value, out int dayNum))
{
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
var calcDay = new DateTimeOffset(
year, monthNum, dayNum,
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if (calcDay < DateTimeOffset.UtcNow)
calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
return calcDay;
}
private DateTimeOffset GetNextDateAnnually2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
{
var monthADict = new Dictionary<string, int>
{
{ "Января", 1 },
{ "Февраля", 2 },
{ "Марта", 3 },
{ "Апреля", 4 },
{ "Мая", 5 },
{ "Июня", 6 },
{ "Июля", 7 },
{ "Августа", 8 },
{ "Сентября", 9 },
{ "Октября", 10 },
{ "Ноября", 11 },
{ "Декабря", 12 }
};
if (!monthADict.TryGetValue(values[2].Value.Value, out int monthNum))
{
logger.LogError("Неизвестное значение номера месяца: {Value}", values[2].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[2].Value.Value}");
}
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
{
logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum))
{
logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
}
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
year += 1;
var calcDay = new DateTimeOffset(
year, monthNum, 1,
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
List<DateTimeOffset> daysList = FindAllDaysInMonth(dwNum, calcDay);
if (orderNum == 5)//5-последний
calcDay = daysList.Last();
else
calcDay = daysList[orderNum - 1];
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if (calcDay < DateTimeOffset.UtcNow)
calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
return calcDay;
}
private List<DateTimeOffset> FindAllDaysInMonth(int dwNum, DateTimeOffset startDay)
{
var calcDay = startDay;
if (((int)calcDay.DayOfWeek) != dwNum)
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
var daysList = new List<DateTimeOffset>();
var monthNum = calcDay.Month;
while (calcDay.Month == monthNum)
{
daysList.Add(calcDay);
calcDay = calcDay.AddDays(7);
}
return daysList;
}
private DateTimeOffset OffsetToDayOfWeek(int dwNum, DateTimeOffset calcDay)
{
var daysOffset = dwNum - ((int)calcDay.DayOfWeek);
if (daysOffset < 0)
daysOffset = 7 + daysOffset;
return calcDay.AddDays(daysOffset);
}
public DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
{
var nextRun = lastRun;
switch (periodType)
{
case (DistributionPeriodTypeEnum.Day):
nextRun = lastRun.AddDays(ParseInt(distributionPeriod));
break;
case (DistributionPeriodTypeEnum.Month):
nextRun = lastRun.AddMonths(ParseInt(distributionPeriod));
break;
case (DistributionPeriodTypeEnum.Year):
nextRun = lastRun.AddYears(ParseInt(distributionPeriod));
break;
}
return nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult();
}
private DateTimeOffset GetPrevDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
{
switch (periodType)
{
case (DistributionPeriodTypeEnum.Day):
return lastRun.AddDays(-ParseInt(distributionPeriod));
case (DistributionPeriodTypeEnum.Month):
return lastRun.AddMonths(-ParseInt(distributionPeriod));
case (DistributionPeriodTypeEnum.Year):
return lastRun.AddYears(-ParseInt(distributionPeriod));
}
return lastRun;
}
public async Task<DateOnly> GetStartPeriodForDateAsync(Guid jobGroupId, DateTimeOffset date, DateTimeOffset referenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod)
{
var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
var currentStartPeriod = referenceDate;
var currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
while (!(date >= currentStartPeriod && date < currentEndPeriod))
{
if (date > currentEndPeriod)
{
currentStartPeriod = currentEndPeriod;
currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
}
else
{
currentEndPeriod = currentStartPeriod;
currentStartPeriod = GetPrevDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
}
}
return DateOnly.FromDateTime(currentStartPeriod.DateTime);
}
private int ParseInt(string value)
{
try
{
return int.Parse(value);
}
catch (Exception ex)
{
logger.LogError(ex, $"{nameof(this.GetType)}, получение конца периода. Не смог распарсить string в int для значения {value}.");
return 0;
}
}
private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
{
var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
return result;
}
}
}