diff --git a/PARR.DAL/NextRunServices/INextRunServiceV2.cs b/PARR.DAL/NextRunServices/INextRunServiceV2.cs
new file mode 100644
index 00000000..b8bdbb86
--- /dev/null
+++ b/PARR.DAL/NextRunServices/INextRunServiceV2.cs
@@ -0,0 +1,14 @@
+using PARR.DAL.NextRunServices.Models;
+
+namespace PARR.DAL.NextRunServices
+{
+ public interface INextRunServiceV2
+ {
+ ///
+ /// Распределить шаблоны в группе работ (получить список распределенных шаблонов с актуальными nextRun)
+ ///
+ ///
+ /// Список TemplateId с NextRun и NextRunOld
+ Task?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId);
+ }
+}
diff --git a/PARR.DAL/NextRunServices/NextRunServiceV2.cs b/PARR.DAL/NextRunServices/NextRunServiceV2.cs
new file mode 100644
index 00000000..515102e2
--- /dev/null
+++ b/PARR.DAL/NextRunServices/NextRunServiceV2.cs
@@ -0,0 +1,240 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.Extensions.Logging;
+using NodaTime;
+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 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 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?> GetNextRunForJobGroupWithAutoDistributionAsync(Guid jobGroupId)
+ {
+ logger.LogInformation("Начинаю распределять шаблоны для группы работ {gobGroupId}", jobGroupId);
+
+ var jobGroup = await jobGroupService.Get()
+ .Include(t => t.DistributionConfig)
+ .ThenInclude(t => t.DistributionPeriod)
+ .AsNoTracking()
+ .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 allTemplates = await templateService.Get()
+ .AsNoTracking()
+ .Include(t => t.Job)
+ .Where(t =>
+ t.Job!.GroupId == jobGroupId
+ && t.StatusTypeId == TemplateStatusTypeEnum.Used
+ ).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();
+
+ if (isGroupingByWorkGroup)
+ {
+ // группировать по РГ
+
+ if (isResponseAreaTimeZone)
+ {
+ // использовать часовой пояс РГ
+ }
+ else
+ {
+ // не нужно использовать часовой пояс РГ (используем часовой пояс УЗ ЕСПП)
+ }
+ }
+ else
+ {
+ //не нужно группировать по РГ
+
+ if (isResponseAreaTimeZone)
+ {
+ // использовать ЧАСОВОЙ ПОЯС РГ (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)
+ }
+ else
+ {
+ // не нужно использовать часовой пояс РГ
+
+ var offset = GetOffsetForDistribution();
+ var templatesForDistribute = allTemplates.Select(t => new TemplateNextRunDto(t.Id, t.NextRun)).ToList();
+ var dateStart = GetDateStart(offset);
+
+ logger.LogInformation("Буду распределать шаблоны {count} шт, их НЕ НУЖНО группировать по РГ, НЕ НУЖНО использовать часовой пояс РГ. dateStart в целевом часовом поясе: {dateStart}, offset: {offset}",
+ templatesForDistribute.Count, dateStart, offset);
+
+ distributedTemplates = await templateDistributor.DistributeTemplatesAsync(dateStart, durationDays, jobGroup.ReferenceDate, offset, templatesForDistribute, jobGroup.DistributionConfig.IsExcludeWeekends);
+ }
+ }
+
+ return distributedTemplates;
+ }
+
+
+ ///
+ /// Получить продолжительность в днях
+ ///
+ ///
+ ///
+ private 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;
+ }
+
+
+ ///
+ /// Получить часовую зону для распределения
+ ///
+ ///
+ private TimeSpan GetOffsetForDistribution(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;
+ }
+ }
+
+
+ ///
+ /// Получить часовой пояс УЗ ЕСПП
+ ///
+ ///
+ private TimeSpan GetEsppAccountOffset()
+ {
+ var esppOffset = TimeSpan.FromHours(settingsFromDb.EsppRobotAccountTimeZoneHour);
+ logger.LogDebug("Оффсет УЗ ЕСПП: {esppOffset}", esppOffset);
+
+ return esppOffset;
+ }
+
+
+ ///
+ /// Получить дату начала распределения в целевом часовом поясе
+ ///
+ ///
+ ///
+ private DateOnly GetDateStart(TimeSpan offset)
+ {
+ var dateStart = DateOnly.FromDateTime(DateTimeOffset.UtcNow.ToOffset(offset).Date);
+ logger.LogDebug("Дата начала распределения (сегодня) в целевом часовом поясе {dateStart}, offset: {offset}", dateStart, offset);
+
+ return dateStart;
+ }
+
+
+
+ }
+}
diff --git a/PARR.DAL/ParrDalInstaller.cs b/PARR.DAL/ParrDalInstaller.cs
index 0d12389e..e329d200 100644
--- a/PARR.DAL/ParrDalInstaller.cs
+++ b/PARR.DAL/ParrDalInstaller.cs
@@ -142,6 +142,7 @@ namespace PARR.DAL
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
#endregion
diff --git a/PARR.DAL/Services/Implementations/Schedule/ScheduleResponseAreaTimeOffsetService.cs b/PARR.DAL/Services/Implementations/Schedule/ScheduleResponseAreaTimeOffsetService.cs
index 5a6a9ac2..8a05b428 100644
--- a/PARR.DAL/Services/Implementations/Schedule/ScheduleResponseAreaTimeOffsetService.cs
+++ b/PARR.DAL/Services/Implementations/Schedule/ScheduleResponseAreaTimeOffsetService.cs
@@ -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
///
private readonly ScheduleResponseAreaTimeOffset defaultOffset;
+ private readonly ILogger logger;
- public ScheduleResponseAreaTimeOffsetService(DataContext dataContext, SettingsFromDb settingsFromDb)
+ public ScheduleResponseAreaTimeOffsetService(DataContext dataContext, SettingsFromDb settingsFromDb, ILogger 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;
}
diff --git a/PARR.Test/NextRun/NextRunTest.cs b/PARR.Test/NextRun/NextRunTest.cs
index 2444d03a..a960e90c 100644
--- a/PARR.Test/NextRun/NextRunTest.cs
+++ b/PARR.Test/NextRun/NextRunTest.cs
@@ -13,13 +13,15 @@ namespace PARR.Test.NextRun
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,
- ITemplateDistributorV2 templateDistributorV2
+ ITemplateDistributorV2 templateDistributorV2,
+ INextRunServiceV2 nextRunServiceV2
)
{
//this.templateDistributor = templateDistributor;
@@ -27,29 +29,40 @@ namespace PARR.Test.NextRun
this.templateService = templateService;
this.shortcodesService = shortcodesService;
this.templateDistributorV2 = templateDistributorV2;
+ this.nextRunServiceV2 = nextRunServiceV2;
}
public async Task Test()
{
- var duration = 2;
- var offset = TimeSpan.FromHours(3);
+ #region тестирование INextRunServiceV2
- var startDate = new DateOnly(2026, 2, 10);
- var referenceDate = new DateTimeOffset(2026, 2, 13, 23, 0, 0, TimeSpan.Zero);
+ var distributeResult = await nextRunServiceV2.GetNextRunForJobGroupWithAutoDistributionAsync(Guid.Parse("d25d8a9e-898f-417a-9bec-ef3a356e7c94"));
- var templates = GetTemplates();
- var templatesEmpty = new List();
- var templatesToHandeler = templatesEmpty; // templatesEmpty // templates
+ #endregion
- // распределить шаблоны
- 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);
+ #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();
+ //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);