BR 1.7: Каталог — товары и категории

Контекст

ЮЛ (BR 1.1), ТТ (BR 1.5), сотрудники (BR 1.4), авторизация (BR 1.3) — готовы. Каталог — последний обязательный блок Фазы 1. Без него невозможно запустить каталог на сайте/POS.

Каталог — единый справочник товаров и категорий франшизы. Управляется только франшизой. К блюдам каталога привязаны техкарты (BR 1.9, живут в Warehouse Service).

Двухуровневое версионирование

Товары версионные — каждое изменение товара (название, цена, описание, техкарта) создаёт новую версию товара:

Маргарита@v1 → Маргарита@v2 (изменилась техкарта)
Капучино@v1  → (не менялся, остаётся v1)

Каталог = версионный список ссылок на конкретные версии товаров:

Каталог v1 (archived): [Маргарита@v1, Капучино@v1, Тирамису@v1]
Каталог v2 (published): [Маргарита@v2, Капучино@v1, Тирамису@v1, Латте@v1]
Каталог v3 (draft):     [Маргарита@v2, Капучино@v1, Тирамису@v2, Латте@v1]

Статусы каталога: draft → published → archived

  • Одна published версия в любой момент
  • При публикации draft → published, старая published → archived
  • Архивные версии сохраняются для истории
  • Published каталог “заморожен” — содержит конкретные версии товаров, изменение товара не ломает published

Механика изменения:

  1. Товар изменился (например, обновилась техкарта) → создаётся новая версия товара
  2. Автоматически создаётся draft каталога (если его нет) с обновлённой ссылкой на новую версию товара
  3. Ссылки на неизменённые товары остаются прежними
  4. Франшиза публикует draft → новый published каталог

Меню для клиента — вычисляется на лету (не отдельная сущность):

Каталог (published версия, конкретные версии товаров + техкарты)
  + Прейскурант (дефолтный или per-ТТ)
  − Стоп-листы (per-ТТ)
  − Проверка складских остатков (per-ТТ)
  = Финальное меню для клиента

На MVP — вычисляется при каждом запросе. В целевом варианте — вычисляется и кэшируется в Redis.

Фронт: Админка Франшизы (erp-admin).

Порядок реализации BR каталога

1.7  Каталог (эта BR)              ← фундамент (с версионированием)
  ├── 1.8  Модификаторы             ← дополняет товары
  ├── 1.9  Прейскуранты             ← цены (дефолтный + per-ТТ)
  ├── 1.10 Стоп-листы              ← per-ТТ скрытие позиций
  └── 1.12 Техкарты                ← рецептуры (Warehouse Service)

Что нужно

1. Категории

Франшиза создаёт дерево категорий для структурирования товаров.

Поля категории:

  • Название (обязательное)
  • Родительская категория (необязательная — null = корневая)
  • Порядок отображения (число, для сортировки внутри уровня)
  • Активность (вкл/выкл — неактивная скрыта от клиентов, видна в админке)

Иерархия:

  • Неограниченная вложенность (adjacency list с parent_id, как в iiko)
  • На практике 2-3 уровня: “Еда” → “Горячее” → “Пицца”
  • Товар привязывается к любой категории любого уровня

CRUD:

  • Создание (с выбором родителя или без)
  • Редактирование (название, порядок, активность, смена родителя)
  • Удаление — блокируется если есть дочерние категории (CATEGORY_HAS_CHILDREN) или привязанные товары (CATEGORY_HAS_PRODUCTS). Сначала перенести/удалить содержимое. Каскадное удаление не поддерживается (как в iiko)
  • Деактивация — каскадная: при отключении родительской категории все дочерние и их товары автоматически деактивируются. Обратная активация — поштучно или каскадно (выбор в UI)

Отображение в админке:

  • Дерево с произвольной глубиной (рекурсивный рендеринг)
  • На будущее: drag-and-drop для порядка. На MVP — числовое поле

2. Товары

Франшиза создаёт единый каталог товаров для всей сети. Каждый товар версионный — изменение создаёт новую версию.

Поля товара:

  • Название (обязательное, уникальное в рамках каталога франшизы)
  • Описание (текстовое, необязательное)
  • Тип: Блюдо (dish) / Продукт (good) / Ингредиент (ingredient) (enum, ADR-009). dish — списание по техкарте, good — 1:1, ingredient — складская позиция
  • Базовая цена (обязательная, >= 0 рублей, допускается 0 для бесплатных добавок). Прейскуранты могут перекрывать — BR 1.10
  • Единица измерения (select): шт, кг, г, л, мл, порция
  • Категория (обязательная)
  • Статус: active / inactive (default active)
  • Версия (автоинкремент, системное поле)

Версионность товара:

  • Редактирование любого поля → создаётся новая версия товара (старая сохраняется)
  • Изменение техкарты (BR 1.9) → тоже новая версия товара
  • Каталог ссылается на конкретную версию товара → published каталог “заморожен”

Что НЕ входит:

  • Фото (S3) — Phase 2
  • Штрихкод, КБЖУ, аллергены — Phase 2
  • Время приготовления — Phase 2
  • Модификаторы — BR 1.8

3. Список товаров

  • Таблица: название, категория, тип, цена, ед. изм., статус
  • Поиск по названию
  • Фильтры: категория, тип, статус
  • Пагинация (20 на страницу)

4. Карточка товара

  • Просмотр: все поля
  • Создание: форма с обязательными полями + выбор категории
  • Редактирование: все поля, включая смену категории

5. Удаление товара

  • Soft delete (помечается deleted_at, скрывается из общего списка)
  • Удалённые товары доступны в отдельной вкладке/выпадашке “Удалённые” с кнопкой “Восстановить”
  • Без дополнительных проверок на MVP

6. Ролевой доступ

РольЧто может
ФраншизаCRUD всех товаров и категорий
ФранчайзиТолько просмотр (видит только разрешённые для своих ТТ — BR 1.9)
Менеджер ТТТолько просмотр
КассирНет доступа в админке

Структура фронта (Админка Франшизы)

Sidebar:
├── ...
├── Каталог
│     ├── /catalog/products         — Список товаров
│     ├── /catalog/products/new     — Создание товара
│     ├── /catalog/products/:id     — Карточка товара
│     ├── /catalog/products/:id/edit — Редактирование товара
│     └── /catalog/categories       — Дерево категорий (CRUD)
├── ...

Модульная структура sidebar — будущие разделы (Модификаторы, Прейскуранты, Стоп-листы) добавляются как отдельные пункты без рефакторинга.


Что НЕ входит (отложено)

  • Фото товаров (S3) — Phase 2
  • КБЖУ, аллергены, штрихкод — Phase 2
  • Модификаторы — BR 1.8
  • Прейскуранты — BR 1.10
  • Стоп-листы — BR 1.10
  • Техкарты — BR 1.9
  • Фото товаров (S3) — Phase 2
  • КБЖУ, аллергены, штрихкод — Phase 2
  • Допродажи — Phase 2+

Решения

  1. Двухуровневое версионирование — товары версионные (каждое изменение = новая версия). Каталог = версионный список ссылок на конкретные версии товаров. Статусы каталога: draft → published → archived. При изменении товара → новая версия товара → draft каталога с обновлённой ссылкой. Неизменённые товары — ссылки те же.
  2. Иерархия категорий — неограниченная вложенность (adjacency list). Удаление — только пустые “листья” (как в iiko)
  3. Деактивация категорий — каскадная вниз, активация — поштучно или каскадно
  4. Уникальность названий — название товара уникально в рамках каталога
  5. Цена — базовая в товаре (>= 0₽), прейскурант перекрывает (BR 1.9)
  6. Тип товара — enum: dish / good / ingredient (ADR-009). dish — списание по техкарте, good — 1:1, ingredient — складская позиция
  7. Ед. измерения — select: шт, кг, г, л, мл, порция
  8. Soft delete — удалённые товары в отдельной вкладке с возможностью восстановления
  9. Меню = вычисляемое — не отдельная сущность. BR 1.11 отменена. Меню вычисляется из каталога + прейскурант − стоп-листы − складские остатки.
  10. Техкарты — привязаны к блюдам каталога по product_id, живут в Warehouse Service (BR 1.9)

Зависимости

  • Catalog Service (:3004) — пустой, нужно создать
  • Warehouse Service (:3008) — техкарты привязаны к блюдам (BR 1.9)
  • Store Service — прейскуранты per-ТТ (BR 1.9), стоп-листы per-ТТ (BR 1.10)

Ссылки