Admin Franchise — BR 1.4.3

Репо: erp-admin (C:\Users\A211\Desktop\erp-admin)

Зависимости

User Service + Auth Service должны быть готовы и задеплоены (для /internal/users/* и /auth/me).


BFF (bff/src/)

Новые proxy-роуты

  • Создать routes/roles.ts — proxy к User Service /api/v1/roles:
    • GET /api/v1/admin/rolesGET /api/v1/roles (c query params status, is_system, search, page, per_page)
    • GET /api/v1/admin/roles/permission-catalogGET /api/v1/roles/permission-catalog
    • GET /api/v1/admin/roles/:idGET /api/v1/roles/:id
    • POST /api/v1/admin/rolesPOST /api/v1/roles
    • PATCH /api/v1/admin/roles/:idPATCH /api/v1/roles/:id
    • DELETE /api/v1/admin/roles/:idDELETE /api/v1/roles/:id
    • POST /api/v1/admin/roles/:id/restorePOST /api/v1/roles/:id/restore
  • Подключить роутер в server.ts
  • requireAuth middleware на всех роутах (уже существует)

Новый роут GET /api/v1/admin/auth/me

  • Создать (или расширить существующий routes/auth.ts) — proxy к Auth Service GET /auth/me
  • Пробросить Authorization header как есть

Изменения в существующих роутах

  • routes/employees.ts — никаких серверных изменений. Payload roles[] в POST/PATCH пробрасывается прозрачно. Убедиться что БФФ не фильтрует это поле.

Web (web/src/)

Навигация и доступ

  • В боковом меню добавить пункт «Роли» (иконка shield-check или key) под «Сотрудниками»
  • Видимость: только для admin_franchise (проверка по multi-tenancy role из JWT)
  • Для admin_franchisee — пункт скрыт (они используют существующие роли через карточку сотрудника)

PermissionContext

  • Создать contexts/PermissionContext.tsx:
    • PermissionProvider — при старте приложения (после AuthContext) вызывает GET /api/v1/admin/auth/me, хранит permissions: string[]
    • Hook usePermission(key: string): boolean
    • Hook useHasAllPermissions(keys: string[]): boolean
  • <RequirePermission keys={['menu.edit']}>{children}</RequirePermission> — компонент-gate; если нет права — рендерит fallback (null / 403 / disabled)
  • Также использовать для скрытия пунктов меню

API-клиент

  • api/roles.ts:
    • listRoles(params): Promise<{ data: RoleListItem[], meta }>
    • getRole(id): Promise<Role>
    • createRole(payload): Promise<Role>
    • updateRole(id, patch): Promise<Role>
    • deleteRole(id): Promise<void> (через DELETE)
    • restoreRole(id): Promise<Role>
    • getPermissionCatalog(): Promise<PermissionCatalog>
  • api/auth.ts — добавить me(): Promise<MeResponse>

Страницы Роли

  • /rolesRoleListPage
    • Таблица (TanStack Table или существующий helper), пагинация, фильтры (Все/Системные/Пользовательские radio + поиск с debounce 300 мс)
    • Query param archive=1 — отображать удалённые (status=deleted)
    • Переключатель «Удалённые роли» в шапке
    • Кнопка «Добавить роль» → /roles/new
    • Меню «…» на строке: Редактировать, Удалить
    • Empty state с CTA «Добавить роль»
  • /roles/newRoleCreatePage
    • 4 вкладки в компоненте <RoleForm mode="create" />
  • /roles/:idRoleViewPage (readonly)
    • Те же 4 вкладки, все поля disabled, кнопки Edit / Delete / Back
  • /roles/:id/editRoleEditPage
    • <RoleForm mode="edit" role={data} />

Компоненты

  • components/roles/RoleForm.tsx — табы + state + submit (создание/редактирование)
  • components/roles/BackofficePermissionsTab.tsx — таблица 16 разделов × {Read, Edit}. Логика Edit-implies-Read (checking Edit автоматически отмечает Read)
  • components/roles/PosPermissionsTab.tsx — 14 чекбоксов
  • components/roles/SalaryFormulaBlock.tsx — блок формулы (переиспользовать существующий компонент из EmployeeForm, выделить в общий если ещё нет)
  • components/roles/DeleteRoleModal.tsx — простое подтверждение
  • components/roles/RoleInUseModal.tsx — модалка со списком сотрудников + CTA «Открыть список сотрудников»

Страницы Сотрудники (обновления)

  • EmployeeListPage (/employees):
    • Добавить колонку «Permissions-роли» — чипы названий (до 3 + «+N»)
    • Добавить фильтр «Permissions-роль» (dropdown из активных ролей, query param role_id)
  • EmployeeViewPage / EmployeeEditPage — вкладка «Роли и магазины»:
    • Секция «Multi-tenancy роль» (existing, readonly бейдж)
    • Новая секция «Permissions-роли» со списком назначенных ролей
    • Каждая карточка роли: название (ссылка /roles/:id), чипы магазинов, кнопки «Изменить магазины» / «Удалить»
    • Кнопка «Добавить роль» открывает AssignRoleModal
    • AssignRoleModal — Select активной роли + мульти-селект магазинов (из множества ТТ сотрудника)
  • EmployeeCreatePage:
    • Поле «Роль» переименовать в «Multi-tenancy роль (scope)» (оставить dropdown manager/cashier)
    • Новая секция «Permissions-роли» (опциональная) с repeater’ом: + Добавить роль → блок с Select роли + Multi-select магазинов

Роутинг и заголовки

  • В AppRouter добавить /roles и /roles/:id, /roles/:id/edit, /roles/new
  • Breadcrumbs для страниц ролей

Тестирование (manual + automated)

  • Smoke-test: создание роли с разными комбинациями permissions
  • Smoke-test: назначение 2+ ролей сотруднику с разными store-scope
  • Edit implies Read — визуальный тест
  • Удаление роли с активными сотрудниками → ROLE_IN_USE модалка
  • Системная роль «Администратор» — все вкладки disabled кроме Общее
  • Фильтры на списке ролей и сотрудников

Выходные критерии

  • BFF собирается и запускается (проксирование работает)
  • Web: /roles загружается, список ролей отображается
  • Создание роли → запись в БД → появление в списке
  • Назначение роли сотруднику → отображение в его карточке
  • /auth/me загружает permissions при старте app
  • UI-гейтинг через <RequirePermission> работает для minimum 3 разделов (Каталог, ТТ, Сотрудники)