Предисловие
Данный документ является руководством по применению подсистемы исполнения бизнес-процессов (business process management, BPM) платформы CUBA.
Целевая аудитория
Данное руководство предназначено для разработчиков приложений на платформе CUBA. Предполагается, что читатель ознакомлен с Руководством по разработке приложений.
Дополнительные материалы
Настоящее Руководство, а также другая документация по платформе CUBA доступны по адресу https://www.cuba-platform.ru/documentation.
Подсистема исполнения бизнес-процессов CUBA основана на фреймворке Activiti, поэтому знакомство с его устройством будет полезным. См. http://www.activiti.org. Исполняемый процесс описывается в нотации BPMN 2.0, поэтому разработчик должен быть знаком с данной нотацией. См. http://www.bpmn.org.
Обратная связь
Если у Вас имеются предложения по улучшению данного руководства, мы будем рады принять ваши pull request’ы и issues в исходниках документации на GitHub. Если вы увидели ошибку или несоответствие в документе, пожалуйста, форкните репозиторий и исправьте проблему. Заранее спасибо!
1. Установка
Для добавления аддона в проект выполните следующие действия:
-
Кликните дважды Add-ons в дереве проекта CUBA.
-
Перейдите на вкладку Marketplace и найдите аддон Business Process Management.
-
Кликните Install и затем Apply & Close.
-
Кликните Continue в появившемся диалоговом окне.
Аддон, соответствующий используемой версии платформы, будет добавлен в проект.
2. Быстрый старт
В данной главе рассмотрим создание небольшого проекта, демонстрирующего работу с бизнес-процессами. Задача − реализовать процесс согласования договора. Процесс согласования выглядит следующим образом:
-
Пользователь создает объект
Contract
, назначает участников процесса и запускает процесс согласования. -
Участник с ролью
Controller
получает задачу проверить приложенный договор на корректность заполнения. -
Если проверка пройдена, то договор попадает к нескольким пользователям с ролью
Manager
, если нет, то процесс завершается, а договору проставляется статусNot valid
. -
После утверждения или отклонения договора менеджерами договор принимает состояния
Approved
илиNot approved
.
2.1. Создание проекта
-
Создайте новый проект в CUBA Studio, как это описано в разделе Создание нового проекта в Руководстве пользователя CUBA Studio:
-
Project namespace:
bpmdemo
-
Root package:
com.company.bpmdemo
-
-
Добавьте в проект аддон, как это описано в разделе Установка.
-
Создайте базу данных на локальном сервере HyperSQL. Выберите пункт меню CUBA > Create database.
2.2. Создание модели данных
Создадим сущность Договор
(Contract
).
-
Перейдите в секцию Data Model дерева CUBA и нажмите New > Entity в её контекстном меню. Появится диалоговое окно New CUBA Entity.
-
В поле Entity name введите название класса сущности –
Contract
. Нажмите OK. В рабочей области откроется страница дизайнера сущности. -
Добавьте атрибуты с помощью дизайнера сущности:
-
number
типаString
; -
date
типаDate
; -
state
типаString
.
-
На этом создание сущности Contract
завершено.
2.3. Создание стандартных экранов
Создадим экраны приложения, позволяющие управлять информацией о договорах.
-
Выделите сущность
Contract
в секции Data Model и в контекстном меню сущности нажмите New > Screen. После этого на экране отобразится страница создания стандартных экранов сущности. -
Для создания стандартных экранов просмотра и редактирования в списке доступных шаблонов выберите Entity browser and editor screens и нажмите Next.
-
Оставьте значения по умолчанию, нажмите Next и в следующем окне - Finish.
В разделе Screens секции Generic UI появятся файлы созданных экранов:
contract-browse.xml
– дескриптор экрана просмотра,
ContractBrowse
– контроллер экрана просмотра,
contract-edit.xml
– дескриптор экрана редактирования,
ContractEdit
– контроллер экрана редактирования.
2.4. Бин ApprovalHelper
Создайте бин ApprovalHelper
, как это описано в разделе Создание бинов. Замените содержимое файла на следующий код:
package com.company.bpmdemo.core;
import org.springframework.stereotype.Component;
import com.company.bpmdemo.entity.Contract;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import javax.inject.Inject;
import java.util.UUID;
@Component(ApprovalHelper.NAME)
public class ApprovalHelper {
public static final String NAME = "demo_ApprovalHelper";
@Inject
private Persistence persistence;
public void updateState(UUID entityId, String state) {
try (Transaction tx = persistence.getTransaction()) {
Contract contract = persistence.getEntityManager().find(Contract.class, entityId);
if (contract != null) {
contract.setState(state);
}
tx.commit();
}
}
}
Метод updateState()
бина ApprovalHelper
будет вызываться из процесса согласования для установки состояния договора.
Параметры метода:
-
entityId
– идентификатор сущности договора; -
state
– состояние договора.
2.5. Создание базы данных и запуск приложения
-
Для создания таблиц базы данных нажмите Generate Database Scripts в главном меню CUBA. После этого откроется страница Database Scripts.
-
Для сохранения сгенерированных скриптов нажмите на кнопку Save and close.
-
Для запуска скриптов обновления выполните CUBA > Update database.
-
Посмотрим, как созданные нами экраны выглядят в работающем приложении. Для этого нажмите на кнопку в главной панели инструментов.
-
Откройте приложение в браузере по адресу http://localhost:8080/app. Вы также можете открыть запущенное приложение в браузере, используя элемент дерева CUBA Runs At…
-
Авторизуйтесь, используя логин и пароль пользователя −
admin
/admin
. Пользователь Администратор имеет все права доступа. При создании другого пользователя, который будет моделировать процессы, необходимо назначить ему роль bpm-process-admin.
Запущенное приложение содержит пункты меню BPM, Application, Administration и Help, функциональность подсистемы безопасности и администрирования системы.
2.6. Создание процесса
В этом разделе мы создадим и развернём процесс.
2.6.1. Создание модели процесса
Конечная версия модели процесса будет выглядеть следующим образом:
Рассмотрим последовательность шагов для создания модели.
В веб-интерфейсе запущенного приложения откройте экран BPM > Process models и нажмите Create. Введите имя модели Contract approval
и нажмите OK. Откроется новая вкладка браузера Model editor.
Tip
|
При создании или копировании модели процесса появляется уведомление со ссылкой для перехода. При нажатии на кнопку Edit редактор модели процесса открывается в новой вкладке браузера. |
В панели свойств модели выберите свойство Process roles. Откроется окно редактирования процессных ролей.
В процессе должно быть два типа участников: контролер и менеджер. Создайте две роли: 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.
Warning
|
Для перехода, помеченного как Default flow, значение в выпадающем списке Flow outcome должно быть пустым. |
Далее выделите Exclusive gateway и откройте редактор свойства Flow order. Убедитесь, что переход Not valid
стоит первым в списке. Если это не так, измените порядок обработки переходов.
Перейдем к узлу Set 'Not valid' state
. Нам необходимо установить значение свойства state
сущности Contract
в Not valid
. Выделите узел. В поле свойства Script format введите groovy
, т.к. мы будем писать groovy-скрипт. Нажмите на поле свойства Script узла. Откроется окно редактирования скрипта. Скопируйте и вставьте туда следующий текст:
import com.company.bpmdemo.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 последние созданные задачи, нажмите кнопку сохранения модели − модель готова. Перейдем к её развёртыванию.
2.6.2. Развёртывание модели процесса
Процесс развёртывания модели состоит из следующих этапов:
-
Формирование XML процесса в нотации BPMN из модели.
-
Развёртывание процесса во внутренние таблицы Activiti Engine.
-
Создание объекта
ProcDefinition
, связанного с загруженным в Activiti Engine процессом. -
Создание объектов
ProcRole
для процессных ролей, объявленных в модели.
Выделите модель в списке на экране Process models. Нажмите кнопку Deploy. Откроется окно развёртывания модели. Модель разворачивается первый раз, поэтому выбрана опция Create new process. При последующих изменениях модели можно будет разворачивать модель в уже существующий процесс. Нажмите OK. Процесс создан.
Откройте экран BPM > Process definitions. Откройте строку с Contract approval
для редактирования. Поле Code имеет значение contractApproval
. Запомните его. В дальнейшем мы используем это значение, чтобы идентифицировать процесс.
2.7. Адаптация экранов к процессу
В данном разделе мы добавим в экран редактирования договора возможность работы с процессом согласования.
2.7.1. Компоновка экрана редактирования договора
Откройте файл contract-edit.xml
в студии и полностью замените его содержимое на следующий код:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
caption="msg://editorCaption"
focusComponent="form"
messagesPack="com.company.bpmdemo.web.screens.contract">
<data>
<instance id="contractDc"
class="com.company.bpmdemo.entity.Contract"
view="_local">
<loader id="contractDl"/>
</instance>
<collection id="procAttachmentsDc"
class="com.haulmont.bpm.entity.ProcAttachment"
view="procAttachment-browse">
<loader id="procAttachmentsDl">
<query><![CDATA[select e from bpm$ProcAttachment e
where e.procInstance.entity.entityId = :entityId
order by e.createTs]]>
</query>
</loader>
</collection>
</data>
<dialogMode height="600"
width="800"/>
<layout expand="editActions"
spacing="true">
<form id="form"
dataContainer="contractDc">
<column width="250px">
<textField id="numberField"
property="number"/>
<dateField id="dateField"
property="date"/>
<textField id="stateField"
property="state"/>
</column>
</form>
<groupBox id="procActionsBox"
caption="msg://process"
spacing="true"
width="AUTO"
orientation="vertical">
<fragment id="procActionsFragment"
screen="bpm_ProcActionsFragment"/>
</groupBox>
<groupBox caption="msg://attachments"
height="300px"
spacing="true"
width="700px">
<table id="attachmentsTable"
dataContainer="procAttachmentsDc"
height="100%"
width="100%">
<columns>
<column id="file.name"/>
<column id="author"/>
<column id="type"/>
<column id="comment"
maxTextLength="50"/>
</columns>
</table>
</groupBox>
<hbox id="editActions"
spacing="true">
<button action="windowCommitAndClose"/>
<button action="windowCommit"/>
<button action="windowClose"/>
</hbox>
</layout>
</window>
Экран содержит группу полей для редактирования самого договора, фрагмент для отображения действий по процессу и таблицу с вложениями, созданными во время выполнения процесса.
2.7.2. Контроллер экрана редактирования договора
Откройте экран ContractEdit
и замените его содержимое на следующий код:
package com.company.bpmdemo.web.screens.contract;
import com.haulmont.bpm.entity.ProcAttachment;
import com.haulmont.bpm.gui.procactionsfragment.ProcActionsFragment;
import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper;
import com.haulmont.cuba.gui.components.Table;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.model.InstanceLoader;
import com.haulmont.cuba.gui.screen.*;
import com.company.bpmdemo.entity.Contract;
import javax.inject.Inject;
@UiController("bpmdemo_Contract.edit")
@UiDescriptor("contract-edit.xml")
@EditedEntityContainer("contractDc")
public class ContractEdit extends StandardEditor<Contract> {
private static final String PROCESS_CODE = "contractApproval";
@Inject
private CollectionLoader<ProcAttachment> procAttachmentsDl;
@Inject
private InstanceContainer<Contract> contractDc;
@Inject
protected ProcActionsFragment procActionsFragment;
@Inject
private Table<ProcAttachment> attachmentsTable;
@Inject
private InstanceLoader<Contract> contractDl;
@Subscribe
private void onBeforeShow(BeforeShowEvent event) {
contractDl.load();
procAttachmentsDl.setParameter("entityId",contractDc.getItem().getId());
procAttachmentsDl.load();
procActionsFragment.initializer()
.standard()
.init(PROCESS_CODE, getEditedEntity());
FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file");
}
}
Рассмотрим код контроллера более подробно.
ProcessActionsFragment
− это стандартный фрагмент для отображения кнопок доступных в данный момент процессных действий. Во время инициализации фрагмента по двум параметрам (код процесса и сущность) ищется связанный экземпляр ProcInstance
. Если процесс не находится, то создается новый ProcInstance
, и фрагмент отображает кнопку запуска процесса. Если связанный с сущностью процесс найден, то проверяется, имеется ли незавершенная процессная задача для текущего пользователя, и если да, то отображаются кнопки завершения данной задачи. Подробнее о ProcActionsFragment
см. ProcActionsFragment.
В контроллере редактора договора инициализация фрагмента процессных действий происходит в методе onBeforeShow()
. Самая важная часть метода − вызов init(PROCESS_CODE, getEditedEntity())
. Константа PROCESS_CODE
хранит код процесса (contractApproval
− это значение мы видели при развёртывании процесса, см. Развёртывание модели процесса). Второй аргумент getEditedEntity()
− текущий договор.
Стандартная инициализация фрагмента (standard()
) делает следующее:
-
Инициализирует стандартные предикаты для действий запуска процесса и завершения задачи. Предикаты вызывают коммит редактора договора.
-
Инициализирует стандартные слушатели, вызываемые после завершения процессного действия. Данные слушатели отображают уведомления ("Процесс запущен", "Действие выполнено", и т.п.), а также обновляют
procActionsFragment
.
2.8. Работа с приложением
По умолчанию в Cuba Studio включен механизм Hot Deploy, и изменения в экране редактирования договора уже должны быть отправлены на сервер. Если Hot Deploy у вас был отключен, то перезапустите сервер, выполнив в Studio команду CUBA > Restart Application Server.
2.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
Назначьте роль bpm-process-actor, чтобы предоставить доступ к сущностям BPM.
Создайте роль quick-start и предоставьте ей необходимые для работы с проектом разрешения:
-
Перейдите на экран Administration → Roles и создайте новую роль с именем quick-start.
-
На вкладке Screens разрешите доступ к экрану BPM → Process Tasks. Этот экран не доступен в роли bpm-process-actor, так как может понадобиться не всем проектам.
-
Разрешите доступ к экранам:
-
Application →Contracts
-
bpm$ProcTask.edit
-
bpmdemo_Contract.edit
-
bpm$ProcInstance.edit
-
-
Разрешите bpmdemo_Contract во вкладках Entities и Attributes.
-
Сохраните роль.
Назначьте роль quick-start созданным ранее пользователям.
2.8.2. Создание договора и запуск процесса
-
Откройте список договоров Application > Contracts и создайте новый договор. Заполните поля Number и Date и нажмите кнопку Save.
-
Нажмите на кнопку Start process. Перед вами появится форма запуска процесса. При создании модели для узла Start event мы указали форму
Standard form
с атрибутамиprocActorsVisible=true
иattachmentsVisible=true
, поэтому сейчас перед нами форма с компонентами для указания участников процесса и добавления вложений. -
Введите комментарий для процесса, добавьте участников: контролер
norman
и два менеджера:pierce
иroberts
. -
Загрузите вложение к договору, нажав на кнопку Upload таблицы Attachments.
Рисунок 17. Форма запуска процесса -
Нажмите ОК − процесс запущен.
2.8.3. Этап проверки контролером
Зайдите в систему под пользователем norman
.
При достижении процессом узла User task создается объект ProcTask
, связанный с определенным участником процесса. В подсистеме BPM есть экран для отображения списка невыполненных задач для текущего пользователя. Откройте его: BPM > Process tasks.
Видим, что для пользователя norman
есть одна задача Validation
по процессу Contract approval
. Выделите ее и нажмите кнопку Open entity editor - откроется экран редактирования договора.
Так как для текущего пользователя (norman
) имеется незавершенная задача (ProcTask
), то procActionsFragment
отображает доступные действия. Когда мы описывали узел UserTask с именем Validation
, то мы указали для него два возможных выхода Valid
и Not valid
. На основании этой информации в фрагмент и добавлено две кнопки.
Нажмите на Valid. В открывшемся окне введите комментарий:
Нажмите OK.
После успешной валидации договор должен уйти к менеджерам на параллельное согласование.
2.8.4. Этап утверждения менеджерами
Войдите в систему под пользователем pierce
.
Откройте список текущих задач BPM > Process tasks. Имеется одна задача Approval
.
Выделите ее и на этот раз нажмите кнопку Open process instance − откроется системный экран для работы с экземпляром ProcInstance
.
В нем отображается информация о времени запуска процесса, инициаторе процесса, список вложений, участников, текущих и выполненных задач в рамках данного процесса. Также экран позволяет перейти к связанной сущности и выполнить процессное действие.
Обратите внимание на таблицу Tasks. Предыдущая задача Validation
завершена с результатом Valid
, и после успешной валидации контролером создались две новые задачи Approval
на менеджеров pierce
и roberts
.
Утвердите договор, воспользовавшись кнопкой Approve.
Далее войдите в систему под пользователем roberts
. Откройте договор из списка Application > Contracts.
Пользователь roberts
имеет незавершенную задачу по договору, следовательно, фрагмент procActionsFragment
отображает для него действия Approve и Reject. Нажмите кнопку Reject.
Так как при описании выхода Reject
в дизайнере мы указали параметр формы commentRequired=true
, то комментарий в форме завершения данного действия обязателен. Введите комментарий и нажмите ОК.
Один из менеджеров отклонил договор, поэтому ему должно установиться состояние Not approved
. Проверим это, открыв договор.
Процесс согласования завершен с состоянием Not approved
.
3. Модель данных
Tip
|
Атрибуты, имена которых начинаются с префикса |
-
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
(см. Переходы в зависимости от выхода задачи). -
cancelled
– признак, что задача была завершена при отмене процесса. -
candidateUsers
– список возможных участников для групповой задачи. -
procInstance
– ссылка на экземпляр процесса.
-
-
ProcAttachment
– процессное вложение. Атрибуты сущности:-
file
– ссылка наFileDescriptor
. -
type
– тип вложения (ProcAttachmentType
). -
comment
– комментарий. -
author
– автор вложения, ссылка на пользователя. -
procInstance
– ссылка на экземпляр процесса. -
procTask
– необязательная ссылка на задачу, в рамках которой было добавлено вложение.
-
-
ProcAttachmentType
– тип вложения. Атрибуты сущности:-
code
– код типа вложения. -
name
– имя типа вложения.
-
4. Функциональность
Для исполнения бизнес-процессов подсистема BPM использует Activiti Engine, для моделирования процессов в нотации BPMN используется доработанный редактор из веб-приложения Activiti Explorer. В дополнение к возможностям фреймворка Activiti подсистема BPM предоставляет дополнительную функциональность, которая описана далее в этом разделе. Перечисление возможностей фреймворка Activiti не входит в задачу данного руководства. Вы можете ознакомиться с ними на сайте Activiti: https://www.activiti.org/userguide/.
4.1. BpmActivitiListener
Слушатель событий BpmActivitiListener
автоматически добавляется в процесс при создании модели процесса. BpmActivitiListener
является реализацией интерфейса ActivitiEventListener
(см. http://www.activiti.org/userguide/#eventDispatcher). Слушатель отвечает за создание и изменение сущностей подсистемы BPM при наступлении определенных событий процесса (вход в пользовательскую задачу, отмена процесса, завершение задачи, и т.д.). Именно он создает объекты ProcTask
и проставляет значение endDate
для ProcInstance
.
4.2. Процессные роли (Process Roles)
Процессные роли определяют типы участников процесса, например, оператор или менеджер. Чтобы открыть экран редактирования процессных ролей в дизайнере процессов, выберите свойство 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>
4.3. Форма запуска процесса (Process Form)
Для задания формы, которая будет отображаться при запуске процесса, используется свойство Start form элемента Start event. Подробнее о формах см. Процессные формы (ProcForm).
<startEvent id="startEvent">
<extensionElements>
<cuba:form name="standardProcForm">
<cuba:param name="procActorsVisible" value="true"></cuba:param>
</cuba:form>
</extensionElements>
</startEvent>
4.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
.
Задать участника возможно и не указывая его заранее в объекте ProcActor
, а задав в свойстве assignee элемента User Task. Свойство может содержать строку с идентификатором пользователя CUBA: da8159cc-757f-7f59-6579-7f629ac02f72
, переменную процесса, которая содержит строку с идентификатором пользователя: ${varialbeName}
, или выражение вызова сервиса, который возвращает строку с идентификатором пользователя: ${someService.getSomeUserId()}
. При этом свойство procRole элемента UserTask должно быть заполнено. Когда процесс достигает подобной задачи, сначала ищется объект ProcActor
с указанным пользователем и процессной ролью. Если ProcActor
не найден, будет создан новый экземпляр ProcActor
. Для задания свойства assignee в редакторе моделей выделите элемент User Task, кликните на ссылку Show advanced properties, откройте редактор свойства Assignments и в появившемся диалоге заполните поле Assignee.
Также возможен вариант, когда задача не должна быть сразу назначена на пользователя, а должна появиться в списке доступных, и один из пользователей должен забрать задачу себе. Для определения такой задачи необходимо установить флажок Claim allowed. В этом случае задача появится в списке доступных у всех участников процесса с ролью, заданной в свойстве Process role.
<userTask id="managerApproval" name="Manager approval">
<extensionElements>
<cuba:claimAllowed>true</cuba:claimAllowed>
</extensionElements>
</process>
4.5. Выходы задачи (Outcomes)
Обычно задача требует от пользователя принятия решения (например, согласовать или отклонить). Дальнейший маршрут по процессу зависит от принятого решения. Для задания списка выходов из задачи используется свойство Task outcomes узла User Task. Для каждого выхода задается имя и выбирается форма, которая должна отображаться при выборе этого выхода, а также список параметров формы (подробнее о формах см. Процессные формы (ProcForm)).
<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>
4.6. Переходы в зависимости от выхода задачи
В отличие от 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}
4.6.1. Порядок обработки переходов
Обратите внимание, что необходимо задать порядок обработки переходов. Иначе Activiti может, например, обработать переход по умолчанию до переходов с явно заданными условиями. Для задания порядка вычисления условий установите свойство Flow order у узла Exclusive gateway.
4.7. Вызов скрипта
Для выполнения скрипта используется элемент Script task. При достижении элемента система анализирует содержимое поля Script. Если содержимое является путем к файлу, и данный файл существует, то система исполнит указанный файл. Если файла по указанному пути нет, то содержимое поля Script будет исполнено.
Внутри скрипта можно использовать объекты persistence
и metadata
.
4.8. Вызов методов бинов среднего слоя
Для вызова метода сервиса используется элемент Service task. Activiti Engine интегрирован со Spring Framework, т.е. возможно обращение к бинам среднего слоя по имени. Для вызова метода Spring-бина в поле Expression пишется выражение вида:
${beanName.methodName(processVarName, 'someStringParam')}
В качестве параметров вызова метода можно использовать процессные переменные, в том числе автоматически созданные при старте процесса (entityId
, bpmProcInstanceId
и т.д., как описано в ProcessRuntimeService).
4.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>
Tip
|
По умолчанию Job executor для обработки заданий таймеров отключен. Для его включения установите свойство приложения |
4.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>
4.11. Подмодели
Узел Sub model группы Structural позволяет использовать существующую модель в качестве части новой модели. При развертывании процесса из модели элементы подмодели вставляются в текущую модель, и из результата этой операции формируется XML с процессом.
4.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
-
Stencil 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
|
|
При развёртывании процесса пользовательский элемент будет преобразован в serviceTask
:
<serviceTask id="sid-5C184F22-6071-45CD-AEA9-1792512BBDCE" name="Make discount" activiti:expression="${app_DiscountManager.makeDiscount(10,entityId)}"></serviceTask>
Набор элементов модели может быть экспортирован в ZIP-архив и, соответственно, восстановлен из архива. Это полезно при разработке, когда элементы создаются на машине разработчика, а затем импортируются на продакшн-сервер. Импорт и экспорт осуществляются с помощью соответствующих кнопок в редакторе элементов модели.
Нажатие на кнопку Reset удаляет все группы и элементы, созданные разработчиком, и возвращает набор элементов в исходное состояние.
5. Основные сервисы
Раздел содержит общее описание сервисов. Описание методов сервисов находится в Javadoc в исходном коде классов.
5.1. ProcessRepositoryService
Служит для работы с описаниями процесса (ProcDefinition
). Сервис используется для:
-
загрузки описания процесса из XML;
-
удаления процесса из Activiti engine;
-
преобразования JSON модели в BPMN XML.
Для доступа к функциональности сервиса в коде middleware используйте ProcessRepositoryManager
бин.
5.2. ProcessRuntimeService
Служит для работы с экземпляром процесса (ProcInstance
). Методы сервиса позволяют:
-
отменять процесс;
-
завершать задачу;
-
назначать задачу на пользователя.
При запуске процесса автоматически создаются следующие процессные переменные:
-
bpmProcInstanceId
– ID экземпляраProcInstance
; -
entityName
– имя связанной сущности; -
entityId
– идентификатор связанной сущности.
Для доступа к функциональности сервиса в коде middleware используйте ProcessRuntimeManager
бин.
5.3. ProcessFormService
Служит для получения информации о выходах из задачи, формах, которые должны быть показаны пользователю при выборе того или иного выхода, а также для получения информации о формах старта и отмены процесса.
Для доступа к функциональности сервиса в коде middleware используйте ProcessFormManager
бин.
6. Компоненты UI
В этом разделе описаны компоненты пользовательского интерфейса подсистемы BPM.
6.1. ProcActionsFragment
ProcActionsFragment
– фрагмент для работы с процессными действиями. После инициализации во фрагменте автоматически отобразятся:
-
кнопка запуска процесса, если процесс не запущен;
-
кнопки, соответствующие выходам из задачи, если процесс запущен, и текущий пользователь имеет активную задачу;
-
кнопка отмены процесса;
-
информация о задаче (имя и дата создания).
Каждому из действий возможно задать предикат, вычисляемый перед выполнением этого действия, что позволяет сделать проверку возможности выполнения действия в настоящее время (например, выполнить коммит экрана и в случае неудачи не выполнять процессное действие). Также можно задать слушатель, который будет выполнен после завершения действия (например, закрыть экран редактирования сущности и отобразить сообщение пользователю).
ProcActionsFragment
должен быть связан с экземпляром ProcInstance
. Связывание происходит во время инициализации фрагмента.
Пример инициализации фрагмента:
procActionsFragment.initializer()
.setBeforeStartProcessPredicate(() -> {
getScreenData().getDataContext().commit();
return true;
})
.setAfterStartProcessListener(() -> {
notifications.create()
.withCaption(messageBundle.getMessage("processStarted"))
.withType(Notifications.NotificationType.HUMANIZED)
.show();
})
.init(PROCESS_CODE, getEditedEntity());
-
Метод
initializer()
возвращает объект, используемый для инициализации фрагмента. -
Метод
setBeforeStartProcessPredicate
устанавливает предикат, который будет вычислен перед выполнением запуска процесса. Если предикат вернетfalse
, запуск процесса будет прерван. -
Метод
setAfterStartProcessListener
задает слушатель, который будет вызван после завершения действия запуска процесса. -
Самое главное – метод
init
– принимает два параметра: код процесса и экземпляр сущности. При вызове этого метода происходит поиск объектаProcInstance
, связанного с указанным экземпляром сущности и ссылающимся наProcDefinition
с указанным кодом. ЕслиProcInstance
существует, фрагмент связывается с ним, если нет, то создается новый объектProcInstance
.
Самый простой способ проинициализировать ProcActionsFragment
– использовать метод standard()
:
procActionsFragment.initializer()
.standard()
.init(PROCESS_CODE, getEditedEntity());
Стандартная инициализация делает следующее:
-
создает предикаты, которые вызывают коммит редактора сущности перед процессным действием запуска процесса или завершения задачи;
-
создает слушатели, показывающие сообщения "Процесс запущен", "Задача завершена" после процессного действия, а также вызывающие повторную инициализацию
ProcActionsFragment
.
Полный список методов, которые можно использовать для работы с фрагментом:
- Жизненный цикл процесса
-
-
initializer()
– возвращает экземпляр инициализатора фрагмента.
-
init()
– пробует найти экземпляр процесса по переданному коду и ссылке на сущность. Если существующий процесс не найден, создаётся новый. Затем происходит инициализация UI, отображающего действия, доступные для этого процесса и для текущего пользователя.
-
- Конфигурация процесса
-
-
setStartProcessEnabled()
– устанавливает разрешение или запрет на запуск процесса.
-
setCancelProcessEnabled()
– устанавливает разрешение или запрет на отмену процесса.
-
setCompleteTaskEnabled()
– устанавливает разрешение или запрет на завершение задачи.
-
setClaimTaskEnabled()
– устанавливает разрешение или запрет на выбор задачи самим пользователем.
-
setTaskInfoEnabled()
– управляет видимостью контейнера, отображающего локализованное имя задачи и дату её начала.
-
setButtonWidth()
– устанавливает ширину кнопок управления процессом. По умолчанию кнопки имеют ширину 150 px.
-
addActionButton()
– позволяет добавить к фрагменту дополнительную кнопку, помимо создаваемых автоматически.
-
- Предикаты
-
-
setBeforeStartProcessPredicate()
– устанавливает предикат, который должен быть вычислен перед запуском процесса. Если предикат вернётfalse
, запуск процесса будет прерван.
-
setBeforeCompleteTaskPredicate()
– устанавливает предикат, который должен быть вычислен перед завершением задачи. Если предикат вернётfalse
, завершение задачи будет прервано.
-
setBeforeClaimTaskPredicate()
– устанавливает предикат, который должен быть вычислен перед назначением задачи на пользователя. Если предикат вернётfalse
, назначение задачи будет прервано.
-
setBeforeCancelProcessPredicate()
– устанавливает предикат, который должен быть вычислен перед отменой задачи. Если предикат вернётfalse
, задача не будет отменена.
-
- Слушатели процесса и задач
-
-
setAfterStartProcessListener()
– задаёт слушатель, который будет вызван после запуска процесса.
-
setAfterCompleteTaskListener()
– задаёт слушатель, который будет вызван после завершения задачи.
-
setAfterClaimTaskListener()
– задаёт слушатель, который будет вызван после выбора задачи пользователем.
-
setAfterCancelProcessListener()
– задаёт слушатель, который будет вызван после отмены выполнения процесса.
-
- Поставщики переменных и параметров
-
-
setStartProcessActionProcessVariablesSupplier()
– задаёт поставщик переменных процесса. Поставщик возвращает мэп переменных процесса, которые должны быть добавлены к экземпляру процесса Activiti при запуске процесса.
-
setCompleteTaskActionProcessVariablesSupplier()
– задаёт поставщик переменных процесса. Поставщик возвращает мэп переменных процесса, которые должны быть добавлены к экземпляру процесса Activiti при завершении задачи.
-
setStartProcessActionScreenParametersSupplier()
– задаёт поставщик параметров экрана процессной формы. Поставщик возвращает мэп параметров экрана, которые должны быть переданы в процессную форму, отображаемую с помощьюStartProcessAction
.
-
setCompleteTaskActionScreenParametersSupplier()
– задаёт поставщик параметров экрана процессной формы. Поставщик возвращает мэп параметров экрана, которые должны быть переданы в процессную форму, отображаемую с помощьюCompleteTaskAction
.
-
- ProcActionsFragment API
- Initializer API
-
init – setAfterCancelProcessListener – setAfterClaimTaskListener – setAfterCompleteTaskListener – setAfterStartProcessListener – setBeforeCancelProcessPredicate – setBeforeClaimTaskPredicate – setBeforeCompleteTaskPredicate – setBeforeStartProcessPredicate – setButtonWidth – setCancelProcessEnabled – setClaimTaskEnabled – setCompleteTaskActionProcessVariablesSupplier – setCompleteTaskActionScreenParametersSupplier – setCompleteTaskEnabled – setStartProcessActionProcessVariablesSupplier – setStartProcessActionScreenParametersSupplier – setStartProcessEnabled – setTaskInfoEnabled
6.2. Процессные формы (ProcForm)
- Интерфейс ProcForm
-
При определении выхода (outcome) для задачи и в узле начала процесса возможно указать форму, которая будет показана пользователю. Форма должна реализовывать интерфейс
ProcForm
.Методы интерфейса
ProcForm
:-
getComment(): String
– значение, возвращаемое этим методом, будет записано в полеcomment
объектаProcTask
при завершении задачи или в полеstartComment
уProcInstance
, если форма отображается на старте процесса;
-
getFormResult(): Map<String, Object>
– возвращает список объектов, которые будут добавлены к переменным процесса после коммита формы.
-
- Задание списка форм для дизайнера моделей процесса
-
Список форм, доступный в дизайнере моделей процесса, формируется на основе конфигурационных файлов, определенных свойством приложения
bpm.formsConfig
. Для добавления новой процессной формы выполните следующие шаги:-
Создайте и зарегистрируйте экран для новой формы. Контроллер экрана должен реализовывать интерфейс
ProcForm
. -
Создайте файл, например
app-bpm-forms.xml
, для конфигурации новых процессных форм. Положите его в директориюsrc
модуля web или gui.<?xml version="1.0" encoding="UTF-8"?> <forms xmlns="http://schemas.haulmont.com/cuba/bpm-forms.xsd"> <form name="myCustomForm" default="true"> <param name="someParam" value="hello"/> <param name="otherParam"/> </form> </forms>
myCustomForm
– идентификатор экрана.Помимо имени форм в файле определены имена возможных параметров формы и их значения по умолчанию.
Форма с атрибутом
default="true"
будет использоваться в дизайнере как форма по умолчанию. -
Переопределите свойство
bpm.formsConfig
в файлеweb-app.properties
.bpm.formsConfig = com/haulmont/bpm/forms.xml app-bpm-forms.xml
-
- API
7. Примеры
7.1. Пример Task Execution
Пример показывает:
-
Как программно задать участников процесса на старте, используя
ProcActionsFragment
; -
Как передать процессные переменные на старте процесса, используя
ProcActionsFragment
; -
Как получить и изменить стандартные процессные действия, автоматически сгенерированные фрагментом
ProcActionsFragment
(например, изменить заголовок кнопки "Запустить процесс"); -
Как программно запустить процесс без использования
ProcActionsFragment
; -
Как с помощью
ActivitiEventListener
автоматически обновлять поле сущности (в данном примереprocessState
) при движении по процессу.
Пример использует модель Task execution – 1:
В текущем примере не используется StandardProcForm
. Для назначения участников процесса мы воспользуемся before start process predicate фрагмента ProcActionsFragment
. См. метод setBeforeStartProcessPredicate()
.
@UiController("bpmsamples$Task.edit")
@UiDescriptor("task-edit.xml")
@EditedEntityContainer("taskDc")
@LoadDataBeforeShow
public class TaskEdit extends StandardEditor<Task> {
public static final String PROCESS_CODE = "taskExecution-1";
@Inject
protected ProcActionsFragment procActionsFragment;
@Inject
protected BpmEntitiesService bpmEntitiesService;
@Inject
protected ProcessRuntimeService processRuntimeService;
@Inject
private MessageBundle messageBundle;
@Inject
private Notifications notifications;
@Inject
private Metadata metadata;
@Inject
private Messages messages;
@Inject
private InstanceLoader<Task> taskDl;
...
/** * Method starts the process without {@link ProcActionsFragment} */
@Subscribe("startProcessProgrammaticallyBtn")
private void onStartProcessProgrammaticallyBtnClick(Button.ClickEvent event) {
commitChanges()
.then(() -> {
/*The ProcInstanceDetails object is used for describing a ProcInstance to be created with its proc actors*/
BpmEntitiesService.ProcInstanceDetails procInstanceDetails = new BpmEntitiesService.ProcInstanceDetails(PROCESS_CODE)
.addProcActor("initiator", getEditedEntity().getInitiator())
.addProcActor("executor", getEditedEntity().getExecutor())
.setEntity(getEditedEntity());
/*The created ProcInstance will have two proc actors. None of the entities is persisted yet.*/
ProcInstance procInstance = bpmEntitiesService.createProcInstance(procInstanceDetails);
/*A map with process variables that must be passed to the Activiti process instance when it is started. This variable is used in the model to make a decision for one of gateways.*/
HashMap<String, Object> processVariables = new HashMap<>();
processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());
/*Starts the process. The "startProcess" method automatically persists the passed procInstance with its actors*/
processRuntimeService.startProcess(procInstance, "Process started programmatically", processVariables);
notifications.create()
.withCaption(messageBundle.getMessage("processStarted"))
.withType(Notifications.NotificationType.HUMANIZED)
.show();
/*refresh the procActionsFragment to display complete tasks buttons (if a process task appears for the current user after the process is started)*/
initProcActionsFragment();
});
}
private void initProcActionsFragment() {
procActionsFragment.initializer()
.standard()
.setBeforeStartProcessPredicate(() -> {
/*the predicate creates process actors and sets them to the process instance created by the ProcActionsFragment*/
if (commitChanges().getStatus() == OperationResult.Status.SUCCESS) {
ProcInstance procInstance = procActionsFragment.getProcInstance();
ProcActor initiatorProcActor = createProcActor("initiator", procInstance, getEditedEntity().getInitiator());
ProcActor executorProcActor = createProcActor("executor", procInstance, getEditedEntity().getExecutor());
Set<ProcActor> procActors = new HashSet<>();
procActors.add(initiatorProcActor);
procActors.add(executorProcActor);
procInstance.setProcActors(procActors);
return true;
}
return false;
})
.setStartProcessActionProcessVariablesSupplier(() -> {
/*the supplier returns a map with process variables that will be used by the Activiti process*/
Map<String, Object> processVariables = new HashMap<>();
processVariables.put("acceptanceRequired", getEditedEntity().getAcceptanceRequired());
return processVariables;
})
.setAfterStartProcessListener(() -> {
/*custom listener in addition to the standard behavior refreshes the "taskDs", because the process automatically updates the "processState" field of the "Task" entity.*/
notifications.create()
.withCaption(messages.getMessage(ProcActionsFragment.class,"processStarted"))
.withType(Notifications.NotificationType.HUMANIZED)
.show();
initProcActionsFragment();
taskDl.setEntityId(getEditedEntity().getId());
taskDl.load();
})
.setAfterCompleteTaskListener(() -> {
notifications.create()
.withCaption(messages.getMessage(ProcActionsFragment.class,"taskCompleted"))
.withType(Notifications.NotificationType.HUMANIZED)
.show();
initProcActionsFragment();
taskDl.setEntityId(getEditedEntity().getId());
taskDl.load();
})
.init(PROCESS_CODE, getEditedEntity());
}
private ProcActor createProcActor(String procRoleCode, ProcInstance procInstance, User user) {
ProcActor initiatorProcActor = metadata.create(ProcActor.class);
initiatorProcActor.setUser(user);
ProcRole initiatorProcRole = bpmEntitiesService.findProcRole(PROCESS_CODE, procRoleCode, View.MINIMAL);
initiatorProcActor.setProcRole(initiatorProcRole);
initiatorProcActor.setProcInstance(procInstance);
return initiatorProcActor;
}
/** * Method demonstrates how to get and modify process actions automatically created by the ProcActionsFragment */
private void changeStartProcessBtnCaption() {
StartProcessAction startProcessAction = procActionsFragment.getStartProcessAction();
if (startProcessAction != null) {
startProcessAction.setCaption("Start process using ProcActionsFragment");
}
}
}
Метод setStartProcessActionProcessVariablesSupplier()
демонстрирует, как передать процессные переменные на старте процесса с помощью ProcActionsFragment
. Процессная переменная acceptanceRequired
будет использоваться одним из gateway в процессе для принятия решения, должен ли процесс выполнения задачи завершиться, либо же задача должна уйти инициатору для утверждения.
Метод changeStartProcessBtnCaption()
показывает, как можно получить действие запуска процесса, которое было автоматически создано фрагментом ProcActionsFragment
, и поменять заголовок кнопки "Start process" на произвольный.
Метод onStartProcessProgrammaticallyBtnClick()
демонстрирует, как программно запустить процесс без использования фрагмента ProcActionsFragment
.
UpdateProcessStateListener.java
– это реализация интерфейса org.activiti.engine.delegate.event.ActivitiEventListener
. Данный слушатель зарегистрирован на уровне процесса. Он выполняет следующее: каждый раз, когда процесс входит в новое состояние, в поле processState
связанной с процессом сущности com.company.bpmsamples.entity.Task
проставляется имя текущего шага процесса.
/** * The listener updates the "processState" field of the {@link HasProcessState} with the name of current BPM process * node. This listener is used in the "taskExecution-1" BPM process */
public class UpdateProcessStateListener implements ActivitiEventListener {
private static final Logger log = LoggerFactory.getLogger(UpdateProcessStateListener.class);
private Metadata metadata;
public UpdateProcessStateListener() {
metadata = AppBeans.get(Metadata.class);
}
@Override
public void onEvent(ActivitiEvent event) {
RuntimeService runtimeService = event.getEngineServices().getRuntimeService();
String executionId = event.getExecutionId();
UUID entityId = (UUID) runtimeService.getVariable(executionId, "entityId");
String entityName = (String) runtimeService.getVariable(executionId, "entityName");
if (entityId == null) {
log.error("Cannot update process state. entityId variable is null");
return;
}
if (Strings.isNullOrEmpty(entityName)) {
log.error("Cannot update process state. entityName variable is null");
return;
}
MetaClass metaClass = metadata.getClass(entityName);
if (metaClass == null) {
log.error("Cannot update process state. MetaClass {} not found", entityName);
return;
}
if (!HasProcessState.class.isAssignableFrom(metaClass.getJavaClass())) {
log.error("{} doesn't implement the HasProcessState");
return;
}
switch (event.getType()) {
case ACTIVITY_STARTED:
//activityName is the name of the current element taken from the process model
String activityName = ((ActivitiActivityEvent) event).getActivityName();
if (!Strings.isNullOrEmpty(activityName)) {
updateProcessState(metaClass, entityId, activityName);
}
break;
}
}
/** * Method updates the process state of the entity linked with the process instance */
private void updateProcessState(MetaClass metaClass, UUID entityId, String processState) {
Persistence persistence = AppBeans.get(Persistence.class);
try (Transaction tx = persistence.getTransaction()) {
EntityManager em = persistence.getEntityManager();
Entity entity = em.find(metaClass.getJavaClass(), entityId);
if (entity != null) {
((HasProcessState) entity).setProcessState(processState);
} else {
log.error("Entity {} with id {} not found", metaClass.getName(), entityId);
}
tx.commit();
}
}
@Override
public boolean isFailOnException() {
return false;
}
}
Конфигурация данного слушателя в модели процесса:
Для открытия данного экрана щёлкните в пустое место в моделере, затем кликните на ссылку Show advanced properties и откройте редактор свойства Event listeners.