feat(templateDistributor): логика распределения шаблонов по периоду

This commit is contained in:
Mikhail Kuznetsov
2024-07-05 15:17:48 +10:00
parent cf5a465546
commit 21a175d197
8 changed files with 370 additions and 19 deletions

View File

@@ -3,6 +3,7 @@ using PARR.Constants;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.Extensions; using PARR.DAL.Extensions;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
namespace PARR.DAL.TransformServices namespace PARR.DAL.TransformServices
@@ -10,6 +11,7 @@ namespace PARR.DAL.TransformServices
internal class EsppScheduleTransformService : IEsppScheduleTransformService internal class EsppScheduleTransformService : IEsppScheduleTransformService
{ {
private readonly IEsppSchTypeConfigService esppSchTypeConfigService; private readonly IEsppSchTypeConfigService esppSchTypeConfigService;
private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly ILogger<EsppScheduleTransformService> logger; private readonly ILogger<EsppScheduleTransformService> logger;
private readonly Dictionary<string, int> monthDict = new Dictionary<string, int>() private readonly Dictionary<string, int> monthDict = new Dictionary<string, int>()
@@ -49,11 +51,14 @@ namespace PARR.DAL.TransformServices
}; };
public EsppScheduleTransformService( public EsppScheduleTransformService(
ILogger<EsppScheduleTransformService> logger,
IEsppSchTypeConfigService esppSchTypeConfigService, IEsppSchTypeConfigService esppSchTypeConfigService,
ILogger<EsppScheduleTransformService> logger IApplicationsInWorkService applicationsInWorkService
) )
{ {
this.esppSchTypeConfigService = esppSchTypeConfigService; this.esppSchTypeConfigService = esppSchTypeConfigService;
this.applicationsInWorkService = applicationsInWorkService;
this.logger = logger; this.logger = logger;
} }
@@ -130,7 +135,9 @@ namespace PARR.DAL.TransformServices
public async Task<DateTimeOffset> GetNextDateAsync(Guid applicationInWorkId, DateTimeOffset lastRun) public async Task<DateTimeOffset> GetNextDateAsync(Guid applicationInWorkId, DateTimeOffset lastRun)
{ {
return GetNextDate(await GetEsppScheduleAsync(applicationInWorkId), lastRun); var esppSchedule = await GetEsppScheduleAsync(applicationInWorkId);
return GetNextDate(esppSchedule, lastRun);
} }
@@ -337,10 +344,6 @@ namespace PARR.DAL.TransformServices
case (DistributionPeriodTypeEnum.Year): case (DistributionPeriodTypeEnum.Year):
return lastRun.AddYears(ParseInt(distributionPeriod)); return lastRun.AddYears(ParseInt(distributionPeriod));
//case (DistributionPeriodTypeEnum.TimeSpan):
// var timeSpan = ParseTimeSpan(distributionPeriod);
// return timeSpan.Days > 0 ? startPeriod.AddDays();
} }
@@ -348,6 +351,23 @@ namespace PARR.DAL.TransformServices
return lastRun; return lastRun;
} }
public async Task<DateOnly> GetStartPeriodForDateAsync(Guid applicationInWorkId, DateTimeOffset date, DateTimeOffset refrenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod)
{
var esppSchedule = await GetEsppScheduleAsync(applicationInWorkId);
var currentStartPeriod = refrenceDate;
var currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod,periodType,distributionPeriod);
while (!(date>=currentStartPeriod && date< currentEndPeriod))
{
currentStartPeriod = currentEndPeriod;
currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
}
return DateOnly.FromDateTime(currentStartPeriod.DateTime);
}
private int ParseInt(string value) private int ParseInt(string value)
{ {

View File

@@ -49,5 +49,14 @@ namespace PARR.DAL.TransformServices
/// <param name="distributionPeriod"></param> /// <param name="distributionPeriod"></param>
/// <returns></returns> /// <returns></returns>
DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod); DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod);
/// <summary>
/// Получить дату начала периода распределения относительно опорной даты (Reference Date)
/// </summary>
/// <param name="applicationInWorkId"></param>
/// <param name="date"></param>
/// <returns></returns>
Task<DateOnly> GetStartPeriodForDateAsync(Guid applicationInWorkId, DateTimeOffset date, DateTimeOffset refrenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod);
} }
} }

View File

@@ -10,7 +10,7 @@ namespace PARR.TemplateDistributor
/// <param name="templates"></param> /// <param name="templates"></param>
/// <param name="applicationInWorkId"></param> /// <param name="applicationInWorkId"></param>
/// <returns></returns> /// <returns></returns>
Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId); Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId, Guid workGroupid);
/// <summary> /// <summary>
/// Обновляет расписание запуска шаблонов по РР (сохраняет в БД) /// Обновляет расписание запуска шаблонов по РР (сохраняет в БД)

View File

@@ -1,12 +1,74 @@
namespace PARR.TemplateDistributor using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces;
using PARR.TemplateDistributor.Services;
using PARR.TemplateDistributor.Settings;
namespace PARR.TemplateDistributor
{ {
internal class MqTemplateDistributor : IMqTemplateDistributor internal class MqTemplateDistributor : IMqTemplateDistributor
{ {
public void Start() { private readonly MqSettings mqSettings;
private readonly IMqService mqService;
private readonly ILogger<MqTemplateDistributor> logger;
private readonly ITransformService transformService;
private readonly IValidatorService validatorService;
private readonly IServiceProvider serviceProvider;
// принимать TemplateDistributorMq public MqTemplateDistributor(MqSettings mqSettings,
IMqService mqService,
ILogger<MqTemplateDistributor> logger,
ITransformService transformService,
IValidatorService validatorService,
IServiceProvider serviceProvider
)
{
this.mqSettings = mqSettings;
this.mqService = mqService;
this.logger = logger;
this.transformService = transformService;
this.validatorService = validatorService;
this.serviceProvider = serviceProvider;
}
public void Start()
{
var isConnected = mqService.InitConsumer(mqSettings, UpdateScheduleAsync);
if (!isConnected)
throw new Exception("Ошибка при подключении к RabbitMq");
}
public void Stop()
{
mqService.Dispose();
}
private async Task UpdateScheduleAsync(string msg)
{
logger.LogInformation($"Получили запрос: {msg}");
var query = transformService.GetModelFromJson<TemplateDistributorMq>(msg);
if (query == null)
return;
if (!await validatorService.IsValidApplicationAndWorksAsync(query.ApplicationInWorkId))
{
logger.LogError($"Некорректные параметры регалментной работы {nameof(query.ApplicationInWorkId)}: {query.ApplicationInWorkId}");
return;
}
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<ITemplateDistributor>();
if (service == null)
throw new Exception($"Не найден сервис: {nameof(service.GetType)}");
await service.UpdateScheduleAsync(query.ApplicationInWorkId);
}
} }
public void Stop() { }
} }
} }

View File

@@ -0,0 +1,7 @@
namespace PARR.TemplateDistributor.Services
{
internal interface IValidatorService
{
Task<bool> IsValidApplicationAndWorksAsync(Guid applicationInWorkId);
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.DAL.Services.Interfaces;
namespace PARR.TemplateDistributor.Services
{
internal class ValidatorService : IValidatorService
{
private readonly IServiceProvider serviceProvider;
private readonly ILogger<ValidatorService> logger;
public ValidatorService(IServiceProvider serviceProvider,
ILogger<ValidatorService> logger
)
{
this.serviceProvider = serviceProvider;
this.logger = logger;
}
public async Task<bool> IsValidApplicationAndWorksAsync(Guid applicationInWorkId)
{
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<IApplicationsInWorkService>();
if (service == null)
throw new Exception($"Не найден сервис: {nameof(IApplicationsInWorkService)}");
var appInWork = await service.Get()
.Include(aiw => aiw.EsppSchValues)
.FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
if (appInWork == null)
{
logger.LogError($"Не найдена регалментная работа {nameof(applicationInWorkId)}: {applicationInWorkId}");
return false;
}
if (appInWork.IsAutoDistributionEnabled && appInWork.EsppSchValues.Count !=1)
{
logger.LogError($"Регалментная работа {nameof(applicationInWorkId)}: {applicationInWorkId} должна иметь только одно значение EsppSchTypeValue" +
$", соответствующее режиму равномерного распределения по периоду");
return false;
}
return true;
}
}
}
}

View File

@@ -1,16 +1,40 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.BLL.Services.Interfaces;
using PARR.Constants;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
namespace PARR.TemplateDistributor namespace PARR.TemplateDistributor
{ {
internal class TemplateDistributor : ITemplateDistributor internal class TemplateDistributor : ITemplateDistributor
{ {
private readonly ILogger<TemplateDistributor> logger;
private readonly ITemplateService templateService;
private readonly IApplicationsInWorkService applicationsInWorkService; private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly IEsppSchTypeScheduleService esppSchTypeScheduleService;
private readonly ICalendarService calendarService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IWeekendDayService weekendDayService;
public TemplateDistributor(IApplicationsInWorkService applicationsInWorkService) public TemplateDistributor(
ILogger<TemplateDistributor> logger,
ITemplateService templateService,
IApplicationsInWorkService applicationsInWorkService,
IEsppSchTypeScheduleService esppSchTypeScheduleService,
ICalendarService calendarService,
IEsppScheduleTransformService esppScheduleTransformService,
IWeekendDayService weekendDayService
)
{ {
this.logger = logger;
this.templateService = templateService;
this.applicationsInWorkService = applicationsInWorkService; this.applicationsInWorkService = applicationsInWorkService;
this.esppSchTypeScheduleService = esppSchTypeScheduleService;
this.calendarService = calendarService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.weekendDayService = weekendDayService;
} }
@@ -20,27 +44,204 @@ namespace PARR.TemplateDistributor
// группирует по РР // группирует по РР
// -> DistributeTemplateForPeriodAsync // -> DistributeTemplateForPeriodAsync
// сохранить в БД // сохранить в БД
//applicationInWorkId = Guid.Parse("bdfefd77-bce3-4f62-a484-5042c06f4467");
var appInWork = await applicationsInWorkService.Get()
.Include(aiw => aiw.EsppSchValues)
.ThenInclude(esv => esv.EsppSchTypeValue)
.ThenInclude(etv => etv!.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
var templates = await templateService.Get()
.Include(t => t.Host)
.ThenInclude(h => h.WorkGroup)
.Where(t => t.ApplicationInWorkId == applicationInWorkId)
.ToListAsync();
//var refDate = appInWork!.ReferenceDate;
//var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
//DistributionPeriodTypeEnum periodType = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), period!.Type);
//var workGroupsWithTemplates = templates.GroupBy(t => new { t.Host!.WorkGroupId, t.NextRun});
var workGroupsWithTemplates = templates.GroupBy(t => t.Host!.WorkGroupId);
//var startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(applicationInWorkId, DateTimeOffset.UtcNow/*.AddDays(45)*/, refDate, periodType, period.Duration);
//var workdays = calendarService.GetWorkDatesForPeriod(startPeriod, periodType, period.Duration, weekendDayService.GetWeekends);
//var templatePerWorkDay = (int)Math.Ceiling((double)templates.Count() / workdays.Count());//
foreach (var item in workGroupsWithTemplates)
{
var wgId = item.Key ?? Guid.NewGuid();//TODO сделать правильно
var values = item.ToList();
var distrTemplates = await DistributeTemplateAsync(values, applicationInWorkId, wgId);
}
// saveChanges
if (!await applicationsInWorkService.CommitAsync())
logger.LogError($"Ошибка записи изменений в БД при перераспределении NextRun шаблонов");
} }
public async Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId) public async Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId, Guid workGroupid)
//public async Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId)
{ {
//TODO: ЗАГУЛШКА переделать! //TODO: ЗАГУЛШКА переделать!
// распределить? // распределить?
// не распределить, NextRun = appInWork.ReferenceDate // не распределить, NextRun = appInWork.ReferenceDate
var appInWork = await applicationsInWorkService.Get().FirstOrDefaultAsync(t => t.Id == applicationInWorkId); var appInWork = await applicationsInWorkService.Get()
if (appInWork == null) .Include(aiw => aiw.EsppSchValues)
.ThenInclude(esv => esv.EsppSchTypeValue)
.ThenInclude(etv => etv!.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
//appInWork!.IsAutoDistributionEnabled = true;//!!!!!!!!!!!!!!!!!!!КОСТЫЛЬ пока нет связки с фронтом
//Проверяем наличие распределения РР на период
if (appInWork!.IsAutoDistributionEnabled)
{ {
throw new Exception("ERROROORORO!!!"); var existingTemplates = new List<Template>();
//Если распределенная РР получаем начало
//DateOnly startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(applicationInWorkId,DateTimeOffset.UtcNow);
var refDate = appInWork.ReferenceDate;
var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
//if(period != null) {
DistributionPeriodTypeEnum periodType = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), period!.Type);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//periodType = DistributionPeriodTypeEnum.Day;
//period.Duration = "90";
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
var startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(applicationInWorkId, DateTimeOffset.UtcNow/*.AddDays(45)*/, appInWork.ReferenceDate, periodType, period.Duration);
var workdays = calendarService.GetWorkDatesForPeriod(startPeriod, periodType, period.Duration, weekendDayService.GetWeekends);
var d = new Dictionary<DateOnly, int>();
foreach (var wd in workdays)
{
var wrokDateTimeOffset = new DateTimeOffset(
wd.Year, wd.Month, wd.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0)); ;
var assignedTemplateCount = existingTemplates.Where(t => t.NextRun == wrokDateTimeOffset).Count();
d.Add(wd, assignedTemplateCount);
} }
//templates = templates.Take(5).ToList();
templates.ForEach(t => t.NextRun = appInWork.ReferenceDate); //var templatesPerStep = (int)Math.Ceiling((double)templates.Count() / workdays.Count());//
var templatesPerStep = (double)templates.Count() / workdays.Count();//
//templatesPerStep = (templatesPerStep < 1) ? 1 : templatesPerStep;
var currentDay = new DateTimeOffset(
startPeriod.Year, startPeriod.Month, startPeriod.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0)); ;
var balance = templates.Count();
var step = 0;
var stepDays = (int)Math.Floor((double)workdays.Count() / templates.Count());
stepDays = (stepDays < 1) ? 1 : stepDays;
var currentTemplateStep = (int)Math.Ceiling(templatesPerStep);
var delta = templatesPerStep - currentTemplateStep;
templates.First().NextRun = currentDay;
var templateDistributed = 0;
//Отталкиваясь от количества
while (templateDistributed < templates.Count())
{
var wdCounts = d.Select(wd => wd.Value).OrderBy(count => count).ToArray();
// = existingTemplates.Select(t => t.NextRun).OrderBy(nextRun => nextRun).Distinct().ToArray();//managers.Select(m => m.Orders.Count).OrderBy(count => count).Distinct().ToArray();
var templateCountDelta = d.Max(t => t.Value) - d.Max(t => t.Value);
var addingTemplatesCount = wdCounts.Length > 1 ?
(templateCountDelta == 0) ? (int)Math.Ceiling(templatesPerStep) : templateCountDelta :
(int)Math.Ceiling(templatesPerStep);
var workDaysWithMinTemplates = d.Where(wd => wd.Value == wdCounts[0]).ToArray();
if (workDaysWithMinTemplates.Length * addingTemplatesCount < templates.Count())
{
foreach (var wd in workDaysWithMinTemplates)
{
//Считаем DateTimeOffset, потому что у нас только дата пока
currentDay = new DateTimeOffset(
wd.Key.Year, wd.Key.Month, wd.Key.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0));
if (currentDay < DateTimeOffset.UtcNow)
currentDay = esppScheduleTransformService.GetNextDateForDistributionRun(currentDay, periodType, period.Duration);
templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t => t.NextRun = currentDay);
templateDistributed += addingTemplatesCount;
d[wd.Key] = wd.Value + addingTemplatesCount;
}
}
else
{
while (templateDistributed < templates.Count())
{
//var wdIndex = templateDistributed % workDaysWithMinTemplates.Length;
//var wd = workDaysWithMinTemplates[wdIndex];
var wd = d.Where(wd => wd.Value == d.Min(t => t.Value)).First();
//Считаем DateTimeOffset, потому что у нас только дата пока
currentDay = new DateTimeOffset(
wd.Key.Year, wd.Key.Month, wd.Key.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0));
if (currentDay < DateTimeOffset.UtcNow)
currentDay = esppScheduleTransformService.GetNextDateForDistributionRun(currentDay, periodType, period.Duration);
templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t => t.NextRun = currentDay);
d[wd.Key] = wd.Value + addingTemplatesCount;
templateDistributed += addingTemplatesCount;
}
}
}
////Отталкиваясь от количества рабочих дней
//foreach (var wd in workdays)
//{
// if (currentTemplateStep > 1 && currentTemplateStep >= templatesPerStep)
// {
// var roundedCurTemplStep = (int)Math.Round(currentTemplateStep);
// //Считаем DateTimeOffset, потому что у нас только дата пока
// currentDay = new DateTimeOffset(
// wd.Year, wd.Month, wd.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// if (currentDay < DateTimeOffset.UtcNow)
// currentDay = esppScheduleTransformService.GetNextDateForDistributionRun(currentDay, periodType, period.Duration);
// templates.Skip(templates.Count - balance).Take(roundedCurTemplStep).ToList().ForEach(t =>
// {
// t.NextRun = currentDay;
// });
// balance -= roundedCurTemplStep;
// delta = currentTemplateStep - roundedCurTemplStep;
// }
// else
// delta = currentTemplateStep;
// currentTemplateStep = delta + templatesPerStep;
//}
}
else
templates.ForEach(t => t.NextRun = appInWork!.ReferenceDate);
return templates; return templates;
} }
} }
} }

View File

@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PARR.BLL; using PARR.BLL;
using PARR.DAL; using PARR.DAL;
using PARR.TemplateDistributor.Services;
using PARR.TemplateDistributor.Settings; using PARR.TemplateDistributor.Settings;
namespace PARR.TemplateDistributor namespace PARR.TemplateDistributor
@@ -19,6 +20,7 @@ namespace PARR.TemplateDistributor
services.AddTransient<ITemplateDistributor, TemplateDistributor>(); services.AddTransient<ITemplateDistributor, TemplateDistributor>();
services.AddTransient<IMqTemplateDistributor, MqTemplateDistributor>(); services.AddTransient<IMqTemplateDistributor, MqTemplateDistributor>();
services.AddTransient<IValidatorService,ValidatorService>();
} }
} }
} }