Карточка товара с выбором цвета и размера выглядит как одна страница одного товара. В базе за ней стоят два инфоблока, десятки элементов и свойство-связь, про существование которого узнают обычно в тот момент, когда половина товаров перестала показываться в каталоге.
Торговые предложения — самая структурно сложная часть каталога Битрикса и одновременно самая распространённая. Через них работает любой магазин одежды, обуви, техники с модификациями. И почти все проблемы с ними сводятся к одной причине: цена, остаток и свойства выбора живут не там, где их ищут.
Что такое торговое предложение
Торговое предложение — это элемент отдельного инфоблока, представляющий одну конкретную комбинацию характеристик товара. Синяя футболка размера M — предложение. Просто «футболка» — товар.
Разделение проходит по одному признаку: что различается между вариантами, а что общее.
| На товаре | На предложении |
|---|---|
| Название, описание | Цена |
| Картинки (общие) | Остаток на складе |
| Бренд, страна | Артикул, штрихкод |
| Раздел каталога | Свойства выбора (цвет, размер) |
| SEO-поля, URL | Картинка конкретного варианта |
Отсюда правило, нарушение которого даёт больше всего потерянных часов: у товара с предложениями нет своей цены. Совсем. Цена, проставленная на элементе основного инфоблока, будет проигнорирована витриной, но останется лежать в базе и будет попадаться на глаза при отладке, сбивая с толку.
Как устроена связь
Три сущности держат конструкцию вместе.
Два инфоблока. Основной — товары, второй — предложения. Второй привязан к первому в настройках модуля торгового каталога.
Свойство связи. В инфоблоке предложений есть свойство типа «привязка к элементам», указывающее на товар. При обмене с 1С Битрикс создаёт его сам с кодом CML2_LINK.
Тип товара. Каждый элемент помечен типом: товар с предложениями, само предложение, простой товар, комплект. Витрина смотрит на этот тип, решая, как выводить карточку.
Проверить связку целиком можно одним запросом через D7-ORM:
<?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 идентификатор предложения составной:
<Предложение>
<Ид>f4e2c1a9-...#9a71b3d8-...</Ид>
<Наименование>Футболка базовая (Синий, M)</Наименование>
<ХарактеристикиТовара>
<ХарактеристикаТовара>
<Наименование>Цвет</Наименование>
<Значение>Синий</Значение>
</ХарактеристикаТовара>
<ХарактеристикаТовара>
<Наименование>Размер</Наименование>
<Значение>M</Значение>
</ХарактеристикаТовара>
</ХарактеристикиТовара>
<Цены>...</Цены>
<Количество>7</Количество>
</Предложение>Часть до # — GUID товара, часть после — GUID характеристики. Битрикс разбирает строку, ищет товар по первой части и записывает связь. Механика опознания по GUID разобрана в статье про протокол обмена по CommerceML.
Отсюда следует порядок, который нельзя нарушать: import.xml разбирается раньше offers.xml. Если предложения приедут первыми или товар не приедет вовсе, связывать будет не с чем.
Предложение без связи не исчезает — оно становится невидимым. Витрина его не покажет, в каталоге его не будет, но элемент останется в базе и продолжит обновляться каждым обменом: занимать место, участвовать в выборках по инфоблоку и попадать в выгрузки на маркетплейсы, если те работают по инфоблоку напрямую. Это не «ничего не произошло», а тихое накопление мусора, который однажды всплывёт в чужом отчёте.
Свойства выбора на карточке
Предложения есть, связь стоит, а на карточке нет ни цвета, ни размера — только цена. Причина почти всегда в том, что свойства не отмечены как участвующие в выборе.
Механизм двухступенчатый, и путаются обычно на первой ступени:
В настройках инфоблока предложений у свойства должен стоять флаг участия в выборе (в интерфейсе он называется по-разному в зависимости от версии, но смысл один — «показывать в списке выбора предложений»).
В параметрах компонента карточки эти свойства перечисляются отдельно, чаще всего в параметре вида OFFER_TREE_PROPS. Компонент строит дерево выбора только по перечисленным.
Пропуск любой ступени даёт один и тот же симптом. Проверять нужно обе.
<?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С Битрикс обычно создаёт свойства-списки сам; проблема возникает там, где свойства заводили руками.
Диагностика: пять запросов
Порядок от общего к частному. Каждый запрос отвечает на один вопрос.
-- 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 000 | 5 | 5 000 |
| 10 000 | 12 | 120 000 |
| 10 000 | 40 | 400 000 |
| 50 000 | 40 | 2 000 000 |
Последняя строка — уже полноценная нагруженная база, где имеют значение и режим хранения свойств, и индексы, и время обмена. Причём каталог «пятьдесят тысяч товаров» звучит скромно, а два миллиона элементов — нет, и осознание этого расхождения обычно приходит поздно.
Итоги
Товар и предложение — разные элементы разных инфоблоков. Всё, что различается между вариантами, лежит на предложении. У товара с предложениями своей цены не существует.
Связь держится на одном свойстве. Пустое — предложение невидимо, но продолжает существовать и обновляться. Это самая частая и самая незаметная поломка.
Выбор на карточке настраивается в двух местах. Инфоблок и параметры компонента. Симптом при пропуске любого из них одинаков.
Свойства выбора — только списки. Строки дают дубли вариантов, различающиеся пробелом.
Считайте элементы, а не товары. Инфоблок предложений растёт мультипликативно и достигает нагруженных размеров раньше, чем это становится очевидно.
Наблюдение напоследок: почти каждый раз, когда «часть товаров пропала из каталога» после обмена, пропали не товары. Пропала связь у предложений — товары на месте, просто показывать в них стало нечего. Проверка занимает один SQL-запрос, и начинать разбирательство стоит именно с него, а не с настроек вывода каталога.
Частые вопросы
Что такое торговое предложение в Битриксе?
Это элемент отдельного инфоблока предложений, представляющий одну конкретную комбинацию характеристик товара — например, синюю футболку размера M. Товар в основном инфоблоке хранит общее: название, описание, картинки. Предложение хранит то, что различается: цену, остаток, артикул и свойства выбора.
Почему у товара не показываются торговые предложения?
Три причины по частоте. Первая — у предложения пустое свойство связи с товаром, и витрина его не видит. Вторая — тип товара в карточке каталога не соответствует товару с предложениями. Третья — свойства не отмечены как участвующие в выборе, поэтому предложения есть, но выбирать по ним нечего.
Где хранится цена товара с торговыми предложениями?
На предложении, а не на товаре. Это принципиально: у товара с предложениями своей цены нет вообще, витрина показывает минимальную или первую доступную цену среди предложений. Цена, проставленная на самом товаре, будет проигнорирована и станет источником путаницы при отладке.
Как связаны товар и предложение при обмене с 1С?
В offers.xml идентификатор предложения имеет вид GUID_товара#GUID_характеристики. Битрикс разбирает эту строку, находит товар по первой части и записывает связь в свойство CML2_LINK предложения. Если родительский товар не пришёл или не создан, связь останется пустой, а предложение — невидимым для витрины.
Сколько торговых предложений может быть у одного товара?
Технического предела нет, но каждое предложение — полноценный элемент инфоблока со своими свойствами и ценами. Товар с 5 цветами и 8 размерами даёт 40 элементов, и на каталоге в 10 000 таких товаров получается 400 000 элементов в инфоблоке предложений — это уже размер, при котором нужно думать о производительности выборок и об индексах.