Самый частый вопрос, задаваемый начинающим разработчиком - "как". Как создать цены, как добавить в корзину, как вывести... Вопрос "почему" задается гораздо реже. Сдав три-четыре, а лучше пять-десять проектов, разработчик уже чувствует себя уверенней - есть стандартные наработки и приемы, стереотипные ходы, можно браться за реализацию задач поинтересней. Самое время для "почему". Но - те самые стереотипы, опыт (руль - он одинаковый и у легковушки, и у фуры), формирующий картину мира, время, которое деньги. Тем более, что все работает. "Почему" может и не прозвучать. А может прозвучать, но остаться без ответа.
Эта статья открывает серию "почему" и "зачем" для разработчиков магазинов. Темы будут определяться исходя из вопросов на форуме и в блогах, обращений в техподдержку, предложений сайта идей. За рамками публикаций сознательно будут оставлены вопросы, касающиеся причин тех или иных архитектурных решений.
Товар — это то, что можно положить в корзину и на что оформить заказ. Сразу оговорюсь — речь пойдет исключительно о штатном функционале, т.е. модулях catalog и iblock (Торговый каталог и Информационные блоки). В рамках этих ограничений любой товар — элемент инфоблока. Обратное утверждение не столь категорично — далеко не каждый элемент инфоблока является товаром. Инфоблок должен быть торговым каталогом — это абсолютно необходимое, но недостаточное условие. Второе условие — для элемента инфоблока должны быть заданы характеристики товара (количественный учет и доступность, вес и размеры, etc) — иными словами, для элемента инфоблока должна быть запись в таблице товаров БД. За работу с характеристиками товара (далее – товаром) отвечает класс CCatalogProduct модуля catalog. Этот класс относится к старому ядру. Таблет orm \Bitrix\Catalog\ProductTable на текущий момент (catalog 17.0.1) может быть использован только для выборок. Полный перечень характеристик можно посмотреть в документации.
ВАЖНО! Элемент, у которого есть цены, но нет характеристик товара — не товар. Вы не сможете работать с ним в разрезе интернет-магазина. Элемент с характеристиками, но без доступных цен (частный случай — полное отсутствие цен) – это всего-навсего товар, который нельзя продать. Возможен вариант, когда владельцу магазина (именно ему, а не разработчику) характеристики товара не нужны. В таком случае достаточно создать запись с дефолтными значениями параметров и забыть о ней.
Типичный пример неправильного создания товара:
if (\Bitrix\Main\Loader::includeModule('catalog'))
{
// создание элемента инфоблока
$element = new CIBlockElement;
// минимальный перечень ключей, не учитывающий настройки обязательности полей элемента (например, привязку к разделам)
$fields = array(
'IBLOCK_ID' => ID_торгового_каталога,
'NAME' => 'Товар'
);
$id = (int)$element->Add($fields);
if ($id > 0)
{
// сохранение базовой цены
$result = CPrice::SetBasePrice($id, 100, 'RUB');
}
}
А вот как правильно (актуальный вариант - catalog 18.0.0 и выше):
if (\Bitrix\Main\Loader::includeModule('catalog'))
{
// проверка, что используется складской учет (включает в себя ограничения редакции коробки / тарифа Б24)
$useStoreControl = \Bitrix\Catalog\Config\State::isUsedInventoryManagement();
$element = new CIBlockElement;
$fields = [
'IBLOCK_ID' => ID_торгового_каталога,
'NAME' => 'Товар'
];
$id = (int)$element->Add($fields);
if ($id > 0)
{
$fields = [
'ID' => $id,
'QUANTITY_TRACE' => \Bitrix\Catalog\ProductTable::STATUS_DEFAULT,
'CAN_BUY_ZERO' => \Bitrix\Catalog\ProductTable::STATUS_DEFAULT,
'WEIGHT' => 0,
'MEASURE' => ID_единицы_измерения
];
if (!$useStoreControl)
{
// выключен складской учет
$fields['QUANTITY'] = общее_количество_товара;
}
// создание товара
$result = \Bitrix\Catalog\Model\Product::add([
'external_fields' => [
'IBLOCK_ID' => ID_торгового_каталога, // необязательный ключ, минус 1 запрос к базе
],
'fields' => $fields,
]);
if ($result->isSuccess())
{
// добавление коэффициента единицы измерения товара
$ratioResult = \Bitrix\Catalog\MeasureRatioTable::add([
'PRODUCT_ID' => $id,
'RATIO' => коэффициент_единицы_измерения
]);
if (!$ratioResult->isSuccess())
{
echo 'Ошибка создания коэффициента<br>'.implode('<br>', $ratioResult->getErrorMessages());
}
// добавление цены
$result = \Bitrix\Catalog\Model\Price::add([
'fields' => [
'PRODUCT_ID' => $id,
'CATALOG_GROUP_ID' => ID_типа_цены,
'PRICE' => 100,
'CURRENCY' => 'RUB',
]
]);
if ($result->isSuccess())
{
$priceId = $result->getId();
}
else
{
$error = implode('<br>', $result->getErrorMessages());
echo ($error !== '' ? $error : 'Неизвестная ошибка при создании товара');
}
}
else
{
$error = implode('<br>', $result->getErrorMessages());
echo ($error !== '' ? $error : 'Неизвестная ошибка при создании товара');
}
}
}
Исходя из вышеизложенного, проверка существования товара выглядит так (с учетом прав текущего пользователя):
С выпуском catalog 14.0.0 появилось понятие типа товара. На тот момент их было всего два – товар и комплект. Начиная с catalog 16.0.3 в продукте поддерживаются 6 типов товаров: Основные
\Bitrix\Catalog\ProductTable::TYPE_PRODUCT – простой товар
\Bitrix\Catalog\ProductTable::TYPE_SET – комплект
\Bitrix\Catalog\ProductTable::TYPE_SKU – товар с торговыми предложениями
\Bitrix\Catalog\ProductTable::TYPE_OFFER – торговое предложение
Дополнительные
\Bitrix\Catalog\ProductTable::TYPE_FREE_OFFER – торговое предложение, у которого нет товара (не указан или удален)
\Bitrix\Catalog\ProductTable::TYPE_EMPTY_SKU – специфический тип, означает невалидный товар с торговыми предложениями.
Установка типа товара происходит автоматически, хотя и может быть определена вручную.
Типы товаров, которые могут быть добавлены в корзину:
\Bitrix\Catalog\ProductTable::TYPE_PRODUCT – простой товар
\Bitrix\Catalog\ProductTable::TYPE_SET – комплект
\Bitrix\Catalog\ProductTable::TYPE_OFFER – торговое предложение
Варианты торгового каталога
От настроек торгового каталога зависит, какие типы товаров могут быть в нем. Получить эту информацию можно, вызывая метод CCatalogSku::GetInfoByIBlock(). Метод возвращает либо false, либо массив. В первом случае это обычный инфоблок, не имеющий отношения к магазину. Во втором (вернулся массив) необходимо проверять значение ключа CATALOG_TYPE:
if (\Bitrix\Main\Loader::includeModule('catalog'))
{
$catalog = CCatalogSku::GetInfoByIBlock(ID_инфоблока);
if (empty($catalog))
{
echo 'Инфоблок не найден или не имеет отношения к торговому каталогу';
}
else
{
switch ($catalog['CATALOG_TYPE'] )
{
case CCatalogSku::TYPE_CATALOG:
echo 'Простой торговый каталог';
break;
case CCatalogSku::TYPE_FULL:
echo 'Расширенный торговый каталог';
break;
case CCatalogSku::TYPE_PRODUCT:
echo 'Инфоблок товаров с торговыми предложениями';
break;
case CCatalogSku::TYPE_OFFERS:
echo 'Торговый каталог предложений';
}
}
}
CCatalogSku::TYPE_CATALOG - простой торговый каталог. Может содержать следующие типы товаров:
\Bitrix\Catalog\ProductTable::TYPE_PRODUCT – простой товар
\Bitrix\Catalog\ProductTable::TYPE_SET – комплект
CCatalogSku::TYPE_FULL - расширенный торговый каталог. Может содержать все типы товаров простого торгового каталога плюс товары с торговыми предложениями:
\Bitrix\Catalog\ProductTable::TYPE_PRODUCT – простой товар
\Bitrix\Catalog\ProductTable::TYPE_SET – комплект
\Bitrix\Catalog\ProductTable::TYPE_SKU – товар с торговыми предложениями
CCatalogSku::TYPE_PRODUCT - инфоблок товаров с торговыми предложениями. Не является торговым каталогом. Содержит лишь один основной тип товара и один дополнительный:
\Bitrix\Catalog\ProductTable::TYPE_SKU – товар с торговыми предложениями
\Bitrix\Catalog\ProductTable::TYPE_EMPTY_SKU – товар, у которого отсутствуют торговые предложения (удалены либо не создавались)
CCatalogSku::TYPE_OFFERS - торговый каталог предложений. Его типы товаров:
\Bitrix\Catalog\ProductTable::TYPE_OFFER – торговое предложение
\Bitrix\Catalog\ProductTable::TYPE_FREE_OFFER – торговое предложение, у которого нет товара (не указан)
ВАЖНО! До версии catalog 16.0.3, если инфоблок переставал быть торговым каталогом, у всех элементов этого инфоблока немедленно удалялись характеристики товара и цены. Сейчас эта информация стирается только при удалении самого элемента.
Доступность товара и возможность его покупки
До выхода обновления catalog 16.0.3 доступность товара рассчитывалась «на лету» при выборке из базы. Вследствие этого нельзя было получить доступность товара с торговыми предложениями. Сейчас доступность рассчитывается в момент изменения характеристик товара и сохраняется в базе. Простой товар, комплект или торговое предложение считаются недоступным, если:
для него включен количественный учет
запрещена покупка при отсутствии
количество меньше либо равно нулю
Во всех остальных случаях товар доступен.
Товар с торговыми предложениями считается недоступным, если все(!) его предложения недоступны (алгоритм расчета см. выше).
Для комплекта параметры, участвующие в расчете доступности, выставляются автоматически и не могут быть изменены.
Доступность не означает, что товар может быть куплен. Полный перечень условий для покупки:
Товар доступен
Элемент инфоблока активен
Даты активности элемента отсутствуют либо текущая дата попадает в диапазон активности
Условия, завязанные на конкретного покупателя:
минимальные права доступа – чтение
у товара есть цены тех типов, по которым клиент может покупать
Доступность товара пересчитывается при вызове следующих методов:
CIBlockElement::Add
CIBlockElement::Update
CIBlockElement:: Delete
CIBlockElement::SetPropertyValues (при передаче свойства привязки торгового предложения к головному товару)
CIBlockElement::SetPropertyValuesEx (при передаче свойства привязки торгового предложения к головному товару) - с версии iblock 17.6.5 + catalog 17.6.
CCatalogProduct::Add
CCatalogProduct::Update
CCatalogProduct:: Delete
\Bitrix\Catalog\Model\Product::add (catalog 17.6.0 и выше)
\Bitrix\Catalog\Model\Product::update (catalog 17.6.0 и выше)
\Bitrix\Catalog\Model\Product::delete (catalog 17.6.0 и выше)
Настройки модуля Торговый каталог
Вкратце коснемся настроек модуля, непосредственно влияющих на товары
Показывать вкладку Торговый каталог для товаров, имеющих торговые предложения Включение этой настройки отключает автоматический пересчет доступности товаров с торговыми предложениями.
Значения параметров товаров по умолчанию Изменение настроек в этом разделе касается лишь тех товаров, у которых в соответствующих параметрах выставлено «по умолчанию».
В случае проблем с товарами (неверная доступность, неверные типы товаров, etc) можно воспользоваться одной из служебных процедур модуля из раздела Переиндексация данных - Переиндексацией товаров. Начиная с версии catalog 17.0.1 эта процедура дополнительно валидирует коэффициенты единиц измерения.
В следующей публикации будут рассмотрены возможности api по групповым выборкам предложений.
P.S. Устаревшие примеры создания и проверки существования товара (оставлены лишь для ознакомления и сравнения)
Создание:
if (\Bitrix\Main\Loader::includeModule('catalog'))
{
$useStoreControl = (string)\Bitrix\Main\Config\Option::get('catalog', 'default_use_store_control') === 'Y';
$element = new CIBlockElement;
$fields = array(
'IBLOCK_ID' => ID_торгового_каталога,
'NAME' => 'Товар'
);
$id = (int)$element->Add($fields);
if ($id > 0)
{
$fields = array(
'ID' => $id,
'QUANTITY_TRACE' => \Bitrix\Catalog\ProductTable::STATUS_DEFAULT,
'CAN_BUY_ZERO' => \Bitrix\Catalog\ProductTable::STATUS_DEFAULT,
'WEIGHT' => 0,
'MEASURE' => ID_единицы_измерения
);
if (!$useStoreControl)
{
// выключен складской учет
$fields['QUANTITY'] = общее_количество_товара;
}
// создание товара
$result = CCatalogProduct::Add($fields);
if ($result)
{
// добавление коэффициента единицы измерения товара
$ratioResult = \Bitrix\Catalog\MeasureRatioTable::add(array(
'PRODUCT_ID' => $id,
'RATIO' => коэффициент_единицы_измерения
) );
if (!$ratioResult->isSuccess())
{
echo 'Ошибка создания коэффициента<br>'.implode('<br>', $ratioResult->getErrorMessages());
}
// добавление цены
$priceId = CPrice::Add(array(
'PRODUCT_ID' => $id,
'CATALOG_GROUP_ID' => ID_типа_цены,
'PRICE' => 100,
'CURRENCY' => 'RUB'
));
if (!$priceId)
{
if ($ex = $APPLICATION->GetException())
echo 'Ошибка создания цены: '.$ex->GetString();
else
echo 'Неизвестная ошибка при создании цены';
unset($ex);
}
}
else
{
if ($ex = $APPLICATION->GetException())
echo 'Ошибка создания товара: '.$ex->GetString();
else
echo 'Неизвестная ошибка при создании товара';
unset($ex);
}
}
}
Евгений, спасибо за статью! Разбор алгоритмов работы магазина крайне важен для качественной разработки, ждем продолжения
Исходя из написанного, если я изменяю привязку к элементу ИБ через CIBlockElement::SetPropertyValuesEx, то доступность пересчитана не будет ? Какой метод ее пересчитывает ?
Придирка 1. Вы очень строго ссылаетесь на характеристики товара >>Элемент, у которого есть цены, но нет характеристик товара — не товар
а при этом сами перечисляете их без укзания точного состава и обязательности >>(количественный учет и доступность, вес и размеры, etc).
Придирка 2. Наверное что-то удалили или не дописали: как-то не очень клево 2 раза использовать одну и ту же переменную $result под разные цели. При этом второй раз в нее пишите, а не используете
Придирка 1. Вы очень строго ссылаетесь на характеристики товара >>Элемент, у которого есть цены, но нет характеристик товара — не товар
а при этом сами перечисляете их без укзания точного состава и обязательности >>(количественный учет и доступность, вес и размеры, etc).
Действительно, мой недосмотр. Я как-то не сообразил, что характеристики товара могут восприниматься не как единая сущность. Формулировка изменена.
Придирка 2.
Это пример, а не готовый к использованию кусок кода. Он предполагает одновременную работу с документацией по приведенным методам. Впрочем, код дополнен.
Евгений, спасибо! Полезный обзор, особенно, когда начинаются вопросы про типы товаров и тд - будет теперь, что скинуть почитать. --- Еще бы заголовки у статьи сделать более дружелюбными для поисковиков
Михаил, вполне возможно, такие методы и появятся. Возможно даже, что и в предложенном Вами варианте. Однако данная статья исключительно о том, как правильно делать сейчас, на основе существующего api. Вы вполне можете создать обращение на сайте идей по расширению api, мы его рассмотрим.
Доступность - это поле CAN_BUY? Что-то не так очевидно работает... У меня каталог содержит простые товары и товары с SKU. Компонент catalog.section со стандартным шаблоном адекватно выводит CAN_BUY только для простых товаров, а для товаров с SKU всегда false. При этом в админке в поле "доступность" отображается адекватная доступность.
Нет. CAN_BUY - это возможность покупки. Более того, это всего лишь ключ $arResult, заполняемый в компонентах. Доступность - поле AVAILABLE в характеристиках товара (CCatalogProduct::GetList, \Bitrix\Catalog\ProductTable::getList, etc).
Спасибо за уточнение! Не сочтите за занудство, но с терминологией беда: чем возможность покупки отличается от доступности? У вас даже раздел в статье называется "Доступность товара и возможность его покупки", а речь там только о доступности )
Доступность не означает, что товар может быть куплен. Полный перечень условий для покупки: Товар доступен Элемент инфоблока активен Даты активности элемента отсутствуют либо текущая дата попадает в диапазон активности Условия, завязанные на конкретного покупателя: минимальные права доступа – чтение у товара есть цены тех типов, по которым клиент может покупать
Доброго дня! Подскажите сталкивался кто-нибудь с проблемой интеграции с 1С после переключения версии PHP c 5.6 на 7.1 в контексте неправильной доступности товара и неправильного отображения типа товара. Остатки по складам есть общее количество и цена отсутствуют у торговых предложений?
Судя по скрину, товар у вас не создается, только элемент инфоблока. Поскольку у себя не наблюдаю на 7.1, могу предположить кастомный обработчик события(й) OnBeforeProductAdd / OnProductAdd. Лог ошибок php смотрели? Создавайте обращение в ТП, потребуется административный доступ.
Спасибо Огромное! Обращение конечно создал - отвечают медленно - раз в сутки. Обработчики есть и тот и другой и еще от старого сайта(я пологаю). Но товарные предложения по складам есть: https://yadi.sk/i/WRPz0JHL4CSLuw. А цен и количества нету https://yadi.sk/i/v8bApOipjFIj3w И в ручную не ставится в админке... Думал вдруг кто-нибудь сталкивался...Спасибо!
2307189 - НО! со вчерашнего дня мяч на моей стороне - я не предоставил лог сессии прокси сервера fiddler (который потребовали установить для дальнейшей диагностики) связанно с временным отсутствием "хозяина" 1С.
Здравствуйте. Подскажите, у меня обработчик создает простые товары и торговые предложения с помощью CCatalogProduct::Add($arFields); Простые товары нормально создаются, а вот предложения без количества. Метод устарел, но каким теперь создавать?
Проверяйте обработчики событий. В самом методе нет никаких блокировок изменения количества. Ну и свой обработчик покажите - код и на какое событие вешаете.
ДА копирование. Есть один инфоблок, с обычными разделами и товарами, я с него в другом делаю товары с предложениями. $arFields["AVAILABLE"]="Y" в ставил т.к. товары не доступны были, но не помогло...
Исходя из написанного, если я изменяю привязку к элементу ИБ через CIBlockElement::SetPropertyValuesEx, то доступность пересчитана не будет ? Какой метод ее пересчитывает ?
Придирка 1. Вы очень строго ссылаетесь на характеристики товара
>>Элемент, у которого есть цены, но нет характеристик товара — не товар
а при этом сами перечисляете их без укзания точного состава и обязательности
>>(количественный учет и доступность, вес и размеры, etc).
Придирка 2. Наверное что-то удалили или не дописали: как-то не очень клево 2 раза использовать одну и ту же переменную $result под разные цели. При этом второй раз в нее пишите, а не используете
>>Элемент, у которого есть цены, но нет характеристик товара — не товар
а при этом сами перечисляете их без укзания точного состава и обязательности
>>(количественный учет и доступность, вес и размеры, etc).
---
Еще бы заголовки у статьи сделать более дружелюбными для поисковиков
То что есть сейчас "слегка" неудобно, хотя конечно и работает.
Вопрос риторический, понимаю, что тлен
А статья хорошая, вопросов нет!
CCatalogProduct::GetByID его не возвращает
У меня каталог содержит простые товары и товары с SKU. Компонент catalog.section со стандартным шаблоном адекватно выводит CAN_BUY только для простых товаров, а для товаров с SKU всегда false. При этом в админке в поле "доступность" отображается адекватная доступность.
Товар доступен
Элемент инфоблока активен
Даты активности элемента отсутствуют либо текущая дата попадает в диапазон активности
Условия, завязанные на конкретного покупателя:
минимальные права доступа – чтение
у товара есть цены тех типов, по которым клиент может покупать
Подскажите сталкивался кто-нибудь с проблемой интеграции с 1С после переключения версии PHP c 5.6 на 7.1 в контексте неправильной доступности товара и неправильного отображения типа товара. Остатки по складам есть общее количество и цена отсутствуют у торговых предложений?
и так во всех вновь созданных товарах.
catalog 18.6.1, iblock 18.6.2
И в ручную не ставится в админке... Думал вдруг кто-нибудь сталкивался...Спасибо!
Подскажите, у меня обработчик создает простые товары и торговые предложения с помощью CCatalogProduct::Add($arFields); Простые товары нормально создаются, а вот предложения без количества. Метод устарел, но каким теперь создавать?
Ну и свой обработчик покажите - код и на какое событие вешаете.
function SaveCatalogElement($FirstID,$ToID)
{
$ar_res = CCatalogProduct::GetByID($FirstID);
$arFields=$ar_res;
//AddMessage2Log( $arFields, "my_module_id");
$arFields["ID"]=$ToID;
unset($arFields["TIMESTAMP_X"]);
if(!$arFields["PURCHASING_CURRENCY"])
$arFields["PURCHASING_CURRENCY"]="RUB";
$arFields["AVAILABLE"]="Y";
CCatalogProduct::Add($arFields);
$db_res = CPrice::GetList(
array(),
array(
"PRODUCT_ID" => $FirstID,
)
);
while ($ar_res = $db_res->Fetch())
{
$arFields = Array(
"PRODUCT_ID" => $ToID,
"CATALOG_GROUP_ID" => $ar_res["CATALOG_GROUP_ID"],
"PRICE" => $ar_res["PRICE"],
"CURRENCY" => $ar_res["CURRENCY"],
"QUANTITY_FROM" => $ar_res["QUANTITY_FROM"],
"QUANTITY_TO" => $ar_res["QUANTITY_TO"],
);
$res = CPrice::GetList(
array(),
array(
"PRODUCT_ID" => $ToID,
"CATALOG_GROUP_ID" => $ar_res["CATALOG_GROUP_ID"],
)
);
if ($arr = $res->Fetch())
{
CPrice::Update($arr["ID"], $arFields);
}
else
{
CPrice::Add($arFields);
}
}
}