Миграции конфигурации Битрикс24 CRM: как мы перестали делать это руками

от автора

Если вы разрабатываете на Битрикс24 и поддерживаете несколько окружений — тест, стейдж, прод — вы знаете эту боль. Настроил воронку, добавил пользовательские поля, написал робота с десятком условий, всё это поправил в карточке, назначил права. А потом нужно повторить то же самое на проде. Руками. Забыв половину.

Конфигурация CRM — это не код. Она живёт в базе данных, не попадает в git, и нет адекватного механизма переноса между окружениями. При этом объём этой конфигурации на реальных проектах значительный: десятки смарт-процессов, сотни пользовательских полей, сложные роботы с условиями, матрицы прав доступа, кастомные виды карточек. Всё это нужно как-то синхронизировать.

В Битрикс24 есть разрозненные инструменты для переноса отдельных частей настроек — штатный экспорт некоторых сущностей через интерфейс, партнёрские модули, закрывающие часть задач. Но каждый работает по-своему, покрывает свой кусок, и ни один не даёт того, что нужно на реальном проекте: полного покрытия CRM-конфигурации в одном инструменте, версионируемого вместе с кодом.

Мы прошли этот путь и в итоге написали набор Version Builder’ов для модуля sprint.migration, покрывающих основные сущности CRM Битрикс24. В этой статье — о самой задаче, подходе и подводных камнях.

Схема: Билдер → PHP-файл → git → up() на проде

Схема: Билдер → PHP-файл → git → up() на проде

Контекст: sprint.migration и Version Builder

sprint.migration — это инструмент миграций для Битрикс24, аналог Flyway или Liquibase для БД. Каждая миграция — PHP-класс с методами up() и down(). Классы хранятся в репозитории, запускаются последовательно, их статус отслеживается.

Кроме обычных миграций, в sprint.migration есть Version Builder — интерактивный генератор. Билдер запускается через UI в админке Битрикс24: пользователь выбирает что экспортировать, билдер собирает данные из БД и рендерит готовый PHP-файл миграции. Миграция коммитится в git и применяется на других окружениях командой или через CI.

Главная проблема: нестабильные идентификаторы

Битрикс24 хранит почти всё по автоинкрементным ID в MySQL. Смарт-процесс, созданный первым на тестовом сервере, получит ENTITY_TYPE_ID = 1032. На продовом сервере, где до него создали другие сущности, он получит ENTITY_TYPE_ID = 1200. Воронки, стадии, роли, пользователи, подразделения — всё имеет разные ID в разных окружениях.

Если просто выгрузить данные как есть и применить — ничего не заработает. Миграция сослалась на CATEGORY_ID = 84, а на целевом сервере эта воронка живёт под ID 12.

Решение — паттерн стабилизации: перед экспортом все нестабильные ID заменяются на стабильные человекочитаемые токены, а при применении миграции токены резолвятся обратно в локальные ID целевого окружения.

Тип ID

Нестабильная форма

Стабильный токен

Смарт-процесс

CRM_1032

SMART_PROCESS:Подбор персонала

Воронка смарт-процесса

DYNAMIC_1032_C10

##CATFUNNEL(Подбор персонала|Общая)##

Стадия смарт-процесса

DT1032_10:UC_NFJ4TL

##STAGE_IN(Подбор персонала|Общая|Доработка)##

Пользователь

U123

U_LOGIN:ivanov

Подразделение HR

SNDR42

DR_PATH:Компания|Отдел продаж|Менеджеры

Группа пользователей

G5

G_NAME:Руководители

Токены хранятся прямо в теле миграции. При запуске up() шаблон резолвит их в локальные ID и применяет данные.

До и после стабилизации: массив с сырыми ID слева, тот же массив с токенами справа

До и после стабилизации: массив с сырыми ID слева, тот же массив с токенами справа

Устройство каждого билдера

Каждый билдер — PHP-класс, наследующий Sprint\Migration\VersionBuilder. Три обязательных метода:

  • isBuilderEnabled() — проверяет доступность нужного модуля (crm, bizproc, humanresources). Если модуль не подключён, билдер не показывается в UI.

  • initialize() — задаёт название, группу, описание, вызывает addVersionFields().

  • execute() — интерактив: через addFieldAndReturn() рендерит виджет выбора, собирает данные, вызывает createVersionFile().

class CrmFunnelBuilder extends VersionBuilder{    protected function isBuilderEnabled(): bool    {        return Loader::includeModule('crm');    }    protected function initialize(): void    {        $this->setTitle('Воронки и смарт-процессы CRM');        $this->setGroup('CRM');        $this->addVersionFields();    }    protected function execute(): void    {        $selected = $this->addFieldAndReturn('entity_keys', [            'title'    => 'Выберите воронки и смарт-процессы',            'multiple' => true,            'items'    => $this->buildEntitySelect(),        ]);        if (empty($selected)) {            $this->rebuildField('entity_keys');        }        $this->createVersionFile(            __DIR__ . '/../templates/CrmFunnelExport.php',            ['items' => $this->collectData($selected)]        );    }}

Шаблон миграции — отдельный PHP-файл. Билдер передаёт в него массив уже стабилизированных данных; шаблон рендерит полноценный класс миграции с логикой в up(). Резолв токенов происходит именно там — непосредственно перед применением данных на целевом сервере:

// ##STAGE_IN(SmartTitle|FunnelName|StageName)## → локальный statusId$value = preg_replace_callback(    '/##STAGE_IN\((.+?)\|(.+?)\|(.+?)\)##/',    function (array $m): string {        $entityKey = 'SMART_PROCESS:' . $this->uesc($m[1]);        $catId     = $this->resolveFunnelName($this->uesc($m[2]), $entityKey);        $statusId  = $this->resolveStageByNameInCat($this->uesc($m[3]), $entityKey, $catId);        return $statusId ?? $m[0];    },    $value);

Что покрыто

Воронки и смарт-процессы (CrmFunnelBuilder)

Мигрирует воронки Сделок и смарт-процессы вместе со стадиями, IS_*-настройками (поддержка документов, источники, автоматизация и т.д.), связями между сущностями и привязками к пользовательским полям. При применении на целевом сервере: смарт-процесс не существует — создаётся; существует — обновляются настройки. Воронки и стадии — upsert по имени.

Пользовательские поля (CrmUserFieldBuilder)

Мигрирует UF-поля CRM-сущностей: тип поля, настройки, значения списка (enumeration) с XML_ID. ENTITY_ID стабилизируется — вместо CRM_1032 в файле лежит SMART_PROCESS:Подбор персонала. Применение через хелпер sprint.migration: saveUserTypeEntity().

Вид карточки элемента CRM (CrmCardConfigBuilder)

Это один из самых сложных билдеров. Карточка CRM — не просто список полей. Есть общий вид (COMMON), личные настройки (PERSONAL), кастомные виды с матрицей доступа по ролям/отделам/пользователям. Плюс конфигурация табов, ссылки на REST-приложения, атрибуты полей (обязательность по стадиям).

Нестабильны: ID смарт-процессов в именах полей (PARENT_ID_1032), ID воронок в атрибутах стадий, ID REST-обработчиков, ключи табов (содержат entityTypeId и catId), HR-узлы. Всё это стабилизируется в соответствующие токены и резолвится обратно при применении.

Настройки списков CRM (CrmListConfigBuilder)

Мигрирует настройки колонок в списочных представлениях CRM: какие колонки показаны, их порядок и ширина. Идентификаторы гридов зависят от типа сущности и воронки, поэтому стабилизируются аналогично.

Источники лидов (CrmSourceBuilder)

Мигрирует источники лидов CRM. Источники идентифицируются по строковому STATUS_ID — он стабилен по природе, поэтому этот билдер самый простой в реализации.

Списки и Процессы (CrmListProcessBuilder)

Мигрирует универсальные списки и процессы Битрикс24 — тип, поля, настройки отображения, права.

Роли доступа CRM (CrmRoleBuilder)

Мигрирует роли CRM со всей матрицей прав: действия над сущностями (Чтение/Создание/Изменение/Удаление), ограничения по ответственному и воронке, участники роли (пользователи, группы, подразделения). Права хранятся во внутреннем формате ATTR, который разбирается и собирается обратно.

Бизнес-процессы, роботы и триггеры (CrmBizProcBuilder)

Самый объёмный билдер. БП в Битрикс24 — это XML-подобные деревья активити с вложенными свойствами. Нестабильных ID там очень много:

  • Стадии сделок и смарт-процессов в условиях и переходах

  • ID смарт-процессов в активити типа CrmGetDynamicInfo, CrmCreateDynamic

  • Пользователи в полях «Ответственный», «Наблюдатели», условиях

  • Подразделения и группы

  • ID воронок (CATEGORY_ID) для CreateDynamic-активити — причём воронка принадлежит не документу, а создаваемой сущности

  • UUID значений пользовательских полей типа enumeration

  • ID чатов, объектов Диска

Стабилизация рекурсивна: обходит всё дерево активити, анализирует каждое свойство по типу активити, ключу и значению.

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

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

Глобальные константы и переменные БП (CrmBpGlobalConstVarBuilder)

Мигрирует глобальные константы и переменные бизнес-процессов вместе с их типами и значениями.

Ключевые технические трудности

Кросс-сущностный контекст в БП

Активити CrmCreateDynamicActivity создаёт элемент другого смарт-процесса прямо внутри БП. Свойства этого активити содержат CATEGORY_ID и STAGE_ID для создаваемой сущности, а не для документа-источника.

Поначалу это не было очевидно: при стабилизации весь контекст берётся из документа БП, и для большинства свойств это работает. Но для CrmCreateDynamicActivity контекст другой — целевая сущность читается из соседнего поля DynamicTypeId, и дальше воронка/стадия резолвятся уже в её рамках. В токен включаем имя сущности явно: ##CAT_ID(Рабочее место сотрудника|Создание)## — иначе при применении на целевом сервере резолвер искал бы воронку «Создание» в документе-источнике и не находил.

Options как два разных типа данных

В дескрипторе полей DynamicEntityFields свойство Options может быть либо картой значение → отображение (для select-полей), либо объектом настроек поля (для iblock_element, datetime и т.п.). Один и тот же ключ, разная семантика — и разная стабилизация.

Попытка обработать объект настроек как карту значений давала неочевидные баги: IBLOCK_ID оказывался в значениях, которые пытались заматчить на стадии и воронки. Пришлось ввести эвристику: если среди ключей есть известные настроечные ключи (IBLOCK_ID, DISPLAY, DEFAULT_VALUE…), это объект настроек и его нужно обрабатывать иначе.

Обратная совместимость токенов

Формат ряда токенов менялся по ходу разработки. Например, токен стадии ##STAGE_IN## изначально был 2-частным (воронка + стадия), потом стал 3-частным — добавили имя смарт-процесса, чтобы резолв работал независимо от контекста документа. Старые миграции с 2-частными токенами должны продолжать работать. Реализован fallback: если 3-частный токен не резолвится по основному пути, применяется поиск по всем смарт-процессам.

Совместимость версий Битрикс24

Некоторые поля сущностей появились в новых версиях платформы (IS_RECURRING_ENABLED в TypeTable, ON_ADD/ON_UPDATE в EntityFormConfigTable). При запуске билдера на старой версии запрос с такими полями в select бросает SystemException с говорящим текстом «Unknown field definition». Решение: перед запросом получаем список реально существующих полей через getEntity()->getFields() и фильтруем select динамически.

Итог

Билдеры покрывают основной пласт конфигурации CRM: структуру данных (воронки, стадии, смарт-процессы), поля, права доступа, автоматизацию, внешний вид. Конфигурация CRM теперь живёт в git наравне с кодом — её можно ревьюить, откатывать, применять в CI. Изменения становятся прослеживаемыми: видно кто, что и когда поменял в структуре процессов, а не только в коде.

Это особенно важно на проектах с несколькими командами или долгим циклом разработки, где между «настроили на тесте» и «применили на проде» может пройти несколько спринтов.

ссылка на оригинал статьи https://habr.com/ru/articles/1036734/