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

推荐订阅源

P
Privacy & Cybersecurity Law Blog
Engineering at Meta
Engineering at Meta
Forbes - Security
Forbes - Security
MongoDB | Blog
MongoDB | Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
About on SuperTechFans
量子位
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
雷峰网
雷峰网
腾讯CDC
P
Proofpoint News Feed
S
Schneier on Security
S
Secure Thoughts
V
Visual Studio Blog
Help Net Security
Help Net Security
The Hacker News
The Hacker News
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy International News Feed
SecWiki News
SecWiki News
S
SegmentFault 最新的问题
T
Threatpost
小众软件
小众软件
MyScale Blog
MyScale Blog
F
Fortinet All Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
T
Tailwind CSS Blog
I
Intezer
C
CERT Recently Published Vulnerability Notes
U
Unit 42
V
V2EX
Cyberwarzone
Cyberwarzone
Recorded Future
Recorded Future
O
OpenAI News
Project Zero
Project Zero
有赞技术团队
有赞技术团队
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
Know Your Adversary
Know Your Adversary
C
Cybersecurity and Infrastructure Security Agency CISA
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术
博客园 - 叶小钗
S
Securelist
A
Arctic Wolf
The Cloudflare Blog
W
WeLiveSecurity
T
Threat Research - Cisco Blogs
博客园 - Franky

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Миграции конфигурации Битрикс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. Изменения становятся прослеживаемыми: видно кто, что и когда поменял в структуре процессов, а не только в коде.

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