BR 1.5: Permission-based UI gating
Зависит от:
Референс
YumaPOS: нет
section.read→ пункт меню скрыт. Естьreadбезedit→ страница read-only (кнопки edit/delete скрыты). POS-only сотрудники не имеют доступа к бэк-офису.
Контекст
После BR 1.4.3 и 1.4.4 бэкенд правильно проверяет permissions и возвращает 403 при отсутствии прав. Но фронт показывает все разделы и кнопки всем сотрудникам — sidebar одинаковый, кнопки «Создать/Редактировать/Удалить» видны, даже если пользователь не имеет permission. Менеджер ТТ видит «Юр. лица» и «Роли» хотя доступа нет.
Инфраструктура уже есть (из BR 1.4.3):
usePermission(key)hook<RequirePermission keys={[...]}>компонентPermissionContextс массивомpermissions[]
Но используется только в 2 файлах из ~25.
Требования
1. Sidebar — скрытие разделов по .read permission
Каждый пункт бокового меню привязан к permission. Нет permission → пункт не рендерится:
| Раздел | Permission для видимости | Примечание |
|---|---|---|
| Дашборд | — (всегда видно) | Fallback-страница для всех |
| Юр. лица | legal_entities.read | + скрыто при franchise.type=individual (уже реализовано) |
| Торговые точки | stores.read | |
| Каталог → Товары | menu.read | |
| Каталог → Категории | menu.read | |
| Каталог → Модификаторы | menu.read | |
| Каталог → Прейскуранты | price_list.read | |
| Каталог → Ингредиенты | ingredients.read | |
| Каталог → Стоп-листы | stoplists.read | |
| Склад → Остатки | warehouse.read | |
| Склад → Приёмки | warehouse.read | |
| Склад → Списания | warehouse.read | |
| Сотрудники | employees.read | |
| Роли | roles.read | |
| Расписание | schedule.read | |
| Учёт рабочего времени | time_tracking.read | |
| Зарплата | payroll.read | |
| Заказы | orders.read | |
| Отчёты | reports.read |
Группы (Каталог, Склад): группа видна если хотя бы один подпункт доступен. Если нет menu.read, но есть price_list.read → «Каталог» виден, но внутри только «Прейскуранты».
Franchise owner (scope.type=all_franchise): bypass — видит ВСЁ (уже реализовано в usePermission).
2. Кнопки edit/delete/create — скрытие по .edit permission
На каждой странице кнопки действий скрываются если нет соответствующего .edit:
| Страница | Кнопки | Permission |
|---|---|---|
| ТТ: список | «Добавить» | stores.edit |
| ТТ: карточка | «Редактировать», «Удалить», «Опубликовать/Снять» | stores.edit |
| Каталог: товары | «Добавить товар» | menu.edit |
| Каталог: товар карточка | «Редактировать», «Удалить» | menu.edit |
| Каталог: категории | «Добавить», drag-n-drop | menu.edit |
| Каталог: модификаторы | «Добавить», «Редактировать», «Удалить» | menu.edit |
| Прейскуранты | «Добавить», «Редактировать» | price_list.edit |
| Ингредиенты | «Добавить», «Редактировать» | ingredients.edit |
| Стоп-листы | Toggle on/off | stoplists.edit |
| Склад: приёмки | «Добавить акт» | warehouse.edit |
| Склад: списания | «Добавить акт» | warehouse.edit |
| Сотрудники: список | «Добавить», меню действий | employees.edit |
| Сотрудники: карточка | «Редактировать», «Деактивировать/Реактивировать» | employees.edit |
| Роли: список | «Добавить», «Удалить» | roles.edit |
| Роли: карточка | «Редактировать», «Удалить» | roles.edit |
| Расписание | «Добавить смену» | schedule.edit |
| Зарплата: формулы | «Добавить», «Редактировать» | payroll.edit |
| Зарплата: ведомости | «Рассчитать», «Подтвердить», «Отметить выплаченной» | payroll.edit |
| Заказы | Зависит от flow | orders.edit |
Есть section.read без section.edit → страница открывается в read-only режиме: данные видны, кнопки действий скрыты, формы не доступны.
3. Route-level guard
При вбивании URL напрямую (закладка, ссылка) без нужного permission → страница «У вас нет доступа к этому разделу» с кнопкой «На главную».
Компонент <PermissionRoute permission="stores.read"> оборачивает маршрут. Если нет permission — рендерит 403-страницу.
4. Блокировка входа в админку для POS-only сотрудников
Если у сотрудника нет ни одного backoffice .read permission (только POS-операции типа pos.access, pos.shift.open и т.д.) → логин в админку запрещён.
При попытке залогиниться — ошибка: «У вашей роли нет доступа к бэк-офису. Используйте POS-приложение.»
Проверка:
- Фронт после логина вызывает
/auth/me, получаетpermissions[] - Если ни одного
*.read(кромеpos.*) → показать сообщение и не пускать - Или бэк-end: при login, если нет ни одного backoffice-permission → возвращать специальную ошибку
NO_BACKOFFICE_ACCESS
В Юме: POS-only сотрудники используют POS через PIN. У них нет email/пароля для бэк-офиса (или есть, но система блокирует вход).
5. Франчайзи (владелец партнёра) — sidebar по его permissions
Когда владелец партнёра логинится (scope=legal_entity_ids, permissions из скрытой роли):
- Sidebar отображает только разделы из его permission-набора
- Если franchise-admin дал ему
menu.read + stores.read + employees.read→ видит только Каталог, ТТ, Сотрудники - Если дал
menu.readбезmenu.edit→ Каталог виден, но кнопки add/edit скрыты
6. Обработка 403 от API
При получении 403 FORBIDDEN от бэкенда:
- Toast: «У вас нет прав на это действие» (вместо raw JSON)
- Не редиректить — просто уведомить (пользователь остаётся на странице)
Бизнес-правила
- Нет permission → нет UI-элемента. Пользователь не должен видеть то, к чему нет доступа
- Дашборд — всегда доступен (если пользователь вообще попал в бэк-офис)
- Группа разделов (Каталог, Склад) видна если хотя бы один подпункт доступен
- Franchise owner bypass — видит ВСЁ, никаких проверок
- Read без Edit = read-only страница (данные видны, действия скрыты)
- Backend — source of truth: фронт гейтит UI, но бэк всегда проверяет (defence in depth)
- POS-only сотрудники не имеют доступа к бэк-офису
Затронутые сервисы
| Сервис | Что меняется |
|---|---|
| Admin Web | Layout.tsx (sidebar gating), App.tsx (route guards), ~25 страниц (кнопки), api/client.ts (403 обработка), LoginPage (POS-only block) |
| Admin BFF | Без изменений (проксирование) |
| Auth Service | Опционально: ошибка NO_BACKOFFICE_ACCESS при login если нет backoffice permissions |
| User Service | Без изменений |
Открытые вопросы
- Настройки (
settings.read/edit) — нужен ли отдельный раздел «Настройки» в sidebar? Сейчас нет такой страницы. Оставить на потом? - Дашборд виджеты — показывать ли виджет «Заказы» если нет
orders.read? Или дашборд = всегда полный? - Активность / Терминалы — какие permissions нужны? Пока нет в каталоге