From 7dd40d17010d087660e7cc9a9b0f1aadd5529bf1 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Mon, 9 Feb 2026 12:00:46 +1000 Subject: [PATCH] =?UTF-8?q?feat(esppOrderLoader):=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D1=80=D0=B2=D0=BE=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implementations/IntervalService.cs | 24 ++- .../Services/Interfaces/IIntervalService.cs | 2 +- PARR.DAL/Contracts/SettingsFromDb.cs | 24 +-- .../UnitFilterService/UnitFilterService.cs | 2 +- PARR.DAL/Services/Abstracts/BaseService.cs | 83 +++++++--- .../Services/Implementations/OrderService.cs | 2 +- .../Implementations/OrderStatusService.cs | 14 +- .../Implementations/TemplateService.cs | 75 +++++++-- PARR.EsppApi/EsppApiService.cs | 67 +++++--- PARR.EsppApi/Models/EsppOrder.cs | 23 ++- PARR.EsppApi/Models/Query/FindOrdersQuery.cs | 4 +- PARR.EsppOrderLoader/EsppOrderLoader.cs | 136 +++++++++------ .../Services/OrderItemService.cs | 155 ++++++++++-------- PARR.EsppOrderLoaderWorker/Worker.cs | 6 - .../appsettings.Development.json | 47 +++--- PARR.TemplateMatcher/MqTemplateMatcher.cs | 22 ++- 16 files changed, 434 insertions(+), 252 deletions(-) diff --git a/PARR.BLL/Services/Implementations/IntervalService.cs b/PARR.BLL/Services/Implementations/IntervalService.cs index 609b0849..4d61f401 100644 --- a/PARR.BLL/Services/Implementations/IntervalService.cs +++ b/PARR.BLL/Services/Implementations/IntervalService.cs @@ -14,6 +14,8 @@ namespace PARR.BLL.Services.Implementations public async Task IntervalInitAsync(IntervalHandlerDelegate intervalHandler, TimeSpan interval) { + logger.LogInformation("Инициализация интервального сервиса с интервалом: {Interval}", interval); + var nextStart = DateTimeOffset.UtcNow; while (true) @@ -22,22 +24,34 @@ namespace PARR.BLL.Services.Implementations logger.LogInformation("Запуск по расписанию. Интервал: {interval}, дата: {nextStart}", interval, nextStart); - await intervalHandler.Invoke(); + try + { + await intervalHandler.Invoke(); + } + catch (Exception ex) + { + logger.LogError(ex, "Ошибка при выполнении интервального обработчика"); + } nextStart = nextStart.Add(interval); - logger.LogInformation("Следующая дата запуска: {nextStart}. Интервал: {interval}.", nextStart, interval); + logger.LogInformation("Следующая дата запуска: {NextStart}. Интервал: {Interval}.", nextStart, interval); var intervalOffset = nextStart - DateTimeOffset.UtcNow; if (intervalOffset < TimeSpan.Zero) { intervalOffset = TimeSpan.Zero; - logger.LogWarning("Время следующего запуска изменено на сейчас. Так как рассчитанное время запуска меньше чем сейчас, {nextStart}<{now}. " + - $"Это произошло потому что время выполнения основного метода больше чем заданный интервал повторений.", nextStart, DateTimeOffset.UtcNow); + logger.LogWarning( + "Время следующего запуска изменено на текущее время. Рассчитанное время запуска ({NextStart}) меньше текущего времени ({Now}). " + + "Это произошло потому что время выполнения основного метода больше чем заданный интервал повторений.", + nextStart, + DateTimeOffset.UtcNow); } + logger.LogDebug("Ожидание {Delay} перед следующим запуском", intervalOffset); + await Task.Delay(intervalOffset); } } } -} +} \ No newline at end of file diff --git a/PARR.BLL/Services/Interfaces/IIntervalService.cs b/PARR.BLL/Services/Interfaces/IIntervalService.cs index fb93ba11..ac45f577 100644 --- a/PARR.BLL/Services/Interfaces/IIntervalService.cs +++ b/PARR.BLL/Services/Interfaces/IIntervalService.cs @@ -12,4 +12,4 @@ /// Task IntervalInitAsync(IntervalHandlerDelegate intervalHandler, TimeSpan interval); } -} +} \ No newline at end of file diff --git a/PARR.DAL/Contracts/SettingsFromDb.cs b/PARR.DAL/Contracts/SettingsFromDb.cs index 3e12a1eb..e8416866 100644 --- a/PARR.DAL/Contracts/SettingsFromDb.cs +++ b/PARR.DAL/Contracts/SettingsFromDb.cs @@ -12,7 +12,6 @@ namespace PARR.DAL.Contracts { // При добавлении свойств, добавлять в миграцию: PARR.DAL.Context.DataContext - /// /// Инициатор регламентной работы, указывается при создании шаблона в ЕСПП. /// @@ -51,8 +50,9 @@ namespace PARR.DAL.Contracts { return JsonSerializer.Deserialize>(TemplateNameConstantParts) ?? new List(); } - catch + catch (Exception ex) { + // В будущем можно добавить логирование ошибки десериализации return new List(); } } @@ -66,7 +66,6 @@ namespace PARR.DAL.Contracts } } - /// /// Количество попыток выполнения задания роботом /// @@ -77,21 +76,6 @@ namespace PARR.DAL.Contracts /// public TimeSpan RobotWaitTime { get; set; } - ///// - ///// Расписание регламентной работы - В каком часовом поясе - ///// - //public string ScheduleTimezone { get; set; } = string.Empty; - - ///// - ///// Расписание регламентной работы - Тип исключения - ///// - //public string ScheduleExcludeType { get; set; } = string.Empty; - - ///// - ///// Расписание регламентной работы - Календарь - ///// - //public string ScheduleExcludeCalendar { get; set; } = string.Empty; - /// /// Расписание регламентной работы - Диапазон повторов /// @@ -126,7 +110,5 @@ namespace PARR.DAL.Contracts /// Зона ответственности для получения тайм зоны по умолчанию для расписаний /// public string DefaultResponseAreaToTimeOffset { get; set; } = string.Empty; - } - -} +} \ No newline at end of file diff --git a/PARR.DAL/DomainServices/UnitFilterService/UnitFilterService.cs b/PARR.DAL/DomainServices/UnitFilterService/UnitFilterService.cs index c4c369a2..1f456b0b 100644 --- a/PARR.DAL/DomainServices/UnitFilterService/UnitFilterService.cs +++ b/PARR.DAL/DomainServices/UnitFilterService/UnitFilterService.cs @@ -14,7 +14,7 @@ namespace PARR.DAL.DomainServices.UnitFilterService; internal class UnitFilterService : IUnitFilterService { #if DEBUG - private readonly Guid targetUnitId = Guid.Parse("87fc4c36-1ea8-4163-983f-1605fee1de99"); + private readonly Guid targetUnitId = Guid.Parse("ca9b4a9f-df74-48e1-95dc-588e36e3d5aa"); #endif private readonly ILogger logger; diff --git a/PARR.DAL/Services/Abstracts/BaseService.cs b/PARR.DAL/Services/Abstracts/BaseService.cs index 28142b37..0470d73b 100644 --- a/PARR.DAL/Services/Abstracts/BaseService.cs +++ b/PARR.DAL/Services/Abstracts/BaseService.cs @@ -25,29 +25,38 @@ namespace PARR.DAL.Services.Abstracts public virtual async Task AddRangeAsync(List objs) { + logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}", + typeof(T).Name, objs.Count); + objs.ForEach(item => item.DateCreated = DateTimeOffset.UtcNow); try { await EntitySet.AddRangeAsync(objs); + logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}", + objs.Count, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении диапазона в БД."); + logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name); return false; } } public async Task CommitAsync(IHistoryInitiator? initiator = null) { + logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name); + #region Изменения var modifiedEntrities = EntitiContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/); + logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count()); + foreach (var obj in modifiedEntrities) { - // Если нунжо, обновляем дату изменения + // Если нужно, обновляем дату изменения DateModifiedResolver(obj); //Если нужно, пишем историю @@ -59,12 +68,14 @@ namespace PARR.DAL.Services.Abstracts try { - await EntitiContext.SaveChangesAsync(); + var changedCount = await EntitiContext.SaveChangesAsync(); + logger.LogInformation("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}", + changedCount, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при Commit"); + logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name); return false; } } @@ -76,10 +87,15 @@ namespace PARR.DAL.Services.Abstracts /// private void SetInitiator(IHistoryInitiator? initiator) { + logger.LogDebug("Устанавливаю инициатора для изменений"); + // Задаем инициатора только для новых и измененных записей var entrities = EntitiContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified || t.State == EntityState.Added); + var entityCount = entrities.Count(); + logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount); + // смотрим есть ли у объекта интерфейс IHistoryInitiator, если есть, задаём значения foreach (var obj in entrities) { @@ -89,6 +105,8 @@ namespace PARR.DAL.Services.Abstracts (obj.Entity as IHistoryInitiator)!.InitiatorIp = initiator?.InitiatorIp ?? null; (obj.Entity as IHistoryInitiator)!.InitiatorParrComponentId = initiator?.InitiatorParrComponentId ?? null; (obj.Entity as IHistoryInitiator)!.InitiatorComment = initiator?.InitiatorComment ?? null; + + logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name); } } } @@ -100,11 +118,11 @@ namespace PARR.DAL.Services.Abstracts /// private void DateModifiedResolver(EntityEntry obj) { - //if (obj.Entity is IBase) - // (obj.Entity as IBase)!.DateModified = DateTimeOffset.UtcNow; - if (obj.Entity is IBaseDateModified) + { + logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name); (obj.Entity as IBaseDateModified)!.DateModified = DateTimeOffset.UtcNow; + } } /// @@ -113,6 +131,8 @@ namespace PARR.DAL.Services.Abstracts /// private void TableHistoryResolver(EntityEntry obj) { + logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name); + var myHistoryInterface = obj.Entity.GetType().GetInterfaces() .Where(t => t.IsGenericType) .Where(t => t.GetGenericTypeDefinition() == typeof(IMyHistory<>)) @@ -120,7 +140,10 @@ namespace PARR.DAL.Services.Abstracts // у этого объекта нет интерфейса IMyHistory<>. Не ведем историю if (myHistoryInterface == null) + { + logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name); return; + } // !!! Эта таблица хочет хранить историю !!! @@ -128,10 +151,13 @@ namespace PARR.DAL.Services.Abstracts var historyType = myHistoryInterface.GetGenericArguments().First(); var historyProps = historyType.GetProperties(/*BindingFlags.DeclaredOnly | */ /*BindingFlags.Public*/).ToList(); + logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}", + obj.Entity.GetType().Name, historyType.Name); + var historyInstance = Activator.CreateInstance(historyType); if (historyInstance == null) { - logger.LogError($"Не смог создать инстанс для ведения истории {historyType.Name}"); + logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name); return; } @@ -147,10 +173,11 @@ namespace PARR.DAL.Services.Abstracts try { EntitiContext.Add(historyInstance); + logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name); } catch (Exception ex) { - logger.LogError(ex, $"Ошибка при добавлении объекта в историю {historyType.Name}"); + logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name); } } @@ -163,6 +190,8 @@ namespace PARR.DAL.Services.Abstracts /// private void FillHistoryProps(EntityEntry originalObj, ref object historyInstance, List propsList) { + logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name); + foreach (var prop in propsList) { if (originalObj.OriginalValues.Properties.FirstOrDefault(t => t.Name == prop.Name) == null) @@ -185,6 +214,8 @@ namespace PARR.DAL.Services.Abstracts var origValues = originalObj.Property(prop.Name).OriginalValue; histProp.SetValue(historyInstance, origValues); } + + logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name); } @@ -200,14 +231,15 @@ namespace PARR.DAL.Services.Abstracts var histProp = instanceObj.GetType().GetProperty(propName); if (histProp == null) { - logger.LogError($"При изменении объекта для БД, не найдено свойство ${propName}"); + logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName); return; } // сравним типы if (histProp.PropertyType != typeof(TValue)) { - logger.LogError($"При изменении объекта для БД, не совпадают типы у свойства {propName}, {histProp.PropertyType.Name}!={typeof(TValue).Name}"); + logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}", + propName, histProp.PropertyType.Name, typeof(TValue).Name); return; } @@ -223,7 +255,7 @@ namespace PARR.DAL.Services.Abstracts // return; // var template = (Template)obj.Entity; - // // https://stackoverflow.com/questions/15012621/how-to-get-original-entity-from-changetracker + // // https://stackoverflow.com/questions/15012621/how-to-get-original-entity-from-changetracker // var history = new TemplateHistory(); // // { // history.Id = Guid.NewGuid(); @@ -273,74 +305,85 @@ namespace PARR.DAL.Services.Abstracts public virtual async Task CreateAsync(T obj) { + logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name); + obj.DateCreated = DateTimeOffset.UtcNow; try { await EntitySet.AddAsync(obj); + logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении в БД."); + logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name); return false; } } public virtual bool Delete(T obj) { + logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name); + try { EntitySet.Remove(obj); + logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении из БД."); + logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name); return false; } } public virtual async Task DeleteAsync(Guid id) { + logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id); + try { var exist = await GetAsync(id); if (exist == null) { - logger.LogError($"Ошибка при удалении из БД. Не найдена запись в БД с id: {id}"); + logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}", + typeof(T).Name, id); return false; } EntitySet.Remove(exist); + logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении из БД."); + logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id); return false; } } public virtual IQueryable Get() { + logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name); return EntitySet; } public virtual async Task GetAsync(Guid id) { + logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id); return await EntitySet.FirstOrDefaultAsync(t => t.Id == id); } public virtual IQueryable GetPage(IQueryable query, PaginationFilter paginationFilter) { - //if (paginationFilter.IsDisabledPagination) - // return query; - + logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}", + typeof(T).Name, paginationFilter.PageNumber, paginationFilter.PageSize); int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize; return query.Skip(skip).Take(paginationFilter.PageSize); } } -} +} \ No newline at end of file diff --git a/PARR.DAL/Services/Implementations/OrderService.cs b/PARR.DAL/Services/Implementations/OrderService.cs index 5124f436..45a17e80 100644 --- a/PARR.DAL/Services/Implementations/OrderService.cs +++ b/PARR.DAL/Services/Implementations/OrderService.cs @@ -20,4 +20,4 @@ namespace PARR.DAL.Services.Implementations protected override DataContext EntitiContext => dataContext; } -} +} \ No newline at end of file diff --git a/PARR.DAL/Services/Implementations/OrderStatusService.cs b/PARR.DAL/Services/Implementations/OrderStatusService.cs index 9368e415..38cb568c 100644 --- a/PARR.DAL/Services/Implementations/OrderStatusService.cs +++ b/PARR.DAL/Services/Implementations/OrderStatusService.cs @@ -19,28 +19,38 @@ namespace PARR.DAL.Services.Implementations public IQueryable Get() { + logger.LogDebug("Получаю набор статусов нарядов"); return dataContext.OrderStatuses; } public OrderStatusEnum GetOrderStatusByName(string statusName) { + logger.LogDebug("Поиск статуса наряда по имени: {StatusName}", statusName); + switch (statusName.Trim()) { case "1-Направлен в группу": + logger.LogDebug("Найден статус: {StatusEnum}", OrderStatusEnum.New); return OrderStatusEnum.New; case "2-В работе": + logger.LogDebug("Найден статус: {StatusEnum}", OrderStatusEnum.InWork); return OrderStatusEnum.InWork; case "3-Приостановлен": + logger.LogDebug("Найден статус: {StatusEnum}", OrderStatusEnum.Stop); return OrderStatusEnum.Stop; case "4-Выполнен": + logger.LogDebug("Найден статус: {StatusEnum}", OrderStatusEnum.Complete); return OrderStatusEnum.Complete; case "5-Закрыт": + logger.LogDebug("Найден статус: {StatusEnum}", OrderStatusEnum.Closed); return OrderStatusEnum.Closed; default: - logger.LogWarning($"Не смог распознать статус наряда, вернул значение по умполчанию: {OrderStatusEnum.New.ToString()}, входящее значение: {statusName}"); + logger.LogWarning( + "Не смог распознать статус наряда, вернул значение по умолчанию: {DefaultStatus}, входящее значение: {InputStatus}", + OrderStatusEnum.New, statusName); return OrderStatusEnum.New; } } } -} +} \ No newline at end of file diff --git a/PARR.DAL/Services/Implementations/TemplateService.cs b/PARR.DAL/Services/Implementations/TemplateService.cs index afcad1d1..0a54e1ff 100644 --- a/PARR.DAL/Services/Implementations/TemplateService.cs +++ b/PARR.DAL/Services/Implementations/TemplateService.cs @@ -28,13 +28,28 @@ namespace PARR.DAL.Services.Implementations public async Task GetTemplateByNameAsync(string name) { - return await GetWithIncludes() + logger.LogDebug("Поиск шаблона по имени: {TemplateName}", name); + + var template = await GetWithIncludes() .Include(t => t.RobotConfigurations) .FirstOrDefaultAsync(t => t.Name == name); + + if (template != null) + { + logger.LogDebug("Шаблон найден: {TemplateId}, имя: {TemplateName}", template.Id, template.Name); + } + else + { + logger.LogDebug("Шаблон с именем {TemplateName} не найден", name); + } + + return template; } public IQueryable