Предисловие
Данный документ является руководством по применению подсистемы исполнения бизнес-процессов (business process management, BPM) платформы CUBA.
Целевая аудитория
Данное руководство предназначено для разработчиков, создающих на платформе CUBA приложения с возможностью исполнения бизнес-процессов. Предполагается, что читатель ознакомлен с Руководством по разработке приложений, доступным по адресу https://www.cuba-platform.ru/manual.
Дополнительные материалы
Настоящее Руководство, а также другая документация по платформе CUBA доступны по адресу https://www.cuba-platform.ru/manual.
Подсистема исполнения бизнес-процессов CUBA основана на фреймворке Activiti, поэтому знакомство с его устройством будет полезным. См. http://www.activiti.org.
Исполняемый процесс должен быть описан в нотации BPMN 2.0. Разработчик должен быть знаком с данной нотацией. См. http://www.bpmn.org.
Обратная связь
Если у Вас имеются предложения по улучшению данного руководства, обратитесь пожалуйста в службу поддержки по адресу http://www.cuba-platform.ru/support/topics.
При обнаружении ошибки в документации укажите, пожалуйста, номер главы и приведите небольшой участок окружающего текста для облегчения поиска.
1. Быстрый старт
В данной главе рассмотрим создание небольшого проекта, демонстрирующего работу с бизнес-процессами. Задача - реализовать процесс согласования договора. Процесс согласования выглядит следующим образом:
-
Пользователь создает объект
Contract
, назначает участников процесса и запускает процесс согласования. -
Участник с ролью
Controller
получает задачу проверить приложенный договор на корректность заполнения. -
Если проверка пройдена, то договор попадает к нескольким пользователям с ролью
Manager
, если нет, то процесс завершается, а договору проставляется статусNot valid
. -
После утверждения или отклонения договора менеджерами договор принимает состояния
Approved
илиNot approved
.
1.1. Создание проекта
-
Создайте новый проект в Cuba Studio:
-
Project name:
bpm-demo
-
Project namespace:
demo
-
Root package:
com.company.demo
-

-
Откройте окно редактирования свойств проекта (секция Project properties, кнопка Edit).
-
В группе Base Projects подключите базовый проект bpm.

-
Нажмите кнопку OK в окне редактирования свойств проекта. Система запросит подтверждение перезаписи скриптов сборки. Соглашаемся.
1.2. Создание модели данных
Перейдите на вкладку Entities и нажмите New entity. Имя класса: Contract
.

Создайте следующие атрибуты сущности:
-
number
(типString
) -
date
(типDate
) -
state
(типString
)

Перейдите на вкладку Instance name. В поле Name pattern введите значение Contract %s
и добавьте атрибут number
в Name pattern attributes.

Сохраните сущность, нажав кнопку OK.
1.3. Создание стандартных экранов
В секции Entities панели навигатора выделите сущность Contract и нажмите кнопку Create standard screens. Значения полей, по умолчанию заполненные в форме создания экрана, нас устраивают, поэтому нажмите Create.

1.4. Создание бинов с бизнес-логикой
1.4.1. Бин ApprovalHelper
Метод updateState()
бина ApprovalHelper
будет вызываться из процесса согласования для установки состояния договора.
Параметры метода:
-
entityId
- идентификатор сущности договора -
state
- состояние договора
Откройте проект в IDE. Простой способ сделать это - воспользоваться кнопкой IDE из какой-либо секции навигатора студии, например, Project properties.
В модуле core
создайте пакет com.company.demo.core
. В этом пакете создайте класс ApprovalHelper
.

package com.company.demo.core;
import com.company.demo.entity.Contract;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import javax.annotation.ManagedBean;
import javax.inject.Inject;
import java.util.UUID;
@ManagedBean("demo_ApprovalHelper")
public class ApprovalHelper {
@Inject
private Persistence persistence;
public void updateState(UUID entityId, String state) {
Transaction tx = persistence.getTransaction();
try {
EntityManager em = persistence.getEntityManager();
Contract contract = em.find(Contract.class, entityId);
if (contract != null) {
contract.setState(state);
}
tx.commit();
} finally {
tx.end();
}
}
}
1.5. Создание базы данных и запуск приложения
В студии в секции Entities навигатора нажмите на Generate DB scripts. В открывшемся окне менеджера базы данных нажмите кнопку Create database.

Запустите сервер, выполнив команду Run → Start application server.
Откройте приложение в браузере по адресу http://localhost:8080/app или кликните на ссылку в нижней части навигатора студии.
1.6. Создание процесса
1.6.1. Создание модели процесса
Конечная версия модели процесса будет выглядеть следующим образом:

Рассмотрим последовательность шагов для создания модели.
В веб-интерфейсе запущенного приложения откройте экран BPM → Process models и нажмите Create. Введите имя модели Contract approval
и нажмите OK. Откроется новая закладка браузера Model editor.
Tip
|
Если закладка не появилась, убедитесь, что браузер не заблокировал вспылывающее окно. |
В панели свойств модели выберите свойство Process roles - откроется окно редактирования процессных ролей.

В процессе должно быть два типа участников: контролер и менеджер. Создайте 2 роли: Controller
и Manager
.

Перетащите в рабочую область узел Start event из группы Start events. При старте процесса нам необходимо отображать форму выбора участников процесса. Для этого выделите узел Start event. В панели свойств выберите Start form - откроется окно выбора формы. В списке Form name выберите Standard form
. После этого добавьте два параметра формы:
-
procActorsVisible
со значениемtrue
говорит о том, что на форме будет показана таблица для выбора участников процесса; -
attachmentsVisible
со значениеtrue
говорит о том, что на форме будет показана таблица для добавления вложений к процессу.

Добавьте в модель узел User task из группы Activities. Назовите его Validation
.

Выделите этот узел, и на панели свойств задайте свойству Process role значение controller
. Так мы указали, что задача будет назначена на участника процесса с ролью controller
.

Далее выберите свойство Task outcomes. Откроется окно редактирования выходов из задачи. Выходы определяют возможные действия пользователя при получении задачи. Создайте два выхода: Valid
и Not valid
. Для каждого из них укажите форму Standard form
. Для выхода Not valid
добавьте параметр формы commentRequired = true
. Это нужно, чтобы в случае некорректного договора пользователь обязательно добавил свой комментарий.

В зависимости от решения контролера нам необходимо либо отправить договор далее на утверждение группе менеджеров, либо завершить процесс, предварительно установив договору состояние Not valid
. Для контроля над маршрутом процесса используется узел Exclusive gateway из группы Gateways. Добавьте его на рабочую область, а затем добавьте еще два элемента: Script task с именем Set 'Not valid' state
и User task с именем Approval
. Переход к Script task назовите Not valid
, переход к узлу Approval
назовите Valid
.

Выделите переход Not valid
. В панели свойств разверните выпадающий список Flow outcome. В нем представлены выходы из предыдущей задачи. Выберите Not valid
.

Теперь в случае выбора пользователем решения Not valid
будет осуществлен переход именно по этой ветке.
Переход Valid
сделаем переходом по умолчанию (если не выполнилось никакое из условий на других переходах узла). Для этого выделите переход Valid
и поставьте галочку в его свойстве Default flow.
Далее выделите Exclusive gateway и откройте редактор свойства Flow order. Убедитесь, что переход Not valid
стоит первым в списке. Если это не так, измените порядок обработки переходов.

Перейдем к узлу Set 'Not valid' state
. Нам необходимо установить значение свойства state
сущности Contract
в Not valid
. Выделите узел. В поле свойства Script format введите groovy
, т.к. мы будем писать groovy-скрипт. Нажмите на поле свойства Script узла. Откроется окно редактирования скрипта. Скопируйте и вставьте туда следующий текст:
import com.company.demo.entity.Contract
def em = persistence.getEntityManager()
def contract = em.find(Contract.class, entityId)
contract.setState('Not valid')
В скрипте можно использовать процессные переменные, а также объекты платформы persistence
и metadata
(см. Руководство по разработке приложений). Переменная entityId
создается при запуске процесса и хранит идентификатор связанной сущности.
После того, как состояние договора изменено, процесс должен быть завершен - добавляем узел End event из группы End Events и соединяем его с узлом Set 'Not valid' state
.
Вернемся к задаче Approval
. Как и в случае с первой задачей, укажите для нее процессную роль - в данном случае это будет роль manager
. Так как предполагается, что эта задача должна быть назначена одновременно нескольким менеджерам, то установим её свойство Multi-instance type в значение Parallel
.

Создайте для задачи два выхода: Approve
и Reject
(свойство Task outcomes). Задайте для обоих выходов форму Standard form
, для перехода Reject
установите параметр commentRequired
в true
.
После того, как согласование завершится, договору должно установиться состояние Approved
или Not approved
в зависимости от результата согласования. Добавьте узел Exclusive gateway после задачи Approval
. После Exclusive gateway
добавьте две Service task: Set 'Approved' state
и Set 'Not approved' state
. Они будут делать то же самое, что и Script task, созданная ранее, но другим способом - вызывая метод Spring-бина. Переход к Set 'Approved' state
назовите Approved
, переход к Set 'Not approved' state
назовите Not approved
.

Выделите переход Not approved
и в списке Flow outcome выберите значение Reject
. Теперь если хотя бы один из менеджеров выполнит действие Reject
, то будет инициирован этот переход. Выделите переход Approved
и установите флажок Default flow - если остальные переходы не сработали (не было выбора Reject
), то будет инициирован переход Approved
.
По аналогии с предыдущим Exclusive gateway установите порядок обработки переходов для текущего. Выделите Exclusive gateway и откройте редактор свойства Flow order. Первым должен обрабатываться переход Not approved
.

Вернемся к Service task. Выделите узел Set 'Approved' state
и задайте свойству Expression значение:
${demo_ApprovalHelper.updateState(entityId, 'Approved')}
Для Set 'Not approved' state
:
${demo_ApprovalHelper.updateState(entityId, 'Not approved')}
Activiti Engine интегрирован со Spring Framework, поэтому мы можем обращаться к объектам, управляемым Spring, по их имени. entityId
- процессная переменная, хранящая идентификатор сущности связанного с процессом договора. Ее значение будет записано при старте процесса.
Соедините с End event последние созданные задачи, нажмите кнопку сохранения модели - модель готова. Переходим к её развертыванию.

1.6.2. Развертывание модели процесса
Процесс развертывания модели состоит из следующих этапов:
-
Формирование XML процесса в нотации BPMN из модели.
-
Деплой процесса во внутренние таблицы Activiti Engine.
-
Создание объекта ProcDefinition, связанного с загруженным в Activiti Engine процессом.
-
Создание объектов ProcRole для процессных ролей, объявленных в модели.
Выделите модель в списке на экране Process models. Нажмите кнопку Deploy. Откроется окно развертывания модели. Модель разворачивается первый раз, поэтому выбрана опция Create new process. При последующих изменениях модели можно будет разворачивать модель в уже существующий процесс. Нажмите OK. Процесс создан.

Откройте экран BPM → Process definitions. Откройте строку с Contract approval
для редактирования. Поле Code имеет значение contractApproval
. Запомните его. В дальнейшем мы используем это значение чтобы идентифицировать процесс.

1.7. Адаптация экранов к процессу
В данном разделе мы добавим в экран редактирования договора возможность работы с процессом согласования.
1.7.1. Компоновка экрана редактирования договора
Найдите в секции Screens на панели навигатора студии экран contract-edit.xml
и откройте его на редактирование. Перейдите на вкладку XML и полностью замените ее содержимое на следующий код:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://editCaption"
class="com.company.demo.web.contract.ContractEdit"
datasource="contractDs"
focusComponent="fieldGroup"
messagesPack="com.company.demo.web.contract">
<dsContext>
<datasource id="contractDs"
class="com.company.demo.entity.Contract"
view="_local"/>
<collectionDatasource id="procAttachmentsDs"
class="com.haulmont.bpm.entity.ProcAttachment"
view="procAttachment-browse">
<query><![CDATA[select a from bpm$ProcAttachment a
where a.procInstance.entityId = :ds$contractDs order by a.createTs]]></query>
</collectionDatasource>
</dsContext>
<layout expand="windowActions" spacing="true">
<fieldGroup id="fieldGroup" datasource="contractDs">
<column width="250px">
<field id="number"/>
<field id="date"/>
<field id="state" editable="false"/>
</column>
</fieldGroup>
<groupBox id="procActionsBox"
caption="msg://process"
orientation="vertical"
spacing="true"
width="AUTO">
<frame id="procActionsFrame" screen="procActionsFrame"/>
</groupBox>
<groupBox caption="msg://attachments"
width="700px"
height="300px">
<table id="attachmentsTable"
height="100%"
width="100%">
<columns>
<column id="file.name"/>
<column id="author"/>
<column id="type"/>
<column id="comment" maxTextLength="50"/>
</columns>
<rows datasource="procAttachmentsDs"/>
</table>
</groupBox>
<frame id="windowActions" screen="extendedEditWindowActions"/>
</layout>
</window>
Перейдите на вкладку Layout. Компоновка экрана станет следующей:

Экран содержит группу полей для редактирования самого договора, фрейм для отображения действий по процессу и таблицу с вложениями, созданными во время выполнения процесса.
1.7.2. Контроллер экрана редактирования договора
Перейдите на вкладку Controller и замените ее содержимое на следующий код:
package com.company.demo.web.contract;
import com.company.demo.entity.Contract;
import com.haulmont.bpm.entity.ProcAttachment;
import com.haulmont.bpm.gui.procactions.ProcActionsFrame;
import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.components.Table;
import javax.inject.Inject;
public class ContractEdit extends AbstractEditor<Contract> {
private static final String PROCESS_CODE = "contractApproval";
@Inject
private ProcActionsFrame procActionsFrame;
@Inject
private Table<ProcAttachment> attachmentsTable;
@Override
protected void postInit() {
FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file");
initProcActionsFrame();
}
private void initProcActionsFrame() {
procActionsFrame.initializer()
.setBeforeStartProcessPredicate(this::commit)
.setAfterStartProcessListener(() -> {
showNotification(getMessage("processStarted"), NotificationType.HUMANIZED);
close(COMMIT_ACTION_ID);
})
.setBeforeCompleteTaskPredicate(this::commit)
.setAfterCompleteTaskListener(() -> {
showNotification(getMessage("taskCompleted"), NotificationType.HUMANIZED);
close(COMMIT_ACTION_ID);
})
.init(PROCESS_CODE, getItem());
}
}
Сохраните изменения, нажав кнопку OK.
Рассмотрим код контроллера более подробно.
ProcActionsFrame
- это стандартный фрейм для отображения кнопок доступных в данный момент процессных действий. Во время инициализации фрейма по двум параметрам (код процесса и сущность) ищется связанный экезмпляр ProcInstance
. Если процесс не находится, то создается новый ProcInstance
, и фрейм отображает кнопку запуска процесса. Если связанный с сущностью процесс найден, то проверяется, имеется ли незавершенная процессная задача для текущего пользователя, и если да, то отображаются кнопки завершения данной задачи. Подробнее о ProcActionsFrame см. ProcActionsFrame.
В контроллере редактора договора инициализация фрейма процессных действий происходит в методе initProcActionsFrame()
. Самая важная часть метода - вызов init(PROCESS_CODE, getItem())
. Константа PROCESS_CODE
хранит код процесса (contractApproval
- это значение мы видели при равертывании процесса, см. Развертывание модели процесса). Второй аргумент getItem()
- текущий договор.
Во время инициализации фрейма мы также объявили дополнительную логику, связанную с процессными действиями:
-
setBeforeStartProcessPredicate
добавляет проверку, выполняемую перед запуском процесса. Выполняется коммит экрана, если коммит не проходит успешно, процесс не запускается -
setAfterStartProcessListener
отображает уведомление и закрывает редактор договора после запуска процесса -
setBeforeCompleteTaskPredicate
вызывает коммит редактора договора перед выполнением процессного действия. В случае неуспешного коммита, процессное действие не выполняется -
setAfterCompleteTaskListener
отображает уведомление и закрывает редактор договора после завершения процессного действия.
1.7.3. Файл локализованных сообщений
В студии откройте файл messages.properties
, расположенный в пакете с экранами для договора. Измените его содержимое на следующим текстом:
browseCaption = Contract browser
editCaption = Contract editor
attachments = Attachments
process = Contract approval
processStarted = Process started
taskCompleted = Task completed
1.8. Работа с приложением
По умолчанию в Cuba Studio включен механизм Hot Deploy, и изменения в экране редактирования договора уже должны быть отправлены на сервер. Если Hot Deploy у вас был отключен, то перезапустите сервер, выполнив в Студии команду Run → Restart application server.
1.8.1. Создание пользователей
Для демонстрации работы процесса необходимо создать несколько тестовых пользователей. Откройте экран Administration → Users и создайте трех пользователей:
-
login:
norman
, First name:Tommy
, Last name:Norman
, Full name:Tommy Norman
-
login:
roberts
, First name:Casey
, Last name:Roberts
, Full name:Casey Roberts
-
login:
pierce
, First name:Walter
, Last name:Pierce
, Full name:Walter Pierce
1.8.2. Создание договора и запуск процесса
-
Откройте список договоров Application → Contracts и создайте новый договор. Заполните поля Number и Date и нажмите кнопку Save.
-
Нажмите на кнопку Start process - перед вами появится форма запуска процесса. При создании модели для узла Start event мы указали форму
Standard form
с атрибутамиprocActorsVisible=true
иattachmentsVisible=true
, поэтому сейчас перед нами форма с компонентами для указания участников процесса и добавления вложений. -
Введите комментарий для процесса, добавьте участников: контролер
norman
и два менеджера:pierce
иroberts
. -
Загрузите вложение к договору, нажав на кнопку Upload таблицы Attachments.

-
Нажмите ОК - процесс запущен.
1.8.3. Этап проверки контролером
Зайдите в систему под пользователем norman
.
При достижении процессом узла User task создается объект ProcTask
, связанный с определенным участником процесса. В подсистеме BPM есть экран для отображения списка невыполненных задач для текущего пользователя. Откройте его: BPM → Process tasks.

Видим, что для пользователя norman
есть одна задача Validation
по процессу Contract approval
. Выделите ее и нажмите кнопку Open entity editor - откроется экран редактирования договора.

Так как для текущего пользователя (norman
) имеется незавершенная задача (ProcTask
), то procActionsFrame
отображает доступные действия. Когда мы описывали узел UserTask с именем Validation
, то мы указали для него два возможных выхода Valid
и Not valid
. На основании этой информации в фрейм и добавлено две кнопки.
Нажмите на Valid. В открывшемся окне введите комментарий:

Нажмите OK.
После успешной валидации договор должен уйти к менеджерам на параллельное согласование.
1.8.4. Этап утверждения менеджерами
Войдите в систему под пользователем pierce
.
Откройте список текущих задач BPM → Process tasks. Имеется одна задача Approval
.

Выделите ее и на этот раз нажмите кнопку Open process instance - откроется системный экран для работы с экземпляром ProcInstance
.

В нем отображается информация о времени запуска процесса, инициаторе процесса, список вложений, участников, текущих и выполненных задач в рамках данного процесса. Также экран позволяет перейти к связанной сущности и выполнить процессное действие.
Обратите внимание на таблицу Tasks. Предыдущая задача Validation
завершена с результатом Valid
, и после успешной валидации контролером создались две новые задачи Approval
на менеджеров pierce
и roberts
.
Утвердите договор, воспользовавшись кнопкой Approve.
Далее войдите в систему под пользователем roberts
. Откройте договор из списка Application → Contracts.
Пользователь roberts
имеет незавершенную задачу по договору, следовательно фрейм procActionsFrame
отображает для него действия Approve и Reject. Нажмите кнопку Reject.

Так как при описании выхода Reject
в дизайнере мы указали параметр формы commentRequired=true
, то комментарий в форме завершения данного действия обязателен. Введите комментарий и нажмите ОК.
Один из менеджеров отклонил договор, поэтому ему должно установиться состояние Not approved
. Проверим это, открыв договор.

Процесс согласования завершен.
2. Модель данных

Note
|
Атрибуты, имена которых начинаются с префикса act* являются ссылками на идентификаторы из Activiti. |
-
ProcModel
- модель процесса. Атрибуты модели:-
name
- имя модели. -
description
- описание модели. -
actModelId
- ID модели Activiti engine в таблице ACT_RE_MODEL.
-
-
ProcDefinition
- описание процесса. Может быть получен из модели, либо загружен напрямую из XML файла. Атрибуты сущности:-
name
- имя процесса. -
code
- код процесса. Может использоваться для поиска экземпляра сущности из кода приложения. -
actId
- ID объекта процесса из Activiti. Необходим для доступа к модели BPMN (из нее читаются extensionElements). -
active
- определяет, возможен ли запуск новых процессов для текущего ProcDefinition. -
procRoles
- коллекция объектов, определяющих участников процесса. -
model
- ссылка на модель, из которой получено описание процесса
-
-
ProcRole
- роль в процессе. Объекты данного типа создаются автоматически при развертывании процесса на основе информации из XML файла с процессом. Можно сказать, что роли определяют типы участников процесса. Атрибуты сущности:-
name
- имя роли. -
code
- код роли. Может использоваться кодом приложения для идентификации роли. -
order
- порядковый номер. Может использоваться приложением для определения порядка отрисовки ролей. -
procDefinition
- ссылка на описание процесса.
-
-
ProcInstance
- экземпляр процесса. ProcInstance может быть запущен как с привязкой к сущности проекта (например, процесс согласования договора может быть привязан к экземпляру сущности "Договор"), так и без нее. Атрибуты сущности:-
description
- описание экземпляра процесса. -
startDate
- дата запуска процесса. -
endDate
- дата завершения процесса. -
startedBy
- пользователь, запустивший процесс. -
active
- признак, что процесс запущен и еще не завершен. -
cancelled
- признак, что процесс был принудительно отменен. -
actProcessInstanceId
- идентификатор соответствующего ProcessInstance из Activiti. -
startComment
- комментарий, заданный при старте процесса. -
cancelComment
- комментарий, заданный при отмене процесса. -
entityName
- имя сущности, с которой связан процесс. -
entityId
- ID сущности, с которой связан процесс. -
entityEditorName
- имя экрана, который будет использоваться для открытия связанной сущности, если он отличается от стандартного. -
procTasks
- коллекция задач процесса. -
procActors
- коллекция участников процесса. -
procAttachments
- коллекция вложений процесса.
-
-
ProcActor
- участник процесса. Сущность определяет исполнителей для ролей процесса по конкретному экземпляру процесса. Атрибуты сущности:-
user
- ссылка на пользователя. -
procInstance
- ссылка на экземпляр процесса. -
procRole
- ссылка на процессную роль. -
order
- порядковый номер. Используется при определении порядка участников для последовательной задачи на многих пользователей.
-
-
ProcTask
- задача по процессу. Объекты данного типа автоматически создаются при достижении процессом узла User task. Атрибуты сущности:-
name
- имя задачи. -
startDate
- дата начала выполнения задачи. -
claimDate
- дата принятия задачи пользователем в случае задачи без явного участника. -
endDate
- дата завершения задачи. -
outcome
- результат выполнения задачи (выход, по которому пользователь завершил задачу). -
comment
- комментарий при завершении задачи. -
procActor
- исполнитель. -
actTaskId
- Activiti task ID. Используется при сигнале Activiti engine о завершения задачи. -
`actExecutionId `- Activiti execution ID. Используется для записи/чтения процессных переменных.
-
actTaskDefinitionKey
- в XML процесса это поле id у UserTask. Используется при формировании имени переменной, хранящей результат задачи [taskId]_result (см. Переходы в зависимости от выхода (outcome) задачи). -
cancelled
- признак, что задача была завершена при отмене процесса. -
candidateUsers
- список возможных участников для групповой задачи. -
procInstance
- ссылка на экземпляр процесса.
-
-
ProcAttachment
- процессное вложение. Атрибуты сущности:-
file
- ссылка на FileDescriptor. -
type
- тип вложения (ProcAttachmentType). -
comment
- комментарий. -
author
- автор вложения, ссылка на пользователя. -
procInstance
- ссылка на экземпляр процесса. -
procTask
- необязательная ссылка на задачу, в рамках которой было добавлено вложение.
-
-
ProcAttachmentType
- тип вложения. Атрибуты сущности:-
code
- код типа вложения. -
name
- имя типа вложения.
-
3. Функциональность
Для исполнения бизнес-процессов подсистема BPM использует Activiti Engine, для моделирования процессов в нотации BPMN используется доработанный редактор из веб-приложения Activiti Explorer. В дополнение к возможностям фреймворка Activiti подсистема BPM предоставляет дополнительную функциональность, которая описана далее в этом разделе. Перечисление возможностей фреймворка Activiti не входит в задачу данного руководства. Вы можете ознакомиться с ними на сайте Activiti: http://www.activiti.org/userguide/.
3.1. BpmActivitiListener
При создании модели процесса, в процесс автоматически добавляется слушатель событий BpmActivitiListener
. BpmActivitiListener
является реализацией интерфейса ActivitiEventListener
(см. http://www.activiti.org/userguide/#eventDispatcher). Слушатель отвечает за создание и изменение сущностей подсистемы BPM при наступлении определенных событий процесса (вход в пользовательскую задачу, отмена процесса, завершение задачи, и т.д.). Именно он создает объекты ProcTask и проставляет значение endDate
для ProcInstance.
3.2. Процессные роли
Процессные роли определяют типы участников процесса, например, оператор или менеджер. Чтобы открыть экран редактирования процессных ролей в дизайнере процессов, выберите свойство Process roles в панели свойств процесса. При развертывании модели информация о ролях будет записана в XML процесса в секцию extensionElements
элемента process
, а затем будут созданы соответствующие объекты ProcRole.
<process id="testProcess" name="Test process">
<extensionElements>
<cuba:procRoles>
<cuba:procRole name="Manager" code="manager"/>
<cuba:procRole name="Operator" code="operator"/>
</cuba:procRoles>
</extensionElements>
</process>
3.3. Форма запуска процесса
Для задания формы, которая будет отображаться при запуске процесса используется свойство Start form элемента Start event. Подробнее о формах см. Процессные формы.
<startEvent id="startEvent">
<extensionElements>
<cuba:form name="standardProcForm">
<cuba:param name="procActorsVisible" value="true"></cuba:param>
</cuba:form>
</extensionElements>
</startEvent>
3.4. Пользовательская задача (User Task)
Для определения пользователя, на которого будет назначена задача, необходимо в свойстве Process role элемента User task выбрать одну из процессных ролей, определенных в модели. При достижении процессом задачи среди участников процесса (ProcActor) будут найдены участники с указанной процессной ролью, и задача будет назначена на них.
<userTask id="managerApproval" name="Manager approval">
<extensionElements>
<cuba:procRole>manager</cuba:procRole>
</extensionElements>
</process>
Если необходимо, чтобы задача была назначена одновременно нескольким пользователям, то в свойстве Multi-instance type элемента User task необходимо выбрать значение Parallel
или Sequential
.
Также возможен вариант, когда задача не должна быть сразу назначена на пользователя, а должна появиться в списке доступных, и один из пользователей должен забрать задачу себе. Для определения такой задачи необходимо установить фдажок Claim allowed. В этом случае задача появится в списке доступных у всех участников процесса с ролью, заданной в свойстве Process role.
<userTask id="managerApproval" name="Manager approval">
<extensionElements>
<cuba:claimAllowed>true</cuba:claimAllowed>
</extensionElements>
</process>
3.5. Выходы задачи
Обычно задача требует от пользователя принятия решения (например, согласовать или отклонить). Дальнейший маршрут по процессу зависит от принятого решения. Для задания списка выходов из задачи используется свойство Task outcomes узла User Task. Для каждого выхода задается имя и выбирается форма, которая должна отображаться при выборе этого выхода, а также список параметров формы (подробнее о формах см. Процессные формы).
<userTask id="managerApproval" name="Manager approval">
<extensionElements>
<cuba:outcomes>
<cuba:outcome name="approve">
<cuba:form name="standardProcessForm">
<cuba:param name="commentRequired">true</cuba:param>
<cuba:param name="attachmentsVisible">true</cuba:param>
</cuba:form>
</cuba:outcome>
<cuba:outcome name="reject">
<cuba:form name="someOtherProcessForm">
</cuba:form>
</cuba:outcome>
</cuba:outcomes>
</extensionElements>
</process>
3.6. Переходы в зависимости от выхода (outcome) задачи
В отличие от jBPM в нотации BPMN отсутствует возможность указать несколько выходов из одной задачи. Чтобы направить процесс по нужной ветке в зависимости от результата используется узел Exclusive Gateway, переходы из которого имеют условия, оперирующие результатом выполнения задачи, расположенной перед этим Gateway. При завершении пользователем задачи, результат его действия записывается в процессную переменную с именем [taskId]_result
. Тип этой переменной - ProcTaskResult
.
Методы класса ProcTaskResult
:
-
int count(String outcomeName)
- возвращает количество пользователей, завершивших задачу с данным выходом -
boolean exists(String outcomeName)
- возвращаетtrue
, если есть хотя бы один пользователь, завершивший задачу с указанным выходом.
Далее объект с результатом используется в выражении Flow condition для переходов, выходящих из Gateway.
Пример:

Предположим, что задача approval
была параллельно назначена нескольким пользователям. Для задачи были определены два возможных выхода: approve
и reject
. После того, как все пользователи завершат задачу процесс перейдет к Exclusive gateway. Нам нужно следующее поведение: если хоть кто-либо выбрал вариант reject
, то переходим по переходу Rejected
, если все согласились (approve
), то по Approved
.
Задание условия в поле Flow outcome
Самым удобным вариантом задания условия, который подойдет для большинства случаев, является выбор имени outcome предыдущей задачи в свойстве Flow outcome стрелки перехода. Данный переход сработает, если было хотя бы одно завершение задачи с указанным outcome.
Задание сложных условий для перехода
Если необходимо иметь более сложные условия для перехода, то их можно задать в поле Flow condition. Например условие "Более 5 пользователей выбрали вариант `Reject`" будет выглядеть следующим образом:
${approval_result.count('reject') > 5}
3.6.1. Порядок обработки переходов
Обратите внимание, что необходимо задать порядок обработки переходов. Иначе Activiti может, например, обработать переход по умолчанию до переходов с явно заданными условиями. Для задания порядка вычисления условий установите свойство Flow order у узла Exclusive gateway.
3.7. Вызов скрипта
Для выполнения скрипта используется элемент Script task. При достижении элемента, система анализирует содержимое поля Script. Если содержимое является путем к файлу и данный файл существует, то система исполнит указанный файл. Если файла по указанному пути нет, то содержимое поля Script будет исполнено.
Внутри скрипта можно использовать объекты persistence
и metadata
.
3.8. Вызов методов бинов среднего слоя
Для вызова метода сервиса используется элемент Service task. Activiti Engine интегрирован со Spring Framework, т.е. возможно обращение к бинам среднего слоя по имени. Для вызова метода управляемого бина в поле Expression пишется выражение вида:
${beanName.methodName(processVarName, 'someStringParam')}
В качестве параметров вызова метода можно использовать процессные переменные, в том числе автоматически созданные при старте процесса (entityId, bpmProcInstanceId и т.д., как описано в ProcessRuntimeService).
3.9. Завершение задачи по таймеру
Для того, чтобы завершить задачу после истечения периода времени необходимо:
-
Добавить к элементу задачи элемент Boundary timer event.
-
От элемента таймера нарисовать переход к нужному этапу процесса.
-
В свойстве таймера Time duration написать выражение для периода времени. Например,
PT15M
(15 минут). -
Установить флажок Cancel activity, чтобы по срабатыванию таймера текущая задача завершилась.
-
В свойстве Timer outcome указать имя выхода задачи, которое должно быть использовано при завершении по таймеру.

<boundaryEvent id="managerApprovalTimer" cancelActivity="true" attachedToRef="managerApproval">
<extensionElements>
<cuba:outcome>approve</cuba:outcome>
</extensionElements>
</boundaryEvent>
Note
|
По умолчанию Job executor для обработки заданий таймеров отключен. Для его включения установите свойство приложения bpm.activiti.asyncExecutorEnabled = true . |
3.10. Локализация
Процесс может содержать локализованные сообщения, которые будут использованы при отображении в пользовательском интерфейсе имен задач, выходов из задач и т.д.
Для открытия экрана задания локализованных значений выберите свойство Localization модели.
Для локализации имени задачи необходимо создать запись, ключом которой является id задачи.
Для локализации имени выхода из задачи необходимо создать запись, ключом которой является выражение вида TASK_ID.OUTCOME_NAME
.
Для локализации имени процессной роли необходимо создать запись, ключом которой является код роли.
<process id="testProcess" name="Test process">
<extensionElements>
<cuba:localizations>
<cuba:localization lang="en">
<cuba:msg key="key1" value="value1"/>
<cuba:msg key="key2" value="value2"/>
</cuba:localization>
<cuba:localization lang="ru">
<cuba:msg key="key1" value="value1"/>
<cuba:msg key="key2" value="value2"/>
</cuba:localization>
</cuba:localizations>
</extensionElements>
</process>
3.11. Подмодели
Узел Sub model группы Structural позволяет использовать существующую модель в качестве части новой модели. При развертывании процесса из модели элементы подмодели вставляются в текущую модель, и из результата этой операции формируется XML с процессом.
3.12. Создание элементов для дизайнера модели
Подсистема BPM позволяет создавать собственные элементы для дизайнера моделей процесса. Новый элемент - это по сути ServiceTask
, избавляющий разработчика модели от необходимости вводить длинные выражения для вызова метода, такие как ${app_MyBean.someMethod(argument1, 'argument2')}
. Ниже приведен пример создания элемента.
Предположим, в системе имеется бин среднего слоя с именем app_DiscountManager
. В бине имеется метод makeDiscount(BigDecimal discountPercent, UUID entityId)
. Метод обновляет стоимость договора, вычитая из нее указанную скидку.
В этом примере мы создадим кастомный элемент, который будет вызывать указанный выше метод, а процент скидки будет задаваться в редакторе модели как параметр элемента.
Откройте редактор элементов с помощью пункта меню BPM → Model Elements Editor.
Нажмите на кнопку Add group. Введите имя группы - Discounts.

Выделите созданную группу Discounts и нажмите кнопку Add element.

Введите следующие значения в поля редактирования свойств элемента:
-
Title: Contract discount
-
Element ID: contractDiscount
-
Icon: нажмите на кнопку Upload и выберите файл с икнонкой (опционально)
-
Bean name: выберите app_DiscountManager
-
Method name: выберите makeDiscount
Warning
|
Выпадающий список Bean name содержит только бины, реализующие какой-либо интерфейс. В списке Method name отображаются методы реализуемых интерфейсов. |
В таблице Method arguments отображаются аргументы метода. Вы можете изменить заголовок и значение по умолчанию для каждого из аргументов.
Сохраните набор элементов, нажав на кнопку Save.
Откройте редактор модели (BPM → Process Models). В списке элементов появилась группа Discounts и элемент Contract discount. Перетащите новый элемент на экран и выделите его. Видим, что в панели свойств появились поля для ввода значений процента скидки и имени процессной переменной с идентификатором сущности.

Tip
|
entityId - это имя процессной переменной, которая автоматически добавляется во все процессы, связанные с сущностью. Она хранит идентификатор связанной сущности, вы можете использовать ее в вызовах любых методов. |
При развертывании процесса, кастомный элемент будет преобразован в serviceTask:
<serviceTask id="sid-5C184F22-6071-45CD-AEA9-1792512BBDCE" name="Make discount" activiti:expression="${app_DiscountManager.makeDiscount(10,entityId)}"></serviceTask>
Набор элементов модели может быть экспортирован в ZIP-архив и соответственно восстановлен из архива. Это полезно при разработке, когда элементы создаются на машине разработчика, а затем импортируются на продакшн-сервер. Импорт и экспорт осуществляеются с помощью соответствующих кнопок в редакторе элементов модели.
Нажатие на кнопку Reset удаляет все группы и элементы, созданные разработчиком, и возвращает набор элементов в исходное состояние.
4. Основные сервисы
Раздел содержит общее описание сервисов. Описание методов сервисов находится в Javadoc в исходном коде классов.
4.1. ProcessRepositoryService
Служит для работы с описаниями процесса (ProcDefinition
). Сервис использутся для:
-
загрузки описания процесса из XML;
-
удаления процесса из Activiti engine;
-
преобразования JSON модели в BPMN XML.
Для доступа к функциональности сервиса в коде middleware используйте ProcessRepositoryManager
.
4.2. ProcessRuntimeService
Служит для работы с экземпляром процесса (ProcInstance
). Методы сервиса позволяют:
-
запускать процесс;
-
отменять процесс;
-
завершать задачу;
-
назначать задачу на пользователя.
При запуске процесса автоматически создаются следующие процессные переменные:
-
bpmProcInstanceId
- ID экземпляра ProcInstance -
entityName
- имя связанной сущности -
entityId
- идентификатор связанной сущности
Для доступа к функциональности сервиса в коде middleware используйте ProcessRuntimeManager
.
4.3. ProcessFormService
Служит для получения информации о выходах из задачи, формах, которые должны быть показаны пользователю при выборе того или иного выхода, а также для получения информации о формах старта и отмены процесса.
Для доступа к функциональности сервиса в коде middleware используйте ProcessFormManager
.
5. Компоненты UI
5.1. ProcActionsFrame
ProcActionsFrame
- фрейм для работы с процессными действиями. После инициализации во фрейме автоматически отобразятся:
-
кнопка запуска процесса, если процесс не запущен;
-
кнопки, соответствующие выходам из задачи, если процесс запущен и текущий пользователь имеет активную задачу;
-
кнопка отмены процесса;
-
информация о задаче (имя и дата создания).
Каждому из действий возможно задать предикат, вычисляемый перед выполнением этого действия, что позволяет сделать проверку возможности выполнения действия в настоящее время (например, выполнить коммит экрана и в случае неудачи не выполнять процессное действие). Также можно задать слушатель, который будет выполнен после завершения действия (например, закрыть экран редактирования сущности и отобразить сообщение пользователю).
ProcActionsFrame
должен быть связан с экземпляром ProcInstance
. Связывание происходит во время инициализации фрейма.
procActionsFrame.initializer()
.setBeforeStartProcessPredicate(this::commit)
.setAfterStartProcessListener(showNotification(getMessage("processStarted"), NotificationType.HUMANIZED))
.init(PROCESS_CODE, entity);
-
Метод
initializer()
возвращает объект, используемый для инициализации фрейма. -
Метод
setBeforeStartProcessPredicate
устанавливает предикат, который будет вычислен перед выполнением запуска процесса. Если предикат вернетfalse
, запуск процесса будет прерван. -
Метод
setAfterStartProcessListener
задает слушатель, который будет вызыван после завершения действия запуска процесса. -
Самое главное - метод
init
принимает два параметра: код процесса и экземпляр сущности. При вызове этого метода происходит поиск объектаProcInstance
, связанного с указанным экземпляром сущности и ссылающимся наProcDefinition
с указанным кодом. ЕслиProcInstance
существует, фрейм связывается с ним, если нет, то создается новый объектProcInstance
.
О прочих методах, выполняющих настройку фрейма, можно узнать из javadoc класса.
5.2. Процессные формы
5.2.1. Интерфейс ProcForm
При определении выхода (outcome) для задачи и в узле начала процесса возможно указать форму, которая будет показана пользователю. Форма должна реализовывать интерфейс ProcForm
. Методы интерфейса ProcForm
:
-
String getComment()
- значение, возвращаемое этим методом, будет записано в полеcomment
объекта ProcTask при завершении задачи или в полеstartComment
у ProcInstance если форма отображается на старте процесса; -
Map<String, Object> getFormResult()
- возвращает список объектов, которые будут добавлены к переменным процесса после коммита формы.
5.2.2. Задание списка форм для дизайнера моделей процесса
Список форм, доступный в дизайнере моделей процесса, формируется на основе конфигурационных файлов, определенных свойством приложения bpm.formsConfig
. Для добавления новой процессной формы выполните следующие шаги:
-
Создайте и зарегистрируйте экран для новой формы. Контроллер экрана должен реализовывать интерфейс
ProcForm
. -
Создайте файл с конфигурацией для новых процессных форм. Положите его в директорию
src
модуля web или gui.app-bpm-forms.xml<?xml version="1.0" encoding="UTF-8"?> <forms xmlns="http://schemas.haulmont.com/cuba/bpm-forms.xsd"> <form name="myCustomForm" defult="true"> <param name="someParam" value="hello"/> <param name="otherParam"/> </form> </forms>
myCustomForm
- идентификатор экрана.Помимо имени форм в файле определены имена возможных параметров формы и их значения по умолчанию.
Форма с атрибутом
default="true"
будет использоваться в дизайнере как форма по умолчанию. -
Переопределите свойство
bpm.formsConfig
в файлеweb-app.properties
.bpm.formsConfig = bpm-forms.xml app-bpm-forms.xml