UI-гейтинг по permissions
(Введено в BR 1.5)
Принцип
Нет
section.read→ раздел скрыт. Естьreadбезedit→ read-only страница (данные видны, кнопки действий скрыты). Backend — source of truth; фронт гейтит UI, бэк всегда проверяет (defence in depth).
1. Sidebar гейтинг
Каждый пункт бокового меню привязан к 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 | |
| — Зарплата (ведомости) | payroll.read | |
| Заказы (группа) | ||
| — Активные | orders.read | |
| — История | orders.read | |
| Клиенты (группа) | Видна если хотя бы один подпункт доступен (BR 3.1) | |
| — Клиенты | customers.read | Пункт меню «Клиенты» |
| — Группы клиентов | customer_groups.read | Пункт меню «Группы клиентов» (только владелец франшизы — у партнёра customer_groups.read обычно не выдаётся) |
| Отчёты | reports.read |
Правила
- Дашборд всегда видно — fallback при отсутствии прав на другие разделы
- Группа (Каталог, Склад, Сотрудники, Заказы) видна если >= 1 подпункт виден. Пример: нет
menu.read, но естьprice_list.read→ «Каталог» виден, внутри только «Прейскуранты» - Franchise owner (
scope.type = all_franchise): bypass — видит ВСЁ, проверки пропускаются (usePermissionhook уже реализует это)
2. Кнопки на страницах
На каждой странице кнопки действий скрываются если нет соответствующего .edit permission.
Read без Edit = read-only
Если есть
section.readбезsection.edit→ страница открывается, данные видны, но все кнопки создания/редактирования/удаления скрыты, формы заблокированы.
Полная таблица
| Страница | Кнопки | Permission |
|---|---|---|
| ТТ: список | «Добавить» | stores.edit |
| ТТ: карточка | «Редактировать», «Удалить», «Опубликовать/Снять» | stores.edit |
| Каталог: товары список | «Добавить товар» | menu.edit |
| Каталог: товар карточка | «Редактировать», «Удалить» | menu.edit |
| Каталог: категории | «Добавить», drag-n-drop | menu.edit |
| Каталог: модификаторы список | «Добавить» | menu.edit |
| Каталог: модификатор карточка | «Редактировать», «Удалить» | menu.edit |
| Прейскуранты: список | «Добавить» | price_list.edit |
| Прейскуранты: карточка | «Редактировать» | price_list.edit |
| Ингредиенты: список | «Добавить» | ingredients.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 |
| ЮЛ: список | «Добавить» | legal_entities.edit |
| ЮЛ: карточка | «Редактировать», «Удалить» | legal_entities.edit |
3. Route guard
Компонент <PermissionRoute permission="section.read"> оборачивает маршрут. Если у пользователя нет нужного permission — рендерит страницу «Нет доступа» вместо children.
Поведение
- Пользователь переходит по URL (закладка, прямая ссылка)
<PermissionRoute>проверяетusePermission(permission)- Если permission есть → рендерит children (страницу)
- Если permission нет → рендерит
<NoAccessPage />
ASCII-макет страницы «Нет доступа» (403)
┌──────────────────────────────────────────┐
│ [Sidebar] │ │
│ │ │
│ Dashboard │ ┌────────────────────┐ │
│ ... │ │ 🔒 │ │
│ │ │ │ │
│ │ │ У вас нет │ │
│ │ │ доступа к этому │ │
│ │ │ разделу │ │
│ │ │ │ │
│ │ │ [ На главную ] │ │
│ │ └────────────────────┘ │
│ │ │
└──────────────────────────────────────────┘
- Иконка замка
- Текст: «У вас нет доступа к этому разделу»
- Кнопка «На главную» → переход на
/(дашборд)
4. POS-only блокировка
Если у сотрудника нет ни одного backoffice *.read permission (только POS-операции: pos.access, pos.shift.open и т.д.) — вход в бэк-офис запрещён.
Сценарий
- Сотрудник вводит email + пароль на
/login - Фронт отправляет
POST /auth/login - Вариант A (бэкенд): Auth Service проверяет permissions при логине. Если нет ни одного backoffice
*.read→ возвращает ошибкуNO_BACKOFFICE_ACCESS(403). Фронт показывает сообщение. - Вариант B (фронтенд): Login успешен → фронт вызывает
GET /auth/me→ получаетpermissions[]→ если нет ни одного backoffice*.read(кромеpos.*) → показывает модалку и не пускает дальше.
Реализовать оба варианта
Бэкенд возвращает
NO_BACKOFFICE_ACCESS(defence in depth). Фронт дополнительно проверяет после/auth/me(для случаев когда бэкенд ещё не обновлён или permissions изменились после логина).
Модалка POS-only
┌─────────────────────────────────────┐
│ │
│ У вашей роли нет доступа │
│ к бэк-офису. │
│ │
│ Используйте POS-приложение. │
│ │
│ [ Выйти ] │
│ │
└─────────────────────────────────────┘
- Модалка без возможности закрыть (нет крестика)
- Единственное действие — кнопка «Выйти» → logout + redirect на
/login
5. Обработка 403 от API
При получении HTTP 403 FORBIDDEN от любого бэкенд-запроса:
api/client.tsперехватывает 403 в response interceptor- Показывает toast: «У вас нет прав на это действие»
- Не редиректит — пользователь остаётся на текущей странице
- Toast исчезает через 5 секунд (стандартное поведение)
Не путать с 401
401 (UNAUTHORIZED) → redirect на
/login(токен истёк). 403 (FORBIDDEN) → toast (прав нет, но сессия валидна).
6. Состояния
| Состояние | Описание | Что видит пользователь |
|---|---|---|
| Loading permissions | /auth/me ещё не вернул ответ после логина | Sidebar в skeleton-режиме (серые плашки вместо пунктов меню). Контент-область — спиннер |
| POS-only block | Нет ни одного backoffice *.read | Модалка «У вашей роли нет доступа к бэк-офису» с кнопкой «Выйти» |
| 403 page | Прямой переход по URL без permission | Страница «У вас нет доступа к этому разделу» + кнопка «На главную» |
| 403 toast | Бэкенд вернул 403 на действие | Toast «У вас нет прав на это действие» (5 сек) |
| Read-only mode | Есть .read без .edit | Страница отображает данные, но кнопки create/edit/delete скрыты |