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

    Для автоматизации своих операций бизнес часто использует Битрикс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 в админке:



    $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);
        }
    }

    SimbirSoft
    89,86
    Лидер в разработке современных ИТ-решений на заказ
    Поделиться публикацией

    Комментарии 0

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

    Самое читаемое