Доля мобильных устройств в структуре интернет трафика продолжает расти. Согласно отчету аналитического агентства We Are Social (начало 2019) на мобильные устройства уже приходится почти половина всего времени, которые люди проводят в сети Интернет.
Интернет пользователи в России в среднем тратят около 2,5 часов на просмотр социальных сетей.
Проникновение социальных сетей на территории восточной Европы составляет 48%, т.е. каждый второй гражданин имеет аккаунт в той или иной социальной сети.
Наибольшая активность наблюдается преимущественно в мессенджерах и социальных сетях, которые также имеют удобные мобильные клиенты для общения.
Не трудно заметить, что эти социальные сети и мессенджеры имеют встроенные механизмы для чат-ботов. Параллельно с этим продажи товаров и услуг через интернет растут в среднем на 14% в год.
Чтобы удовлетворить растущий спрос развиваются новые бизнес-инструменты. В настоящее время в тренде чат-боты для различных бизнес-задач.
Не так давно был анонсирован выпуск технического решения «DIRECTUM Bot» для DirectumRX. Данное техническое решение позволяет реализовать чат-бота, интегрированного с системой DirectumRX, практически для любых задач.
В данной статье рассмотрим один из востребованных бизнес-кейсов - выполнение заданий вне системы.
Данный кейс востребован в организациях, в которых:
Данное решение не подойдёт для заданий, которые требуют каких-либо сложных действий от пользователя:
Можно вложить много усилий в создание сложного чат-бота, который сможет делать всё, но это приведет к существенному удорожанию его разработки и необходимости регулярной поддержки, при каждом обновлении системы.
Поэтому на чат-бот можно выносить выполнение простых задач, которые требуют от пользователя выполнение элементарных действий.
Перед разработкой примем ряд постулатов, которым будем следовать:
Для реализации пункта 1 и 4 необходимо создать специальный справочник, который будет содержать информацию о задании, процессе чат-бота (работа в рамках одного задания) и состояние отправки.
В справочнике ChatBotProcessAssignment будут храниться данные для каждого процесса чат-бота и идентификаторы заданий. Актуальность записи будет определяться свойством Status (Active – в работе, Closed – строка обработана).
Для привязки процесса чат-бота к заданию умышленно используется целое число, содержащее идентификатор задания, а не свойство-ссылка, поскольку нельзя сделать ссылку на простое платформенное задание, только на потомков задания в прикладном коде.
Создадим фоновый процесс, который будет собирать информацию о новых стартованных заданиях и выполнять отправку сообщений пользователю чат-бота. В случае наличия неотправленных сообщений, они должны быть поставлены в очередь на отправку вновь.
/// <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 с использованием 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 и получения ответа пользователя реализована. Приступаем к тестированию разработки.
Тестовый стенд имел следующую схему:
На стороне Viber:
На стороне DirectumRX:
На стороне Viber:
На стороне DirectumRX:
В данной статье был рассмотрен вариант доработки существующего решения «DIRECTUM Bot», который решает бизнес-кейс «выполнение простых заданий через мессенджер Viber». Данный вариант реализации предусматривает расширение функциональности путем добавления новых заданий, которые сможет рассылать чат-бот пользователям, однако необходимо иметь ввиду, не каждое задание можно выполнить с использованием простого мессенджера. Используйте его только для простых заданий. Для работы с более сложными процессами с мобильных устройств лучше использовать специализированные мобильные приложения.
С исходными кодами можно ознакомиться по ссылке: https://github.com/DirectumCompany/DirectumBotCompleteAssignments
Авторизуйтесь, чтобы написать комментарий