Если вы разрабатываете на Битрикс24 и поддерживаете несколько окружений — тест, стейдж, прод — вы знаете эту боль. Настроил воронку, добавил пользовательские поля, написал робота с десятком условий, всё это поправил в карточке, назначил права. А потом нужно повторить то же самое на проде. Руками. Забыв половину.
Конфигурация CRM — это не код. Она живёт в базе данных, не попадает в git, и нет адекватного механизма переноса между окружениями. При этом объём этой конфигурации на реальных проектах значительный: десятки смарт-процессов, сотни пользовательских полей, сложные роботы с условиями, матрицы прав доступа, кастомные виды карточек. Всё это нужно как-то синхронизировать.
В Битрикс24 есть разрозненные инструменты для переноса отдельных частей настроек — штатный экспорт некоторых сущностей через интерфейс, партнёрские модули, закрывающие часть задач. Но каждый работает по-своему, покрывает свой кусок, и ни один не даёт того, что нужно на реальном проекте: полного покрытия CRM-конфигурации в одном инструменте, версионируемого вместе с кодом.
Мы прошли этот путь и в итоге написали набор Version Builder’ов для модуля sprint.migration, покрывающих основные сущности CRM Битрикс24. В этой статье — о самой задаче, подходе и подводных камнях.
Контекст: 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 |
Нестабильная форма |
Стабильный токен |
|---|---|---|
|
Смарт-процесс |
|
|
|
Воронка смарт-процесса |
|
|
|
Стадия смарт-процесса |
|
|
|
Пользователь |
|
|
|
Подразделение HR |
|
|
|
Группа пользователей |
|
|
Токены хранятся прямо в теле миграции. При запуске up() шаблон резолвит их в локальные 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 чатов, объектов Диска
Стабилизация рекурсивна: обходит всё дерево активити, анализирует каждое свойство по типу активити, ключу и значению.
Глобальные константы и переменные БП (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/