How we set up migrations for business processes in Bitrix24

To automate their operations, the business often uses Bitrix24. In this article, we talk about some of the possible problems when changing business processes and how we solved them.







Bitrix24 is one of the common CRM systems. It includes a visual designer (designer) for building business process diagrams. It is important to remember that when editing these processes, difficulties are possible - especially on large existing projects, where any changes are first checked on the local and test servers. In such cases, when transferring to production, we use the business process migration mechanism (hereinafter - BP).



Small companies, as a rule, can do without migration and simply suspend a particular business process for 2-3 days. A large business usually cannot afford it, therefore it uses test servers and deployment.







Bitrix24 on how a business process template works



Work with migration has its own characteristics. In particular, it is complicated by the large number of objects and ID involved. In addition, Bitrix24 also does not provide for the migration of business processes as such - often this problem is solved through import and export, and there may be various inconsistencies. Let's consider what problems are possible at the same time from the point of view of development.



Problem finding a business process template



When creating a business process, you can only assign a name to the template, not a unique code. In this case, when updating a business process, it will have to be obtained from the database by name. Names sometimes change because the system uses them to display in the list of processes at startup. Accordingly, there may be situations when it is impossible to find a template during the update. In general, searching by name is not such a good idea.



Decision:



All business process templates created in the system are stored in the b_bp_workflow_template table. Having opened the table, among the fields we see SYSTEM_CODE: there is a field for the code, it is simply not output to the interface. We can set the code ourselves using the template id - it can be seen in the url on the process editing page:







We need to create a function in order to get the id of the template and the code, check for duplication and the completeness of the field for the template to be changed, and also set its code.



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





Move on. For example, create a test business process on the lists:







To transfer a locally developed process to a test server (and then to production), we use the migration mechanism.



Bitrix24 allows you to export a business process. We will use this opportunity.







The transfer scheme is as follows:





Next, consider how this process occurs.



Create migration



We will use the migration module from the marketplace: https://marketplace.1c-bitrix.ru/solutions/ws.migrations/ .



Migration files in our project are located at local / migrations / scenarios









Open the process template page and export. Inside the migration directory, create the files directory and put the exported file there. It turns out like this:



local / migrations / scenarios / files / bp-94.bpt



Create a migration scenario:



 class ws_m_1565783124_approve_task extends \WS\Migrations\ScriptScenario {
      
      





Define the parameters of the business process template:



 class ws_m_1565783124_approve_task extends \WS\Migrations\ScriptScenario { private $arBPFields = [ 'DOCUMENT_TYPE' => [ 'lists', 'BizprocDocument', 'iblock_' ], 'AUTO_EXECUTE' => 0, 'NAME' => ' ', 'CODE' => 'TEST', ];
      
      





We implement the import function of the business process:



 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']; }
      
      





Here we first determine the ID of the information block for which we are applying the process, and get the id of the process template with the given code.



If the template is found, we update it. If not found - add.

The function returns the id of the created or updated process, and for what it is needed - we will tell further.



We define a commit function that will add / update our business process:



 public function commit() { $pathBPElement = __DIR__ . '/files/bp-94-approve-task.bpt'; $id = $this->importBP($pathBPElement); }
      
      





So, at this step we are already able to create and update a specific business process through the migration module.



Problem updating template data



Let's go back to our business process and add an action there - user notification.







As the sender we select the Author. Recipients will:





And now we look how the business process is recorded in the database. To do this, we get and print the template in the PHP console in the admin panel:







 $arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => 94])->GetNext(); echo '<pre>'; var_dump($arFieldsTemplate);
      
      







In the array of process parameters, we see these occurrences:







We look at the line group_g15. Here 15 is the HR group ID.

We look at the line user_579. Here 579 is the user ID.



This means that if we import the process at another site, we will have continuous inconsistencies.



T.O. we need to make a replacement after migrating these IDs to those that are relevant for the site where we are importing the process.



Groups are identified by symbolic code, users by login.



To begin with, on the site where the process was created, we get the group symbolic code and user login. In the event that you do not have symbolic group codes set, it is better to write the migration and install them first.



In our example:





Next, we write in the migration functions that, by code and login, will give us the id of the group and user:





Finally, update the appropriate values ​​in the template:



 /** * Write action by apply scenario. Use method `setData` for save need rollback data **/ public function commit() {
      
      







Import a business process:



 $pathBPElement = __DIR__ . '/files/bp-94-approve-task.bpt'; $id = $this->importBP($pathBPElement);
      
      







We get the template data:



 $arFieldsTemplate = \CBPWorkflowTemplateLoader::GetList([], ['ID' => $id])->GetNext(); $template = $arFieldsTemplate["TEMPLATE"];
      
      







Replace the user id inside the business process:



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





Here, when starting the migration, we upload the file and create / update the process with the importBP function. Next, we get the structure of the business process template into an array, replace the ID and update the template.



To summarize



In this article, we touched on only a few cases where inconsistencies may occur during the transfer between sites, and outlined what to look for. In general, in our practice, we came across the following id bindings:





If everything is done correctly, transferring a debugged business process to production is quick and smooth.



We hope that our experience was useful to you!



Show full example
 <?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); } }
      
      








All Articles