Перейти к содержанию

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

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

В примерах $productId обозначает простой товар или родительскую карточку, а $offerId — конкретное торговое предложение. Для простого товара цену и остаток меняют по $productId. Для товара с торговыми предложениями цену и остаток меняют по $offerId.

Значения в примерах демонстрационные: замените их на данные собственного проекта. Перед выполнением примеров подключите модули iblock и catalog и проверьте права на изменение инфоблоков и каталога.

Создать простой товар в каталоге

Создайте простой товар, если у него нет вариантов: одна карточка, одна цена, один остаток. Например, книга без размеров и цветов. Для такой структуры хватает одного инфоблока, который зарегистрирован как торговый каталог.

Создать инфоблок товаров

Создайте инфоблок с помощью CIBlock::Add(). Передайте в метод массив с параметрами:

  • IBLOCK_TYPE_ID — код существующего типа инфоблоков, например, catalog,

  • NAME — название нового инфоблока,

  • CODE — символьный код для обращения в коде,

  • API_CODE — код для работы через ORM инфоблоков, код должен быть уникальным в рамках типа инфоблоков,

  • ACTIVE — признак активности: Y или N,

  • LID — массив кодов сайтов для привязки, в примере используется сайт s1.

$iblock = new \CIBlock;
$productIblockId = $iblock->Add([
    'IBLOCK_TYPE_ID' => 'catalog',
    'NAME'           => 'Каталог товаров',
    'CODE'           => 'products',
    'API_CODE'       => 'Products',
    'ACTIVE'         => 'Y',
    'LID'            => ['s1'],
]);

if (!$productIblockId)
{
    throw new \RuntimeException($iblock->getLastError()->getMessage());
}

Метод Add() возвращает идентификатор созданного инфоблока или false при ошибке. Сохраните идентификатор $productIblockId. Он понадобится на следующих шагах для регистрации каталога и добавления товаров.

Сделать инфоблок торговым каталогом

Обычный инфоблок хранит карточки, но не работает с ценами, остатками и другими возможностями торгового каталога. Чтобы элементы инфоблока стали товарами, зарегистрируйте инфоблок в модуле catalog с помощью CCatalog::Add().

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

  • PRODUCT_IBLOCK_ID = 0 — нет отдельного инфоблока предложений,

  • SKU_PROPERTY_ID = 0 — нет свойства привязки.

Дополнительные параметры:

  • YANDEX_EXPORT — использовать каталог в экспорте в Яндекс.Маркет: Y или N,

  • SUBSCRIPTION — разрешить подписку на поступление: Y или N,

  • VAT_ID — ставка НДС по умолчанию или 0.

$result = \CCatalog::Add([
    'IBLOCK_ID'      => $productIblockId,
    'YANDEX_EXPORT'  => 'N',
    'SUBSCRIPTION'   => 'Y',
    'VAT_ID'         => 0,
    'PRODUCT_IBLOCK_ID' => 0,
    'SKU_PROPERTY_ID'   => 0,
]);

if (!$result)
{
    throw new \RuntimeException('Не удалось создать торговый каталог');
}

После успешной регистрации инфоблок получает функции торгового каталога: цены, остатки, скидки.

Создать товар

Создание товара состоит из двух шагов.

  1. Создайте элемент инфоблока $productId через CIBlockElement::Add().

  2. Добавьте к нему товарные параметры через \Bitrix\Catalog\Model\Product::add().

Пока не выполнен второй шаг, элемент инфоблока не считается товаром каталога.

Параметры товарной записи:

  • TYPE — тип товара: TYPE_PRODUCT для простого товара,

  • QUANTITY — общий остаток,

  • QUANTITY_TRACE — вести количественный учет: Y или N,

  • CAN_BUY_ZERO — разрешить покупку при нулевом остатке: Y или N,

  • SUBSCRIBE — разрешить подписку на поступление: Y или N.

$element = new \CIBlockElement;
$productId = $element->Add([
    'IBLOCK_ID' => $productIblockId, // идентификатор инфоблока
    'NAME'      => 'Настольная лампа',
    'CODE'      => 'desk-lamp',
    'ACTIVE'    => 'Y',
]);

if (!$productId)
{
    throw new \RuntimeException($element->LAST_ERROR);
}

$result = \Bitrix\Catalog\Model\Product::add([
    'ID'              => $productId, // идентификатор элемента инфоблока
    'TYPE'            => \Bitrix\Catalog\ProductTable::TYPE_PRODUCT,
    'QUANTITY'        => 15,
    'QUANTITY_TRACE'  => 'Y',
    'CAN_BUY_ZERO'    => 'N',
    'SUBSCRIBE'       => 'Y',
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Метод Product::add() возвращает объект результата. После успешного выполнения идентификатор товара совпадает с $productId — идентификатором элемента инфоблока.

Добавить цену товару

Цена хранится отдельно от товарных параметров. Добавьте цену через \Bitrix\Catalog\Model\Price::add(). В метод передайте:

  • PRODUCT_ID — идентификатор товара,

  • CATALOG_GROUP_ID — идентификатор типа цены,

  • PRICE — числовое значение цены,

  • CURRENCY — код валюты.

Идентификатор типа цены получите заранее и сохраните в переменной. Например, можно получить базовый тип цены $basePriceTypeId.

Совет

О том, как получить идентификатор типа цены, читайте в статье Базовые настройки каталога.

$result = \Bitrix\Catalog\Model\Price::add([
    'PRODUCT_ID'       => $productId, // идентификатор товара
    'CATALOG_GROUP_ID' => $basePriceTypeId,
    'PRICE'            => 1250.00,
    'CURRENCY'         => 'RUB',
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Метод Price::add() возвращает объект результата. Идентификатор созданной цены можно получить через $result->getId().

В результате товар готов к продаже: у него есть карточка в инфоблоке, товарные параметры и цена.

Создать услугу

Создавайте услугу, если продаете работу или цифровой результат без складского остатка: доставку, консультацию или доступ к сервису. Услуга остается элементом инфоблока, но в товарной записи получает тип 'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SERVICE.

Сценарий создания услуги состоит из трех шагов.

  1. Создайте элемент инфоблока через CIBlockElement::Add().

  2. Добавьте товарную запись через \Bitrix\Catalog\Model\Product::add() с типом \Bitrix\Catalog\ProductTable::TYPE_SERVICE.

  3. Добавьте цену через \Bitrix\Catalog\Model\Price::add().

Для услуги не нужно вести складской остаток. Если услуга продается по фиксированной цене, достаточно карточки, товарной записи с типом услуги и цены.

$element = new \CIBlockElement;
$serviceId = $element->Add([
    'IBLOCK_ID' => $productIblockId, // идентификатор инфоблока
    'NAME'      => 'Настройка оборудования',
    'CODE'      => 'equipment-setup',
    'ACTIVE'    => 'Y',
]);

if (!$serviceId)
{
    throw new \RuntimeException($element->LAST_ERROR);
}

$productResult = \Bitrix\Catalog\Model\Product::add([
    'ID'   => $serviceId,
    'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SERVICE,
]);

if (!$productResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $productResult->getErrorMessages()));
}

$priceResult = \Bitrix\Catalog\Model\Price::add([
    'PRODUCT_ID'       => $serviceId,
    'CATALOG_GROUP_ID' => $basePriceTypeId,
    'PRICE'            => 3000.00,
    'CURRENCY'         => 'RUB',
]);

if (!$priceResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $priceResult->getErrorMessages()));
}

После успешного выполнения услуга доступна как продаваемая позиция каталога. Для проверки цены используйте сценарии из статьи Доступность, цены и подписка.

Создать каталог с торговыми предложениями

Каталог с предложениями нужен, когда у товара есть варианты с разными параметрами: цвет, размер, комплектация, своя цена и свой остаток. Пример: диван в нескольких цветах.

Логика структуры каталога с предложениями:

  • основная карточка хранит родительский товар без цены и остатка,

  • каждое предложение хранит конкретный вариант, его цену и остаток.

Каталог с предложениями можно собрать за шесть шагов.

  1. Создайте инфоблок товаров и инфоблок торговых предложений.

  2. Создайте в инфоблоке предложений свойство привязки к товару.

  3. Зарегистрируйте оба инфоблока как торговые каталоги и свяжите их через свойство привязки.

  4. Создайте основной товар с типом TYPE_SKU.

  5. Создайте торговое предложение с типом TYPE_OFFER.

  6. Добавьте цену и остаток к торговому предложению, а не к основной карточке.

В результате будет два инфоблока:

  • инфоблок товаров — хранит основные карточки товаров,

  • инфоблок торговых предложений — хранит варианты товаров и свойство привязки к основному товару.

Создать инфоблоки

Создайте два инфоблока через CIBlock::Add(): один для товаров, второй для предложений. У каждого должны быть уникальные CODE и API_CODE.

$iblock = new \CIBlock;

$productIblockId = $iblock->Add([
    'IBLOCK_TYPE_ID' => 'catalog',        // код типа инфоблоков
    'NAME'           => 'Каталог мебели', // название инфоблока
    'CODE'           => 'furniture',      // символьный код
    'API_CODE'       => 'Furniture',      // код для работы через ORM
    'ACTIVE'         => 'Y',              // признак активности
    'LID'            => ['s1'],           // массив кодов сайтов для привязки
]);

if (!$productIblockId)
{
    throw new \RuntimeException($iblock->getLastError()->getMessage());
}

$offerIblockId = $iblock->Add([
    'IBLOCK_TYPE_ID' => 'catalog',
    'NAME'           => 'Торговые предложения мебели',
    'CODE'           => 'furniture_offers',
    'API_CODE'       => 'FurnitureOffers',
    'ACTIVE'         => 'Y',
    'LID'            => ['s1'],
]);

if (!$offerIblockId)
{
    throw new \RuntimeException($iblock->getLastError()->getMessage());
}

Каждый вызов Add() возвращает идентификатор созданного инфоблока или false при ошибке. Сохраните $productIblockId и $offerIblockId — они понадобятся для связи каталогов.

Создать свойство привязки предложения к товару

В инфоблоке предложений создайте свойство-связку с товаром через CIBlockProperty::Add(). Это свойство будет хранить ссылку на родительский товар.

Ключевые параметры метода CIBlockProperty::Add():

  • IBLOCK_ID — идентификатор инфоблока предложений $offerIblockId,

  • NAME — название свойства,

  • CODE — символьный код, используйте CML2_LINK для совместимости,

  • PROPERTY_TYPE — тип E: привязка к элементу,

  • USER_TYPE — тип SKU: специальный тип для торговых предложений,

  • LINK_IBLOCK_ID — идентификатор инфоблока товаров $productIblockId,

  • MULTIPLE — признак множественности: N для одного товара на предложение.

$property = new \CIBlockProperty;
$skuPropertyId = $property->Add([
    'IBLOCK_ID'        => $offerIblockId,
    'NAME'             => 'Товар',
    'CODE'             => 'CML2_LINK',
    'PROPERTY_TYPE'    => 'E',
    'USER_TYPE'        => 'SKU',
    'LINK_IBLOCK_ID'   => $productIblockId,
    'MULTIPLE'         => 'N',
]);

if (!$skuPropertyId)
{
    throw new \RuntimeException($property->LAST_ERROR);
}

Метод возвращает идентификатор свойства или false. Сохраните $skuPropertyId — он нужен для регистрации каталога предложений.

Связать каталоги

Зарегистрируйте оба инфоблока в модуле catalog через CCatalog::Add().

Для инфоблока товаров укажите:

  • PRODUCT_IBLOCK_ID = 0 — нет родительского инфоблока,

  • SKU_PROPERTY_ID = 0 — нет свойства привязки.

Для инфоблока предложений укажите:

  • PRODUCT_IBLOCK_ID — идентификатор инфоблока товаров,

  • SKU_PROPERTY_ID — идентификатор свойства CML2_LINK.

$productCatalogCreated = \CCatalog::Add([
    'IBLOCK_ID'      => $productIblockId,
    'YANDEX_EXPORT'  => 'N',
    'SUBSCRIPTION'   => 'Y',
    'VAT_ID'         => 0,
    'PRODUCT_IBLOCK_ID' => 0,
    'SKU_PROPERTY_ID'   => 0,
]);

if (!$productCatalogCreated)
{
    throw new \RuntimeException('Не удалось создать каталог товаров');
}

$offerCatalogCreated = \CCatalog::Add([
    'IBLOCK_ID'      => $offerIblockId,
    'YANDEX_EXPORT'  => 'N',
    'SUBSCRIPTION'   => 'Y',
    'VAT_ID'         => 0,
    'PRODUCT_IBLOCK_ID' => $productIblockId,
    'SKU_PROPERTY_ID'   => $skuPropertyId,
]);

if (!$offerCatalogCreated)
{
    throw new \RuntimeException('Не удалось создать каталог торговых предложений');
}

В результате инфоблок предложений становится полноценным каталогом. Каждое предложение получает собственные товарные параметры, цены и остатки.

Создать основной товар

Создайте родительский товар в инфоблоке товаров через CIBlockElement::Add(). Затем добавьте товарные параметры и задайте тип TYPE_SKU. Это означает, что товар продается через предложения.

$element = new \CIBlockElement;
$productId = $element->Add([
    'IBLOCK_ID' => $productIblockId, // идентификатор родительского инфоблока
    'NAME'      => 'Диван',
    'CODE'      => 'sofa',
    'ACTIVE'    => 'Y',
]);

if (!$productId)
{
    throw new \RuntimeException($element->LAST_ERROR);
}

$result = \Bitrix\Catalog\Model\Product::add([
    'ID'   => $productId,
    'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SKU,
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Метод Product::add() возвращает объект результата. После успешного выполнения товар $productId зарегистрирован как родительская карточка каталога.

Цену и остаток храните на предложениях. Родительская карточка обычно не участвует в расчете цены и остатка.

Создать торговое предложение

Создайте предложение в инфоблоке предложений через CIBlockElement::Add(). Обязательно заполните CML2_LINK — это связь с родительским товаром.

После создания элемента добавьте товарные параметры:

  • TYPE — тип TYPE_OFFER означает, что это вариант товара,

  • QUANTITY — остаток конкретного варианта,

  • QUANTITY_TRACE, CAN_BUY_ZERO, SUBSCRIBE — правила учета и подписки.

$element = new \CIBlockElement;
$offerId = $element->Add([
    'IBLOCK_ID' => $offerIblockId, // идентификатор инфоблока предложений
    'NAME'      => 'Диван, цвет бежевый',
    'CODE'      => 'sofa-beige',
    'ACTIVE'    => 'Y',
    'PROPERTY_VALUES' => [
        'CML2_LINK' => $productId, // идентификатор основного товара
    ],
]);

if (!$offerId)
{
    throw new \RuntimeException($element->LAST_ERROR);
}

$result = \Bitrix\Catalog\Model\Product::add([
    'ID'              => $offerId,
    'TYPE'            => \Bitrix\Catalog\ProductTable::TYPE_OFFER,
    'QUANTITY'        => 7,
    'QUANTITY_TRACE'  => 'Y',
    'CAN_BUY_ZERO'    => 'N',
    'SUBSCRIBE'       => 'Y',
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Добавить цену торговому предложению

Добавьте цену торговому предложению с помощью \Bitrix\Catalog\Model\Price::add() как для простого товара, но в PRODUCT_ID передайте идентификатор предложения $offerId.

Внимание

Типичная ошибка — добавить цену к основному товару с типом TYPE_SKU, а не к предложению. В этом случае цена не попадет в расчет при выборе конкретного варианта. Всегда передавайте в PRODUCT_ID идентификатор предложения $offerId.

$result = \Bitrix\Catalog\Model\Price::add([
    'PRODUCT_ID'       => $offerId,
    'CATALOG_GROUP_ID' => $basePriceTypeId, // идентификатор типа цены
    'PRICE'            => 1500.00,
    'CURRENCY'         => 'RUB',
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Метод возвращает объект результата. Идентификатор созданной цены доступен через $result->getId().

Получить торговые предложения товара

Самый простой способ получить предложения — использовать метод CCatalogSKU::getOffersList().

Параметры метода:

  • productID — идентификатор товара или массив идентификаторов,

  • iblockID — идентификатор инфоблока предложений или 0 для автоопределения,

  • skuFilter — фильтр предложений,

  • fields — список полей для выборки.

$offersByProduct = \CCatalogSKU::getOffersList(
    $productId,
    0,
    ['ACTIVE' => 'Y'],
    ['ID', 'NAME', 'IBLOCK_ID']
);

$offers = $offersByProduct[$productId] ?? [];

Метод getOffersList() возвращает массив, который сгруппирован по идентификаторам родительских товаров. В переменной $offers остаются предложения для $productId.

Для кастомного запроса сначала получите параметры связки инфоблоков через CCatalogSKU::GetInfoByProductIBlock(). Затем отфильтруйте элементы предложений по свойству привязки.

$skuInfo = \CCatalogSKU::GetInfoByProductIBlock($productIblockId);

if (!$skuInfo)
{
    throw new \RuntimeException('Инфоблок не связан с торговыми предложениями');
}

$offersIterator = \CIBlockElement::GetList(
    [],
    [
        'IBLOCK_ID' => $skuInfo['IBLOCK_ID'],
        'PROPERTY_' . $skuInfo['SKU_PROPERTY_ID'] => $productId,
        'ACTIVE' => 'Y',
    ],
    false,
    false,
    ['ID', 'NAME']
);

while ($offer = $offersIterator->Fetch())
{
    echo $offer['ID'] . ': ' . $offer['NAME'] . "\n";
}

Метод CIBlockElement::GetList() возвращает итератор CDBResult. Метод Fetch() по шагам выдает строки предложений.

Изменить товар

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

|

|| Что изменить | Метод || || Название, символьный код, активность и свойства карточки | CIBlockElement::Update() || || Тип товара, общий остаток, правила учета количества и подписки | \Bitrix\Catalog\Model\Product::update() || || Цену | \Bitrix\Catalog\Model\Price::update() || || НДС, единицу измерения и коэффициент продажи | \Bitrix\Catalog\Model\Product::update() и таблицы каталога. Подробнее смотрите в статье Базовые настройки каталога || || Остатки, доступность и подписку | Методы товарных параметров. Подробные сценарии читайте в статье Доступность, цены и подписка || || Комплекты, наборы и скидки | CCatalogProductSet и методы скидок. Подробнее читайте в статье Комплекты, наборы и скидки || |#

Изменить карточку товара

Чтобы изменить поля элемента инфоблока, используйте CIBlockElement::Update(). Метод подходит для названия, символьного кода, активности и свойств карточки. Передайте идентификатор элемента инфоблока и массив полей.

Для простого товара или родительской карточки передайте $productId. Родительскую карточку обновляйте, когда нужно изменить общее описание товара: название, код, активность или свойства, которые относятся ко всем вариантам.

Настольная лампа с регулировкой и adjustable-desk-lamp — примеры новых значений названия и символьного кода.

$element = new \CIBlockElement;
$updated = $element->Update($productId, [
    'NAME'   => 'Настольная лампа с регулировкой',
    'CODE'   => 'adjustable-desk-lamp',
    'ACTIVE' => 'Y',
]);

if (!$updated)
{
    throw new \RuntimeException($element->LAST_ERROR);
}

Для торгового предложения передайте в первый параметр $offerId, если нужно изменить карточку конкретного варианта.

Изменить товарные параметры

Чтобы изменить данные модуля catalog, используйте \Bitrix\Catalog\Model\Product::update(). Метод меняет товарную запись, а не поля элемента инфоблока. Передайте идентификатор товара $productId или предложения $offerId и массив товарных полей.

$result = \Bitrix\Catalog\Model\Product::update($productId, [
    'QUANTITY_TRACE' => 'Y',
    'CAN_BUY_ZERO'   => 'N',
    'SUBSCRIBE'      => 'Y',
]);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

Совет

Про правила обновления остатков читайте в статье Доступность, цены и подписка.

Изменить цену без дублей

Цена хранится в отдельной записи. При изменении цены обновите существующую запись для пары PRODUCT_ID и CATALOG_GROUP_ID. Не создавайте новую для того же товара и типа цены.

Для простого товара в PRODUCT_ID используйте $productId. Для товара с торговыми предложениями используйте $offerId, потому что цена продаваемого варианта хранится на предложении.

Полный сценарий создания или обновления цены без дублей смотрите в статье Доступность, цены и подписка.

Удалить товар

Перед удалением проверьте наличие торговых предложений. Если предложения существуют, удалите их сначала. Затем удалите родительскую карточку. Система не удаляет предложения автоматически вместе с родителем.

В примерах удаления $productId — идентификатор простого товара или родительского товара с предложениями.

Удалить простой товар

Для простого товара без предложений используйте CIBlockElement::Delete(). В метод передайте идентификатор элемента инфоблока.

if (!\CIBlockElement::Delete($productId))
{
    global $APPLICATION;
    $exception = $APPLICATION->GetException();
    $message = $exception ? $exception->GetString() : 'Не удалось удалить товар';

    throw new \RuntimeException($message);
}

Метод CIBlockElement::Delete() возвращает true при успешном удалении и false при ошибке. Он очищает данные из инфоблока и связанные товарные параметры модуля catalog.

Удалить товар с торговыми предложениями

Чтобы удалить товар с торговыми предложениями, выполните три шага.

  1. Получите список предложений через CCatalogSKU::getOffersList().

  2. Удалите каждое предложение в цикле с помощью CIBlockElement::Delete().

  3. Удалите родительскую карточку через CIBlockElement::Delete().

$offersByProduct = \CCatalogSKU::getOffersList(
    $productId,
    0,
    [],
    ['ID']
);

$offers = $offersByProduct[$productId] ?? [];

foreach ($offers as $offer)
{
    if (!\CIBlockElement::Delete((int)$offer['ID']))
    {
        global $APPLICATION;
        $exception = $APPLICATION->GetException();
        $message = $exception ? $exception->GetString() : 'Не удалось удалить торговое предложение';

        throw new \RuntimeException($message);
    }
}

if (!\CIBlockElement::Delete($productId))
{
    global $APPLICATION;
    $exception = $APPLICATION->GetException();
    $message = $exception ? $exception->GetString() : 'Не удалось удалить родительский товар';

    throw new \RuntimeException($message);
}

Цикл обрабатывает каждое предложение по идентификатору. После завершения цикла метод удаляет родительский элемент.

Продолжить изучение