Как работает сопоставление разделов Битрикс и 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. Это позволяет гибко настраивать соответствия без правки кода под каждого клиента. Если маппинга нет, событие просто пропускает раздел, и он создаётся в корне. Такой подход даёт прозрачность: всегда видно, какой раздел откуда берётся.
// 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С товар может быть привязан к группе, которая не попала в маппинг, мы не теряем данные, а просто размещаем их временно.
// 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.
Этапы настройки ручного сопоставления:
- Создать таблицу маппинга разделов в базе данных, в ней будет храниться соответствие GUID из 1С и ID раздела в Битрикс.
- Написать обработчик на событие
OnBeforeCatalogImport1C, который подменяет родительский раздел при создании элемента каталога. - Протестировать импорт на тестовом разделе, проверив, что товары попадают в нужные ветки каталога.
Создание таблицы маппинга разделов
Таблица маппинга, это простая связующая структура, которая хранит идентификатор группы из 1С и соответствующий ей ID раздела в Битрикс. Мы обычно создаём её в отдельной базе данных или в той же, где лежит каталог, с префиксом, чтобы не пересекаться с системными таблицами. Структура минималистична: GUID из 1С, ID раздела Битрикс и опциональный внешний код для дополнительной привязки.
Вот SQL для создания такой таблицы. Обратите внимание: поле external_code необязательно, но в проектах с большим каталогом мы его добавляем, оно позволяет маппить не только по GUID, но и по артикулу группы, если 1С передаёт его в XML.
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
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, о которых мы говорили выше.
- Определить структуру разделов в Битрикс (создать вручную). Создайте все необходимые разделы инфоблока через административный интерфейс: укажите названия, символьные коды, родителей. Зафиксируйте
IDкаждого раздела, они понадобятся для маппинга. Не полагайтесь на автоматическое создание из 1С, иначе ручное сопоставление теряет смысл. - Создать таблицу маппинга или конфиг-файл. Свяжите
XML_ID(внешний код) раздела из 1С сIDраздела в Битрикс. Проще всего хранить маппинг в виде PHP-массива вinit.phpили отдельном конфиге. Если разделов много, вынесите в.phpфайл с возвратом массива, чтобы не захламлятьinit.php. Пример структуры:'1C_GROUP_123' => ['bitrix_section_id' => 42, 'parent_id' => 10]. - Написать обработчик
OnBeforeIBlockSectionAdd, который подменяет внешний код и родителя. Этот хук срабатывает до создания раздела из 1С. Ваша задача, перехватить$arFields, найти в маппингеXML_ID, подставить нужныйIBLOCK_SECTION_IDи изменитьXML_IDна значение, которого нет в маппинге (чтобы повторно не сработало). Если раздела в маппинге нет, пропускаете, 1С создаст его как есть.
<?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';
}
}
);- Написать обработчик
OnBeforeIBlockElementAddдля привязки товаров к нужным разделам. Когда товар добавляется из 1С, у него в$arFields['IBLOCK_SECTION_ID']лежит ID раздела, который только что создал или нашёл штатный механизм. Но мы подменилиIDраздела в предыдущем шаге, поэтому товар получит правильную привязку. Однако если структура глубже, товар может прийти с родительским разделом, которого нет в маппинге. В этом случае подменяйтеIBLOCK_SECTION_IDна основеXML_IDтовара или дополнительной логики.
<?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. Штатная привязка по имени в этом случае приведёт к путанице и дублям. Обязательно укажите в настройках обмена режим «по идентификатору» и реализуйте логику в событиях для корректной обработки дублирующихся имён.