Торговые предложения: SKU, свойства и выбор на карточке

Как устроена связка двух инфоблоков, почему цена и остаток живут на предложении, а не на товаре, и что делать с предложениями, потерявшими родителя.

Дмитрий Мещеряков
Дмитрий Мещеряков
📅 19 октября 2026 г.📖 7 мин чтения

Карточка товара с выбором цвета и размера выглядит как одна страница одного товара. В базе за ней стоят два инфоблока, десятки элементов и свойство-связь, про существование которого узнают обычно в тот момент, когда половина товаров перестала показываться в каталоге.

Торговые предложения — самая структурно сложная часть каталога Битрикса и одновременно самая распространённая. Через них работает любой магазин одежды, обуви, техники с модификациями. И почти все проблемы с ними сводятся к одной причине: цена, остаток и свойства выбора живут не там, где их ищут.

Что такое торговое предложение

Торговое предложение — это элемент отдельного инфоблока, представляющий одну конкретную комбинацию характеристик товара. Синяя футболка размера M — предложение. Просто «футболка» — товар.

Разделение проходит по одному признаку: что различается между вариантами, а что общее.

На товареНа предложении
Название, описаниеЦена
Картинки (общие)Остаток на складе
Бренд, странаАртикул, штрихкод
Раздел каталогаСвойства выбора (цвет, размер)
SEO-поля, URLКартинка конкретного варианта

Отсюда правило, нарушение которого даёт больше всего потерянных часов: у товара с предложениями нет своей цены. Совсем. Цена, проставленная на элементе основного инфоблока, будет проигнорирована витриной, но останется лежать в базе и будет попадаться на глаза при отладке, сбивая с толку.

Как устроена связь

Три сущности держат конструкцию вместе.

Два инфоблока. Основной — товары, второй — предложения. Второй привязан к первому в настройках модуля торгового каталога.

Свойство связи. В инфоблоке предложений есть свойство типа «привязка к элементам», указывающее на товар. При обмене с 1С Битрикс создаёт его сам с кодом CML2_LINK.

Тип товара. Каждый элемент помечен типом: товар с предложениями, само предложение, простой товар, комплект. Витрина смотрит на этот тип, решая, как выводить карточку.

Проверить связку целиком можно одним запросом через D7-ORM:

php
<?php
use Bitrix\Catalog\CatalogIblockTable;
use Bitrix\Main\Loader;

Loader::includeModule('catalog');

$rows = CatalogIblockTable::getList([
    'select' => ['IBLOCK_ID', 'PRODUCT_IBLOCK_ID', 'SKU_PROPERTY_ID'],
])->fetchAll();

foreach ($rows as $row) {
    if ((int)$row['PRODUCT_IBLOCK_ID'] > 0) {
        printf(
            "Инфоблок предложений %d → товары %d, свойство связи %d\n",
            $row['IBLOCK_ID'],
            $row['PRODUCT_IBLOCK_ID'],
            $row['SKU_PROPERTY_ID']
        );
    }
}

Строка PRODUCT_IBLOCK_ID = 0 означает обычный каталог без предложений. Если вы ждёте предложений, а её нет — привязка инфоблоков не настроена, и дальше искать бессмысленно.

💡 Совет

SKU_PROPERTY_ID — это ID того самого свойства связи. Зная его, вы можете проверить любое предложение напрямую и не гадать, какое из свойств инфоблока отвечает за привязку. На проектах, где инфоблоки заводили руками, код свойства бывает любым — CML2_LINK гарантирован только для инфоблоков, созданных обменом.

Как предложения приезжают из 1С

В offers.xml идентификатор предложения составной:

xml
<Предложение>
  <Ид>f4e2c1a9-...#9a71b3d8-...</Ид>
  <Наименование>Футболка базовая (Синий, M)</Наименование>
  <ХарактеристикиТовара>
    <ХарактеристикаТовара>
      <Наименование>Цвет</Наименование>
      <Значение>Синий</Значение>
    </ХарактеристикаТовара>
    <ХарактеристикаТовара>
      <Наименование>Размер</Наименование>
      <Значение>M</Значение>
    </ХарактеристикаТовара>
  </ХарактеристикиТовара>
  <Цены>...</Цены>
  <Количество>7</Количество>
</Предложение>

Часть до # — GUID товара, часть после — GUID характеристики. Битрикс разбирает строку, ищет товар по первой части и записывает связь. Механика опознания по GUID разобрана в статье про протокол обмена по CommerceML.

Отсюда следует порядок, который нельзя нарушать: import.xml разбирается раньше offers.xml. Если предложения приедут первыми или товар не приедет вовсе, связывать будет не с чем.

⚠️ Важно

Предложение без связи не исчезает — оно становится невидимым. Витрина его не покажет, в каталоге его не будет, но элемент останется в базе и продолжит обновляться каждым обменом: занимать место, участвовать в выборках по инфоблоку и попадать в выгрузки на маркетплейсы, если те работают по инфоблоку напрямую. Это не «ничего не произошло», а тихое накопление мусора, который однажды всплывёт в чужом отчёте.

Свойства выбора на карточке

Предложения есть, связь стоит, а на карточке нет ни цвета, ни размера — только цена. Причина почти всегда в том, что свойства не отмечены как участвующие в выборе.

Механизм двухступенчатый, и путаются обычно на первой ступени:

В настройках инфоблока предложений у свойства должен стоять флаг участия в выборе (в интерфейсе он называется по-разному в зависимости от версии, но смысл один — «показывать в списке выбора предложений»).

В параметрах компонента карточки эти свойства перечисляются отдельно, чаще всего в параметре вида OFFER_TREE_PROPS. Компонент строит дерево выбора только по перечисленным.

Пропуск любой ступени даёт один и тот же симптом. Проверять нужно обе.

php
<?php
use Bitrix\Iblock\PropertyTable;
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

// Свойства инфоблока предложений — что там вообще есть
$rows = PropertyTable::getList([
    'select' => ['ID', 'CODE', 'NAME', 'PROPERTY_TYPE', 'MULTIPLE'],
    'filter' => ['=IBLOCK_ID' => 13],   // инфоблок предложений
    'order'  => ['SORT' => 'ASC'],
])->fetchAll();

foreach ($rows as $row) {
    printf("%-20s %-28s тип=%s\n", $row['CODE'], $row['NAME'], $row['PROPERTY_TYPE']);
}
💡 Совет

Свойства выбора должны быть типа «список», а не «строка». Строковое свойство технически можно вывести в выбор, но тогда «Синий» и «синий » с пробелом станут двумя разными вариантами, и на карточке появятся два визуально одинаковых цвета. При обмене с 1С Битрикс обычно создаёт свойства-списки сам; проблема возникает там, где свойства заводили руками.

Диагностика: пять запросов

Порядок от общего к частному. Каждый запрос отвечает на один вопрос.

sql
-- 1. Сколько предложений вообще есть
SELECT COUNT(*) FROM b_iblock_element WHERE IBLOCK_ID = 13;

-- 2. Сколько из них потеряли связь с товаром.
-- PROPERTY_ID берём из SKU_PROPERTY_ID, полученного выше
SELECT COUNT(*)
FROM b_iblock_element e
LEFT JOIN b_iblock_element_property p
    ON p.IBLOCK_ELEMENT_ID = e.ID AND p.IBLOCK_PROPERTY_ID = 247
WHERE e.IBLOCK_ID = 13
  AND (p.VALUE IS NULL OR p.VALUE = '');

-- 3. Товары, у которых нет ни одного предложения,
--    хотя тип товара говорит, что должны быть
SELECT COUNT(*) FROM b_catalog_product
WHERE TYPE = 3 AND ID NOT IN (
    SELECT DISTINCT p.VALUE_NUM
    FROM b_iblock_element_property p
    WHERE p.IBLOCK_PROPERTY_ID = 247 AND p.VALUE_NUM > 0
);

-- 4. Есть ли цены на самих товарах — их там быть не должно
SELECT COUNT(*) FROM b_catalog_price pr
JOIN b_iblock_element e ON e.ID = pr.PRODUCT_ID
WHERE e.IBLOCK_ID = 12;

-- 5. Предложения без цены — будут показаны как «нет в наличии»
SELECT COUNT(*) FROM b_iblock_element e
LEFT JOIN b_catalog_price pr ON pr.PRODUCT_ID = e.ID
WHERE e.IBLOCK_ID = 13 AND pr.ID IS NULL;
⚠️ Важно

Числовое значение типа товара в третьем запросе сверьте со своей версией. В коде для этого есть именованные константы ProductTable::TYPE_SKU и ProductTable::TYPE_OFFER, и в PHP правильно пользоваться ими, а не числами. Прямой SQL с зашитой цифрой удобен для разовой диагностики, но переносить его в код проекта не стоит: числа за именами могут отличаться, и молча неверный фильтр даст неверную статистику, на которую вы будете опираться.

Что ломается чаще всего

Товар приехал, предложения нет. Смотрите порядок разбора файлов и наличие товара с нужным GUID в import.xml.

Предложение есть, но не показывается. Пустая связь. Второй запрос из списка выше даёт число таких сразу.

Цена не та. Проверьте, не проставлена ли цена на самом товаре, и сопоставлены ли типы цен — про это в статье про цены из 1С.

Выбор не строится. Свойства не отмечены как участвующие в выборе или не перечислены в параметрах компонента. Обе ступени.

Дубли вариантов на карточке. Строковые свойства вместо списков, значения различаются пробелами или регистром.

Карточка стала медленной. Товар с сорока предложениями — это сорок элементов со свойствами и ценами, которые нужно выбрать и обработать. Здесь помогает кэширование, устройство которого для каталога разобрано в статье про политику кэширования.

Масштаб

Про это редко думают на старте, а зря. Число элементов в инфоблоке предложений растёт мультипликативно:

ТоваровВариантов на товарЭлементов в инфоблоке предложений
1 00055 000
10 00012120 000
10 00040400 000
50 000402 000 000

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

Итоги

Товар и предложение — разные элементы разных инфоблоков. Всё, что различается между вариантами, лежит на предложении. У товара с предложениями своей цены не существует.

Связь держится на одном свойстве. Пустое — предложение невидимо, но продолжает существовать и обновляться. Это самая частая и самая незаметная поломка.

Выбор на карточке настраивается в двух местах. Инфоблок и параметры компонента. Симптом при пропуске любого из них одинаков.

Свойства выбора — только списки. Строки дают дубли вариантов, различающиеся пробелом.

Считайте элементы, а не товары. Инфоблок предложений растёт мультипликативно и достигает нагруженных размеров раньше, чем это становится очевидно.

Наблюдение напоследок: почти каждый раз, когда «часть товаров пропала из каталога» после обмена, пропали не товары. Пропала связь у предложений — товары на месте, просто показывать в них стало нечего. Проверка занимает один SQL-запрос, и начинать разбирательство стоит именно с него, а не с настроек вывода каталога.

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

Что такое торговое предложение в Битриксе?

Это элемент отдельного инфоблока предложений, представляющий одну конкретную комбинацию характеристик товара — например, синюю футболку размера M. Товар в основном инфоблоке хранит общее: название, описание, картинки. Предложение хранит то, что различается: цену, остаток, артикул и свойства выбора.

Почему у товара не показываются торговые предложения?

Три причины по частоте. Первая — у предложения пустое свойство связи с товаром, и витрина его не видит. Вторая — тип товара в карточке каталога не соответствует товару с предложениями. Третья — свойства не отмечены как участвующие в выборе, поэтому предложения есть, но выбирать по ним нечего.

Где хранится цена товара с торговыми предложениями?

На предложении, а не на товаре. Это принципиально: у товара с предложениями своей цены нет вообще, витрина показывает минимальную или первую доступную цену среди предложений. Цена, проставленная на самом товаре, будет проигнорирована и станет источником путаницы при отладке.

Как связаны товар и предложение при обмене с 1С?

В offers.xml идентификатор предложения имеет вид GUID_товара#GUID_характеристики. Битрикс разбирает эту строку, находит товар по первой части и записывает связь в свойство CML2_LINK предложения. Если родительский товар не пришёл или не создан, связь останется пустой, а предложение — невидимым для витрины.

Сколько торговых предложений может быть у одного товара?

Технического предела нет, но каждое предложение — полноценный элемент инфоблока со своими свойствами и ценами. Товар с 5 цветами и 8 размерами даёт 40 элементов, и на каталоге в 10 000 таких товаров получается 400 000 элементов в инфоблоке предложений — это уже размер, при котором нужно думать о производительности выборок и об индексах.