Как стать автором
Обновить
105.86
SimbirSoft
Лидер в разработке современных ИТ-решений на заказ

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

Время на прочтение8 мин
Количество просмотров7.1K
Для автоматизации своих операций бизнес часто использует Битрикс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);
    }
}

Теги:
Хабы:
Всего голосов 9: ↑8 и ↓1+7
Комментарии1

Публикации

Информация

Сайт
www.simbirsoft.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия