User Service — BR 1.4.3
Репо: erp-user-service (C:\Users\A211\Desktop\erp-user-service)
Контракт: API + Data Model
Фаза 1 — Миграции (Flyway SQL)
- Миграция: создать
roles(id, franchise_id, name, description, is_system, deleted_at, created_at, updated_at) + UNIQUE(franchise_id, LOWER(name)) WHERE deleted_at IS NULL + indexes - Миграция: создать
role_permissions(role_id FK CASCADE, permission_key varchar(100), granted bool) + PK (role_id, permission_key) - Миграция: создать
employee_roles(employee_id FK CASCADE, role_id FK RESTRICT, created_at) + PK + indexes - Миграция: создать
employee_role_stores(employee_id, role_id, store_id) + PK + composite FK → employee_roles CASCADE + index (store_id) - Миграция:
salary_formulas— добавитьrole_id UUID NULL FK roles(id), пересоздать уникальные индексы (drop olduq_salary_formulas_role, create(role_id) UNIQUE WHERE employee_id IS NULL). Колонкаrole(varchar) — удалить (все записи будут пустые после миграции данных ниже) - Миграция: индексы для запросов —
idx_employee_roles_role,idx_employee_roles_employee,idx_employee_role_stores_store
Фаза 2 — Entity + Repository + DTO
- JPA Entity:
Role,RolePermission(EmbeddedId),EmployeeRole(EmbeddedId),EmployeeRoleStore(EmbeddedId) - Repositories:
RoleRepository,RolePermissionRepository,EmployeeRoleRepository,EmployeeRoleStoreRepository - Методы в
RoleRepository:findByFranchiseIdAndDeletedAtIsNull,findByFranchiseIdAndNameIgnoreCase,countEmployeesByRoleId(roleId), и т.п. - DTOs:
RoleResponse,RoleListItem(сemployee_count),CreateRoleRequest,UpdateRoleRequest,PermissionCatalog
Фаза 3 — Permission catalog (константа)
- Класс
PermissionCatalogс константамиBACKOFFICE_SECTIONS(16 разделов с лейблами и наличием edit) иPOS_OPERATIONS(14 операций) - Метод
isValidKey(String key): booleanдля валидации при сохранении роли - Метод
all(): Set<String>для seed системной роли
Фаза 4 — Roles CRUD Service + Controller
-
RoleService.list(filters, pagination)с агрегациейemployee_count(JOIN employee_roles) -
RoleService.get(id)+ проверка franchise_id из JWT -
RoleService.create(request)— валидация name unique, permission keys, Edit-implies-Read, сохранение в транзакции (role + role_permissions + optional salary_formula) -
RoleService.update(id, patch)— частичное обновление; дляis_system=trueразрешить только name/description -
RoleService.delete(id)— проверка ROLE_IN_USE; soft delete (set deleted_at) -
RoleService.restore(id)— проверка NAME_DUPLICATE с активными + unset deleted_at -
RoleService.getPermissionCatalog()— из константы с i18n-лейблами -
RoleControllerс эндпоинтамиGET/POST/PATCH/DELETE /api/v1/roles,POST /{id}/restore,GET /permission-catalog - Exception handling:
NAME_DUPLICATE,UNKNOWN_PERMISSION_KEY,ROLE_IN_USE,SYSTEM_ROLE_PROTECTED,ROLE_NOT_FOUND
Фаза 5 — Изменения в EmployeeService
-
CreateEmployeeRequest+UpdateEmployeeRequest— добавить полеroles: List<EmployeeRoleAssignment>(role_id + store_ids[]) -
EmployeeService.create— валидировать role_ids (same franchise, not deleted), store_ids (subset of multi-tenancy scope), сохранить вemployee_roles+employee_role_storesв одной транзакции -
EmployeeService.update— идемпотентно заменить связи (delete old → insert new) -
EmployeeResponse— в detail-ответе добавитьroles: [{ id, name, store_ids }] - Errors:
ROLE_NOT_FOUND,ROLE_STORE_OUT_OF_SCOPE
Фаза 6 — Internal API
- Новый
GET /internal/users/{id}/permissions— возвращает{ user_id, role_ids[], permissions[] }(агрегат granted=true со всех ролей сотрудника) - Обновить
POST /internal/users/validate-credentials,GET /internal/users/by-email,POST /internal/users/validate-pin— добавитьrole_ids[]иpermissions[]в response - Переиспользовать общий helper
PermissionAggregator.forUser(employeeId)
Фаза 7 — Seed системной роли
-
FranchiseSeedService(новый или расширение существующегоDataSeeder): при создании франшизы вызыватьcreateSystemAdministratorRole(franchiseId):rolesзапись сname="Администратор",is_system=true- все ключи из
PermissionCatalog.all()вrole_permissionsс granted=true
- В
application-dev.yml/application-test.yml— убедиться что seed выполняется на пустой БД
Фаза 8 — Разовый скрипт миграции данных (SQL-файл)
- Flyway миграция
V{NN}__BR_1.4.3_migrate_existing_employees.sql:INSERT INTO roles— системная роль «Администратор» для каждой существующей франшизы (если не создана seed’ом)INSERT INTO role_permissions— все ключи для этой ролиINSERT INTO employee_roles— привязка всех активныхadmin_franchiseк системной ролиUPDATE legal_entities SET owner_user_id = NULL WHERE owner_user_id IN (SELECT id FROM employees WHERE role='admin_franchisee')DELETE FROM employees WHERE role IN ('admin_franchisee', 'manager', 'cashier')
Фаза 9 — Тесты
- Unit:
RoleService— все сценарии CRUD + валидация - Unit:
PermissionAggregator— агрегат granted=true с учётом нескольких ролей - Integration: POST /roles → GET list → назначение сотруднику → GET /internal/users/{id}/permissions → проверка агрегата
- Integration: DELETE /roles/{id} →
ROLE_IN_USEпри назначенных сотрудниках - Integration: SYSTEM_ROLE_PROTECTED при попытке изменить права системной
Выходные критерии
- Все миграции применяются на чистой БД и на БД с существующими данными
- Linting/сompile без ошибок
- Unit-тесты проходят
- Integration-тесты проходят на Testcontainers
- Сборка Docker-образа без ошибок