task-l.1
This commit is contained in:
72
docs/adr/0014-crm-multi-role-user-assignment.md
Normal file
72
docs/adr/0014-crm-multi-role-user-assignment.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# ADR 0014: CRM Multi-Role User Assignment
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Task L introduced CRM role profiles and resolved CRM access, but authorization still depended on a single `memberships.businessRole` value per organization membership.
|
||||
|
||||
That model is too restrictive for real CRM operations because one user may need multiple CRM responsibilities inside the same organization, such as:
|
||||
|
||||
- `sales` + `sales_manager`
|
||||
- `crm_admin` + `department_manager`
|
||||
- `marketing` + `sales_support`
|
||||
|
||||
We need to keep `memberships` as organization access while moving CRM authorization into its own persistent model.
|
||||
|
||||
## Decision
|
||||
|
||||
We adopt:
|
||||
|
||||
- `memberships` as organization/workspace access
|
||||
- `crm_user_role_assignments` as CRM authorization
|
||||
|
||||
One user can have many CRM role assignments per organization.
|
||||
|
||||
Each assignment stores:
|
||||
|
||||
- `roleProfileId`
|
||||
- branch scope mode and branch IDs
|
||||
- product-type scope mode and product-type IDs
|
||||
- primary/display flag
|
||||
- active/inactive lifecycle
|
||||
|
||||
Effective CRM access is resolved from:
|
||||
|
||||
1. membership role
|
||||
2. all active CRM role assignments
|
||||
3. all assigned CRM role profile permissions
|
||||
4. direct membership permissions
|
||||
|
||||
Rules:
|
||||
|
||||
- permissions are the union of all active role-profile permissions plus membership permissions
|
||||
- branch scope is the union of active assignment scopes unless any active assignment grants `all`
|
||||
- product-type scope is the union of active assignment scopes unless any active assignment grants `all`
|
||||
- approval authority uses the highest active authority among assigned roles
|
||||
- primary role is display-only and does not limit the permission union
|
||||
- `memberships.businessRole` remains temporarily as a compatibility fallback only when no active CRM role assignment exists
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- supports realistic multi-role CRM operation
|
||||
- separates organization access from CRM-specific authorization
|
||||
- allows per-role scope assignment per user
|
||||
- keeps rollout compatible with existing membership data through lazy backfill
|
||||
|
||||
### Negative
|
||||
|
||||
- resolver complexity increases
|
||||
- old `memberships.businessRole` semantics must be maintained during transition
|
||||
- UI and audit coverage must handle assignment lifecycle, not just role-profile maintenance
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
- create `crm_user_role_assignments`
|
||||
- backfill one primary assignment from `memberships.businessRole` when possible
|
||||
- treat `memberships.businessRole` as deprecated for CRM authorization after Task L.1
|
||||
- keep the column until user-management and downstream integrations fully move to assignment-based CRM access
|
||||
@@ -0,0 +1,46 @@
|
||||
# Task L.1: CRM Multi-Role User Assignment
|
||||
|
||||
## Summary
|
||||
|
||||
Task L.1 extends the CRM authorization foundation so one user can hold multiple CRM role assignments inside the same organization.
|
||||
|
||||
## Implemented
|
||||
|
||||
- Added `crm_user_role_assignments` schema for CRM-specific authorization rows
|
||||
- Added assignment permissions:
|
||||
- `crm.role.assignment.read`
|
||||
- `crm.role.assignment.create`
|
||||
- `crm.role.assignment.update`
|
||||
- `crm.role.assignment.delete`
|
||||
- `crm.role.assignment.manage`
|
||||
- Added lazy compatibility backfill from `memberships.businessRole` into CRM role assignments
|
||||
- Updated CRM access resolution to:
|
||||
- union permissions across all active assignments
|
||||
- union branch/product scope across assignments
|
||||
- choose the most permissive ownership scope
|
||||
- choose the highest approval authority
|
||||
- use primary role for display only
|
||||
- Added `CRM Settings > User Role Assignments`
|
||||
- Added assignment API routes:
|
||||
- `GET /api/crm/settings/user-role-assignments`
|
||||
- `POST /api/crm/settings/user-role-assignments`
|
||||
- `PATCH /api/crm/settings/user-role-assignments/[id]`
|
||||
- `DELETE /api/crm/settings/user-role-assignments/[id]`
|
||||
- Added audit logging for `crm_user_role_assignment`
|
||||
- Added ADR `0014-crm-multi-role-user-assignment`
|
||||
|
||||
## Compatibility
|
||||
|
||||
- `memberships.businessRole` remains in place
|
||||
- when no active CRM role assignment exists, resolver still falls back safely
|
||||
- when role assignments exist, CRM authorization no longer treats `memberships.businessRole` as the primary source
|
||||
|
||||
## Verification
|
||||
|
||||
- Run: `npx tsc --noEmit`
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- user-creation and user-edit flows still center around legacy `memberships.businessRole`
|
||||
- some CRM modules beyond the current resolver consumers may still need deeper scenario verification with mixed-role fixtures
|
||||
- a dedicated one-shot operational migration script can still be useful even though lazy backfill is active
|
||||
@@ -365,6 +365,19 @@ Future:
|
||||
- define whether mixed-role users see own, team, or organization scope by default
|
||||
- keep exports aligned with the same scope policy once enforced
|
||||
|
||||
## After Task L.1
|
||||
|
||||
### Deprecated membership.businessRole for CRM authorization
|
||||
|
||||
Current:
|
||||
`memberships.businessRole` is now a compatibility fallback only. Primary CRM authorization moves to `crm_user_role_assignments`.
|
||||
|
||||
Future:
|
||||
|
||||
- remove CRM authorization dependence on `memberships.businessRole` entirely
|
||||
- update user-management flows to assign CRM roles through assignment rows instead of membership payloads
|
||||
- remove the deprecated column only after all CRM modules and admin flows have migrated
|
||||
|
||||
### Global project-party filter coverage
|
||||
|
||||
Task J adds a global `Project Party Role` filter in the dashboard UI, but the strongest enforcement today is in revenue analytics and export sections.
|
||||
|
||||
Reference in New Issue
Block a user