Предложение создаётся без ошибки, но не появляется в карточке товара. Цена ошибки — администратор видит ID записи, а покупатель не видит вариант, цену или остаток.
В старом рецепте важен вызов API создания. Для читателя нужно явно разделить элемент инфоблока и товарную модель: предложение — это запись, связанная с товаром, а цена и остаток живут в дополнительных сущностях каталога.
Что сохраняем из исходной заметки
Хороший пример создания (добавления) торгового предложения в Bitrix через API:
use \Bitrix\Main\Loader;
if (!Loader::includeModule('iblock') || !Loader::includeModule('catalog'))
{
die('Error loading module iblock or catalog');
}
$IBlockOffersCatalogId = 3; // ID инфоблока предложений (должен быть торговым каталогом)
$productName = "Товар"; // наименование товара
$offerName = "Торговое предложение"; // наименование торгового предложения
$offerPrice = 100.50; // Цена торгового предложения
$arCatalog = CCatalog::GetByID($IBlockOffersCatalogId);
$IBlockCatalogId = $arCatalog['PRODUCT_IBLOCK_ID']; // ID инфоблока товаров
$SKUPropertyId = $arCatalog['SKU_PROPERTY_ID']; // ID свойства в инфоблоке предложений типа "Привязка к товарам (SKU)"
$obElement = new CIBlockElement();
$arFields = array(
'NAME' => $productName,
'IBLOCK_ID' => $IBlockCatalogId,
'ACTIVE' => 'Y'
);
$productId = $obElement->Add($arFields); // добавили товар, получили ID
if ($productId)
{
$obElement = new CIBlockElement();
// свойства торгвоого предложения
$arOfferProps = array(
$SKUPropertyId => $productId,
);
$arOfferFields = array(
'NAME' => $offerName,
'IBLOCK_ID' => $IBlockOffersCatalogId,
'ACTIVE' => 'Y',
'PROPERTY_VALUES' => $arOfferProps
);
$offerId = $obElement->Add($arOfferFields); // ID торгового предложения
if ($offerId)
{
// добавляем как товар и указываем цену
$catalogProductAddResult = CCatalogProduct::Add(array(
"ID" => $offersId,
"VAT_INCLUDED" => "Y", //НДС входит в стоимость
));
if ($catalogProductAddResult && !CPrice::SetBasePrice($offerId, $offerPrice, "RUB"))
throw new Exception("Ошибка установки цены торгового предложения \"{$offerId}\"");
else
throw new Exception("Ошибка добавления параметров торгового предложения \"{$offerId}\" в каталог товаров");
}
else
{
throw new Exception("Ошибка добавления торгового предложения: " . $obElement->LAST_ERROR);
}
}
else
{
throw new Exception("Ошибка добавления товара: " . $obElement->LAST_ERROR);
}
Что проверить после создания предложения
После добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU, не сохранён товарный каталог или не обновлены кеши.
- Убедитесь, что модуль iblock подключён перед созданием элемента.
- Если задаются цена и складской остаток, подключите модуль catalog.
- Проверьте свойство связи с товаром, чаще всего это CML2_LINK или настроенное в конкретном каталоге свойство SKU.
- После записи цены и остатков очистите кеш компонента каталога.
Минимальная последовательность такая: создать элемент торгового предложения в инфоблоке SKU, записать связь с товаром, сохранить параметры товара, установить цену, установить количество. Если хотя бы один шаг пропущен, предложение может быть видно в админке, но не попадёт в публичный каталог.
CModule::IncludeModule('iblock');
CModule::IncludeModule('catalog');
$offerId = $el->Add($fields);
CCatalogProduct::Add([
'ID' => $offerId,
'QUANTITY' => 10,
]);
CPrice::SetBasePrice($offerId, 990, 'RUB');
На боевом проекте обязательно логируйте результат Add и текст ошибки LAST_ERROR. Bitrix часто молча возвращает false, а настоящая причина находится именно там: обязательное поле, неправильный инфоблок, неверный код свойства или недостаточные права.
Механизм без лишних обещаний
`CIBlockElement::Add` возвращает ID элемента, но не гарантирует, что у него есть обязательные свойства связи. Сначала сохраняют минимальный набор полей и проверяют ошибку, затем создают или обновляют товарную часть.
Связь с родительским товаром должна быть однозначной. Если свойство XML_ID, SKU или ID заполнено неверно, каталог может показать пустой результат, хотя запись физически есть.
После записи нужна выборка тем же способом, которым читает каталог. Это ловит ошибки активности, дат, прав, раздела и кеша без догадки о том, какая кнопка «обновить» поможет.
Минимальный воспроизводимый пример
Ниже — маленькая проверка, которую можно запустить или адаптировать в отдельном тестовом окружении. Значения демонстрационные; проектные идентификаторы, пути и версии нужно заменить своими и сохранить рядом с результатом.
<?php
$element = new CIBlockElement();
$offerId = $element->Add([
"IBLOCK_ID" => $offerIblockId,
"NAME" => "Кофе 250 г",
"ACTIVE" => "Y",
"PROPERTY_VALUES" => [
"CML2_LINK" => $productId,
"ARTNUMBER" => "COFFEE-250",
],
]);
if (!$offerId) {
throw new RuntimeException($element->LAST_ERROR);
}
// Затем отдельно проверяем цену, остаток и связь с productId.
Матрица диагностики
| Слой | Что сохраняется | Чем подтверждается |
|---|---|---|
| Инфоблок | ID, имя, активность, свойства | Повторная выборка элемента |
| Связь | ID родительского товара | Свойство связи совпадает с товаром |
| Цена | Тип цены и значение | Запрос каталога возвращает цену |
| Остаток | Количество и доступность | Карточка показывает ожидаемый статус |
Порядок действий
- Назвать ID инфоблока предложения и ID родительского товара.
- Сохранить элемент через API и сразу обработать `LAST_ERROR`.
- Проверить обязательное свойство связи и уникальный артикул.
- Создать или обновить товарные параметры отдельным шагом.
- Прочитать предложение через тот же компонент или запрос, что использует витрина.
- Только после проверки данных обновить кеш и индекс по правилам проекта.
Ограничения и безопасный следующий шаг
API каталога менялся между версиями Bitrix; исторический код нельзя переносить без сверки версии.
ID элемента не подтверждает наличие цены, остатка или видимость в компоненте.
Массовую загрузку нужно защищать от повторного запуска и гонки уникальности.
После проверки должен остаться конкретный артефакт: вывод команды, тест, diff конфигурации или запись результата. Если его нет, формулировку нужно вернуть к симптому и не выдавать гипотезу за исправление.
Что записать в ревью
Короткая запись должна отвечать на четыре вопроса: какой вход использовали, какой результат увидели, какая граница была проверена и какое действие разрешено дальше. Такая форма полезнее длинного вывода «всё работает»: другой инженер сможет повторить проверку и понять, где заканчивается пример.
Если результат зависит от версии Windows, PHP, Bitrix, D или браузера, версию фиксируем рядом с командой. Если проверка не охватывает сеть, production или реальные пользовательские данные, это ограничение пишем прямо. Тогда следующий шаг расширяет evidence, а не расширяет обещание.
Проверяемые источники
- Bitrix: CIBlockElement::Add — описывает создание элемента и обработку ошибки
- Bitrix: каталог — разделяет элемент, товар и цены
- Bitrix: торговые предложения — помогает проверить связь SKU с товаром