Files
parr_api/PARR.TemplateDistributor/TemplateDistributor.cs

325 lines
15 KiB
C#
Raw Normal View History

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.BLL.Services.Interfaces;
using PARR.Constants;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
namespace PARR.TemplateDistributor
{
internal class TemplateDistributor : ITemplateDistributor
{
private readonly ILogger<TemplateDistributor> logger;
private readonly ITemplateService templateService;
private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly IEsppSchTypeScheduleService esppSchTypeScheduleService;
private readonly ICalendarService calendarService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IWeekendDayService weekendDayService;
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.esppSchTypeScheduleService = esppSchTypeScheduleService;
this.calendarService = calendarService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.weekendDayService = weekendDayService;
}
public async Task UpdateScheduleAsync(Guid applicationInWorkId)
{
// все шаблоны по applicationInWorkId
// группирует по РР
// -> DistributeTemplateForPeriodAsync
// сохранить в БД
//applicationInWorkId = Guid.Parse("bdfefd77-bce3-4f62-a484-5042c06f4467");
var appInWork = await applicationsInWorkService.GetAsync(applicationInWorkId );
// .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 workGroupsWithTemplates = templates.GroupBy(t => t.Host!.WorkGroupId);
foreach (var workGroupWithTemplates in workGroupsWithTemplates)
{
var wgId = workGroupWithTemplates.Key ?? Guid.NewGuid();//TODO сделать правильно
var values = workGroupWithTemplates.ToList();
if (!values.Any())
continue;
//Обновляем сразу время при необходимости
values.ForEach(t=>{
if (t.NextRun.TimeOfDay != appInWork!.ReferenceDate.TimeOfDay)
t.NextRun = new DateTimeOffset(t.NextRun.DateTime, new TimeSpan(appInWork!.ReferenceDate.Hour, appInWork!.ReferenceDate.Minute, appInWork!.ReferenceDate.Second));
});
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, Guid workGroupId)
{
var appInWork = await applicationsInWorkService.Get()
.Include(aiw => aiw.EsppSchValues)
.ThenInclude(esv => esv.EsppSchTypeValue)
.ThenInclude(etv => etv!.DistributionPeriod)
.FirstOrDefaultAsync(t => t.Id == applicationInWorkId);
var refDate = appInWork!.ReferenceDate;
//Проверяем наличие распределения РР на период
if (appInWork!.IsAutoDistributionEnabled)
{
var existingTemplates = await templateService.Get()
.Include(t => t.Host)
.Where(t => t.ApplicationInWorkId == applicationInWorkId && t.Host!.WorkGroupId == workGroupId)
.ToListAsync();
//Если распределенная РР получаем начало
var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
var periodType = ParseDistributionPeriodType(period!.Type);
var workDays = await GetWorkDaysAsync(appInWork);
//Готовим план распределения
var distrPlan = GetDistributionPlan(workDays, templates.Count + existingTemplates.Count);
var templateToDistrib = new List<Template>();
if (existingTemplates.Count > 0)
templateToDistrib = GetTemplatesToDistribute(ref distrPlan, existingTemplates);
templateToDistrib.AddRange(templates.Where(t=>existingTemplates.Any(et=>et.Id==t.Id)==false));
if (!templateToDistrib.Any())
return new List<Template>();
var distributedTemplates = 0;
foreach (var workDay in distrPlan)
{
var templatesCountForCurDay = workDay.Value;
templateToDistrib.Skip(distributedTemplates).Take(templatesCountForCurDay).ToList().ForEach(t =>
{
var nextRun = new DateTimeOffset(
workDay.Key.Year, workDay.Key.Month, workDay.Key.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0));
if (nextRun < DateTimeOffset.UtcNow)
nextRun = esppScheduleTransformService.GetNextDateForDistributionRun(nextRun, periodType, period.Duration);
t.NextRun = nextRun;
});
distributedTemplates += templatesCountForCurDay;
}
return templateToDistrib;
//var d = new Dictionary<DateOnly, List<Template>>();
//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));
// if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
// wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
// var assignedTemplates = existingTemplates.Where(t => t.NextRun == wrokDateTimeOffset).ToList();
// d.Add(wd, assignedTemplates);
//}
//var templatesPerStep = (double)templates.Count() / workDays.Count();//
//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();
// var templateCountDelta = d.Max(t => t.Value.Count()) - d.Min(t => t.Value.Count());
// 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, потому что у нас только дата пока
// var currentDay = new DateTimeOffset(
// wd.Key.Year, wd.Key.Month, wd.Key.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// {
// t.NextRun = currentDay;
// d[wd.Key].Add(t);
// });
// templateDistributed += addingTemplatesCount;
// }
// }
// else
// {
// while (templateDistributed < templates.Count())
// {
// var wd = d.Where(wd => wd.Value == d.Min(t => t.Value)).First();
// //Считаем DateTimeOffset, потому что у нас только дата пока
// var currentDay = new DateTimeOffset(
// wd.Key.Year, wd.Key.Month, wd.Key.Day,
// refDate.Hour, refDate.Minute, refDate.Second,
// new TimeSpan(0, 0, 0));
// templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// {
// t.NextRun = currentDay;
// d[wd.Key].Add(t);
// });
// templateDistributed += addingTemplatesCount;
// }
// }
//}
}
else
templates.ForEach(t => t.NextRun = appInWork!.ReferenceDate);
return templates;
}
private async Task<List<DateOnly>> GetWorkDaysAsync(ApplicationsInWork appInWork)
{
var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
var periodType = ParseDistributionPeriodType(period!.Type);
var startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(appInWork.Id, appInWork.ReferenceDate, appInWork.ReferenceDate, periodType, period.Duration);
var result = calendarService.GetWorkDatesForPeriod(startPeriod, periodType, period.Duration, weekendDayService.GetWeekends);
result.ForEach(wd =>
{
var wrokDateTimeOffset = new DateTimeOffset(
wd.Year, wd.Month, wd.Day,
appInWork.ReferenceDate.Hour, appInWork.ReferenceDate.Minute, appInWork.ReferenceDate.Second,
new TimeSpan(0, 0, 0));
if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
{
wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
wd = DateOnly.FromDateTime(wrokDateTimeOffset.DateTime);
}
});
return result;
}
private List<Template> GetTemplatesToDistribute(ref Dictionary<DateOnly, int> distributionPlan, List<Template> templates)
{
var result = new List<Template>();
foreach (var wd in distributionPlan)
{
var templatesInWd = templates.Where(t => DateOnly.FromDateTime(t.NextRun.DateTime) == wd.Key).ToList();
var templatesToDistribute = templatesInWd.Skip(wd.Value).Take(templatesInWd.Count - wd.Value).ToList();
distributionPlan[wd.Key] -= templatesInWd.Count;
result.AddRange(templatesToDistribute);
}
return result;
}
private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
{
var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
return result;
}
private Dictionary<DateOnly, int> GetDistributionPlan(List<DateOnly> workDays, int templateCount)
{
var d = new Dictionary<DateOnly, int>();
foreach (var wd in workDays)
{
var workDateTimeOffset = new DateOnly(wd.Year, wd.Month, wd.Day);
d.Add(workDateTimeOffset, 0);
}
var templateDistributed = 0;
if (templateCount > workDays.Count)
{
var mainPartTemplateCountToDistribute = templateCount / workDays.Count;
foreach (var item in d)
{
d[item.Key] = mainPartTemplateCountToDistribute;
templateDistributed += mainPartTemplateCountToDistribute;
}
}
var templatesPerWorkDay = (double)workDays.Count / (templateCount % workDays.Count);
var currentStep = (double)Math.Floor(templatesPerWorkDay);//??????? точно double?
var balance = (double)templatesPerWorkDay - Math.Floor(currentStep);
var currentIndex = 0;
while (templateDistributed < templateCount)
{
if (currentStep > 0)
{
d[d.ElementAt(currentIndex).Key] += 1;
templateDistributed++;
currentIndex += (int)Math.Floor(currentStep);
currentStep = templatesPerWorkDay + balance;
balance = currentStep - templatesPerWorkDay;
}
}
return d;
}
}
}