惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
Microsoft Security Blog
Microsoft Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
D
DataBreaches.Net
U
Unit 42
P
Proofpoint News Feed
I
InfoQ
Apple Machine Learning Research
Apple Machine Learning Research
Google DeepMind News
Google DeepMind News
博客园 - Franky
博客园_首页
IT之家
IT之家
博客园 - 叶小钗
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Миграции конфигурации Битрикс24 CRM: как мы перестали дел...
mrq · 2026-05-19 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение7 мин

Охват и читатели11

Кейс

Если вы разрабатываете на Битрикс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. Изменения становятся прослеживаемыми: видно кто, что и когда поменял в структуре процессов, а не только в коде.

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