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 BFF | Proxy новых эндпоинтов. |
| Admin Web | Новый раздел “Прейскуранты” в sidebar. Таб “Цены” в конструкторе каталога (версия). Назначение прейскуранта в карточке ТТ. |
Открытые вопросы
Все вопросы разрешены в процессе сбора требований.
Ссылки
- Каталог (спека) — текущая логика каталога, формула сборки меню
- Модификаторы (спека) — base_price в опциях
- ADR-007 — решение: только Франшиза задаёт цены
- Catalog Service Data Model — текущие таблицы с base_price
- Store Service — ТТ и их связи
- Ролевая модель — доступ к каталогу и ценам