|
Professor Seleznov
|
Если вы разрабатываете на Битрикс24 и поддерживаете несколько окружений — тест, стейдж, прод — вы знаете эту боль. Настроил воронку, добавил пользовательские поля, написал робота с десятком условий, всё это поправил в карточке, назначил права. А потом нужно повторить то же самое на проде. Руками. Забыв половину. Конфигурация CRM — это не код. Она живёт в базе данных, не попадает в git, и нет адекватного механизма переноса между окружениями. При этом объём этой конфигурации на реальных проектах значительный: десятки смарт-процессов, сотни пользовательских полей, сложные роботы с условиями, матрицы прав доступа, кастомные виды карточек. Всё это нужно как-то синхронизировать. В Битрикс24 есть разрозненные инструменты для переноса отдельных частей настроек — штатный экспорт некоторых сущностей через интерфейс, партнёрские модули, закрывающие часть задач. Но каждый работает по-своему, покрывает свой кусок, и ни один не даёт того, что нужно на реальном проекте: полного покрытия CRM-конфигурации в одном инструменте, версионируемого вместе с кодом. Мы прошли этот путь и в итоге написали набор Version Builder'ов для модуля sprint.migration, покрывающих основные сущности CRM Битрикс24. В этой статье — о самой задаче, подходе и подводных камнях.

Схема: Билдер → 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 слева, тот же массив с токенами справа Устройство каждого билдера Каждый билдер — 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 — выбор бизнес-процессов для экспорта Глобальные константы и переменные БП (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. Изменения становятся прослеживаемыми: видно кто, что и когда поменял в структуре процессов, а не только в коде. Это особенно важно на проектах с несколькими командами или долгим циклом разработки, где между «настроили на тесте» и «применили на проде» может пройти несколько спринтов.-Источник
|