Admin Franchise — BR 1.4.4
Репо: erp-admin
Зависимости
Бэкенд (User, Auth) должен быть собран и задеплоен под новые контракты BR 1.4.4.
BFF (bff/src/)
Новые proxy routes
- В
routes/legal-entities.tsдобавить:GET /api/v1/admin/legal-entities/:id/owner-permissions→ User ServicePUT /api/v1/admin/legal-entities/:id/owner-permissions→ User Service
- Новый
routes/franchises.ts:GET /api/v1/admin/franchises/:id→ User Service/api/v1/franchises/{id}
- Регистрация в
server.ts
Обновление existing
-
routes/auth.ts—GET /auth/meresponse прозрачно проксируется (структура меняется у Auth Service, BFF не правим логику) -
routes/employees.ts— payload create/update: убрать полеroleиз shape (если оно где-то жёстко валидируется в BFF-обёртке)
Web (web/src/)
Навигация и gating
-
Layout.tsx/Sidebar.tsx— пункт «Юр. лица» скрывается приfranchise.type === 'individual' -
App.tsx/ роутер —/admin/legal-entities/*редиректит на/admin/dashboardприindividual, либо показывает 404-страницу
PermissionContext расширение
-
contexts/PermissionContext.tsx:- State: добавить
franchise: { id, type }иscope: { type, legal_entity_ids?, store_ids? }— из/auth/meresponse - Hooks:
useFranchiseType(): 'corporate' | 'individual',useScope(): Scope,useIsOwner(): boolean
- State: добавить
- Новый компонент
<HideForIndividual>{children}</HideForIndividual>— гейтинг по типу франшизы
API client
-
api/ownerPermissions.ts:getOwnerPermissions(legalEntityId)updateOwnerPermissions(legalEntityId, payload)
-
api/franchises.ts:getFranchise(id)— если где-то нужен отдельный вызов помимо/auth/me
Страницы сотрудников
-
pages/employees/CreatePage.tsx:- Убрать Select «Multi-tenancy роль» (Менеджер/Кассир)
- Убрать Select «Торговая точка» (одиночный)
- Оставить секцию «Роли» (repeater из BR 1.4.3 — уже реализована)
- Payload: убрать
role,store_ids(верхний уровень); оставить толькоroles[]
-
pages/employees/EditPage.tsx— аналогично CreatePage -
pages/employees/ListPage.tsx:- Убрать колонку «Multi-tenancy роль»
- Убрать фильтр «Multi-tenancy роль»
- Колонка «Торговые точки» — рассчитывается как уникальный набор из
roles[].store_ids - В колонке ФИО: добавить бейдж «Владелец франшизы» / «Владелец партнёра» при соответствующем scope (получать scope для каждого сотрудника не будем — только для текущего user из /auth/me; для остальных — бэк вернёт флаг
is_ownerили рассчитаем черезlegal_entities.owner_user_id. Возможно потребует доработки API)
-
pages/employees/ViewPage.tsx— убрать отображение enum-роли, оставить блок permissions-ролей
Карточка ЮЛ партнёра
-
pages/legal-entities/ViewPage.tsx:- Переделать на табы:
Реквизиты | Владелец | Права | ТТ(вкладка «Права» только дляtype=franchisee) - Компонент
tabs/OwnerPermissionsTab.tsx— новый:- Radio «Полный доступ» / «Настроенные права»
- При «Настроенные» — матрица permissions (переиспользовать
BackofficePermissionsTab+PosPermissionsTabиз Roles) - Минимум (
pos.access,stores.read,employees.read) — checkbox’ы disabled - Кнопка «Сохранить» →
updateOwnerPermissions(le_id, payload)
- Переделать на табы:
Форма создания ЮЛ партнёра
-
pages/legal-entities/CreatePage.tsx:- Добавить секцию «Права владельца» (reuse компонент вкладки Права)
- Payload расширить
owner_permissions: { mode, permissions? }
Shared types (shared/src/types/)
-
employee.ts:- Убрать
role: EmployeeRoleиз Employee, из CreateEmployeeRequest, из UpdateEmployeeRequest - Убрать
store_idsкак отдельное поле (осталось толькоroles[].store_ids) - Убрать тип
EmployeeRoleсовсем (если не используется в старом коде)
- Убрать
-
role.ts:- Добавить
ownerLegalEntityId?: stringв Role - Pagination response для
/rolesуже фильтрует скрытые — без правок клиента
- Добавить
-
auth.ts(или равнозначный):- MeResponse: убрать
role,store_ids,legal_entity_id - Добавить
franchise: { id, type },scope: { type, legal_entity_ids?, store_ids? }
- MeResponse: убрать
- Новый
franchise.ts:Franchise { id, name, type: 'corporate'|'individual', created_at }FranchiseType = 'corporate' | 'individual'Scope { type: 'all_franchise'|'legal_entity_ids'|'store_ids', legal_entity_ids?, store_ids? }
-
legal-entity.ts:OwnerPermissions { mode: 'full'|'custom', permissions: string[] }CreateLegalEntityRequest— добавитьowner_permissions?: OwnerPermissions
Списки страниц — фильтры по ТТ
В ListPage сотрудников / складских операций / заказов — набор доступных ТТ для фильтра должен браться из:
scope.type=all_franchise→ все ТТ франшизы (GET /stores)scope.type=legal_entity_ids→ ТТ из этих ЮЛscope.type=store_ids→ только эти ТТ
Пока серверная фильтрация — можно не менять UI: API сам отдаёт только доступные данные. Но в dropdown ТТ — фильтр по scope имеет смысл.
Тестирование (manual)
- Login под admin@erp.local — видит «Юр. лица» (type=corporate)
- (Симуляция) — если
franchise.type=individual— раздел «Юр. лица» скрыт, URL/legal-entitiesредиректит - Создать партнёра «Петров» с режимом «Полный доступ» → у Петрова системная «Администратор»
- Создать партнёра «Сидоров» с режимом «Настроенные права» с ограничением (например, без
stores.edit) → у Сидорова скрытая роль - В карточке ЮЛ Петрова → вкладка «Права» → переключить режим → работает
- В
/admin/roles— ни Петрова, ни Сидорова персональных ролей не видно (только системная и обычные) - Форма создания сотрудника — только единый блок «Роли», никаких Менеджер/Кассир
Выходные критерии
- TypeScript компиляция: 0 ошибок (web + shared)
- BFF собирается (известные pre-existing ошибки не считаются)
- Smoke-test full login → view legal entities → view owner permissions
- Коммит + push