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.API.Settings; using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; using PARR.Constants; using PARR.DAL.Contracts; using PARR.DAL.DomainModels; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; using System.Text.Json; using static PARR.API.Contracts.V1.ApiRoutes; namespace PARR.API.Controllers.V1 { [Authorize(Roles = ParrRoles.Administrator.Role)] public class ApplicationInWorkController : BaseApiController { private readonly ILogger logger; private readonly IMapper mapper; private readonly IApplicationsInWorkService applicationsInWorkService; private readonly IValidator validator; private readonly IUriService uriService; private readonly ITemplateService templateService; private readonly IMqService mqService; private readonly MqSettings mqSettings; public ApplicationInWorkController( ILogger logger, IMapper mapper, IApplicationsInWorkService applicationsInWorkService, IValidator validator, IUriService uriService, ITemplateService templateService, IMqService mqService, MqSettings mqSettings ) { this.logger = logger; this.mapper = mapper; this.applicationsInWorkService = applicationsInWorkService; this.validator = validator; this.uriService = uriService; this.templateService = templateService; this.mqService = mqService; this.mqSettings = mqSettings; } /// /// Получить список заданий на выполнение работ(ApplicationInWork) постранично /// /// [HttpGet(ApiRoutes.Job.GetAll)] public async Task GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] ApplicationInWorkQuery filter) { var paginationFilter = mapper.Map(paginationQuery); IQueryable query = applicationsInWorkService.Get().Include(t => t.Application).ThenInclude(t => t!.ApplicationType); if (filter.IsLight != true) { query = query .Include(t => t.Work) //.Include(t => t.Templates) - большой запрос, делаем его отдельно .Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup); } query = query.OrderBy(t => t.ShortDescription).ThenBy(t => t.Application!.Name); if (!string.IsNullOrEmpty(filter.ShortDescription)) query = query.Where(t => t.ShortDescription.ToLower().Contains(filter.ShortDescription.ToLower())); if (filter.WorkId.HasValue) query = query.Where(t => t.WorkId == filter.WorkId.Value); var appInWorks = await applicationsInWorkService.GetPage(query, paginationFilter).ToListAsync(); if (!appInWorks.Any()) return NoContent(); var response = mapper.Map>(appInWorks); if (filter.IsLight != true) { //Если запрос не легкий, загружаем кол-во шаблонов отдельно, это значительно ускоряет запрос foreach (var item in response) { item.TemplatesCount = await templateService.Get().CountAsync(t => t.ApplicationInWorkId == item.Id); var statistics = await GetStatisticsAsync(item.Id); BindStatistics(item, statistics); } } var paginationResponse = new PagedResponse(response, true).GetPaginatedProps(paginationFilter, query); return Ok(paginationResponse); } /// /// Получить задание на выполнение работ по id /// /// /// [HttpGet(ApiRoutes.Job.Get)] public async Task GetById([FromRoute] Guid id) { var applicationInWork = await applicationsInWorkService.Get() .Include(t => t.Application).ThenInclude(t => t!.ApplicationType) .Include(t => t.Work) //.Include(t => t.Templates) .Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) .FirstOrDefaultAsync(t => t.Id == id); if (applicationInWork == null) return NotFound(); var response = mapper.Map(applicationInWork); response.TemplatesCount = await templateService.Get().CountAsync(t => t.ApplicationInWorkId == id); var statistics = await GetStatisticsAsync(response.Id); BindStatistics(response, statistics); return Ok(new Response(response, true)); } /// /// Создать задание на выполнение работ (ApplicationInWork) /// /// /// [HttpPost(ApiRoutes.Job.Create)] public async Task Create([FromBody] ApplicationInWorkRequest request) { var resultValidate = await validator.ValidateAsync(request); if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); //уникальная запись по полям ApplicationId, WorkId var existSameAiW = await applicationsInWorkService.GetAsync(request.ApplicationId, request.WorkId); if (existSameAiW != null) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Задание на выполнение работ с такими ПО и работой уже существует" } })); var applicationInWork = new ApplicationsInWork { Id = Guid.NewGuid(), WorkId = request.WorkId, ApplicationId = request.ApplicationId, TemplateDuration = request.TemplateDuration, ShortDescription = request.ShortDescription.Trim(), FullDescription = request.FullDescription.Trim(), Solution = request.Solution.Trim(), //NextRun = request.NextRun, IsAutoDistributionEnabled = request.IsAutoDistributionEnabled, ReferenceDate = request.ReferenceDate, IsAgent = request.IsAgent, AgentName = request.AgentName?.Trim(), AgentTimeOutSec = request.AgentTimeOutSec, AgentScript = request.AgentScript?.Trim() }; //Добавляем настройки планировщика request.Schedule.ForEach(item => { applicationInWork.EsppSchValues.Add(new EsppSchValue { ApplicationsInWorkId = applicationInWork.Id, TypeConfigId = item.TypeConfigId, TypeValueId = item.TypeValueId }); }); //Добавляем рабочие группы request.WorkGroups.ForEach(workGroupId => { applicationInWork.WorkGroups.Add(new AppInWorkInWorkGroup { ApplicationsInWorkId = applicationInWork.Id, WorkGroupId = workGroupId }); }); if (!await applicationsInWorkService.CreateAsync(applicationInWork) || !await applicationsInWorkService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {applicationInWork.Id}, {applicationInWork.ShortDescription}, {applicationInWork.Solution}"); var createdApplicationInWork = await applicationsInWorkService.Get() .Include(t => t.Application).ThenInclude(t => t!.ApplicationType) .Include(t => t.Work) //.Include(t => t.Templates) .Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) .FirstAsync(t => t.Id == applicationInWork.Id); var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdApplicationInWork.Id); var response = mapper.Map(createdApplicationInWork); // так как мы только что создали AppInW, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос) response.TemplatesCount = 0; return Created(locationUri, new Response(response, true)); } /// /// Обновить задание на выполнение работ (ApplicationInWork) /// /// /// /// [HttpPut(ApiRoutes.Job.Update)] public async Task Update([FromRoute] Guid id, [FromBody] ApplicationInWorkRequest request) { var resultValidate = await validator.ValidateAsync(request); if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); //уникальная запись по полям ApplicationId, WorkId у которой id!=[FromRoute]id var existSameAiW = await applicationsInWorkService.Get() .FirstOrDefaultAsync(t => t.Id != id && t.ApplicationId == request.ApplicationId && t.WorkId == request.WorkId); if (existSameAiW != null) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Задание на выполнение работ с такими ПО и работой уже существует" } })); var orig = await applicationsInWorkService.Get() .Include(t => t.WorkGroups) .Include(t => t.EsppSchValues) .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.WorkId = request.WorkId; orig.ApplicationId = request.ApplicationId; orig.TemplateDuration = request.TemplateDuration; orig.ShortDescription = request.ShortDescription.Trim(); orig.FullDescription = request.FullDescription.Trim(); orig.Solution = request.Solution.Trim(); //orig.NextRun = request.NextRun; orig.ReferenceDate = request.ReferenceDate; orig.IsAutoDistributionEnabled = request.IsAutoDistributionEnabled; orig.IsAgent = request.IsAgent; orig.AgentName = request.AgentName?.Trim(); orig.AgentTimeOutSec = request.AgentTimeOutSec; orig.AgentScript = request.AgentScript?.Trim(); orig.DateModified = DateTimeOffset.UtcNow; //обновляем планировщик orig.EsppSchValues.Clear(); request.Schedule.ForEach(item => { orig.EsppSchValues.Add(new EsppSchValue { ApplicationsInWorkId = orig.Id, TypeConfigId = item.TypeConfigId, TypeValueId = item.TypeValueId }); }); //Обновляем рабочие группы orig.WorkGroups.Clear(); request.WorkGroups.ForEach(workGroupId => { orig.WorkGroups.Add(new AppInWorkInWorkGroup { ApplicationsInWorkId = orig.Id, WorkGroupId = workGroupId }); }); if (!await applicationsInWorkService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении задания на выполнение работ." } })); logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," + $" {orig.WorkId}, {orig.ApplicationId}, {orig.TemplateDuration}, {orig.ShortDescription}," + $" {orig.FullDescription}, {orig.Solution}, {nameof(orig.IsAutoDistributionEnabled)}: {orig.IsAutoDistributionEnabled}, {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); 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 updatedApplicationInWork = await applicationsInWorkService.Get() .Include(t => t.Application).ThenInclude(t => t!.ApplicationType) .Include(t => t.Work) //.Include(t => t.Templates) .Include(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) .FirstAsync(t => t.Id == orig.Id); var response = mapper.Map(updatedApplicationInWork); response.TemplatesCount = await templateService.Get().CountAsync(t => t.ApplicationInWorkId == id); var statistics = await GetStatisticsAsync(response.Id); BindStatistics(response, statistics); return Ok(new Response(response, true)); } /// /// Удалить задание на выполнение работ (только если нет связанных шаблонов) /// /// /// [HttpDelete(ApiRoutes.Job.Delete)] public async Task Delete([FromRoute] Guid id) { var applicationsInWork = await applicationsInWorkService.Get() //.Include(t => t.Templates) .FirstOrDefaultAsync(t => t.Id == id); if (applicationsInWork == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ. Не найдено задание на выполнение работ Id: {id}" } })); var templateCount = await templateService.Get().CountAsync(t => t.ApplicationInWorkId == id); //if (applicationsInWork.Templates.Any()) if (templateCount > 0) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ. С данным заданием связаны шаблоны: {templateCount} шт." } })); if (!applicationsInWorkService.Delete(applicationsInWork) || !await applicationsInWorkService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {applicationsInWork.Id},{applicationsInWork.WorkId}," + $" {applicationsInWork.ApplicationId}, {applicationsInWork.TemplateDuration}, {applicationsInWork.ShortDescription}," + $" {applicationsInWork.FullDescription}, {applicationsInWork.Solution}, {nameof(applicationsInWork.IsAutoDistributionEnabled)}: {applicationsInWork.IsAutoDistributionEnabled}, {applicationsInWork.IsAgent}," + $" {applicationsInWork.AgentName}, {applicationsInWork.AgentTimeOutSec}, {applicationsInWork.AgentScript}"); return NoContent(); } /// /// Проверка, были ли изменения в расписании /// /// /// /// private bool IsScheduleChanged(ApplicationsInWork orig, ApplicationInWorkRequest 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.ApplicationsInWorkId == orig.Id && t.TypeValueId == requestSchedule.TypeValueId && t.TypeConfigId == requestSchedule.TypeConfigId); if (schExist == null) isScheduleChanged = true; }); return isScheduleChanged; } /// /// Загрузка статистики /// /// /// private async Task GetStatisticsAsync(Guid applicationInWorkId) { var statResult = await applicationsInWorkService.Get() .Include(t => t.Templates).ThenInclude(t => t.RobotConfigurations) .Where(x => x.Id == applicationInWorkId) .Select(t => new { TemplateActivated = t.Templates.Count(x => x.IsActiveTemplate), TemplateSynchronized = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.TaskStatusCode == (int)TaskStatusEnum.Ok && c.RobotCode == (int)RobotsEnum.TemplateOrder)), TemplateErrors = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.RobotStatusCode == (int)RobotStatusEnum.Error && c.RobotCode == (int)RobotsEnum.TemplateOrder)), ScheduleActivated = t.Templates.Count(x => x.IsActiveSchedule), ScheduleSynchronized = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.TaskStatusCode == (int)TaskStatusEnum.Ok && c.RobotCode == (int)RobotsEnum.ScheduleOrder)), ScheduleErrors = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.RobotStatusCode == (int)RobotStatusEnum.Error && c.RobotCode == (int)RobotsEnum.ScheduleOrder)) }).FirstOrDefaultAsync(); return new AppInWorkStatModel { ScheduleStatistics = new ScheduleStats { Activated = statResult?.ScheduleActivated ?? 0, Errors = statResult?.ScheduleErrors ?? 0, Synchronized = statResult?.ScheduleSynchronized ?? 0 }, TemplateStatistics = new TemplateStats { Activated = statResult?.TemplateActivated ?? 0, Errors = statResult?.TemplateErrors ?? 0, Synchronized = statResult?.TemplateSynchronized ?? 0 } }; } private void BindStatistics(ApplicationInWorkResponse applicationsInWork, AppInWorkStatModel statistics) { applicationsInWork.TemplateStatistics = new TemplateStats { Activated = statistics.TemplateStatistics.Activated, Errors = statistics.TemplateStatistics.Errors, Synchronized = statistics.TemplateStatistics.Synchronized }; applicationsInWork.ScheduleStatistics = new ScheduleStats { Activated = statistics.ScheduleStatistics.Activated, Errors = statistics.ScheduleStatistics.Errors, Synchronized = statistics.ScheduleStatistics.Synchronized }; } } public class AppInWorkStatModel { public TemplateStats TemplateStatistics { get; set; } public ScheduleStats ScheduleStatistics { get; set; } } }