Модификация решения «DIRECTUM Bot» для DirectumRX. Выполнение простых заданий через чат-бот Viber

21 0

Почему важно обратить внимание на мессенджеры?

Доля мобильных устройств в структуре интернет трафика продолжает расти. Согласно отчету аналитического агентства We Are Social (начало 2019) на мобильные устройства уже приходится почти половина всего времени, которые люди проводят в сети Интернет.

Интернет пользователи в России в среднем тратят около 2,5 часов на просмотр социальных сетей.

Проникновение социальных сетей на территории восточной Европы составляет 48%, т.е. каждый второй гражданин имеет аккаунт в той или иной социальной сети.

Наибольшая активность наблюдается преимущественно в мессенджерах и социальных сетях, которые также имеют удобные мобильные клиенты для общения.

Не трудно заметить, что эти социальные сети и мессенджеры имеют встроенные механизмы для чат-ботов. Параллельно с этим продажи товаров и услуг через интернет растут в среднем на 14% в год.

Чтобы удовлетворить растущий спрос развиваются новые бизнес-инструменты. В настоящее время в тренде чат-боты для различных бизнес-задач.

Не так давно был анонсирован выпуск технического решения «DIRECTUM Bot» для DirectumRX. Данное техническое решение позволяет реализовать чат-бота, интегрированного с системой DirectumRX, практически для любых задач.

В данной статье рассмотрим один из востребованных бизнес-кейсов - выполнение заданий вне системы.

Данный кейс востребован в организациях, в которых:

  • существует большой штат сотрудников, которым нет необходимости иметь доступ к системе, однако необходимо выполнять некоторые задачи (ознакомление с документами, выполнение мелких поручений), что позволяет существенно сэкономить на количестве лицензий;
  • ведётся работа с внешними сотрудниками, или партнёрами, которые не могут иметь доступ к системе в принципе, но которые участвуют в бизнес-процессах;
  • руководящий состав использует определенные средства коммуникации (почтовый клиент, конкретный мессенджер).

Данное решение не подойдёт для заданий, которые требуют каких-либо сложных действий от пользователя:

  • сканирование документов и занесение их в систему;
  • регистрация документов и подписание;
  • операции, где требуется выполнить определенные настроечные операции (выдача прав, изменение регламента, продление срока согласования и пр.)

Можно вложить много усилий в создание сложного чат-бота, который сможет делать всё, но это приведет к существенному удорожанию его разработки и необходимости регулярной поддержки, при каждом обновлении системы.

Поэтому на чат-бот можно выносить выполнение простых задач, которые требуют от пользователя выполнение элементарных действий.

Реализация чат-бота для выполнения простых заданий и заданий на ознакомления

Перед разработкой примем ряд постулатов, которым будем следовать:

  1. Минимальное количество перекрытий. Зачастую перекрытия приводят к удорожанию последующей поддержки и могут нарушать логику других «кастомных» разработок, в которых используются те же перекрытия.
  2. В качестве мессенджера будет использоваться Viber.
  3. Отправка сообщений в с сервис чат-бота будет выполняться только в рамках фонового процесса.
  4. В случае какого-либо сбоя, сообщение должно быть отправлено повторно.
  5. Выполненные и просмотренные задания отправлять в чат-бот не требуется.

Для реализации пункта 1 и 4 необходимо создать специальный справочник, который будет содержать информацию о задании, процессе чат-бота (работа в рамках одного задания) и состояние отправки.

 

В справочнике ChatBotProcessAssignment будут храниться данные для каждого процесса чат-бота и идентификаторы заданий. Актуальность записи будет определяться свойством Status (Active – в работе, Closed – строка обработана).

Для привязки процесса чат-бота к заданию умышленно используется целое число, содержащее идентификатор задания, а не свойство-ссылка, поскольку нельзя сделать ссылку на простое платформенное задание, только на потомков задания в прикладном коде.

Отправка сообщения через чат-бот пользователю Viber

Создадим фоновый процесс, который будет собирать информацию о новых стартованных заданиях и выполнять отправку сообщений пользователю чат-бота. В случае наличия неотправленных сообщений, они должны быть поставлены в очередь на отправку вновь.

    /// <summary>
    /// Отправка уведомления о заданиях.
    /// </summary>
    public virtual void SendChatBotNotification()
    {
      Functions.Module.SendNotification();
    }

    /// <summary>
    /// Отправить уведомления о заданиях.
    /// </summary>
    [Remote, Public]
    public virtual void SendNotification()
    {
      var previousRun = GetLastNotificationDate();
      var notificationDate = Calendar.Now;
      try
      {
        TrySendNewAssignmentsToChatBot(previousRun, notificationDate);
      }
      finally
      {
        UpdateLastNotificationDate(notificationDate);
      }
    }

 

Для фиксации даты последней отправки будем использовать специальную запись в таблице Sungero_Docflow_Params.

Задания будут отправляться только тем сотрудникам, которые указаны в справочнике ChatBotUsers из модуля ChatBotInfrastructure. Для удобства ссылку на справочник можно вынести на обложку модуля Компания.

Кейсов использования чат-ботов довольно много и не всем пользователям, указанным в списке «Пользователи чат-бота», может быть необходимо отправлять сообщения в чат-боту, поэтому добавим признак, разрешающий пользователю выполнять задание через чат-бота. Вынесем этот признак на карточку сотрудника:

Зададим фильтр типов заданий, которые будет можно выполнять через чат-бот:

    /// <summary>
    /// Проверить тип задания.
    /// </summary>
    /// <param name="typeName">Наименование типа задания.</param>
    /// <returns>Возвращает true, если задание: простое, на ознакомление.
    /// Иначе - false. </returns>
    public virtual bool CheckAssignmentType(string typeName)
    {
      return (typeName == Constants.Module.SimpleAssignmentName) ||
        (typeName == Constants.Module.AcquaintanceAssignmentName);
    }

Текст задания, передаваемый в чат-бота, наименование кнопок и прочие метаданные будут храниться в структурах:

  /// <summary>
  /// Структура с результатом выполнения задания и сопроводительным текстом.
  /// </summary>
  partial class AllowResultData
  {
    /// <summary>
    /// Результат выполнения задания.
    /// </summary>
    public string Result { get; set; }
    
    /// <summary>
    /// Результат выполнения задания.
    /// </summary>
    public string ResultAssignmentText { get; set; }
  }
  
  /// <summary>
  /// Структура для хранения типа задания, выполнимого через чат-бота.
  /// </summary>
  partial class ChatBotProcessAssignmentType
  {
    /// <summary>
    /// Тип задания.
    /// </summary>
    public string AssignmentType { get; set; }
    
    /// <summary>
    /// Текст сообщения.
    /// </summary>
    public string IncomingInstructionText { get; set; }
    
    /// <summary>
    /// Результаты выполнения задания с учетом порядка раположения кнопок выполнения данного задания.
    /// </summary>
    public List<DirRX.ChatBotTask.Structures.Module.AllowResultData> AllowResults { get; set; }
  }
  
  /// <summary>
  /// Структура с результатами выполнения задания (код и локализация).
  /// </summary>
  partial class AllowResultCodes
  {
    /// <summary>
    /// Результат выполнения задания.
    /// </summary>
    public string ResultCode { get; set; }
    
    /// <summary>
    /// Локализованный результат выполнения задания.
    /// </summary>
    public string ResultLocale { get; set; }
  }

А структуры, в свою очередь, будем заполнять следующей функцией:

    /// <summary>
    /// Получить тип и текстовые данные для выполняемого задания.
    /// </summary>
    /// <param name="assignment">Задание.</param>
    /// <returns>Правило выполнения задания.</returns>
    public virtual Structures.Module.ChatBotProcessAssignmentType GetChatBotProcessAssignmentType(Sungero.Workflow.IAssignmentBase assignment)
    {
      var typeName = assignment.Info.Name;
      var assignmentText = Resources.AssignmentTextFormat(assignment.Subject, assignment.Author.Name, assignment.Deadline.HasValue ? assignment.Deadline.Value.ToString() : " - ", assignment.Task.ActiveText);
      // Простое задание.
      if (typeName == Constants.Module.SimpleAssignmentName)
      {
        var resultItems = new List<Structures.Module.AllowResultData>() {
          Structures.Module.AllowResultData.Create("Complete", Resources.SimpleAssignmentCompleteText) };
        return Structures.Module.ChatBotProcessAssignmentType.Create(Constants.Module.SimpleAssignmentName, assignmentText, resultItems);
      }
      
      // Задание на ознакомление с документом.
      if (typeName == Constants.Module.AcquaintanceAssignmentName)
      {
        var resultItems = new List<Structures.Module.AllowResultData>() {
          Structures.Module.AllowResultData.Create(Sungero.RecordManagement.AcquaintanceAssignment.Result.Acquainted.Value, Resources.AcquaintanceAssignmentAcquaintanedText) };
        return Structures.Module.ChatBotProcessAssignmentType.Create(Constants.Module.AcquaintanceAssignmentName, assignmentText, resultItems);
      }
    }

Перед выполнением рассылки по новым заданиям получим их список. В список попадут как новые, так и задания из записей справочника ChatBotProcessAssignment, помеченные как Active:

    /// <summary>
    /// Запустить рассылку по новым заданиям.
    /// </summary>
    /// <param name="previousRun">Дата прошлого запуска рассылки.</param>
    /// <param name="notificationDate">Дата текущей рассылки.</param>
    [Public]
    public virtual void TrySendNewAssignmentsToChatBot(DateTime previousRun, DateTime notificationDate)
    {
      // Получаем новые задания.
      var newAssignments = Sungero.Workflow.Assignments
        .GetAll(a => previousRun <= a.Created && a.Created < notificationDate && a.IsRead == false && a.Status != Sungero.Workflow.AssignmentBase.Status.Aborted)
        .ToList();
      
      // Получаем задания, по которым отправка еще не завершена.
      foreach (var chatBotProcess in DirRX.ChatBotTask.ChatBotProcessAssignments.GetAll(x => x.Status != DirRX.ChatBotTask.ChatBotProcessAssignment.Status.Closed))
      {
        var assignmentsNeedRepeat = Sungero.Workflow.Assignments.GetAll(x => x.Id == chatBotProcess.AssignmentId && x.Status == Sungero.Workflow.Assignment.Status.InProcess && x.IsRead == false).FirstOrDefault();
        if (assignmentsNeedRepeat != null)
          newAssignments.Add(assignmentsNeedRepeat);
      }
      
      foreach (var assignment in newAssignments)
      {
        var employee = DirRX.Solution.Employees.As(assignment.Performer);
        if (employee == null)
          continue;
        if (employee.IsAllowExecuteThroughChatBot != true)
          continue;
        TrySendAssignmentMessage(assignment);
      }
      if (!newAssignments.Any())
        Logger.Debug("No new assignments for sending");
    }

Реализуем вызов функции отправки сообщений:

/// <summary>
    /// Отправка сообщения пользователю чат-бота о новом задании.
    /// </summary>
    /// <param name="assignment">Задание.</param>
    [Public]
    public virtual void TrySendAssignmentMessage(Sungero.Workflow.IAssignment assignment)
    {
      var chatBotUser = DirRX.ChatBotInfrastructure.ChatBotUsers.Null;
      var employee = Sungero.Company.Employees.As(assignment.Performer);
      if (employee != null)
        chatBotUser = DirRX.ChatBotInfrastructure.ChatBotUsers.GetAll(u => u.Person != null && Equals(u.Person, employee.Person)).FirstOrDefault();
      
      if (this.CheckAssignmentType(assignment.Info.Name) && chatBotUser != null)
      {
        SendAssignmentMessage(assignment, chatBotUser);
      }
    }
    
    /// <summary>
    /// Отправка сообщения пользователю чат-бота о задании.
    /// </summary>
    /// <param name="assignment">Задание.</param>
    [Public]
    public virtual void SendAssignmentMessage(Sungero.Workflow.IAssignment assignment, DirRX.ChatBotInfrastructure.IChatBotUser chatBotUser)
    {
      /// Получаем вложения из задания.
      var documents = GetAttachements(assignment);
      var processAssignmentType = GetChatBotProcessAssignmentType(assignment);
      /// Формируем текстовку задания.
      var message = processAssignmentType.IncomingInstructionText;
      var actions = GetResultVariants(assignment);
      /// Связываем ИД задания и процесс чат-бота.
      var chatBotProcessAssignment = DirRX.ChatBotTask.ChatBotProcessAssignments.Create();
      chatBotProcessAssignment.AssignmentId = assignment.Id;
      chatBotProcessAssignment.ChatBotProcess = DirRX.ChatBotInfrastructure.PublicFunctions.Module.StartProcess(processAssignmentType.AssignmentType, chatBotUser);
      chatBotProcessAssignment.Save();
      Logger.DebugFormat("Send assignment: {0}", assignment.Id);
      bool isError = false;
      try
      {
        /// Вызываем метод отправки сообщения в чат-бот.
        DirRX.ChatBotInfrastructure.PublicFunctions.Module.SendAttachmentsMessage(
          message, new List<DirRX.ChatBotInfrastructure.IChatBotUser> {chatBotUser}, chatBotProcessAssignment.ChatBotProcess, processAssignmentType.AssignmentType,
          !string.IsNullOrEmpty(actions[0].ResultLocale) ? DirRX.ChatBotInfrastructure.PublicFunctions.Module.CreateSimpleAction(actions[0].ResultLocale) : string.Empty,
          !string.IsNullOrEmpty(actions[1].ResultLocale) ? DirRX.ChatBotInfrastructure.PublicFunctions.Module.CreateSimpleAction(actions[1].ResultLocale) : string.Empty,
          !string.IsNullOrEmpty(actions[2].ResultLocale) ? DirRX.ChatBotInfrastructure.PublicFunctions.Module.CreateSimpleAction(actions[2].ResultLocale) : string.Empty,
          !string.IsNullOrEmpty(actions[3].ResultLocale) ? DirRX.ChatBotInfrastructure.PublicFunctions.Module.CreateSimpleAction(actions[3].ResultLocale) : string.Empty,
          !string.IsNullOrEmpty(actions[4].ResultLocale) ? DirRX.ChatBotInfrastructure.PublicFunctions.Module.CreateSimpleAction(actions[4].ResultLocale) : string.Empty,
          documents[0], documents[1], documents[2], documents[3], documents[3]
         );
      }
      catch (Exception ex)
      {
        Logger.ErrorFormat("Message sending failed. Error: {0}", ex.Message);
        isError = true;
      }
      if (!isError)
      {
        chatBotProcessAssignment.Status = DirRX.ChatBotTask.ChatBotProcessAssignment.Status.Closed;
        chatBotProcessAssignment.Save();
      }
    }

Сопутствующие методы в статью выносить не будем. Вы сможете с ними ознакомиться из исходных кодов, прикрепленных к статье.

Получение сообщений из Viber и обработка на стороне DirectumRX

Для того, чтобы начать получать данные из Viber с использованием WebApi из комплекта поставки технического решения и перенаправлять ответы в собственный обработчик необходимо перекрыть модуль ChatBotInfrastructurePI и серверную функцию ProcessJob:

    /// <summary>
    /// Обработка джоба.
    /// </summary>
    /// <param name="chatJob">Джоб.</param>
    [Public]
    public override void ProcessJob(DirRX.ChatBotInfrastructure.IChatJob chatJob)
    {
      base.ProcessJob(chatJob);
      DirRX.ChatBotTask.PublicFunctions.Module.ReceiveJob(chatJob);
    }

При получении сообщения из Viber сервис WebApi будет передавать полученные данные в функцию ProcessJob и в свою очередь в наш обработчик:

    /// <summary>
    /// Обработка сообщения.
    /// </summary>
    /// <param name="chatJob">Джоб.</param>
    [Public]
    public virtual void ReceiveJob(DirRX.ChatBotInfrastructure.IChatJob chatJob)
    {
      /// Сразу проверим, можем ли мы обрабатывать такие задаения. Тип задания будет содержаться в свойстве ProcessStage.
      if (CheckAssignmentType(chatJob.ProcessStage))
      {
        ProcessAssignment(chatJob);
      }
    }

Выполнение задания будет выполняться за счет следующих функций:

    /// <summary>
    /// Выполнение задания.
    /// </summary>
    /// <param name="chatJob">Сообщение от чат-бота.</param>
    public virtual void ProcessAssignment(DirRX.ChatBotInfrastructure.IChatJob chatJob)
    {
      // Получаем запись справочника ChatBotProcessAssignment связанную с текущим процессом чат-бота.
      var chatBotProcessAssignment = DirRX.ChatBotTask.ChatBotProcessAssignments.GetAll().Where(x => Equals(x.ChatBotProcess, chatJob.Process)).FirstOrDefault();
      if (chatBotProcessAssignment == null)
        return;
      /// Получаем задание по процессу чат-бота. Одновременно проверяем, не выполнено/просмотрено ли задание.
      var assignment = Sungero.Workflow.Assignments.GetAll().Where(x => x.Id == chatBotProcessAssignment.AssignmentId && x.Status == Sungero.Workflow.Assignment.Status.InProcess).FirstOrDefault();
      if (assignment != null)
      {
        /// Определяем вариант выполнения. Локализованный вариант выполнения содержится в chatJob.Action.  
        var processAssignmentType = GetChatBotProcessAssignmentType(assignment);
        var resultVariant = GetResultVariants(assignment).Where(x => x.ResultLocale == chatJob.Action).FirstOrDefault();
        if (resultVariant != null)
        {
          var assignmentResult = resultVariant.ResultCode;
          var res = processAssignmentType.AllowResults.Where(x => x.Result == assignmentResult).FirstOrDefault();
          var activeText = res != null ? res.ResultAssignmentText : string.Empty;
          ExecuteAssigment(assignment, assignmentResult, activeText);
        }
      }
      DirRX.ChatBotTask.ChatBotProcessAssignments.Delete(chatBotProcessAssignment);
    }
       
    /// <summary>
    /// Выполнить задание.
    /// </summary>
    /// <param name="assignment">Задание.</param>
    /// <param name="resultComplete">Результат выполнения задания.</param>
    /// <param name="activeText">Текст задания.</param>
    private void ExecuteAssigment(Sungero.Workflow.IAssignment assignment, string resultComplete, string activeText)
    {
      assignment.ActiveText = activeText;
      // Выполнение простого задания. Т.к. он платформенный, то выполняется таким образом.
      if (Sungero.Workflow.SimpleAssignments.Is(assignment) && resultComplete == "Complete")
      {
        assignment.Complete(null);
      }
      else
      {
        if (this.CheckAssignmentType(assignment.Info.Name))
          assignment.Complete(new Enumeration(resultComplete));
      }
    }

Логика отправки сообщения в чат-бот Viber и получения ответа пользователя реализована. Приступаем к тестированию разработки.

Результаты работы реализованного механизма

Тестовый стенд имел следующую схему:

  1. На одной виртуальной машине был развернут тестовый стенд DirectumRX и сервис WebApi (сервис ChatBotProcessMessage) из комплекта поставки технического решения.
  2. На стороне Viber был создан публичный аккаунт.
  3. Сервисы были сконфигурированы в соответствии с инструкцией по техническому решению.

Тестирование выполнения задания на ознакомление с документом

На стороне Viber:

На стороне DirectumRX:

Тестирование выполнения простого задания

На стороне Viber:

На стороне DirectumRX:

Заключение

В данной статье был рассмотрен вариант доработки существующего решения «DIRECTUM Bot», который решает бизнес-кейс «выполнение простых заданий через мессенджер Viber». Данный вариант реализации предусматривает расширение функциональности путем добавления новых заданий, которые сможет рассылать чат-бот пользователям, однако необходимо иметь ввиду, не каждое задание можно выполнить с использованием простого мессенджера. Используйте его только для простых заданий. Для работы с более сложными процессами с мобильных устройств лучше использовать специализированные мобильные приложения.

С исходными кодами можно ознакомиться по ссылке: https://github.com/DirectumCompany/DirectumBotCompleteAssignments

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий