BR 1.4.3: Пользовательские роли с правами (Yuma-стиль)

Зависит от:

  • BR 1.4 — базовая модель сотрудников
  • BR 1.4.1 — формулы зарплаты по ролям (переиспользуются без изменений)
  • BR 1.4.2 — текущий enum admin_franchise / admin_franchisee / manager / cashier

Референс

YumaPOS: _reference/yumapos/roles.md, staff-list.md, how-to-add-new-staff-member.md


Контекст

Сейчас роль сотрудника — жёсткий enum из 4 значений (admin_franchise, admin_franchisee, manager, cashier), зашитый в код каждого сервиса через switch (user.getRole()). Набор прав для manager и cashier единый для всей сети — владелец франшизы и партнёр не могут настроить индивидуальный набор прав для своих сотрудников.

Это создаёт проблемы:

  • Нельзя разрешить кассиру применять скидки, но запретить возвраты
  • Нельзя создать роль «Официант» или «Курьер» с отдельным набором операций
  • Нельзя запретить менеджеру одной ТТ редактировать склад, разрешив той же роли в другой ТТ
  • Добавление новых ролей требует миграции БД и кода во всех сервисах

YumaPOS решает это через роль как сущность: владелец создаёт произвольные роли, выставляет чекбоксы разрешений на разделы Бэк-офиса и операции POS, сотрудник получает одну или несколько ролей с привязкой к конкретным ТТ.

Эта BR переносит Yuma-модель ролей на нашу платформу. Логика franchise/franchisee остаётся как multi-tenancy слой поверх Yuma-модели: permissions определяют что делает пользователь, а franchise_id / store_idsнад какими данными.


Требования

1. Роль как сущность с CRUD

Роль — объект с полями:

ПолеТипОбязательностьОписание
НазваниеstringОбязательноНазвание роли («Кассир», «Официант», «Су-шеф»); уникально в рамках франшизы
ОписаниеstringОпциональноКороткое пояснение для чего роль
Разрешения Бэк-офисасм. раздел 2ОбязательноНабор прав Read/Edit по разделам
Разрешения POSсм. раздел 3ОбязательноНабор прав на операции кассы
Формула зарплатысм. раздел 4ОпциональноКак считается ЗП для сотрудников с этой ролью
СистемнаяbooleanАвтофлагtrue — нельзя удалить/редактировать права
СтатусenumАктивна / Удалена (soft delete)Удалённые попадают в архив с возможностью восстановления

Операции над ролями:

  • Создание роли с нуля
  • Редактирование (название, описание, любые permissions, формула)
  • Удаление (soft delete) — нельзя удалить роль, назначенную хоть одному активному сотруднику
  • Восстановление из архива «Удалённые роли»
  • Просмотр списка ролей с колонкой «Количество работников» (клик ведёт на отфильтрованный список сотрудников)

2. Разрешения: Разделы Бэк-офиса

Для каждого раздела админки — два флажка: Чтение и Редактирование.

Базовый каталог разделов (уточняется при спеке):

  • Меню (товары, категории, модификаторы)
  • Прейскуранты
  • Техкарты и ингредиенты
  • Склад (операции приёма/списания, инвентаризации)
  • Торговые точки
  • Юридические лица
  • Сотрудники и роли
  • Расписание и учёт рабочего времени
  • Зарплата и ведомости
  • Отчёты и аналитика
  • Настройки франшизы

Правило: Редактирование без Чтения не имеет смысла; при выставлении Edit=✅ — Read=✅ автоматически.

3. Разрешения: Функции POS-терминала

Флажки на операции, которые сотрудник может выполнять на кассе. У некоторых операций — лимиты Минимум / Максимум (в рублях или штуках).

Базовый каталог операций (уточняется при спеке):

  • Работа с POS-приложением (базовый доступ)
  • Открытие / закрытие смены
  • Приём заказов
  • Применение скидок (лимит % или ₽)
  • Внесение наличных в кассу (лимит ₽)
  • Изъятие наличных из кассы (лимит ₽)
  • Возврат средств клиенту
  • Аннулирование заказа / позиции
  • Изменение цены в чеке (для товаров с открытой ценой)
  • Управление настройками кассы
  • Инкассация

4. Разрешения: Зарплата

Переиспользуется без изменений из BR 1.4.1:

  • Три типа формулы: hourly (ставка × часы), fixed (оклад за месяц), mixed (оклад + ставка сверх нормы)
  • Иерархия применения: индивидуальная формула сотрудника → формула роли → нет формулы

Изменение по сравнению с 1.4.1: формула привязывается к role_id, а не к enum-значению роли. Логика расчёта остаётся той же.

5. Присвоение ролей сотруднику

Один сотрудник → N ролей одновременно. Для каждой роли сотрудника отдельно задаётся набор магазинов, где эта роль действует.

Пример:

Иван Петров (employee):
  ├── Роль «Менеджер»  → магазины: ТТ-1, ТТ-2
  ├── Роль «Курьер»    → магазины: ТТ-3
  └── Роль «Бариста»   → магазины: ТТ-2

В карточке сотрудника:

  • Вкладка «Роли» со списком назначенных ролей
  • Для каждой роли — поле «Магазины» (мультивыбор)
  • Кнопки «Добавить роль» / «Удалить роль»

6. Системная роль «Администратор»

  • Создаётся автоматически при seed франшизы
  • Неудаляемая, права не редактируются
  • Полный доступ ко всем разделам Бэк-офиса и всем операциям POS
  • Все магазины франшизы — автоматически
  • Не путать с текущим enum-значением admin_franchise — судьба этого слоя мульти-тенантности (franchise/franchisee) решается отдельной BR

7. Soft delete и архив ролей

  • Удаление роли = установка флага «Удалена», скрытие из основного списка
  • Страница «Удалённые роли» — архив с возможностью восстановления
  • Нельзя удалить роль, назначенную хоть одному активному сотруднику: UI показывает ошибку «Снимите эту роль со всех сотрудников перед удалением»
  • При восстановлении роль возвращается со всеми правами и формулой зарплаты (переживает период нахождения в архиве)

8. Список ролей

Страница «Сотрудники → Роли»:

  • Колонки: Название, Количество работников, Системная (флаг), Дата создания
  • Клик по числу работников → фильтрованный список сотрудников с этой ролью
  • Фильтры: системные / пользовательские, активные / удалённые
  • Кнопки: Добавить, Удалить, Удалённые роли

9. Миграция существующих сотрудников

При выкатке новой модели на тестовую среду:

  • Удалить всех сотрудников с ролями manager, cashier, admin_franchisee
  • Сохранить только сотрудников с ролью admin_franchise — они получают системную роль «Администратор»
  • Для удаляемых admin_franchisee — их ЮЛ либо удаляются каскадно, либо legal_entities.owner_user_idNULL (см. открытый вопрос)

Тестовая среда — реальных данных нет, ручная миграция приемлема. Production-миграции в этой BR не предусматривается (ERP ещё не в проде).


Бизнес-правила

  • Один сотрудник — N ролей, каждая со своим scope по магазинам
  • Уникальность названия роли — в рамках одной франшизы
  • Системные роли (флаг is_system=true) — нельзя удалить, нельзя изменить права, название фиксированное
  • Каскад при удалении роли — запрещён, пока есть активные сотрудники с этой ролью
  • Permissions НЕ кладутся в JWT — JWT несёт только user_id, franchise_id, store_ids, role_ids. Полный список permissions — отдельный источник:
    • Фронт получает через GET /auth/me (один раз при логине, опционально re-fetch при изменении)
    • Сервисы получают через POST /internal/auth/validate (existing endpoint расширяется полем permissions[])
  • Мгновенное применение изменений прав — при редактировании permissions роли новые права действуют со следующего запроса, без форс-релогина активных JWT
  • Соответствие Edit/Read — включение Edit автоматически включает Read (нельзя редактировать то, что не видишь)

Ролевой доступ к модулю ролей

Действиеadmin_franchiseadmin_franchiseemanagercashier
Просмотр списка ролей✅ все роли франшизы✅ только роли, назначенные сотрудникам в своих ТТ
Создание роли
Редактирование роли
Удаление роли
Назначение роли сотруднику✅ только своим сотрудникам

(В этой таблице используются текущие enum-роли как обозначения слоя мульти-тенантности, в соответствии с решением в BR 1.4.2).


Затронутые сервисы

СервисЧто меняется
User ServiceНовые таблицы: roles, role_permissions, employee_roles, employee_role_stores. CRUD-эндпоинты для ролей. Замена employees.role (enum) на связь many-to-many через employee_roles. Миграция: удаление всех manager/cashier/admin_franchisee
Auth Service/internal/auth/validate дополняется списком permissions[] (агрегат из всех ролей пользователя). /auth/me возвращает roles[] + permissions[] для фронта. JWT payload меняется: rolerole_ids[]
Store / Catalog / Warehouse / Order ServicesПроверки прав переводятся с switch(user.getRole()) на permissions.contains("stores.edit") и подобные. В скоупе отдельной BR/миграции, не в этой
Admin BFFНовый роут /api/v1/admin/roles/* (проксирование к User Service)
Admin Franchise (web)Новый раздел «Сотрудники → Роли»: список, форма создания/редактирования с тремя вкладками (Разделы Бэк-офиса, Функции POS, Зарплата). Во вкладке «Роли» карточки сотрудника — мультивыбор ролей + магазинов

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

  1. Каталог permission-ключей — составить точный список ключей для Back-office разделов (menu.read, menu.write, payroll.read, …) и POS-операций (pos.login, pos.discount.apply, pos.cash.withdraw, …). Взять 1:1 из Yuma или составить свой?
  2. Судьба enum-ролей admin_franchise / admin_franchisee — отдельная BR. Они остаются как multi-tenancy layer, но механика их создания (сейчас автосоздание при регистрации ЮЛ) требует пересмотра в контексте новой ролевой модели.
  3. ЮЛ при удалении admin_franchisee-владельцев — каскадное удаление ЮЛ или owner_user_id → NULL?
  4. ТТ-зависимые формулы зарплаты — BR 1.4.1 говорит: «формулы по ролям настраиваются по ТТ — ставки могут отличаться по точкам». В новой модели это сохраняется как role × store → formula или упрощается до role → formula (одна на роль, без per-store)?
  5. Лимиты min/max на POS-операциях — нужны ли они сразу, или начать без лимитов (только флажки) и добавить в отдельной итерации?
  6. Системная роль «Администратор» — должна ли быть одна на франшизу (как в Yuma) или отдельная на Франшизу и на каждого Франчайзи?

Ссылки