Товары и каталог
Карточка товара, цена, варианты, остатки, тиры оптовых цен, продажа по заявке, управление каталогом, импорт CSV, массовые операции.
Товары и каталог
Каталог товаров живёт в текущей активной группе. Один товар может показываться сразу на нескольких сайтах и быть доступен нескольким агентам — это управляется правилами видимости.
Где создавать
/account/commerce → вкладка «Товары» → кнопка «Создать товар».
Поля карточки
| Поле | Описание |
|---|---|
| Название | Видно покупателю и попадает в search_products LLM-tool. |
| Slug | Короткое имя для URL /p/<slug>. Уникален в пределах группы. Заполняется автоматически из названия, можно править. |
| Описание | Краткий текст. LLM использует его при поиске и пересказе. |
Подробное описание (long_description) | Развёрнутый HTML-текст: преимущества, инструкция, история бренда. Отображается на странице товара как отдельная секция. |
Характеристики (specs) | Таблица пар «название — значение» (материал, вес, размер и т.д.). Рисуется таблицей под описанием, если секция включена. |
Бейдж (badge) | Короткая пометка в карточке: «Хит», «Новинка», «Акция». Плюс авто-бейдж скидки −N% появляется сам, когда задана comparePrice. |
| Цена | В рублях, хранится в копейках. Можно задать зачёркнутую цену (comparePrice) для отображения скидки. |
| Валюта | По умолчанию RUB. |
| SKU | Опциональный артикул для учёта. |
| Галерея | Массив картинок (url, alt). Первая используется как preview и og:image. |
| Варианты | Размер / цвет / любой атрибут. Каждый вариант — со своим priceDelta к базе и собственным stock. Если массив пуст — товар без вариантов. |
Остатки (stock) | Целое число. Работает только при trackInventory=true. Декрементируется при paid, возвращается при cancel. |
Учитывать остатки (trackInventory) | Чек: если выключен — товар продаётся неограниченно (актуально для цифровых товаров). |
| Теги | Массив строк для AI-поиска и быстрых фильтров. |
Требует доставки (requiresShipping) | Если включено — в чекауте появляется блок адреса и способа доставки. Включает cash-on-delivery. |
| Активен | Если выключен — товар не виден в публичном каталоге и не находится через search_products. |
| Режим продажи | buy (оплата онлайн) или inquiry (по заявке — см. ниже). |
| 54-ФЗ: paymentSubject | commodity / service / job. Попадает в фискальный чек. |
| 54-ФЗ: vatCode | 1 без НДС (по умолчанию, типично для УСН/самозанятых), 4 НДС 20% и т.д. |
Страница товара /p/<slug>
Каждый товар получает публичную SSR-страницу по адресу /p/<slug>. Она наследует дизайн-систему проекта (--ds-* CSS-переменные), выглядит органично в стиле сайта и не требует отдельного шаблона.
Богатые секции рисуются, только если у товара есть данные под них и секция включена в настройках витрины:
- Подробное описание (
long_description) — sanitized-HTML с форматированием. - Таблица характеристик (
specs). - Бейдж-лейбл + авто-бейдж скидки
−N%. - Блок «Похожие товары» (авто-подбор по категориям/тегам).
SEO: Product JSON-LD, og:image, canonical. Товары попадают в sitemap.xml с корректным lastmod.
Клик по карточке в каталоге ведёт на эту страницу. В списке товаров кабинета — кнопка «Открыть на сайте».
Опт и количество
Настраивается в форме товара в блоке «Количество и оптовые цены».
Ограничения количества
| Параметр | Описание |
|---|---|
Минимум (minQuantity) | Минимальное количество за заказ. По умолчанию 1. |
Максимум (maxQuantity) | Максимальное количество. Пусто = без ограничения. |
Кратность (quantityStep) | Шаг упаковки. Например, 6 — продаётся упаковками по 6 шт. |
| Единица измерения | Пишется рядом с числом: «шт», «кг», «упак», «м» — до 20 символов. |
Оптовые тиры цен
До 8 тиров формата «от N шт — цена за шт»:
| От количества | Цена за шт |
|---|---|
| 1 (базовая) | 1 500 ₽ |
| 5 | 1 350 ₽ |
| 20 | 1 100 ₽ |
На странице товара /p/<slug>:
- Селектор количества
−/+(с учётом min/max/step). - Таблица оптовых диапазонов — активный тир подсвечен, у каждого тира бейдж экономии
−X%. - Прогресс-бар «до скидки X% осталось Y ₽» (для скидок от суммы).
- Динамическая сумма в кнопке «Купить» пересчитывается в реальном времени.
Тиры действуют и в quick-view модалке каталога.
Сервер всегда пересчитывает тиры сам (при создании черновика и при финализации заказа). Клиентской цене сервер не доверяет.
AI-агент видит опт-правила в ответе search_products и понимает ошибки типа «минимальный заказ — 5 шт».
Продажа «по заявке» (inquiry)
Для товаров, где оплата онлайн не нужна или невозможна: материалы на заказ, изделия под размер, B2B-позиции с договорной ценой.
Включение: в форме товара переключатель «Режим продажи» → «По заявке». В каталоге кабинета появляется бейдж «по заявке».
Флоу для покупателя:
- Клиент открывает страницу товара
/p/<slug>или quick-view модалку. - Выбирает количество (тиры применяются, сумма считается).
- Нажимает «Заказать» — открывается оверлей с формой: имя, телефон или email, комментарий.
- После отправки — лид попадает в CRM.
Что попадает в CRM (project_collection, source: product_inquiry):
- Контакты покупателя (имя, email, телефон).
- Снапшот выбора: название товара, количество, цена за единицу по тиру, итоговая сумма со скидкой корзины.
- Геопривязка по IP.
Сумма в лиде совпадает с тем, что видел покупатель — считается сервером по тем же правилам, что и заказ.
Уведомления: email на адрес, указанный в настройках уведомлений проекта + push в кабинете.
Важно:
- Работает без подключённого мерчант-аккаунта. Деньги онлайн не проводятся.
- Стандартный чекаут (для
buy-товаров) такие товары отклоняет — нельзя смешать в одну корзину. - AI-агент в режиме inquiry не создаёт заказ и не выставляет ссылку на оплату: вместо этого собирает контакты покупателя и сообщает, что менеджер свяжется.
Настройки витрины
/account/commerce → кнопка «Витрина» (диалог CommerceSettingsDialog).
Глобальные переключатели секций страниц товаров и услуг:
| Параметр | Что управляет |
|---|---|
| Подробное описание | Секция long_description |
| Характеристики | Таблица specs |
| Похожие товары | Блок авто-подбора по категориям/тегам |
| Кол-во похожих | Число карточек в блоке (1–12, по умолчанию 4) |
| Селектор количества | Поле −/+ на странице товара и в модалке |
| Оптовые цены | Таблица тиров с подсветкой активного |
| Прогресс скидки | Прогресс-бар «до скидки X% осталось Y ₽» |
Секция рисуется, только если тумблер включён и у товара есть данные под неё.
Правила корзины группы
В том же диалоге настраиваются правила, применяемые к любому заказу в группе.
Минимальная сумма заказа (товарная часть, без доставки):
- Если задана — заказы ниже порога отклоняются сервером.
- На кнопке «Купить» показывается подсказка «не хватает N ₽».
Скидки от суммы корзины (до 6 порогов):
- Формат: «от X ₽ — Y% скидки».
- Применяются сервером при создании заказа; 54-ФЗ чек сходится копейка в копейку (скидка размазывается по юнит-ценам).
- На странице товара → прогресс-бар «до скидки X% осталось Y ₽» (если включено).
- В чекауте — строка «Скидка N%» и поле
discount_amount/discount_percentна заказе.
Хранится в workspace.commerce_settings, контракт + дефолты — lib/commerceSettings.ts.
Категории
/account/commerce → вкладка «Категории».
- Иерархия через self-FK (
parentId): категория может иметь подкатегории. - Slug уникален в пределах группы.
- Один товар через M2M (
product_to_category) может быть в нескольких категориях. Например, «футболка» — в «Одежда» и в «Лето». - Категории общие для товаров и услуг: M2M
service_to_categoryиспользует ту же таблицу. Полезно для смешанного каталога (косметология — товары + занятия в одной рубрике).
Видимость
По умолчанию товар виден везде в группе — на всех сайтах и у всех агентов. Если нужны исключения — заполните таблицы видимости:
- Видимость по сайтам (
product_site_visibility) — товар показывается только на выбранных сайтах. - Видимость по агентам (
product_agent_visibility) — товар продаётся только указанными агентами.
Логика «пусто = виден всем» одинаковая для обеих таблиц. В карточке товара — бейдж «выборочно», если заданы конкретные сайты.
Управление каталогом
Inline-редактирование
Прямо в таблице/карточке — клик по цене или остатку переводит поле в режим редактирования. Изменение активности — переключатель без открытия формы. Всё с оптимистичным обновлением и откатом при ошибке.
Массовые операции
Выберите несколько товаров (чекбоксы) — появляется sticky-тулбар. Доступные действия:
- Показать / скрыть (активен/неактивен).
- Скрыть из продажи.
- Добавить категорию.
- Изменить цену на ±N% (двигает и
comparePrice). - Добавить / убрать тег.
Бэкенд — POST /api/products/bulk с проверкой прав на каждый ID.
Импорт и экспорт CSV
Экспорт (GET /api/products/export): все товары группы в CSV с BOM для корректного открытия в Excel с кириллицей.
Импорт (ProductImportDialog + POST /api/products/import):
- Клиентский CSV-парсер с авто-маппингом русских и английских заголовков.
- Превью первых строк до фактической загрузки.
- Режим create (только новые) или upsert (обновить по slug или SKU).
- Построчный отчёт об ошибках после импорта.
Дублирование товара
POST /api/products/duplicate — копия со всеми полями, категориями и видимостью. Создаётся неактивной; slug и SKU авто-разводятся суффиксом, чтобы не конфликтовать.
Low-stock
- Пилюля-фильтр «Заканчивается» в списке.
- Бейдж «осталось N» (янтарный) в карточке и строке таблицы.
- Per-товар порог
low_stock_threshold(по умолчанию 5). API-фильтрlowStockсравниваетstock <= threshold.
AI-генерация карточки
POST /api/products/ai-generate: по названию генерируется описание, теги, SEO-описание, подбирается категория из уже существующих в группе. Расход токенов списывается с баланса owner'а (как генерация агента).
Поиск
Каталог индексируется через pg_trgm (GIN-индексы на name + description). Сортировки:
relevance— pg_trgm similarity (name ×2 + description);price_asc/price_desc;newest.
LLM-tool search_products бьёт по тому же индексу — скорость и качество одинаковы что в чате, что в UI.
Что хранится в БД
| Таблица | Что |
|---|---|
commerce_product | Карточка товара (включая priceTiers jsonb, specs jsonb, salesMode, minQuantity, maxQuantity, quantityStep, unitLabel) |
product_category | Категории (иерархия через parentId) |
product_to_category | M2M товар ↔ категория |
product_site_visibility | Видимость по сайтам (пусто = везде) |
product_agent_visibility | Видимость по агентам (пусто = всем) |