Содержание
Данный документ является руководством по применению подсистемы исполнения бизнес-процессов (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://ru.cuba-platform.com/support/topics.
При обнаружении ошибки в документации укажите, пожалуйста, номер главы и приведите небольшой участок окружающего текста для облегчения поиска.
В данной главе рассмотрим создание небольшого проекта, демонстрирующего работу с бизнес-процессами. Задача - реализовать процесс согласования договора. Процесс согласования выглядит следующим образом:
Controller
получает задачу проверить приложенный договор на корректность заполнения.Manager
, если нет, то процесс завершается, а договору проставляется статус Not valid
.Approved
или Not approved
.Создайте новый проект в Cuba Studio:
bpm-demo
demo
com.company.demo
Перейдите на вкладку Entities и нажмите New entity. Имя класса: Contract
.
Создайте следующие атрибуты сущности:
number
(тип String
)date
(тип Date
)state
(тип String
)Перейдите на вкладку Instance name. В поле Name pattern введите значение Contract %s
и добавьте атрибут number
в Name pattern attributes.
Сохраните сущность, нажав кнопку OK.
В секции Entities панели навигатора выделите сущность Contract и нажмите кнопку Generate standard screens. Значения полей, по умолчанию заполненные в форме создания экрана, нас устраивают, поэтому нажмите Create.
Метод updateState()
бина ApprovalHelper
будет вызываться из процесса согласования для установки состояния договора.
Параметры метода:
entityId
- идентификатор сущности договораstate
- состояние договораОткройте проект в IDE. Простой способ сделать это - воспользоваться кнопкой IDE из какой-либо секции навигатора студии, например, Project properties.
В модуле core
создайте пакет com.company.demo.core
. В этом пакете создайте класс ApprovalHelper
.
ApprovalHelper.java.
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(); } } }
В студии в секции Entities навигатора нажмите на Generate DB scripts. В открывшемся окне менеджера базы данных нажмите кнопку Create database.
Запустите сервер, выполнив команду Run → Start application server.
Откройте приложение в браузере по адресу http://localhost:8080/app или кликните на ссылку в нижней части навигатора студии.
Конечная версия модели процесса будет выглядеть следующим образом:
Рассмотрим последовательность шагов для создания модели.
В веб-интерфейсе запущенного приложения откройте экран BPM → Process models и нажмите Create. Введите имя модели Contract approval
и нажмите OK. Откроется новая закладка браузера Model editor.
В панели свойств модели выберите свойство Process roles - откроется окно редактирования процессных ролей.
В процессе должно быть 2 типа участников: контролер и менеджер. Создайте 2 роли: Controller
и Manager
.
Перетащите в рабочую область узел Start event из группы Start events. При старте процесса нам необходимо отображать форму выбора участников процесса. Для этого выделите узел Start event. В панели свойств выберите Start form - откроется окно выбора формы. В списке Form name выберите Standard form
. После этого добавьте 2 параметра формы:
procActorsVisible
со значением true
говорит о том, что на форме будет показана таблица для выбора участников процессаattachmentsVisible
со значение true
говорит о том, что на форме будет показана таблица для добавления вложений к процессуДобавьте в модель узел User task из группы Activities. Назовите его Validation
.
Выделите этот узел, и на панели свойств задайте свойству Process role значение controller
. Так мы указали, что задача будет назначена на участника процесса с ролью controller
.
Далее выберите свойство Task outcomes. Откроется окно редактирования выходов из задачи. Выходы определяют возможные действия пользователя при получении задачи. Создайте 2 выхода: Valid
и Not valid
. Для каждого из них укажите форму Standard form
. Для выхода Not valid
добавьте параметр формы commentRequired = true
. Это нужно, чтобы в случае некорректного договора от пользователь обязательно добавил свой комментарий.
В зависимости от решения контролера нам необходимо либо отправить договор далее на утверждение группе менеджеров, либо завершить процесс, предварительно установив договору состояние Not valid
. Для контроля над маршрутом процесса используется узел Exclusive gateway из группы Gateways. Добавьте его на рабочую область, а затем добавьте еще 2 элемента: 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
.
Создайте для задачи 2 выхода: Approve
и Reject
(свойство Task outcomes). Задайте для обоих выходов форму Standard form
, для перехода Reject
установите параметр commentRequired
в true
.
После того, как согласование завершится, договору должно установиться состояние Approved
или Not approved
в зависимости от результата согласования. Добавьте узел Exclusive gateway после задачи Approval
. После Exclusive gataway добавьте две 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, поэтому мы можем обращаться к управляемым спрингом объектам по их имени. entityId
- процессная переменная, хранящая идентификатор сущности связанного с процессом договора. Ее значение будет записано при старте процесса.
Соедините с End event последние созданные задачи, нажмите кнопку сохранения модели - модель готова. Переходим к её развертыванию.
Процесс развертывания модели состоит из следующих этапов:
Выделите модель в списке на экране Process models. Нажмите кнопку Deploy. Откроется окно развертывания модели. Модель разворачивается первый раз, поэтому выбрана опция Create new process. При последующих изменениях модели можно будет разворачивать модель в уже существующий процесс. Нажмите OK. Процесс создан.
Откройте экран BPM → Process definitions. Откройте строку с 'Contract approval' для редактирования. Измените значение поля Code на contractApproval
. По этому атрибуту мы в дальнейшем будем искать объект с описанием процесса.
В данном разделе мы добавим в экран редактирования договора возможность работы с процессом согласования.
Найдите в секции Screens на панели навигатора студии экран contract-edit.xml
и откройте его на редактирование. Перейдите на вкладку XML и полностью замените ее содержимое на следующий код:
contract-edit.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.gui.contract.ContractEdit" datasource="contractDs" focusComponent="fieldGroup" messagesPack="com.company.demo.gui.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"> <iframe 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> <iframe id="windowActions" screen="extendedEditWindowActions"/> </layout> </window>
Перейдите на вкладку Layout. Компоновка экрана станет следующей:
Экран содержит группу полей для редактирования самого договора, фрейм для отображения действий по процессу и таблицу с вложениями, созданными во время выполнения процесса.
Перейдите на вкладку Controller и замените ее содержимое на следующий код:
ContractEdit.java.
package com.company.demo.gui.contract; import com.haulmont.bpm.entity.ProcDefinition; import com.haulmont.bpm.entity.ProcInstance; import com.haulmont.bpm.gui.action.ProcAction; import com.haulmont.bpm.gui.procactions.ProcActionsFrame; import com.haulmont.cuba.core.global.*; import com.haulmont.cuba.gui.WindowManager; import com.haulmont.cuba.gui.app.core.file.FileDownloadHelper; import com.haulmont.cuba.gui.components.*; import com.company.demo.entity.Contract; import com.haulmont.cuba.gui.components.actions.BaseAction; import com.haulmont.cuba.gui.data.DsContext; import com.haulmont.cuba.gui.xml.layout.ComponentsFactory; import javax.annotation.Nullable; import javax.inject.Inject; import java.util.Map; public class ContractEdit extends AbstractEditor<Contract> { private static final String PROCESS_CODE = "contractApproval"; @Inject private DataManager dataManager; private ProcDefinition procDefinition; private ProcInstance procInstance; @Inject private ProcActionsFrame procActionsFrame; @Inject private GroupBoxLayout procActionsBox; @Inject private ComponentsFactory componentsFactory; @Inject private Table attachmentsTable; @Inject private Metadata metadata; @Override protected void postInit() { super.postInit(); procDefinition = findProcDefinition(); if (procDefinition != null) { procInstance = findProcInstance(); if (procInstance == null) { procInstance = metadata.create(ProcInstance.class); procInstance.setProcDefinition(procDefinition); procInstance.setEntityName("demo$Contract"); procInstance.setEntityId(getItem().getId()); } initProcActionsFrame(); } getDsContext().addListener(new DsContext.CommitListenerAdapter() { @Override public void beforeCommit(CommitContext context) { if (procInstance != null && PersistenceHelper.isNew(procInstance)) { context.getCommitInstances().add(procInstance); } } }); FileDownloadHelper.initGeneratedColumn(attachmentsTable, "file"); } private void initProcActionsFrame() { procActionsFrame.setBeforeStartProcessPredicate(new ProcAction.BeforeActionPredicate() { @Override public boolean evaluate() { if (PersistenceHelper.isNew(getItem())) { showNotification(getMessage("saveContract"), NotificationType.WARNING); return false; } return true; } }); procActionsFrame.setAfterStartProcessListener(new ProcAction.AfterActionListener() { @Override public void actionCompleted() { showNotification(getMessage("processStarted"), NotificationType.HUMANIZED); close(COMMIT_ACTION_ID); } }); procActionsFrame.setBeforeCompleteTaskPredicate(new ProcAction.BeforeActionPredicate() { @Override public boolean evaluate() { return commit(); } }); procActionsFrame.setAfterCompleteTaskListener(new ProcAction.AfterActionListener() { @Override public void actionCompleted() { showNotification(getMessage("taskCompleted"), NotificationType.HUMANIZED); close(COMMIT_ACTION_ID); } }); procActionsFrame.setCancelProcessEnabled(false); procActionsFrame.init(procInstance); } @Nullable private ProcDefinition findProcDefinition() { LoadContext ctx = new LoadContext(ProcDefinition.class); ctx.setQueryString("select pd from bpm$ProcDefinition pd where pd.code = :code") .setParameter("code", PROCESS_CODE); return dataManager.load(ctx); } @Nullable private ProcInstance findProcInstance() { LoadContext ctx = new LoadContext(ProcInstance.class).setView("procInstance-start"); ctx.setQueryString("select pi from bpm$ProcInstance pi where pi.procDefinition.id = :procDefinition and pi.entityId = :entityId") .setParameter("procDefinition", procDefinition) .setParameter("entityId", getItem()); return dataManager.load(ctx); } }
Сохраните изменения, нажав кнопку OK.
Рассмотрим код контроллера более подробно.
Чтобы запустить процесс, мы должны создать экземпляр процесса - объект ProcInsntance
, связать его с описанием процесса (ProcDefinition
) и выполнить запуск. Экземпляр процесса (ProcInstance
) может быть запущен как самостоятельно, так и с привязкой к какой-либо сущности проекта. В нашем случае нужна привязка к договору.
В начале метода postInit()
производится поиск экземпляра процесса согласования договора. Метод findProcDefinition()
по коду contractApproval
ищет описание процесса.
Далее проверяется нет ли в базе объекта ProcInstance
, связанного с текущим договором (метод findProcInstance()
). Если экземпляр процесса для данного договора еще не создан, то создаем его, заполняя ссылку на описание процесса, устанавливая имя связанной сущности и ее идентификатор.
if (procInstance == null) { procInstance = metadata.create(ProcInstance.class); procInstance.setProcDefinition(procDefinition); procInstance.setEntityName("demo$Contract"); procInstance.setEntityId(getItem().getId()); }
CommitListener
добавляет в список сущностей, отправляемых на средний слой для коммита, созданный объект ProcInstance
.
getDsContext().addListener(new DsContext.CommitListenerAdapter() { @Override public void beforeCommit(CommitContext context) { if (procInstance != null && PersistenceHelper.isNew(procInstance)) { context.getCommitInstances().add(procInstance); } } });
Далее переходим к методу initProcActionsFrame()
.
ProcActionsFrame
- это стандартный фрейм для отображения кнопок доступных в данный момент процессных действий. ProcActiosnFrame
связан с экземпляром ProcInstance
. Если процесс не запущен, то фрейм отобразит кнопку запуска процесса, если процесс запущен и для текущего пользователя имеются активные задачи, то он отобразит кнопки завершения текущей задачи в соответствии с определенными в модели процесса выходами из задачи (Task outcomes). Подробнее о ProcActionsFrame см. Раздел 6.1, «ProcActionsFrame».
private void initProcActionsFrame() { procActionsFrame.setBeforeStartProcessPredicate(new ProcAction.BeforeActionPredicate() { @Override public boolean evaluate() { if (PersistenceHelper.isNew(getItem())) { showNotification(getMessage("saveContract"), NotificationType.WARNING); return false; } return true; } }); procActionsFrame.setAfterStartProcessListener(new ProcAction.AfterActionListener() { @Override public void actionCompleted() { showNotification(getMessage("processStarted"), NotificationType.HUMANIZED); close(COMMIT_ACTION_ID); } }); procActionsFrame.setBeforeCompleteTaskPredicate(new ProcAction.BeforeActionPredicate() { @Override public boolean evaluate() { return commit(); } }); procActionsFrame.setAfterCompleteTaskListener(new ProcAction.AfterActionListener() { @Override public void actionCompleted() { showNotification(getMessage("taskCompleted"), NotificationType.HUMANIZED); close(COMMIT_ACTION_ID); } }); procActionsFrame.setCancelProcessEnabled(false); procActionsFrame.init(procInstance); }
Метод procActionsFrame.setBeforeStartProcessPredicate()
добавляет проверку, выполняемую перед запуском процесса. Если объект с договором еще не сохранен, то процесс не запустится и будет выведено соответствующее предупреждение.
Метод procActionsFrame.setBeforeCompleteTaskPredicate()
вызывает коммит редактора и позволяет завершить процессное действие только если коммит редактора прошел успешно.
Методы setAfterProcessStartListener
и setAfterCompleteTaskListener
будут вызваны после соответствующего события. Они отобразят уведомление и закроют редактор договора.
После того, как необходимые слушатели и предикаты для procActionsFrame
заданы, вызывается инициализация фрейма.
procActionsFrame.init(procInstance);
Во время инициализации и происходит создание необходимых элементов управления внутри фрейма.
В студии откройте файл messages.properties
, расположенный в пакете с экранами для договора. Измените его содержимое на следующим текстом:
messages.properties browseCaption = Contract browser editCaption = Contract editor attachments = Attachments process = Contract approval saveContract = Save the contract before starting a process processStarted = Process started taskCompleted = Task completed
По умолчанию в Cuba Studio включен механизм Hot Deploy, и изменения в экране редактирования договора уже должны быть отправлены на сервер. Если Hot Deploy у вас был отключен, то перезапустите сервер, выполнив в Студии команду Run → Restart application server.
Для демонстрации работы процесса необходимо создать несколько тестовых пользователей. Откройте экран Administration → Users и создайте трех пользователей:
norman
, First name: Tommy
, Last name: Norman
, Full name: Tommy Norman
roberts
, First name: Casey
, Last name: Roberts
, Full name: Casey Roberts
pierce
, First name: Walter
, Last name: Pierce
, Full name: Walter Pierce
Standard form
с атрибутами procActorsVisible=true
и attachmentsVisible=true
, поэтому сейчас перед нами форма с компонентами для указания участников процесса и добавления вложений.norman
и 2 менеджера: pierce
и roberts
.Зайдите в систему под пользователем norman.
При достижении процессом узла User task создается объект ProcTask
, связанный с определенным участником процесса. В подсистеме BPM есть экран для отображения списка невыполненных задач для текущего пользователя. Откройте его: BPM → Process tasks.
Видим, что для пользователя norman
есть одна задача Validation
по процессу Contract approval
. Выделите ее и нажмите кнопку Open process instance - откроется системный экран для работы с экземпляром ProcInstance.
В нем отображается информация о времени запуска процесса, инициаторе процесса, список вложений, участников, текущих и выполненных задач в рамках данного процесса. Также экран позволяет перейти к связанной сущности и выполнить процессное действие. Мы завершим действие другим способом - воспользовавшись procActionsFrame
, который мы добавили ранее в редактор договора.
Закройте Proc Instance Edit и откройте на редактирование созданный договор.
Т.к. для текущего пользователя (norman
) имеется незавершенная задача (ProcTask), то procActionsFrame
отображает доступные действия. Когда мы описывали узел UserTask с именем Validation
, то мы указали для него 2 возможных выхода Valid
и Not valid
. На основании этой информации в фрейм и добавлено 2 кнопки.
Нажмите на Valid. В открывшемся окне введите комментарий:
Нажмите OK.
После успешной валидации договор должен уйти к менеджерам на параллельное согласование.
Войдите в систему под пользователем pierce
.
Откройте список текущих задач BPM → Process tasks. Имеется одна задача Approval
.
Откройте process instance editor.
Обратите внимание на таблицу Tasks. Предыдущая задача Validation
завершена с результатом Valid
, и после успешной валидации контролером создались 2 новые задачи Approval
на менеджеров pierce
и roberts
.
Утвердите договор, воспользовавшись кнопкой Approve.
Далее войдите в систему под пользователем roberts
. Откройте договор из списка Application → Contracts.
Пользователь roberts
имеет незавершенную задачу по договору, следовательно фрейм procActionsFrame
отображает для него действия Approve и Reject. Нажмите кнопку Reject.
Т.к. при описании выхода Reject
в дизайнере мы указали параметр формы commentRequired=true
, то комментарий в форме завершения данного действия обязателен. Введите комментарий и нажмите ОК.
Один из менеджеров отклонил договор, поэтому ему должно установиться состояние Not approved
. Проверим это, открыв договор.
Процесс согласования завершен.
Атрибуты, имена которых начинаются с префикса act* являются ссылками на идентификаторы из Activiti.
ProcModel - модель процесса. Атрибуты модели:
ProcDefinition - описание процесса. Может быть получен из модели, либо загружен напрямую из XML файла. Атрибуты сущности:
ProcRole - роль в процессе. Объекты данного типа создаются автоматически при развертывании процесса на основе информации из XML файла с процессом. Можно сказать, что роли определяют типы участников процесса. Атрибуты сущности:
ProcInstance - экземпляр процесса. ProcInstance может быть запущен как с привязкой к сущности проекта (например, процесс согласования договора может быть привязан к экземпляру сущности "Договор"), так и без нее. Атрибуты сущности:
ProcActor - участник процесса. Сущность определят исполнителей для ролей процесса по конкретному экземпляру процесса. Атрибуты сущности:
ProcTask - задача по процессу. Объекты данного типа автоматически создаются при достижении процессом узла User task. Атрибуты сущности:
ProcAttachment - процессное вложение. Атрибуты сущности:
ProcAttachmentType - тип вложения. Атрибуты сущности:
Для исполнения бизнес-процессов подсистема BPM использует Activiti engine, для моделирования процессов в нотации BPMN используется доработанный редактор из веб-приложения Activiti Explorer. В дополнение к возможностям фреймворка Activiti подсистема BPM предоставляет дополнительную функциональность, которая описана далее в этом разделе. Перечисление возможностей фреймворка Activiti не входит в задачу данного руководства. Вы можете ознакомиться с ними на сайте Activiti: http://www.activiti.org/userguide.
При создании модели процесса, в процесс автоматически добавляется слушатель событий BpmActivitiListener
. BpmActivitiListener
является реализацией интерфейса ActivitiEventListener
(см. http://www.activiti.org/userguide/#eventDispatcher). Слушатель отвечает за создание и изменение сущностей подсистемы BPM при наступлении определенных событий процесса (вход в пользовательскую задачу, отмена процесса, завершение задачи, и т.д.). Именно он создает объекты ProcTask и проставляет значение endDate
для ProcInstance.
Процессные роли определяют типы участников процесса, например, оператор или менеджер. Чтобы открыть экран редактирования процессных ролей в дизайнере процессов, выберите свойство Process roles в панели свойств процесса. При развертывании модели информация о ролях будет записана в XML процесса в секцию extensionElements
элемента process
, а затем будут созданы соответствующие объекты ProcRole.
Описание процессных ролей в XML.
<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>
Для задания формы, которая будет отображаться при запуске процесса используется свойство Start form элемента Start event. Подробнее о формах см. Раздел 6.2, «Процессные формы».
Форма запуска процесса в XML.
<startEvent id="startEvent"> <extensionElements> <cuba:form name="standardProcForm"> <cuba:param name="procActorsVisible" value="true"></cuba:param> </cuba:form> </extensionElements> </startEvent>
Для определения пользователя, на которого будет назначена задача, необходимо в свойстве Process role элемента User task выбрать одну из процессных ролей, определенных в модели. При достижении процессом задачи среди участников процесса (ProcActor) будут найдены участники с указанной процессной ролью, и задача будет назначена на них.
Задание процессной роли для задачи в XML.
<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.
Задача без конкретного участника в XML.
<userTask id="managerApproval" name="Manager approval"> <extensionElements> <cuba:claimAllowed>true</cuba:claimAllowed> </extensionElements> </process>
Обычно задача требует от пользователя принятия решения (например, согласовать или отклонить). Дальнейший маршрут по процессу зависит от принятого решения. Для задания списка выходов из задачи используется свойство Task outcomes узла User Task. Для каждого выхода задается имя и выбирается форма, которая должна отображаться при выборе этого выхода, а также список параметров формы (подробнее о формах см. Раздел 6.2, «Процессные формы»).
Описание выходов из задачи в XML.
<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>
В отличие от jBPM в нотации BPMN отсутствует возможность указать несколько выходов из одной задачи. Чтобы направить процесс по нужной ветке в зависимости от результата используется узел Exclusive Gateway, переходы из которого имеют условия, оперирующие результатом выполнения задачи, расположенной перед этим Gateway. При завершении пользователем задачи, результат его действия записывается в процессную переменную с именем [taskId]_result
. Тип этой переменной - ProcTaskResult
.
Методы класса ProcTaskResult
:
int count(String outcomeName)
- возвращает количество пользователей, завершивших задачу с данным выходомboolean exists(String outcomeName)
- возвращает true
, если есть хотя бы один пользователь, завершивший задачу с указанным выходом.Далее объект с результатом используется в выражении Flow condition для переходов, выходящих из Gateway.
Пример:
Предположим, что задача approval
была параллельно назначена нескольким пользователям. Для задачи были определены 2 возможных выхода: approve
и reject
. После того, как все пользователи завершат задачу процесс перейдет к exclusive gateway. Нам нужно следующее поведение: если хоть кто-либо выбрал вариант reject
, то переходим по переходу Rejected
, если все согласились (approve
), то по Approved
.
Самым удобным вариантом задания условия, который подойдет для большинства случаев, является выбор имени outcome предыдущей задачи в свойстве Flow outcome стрелки перехода. Данный переход сработает, если было хотя бы одно завершение задачи с указанным outcome.
Если необходимо иметь более сложные условия для перехода, то их можно задать в поле Flow condition. Например условие "Более 5 пользователь выбрали вариант Reject" будет выглядеть следующим образом:
${approval_result.count('reject') > 5}
Обратите внимание, что необходимо задать порядок обработки переходов. Иначе Activiti может, например, обработать переход по умолчанию до переходов с явно заданными условиями. Для задания порядка вычисления условий установите свойство Flow order у узла Exclusive gateway.
Для выполнения скрипта используется элемент Scirpt task. При достижении элемента, система анализирует содержимое поля Script. Если содержимое является путем к файлу и данный файл существует, то система исполнит указанный файл. Если файла по указанному пути нет, то содержимое поля скрипт будет исполнено.
Внутри скрипта можно использовать объекты persistence
и metadata
.
Для вызова метода сервиса используется элемент Service task. Activiti engine интегрирован со Spring framework, т.е. возможно обращение к бинам среднего слоя по имени. Для вызова метода управляемого бина в поле Expression пишется выражение вида:
${beanName.methodName(processVarName, 'someStringParam')}
Для того, чтобы завершить задачу после истечения периода времени необходимо:
PT15M
(15 минут).Задание выхода для таймера.
<boundaryEvent id="managerApprovalTimer" cancelActivity="true" attachedToRef="managerApproval"> <extensionElements> <cuba:outcome>approve</cuba:outcome> </extensionElements> </boundaryEvent>
По умолчанию Job executor для обработки заданий таймеров отключен. Для его включения установите свойство приложения bpm.activiti.asyncExecutorEnabled = true
Процесс может содержать локализованные сообщения, которые будут использованы при отображении в пользовательском интерфейсе имен задач, выходов из задач и т.д.
Для открытия экрана задания локализованных значений выберите свойство Localization модели.
Для локализации имени задачи необходимо создать запись, ключом которой является id задачи.
Для локализации имени выхода из задачи необходимо создать запись, ключом которой является выражение вида TASK_ID.OUTCOME_NAME
Для локализации имени процессной роли необходимо создать запись, ключом которой является код роли.
Локализованные сообщения в XML.
<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>
Раздел содержит общее описание сервисов. Описание методов сервисов находится в Javadoc в исходном коде классов.
Служит для работы с описаниями процесса (ProcDefinition). Сервис использутся для:
Для доступу к функциональности сервиса в коде middleware используйте ProcessRepositoryManager.
Служит для работы с экземпляром процесса (ProcInstance). Методы сервиса позволяют:
Для доступу к функциональности сервиса в коде middleware используйте ProcessRuntimeManager.
Служит для получения информации о выходах из задачи, формах, которые должны быть показаны пользователю при выборе того или иного выхода, а также для получения информации о формах старта и отмены процесса.
Для доступу к функциональности сервиса в коде middleware используйте ProcessFormManager.
Служит для доступа к локализованным сообщениям, определенным в процессе.
Для доступу к функциональности сервиса в коде middleware используйте ProcessMessagesManager.
ProcActionsFrame
может быть встроен в экраны приложения. После связки с экземпляром процесса во фрейме автоматически отобразятся:
Каждому из действий возможно задать предикат, вычисляемый перед выполнением этого действия, что позволяет сделать проверку возможности выполнения действия в настоящее время (например, выполнить коммит экрана и с случае неудачи не выполнять процессное действие). Также можно задать слушатель, который будет выполнен после завершения действия (например, закрыть экран редактирования сущности и отобразить сообщение пользователю).
Последовательность инициализации фрейма в коде контроллера экрана:
init()
передав в него экземпляр procInstance
При определении выхода (outcome) для задачи и в узле начала процесса возможно указать форму, которая будет показана пользователю. Форма должна реализовывать интерфейс ProcForm
.
Методы интерфейса ProcForm
:
String getComment()
- значение, возвращаемое этим методом, будет записано в поле comment
объекта ProcTask при завершении задачи или в поле startComment
у ProcInstance если форма отображается на старте процесса;Map<String, Object> getFormResult()
- возвращает список объектов, которые будут добавлены к переменным процесса после коммита формы.Список форм, доступный в дизайнере моделей процесса, формируется на основе конфигурационных файлов, определенных свойством приложения bpm.formsConfig
.
Пример файла bpm-forms.xml.
<?xml version="1.0" encoding="UTF-8"?> <forms xmlns="http://schemas.haulmont.com/cuba/5.5/bpm-forms.xsd"> <form name="standardProcForm" default="true"> <param name="commentRequired" value="true"/> <param name="procActorsVisible" value="true"/> <param name="attachmentsVisible" value="true"/> </form> <form name="otherForm"> <param name="someParam" value="hello"/> </form> </forms>
Помимо имени форм в файле определены имена возможных параметров формы и их значения по умолчанию.
Форма с атрибутом default="true"
будет использоваться в дизайнере как форма по умолчанию.