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

Своё действие бизнес-процесса - папка, описание, форма настроек

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

Механика

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

Файлов минимум три, и у каждого своя работа. Описание рассказывает конструктору имя, класс и место в списке; класс выполняет работу; языковые файлы дают подписи для интерфейса на нужном языке.

Свойства действия объявляются в классе, а собираются формой настроек. Форму рисует статический метод класса, он же разбирает введённые значения обратно - поэтому имена полей формы и имена свойств держат согласованными.

Точка входа возвращает состояние, и это важнее, чем кажется. Завершённое действие отдаёт признак закрытия, а долгое - признак продолжения; во втором случае процесс остаётся на этом шаге и ждёт внешнего события.

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

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

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

Шаги

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

Код

Раскладываем файлы действия:

/bitrix/activities/custom/vendorsendsms/
├── .description.php - описание для конструктора процессов
├── vendorsendsms.php - класс действия
├── icon.gif - значок в списке действий
└── lang/ru/
├── .description.php - подписи описания
└── vendorsendsms.php - подписи формы и ошибок
// каталог custom - для своих действий, обновление его не трогает
// каталог bitrix рядом - для штатных, и он перезаписывается целиком

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

Описываем действие для конструктора:

.description.php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
$arActivityDescription = [
'NAME' => GetMessage('VENDOR_SMS_NAME'),
'DESCRIPTION' => GetMessage('VENDOR_SMS_DESC'),
'TYPE' => 'activity', // или condition для условия
'CLASS' => 'CBPVendorSendSms',
'JSCLASS' => 'BizProcActivity',
'CATEGORY' => ['ID' => 'other'], // раздел в списке действий
'ADDITIONAL_RESULT' => ['EntityFields'], // доп. результаты следующим шагам
];
// описание не возвращает массив, а заполняет переменную с этим именем

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

Пишем класс действия:

class CBPVendorSendSms extends CBPActivity
{
public function __construct($name)
{
parent::__construct($name);
$this->arProperties = ['Title' => '', 'Phone' => '', 'Text' => ''];
}
public function Execute()
{
$result = VendorSms::send($this->Phone, $this->Text); // своя отправка
$this->WriteToTrackingService($result ? 'СМС отправлено' : 'Отказ отправки');
return CBPActivityExecutionStatus::Closed; // действие завершено
}
}

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

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

Рисуем форму настроек:

public static function GetPropertiesDialog($documentType, $activityName,
$template, $params, $variables, $currentValues = null, $formName = '')
{
$dialog = new \Bitrix\Bizproc\Activity\PropertiesDialog(__FILE__, [
'documentType' => $documentType, 'activityName' => $activityName,
'workflowTemplate' => $template, 'currentValues' => $currentValues,
]);
return $dialog; // разметку берут из файла properties_dialog.php рядом
}

Разбираем значения формы обратно:

public static function GetPropertiesDialogValues($documentType, $activityName,
&$template, &$params, &$variables, $currentValues, &$errors)
{
$errors = [];
if (trim($currentValues['phone']) === '') {
$errors[] = ['code' => 'empty', 'message' => 'Не указан телефон'];
return false; // отказ не даёт сохранить настройку
}
// имена полей формы и имена свойств действия держат согласованными
return true;
}

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

Форма настроек - самая долгая часть работы. Разметку полей держат в отдельном файле рядом с классом, а сам метод только собирает объект диалога и передаёт ему текущие значения.

Выносим подписи в языковые файлы:

lang/ru/.description.php
$MESS['VENDOR_SMS_NAME'] = 'Отправить СМС';
$MESS['VENDOR_SMS_DESC'] = 'Отправляет сообщение через нашего провайдера';
// подписи формы кладут в файл с именем действия, рядом с этим

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

Пишем ход работы в журнал процесса:

$this->WriteToTrackingService('Отправляю на ' . $this->Phone);
// журнал виден в карточке процесса: это единственная отладка для боевого сайта
// вывод на экран из действия не работает: процесс идёт вне страницы

Ограничения

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

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

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

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

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

Действие переживает обновления платформы, а его вызовы - не всегда. Методы чужих модулей, которые оно дёргает, меняются между версиями, и это проверяют при каждом крупном обновлении.

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

Действие не появилось в конструкторе процессов.

Имя каталога, имя файла класса и имя в описании не совпадают. Платформа ищет файл по имени каталога и молча пропускает несовпадение.

Действие видно, но при выполнении процесс падает.

Класс не найден или в нём ошибка: имя класса в описании отличается от объявленного. Ошибку такого рода видно в журнале процесса, а не в конструкторе шаблона.

В настройках действия пустые подписи полей.

Языковые файлы не найдены: путь или имя файла не совпадают с именем действия. Подписи берутся по тем же именам, что и класс.

Процесс завис на своём действии.

Точка входа вернула признак продолжения, а внешнее событие так и не пришло. Для завершённой работы возвращают признак закрытия.

Действие сработало, но результата не видно.

Отладочный вывод шёл на экран, а процесс выполняется вне страницы. Ход работы пишут в журнал процесса.

После обновления платформы действие исчезло.

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

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

Где должен лежать каталог своего действия?

В каталоге пользовательских действий рядом со штатными: обновление перезаписывает только каталог штатных, а пользовательский не трогает. Имя каталога, имя файла класса и имя класса в описании держат согласованными.

Чем действие отличается от условия?

Типом в описании: обычное действие выполняет работу, условие управляет ветвлением шаблона. Класс и набор методов у них одинаковые, отличается только объявленный тип.

Как отладить действие на боевом сайте?

Записями в журнал процесса: он виден в карточке экземпляра и переживает завершение процесса. Вывод на экран не работает - процесс выполняется вне страницы, часто из задания по расписанию.

Что вернуть, если действие ждёт ответа внешней системы?

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

Нужно ли перезапускать что-то после добавления файлов?

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

Смежное

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