using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Core.Repositories.Interfaces.TaskRepositories; using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.TaskEntities; using PARR.Domain.Enums; using PARR.Domain.Settings; using System.Text.Encodings.Web; using System.Text.Json; namespace PARR.DAL.TaskServices { internal class TaskManagementService : ITaskManagementService { private readonly ILogger logger; private readonly ITaskTypeRepository taskTypeService; private readonly ITaskRepository taskService; public TaskManagementService( ILogger logger, ITaskTypeRepository taskTypeService, ITaskRepository taskService ) { this.logger = logger; this.taskTypeService = taskTypeService; this.taskService = taskService; } public async Task CreateTaskAsync(TaskTypeEnum typeCode, T payload, IHistoryInitiator initiator, IMqSettings mqSettings) { var taskType = await taskTypeService.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Code == typeCode); if (taskType == null) { logger.LogError("Тип задачи {TypeCode} не найден в БД", typeCode); throw new InvalidOperationException($"Тип задачи typeCode не найден в БД"); } // Проверка IsSingleton: если задача уже активна — возвращаем её if (taskType.IsSingleton) { var existingTask = await GetActiveSingletonTaskAsync(typeCode); if (existingTask != null) { logger.LogInformation( "Задача типа {TypeCode} уже активна (id: {ExistingId}). Возвращаем существующую.", typeCode, existingTask.Id); return existingTask.Id; } } var payloadStr = PayloadToString(payload); // Создаем новую запись задачи var task = new TaskItem { Id = Guid.NewGuid(), TypeCode = typeCode, StatusCode = TaskItemStatusEnum.Pending, Payload = payloadStr, RetryCount = 0, ProcessedAt = null, InitiatorIp = initiator.InitiatorIp, InitiatorParrComponentId = initiator.InitiatorParrComponentId, InitiatorComment = initiator.InitiatorComment }; if (!await taskService.CreateAsync(task) || !await taskService.CommitAsync()) { logger.LogError("Ошибка при сохранении задачи в БД"); return default; } logger.LogInformation("Задача {TaskId} типа {TypeCode} сохранена в БД со статусом Pending", task.Id, typeCode); // Публикуме задачу в очередь. Если вдруг даже не получится, // задача останется в БД со статусом pending, и Reconciliation Task позже ее возмет в работу // todo: опубликовать в очередь // вопросы, зачем scoped? может можно AddTransient? // нужно придумать шаблонную модель для очереди return task.Id; } /// /// Получить активную singleton задачу указанного типа /// /// /// private async Task GetActiveSingletonTaskAsync(TaskTypeEnum typeCode) { return await taskService.Get().AsNoTracking() .FirstOrDefaultAsync(t => t.TypeCode == typeCode && ( t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing )); } /// /// Payload конвертировать в string /// /// /// /// private string PayloadToString(T payload) { var jsonOptions = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; return JsonSerializer.Serialize(payload, jsonOptions); } } }