BR 1.5: Permission-based UI gating

Зависит от:

  • BR 1.4.3 — permission-модель (roles + permission keys)
  • BR 1.4.4 — scope-based авторизация, franchise.type

Референс

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-dropmenu.edit
Каталог: модификаторы«Добавить», «Редактировать», «Удалить»menu.edit
Прейскуранты«Добавить», «Редактировать»price_list.edit
Ингредиенты«Добавить», «Редактировать»ingredients.edit
Стоп-листыToggle on/offstoplists.edit
Склад: приёмки«Добавить акт»warehouse.edit
Склад: списания«Добавить акт»warehouse.edit
Сотрудники: список«Добавить», меню действийemployees.edit
Сотрудники: карточка«Редактировать», «Деактивировать/Реактивировать»employees.edit
Роли: список«Добавить», «Удалить»roles.edit
Роли: карточка«Редактировать», «Удалить»roles.edit
Расписание«Добавить смену»schedule.edit
Зарплата: формулы«Добавить», «Редактировать»payroll.edit
Зарплата: ведомости«Рассчитать», «Подтвердить», «Отметить выплаченной»payroll.edit
ЗаказыЗависит от floworders.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 WebLayout.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Без изменений

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

  1. Настройки (settings.read/edit) — нужен ли отдельный раздел «Настройки» в sidebar? Сейчас нет такой страницы. Оставить на потом?
  2. Дашборд виджеты — показывать ли виджет «Заказы» если нет orders.read? Или дашборд = всегда полный?
  3. Активность / Терминалы — какие permissions нужны? Пока нет в каталоге

Ссылки