Перейти к содержимому

Артикулы товаров - хранение, уникальность и поиск

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

Решение

Где хранится артикул

Заводим свойство под артикул:

$prop = new \CIBlockProperty();
$propId = $prop->Add([
'IBLOCK_ID' => 5,
'NAME' => 'Артикул',
'CODE' => 'ARTNUMBER', // код обязателен: без него свойство не видно ORM
'PROPERTY_TYPE' => 'S', // строка: ведущие нули и буквы сохраняются
'IS_REQUIRED' => 'Y',
]);

Отдельного поля под артикул в торговом каталоге нет. Демо-каталог держит его свойством ARTNUMBER, обмен с 1С заводит своё свойство CML2_ARTICLE, и на одном сайте эти два свойства нередко уживаются вместе.

Артикул легко спутать с внешним кодом XML_ID. Внешний код - поле элемента, и его ставит учётная система, чтобы сопоставлять позиции при обмене. Артикул назначает человек, и для платформы это обычная строка. Штрихкод - третья сущность: он лежит в своей таблице каталога и участвует в приёмке.

Артикул у товаров с вариантами

Смотрим, в какой инфоблок класть свойство:

$info = \CCatalogSKU::getInfoByIBlock($iblockId);
// 'P' - инфоблок товаров, предложения лежат в отдельном инфоблоке
// 'O' - инфоблок предложений: артикул заводят здесь
printf("%s -> %s\n", $info['CATALOG_TYPE'], $info['IBLOCK_ID'] ?? '-');

Продаётся у такого товара предложение, а не родительская карточка. Артикул поэтому принадлежит предложению: у родителя он остаётся названием серии и в заказ не попадает.

Поиск товара по артикулу

Ищем позицию точным совпадением:

$rs = \CIBlockElement::GetList([], [
'IBLOCK_ID' => $offersIblockId,
'=PROPERTY_ARTNUMBER' => trim($query), // знак равенства - точное совпадение
], false, ['nTopCount' => 2], ['ID', 'NAME', 'PROPERTY_CML2_LINK']);
$found = $rs->Fetch();

Это выборка по инфоблоку, а не полнотекстовый поиск. Одна найденная позиция - повод сразу открыть карточку товара, две и больше - показать обычный список.

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

Проверка уникальности

Проверяем артикул до записи элемента:

AddEventHandler('iblock', 'OnBeforeIBlockElementUpdate', 'checkArticle');
function checkArticle(&$arFields) { // на добавление вешается отдельной строкой
global $APPLICATION;
$article = trim($arFields['PROPERTY_VALUES']['ARTNUMBER'] ?? '');
if ($article === '') { return; }
$rs = \CIBlockElement::GetList([], ['IBLOCK_ID' => $arFields['IBLOCK_ID'],
'=PROPERTY_ARTNUMBER' => $article, '!ID' => $arFields['ID']],
false, ['nTopCount' => 1], ['ID']);
if ($rs->Fetch()) {
$APPLICATION->ThrowException('Артикул уже занят другим товаром');
return false; // отказ виден и в админке, и в своём скрипте импорта
}
}

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

Уже накопленные дубли обработчик не покажет: он смотрит только на текущую запись. Их ищут разовой проверкой каталога одним запросом к таблице свойств инфоблока.

Вывод артикула в карточке, корзине и заказе

Добираем артикулы к позициям заказа:

$items = [];
foreach ($order->getBasket() as $basketItem) {
$items[$basketItem->getProductId()] = ['PROPERTIES' => []]; // ключ обязателен
}
\CIBlockElement::GetPropertyValuesArray($items, $offersIblockId,
['ID' => array_keys($items)], ['CODE' => 'ARTNUMBER']);

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

В карточке артикул печатает компонент каталога, и свойство должно стоять в его списке свойств. Тот же список решает, попадёт ли артикул снимком в позицию корзины: в демо-каталоге туда штатно кладут ARTNUMBER.

Обмен и импорт из файла

Записываем артикулы пачкой:

foreach ($rows as $id => $article) {
\CIBlockElement::SetPropertyValuesEx($id, $iblockId, ['ARTNUMBER' => $article]);
}
\CIBlock::clearIblockTagCache($iblockId); // метод пишет свойство, но кеш не чистит

Метод трогает только переданное свойство и не обнуляет остальные. Коды свойств регистрозависимы: с ключом artnumber вместо ARTNUMBER значение не запишется и ошибки не будет.

Штатный обмен сопоставляет позиции по внешнему коду, а не по артикулу. Артикул приходит свойством и переписывается каждой выгрузкой, поэтому правка на сайте живёт до следующего обмена. В импорте из файла артикул задают колонкой IP_PROP с номером своего свойства.

Типичные проблемы

Обмен затирает артикул, поправленный в админке.

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

Артикул виден в карточке, а в заказе его нет.

Позиция корзины хранит название, цену и количество, а свойства товара в неё не переносятся. Их добирают выборкой по идентификатору товара или кладут снимком при добавлении.

Поиск находит артикул МГ-14, но не находит МГ14.

Полнотекстовый индекс режет строку на слова, и написание без разделителя в него не попадает. Точный поиск выборкой по свойству от написания не зависит.

У товаров с вариантами артикул после обмена пустой.

Свойство заведено в инфоблоке товаров, а предложения лежат в отдельном инфоблоке. Свойство создают там, где живут сами предложения.

Ведущие нули артикула пропали после загрузки таблицы.

Свойство заведено числом либо файл открывали в табличном редакторе и он обрезал нули. Артикул хранят строкой и приводят к строке прямо при чтении файла.

Частые вопросы

Чем артикул отличается от внешнего кода товара?

Внешний код XML_ID - поле элемента и ключ сопоставления при обмене и импорте. Артикул - свойство инфоблока, его читают люди и печатают в документах. Совпадать они не обязаны, и по артикулу штатный обмен позиции не связывает.

Как сделать подбор товара по артикулу в интернет-магазине?

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

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

В инфоблоке предложений. Учётная позиция здесь - предложение: у него свой остаток, своя цена и свой артикул. У родительской карточки артикул полезен как код серии, но в корзину и заказ он не уходит.

Как вывести список артикулов в письме о заказе?

Собрать их выборкой по идентификаторам товаров заказа и подставить в шаблон письма. Читать свойства по одному товару в цикле не стоит: на заказе из двадцати позиций это двадцать лишних запросов.

Есть ли штатная галочка «артикул уникален»?

Нет, уникальность значений свойства платформа не проверяет. Её делают обработчиком события до записи элемента, а накопленные дубли ищут отдельной проверкой каталога.

Смежное

Первоисточник