• Ru

UniBPM Connector

Назначение

unibpm-external-task-unibpm — это коннектор UniBPM для выполнения внутренних действий платформы через механизм External Task.

Коннектор предназначен для использования в unibpm-integration-runtime и позволяет процессам вызывать бизнес-функции UniBPM без прямого обращения к REST API из BPMN-модели. Процессная модель оперирует бизнес-действиями (action), а детали HTTP/Feign, URL и credentials остаются на уровне runtime-конфигурации.

Подход соответствует общей архитектуре External Task в UniBPM:

  • unibpm-engine публикует задание
  • unibpm-integration-runtime принимает его и находит подходящий handler
  • handler использует unibpm-api-client
  • unibpm-app исполняет бизнес-логику и возвращает результат

Архитектурный принцип

Коннектор unibpm является бизнесовым, а не техническим.

Это означает, что в BPMN задаются:

  • uni.connector
  • uni.action
  • uni.version

и бизнесовые входы/выходы

В BPMN не должны задаваться:

  • HTTP method
  • URL
  • path
  • query parameters
  • credentials

Пример правильного подхода:

uni.connector = unibpm
uni.action = comments.add
uni.version = v1

Пример неправильного подхода:

uni.connector = unibpm
uni.action = rest.invoke
unibpm.method = POST
unibpm.path = /api/...

Включение коннектора

unibpm:
  integration:
    connectors:
      unibpm:
        enabled: true

Конфигурация API-клиента

Handlers коннектора используют unibpm-api-client.

Credentials и URL определяются не в handler-ах, а на уровне конкретного инстанса unibpm-integration-runtime.

Пример:

unibpm:
  client:
    url: http://unibpm-app:8099

Способ авторизации зависит от конфигурации unibpm-api-client / FeignClient.

Важно:
External Task handler не должен знать credentials.
Это принципиальная архитектурная договорённость.


Общий контракт External Task

Базовые extension properties

uni.connector = unibpm
uni.action = <business-action>
uni.version = v1

Входные параметры

unibpm.in.<name> = <value or expression>

Выходные параметры

unibpm.out.<name> = <processVariableName>

Подстановка переменных процесса

Поддерживаются выражения вида:

unibpm.in.ticketId = ${ticketId}

Разрешение значений выполняется через ExtensionPropertyResolver.

Проекция результата (ticket.select)

Для операций, возвращающих объект заявки (ticket.get, ticket.duplicate, ticket.move, ticket.merge), поддерживается свойство:

ticket.select = id,number,name

Свойство работает как whitelist-проекция полей ответа UniBPM API.

Поддерживаются:

  • простые поля (id, number, name, priority, impact, dueDate)
  • вложенные поля через dot notation (owner.id, owner.name, type.id, type.name, state.id, state.name)

Если ticket.select не задано, по умолчанию возвращаются только поля:

id,number,name

Это сделано для того, чтобы не сохранять в переменные процесса полный JSON заявки, который может быть слишком большим для Camunda (например, из-за body, вложенных объектов и списков).

Результат возвращается в одну переменную процесса:

unibpm.out.result

Пример:

uni.connector = unibpm
uni.action = ticket.get
uni.version = v1

unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,owner.id,owner.name,type.id,type.name,priority

Результат:

{
  "id": "5f758930-f1bb-48be-b687-d2518a94466a",
  "number": 6567,
  "name": "Баги. Ошибки при скачивании pdf-файла в filepicker",
  "owner": {
    "id": "v.ivanova@acme.com",
    "name": "Victoria Ivanova"
  },
  "type": {
    "id": "72a23e56-85c6-410b-865c-dbd9b04a6aac",
    "name": "Issue"
  },
  "priority": "VERY_HIGH"
}

Если в ticket.select указано несуществующее поле, в результате для него будет возвращено null.


Базовый абстрактный handler

Все handlers коннектора наследуются от AbstractUnibpmHandler.

Ответственность AbstractUnibpmHandler

  • фиксирует connector = "unibpm"
  • фиксирует version = "v1"
  • даёт helper-методы для чтения extension properties
  • централизует использование ExtensionPropertyResolver

Реализованные operations

На текущем этапе в коннекторе реализованы следующие бизнес-операции:

  • comments.add
  • approvals.results.get
  • approvals.aggregate
  • message.send
  • ticket.assign-owner
  • ticket.assign-responsible
  • ticket.change-body
  • ticket.change-due-date
  • ticket.change-impact
  • ticket.change-name
  • ticket.change-priority
  • ticket.change-type
  • ticket.get
  • ticket.duplicate
  • ticket.move
  • ticket.merge

1. Operation comments.add

Назначение

Добавляет внутренний комментарий к заявке UniBPM.

Комментарий создаётся как INTERNAL.

Action

uni.action = comments.add

Входы

Обязательные

unibpm.in.ticketId
unibpm.in.body

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.body — текст/HTML комментария

Выходы

Опциональные

unibpm.out.commentId
unibpm.out.createdAt
  • unibpm.out.commentId — имя process variable, куда будет положен ID созданного комментария
  • unibpm.out.createdAt — имя process variable, куда будет положено время создания комментария

Пример

uni.connector = unibpm
uni.action = comments.add
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.body = <p>Комментарий, добавленный процессом</p>

unibpm.out.commentId = createdCommentId
unibpm.out.createdAt = createdCommentCreatedAt

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Если заданы output-mapping свойства, в процесс будут записаны:

  • ID комментария
  • время создания комментария

Особенности

  • комментарий всегда создаётся как CommentType.INTERNAL
  • credentials определяются Feign client configuration
  • в BPMN не используются детали REST API

2. Operation approvals.results.get

Назначение

Получает по заявке список сохранённых результатов согласования (ApprovalDecisionDto).

Используется в случаях, когда процессу нужен сырой список решений согласующих.

Операция возвращает все сохранённые результаты согласования по заявке без дополнительной фильтрации по попытке или этапу согласования.

Action

uni.action = approvals.results.get

Входы

Обязательные

unibpm.in.ticketId

Выходы

Опциональные

unibpm.out.results
unibpm.out.count
  • unibpm.out.results — имя process variable для списка ApprovalDecisionDto
  • unibpm.out.count — имя process variable для количества решений

Пример

uni.connector = unibpm
uni.action = approvals.results.get
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.out.results = approvalResults
unibpm.out.count = approvalResultsCount

Результат

В процесс могут быть записаны:

  • approvalResults — список объектов ApprovalDecisionDto
  • approvalResultsCount — число решений

Если в процессе требуется отбор результатов только по конкретной попытке согласования и этапу, рекомендуется использовать operation approvals.aggregate.


3. Operation approvals.aggregate

Назначение

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

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

  • согласовано
  • вернуть на доработку

Action

uni.action = approvals.aggregate

Бизнес-логика агрегации

Handler получает из UniBPM результаты согласования по:

  • заявке
  • попытке согласования
  • этапу согласования

После этого применяется правило агрегации, явно заданное в extension properties.

В текущей версии поддерживается правило:

  • ANY_REJECT_REWORK

Смысл правила:

  • если есть хотя бы один результат, равный значению notApprovedValue, итог = REVISION_REQUIRED
  • если результатов notApprovedValue нет, итог = APPROVED

Дополнительно handler:

  • считает количество approve
  • считает количество reject
  • собирает комментарии отказавших
  • может вернуть сырой список решений

Входы

Обязательные

unibpm.in.ticketId
unibpm.in.approval.attempt
unibpm.in.approval.stage
unibpm.in.approval.rule

Опциональные

unibpm.in.approval.approvedValue
unibpm.in.approval.notApprovedValue

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.approval.attempt — номер попытки согласования
  • unibpm.in.approval.stage — имя этапа согласования
  • unibpm.in.approval.rule — правило агрегации результатов согласования
  • unibpm.in.approval.approvedValue — значение, которое трактуется как положительное решение (по умолчанию APPROVED)
  • unibpm.in.approval.notApprovedValue — значение, которое трактуется как отрицательное решение (по умолчанию NOT_APPROVED)

Выходы

Опциональные

unibpm.out.result
unibpm.out.approveCount
unibpm.out.rejectCount
unibpm.out.comments
unibpm.out.rawResults

Значения

  • unibpm.out.result — итоговое бизнес-решение
  • unibpm.out.approveCount — количество решений, равных approvedValue
  • unibpm.out.rejectCount — количество решений, равных notApprovedValue
  • unibpm.out.comments — список комментариев отказавших согласующих
  • unibpm.out.rawResults — сырой список ApprovalDecisionDto

Пример

uni.connector = unibpm
uni.action = approvals.aggregate
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.approval.attempt = ${approvalAttempt}
unibpm.in.approval.stage = ${approvalStage}
unibpm.in.approval.rule = ANY_REJECT_REWORK
unibpm.in.approval.approvedValue = APPROVED
unibpm.in.approval.notApprovedValue = NOT_APPROVED

unibpm.out.result = approvalResult
unibpm.out.approveCount = approvalApproveCount
unibpm.out.rejectCount = approvalRejectCount
unibpm.out.comments = approvalRejectComments
unibpm.out.rawResults = approvalResults

Пример результата

{
  "approvalResult": "REVISION_REQUIRED",
  "approvalApproveCount": 2,
  "approvalRejectCount": 1,
  "approvalRejectComments": [
    "ne ok"
  ]
}

Рекомендуемое использование в BPMN

Gateway

${approvalResult == "APPROVED"}

и

${approvalResult == "REVISION_REQUIRED"}

Использование комментариев

approvalRejectComments можно использовать:

  • в задаче на доработку
  • в комментарии к заявке
  • в уведомлениях
  • в UI

Рекомендуемый BPMN-паттерн

Для кейса согласования рекомендуется использовать именно approvals.aggregate, а не сырые ApprovalDecisionDto, если процессу нужно принять решение.

Рекомендуемый вариант

Start
  ->
ServiceTask approvals.aggregate
  ->
ExclusiveGateway
  ├─ APPROVED
  └─ REVISION_REQUIRED

Почему так лучше

  • BPMN остаётся бизнесовой
  • gateway использует простую переменную
  • не нужно писать сложные FEEL/EL выражения по массиву DTO
  • легче сопровождать модель

Обработка ошибок

При технических ошибках handlers выбрасывают ExternalTaskFailureException.

Типовые причины:

  • отсутствует обязательная extension property
  • не удалось разрешить ${variable}
  • ticketId имеет некорректный формат UUID
  • unibpm-app недоступен
  • API вернул ошибку
  • задано неподдерживаемое правило агрегации в ‘unibpm.in.approval.rule’

Типовой retry timeout:

DEFAULT_RETRY_DURATION_MS = 30_000L

4. Operation message.send

Назначение

Создаёт исходящее сообщение в UniBPM через SenderMessageApi.

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

Action

uni.action = message.send

Бизнес-логика

Handler:

  • создаёт SenderMessage
  • устанавливает direction = OUTBOX
  • устанавливает completed = false
  • устанавливает retries = 3
  • вызывает SenderMessageApiClient.createSenderMessage(...)
  • возвращает в процесс идентификатор созданного сообщения и признак его обработки

Входы

Обязательные

unibpm.in.sender
unibpm.in.recipients
unibpm.in.subject

Опциональные

unibpm.in.copy
unibpm.in.body
unibpm.in.channelName
unibpm.in.inReplyTo
unibpm.in.attachments

Значения

  • unibpm.in.sender — отправитель сообщения
  • unibpm.in.recipients — список получателей через запятую
  • unibpm.in.copy — список получателей копии через запятую
  • unibpm.in.subject — тема сообщения
  • unibpm.in.body — тело сообщения
  • unibpm.in.channelName — имя канала доставки
  • unibpm.in.inReplyTo — идентификатор родительского сообщения / значение для reply-сценария
  • unibpm.in.attachments — список UUID вложений через запятую

Выходы

Handler всегда возвращает в process variables:

  • messageId — UUID созданного сообщения
  • messageCompleted — признак того, что сообщение уже обработано системой доставки.

На момент создания сообщения значение обычно равно false, так как доставка выполняется асинхронно внутренними механизмами UniBPM.

Пример

uni.connector = unibpm
uni.action = message.send
uni.version = v1

unibpm.in.sender = info@reunico.com
unibpm.in.recipients = employee@company.com
unibpm.in.copy = hr@company.com
unibpm.in.subject = Заявление на отпуск согласовано
unibpm.in.body = Добрый день!

Заявление на отпуск согласовано. В настоящее время формируются необходимые документы. После подготовки вам будет направлено уведомление для их подписания.

unibpm.in.channelName = EMAIL

Пример с подстановкой переменных

uni.connector = unibpm
uni.action = message.send
uni.version = v1

unibpm.in.sender = info@reunico.com
unibpm.in.recipients = ${employeeEmail}
unibpm.in.subject = Заявление на отпуск не согласовано
unibpm.in.body = Добрый день!

Заявление на отпуск не согласовано.

Замечания по заявлению: ${approvalRejectComments}

unibpm.in.channelName = EMAIL
unibpm.in.attachments = ${attachmentIds}

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED и process variables:

  • messageId
  • messageCompleted

Особенности

  • action не отправляет сообщение напрямую во внешний канал, а создаёт запись SenderMessage в UniBPM
  • unibpm.in.recipients, unibpm.in.copy и unibpm.in.attachments передаются как CSV-строки
  • unibpm.in.body может быть многострочным
  • unibpm.in.channelName является опциональным и может использоваться для маршрутизации по каналам доставки
  • retries исходящего автоматически устанавливается в 3

Рекомендуемое использование в BPMN

Рекомендуется использовать action для пользовательских и системных уведомлений процесса:

  • уведомление о согласовании
  • уведомление о возврате на доработку
  • сервисные сообщения участникам процесса

Пример

Start
  ->
ServiceTask message.send
  ->
End

5. Operation ticket.assign-owner

Назначение

Назначает владельца заявки UniBPM или снимает владельца.

Action

uni.action = ticket.assign-owner

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.ownerId

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.ownerId — идентификатор пользователя, назначаемого владельцем

Если unibpm.in.ownerId не задан, равен null или содержит только пробелы, владелец снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.assign-owner
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.ownerId = ${employeeKey}

6. Operation ticket.assign-responsible

Назначение

Назначает ответственного по заявке UniBPM или снимает ответственного.

Action

uni.action = ticket.assign-responsible

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.responsibleId

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.responsibleId — идентификатор пользователя, назначаемого ответственным

Если unibpm.in.responsibleId не задан, равен null или содержит только пробелы, ответственный снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.assign-responsible
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.responsibleId = ${employeeKey}

7. Operation ticket.change-body

Назначение

Изменяет тело заявки UniBPM.

Action

uni.action = ticket.change-body

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.body

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.body — новое тело заявки

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-body
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.body = <p>Обновлённое описание заявки</p>

8. Operation ticket.change-due-date

Назначение

Изменяет срок исполнения заявки UniBPM.

Action

uni.action = ticket.change-due-date

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.dueDate

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.dueDate — новая дата срока исполнения в формате yyyy-MM-dd

Если unibpm.in.dueDate не задан, равен null или содержит только пробелы, срок исполнения снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-due-date
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.dueDate = 2026-05-01

9. Operation ticket.change-impact

Назначение

Изменяет impact заявки UniBPM.

Action

uni.action = ticket.change-impact

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.impact

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.impact — новое значение Impact

Допустимые значения Impact:

  • VERY_LOW
  • LOW
  • MEDIUM
  • HIGH
  • VERY_HIGH

Если unibpm.in.impact не задан, равен null или содержит только пробелы, значение Impact снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-impact
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.impact = HIGH

10. Operation ticket.change-name

Назначение

Изменяет наименование заявки UniBPM.

Action

uni.action = ticket.change-name

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.name

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.name — новое наименование заявки

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-name
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.name = Обновлённое наименование заявки

11. Operation ticket.change-priority

Назначение

Изменяет приоритет заявки UniBPM.

Action

uni.action = ticket.change-priority

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.priority

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.priority — новое значение TicketPriority

Допустимые значения TicketPriority:

  • VERY_LOW — очень низкий: косметика, nice-to-have
  • LOW — низкий: несущественная проблема / вопрос
  • MEDIUM — средний: обычные инциденты / запросы, есть обходные пути
  • HIGH — высокий: работа пользователей блокирована, проблема встречается часто
  • VERY_HIGH — очень высокий: критичный блокер, массовое влияние, бизнес-процессы остановлены

Если unibpm.in.priority не задан, равен null или содержит только пробелы, приоритет снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-priority
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.priority = VERY_HIGH

12. Operation ticket.change-type

Назначение

Изменяет тип заявки UniBPM.

Action

uni.action = ticket.change-type

Входы

Обязательные

unibpm.in.ticketId

Опциональные

unibpm.in.typeId

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.typeId — UUID нового типа заявки (TicketType)

Если unibpm.in.typeId не задан, равен null или содержит только пробелы, тип снимается.

Результат

При успешном выполнении handler возвращает ExternalTaskResult со статусом COMPLETED.

Пример

uni.connector = unibpm
uni.action = ticket.change-type
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.typeId = ${ticketTypeId}

13. Operation ticket.get

Назначение

Получает заявку UniBPM и возвращает в процесс её проекцию.

Action

uni.action = ticket.get

Входы

Обязательные

unibpm.in.ticketId

Опциональные

ticket.select

Значения

  • unibpm.in.ticketId — UUID заявки
  • ticket.select — whitelist-список полей результата через запятую

Если ticket.select не задано, по умолчанию возвращаются только поля id, number, name.

Выходы

Handler возвращает одну process variable:

unibpm.out.result

Пример

uni.connector = unibpm
uni.action = ticket.get
uni.version = v1

unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,owner.id,owner.name,state.id,state.name

14. Operation ticket.duplicate

Назначение

Создаёт дубликат заявки UniBPM и возвращает в процесс проекцию созданной заявки.

Action

uni.action = ticket.duplicate

Входы

Обязательные

unibpm.in.ticketId

Опциональные

ticket.select

Значения

  • unibpm.in.ticketId — UUID исходной заявки
  • ticket.select — whitelist-список полей результата через запятую

Если ticket.select не задано, по умолчанию возвращаются только поля id, number, name.

Выходы

Handler возвращает одну process variable:

unibpm.out.result

Пример

uni.connector = unibpm
uni.action = ticket.duplicate
uni.version = v1

unibpm.in.ticketId = ${ticketId}
ticket.select = id,number,name,workflow.id,workflow.name,type.id,type.name

15. Operation ticket.move

Назначение

Перемещает заявку UniBPM в другое пространство (workflow) и при необходимости меняет её тип.

Action

uni.action = ticket.move

Входы

Обязательные

unibpm.in.ticketId
unibpm.in.workflowId

Опциональные

unibpm.in.ticketTypeId
ticket.select

Значения

  • unibpm.in.ticketId — UUID заявки
  • unibpm.in.workflowId — UUID целевого workflow
  • unibpm.in.ticketTypeId — UUID целевого типа заявки
  • ticket.select — whitelist-список полей результата через запятую

Если unibpm.in.ticketTypeId не задан, равен null или содержит только пробелы, тип заявки при перемещении не изменяется.

Если ticket.select не задано, по умолчанию возвращаются только поля id, number, name.

Выходы

Handler возвращает одну process variable:

unibpm.out.result

Пример

uni.connector = unibpm
uni.action = ticket.move
uni.version = v1

unibpm.in.ticketId = ${ticketId}
unibpm.in.workflowId = ${targetWorkflowId}
unibpm.in.ticketTypeId = ${targetTicketTypeId}
ticket.select = id,number,name,workflow.id,workflow.name,type.id,type.name

16. Operation ticket.merge

Назначение

Объединяет две заявки UniBPM и возвращает в процесс проекцию целевой заявки после объединения.

Action

uni.action = ticket.merge

Входы

Обязательные

unibpm.in.sourceTicketId
unibpm.in.targetTicketId

Опциональные

ticket.select

Значения

  • unibpm.in.sourceTicketId — UUID исходной заявки, которая будет объединена
  • unibpm.in.targetTicketId — UUID целевой заявки, в которую выполняется объединение
  • ticket.select — whitelist-список полей результата через запятую

Если ticket.select не задано, по умолчанию возвращаются только поля id, number, name.

Выходы

Handler возвращает одну process variable:

unibpm.out.result

Пример

uni.connector = unibpm
uni.action = ticket.merge
uni.version = v1

unibpm.in.sourceTicketId = ${sourceTicketId}
unibpm.in.targetTicketId = ${targetTicketId}
ticket.select = id,number,name,state.id,state.name,workflow.id,workflow.name

План дальнейшего развития

Потенциальные следующие business actions:

  • tickets.search
  • comments.list
  • attachments.sign
  • attachments.verify
  • workflows.start
  • permissions.check

Итог

unibpm-external-task-unibpm — это системный бизнес-коннектор UniBPM для вызова внутренних возможностей платформы через External Task.

Его ключевые свойства:

  • бизнесовый контракт
  • отсутствие transport-деталей в BPMN
  • использование unibpm-api-client
  • отсутствие knowledge о credentials внутри handler-ов
  • поддержка ticket-operations для работы с заявками UniBPM
  • удобство расширения новыми action