Битрикс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 в админке:
В массиве параметров процесса мы видим вот такие вхождения:
Смотрим на строку 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); } }
ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/466823/