feat(dal,core): Перенесены оставшиеся объекты из dal в core.
This commit is contained in:
@@ -3,12 +3,18 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PARR.Core.Common.Implementations;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Services.MatchingStatusService;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.NextRunServices.Subservices;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
using PARR.Core.Services.TaskServices.Handlers;
|
||||
using PARR.Core.Services.TaskServices.Handlers.Factory;
|
||||
using PARR.Core.Services.TaskServices.Implementations;
|
||||
using PARR.Core.Services.TaskServices.Interfaces;
|
||||
using PARR.Core.Services.TaskServices.Providers;
|
||||
using PARR.Core.Services.TaskServices.ReconciliationHosted;
|
||||
using PARR.Core.Services.UnitFilterService;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Core.Services.Workload.Implementations;
|
||||
using PARR.Core.Services.Workload.Interfaces;
|
||||
using PARR.Domain.Enums;
|
||||
@@ -65,9 +71,31 @@ namespace PARR.Core
|
||||
|
||||
#region Services
|
||||
|
||||
services.AddTransient<IShortcodesService, ShortcodesService>();
|
||||
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
|
||||
|
||||
|
||||
//services.AddScoped<IUserService, UserService>();
|
||||
//services.AddTransient<IShortcodesService, ShortcodesService>();
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region NextRun
|
||||
|
||||
services.AddTransient<IEsppScheduleTransformService, EsppScheduleTransformService>();
|
||||
services.AddTransient<ITemplateDistributor, TemplateDistributor>();
|
||||
services.AddTransient<INextRunService, NextRunService>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region UnitFilterService
|
||||
|
||||
services.AddTransient<IUnitFilterService, UnitFilterService>();
|
||||
services.Configure<UnitFilterServiceOptions>(options =>
|
||||
{
|
||||
options.LoadBatchSize = 50;
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
55
PARR.Core/Extensions/GeneralExtesions.cs
Normal file
55
PARR.Core/Extensions/GeneralExtesions.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace PARR.Core.Extensions
|
||||
{
|
||||
public static class GeneralExtesions
|
||||
{
|
||||
/// <summary>
|
||||
/// Преобразует объект в Json, с учетом Cyrillic
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string? ToJson(this object value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Serialize(value, new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.Cyrillic) });
|
||||
}
|
||||
catch// (Exception ex)
|
||||
{
|
||||
// todo: писать лог??? или и так пойдет
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static DateTimeOffset EndOfDay(this DateTimeOffset date)
|
||||
{
|
||||
return new DateTimeOffset(date.Year, date.Month, date.Day, 23, 59, 59, 999, new TimeSpan(0));
|
||||
}
|
||||
|
||||
public static DateTimeOffset StartOfDay(this DateTimeOffset date)
|
||||
{
|
||||
return new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, 0, new TimeSpan(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sourceString">Исходная строка, включающая переменные части вида %ШАБЛОН%</param>
|
||||
/// <param name="shortcode">Код шаблона из PARR.Constants.ShortcodeEnum.ShortcodeEnum</param>
|
||||
/// <param name="shortcodeValue">Итоговое строковое значение</param>
|
||||
/// <returns></returns>
|
||||
//public static string ApplyShortcode(this string sourceString, ShortcodeEnum shortcode, string shortcodeValue)
|
||||
//{
|
||||
// var shortcodeName = Shortcodes.GetShortcodeName(shortcode);
|
||||
|
||||
// return sourceString.Replace(shortcodeName, shortcodeValue);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.5.2" />
|
||||
<PackageReference Include="InfluxDB.Client" Version="4.17.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
4
PARR.Core/Properties/AssemblyInfo.cs
Normal file
4
PARR.Core/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("PARR.DAL.Tests")]
|
||||
[assembly: InternalsVisibleTo("PARR.DAL.IntegrationTests")]
|
||||
@@ -24,5 +24,7 @@ namespace PARR.Core.Repositories.Interfaces.Unit
|
||||
Task<string?> GetMostFrequentValueForFieldAsync(List<Guid> unitIds, string fieldName);
|
||||
|
||||
IQueryable<UnitInValue> Get();
|
||||
|
||||
IQueryable<Guid> GetMatchingTargetIds(Guid fieldId, string valueMask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,18 @@ namespace PARR.Core.Repositories.Interfaces.Unit
|
||||
{
|
||||
public interface IUnitRepository : IBaseRepository<Domain.Entities.Unit.Unit>
|
||||
{
|
||||
IQueryable<Guid> GetInitialUnitIds(string dbValueMask);
|
||||
|
||||
/// <summary>
|
||||
/// Получить юниты по Id атрибута и маски значения
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="fieldId"></param>
|
||||
/// <param name="valueMask"></param>
|
||||
/// <param name="isInverse">true - не содержит, false - содержит</param>
|
||||
/// <returns></returns>
|
||||
IQueryable<Domain.Entities.Unit.Unit> GetUnitByFieldAndValue(IQueryable<Domain.Entities.Unit.Unit> query, Guid fieldId, string valueMask, bool isInverse = false);
|
||||
|
||||
IQueryable<Domain.Entities.Unit.Unit> GetWithIncludes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.DTOs.Matching;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.MatchingStatusService
|
||||
{
|
||||
/// <summary>
|
||||
/// Сервис по управлению статусом Matching`a
|
||||
/// </summary>
|
||||
public interface IMatchingStatusService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить статус matching`a
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
Task<MatchingStatus> GetStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type);
|
||||
|
||||
/// <summary>
|
||||
/// Установить/обновить статус matching`a
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="cacheDuration"></param>
|
||||
/// <returns></returns>
|
||||
Task SetMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type, MatchingStatusItem data, TimeSpan cacheDuration);
|
||||
|
||||
/// <summary>
|
||||
/// Удалить статус matching`a
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.DTOs.Matching;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.MatchingStatusService
|
||||
{
|
||||
internal class MatchingStatusService : IMatchingStatusService
|
||||
{
|
||||
private readonly IRedisCacheService redisCacheService;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly IJobGroupRepository jobGroupService;
|
||||
|
||||
public MatchingStatusService(
|
||||
IRedisCacheService redisCacheService,
|
||||
IJobRepository jobService,
|
||||
IJobGroupRepository jobGroupService
|
||||
)
|
||||
{
|
||||
this.redisCacheService = redisCacheService;
|
||||
this.jobService = jobService;
|
||||
this.jobGroupService = jobGroupService;
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type)
|
||||
{
|
||||
var key = GetKey(objectId, type);
|
||||
await redisCacheService.DeleteCachedDataAsync(key);
|
||||
}
|
||||
|
||||
public async Task<MatchingStatus> GetStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type)
|
||||
{
|
||||
var jobGroupIdList = new List<Guid>();
|
||||
var jobIdList = new List<Guid>();
|
||||
|
||||
//формируем список jobId и jobGroupId для поиска в КЭШ, происходит ли у кого-то из них сейчас matching
|
||||
switch (type)
|
||||
{
|
||||
case SyncTaskEntityTypeEnum.Job:
|
||||
var job = await jobService.Get().FirstOrDefaultAsync(t => t.Id == objectId);
|
||||
if (job != null)
|
||||
{
|
||||
jobIdList.Add(job.Id);
|
||||
jobGroupIdList = await jobGroupService.Get().Where(t => t.Id == job.GroupId).Select(t => t.Id).ToListAsync();
|
||||
}
|
||||
break;
|
||||
case SyncTaskEntityTypeEnum.JobGroup:
|
||||
var jobGroup = await jobGroupService.Get().FirstOrDefaultAsync(t => t.Id == objectId);
|
||||
if (jobGroup != null)
|
||||
{
|
||||
jobGroupIdList.Add(jobGroup.Id);
|
||||
jobIdList = await jobService.Get().Where(t => t.GroupId == jobGroup.Id).Select(t => t.Id).ToListAsync();
|
||||
}
|
||||
break;
|
||||
case SyncTaskEntityTypeEnum.Template:
|
||||
throw new Exception("Нет реализации получения кэша статуса matching для template");
|
||||
//todo: У нас вообще нет matching для Template, позже надо удалить это вообще из Enum SyncTaskEntityTypeEnum
|
||||
break;
|
||||
}
|
||||
|
||||
// Если списки пустые, мало ли, говорим что нет matching`a
|
||||
if (!jobGroupIdList.Any() && !jobIdList.Any())
|
||||
return new MatchingStatus { DetailsJobGroups = null, DetailsJobs = null };
|
||||
|
||||
// Ищем в КЭШ
|
||||
var jobGroupDetails = new List<MatchingStatusItem>();
|
||||
foreach (var jobGroupId in jobGroupIdList)
|
||||
{
|
||||
var cacheResult = await redisCacheService.GetCachedDataAsync<MatchingStatusItem>(GetKey(jobGroupId, SyncTaskEntityTypeEnum.JobGroup));
|
||||
if (cacheResult != null)
|
||||
jobGroupDetails.Add(cacheResult);
|
||||
}
|
||||
|
||||
var jobDetails = new List<MatchingStatusItem>();
|
||||
foreach (var jobId in jobIdList)
|
||||
{
|
||||
var cacheResult = await redisCacheService.GetCachedDataAsync<MatchingStatusItem>(GetKey(jobId, SyncTaskEntityTypeEnum.Job));
|
||||
if (cacheResult != null)
|
||||
jobDetails.Add(cacheResult);
|
||||
}
|
||||
|
||||
return new MatchingStatus
|
||||
{
|
||||
DetailsJobGroups = jobGroupDetails.Any() ? jobGroupDetails : null,
|
||||
DetailsJobs = jobDetails.Any() ? jobDetails : null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task SetMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type, MatchingStatusItem data, TimeSpan cacheDuration)
|
||||
{
|
||||
var key = GetKey(objectId, type);
|
||||
await redisCacheService.SetCachedDataAsync(key, data, cacheDuration);
|
||||
}
|
||||
|
||||
private string GetKey(Guid id, SyncTaskEntityTypeEnum type)
|
||||
{
|
||||
var typeStr = "";
|
||||
switch (type)
|
||||
{
|
||||
case SyncTaskEntityTypeEnum.Job:
|
||||
typeStr = "job";
|
||||
break;
|
||||
case SyncTaskEntityTypeEnum.JobGroup:
|
||||
typeStr = "group";
|
||||
break;
|
||||
case SyncTaskEntityTypeEnum.Template:
|
||||
typeStr = "template";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return redisCacheService.GetKey(new string[] { "matching", typeStr, id.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
66
PARR.Core/Services/NextRunServices/INextRunService.cs
Normal file
66
PARR.Core/Services/NextRunServices/INextRunService.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using PARR.Core.Services.NextRunServices.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.NextRunServices
|
||||
{
|
||||
public interface INextRunService
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace PARR.Core.Services.NextRunServices.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Модель шаблона, для расчета NextRun
|
||||
/// </summary>
|
||||
public record TemplateNextRunDto(Guid Id, DateTimeOffset? NextRun);
|
||||
|
||||
//public class TemplateNextRunDto
|
||||
//{
|
||||
// public Guid Id { get; set; }
|
||||
|
||||
// public DateTimeOffset? NextRun { get; set; }
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace PARR.Core.Services.NextRunServices.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Модель, результат распределения шаблона
|
||||
/// </summary>
|
||||
public record TemplateNextRunResultDto(Guid Id, DateTimeOffset NextRun, DateTimeOffset? NextRunOld);
|
||||
|
||||
//public class TemplateNextRunResultDto
|
||||
//{
|
||||
// public Guid Id { get; set; }
|
||||
|
||||
// public DateTimeOffset NextRun { get; set; }
|
||||
|
||||
// public DateTimeOffset? NextRunOld { get; set; }
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace PARR.Core.Services.NextRunServices.Models
|
||||
{
|
||||
internal record TemplateWithWorkGroupDto(Guid Id, DateTimeOffset? NextRun, string WorkGroup);
|
||||
|
||||
}
|
||||
833
PARR.Core/Services/NextRunServices/NextRunService.cs
Normal file
833
PARR.Core/Services/NextRunServices/NextRunService.cs
Normal file
@@ -0,0 +1,833 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Services.NextRunServices.Models;
|
||||
using PARR.Core.Services.NextRunServices.Subservices;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Settings;
|
||||
|
||||
namespace PARR.Core.Services.NextRunServices
|
||||
{
|
||||
internal class NextRunService : INextRunService
|
||||
{
|
||||
private readonly ILogger<NextRunService> logger;
|
||||
private readonly ITemplateRepository templateService;
|
||||
private readonly IJobGroupRepository jobGroupService;
|
||||
private readonly IEsppScheduleTransformService esppScheduleTransformService;
|
||||
private readonly ITemplateDistributor templateDistributor;
|
||||
private readonly IShortcodesService shortcodesService;
|
||||
private readonly IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService;
|
||||
private readonly SettingsFromDb settingsFromDb;
|
||||
|
||||
public NextRunService(
|
||||
ILogger<NextRunService> logger,
|
||||
ITemplateRepository templateService,
|
||||
IJobGroupRepository jobGroupService,
|
||||
IEsppScheduleTransformService esppScheduleTransformService,
|
||||
ITemplateDistributor templateDistributor,
|
||||
IShortcodesService shortcodesService,
|
||||
IScheduleResponseAreaTimeOffsetRepository 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
{
|
||||
internal class EsppScheduleTransformService : IEsppScheduleTransformService
|
||||
{
|
||||
private readonly IEsppSchTypeConfigRepository esppSchTypeConfigService;
|
||||
private readonly ILogger<EsppScheduleTransformService> logger;
|
||||
|
||||
private static readonly Dictionary<string, int> monthDict = new Dictionary<string, int>()
|
||||
{
|
||||
{ "Январь", 1 },
|
||||
{ "Февраль", 2 },
|
||||
{ "Март", 3 },
|
||||
{ "Апрель", 4 },
|
||||
{ "Май", 5 },
|
||||
{ "Июнь", 6 },
|
||||
{ "Июль", 7 },
|
||||
{ "Август", 8 },
|
||||
{ "Сентябрь", 9 },
|
||||
{ "Октябрь", 10 },
|
||||
{ "Ноябрь", 11 },
|
||||
{ "Декабрь", 12 }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, int> orderDict = new Dictionary<string, int>()
|
||||
{
|
||||
{ "Первый", 1 },
|
||||
{ "Второй", 2 },
|
||||
{ "Третий", 3 },
|
||||
{ "Четвертый", 4 },
|
||||
{ "Последний", 5 }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, int> dwDict = new Dictionary<string, int>()
|
||||
{
|
||||
{"Понедельник", 1 },
|
||||
{"Вторник", 2 },
|
||||
{"Среда", 3 },
|
||||
{"Четверг", 4 },
|
||||
{"Пятница", 5 },
|
||||
{"Суббота", 6 },
|
||||
{"Воскресенье", 0 }
|
||||
};
|
||||
|
||||
|
||||
public EsppScheduleTransformService(
|
||||
ILogger<EsppScheduleTransformService> logger,
|
||||
IEsppSchTypeConfigRepository esppSchTypeConfigService
|
||||
)
|
||||
{
|
||||
this.esppSchTypeConfigService = esppSchTypeConfigService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate, TimeSpan? offset = null)
|
||||
{
|
||||
var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
|
||||
|
||||
return GetNextDate(esppSchedule, referenceDate, offset);
|
||||
}
|
||||
|
||||
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 = offset ?? TimeSpan.Zero;
|
||||
|
||||
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
|
||||
|
||||
logger.LogDebug("Дата после добавления offset - {offsetReferenceDate}", offsetReferenceDate);
|
||||
|
||||
////---------
|
||||
//// Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД
|
||||
//var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0));
|
||||
////---------
|
||||
|
||||
//// может быть offset ,это работает когда смещение для УЗ ЕСПП и применение ЗО РГ
|
||||
//if (offset.HasValue)
|
||||
// offsetReferenceDate = offsetReferenceDate.Off(offsetReferenceDate.Offset + offset);
|
||||
// //offsetReferenceDate = offsetReferenceDate.Add(offset.Value);
|
||||
|
||||
|
||||
switch (esppSchedule.TypeSchedule.Id)
|
||||
{
|
||||
case (int)EsppSchTypeScheduleEnum.Regularly:
|
||||
nextRun = GetNextDateRegularly(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
case (int)EsppSchTypeScheduleEnum.Weekly:
|
||||
nextRun = GetNextDateWeekly(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
case (int)EsppSchTypeScheduleEnum.Monthly:
|
||||
nextRun = GetNextDateMonthly(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
case (int)EsppSchTypeScheduleEnum.Monthly2:
|
||||
nextRun = GetNextDateMonthly2(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
case (int)EsppSchTypeScheduleEnum.Annually:
|
||||
nextRun = GetNextDateAnnually(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
case (int)EsppSchTypeScheduleEnum.Annually2:
|
||||
nextRun = GetNextDateAnnually2(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.LogDebug("Рассчитанная дата, с учетом offset: {nextRun}", nextRun);
|
||||
|
||||
// Возвращаем обратно в ЮТС
|
||||
nextRun = nextRun.ToOffset(TimeSpan.Zero);
|
||||
|
||||
logger.LogDebug("Рассчитанная дата с нулевым offset: {nextRun}", nextRun);
|
||||
|
||||
return nextRun;
|
||||
}
|
||||
|
||||
|
||||
/// <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);
|
||||
if (esppSchedule == null)
|
||||
{
|
||||
logger.LogError($"Не найдено расписание в БД, jobId: {jobGroupId}");
|
||||
throw new Exception($"Не найдено расписание в БД, jobId: {jobGroupId}");
|
||||
}
|
||||
|
||||
return esppSchedule;
|
||||
}
|
||||
|
||||
private DateTimeOffset GetNextDateRegularly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
var regDict = new Dictionary<string, int>
|
||||
{
|
||||
{"Каждый час",1},
|
||||
{"Каждые 2 часа",2},
|
||||
{"Каждые 3 часа", 3},
|
||||
{"Каждые 4 часа", 4},
|
||||
{"Каждые 6 часов", 6},
|
||||
{"Каждые 12 часов", 12},
|
||||
{"Ежедневно", 24},
|
||||
{"Каждые 72 часа", 72},
|
||||
{"Каждые 96 часов", 96},
|
||||
{"Каждые 2 недели", 336},
|
||||
{"Каждые 60 дней", 1440},
|
||||
{"Каждые 80 дней", 1920},
|
||||
{"Каждые 90 дней", 2160},
|
||||
{"Каждые полгода", 4380},
|
||||
{"Каждые 1,5 года", 13140},
|
||||
{"Каждые 3 года", 26280},
|
||||
|
||||
};
|
||||
|
||||
|
||||
if (!regDict.TryGetValue(values[0].Value.Value, out int regNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
var calcDay = referenceDate;
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateRegularly(values, calcDay.AddHours(regNum));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset GetNextDateWeekly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!dwDict.TryGetValue(values[0].Value.Value, out int dwNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня недели: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
var calcDay = referenceDate;
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
{
|
||||
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);
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset GetNextDateMonthly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!int.TryParse(values[0].Value.Value, out int dayNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
if (referenceDate < DateTimeOffset.UtcNow)
|
||||
{
|
||||
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: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset GetNextDateMonthly2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
//var startDay = referenceDate < DateTimeOffset.UtcNow
|
||||
// ? new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset)
|
||||
// : new DateTimeOffset(referenceDate.Year, referenceDate.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
var startDay = referenceDate < DateTimeOffset.UtcNow
|
||||
? 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);
|
||||
|
||||
DateTimeOffset calcDay;
|
||||
if (orderNum == 5) // Последний
|
||||
{
|
||||
calcDay = daysList.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (orderNum > daysList.Count)
|
||||
{
|
||||
logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
|
||||
throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
|
||||
}
|
||||
else
|
||||
calcDay = daysList[orderNum - 1];
|
||||
}
|
||||
|
||||
// Если дата <= текущей, переходим на следующий месяц
|
||||
if (calcDay <= DateTimeOffset.UtcNow)
|
||||
return GetNextDateMonthly2(values, startDay.AddMonths(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset GetNextDateAnnually(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
|
||||
if (!monthDict.TryGetValue(values[0].Value.Value, out int monthNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение номера месяца: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(values[1].Value.Value, out int dayNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
//сравниваем года в одном часовом поясе
|
||||
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
var year = (referenceDate.Year < nowInTargetTz.Year)
|
||||
? nowInTargetTz.Year
|
||||
: referenceDate.Year;
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, monthNum, dayNum,
|
||||
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset GetNextDateAnnually2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
var monthADict = new Dictionary<string, int>
|
||||
{
|
||||
{ "Января", 1 },
|
||||
{ "Февраля", 2 },
|
||||
{ "Марта", 3 },
|
||||
{ "Апреля", 4 },
|
||||
{ "Мая", 5 },
|
||||
{ "Июня", 6 },
|
||||
{ "Июля", 7 },
|
||||
{ "Августа", 8 },
|
||||
{ "Сентября", 9 },
|
||||
{ "Октября", 10 },
|
||||
{ "Ноября", 11 },
|
||||
{ "Декабря", 12 }
|
||||
};
|
||||
|
||||
if (!monthADict.TryGetValue(values[2].Value.Value, out int monthNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение номера месяца: {Value}", values[2].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[2].Value.Value}");
|
||||
}
|
||||
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
// сравниваем года в одном часовом поясе
|
||||
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 == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
|
||||
year += 1;
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, monthNum, 1,
|
||||
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
List<DateTimeOffset> daysList = FindAllDaysInMonth(dwNum, calcDay);
|
||||
|
||||
if (orderNum == 5)//5-последний
|
||||
calcDay = daysList.Last();
|
||||
else
|
||||
calcDay = daysList[orderNum - 1];
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
private List<DateTimeOffset> FindAllDaysInMonth(int dwNum, DateTimeOffset startDay)
|
||||
{
|
||||
var calcDay = startDay;
|
||||
if (((int)calcDay.DayOfWeek) != dwNum)
|
||||
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
|
||||
|
||||
var daysList = new List<DateTimeOffset>();
|
||||
var monthNum = calcDay.Month;
|
||||
while (calcDay.Month == monthNum)
|
||||
{
|
||||
daysList.Add(calcDay);
|
||||
calcDay = calcDay.AddDays(7);
|
||||
}
|
||||
|
||||
return daysList;
|
||||
}
|
||||
|
||||
|
||||
private DateTimeOffset OffsetToDayOfWeek(int dwNum, DateTimeOffset calcDay)
|
||||
{
|
||||
var daysOffset = dwNum - ((int)calcDay.DayOfWeek);
|
||||
|
||||
if (daysOffset < 0)
|
||||
daysOffset = 7 + daysOffset;
|
||||
|
||||
return calcDay.AddDays(daysOffset);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
{
|
||||
/// <summary>
|
||||
/// Сервис трансформации расписания ЕСПП в дату/расписание
|
||||
/// </summary>
|
||||
internal interface IEsppScheduleTransformService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить следующую дату по jobGroupId
|
||||
/// </summary>
|
||||
/// <param name="jobGroupId"></param>
|
||||
/// <param name="referenceDate">referenceDate или nextRun</param>
|
||||
/// <param name="offset">offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК</param>
|
||||
/// <returns></returns>
|
||||
Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate, TimeSpan? offset = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using PARR.Core.Services.NextRunServices.Models;
|
||||
|
||||
namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
{
|
||||
internal interface ITemplateDistributor
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
13
PARR.Core/Services/Shortcodes/IShortcodesService.cs
Normal file
13
PARR.Core/Services/Shortcodes/IShortcodesService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using PARR.Domain.DTOs.Shortcode;
|
||||
using PARR.Domain.Entities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PARR.Core.Services.Shortcodes
|
||||
{
|
||||
public interface IShortcodesService
|
||||
{
|
||||
Task<string> ApplyShortcodesAsync(string str, Template template, [CallerMemberName] string? caller = null);
|
||||
|
||||
Task<List<ShortcodeInfoDto>> GetAvailableShortcodesAsync();
|
||||
}
|
||||
}
|
||||
1031
PARR.Core/Services/Shortcodes/ShortcodesService.cs
Normal file
1031
PARR.Core/Services/Shortcodes/ShortcodesService.cs
Normal file
File diff suppressed because it is too large
Load Diff
67
PARR.Core/Services/UnitFilterService/IUnitFilterService.cs
Normal file
67
PARR.Core/Services/UnitFilterService/IUnitFilterService.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService
|
||||
{
|
||||
public interface IUnitFilterService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получает список ID Unit'ов, соответствующих фильтрам Job.
|
||||
/// </summary>
|
||||
/// <param name="jobId">ID Job</param>
|
||||
/// <param name="takeCount">Максимальное количество Unit'ов (опционально)</param>
|
||||
/// <returns>Список ID Unit'ов или null, если Job не найден</returns>
|
||||
//Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(
|
||||
// Guid jobId,
|
||||
// int? takeCount = null);
|
||||
|
||||
/// <summary>
|
||||
/// Получает список ID Unit'ов, соответствующих фильтрам Job.
|
||||
/// </summary>
|
||||
/// <param name="job">Job с предзагруженными UnitFilters, Group, GroupType</param>
|
||||
/// <param name="takeCount">Максимальное количество Unit'ов (опционально)</param>
|
||||
/// <returns>Список ID Unit'ов</returns>
|
||||
Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(
|
||||
Job job,
|
||||
int? takeCount = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получает отфильтрованные Unit'ы с полной информацией о значениях полей и связях.
|
||||
/// Возвращает DTO с данными для дальнейшей обработки в синхронизаторах.
|
||||
/// </summary>
|
||||
/// <param name="jobId">ID Job</param>
|
||||
/// <param name="takeCount">Максимальное количество Unit'ов (опционально)</param>
|
||||
/// <returns>Список UnitFilterResultDto или null, если Job не найден</returns>
|
||||
Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
|
||||
Guid jobId,
|
||||
int? takeCount = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получает отфильтрованные Unit'ы с полной информацией о значениях полей и связях.
|
||||
/// Возвращает DTO с данными для дальнейшей обработки в синхронизаторах.
|
||||
/// </summary>
|
||||
/// <param name="job">Job с предзагруженными UnitFilters, Group, GroupType</param>
|
||||
/// <param name="takeCount">Максимальное количество Unit'ов (опционально)</param>
|
||||
/// <returns>Список UnitFilterResultDto</returns>
|
||||
Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
|
||||
Job job,
|
||||
int? takeCount = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Получает имена связанных Unit'ов для заданного Unit, отфильтрованных по RelationshipFilters Job.
|
||||
/// Используется для шорткода %СВЯЗИ%.
|
||||
/// </summary>
|
||||
/// <param name="jobId">ID Job</param>
|
||||
/// <param name="unitId">ID Unit, для которого ищутся связи</param>
|
||||
/// <returns>Список имён связанных Unit'ов (уникальные, без дублей)</returns>
|
||||
Task<List<string>> GetRelatedUnitNamesAsync(
|
||||
Guid jobId,
|
||||
Guid unitId,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
internal class FilteredUnitContext
|
||||
{
|
||||
public Guid UnitId { get; set; }
|
||||
public HashSet<Guid> ValidParentIds { get; set; } = new();
|
||||
public HashSet<Guid> ValidChildIds { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Связанный юнит с его значениями
|
||||
/// </summary>
|
||||
public class RelatedUnitDto
|
||||
{
|
||||
/// <summary>
|
||||
/// ID связанного юнита
|
||||
/// </summary>
|
||||
public Guid UnitId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Наименование связанного юнита
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Значения полей связанного юнита
|
||||
/// </summary>
|
||||
public List<UnitValueDto> Values { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Результат фильтрации Unit'ов с полной информацией для синхронизаторов.
|
||||
/// </summary>
|
||||
public class UnitFilterResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Уникальный идентификатор юнита
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Наименование юнита
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Значения полей юнита
|
||||
/// </summary>
|
||||
public List<UnitValueDto> Values { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Связанные родительские юниты
|
||||
/// </summary>
|
||||
public List<RelatedUnitDto> Parents { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Связанные дочерние юниты
|
||||
/// </summary>
|
||||
public List<RelatedUnitDto> Children { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
internal class UnitFilterServiceOptions
|
||||
{
|
||||
public int LoadBatchSize { get; set; } = 50;
|
||||
}
|
||||
}
|
||||
19
PARR.Core/Services/UnitFilterService/Models/UnitValueDto.cs
Normal file
19
PARR.Core/Services/UnitFilterService/Models/UnitValueDto.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля юнита
|
||||
/// </summary>
|
||||
public class UnitValueDto
|
||||
{
|
||||
/// <summary>
|
||||
/// ID поля
|
||||
/// </summary>
|
||||
public Guid FieldId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
}
|
||||
1025
PARR.Core/Services/UnitFilterService/UnitFilterService.cs
Normal file
1025
PARR.Core/Services/UnitFilterService/UnitFilterService.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user