Merge branch 'next-run-service' into dev

# Conflicts:
#	PARR.DAL/Contracts/SettingsFromDb.cs
This commit is contained in:
Mikhail Trubnikov
2026-02-26 08:42:07 +10:00
37 changed files with 7328 additions and 897 deletions

View File

@@ -1,10 +1,23 @@
namespace PARR.API.Contracts.V1.Requests.BaseRequests
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace PARR.API.Contracts.V1.Requests.BaseRequests
{
/// <summary>
/// Смещение таймзоны относительно клиента
/// </summary>
public class TimeZoneOffsetClient
{
/// <summary>
/// Удалить TimeZoneOffsetHours и не использовать!!!!!! Использовать только в минутах TimeZoneOffsetMinutes
/// </summary>
public int TimeZoneOffsetHours { get; set; } = 0;
/// <summary>
/// Часовая зона клиента, в минутах
/// </summary>
public int TimeZoneOffsetMinutes { get; set; } = 0;
[BindNever]
public TimeSpan TimeZoneOffset => TimeSpan.FromMinutes(TimeZoneOffsetMinutes);
}
}

View File

@@ -23,6 +23,8 @@
public DateTimeOffset ReferenceDate { get; set; }
public int? UserTimeZoneOffsetMinutes { get; set; }
/// <summary>
/// Использовать таймзону рабочей группы ответственного за ЭК шаблона
/// </summary>

View File

@@ -18,6 +18,8 @@
public DateTimeOffset ReferenceDate { get; set; }
public int? UserTimeZoneOffsetMinutes { get; set; }
public bool IsWorkGroupTimezone { get; set; }
public required JobGroupTypeResponse GroupType { get; set; }

View File

@@ -29,7 +29,10 @@
public class StatDistributorItemResponse
{
public string? WorkGroupName { get; set; }
/// <summary>
/// Наименование поля по которому группируется
/// </summary>
public string? GroupFieldName { get; set; }
public int AllCount => Statistics?.Sum(t => t.AllCount) ?? 0;
public int IsActivatedCount => Statistics?.Sum(t => t.IsActivatedCount) ?? 0;
@@ -47,15 +50,19 @@
public int IsActivatedCount { get; set; }
public int IsDeactivatedCount { get; set; }
public int TemplatesInWeekendCount => !IsWorkDay && Templates != null ? Templates.Count : 0;
public List<StatTempleteDistribItem>? Templates { get; set; }
public List<StatTemplateDistribItem>? Templates { get; set; }
}
public class StatTempleteDistribItem
public class StatTemplateDistribItem
{
/// <summary>
/// TemplateId
/// </summary>
public Guid Id { get; set; }
public required string Name { get; set; }
public DateTimeOffset NextRun { get; set; }
public bool IsActiveTemplate { get; set; }
public bool IsActiveSchedular { get; set; }
public required ScheduleResponseAreaTimeOffsetResponse ResponseAreaOffset { get; set; }
//public required ScheduleResponseAreaTimeOffsetResponse ResponseAreaOffset { get; set; }
}
}

View File

@@ -45,7 +45,7 @@
public bool IsAutoDistributionEnabled { get; set; }
public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; }
//public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; }
}

View File

@@ -39,7 +39,6 @@ namespace PARR.API.Controllers.V1
private readonly IEsppSchTypeConfigService esppConfigService;
private readonly IValidator<JobGroupRequest> validator;
private readonly IJobGroupTypeService jobGroupTypeService;
private readonly SettingsFromDb settingsFromDb;
private readonly IMatchingStatusService matchingStatusService;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
@@ -52,7 +51,6 @@ namespace PARR.API.Controllers.V1
IEsppSchTypeConfigService esppConfigService,
IValidator<JobGroupRequest> validator,
IJobGroupTypeService jobGroupTypeService,
SettingsFromDb settingsFromDb,
IMatchingStatusService matchingStatusService,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
)
@@ -65,7 +63,6 @@ namespace PARR.API.Controllers.V1
this.esppConfigService = esppConfigService;
this.validator = validator;
this.jobGroupTypeService = jobGroupTypeService;
this.settingsFromDb = settingsFromDb;
this.matchingStatusService = matchingStatusService;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
}
@@ -168,6 +165,7 @@ namespace PARR.API.Controllers.V1
Solution = request.Solution.Trim(),
TemplateDuration = request.TemplateDuration.Trim(),
ReferenceDate = request.ReferenceDate,
UserTimeZoneOffsetMinutes = request.UserTimeZoneOffsetMinutes,
ScheduleExcludeTypeId = request.ScheduleExcludeTypeId,
ScheduleExcludeTypeCalendarId = request.ScheduleExcludeTypeCalendarId,
IsAutoDistributionEnabled = request.IsAutoDistributionEnabled,
@@ -262,6 +260,7 @@ namespace PARR.API.Controllers.V1
orig.Solution = request.Solution.Trim();
orig.TemplateDuration = request.TemplateDuration.Trim();
orig.ReferenceDate = request.ReferenceDate;
orig.UserTimeZoneOffsetMinutes = request.UserTimeZoneOffsetMinutes;
orig.ScheduleExcludeTypeId = request.ScheduleExcludeTypeId;
orig.ScheduleExcludeTypeCalendarId = request.ScheduleExcludeTypeCalendarId;
orig.IsAutoDistributionEnabled = request.IsAutoDistributionEnabled;

View File

@@ -31,7 +31,7 @@ namespace PARR.API.Controllers.V1
private readonly IClientService clientService;
private readonly IRobotHistoryService robotHistoryService;
private readonly IShortcodesService shortcodesService;
private readonly INextRunService nextRunService;
private readonly INextRunServiceV2 nextRunService;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
public RobotTaskController(
@@ -43,7 +43,7 @@ namespace PARR.API.Controllers.V1
IClientService clientService,
IRobotHistoryService robotHistoryService,
IShortcodesService shortcodesService,
INextRunService nextRunService,
INextRunServiceV2 nextRunService,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
)
{
@@ -156,11 +156,6 @@ namespace PARR.API.Controllers.V1
// сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
#region Так нельзя делать!!! Должно всегда сортироваться по NextRun и не важно какой тип у шаблона!!!
// Сортируем сначала по StatusTypeId, т е те что used, будут первыми, потом сортируем по NextRun, чтобы ближайшие даты выполнились скорее
//query = query.OrderBy(t => t.Template!.StatusTypeId).ThenBy(t => t.Template!.NextRun);
#endregion
RobotConfiguration? task = null;
//ищем задание в ожидании, если нашли, выбираем его
@@ -213,20 +208,35 @@ namespace PARR.API.Controllers.V1
}
case RobotsEnum.ScheduleOrder:
{ // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
await UpdateLastNextRunDate(task);
var resultUpdateNextRun = await UpdateNextRunAsync(task);
if (!resultUpdateNextRun)
{
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
}
//RobotTaskScheduleResponse
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
robotTaskScheduleResponse.Timezone = GetTimezoneForTemplate(task.Template!);
robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
var nextRunWithTimezone = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
//var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
//nextRun в часовой зоне УЗ Робота ЕСПП
var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithTimezone);
robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithTimezone);
//на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
{
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
}
robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
}
@@ -243,54 +253,36 @@ namespace PARR.API.Controllers.V1
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
private async Task UpdateLastNextRunDate(RobotConfiguration task)
private async Task<bool> UpdateNextRunAsync(RobotConfiguration task)
{
var template = task.Template!;
#region old
// передаем LastRun, если его нет, то NextRun
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun);
// всегда считаем по nextRun
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template.NextRun);
#endregion
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
if (!nextRun.HasValue)
{
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
return false;
}
if (nextRun.Value < DateTimeOffset.UtcNow)
{
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
return false;
}
if (nextRun != template.NextRun)
{
logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
template.LastRun = template.NextRun;
template.NextRun = nextRun;
template.NextRun = nextRun.Value;
await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
}
#region Old, до выноса lastRun, nextRun в templates
//var appInWorks = task.Template!.ApplicationsInWork!;
//// передаем LastRun, если его нет, то NextRun
////var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun);
//// всегда считаем по nextRun
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.NextRun);
//if (nextRun != appInWorks.NextRun)
//{
// logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю nextRun, новое значение {nextRun}, старое значение {appInWorks.NextRun}");
// if (appInWorks.NextRun < DateTimeOffset.UtcNow)
// {
// logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю lastRun, новое значение {appInWorks.NextRun}, старое значение {appInWorks.LastRun}");
// appInWorks.LastRun = appInWorks.NextRun;
// }
// appInWorks.NextRun = nextRun;
// await robotConfigurationService.CommitAsync();
//}
#endregion
return true;
}
@@ -322,33 +314,8 @@ namespace PARR.API.Controllers.V1
if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
return false;
return true;
}
/// <summary>
/// Получить таймзону для шаблона
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private string GetTimezoneForTemplate(Template template)
{
if (template.Job?.Group?.IsResponseAreaTimezone != true)
return scheduleResponseAreaTimeOffsetService.GetDefault.EsppValue;
var responseArea = template.Unit?.BaseFields?.ResponseArea;
if (string.IsNullOrEmpty(responseArea))
{
throw new InvalidOperationException(
$"У шаблона Id={template.Id}, Name='{template.Name}' не задана ResponseArea в Unit.BaseFields, " +
"но включена настройка 'использовать часовой пояс рабочей группы'.");
}
return scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).EsppValue;
}
}
}

View File

@@ -29,7 +29,7 @@ namespace PARR.API.Controllers.V1.Statistics
private readonly IJobGroupService jobGroupService;
private readonly IMapper mapper;
private readonly IShortcodesService shortcodesService;
private readonly INextRunService nextRunService;
private readonly INextRunServiceV2 nextRunService;
private readonly ILogger<StatTemplateDistributionController> logger;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
@@ -38,7 +38,7 @@ namespace PARR.API.Controllers.V1.Statistics
IJobGroupService jobGroupService,
IMapper mapper,
IShortcodesService shortcodesService,
INextRunService nextRunService,
INextRunServiceV2 nextRunService,
ILogger<StatTemplateDistributionController> logger,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
)
@@ -63,8 +63,6 @@ namespace PARR.API.Controllers.V1.Statistics
{
// eadc5498-dba6-4f10-9b4b-a1653e3c3e61
//TODO: похоже что timeZoneQuery лишняя
var jobGroup = await jobGroupService.Get()
.Include(t => t.DistributionConfig)
.ThenInclude(t => t.DistributionPeriod)
@@ -95,65 +93,86 @@ namespace PARR.API.Controllers.V1.Statistics
ItemsList = null
}, true));
var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffsetHours);
var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Min(t => t.Template.NextRun), items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffset);
// получаем список рабочих дней
// получет все дни, даже с выходными, чтоб видеть реальную картину
var workDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false, jobGroup.ReferenceDate);
var allDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false);
// получить список выходных дней (показываем только если IsExcludeWeekends = true)
var weekends = jobGroup.DistributionConfig.IsExcludeWeekends
? await nextRunService.GetWeekendsAsync(dateStart, dateEnd, jobGroup.ReferenceDate)
? await nextRunService.GetWeekendsAsync(dateStart, dateEnd)
: new HashSet<DateOnly>();
var responseItemsList = new List<StatDistributorItemResponse>();
if (jobGroup.DistributionConfig.IsGroupingByWorkGroup)
{
// тут группируем по рабочим группам
logger.LogDebug("Группируем по рабочим группам");
// Получаем список пар (WorkGroupName, Template)
var templatesWithWorkGroupNames = new List<(string WorkGroupName, (Template Template, string ResponseArea))>();
var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>();
// получаем названия рабочих групп и ЗО
// получаем названия рабочих групп
foreach (var item in items)
{
var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroupMask, item.Template);
var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template);
templatesWithWorkGroupNames.Add((workGroupName, (item.Template, responseArea)));
templatesWithWorkGroupNames.Add((workGroupName, item.Template));
}
// Группируем по WorkGroupName
var grouped = templatesWithWorkGroupNames
.GroupBy(x => x.WorkGroupName)
.ToDictionary(g => g.Key, g => g.Select(x => x.Item2).ToList());
.ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList());
foreach (var groupedItem in grouped)
{
var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate);
var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
responseItemsList.Add(statResult);
}
}
else if (jobGroup.IsResponseAreaTimezone && !jobGroup.DistributionConfig.IsGroupingByWorkGroup)
{
// грппируем только по ЗО
logger.LogDebug("Группируем по ЗО");
// Будет группироваться по EsppValue, по MSK, MSK+1...
// получаем названия ЗО, из ЗО часовой пояс, формируем список шаблонов с часовым поясом ЗО
var templatesWithTimeZone = new List<(Template Template, string MskTimeZone)>();
foreach (var item in items)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template);
var mskTimeZone = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea);
templatesWithTimeZone.Add((item.Template, mskTimeZone.EsppValue));
}
// Группируем по часовому поясу
var grouped = templatesWithTimeZone
.GroupBy(x => x.MskTimeZone)
.ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList());
foreach (var groupedItem in grouped)
{
var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
responseItemsList.Add(statResult);
}
}
else
{
// не нужно группировать по рабочим группам
// получаем названия ЗО
var templatesWithResponseArea = new List<(Template Template, string ResponseArea)>();
foreach (var item in items)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template);
templatesWithResponseArea.Add((item.Template, responseArea));
}
// не нужно группировать
logger.LogDebug("Без группировки");
// сразу формируем response
responseItemsList.Add(GetResponseByWorkGroup(null, templatesWithResponseArea, workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate));
responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset));
}
var response = new StatTemplateDistributorResponse
{
JobGroup = mapper.Map<JobGroupWithDistributionConfigResponse>(jobGroup),
ItemsList = responseItemsList.OrderBy(t => t.WorkGroupName).ToList()
ItemsList = responseItemsList.OrderBy(t => t.GroupFieldName).ToList()
};
return Ok(new Response<StatTemplateDistributorResponse>(response, true));
@@ -163,29 +182,33 @@ namespace PARR.API.Controllers.V1.Statistics
/// Получить дату начала и конца периода
/// </summary>
/// <param name="jobGroup"></param>
/// <param name="minNextRunTemplate"></param>
/// <param name="maxNextRunTemplate"></param>
/// <param name="timeZoneOffsetHours"></param>
/// <param name="clientOffset"></param>
/// <returns></returns>
private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset maxNextRunTemplate, int timeZoneOffsetHours)
private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset minNextRunTemplate, DateTimeOffset maxNextRunTemplate, TimeSpan clientOffset)
{
var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!);
var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.AddHours(timeZoneOffsetHours));
var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Add(clientOffset));
// последний день, не может быть меньше чем durationDays
var dateEnd = dateStart.AddDays(durationDays);
// последний день, не может быть меньше чем refDate+durationDays
var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.AddHours(timeZoneOffsetHours).AddDays(durationDays).Date);
var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset).AddDays(durationDays).Date);
if (dateEnd < sumRefDuration)
dateEnd = sumRefDuration;
// с DateEnd вообще какая-то шурпатня :(
// как наглядно понять ок не ок распределяется, если на каком-то графике dateEnd может ухеать за период распределения
// последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate
var lastTempalteDate = DateOnly.FromDateTime(maxNextRunTemplate.AddHours(timeZoneOffsetHours).Date);
if (dateEnd < lastTempalteDate)
dateEnd = lastTempalteDate;
var lastTemplateDate = DateOnly.FromDateTime(maxNextRunTemplate.Add(clientOffset).Date);
if (dateEnd < lastTemplateDate)
dateEnd = lastTemplateDate;
// Дата начала, не может быть меньше чем minNextRunTemplate
var minNextRunDate = DateOnly.FromDateTime(minNextRunTemplate.Add(clientOffset).Date);
if (dateStart > minNextRunDate)
dateStart = minNextRunDate;
return (dateStart, dateEnd);
}
@@ -193,49 +216,49 @@ namespace PARR.API.Controllers.V1.Statistics
/// <summary>
/// Заполнить респонс
/// </summary>
/// <param name="workGroupName"></param>
/// <param name="groupFieldName"></param>
/// <param name="templateList"></param>
/// <param name="workDays"></param>
/// <param name="timeZoneOffsetHours"></param>
/// <param name="allDays"></param>
/// <param name="weekends"></param>
/// <param name="clientOffset"></param>
/// <returns></returns>
private StatDistributorItemResponse GetResponseByWorkGroup(string? workGroupName, List<(Template Template, string ResponseArea)> templateList, List<DateOnly> workDays, HashSet<DateOnly> weekends, int timeZoneOffsetHours, DateTimeOffset referenceDate)
private StatDistributorItemResponse GetResponseByWorkGroup(string? groupFieldName, List<Template> templateList, List<DateOnly> allDays, HashSet<DateOnly> weekends, TimeSpan clientOffset)
{
var result = new StatDistributorItemResponse
{
WorkGroupName = workGroupName,
Statistics = workDays.OrderBy(t => t).Select(t => new StatTemplateDistributorDateStat
GroupFieldName = groupFieldName,
Statistics = allDays.OrderBy(t => t).Select(t => new StatTemplateDistributorDateStat
{
AllCount = 0,
Date = new DateTimeOffset(t.Year, t.Month, t.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset),
// вычтем clientOffset, чтоб когда на клиенте он прибавился обратно, дата стала верной для клиента
Date = new DateTimeOffset(t.Year, t.Month, t.Day, 0, 0, 0, TimeSpan.Zero).Add(-clientOffset),
IsWorkDay = !weekends.Contains(t),
IsActivatedCount = 0,
IsDeactivatedCount = 0,
Templates = new List<StatTempleteDistribItem>()
Templates = new List<StatTemplateDistribItem>()
}).ToList()
};
//foreach (var templatesFromDate in templates.GroupBy(t => DateOnly.FromDateTime(t.NextRun.Date)))
// смещаем по часовой зоне и группируем по дате
//foreach (var templatesFromDate in templates.GroupBy(t => DateOnly.FromDateTime(t.NextRun.AddHours(timeZoneOffsetHours).Date)))
foreach (var templatesFromDate in templateList.GroupBy(t => DateOnly.FromDateTime(t.Template.NextRun.AddHours(timeZoneOffsetHours).Date)))
// смещаем по часовой зоне клиента и группируем по дате
foreach (var templatesFromDate in templateList.GroupBy(t => DateOnly.FromDateTime(t.NextRun.Add(clientOffset).Date)))
{
var statisticsForDate = result.Statistics.FirstOrDefault(t => DateOnly.FromDateTime(t.Date.AddHours(timeZoneOffsetHours).Date) == templatesFromDate.Key);
var statisticsForDate = result.Statistics.FirstOrDefault(t => DateOnly.FromDateTime(t.Date.Add(clientOffset).Date) == templatesFromDate.Key);
if (statisticsForDate == null)
{
logger.LogWarning("В списке рабочих дней, нет дня для шаблонов {count} шт. выполняющихся в {date}", templatesFromDate.Count(), templatesFromDate.Key);
logger.LogWarning("В списке дней, нет дня для шаблонов {count} шт. выполняющихся в {date}", templatesFromDate.Count(), templatesFromDate.Key);
continue;
}
statisticsForDate.AllCount = templatesFromDate.Count();
statisticsForDate.IsDeactivatedCount = templatesFromDate.Count(t => !t.Template.IsActiveTemplate || !t.Template.IsActiveSchedule);
statisticsForDate.IsActivatedCount = templatesFromDate.Count(t => t.Template.IsActiveTemplate && t.Template.IsActiveSchedule);
statisticsForDate.Templates = templatesFromDate.Select(t => new StatTempleteDistribItem
statisticsForDate.IsDeactivatedCount = templatesFromDate.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule);
statisticsForDate.IsActivatedCount = templatesFromDate.Count(t => t.IsActiveTemplate && t.IsActiveSchedule);
statisticsForDate.Templates = templatesFromDate.Select(t => new StatTemplateDistribItem
{
Name = t.Template.Name,
IsActiveSchedular = t.Template.IsActiveSchedule,
IsActiveTemplate = t.Template.IsActiveTemplate,
NextRun = t.Template.NextRun,
ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(t.ResponseArea))
Id = t.Id,
Name = t.Name,
IsActiveSchedular = t.IsActiveSchedule,
IsActiveTemplate = t.IsActiveTemplate,
NextRun = t.NextRun
})
.OrderBy(t => t.Name)
.ToList();

View File

@@ -17,7 +17,6 @@ using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.NextRunServices;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Schedule;
@@ -36,7 +35,6 @@ namespace PARR.API.Controllers.V1
private readonly ILogger<TemplateController> logger;
private readonly IShortcodesService shortcodesService;
private readonly IOrderService orderService;
private readonly INextRunService nextRunService;
private readonly SettingsFromDb settingsFromDb;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
@@ -48,7 +46,6 @@ namespace PARR.API.Controllers.V1
ILogger<TemplateController> logger,
IShortcodesService shortcodesService,
IOrderService orderService,
INextRunService nextRunService,
SettingsFromDb settingsFromDb,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
)
@@ -60,7 +57,6 @@ namespace PARR.API.Controllers.V1
this.logger = logger;
this.shortcodesService = shortcodesService;
this.orderService = orderService;
this.nextRunService = nextRunService;
this.settingsFromDb = settingsFromDb;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
}
@@ -138,7 +134,7 @@ namespace PARR.API.Controllers.V1
{
//await ApplyTemplateShortcodesAsync(responseItem, templates.First(t => t.Id == responseItem.Id));
await ApplyTemplateShortcodesAsync(responseItem, templatesDict[responseItem.Id]);
FillResponseAreaOffset(responseItem);
//FillResponseAreaOffset(responseItem);
}
//logger.LogDebug("Получение шорткодов: {ElapsedMs} мс", sw.ElapsedMilliseconds);
//sw.Stop();
@@ -183,7 +179,7 @@ namespace PARR.API.Controllers.V1
var response = mapper.Map<TemplateResponse>(template);
await ApplyTemplateShortcodesAsync(response, template);
FillResponseAreaOffset(response);
//FillResponseAreaOffset(response);
var ordersCountResult = await GetOrdersCountAsync(new List<Guid> { response.Id });
response.OrderCount = ordersCountResult.Count > 0 ? ordersCountResult.First().Value : 0;
@@ -277,7 +273,7 @@ namespace PARR.API.Controllers.V1
var response = mapper.Map<TemplateListResponse>(templateToResponse);
await ApplyTemplateShortcodesAsync(response, templateToResponse);
FillResponseAreaOffset(response);
//FillResponseAreaOffset(response);
var ordersCountResult = await GetOrdersCountAsync(new List<Guid> { response.Id });
response.OrderCount = ordersCountResult.Count > 0 ? ordersCountResult.First().Value : 0;
@@ -323,36 +319,36 @@ namespace PARR.API.Controllers.V1
}
/// <summary>
/// Заполнить ResponseAreaOffset
/// </summary>
/// <param name="response"></param>
private void FillResponseAreaOffset(TemplateBaseResponse response)
{
// если стоит галка IsResponseAreaTimezone и есть ЗО, то возвращаем оффсет
if (response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea))
{
var responseArea = response.ResponseArea;
response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
}
else
{
response.ResponseAreaOffset = null;
}
///// <summary>
///// Заполнить ResponseAreaOffset
///// </summary>
///// <param name="response"></param>
//private void FillResponseAreaOffset(TemplateBaseResponse response)
//{
// // если стоит галка IsResponseAreaTimezone и есть ЗО, то возвращаем оффсет
// if (response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea))
// {
// var responseArea = response.ResponseArea;
// response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
// }
// else
// {
// response.ResponseAreaOffset = null;
// }
//var responseArea = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
// ? response.ResponseArea
// : settingsFromDb.DefaultResponseAreaToTimeOffset;
// //var responseArea = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
// // ? response.ResponseArea
// // : settingsFromDb.DefaultResponseAreaToTimeOffset;
//response.NextRunResponseAreaInLocal = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
// ? nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, response.ResponseArea)
// // возвращаем в дефолтной зоне
// : nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, settingsFromDb.DefaultResponseAreaToTimeOffset);
// //response.NextRunResponseAreaInLocal = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
// // ? nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, response.ResponseArea)
// // // возвращаем в дефолтной зоне
// // : nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, settingsFromDb.DefaultResponseAreaToTimeOffset);
//response.NextRunResponseAreaInLocal = nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, responseArea);
// //response.NextRunResponseAreaInLocal = nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, responseArea);
//response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
}
// //response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
//}
}
}

View File

@@ -1,5 +1,6 @@
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using PARR.API.Contracts.V1.Requests;
using PARR.DAL.Contracts;
using PARR.DAL.Services.Interfaces;
@@ -123,6 +124,14 @@ namespace PARR.API.Validators
return true;
}).WithMessage("Некорректное значение периода распределения");
RuleFor(t => t.UserTimeZoneOffsetMinutes)
.Must((entity, value, c) =>
{
// если IsResponseAreaTimezone == true, то поле UserTimeZoneOffsetMinutes обязательно.
// если IsResponseAreaTimezone == false, то UserTimeZoneOffsetMinutes должно быть null
return (entity.IsWorkGroupTimezone && value != null) || (!entity.IsWorkGroupTimezone && value == null);
}).WithMessage("Некорректное значение."); ;
}
}
}

View File

@@ -189,7 +189,7 @@ namespace PARR.DAL.Context
new { Name = nameof(SettingsFromDb.TemplatePrefixName), Description = "Префикс имени шаблона в ЕСПП", Value = "%PREFIX%-ЭИТИ-ПТК-ПАРР" },
new { Name = nameof(SettingsFromDb.RobotAttemptsNumber), Description = "Количество попыток выполнения задания роботом", Value = "3" },
new { Name = nameof(SettingsFromDb.RobotWaitTime), Description = "Время ожидания выполнения роботом задания", Value = "00:15:00" },
//new { Name = nameof(SettingsFromDb.ScheduleTimezone), Description = "Расписание регламентной работы - В каком часовом поясе", Value = "MSK" },
new { Name = nameof(SettingsFromDb.EsppScheduleTimezone), Description = "Расписание регламентной работы - В каком часовом поясе", Value = "MSK" },
//new { Name = nameof(SettingsFromDb.ScheduleExcludeType), Description = "Расписание регламентной работы - Тип исключения", Value = "Выполнить ТОЛЬКО В указанном календаре" },
//new { Name = nameof(SettingsFromDb.ScheduleExcludeCalendar), Description = "Расписание регламентной работы - Календарь", Value = "8x5 (8.00-17.00)" },
new { Name = nameof(SettingsFromDb.ScheduleRepeatRange), Description = "Расписание регламентной работы - Диапазн повторов", Value = "Отсутствует дата завершения" },

View File

@@ -76,6 +76,21 @@ namespace PARR.DAL.Contracts
/// </summary>
public TimeSpan RobotWaitTime { get; set; }
/// <summary>
/// Расписание регламентной работы, В каком часовом поясе
/// </summary>
public string EsppScheduleTimezone { get; set; } = string.Empty;
///// <summary>
///// Расписание регламентной работы - Тип исключения
///// </summary>
//public string ScheduleExcludeType { get; set; } = string.Empty;
///// <summary>
///// Расписание регламентной работы - Календарь
///// </summary>
//public string ScheduleExcludeCalendar { get; set; } = string.Empty;
/// <summary>
/// Расписание регламентной работы - Диапазон повторов
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PARR.DAL.Migrations
{
/// <inheritdoc />
public partial class tblJobGroupAddUserTimeZoneOffsetMinutes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UserTimeZoneOffsetMinutes",
schema: "job",
table: "Groups",
type: "integer",
nullable: true,
comment: "Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UserTimeZoneOffsetMinutes",
schema: "job",
table: "Groups");
}
}
}

View File

@@ -1702,6 +1702,10 @@ namespace PARR.DAL.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<int?>("UserTimeZoneOffsetMinutes")
.HasColumnType("integer")
.HasComment("Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate");
b.HasKey("Id");
b.HasIndex("GroupTypeId");

View File

@@ -92,6 +92,21 @@ namespace PARR.DAL.Models.Job
public DateTimeOffset ReferenceDate { get; set; }
/// <summary>
/// Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate
/// </summary>
[Comment("Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate")]
public int? UserTimeZoneOffsetMinutes { get; set; }
/// <summary>
/// Смещение часового пояса пользователя относительно UTC на момент ReferenceDate, рассчитывается из поля UserTimeZoneOffsetMinutes
/// </summary>
[NotMapped]
public TimeSpan? UserTimeZoneOffset =>
UserTimeZoneOffsetMinutes.HasValue
? TimeSpan.FromMinutes(UserTimeZoneOffsetMinutes.Value)
: null;
/// <summary>
/// Использовать таймзону рабочей группы ответственного за ЭК шаблона
/// </summary>

View File

@@ -72,12 +72,12 @@ namespace PARR.DAL.NextRunServices
/// <returns></returns>
DateTimeOffset GetNextRunWithTimezoneEsppAndResponseArea(DateTimeOffset nextRun, bool? isResponseAreaTimezone = null, string? responseArea = null);
/// <summary>
/// Получить NextRun согласно таймзоны ЗО
/// </summary>
/// <param name="nextRun"></param>
/// <param name="responseArea"></param>
/// <returns></returns>
DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea);
///// <summary>
///// Получить NextRun согласно таймзоны ЗО
///// </summary>
///// <param name="nextRun"></param>
///// <param name="responseArea"></param>
///// <returns></returns>
//DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea);
}
}

View File

@@ -0,0 +1,66 @@
using PARR.Constants;
using PARR.DAL.Models.Job;
using PARR.DAL.NextRunServices.Models;
namespace PARR.DAL.NextRunServices
{
public interface INextRunServiceV2
{
/// <summary>
/// Распределить шаблоны в группе работ (получить список распределенных шаблонов с актуальными nextRun)
/// </summary>
/// <param name="jobGroupId">ИД группы работ</param>
/// <param name="templateStatusType">Статус шаблона, если не передать, берутся все шаблоны группы</param>
/// <returns>Список TemplateId с NextRun и NextRunOld</returns>
Task<List<TemplateNextRunResultDto>?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId, TemplateStatusTypeEnum? templateStatusType);
/// <summary>
/// Получить nextRun для вновь создаваемого шаблона, которого еще нет в БД
/// </summary>
/// <param name="jobGroupId">ИД группы работ</param>
/// <param name="workGroupName">Рабочая группа, не маска</param>
/// <param name="responseAreaName">Зона ответственности, не маска</param>
/// <returns>NextRun или null, если null - то ошибка</returns>
Task<DateTimeOffset?> GetNextRunForNewTemplateAsync(Guid jobGroupId, string workGroupName, string responseAreaName);
/// <summary>
/// Получить nextRun по id существующего в БД шаблона
/// </summary>
/// <param name="templateId">ИД шаблона</param>
/// <param name="isNew">При перемещении шаблона в новую для него работу, ставить isNew=true</param>
/// <returns>NextRun или null, если null - то ошибка</returns>
Task<DateTimeOffset?> GetNextRunForTemplateAsync(Guid templateId, bool isNew);
/// <summary>
/// Получить часовой пояс УЗ ЕСПП
/// </summary>
/// <returns></returns>
TimeSpan GetEsppAccountOffset();
/// <summary>
/// Получить продолжительность в днях
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
int GetDurationDays(JobGroupDistributionConfig config);
/// <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);
/// <summary>
/// Получить список выходных дней
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate);
}
}

View File

@@ -153,7 +153,7 @@ namespace PARR.DAL.NextRunServices
public async Task<DateTimeOffset> GetNextRunForNewTemplateAsync(Guid jobGroupId, string responseArea)
{
// определяю это автораспределение или нет, вызываю соответствующий рассчет
// определяю это автораспределение или нет, вызываю соответствующий расчет
var jobGroup = await jobGroupService.Get()
.Include(t => t.DistributionConfig)
@@ -295,6 +295,7 @@ namespace PARR.DAL.NextRunServices
}
#region Ok
/// <summary>
/// Получить продолжительность в днях
@@ -331,6 +332,8 @@ namespace PARR.DAL.NextRunServices
return periodDays;
}
#endregion
/// <summary>
/// Получить дату начала распределения
@@ -438,14 +441,15 @@ namespace PARR.DAL.NextRunServices
//return resultNextRun;
}
public DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea)
{
var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
var resulstNextRun = nextRun.Add(responseAreaOffset);
logger.LogDebug("Смещение nextRun для ЗО {responseArea}, входящий: {nextRun}, смещение: {offset}, результат: {result}", responseArea, nextRun, responseAreaOffset, resulstNextRun);
//// был паблик
//private DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea)
//{
// var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
// var resulstNextRun = nextRun.Add(responseAreaOffset);
// logger.LogDebug("Смещение nextRun для ЗО {responseArea}, входящий: {nextRun}, смещение: {offset}, результат: {result}", responseArea, nextRun, responseAreaOffset, resulstNextRun);
return resulstNextRun;
}
// return resulstNextRun;
//}
/// <summary>

View File

@@ -0,0 +1,833 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.NextRunServices.Models;
using PARR.DAL.NextRunServices.Subservices;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Schedule;
namespace PARR.DAL.NextRunServices
{
internal class NextRunServiceV2 : INextRunServiceV2
{
private readonly ILogger<NextRunServiceV2> logger;
private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly ITemplateDistributorV2 templateDistributor;
private readonly IShortcodesService shortcodesService;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
private readonly SettingsFromDb settingsFromDb;
public NextRunServiceV2(
ILogger<NextRunServiceV2> logger,
ITemplateService templateService,
IJobGroupService jobGroupService,
IEsppScheduleTransformService esppScheduleTransformService,
ITemplateDistributorV2 templateDistributor,
IShortcodesService shortcodesService,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService,
SettingsFromDb settingsFromDb
)
{
this.logger = logger;
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.templateDistributor = templateDistributor;
this.shortcodesService = shortcodesService;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
this.settingsFromDb = settingsFromDb;
}
public async Task<List<TemplateNextRunResultDto>?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId, TemplateStatusTypeEnum? templateStatusType)
{
logger.LogInformation("Начинаю распределять шаблоны для группы работ {gobGroupId}, templateStatusType: {templateStatusType}", jobGroupId, templateStatusType);
var jobGroup = await jobGroupService.Get()
.AsNoTracking()
.Include(t => t.DistributionConfig)
.ThenInclude(t => t.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == jobGroupId);
if (jobGroup == null)
{
logger.LogError("Не найдена группа работа с id: {id}", jobGroupId);
return null;
}
if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null)
{
logger.LogError("Группа работ id: {id} не подходит для автораспределения, у нее или отсутствуют настройки или не включено автораспределение. IsAutoDistributionEnabled: {IsAutoDistributionEnabled}. Есть конфиг: {DistributionConfig}", jobGroupId, jobGroup.IsAutoDistributionEnabled, jobGroup.DistributionConfig != null);
return null;
}
logger.LogInformation("Параметры распределения. groupId: {groupId}, name: {groupName}, referenceDate: {referenceDate}, " +
"distributionPeriodName: {distributionPeriodName}, distributionPeriodDuration: {distributionPeriodDuration}, " +
"distributionPeriodType: {distributionPeriodType}, IsExcludeWeekends: {IsExcludeWeekends}, IsGroupingByWorkGroup: {IsGroupingByWorkGroup}, IsResponseAreaTimezone: {IsResponseAreaTimezone}",
jobGroupId, jobGroup.GroupName, jobGroup.ReferenceDate, jobGroup.DistributionConfig.DistributionPeriod.Name, jobGroup.DistributionConfig.DistributionPeriod.Duration, jobGroup.DistributionConfig.DistributionPeriod.Type,
jobGroup.DistributionConfig.IsExcludeWeekends, jobGroup.DistributionConfig.IsGroupingByWorkGroup, jobGroup.IsResponseAreaTimezone);
//получаем шаблоны только в статусе Used
var templateQuery = templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.Where(t =>
t.Job!.GroupId == jobGroupId
//&& t.StatusTypeId == TemplateStatusTypeEnum.Used
);
if (templateStatusType.HasValue)
templateQuery = templateQuery.Where(t => t.StatusTypeId == templateStatusType);
var allTemplates = await templateQuery.ToListAsync();
if (!allTemplates.Any())
{
logger.LogInformation("В группе c ИД {jobGroupId} отсутствуют шаблоны в статусе Used", jobGroupId);
return null;
}
logger.LogDebug("Всего шаблонов для распределения в статусе Used: {count} шт.", allTemplates.Count);
// продолжительность в днях
var durationDays = GetDurationDays(jobGroup.DistributionConfig);
// группировать по РГ
var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;
// использовать часовой пояс РГ
var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;
var distributedTemplates = new List<TemplateNextRunResultDto>();
if (isGroupingByWorkGroup)
{
// группировать по РГ
logger.LogDebug("Нужно группировать по РГ");
// получаем для каждого шаблона РГ и сразу группируем по РГ
var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates)
{
var workGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
// Если даже workGroupName == null, ну и ладно, сгруппируем по null, и распределим шаблоны в рамках этого null
if (workGroupName == null)
logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
template.Id, template.Job!.WorkGroupMask, workGroupName);
// Добавляем шаблон в группу
if (!templatesByWorkGroup.TryGetValue(workGroupName, out var templatesList))
{
templatesList = new List<Template>();
templatesByWorkGroup[workGroupName] = templatesList;
}
templatesList.Add(template);
}
logger.LogDebug("Групп для распределения: {count}", templatesByWorkGroup.Count);
var dateStart = GetDateStart();
// Будем распределять каждую РГ отдельно
foreach (var (workGroupName, templates) in templatesByWorkGroup)
{
logger.LogDebug("Готовлюсь распределать шаблоны {templateCount} шт. в РГ '{workGroup}'", templates.Count, workGroupName);
if (isResponseAreaTimeZone)
{
// использовать часовой пояс РГ
logger.LogDebug("Использовать часовой пояс РГ");
// считаем, что у всех шаблонов сгруппированных по РГ, одна ЗО
// получаем ЗО для первого шаблона
var firstTemplate = templates.First();
var responseArea = await shortcodesService.ApplyShortcodesAsync(firstTemplate.Job!.ResponseAreaMask, firstTemplate);
if (responseArea == null)
logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
firstTemplate.Id, firstTemplate.Job!.ResponseAreaMask, responseArea);
var offset = GetOffsetForDistributionByResponseArea(responseArea);
var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
var referenceDate = GetReferenceDate(jobGroup, responseArea);
logger.LogInformation("Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', использую часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
templatesForDistribute.Count, workGroupName, dateStart, offset, referenceDate);
var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
logger.LogDebug("Закончил распределение для РГ '{workGroupName}', referenceDate: {referenceDate}, часового пояса ЗО {responseArea}, utcOffset: {offset}, распределил шаблонов: {count} шт.",
workGroupName, referenceDate, responseArea, offset, distributionResult.Count);
distributedTemplates.AddRange(distributionResult);
}
else
{
// не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");
var offset = GetOffsetForDistributionByResponseArea();
var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
var referenceDate = GetReferenceDate(jobGroup, null);
logger.LogInformation("Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', НЕ НУЖНО использовать часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
templatesForDistribute.Count, workGroupName, dateStart, offset, referenceDate);
var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
logger.LogDebug("Закончил распределение для РГ '{workGroup}', referenceDate: {referenceDate}, utcOffset: {offset}, распределил шаблонов: {count} шт.", workGroupName, referenceDate, offset, distributionResult.Count);
distributedTemplates.AddRange(distributionResult);
}
}
}
else
{
//не нужно группировать по РГ
logger.LogDebug("Не нужно группировать по РГ");
if (isResponseAreaTimeZone)
{
// использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)
logger.LogDebug("Использовать ЧАСОВОЙ ПОЯС ЗО (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)");
// получаем для каждого шаблона ЗО и сразу группируем по ЗО
var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
if (responseArea == null)
logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
template.Id, template.Job!.ResponseAreaMask, responseArea);
// Добавляем шаблон в группу
if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
{
templateList = new List<Template>();
templatesByResponseArea[responseArea] = templateList;
}
templateList.Add(template);
}
logger.LogDebug("Групп для распределения: {count}", templatesByResponseArea.Count);
var dateStart = GetDateStart();
// для каждой ЗО получить offset и распределить шаблоны
foreach (var (responseArea, templates) in templatesByResponseArea)
{
var offset = GetOffsetForDistributionByResponseArea(responseArea);
var templatesForDistribute = templates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
var referenceDate = GetReferenceDate(jobGroup, responseArea);
logger.LogInformation("Буду распределять шаблоны {count} шт, их НЕ НУЖНО группировать по РГ, НУЖНО использовать часовой пояс ЗО: {responseArea}, utcOffset: {offset}. dateStart: {dateStart}, referenceDate: {referenceDate}",
templatesForDistribute.Count, responseArea, offset, dateStart, referenceDate);
var distributionResult = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
logger.LogDebug("Закончил распределение для часового пояса ЗО {responseArea}, utcOffset: {offset}, referenceDate: {referenceDate}, распределил шаблонов: {count} шт.", responseArea, offset, referenceDate, distributionResult.Count);
distributedTemplates.AddRange(distributionResult);
}
}
else
{
// не нужно использовать часовой пояс ЗО
logger.LogDebug("Не нужно использовать часовой пояс ЗО. Распределю все шаблоны без группировки.");
var offset = GetOffsetForDistributionByResponseArea();
var templatesForDistribute = allTemplates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
var referenceDate = GetReferenceDate(jobGroup, null);
var dateStart = GetDateStart();
logger.LogInformation("Буду распределать шаблоны {count} шт, их НЕ НУЖНО группировать по РГ, НЕ НУЖНО использовать часовой пояс ЗО. dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}",
templatesForDistribute.Count, dateStart, offset, referenceDate);
distributedTemplates = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, referenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
}
}
logger.LogInformation("Закончил распределение шаблонов {count} шт. для jobGroupId: {jobGroupId}. Изменено: {changedCount}, без изменений: {oldCount}",
distributedTemplates.Count, jobGroupId, distributedTemplates.Count(t => t.NextRun != t.NextRunOld), distributedTemplates.Count(t => t.NextRun == t.NextRunOld));
return distributedTemplates;
}
public async Task<DateTimeOffset?> GetNextRunForNewTemplateAsync(Guid jobGroupId, string workGroupName, string responseAreaName)
{
logger.LogDebug("Получить nextRun для вновь создаваемого шаблона. jobGroupId: {jobGroupId}, workGroupName: {workGroupName}, responseAreaName: {responseAreaName}",
jobGroupId, workGroupName, responseAreaName);
var jobGroup = await jobGroupService.Get()
.AsNoTracking()
.Include(t => t.DistributionConfig)
.ThenInclude(t => t.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == jobGroupId);
if (jobGroup == null)
{
logger.LogError("Не найдена группа работ с Id: {jobGroupId}.", jobGroupId);
//throw new ArgumentNullException(nameof(jobGroupId), $"Не найдена группа работ с Id: {jobGroupId}");
return null;
}
if (jobGroup.IsAutoDistributionEnabled)
{
// автораспределение
logger.LogDebug("Расчет используя метод распределения работ");
if (jobGroup.DistributionConfig == null)
{
logger.LogError("Для группы работ {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", jobGroupId, nameof(JobGroupDistributionConfig));
//throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {jobGroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}");
return null;
}
var startDate = GetDateStart();
var duration = GetDurationDays(jobGroup.DistributionConfig);
var excludeWeekends = jobGroup.DistributionConfig.IsExcludeWeekends;
var targetTemplate = new TemplateNextRunDto(Guid.Empty, null);
var templateStatusType = TemplateStatusTypeEnum.Used;
var allTemplatesQuery = templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.Where(t => t.StatusTypeId == templateStatusType && t.Job!.GroupId == jobGroupId);
// группировать по РГ
var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;
// использовать часовой пояс РГ
var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;
if (isGroupingByWorkGroup)
{
// группировать по РГ
logger.LogDebug("Нужно группировать по РГ");
// получаем для всех шаблонов РГ и группируем их по РГ
var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
var allTemplates = await allTemplatesQuery.ToListAsync();
foreach (var template in allTemplates)
{
var templateWorkGroupName = await shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
// Если даже workGroupName == null, ну и ладно, сгруппируем по null
if (templateWorkGroupName == null)
logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
template.Id, template.Job!.WorkGroupMask, templateWorkGroupName);
// Добавляем шаблон в группу
if (!templatesByWorkGroup.TryGetValue(templateWorkGroupName, out var templatesList))
{
templatesList = new List<Template>();
templatesByWorkGroup[templateWorkGroupName] = templatesList;
}
templatesList.Add(template);
}
//Берем шаблоны только нашей РГ workGroupName
templatesByWorkGroup.TryGetValue(workGroupName, out var templatesWithWorkGroup);
var templatesByWorkGroupDto = (templatesWithWorkGroup ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Шаблонов в РГ {workGroupName} - {count} шт.", workGroupName, templatesByWorkGroupDto.Count);
if (isResponseAreaTimeZone)
{
// использовать часовой пояс РГ
logger.LogDebug("Использовать часовой пояс РГ '{responseAreaName}'", responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");
var offset = GetOffsetForDistributionByResponseArea();
var referenceDate = GetReferenceDate(jobGroup, null);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
}
else
{
// не нужно группировать по РГ
logger.LogDebug("Не нужно группировать по РГ");
if (isResponseAreaTimeZone)
{
// использовать ЧАСОВОЙ ПОЯС ЗО
logger.LogDebug("Нужно использовать часовой пояс ЗО '{responseAreaName}'", responseAreaName);
// получить все шаблоны, сгруппировать их по ЗО, получить nextRun в рамках переданной ЗО
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
var allTemplates = await allTemplatesQuery.ToListAsync();
// получаем для каждого шаблона ЗО и сразу группируем по ЗО
var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var template in allTemplates)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
if (responseArea == null)
logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
template.Id, template.Job!.ResponseAreaMask, responseArea);
// Добавляем шаблон в группу
if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
{
templateList = new List<Template>();
templatesByResponseArea[responseArea] = templateList;
}
templateList.Add(template);
}
// из сгруппированных шаблонов, получаем существующие с нашей переданной responseAreaName
templatesByResponseArea.TryGetValue(responseAreaName, out var templatesWithResponseArea);
var templatesWithResponseAreaDto = (templatesWithResponseArea ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Существующих шаблонов в БД с responseAreaName: {responseAreaName} - {count} шт.", responseAreaName, templatesWithResponseAreaDto.Count);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesWithResponseAreaDto, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс ЗО
logger.LogDebug("Не нужно использовать часовой пояс ЗО.");
var referenceDate = GetReferenceDate(jobGroup, null);
var offset = GetOffsetForDistributionByResponseArea();
var allTemplates = await allTemplatesQuery
.Select(t => new TemplateNextRunDto(t.Id, t.NextRun))
.ToListAsync();
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, allTemplates, excludeWeekends, isNew: true);
return nextRun.NextRun;
}
}
}
else
{
// расписание ЕСПП
logger.LogDebug("Расчет используя метод ЕСПП");
if (jobGroup.IsResponseAreaTimezone)
{
// использовать часовой пояс ЗО
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
logger.LogDebug("Расчет nextRun используя метод ЕСПП, использую часовой пояс ЗО '{responseAreaName}, referenceDate: {referenceDate}, offset: {offset}'", responseAreaName, referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);
return nextRun;
}
else
{
// не использовать часовой пояс ЗО
// так как это классическое расписание еспп, и не используется часовой пояс РГ, то нам нужно считать в часовом поясе УЗ ЕСПП, т.е. МСК
var referenceDate = GetReferenceDate(jobGroup, null);
var offset = GetOffsetForDistributionByResponseArea();
logger.LogDebug("Расчет nextRun используя метод ЕСПП, не использую часовой пояс ЗО, использую часовой пояс УЗ ЕСПП, referenceDate: {referenceDate}, offset: {offset}", referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroupId, referenceDate, offset);
return nextRun;
}
}
}
public async Task<DateTimeOffset?> GetNextRunForTemplateAsync(Guid templateId, bool isNew)
{
logger.LogDebug("Получить nextRun для существующего шаблона, templateId: {templateId}, isNew: {isNew}", templateId, isNew);
var template = await templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.ThenInclude(t => t.Group).ThenInclude(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == templateId);
if (template == null)
{
logger.LogError("Не найден шаблон с Id: {templateId}.", templateId);
//throw new ArgumentNullException(nameof(templateId), $"Не найден шаблон с Id: {templateId}");
return null;
}
if (template.Job!.Group!.IsAutoDistributionEnabled == true)
{
// включено автораспределение
logger.LogDebug("Включено автораспределение");
if (template.Job!.Group.DistributionConfig == null)
{
logger.LogError("Для шаблона {templateId}, jobGroupId {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}", template.Id, template.Job.GroupId, nameof(JobGroupDistributionConfig));
// throw new ArgumentNullException(nameof(JobGroupDistributionConfig), $"Для jobGroupId: {template.Job.GroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}");
return null;
}
var startDate = GetDateStart();
var duration = GetDurationDays(template.Job.Group.DistributionConfig);
var excludeWeekends = template.Job.Group.DistributionConfig.IsExcludeWeekends;
var targetTemplate = new TemplateNextRunDto(template.Id, template.NextRun);
var templateStatusType = TemplateStatusTypeEnum.Used;
var jobGroup = template.Job.Group;
// получаем все шаблоны из этой же группы что и template
var allTemplatesQuery = templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.Where(t => t.StatusTypeId == templateStatusType && t.Job!.GroupId == jobGroup.Id);
// группировать по РГ
var isGroupingByWorkGroup = jobGroup.DistributionConfig.IsGroupingByWorkGroup;
// использовать часовой пояс РГ
var isResponseAreaTimeZone = jobGroup.IsResponseAreaTimezone;
if (isGroupingByWorkGroup)
{
var targetTemplateWorkGroup = await shortcodesService.ApplyShortcodesAsync(template.Job.WorkGroupMask, template);
// группировать по РГ
logger.LogDebug("Нужно группировать по РГ");
// получаем для всех шаблонов РГ и группируем их по РГ
var templatesByWorkGroup = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
var allTemplates = await allTemplatesQuery.ToListAsync();
foreach (var templateItem in allTemplates)
{
var templateWorkGroupName = await shortcodesService.ApplyShortcodesAsync(templateItem.Job!.WorkGroupMask, templateItem);
// Если даже workGroupName == null, ну и ладно, сгруппируем по null
if (templateWorkGroupName == null)
logger.LogWarning("Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'",
templateItem.Id, templateItem.Job!.WorkGroupMask, templateWorkGroupName);
// Добавляем шаблон в группу
if (!templatesByWorkGroup.TryGetValue(templateWorkGroupName, out var templatesList))
{
templatesList = new List<Template>();
templatesByWorkGroup[templateWorkGroupName] = templatesList;
}
templatesList.Add(templateItem);
}
//Берем шаблоны только нашей РГ workGroupName
templatesByWorkGroup.TryGetValue(targetTemplateWorkGroup, out var templatesWithWorkGroup);
var templatesByWorkGroupDto = (templatesWithWorkGroup ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Шаблонов в РГ {targetTemplateWorkGroup} - {count} шт.", targetTemplateWorkGroup, templatesByWorkGroupDto.Count);
if (isResponseAreaTimeZone)
{
// использовать часовой пояс РГ
var targetTemplateResponseArea = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
logger.LogDebug("Использовать часовой пояс РГ '{responseAreaName}'", targetTemplateResponseArea);
var offset = GetOffsetForDistributionByResponseArea(targetTemplateResponseArea);
var referenceDate = GetReferenceDate(jobGroup, targetTemplateResponseArea);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
logger.LogDebug("Не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)");
var offset = GetOffsetForDistributionByResponseArea();
var referenceDate = GetReferenceDate(jobGroup, null);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesByWorkGroupDto, excludeWeekends, isNew);
return nextRun.NextRun;
}
}
else
{
// не нужно группировать по РГ
logger.LogDebug("Не нужно группировать по РГ");
if (isResponseAreaTimeZone)
{
// использовать ЧАСОВОЙ ПОЯС ЗО
var responseAreaName = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
logger.LogDebug("Нужно использовать часовой пояс ЗО '{responseAreaName}'", responseAreaName);
// получить все шаблоны, сгруппировать их по ЗО, получить nextRun в рамках переданной ЗО
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
var allTemplates = await allTemplatesQuery.ToListAsync();
// получаем для каждого шаблона ЗО и сразу группируем по ЗО
var templatesByResponseArea = new Dictionary<string?, List<Template>>(StringComparer.OrdinalIgnoreCase);
foreach (var templateItem in allTemplates)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(templateItem.Job!.ResponseAreaMask, templateItem);
if (responseArea == null)
logger.LogWarning("Для шаблона {id}, с маской ЗО '{responseAreaMask}', не смог с помощью шорткода определить ЗО, шорткод сервис вернул ЗО: '{responseArea}'",
templateItem.Id, templateItem.Job!.ResponseAreaMask, responseArea);
// Добавляем шаблон в группу
if (!templatesByResponseArea.TryGetValue(responseArea, out var templateList))
{
templateList = new List<Template>();
templatesByResponseArea[responseArea] = templateList;
}
templateList.Add(templateItem);
}
// из сгруппированных шаблонов, получаем существующие с ЗО нашего шаблона responseAreaName
templatesByResponseArea.TryGetValue(responseAreaName, out var templatesWithResponseArea);
var templatesWithResponseAreaDto = (templatesWithResponseArea ?? new List<Template>()).Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
logger.LogDebug("Существующих шаблонов в БД с responseAreaName: {responseAreaName} - {count} шт.", responseAreaName, templatesWithResponseAreaDto.Count);
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesWithResponseAreaDto, excludeWeekends, isNew);
return nextRun.NextRun;
}
else
{
// не нужно использовать часовой пояс ЗО
logger.LogDebug("Не нужно использовать часовой пояс ЗО.");
var referenceDate = GetReferenceDate(jobGroup, null);
var offset = GetOffsetForDistributionByResponseArea();
var allTemplates = await allTemplatesQuery
.Select(t => new TemplateNextRunDto(t.Id, t.NextRun))
.ToListAsync();
var nextRun = await templateDistributor.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, allTemplates, excludeWeekends, isNew);
return nextRun.NextRun;
}
}
}
else
{
// метод расчета ЕСПП
logger.LogDebug("Метод расчета ЕСПП");
if (template.Job.Group.IsResponseAreaTimezone)
{
// использовать часовой пояс ЗО
var responseAreaName = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
var jobGroup = template.Job.Group;
var referenceDate = GetReferenceDate(jobGroup, responseAreaName);
var offset = GetOffsetForDistributionByResponseArea(responseAreaName);
logger.LogDebug("Расчет nextRun используя метод ЕСПП, использую часовой пояс ЗО '{responseAreaName}, referenceDate: {referenceDate}, offset: {offset}'", responseAreaName, referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(jobGroup.Id, referenceDate, offset);
return nextRun;
}
else
{
// не использовать часовой пояс ЗО
// так как это классическое расписание еспп, и не используется часовой пояс РГ, то нам нужно считать в часовом поясе УЗ ЕСПП, т.е. МСК
var referenceDate = GetReferenceDate(template.Job.Group, null);
var offset = GetOffsetForDistributionByResponseArea();
logger.LogDebug("Расчет nextRun используя метод ЕСПП, не использую часовой пояс ЗО, использую часовой пояс УЗ ЕСПП, referenceDate: {referenceDate}, offset: {offset}", referenceDate, offset);
var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job.GroupId, referenceDate, offset);
return nextRun;
}
}
}
public int GetDurationDays(JobGroupDistributionConfig config)
{
var period = config.DistributionPeriod;
var periodType = period!.Type;
int.TryParse(period.Duration, out var duration);
var periodDays = duration;
if (periodType == DistributionPeriodTypeEnum.Day.ToString())
periodDays = duration;
if (periodType == DistributionPeriodTypeEnum.Month.ToString())
// в месяце 30 дней, duration*30
periodDays = duration * 30;
if (periodType == DistributionPeriodTypeEnum.Year.ToString())
// в году 365 дней, duration*365
periodDays = duration * 365;
logger.LogDebug("Период распределения: {name}, duration: {duration}, type: {type}. Итого в днях: {days}", period.Name, period.Duration, period.Type, periodDays);
if (periodDays == 0)
{
periodDays = 1;
logger.LogWarning("Полученный период распределения 0 дней. Неверный конфиг распределения в таблице {table}. {name}, duration: {duration}, type: {type}. Устанавливаем минимальный период распределения {periodDays} дней.", nameof(DistributionPeriod), period.Name, period.Duration, period.Type, periodDays);
}
return periodDays;
}
public async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
{
return await templateDistributor.GetWorkDaysAsync(start, end, excludeWeekends);
}
public async Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
{
return await templateDistributor.GetWeekendsAsync(startDate, endDate);
}
/// <summary>
/// Получить часовую зону для распределения
/// </summary>
/// <returns></returns>
private TimeSpan GetOffsetForDistributionByResponseArea(string? responseArea = null)
{
// или часовая зона робота, или часовая зона РГ
if (responseArea != null)
{
// возвращаем часовой пояс ЗО
var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
logger.LogDebug("Получил часовой пояс для ЗО: {responseArea}, UtcTimeOffset: {responseAreaOffset}", responseArea, responseAreaOffset);
return responseAreaOffset;
}
else
{
// возвращаем часовой пояс робота
var esppOffset = GetEsppAccountOffset();
logger.LogDebug("Не передана ЗО, использую часовой пояс УЗ ЕСПП, UtcTimeOffset: {UtcTimeOffset}", esppOffset);
return esppOffset;
}
}
public TimeSpan GetEsppAccountOffset()
{
var esppOffset = TimeSpan.FromHours(settingsFromDb.EsppRobotAccountTimeZoneHour);
logger.LogDebug("Оффсет УЗ ЕСПП: {esppOffset}", esppOffset);
return esppOffset;
}
/// <summary>
/// Получить дату начала распределения
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
private DateOnly GetDateStart()
{
//var dateStart = DateOnly.FromDateTime(DateTimeOffset.UtcNow.ToOffset(offset).Date);
//logger.LogDebug("Дата начала распределения (сегодня) в целевом часовом поясе {dateStart}, offset: {offset}", dateStart, offset);
var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
logger.LogDebug("Дата начала распределения (сегодня): {dateStart}", dateStart);
return dateStart;
}
/// <summary>
/// Получить referenceDate для расчета с учетом часового пояса ЗО или без учета
/// </summary>
/// <param name="jobGroup"></param>
/// <param name="responseArea">Не обзяательный параметр, нужен обязательно при расчете с учетом часового пояса ЗО</param>
/// <returns></returns>
private DateTimeOffset GetReferenceDate(JobGroup jobGroup, string? responseArea)
{
// для того чтобы посчитать nextRun в часовом поясе ЗО, нужно пересчитать refDate чтобы понять в какое время хотел выполнять заказчик
logger.LogDebug("Исходный referenceDate UTC: {referenceDate}", jobGroup.ReferenceDate);
if (jobGroup.IsResponseAreaTimezone)
{
// нужно вернуть с учетом часового пояса клиента сохранившего refDate (используется для расчета с учетом часового пояса ЗО)
var clientOffset = jobGroup.UserTimeZoneOffset;
if (!clientOffset.HasValue)
{
logger.LogError("При расчете referenceDate для ЗО '{responseArea}', UserTimeZoneOffset=null. Вернул referenceDate {referenceDate} без смещения ЗО",
responseArea, jobGroup.ReferenceDate);
return jobGroup.ReferenceDate;
}
if (responseArea == null)
{
logger.LogError("При расчете referenceDate для ЗО '{responseArea}', responseArea=null. Вернул referenceDate {referenceDate} без смещения ЗО",
responseArea, jobGroup.ReferenceDate);
return jobGroup.ReferenceDate;
}
var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
// = реф + офКлиента(который нажал в ГУИ сохранить)-офЗО
var resultOffset = clientOffset.Value - responseAreaOffset;
var refDateWithResponseAreaOffset = jobGroup.ReferenceDate.Add(resultOffset);
logger.LogDebug("Клиент ожидает, что будет выполняться задача в каждой ЗО в {time}. referenceDate+clientOffset, {referenceDate}, {clientOffset}",
TimeOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset.Value).DateTime), jobGroup.ReferenceDate, clientOffset);
logger.LogDebug("Итоговый referenceDate UTC: {resultRefDate}, для использования часовой зоны ЗО: '{responseArea}', добавлено времени {resultOffset} к исходному {refDate}",
refDateWithResponseAreaOffset, responseArea, resultOffset, jobGroup.ReferenceDate);
logger.LogDebug("Если итоговый referenceDate UTC {refDateWithResponseAreaOffset} перевести в часовой пояс ЗО {responseAreaOffset}, то дата в часовом поясе ЗО будет {refDateTargetTz}",
refDateWithResponseAreaOffset, responseAreaOffset, refDateWithResponseAreaOffset.Add(responseAreaOffset));
return refDateWithResponseAreaOffset;
}
else
{
// если НЕ НУЖНО в часовом поясе ЗО, то просто возвращаем refDate
logger.LogDebug("Итоговый referenceDate UTC: {refDate}, не нужно использовать часовой пояс ЗО", jobGroup.ReferenceDate);
return jobGroup.ReferenceDate;
}
}
}
}

View File

@@ -2,14 +2,13 @@
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
using System.Text.Json;
namespace PARR.DAL.NextRunServices.Subservices
{
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>()
@@ -52,11 +51,9 @@ namespace PARR.DAL.NextRunServices.Subservices
public EsppScheduleTransformService(
ILogger<EsppScheduleTransformService> logger,
IEsppSchTypeConfigService esppSchTypeConfigService
//INextRunModifierService nextRunModifierService
)
{
this.esppSchTypeConfigService = esppSchTypeConfigService;
//this.nextRunModifierService = nextRunModifierService;
this.logger = logger;
}
@@ -69,12 +66,19 @@ namespace PARR.DAL.NextRunServices.Subservices
private DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate, TimeSpan? offset)
{
//offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК
logger.LogDebug("Начинаю рассчет nextRun по расписанию ЕСПП. Входные данные: date: {referenceDate}, offset: {offset}", referenceDate, offset);
var nextRun = referenceDate;
var finalOffset = new TimeSpan(3, 0, 0) + offset ?? TimeSpan.Zero;
//var finalOffset = new TimeSpan(3, 0, 0) + (offset ?? TimeSpan.Zero);
var finalOffset = offset ?? TimeSpan.Zero;
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
logger.LogDebug("Дата после добавления offset - {offsetReferenceDate}", offsetReferenceDate);
////---------
//// Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД
//var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0));
@@ -108,71 +112,23 @@ namespace PARR.DAL.NextRunServices.Subservices
break;
}
logger.LogDebug("Рассчитанная дата, с учетом offset: {nextRun}", nextRun);
// Возвращаем обратно в ЮТС
nextRun = nextRun.ToOffset(TimeSpan.Zero);
logger.LogDebug("Рассчитанная дата с нулевым offset: {nextRun}", nextRun);
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;
//}
/// <summary>
/// Получить расписание из БД
/// </summary>
/// <param name="jobGroupId"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private async Task<EsppScheduleDto> GetEsppScheduleAsync(Guid jobGroupId)
{
var esppSchedule = await esppSchTypeConfigService.GetEsppScheduleDtoAsync(jobGroupId);
@@ -236,7 +192,22 @@ namespace PARR.DAL.NextRunServices.Subservices
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);
{
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
calcDay = new DateTimeOffset(
nowInTargetTz.Year,
//DateTimeOffset.UtcNow.Year,
nowInTargetTz.Month,
//DateTimeOffset.UtcNow.Month,
nowInTargetTz.Day,
//DateTimeOffset.UtcNow.Day,
referenceDate.Hour,
referenceDate.Minute,
referenceDate.Second,
referenceDate.Offset
);
}
if (((int)calcDay.DayOfWeek) != dwNum)
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
@@ -260,7 +231,20 @@ namespace PARR.DAL.NextRunServices.Subservices
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);
{
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
calcDay = new DateTimeOffset(
nowInTargetTz.Year,
nowInTargetTz.Month,
dayNum,
referenceDate.Hour,
referenceDate.Minute,
referenceDate.Second,
referenceDate.Offset
);
//calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
}
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
@@ -285,9 +269,29 @@ namespace PARR.DAL.NextRunServices.Subservices
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 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);
? new DateTimeOffset(
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Year,
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).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);
@@ -330,7 +334,12 @@ namespace PARR.DAL.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
//сравниваем года в одном часовом поясе
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
var year = (referenceDate.Year < nowInTargetTz.Year)
? nowInTargetTz.Year
: referenceDate.Year;
var calcDay = new DateTimeOffset(
year, monthNum, dayNum,
@@ -379,9 +388,15 @@ namespace PARR.DAL.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
}
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
// сравниваем года в одном часовом поясе
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
var year = (referenceDate.Year < nowInTargetTz.Year)
? nowInTargetTz.Year
: referenceDate.Year;
if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
//if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
year += 1;
var calcDay = new DateTimeOffset(
@@ -432,91 +447,5 @@ namespace PARR.DAL.NextRunServices.Subservices
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;
//}
}
}

View File

@@ -5,55 +5,13 @@
/// </summary>
internal interface IEsppScheduleTransformService
{
///// <summary>
///// Получить следующую дату согласно расписания
///// </summary>
///// <param name="esppSchedule"></param>
///// <param name="lastRun"></param>
///// <returns></returns>
//DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset lastRun);
///// <summary>
///// Получить расписание
///// </summary>
///// <param name="esppSchedule"></param>
///// <param name="lastRun"></param>
///// <returns></returns>
//List<DateTimeOffset> GetNextSchedule(EsppScheduleDto esppSchedule, DateTimeOffset lastRun);
/// <summary>
/// Получить следующую дату по jobGroupId
/// </summary>
/// <param name="jobGroupId"></param>
/// <param name="lastRun"></param>
/// <param name="referenceDate">referenceDate или nextRun</param>
/// <param name="offset">offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК</param>
/// <returns></returns>
Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset lastRun, TimeSpan? offset = null);
///// <summary>
///// Получить расписание по jobGroupId
///// </summary>
///// <param name="jobGroupId"></param>
///// <param name="lastRun"></param>
///// <returns></returns>
//Task<List<DateTimeOffset>> GetNextScheduleAsync(Guid jobGroupId, DateTimeOffset lastRun);
///// <summary>
///// Получить следующую дату срабатывания в указанном периоде
///// </summary>
///// <param name="lastRun"></param>
///// <param name="periodType"></param>
///// <param name="distributionPeriod"></param>
///// <returns></returns>
//DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod);
///// <summary>
///// Получить дату начала периода распределения относительно опорной даты (Reference Date)
///// </summary>
///// <param name="jobGroupId"></param>
///// <param name="date"></param>
///// <returns></returns>
//Task<DateOnly> GetStartPeriodForDateAsync(Guid jobGroupId, DateTimeOffset date, DateTimeOffset refrenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod);
Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate, TimeSpan? offset = null);
}
}

View File

@@ -0,0 +1,51 @@
using PARR.DAL.NextRunServices.Models;
namespace PARR.DAL.NextRunServices.Subservices
{
internal interface ITemplateDistributorV2
{
/// <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>
Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TimeSpan? offset, List<TemplateNextRunDto> allTemplates, bool excludeWeekends);
/// <summary>
/// Метод проверяет, актуален ли текущий NextRun шаблона, если не актуален, возвращает актуальное значение
/// При проверке загружаются шаблоны из БД! Если хотим рассчитывать nextRun для НЕСКОЛЬКИХ шаблонов, то следует рассчитать 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>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
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);
}
}

View File

@@ -27,6 +27,9 @@ namespace PARR.DAL.NextRunServices.Subservices
public async Task<TemplateNextRunResultDto> GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew)
{
//if (offset.HasValue)
// referenceDate = referenceDate.ToOffset(offset.Value);
//todo: allTemplates - пока не используется. Будет использоваться когда каждый раз будем строить план
// Пока работает так:
@@ -104,6 +107,10 @@ namespace PARR.DAL.NextRunServices.Subservices
public async Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends)
{
//if (offset.HasValue)
//referenceDate = referenceDate.ToOffset(offset.Value);
var _startDate = GetStartPeriodDate(startDate, referenceDate);
var _endDate = GetEndPeriodDate(_startDate, duration);
@@ -417,7 +424,7 @@ namespace PARR.DAL.NextRunServices.Subservices
return CreateDateTimeOffset(minDay, referenceDate);
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Получить план: дни, на которые должны быть назначены шаблоны
@@ -492,7 +499,7 @@ namespace PARR.DAL.NextRunServices.Subservices
/// <returns></returns>
private DateTimeOffset CreateDateTimeOffset(DateOnly date, DateTimeOffset refDate)
{
return new DateTimeOffset(date.Year, date.Month, date.Day, refDate.Hour, refDate.Minute, refDate.Second, TimeSpan.Zero);
return new DateTimeOffset(date.Year, date.Month, date.Day, refDate.Hour, refDate.Minute, refDate.Second, refDate.Offset);
}
@@ -507,20 +514,61 @@ namespace PARR.DAL.NextRunServices.Subservices
// дата начала не может быть раньше referenceDate
// и не может быть раньше чем сегодня
var dateStart = startDate;
#region old
//var dateStart = startDate;
// если dateStart < чем refDate, то берем refDate
if (dateStart < DateOnly.FromDateTime(referenceDate.Date))
dateStart = DateOnly.FromDateTime(referenceDate.Date);
//// если dateStart < чем refDate, то берем refDate
//if (dateStart < DateOnly.FromDateTime(referenceDate.Date))
// dateStart = DateOnly.FromDateTime(referenceDate.Date);
// если dateStart < чем текущая дата, то берем текущую дату
if (dateStart < DateOnly.FromDateTime(DateTime.UtcNow.Date))
dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
//// если dateStart < чем текущая дата, то берем текущую дату
//if (dateStart < DateOnly.FromDateTime(DateTime.UtcNow.Date))
// dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
// если текущее время > referenceDate, то прибавляем 1 день
//// если текущее время > referenceDate, то прибавляем 1 день
//var dateTimeStart = CreateDateTimeOffset(dateStart, referenceDate);
//if (dateTimeStart < DateTimeOffset.UtcNow)
// dateStart = dateStart.AddDays(1);
//return dateStart;
#endregion
// 1. Конвертируем startDate (UTC) в целевой часовой пояс
var startDateUtc = new DateTimeOffset(
startDate.Year, startDate.Month, startDate.Day,
0, 0, 0,
TimeSpan.Zero);
var startDateInTargetTz = startDateUtc.ToOffset(referenceDate.Offset);
var dateStart = DateOnly.FromDateTime(startDateInTargetTz.DateTime);
logger.LogDebug("startDate UTC ({startDate} 00:00 UTC) конвертирована в целевой часовой пояс {offset}: {dateStart}", startDate, referenceDate.Offset, dateStart);
// 2. Дата начала не может быть раньше даты из referenceDate (в его часовом поясе)
var refDateInTargetTz = DateOnly.FromDateTime(referenceDate.Date);
if (dateStart < refDateInTargetTz)
{
logger.LogDebug("Дата начала {dateStart} раньше даты из referenceDate {refDate}. Используем дату из referenceDate.", dateStart, refDateInTargetTz);
dateStart = refDateInTargetTz;
}
// 3. Дата начала не может быть раньше "сегодня" в целевом часовом поясе
var todayInTargetTz = DateOnly.FromDateTime(DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Date);
if (dateStart < todayInTargetTz)
{
logger.LogDebug("Дата начала {dateStart} раньше 'сегодня' в целевом поясе {today}. Используем 'сегодня'.", dateStart, todayInTargetTz);
dateStart = todayInTargetTz;
}
// 4. Если момент времени "начало дня в целевом поясе" уже прошёл — добавляем 1 день
var dateTimeStart = CreateDateTimeOffset(dateStart, referenceDate);
if (dateTimeStart < DateTimeOffset.UtcNow)
{
logger.LogDebug("Момент времени {dateTimeStart} уже прошёл (сейчас {now}). Добавляем 1 день.", dateTimeStart, DateTimeOffset.UtcNow);
dateStart = dateStart.AddDays(1);
}
logger.LogDebug("Итоговая дата начала распределения: {dateStart} (в часовом поясе {offset})", dateStart, referenceDate.Offset);
return dateStart;
}
@@ -565,41 +613,44 @@ namespace PARR.DAL.NextRunServices.Subservices
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
public async Task<HashSet<DateOnly>> GetWeekendsV2Async(DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate)
public async Task<HashSet<DateOnly>> GetWeekendsV2Async(DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate, TimeSpan? offset = null)
{
if (offset.HasValue)
referenceDate = referenceDate.ToOffset(offset.Value);
// проверяем это текущие или прошлые сутки относительно refDate, если надо, корректируем dateStart, dateEnd
bool isPreviousDays = IsPreviousDays(referenceDate);
//bool isPreviousDays = IsPreviousDays(referenceDate);
if (isPreviousDays)
{
// Это предыдущие сутки, корректируем
startDate = startDate.AddDays(-1);
// так как мы потом будем сдвигать выходные дни влево, то надо взять на один день дальше, чтоб знать какой он
// вроде бы это не костыль
endDate = endDate.AddDays(1);
//endDate = endDate.AddDays(-1);
//if (isPreviousDays)
//{
// // Это предыдущие сутки, корректируем
// startDate = startDate.AddDays(-1);
// // так как мы потом будем сдвигать выходные дни влево, то надо взять на один день дальше, чтоб знать какой он
// // вроде бы это не костыль
// endDate = endDate.AddDays(1);
// //endDate = endDate.AddDays(-1);
logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты для получения выходных дней, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, startDate, endDate);
}
// logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты для получения выходных дней, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, startDate, endDate);
//}
var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
var weekendsResult = new List<DateOnly>();
if (isPreviousDays)
{
// так как должны быть предыдущие дни, отнимаем 1 день
foreach (var weekend in weekends)
{
var previous = weekend.AddDays(-1);
logger.LogDebug("Выходной день, -1, так как день должен быть предыдущий, старое значение {weekend}, новое {previous}", weekend, previous);
weekendsResult.Add(previous);
}
}
else
{
weekendsResult = weekends;
}
//if (isPreviousDays)
//{
// // так как должны быть предыдущие дни, отнимаем 1 день
// foreach (var weekend in weekends)
// {
// var previous = weekend.AddDays(-1);
// logger.LogDebug("Выходной день, -1, так как день должен быть предыдущий, старое значение {weekend}, новое {previous}", weekend, previous);
// weekendsResult.Add(previous);
// }
//}
//else
//{
// weekendsResult = weekends;
//}
logger.LogDebug("Найдено не рабочих дней: {Count}", weekendsResult.Count);
foreach (var weekend in weekendsResult)
@@ -644,19 +695,22 @@ namespace PARR.DAL.NextRunServices.Subservices
//}
public async Task<List<DateOnly>> GetWorkDaysV2Async(DateOnly start, DateOnly end, bool excludeWeekends, DateTimeOffset referenceDate)
public async Task<List<DateOnly>> GetWorkDaysV2Async(DateOnly start, DateOnly end, bool excludeWeekends, DateTimeOffset referenceDate, TimeSpan? offset = null)
{
if (offset.HasValue)
referenceDate = referenceDate.ToOffset(offset.Value);
logger.LogDebug("Формирование списка рабочих дней, start: {start}, end: {end}, excludeWeekends: {excludeWeekends}, referenceDate: {referenceDate}", start, end, excludeWeekends, referenceDate);
// проверяем это текущие или прошлые сутки относительно refDate, если надо, корректируем dateStart, dateEnd
if (IsPreviousDays(referenceDate))
{
// Это предыдущие сутки, корректируем
start = start.AddDays(-1);
end = end.AddDays(-1);
//// проверяем это текущие или прошлые сутки относительно refDate, если надо, корректируем dateStart, dateEnd
//if (IsPreviousDays(referenceDate))
//{
// // Это предыдущие сутки, корректируем
// start = start.AddDays(-1);
// end = end.AddDays(-1);
logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, start, end);
}
// logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, start, end);
//}
// Получаем список выходных/праздничных дней
var weekends = new HashSet<DateOnly>();
@@ -683,33 +737,45 @@ namespace PARR.DAL.NextRunServices.Subservices
return result;
}
/// <summary>
/// Это предыдущие сутки? Относительно referenceDate и МСК времени
/// </summary>
/// <param name="referenceDate"></param>
/// <returns></returns>
private bool IsPreviousDays(DateTimeOffset referenceDate)
public Task<HashSet<DateOnly>> GetWeekendsV2Async(DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate)
{
// смещение часов относительно UTC - MSK
int offsetMskHour = 3;
// если время больше 21 часа, то это текущие сутки, если меньше, то прошлые
int maxHour = 24 - offsetMskHour;
if (referenceDate.Hour >= maxHour && referenceDate.Hour <= 23)
{
// это прошлые сутки
logger.LogDebug("ReferenceDate {refDate} - прошлые стуки", referenceDate);
return true;
}
else
{
// это текущие сутки
logger.LogDebug("ReferenceDate {refDate} - текущие стуки", referenceDate);
return false;
}
throw new NotImplementedException();
}
public Task<List<DateOnly>> GetWorkDaysV2Async(DateOnly start, DateOnly end, bool excludeWeekends, DateTimeOffset referenceDate)
{
throw new NotImplementedException();
}
///// <summary>
///// Это предыдущие сутки? Относительно referenceDate и МСК времени
///// </summary>
///// <param name="referenceDate"></param>
///// <returns></returns>
//private bool IsPreviousDays(DateTimeOffset referenceDate)
//{
// // смещение часов относительно UTC - MSK
// int offsetMskHour = 3;
// // если время больше 21 часа, то это текущие сутки, если меньше, то прошлые
// int maxHour = 24 - offsetMskHour;
// if (referenceDate.Hour >= maxHour && referenceDate.Hour <= 23)
// {
// // это прошлые сутки
// logger.LogDebug("ReferenceDate {refDate} - прошлые стуки", referenceDate);
// return true;
// }
// else
// {
// // это текущие сутки
// logger.LogDebug("ReferenceDate {refDate} - текущие стуки", referenceDate);
// return false;
// }
//}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.DAL.Cache.Services.Base;
using PARR.DAL.Configurations.DbSettings;
using PARR.DAL.Context;
@@ -141,6 +142,8 @@ namespace PARR.DAL
services.AddTransient<IEsppScheduleTransformService, EsppScheduleTransformService>();
services.AddTransient<INextRunService, NextRunService>();
services.AddTransient<ITemplateDistributor, TemplateDistributor>();
services.AddTransient<ITemplateDistributorV2, TemplateDistributorV2>();
services.AddTransient<INextRunServiceV2, NextRunServiceV2>();
#endregion
@@ -189,8 +192,9 @@ namespace PARR.DAL
using var scope = provider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
var settingsFromDb = scope.ServiceProvider.GetRequiredService<SettingsFromDb>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ScheduleResponseAreaTimeOffsetService>>();
return new ScheduleResponseAreaTimeOffsetService(dbContext, settingsFromDb);
return new ScheduleResponseAreaTimeOffsetService(dbContext, settingsFromDb, logger);
});
}

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.Context;
using PARR.DAL.Contracts;
using PARR.DAL.Models.Schedule;
@@ -17,8 +18,9 @@ namespace PARR.DAL.Services.Implementations.Schedule
/// Настройки, если не нашли в offsetList
/// </summary>
private readonly ScheduleResponseAreaTimeOffset defaultOffset;
private readonly ILogger<ScheduleResponseAreaTimeOffsetService> logger;
public ScheduleResponseAreaTimeOffsetService(DataContext dataContext, SettingsFromDb settingsFromDb)
public ScheduleResponseAreaTimeOffsetService(DataContext dataContext, SettingsFromDb settingsFromDb, ILogger<ScheduleResponseAreaTimeOffsetService> logger)
{
offsetList = dataContext.ScheduleResponseAreaTimeOffsets
.AsNoTracking()
@@ -36,6 +38,7 @@ namespace PARR.DAL.Services.Implementations.Schedule
EsppValue = "",
UtcTimeOffset = new TimeSpan(3, 0, 0)
};
this.logger = logger;
}
@@ -45,7 +48,20 @@ namespace PARR.DAL.Services.Implementations.Schedule
public ScheduleResponseAreaTimeOffset GetByResponseAreaOrDefault(string responseArea)
{
return offsetList.TryGetValue(responseArea, out var value) ? value : defaultOffset;
offsetList.TryGetValue(responseArea, out var value);
if (value != null)
{
logger.LogDebug("Получил часовой пояс по ЗО '{responseArea}', esppValue: {EsppValue}, utcTimeOffset: {utcTimeOffset}", responseArea, value.EsppValue, value.UtcTimeOffset);
return value;
}
else
{
logger.LogWarning("Не смог получить часовой пояс по ЗО '{responseArea}', вернул часово пояс по умолчанию: name: {name}, esppValue: {EsppValue}, utcTimeOffset: {utcTimeOffset}",
responseArea, defaultOffset.ResponseArea, defaultOffset.EsppValue, defaultOffset.UtcTimeOffset);
return defaultOffset;
}
//return offsetList.TryGetValue(responseArea, out var value) ? value : defaultOffset;
}

View File

@@ -2,9 +2,9 @@
using Microsoft.Extensions.DependencyInjection;
using PARR.BLL;
using PARR.DAL;
using PARR.EsppSync;
using PARR.EsppScheduleSync.Domain;
using PARR.EsppScheduleSync.Settings;
using PARR.EsppSync;
using PARR.EsppSync.Domain;
namespace PARR.EsppScheduleSync
{

View File

@@ -3,16 +3,17 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.BLL.Helpers;
using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.NextRunServices;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Schedule;
using PARR.EsppScheduleSync.Domain;
using PARR.EsppScheduleSync.Settings;
using PARR.EsppSync;
using PARR.EsppSync.Domain;
using PARR.EsppSync.Helpers;
namespace PARR.EsppScheduleSync
{
@@ -24,9 +25,7 @@ namespace PARR.EsppScheduleSync
private readonly ISyncService<EsppObjectSchedule> syncService;
private readonly SettingsFromDb settingsFromDb;
private readonly IServiceProvider serviceProvider;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
private string noneExcludeCalendarEsppValue;
//private Dictionary<string, string> responseAreaTimeOffsetDict;
public ScheduleSyncher(
@@ -35,8 +34,7 @@ namespace PARR.EsppScheduleSync
IMqService mqService,
ISyncService<EsppObjectSchedule> syncService,
SettingsFromDb settingsFromDb,
IServiceProvider serviceProvider,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
IServiceProvider serviceProvider
)
{
this.logger = logger;
@@ -45,8 +43,6 @@ namespace PARR.EsppScheduleSync
this.syncService = syncService;
this.settingsFromDb = settingsFromDb;
this.serviceProvider = serviceProvider;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
//this.responseAreaTimeOffsetDict = new Dictionary<string, string>();
this.noneExcludeCalendarEsppValue = string.Empty;
if (globalSettings.MqSettings == null)
@@ -66,8 +62,7 @@ namespace PARR.EsppScheduleSync
if (string.IsNullOrEmpty(noneExcludeCalendarEsppValue))
await GetNoneExcludeCalendarEsppValueAsync();
//if (responseAreaTimeOffsetDict.Count == 0)
// await GetResponseAreaTimeOffsetsAsync();
var isConnected = await mqService.InitConsumerAsync(globalSettings!.MqSettings!, SyncScheduleAsync);
@@ -77,14 +72,20 @@ namespace PARR.EsppScheduleSync
logger.LogInformation("Запущена проверка очереди {QueueName}.", globalSettings.MqSettings!.QueueName);
}
//private async Task GetResponseAreaTimeOffsetsAsync()
//{
// using var scope = serviceProvider.CreateScope();
// var service = scope.ServiceProvider.GetRequiredService<IScheduleResponseAreaTimeOffsetService>();
// responseAreaTimeOffsetDict = await service.Get().ToDictionaryAsync(t => t.ResponseArea, t => t.TimeOffset);
//}
public async Task StopAsync()
{
await mqService.DisposeAsync();
logger.LogInformation("=== === === Соединение с очередью {QueueName} закрыто === === ===", globalSettings.MqSettings!.QueueName);
}
/// <summary>
/// Получение типа исключения
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private async Task GetNoneExcludeCalendarEsppValueAsync()
{
using var scope = serviceProvider.CreateScope();
@@ -98,153 +99,98 @@ namespace PARR.EsppScheduleSync
noneExcludeCalendarEsppValue = noneExcludeType.EsppValue;
}
public async Task StopAsync()
{
await mqService.DisposeAsync();
logger.LogInformation("=== === === Соединение с очередью {QueueName} закрыто === === ===", globalSettings.MqSettings!.QueueName);
}
/// <summary>
/// Старт синхронизации, получили сообщение
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private async Task SyncScheduleAsync(string str)
{
try
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj, CustomComparisionCheckAsyncHandler, AfterParseStringToEsppObjectAsyncHandler);
}
/// <summary>
/// Выполняется после парсинга строки в объект ЕСПП.
/// Проверка, есть ли у расписания шаблон. Если имена шаблона и расписания не совпадают, установить шаблону статус обновления.
/// </summary>
/// <param name="esppObject"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task AfterParseStringToEsppObjectAsyncHandler(EsppObjectSchedule esppObject)
{
var esppScheduleId = esppObject.Code;
var esppTemplateName = esppObject.TemplateName;
using var scope = serviceProvider.CreateScope();
var templateService = scope.ServiceProvider.GetRequiredService<ITemplateService>();
var candidates = await templateService.Get()
.Include(t => t.RobotConfigurations)
.Where(t => t.Name == esppTemplateName || t.ScheduleEsppId == esppScheduleId)
.ToListAsync();
var templateByName = candidates.FirstOrDefault(t => t.Name == esppTemplateName);
var templateByScheduleId = candidates.FirstOrDefault(t => t.ScheduleEsppId == esppScheduleId);
if (templateByName == null && templateByScheduleId == null)
{
const int expectedParts = 24;
var separator = globalSettings.ParsingSeparator;
var parts = str.Split(separator);
if (parts.Length != expectedParts)
{
logger.LogWarning("Некорректное количество полей в строке расписания: {Actual} (ожидается {Expected})", parts.Length, expectedParts);
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
return;
}
var esppScheduleId = parts[0]; // ScheduleEsppId из ЕСПП
var templateNameRaw = parts[6];
var templateName = templateNameRaw.ToUpper();
// Проверка префикса (как в ParseStrToEsppObject)
if (string.IsNullOrEmpty(settingsFromDb.TemplatePrefixWithoutVariable) ||
!templateName.Contains(settingsFromDb.TemplatePrefixWithoutVariable))
{
logger.LogWarning("Имя шаблона '{TemplateName}' не соответствует префиксу '{Prefix}'. Пропущено.", templateName, settingsFromDb.TemplatePrefixWithoutVariable);
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
return;
}
// === ЕДИНЫЙ ЗАПРОС: ищем по имени ИЛИ по ScheduleEsppId ===
using var scope = serviceProvider.CreateScope();
var templateService = scope.ServiceProvider.GetRequiredService<ITemplateService>();
var candidates = await templateService.Get()
.Include(t => t.RobotConfigurations)
.Where(t => t.Name == templateName || t.ScheduleEsppId == esppScheduleId)
.ToListAsync();
var templateByName = candidates.FirstOrDefault(t => t.Name == templateName);
var templateByScheduleId = candidates.FirstOrDefault(t => t.ScheduleEsppId == esppScheduleId);
if (templateByName == null && templateByScheduleId == null)
{
// Нет ни по имени, ни по ID
logger.LogWarning(
"Расписание из ЕСПП не привязано ни к одному шаблону: TemplateName='{TemplateName}', ScheduleEsppId='{EsppId}'",
templateName,
esppScheduleId
);
}
else if (templateByName == null && templateByScheduleId != null)
{
// Есть только по ID → имя не совпадает
logger.LogWarning(
"Расхождение привязки: расписание из ЕСПП с ScheduleEsppId='{EsppId}' и TemplateName='{TemplateName}' " +
"соответствует шаблону в БД с именем '{DbTemplateName}', Id='{TemplateId}'.",
esppScheduleId,
templateName,
templateByScheduleId.Name,
templateByScheduleId.Id
);
// Принудительно выставляем статус обновления шаблона, так как имя в ЕСПП изменилось
try
{
var robotConfigurationService = scope.ServiceProvider.GetRequiredService<IRobotConfigurationService>();
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, templateByScheduleId);
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
// Сохраняем изменения в базу данных
if (!await robotConfigurationService.CommitAsync())
{
logger.LogError("Не удалось сохранить изменения статуса задачи для шаблона Id='{TemplateId}'", templateByScheduleId.Id);
}
else
{
logger.LogDebug($"Для шаблона {nameof(templateByScheduleId.Id)}:{templateByScheduleId.Id} установлен статус {RobotStatusEnum.Wait.ToString()} при обнаружении расхождения имени с ЕСПП");
}
}
catch (Exception updateEx)
{
logger.LogError(updateEx, "Ошибка при установке статуса обновления для шаблона Id='{TemplateId}'", templateByScheduleId.Id);
}
}
else if (templateByName != null)
{
var dbScheduleId = templateByName.ScheduleEsppId ?? string.Empty;
if (string.IsNullOrEmpty(dbScheduleId))
{
// Утерян ID в БД
logger.LogWarning(
"У шаблона '{TemplateName}' (Id='{TemplateId}') отсутствует ScheduleEsppId в БД, но в ЕСПП он равен '{EsppId}'",
templateName,
templateByName.Id,
esppScheduleId
);
}
else if (dbScheduleId != esppScheduleId)
{
// ID не совпадают — проверяем, не занят ли esppScheduleId другим шаблоном
var conflictingTemplate = candidates.FirstOrDefault(t =>
t.Id != templateByName.Id && t.ScheduleEsppId == esppScheduleId);
if (conflictingTemplate != null)
{
logger.LogWarning(
"Конфликт ScheduleEsppId: расписание '{EsppId}' из ЕСПП с именем '{TemplateName}' " +
"уже привязано к другому шаблону '{OtherTemplateName}' (Id='{OtherTemplateId}') в БД.",
esppScheduleId,
templateName,
conflictingTemplate.Name,
conflictingTemplate.Id
);
}
else
{
logger.LogWarning(
"Несовпадение ScheduleEsppId для шаблона '{TemplateName}' (Id='{TemplateId}'): в БД='{DbId}', в ЕСПП='{EsppId}'",
templateName,
templateByName.Id,
dbScheduleId,
esppScheduleId
);
}
}
}
// Передаём оригинальную строку в стандартный синхронизатор
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
// Нет ни по имени, ни по ID
logger.LogWarning("Расписание из ЕСПП не привязано ни к одному шаблону: TemplateName='{TemplateName}', ScheduleEsppId='{EsppId}'", esppTemplateName, esppScheduleId);
}
catch (Exception ex)
else if (templateByName == null && templateByScheduleId != null)
{
logger.LogError(ex, "Ошибка при синхронизации расписания из строки: {InputString}", str);
// Есть только по ID → имя не совпадает
logger.LogWarning("Расхождение привязки: расписание из ЕСПП с ScheduleEsppId='{EsppId}' и TemplateName='{TemplateName}' соответствует шаблону в БД с именем '{DbTemplateName}', Id='{TemplateId}'.",
esppScheduleId, esppTemplateName, templateByScheduleId.Name, templateByScheduleId.Id);
// Принудительно выставляем статус обновления шаблона, так как имя в ЕСПП изменилось
var robotConfigurationService = scope.ServiceProvider.GetRequiredService<IRobotConfigurationService>();
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, templateByScheduleId);
logger.LogDebug("Текущий статус задания {taskStatusCode} шаблона {templateId}", config.TaskStatusCode, templateByScheduleId.Id);
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
// Сохраняем изменения в базу данных
if (!await templateService.CommitAsync(new HistoryInitiator { InitiatorParrComponentId = ParrComponentsEnum.EsppScheduleSync, InitiatorComment = "Расхождение привязки, расписание из ЕСПП не соответствует имени шаблона в БД" }))
{
logger.LogError("Не удалось сохранить изменения статуса задачи для шаблона Id='{TemplateId}'", templateByScheduleId.Id);
}
else
{
logger.LogDebug("Для шаблона {templateId} установлен статус задания {taskStatus} при обнаружении расхождения имени с ЕСПП",
templateByScheduleId.Id, TaskStatusEnum.Updating.ToString());
}
}
else if (templateByName != null)
{
var dbScheduleId = templateByName.ScheduleEsppId ?? string.Empty;
if (string.IsNullOrEmpty(dbScheduleId))
{
// Утерян ID в БД
logger.LogWarning("У шаблона '{TemplateName}' (Id='{TemplateId}') отсутствует ScheduleEsppId в БД, но в ЕСПП он равен '{EsppId}'", esppTemplateName, templateByName.Id, esppScheduleId);
}
else if (dbScheduleId != esppScheduleId)
{
// ID не совпадают — проверяем, не занят ли esppScheduleId другим шаблоном
var conflictingTemplate = candidates.FirstOrDefault(t => t.Id != templateByName.Id && t.ScheduleEsppId == esppScheduleId);
if (conflictingTemplate != null)
{
logger.LogWarning("Конфликт ScheduleEsppId: расписание '{EsppId}' из ЕСПП с именем '{TemplateName}' уже привязано к другому шаблону '{OtherTemplateName}' (Id='{OtherTemplateId}') в БД.",
esppScheduleId, esppTemplateName, conflictingTemplate.Name, conflictingTemplate.Id);
}
else
{
logger.LogWarning("Несовпадение ScheduleEsppId для шаблона '{TemplateName}' (Id='{TemplateId}'): в БД='{DbId}', в ЕСПП='{EsppId}'",
esppTemplateName, templateByName.Id, dbScheduleId, esppScheduleId);
}
}
}
}
/// <summary>
/// Преобразование модели БД в модель для сравнения
/// </summary>
@@ -253,10 +199,10 @@ namespace PARR.EsppScheduleSync
/// <exception cref="NotImplementedException"></exception>
private EsppObjectSchedule ConvertDbObjToEsppObj(Template template)
{
//дефолтное значение, изменится в сервисе nextRunModifierService
var nextRunWithTimeZone = DateTimeOffset.MinValue;
// дефолтное значение, изменится в ApplyShortcodesAsync
var responseArea = "%ЗО_РГ%";
//дефолтное значение, nextRun в часовом поясе робота ЕСПП
var nextRunWithEsppTz = DateTimeOffset.MinValue;
//// дефолтное значение, изменится в ApplyShortcodesAsync
//var responseArea = "%ЗО_РГ%";
using (var scope = serviceProvider.CreateScope())
{
@@ -266,11 +212,11 @@ namespace PARR.EsppScheduleSync
//nextRunWithTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun);
var nextRunService = scope.ServiceProvider.GetRequiredService<INextRunService>();
var shortcodeService = scope.ServiceProvider.GetRequiredService<IShortcodesService>();
var nextRunService = scope.ServiceProvider.GetRequiredService<INextRunServiceV2>();
//var shortcodeService = scope.ServiceProvider.GetRequiredService<IShortcodesService>();
responseArea = shortcodeService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template).GetAwaiter().GetResult();
nextRunWithTimeZone = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(template.NextRun, template.Job?.Group?.IsResponseAreaTimezone, responseArea);
//responseArea = shortcodeService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template).GetAwaiter().GetResult();
nextRunWithEsppTz = template.NextRun.Add(nextRunService.GetEsppAccountOffset());
}
var esppObjectFromDb = new EsppObjectSchedule
@@ -289,9 +235,9 @@ namespace PARR.EsppScheduleSync
//Scheduled = EsppScheduleHelpers.GetNextRun(template.NextRun),
//Scheduled = EsppScheduleHelpers.GetNextRun(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
//BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
Scheduled = EsppScheduleHelpers.GetNextRun(nextRunWithTimeZone),
BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithTimeZone),
Timezone = GetTimezone(template, responseArea),
Scheduled = EsppScheduleHelpers.GetNextRun(nextRunWithEsppTz),
BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithEsppTz),
Timezone = GetTimezone(/*template, responseArea*/),
//Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать
TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "",
CompleteAfter = ""
@@ -301,34 +247,117 @@ namespace PARR.EsppScheduleSync
return ClearOptionalFields(esppObjectFromDb);
}
private string GetTimezone(Template template, string responseArea)
/// <summary>
/// Кастомная дополнительная проверка полей
/// </summary>
/// <param name="esppObject"></param>
/// <param name="dbObject"></param>
/// <param name="templateId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task<bool> CustomComparisionCheckAsyncHandler(EsppObjectSchedule esppObject, EsppObjectSchedule dbObject, Guid templateId)
{
// рассчитать nextRun, и сравнить все три nextRun, БД - ЕСПП - Расчитанное
//if (template.Job?.Group?.IsWorkGroupTimezone != true)
// return settingsFromDb.ScheduleTimezone;
// в часовой зоне робота
var calculatedNextRun = await CalcNextRunWithRobotTzAsync(templateId);
if (calculatedNextRun == null)
{
return false;
}
if (template.Job?.Group?.IsResponseAreaTimezone != true)
return scheduleResponseAreaTimeOffsetService.GetDefault.EsppValue;
var scheduledCalculated = EsppScheduleHelpers.GetNextRun(calculatedNextRun.Value);
var basisTimeCalculated = EsppScheduleHelpers.GetGenerationTime(calculatedNextRun.Value);
//var responseArea = template.Unit?.BaseFields?.ResponseArea;
logger.LogDebug("Рассчитанные значения для шаблона '{templateName}', {templateId}, следующее срабатывание {scheduledCalculated}, время создания наряда: {basisTimeCalculated}",
dbObject.TemplateName, templateId, scheduledCalculated, basisTimeCalculated);
//if (string.IsNullOrEmpty(responseArea))
//{
// throw new InvalidOperationException(
// $"У шаблона Id={template.Id}, Name='{template.Name}' не задана ResponseArea в Unit.BaseFields, " +
// "но включена настройка 'использовать часовой пояс рабочей группы'.");
//}
if (EsppSyncHelpers.Normalize(dbObject.Scheduled) != EsppSyncHelpers.Normalize(esppObject.Scheduled) || EsppSyncHelpers.Normalize(dbObject.Scheduled) != scheduledCalculated || EsppSyncHelpers.Normalize(esppObject.Scheduled) != scheduledCalculated)
{
logger.LogInformation("Не совпадают поля ({propName}), dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}, scheduledCalculated: {scheduledCalculated}. Имя шаблона: {templateName}",
nameof(dbObject.Scheduled), dbObject.Scheduled, esppObject.Scheduled, scheduledCalculated, dbObject.TemplateName);
return false;
}
//if (!responseAreaTimeOffsetDict.TryGetValue(responseArea, out var offset))
//{
// throw new InvalidOperationException(
// $"Не найдено временное смещение для ResponseArea '{responseArea}' у шаблона Id={template.Id}, Name='{template.Name}'. " +
// "Проверьте наличие записи в таблице ScheduleResponseAreaTimeOffset.");
//}
if (EsppSyncHelpers.Normalize(dbObject.BasisTime) != EsppSyncHelpers.Normalize(esppObject.BasisTime) || EsppSyncHelpers.Normalize(dbObject.BasisTime) != basisTimeCalculated || EsppSyncHelpers.Normalize(esppObject.BasisTime) != basisTimeCalculated)
{
logger.LogInformation("Не совпадают поля ({propName}), dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}, basisTimeCalculated: {basisTimeCalculated}. Имя шаблона: {templateName}",
nameof(dbObject.BasisTime), dbObject.BasisTime, esppObject.BasisTime, basisTimeCalculated, dbObject.TemplateName);
return false;
}
//return offset;
logger.LogDebug("Значения nextRun в БД, ЕСПП, расчитанное, все совпадают. Scheduled: {scheduled}, basisTime: {basisTime}", scheduledCalculated, basisTimeCalculated);
return scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).EsppValue;
return true;
}
/// <summary>
/// Рассчитать nextRun в часовом поясе УЗ робота
/// </summary>
/// <param name="templateId"></param>
/// <returns></returns>
private async Task<DateTimeOffset?> CalcNextRunWithRobotTzAsync(Guid templateId)
{
using var scope = serviceProvider.CreateScope();
var nextRunService = scope.ServiceProvider.GetRequiredService<INextRunServiceV2>();
var nextRun = await nextRunService.GetNextRunForTemplateAsync(templateId, isNew: false);
if (nextRun.HasValue)
{
var nextRunWithEsppAccountTz = nextRun.Value.Add(nextRunService.GetEsppAccountOffset());
logger.LogDebug("Расчитанный nextRun для шаблона {templateId}, UTC: {nextRun}, EsppAccountTz: {nextRunWithEsppAccountTz}", templateId, nextRun, nextRunWithEsppAccountTz);
return nextRunWithEsppAccountTz;
}
else
{
logger.LogError("При расчете nextRun для templateId: {templateId} верунлся null", templateId);
return null;
}
}
/// <summary>
/// Получить "В каком часовом поясе"
/// </summary>
/// <returns></returns>
private string GetTimezone(/*Template template, string responseArea*/)
{
// договорились, что у роботоа ТЗ МСК
return settingsFromDb.EsppScheduleTimezone;
#region old
////if (template.Job?.Group?.IsWorkGroupTimezone != true)
//// return settingsFromDb.ScheduleTimezone;
//if (template.Job?.Group?.IsResponseAreaTimezone != true)
// return scheduleResponseAreaTimeOffsetService.GetDefault.EsppValue;
////var responseArea = template.Unit?.BaseFields?.ResponseArea;
////if (string.IsNullOrEmpty(responseArea))
////{
//// throw new InvalidOperationException(
//// $"У шаблона Id={template.Id}, Name='{template.Name}' не задана ResponseArea в Unit.BaseFields, " +
//// "но включена настройка 'использовать часовой пояс рабочей группы'.");
////}
////if (!responseAreaTimeOffsetDict.TryGetValue(responseArea, out var offset))
////{
//// throw new InvalidOperationException(
//// $"Не найдено временное смещение для ResponseArea '{responseArea}' у шаблона Id={template.Id}, Name='{template.Name}'. " +
//// "Проверьте наличие записи в таблице ScheduleResponseAreaTimeOffset.");
////}
////return offset;
//return scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).EsppValue;
#endregion
}
@@ -373,20 +402,6 @@ namespace PARR.EsppScheduleSync
logger.LogDebug("Для шаблона шаблона {TemplateName}, {TemplateId} тип исключения \"{Name}\", EsppValue: {EsppValue}", template.Name, template.Id, template.Job.Group.ScheduleExcludeType.Title, template.Job.Group.ScheduleExcludeType.EsppValue);
return template.Job.Group.ScheduleExcludeType.EsppValue;
#region old logic
//// Это костыль, нужно придумать как это хранить в БД.
//switch (settingsFromDb.ScheduleExcludeType.ToLower())
//{
// case ("нет исключений"):
// return "NONE";
// case ("выполнить только в указанном календаре"):
// return "ONLY";
// default:
// return "";
//}
#endregion
}
@@ -426,17 +441,7 @@ namespace PARR.EsppScheduleSync
esppObject.Dayofweek = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
break;
case EsppSchTypeScheduleEnum.Monthly:
//если включено автораспределение, подставляем дату месяца из NextRun
//if (template.ApplicationsInWork?.IsAutoDistributionEnabled == true)//TODO Migration to job
//{
// var nextRunByRobotTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun);
// esppObject.Dayofmonth = nextRunByRobotTimeZone.Day.ToString();
// // esppObject.Dayofmonth = template.NextRun.Day.ToString();
//}
//else
//{
esppObject.Dayofmonth = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
//}
break;
case EsppSchTypeScheduleEnum.Monthly2:
esppObject.Md1 = esppSchedule.Values.First(t => t.Order == 0).Value.EsppExportValue;
@@ -452,8 +457,6 @@ namespace PARR.EsppScheduleSync
esppObject.An3 = esppSchedule.Values.First(t => t.Order == 2).Value.EsppExportValue;
break;
}
}
}

View File

@@ -1,10 +1,9 @@
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.EsppSync;
namespace PARR.EsppScheduleSync.Domain
namespace PARR.EsppSync.Domain
{
internal class EsppObjectSchedule : IEsppObject
public class EsppObjectSchedule : IEsppObject
{
//Все поля описаны в документации по роботам: http://gitlab.dvgd.oao.rzd/devptk/parr/parr_api/-/wikis/EsppRobots
@@ -12,6 +11,9 @@ namespace PARR.EsppScheduleSync.Domain
public required string TemplateName { get; set; }
/// <summary>
/// ИД расписания
/// </summary>
public string Code { get; set; } = string.Empty;
public string ScheduleName { get; set; } = string.Empty;

View File

@@ -0,0 +1,25 @@
namespace PARR.EsppSync.Helpers
{
/// <summary>
/// Хелперы для EsppSync
/// </summary>
public static class EsppSyncHelpers
{
/// <summary>
/// Првести строку в стандарт сравнения (для сравнения объектов)
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string Normalize(string? str)
{
if (str == null)
return string.Empty;
str = str.Replace("\r", string.Empty);
str = str.Replace("\n", string.Empty);
str = str.Replace(" ", string.Empty);
return str.ToLower();
}
}
}

View File

@@ -8,7 +8,7 @@ namespace PARR.EsppSync
/// <typeparam name="EsppObject"></typeparam>
/// <param name="str"></param>
/// <returns></returns>
public delegate EsppObject? ParserHandlerDelegate<EsppObject>(string str) where EsppObject : class, IEsppObject;
public delegate EsppObject? ParseStringToEsppObject<EsppObject>(string str) where EsppObject : class, IEsppObject;
/// <summary>
/// Конвертирует Template в модель для сравнения
@@ -16,17 +16,44 @@ namespace PARR.EsppSync
/// <typeparam name="EsppObject"></typeparam>
/// <param name="templateName"></param>
/// <returns></returns>
public delegate EsppObject ConvertDbObjToComparisonObjHandlerDelegate<EsppObject>(Template template) where EsppObject : class, IEsppObject;
public delegate EsppObject ConvertDbToEsppObject<EsppObject>(Template template) where EsppObject : class, IEsppObject;
/// <summary>
/// Дополнительная проверка полей, если проверка прошла, то true
/// </summary>
/// <typeparam name="EsppObject"></typeparam>
/// <param name="esppObject"></param>
/// <param name="dbObject"></param>
/// <param name="templateId"></param>
/// <returns>true - проверка пройдена, false - проверка не пройдена</returns>
public delegate Task<bool> CustomComparisionCheckAsync<EsppObject>(EsppObject esppObject, EsppObject dbObject, Guid templateId) where EsppObject : class, IEsppObject;
/// <summary>
/// Выполнить произвольную логику после парсинга строки в объект ЕСПП
/// </summary>
/// <typeparam name="EsppObject"></typeparam>
/// <param name="esppObject"></param>
/// <returns></returns>
public delegate Task AfterParseStringToEsppObjectAsync<EsppObject>(EsppObject esppObject) where EsppObject : class, IEsppObject;
public interface ISyncService<EsppObject> where EsppObject : class, IEsppObject
{
/// <summary>
///
/// Синхронизировать объекты
/// </summary>
/// <param name="str">Строка из RabbitMQ</param>
/// <param name="parser">Делегат парсинга из строки в модель EsppObject</param>
/// <param name="converterToEsppObject"> Конвертирует BD Template в модель для сравнения</param>
/// <param name="parseStringToEsppObject">Делегат парсинга из строки в модель EsppObject</param>
/// <param name="converterDbToEsppObject"> Конвертирует BD Template в модель для сравнения</param>
/// <param name="customComparisionAsync">Кастомное сравнение полей</param>
/// <param name="afterParseStringToEsppObjectAsync">Выполнить произвольный метод после парсинга строки в модель EsppObject</param>
/// <returns></returns>
Task SyncEsppObjectAsync(string str, ParserHandlerDelegate<EsppObject> parser, ConvertDbObjToComparisonObjHandlerDelegate<EsppObject> converterToEsppObject);
Task SyncEsppObjectAsync(string str,
ParseStringToEsppObject<EsppObject> parseStringToEsppObject,
ConvertDbToEsppObject<EsppObject> converterDbToEsppObject,
CustomComparisionCheckAsync<EsppObject>? customComparisionAsync = null,
AfterParseStringToEsppObjectAsync<EsppObject>? afterParseStringToEsppObjectAsync = null
);
}
}

View File

@@ -6,6 +6,7 @@ using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.EsppSync.Helpers;
using System.Reflection;
namespace PARR.EsppSync
@@ -27,8 +28,10 @@ namespace PARR.EsppSync
public async Task SyncEsppObjectAsync(
string str,
ParserHandlerDelegate<EsppObject> parser,
ConvertDbObjToComparisonObjHandlerDelegate<EsppObject> converterToEsppObject
ParseStringToEsppObject<EsppObject> parseStringToEsppObject,
ConvertDbToEsppObject<EsppObject> converterDbToEsppObject,
CustomComparisionCheckAsync<EsppObject>? customComparisionAsync = null,
AfterParseStringToEsppObjectAsync<EsppObject>? afterParseStringToEsppObjectAsync = null
)
{
logger.LogDebug("Получил строку. Начинаю работать. Строка: {String}", str);
@@ -39,7 +42,7 @@ namespace PARR.EsppSync
return;
}
var esppObject = parser.Invoke(str);
var esppObject = parseStringToEsppObject.Invoke(str);
if (esppObject == null)
{
@@ -47,18 +50,22 @@ namespace PARR.EsppSync
return;
}
// Вызовем кастомный метод, если он есть
if (afterParseStringToEsppObjectAsync != null)
await afterParseStringToEsppObjectAsync.Invoke(esppObject);
using (var scope = serviceProvider.CreateScope())
{
var templateService = GetServiceInScope<ITemplateService>(scope);
var robotConfigurationService = GetServiceInScope<IRobotConfigurationService>(scope);
var shortcodesService = GetServiceInScope<IShortcodesService>(scope);
try
{
// Загружаем Template и TemplateForShortcodes в одном запросе
var query = templateService.Get()
//.AsNoTracking() <- не надо так, а то потом не сохранится
//.AsNoTracking()
.Include(h => h.Unit)
.ThenInclude(t => t!.UnitValues)
.ThenInclude(t => t.Value)
@@ -85,32 +92,15 @@ namespace PARR.EsppSync
if (template == null)
{
logger.LogWarning("Найден объект в ЕСПП с именем шаблона {TemplateName} незарегистрированный в ПАРР. Строка: {String}", esppObject.TemplateName, str);
logger.LogWarning("Найден объект в ЕСПП с именем шаблона '{TemplateName}' незарегистрированный в ПАРР. Строка: {String}", esppObject.TemplateName, str);
return;
}
var dbObjectInEsppObject = converterToEsppObject.Invoke(template);
var dbObjectInEsppObject = converterDbToEsppObject.Invoke(template);
// Проверяем наличие Shortcode в полях объекта из БД
var properties = dbObjectInEsppObject.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(string))
{
var value = property.GetValue(dbObjectInEsppObject)?.ToString();
if (!string.IsNullOrEmpty(value))
{
// Передаём исходный template — он уже загружен с Include
var processedValue = await shortcodesService.ApplyShortcodesAsync(value, template);
property.SetValue(dbObjectInEsppObject, processedValue);
}
}
}
await ApplyShortcodesAsync(dbObjectInEsppObject, template, shortcodesService);
bool isChanged = false;
//TODO: FIX ME Please, BRO
//bool isChanged;
// если в БД isActive == false, то синхронизировать только по полям из IEsppObject
@@ -126,6 +116,30 @@ namespace PARR.EsppSync
{
logger.LogDebug("Объект активирован в ПАРР. Сравниваем все поля. {TemplateName}", esppObject.TemplateName);
isChanged = IsChanged(esppObject, dbObjectInEsppObject, esppObject.TemplateName);
// выполняем кастомную дополнительную проверку (только если isChanged==false, чтоб лишний раз не гонять)
if (!isChanged)
{
if (customComparisionAsync != null)
{
// добавлена дополнительная проверка
var isCustomComparision = await customComparisionAsync.Invoke(esppObject, dbObjectInEsppObject, template.Id);
if (isCustomComparision)
{
logger.LogDebug("Дополнительная проверка прошла.");
}
else
{
// если дополнительная проверка не прошла, то говорим что есть изменения
isChanged = true;
logger.LogDebug("Дополнительная проверка не прошла, ставим статус isChanged: {isChanged}", isChanged);
}
}
else
{
logger.LogDebug("Дополнительная проверка отсутствует");
}
}
}
if (isChanged)
@@ -151,15 +165,6 @@ namespace PARR.EsppSync
logger.LogInformation("Есть изменения в Template {TemplateName}, но предыдущий статус TaskStatusCode: {TaskStatusCode}. Не меняем статус, будем разбираться вручную.", template.Name, (TaskStatusEnum)config.TaskStatusCode);
}
#region Old logic
//SetUpdateStatus(ref template, robotConfigurationService, esppObject.Robot);
//if (!await templateService.CommitAsync())
// logger.LogError($"Не удалось изменить запись Template {template.Name}, Robot: {esppObject.Robot}");
//else
// logger.LogInformation($"Установлен принудительный статус {TaskStatusEnum.Updating}, Template {template.Name}, Robot: {esppObject.Robot}");
#endregion
}//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит
else
{
@@ -186,6 +191,40 @@ namespace PARR.EsppSync
}
/// <summary>
/// Применяем шорткоды
/// </summary>
/// <param name="dbObjectInEsppObject"></param>
/// <param name="template"></param>
/// <param name="shortcodesService"></param>
/// <returns></returns>
private async Task ApplyShortcodesAsync(EsppObject dbObjectInEsppObject, Template template, IShortcodesService shortcodesService)
{
// Проверяем наличие Shortcode в полях объекта из БД
var properties = dbObjectInEsppObject.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(string))
{
var value = property.GetValue(dbObjectInEsppObject)?.ToString();
if (!string.IsNullOrEmpty(value))
{
// Передаём исходный template — он уже загружен с Include
var processedValue = await shortcodesService.ApplyShortcodesAsync(value, template);
property.SetValue(dbObjectInEsppObject, processedValue);
}
}
}
}
/// <summary>
/// Установить статус - Обновить
/// </summary>
/// <param name="template"></param>
/// <param name="robotConfigurationService"></param>
/// <param name="robot"></param>
private void SetUpdateStatus(ref Template template, IRobotConfigurationService robotConfigurationService, RobotsEnum robot)
{
var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(robot, template);
@@ -193,6 +232,13 @@ namespace PARR.EsppSync
}
/// <summary>
/// Получить сервис из scope
/// </summary>
/// <typeparam name="Service"></typeparam>
/// <param name="scope"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private Service GetServiceInScope<Service>(IServiceScope scope)
{
var service = scope.ServiceProvider.GetService<Service>();
@@ -203,7 +249,13 @@ namespace PARR.EsppSync
}
//private bool IsChanged(EsppObject esppObj, EsppObject dbObj)
/// <summary>
/// Сравнение объектов
/// </summary>
/// <param name="esppObj"></param>
/// <param name="dbObj"></param>
/// <param name="templateName"></param>
/// <returns></returns>
private bool IsChanged(object esppObj, object dbObj, string templateName)
{
foreach (var prop in dbObj.GetType().GetProperties())
@@ -218,8 +270,8 @@ namespace PARR.EsppSync
continue;
//Replace("\r","").Replace("\n","") - в подробном описании могут быть переносы строк, в Rabbit прилетает без переносов. Убираем переносы для стравнения
var dbValueStr = Normalize(dbValue!.ToString()!);
var esppValueStr = Normalize(esppValue!.ToString()!);
var dbValueStr = EsppSyncHelpers.Normalize(dbValue!.ToString());
var esppValueStr = EsppSyncHelpers.Normalize(esppValue!.ToString());
if (dbValueStr != esppValueStr)
{
@@ -232,19 +284,5 @@ namespace PARR.EsppSync
return false;
}
/// <summary>
/// Удаляет ненужные символы из строки
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string Normalize(string str)
{
str = str.Replace("\r", string.Empty);
str = str.Replace("\n", string.Empty);
str = str.Replace(" ", string.Empty);
return str.ToLower();
}
}
}

View File

@@ -59,16 +59,7 @@ namespace PARR.EsppTemplateSync
private async Task SyncTemplateAsync(string str)
{
//using (var scope = serviceProvider.CreateScope())
//{
// var syncService = scope.ServiceProvider.GetService<ISyncService<EsppObjectTemplate>>();
// if (syncService == null)
// throw new Exception($"Не смог получить серивс {nameof(ITemplateSyncer)}");
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
//}
}

View File

@@ -43,10 +43,10 @@ namespace PARR.NextRun
//var esppSchService = scope.ServiceProvider.GetService<IEsppScheduleTransformService>();
var templateService = scope.ServiceProvider.GetService<ITemplateService>();
var jobGroupService = scope.ServiceProvider.GetService<IJobGroupService>();
var nextRunService = scope.ServiceProvider.GetService<INextRunService>();
var nextRunService = scope.ServiceProvider.GetService<INextRunServiceV2>();
if (templateService == null || nextRunService == null || jobGroupService == null)
throw new Exception($"Не смог получить серивс {nameof(ITemplateService)} или {nameof(INextRunService)} или {nameof(IJobGroupService)}");
throw new Exception($"Не смог получить серивс {nameof(ITemplateService)} или {nameof(INextRunServiceV2)} или {nameof(IJobGroupService)}");
await HandlerAsync(templateService, nextRunService, jobGroupService);
}
@@ -58,7 +58,7 @@ namespace PARR.NextRun
/// Рассчет следующей даты срабатываения
/// </summary>
/// <returns></returns>
private async Task HandlerAsync(ITemplateService templateService, INextRunService nextRunService, IJobGroupService jobGroupService)
private async Task HandlerAsync(ITemplateService templateService, INextRunServiceV2 nextRunService, IJobGroupService jobGroupService)
{
logger.LogInformation("Начинаю обновлять NextRun по расписанию");
@@ -74,7 +74,7 @@ namespace PARR.NextRun
}
private async Task UpdateNextRunAsync(ITemplateService templateService, INextRunService nextRunService, IJobGroupService jobGroupService)
private async Task UpdateNextRunAsync(ITemplateService templateService, INextRunServiceV2 nextRunService, IJobGroupService jobGroupService)
{
// выбираем все шаблоны с просроченным nextRun в статусе Used
@@ -94,8 +94,15 @@ namespace PARR.NextRun
{
var newNextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
template.LastRun = template.NextRun;
template.NextRun = newNextRun;
if (newNextRun.HasValue)
{
template.LastRun = template.NextRun;
template.NextRun = newNextRun.Value;
}
else
{
logger.LogError("При расчете nextRun для шаблона {templateId}, '{templateName}', nextRun=null. Это значит что при расчете возникла ошибка.", template.Id, template.Name);
}
}
if (await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Обновлён NextRun", InitiatorParrComponentId = ParrComponentsEnum.NextRun }))
@@ -110,139 +117,6 @@ namespace PARR.NextRun
}
#region old
/// <summary>
/// Обновить nextRun для ВСЕХ шаблонов, для групп у которых расписание ЕСПП (выключено автораспределение)
/// </summary>
/// <returns></returns>
private async Task UpdateNextRunWithEsppScheduleAsync(ITemplateService templateService, INextRunService nextRunService, IJobGroupService jobGroupService)
{
// выбираем шаблоны которые не учавствуют в автораспределении
// и у которых Used
// обновляем для всех шаблонов, у которых nextRun!=рассчитанному
var templatesForUpdate = await templateService.Get()
.Where(t =>
t.StatusTypeId == TemplateStatusTypeEnum.Used
&& t.Job!.Group!.IsAutoDistributionEnabled == false
&& t.NextRun < DateTimeOffset.UtcNow
).ToListAsync();
logger.LogInformation("Найдено шаблонов с выключенным автораспределением, с просроченным NextRun {count} шт.", templatesForUpdate.Count);
if (!templatesForUpdate.Any())
return;
foreach (var template in templatesForUpdate)
{
var newNextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
template.LastRun = template.NextRun;
template.NextRun = newNextRun;
}
if (await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Обновлён NextRun", InitiatorParrComponentId = ParrComponentsEnum.NextRun }))
{
logger.LogInformation("Обновлены значения ПРОСРОЧЕННЫХ полей NextRun для шаблонов с ВЫКЛЮЧЕННЫМ автораспределением, {count} шт.", templatesForUpdate.Count);
}
else
{
logger.LogError("Ошибка при обновлении ПРОСРОЧЕННЫХ значений полей NextRun, для шаблонов {count} шт., с ВЫКЛЮЧЕННЫМ автораспределением", templatesForUpdate.Count);
}
//// выбираем шаблоны которые не учавствуют в автораспределении
//// и у которых Used
//// затем группируем по GroupId, так как nextRun и расписание настраивается для группы, то для всех дочерних шаблонов, nextRun будет одинаковым
//// обновляем для всех шаблонов, у которых nextRun!=рассчитанному
//var groups = await jobGroupService.Get()
// .Where(t => t.IsAutoDistributionEnabled == false)
// .Select(t => new { t.Id, t.ReferenceDate })
// .ToListAsync();
//logger.LogInformation("Найдено {GroupCount} групп с выключенным автораспределением для обработки.", groups.Count);
//foreach (var group in groups)
//{
// //получаем по каждой группе следующий nextRun и сравниваем с существующим, если не равны, то обновляем
// //var nextRun = await esppScheduleTransformService.GetNextDateAsync(group.Id, group.ReferenceDate);
// var nextRun = await nextRunService.GetNextRunForJobGroupWithEsppSchedulleAsync(group.Id);
// // Загружаем только шаблоны текущей группы где nextRun в БД не равен расчетному
// var templatesToUpdate = await templateService.Get()
// .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && t.Job!.GroupId == group.Id && t.NextRun != nextRun)
// .ToListAsync();
// logger.LogInformation("Для группы {groupId}, шаблонов для обновления где nextRun!=рассчетному {newNextRun} найдено {count} шт.", group.Id, nextRun, templatesToUpdate.Count);
// if (!templatesToUpdate.Any())
// continue;
// // Обновляем
// foreach (var template in templatesToUpdate)
// {
// template.LastRun = template.NextRun;
// template.NextRun = nextRun;
// }
// // Коммитим изменения для этой группы
// if (await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Обновлён NextRun", InitiatorParrComponentId = ParrComponentsEnum.NextRun }))
// logger.LogInformation($"Обновлены значения полей NextRun для шаблонов группы: {group.Id} с выключенным автораспределением, " +
// $"refDate: {group.ReferenceDate}. Обновлен nextRun: {nextRun} для шаблонов {templatesToUpdate.Count} шт.");
// else
// {
// logger.LogError($"Ошибка при обновлении значений полей NextRun: {nextRun}, для шаблонов {templatesToUpdate.Count} шт., группы {group.Id} с выключенным автораспределением, refDate: {group.ReferenceDate} ");
// }
//}
}
/// <summary>
/// Обновить ПРОСРОЧЕННЫЕ NextRun для групп у которых включено автораспределение
/// </summary>
/// <param name="templateService"></param>
/// <param name="nextRunService"></param>
/// <param name="jobGroupService"></param>
/// <returns></returns>
private async Task UpdateNextRunWithAutodistributeScheduleAsync(ITemplateService templateService, INextRunService nextRunService, IJobGroupService jobGroupService)
{
// получаем список всех шаблонов с включенным автораспределением у которых ПРОСРОЧЕН nextRun и они Used
// обновляем у них NextRun
var templatesForUpdate = await templateService.Get()
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && t.Job.Group.IsAutoDistributionEnabled == true && t.NextRun < DateTimeOffset.UtcNow)
.ToListAsync();
logger.LogInformation("Найдено шаблонов с автораспределением, с просроченным NextRun {count} шт.", templatesForUpdate.Count);
if (!templatesForUpdate.Any())
return;
foreach (var template in templatesForUpdate)
{
var newNextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
template.LastRun = template.NextRun;
template.NextRun = newNextRun;
}
if (await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Обновлён NextRun", InitiatorParrComponentId = ParrComponentsEnum.NextRun }))
{
logger.LogInformation("Обновлены значения ПРОСРОЧЕННЫХ полей NextRun для шаблонов с ВКЛЮЧЕННЫМ автораспределением, {count} шт.", templatesForUpdate.Count);
}
else
{
logger.LogError("Ошибка при обновлении ПРОСРОЧЕННЫХ значений полей NextRun, для шаблонов {count} шт., с ВКЛЮЧЕННЫМ автораспределением", templatesForUpdate.Count);
}
}
#endregion
}
}

View File

@@ -11,13 +11,15 @@ namespace PARR.TemplateDistributor
internal class TemplateDistributor : ITemplateDistributor
{
private readonly ILogger<TemplateDistributor> logger;
private readonly INextRunService nextRunService;
//private readonly INextRunService nextRunService;
private readonly INextRunServiceV2 nextRunService;
private readonly ITemplateService templateService;
private readonly IRobotConfigurationService robotConfigurationService;
public TemplateDistributor(
ILogger<TemplateDistributor> logger,
INextRunService nextRunService,
//INextRunService nextRunService,
INextRunServiceV2 nextRunService,
ITemplateService templateService,
IRobotConfigurationService robotConfigurationService
)
@@ -32,20 +34,22 @@ namespace PARR.TemplateDistributor
public async Task DistributeAsync(TemplateDistributorMq mqResponse)
{
var jobGroupId = mqResponse.JobGroupId;
// распределяем шаблоны только в статусе Used
var templateStatusType = TemplateStatusTypeEnum.Used;
// вызвать метод распределения, и получить новые даты
var distributedTemplates = await nextRunService.GetNextRunForJobGroupWithAutoDistributionAsync(jobGroupId);
var distributedTemplates = await nextRunService.GetNextRunForJobGroupWithAutoDistributionAsync(jobGroupId, templateStatusType);
if (distributedTemplates == null)
{
logger.LogWarning("При распределении шаблонов по jobGroupId {jobGroupId} вернулся null. Это ошибка. Прекращаю распределение.", jobGroupId);
logger.LogError("При распределении шаблонов по jobGroupId {jobGroupId} вернулся null. Это ошибка. Прекращаю распределение.", jobGroupId);
return;
}
// получить список шаблонов, сравнить их с распределенными, обновить даты, сохранить
var groupTemplates = await templateService.Get()
.Include(t => t.RobotConfigurations)
.Where(t => t.Job.GroupId == jobGroupId)
.Where(t => t.Job!.GroupId == jobGroupId && t.StatusTypeId == templateStatusType)
.ToListAsync();
if (!groupTemplates.Any())

View File

@@ -1,33 +1,83 @@
using Microsoft.EntityFrameworkCore;
using PARR.Constants;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.NextRunServices;
using PARR.DAL.NextRunServices.Models;
using PARR.DAL.NextRunServices.Subservices;
using PARR.DAL.Services.Interfaces;
using System.Threading.Tasks;
namespace PARR.Test.NextRun
{
internal class NextRunTest
{
//private readonly ITemplateDistributor templateDistributor;
private readonly INextRunService nextRunService;
//private readonly INextRunService nextRunService;
private readonly ITemplateService templateService;
private readonly IShortcodesService shortcodesService;
//private readonly ITemplateDistributorV2 templateDistributorV2;
private readonly INextRunServiceV2 nextRunServiceV2;
public NextRunTest(/*ITemplateDistributor templateDistributor, */INextRunService nextRunService, ITemplateService templateService, IShortcodesService shortcodesService)
public NextRunTest(
/*ITemplateDistributor templateDistributor, */
//INextRunService nextRunService,
ITemplateService templateService,
IShortcodesService shortcodesService,
//ITemplateDistributorV2 templateDistributorV2,
INextRunServiceV2 nextRunServiceV2
)
{
//this.templateDistributor = templateDistributor;
this.nextRunService = nextRunService;
//this.nextRunService = nextRunService;
this.templateService = templateService;
this.shortcodesService = shortcodesService;
//this.templateDistributorV2 = templateDistributorV2;
this.nextRunServiceV2 = nextRunServiceV2;
}
int periodDays = 10;
DateTimeOffset referenceDate = new DateTimeOffset(2025, 1, 20, 3, 0, 0, TimeSpan.Zero);
public async Task Test()
{
#region тестирование INextRunServiceV2
//рапсределить
//var distributeResult = await nextRunServiceV2.GetNextRunForJobGroupWithAutoDistributionAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"), TemplateStatusTypeEnum.Used);
// новый шаблон - автодистриб
//var nextRunForNewTemplateDistrib = await nextRunServiceV2.GetNextRunForNewTemplateAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"), workGroupName: "ЦКИТ-ВСИБ", responseAreaName: "92-ВСИБ");
// новый шаблон - еспп
//var nextRunForNewTemplateEspp = await nextRunServiceV2.GetNextRunForNewTemplateAsync(Guid.Parse("692531d6-4ef4-4a6d-99be-d2ef375d520f"), workGroupName: "ЦКИТ-ЗСИБ", responseAreaName: "83-ЗСИБ");
// существующий шаблон - автодистриб
//var nextRunForExistTemplateDistib = await nextRunServiceV2.GetNextRunForTemplateAsync(Guid.Parse("05769f8d-9630-4683-b07a-070d69e1dc12"), false);
// существующий шаблон - еспп
//var nextRunForExistTemplateEspp = await nextRunServiceV2.GetNextRunForTemplateAsync(Guid.Parse("1329dfca-5e09-4b19-a2e6-560a4896080c"), false);
#endregion
#region Тестирование templateDistributorV2
//var duration = 2;
//var offset = TimeSpan.FromHours(3);
//var startDate = new DateOnly(2026, 2, 10);
//var referenceDate = new DateTimeOffset(2026, 2, 13, 23, 0, 0, TimeSpan.Zero);
//var templates = GetTemplates();
//var templatesEmpty = new List<TemplateNextRunDto>();
//var templatesToHandeler = templatesEmpty; // templatesEmpty // templates
//// распределить шаблоны
//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, 15, 23, 30, 0, TimeSpan.Zero));
////var validNextRun = await templateDistributorV2.GetNextRunForTemplateAsync(startDate, duration, referenceDate, offset, targetTemplate, templatesToHandeler, true, isNew: false);
#endregion
//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 responseAreae = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
//var nextRunWithTimezone = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(template.NextRun, template.Job?.Group?.IsResponseAreaTimezone, responseAreae);
@@ -117,14 +167,14 @@ namespace PARR.Test.NextRun
}
private DateOnly GetDateStart()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow.Date);
//private DateOnly GetDateStart()
//{
// var today = DateOnly.FromDateTime(DateTime.UtcNow.Date);
var refDate = DateOnly.FromDateTime(referenceDate.Date);
// var refDate = DateOnly.FromDateTime(referenceDate.Date);
return today < refDate ? refDate : today;
}
// return today < refDate ? refDate : today;
//}
//private DateOnly GetDateEnd()
//{
@@ -143,7 +193,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("619D9621-2B45-4108-AF54-CF96862A8E9B"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("D74D831C-D435-46BF-9B5F-A24884AAA7DC"), NextRun: new DateTimeOffset(2025, 12, 25, 3, 0, 0, TimeSpan.Zero)));
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("DE7241F1-5C62-4164-A3DC-99B946350E10"), NextRun: new DateTimeOffset(2025, 12, 18, 3, 0, 0, TimeSpan.Zero)));
templates.Add(new TemplateNextRunDto(Id: Guid.Parse("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("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)));