BR 1.10: Прейскуранты

Зависит от:

Контекст

Сейчас цена товара хранится в base_price самого товара, а цена опции модификатора — в base_price опции. Это неверная модель: каталог — это ассортимент (набор блюд с модификаторами), а не ценник. Цены должны определяться прейскурантом, который применяется к каталогу.

Прейскурант позволяет:

  • Иметь единую базовую цену (дефолтный прейскурант) на все ТТ
  • Назначать кастомные цены на конкретные ТТ (например, в аэропорту дороже)
  • Менять цены без изменения каталога (ассортимент не меняется — только ценник)

Прейскурант по регионам — отложен, не входит в MVP.


Требования

1. Сущность “Прейскурант”

Прейскурант — именованный набор цен на товары и опции модификаторов. Живёт на уровне франшизы.

  • Название — уникальное в рамках франшизы (обязательное)
  • Флаг is_default — ровно один прейскурант на франшизу помечен как дефолтный
  • Статус — active / inactive

Если у ТТ не назначен конкретный прейскурант — применяется дефолтный.

2. Версионирование прейскуранта (привязка к каталогу)

Прейскурант версионируется вместе с каталогом (связь 1:1 через версию каталога):

  • При создании draft каталога → автоматически создаются draft-версии всех прейскурантов (дефолтного + всех кастомных). Draft прейскуранта копирует цены из предыдущей published-версии.
  • При публикации каталога → публикуются все draft-прейскуранты вместе с ним.
  • При удалении draft каталога → удаляются все draft-прейскуранты.

Таким образом:

  • Каталог v3 (published) → Прейскурант “Стандартный” v3 (published) + Прейскурант “Аэропорт” v3 (published)
  • Каталог v4 (draft) → Прейскурант “Стандартный” v4 (draft) + Прейскурант “Аэропорт” v4 (draft)

3. Позиции прейскуранта (цены)

Каждая версия прейскуранта содержит цены:

  • Цена товара — для каждого товара из каталога данной версии
  • Цена опции модификатора — для каждой опции модификатора, привязанного к товарам в каталоге

Цена обязательна для каждого товара. Если новый товар добавлен в draft каталога — его цена в draft прейскуранте = 0 (франшиза должна задать).

4. Убрать base_price из товаров и модификаторов

Breaking change: Поле base_price убирается из:

  • Товаров (products.base_price, product_versions.base_price)
  • Опций модификаторов (modifier_options.base_price)

Цена больше не является свойством товара. Товар — это описание блюда (название, тип, единица измерения). Цена — свойство прейскуранта.

Миграция данных

Существующие base_price товаров и опций нужно перенести в дефолтный прейскурант при миграции.

5. Назначение прейскуранта на ТТ

  • Франшиза назначает прейскурант на конкретные ТТ
  • Один прейскурант может быть назначен на несколько ТТ (даже из разных франчайзи)
  • У ТТ может быть максимум один назначенный прейскурант
  • Если у ТТ нет назначенного → используется дефолтный

6. Управление прейскурантами (CRUD)

Создание

  • Франшиза создаёт прейскурант: название, is_default (опционально)
  • При создании — если это первый прейскурант, он автоматически становится дефолтным
  • Если отмечен как is_default — предыдущий дефолтный снимает флаг (ровно один дефолтный)

Редактирование цен

  • Редактирование возможно только в draft-версии (привязанной к draft-каталогу)
  • Список товаров берётся из draft каталога
  • Для каждого товара задаётся цена (decimal, >= 0)
  • Для каждой опции модификатора, привязанной к товару — задаётся цена

Удаление

  • Можно удалить прейскурант если:
    • Он не is_default
    • Он не назначен ни на одну ТТ
  • При попытке удалить назначенный → ошибка с указанием ТТ

7. Сборка меню (логика резолвинга цены)

При формировании меню для конкретной ТТ:

1. Определить каталог → текущий published
2. Определить прейскурант ТТ → назначенный на ТТ, или дефолтный (fallback)
3. Для каждого товара каталога:
   a. Цена товара = из прейскуранта (версия = published каталога)
   b. Для каждого модификатора товара:
      - Цена каждой опции = из прейскуранта
4. Применить стоп-листы (будущая BR)
5. = Финальное меню

Бизнес-правила

  • Ровно один прейскурант на франшизу помечен как is_default в любой момент времени
  • Дефолтный прейскурант нельзя удалить (можно перенести флаг на другой, потом удалить)
  • Все товары в прейскуранте должны иметь цену (>= 0). Новые товары в draft получают цену 0 — франшиза обязана задать перед публикацией
  • Публикация каталога с товарами у которых цена = 0 в дефолтном прейскуранте — запрещена (валидация)
  • Прейскурант не может быть назначен на ТТ если он inactive
  • При смене дефолтного прейскуранта — ТТ без явного назначения автоматически переходят на нового дефолтного

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

ДействиеФраншизаФранчайзиМенеджерКассир
Создание прейскуранта
Редактирование цен
Удаление прейскуранта
Назначение на ТТ
Просмотр прейскурантов✅ (свои ТТ)✅ (своя ТТ)
Просмотр цен в меню✅ (POS)

Затронутые сервисы

СервисЧто меняется
Catalog ServiceНовые сущности: price_lists, price_list_versions, price_list_items. Убрать base_price из products/product_versions/modifier_options. Обновить createDraft/publish — каскадно создавать/публиковать прейскуранты. Новые CRUD эндпоинты.
Store ServiceНовая связь: store → price_list (назначение). Новый эндпоинт назначения.
Admin BFFProxy новых эндпоинтов.
Admin WebНовый раздел “Прейскуранты” в sidebar. Таб “Цены” в конструкторе каталога (версия). Назначение прейскуранта в карточке ТТ.

Открытые вопросы

Все вопросы разрешены в процессе сбора требований.


Ссылки