Действия

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

С точки зрения кода действие — это php-класс, который наследуется от абстрактного класса CBPActivity или его потомков. Его название класса должно начинаться с подстроки CBP и может состоять из латинских букв и цифр.

Например:

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();

class CBPMyActivity1
    extends \CBPActivity
{
	// . . .
}

В дистрибутиве по умолчанию есть несколько десятков различных действий (Activity) для использования их в шаблонах бизнес-процессов. Для любопытных рекомендуем изучить работу некоторых базовых действий для составления полной картины:

  • CBPCompositeActivity - абстрактный базовый класс составных действий, т.е. действий, которые могут содержать в себе дочерние действия.
  • CBPSequenceActivity - Последовательно запускает набор дочерних действий.
  • CBPCodeActivity - самое простое действие - запускает на выполнение произвольный PHP-код.
  • CBPSetVariableActivity - устанавливает значения переменных бизнес-процесса.
  • CBPDelayActivity - Реализует ожидание, откладывая выполнение на определенный срок.
  • CBPHandleExternalEventActivity - Реализует действие, которое ожидает внешнее событие. Бизнес-процесс останавливается до получения данного внешнего события.
  • CBPIfElseActivity и CBPIfElseBranchActivity - реализуют функционал условия и ветки условия.
  • CBPWhileActivity - Реализует функционал цикла.
  • CBPListenActivity - Реализует ожидание одного из нескольких возможных событий. Когда одно из событий происходит, остальные перестают ожидать событий и отменяются.
  • CBPParallelActivity - Параллельно запускает набор дочерних действий.

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

Классификация действий

С точки зрения выполнения бизнес-процесса каждое действие можно классифицировать на простые и комплексные. Комплексные (композитные) могут состоять из нескольких простых действий.

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

  • Немедленные - выполняются в рамках бизнес-процесса, не блокируют процесс выполнения в нормальном режиме работы.
  • Событийные - выполняются, проверяют условие (необходимость) ожидания события, ставят процесс на паузу до наступления события или даты окончания ожидания.
  • Задания - выполняются, останавливают свое выполнение до выполнения задания сотрудников или даты окончания ожидания.

По-умолчанию любое действие является немедленным. Для того чтобы сделать действие событийным, оно должно реализовывать интерфейсы IBPEventActivity и IBPActivityExternalEventListener, о событийных действиях мы поговорим позже. Задания в свою очередь можно рассматривать как событийное действие с дополнительными возможностями - нужно наследоваться от CBPCompositeActivity, содержать дополнительные методы и реализовывать те же интерфейсы, что и событийная модель.

Создание своего действия

Свои действия располагаются в одной из директорий (перечислены в порядке приоритета от корня сайта):

  • /local/activities
  • /local/activities/custom
  • /bitrix/activities/custom
  • /bitrix/activities/bitrix
  • /bitrix/modules/bizproc/activities

Каждое действие располагается в отдельной директории и ее название должно совпадать с именем класса действия, но без первых символов CBP. Кроме того имя директории должно быть записано строчными буквами (в нижнем регистре).

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

.description.php

В файле .description.php описывается переменная $arActivityDescription которая содержит мета-описание необходимое для корректной работы действия.

Пример:

<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();

$arActivityDescription = array(
    "NAME" => GetMessage("MYACTIVITY_DESCR_NAME"),
    "DESCRIPTION" => GetMessage("MYACTIVITY_DESCR_DESCR"),
    "TYPE" => "activity",
    "CLASS" => "MyActivity",
    "JSCLASS" => "BizProcActivity",
    "CATEGORY" => array(
        "ID" => "other",
    ),
);
?>

Рассмотрим все возможные ключи массива $arActivityDescription:

  • NAME (string) - Локализованное название действия - отображается в списке действий, а так же в заголовке всплывающего окна настроек
  • DESCRIPTION (string) - Локализованное описание действия - отображается во всплывающем окне настроек
  • TYPE (string or array)- Тип действия. В случае условия может принимать только condition, в случае действия может быть либо activiy, либо robot_activity либо массивом из этих же элементов
  • CLASS (string) - название php-класса обработчика действия. Должен совпадать с названием директории
  • JSCLASS (string) - название js-класса обработчика действия. По-умолчанию BizProcActivity.
  • CATEGORY (array) - опциональное описание раздела для отображения в дизайнере БП в случае TYPE=activity
  • ROBOT_SETTINGS (array) - опциональное описание раздела для отображения в роботах в случае TYPE=robot_activity
  • FILTER (array) - структура описывающая условия отображения действия
  • RETURN (array) - структура описывающая возвращаемые значения
  • ADDITIONAL_RESULT (array) - набор из кодов свойств действия, возвращающихся как дополнительные значения, которые могут быть переданы в другие действия во время выполнения бизнес-процесса.

CATEGORY

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

'CATEGORY' => [
    'ID' => 'other',
]

Это является минимально необходимым описанием для размещения действия в панели редактора шаблона бизнес-процесса.

Однако иногда бывает полезным создать собственный раздел. Для этого в категории необходимо указать еще несколько ключей: OWN_ID (string) - симв. код нового раздела и OWN_NAME (string) - отображаемое название раздела

Например, так:

'CATEGORY' => [
    'ID'       => 'own_super_group',
    'OWN_ID'   => 'own_super_group',
    'OWN_NAME' => 'My own super group',
],

ROBOT_SETTINGS

Аналогично структуре CATEGORY есть структура описывающая расположение карточки робота в интерфейсе выбора роботов:

'ROBOT_SETTINGS' => [
    'GROUP' => ['elementControl'],
    'SORT' => 2800,
],

Описание структуры:

  • GROUP - набор групп в которых отображается робот.
  • SORT - приоритет отображения робота в группе.

Список доступных групп (на момент написания статьи):

  • clientCommunication - Коммуникация с клиентом
  • informingEmployee - Информирование сотрудников
  • employeeControl - Контроль сотрудников
  • paperwork - Оформление документов
  • payment - Оплата товаров и услуг
  • delivery - Управление доставкой
  • repeatSales - Повторные продажи
  • ads - Запуск рекламы
  • elementControl - Управление элементом
  • clientData - Данные о клиентах
  • taskManagement - Управление задачами
  • modificationData - Хранение и изменение данных
  • digitalWorkplace - Автоматизация рабочих мест
  • other - Другие роботы

FILTER

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

  • INCLUDE - описывает типы документов к которым применимо данное активити
  • EXCLUDE - описывает типы документов к не применимо данное активити

Например, разрешить действие только для шаблонов по сделкам:

"FILTER" => [
    'INCLUDE' => [
        ['crm', 'CCrmDocumentDeal'],
    ]
],

RETURN

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

Например:

'RETURN' => [
    'ElementId' => [
        'NAME' => 'Displayed name for ElementId property',
        'TYPE' => 'int',
    ],
    'ErrorMessage' => [
        'NAME' => 'Displayed name for Error message',
        'TYPE' => 'string',
    ],
],

Доступные типы (TYPE) возвращаемых значений описаны константами в классе Bitrix\Bizproc\FieldType. Например:

  • FieldType::BOOL (bool)
  • FieldType::DATE (date)
  • FieldType::DATETIME (datetime)
  • FieldType::DOUBLE (double)
  • FieldType::FILE (file)
  • FieldType::INT (int)
  • FieldType::SELECT (select)
  • FieldType::INTERNALSELECT (internalselect)
  • FieldType::STRING (string)
  • FieldType::TEXT (text)
  • FieldType::USER (user)
  • FieldType::TIME (time)

Значения возвращаемые в RESULT без перечисления в ADDITIONAL_RESULT недоступны для использования в других действиях бизнес-процессов.

ADDITIONAL_RESULT

Возвращаемые значения действия (RESULT) имеют ряд недостатков:

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

Чтобы избежать этой ситуации, разработчики добавили специальный ключ ADDITIONAL_RESULT, содержащий перечисление кодов свойств действия, которые будут транслированы в дизайнер бизнес-процессов и могут быть использованы для вставки в параметры других действий через инструмент “Вставка значения”.

Пример использования в мета-файле:

'ADDITIONAL_RESULT' => [
    'FieldsMap'
],

В самом FieldsMap должен содержаться ассоциативный массив, в качестве ключей которого должны выступать свойства действия, а значениями - описание типа значения. В качестве примера рассмотрим действие “Получить информацию об элементе списка” (GetListsDocumentActivity).

В мета-описании файла определено возвращаемое значение FieldsMap.

Рассмотрим части файла связанные непосредственно с обработкой FieldsMap:

class CBPGetListsDocumentActivity extends CBPActivity
{
// ...
    public function __construct($name)
    {
        // ..
        $this->arProperties = [
            // ...
            "Fields" => null,
            "FieldsMap" => null,
        ];
    }
// ..
    public function ReInitialize()
    {
        // ...

        $fields = $this->Fields;
        if ($fields && is_array($fields))
        {
            foreach ($fields as $field)
            {
                $this->{$field} = null;
            }
        }
    }
// ...
    public function Execute()
    {
        // ...
        $map = $this->FieldsMap;

        // ...

        $this->SetPropertiesTypes($map);
        $values = [];

        foreach ($map as $id => $field)
        {
            // ...
            $this->arProperties[$id] = $document[$id];
        }

        // ...
    }
// ...
    public static function GetPropertiesDialogValues($documentType, $activityName, &$arWorkflowTemplate,
        &$arWorkflowParameters, &$arWorkflowVariables, $arCurrentValues, &$errors)
    {
        // ..

        $properties['FieldsMap'] = self::buildFieldsMap($properties['DocumentType'], $properties['Fields']);

        $arCurrentActivity = &CBPWorkflowTemplateLoader::FindActivityByName($arWorkflowTemplate, $activityName);
        $arCurrentActivity["Properties"] = $properties;

        // ..
    }
// ...
    private static function buildFieldsMap(array $documentType, $fields)
    {
        // ...
        $map = [];
        foreach ($fields as $field)
        {
            // ...
                $map[$field] = \Bitrix\Bizproc\FieldType::normalizeProperty($documentFields[$field]);
            // ...
        }
        return $map;
    }

// ...
}

Файл класса

В указанной главе мы рассмотрим лишь базовые методы для создания действия (не робота), рассмотрим обязательные параметры и доступные настройки.

В классе нашего активити нам необходимо:

  1. Описать конструктор __construct, задав значения по-умолчанию для наших свойств
  2. Разработать код для метода Execute - это тот метод, который будет выполняться в запущенном бизнес-процессе
  3. Разработать код для методов GetPropertiesDialog и GetPropertiesDialogValues - методы используемые для отображения диалога настроек и сохранения введенных значений

Предположим, наша задача состоит в разработки действия которое будет записывать информацию в определенный (заранее созданный файл /dump.txt). В конструкторе мы определим необходимые переменные:

public function __construct($name)
{
    parent::__construct($name);

    $this->arProperties = [
        "Title"  => "",
        "MyText" => ""
    ];
}

Мы выполнили родительский метод конструктора (совместимость с CBPActivity), а так же определили заранее свойства с которыми будем работать.

  • Title - название активити
  • MyText - текст который будем записывать в файл

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

public static function GetPropertiesDialog(
    $documentType,
    $activityName,
    $arWorkflowTemplate,
    $arWorkflowParameters,
    $arWorkflowVariables,
    $arCurrentValues = null,
    $formName = "",
    $form,
    $currentSiteId,
    $arWorkflowConstants
)
{
    // .. code here
}

Нам не обязательно нужно формировать html-верстку прямо в этом файле - мы можем подключить любой файл для формирования. Штатно это делается через менеджер текущего выполнения, например так:

$runtime = \CBPRuntime::GetRuntime();

return $runtime->ExecuteResourceFile(
    __FILE__,
    "properties_dialog.php",
    array(
        "arCurrentValues" => $arCurrentValues,
        "formName" => $formName,
    )
);

В результате выполнения данного фрагмента будет подключен файл properties_dialog.php в директории файла __FILE__ и внутри него будут доступны переменные arCurrentValues и formName.

В нашем случае действие будет выглядеть так:

public static function GetPropertiesDialog(
    $documentType,
    $activityName,
    $arWorkflowTemplate,
    $arWorkflowParameters,
    $arWorkflowVariables,
    $arCurrentValues = null,
    $formName = "",
    $form,
    $currentSiteId,
    $arWorkflowConstants
)
{
    $runtime = \CBPRuntime::GetRuntime();

    if ( !is_array($arWorkflowParameters) )
        $arWorkflowParameters = [];

    if ( !is_array($arWorkflowVariables) )
        $arWorkflowVariables = [];

    if ( !is_array($arCurrentValues) )
    {
        $arCurrentValues = ["my_text" => ""]; 

        $arCurrentActivity= &CBPWorkflowTemplateLoader::FindActivityByName(
            $arWorkflowTemplate,
            $activityName
    );
    
    if ( is_array($arCurrentActivity["Properties"]) )
    {
        $arCurrentValues["my_text"] = $arCurrentActivity["Properties"]["MyText"];
    }

    return $runtime->ExecuteResourceFile(
        __FILE__,
        "properties_dialog.php",
        array(
            "arCurrentValues" => $arCurrentValues,
            "formName" => $formName,
        )
    );
}

Теперь когда мы реализовали метод открытия диалога необходимо реализовать метод отвечающий за сохранение введенных значений. В классе действия за это отвечает статический метод GetPropertiesDialogValues

Пример:

public static function GetPropertiesDialogValues(
    $documentType,
    $activityName, 
    &$arWorkflowTemplate,
    &$arWorkflowParameters,
    &$arWorkflowVariables,
    $arCurrentValues,
    &$arErrors,
    $arWorkflowConstants,
)
{
    $arErrors = [];

    $runtime = \CBPRuntime::GetRuntime();

    if ( mb_strlen($arCurrentValues["my_text"]) <= 0 )
    {
        $arErrors[] = [
            "code" => "emptyCode",
            "message" => GetMessage("MYACTIVITY_EMPTY_TEXT"),
        ];
        return false;
    }

    $arProperties = ["MyText" => $arCurrentValues["my_text"]];

    $arCurrentActivity = &CBPWorkflowTemplateLoader::FindActivityByName(
        $arWorkflowTemplate,
        $activityName
    );
    $arCurrentActivity["Properties"] = $arProperties;

    return true;
}

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

public function Execute()
{
    file_put_contents($_SERVER["DOCUMENT_ROOT"]."/dump.txt", $this->MyText, FILE_APPEND);

    // Возвратим исполняющей системе указание, что действие завершено
    return \CBPActivityExecutionStatus::Closed;
}

Активити в результате своей работы должно вернуть один из следующих статусов:

  • CBPActivityExecutionStatus::Executing - действие еще не завершило свою работу
  • CBPActivityExecutionStatus::Closed - действие завершило свою работу
  • CBPActivityExecutionStatus::Faulting - ошибка при выполнении действия, прекратить выполнение бизнес-процесса

properties_dialog.php

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

Пример:

<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
?>
<tr>
    <td align="right" width="40%"><span style="color:#FF0000;">*</span> :</td>
    <td width="60%">
        <textarea name="my_text" id="id_my_text " rows="5" cols="40"><?= htmlspecialchars($arCurrentValues["my_text"]) ?></textarea>
        <input type="button" value="..." onclick="BPAShowSelector('id_my_text', 'string');">
    </td>
</tr>

В рамках диалога можно использовать любой набор переменных определенный и переданный в него через метод GetPropertiesDialog.

В текущем примере пользователь, настраивающий действие в редакторе, может ввести в поле my_text явное значение или выбрать одно из значений с помощью диалога, открывающегося по управляющей кнопке-бургеру слева.

robot_properties_dialog.php

Подобно properties_dialog.php, файл robot_properties_dialog.php отвечает за отображение настроек робота.

Пример:

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();

/** @var \Bitrix\Bizproc\Activity\PropertiesDialog $dialog */
$elementId = $dialog->getMap()['ElementId'];
?>

<div class="bizproc-automation-popup-settings">
    <span class="bizproc-automation-popup-settings-title"><?=htmlspecialcharsbx($elementId['Name'])?>: </span>
    <?=$dialog->renderFieldControl($elementId)?>
</div>

Дополнительная литература

  1. Основные стандартные действия
  2. Создание своего действия