Расписание меню (Menu Availability)
Зачем
Бизнес-ланчи, утреннее меню, ночная карта — позиции которые должны быть видны в POS только в определённое окно. В отличие от временных тарифов здесь не меняется цена — позиция либо есть в меню, либо её нет.
Сущность
MenuAvailability:
| Поле | Тип | Описание |
|---|---|---|
id | UUID | |
franchise_id | UUID | Тенант |
name | string ≤100 | «Бизнес-ланч», «Утреннее меню» |
days_of_week | int[] | ISO 1=Пн..7=Вс |
time_from / time_to | TIME | Окно действия; time_from < time_to |
scope | enum | categories / products (без all — закрытие всего меню = закрытие ТТ) |
target_ids | UUID[] | Категории или товары (≥1) |
store_ids | UUID[] | На какие ТТ распространяется (≥1) |
status | enum | active / disabled |
created_at, updated_at | TIMESTAMP |
Логика видимости
Для пары (store, product, now):
- Не упомянут ни в одном
MenuAvailability→ виден (default). - Упомянут хотя бы в одном, и сейчас попадает в окно хотя бы одного → виден.
- Упомянут, но
nowне попадает ни в одно из окон → скрыт.
Один товар может относиться к нескольким окнам — например, бизнес-ланч 12-16 + утренний 8-11. Тогда виден в любое из двух окон.
Где применяется
| Слой | Поведение |
|---|---|
POS меню (/internal/catalog/menu?store_id=X) | Скрытые позиции отфильтровываются — кассир их не видит |
| POS заказ | Невозможно — позиция не выбирается из меню |
| Инвойс PayKeeper | Не попадает в cart → не на чек |
| Каталог в ЛК PK | Не трогается — там полный каталог. Это OK: доступность определяется на уровне POS, а не PK |
Бизнес-правила
- Per-ТТ. Расписание относится к конкретным ТТ.
- Только повторяющиеся окна. Одноразовые исключения вне scope.
- Нет конфликта с временными тарифами. Скидка может действовать на товар, который сейчас скрыт расписанием — товар просто не появится в меню, и тариф к нему никто не применит.
- Пересечение с ручным стоп-листом. Если позиция вручную в стоп-листе — скрыта приоритетнее. Расписание добавляет автоматическое скрытие сверху.
Ролевая матрица
| Роль | List | View | CRUD |
|---|---|---|---|
| Владелец франшизы | ✅ все ТТ | ✅ | ✅ все ТТ |
| Владелец партнёра | ✅ свои ТТ | ✅ свои | ✅ свои ТТ |
| Менеджер ТТ | ❌ | — | — |
| Кассир | — | — | — |
Permissions: menu.read для GET, menu.edit для CRUD (переиспользуем).
UI
Страница Каталог → Расписание меню:
- Сначала пикер ТТ (как в Временные тарифы).
- После выбора ТТ — список расписаний этой ТТ + «Добавить расписание».
- Форма: название, дни недели, окно, scope (Товары / Категории), пикер с поиском и группировкой товаров по категориям.
Точность времени
Кэш активных окон ТТ — 30 сек. POS подгружает меню при загрузке страницы кассы или периодически — задержка реальная ≤30 сек после фактического начала/конца окна.
Что НЕ поддерживается
- One-off (одноразовые) окна.
- Закрытие ТТ целиком (для этого есть отдельные механизмы — расписание работы ТТ, suspended status).
- Минимальное количество и порядок: «бизнес-ланч из 3 позиций» не разрешает сборку только из 2-х. Это бизнес-правило ресторана, не каталог.
Ссылки
- Каталог — основной модуль
- Временные тарифы — параллельный механизм для скидок
- Стоп-листы — ручное скрытие позиций