{ "index": 363, "slug": "bitrix-api-создание-добавление-торгового-пре", "title": "Bitrix API: как создать торговое предложение, связать его с товаром и проверить каталог", "excerpt": "Торговое предложение в Bitrix состоит не только из элемента инфоблока. Разбираем связь SKU с товаром, товарные параметры, цену, типичные симптомы ошибки и проверку через тот же путь, которым читает витрина.", "contentHtml": "

Элемент торгового предложения появился в админке, метод CIBlockElement::Add вернул ID, но в карточке товара нет варианта. Цена ошибки — менеджер видит созданную запись, а покупатель не видит размер, цвет, цену или доступный остаток. Повторный запуск импорта может добавить ещё один такой же элемент.

\n

Причина обычно не в одном вызове API. Bitrix хранит предложение в инфоблоке SKU, связь с родительским товаром — в свойстве предложения, а цену и остаток — в данных каталога. Успешная запись одного слоя не подтверждает готовность остальных.

\n

Тезис

\n

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

\n

Как устроена запись

\n

Сначала определите два инфоблока: инфоблок товаров и инфоблок торговых предложений. Их связь задаёт настройка торгового каталога. Не подставляйте свойство CML2_LINK вслепую: в конкретном проекте его код или ID может отличаться.

\n

У предложения должен быть родительский товар. Значение свойства связи — это ID товара, а не ID самого предложения, его символьный код или артикул. Артикул можно сохранить отдельным свойством, но он не заменяет связь SKU.

\n

После создания элемента каталогу нужны товарные параметры. Для старого API это запись через CCatalogProduct::Add. В ней можно указать количество, НДС и правила доступности. Цена хранится отдельно. Поэтому вызов CPrice::SetBasePrice — ещё один шаг, а не часть CIBlockElement::Add.

\n

Названия методов зависят от версии Bitrix. Документация помечает CCatalogProduct::Add и CPrice::SetBasePrice устаревшими в новых версиях. Для нового кода сверяйте актуальные модели каталога и цены. Пример ниже нужен для понимания границ старого API и для поддержки проектов, где эти классы ещё используются.

\n
\"Слои
ID элемента — только начало цепочки. Витрина должна увидеть связь, товарные параметры и цену.
\n

Учебный пример создания

\n

Код ниже намеренно использует условные ID и свойства. Он не заявляет, что в вашем каталоге связь называется CML2_LINK. Перед запуском получите реальные значения из настройки инфоблока SKU и из схемы свойств.

\n
<?php\n\nuse Bitrix\\Main\\Loader;\n\nif (!Loader::includeModule('iblock') || !Loader::includeModule('catalog')) {\n    throw new RuntimeException('Не удалось подключить iblock или catalog');\n}\n\n$offerIblockId = 12; // учебное значение\n$productId = 345;    // ID существующего товара\n$offerName = 'Кофе, упаковка 250 г';\n$offerPrice = 990.00;\n$currency = 'RUB';\n\n$element = new CIBlockElement();\n$offerId = $element->Add([\n    'IBLOCK_ID' => $offerIblockId,\n    'NAME' => $offerName,\n    'ACTIVE' => 'Y',\n    'PROPERTY_VALUES' => [\n        'CML2_LINK' => $productId, // проверьте код свойства в проекте\n        'ARTNUMBER' => 'COFFEE-250',\n    ],\n]);\n\nif (!$offerId) {\n    throw new RuntimeException(\n        'Не удалось создать предложение: ' . $element->LAST_ERROR\n    );\n}\n\nif (!CCatalogProduct::Add([\n    'ID' => $offerId,\n    'VAT_INCLUDED' => 'Y',\n    'QUANTITY' => 10,\n])) {\n    throw new RuntimeException('Не удалось записать параметры товара');\n}\n\nif (!CPrice::SetBasePrice($offerId, $offerPrice, $currency)) {\n    throw new RuntimeException('Не удалось записать базовую цену');\n}\n\n// В production здесь нужна повторная выборка и проверка витрины.\necho 'Создано предложение: ' . $offerId;
\n

В старом фрагменте статьи встречается переменная $offersId, хотя ID сохраняется в $offerId. Такая опечатка ломает запись товарных параметров. Вторая опасность — перепутанная логика if: код может выбросить исключение после успешной операции. Проверяйте каждый результат отдельным условием, как в примере.

\n

Не создавайте новый товар автоматически, если задача требует только предложения. Сначала найдите родительский товар по устойчивому ключу проекта и проверьте, что он принадлежит ожидаемому инфоблоку. Иначе импорт создаст «сиротское» SKU или свяжет вариант с похожим товаром.

\n

Симптомы и диагностика

\n\n\n\n\n\n\n\n\n\n
СимптомПричинаПроверкаДействие
В админке есть ID, в карточке нет предложенияНеверный инфоблок, неактивный элемент или неправильная связь SKUВыбрать элемент по ID и прочитать свойство связи тем же фильтром, что использует компонентИсправить IBLOCK_ID или значение свойства; затем повторить чтение
Вариант виден, но цена пустаяЦена не записана, неверен тип цены или валютаПолучить цены по PRODUCT_ID, равному ID предложенияЗаписать нужный тип цены и проверить результат метода
Цена есть, но купить нельзяНет записи товарных параметров, нулевой остаток или запрещена покупка без остаткаПрочитать товарные параметры и правила доступностиСоздать или обновить параметры, затем проверить итоговый флаг доступности
Каждый импорт добавляет новый вариантНет идемпотентного ключа и поиска существующего SKUНайти элемент по XML_ID или уникальному артикулу до вызова AddРазделить ветки create и update; не использовать имя как единственный ключ
Запись правильная, витрина показывает старое состояниеКеш, индекс или отдельный слой чтения не обновилсяСравнить прямую выборку и ответ публичного компонентаВыполнить штатное обновление кеша или индекса после успешной проверки данных
\n

Порядок действий

\n
    \n
  1. Зафиксируйте версию Bitrix, ID инфоблока товаров и ID инфоблока предложений.
  2. \n
  3. Проверьте настройку каталога и найдите реальное свойство, которое связывает предложение с товаром.
  4. \n
  5. Найдите родительский товар по XML_ID, артикулу или другому уникальному ключу. Убедитесь, что найден ровно один элемент.
  6. \n
  7. Найдите существующее предложение по тому же ключу. Если оно найдено, используйте обновление. Не создавайте дубль.
  8. \n
  9. Создайте элемент через CIBlockElement::Add с активностью, названием и обязательными свойствами.
  10. \n
  11. При результате false запишите LAST_ERROR и остановите цепочку. Не пытайтесь ставить цену для неизвестного ID.
  12. \n
  13. Проверьте повторной выборкой ID товара в свойстве связи. Сверьте инфоблок и активность.
  14. \n
  15. Создайте или обновите товарные параметры. Явно задайте только значения, которые нужны правилам проекта.
  16. \n
  17. Запишите цену через актуальный для версии Bitrix API и проверьте тип цены, сумму и валюту.
  18. \n
  19. Прочитайте предложение через тот же компонент, endpoint или GraphQL-запрос, которым пользуется витрина.
  20. \n
  21. Только после успешного чтения обновите кеш, поисковый индекс или агрегаты каталога.
  22. \n
  23. Сохраните в журнале ключ операции, ID товара, ID предложения и результат каждого слоя. Не записывайте секреты и персональные данные.
  24. \n
\n

Отрицательный путь и повторный запуск

\n

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

\n

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

\n

Два параллельных импорта могут одновременно не найти SKU и оба пройти к созданию. Проверки в PHP недостаточно. Защитите уникальность на уровне модели данных или очереди, а после конфликта повторите поиск существующей записи.

\n

Ограничения

\n\n

Критерий готовности

\n

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

\n

Проверяемые источники

\n" }