Files
progcode/editorial/agent-rewrites/363.json
T

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 363,
"slug": "bitrix-api-создание-добавление-торгового-пре",
"title": "Bitrix API: как создать торговое предложение, связать его с товаром и проверить каталог",
"excerpt": "ID элемента ещё не делает SKU готовым к продаже. Разбираем связь предложения с товаром, параметры каталога, цену, идемпотентный импорт и проверку через тот же путь чтения, которым пользуется витрина.",
"contentHtml": "<p>Элемент торгового предложения появился в админке, метод <code>CIBlockElement::Add</code> вернул ID, но в карточке товара нет варианта. Цена ошибки — менеджер видит созданную запись, а покупатель не видит размер, цвет, цену или доступный остаток. Повторный запуск импорта может добавить ещё один такой же элемент.</p>\n<p>Причина обычно не в одном вызове API. Bitrix хранит предложение в инфоблоке SKU, связь с родительским товаром — в свойстве предложения, а цену и остаток — в данных каталога. Успешная запись одного слоя не подтверждает готовность остальных.</p>\n<h2>Тезис</h2>\n<p>Создавайте торговое предложение как последовательность проверяемых слоёв: элемент инфоблока, связь с товаром, товарные параметры, цена и чтение через публичный запрос. Останавливайте процесс на первом отрицательном результате. Такой порядок показывает, где именно данные потерялись.</p>\n<h2>Как устроена запись</h2>\n<p>Сначала определите инфоблок торговых предложений и его настройку в торговом каталоге. Для связки Bitrix хранит ID инфоблока товаров и ID свойства, которое соединяет предложение с товаром. Эти значения можно получить из настройки каталога через <code>CCatalog::GetByID</code>; не подставляйте <code>CML2_LINK</code> вслепую.</p>\n<p>У предложения должен быть родительский товар. Значение свойства связи — это ID товара, а не ID самого предложения, его символьный код или артикул. Артикул можно сохранить отдельным свойством, но он не заменяет связь SKU.</p>\n<p>После создания элемента каталогу нужны товарные параметры. Для старого API это запись через <code>CCatalogProduct::Add</code>. В ней можно указать количество, НДС и правила доступности. Цена хранится отдельно. Поэтому вызов <code>CPrice::SetBasePrice</code> — ещё один шаг, а не часть <code>CIBlockElement::Add</code>.</p>\n<p><code>CCatalogProduct::Add</code> устарел с версии 17.6.0, а <code>CPrice::SetBasePrice</code> — с версии 17.6.0. Для legacy-проекта эти вызовы нужно проверять по версии модулей. Для нового D7-кода используйте модели каталога: у цены должен быть существующий тип, а результат операции нужно проверить через <code>isSuccess()</code>. Пример ниже показывает границы старого API, а не рекомендуемый путь для новой интеграции.</p>\n<figure><img src=\"/assets/illustrations/bitrix-offer-api.svg\" alt=\"Слои торгового предложения в Bitrix: элемент, связь, цена и остаток\" /><figcaption>ID элемента — только начало цепочки. Витрина должна увидеть связь, товарные параметры и цену.</figcaption></figure>\n<h2>Учебный пример создания</h2>\n<p>Код ниже намеренно использует условные ID. <code>CML2_LINK</code> в нём — только имя-заглушка: в рабочем проекте подставьте реальный числовой ID свойства из <code>CCatalog::GetByID</code> и проверьте, что <code>$productId</code> принадлежит указанному инфоблоку товаров. Перед запуском сверяйте версию модулей, права и внешний ключ операции.</p>\n<pre><code>&lt;?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-&gt;Add([\n 'IBLOCK_ID' =&gt; $offerIblockId,\n 'NAME' =&gt; $offerName,\n 'ACTIVE' =&gt; 'Y',\n 'PROPERTY_VALUES' =&gt; [\n 'CML2_LINK' =&gt; $productId, // проверьте код свойства в проекте\n 'ARTNUMBER' =&gt; 'COFFEE-250',\n ],\n]);\n\nif (!$offerId) {\n throw new RuntimeException(\n 'Не удалось создать предложение: ' . $element-&gt;LAST_ERROR\n );\n}\n\nif (!CCatalogProduct::Add([\n 'ID' =&gt; $offerId,\n 'VAT_INCLUDED' =&gt; 'Y',\n 'QUANTITY' =&gt; 10,\n])) {\n throw new RuntimeException('Не удалось записать параметры товара');\n}\n\nif (!CPrice::SetBasePrice($offerId, $offerPrice, $currency)) {\n throw new RuntimeException('Не удалось записать базовую цену');\n}\n\n// В production здесь нужна повторная выборка и проверка витрины.\necho 'Создано предложение: ' . $offerId;</code></pre>\n<p>В старом фрагменте статьи встречается переменная <code>$offersId</code>, хотя ID сохраняется в <code>$offerId</code>. Такая опечатка ломает запись товарных параметров. Вторая опасность — перепутанная логика <code>if</code>: код может выбросить исключение после успешной операции. Проверяйте каждый результат отдельным условием, как в примере.</p>\n<p>Не создавайте новый товар автоматически, если задача требует только предложения. Сначала найдите родительский товар по устойчивому ключу проекта и проверьте, что он принадлежит ожидаемому инфоблоку. Иначе импорт создаст «сиротское» SKU или свяжет вариант с похожим товаром.</p>\n<h2>Симптомы и диагностика</h2>\n<table>\n<thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead>\n<tbody>\n<tr><td>В админке есть ID, в карточке нет предложения</td><td>Неверный инфоблок, неактивный элемент или неправильная связь SKU</td><td>Выбрать элемент по ID и прочитать свойство связи тем же фильтром, что использует компонент</td><td>Исправить IBLOCK_ID или значение свойства; затем повторить чтение</td></tr>\n<tr><td>Вариант виден, но цена пустая</td><td>Цена не записана, неверен тип цены или валюта</td><td>Получить запись цены по ID предложения и нужному типу цены</td><td>Записать нужный тип цены и проверить результат метода</td></tr>\n<tr><td>Цена есть, но купить нельзя</td><td>Нет записи товарных параметров, нулевой остаток или запрещена покупка без остатка</td><td>Прочитать товарные параметры и правила доступности</td><td>Создать или обновить параметры, затем проверить итоговый флаг доступности</td></tr>\n<tr><td>Каждый импорт добавляет новый вариант</td><td>Нет идемпотентного ключа и поиска существующего SKU</td><td>Найти элемент по XML_ID или уникальному артикулу до вызова Add</td><td>Разделить ветки create и update; не использовать имя как единственный ключ</td></tr>\n<tr><td>Запись правильная, витрина показывает старое состояние</td><td>Кеш, индекс или отдельный слой чтения не обновился</td><td>Сравнить прямую выборку и ответ публичного компонента</td><td>Выполнить штатное обновление кеша или индекса после успешной проверки данных</td></tr>\n</tbody>\n</table>\n<h2>Порядок действий</h2>\n<ol>\n<li>Зафиксируйте версию Bitrix, ID инфоблока товаров и ID инфоблока предложений.</li>\n<li>Проверьте настройку каталога и найдите реальное свойство, которое связывает предложение с товаром.</li>\n<li>Найдите родительский товар по XML_ID, артикулу или другому уникальному ключу. Убедитесь, что найден ровно один элемент.</li>\n<li>Найдите существующее предложение по тому же ключу. Если оно найдено, используйте обновление. Не создавайте дубль.</li>\n<li>Создайте элемент через <code>CIBlockElement::Add</code> с активностью, названием и обязательными свойствами.</li>\n<li>При результате <code>false</code> запишите <code>LAST_ERROR</code> и остановите цепочку. Не пытайтесь ставить цену для неизвестного ID.</li>\n<li>Проверьте повторной выборкой ID товара в свойстве связи. Сверьте инфоблок и активность.</li>\n<li>Создайте или обновите товарные параметры. Явно задайте только значения, которые нужны правилам проекта.</li>\n<li>Запишите цену через актуальный для версии Bitrix API и проверьте тип цены, сумму и валюту.</li>\n<li>Прочитайте предложение через тот же компонент, endpoint или GraphQL-запрос, которым пользуется витрина.</li>\n<li>Только после успешного чтения обновите кеш, поисковый индекс или агрегаты каталога.</li>\n<li>Сохраните в журнале внешний ключ операции, ID товара, ID предложения и результат каждого слоя. Не записывайте секреты и персональные данные.</li>\n</ol>\n<h2>Отрицательный путь и повторный запуск</h2>\n<p>Если не загрузился модуль, не продолжайте выполнение. Если не найден родительский товар, верните понятную ошибку импорта. Если найдено несколько товаров, остановитесь: автоматический выбор создаёт труднообратимую ошибку данных.</p>\n<p>Если элемент создался, а цена не записалась, повторный запуск не должен создавать новый элемент. Сохраните ID предложения, исправьте причину и выполните операцию обновления. Для этого нужен внешний ключ: XML_ID, артикул в пределах каталога или ключ, заданный интеграцией.</p>\n<p>Два параллельных импорта могут одновременно не найти SKU и оба пройти к созданию. Проверки в PHP недостаточно. Защитите уникальность на уровне модели данных или очереди, а после конфликта повторите поиск существующей записи.</p>\n<h2>Ограничения</h2>\n<ul>\n<li>Коды свойств, типы цен, настройки остатков и правила доступности зависят от конкретного каталога.</li>\n<li><code>CCatalogProduct::Add</code> и <code>CPrice::SetBasePrice</code> остаются legacy-вызовами; для нового кода сверяйте D7-модели и версию модуля.</li>\n<li>Очистка кеша не исправляет неверную связь, цену или остаток. Выполняйте её последней.</li>\n<li>Учебные ID, артикул и цена в примере не являются production-данными и требуют замены.</li>\n<li>Проверка через прямой API не заменяет проверку прав, обработчиков событий и бизнес-ограничений проекта.</li>\n</ul>\n<h2>Критерий готовности</h2>\n<p>Операция готова, если повторный запуск не создаёт дубль, а один и тот же публичный запрос возвращает предложение с нужным родительским товаром, активностью, ожидаемой ценой, валютой и остатком. В журнале есть успешный результат каждого шага. При ошибке цепочка останавливается на конкретном слое и сохраняет диагностическое сообщение.</p>\n<h2>Проверяемые источники</h2>\n<ul>\n<li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/add.php\">Документация Bitrix: CIBlockElement::Add</a> — создание элемента инфоблока и возвращаемый результат.</li>\n<li><a href=\"https://dev.1c-bitrix.ru/api_help/catalog/classes/ccatalogproduct/add.php\">Документация Bitrix: CCatalogProduct::Add</a> — товарные параметры, остаток и статус устаревшего метода.</li>\n<li><a href=\"https://dev.1c-bitrix.ru/api_help/catalog/classes/cprice/cprice__setbaseprice.a8de1fcf.php\">Документация Bitrix: CPrice::SetBasePrice</a> — установка базовой цены и переход к актуальным методам.</li>\n<li><a href=\"https://dev.1c-bitrix.ru/api_help/catalog/classes/ccatalog/ccatalog__getbyid.d6f66bc1.php\">Документация Bitrix: CCatalog::GetByID</a> — сведения о настройке инфоблока торгового каталога и связи SKU.</li>\n<li><a href=\"https://dev.1c-bitrix.ru/api_d7/bitrix/catalog/model/price/index.php\">Документация Bitrix D7: модель Price</a> — добавление и обновление цен через актуальную модель каталога.</li>\n<li><a href=\"https://dev.1c-bitrix.ru/api_help/catalog/available.php\">Документация Bitrix: доступность товара и возможность покупки</a> — условия пересчёта доступности после операций с элементом и параметрами каталога.</li>\n</ul>"
}