Как мы настраивали миграции для бизнес-процессов в Битрикс24

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



Битрикс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/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *