Примеры¶
В данном разделе описаны следующие примеры:
-
Примеры настройки процессов:
-
Создание процесса - введение в создание процессов в редакторе бизнес-процессов. Поэтапно описано, как создать и настроить свой процесс с использованием типовых действий. В даном примере все задания отправляются с помощью действия “Задание”.
-
Примеры процессов с применением действий из группы “Маршруты” - примеры создания и настройки маршрутов с использованием действий из группы “Маршруты” (т.е. задания согласования, подписания и т.п., аналогичные тем, которые используютcя в подсистеме маршрутов, описанной в Руководстве Администратора).
Note
Примеры, указанные выше, можно посмотреть в архиве по ссылке.
-
-
Прочие примеры:
-
Создание действий - создание новых типов действий для использования их в процессах.
-
Создание расширений на проверку доступа к тайлам - пример реализации дополнительной проверки доступа к кнопкам процесса.
-
Создание процесса¶
Рассмотрим поэтапное создание процесса для простого согласования, реализованное с использованием стандартных действий.
Important
Реализуемый в данном примере процесс можно настроить более простым способом, используя действия из группы “Маршруты”. Пример их использования см. в разделе Примеры процессов с применением действий из группы “Маршруты”.
Этап 1. Создание отправки задания согласования с доработкой¶
В данном примере мы рассмотрим:
-
Создание и настройка карточки шаблона процесса
-
Создание кнопки для запуска процесса
-
Создание простого процесса
-
Настройку действий старта процесса, задания, завершения процесса
Создаем новую карточку шаблона бизнес-процесса и заполняем параметры:
-
Задаем имя шаблона (например, “Согласование (пример)”).
-
Задаем группу, если нужно (удобно использовать для разделения шаблонов по логике использования)
-
Флаги Запуск из карточки и Можно запускать несколько экземпляров оставляем как есть.
-
В Типы карточек добавляем тип Договорной документ.
-
В расширения проверки состояния для тайлов добавляем Проверка состояния и Проверка контекстных ролей.
-
Доступ на редактирование можно оставить как есть, доступ на чтение экземпляров можно установить Все сотрудники.
-
Добавляем новую кнопку, настраиваем ее по примеру
-
Сохраняем карточку шаблона. В итоге получаем примерно следующую картину.
Вы можете проверить наличие кнопки на боковой панели карточки договора.
Дальше переходим в редактор версии процесса. Для начала необходимо нажать на кнопку Заблокировать, чтобы получить возможность редактировать версию процесса.
Добавляем на наш макет следующие действия:
-
Старт процесса - будет использоваться для определения места старта процесса.
-
Конец процесса - будет использоваться для определения места конца процесса.
-
Два действия Задание - одно будет использоваться для задания Согласования, а второе для задания Доработки.
Note
Это пример поэтапного создания процесса со своими настройками, где используется стандартное действие “Задание”. Однако для заданий согласования и доработки удобно использовать соответствующие действия из группы “Маршруты”, т.к. в них уже заложено много функциональности, например, запрос дополнительного согласования, делегирование и т.п. Примеры с использованием действий группы “Маршруты” описаны в следующем разделе.
Располагаем созданные узлы на макете, даем им соответствующие заголовки:
Следующим шагом будет создание связей между узлами. Добавим связи между:
-
Узлом запуска процесса и узлом согласования.
-
Узлом согласования и концом процесса
-
Узлом согласования и доработкой (две связи, в обоих направлениях)
-
Узлом доработки и концом процесса
Новым связям задаем заголовки, чтобы показать их значение на макете. Связь из доработки в согласования можно изменить, чтобы она не пересекалась со связью из согласования в доработку. Получаем примерно следующую картину:
Далее переходим к настройке действий.
Действие Старт процесса дополнительно настраивать не нужно, т.к. по умолчанию оно ожидает сигнал с типом Start, который мы указали для нашей кнопки запуска процесса.
Действие Конец процесса также дополнительно настраивать не нужно.
Действие Согласование мы настроим следующим образом:
-
Тип задания “Согласование” (тип из примеров по ссылке), чтобы оно отправляло задание с данным типом.
-
Укажем роль Регистраторы, чтобы задание приходило на данную роль
-
Автора задания не будем заполнять, по умолчанию в качестве автора пропишется текущий сотрудник.
-
Укажем текст задания. В данном поле можно использовать плейсхолдеры, чтобы использовать данные карточки (например, номер договора).
-
Укажем длительность задания - 3 дня.
-
В результат записывается то, что попадает в поле Результат в истории заданий. Также поддерживает плейсхолдеры.
-
Настраиваем варианты завершения, чтобы при завершении с вариантом Согласовать мы переходили бы в узел Конец процесса, а при Не согласовать - на доработку.
В итоге мы получаем следующее настроенное действие:
Дальше переходим к настройке действия Доработка:
-
Укажем в данном действии Тип задания - На доработку.
-
Роль - Автор документа. Мы хотим, чтобы задание на доработку отправлялось автору документа.
-
Укажем текст задания, результат и длительность задания.
-
Настроим варианты завершения, чтобы при завершении задания с вариантом Начать новый цикл мы возвращались на согласование, а при варианте Отозвать - мы переходили в конец процесса.
В итоге получаем готовый простенький процесс.
Этап 2. Последовательное согласование, смена состояния, отправка уведомлений, отзыв с согласования.¶
В данном примере мы рассмотрим:
-
Управление версиями процесса
-
Кнопки для управления процессом
-
Действия Смена состояния, Уведомление, Управление заданием.
-
Настройка списка сигналов для обработки
-
Настройка режима перехода связи
Шаблоны бизнес-процессов поддерживают версионность. Это позволяет изменять процесс, но при этом активные процессы продолжат функционировать без изменений. Версию процесса также можно удалить, но только при условии, что для нее нет активных экземпляров процесса.
Т.к. мы планируем доработать процесс, то для начала нужно создать новую версию процесса. Для этого нужно выделить текущую версию процесса и нажать на кнопку Копировать в новую версию. В таблице версий у нас появится новая версия процесса. После сохранения ей выделится номер.
После создания новой версии, заблокируем ее на редактирование и откроем редактор.
В первую очередь мы добавим еще одно согласование после первого. Это можно сделать несколькими способами:
-
Можно просто добавить новое действие типа Задание на связь между узлами Согласование и Конец процесса
-
Можно копировать узел Согласование и вставить его на ту же связь, что и в первом варианте. Этот вариант позволит перенести ряд настроек действия Задание со скопированного узла.
Из нового узла добавим связь в узел Доработка. Процесс примет примерно следующий вид:
Далее произведем настройку нового действия задания аналогично тому, как делали в предыдущем примере. В случае копирования особое внимание следует уделить настройкам переходов по вариантам завершения.
Следующим этапом добавим действие Смена состояния в процесс. Данное действие можно добавить непосредственно в узлы Согласование и Доработка при смене состояния при отправке задания, не связь между вторым согласованием и узлом Конец процесса, а также между узлами Доработка и Конец процесса.
Настроим каждому действию состояния соответствующее состояние, а для тех, что добавлены в узлы с заданием, также укажем в список обрабатываемых сигналов тип Default, чтобы смена состояния производилась только при отправке заданий.
Теперь добавим отправку уведомлений при получении задания на доработку. Это можно сделать двумя способами:
-
Можно добавить новый узел с действием Уведомление перед узлом Доработка, чтобы при отправке на доработку сначала выполнялся узел с уведомлением, а за ним уже узел с заданием на доработку.
-
Можно добавить новое действие непосредственно в узел Доработка. Но при таком варианте необходимо в список обрабатываемых сигналов для действия Уведомления добавить тип сигнала Default, чтобы отправка уведомления производилась только при отправке задания.
Настроим действие Уведомление. Укажем в качестве роли получателя роль, которая указана как исполнитель задания. В качестве уведомления можно указать из справочника уведомлений, или настроить кастомное уведомление.
Далее рассмотрим реализацию кастомной кнопки на боковой панели для управления процессом на примере простого отзыва.
Откроем карточку шаблона и добавим новую кнопку. Настроим ее следующим образом:
В качестве типа сигнала введем кастомный тип сигнала Revoke (его можно вводить вручную или добавить в перечисление WorkflowSignalTypes в схеме в рамках проекта).
Теперь доработаем сам процесс. Добавим новый узел Отзыв с действием Управление заданием, из которого мы будем отправлять сигнал с отзывом задания в узлы согласования. В настройки узла укажем в поле Подписки узла по умолчанию тип сигнала Revoke. В само действие мы настроим так, чтобы оно завершало задание с вариантом Отозвано.
Из узла Отзыв создадим связи в узлы согласования. Для новых связей в настройках укажем в поле Режим перехода значение Никогда не создавать экземпляр узла. Это значит, что обработка произойдет только тогда, когда узел активен (т.е. есть задание на согласование). Также добавим действие Смена состояния в узел Отзыв или отдельный узел, и соединим связями так, чтобы из узла Отзыв мы также переходили в узел Конец процесса.
В итоге мы получили процесс с двумя последовательными согласованиями, сменой состояний, отправкой уведомления и отзывом согласования.
Этап 3. Параллельное согласование, условие перехода, скрипты.¶
В данном примере мы рассмотрим:
-
Параметры процесса
-
Параллельная отправка заданий
-
Условие переходов
-
Действия Условие, И, Сценарий
-
Отмена выполнения действия
-
Написание простых скриптов
Внесем следующие изменения в процесс:
-
Добавим действие типа Сценарий в новый узел, и назовем его Инициализация.
-
Добавим новый узел согласования.
-
Добавим связь из узла Отзыв в новый узел согласования. Укажем новой связи настройку перехода Никогда не создавать экземпляр узла.
-
Все связи, что раньше переходили в первый узел согласования (кроме связи из узла Отзыв), перенесем на узел Инициализация.
-
Добавим связи из узла Инициализация в первый узел согласования и новый узел согласования.
-
Добавим узлы с типами Условие и И и настроим связи между ними и узлами согласования следующим образом:
-
Перенастраиваем переходы для узлов согласования.
В итоге мы получаем две параллельные ветви согласования, в одной ветви два задания, а в другой одно. В первой ветви, если первый человек завершил задание с вариантом Не согласовать, то мы пропускаем второе задание, но не отправляем задание на доработку сразу. Мы переходим в узел И, действие которого ожидает, пока в него не придут сигналы от всех узлов, которые имеют связи с этим узлом. В нашем случае, это вторая ветвь согласования.
Однако сейчас, когда завершатся обе ветви согласования и продолжится выполнение от узла И, система отправит сигнал как по связи Согласовано, так и по связи Не согласовано.
Чтобы отследить данную ситуацию можно в параметры процесса добавить новый параметр типа Да/Нет, который мы назовем NeedRework.
Этот параметр будет отвечать за то, нужно ли нам будет переходить на доработку или нет.
В связи, исходящие из узла И, в условие выхода из узла, добавим условие Process.NeedRework
, для связи Не согласовано, и !Process.NeedRework
, для связи Согласовано.
Данный параметр необходимо заполнять при завершении заданий с вариантом Не согласовано и сбрасывать после возврата с доработки.
Добавим в скрипт для действия Сценарий из узла Инициализация Process.NeedRework = false;
, а в скрипт при завершении каждого задания с вариантом Не согласовано - Process.NeedRework = true;
Таким образом, если хоть одно задание завершено с вариантом Не согласовать, мы устанавливаем флаг NeedRework, и проверяем его при завершении согласования.
Далее добавим условие для перехода во вторую ветвь согласования. Будем проверять сумму договора. Допустим, если она не задана или меньше чем 50.000 (в данном примере не рассматриваем валюту), то согласование не требуется.
Эту задачу можно сделать различными способами:
-
Добавить узел с проверкой условия перед второй ветвью согласования и узел после нее, который ожидает или сигнал от первого узла с условием, или от узла с согласованием.
-
Другим вариантом будет отмена действия отправки задания на согласования в предобработке. Это позволяет пропустить выполнение действия Задание, тем самым сразу перейти по связи Завершено к узлу И.
Таким образом можно реализовать несложный процесс согласования с двумя параллельными ветвями согласования, при этом одна из них запускается только при определенных условиях.
Рассмотрим еще один пример. На этот раз мы будем формировать комментарий задания Доработка из комментариев при не согласовании.
В первую очередь добавим параметр ReworkComment в параметры процесса, в который мы будем записывать комментарии сотрудников при отказе в согласовании.
Далее в основной скрипт процесса добавим метод, который записывал бы из задания согласования комментарий в наш параметр. Также в этот метод можно вынести установку параметра NeedRework = true
.
Добавим следующий метод:
protected void AddReworkComent(CardTask task)
{
var section = task.Card.Sections.GetOrAdd("WfResolutions");
string comment = section.RawFields.TryGet<string>("Comment");
Process.ReworkComment = (string)Process.ReworkComment +
(!string.IsNullOrEmpty(comment)
? string.Format("{0} ({1}): {2}\n", task.UserName , DateTime.Now, comment)
: string.Format("{0} ({1})\n", task.UserName , DateTime.Now));
Process.NeedRework = true;
}
Данный метод работает следующим образом:
-
Получаем секцию из карточки задания
-
Получаем значение из поля Комментарий из карточки задания
-
В комментарий отзыва добавляем строку вида <исполнитель> <дата завершения>: <комментарий>, если он задан, или без <комментарий>, если он не задан.
Во все места, где у нас происходит завершение задания с вариантом Не согласовано, добавляем вызов метода AddReworkComent(task)
.
В узел Инициализация добавляем сброс этого комментария Process.ReworkComment = null
.
Далее настроим параметр Описание задания для задания из узла Доработка на параметр ReworkComment.
Таким образом, наш сформированный комментарий будет отображаться как текст задания для задания на доработку.
Этап 4. Дополнительное согласования с возможностью рекурсивной отправки¶
-
Привязки к различным источникам данных
-
Рекурсивная отправка доп. согласований с отзывом дочерних заданий
-
Обновление родительского задания при завершении дочернего
-
Действия Группа заданий, Управление группой заданий
Внесем следующие изменения в макет процесса:
-
Добавим действие тип Группа заданий в новый узел, и назовем его Доп. согласование.
-
В новый узел добавим действие Управление заданием, переместим его в таблице действий перед группой заданий.
-
В новый узел добавим действие Управление группой заданий, переместим его в таблице действий перед группой заданий.
-
Добавим из всех узлов Согласование 1-3 связь в новый узел.
-
Добавим в новый узел связь на себя, установим ему режим перехода Никогда не создавать экземпляр узла.
-
Добавим связь в новый узел из узла Отзыв.
Получим примерно следующее:
Данный узел будет заниматься обработкой всего дерева доп. согласования и при этом существовать в единственном экземпляре. Добиться этого можно с помощью создания новых заданий через управление группы заданий путем добавления новых исполнителей. Но первая отправка должна производиться через действие Группа заданий.
Добавим в узел параметр Draft
и установим ему значение true
. Данный параметр будет определять состояние узла. Если значение равно true
, то задания создаются через Группу заданий, иначе через Управление группы заданий.
Теперь необходимо настроить действия.
В первую очередь настроим действие Группа заданий. Настроим ему поля следующим образом:
Поле Роли настраивается следующим образом:
-
Нажимаем на кнопку для открытия формы настройки привязки (иконка в форме гаечного ключа);
-
Выбираем тип привязки Задание;
-
Из списка типов заданий выбираем наш тип задания;
-
В фильтрах должны стоять флаги Завершенные задания и Только первое задание, остальные должны быть сняты;
-
В контроле со структурой задания выбираем секцию “WfResolutionPerformers”;
-
Поле “Order” автоматически подтянется как поля для сортировки исполнителей. В данной задаче нам это не важно;
-
Нажимаем кнопку “Ок” чтобы сохранить настройки привязки для поля Роли;
Поле Текст задания настраивается также, как и Роли, только в качестве поля для привязки выбирается поле “Comment” из секции “WfResolutions”.
Далее заполним таблицу вариантов завершения. Добавим в нее строки с вариантами завершения Согласовать, Не согласовать и Запросить дополнительное согласование. Для каждого варианта завершения укажем условие выполнения перехода Одно задание, сразу, в список переходов добавим связь на себя.
Для вариантов Согласовать и Не согласовать добавим сценарий:
Signal.Revoke = true;
if (task.ParentRowID.HasValue)
{
var parentTask = Context.GetTask(task.ParentRowID.Value);
parentTask.Digest += "\n"
+ task.UserName
+ " cогласовал/не согласовал с комментарием: "
+ task.Card.Sections["WfResolutions"].RawFields.TryGet<string>("Comment");
parentTask.Flags |= CardTaskFlags.UpdateDigest;
parentTask.State = CardRowState.Modified;
}
Параметр сигнала Revoke будет отвечать за отзыв дочерних заданий доп. согласования.
Вторая часть скрипта занимается тем, что дописывает в текст родительского задания информацию о завершении задания: кто завершил, с каким вариантом и каким комментарием.
Откроем действие Управление группой заданий и заполним его следующим образом:
Мы указываем список обрабатываемых сигналов, чтобы данное действие выполнялось только при получении сигнала с типом Default/
В сценарии предобработки мы проверяем необходимость в добавлении новых исполнителей в группу заданий и отменяем действие, если новые исполнители не должны добавляться (при отзыве заданий).
Поле Новые роли заполнятся аналогично тому, как было заполнено поле Роли в действии Группа заданий.
Далее откроем действие Управление заданием в узле Доп. согласование и заполним его следующим образом:
Данное действие будет заниматься отзывом дерева всех дочерних заданий
В сценарии предобработки мы проверяем необходимость в отзыве заданий группы заданий и отменяем действие, когда отзыв не нужен (добавление новых заданий).
В сценарии постобработки мы рассчитываем список ID заданий taskIDs
, которые необходимо отозвать. Это необходимо делать в постобработке, т.к. действие управления заданием очищается список заданий taskIDs
для обработки (по умолчанию управление применяется ко всем заданиям).
Мы используем свойство Task
для получения ID завершаемого задания.
Следующим шагом будет настройка узлов действий Согласование 1-3. В настройки вариантов завершения для этих узлов в варианты завершения Согласовать и Не согласовать добавим в список переходов связь на узел Доп. согласования а в сценарий добавим следующее:
Signal.Revoke = true;
Также добавим новую строчку с вариантом завершения Запросить дополнительное согласование и с настроенным переходов на узел Доп. согласование.
Таким образом при завершении задания с данным вариантов завершения будет осуществлена отправка заданий на доп. согласование на исполнителей, указанных в секции “WfResolutionPerformers”, а при завершении заданий с другим вариантом завершения будет производиться отзыв отправленных, но не завершенных заданий доп. согласования.
Примеры процессов с применением действий из группы “Маршруты”¶
Согласование договора¶
Рассмотрим пример реализации процесса в Workflow Engine описанного в примере “Инициация нового договора” в Руководстве администратора реализованного с использованием средств подсистемы маршрутов.
Ниже представлена общая схема процесса.
Описание процесса¶
Создайте новую карточку договора и заполните необходимые поля. Если договор дороже 100 000, то после запуска процесса он будет отправлен на согласование, если сумма договора не указана, то она считается меньше 100 000.
После успешного согласование он будет переведен в состояние “На подписании руководителем” и отправлен на подписание руководителю.
Затем договор будет переведен в состояние “На подписании контрагенту” и отправлен инициатору на организацию подписания контрагентом.
И в самом конце договор получит состояние “Подписано сторонами”.
Если кто-либо (руководитель или контрагент) не подписали документ, то после доработки документ вновь уйдет на подписание, а не на полный цикл согласования с прохождением всех согласующих.
Итак, давайте инициируем договор. Пример процесса настроен так, что один и тот же сотрудник выполняет все роли процесса. Это сделано для удобства демонстрации. В реальной жизни все эти роли выполняют разные люди.
Создаёте новую карточку договора и заполните необходимые поля.
Давайте попробуем отправить договор на согласование не внося изменений в сумму. Если договор дороже 100 000, то после запуска процесса он будет отправлен на согласование, если сумма договора не указана, то она считается меньше 100 000. Она меньше 100 000 и это значит, что договор должен сразу попасть на подписание.
Нажмём в левой панели тайл “Согласование договора”.
Запустился процесс и нам сразу же пришло задание на подписание, минуя согласование.
Обратите внимание, что состояние карточки изменилось на “На подписании руководителем”. Состояние карточки можно посмотреть и на основной вкладке карточки, и на вкладке “Маршрут”.
Вернемся к процессу. В задании на подписание нажмите кнопку “В работу”, чтобы взять задание в работу.
Теперь, когда задание у вас в работе, вы можете сделать всё, что предусмотрено возможностями этапа подписания. Вы можете подписать и отказать в подписании, запросить дополнительные комментарии и делегировать свое задание. Давайте подпишем, чтобы процесс перешел к следующему этапу.
Нажмите кнопку “Подписать”, вы увидите поле для ввода комментария. Нажмите “Подписать” еще раз для подтверждения завершения задания.
Нам сразу же приходит следующее задание. Это задание инициатору на организацию подписания контрагентом.
Обратите внимание, что состояние карточки изменилось и теперь оно “На подписании контрагентом”.
Давайте откажем в подписании. Нажмите кнопку “В работу”. Затем нажмите кнопку “Отказать”, введите комментарий (при отказе в подписании он обязательный) и нажмите еще раз “Отказать”.
Нам пришло задание доработки. Состояние карточки изменилось на “На доработке”.
Давайте посмотрим на содержимое листа согласования. Лист согласования - это специальный виртуальный файл, который для вашего удобства система формирует у каждой карточки, участвующей в согласовании. В нем в удобном для просмотра, печати или, например, отправки почтой, в табличном виде представлен процесс согласования и подписания документа. Файл “Лист согласования” всегда актуальный, так как формируется системой автоматически в момент запроса содержимого и физически в карточке не хранится.
Обратите внимание, что действия из группы “Маршруты” формируют лист согласованию такой же как и при использовании подсистемы маршрутов.
Давайте инициируем новый цикл согласования. Возьмите в работу задание редактирования и нажмите кнопку “Начать новый цикл”.
Вы опять увидите задание на подписание руководителем. Система начала новый цикл согласования.
Чтобы успешно завершить процесс, возьмите в работу и нажмите “Подписать” в этом задании и в следующем задании на организацию подписания контрагентом.
Процесс завершится и состояние карточки изменится на “Подписано сторонами”.
Конечно, в реальной жизни процесс сложнее и после успешного подписания необходимо начинать подготовку к выполнению контракта. Все это не сложно сделать при помощи подсистемы маршрутов.
Теперь давайте инициируем договор с большей суммой. Еще раз создаёте карточку договора и введите сумму 200 000.
И опять в левой панели нажмите тайл “Согласование договора”.
И нам сразу же приходит задание на согласование договора, которого не было в прошлый раз.
Посмотрим на выполнение процесса. Для этого на левой панели необходимо выбрать тайл с именем процесса.
Текущее действие “Согласование внутри компании” выполняет последовательное согласование ролями “Руководитель инициатора” и подразделением “Финансовый департамент”.
Если вы согласуете документ (возьмите в работу задание, и нажмите “Согласовать”), а затем сделаете это еще раз в следующем задании, то попадете опять на этап подписания внутри компании - сначала руководителем, а затем и контрагентом.
Обратите внимание, что все аспекты этого процесса реализованы при помощи механизма бизнес-процессов.
Создание процесса¶
Рассмотрим процесс создания описанного процесса.
Создайте новый шаблон бизнес процесса и настройте следующим образом:
-
Название процесса, например, “Согласование договора”;
-
Группа процесса, например, “Примеры”;
-
Состояние флагов Запуск из карточки и Можно запускать несколько экземпляров оставьте без изменений;
-
Укажите тип карточки “Договорной документ”;
-
Задайте расширение проверки доступа для кнопок “Проверка состояния”. Это позволит в кнопке бизнес-процесса указать состояние, при котором она будет доступна;
-
Добавьте кнопку бизнес-процесса и настройте её как указано на рисунке:
В итоге должно получиться:
Откройте шаблон бизнес-процесса и разместите действия и связи как указано на следующем рисунке.
Все узлы содержат по одному действию. Соответствие наименования узлов и действий приведено в следующей таблице.
Заголовок узла |
Действие |
---|---|
Старт процесса | Старт процесса |
Инициализация маршрута | Инициализация маршрута |
Управление историей | Управление историей |
Сумма договора | Условие |
Согласование внутри компании | Согласование |
На подписании руководителем, На подписании контрагентом, Подписано сторонами | Смена состояния |
Подписание руководителем, Подписание контрагентом | Подписание |
Доработка после отказа в согласовании, Доработка после отказа в подписании | Доработка |
Конец процесса | Конец процесса |
Для действия “Инициализация маршрута” не надо задавать никаких параметров, отличных от значений по умолчанию.
Все действия “Управление историей” в данном процессе будут иметь следующие параметры:
Параметры Группа истории заданий и Родительская группа привязаны к параметрам процесса “Process.HistoryGroup” и “Process.ParentHistoryGroup”, соответственно, и имеют следующие значения: “Согласование - цикл” и “Согласование”.
Note
Обратите внимание:
-
Для правильной инициализации маршрута необходимо размещать действие “Инициализация маршрута” перед любыми действиями из группы “Маршруты”.
-
Для корректного формирования Истории заданий при переходе к новой итерации, аналогично используемой по умолчанию в подсистеме маршрутов, необходимо размещать действие “Управление историей” перед действием “Доработка”.
Для корректного формирования Истории заданий на первом цикле, надо перед любым действием, отправляющим задания, добавить действие “Управление историей”. Действие “Управление историей” достаточно добавить один раз в начале маршрута, что обеспечит создание группы истории заданий: Согласование → Согласование - цикл 1 (аналог этого действия в маршрутах - этап “Управление историей” из шаблона этапов “Новая итерация согласования”).
Более подробно про особенности действий из группы “Маршруты” см. в разделе Особенности действий.
Для организации ветвления процесса, в зависимости от суммы договора, в процессе присутствует действие “Условие” имеющее два условия перехода:
-
Сумма договора задана и больше или равна 100 000 или не задана.
Переход в этом случает определяется по сценарию:
#script
if((await this.GetCardObjectAsync()).Sections.TryGetValue("DocumentCommonInfo", out var dCISection)) { return dCISection.Fields.TryGet<decimal?>("Amount") >= 100_000; }
return false;
-
Сумма договора меньше 100 000.
Для реализации этого условия достаточно установить флаг Выполнять, если все другие условия не выполнены.
В итоге должно получиться как показано на следующем рисунке.
Перейдём к узлу “Согласование внутри компании”. Данный узел включает одно действие “Согласование”, настроенное следующим образом:
Note
Все параметры, которые не показаны на данном рисунке, имеют значения по умолчанию.
За доработку документа инициатором в случае несогласования документа отвечает действие “Доработка”, расположенное в узле “Доработка после отказа в согласовании” и настроенное следующим образом:
Затем процесс переходит на подписание руководителем. Из-за того, что необходимо выставить специальное состояние документа “На подписании руководителем”, а не использовать стандартное при подписании “На подписании”, используется действие “Смена состояния”, изменяющее состояние документа на требуемое.
Для реализации подписания руководителем используется действие “Подписание”, настроенное как показано на рисунке.
Обратите внимание, что снят флаг Изменять состояние при старте, иначе, при начале выполнения действия, состояние документа изменится на “На подписании”. Флаг Изменять состояние при завершении снят, т.к. в текущем процессе должно быть выставлено специальное состояние “На подписании контрагентом” и он ни на что не влияет.
Действие, реализующее подписание контрагентом, настроено аналогично действию подписания руководителем:
После подписания состояние документа изменяется на “Подписано сторонами” в действии “Смена состояния”.
Для реализации доработки при отклонении на этапе подписания руководителем или контрагентом используется действие “Доработка”, настроенное аналогично действию “Доработка” в узле “Доработка после отказа в согласовании”.
Подготовка, согласование и исполнение служебной записки/заявки¶
Рассмотрим пример реализации процесса в Workflow Engine описанного в примере “Подготовка, согласование и исполнение служебной записки/заявки” в Руководстве администратора, реализованного с использованием средств подсистемы маршрутов.
Ниже представлена общая схема процесса.
Описание процесса¶
Пользователь инициирует служебную записку (СЗ) разных типов: обычную и финансовую. Пользователь не создает никаких карточек сам, вместо этого он инициирует процесс нужного типа, и уже система решает, что ему нужно для продолжения.
В зависимости от выбранного пользователем типа процесса система создает карточку заявки по различным шаблонам, отправляет по ней инициатору задание на заполнение заявки, при этом процесс организован так, что карточка заявки сама собой открывается пользователю в окне диалога.
Далее инициатор заполняет карточку заявки и отправляет служебную записку на согласование. При отправке на согласование система выполняет проверку того, указан ли тип заявки, если он не указан, то пользователь не сможет отправить заявку далее по процессу.
Если заявка является финансовой:
-
После отправки на согласование будет предложено предоставить финансовое обоснование или ввести комментарий.
-
Когда заявка инициирована и приложено обоснование, она отправляется на согласование. В нем участвуют роли: “Руководитель инициатора”, “Финансовый департамент” и “Главный бухгалтер”. С каждым циклом согласования времени на согласование система будет давать всё меньше.
Далее заявка отправляется на исполнение. Исполняют ее различные роли в зависимости от типа заявки. После исполнения система отправляет задание инициатору с просьбой подтвердить выполнение.
Если инициатор не подтверждает выполнение заявки, то система вновь отправляет задание исполнителю заявки с комментарием недовольного инициатора.
Давайте создадим новую служебную записку. Нажмите тайл “Обычная СЗ” в группе тайлов “СЗ Workflow Engine” на правой панели.
Будет открыто диалоговое окно, содержащее сформированный по шаблону документ.
После отправки на согласование система стартует процесс и присылает задание на исполнение заявки, отправленное на роль “ДИТ”. При этом карточка автоматически открывается в клиентском рабочем месте.
Посмотрим, как выглядит процесс. Для этого откроем редактор экземпляра процесса Левое меню → Действия → Процессы → Подготовка, согласование и исполнение служебной записки/заявки. В настоящий момент активно действие “Исполнение обычной СЗ”.
Как видите, в задании уже написано, что нам нужно сделать.
Теперь нажмите кнопку “В работу” в задании.
Теперь вам доступны все возможности работы с этим заданием. Задание можно просто завершить. Еще вы можете отправить задание другому сотруднику или сотрудникам, вы можете создать подчиненное задание, чтобы получить какие-то дополнительные материалы и данные. Возможностей очень много, и они подробно описаны в руководстве пользователя в разделе “Работа с задачами”.
Давайте завершим задание, чтобы сообщить системе что заполнение заявки вами завершено и можно продолжить маршрут. Нажмите кнопку “Завершить” в задании. Далее на форме завершения задания нажмите кнопку “Завершить” еще раз для подтверждения. Комментарий можно оставить пустым.
Давайте создадим новую обычную служебную записку и очистим поле “Категория документа”. Выделите значение и удалите его, после чего отправьте документ на согласование.
Система создаст карточку, но процесс дальше нельзя продолжить до тех пор, пока не будет указана допустимая категория документа.
Давайте вернемся в карточку и заполним поле “Категория документа”. Выберите “Прочие СЗ” из выпадающего списка и опять отправьте на согласование.
И система немедленно присылает нам задание на исполнение заявки. Для удобства текущий сотрудник включен во все роли процесса, но в реальной жизни эти роли выполняют разные люди.
Состояние карточки изменилось на “На исполнении”. Часто со сменой состояния также настраивают изменения прав доступа на карточку, например, чтобы инициатор имел права на изменение данных и файлов карточки, только пока она в состоянии “Проект”.
Давайте возьмем задание в работу и завершим его. Система прислала инициатору (нам) задание на подтверждение выполнения заявки.
Возьмем задание в работу. Нажмите кнопку “В работу”. Обратите внимание, что это другое задание. В нем нам доступны только два варианта завершения “Выполнено” и “Не принято”. Эти варианты специально настроены для данного действия процесса.
Давайте не примем выполнение. Нажмите кнопку “Не принято”, заполните комментарий и еще раз нажмите “Не принято” для подтверждения завершения.
И мы опять переходим на этап выполнения заявки. Система вновь отправила задание исполнителю с комментарием инициатора.
Давайте опять возьмём задание в работу и затем завершим его. Нажмите “В работу”, затем нажмите “Завершить” и на форме завершения задания еще раз нажмите “Завершить”.
Система вновь присылает нам задание на подтверждение. Возьмите его в работу, и нажмите “Выполнено”.
Исполнение заявки принято, процесс завершен, карточка перешла в состояние “Исполнен”.
А теперь давайте инициируем финансовую заявку, чтобы посмотреть на работу согласования. Нажмите в правой панели тайл “Финансовая СЗ”.
Вновь система создала карточку заявки и открыла её в диалоговом окне. Система запустит процесс и отправит задание на роль “Инициатор”.
Задание уже взято в работу. Нажав на кнопку “Приложить обоснование” откроется диалоговое окно, где можно добавить файл или указать комментарий. Нажмите на панели инструментов кнопку “Отправить” для завершения задания.
Документ отправится дальше по маршруту.
Для примера, система настроена таким образом, что приложенные в диалоге “Предоставление обоснования” файлы прикладываются в карточку документа и введённый комментарий к финансовому обоснованию записывается в поле “Комментарий к обоснованию”:
Согласно схеме процесса на роль “Руководитель инициатора” приходит задание на согласование заявки. Срок задания составляет 5 рабочих дней на первом цикле согласования. Он будет уменьшаться на 1 день на каждом последующем цикле согласования вплоть до 1 дня.
Возьмите задание в работу. Это задание этапа согласования и оно предоставляет множество возможностей. Вы можете его делегировать, можете запрашивать комментарии у различных сотрудников (например, уточнить что-то у инициатора), можете делать дочерние согласования. Это подробно описано в руководстве пользователя в разделе “Согласование документов”.
Давайте не согласуем заявку. Нажмите “В работу”, далее “Не согласовать”, введите комментарий и далее еще раз нажмите “Не согласовать” для подтверждения.
Система отправляет задание инициатору на доработку.
Задача инициатора скорректировать заявку в связи с замечаниями. Давайте сделаем вид, что мы это сделали и начнем сразу новый цикл согласования. Нажмите “В работу” и затем “Начать новый цикл”.
Система снова отправляет задание на предоставление обоснования, где Инициатор в диалоговом окне может приложить файл или указать комментарий.
Теперь давайте согласуем документ. Нажмите “В работу”, затем “Согласовать” и еще раз “Согласовать” в форме завершения. Система сразу же отправляет следующее задание согласования на роль “Финансовый департамент” (и это опять мы - для удобства).
Если завершить и это задание, согласовав документ, мы попадем на этап согласования финансового обоснования главным бухгалтером. Укажем результат проверки и согласуем финансовое обоснование.
Мы попадаем на уже знакомый нам этап исполнения заявки, только исполнителем будет другая роль, потому что категория заявки “Финансовая СЗ”.
Дальнейший процесс мы подробно проходили в первой части этого примера. Обратите внимание, сколько мелких нюансов реализовано в этом процессе и при этом он остался чрезвычайно простым в настройке.
Скачать пример настроенного данного процесса можно по ссылке.
Создание процесса¶
Рассмотрим процесс создания описанного процесса.
Создайте новый шаблон бизнес процесса и настройте его следующим образом:
-
Название процесса, например, “Подготовка, согласование и исполнение служебной записки”;
-
Группа процесса, например, “Примеры”;
-
Снимите флаг Запуск из карточки;
-
Состояние флага Можно запускать несколько экземпляров оставьте без изменений;
-
Укажите тип карточки “Договорной документ”;
-
Добавьте кнопку бизнес-процесса “Обычная СЗ” и настройте её как показано на рисунке:
-
Добавьте кнопку бизнес-процесса “Финансовая СЗ” и настройте её как показано на рисунке:
В итоге должно получиться:
Откройте шаблон бизнес-процесса и разместите действия и связи как на следующем рисунке.
Все узлы содержат по одному действию. Соответствие наименования узлов и действий приведено в таблице.
Заголовок узла |
Действие |
---|---|
Старт процесса по финансовой СЗ, Старт процесса по обычной СЗ | Старт процесса |
Инициализация финансовой СЗ, Инициализация обычной СЗ, Предоставление обоснования, Согласование обоснования | Диалог |
Инициализация маршрута | Инициализация маршрута |
Управление историей | Управление историей |
Категория заявки, Категория заявки | Условие |
Изменение категории СЗ | Задание |
Согласование с руководителем и фин. департаментом | Согласование |
Доработка | Доработка |
На исполнении, Исполнен | Смена состояния |
Исполнение обычной СЗ, Исполнение финансовой СЗ | Выполнение задачи |
Подтвердить выполнение | Настраиваемое задание |
Конец процесса | Конец процесса |
Для реализации возможности создания служебных записок двух разных категорий “Финансовая СЗ” и “Обычная СЗ” процесс содержит два действия “Старт процесса”, каждый из которых настроен на обработку своего сигнала. Так действие из узла “Старт процесса по финансовой СЗ” обрабатывает сигнал “StartFinancial”, а из “Старт процесса по обычной СЗ” - “StartCommon”.
Параметры действия из узла “Инициализация финансовой СЗ”:
Параметры действия из узла “Инициализация обычной СЗ”:
Для реализации создания карточки по шаблону и открытию её в диалоговом окне используется действие “Диалог” расположенное в узлах “Инициализация финансовой СЗ” и “Инициализация обычной СЗ”.
Параметры диалога, инициализирующего финансовую СЗ:
Параметры диалога, инициализирующего обычную СЗ:
Обратите внимание на установленный флаг Без отправки задания. Он позволяет использовать диалог в глобальных процессах, т.е. без использования карточки.
Кнопка “Отправить”, у обоих диалогов, настроена идентично:
В сценарии необходимо указать вызов метода StoreAndOpenCardAsync(WorkflowDialogContext) выполняющего сохранение и последующее открытие инициализированной карточки служебной записки:
await this.StoreAndOpenCardAsync(dialog);
Сам метод расположен в основном скрипте процесса (см. Редактор процесса):
#using System.Threading.Tasks;
#using Tessa.Cards;
#using Tessa.Extensions.Default.Server.Workflow.KrProcess;
#using Tessa.Extensions.Default.Shared.Workflow.KrProcess;
#using Tessa.Extensions.Default.Shared.Workflow.KrProcess.ClientCommandInterpreter;
/// <summary>
/// Асинхронно сохраняет и открывает на клиенте инициализируемую карточку служебной записки.
/// </summary>
/// <param name="dialog">Контекст скриптов обработки диалогов в Workflow Engine.</param>
/// <returns>Асинхронная задача.</returns>
protected async Task StoreAndOpenCardAsync(WorkflowDialogContext dialog)
{
var fileManager = this.Context.Container.Resolve<ICardFileManager>();
var tokenProvider = this.Context.Container.Resolve<IKrTokenProvider>();
var dialogCard = await dialog.GetCardObjectAsync();
// Обработка файлов приложенных к инициализируемой карточке служебной записки.
await using (var container = await fileManager.CreateContainerAsync(dialogCard, cancellationToken: this.Context.CancellationToken))
{
// Подготовка контента приложенных и/или изменённых файлов для последующего сохранения.
foreach(var dFile in container.FileContainer.Files)
{
if(!dFile.Content.HasData)
{
await dFile.ReplaceAsync(await dialog.GetFileContentAsync(dialogCard.Files.Single(i => i.RowID == dFile.ID)));
}
}
var storeResponse = await container.StoreAsync((c, request, ct) =>
{
tokenProvider.CreateToken(request.Card.ID).Set(request.Card.Info);
return new ValueTask();
},
cancellationToken: this.Context.CancellationToken);
this.ValidationResult.Add(storeResponse.ValidationResult);
if (!storeResponse.ValidationResult.IsSuccessful())
{
return;
}
}
// Добавление клиентской команды на открытие инициализированной карточки во вкладке Tessa Client.
if (!(this.Context.ResponseInfo.TryGetValue(KrProcessSharedExtensions.KrProcessClientCommandInfoMark, out var commandsObj)
&& commandsObj is List<object> commands))
{
commands = new List<object>();
this.Context.ResponseInfo[KrProcessSharedExtensions.KrProcessClientCommandInfoMark] = commands;
}
commands.Add(
new KrProcessClientCommand(
DefaultCommandTypes.OpenCard,
new Dictionary<string, object>
{
[KrConstants.Keys.NewCardID] = dialogCard.ID
}).GetStorage());
// Установка инициализированной карточки служебной записки в качестве основной карточки процесса.
this.Context.SetMainCard(dialogCard.ID);
}
После инициализации карточки служебной записки начинается процесс её согласования.
Для действия “Инициализация процесса” не надо задавать никаких параметров, отличных от значений по умолчанию.
Все действия “Управление историей” в данном процессе имеют следующие параметры:
Параметры Группа истории заданий и Родительская группа привязаны к параметрам процесса (см. Форма для создания привязки) “Process.HistoryGroup” и “Process.ParentHistoryGroup”, соответственно, и имеют следующие значения: “Согласование - цикл” и “Согласование”.
Note
Обратите внимание:
-
Для правильной инициализации маршрута необходимо размещать действие “Инициализация маршрута” перед любыми действиями из группы “Маршруты”.
-
Для корректного формирования Истории заданий при переходе к новой итерации, аналогично используемой по умолчанию в подсистеме маршрутов, необходимо размещать действие “Управление историей” перед действием “Доработка”.
Для корректного формирования Истории заданий на первом цикле, надо перед любым действием, отправляющим задания, добавить действие “Управление историей”. Действие “Управление историей” достаточно добавить один раз в начале маршрута, что обеспечит создание группы истории заданий: Согласование → Согласование - цикл 1 (аналог этого действия в маршрутах - этап “Управление историей” из шаблона этапов “Новая итерация согласования”).
Более подробно про особенности действий из группы “Маршруты” см. в разделе Особенности действий.
Для управления выполнением процесса в зависимости от вида заявки используется действие “Условие”, расположенное в узле “Категория заявки”. Оно имеет три условия проверяющих значение категории заявки:
-
Категория заявки равна “Финансовая СЗ”.
Для проверки условия используется сценарий, проверяющий текущее значение идентификатора категории на равенство идентификатору категории “Финансовая СЗ”:
(await this.GetCardAsync()).DocumentCommonInfo.CategoryID == new Guid(0xd51477de, 0x8664, 0x473d, 0xab, 0x5f, 0xd1, 0xee, 0x0e, 0x60, 0x52, 0xc6)
-
Категория заявки равна “Прочие СЗ”.
Для проверки условия используется сценарий, проверяющий текущее значение идентификатора категории на равенство идентификатору категории “Прочие СЗ”:
(await this.GetCardAsync()).DocumentCommonInfo.CategoryID == new Guid(0x39779be7, 0x8715, 0x49af, 0x84, 0x37, 0x1b, 0x8b, 0x55, 0xd1, 0x1e, 0x24)
-
Неизвестная категория заявки.
Если ни одно из предыдущих условий не выполнилось, то это означает, что для данной категории служебной записки нет допустимого процесса и её необходимо изменить. Для этого документ должен быть отправлен инициатору на изменение категории СЗ.
Изменение категории СЗ реализовано, через отправку задания типа “Доработка” из действия “Задача” расположенного в узле “Изменение категории СЗ”.
Перейдём к рассмотрению действий, отвечающих за процесс согласования финансовой СЗ внутри компании.
Предоставление финансового обоснования реализовано через диалог, расположенный в узле “Предоставление обоснования”, настроенный следующим образом:
Диалог имеет одну кнопку “Отправить” настроенную следующим образом:
В сценарии выполняется проверка задания комментария или приложения файлов:
const string justificationSectionAlias = "WfeJustification";
const string commentAlias = "Comment";
var dCard = await dialog.GetCardObjectAsync();
var hasFiles = dCard.TryGetFiles()?.Count > 0;
// Не указан комментарий в поле "Комментарий к обоснованию" карточки диалога и не приложен ни один файл?
if(string.IsNullOrEmpty(dCard.Sections[justificationSectionAlias].Fields.Get<string>(commentAlias))
&& !hasFiles)
{
this.ValidationResult.AddError(this, "Пожалуйста, приложите файл финансового обоснования или заполните комментарий.");
}
Перенос комментария к обоснованию и файлов в карточку служебной записки из карточки диалога выполняется в сценарии сохранения диалога:
#using System.Threading.Tasks;
#using Tessa.Cards;
const string justificationSectionAlias = "WfeJustification";
const string commentAlias = "Comment";
const string bSend = "Send";
const string mCardFileCategoryName = "Финансовое обоснование";
if(dialog.ButtonName == bSend)
{
// Перенос комментария из поля "Комментарий к обоснованию" карточки диалога в одноимённое поле карточки СЗ.
(await this.GetCardObjectAsync()).Sections.GetOrAdd(justificationSectionAlias).Fields[commentAlias] = (await dialog.GetCardObjectAsync()).Sections[justificationSectionAlias].Fields[commentAlias];
// Перенос файлов, приложенных к карточке диалога в карточку СЗ.
var mCardFileContainer = (await this.Context.GetFileContainerAsync(cancellationToken: this.Context.CancellationToken)).FileContainer;
var dCardFileContainer = (await dialog.GetFileContainerAsync()).FileContainer;
foreach(var dFile in dCardFileContainer.Files)
{
if(!dFile.Content.HasData)
{
var result = await dFile.EnsureContentDownloadedInUIAsync(cancellationToken: this.Context.CancellationToken);
this.ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
}
await mCardFileContainer
.BuildFile(dFile.Name)
.SetContent(dFile.Content)
.SetVersionToken(
(ft, ct) =>
{
var lastVersion = dFile.Versions.Last();
ft.Created = lastVersion.Created;
ft.CreatedByID = lastVersion.CreatedByID;
ft.CreatedByName = lastVersion.CreatedByName;
ft.Hash = lastVersion.Hash;
return new ValueTask();
})
.SetCategory(mCardFileCategoryName)
.AddWithNotificationAsync(cancellationToken: this.Context.CancellationToken);
}
}
После приложения финансового обоснования процесс переходит к его согласованию, реализуемого действием “Согласование”, расположенного в узле “Согласование с руководителем и фин. департаментом”. Параметры действия должны быть следующими:
Уменьшение длительности выполнения отправляемого задания реализовано через сценарий предобработки:
#using Tessa.Extensions.Default.Shared.Workflow.WorkflowEngine;
// В сценарии выполняется уменьшение длительности выполнения отправляемого задания в зависимости от числа циклов согласования.
var period = (await this.Context.GetAsync<double?>(WorkflowConstants.KrApprovalActionVirtual.SectionName, WorkflowConstants.KrApprovalActionVirtual.Period) ?? 1) - WorkflowHelper.GetProcessCycle(this.Context.ProcessInstance.Hash) + 1;
if(period < 1)
{
period = 1;
}
WorkflowEngineHelper.Set(this.Context.ActionInstance.Hash, period, WorkflowConstants.KrApprovalActionVirtual.SectionName, WorkflowConstants.KrApprovalActionVirtual.Period);
Для передачи информации о вернувшем на доработку и его комментарии, в сценарии варианта завершения “Не согласовать” задания типа “Согласование (KrApprove)”, выполняется сохранение этой информации в параметры процесса:
this.Process.SentToRebuild = this.Session.User.Name;
this.Process.RebuildComment = taskCard.KrTask.Comment;
После согласования с руководителем и финансовым департаментом, финансовое обоснование отправляется на согласование с главным бухгалтером. Оно реализовано с помощью действия “Диалог” из узла “Согласование обоснования”. Его параметры:
Диалог имеет две кнопки:
-
Согласовано
-
На доработку
Перенос значения комментария к обоснованию и файлов в карточку диалога выполняет сценарий инициализации диалога:
const string justificationSectionAlias = "WfeJustification";
const string commentAlias = "Comment";
const string fileCategoryName = "Финансовое обоснование";
var dCard = await dialog.GetCardObjectAsync();
var mCard = await this.GetCardObjectAsync();
dCard.ID = Guid.NewGuid();
// Перенос комментария к финансовому обоснованию из карточки СЗ в карточку диалога.
dCard.Sections[justificationSectionAlias].Fields[commentAlias] = mCard.Sections[justificationSectionAlias].Fields[commentAlias];
// Подготовка и копирование файлов из карточки СЗ в карточку диалога.
mCard.Files.RemoveAll(i => !string.Equals(i.CategoryCaption, fileCategoryName, StringComparison.Ordinal));
var result = await CardHelper.CopyFilesAsync(
mCard,
dCard,
this.Context.Container,
cancellationToken: this.Context.CancellationToken);
this.ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
var dCardFiles = dCard.Files;
// Сохранение информации о внешнем источнике контента файла.
var fileIDContentSources = new Dictionary<string, object>(dCardFiles.Count, StringComparer.Ordinal);
foreach(var dCardFile in dCardFiles)
{
fileIDContentSources.Add(dCardFile.RowID.ToString("N"), dCardFile.ExternalSource.FileID.ToString("N"));
}
this.Process.FileIDContentSources = fileIDContentSources;
Валидация полей выполняется в сценарии валидации диалога:
const string justificationSectionAlias = "WfeJustification";
const string verificationResultAlias = "VerificationResult";
const string bRebuildAlias = "Rebuild";
var verificationResult = (await dialog.GetCardObjectAsync()).Sections[justificationSectionAlias].Fields.Get<string>(verificationResultAlias);
if(dialog.ButtonName != null)
{
if(string.IsNullOrEmpty(verificationResult))
{
this.ValidationResult.AddError(this, "Заполните, пожалуйста, поле \"Результат проверки\".");
return;
}
if(string.Equals(dialog.ButtonName, bRebuildAlias, StringComparison.Ordinal))
{
this.Process.SentToRebuild = this.Session.User.Name;
this.Process.RebuildComment = verificationResult;
}
}
Для переноса файлов и комментария из поля “Результат проверки” карточки диалога в карточку СЗ используется сценарий сохранения диалога:
#using System.IO;
#using System.Threading.Tasks;
#using Tessa.Cards;
#using Tessa.Cards.Numbers;
const string bAgreedAlias = "Agreed";
const string bRebuildAlias = "Rebuild";
const string justificationSectionAlias = "WfeJustification";
const string verificationResultAlias = "VerificationResult";
const string mCardFileCategoryName = "Финансовое обоснование";
var mCard = await this.Context.GetMainCardAsync(cancellationToken: this.Context.CancellationToken);
var dCard = await dialog.GetCardObjectAsync();
var dCardFiles = dCard.TryGetFiles();
// В карточке диалога нет файлов?
if(dCardFiles?.Any() != true)
{
return;
}
if(string.Equals(dialog.ButtonName, bAgreedAlias, StringComparison.Ordinal)
|| string.Equals(dialog.ButtonName, bRebuildAlias, StringComparison.Ordinal))
{
Dictionary<Guid, Guid> fileIDContentSources;
if(this.Process.FileIDContentSources is Dictionary<string, object> fileIDContentSourcesObjDict)
{
fileIDContentSources = fileIDContentSourcesObjDict.ToDictionary(i => Guid.ParseExact(i.Key, "N"), j => Guid.ParseExact((string)j.Value, "N"));
}
else
{
fileIDContentSources = null;
}
Guid? GetFileIDContentSource(Guid fileRowID)
{
if(fileIDContentSources != null
&& fileIDContentSources.TryGetValue(fileRowID, out var fileIDContentSource))
{
return fileIDContentSource;
}
return default;
}
mCard.Sections[justificationSectionAlias].Fields[verificationResultAlias] = dCard.Sections[justificationSectionAlias].Fields[verificationResultAlias];
var mCardFileContainer = (await this.Context.GetFileContainerAsync(cancellationToken: this.Context.CancellationToken)).FileContainer;
var mFiles = mCardFileContainer.Files;
// Удаление файлов из основной карточки, удалённых из карточки диалога.
var deletedFileIDCollection = mFiles
.Where(mFile => string.Equals(mFile.Category?.Caption, mCardFileCategoryName, StringComparison.Ordinal))
.Select(mFile => mFile.ID)
.Except(dCardFiles
.Select(dCardFile => GetFileIDContentSource(dCardFile.RowID))
.Where(mFileID => mFileID.HasValue)
.Cast<Guid>()).ToArray();
for(var i = 0; i < deletedFileIDCollection.Length; i++)
{
await mFiles.RemoveWithNotificationAsync(mFiles.TryGet(deletedFileIDCollection[i]), this.Context.CancellationToken);
}
// Перенос файлов из карточки диалога в основную карточку.
var dCardFileContainer = (await dialog.GetFileContainerAsync()).FileContainer;
ValidationResult result;
foreach(var dFile in dCardFileContainer.Files)
{
if(!dFile.Content.HasData)
{
result = await dFile.EnsureContentDownloadedInUIAsync(cancellationToken: this.Context.CancellationToken);
this.ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
}
var fileIDContentSource = GetFileIDContentSource(dFile.ID);
// Файл был перенесён из основной карточки?
if(fileIDContentSource.HasValue)
{
var mFile = mFiles.TryGet(fileIDContentSource.Value);
if(dFile.TryGetActualVersion().Number > 1)
{
result = await mFile.ReplaceAsync(await dFile.Content.GetAsync(this.Context.CancellationToken), this.Context.CancellationToken);
this.ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
}
result = await mFile.RenameAsync(dFile.Name, this.Context.CancellationToken);
this.ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
}
else
{
result = (await mCardFileContainer
.BuildFile(dFile.Name)
.SetContent(dFile.Content)
.SetVersionToken(
(ft, ct) =>
{
var lastVersion = dFile.Versions.Last();
ft.Created = lastVersion.Created;
ft.CreatedByID = lastVersion.CreatedByID;
ft.CreatedByName = lastVersion.CreatedByName;
ft.Hash = lastVersion.Hash;
return new ValueTask();
})
.SetCategory(mCardFileCategoryName)
.AddWithNotificationAsync(cancellationToken: this.Context.CancellationToken)).result;
ValidationResult.Add(result);
if(result.HasErrors)
{
return;
}
}
}
}
Доработка при согласовании внутри компании выполняется действием “Доработка”, расположенным в одноимённом узле:
Вывод имени и комментария вернувшего на доработку выполняется с помощью сценария предобработки:
#using Tessa.Extensions.Default.Shared.Workflow.WorkflowEngine;
var sentToRebuild = (string)this.Process.SentToRebuild;
if (!string.IsNullOrEmpty(sentToRebuild))
{
var digest = $"Служебная записка возвращена на доработку: {sentToRebuild}";
var comment = (string)this.Process.RebuildComment;
if(!string.IsNullOrWhiteSpace(comment))
{
digest += Environment.NewLine + Environment.NewLine + comment;
}
WorkflowEngineHelper.Set(
this.Context.ActionInstance.Hash,
digest,
WorkflowConstants.KrAmendingActionVirtual.SectionName,
WorkflowConstants.KrAmendingActionVirtual.Digest);
}
Перейдём к рассмотрению реализации процесса исполнения СЗ.
При переходе СЗ на исполнение у неё изменится состояние на “На исполнении” в действии “Смена состояния” узла “На исполнении”.
После чего процесс снова разветвляется в зависимости от категории СЗ в действии “Условие” узла “Категория заявки”. Условия в данном действии аналогичны ранее рассмотренным в действии “Условие” узла “Категория заявки”. Только отсутствует обработка варианта “Неизвестная категория СЗ”.
Если категория заявки “Финансовая СЗ”, то она отправляется на исполнение в узел “Исполнение финансовой СЗ” для обработки действием “Выполнение задачи”, иначе в узел “Исполнение обычной СЗ”.
Параметры действия из узла “Исполнение финансовой СЗ” указаны на рисунке:
Вывод комментария вернувшего СЗ на доработку выполняется с помощью сценария предобработки:
#using Tessa.Extensions.Default.Shared.Workflow.WorkflowEngine;
if (!string.IsNullOrEmpty((string)this.Process.RejectComment))
{
WorkflowEngineHelper.Set(
this.Context.ActionInstance.Hash,
"Инициатор заявки отклонил выполнение с комментарием:"
+ Environment.NewLine
+ this.Process.RejectComment,
WorkflowConstants.KrResolutionActionVirtual.SectionName,
WorkflowConstants.KrResolutionActionVirtual.Digest);
}
Действие, расположенное в узле “Исполнение обычной СЗ” имеет следующие параметры:
После исполнения СЗ у неё изменяется состояние на “Исполнен” в действии “Смена состояния” в одноимённом узле.
Параметры действия “Настраиваемое задание”, расположенного в узле “Подтвердить выполнение”, приведены на следующем рисунке:
Параметры настраиваемых вариантов завершения:
-
Выполнено.
Вариант завершения не имеет параметров отличных от значений по умолчанию.
-
Не принято.
Для возможности указания пользователем комментария в задании необходимо установить флаг Показать поле комментарий.
Проверка наличия комментария и его сохранение в параметрах процесса для отображения в задании при доработке выполняется с помощью скрипта:
var comment = (string)taskCard.KrTask.Comment;
if (string.IsNullOrWhiteSpace(comment)) { this.ValidationResult.AddError(this, "Пожалуйста, укажите комментарий."); return; }
this.Process.RejectComment = comment;
В итоге, после настройки параметров варианта завершения, должна получиться следующая картина:
Скачать настроенный процесс, описанный в данном разделе, можно по ссылке.
Создание действий¶
Система позволяет создавать свои типы действий и использовать их в бизнес-процессах.
Для того, чтобы сделать новый тип действия нужно реализовать следующий набор объектов и классов.
Дескриптор действия¶
В первую очередь нужно создать объект-дескриптор действия. Для этого нужно создать свой экземпляр объекта с типом WorkflowActionDescriptor
. Данный объект содержит описание действия и связь с карточкой настроек из типов карточек. Данный объект можно создать как статическое поле какого-нибудь класса. Регистрировать данный объект в Unity не нужно.
WorkflowActionDescriptor
имеет следующий набор свойств:
-
ID - определяет уникальный идентификатор типа действия и является идентификатором типа карточки с настройками. Обязательный параметр для заполнения.
-
Icon - имя ресурса с иконкой для действия. Выбранная иконка отображается на боковой панели с действиями и как иконка по умолчанию для новых узлов при добавлении действия. По умолчанию имеет значение “Thin1”.
-
Group - определяет имя группы, в которую включено данное действие на боковой панели. По умолчанию имеет значение “$WorkflowEngine_Groups_Main” (Основные).
-
Order - определяет порядок действий на боковой панели. По умолчанию равен 100.
-
IsStandAlone - определяет, является ли данное действие одиночным (см. раздел Особенности действий). По умолчанию имеет значение false.
-
ProcessNodeExit - определяет, должно ли действие обрабатывать системный Exit-сигнал (см. раздел Особенности действий). По умолчанию имеет значение false.
-
Methods - массив дескрипторов компилируемых методов действия.
Пример¶
public readonly static WorkflowActionDescriptor WfeLoanSetStateDescriptor =
new WorkflowActionDescriptor(WfeLoanSetStateActionTypeID)
{
Group = "Кредитная заявка",
Icon = "Thin411",
Methods = new []
{
CustomMethodDescriptor,
},
};
Дескриптор метода¶
Если в действиях предполагается использование кастомных методов, описанных в настройках действия, то для дескриптора действия нужно создать дескриптор метода. Данный дескриптор также используется для формирования связи между выгруженным в .csproj проектом и местом хранения самих скриптов в настройках действия.
WorkflowActionMethodDescriptor
содержит следующие свойства, которые можно задать:
-
MethodName - имя компилируемого метода.
-
ReturnType - тип возвращаемого результата. По умолчанию имеет значение void.
-
Parameters - набор пар значение типа
string
, обозначающих пары тип параметра-имя параметра. Если не задан, метод не имеет дополнительных параметров. -
StorePath - путь к месту в парметрах действия, где хранится текст скрипта. Если задан ComplexDescriptor, то данное поле определяет путь к месту не в параметрах действия, а в строке.
-
ErrorDescription - описание места возникновения ошибки при возникновении ошибки компиляции в данном методе.
-
MethodDescription - описание метода при выгрузке процесса в проект .csproj. Если не задано, описание формируется автоматически.
-
ComplexDescriptor - описание генератора методов, если метод создается по табличной секции.
WorkflowActionMethodDescriptor имеет следующие методы:
WorkflowActionMethodsComplexDescriptor
содержит следующие свойства, которые можно задать:
-
ListPath - путь к месту в параметрах действия, где хранится таблица со скриптами. Путь к скрипту внутри строки определяется по
StorePath
внутри описания метода. -
GetMethodNameSuffix - функция, которая принимает строку в виде
IDictionary<string, object>
и возвращает суффикс, добавляемый к имени метода. Должен быть задан. -
GetErrorDescription - функция, которая принимает оригинальный
ErrorDescription
дескриптора метода, строку в видеIDictionary<string, object>
и возвращает описание места возникновения ошибки при возникновении ошибки компиляции в данном методе переданной строки. Если не задан, то возвращаетсяErrorDescription
из дескриптора метода. -
GetMethodDescription - функция, которая принимает оригинальный
MethodDescription
дескриптора метода, строку в видеIDictionary<string, object>
и номер строки в таблице и возвращает описание сгенерированного метода при выгрузке процесса в проект .csproj. Если не задано, описание формируется автоматически.
Пример генерации метода¶
public static WorkflowActionMethodDescriptor TaskInitMethod = new WorkflowActionMethodDescriptor()
{
ErrorDescription = "Возникла ошибка в скрипте инициализации", // Описание ошибки
MethodName = "InitMethod", // Имя метода
Parameters = new [] { new Tuple<string, string>("CardTask", "task") }, // метод принимает параметр с типом CardTask и именем task
StorePath = new[] { "InitScript" }, // Текст метода будет хранится в поле InitScript
};
Пример генерации методов по таблице¶
public static WorkflowActionMethodDescriptor TaskEventMethod = new WorkflowActionMethodDescriptor()
{
ErrorDescription = "Возникла ошибка в событии {0}", // Описание ошибки
MethodName = "Event_", // Имя метода без суффикса
Parameters = null, // метод без параметров
StorePath = new[] { "Script" }, // Текст метода будет хранится в поле Script в каждой строке
ComplexDescriptor = new WorkflowActionMethodsComplexDescriptor()
{
// Таблица со строками хранится по ключу Events в параметрах действия
ListPath = new[] { "Events" },
// Возвращаем Event_ + идентификатор строки для гарантирования уникальности имени метода
GetMethodNameSuffix = (hash) => hash.TryGet<Guid>("RowID").ToString("N"),
// Возвращаем описание ошибки с указанием имени события, где возникла ошибка
GetErrorDescription =
(text, hash) => LocalizationManager.Format(
text,
WorkflowEngineHelper.Get<string>(hash, "Event", "Name")),
// Формируем описание метода с помощью имени события и номера строки
GetMethodDescription =
(text, index, hash) =>
LocalizationManager.Format(
"Событие {0} на строке {1}",
WorkflowEngineHelper.Get<string>(hash, "Event", "Name"),
index + 1),
},
};
Обработчик действия¶
Далее необходимо реализовать обработчик действия. Для этого необходимо создать свой тип для обработки действия. Он должен наследоваться от WorkflowActionBase
. В базовый конструктор необходимо передать объект-дескриптор для данного действия. Создавать два класса для обработки по одному дескриптору нельзя. Реализованный тип необходимо зарегистрировать в Unity с указанием какого-нибудь уникального имени.
Для данного класса можно реализовать или переопределить следующий ряд методов:
-
Execute - основной метод, который выполняет обработку действия. Обязателен для реализации действия.
Метод имеет следующие параметры:-
context (тип
IWorkflowEngineContext
) - контекст обработки процесса. -
scriptObject (тип
IWorkflowEngineCompiled
) - объект со всеми скомпилированными скриптами действия. Может быть равенnull
, если конкретное действие не содержит ни одного скрипта.
-
Пример¶
Пример реализации метода, который берет значения из своих настроек и устанавливает их в карточку:
protected override void Execute(IWorkflowEngineContext context, IWorkflowEngineCompiled scriptObject)
{
if (context.Signal.Type == WorkflowSignalTypes.Default)
{
var card = context.MainCard;
int? stepID = context.Get<int?>("ActionSection", "Step", "ID"),
stageID = context.Get<int?>("ActionSection", "Stage", "ID"),
string stageName = context.Get<string>("ActionSection", "Stage", "Name"),
stepName = context.Get<string>("ActionSection", "Step", "Name");
card.Sections["MainLoanSection"].Fields["ProcessStepID"] = stepID;
card.Sections["MainLoanSection"].Fields["ProcessStepName"] = stepName;
card.Sections["MainLoanSection"].Fields["ProcessStagesID"] = stageID;
card.Sections["MainLoanSection"].Fields["ProcessStagesName"] = stageName;
}
}
-
Compile - метод, используемый системой для формирования дополнительных методов для компилируемых объектов по настройкам действия. Все основные методы действия, завязанные на данных действия должны быть описаны через дескрипторы методов в дескрипторе действия. Метод возвращает
true
, если есть методы для компиляции, иначе возвращаетfalse
.
Метод имеет следующие параметры:-
builder (тип
IWorkflowCompilationSyntaxTreeBuilder
) - объект, используемый для добавления новых методов для компиляции действия. -
action (тип
WorkflowActionStorage
) - действие со всеми его параметрами.
-
Пример¶
Пример реализации метода:
public override bool Compile(IWorkflowCompilationSyntaxTreeBuilder builder, WorkflowActionStorage action)
{
// Вызываем базовую реализацию метода
var result = base.Compile(builder, action);
// Получаем код скрипта
var methodBody = action.Hash.TryGet<string>("Script");
// Добавляем дополнительный метод только если задан основной метод скрипта
if (!string.IsNullOrWhiteSpace(methodBody))
{
// Добавляем новый метод, возвращающий void с именем Script и без параметров.
// В случае ошибки компиляции выведется сообщения вида: Ошибка в сценарии {0}, где {0} - имя объекта, в котором произошла ошибка, например "Ошибка в сценарии в действии "WorkflowScriptAction""
builder
.AddMethod(
"void",
"ScriptWithCallInfo",
null,
body: $"AddInfo(\"Method Script called\"); {methodBody}",
errorDescription: "Ошибка в сценарии {0}");
return true;
}
return result;
}
-
PrepareForExecute - метод, используемый системой при создании нового экземпляра действия. Используется для того, чтобы очистить новый экземпляр действия от ненужных параметров.
Метод имеет следующие параметры:-
actionState (тип
WorkflowActionStateStorage
) - объект экземпляра процесса. -
context (тип
IWorkflowEngineContext
) - контекст обработки процесса.
-
Пример¶
Пример реализации метода:
public override void PrepareForExecute(WorkflowActionStateStorage actionState, IWorkflowEngineContext context)
{
// Удаляем значение по ключу Script, т.к. сам текст скрипта нам не нужен для каждого экземпляра.
actionState.Hash.Remove("Script");
}
-
PrepareForSave - метод, используемый системой при сохранении версии шаблона. Позволяет как-то модифицировать шаблон процесса при сохранении действия.
Метод имеет следующие параметры:-
action (тип
WorkflowActionStorage
) - сохраняемое действие. -
node (тип
WorkflowNodeStorage
) - сохраняемый узел, к которому относится данное действие. -
process (тип
WorkflowProcessStorage
) - сохраняемый шаблон процесса, к которому относится данный узел.
-
-
CheckActive - метод, используемый системой для проверки, является ли данное действие активным. Если хотя бы одно действие узла активно, экземпляр узла не удаляется.
Метод имеет следующие параметры:- context (тип
IWorkflowEngineContext
) - контекст обработки процесса.
- context (тип
Пример¶
Пример реализации метода:
protected override bool CheckActive(IWorkflowEngineContext context)
{
// Если в параметрах действия задан параметр с иеменм TaskID, то действие считается активным
// За наличие или отсутствие данного значения обычно отвечает само действие (в методе Execute)
return context.Get<Guid?>("TaskID").HasValue;
}
Обработчик редактора действия¶
Также необходимо реализовать обработчик для редактора действия. Для этого необходимо создать свой тип для обработки редактора действия. Он должен наследоваться от WorkflowActionUIHandlerBase
. В базовый конструктор необходимо передать объект-дескриптор для данного действия. Создавать два класса для обработки по одному дескриптору нельзя. Реализованный тип необходимо зарегистрировать в Unity с указанием какого-нибудь уникального имени.
Для данного класса можно реализовать или переопределить следующий ряд методов:
-
UpdateFormAsync - метод обновления формы редактора действия при ее первом открытии. Может использоваться для организации работы UI редактора действия. Реализация по умолчанию ничего не делает.
Метод имеет следующие параметры:-
action (тип
WorkflowStorageBase
) - редактируемое действие. Может иметь типWorkflowActionStorage
илиWorkflowActionStateStorage
в зависимости от того, происходит редактирование шаблона или экземпляра действия. -
node (тип
WorkflowStorageBase
) - узел, которому принадлежит редактируемое действие. Может иметь типWorkflowNodeStorage
илиWorkflowNodeStateStorage
в зависимости от того, происходит редактирование шаблона или экземпляра узла. -
process (тип
WorkflowStorageBase
) - редактируемый процесс. Может иметь типWorkflowProcessStorage
илиWorkflowProcessStateStorage
в зависимости от того, происходит редактирование шаблона или экземпляра процесса. -
cardModel (тип
ICardModel
) - модель карточки редактора действия. Содержит в себе все контролы редактора и объект карточки, сгенерированный для действия. -
actionTemplate (тип
WorkflowActionStorage
) - шаблон действия, к которому относится редактируемый экземпляр. Данный параметр передается только, если происходит редактирование экземпляра действия. -
cancellationToken (тип
CancellationToken
) - объект, с помощью которого можно отменить асинхронную задачу.
-
Пример¶
Пример реализации метода, в котором мы очищаем значение одного поля, если производится установка значения другого поля:
public override Task UpdateFormAsync(
WorkflowStorageBase action,
WorkflowStorageBase node,
WorkflowStorageBase process,
ICardModel cardModel,
WorkflowActionStorage actionTemplate = null,
CancellationToken cancellationToken = default)
{
var card = cardModel.Card;
var mainSection = card.Sections["MainSection"];
mainSection.FieldChanged += (s, e) =>
{
switch (e.FieldName)
{
case "Period":
if (e.FieldValue != null)
{
mainSection.Fields["Planned"] = null;
}
break;
case "Planned":
if (e.FieldValue != null)
{
mainSection.Fields["Period"] = null;
}
break;
}
};
Task.CompletedTask;
}
-
InvalidateFormAsync - метод, вызываемый системой при завершении жизни редактора действия. Может использоваться для аннулирования подписок на события или высвобождения неких ресурсов, если действие использует что-то подобное. Реализация по умолчанию ничего не делает.
Метод имеет следующие параметры:-
cardModel (тип
ICardModel
) - модель карточки редактора действия. Содержит в себе все контролы редактора и объект карточки, сгенерированный для действия. Имеет значениеnull
, если форма редактора действия не открывалась. -
cancellationToken (тип
CancellationToken
) - объект, с помощью которого можно отменить асинхронную задачу.
-
-
Validate - метод, вызываемый при валидации действия. Возвращает объект
ValidationResult
с сообщением о результате валидации. Реализация по умолчанию возвращаетValidationResult.Empty
Метод имеет следующие параметры:-
action (тип
WorkflowActionStorage
) - действие, валидация которого происходит. -
node (тип
WorkflowNodeStorage
) - узел, которому принадлежит проверяемое действие. -
process (тип
WorkflowStorageBase
) - процесс, к которому принадлежит проверяемое действие.
-
Пример¶
Пример реализации метода, в котором мы проверяем заполнение поля Сценарий. В методе используется вспомогательный метод GetValidateFieldMessage
, который возвращает сформированное сообщение о том, что поле не заполнено в конкретном действии конкретного узла.
public override ValidationResult Validate(
WorkflowActionStorage action,
WorkflowNodeStorage node,
WorkflowProcessStorage process)
{
if (string.IsNullOrWhiteSpace(action.Hash.TryGet<string>("Script")))
{
return ValidationResult.FromText(
GetValidateFieldMessage(action, node, "Сценарий"),
ValidationResultType.Warning);
}
return ValidationResult.Empty;
}
-
AttachToCardCore - метод, вызываемый системой для организации привязки данных карточки редактора действия и параметров действия, а также для создания привязки параметров действия к связям. Реализация действия по умолчанию организует привязку параметров по следующим правилам:
-
Все строковые секцие содержатся в структуре вида
SectionName: { FieldName1: Value1, -- Физическая колонка FieldName2: Value2, -- Физическая колонка ... ComplexFieldName: { -- Комплексная колонка FieldName1: Value1 -- Физическая колонка комплексной колонки (имя без префикса) ... } }
-
Все строки и поля табличных секций содержатся в структуре вида
SectionName: [ { -- Строка 1 RowID: <RowID>, -- ID строки FieldName1: Value1, -- Физическая колонка FieldName2: Value2, -- Физическая колонка ... ComplexFieldName: { -- Комплексная колонка FieldName1: Value1 -- Физическая колонка комплексной колонки (имя без префикса) ... } }, { -- Строка 2 RowID: ... ... }, ... ]
-
Метод имеет следующие параметры:
- bindingContext (тип
WorkflowEngineBindingContext
) - контекст привязки параметров к карточке редактора.
Для организации своей привязки параметров можно использовать следующие методы:
-
AttachEntrySection - производит привязку строковой секции с указанным именем по указанному пути.
-
AttachTableSection - производит привязку табличной секции с указанным именем по указанному пути.
-
AttachField - производит привязку поля строковой секции с заданным именем и типом по указанному пути. Предварительно нужно заполнить
bindingContext.Section
для использования данного метода. -
AttachTableSectionToTemplate - производит привязку табличной секции по указанному пути к шаблону действия. Это означает, что данную табличную секцию можно редактировать только при изменении шаблона и редактирование табличной секции для экземпляра недопустимо. Обычно используется для секций, данные которых не должны передаваться в экземпляр действия.
-
AttachFieldToTemplate - привязку поля строковой секции с заданным именем и типом по указанному пути к шаблону действия. Это означает, что данное поле можно редактировать только при изменении шаблона и его редактирование для экземпляра недопустимо. Обычно используется для полей, данные которых не должны передаваться в экземпляр действия.
Для организации привязки параметров к связям следует использовать следующий метод:
- AddLinkBinding - производит привязку параметра по заданному в
fieldPath
пути (если параметр принадлежит коллекционной секции, следует использовать версию с дополнительным параметромlistPath
, который обозначает путь к месту хранения коллекционной секции) к связям процесса.
Привязка параметров к связям процесса позволяет своевременно обновлять параметры, которые имеют ссылку на связь.
Если необходимо переопределить способ привязки для одного поля/секции, а для остальных использовать поведение по умолчанию, то в своей версии метода AttachToCardCore
вызов базовой версии метода base.AttachToCardCore
нужно производить в самом конце.
Пример¶
Пример реализации метода для действия задания:
protected override void AttachToCardCore(WorkflowEngineBindingContext bindingContext)
{
// Привязываем поле InitTaskScript к шаблону, предварительно указав секцию, в которой производится привязка
bindingContext.Section = bindingContext.Card.Sections.GetOrAdd(WorkflowActionTypes.TaskSectionName);
AttachFieldToTemplate(
bindingContext,
"InitTaskScript",
typeof(string),
WorkflowActionTypes.TaskSectionName,
"InitTaskScript");
// Привязываем оставшиеся поля секции
AttachEntrySection(
bindingContext,
WorkflowActionTypes.TaskSectionName,
WorkflowActionTypes.TaskSectionName);
// Привязываем табличные секции к шаблону действия
AttachTableSectionToTemplate(
bindingContext,
WorkflowActionTypes.TaskOptionsSectionName,
WorkflowActionTypes.TaskOptionsSectionName);
AttachTableSectionToTemplate(
bindingContext,
WorkflowActionTypes.TaskOptionLinksSectionName,
WorkflowActionTypes.TaskOptionLinksSectionName);
AttachTableSectionToTemplate(
bindingContext,
WorkflowTaskActionBase.EventsSectionName,
WorkflowTaskActionBase.EventsSectionName);
// Указываем привязку к связям поля LinkID из секции с вариантами завершения
AddLinkBinding(bindingContext, new [] { "Link", "ID" }, WorkflowActionTypes.TaskOptionLinksSectionName);
}
Создание расширений на проверку доступа к тайлам¶
Данный вид расширений используется для реализации дополнительной проверки доступа к кнопкам процесса с возможностью настройки доступа через редактор кнопки. Если в шаблоне процесса расширение не подключено, то для тайлов данного шаблона процесса проверки данного расширения выполняться не будут.
Для создания своего расширения на проверку доступа к тайлам нужно реализовать новый класс, реализующий интерфейс IWorkflowEngineTileManagerExtension
и зарегистрировать его в UnityContainer
и тип карточки, содержащий структуру данных и интерфейс для редактирования, который подставляется в форму настройки кнопки. Для того, чтобы данные расширения отображались в колонке Настройки таблицы кнопок бизнес-процесса, нужно также создать свой класс, реализующий интерфейс IWorkflowEngineTileManagerUIExtension
и зарегистрировать его в UnityContainer
.
Тип карточки с расширением должен опираться на не виртуальные секции для хранения данных, а также должен иметь секцию BusinessProcessButtonExtension со всеми ее полями.
Интерфейс IWorkflowEngineTileManagerExtension
имеет следующие свойства:
-
ID (тип данных Guid) - уникальный идентификатор расширения.
-
ExtensionTypeID (тип данных Guid) - идентификатор типа карточки расширения.
-
Name (тип данных string) - отображаемое имя расширения.
-
Order (тип данных int) - определяет порядок отображения контролов в окне настройки кнопки.
Интерфейс IWorkflowEngineTileManagerExtension
имеет следующие методы:
-
CheckTileAccessForVisibility - метод для проверки доступа к группе тайлов на видимость. Данный метод получает список идентификаторов проверяемых тайлов и карточку, для который выполняется проверка. Для глобальных тайлов объект
card
равенnull
. Метод возвращает список идентификаторов тайлов, для которых проверка прошла успешно.
Пример реализации метода для проверки состояния карточки:public override IEnumerable<Guid> CheckTileAccessForVisibility(List<Guid> tileIDs, Card card) { // Если проверка идет вне контекста карточки, то возвращаем полный список тайлов if (card == null) { return tileIDs; }
using (dbScope.Create()) { var db = dbScope.Db;
if (card.Sections.TryGetValue("KrApprovalCommonInfoVirtual", out var krSection) && krSection.Fields.TryGetValue("StateID", out var stateIDObj)) { var stateID = stateIDObj == null ? 0 : (int)stateIDObj;
// запрос на проверку состояния карточки return db.SetCommand( dbScope.BuilderFactory .Select().C("bpbe", "ButtonRowID") .From("KrCheckStateTileExtension", "s").NoLock() .InnerJoin("BusinessProcessButtonExtension", "bpbe").NoLock() .On().C("s", "ID").Equals().C("bpbe", "ID") .Where().C("s", "StateID").Equals().P("StateID").And().C("bpbe", "ButtonRowID").In(tileIDs) .Build(), db.Parameter("StateID", stateID)) .LogCommand() .ExecuteScalarList<Guid>(); } }
// Если карточка не имеет состояния, то возвращаем пустой список return EmptyHolder<Guid>.Collection; }
-
CheckTileAccessForExecute - метод для проверки доступа на исполнение конкретного тайла. Метод возвращает результат валидации проверки доступа.
Данный метод получает в качестве параметров:-
tileInfo
(тип данных WorkflowEngineTile) - объект со всеми данными исполняемого тайла. -
cardID
(тип данных Guid) - идентификатор карточки, в рамках которой происходит исполнение тайла. -
cardGetter
(тип данных Func<Card>) - функция для получения объекта карточки.
Пример реализации метода для проверки состояния карточки:public override ValidationResult CheckTileAccessForExecute(WorkflowEngineTile tileInfo, Guid cardID, Func<Card> cardGetter) { using(dbScope.Create()) { var result = db.SetCommand( dbScope.BuilderFactory .Select().Top(1).V(true) .From("KrCheckStateTileExtension", "s").NoLock() .InnerJoin("BusinessProcessButtonExtension", "bpbe").NoLock() .On().C("bpbe", "ID").Equals().C("s", "ID") .LeftJoin("KrApprovalCommonInfo", "kr").NoLock() .On().C("kr", "MainCardID").Equals().P("CardID") .Where().C("s", "StateID").Equals().Coalesce(b => b.C("kr", "StateID").V(0)) .And().C("bpbe", "ButtonRowID").Rquals().P("TileID") .Build(), db.Parameter("CardID", cardID), db.Parameter("TileID", tileInfo.TileID)) .LogCommand() .ExecuteScalar<bool?>() ?? false;
if (result) { return ValidationResult.Empty; } else { return ValidationResult.FromText( this, "$KrTileExtensions_CheckState_AccessDenied", ValidationResultType.Error); } } }
-
Интерфейс IWorkflowEngineTileManagerUIExtension
имеет следующие свойства:
- ID (тип данных Guid) - уникальный идентификатор расширения. Должен соответствовать ID расширения, указанном в классе расширения, реализующем
IWorkflowEngineTileManagerExtension
.
Интерфейс IWorkflowEngineTileManagerUIExtension
имеет следующие методы:
-
ModifyButtonRow - Метод для модификации значения в колонке Настройки для строки из таблицы кнопок бизнес-процесса.
Данный метод получает в качестве параметров:-
context
(тип данных IWorkflowEngineTileManagerUIExtensionContext) - контекст для обработки расширенийIWorkflowEngineTileManagerUIExtension
. Для добавления данных следует использоватьStringBuilder
по свойствуcontext.Result
.
Пример реализации метода:public void ModifyButtonRow(IWorkflowEngineTileManagerUIExtensionContext context) { // Если для интересующей нас секции нет строк с данными, то в настройки ничего не записываем if (context.AllButtonRows.TryGetValue(extendedSectionName, out var contextRoleRows) && contextRoleRows.Count > 0) { // Если другие настройки уже произвели запись результата, то записываем с новой строки if (context.Result.Length > 0) { context.Result.AppendLine(); }
context.Result.Append(LocalizationManager.Localize("$KrTileExtensions_CheckState_States")); context.Result.Append(string.Join("; ", contextRoleRows.Select(x => LocalizationManager.Localize((string)x["StateName"])))); } }
-
Создание кастомного базового класса процесса¶
Для создания своего базового класса процесса нужно реализовать новый класс, наследуемый от WorkflowEngineCompiledBase
, и зарегистрировать его в IWorkflowEngineCompiledBaseRegistry
. Реализованный базовый класс обязательно должен иметь атрибут public
и не иметь атрибута sealed
, а также иметь конструктор без параметров.
При регистрации базового класса необходимо обязательно задать уникальный ID, который будет обозначать данный базовый класс, а также отображаемое имя класса, которое увидит пользователь.
Пример¶
Ниже пример базового класса с дополнительным методом для установки некоторых параметров переданного задания в сигнал:
using Tessa.Cards;
using Tessa.Workflow.Compilation;
namespace Tessa.Extensions.Demo.Server.WorkflowEngine.Wfe
{
public class WfeCompiledBase : WorkflowEngineCompiledBase
{
public void SetParams(CardTask task)
{
Signal.TaskID = task.RowID;
Signal.PerformerID = task.UserID;
}
}
}
Вот пример его регистрации:
this.UnityContainer.Resolve<IWorkflowEngineCompiledBaseRegistry>()
.Register<WfeCompiledBase>(new Guid("8555B8AA-A035-45F8-B216-0F96D443D07E"), "Тестовый базовый класс")
;