using AutoMapper; using FluentValidation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Requests.Queries; using PARR.API.Contracts.V1.Responses; using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; using PARR.API.Extensions; using PARR.API.Services.Interfaces; using PARR.BLL.Helpers; using PARR.Constants; using PARR.DAL.Contracts; using PARR.DAL.DomainModels; using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.Models; using PARR.DAL.Models.Job; using PARR.DAL.NextRunServices; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Schedule; namespace PARR.API.Controllers.V1 { /// /// Управление группами работам /// [Authorize(Roles = ParrRoles.Administrator.Role)] public class JobGroupController : BaseApiController { private readonly ILogger logger; private readonly IMapper mapper; private readonly IUriService uriService; private readonly IJobGroupService groupService; private readonly IJobService jobService; private readonly IEsppSchTypeConfigService esppConfigService; private readonly IValidator validator; private readonly IJobGroupTypeService jobGroupTypeService; private readonly SettingsFromDb settingsFromDb; private readonly IMatchingStatusService matchingStatusService; private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService; public JobGroupController( ILogger logger, IMapper mapper, IUriService uriService, IJobGroupService groupService, IJobService jobService, IEsppSchTypeConfigService esppConfigService, IValidator validator, IJobGroupTypeService jobGroupTypeService, SettingsFromDb settingsFromDb, IMatchingStatusService matchingStatusService, IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService ) { this.logger = logger; this.mapper = mapper; this.uriService = uriService; this.groupService = groupService; this.jobService = jobService; this.esppConfigService = esppConfigService; this.validator = validator; this.jobGroupTypeService = jobGroupTypeService; this.settingsFromDb = settingsFromDb; this.matchingStatusService = matchingStatusService; this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService; } /// /// Получить список групп заданий на выполнение работ постранично /// /// [HttpGet(ApiRoutes.JobGroup.GetAll)] public async Task GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobGroupQuery filter) { var paginationFilter = mapper.Map(paginationQuery); IQueryable query = groupService.Get() .Include(t => t.GroupType) .Include(t => t.GroupingUnitField) .Include(t => t.ScheduleExcludeType) .Include(t => t.ScheduleExcludeTypeCalendar); query = query.OrderBy(t => t.GroupName); if (!string.IsNullOrEmpty(filter.Name)) { //query = query.Where(t => t.GroupName.ToLower().Contains(filter.Name.ToLower())); query = query.Where(t => EF.Functions.Like(t.GroupName.ToLower(), SqlHelpers.RegexToLike(filter.Name))); } if (filter.IsFull) query = query .Include(t => t.Jobs).ThenInclude(t => t.Tnk) .Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod); var jobGroups = await groupService.GetPage(query, paginationFilter).ToListAsync(); if (!jobGroups.Any()) return NoContent(); var response = mapper.Map>(jobGroups); if (filter.IsFull) foreach (var jobGroupResponse in response) await AppendMissingDataAsync(jobGroupResponse); var paginationResponse = new PagedResponse(response, true).GetPaginatedProps(paginationFilter, query); return Ok(paginationResponse); } /// /// Получить группу заданий на выполнение работ по id /// /// /// [HttpGet(ApiRoutes.JobGroup.Get)] public async Task GetById([FromRoute] Guid id) { var jobGroup = await groupService.Get() .Include(t => t.Jobs).ThenInclude(t => t.Tnk) .Include(t => t.GroupType) .Include(t => t.GroupingUnitField) .Include(t => t.ScheduleExcludeType) .Include(t => t.ScheduleExcludeTypeCalendar) .Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod) .FirstOrDefaultAsync(t => t.Id == id); if (jobGroup == null) return NotFound(); var response = mapper.Map(jobGroup); await AppendMissingDataAsync(response); return Ok(new Response(response, true)); } /// /// Создать группу заданий на выполнение работ (JobGroup) /// /// /// [HttpPost(ApiRoutes.JobGroup.Create)] public async Task Create([FromBody] JobGroupRequest request) { var resultValidate = await validator.ValidateAsync(request); if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); var jobGroup = new JobGroup { Id = Guid.NewGuid(), GroupName = request.Name.Trim(), //IsUmbrella = request.IsUmbrella, GroupTypeId = request.GroupTypeId, GroupingUnitFieldId = await GetGroupingUnitFieldIdAsync(request), ShortDescription = request.ShortDescription.Trim(), FullDescription = request.FullDescription.Trim(), Solution = request.Solution.Trim(), TemplateDuration = request.TemplateDuration.Trim(), ReferenceDate = request.ReferenceDate, ScheduleExcludeTypeId = request.ScheduleExcludeTypeId, ScheduleExcludeTypeCalendarId = request.ScheduleExcludeTypeCalendarId, IsAutoDistributionEnabled = request.IsAutoDistributionEnabled, IsResponseAreaTimezone = request.IsWorkGroupTimezone //IsAgent = request.IsAgent, //AgentName = request.AgentName, //AgentTimeOutSec = request.AgentTimeOutSec, //AgentScript = request.AgentScript }; #region Если включено автораспределение, добавляем настройки if (request.IsAutoDistributionEnabled) { // на всякий проверим, но вообще это проверяется в валидаторе if (request.DistributionConfig == null) { logger.LogError("Ошибка при создании группы работ '{name}', отсутствуют настройки автораспределения", request.Name); return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } })); } jobGroup.DistributionConfig = CreateDistributionConfig(request.DistributionConfig, jobGroup.Id); } #endregion //Добавляем настройки планировщика request.Schedule.ForEach(item => { jobGroup.EsppSchValues.Add(new EsppSchValue { JobGroupId = jobGroup.Id, TypeConfigId = item.TypeConfigId, TypeValueId = item.TypeValueId }); }); if (!await groupService.CreateAsync(jobGroup) || !await groupService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}"); var createdJobGroup = await groupService.Get().Include(t => t.Jobs).ThenInclude(t => t.Tnk) .Include(t => t.GroupType) .Include(t => t.GroupingUnitField) .Include(t => t.ScheduleExcludeType) .Include(t => t.ScheduleExcludeTypeCalendar) .Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod) .FirstAsync(t => t.Id == jobGroup.Id); var locationUri = uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id); var response = mapper.Map(createdJobGroup); await AppendMissingDataAsync(response); return Created(locationUri, new Response(response, true)); } /// /// Обновить группу заданий на выполнение работ (JobGroup) /// /// /// /// [HttpPut(ApiRoutes.JobGroup.Update)] public async Task Update([FromRoute] Guid id, [FromBody] JobGroupRequest request) { var resultValidate = await validator.ValidateAsync(request); if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); var orig = await groupService.Get() .Include(t => t.Jobs) .ThenInclude(t => t.Tnk) .Include(t => t.EsppSchValues) .Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod) .FirstOrDefaultAsync(t => t.Id == id); if (orig == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении группы заданий на выполнение работ. Не найдена группа с Id: {id}" } })); // //Расписание было изменено, ниже добавим задание в очередь на обновление расписаний у связанных шаблонов var isScheduleChanged = IsScheduleChanged(orig, request); orig.GroupName = request.Name.Trim(); //orig.IsUmbrella = request.IsUmbrella; orig.GroupTypeId = request.GroupTypeId; orig.GroupingUnitFieldId = await GetGroupingUnitFieldIdAsync(request); orig.ShortDescription = request.ShortDescription.Trim(); orig.FullDescription = request.FullDescription.Trim(); orig.Solution = request.Solution.Trim(); orig.TemplateDuration = request.TemplateDuration.Trim(); orig.ReferenceDate = request.ReferenceDate; orig.ScheduleExcludeTypeId = request.ScheduleExcludeTypeId; orig.ScheduleExcludeTypeCalendarId = request.ScheduleExcludeTypeCalendarId; orig.IsAutoDistributionEnabled = request.IsAutoDistributionEnabled; orig.IsResponseAreaTimezone = request.IsWorkGroupTimezone; //orig.IsAgent = request.IsAgent; //orig.AgentName = request.AgentName; //orig.AgentTimeOutSec = request.AgentTimeOutSec; //orig.AgentScript = request.AgentScript; #region Обновляем настройки автораспределения if (request.IsAutoDistributionEnabled) { if (request.DistributionConfig == null) { logger.LogError("Ошибка при изменении группы работ '{name}', отсутствуют настройки автораспределения", request.Name); return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ" } })); } // если настройки были, меняем, если не было, создаем if (orig.DistributionConfig != null) { orig.DistributionConfig.DistributionPeriodId = request.DistributionConfig.DistributionPeriodId; orig.DistributionConfig.IsExcludeWeekends = request.DistributionConfig.IsExcludeWeekends; orig.DistributionConfig.IsGroupingByWorkGroup = request.DistributionConfig.IsGroupingByWorkGroup; } else { //создаем orig.DistributionConfig = CreateDistributionConfig(request.DistributionConfig, orig.Id); } } else { // удаляем настройки распределения если они были if (orig.DistributionConfig != null) { groupService.DeleteDistributionConfig(orig.DistributionConfig); } } #endregion //обновляем планировщик orig.EsppSchValues.Clear(); request.Schedule.ForEach(item => { orig.EsppSchValues.Add(new EsppSchValue { JobGroupId = orig.Id, TypeConfigId = item.TypeConfigId, TypeValueId = item.TypeValueId }); }); if (!await groupService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ." } })); logger.LogInformation($"Пользователь {User.Identity?.Name} обновил группу заданий на выполнение работ: {orig.Id}," + $" {orig.GroupName}, {orig.ShortDescription}, {orig.FullDescription}," + $" {orig.Solution}, {orig.TemplateDuration}, {orig.ReferenceDate}, {orig.IsAutoDistributionEnabled}" + $", {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); //TODO: Восстановить после перехода на JobGroup //if (isScheduleChanged) //{ // //расписание было обновлено, отправим задание в очередь на перерасчет NextRun // var requestToMq = new TemplateDistributorMq // { // ApplicationInWorkId = id // }; // var msg = JsonSerializer.Serialize(requestToMq); // logger.LogDebug($"Расписание в РР applicationInWorkId: {id} было изменено. Отправляем задание в очередь на перерасчет NextRun"); // var sendResult = mqService.Send(mqSettings.TemplateDistributor, new[] { msg }); // if (sendResult.IsSuccess) // logger.LogInformation($"Задание на перерасчет NextRun успешно отправлено в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); // else // logger.LogError($"Ошибка при отправке задания на перерасчет NextRun в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); //} var updatedJobGroup = await groupService.Get() .Include(t => t.Jobs) .ThenInclude(t => t.Tnk) .Include(t => t.GroupType) .Include(t => t.GroupingUnitField) .Include(t => t.ScheduleExcludeType) .Include(t => t.ScheduleExcludeTypeCalendar) .Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod) .FirstAsync(t => t.Id == orig.Id); var response = mapper.Map(updatedJobGroup); await AppendMissingDataAsync(response); return Ok(new Response(response, true)); } /// /// Удалить группу заданий на выполнение работ (только если нет связанных заданий) /// /// /// [HttpDelete(ApiRoutes.JobGroup.Delete)] public async Task Delete([FromRoute] Guid id) { var jobGroup = await groupService.Get() .FirstOrDefaultAsync(t => t.Id == id); if (jobGroup == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении группы заданий на выполнение работ. Не найдена группа заданий на выполнение работ Id: {id}" } })); var jobCount = await jobService.Get().CountAsync(t => t.GroupId == id); if (jobCount > 0) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении группы заданий на выполнение работ. С данным группой связаны задания: {jobCount} шт." } })); if (!groupService.Delete(jobGroup) || !await groupService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении группы заданий на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} удалил группу заданий на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," + $" {jobGroup.ShortDescription}, {jobGroup.FullDescription}," + $" {jobGroup.Solution}, {jobGroup.TemplateDuration}, {jobGroup.ReferenceDate}," + $" {jobGroup.IsAutoDistributionEnabled}, {jobGroup.IsAgent}, {jobGroup.AgentName}," + $" {jobGroup.AgentTimeOutSec}, {jobGroup.AgentScript}"); return NoContent(); } /// /// Проверка, были ли изменения в расписании /// /// /// /// private bool IsScheduleChanged(JobGroup orig, JobGroupRequest request) { var isScheduleChanged = false; if (orig.ReferenceDate != request.ReferenceDate) isScheduleChanged = true; if (request.Schedule.Count() != orig.EsppSchValues.Count()) isScheduleChanged = true; //if (request.IsAutoDistributionEnabled != orig.IsAutoDistributionEnabled) // isScheduleChanged = true; request.Schedule.ForEach(requestSchedule => { var schExist = orig.EsppSchValues.FirstOrDefault(t => t.JobGroupId == orig.Id && t.TypeValueId == requestSchedule.TypeValueId && t.TypeConfigId == requestSchedule.TypeConfigId); if (schExist == null) isScheduleChanged = true; }); return isScheduleChanged; } /// /// Заполнить обязательные поля /// /// /// private async Task AppendMissingDataAsync(JobGroupResponse jobGroupResponse) { var schedule = await esppConfigService.GetEsppScheduleDtoAsync(jobGroupResponse.Id); if (schedule == null) { logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {jobGroupResponse.Id}"); return; } var scheduleResponse = new JobGroupScheduleResponse { //Timezone = settingsFromDb.ScheduleTimezone, Timezone = scheduleResponseAreaTimeOffsetService.GetDefault.EsppValue, TypeSchedule = mapper.Map(schedule.TypeSchedule), Values = mapper.Map>(schedule.Values).OrderBy(t => t.Order).ToList() }; jobGroupResponse.Schedule = scheduleResponse; jobGroupResponse.JobsCount = await jobService.Get().CountAsync(t => t.GroupId == jobGroupResponse.Id); jobGroupResponse.MatchingStatus = await GetMatchingStatusAsync(jobGroupResponse.Id); } /// /// Смотрим на тип Группы, и возвращаем GroupingUnitFieldId или обнуляем его /// /// /// private async Task GetGroupingUnitFieldIdAsync(JobGroupRequest request) { // Если request.GroupingUnitFieldId == null, то ничего проверять не будем if (request.GroupingUnitFieldId == null) return request.GroupingUnitFieldId; var groupingType = await jobGroupTypeService.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group); if (request.GroupTypeId == groupingType.Id) { // Это сгруппированный тип, все ок return request.GroupingUnitFieldId; } else { // Это не сгруппированный тип, обнуляем GroupingUnitFieldId logger.LogWarning($"При сохраненни JobGroup, был передан {nameof(request.GroupingUnitFieldId)}: {request.GroupingUnitFieldId}, но при этом тип группы был не ГРУППА, а {request.GroupTypeId}. Обнулил request.GroupingUnitFieldId"); return null; } } /// /// Получить статус matching`a /// /// /// private async Task GetMatchingStatusAsync(Guid jobGroupId) { var statusMatching = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); return mapper.Map(statusMatching); } /// /// Создать конфиг распределения /// /// /// /// private JobGroupDistributionConfig CreateDistributionConfig(DistributionConfigRequest distributionConfigRequest, Guid jobGroupId) { return new JobGroupDistributionConfig { GroupId = jobGroupId, DistributionPeriodId = distributionConfigRequest.DistributionPeriodId, IsExcludeWeekends = distributionConfigRequest.IsExcludeWeekends, IsGroupingByWorkGroup = distributionConfigRequest.IsGroupingByWorkGroup }; } } }