Для автоматизации своих операций бизнес часто использует Битрикс24. В этой статье рассказываем о некоторых возможных проблемах при изменении бизнес-процессов и о том, как мы их решали.

Битрикс24 – одна из распространенных систем CRM. В нее входит визуальный конструктор (дизайнер) для выстраивания схем бизнес-процессов. Важно помнить, что при редактировании этих процессов возможны сложности – особенно на крупных действующих проектах, где любые изменения сначала проверяют на локальном и тестовом серверах. В таких случаях при переносе на продакшн мы используем механизм миграции бизнес-процессов (далее – БП).
Небольшие компании, как правило, могут обойтись без миграции и просто приостановить на 2-3 дня тот или иной бизнес-процесс. Крупный бизнес обычно не может себе этого позволить, поэтому использует тестовые сервера и деплоинг.

Битрикс24 о том, как работает шаблон бизнес-процесса
Работа с миграцией имеет свои особенности. В частности, ее осложняет большое количество задействованных объектов и ID. Кроме того, в том же Битрикс24 миграция бизнес-процессов как таковая не предусмотрена – зачастую эту задачу решают посредством импорта и экспорта, и здесь могут быть различные нестыковки. Рассмотрим, какие проблемы возможны при этом с точки зрения разработки.
При создании бизнес-процесса можно присвоить шаблону только название (имя), а не уникальный код. В этом случае при обновления бизнес-процесса его придется получать из базы по имени. Имена иногда изменяются, потому что система использует их для вывода в списке процессов при запуске. Соответственно, возможны ситуации, когда при обновлении невозможно будет найти шаблон. Да и в целом, поиск по имени – не такая уж хорошая идея.
Решение:
Все созданные в системе шаблоны бизнес-процессов хранятся в таблице b_bp_workflow_template. Открыв таблицу, среди полей мы видим SYSTEM_CODE: поле для кода есть, просто не выведено в интерфейс. Мы можем задать код самостоятельно, используя id шаблона — его можно увидеть в url на странице редактирования процесса:

Нам нужно создать функцию, чтобы получить на вход id шаблона и код, провести проверку на дублирование и на заполненность поля у изменяемого шаблона, а также установить его код.
Идем дальше. Для примера создадим тестовый бизнес-процесс на списках:

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

Схема переноса такая:
Далее рассмотрим, как происходит этот процесс.
Будем использовать модуль миграций из маркетплейса: https://marketplace.1c-bitrix.ru/solutions/ws.migrations/.
Файлы миграций в нашем проекте располагаются по адресу local/migrations/scenarios

Открываем страницу шаблона процесса и делаем экспорт. Внутри директории с миграциями создаем директорию files и помещаем туда экспортированный файл. Получается так:
local/migrations/scenarios/files/bp-94.bpt
Создаем сценарий миграций:
Определяем параметры шаблона бизнес-процесса:
Реализуем функцию импорта бизнес-процесса:
Здесь сначала определяем ID инфоблока, для которого мы применяем процесс, и получаем id шаблона процесса с заданным кодом.
Если шаблон найден – мы его обновляем. Если не найден – добавляем.
Функция возвращает id созданного или обновленного процесса, а для чего это нужно – расскажем дальше.
Определяем функцию commit, которая добавит/обновит наш бизнес-процесс:
Итак, на этом шаге мы уже умеем создавать и обновлять конкретный бизнес-процесс через модуль миграций.
Давайте вернемся в наш бизнес-процесс и добавим туда действие – уведомление пользователя.

В качестве отправителя выбираем Автора. Получатели будут:
А теперь смотрим, как бизнес-процесс записан в базе. Для этого получаем и печатаем шаблон в консоли PHP в админке:

В массиве параметров процесса мы видим вот такие вхождения:

Смотрим на строку group_g15. Здесь 15 – это ID группы HR.
Смотрим на строку user_579. Здесь 579 – это ID пользователя.
Это значит, что если мы импортируем процесс на другой площадке, у нас будут сплошные нестыковки.
Т.о. нам нужно сделать замену после миграции этих ID на те, которые актуальны для площадки, куда импортируем процесс.
Группы определяем по символьному коду, пользователей – по логину.
Для начала на той площадке, где создавали процесс, получаем символьный код группы и логин пользователя. В том случае, если у вас не заданы символьные коды групп, лучше сначала написать миграцию и установить их.
В нашем примере:
Далее пишем в миграции функции, которые по коду и логину отдадут нам id группы и пользователя:
Наконец, обновляем в шаблоне соответствующие значения:
Импортируем бизнес-процесс:
Получаем данные шаблона:
Заменяем id пользователей внутри бизнес-процесса:
Здесь при запуске миграции мы загружаем файл и функцией importBP создаем/обновляем процесс. Далее мы получаем структуру шаблона бизнес-процесса в массив, подменяем ID и обновляем шаблон.
В этой статье мы затронули лишь отдельные случаи, где при переносе между площадками могут возникнуть несоответствия, и обозначили, на что обратить внимание. В целом мы в своей практике сталкивались со следующими привязками по id:
Если все сделано правильно, перенос отлаженного бизнес-процесса на продакшн проходит быстро и гладко.
Надеемся, что наш опыт был вам полезен!

Битрикс24 – одна из распространенных систем CRM. В нее входит визуальный конструктор (дизайнер) для выстраивания схем бизнес-процессов. Важно помнить, что при редактировании этих процессов возможны сложности – особенно на крупных действующих проектах, где любые изменения сначала проверяют на локальном и тестовом серверах. В таких случаях при переносе на продакшн мы используем механизм миграции бизнес-процессов (далее – БП).
Небольшие компании, как правило, могут обойтись без миграции и просто приостановить на 2-3 дня тот или иной бизнес-процесс. Крупный бизнес обычно не может себе этого позволить, поэтому использует тестовые сервера и деплоинг.

Битрикс24 о том, как работает шаблон бизнес-процесса
Работа с миграцией имеет свои особенности. В частности, ее осложняет большое количество задействованных объектов и ID. Кроме того, в том же Битрикс24 миграция бизнес-процессов как таковая не предусмотрена – зачастую эту задачу решают посредством импорта и экспорта, и здесь могут быть различные нестыковки. Рассмотрим, какие проблемы возможны при этом с точки зрения разработки.
Проблема поиска шаблона бизнес-процесса
При создании бизнес-процесса можно присвоить шаблону только название (имя), а не уникальный код. В этом случае при обновления бизнес-процесса его придется получать из базы по имени. Имена иногда изменяются, потому что система использует их для вывода в списке процессов при запуске. Соответственно, возможны ситуации, когда при обновлении невозможно будет найти шаблон. Да и в целом, поиск по имени – не такая уж хорошая идея.
Решение:
Все созданные в системе шаблоны бизнес-процессов хранятся в таблице b_bp_workflow_template. Открыв таблицу, среди полей мы видим SYSTEM_CODE: поле для кода есть, просто не выведено в интерфейс. Мы можем задать код самостоятельно, используя id шаблона — его можно увидеть в url на странице редактирования процесса:

Нам нужно создать функцию, чтобы получить на вход id шаблона и код, провести проверку на дублирование и на заполненность поля у изменяемого шаблона, а также установить его код.
use Bitrix\Main\Loader;
Loader::includeModule("bizproc");
$BPloader = CBPWorkflowTemplateLoader::GetLoader();
// set template CODE field
setTemplateCode ($BPloader, 'TEST', '94' );
function setTemplateCode($BPloader, $code, $tempalteId) {
if (isCodeExists($BPloader, $code)) {
die('Такой код уже существует');
}
if (!isCodeEmpty($BPloader, $tempalteId)) {
die('Код уже задан у этого шаблона')
}
$BPloader->UpdateTemplate($tempalteId, ['SYSTEM_CODE' => $code]);
}
// check if $code exists in DB
function isCodeExists($BPloader, string $code) {
$dbRes = $BPloader->GetTemplatesList(
$arOrder = ['ID' => 'DESC'],
$arFilter = ['CODE' => $code],
$arGroupBy = false,
$arNavStartParams = false,
$arSelectFields = ['ID']
);
if (intval($dbRes->SelectedRowsCount()) > 0) {
return true;
}
return false;
}
// check if the template code is not empty
function isCodeEmpty($BPloader, $tempalteId) {
$dbRes = $BPloader->GetTemplatesList(
$arOrder = ['ID' => 'DESC'],
$arFilter = ['CODE' => '', 'ID' => $tempalteId],
$arGroupBy = false,
$arNavStartParams = false,
$arSelectFields = ['ID']
);
if (intval($dbRes->SelectedRowsCount()) > 0) {
return false;
}
return true;
}
return true;
}
Идем дальше. Для примера создадим тестовый бизнес-процесс на списках:

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

Схема переноса такая:
- Экспортируем бизнес-процесс
- Пишем миграцию, прикладываем файл
- На новом стенде делаем бэкап старого процесса
- Применяем миграцию
Далее рассмотрим, как происходит этот процесс.
Создание миграции
Будем использовать модуль миграций из маркетплейса: https://marketplace.1c-bitrix.ru/solutions/ws.migrations/.
Файлы миграций в нашем проекте располагаются по адресу local/migrations/scenarios

Открываем страницу шаблона процесса и делаем экспорт. Внутри директории с миграциями создаем директорию files и помещаем туда экспортированный файл. Получается так:
local/migrations/scenarios/files/bp-94.bpt
Создаем сценарий миграций:
class ws_m_1565783124_approve_task extends \WS\Migrations\ScriptScenario {
Определяем параметры шаблона бизнес-процесса:
class ws_m_1565783124_approve_task extends \WS\Migrations\ScriptScenario {
private $arBPFields = [
'DOCUMENT_TYPE' => [
'lists',
'BizprocDocument',
'iblock_'
],
'AUTO_EXECUTE' => 0,
'NAME' => 'Утверждение задач',
'CODE' => 'TEST',
];
Реализуем функцию импорта бизнес-процесса:
private function importBP($path)
{
CModule::IncludeModule('bizproc');
CModule::IncludeModule('iblock');
//Get iBlock id for which BP is created
$this->arBPFields['DOCUMENT_TYPE'][2] .= $this->getIblockId();
// Get BP id by the CODE
$result = \CBPWorkflowTemplateLoader::GetList(
[],
[
'CODE' => $this->arBPFields['CODE'],
'MODULE_ID' => 'lists'
]
);
if ($arFields = $result->GetNext()) {
$id = $arFields['ID'];
} else {
$id = 0;
}
//read file to a variable
$f = fopen($path, 'rb');
$datum = fread($f, filesize($path));
fclose($f);
//Update BP if id>0, otherwise add BP
\CBPWorkflowTemplateLoader::ImportTemplate(
$id,
$this->arBPFields['DOCUMENT_TYPE'],
$this->arBPFields['AUTO_EXECUTE'],
$this->arBPFields['NAME'],
'',
$datum,
$this->arBPFields['CODE']
);
return $arFields['ID'];
}
Здесь сначала определяем ID инфоблока, для которого мы применяем процесс, и получаем id шаблона процесса с заданным кодом.
Если шаблон найден – мы его обновляем. Если не найден – добавляем.
Функция возвращает id созданного или обновленного процесса, а для чего это нужно – расскажем дальше.
Определяем функцию commit, которая добавит/обновит наш бизнес-процесс:
public function commit() {
$pathBPElement = __DIR__ . '/files/bp-94-approve-task.bpt';
$id = $this->importBP($pathBPElement);
}
Итак, на этом шаге мы уже умеем создавать и обновлять конкретный бизнес-процесс через модуль миграций.
Проблема обновления данных шаблона
Давайте вернемся в наш бизнес-процесс и добавим туда действие – уведомление пользователя.

В качестве отправителя выбираем Автора. Получатели будут:
- Группа пользователей HR
- Пользователь Светлана Кузнецова
А теперь смотрим, как бизнес-процесс записан в базе. Для этого получаем и печатаем шаблон в консоли PHP в админке:

$arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => 94])->GetNext();
echo '<pre>';
var_dump($arFieldsTemplate);
В массиве параметров процесса мы видим вот такие вхождения:

Смотрим на строку group_g15. Здесь 15 – это ID группы HR.
Смотрим на строку user_579. Здесь 579 – это ID пользователя.
Это значит, что если мы импортируем процесс на другой площадке, у нас будут сплошные нестыковки.
Т.о. нам нужно сделать замену после миграции этих ID на те, которые актуальны для площадки, куда импортируем процесс.
Группы определяем по символьному коду, пользователей – по логину.
Для начала на той площадке, где создавали процесс, получаем символьный код группы и логин пользователя. В том случае, если у вас не заданы символьные коды групп, лучше сначала написать миграцию и установить их.
В нашем примере:
- Код группы – HR
- Логин пользователя – svetlana.kuznetsova
Далее пишем в миграции функции, которые по коду и логину отдадут нам id группы и пользователя:
- getUserId($login)
- getGroupId($code);
Наконец, обновляем в шаблоне соответствующие значения:
/**
* Write action by apply scenario. Use method `setData` for save need rollback data
**/
public function commit() {
Импортируем бизнес-процесс:
$pathBPElement = __DIR__ . '/files/bp-94-approve-task.bpt';
$id = $this->importBP($pathBPElement);
Получаем данные шаблона:
$arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => $id])->GetNext();
$template = $arFieldsTemplate["TEMPLATE"];
Заменяем id пользователей внутри бизнес-процесса:
$template[0]['Children'][0]['Properties']["MessageUserTo"][0] = 'group_g' . $this->getGroupId('HR');
$template[0]['Children'][0]['Properties']["MessageUserTo"][1] = 'user_' . $this->getUserId('svetlana.kuznetsova');
$arNewFields = [
“TEMPLATE” => $template,
“VARIABLES” => $arFieldsTemplate["VARIABLES"]
];
$arNewFields["MODIFIER_USER"] = new \CBPWorkflowTemplateUser(CBPWorkflowTemplateUser::CurrentUser);
\CBPWorkflowTemplateLoader::Update($id, $arNewFields);
}
Здесь при запуске миграции мы загружаем файл и функцией importBP создаем/обновляем процесс. Далее мы получаем структуру шаблона бизнес-процесса в массив, подменяем ID и обновляем шаблон.
Подводя итоги
В этой статье мы затронули лишь отдельные случаи, где при переносе между площадками могут возникнуть несоответствия, и обозначили, на что обратить внимание. В целом мы в своей практике сталкивались со следующими привязками по id:
- user_ (привязка к пользователю)
- group_ (привязка к группе пользователей)
- iblock_ (привязка к инфоблоку)
- SequentialWorkflowActivity (запуск бизнес-процесса из шаблона)
- PROPERTY_ (привязка к полю документа с незаданным символьным кодом)
Если все сделано правильно, перенос отлаженного бизнес-процесса на продакшн проходит быстро и гладко.
Надеемся, что наш опыт был вам полезен!
Показать пример целиком
<?php
/**
* Updates migration scenario actions
**/
class ws_m_1565783124_approve_task extends \WS\Migrations\ScriptScenario
{
private $arBPFields = [
'DOCUMENT_TYPE' => [
'lists',
'BizprocDocument',
'iblock_'
],
'AUTO_EXECUTE' => 0,
'NAME' => 'Утверждение задач',
'CODE' => 'TEST',
];
private $codeIBlock = 'APPROVE_TASK';
/**
* Name of scenario
* @return string
**/
public static function name()
{
return 'approve task process';
}
/**
* Description of scenario
* @return string
**/
public static function description()
{
return 'process to approve task and set task deadline +14 days after approving';
}
/**
* @return array First element is hash, second is owner name
*/
public function version()
{
return ['13ebf9abe69204014459b80a7036b7a0', ''];
}
/**
* Return IBlock ID
* @return int
*/
private function getIblockId()
{
$result = CIBlock::GetList(
[],
[
'TYPE' => 'bitrix_processes',
'=CODE' => $this->codeIBlock
],
false,
['nTopCount' => 1]
);
if ($arIBlock = $result->Fetch()) {
return $arIBlock['ID'];
}
return 0;
}
/**
* Start import BP
* @param $path
* @return mixed
*/
private function importBP($path)
{
CModule::IncludeModule('bizproc');
CModule::IncludeModule('iblock');
//Get iBlock id for which BP is created
$this->arBPFields['DOCUMENT_TYPE'][2] .= $this->getIblockId();
// Get BP id by the CODE
$result = \CBPWorkflowTemplateLoader::GetList(
[],
[
'CODE' => $this->arBPFields['CODE'],
'MODULE_ID' => 'lists'
]
);
if ($arFields = $result->GetNext()) {
$id = $arFields['ID'];
} else {
$id = 0;
}
//read file to a variable
$f = fopen($path, 'rb');
$datum = fread($f, filesize($path));
fclose($f);
//Update BP if id>0, otherwise add BP
\CBPWorkflowTemplateLoader::ImportTemplate(
$id,
$this->arBPFields['DOCUMENT_TYPE'],
$this->arBPFields['AUTO_EXECUTE'],
$this->arBPFields['NAME'],
'',
$datum,
$this->arBPFields['CODE']
);
return $arFields['ID'];
}
/**
* @param $login
* @return mixed
*/
private function getUserId($login)
{
$rsUsers = Bitrix\Main\UserTable::getList([
"select" =>['ID'],
"filter" => ['LOGIN' => $login],
]);
$userFields = $rsUsers->fetch();
return $userFields['ID'];
}
/**
* @param $code
* @return mixed
*/
private function getGroupId($code)
{
$rsGroups = \Bitrix\Main\GroupTable::getList(
[
'filter' => ['STRING_ID'=> 'HR'],
'select' => ['ID']
]);
$arFields = $rsGroups->fetch();
return $arFields['ID'];
}
/**
* Write action by apply scenario. Use method `setData` for save need rollback data
**/
public function commit()
{
//make BP import
$pathBPElement = _DIR_ . '/files/bp-94-approve-task.bpt';
$id = $this->importBP($pathBPElement);
//get template data
$arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => $id])->GetNext();
$template = $arFieldsTemplate['TEMPLATE'];
//replace id inside BP tempalte
$template[0]['Children'][0]['Properties']['MessageUserTo'][0] = 'group_g' . $this->getGroupId('HR');
$template[0]['Children'][0]['Properties']['MessageUserTo'][1] = 'user_' . $this->getUserId('svetlana.kuznetsova');
$arNewFields = [
'TEMPLATE' => $template,
'VARIABLES' => $arFieldsTemplate['VARIABLES']
];
$arNewFields['MODIFIER_USER'] = new CBPWorkflowTemplateUser(CBPWorkflowTemplateUser::CurrentUser);
\CBPWorkflowTemplateLoader::Update($id, $arNewFields);
}
/**
* Write action by rollback scenario. Use method `getData` for getting commit saved data
**/
public function rollback()
{
$pathBPElement = _DIR_ . '/files/bp-wt-old.bpt';
$id = $this->importBP($pathBPElement);
$arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => $id])->GetNext();
$template = $arFieldsTemplate['TEMPLATE'];
$arNewFields = [
'TEMPLATE' => $template,
'VARIABLES' => $arFieldsTemplate['VARIABLES']
];
$arNewFields['MODIFIER_USER'] = new CBPWorkflowTemplateUser(CBPWorkflowTemplateUser::CurrentUser);
\CBPWorkflowTemplateLoader::Update($id, $arNewFields);
}
}