ADR-004: Доступ до кількох відділів
- Статус: прийнято
- Дата: 2026-04-16
- Автор: CTO Agent (KALA-148)
Контекст
User.departmentId спочатку представляв увесь data-scope користувача. Це працює для співробітника одного відділу, але падає для менеджерів та команд shared-services. Project manager може потребувати доступу до Legal, Finance і HR, тримаючи при цьому один primary-відділ для ownership і дефолтів.
Система вже мала ізоляцію відділу в REST-маршрутах, WebSocket-підписках, RAG-запитах і фонових воркерах. Multi-department access мав розширити цю ізоляційну модель, не послаблюючи її.
Рішення
Тримаємо User.departmentId і User.primaryDepartmentId як primary/default department-поля і додаємо department scope через role- і user-override масиви:
SystemRole.allDepartmentsSystemRole.departmentIdsUser.extraDepartmentIdsUser.revokedDepartmentIds
Початковий дизайн розглядав join-таблицю UserDepartmentAccess(userId, departmentId). Поточна реалізація використовує масиви, бо очікувані scope-набори малі, обчислення доступу hot-path, а overrides і так обчислюються разом із дозволами.
Ефективний департаментний доступ такий:
if role.allDepartments or legacy role is admin:
all departments
else:
role.departmentIds
+ primaryDepartmentId
+ departmentId
+ extraDepartmentIds
- revokedDepartmentIds
Розглянуті альтернативи
Замінити departmentId на join-таблицю
Відхилено на цій фазі. Це ускладнило б базовий single-department-сценарій і вимагало б від кожного маршруту join або preload access-рядків.
Тримати тільки departmentId
Відхилено. Не моделює cross-functional-менеджерів без видачі повного admin-доступу.
Використати тільки SystemRole.departmentIds
Відхилено. Це робить one-off винятки дорогими, бо кожен виняток потребує нової ролі.
Наслідки
Позитивні
- Single-department-користувачі лишаються простими.
- Менеджери отримують scope-обмежений multi-department-доступ без admin-ролі.
- Revoke може вилучити відділ, навіть якщо його надає базова роль.
- Один
forUser()-хелпер підтримує прямі та вкладені Prisma-фільтри.
Негативні
- Масиви треба валідувати при прийомі department id з API-запитів.
- Дуже великі department scope-и краще представляти через join-таблицю.
- Виклики, яким треба саме один відділ, не мають використовувати
requireDepartmentId()для multi-department-користувачів.
Нотатки про реалізацію
effectiveDepartments()виконує set-арифметику.forUser()виставляєdeptIds,directWhere(),nestedWhere()іcanSeeDept().- Новий route-код має використовувати
scope.canSeeDept(id)для loaded-ресурсів іscope.directWhere()абоscope.nestedWhere()для запитів.