1.4.2 Пересмотр ролевой модели сотрудников

Расширение BR 1.4

BR 1.4 реализовала 4 статичные роли. Эта BR решает проблему смешения структурных ролей владельцев (franchise, franchisee) с операционными ролями сотрудников (manager, cashier).

Кастомные роли — отдельная BR

Полноценный конструктор ролей с permission-based доступом (аналог Юмы) — отдельная большая задача, не в этой BR. Здесь — только разделение системных/обычных ролей и авто-создание владельца при создании ЮЛ.


Проблема

Текущая модель (BR 1.4) имеет архитектурные недочёты:

  1. Franchise может создать другого Franchise через “Сотрудники” — бессмысленное клонирование владельца бренда, потенциально опасно
  2. Franchise может создать Franchisee через “Сотрудники” — но Franchisee это структурная сущность (владелец ЮЛ-партнёра с договором), а не просто сотрудник
  3. Осиротевшие связи — можно создать ЮЛ Франчайзи без учётки владельца (партнёр не сможет войти), либо наоборот — несколько franchisee-учёток для одного ЮЛ (непонятно кто главный)
  4. Смешение уровней ответственности — в форме “Добавить сотрудника” в одном dropdown структурные роли (владелец бренда, владелец партнёра) рядом с операционными (менеджер точки, кассир)

Решение

Разделить:

  • Системные роли (admin_franchise, admin_franchisee) — владельцы уровня бизнеса, создаются автоматически
  • Операционные роли (manager, cashier) — обычные сотрудники, создаются через форму “Сотрудники”

Автоматическое создание владельца Франчайзи при создании ЮЛ Франчайзи — одной транзакцией, чтобы не было осиротевших сущностей.


1. Роли после переименования

РольСтатусКак создаётсяЧто видит
admin_franchiseСистемнаяПри seed / регистрации брендаВсё во всей франшизе
admin_franchiseeСистемная, автосозданиеПри создании ЮЛ Франчайзи (одной транзакцией с ЮЛ)Свой ЮЛ + свои ТТ + своих сотрудников
managerОбычнаяAdmin Franchise или Admin Franchisee через форму “Сотрудники”Свою ТТ (read-only)
cashierОбычнаяAdmin Franchise или Admin FranchiseeТолько POS

Переименование:

  • franchiseadmin_franchise
  • franchiseeadmin_franchisee
  • manager, cashier — без изменений

Русские лейблы в UI:

  • admin_franchise → “Владелец франшизы”
  • admin_franchisee → “Владелец партнёра” (или “Франчайзи”)
  • manager → “Менеджер”
  • cashier → “Кассир”

2. Изменения в форме создания сотрудника

Было

В /employees/new в dropdown “Роль”:

  • Франшиза (для Franchise)
  • Франчайзи (для Franchise)
  • Менеджер
  • Кассир

Стало

В dropdown “Роль” только:

  • Менеджер
  • Кассир

Роли admin_franchise и admin_franchisee не доступны для создания через эту форму — ни для Admin Franchise, ни для Admin Franchisee.

Почему

  • admin_franchise — создаётся только при регистрации бренда (seed), не через UI
  • admin_franchisee — создаётся только автоматически при создании ЮЛ Франчайзи

3. Создание ЮЛ Франчайзи — новая логика

Форма создания ЮЛ Франчайзи

Текущая форма /legal-entities/new (type=franchisee) дополняется блоком “Владелец”:

ПолеТипОбязательностьОписание
Имя владельцаtextОбязательно
Фамилия владельцаtextОбязательно
Email (логин)emailОбязательноУникален в рамках franchise_id
ТелефонtextОпционально
ПарольpasswordОпциональноЕсли не задан — генерируется временный

Транзакционная логика

При POST /legal-entities (type=franchisee), backend в одной транзакции:

  1. Создаёт запись в legal_entities:

    type = 'franchisee'
    name, inn, ogrn, ... — данные ЮЛ
    owner_user_id = NULL  (пока)
    
  2. Создаёт запись в employees:

    role = 'admin_franchisee'
    first_name, last_name, email, password_hash — из блока "Владелец"
    franchise_id = текущая франшиза
    status = 'active'
    
  3. Обновляет legal_entities.owner_user_id → ID только что созданного employee

  4. В ответ API возвращает:

    {
      "data": {
        "legal_entity": { ...все поля ЮЛ... },
        "owner": {
          "id": "uuid",
          "email": "partner@example.com",
          "temporary_password": "Xy9k2P4m"
        }
      }
    }

UX

После сохранения UI показывает модалку “Партнёр создан”:

  • Email: partner@example.com
  • Временный пароль: Xy9k2P4m (крупным шрифтом + кнопка “Скопировать”)
  • Инструкция: “Передайте эти данные партнёру. Пароль можно изменить после первого входа.”

4. Миграция существующих данных

БД

Migration Liquibase:

UPDATE employees SET role = 'admin_franchise' WHERE role = 'franchise';
UPDATE employees SET role = 'admin_franchisee' WHERE role = 'franchisee';

JWT

После деплоя у всех пользователей старые токены с role = 'franchise' перестанут валидироваться → вынужденный relogin.

Опционально: добавить транзитный период — Auth Service принимает оба формата, при перевыдаче токена ставит новый. Упрощает деплой.


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

СервисЧто менять
User ServiceМиграция role в БД (UPDATE + новый CHECK constraint). Валидация в CreateEmployeeRequest.role — отвергать admin_* роли. Новый internal endpoint POST /internal/employees/create-owner либо использование существующего с особым флагом.
Auth ServiceJWT содержит новые значения role. Валидация принимает admin_franchise/admin_franchisee.
Store ServiceОбновить все места где role.equals("franchise") / "franchisee"
Catalog ServiceТо же
Warehouse ServiceТо же
Order ServiceТо же
User Service (LegalEntity flow)При POST /legal-entities (type=franchisee) — создание owner-employee в той же транзакции
Admin BFFПроксирование обновлённого эндпоинта создания ЮЛ Франчайзи (тот же путь, но новый body + response)
Admin WebФорма создания ЮЛ Франчайзи (добавить блок “Владелец”), модалка “Партнёр создан”, форма сотрудника (убрать admin_* роли из dropdown), русские лейблы ролей
Shared typesEmployeeRole = "admin_franchise" | "admin_franchisee" | "manager" | "cashier"

6. Ролевая матрица (структура та же, имена новые)

Действиеadmin_franchiseadmin_franchiseemanagercashier
Просмотр всех ЮЛВсеСвой
Создание ЮЛ Франчайзи (+ авто-владелец)Да
Создание сотрудников (manager/cashier)Все ТТСвои ТТ
Просмотр каталогаДа (редактирование)Да (read)Да (read)Да (в POS)
Работа на POSДа

Новые правила:

  • Никто не может создать admin_franchise / admin_franchisee через форму “Сотрудники”
  • admin_franchise создаёт admin_franchisee только неявно через форму создания ЮЛ Франчайзи
  • admin_franchisee автоматически привязан к своему ЮЛ через legal_entities.owner_user_id

7. Валидация на backend

CreateEmployeeRequest

  • Поле role — разрешены только manager и cashier
  • При попытке создать admin_franchise или admin_franchisee400 VALIDATION_ERROR с details: [{ field: "role", message: "Admin roles cannot be created directly" }]

CreateLegalEntityRequest (type=franchisee)

  • Блок “Владелец” становится обязательным для type=franchisee:
    • owner.first_name — обязательно
    • owner.last_name — обязательно
    • owner.email — обязательно, валидный email, уникален в franchise_id
    • owner.phone — опционально
    • owner.password — опционально (если null — генерируется)

Soft delete

  • При soft-delete ЮЛ Франчайзи — владелец (admin_franchisee) деактивируется (status=inactive)
  • При восстановлении ЮЛ — владелец реактивируется

8. Что НЕ входит в эту BR

ФичаПочемуКогда
Кастомные роли (создание своих ролей с именами)Требует отдельной таблицы roles, переделки авторизацииОтдельная BR
Permission-based доступТаблица permissions, M2M role_permissions, переписывание всей проверки доступа во всех сервисахОтдельная BR (связана с кастомными ролями)
Multi-role сотрудникаСейчас 1 роль, нужно M2M employee_roles с привязкой к магазинамОтдельная BR
Смена владельца ЮЛ Франчайзи”Передать владение” — кнопка в карточке ЮЛОтложено, в MVP — руками в БД
Сброс пароля владельцу через emailНет Auth Service с email-confirmationsОтложено до Auth Service с email
Аудит действий (кто когда что создал)Отдельный модуль логированияБудущее

9. Риски и ограничения

Миграция

  • Breaking change: старые JWT с role = 'franchise' перестанут работать → все пользователи должны перелогиниться
  • Координация релизов: бэкенд + фронт + все сервисы должны обновиться синхронно (один деплой)
  • Опция: транзитный период (Auth принимает оба формата) — упростит деплой, усложнит код

UX

  • Модалка “Партнёр создан” показывает пароль в открытом виде — нужно учесть что пользователь может не скопировать
  • Если email не уникален — показать понятную ошибку до submit

Legacy данные

  • Пустые owner_user_id у существующих ЮЛ Франчайзи — оставить как есть (old records). Новые ЮЛ — всегда с owner.

10. Принятые решения (закрытые вопросы)

#ВопросРешение
1Наименования ролейadmin_franchise / admin_franchisee (явно видно что системные)
2Смена владельца ФранчайзиОтложено в MVP, в будущем — кнопка “Передать владение” в карточке ЮЛ
3Кастомные роли + permissionsОтдельная будущая BR, не в этой

11. Связи с другими BR

  • Расширяет: BR 1.4 (базовая модель сотрудников)
  • Влияет на: BR 1.1 (форма создания ЮЛ Франчайзи)
  • Параллельно: BR 1.4.1 (расписание/зарплата — роли не меняет, но использует их в ролевой матрице)

Соответствие Юме

Юма использует полностью пользовательские роли + одна системная “Администратор”. У нас — гибрид: системные роли для структуры бизнеса (владельцы) + фиксированные операционные (manager, cashier). Кастомные роли добавим отдельно.

Наша модель не противоречит Юме и не мешает будущему переходу к полностью пользовательским ролям — просто admin_* остаются системными, а manager/cashier превращаются в дефолтные шаблоны пользовательских ролей.