ModuleAsyncHandlers.cs 11.4 KB
using System;
using System.Collections.Generic;
using System.Linq;
using Sungero.Core;
using Sungero.CoreEntities;
using Directum.Dci.Sdk;
using Directum.Dci.Common;
using Directum.Dci.Entities;

namespace DirRX.DciCore.Server
{
  public class ModuleAsyncHandlers
  {

    /// <summary>
    /// Отправка сообщений в DCI с изменениями с момента последней синхронизации (в разрезе типа сущности).
    /// Запуск АО инициирует ФП.
    /// </summary>
    /// <param name="args">Параметры вызова асинхронного обработчика.</param>
    public virtual void SendProcessMessage(DirRX.DciCore.Server.AsyncHandlerInvokeArgs.SendProcessMessageInvokeArgs args)
    {
      var handlerName = "SendProcessMessage";   // Имя АО, для логов.
      var idProcessKind = args.IdProcessKind;
      var processName = args.NameProcessKind;
      var startSyncDate = Calendar.Now;
      Logger.Debug(string.Format("{0}. {1} (ИД={2}). Начало отправки изменений во внешнюю систему.", handlerName, processName, idProcessKind));
      
      var errors = new List<string>();
      
      try
      {
        var processKind = DciProcessKinds.Get(idProcessKind);
        var entityTypeGuid = processKind.EntityTypeGUID;
        var lastSyncDate = processKind.LastOutSyncDate.Value;
        processName = processKind.Name;

        var setting = Functions.DciSetting.GetDciSettings();
        if (setting == null)
          throw AppliedCodeException.Create(DirRX.DciCore.Resources.DciSettingsNotFound);
        var processFactory = Functions.Module.GetProcessFactory(setting);

        // TODO нужна адаптация кода под поиск изменения документов и сквозных процессов.
        var changedDatabookRecordIds = Functions.Module.GetChangedDatabookRecords(entityTypeGuid, lastSyncDate, startSyncDate);
        
        Logger.Debug(string.Format("{0}. {1} (ИД={2}). Изменений найдено: {3}", handlerName, processName, idProcessKind, changedDatabookRecordIds.Count()));
        
        foreach (var entityId in changedDatabookRecordIds)
        {
          try
          {
            Logger.Debug(string.Format("{0}. {1} (ИД={2}). Обработка записи с ИД {3}.", handlerName, processName, idProcessKind, entityId));
            var entity = Functions.Module.GetDatabookEntityById(entityId, entityTypeGuid);
            var messageName = Functions.Module.GetDCIMessageNameByEntity(entity);
            
            // TODO вынести проверку из цикла
            // Выполнить проверку существования настройки для процесса и сообщения.
            var checkResult = Functions.DciProcessKind.CheckProcessKindExists(processName, messageName);
            if (!string.IsNullOrWhiteSpace(checkResult))
              throw AppliedCodeException.Create(checkResult);
            
            var dciMessage = Functions.Module.CreateMessage(processName, messageName, string.Empty, entityId, entityTypeGuid, processFactory, setting, true);
            // TODO Есть небольшое дублирование вычисления, глобальный ИД уже хранится в process.Params "RecordGUID", но что быстрее - получить из extLinks или из параметров?
            var externalSystemID = dciMessage.Receiver;
            var entityLink = Functions.Module.GetExternalEntityLink(entityId, entityTypeGuid, externalSystemID);
            var globalEntityId = entityLink.ExtEntityId.ToString();
            // TODO не создавать ссылку если ее еще нет и происходит удаление записи?
            // if (messageName = Constants.DciProcessKind.DefaultDciMessageName.RecordDeletion && entityLink.IsDeleted != true)
            // {
            //   entityLink.IsDeleted = true;
            //   entityLink.Save();
            // }
            
            dciMessage = Functions.Module.PrepareDCIMessageAttach(dciMessage, entity, globalEntityId);
            
            // TODO Извлечь из вложения ведущее сообщение. Добавить зависимость от ведущего.
            
            //Отправка сообщения DCI.
            try
            {
              dciMessage.Send();
            }
            catch (Exception ex)
            {
              errors.Add(string.Format("Ошибка при отправке сообщения DCI. {0}\n{1}", ex.Message, ex.StackTrace));
            }
          }
          catch (Exception ex)
          {
            errors.Add(string.Format("{0}\n{1}", ex.Message, ex.StackTrace));
          }
        }
        
        if (!errors.Any())
        {
          processKind.LastOutSyncDate = startSyncDate;
          processKind.Save();
        }

      }
      catch (Exception ex)
      {
        errors.Add(string.Format("{0}\n{1}", ex.Message, ex.StackTrace));
      }
      
      if (errors.Any())
      {
        try
        {
          Logger.Error(string.Format("{0}. {1} (ИД={2}). При отправке изменений во внешнюю систему произошли ошибки: {3}", handlerName, processName, idProcessKind, string.Join(Environment.NewLine, errors)));
          // TODO возможен спам задачами.
          // TODO сделать новую функцию SendNotificatiobAboutSendErrors на основе SendNotificatiobAboutReceiveErrors
          // Functions.Module.SendNotificatiobAboutReceiveErrors(errors, processName, processGUID, messageName, messageGUID);
        }
        catch (Exception ex)
        {
          Logger.Error(string.Format("{0}. Ошибка при отправке уведомления об ошибках.", handlerName), ex);
        }
      }
      
      Logger.Debug(string.Format("{0}. {1} (ИД={2}). Конец отправки изменений во внешнюю систему.", handlerName, processName, idProcessKind));
    }

    /// <summary>
    /// Прием сообщения из DCI.
    /// Запуск АО инициирует плагин DCI.
    /// </summary>
    /// <param name="args">Параметры вызова асинхронного обработчика.</param>
    public virtual void ReceiveProcessMessage(DirRX.DciCore.Server.AsyncHandlerInvokeArgs.ReceiveProcessMessageInvokeArgs args)
    {
      var handlerName = "ReceiveProcessMessage";   // Имя АО, для логов.
      var messageGUID = args.MessageGUID;
      var messageName = string.Empty;
      var processGUID = string.Empty;
      var processName = string.Empty;
      var messageFolderPath = string.Empty;
      Logger.Debug(string.Format("{0}. {1}. Начало обработки сообщения.", handlerName, messageGUID));
      
      var errors = new List<string>();
      
      try
      {
        var setting = Functions.DciSetting.GetDciSettings();
        if (setting == null)
          throw AppliedCodeException.Create(DirRX.DciCore.Resources.DciSettingsNotFound);
        var processFactory = Functions.Module.GetProcessFactory(setting);
        var message = processFactory.GetProcessMessageByGlobalId(messageGUID);
        if (message == null)
          throw AppliedCodeException.Create(DirRX.DciCore.Resources.NotFoundMessageByGlobalIdFormat(messageGUID));
        var process = processFactory.GetProcessByMessage(message);
        if (process == null)
          throw AppliedCodeException.Create(DirRX.DciCore.Resources.NotFoundProcessByMessageFormat(messageGUID));
        
        messageName = message.Name;
        processGUID = process.GlobalId;
        processName = process.ProcessKindName;
        messageFolderPath = message.FolderName;
        
        var messageError = string.Empty;
        try
        {
          // Выполнить проверку существования настройки для процесса и сообщения.
          var checkResult = Functions.DciProcessKind.CheckProcessKindExists(processName, messageName);
          if (!string.IsNullOrWhiteSpace(checkResult))
            throw AppliedCodeException.Create(checkResult);
          
          Logger.Debug(string.Format("{0}. Процесс: {1} ({2}). Сообщение: {3} ({4}). Подбор обработчика для сообщения.",
                                     handlerName, processGUID, processName, messageGUID, messageName));
          
          // Найти и выполнить обработчик для сообщения.
          messageError = Functions.Module.ProcessDCIMessage(message);
        }
        catch (Exception ex)
        {
          messageError = DirRX.DciCore.Resources.ReceiveHandlerErrorFormat(ex.Message, ex.StackTrace);
        }
        
        var stopProcess = false;
        if (string.IsNullOrEmpty(messageError))
        {
          message.MarkAsReceived();
          stopProcess = true;
        }
        else
        {
          // При блокировке записи, отложить обработку сообщения.
          if (messageError == Constants.Module.ErrorType.EntityIsLocked)
          {
            message.MessageState = MessageState.Pending;
            message.PostponeProcessing(60);  // TODO в секундах. можно вынести в настройки
            // TODO добавить ограничение по количеству попыток обработки
          }
          else
          {
            message.MarkAsErroneous(messageError);
            
            var messageLink = string.Empty;
            if (setting != null)
              messageLink = Functions.Module.GetHyperlinkToDciProcess(setting, processGUID);
            var errorText = DirRX.DciCore.Resources.ReceiveErrorDetailTextFormat(processName, processGUID, messageName, messageGUID, messageFolderPath, messageLink, messageError);
            errors.Add(errorText);
          }
        }
        message.Save();
        
        // Остановить процесс по синхронизации данных.
        if (stopProcess)
          Functions.Module.StopSynchronizationProcess(process);
      }
      catch (Exception ex)
      {
        errors.Add(string.Format("{0}\n{1}", ex.Message, ex.StackTrace));
      }
      
      if (errors.Any())
      {
        try
        {
          Logger.Error(string.Format("{0}. {1}. При обработке входящего сообщения DCI произошла ошибка. {2}", handlerName, messageGUID, string.Join(Environment.NewLine, errors)));
          // TODO возможен спам задачами. Отправку уведомления в системе можно заменить на ФП по периодической проверке сообщений в состоянии Ошибка.
          Functions.Module.SendNotificatiobAboutReceiveErrors(errors, processName, processGUID, messageName, messageGUID);
        }
        catch (Exception ex)
        {
          Logger.Error(string.Format("{0}. Ошибка при отправке уведомления об ошибках.", handlerName), ex);
        }
      }
      
      Logger.Debug(string.Format("{0}. {1}. Конец обработки сообщения.", handlerName, messageGUID));
    }

  }
}