Merge branch 'next-run-service' into dev
# Conflicts: # PARR.DAL/Contracts/SettingsFromDb.cs
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
public DateTimeOffset ReferenceDate { get; set; }
|
||||
|
||||
public int? UserTimeZoneOffsetMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Использовать таймзону рабочей группы ответственного за ЭК шаблона
|
||||
/// </summary>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
public bool IsAutoDistributionEnabled { get; set; }
|
||||
|
||||
public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; }
|
||||
//public ScheduleResponseAreaTimeOffsetResponse? ResponseAreaOffset { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("Некорректное значение."); ;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "Отсутствует дата завершения" },
|
||||
|
||||
@@ -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>
|
||||
|
||||
4148
PARR.DAL/Migrations/20260209010657_tblJobGroupAddUserTimeZoneOffsetMinutes.Designer.cs
generated
Normal file
4148
PARR.DAL/Migrations/20260209010657_tblJobGroupAddUserTimeZoneOffsetMinutes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
66
PARR.DAL/NextRunServices/INextRunServiceV2.cs
Normal file
66
PARR.DAL/NextRunServices/INextRunServiceV2.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
833
PARR.DAL/NextRunServices/NextRunServiceV2.cs
Normal file
833
PARR.DAL/NextRunServices/NextRunServiceV2.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1259
PARR.DAL/NextRunServices/Subservices/TemplateDistributorV2.cs
Normal file
1259
PARR.DAL/NextRunServices/Subservices/TemplateDistributorV2.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
25
PARR.EsppSync/Helpers/EsppSyncHelpers.cs
Normal file
25
PARR.EsppSync/Helpers/EsppSyncHelpers.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user