Торговые предложения изнутри - хранение, типы, выборка, корзина
Разбираем товар с вариантами по слоям: где хранится связь двух инфоблоков и чем родитель отличается от предложения. Дальше смотрим, что из этого попадает в выборку, корзину и строку заказа.
Механика
Товар с вариантами занимает сразу два инфоблока: карточку-родителя и отдельный инфоблок предложений. Каждый вариант - это обычный элемент второго инфоблока со своими свойствами, ценой и остатком.
Связь двух инфоблоков хранит не свойство, а запись торгового каталога у инфоблока предложений. В этой записи заполнены идентификатор инфоблока товаров и идентификатор свойства привязки конкретного проекта.
Тип каталога у инфоблока принимает одно из четырёх значений, и по нему код понимает свою роль. Самостоятельный каталог, инфоблок товаров с вариантами, инфоблок предложений и совмещённый вариант различаются одной буквой.
Само свойство привязки заводится в инфоблоке предложений и имеет тип привязки к элементам. Платформа создаёт его сама при включении вариантов, и множественным оно быть не может.
У каждой товарной записи есть тип, и он важнее любых догадок по структуре. Родитель с вариантами, само предложение, предложение без родителя и родитель без вариантов различаются числом в одном поле.
Цена и остаток принадлежат предложению, а не родительской карточке товара. Витрина показывает минимальную цену среди вариантов, поэтому пустая цена у родителя - это норма, а не поломка обмена.
Доступность считается платформой автоматически и сверху вниз. Предложение недоступно при включённом учёте остатка и нулевом количестве, а родитель недоступен, когда недоступны все его варианты.
Выбор варианта в карточке строится не по всем свойствам предложения. Годятся только немножественные свойства типов «список», «справочник» и «привязка к элементам», остальные покупателю выбирать нечем.
Свойства предложений участвуют в умном фильтре через фасетный индекс. Фильтр по цвету находит родительскую карточку, хотя цвет заведён у варианта, и это работает только при собранном индексе.
В корзину и в заказ попадает именно предложение, а не карточка товара. Родителя приходится доставать отдельным вызовом, когда в письме или в отчёте нужно название исходного товара.
Шаги
- Определить тип каталога у инфоблока и понять его роль в связке.
- Прочитать запись торгового каталога и взять из неё свойство привязки.
- Посмотреть тип товарной записи у родителя и у конкретного варианта.
- Получить предложения товара штатным вызовом и сверить их цены с остатками.
- Проверить, что в корзину уехало предложение, и найти его родителя.
Код
Определяем тип каталога у инфоблока:
\Bitrix\Main\Loader::includeModule('catalog');$info = \CCatalogSku::GetInfoByIBlock($iblockId);printf("тип каталога: %s\n", $info['CATALOG_TYPE'] ?? 'не каталог');// D - обычный каталог, P - товары с вариантами, O - инфоблок предложений, X - оба сразу// пустой результат означает, что инфоблок вообще не подключён к торговому каталогуБуква типа отвечает на главный вопрос до всякой выборки. Код, который ждёт варианты от обычного каталога, получает пустой массив и молча показывает карточку без выбора.
Читаем связку двух инфоблоков напрямую:
$link = \Bitrix\Catalog\CatalogIblockTable::getRow([ 'filter' => ['=IBLOCK_ID' => $offerIblockId], 'select' => ['IBLOCK_ID', 'PRODUCT_IBLOCK_ID', 'SKU_PROPERTY_ID'],]);print_r($link); // PRODUCT_IBLOCK_ID - инфоблок товаров, SKU_PROPERTY_ID - свойство привязки// оба поля заполняются только вместе: у обычного каталога здесь нулиИдентификатор свойства привязки свой на каждом стенде и меняется при пересоздании инфоблока. Поэтому его читают из связки, а не зашивают числом в код проекта.
Смотрим тип товарной записи:
$row = \Bitrix\Catalog\ProductTable::getRow(['filter' => ['=ID' => $elementId], 'select' => ['ID', 'TYPE', 'AVAILABLE', 'QUANTITY', 'QUANTITY_TRACE', 'CAN_BUY_ZERO']]);print_r($row);// TYPE: 3 - родитель с вариантами, 4 - предложение, 5 - предложение без родителя,// 6 - родитель без вариантов, 1 - простой товар, 7 - услугаТипы 5 и 6 читаются как диагноз сразу. Первый означает предложение с потерянной привязкой, второй - карточку, у которой варианты не создались или были удалены обменом.
Получаем предложения товара штатным вызовом:
$offers = \CCatalogSKU::getOffersList($productId, 0, ['ACTIVE' => 'Y'], ['ID', 'NAME', 'QUANTITY'], ['ACTIVE' => 'Y']);print_r($offers[$productId] ?? []); // результат сгруппирован по идентификатору родителя// второй аргумент 0 - автоопределение инфоблока предложений по товаруВызов рассчитан на товары одной страницы, а не на весь каталог. Список полей задают минимальный: каждое лишнее поле умножается на число вариантов и заметно утяжеляет выборку.
Смотрим, где на самом деле лежит цена:
$prices = \Bitrix\Catalog\PriceTable::getList(['filter' => ['=PRODUCT_ID' => $offerId], 'select' => ['CATALOG_GROUP_ID', 'PRICE', 'CURRENCY']])->fetchAll();printf("цен у варианта: %d, у родителя: %d\n", count($prices), \Bitrix\Catalog\PriceTable::getCount(['=PRODUCT_ID' => $productId]));// у родителя ноль цен - это штатное состояние товара с вариантамиПроверяем, что уехало в корзину:
$item = $basket->getItemById($basketItemId);$offerId = $item->getProductId(); // это идентификатор предложения$parent = \CCatalogSku::GetProductInfo($offerId, $offerIblockId);printf("вариант %d, родитель %s\n", $offerId, $parent ? $parent['ID'] : 'потерян');// название позиции корзины хранится копией и после правки товара не меняетсяПозиция корзины хранит копию названия на момент добавления. Поэтому переименование варианта в каталоге не меняет старые заказы, и бухгалтерия видит именно то, что покупал клиент.
Обновляем фасетный индекс после правки свойств варианта:
\Bitrix\Iblock\PropertyIndex\Manager::updateElementIndex($offerIblockId, $offerId);// индекс умного фильтра сам себя не пересчитывает после записи из кода\Bitrix\Iblock\PropertyIndex\Manager::markAsInvalid($offerIblockId); // пометить весь индексОграничения
Уровень вложенности ровно один. У предложения не бывает своих предложений, а сочетание «цвет плюс размер» задают двумя свойствами одного варианта.
Отбор покупателем идёт только по немножественным свойствам списков, справочников и привязок. Множественное свойство или строка в выбор варианта не превращаются, как бы этого ни хотелось маркетологу.
Родительская карточка не покупается. В корзину платформа пускает простые товары, комплекты, услуги и предложения, а родитель с вариантами туда не попадает вовсе.
Инфоблок предложений имеет собственные права доступа. Закрытый для группы инфоблок даёт пустой выбор в карточке, хотя сами варианты в базе активны и доступны.
Доступность родителя пересчитывается после записи товарных данных варианта. Прямая правка остатка запросом мимо модели каталога оставляет флаг доступности прежним.
Кэш карточки хранит и список вариантов. Изменение остатка через код требует сброса кэша или тегированного кэша, иначе витрина показывает вчерашнее наличие.
Типичные проблемы
Код ищет варианты, а получает пустой массив.
Инфоблок оказался обычным каталогом, и связки предложений у него нет. Тип каталога проверяют до выборки, иначе карточка молча выводится без выбора вариантов.
После обмена часть предложений пропала из каталога.
У этих записей тип стал «предложение без родителя»: привязка указывает на несуществующий товар. Такие варианты ищут выборкой по типу товарной записи и чинят привязку.
Фильтр по цвету не находит товары после импорта.
Свойства варианта записаны кодом, а фасетный индекс не пересобран. Индекс обновляют вызовом менеджера индекса сразу после массовой записи предложений.
В письме менеджеру приходит название варианта вместо товара.
Позиция заказа ссылается на предложение и хранит его название копией. Название родителя получают отдельным вызовом по идентификатору варианта.
Остаток изменили запросом, а товар остался недоступным.
Прямая запись в таблицу не пересчитывает флаг доступности ни у варианта, ни у родителя. Остатки меняют моделью каталога, которая пересчитывает доступность сама.
Свойство завели у товара, а выбирать его нужно у варианта.
Свойства товара и предложения живут в разных инфоблоках и не связаны между собой. Свойство выбора заводят именно в инфоблоке предложений и отмечают в параметрах карточки.
Частые вопросы
Как понять, что инфоблок - это предложения?
По типу каталога: у инфоблока предложений он равен букве O, а у инфоблока товаров с вариантами - букве P. Значение отдаёт штатный вызов по идентификатору инфоблока.
Почему у родителя нет цены?
Цена принадлежит варианту, а карточка показывает минимальную цену среди доступных предложений. Пустая цена у родителя - штатное состояние, а не ошибка обмена.
Что означает тип товарной записи 5?
Это предложение без родителя: свойство привязки пустое или указывает на удалённый элемент. В каталоге такие варианты не показываются и не продаются.
Какой идентификатор лежит в корзине?
Идентификатор предложения, а не карточки товара. Родителя получают отдельным вызовом, когда нужно показать название исходного товара в отчёте.
Нужно ли пересобирать фасетный индекс руками?
Да, после записи свойств из кода: сам по себе индекс не обновляется. Для одного элемента вызывают обновление индекса, для массовой правки - пометку индекса недействительным.
Смежное
- Торговые предложения - оглавление подтемы
- Недоступные варианты в карточке: скрытие, выбор по умолчанию, остатки - доступность варианта на витрине
- Торговые предложения: настройка и связь с товаром - как включают связку и чинят привязку
- Свойства торговых предложений: вывод выбора и связь с товаром - какие свойства становятся выбором
- Выборка товаров вместе с предложениями: цены, наличие, фильтр - варианты к списку товаров одним запросом
- Торговые предложения не выводятся: разбор причин - разбор пустого выбора в карточке
- Цена не меняется при выборе варианта: разбор причин - разбор цены, стоящей на месте
- Путь цены: от типа цены до суммы в корзине - что происходит с ценой варианта дальше
- Фасетный индекс: зачем нужен и когда пересобирать - индекс умного фильтра целиком
- Каталог и продажи - устройство продаж целиком