Play
online · 30 мин
Туториалы 12 мин чтения 15.05.2026

Сопоставление разделов Битрикс и 1С УТ при штатном обмене: кастомная иерархия каталога

Штатный обмен между Битрикс и 1С УТ сопоставляет разделы только по GUID, но при несовпадающей иерархии или переносе каталога это ломается. Разбираем, как работает сопоставление по умолчанию и почему для сложных структур нужна кастомная настройка.

Как работает сопоставление разделов Битрикс и 1С по умолчанию

Когда к нам приходят с задачей настроить обмен каталогом между 1С и Битрикс, первое, с чем сталкивается разработчик — штатный механизм сопоставления разделов. По умолчанию модуль обмена («1С:Управление торговлей») связывает группы товаров в 1С и инфоблоки Битрикс исключительно по GUID — глобальному уникальному идентификатору, который присваивается каждой группе в 1С при создании. В простых каталогах это работает безотказно: 1С передаёт GUID, Битрикс находит раздел с таким же значением в поле XML_ID и обновляет его. Проблема начинается, когда иерархия разделов не совпадает с плоской структурой 1С, либо когда каталог переносится из другой системы, и GUID в Битриксе не соответствуют GUID из 1С. У нас в практике регулярно встречаются проекты, где каталог собирается из нескольких источников, и GUID либо отсутствуют, либо дублируются. В таких случаях штатный механизм либо создаёт дубли разделов, либо не может найти родителя и ломает иерархию. Именно здесь возникает потребность в ручном сопоставлении, по внешнему коду группы, артикулу или произвольному маппингу.

Параметр Штатное сопоставление (GUID) Ручное (внешний код / маппинг)
Идентификатор GUID из 1С Внешний код, артикул, произвольный ID
Поле в Битрикс XML_ID UF_EXTERNAL_CODE или свой маппинг
Гибкость Низкая, строго по GUID Высокая, любые правила
Поддержка миграций Нет, GUID меняется Да, маппинг переносится
Риск дублей Высокий при конфликте GUID Минимальный

Штатного механизма достаточно, если каталог изначально создан через обмен из 1С и иерархия разделов не менялась вручную. В таких случаях GUID стабильны, и сопоставление происходит прозрачно. Кастомное сопоставление нужно, когда каталог мигрировал из другой CMS, разделы переименовывались или создавались через API, а также при сборке каталога из нескольких учётных систем. В наших проектах мы переходим на ручной маппинг, когда количество ошибок импорта превышает 5% от общего числа разделов, это сигнал, что GUID не синхронизированы и пора внедрять кастомную логику, о которой поговорим ниже.

Битрикс 1С управление торговлей обмен: события для кастомной иерархии

Реальная история — несколько лет назад на проекте с каталогом в 30 тысяч позиций мы получили задачу: «сделайте, чтобы товары из 1С попадали в правильные разделы, а не в корень». Штатный механизм сопоставления по внешнему коду группы, как мы видели выше, работал, но только если иерархия в 1С и Битрикс совпадает один-в-один. В реальности же у менеджеров своя логика: в 1С товар висит в группе «Столы офисные», а в интернет-магазине он должен оказаться в разделе «Мебель / Офисная / Столы». Здесь на помощь приходят события модулей iblock и catalog, которые стреляют в момент импорта разделов и элементов. Задача разработчика — перехватить эти события и подменить стандартное поведение: подсунуть нужный IBLOCK_SECTION_ID или изменить родительский раздел. В нашей практике мы комбинируем это с маппингом, хранящимся в отдельном справочнике или в пользовательских свойствах разделов.

Событие Момент срабатывания Аргументы Отменяемо
OnBeforeIBlockSectionAdd Перед добавлением раздела &$arFields Да
OnAfterIBlockSectionAdd После добавления раздела &$arFields Нет
OnBeforeIBlockElementAdd Перед добавлением товара &$arFields Да
OnAfterIBlockElementAdd После добавления товара &$arFields Нет

OnBeforeIBlockSectionAdd, перехват создания раздела

Ключевая точка входа для кастомизации иерархии, событие OnBeforeIBlockSectionAdd. Оно вызывается непосредственно перед тем, как раздел будет записан в базу. В аргументе $arFields лежит весь массив данных, которые пришли из XML: имя, символьный код, внешний код (XML_ID), а главное, IBLOCK_SECTION_ID, идентификатор родительского раздела. По умолчанию он берётся из XML-атрибута родительской группы, но мы можем его подменить на любой другой. Например, если в 1С пришла группа «Столы» с внешним кодом 1C_TABLES, а в Битрикс её родитель должен быть разделом «Мебель» с ID 42, мы просто заменяем значение IBLOCK_SECTION_ID на 42. Важно не забыть про кеш: после изменения иерархии нужно сбросить кеш инфоблока, иначе в админке будет путаница.

В наших проектах мы используем маппинг в отдельной таблице или в пользовательском свойстве раздела UF_1C_PARENT. Это позволяет гибко настраивать соответствия без правки кода под каждого клиента. Если маппинга нет, событие просто пропускает раздел, и он создаётся в корне. Такой подход даёт прозрачность: всегда видно, какой раздел откуда берётся.

PHP
// init.php
use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockSectionAdd',
    'customMappingSectionAdd'
);

function customMappingSectionAdd(&$arFields) {
    // Только для нужного инфоблока каталога
    if ($arFields['IBLOCK_ID'] != CATALOG_IBLOCK_ID) {
        return;
    }

    $xmlId = $arFields['XML_ID'] ?? '';
    if (!$xmlId) {
        return;
    }

    // Маппинг: внешний код группы из 1С -> ID родительского раздела в Битрикс
    $parentMap = [
        '1C_TABLES' => 42,   // раздел "Мебель"
        '1C_CHAIRS' => 42,
        '1C_LAMPS'  => 55,   // раздел "Освещение"
    ];

    if (isset($parentMap[$xmlId])) {
        $arFields['IBLOCK_SECTION_ID'] = $parentMap[$xmlId];
    }
}

OnBeforeIBlockElementAdd, привязка товара к нужному разделу

Когда разделы уже созданы с нужной иерархией, следующий шаг, привязать товары к правильным разделам. Событие OnBeforeIBlockElementAdd стреляет перед добавлением элемента каталога. В $arFields приходит массив с данными товара, включая IBLOCK_SECTION_ID, идентификатор раздела, который 1С указала в XML. Чаще всего это внешний код группы, в которой висит товар. Но поскольку мы уже переопределили иерархию разделов, этот идентификатор может вести в никуда или в неверный раздел. Задача обработчика, найти в Битрикс раздел по внешнему коду (XML_ID) и подставить его ID.

В нашей практике мы используем кешированный поиск: храним соответствие XML_ID => ID разделов в локальном массиве на время импорта. Это избавляет от лишних запросов к базе на каждый товар. Если раздел не найден, товар остаётся в корне, что лучше, чем ошибка импорта. Такой подход особенно полезен, когда в 1С товар может быть привязан к группе, которая не попала в маппинг, мы не теряем данные, а просто размещаем их временно.

PHP
// init.php
use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementAdd',
    'customMappingElementAdd'
);

function customMappingElementAdd(&$arFields) {
    if ($arFields['IBLOCK_ID'] != CATALOG_IBLOCK_ID) {
        return;
    }

    $sectionXmlId = $arFields['IBLOCK_SECTION_ID'] ?? '';
    if (!$sectionXmlId) {
        return;
    }

    // Статический кеш соответствий XML_ID => ID разделов
    static $sectionMap = [];

    if (!isset($sectionMap[$sectionXmlId])) {
        $section = \Bitrix\Iblock\SectionTable::getRow([
                'filter' => [
                    '=IBLOCK_ID' => CATALOG_IBLOCK_ID,
                    '=XML_ID'   => $sectionXmlId
                ],
                'select' => ['ID']
        ]);
        $sectionMap[$sectionXmlId] = $section ? (int)$section['ID'] : 0;
    }

    if ($sectionMap[$sectionXmlId] > 0) {
        $arFields['IBLOCK_SECTION_ID'] = $sectionMap[$sectionXmlId];
    } else {
        // Если раздел не найден — оставляем товар в корне
        unset($arFields['IBLOCK_SECTION_ID']);
    }
}

Кастомная иерархия каталога Битрикс: ручное сопоставление групп 1С

В прошлом году у нас был кейс: два крупных бренда объединили каталоги на одном сайте. В 1С каждый бренд жил со своей структурой разделов, но на сайте требовалась единая плоская иерархия по категориям товаров. Штатный механизм сопоставления по GUID тут бессилен — он жёстко привязан к дереву из 1С. Другой частый сценарий — когда магазин работает с товарами нескольких поставщиков, и каждый присылает свою структуру. Или когда нужно «подмешать» товары из одной группы 1С в несколько разделов сайта. Во всех этих случаях помогает ручное сопоставление через отдельную таблицу маппинга. Мы используем этот подход в проектах с нестандартной архитектурой каталога, когда стандартный CIBlockSection::Add из обработчика импорта не даёт нужной гибкости. Результат, полный контроль над тем, в какой раздел Битрикс попадает каждая группа из 1С, независимо от того, как она лежит в исходном XML.

Этапы настройки ручного сопоставления:

  1. Создать таблицу маппинга разделов в базе данных, в ней будет храниться соответствие GUID из 1С и ID раздела в Битрикс.
  2. Написать обработчик на событие OnBeforeCatalogImport1C, который подменяет родительский раздел при создании элемента каталога.
  3. Протестировать импорт на тестовом разделе, проверив, что товары попадают в нужные ветки каталога.

Создание таблицы маппинга разделов

Таблица маппинга, это простая связующая структура, которая хранит идентификатор группы из 1С и соответствующий ей ID раздела в Битрикс. Мы обычно создаём её в отдельной базе данных или в той же, где лежит каталог, с префиксом, чтобы не пересекаться с системными таблицами. Структура минималистична: GUID из 1С, ID раздела Битрикс и опциональный внешний код для дополнительной привязки.

Вот SQL для создания такой таблицы. Обратите внимание: поле external_code необязательно, но в проектах с большим каталогом мы его добавляем, оно позволяет маппить не только по GUID, но и по артикулу группы, если 1С передаёт его в XML.

SQL
CREATE TABLE IF NOT EXISTS mapping_catalog_sections (id INT AUTO_INCREMENT PRIMARY KEY,
                                                                           guid_1c VARCHAR(255) NOT NULL COMMENT 'GUID группы из 1С',
                                                                                                                 section_id_bitrix INT NOT NULL COMMENT 'ID раздела в Битрикс (iblock_section.ID)',
                                                                                                                                                        external_code VARCHAR(100) DEFAULT NULL COMMENT 'Внешний код группы (опционально)',
                                                                                                                                                                                                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                                                                                                                                                                                                                                     UNIQUE KEY uk_guid_1c (guid_1c),
                                                                                                                                                                                                                                                INDEX idx_external_code (external_code)) ENGINE = InnoDB DEFAULT
CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Маппинг групп 1С на разделы Битрикс для кастомной иерархии';

Обработчик, использующий маппинг

Теперь самое интересное, обработчик, который во время импорта подменяет родительский раздел. Ключевая логика: при создании нового раздела из 1С мы ловим событие OnBeforeCatalogImport1C, извлекаем GUID текущей обрабатываемой группы, ищем его в нашей таблице маппинга и, если находим, перезаписываем IBLOCK_SECTION_ID в массиве данных раздела. Важный нюанс: GUID приходит в $arFields['XML_ID'], это стандартное поле, куда 1С пишет идентификатор группы.

В наших проектах мы часто расширяем этот подход: добавляем проверку на наличие external_code, если GUID не найден, пробуем сопоставить по внешнему коду. Это даёт гибкость, когда в 1С меняют структуру, но внешние коды остаются стабильными. Ниже полный код обработчика, который можно разместить в init.php или в собственном модуле.

PHP
<?php

use Bitrix\Main\EventManager;
use Bitrix\Main\Application;

// Регистрируем обработчик на событие создания раздела при импорте из 1С
EventManager::getInstance()->addEventHandler(
    'catalog',
    'OnBeforeCatalogImport1C',
    'customCatalogSectionMappingHandler'
);

function customCatalogSectionMappingHandler(&$arFields)
{
    // Проверяем, что это создание раздела (а не товара)
    if ($arFields['IBLOCK_TYPE_ID'] !== 'catalog' || !isset($arFields['XML_ID'])) {
        return;
    }

    // Получаем GUID из 1С
    $guid1c = $arFields['XML_ID'];
    if (empty($guid1c)) {
        return;
    }

    // Ищем маппинг в базе
    $connection = Application::getConnection();
    $sql = "SELECT section_id_bitrix FROM mapping_catalog_sections WHERE guid_1c = '" .
    $connection->getSqlHelper()->forSql($guid1c) . "'";
    $result = $connection->query($sql);

    if ($row = $result->fetch()) {
        // Подменяем родительский раздел
        $arFields['IBLOCK_SECTION_ID'] = (int)$row['section_id_bitrix'];
        // Сбрасываем автоматическое создание вложенных разделов
        unset($arFields['SECTION_ID']);
    }
}

Штатный модуль обмена 1С Битрикс: настройка и ограничения

На одном из проектов мы столкнулись с ситуацией: в 1С структура каталога была плоской — все товары висели в корне, а в Битриксе требовалась глубокая иерархия с тремя уровнями вложенности. Штатный модуль обмена в административной панели предлагает несколько галочек, которые управляют тем, как создаются и обновляются разделы. Казалось бы, включил нужные опции — и задача решена. Но на практике эти настройки работают только в связке с тем, что приходит из 1С: если там нет структуры, модуль не сможет её «додумать». У нас в практике это регулярно всплывает у клиентов с большим каталогом, когда менеджеры в 1С не хотят или не могут менять свою логику группировки, а на сайте нужна совсем другая навигация.

  • «Создавать разделы», включает автоматическое создание новых групп инфоблока на основе групп из 1С. Если галочка снята, товары из неизвестных групп попадают в корень каталога.
  • «Обновлять разделы», разрешает изменять названия, символьные коды и описания существующих разделов по данным из 1С. При выключенной опции старые разделы остаются нетронутыми.
  • «Использовать внешний код», заставляет модуль сопоставлять разделы Битрикса и группы 1С по полю XML_ID, а не по названию. Это критично для стабильной идентификации при переименованиях.

Проблема в том, что штатные настройки работают в рамках жёсткой схемы: «что пришло из 1С, то и создаётся». Они не умеют перекладывать товары из одной группы в другую, создавать дополнительные уровни вложенности или игнорировать определённые группы. Когда к нам приходят с задачей кастомной иерархии, например, товары из группы «Холодильники» в 1С должны попадать в раздел «Бытовая техника / Крупная / Холодильники» на сайте, одними галочками не обойтись. Модуль просто не знает о такой логике, и любая попытка «договориться» с ним через настройки приводит к дублям разделов или товарам в корне.

Ручное сопоставление групп 1С Битрикс: пошаговый рецепт

Каталог на 50 тысяч SKU, импорт каждые 30 минут. Задача: товары из 1С должны попадать не в те разделы, которые передала 1С, а в заранее спроектированную структуру на сайте. Штатный механизм сопоставления по GUID здесь не поможет — 1С видит свою иерархию, а мы хотим свою. В нашей практике это всплывает на проектах, где каталог перестраивали под SEO-структуру, объединяли несколько каталогов брендов или делали «плоский» вывод товаров с группировкой по свойствам. Ручное сопоставление — это когда вы сами решаете, какой раздел из 1С (по внешнему коду) превращается в какой раздел Битрикс. Результат, полный контроль над иерархией без правок в 1С. Комбинируется с событиями OnBeforeIBlockSectionAdd и OnBeforeIBlockElementAdd, о которых мы говорили выше.

  1. Определить структуру разделов в Битрикс (создать вручную). Создайте все необходимые разделы инфоблока через административный интерфейс: укажите названия, символьные коды, родителей. Зафиксируйте ID каждого раздела, они понадобятся для маппинга. Не полагайтесь на автоматическое создание из 1С, иначе ручное сопоставление теряет смысл.
  2. Создать таблицу маппинга или конфиг-файл. Свяжите XML_ID (внешний код) раздела из 1С с ID раздела в Битрикс. Проще всего хранить маппинг в виде PHP-массива в init.php или отдельном конфиге. Если разделов много, вынесите в .php файл с возвратом массива, чтобы не захламлять init.php. Пример структуры: '1C_GROUP_123' => ['bitrix_section_id' => 42, 'parent_id' => 10].
  3. Написать обработчик OnBeforeIBlockSectionAdd, который подменяет внешний код и родителя. Этот хук срабатывает до создания раздела из 1С. Ваша задача, перехватить $arFields, найти в маппинге XML_ID, подставить нужный IBLOCK_SECTION_ID и изменить XML_ID на значение, которого нет в маппинге (чтобы повторно не сработало). Если раздела в маппинге нет, пропускаете, 1С создаст его как есть.
PHP
<?php
use Bitrix\Main\EventManager;

// Маппинг: внешний код из 1С => ID раздела в Битрикс и родитель
$groupMapping = [
    '1C_GROUP_ELECTRONICS' => ['section_id' => 12, 'parent_id' => 5],
    '1C_GROUP_CLOTHING'    => ['section_id' => 25, 'parent_id' => 5],
    '1C_GROUP_ACCESSORIES' => ['section_id' => 33, 'parent_id' => 25],
];

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockSectionAdd',
    function (&$arFields) use ($groupMapping) {
        $xmlId = $arFields['XML_ID'] ?? '';
        if (isset($groupMapping[$xmlId])) {
            $map = $groupMapping[$xmlId];
            // Подменяем ID раздела, чтобы 1С не создавала новый
            $arFields['ID'] = $map['section_id'];
            $arFields['IBLOCK_SECTION_ID'] = $map['parent_id'];
            // Меняем XML_ID, чтобы хук не сработал повторно
            $arFields['XML_ID'] = $xmlId . '_MAPPED';
        }
    }
);
  1. Написать обработчик OnBeforeIBlockElementAdd для привязки товаров к нужным разделам. Когда товар добавляется из 1С, у него в $arFields['IBLOCK_SECTION_ID'] лежит ID раздела, который только что создал или нашёл штатный механизм. Но мы подменили ID раздела в предыдущем шаге, поэтому товар получит правильную привязку. Однако если структура глубже, товар может прийти с родительским разделом, которого нет в маппинге. В этом случае подменяйте IBLOCK_SECTION_ID на основе XML_ID товара или дополнительной логики.
PHP
<?php
use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementAdd',
    function (&$arFields) use ($groupMapping) {
        // Если товар пришёл из 1С, у него есть XML_ID группы
        $groupXmlId = $arFields['IBLOCK_SECTION'] ?? '';
        if ($groupXmlId && isset($groupMapping[$groupXmlId])) {
            $map = $groupMapping[$groupXmlId];
            $arFields['IBLOCK_SECTION'] = [$map['section_id']];
        }
    }
);

Грабля: если раздел из 1С отсутствует в маппинге, обработчик его пропускает, 1С создаст раздел с оригинальным XML_ID. Товар, привязанный к этому разделу, останется в нём, а не в целевой структуре. В проектах с частичным маппингом это приводит к «потерянным» товарам в корне или в неожиданных ветках. Решение: добавьте в маппинг fallback-раздел (например, «Прочее») и в обработчике OnBeforeIBlockElementAdd при отсутствии groupXmlId в маппинге подставляйте его ID.

Частые вопросы

Можно ли сопоставить один раздел Битрикс с несколькими группами 1С?

Нет, штатный обмен допускает только связь «один раздел Битрикс = одна группа 1С». Если нужно объединить несколько групп 1С в один раздел, придётся перехватывать событие OnBeforeGroupUpdate или OnAfterGroupAdd и вручную менять XML_ID.

Что делать, если после ручного сопоставления при повторном обмене Битрикс снова создаёт дубли разделов?

Проверьте, что у раздела Битрикс заполнено поле XML_ID значением GUID группы 1С. Если поле пустое или содержит старый GUID, обмен создаст новый раздел. Используйте событие OnBeforeGroupAdd, чтобы подставить нужный XML_ID до сохранения.

Чем отличается ручное сопоставление через код от стандартной привязки по имени в настройках обмена?

Стандартная привязка по имени работает только при совпадении названий разделов и не учитывает GUID. Ручное сопоставление через события (OnBeforeGroupUpdate, OnBeforeGroupAdd) позволяет жёстко привязать раздел Битрикс к конкретной группе 1С по её уникальному идентификатору, игнорируя иерархию и переименования.

А если у меня кастомная иерархия каталога, где разделы Битрикс не соответствуют структуре групп 1С?

Вам нужно отключить автоматическое создание разделов в настройках обмена (флаг «создавать группы по данным из 1С») и в обработчике OnBeforeGroupAdd самостоятельно определять родительский раздел и XML_ID, опираясь на данные из CommerceML (например, поле ИдРодителя).

Можно ли использовать штатный модуль обмена, если в 1С УТ разделы имеют одинаковые названия?

Да, но только при ручном сопоставлении через GUID. Штатная привязка по имени в этом случае приведёт к путанице и дублям. Обязательно укажите в настройках обмена режим «по идентификатору» и реализуйте логику в событиях для корректной обработки дублирующихся имён.

#Bitrix #PHP #SQL
автор · Backend / SRE Engineer
Артем Колячек

Backend-разработчик и SRE в студии Paradigma. Занимается тем, что у других проектов обычно обнаруживается за неделю до запуска: переездом Bitrix-проектов между серверами, починкой кешей после миграций, выстраиванием pipeline для контейнеров, мониторингом под нагрузкой.

До Paradigma — 7 лет в backend (PHP + Postgres) и в operations (Linux, Docker, Coolify, restic-бэкапы). Любит когда логи разговаривают полным синтаксисом ошибки, а не «что-то пошло не так».

На блоге пишет ровно про те ситуации с которыми сам разбирался руками: какой Bitrix-апдейт сломал кеш и как откатить, почему Docker-сеть не находит контейнер после рекрейта, что делать когда asyncpg ругается на event loop.