Блок «С этим товаром покупают» в CS-Cart: ручные рекомендации пачкой из CSV, а не поведенческий автомат
На старте магазина статистики для поведенческого автомата нет, а 58 000 товаров не накликать. Аддон CS-Cart: связи «С этим товаром покупают» грузятся пачкой из CSV по артикулам, идемпотентно.
Задача
Интернет-магазин сантехники на CS-Cart, каталог ~58 000 товаров. На карточке нужен блок сопутствующих товаров — «С этим товаром покупают»: к ванне предложить смеситель, сифон и экран, к унитазу — инсталляцию и кнопку смыва. Это кросс-сейл: поднимает средний чек и избавляет покупателя от сборки комплекта по частям вручную.
Сложность в том, что готовые механизмы под этот сценарий не подходили. Поведенческие рекомендации CS-Cart («Покупавшие этот товар также покупали») строятся по истории заказов — на старте магазина заказов ещё нет, блок пустой и наполнится только через сотни продаж. Стандартные «связанные товары» задаются вручную кликами в каждой карточке — на 58 000 товаров это не масштабируется. Вдобавок менеджер магазина оперирует не внутренними ID базы, а артикулами из прайса.
Типичный клиент с такой задачей — магазин после переезда или парсинга каталога, которому кросс-сейл нужен сразу на запуске, а связи между товарами уже известны (лежат в прайсе или учётной системе) и их надо просто массово залить.
Решение
Я написал аддон CS-Cart, который управляет блоком «С этим товаром покупают» через загрузку CSV. Менеджер готовит файл, где строка — это один главный товар и его сопутствующие: артикул_главного, артикул1;артикул2;артикул3. Загружает в админке (Товары → Рекомендации → «Обновить связи»), и блок на карточках обновляется.
Сопоставление по артикулу. Товары в файле задаются по product_code (артикулу), а не по внутреннему product_id CS-Cart. Это сознательно: связи выгружаются из прайса или учётной системы, где фигурируют артикулы, и менеджер не должен знать про ID базы. Скрипт сам резолвит каждый артикул в product_id.
Идемпотентность. Связи хранятся в отдельной таблице product_recommendations (пара главный → сопутствующий, составной первичный ключ). При загрузке для каждого главного товара сначала удаляются его прежние связи, потом пишутся новые через REPLACE. Повторная загрузка того же файла даёт тот же результат, без дублей, а связи товаров, которых нет в файле, не затрагиваются — рекомендации можно обновлять частями, по одному файлу на нужную группу товаров.
Устойчивость к грязным данным. Коды очищаются от пробелов, строки с неполными данными и несуществующие артикулы пропускаются, товар не рекомендует сам себя. Кривая строка не роняет весь прогон.
Вывод через движок ядра. На карточке товара блок показывает сопутствующие сеткой (до 15, с кнопкой «Показать ещё»). Ключевое решение: карточки рекомендаций я не верстаю вручную, а отдаю штатному механизму вывода товаров CS-Cart — тому же, что рисует каталог. Поэтому рекомендации наследуют актуальные цены, фото, скидки и лейблы, в блок попадают только активные товары, и он выглядит как остальной магазин, а не как чужеродная вставка.
Почему не коробка — отдельно для тех, кто выбирает между разработкой и готовым модулем. Поведенческий блок CS-Cart полезен, но работает на накопленной статистике заказов и бесполезен на старте. Кликовые «связанные товары» не годятся для каталога такого размера. Здесь был нужен ручной массовый контроль: владелец сам решает, что предложить к товару, и заливает это пачкой. Аддон закрывает ровно этот сценарий.

Стек
CS-Cart 4.18.3 · PHP 8.1 · MySQL 8.0 (MyISAM) · Smarty · CSV · API ядра CS-Cart (fn_get_products, fn_gather_additional_products_data, grid_list, backend-контроллер, template-хук карточки товара)
Результат
- Блок «С этим товаром покупают» работает на карточках с первого дня, не дожидаясь накопления статистики заказов.
- Связи задаются и обновляются массово — одной загрузкой CSV по артикулам, без ручного проставления в каждой карточке.
- Прогон идемпотентен: повторная загрузка обновляет связи без дублей, рекомендации можно править частями.
- Рекомендации наследуют цены, фото и скидки каталога — выводятся штатным движком CS-Cart, а не отдельной вёрсткой.


Ограничение, честно. Импорт синхронный: на каждый артикул — отдельный запрос к базе, без батчей и транзакции. Для разовой и периодической загрузки менеджером этого достаточно — файл связей обрабатывается за один проход в админке. Это инструмент мерчандайзинга под конкретный магазин, а не высоконагруженный сервис; под частые массовые обновления его пришлось бы переписать на батч-вставку.