Перейти к содержимому

Бизнес-процесс изнутри - шаблон, экземпляр, продолжение

Разбираем бизнес-процесс по слоям: чем шаблон отличается от запущенного экземпляра и где живёт состояние между шагами. Дальше смотрим, как процесс продолжается после задачи и что остаётся после его завершения.

Механика

Процесс всегда выполняется над конкретным документом: элементом инфоблока, элементом универсального списка или документом другого модуля. Документ задают тройкой из модуля-владельца, класса документа и его собственного идентификатора в базе.

Схему рисует шаблон, а работает экземпляр - отдельный запуск этой схемы. Одному шаблону соответствует сколько угодно одновременно живущих экземпляров над разными документами.

Экземпляр забирает копию схемы в момент старта и доигрывает именно по ней. Правка шаблона на уже запущенные процессы не влияет, и это первое объяснение расхождений между дизайнером и поведением.

Тип шаблона обязан совпадать с типом документа вплоть до последней части тройки. Шаблон для элементов инфоблока не запустится над элементом списка, даже когда поля у них выглядят одинаково.

Процессы бывают ровно двух видов, и выбор вида определяет всю дальнейшую структуру схемы. Последовательный идёт от точки входа к выходу, а процесс со статусами живёт переходами между состояниями документа.

Между шагами процесс не живёт ни в памяти сервера, ни в сессии пользователя. Состояние экземпляра хранится записью в базе, поэтому продолжение всегда приходит снаружи: ответом на задачу, внешним событием или срабатыванием агента.

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

Переменные и параметры шаблона существуют только во время работы одного запущенного экземпляра. Данные, нужные после завершения, записывают в поля документа, иначе они пропадут вместе с экземпляром.

Поля документа читают через сервис документов, а не запросом к инфоблоку. Сервис знает тип документа и отдаёт значения в том виде, в каком их ждут действия процесса.

Журнал выполнения остаётся главным инструментом разбора и для застрявшего, и для завершённого процесса. Он показывает каждое выполненное действие с результатом, и своё действие тоже обязано писать туда ход работы.

Шаги

  1. Найти шаблоны нужного типа документа и убедиться, что типы совпадают до последней части.
  2. Посмотреть состояния процессов конкретного документа и понять, в каких точках они стоят.
  3. Прочитать журнал выполнения экземпляра и найти в нём последнее выполненное действие процесса.
  4. Определить ожидающее действие, его системное имя и адресата задачи, которую оно создало.
  5. Продолжить процесс внешним событием либо остановить его и разобрать причину остановки отдельно.

Код

Смотрим шаблоны для типа документа:

\Bitrix\Main\Loader::includeModule('bizproc');
$documentType = ['iblock', 'CIBlockDocument', 'iblock_' . $iblockId];
$res = CBPWorkflowTemplateLoader::GetList([], ['DOCUMENT_TYPE' => $documentType],
false, false, ['ID', 'NAME', 'AUTO_EXECUTE', 'MODIFIED']);
while ($row = $res->Fetch()) { printf("%d %s автозапуск=%s\n", $row['ID'], $row['NAME'], $row['AUTO_EXECUTE']); }
// идентификаторы шаблонов различаются на стендах: искать шаблон нужно по имени и типу

Тип документа собирается из трёх частей и на инфоблоках включает его идентификатор. Тот же массив передаётся при запуске процесса, и ошибка в нём даёт пустой список шаблонов вместо внятной ошибки.

Смотрим состояния процессов документа:

$documentId = ['iblock', 'CIBlockDocument', $elementId];
$states = CBPDocument::GetDocumentStates($documentType, $documentId);
foreach ($states as $state) {
printf("%s: %s\n", $state['ID'] ?: 'не запущен', $state['STATE_TITLE'] ?: $state['TEMPLATE_NAME']);
}
// строки без идентификатора экземпляра - это доступные шаблоны, а не работающие процессы
// STATE_TITLE у процесса со статусами показывает текущий статус документа

Читаем журнал выполнения экземпляра:

$res = CBPTrackingService::GetList(['ID' => 'ASC'], ['WORKFLOW_ID' => $workflowId],
false, false, ['ID', 'TYPE', 'MODIFIED', 'ACTION_NAME', 'ACTION_TITLE', 'ACTION_NOTE']);
while ($row = $res->Fetch()) {
printf("%s %-24s %s\n", $row['MODIFIED'], $row['ACTION_TITLE'], $row['ACTION_NOTE']);
}
// последняя строка журнала - это точка, в которой процесс стоит прямо сейчас
// TYPE различает служебные записи ядра и сообщения, написанные самими действиями

Журнал отвечает на вопрос «где стоит процесс» точнее любых догадок по схеме. Пустой журнал у запущенного экземпляра означает, что процесс не стартовал: обычно из-за ошибки в условии автозапуска.

Продолжаем ожидающий процесс из кода:

CBPDocument::SendExternalEvent($workflowId, 'A123_45', // системное имя ожидающего действия
['USER_ID' => 1, 'APPROVE' => true]); // false означает отклонение
// состав параметров свой у каждого действия: его смотрят в коде самого действия
// для согласования это идентификатор пользователя и признак утверждения

Системное имя действия берут из журнала или из шаблона процесса. Угаданное имя молча ничего не делает, и процесс остаётся стоять в той же точке.

Останавливаем зависший экземпляр:

$errors = [];
CBPDocument::TerminateWorkflow($workflowId, $documentId, $errors, 'снят вручную');
print_r($errors);
// остановка не откатывает уже выполненные действия: письма и правки документа остаются

Пишем ход работы в журнал из своего действия:

// внутри класса действия, метод Execute
$document = $this->workflow->GetService('DocumentService')->getDocument($this->getDocumentId());
$this->WriteToTrackingService('получен документ: ' . ($document['NAME'] ?? 'без имени'));
return CBPActivityExecutionStatus::Closed; // Executing оставляет действие работающим

Возврат состояния завершения обязателен. Действие, вернувшее состояние работы без последующего события, останавливает экземпляр навсегда и не даёт ошибки.

Сохраняем результат в поля документа:

CModule::IncludeModule('iblock');
CIBlockElement::SetPropertyValuesEx($elementId, $iblockId, ['RESULT' => 'согласовано']);
// переменные процесса исчезнут вместе с экземпляром, а свойство документа останется

Свойство документа переживает и сам экземпляр, и любую последующую правку схемы шаблона. Поэтому итог согласования записывают именно в документ, а журнал оставляют для разбора хода работы.

Ограничения

Правка шаблона не догоняет запущенные экземпляры. Изменения увидят только те процессы, которые стартовали после сохранения новой схемы шаблона.

Идентификаторы шаблонов различаются на разных установках платформы и меняются после переноса проекта. Зашитый числом идентификатор ломает запуск после переноса проекта на другой стенд.

Штатное действие «PHP-код» работает в особом и довольно скудном контексте выполнения процесса. Модули там не подключены, объект текущего пользователя ненадёжен, а запуск такого действия требует прав администратора.

Остановка процесса не откатывает уже выполненные действия и их последствия в документе. Отправленные письма, изменённые поля и созданные задачи остаются, и убирать их приходится отдельно.

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

Типичные проблемы

Шаблон исправили, а процессы работают по-старому.

Запущенные экземпляры доигрывают по копии схемы, снятой в момент их собственного старта. Новую логику увидят только те процессы, которые запущены после сохранения шаблона в дизайнере.

Процесс не запускается после переноса на другой стенд.

Идентификатор шаблона зашит в коде числом, а на новом стенде он совершенно другой. Шаблон ищут по имени и типу документа, а идентификатор берут из результата поиска.

Экземпляр висит неделями и ничего не делает.

Он стоит на ожидающем действии без срока: адресат задачи уволен или поле ответственного пустое. Такие процессы находят по давней дате изменения и продолжают внешним событием из кода.

После завершения процесса данные пропали.

Переменные и параметры процесса существуют только во время работы конкретного экземпляра схемы. Всё нужное после финиша записывают в поля документа прямо в процессе.

Своё действие останавливает процесс без ошибки.

Метод выполнения вернул состояние работы, а внешнего события для продолжения нет. Действие обязано вернуть состояние завершения, если оно ничего не ждёт.

Таблица журнала занимает больше половины базы.

Включена подробная запись каждого действия процесса, а срок хранения журнала не ограничен. Подробность и срок хранения журнала задают в настройках модуля процессов.

Частые вопросы

Чем шаблон отличается от экземпляра?

Шаблон - это схема в дизайнере, экземпляр - её отдельный запуск над конкретным документом. Экземпляр хранит копию схемы и не меняется при правке шаблона.

Где хранится процесс между шагами?

Записью в базе: между запросами процесс не живёт в памяти сервера. Продолжение приходит извне - ответом на задачу, внешним событием или агентом.

Как узнать, на каком действии стоит процесс?

По журналу выполнения экземпляра: последняя запись показывает выполненное действие и его результат. Системное имя ожидающего действия берут оттуда же.

Можно ли продолжить процесс из кода?

Да, отправкой внешнего события с системным именем ожидающего действия и его параметрами. Состав параметров свой у каждого действия и угадыванию не поддаётся.

Что остаётся после завершения процесса?

Статус и записи журнала, а переменные и параметры исчезают вместе с экземпляром. Поэтому результат сохраняют в поля документа во время работы процесса.

Смежное

Первоисточник