576 lines
8.2 KiB
Markdown
576 lines
8.2 KiB
Markdown
# Task L.1: CRM Multi-Role User Assignment & Scope Management
|
|
|
|
## Objective
|
|
|
|
Extend Task L authorization foundation so one user can hold multiple CRM role profiles within the same organization.
|
|
|
|
Business decision:
|
|
|
|
```txt
|
|
1 User = Many CRM Roles
|
|
```
|
|
|
|
Examples:
|
|
|
|
```txt
|
|
Sales Manager + Sales
|
|
CRM Admin + Department Manager
|
|
Sales Support + Marketing
|
|
```
|
|
|
|
This task adds role assignment persistence, assignment UI, and updates effective CRM access resolution.
|
|
|
|
---
|
|
|
|
# Background
|
|
|
|
Task L introduced:
|
|
|
|
```txt
|
|
crmRoleProfiles
|
|
memberships.branchScopeIds
|
|
memberships.productTypeScopeIds
|
|
resolved CRM access helper
|
|
CRM Settings > Roles
|
|
```
|
|
|
|
Current limitation:
|
|
|
|
```txt
|
|
membership.businessRole = single role
|
|
```
|
|
|
|
This is not enough for real business operation.
|
|
|
|
Task L.1 moves CRM role assignment into a dedicated model while keeping membership as organization access.
|
|
|
|
---
|
|
|
|
# Target Model
|
|
|
|
Membership remains organization-level access:
|
|
|
|
```txt
|
|
memberships
|
|
- userId
|
|
- organizationId
|
|
- role
|
|
- permissions
|
|
```
|
|
|
|
CRM role assignments become CRM-specific authorization rows:
|
|
|
|
```txt
|
|
crm_user_role_assignments
|
|
- id
|
|
- organizationId
|
|
- userId
|
|
- roleProfileId
|
|
- branchScopeMode
|
|
- branchScopeIds
|
|
- productTypeScopeMode
|
|
- productTypeScopeIds
|
|
- isPrimary
|
|
- isActive
|
|
- assignedBy
|
|
- assignedAt
|
|
- createdAt
|
|
- updatedAt
|
|
- deletedAt
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.1 Schema
|
|
|
|
Add table:
|
|
|
|
```txt
|
|
crm_user_role_assignments
|
|
```
|
|
|
|
Fields:
|
|
|
|
```txt
|
|
id
|
|
organizationId
|
|
userId
|
|
roleProfileId
|
|
|
|
branchScopeMode
|
|
branchScopeIds
|
|
|
|
productTypeScopeMode
|
|
productTypeScopeIds
|
|
|
|
isPrimary
|
|
isActive
|
|
|
|
assignedBy
|
|
assignedAt
|
|
|
|
createdAt
|
|
updatedAt
|
|
deletedAt
|
|
```
|
|
|
|
Recommended scope modes:
|
|
|
|
```txt
|
|
all
|
|
selected
|
|
none
|
|
inherit
|
|
```
|
|
|
|
Unique constraint:
|
|
|
|
```txt
|
|
organizationId + userId + roleProfileId
|
|
```
|
|
|
|
Rules:
|
|
|
|
* one user can have many role profiles
|
|
* same role profile cannot be duplicated for the same user/org
|
|
* only one primary CRM role per user/org
|
|
* soft delete supported
|
|
* old `membership.businessRole` remains temporarily for compatibility
|
|
|
|
---
|
|
|
|
# Scope L1.2 Migration / Backfill
|
|
|
|
Backfill existing users.
|
|
|
|
For each membership:
|
|
|
|
```txt
|
|
membership.businessRole
|
|
```
|
|
|
|
create one CRM role assignment matching the role profile.
|
|
|
|
Rules:
|
|
|
|
* if businessRole has a matching `crmRoleProfiles.code`, create assignment
|
|
* if no matching role profile exists, skip and log warning
|
|
* set `isPrimary = true`
|
|
* branch/product scope from membership fields if available
|
|
* idempotent migration
|
|
* no duplicate assignments
|
|
|
|
---
|
|
|
|
# Scope L1.3 Effective CRM Access Resolver
|
|
|
|
Update resolved access helper.
|
|
|
|
Old:
|
|
|
|
```txt
|
|
membership.businessRole
|
|
+
|
|
membership.permissions
|
|
+
|
|
roleProfile.permissions
|
|
```
|
|
|
|
New:
|
|
|
|
```txt
|
|
membership
|
|
+
|
|
all active crm_user_role_assignments
|
|
+
|
|
all assigned crmRoleProfiles
|
|
+
|
|
direct membership.permissions
|
|
```
|
|
|
|
Permission behavior:
|
|
|
|
```txt
|
|
effectivePermissions = union(all roleProfile.permissions, membership.permissions)
|
|
```
|
|
|
|
Ownership behavior:
|
|
|
|
Use most permissive access among active role profiles:
|
|
|
|
```txt
|
|
own
|
|
team
|
|
branch
|
|
organization
|
|
all
|
|
```
|
|
|
|
Branch scope behavior:
|
|
|
|
```txt
|
|
if any role has all -> all
|
|
else union selected branchScopeIds
|
|
else none
|
|
```
|
|
|
|
Product type scope behavior:
|
|
|
|
```txt
|
|
if any role has all -> all
|
|
else union selected productTypeScopeIds
|
|
else none
|
|
```
|
|
|
|
Approval authority:
|
|
|
|
```txt
|
|
effectiveApprovalAuthority = union(all roleProfile.approvalAuthority)
|
|
```
|
|
|
|
Primary role:
|
|
|
|
```txt
|
|
used for display only
|
|
not permission limit
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.4 User Assignment UI
|
|
|
|
Add page or tab:
|
|
|
|
```txt
|
|
CRM Settings
|
|
└─ User Role Assignments
|
|
```
|
|
|
|
or add tab inside existing user detail:
|
|
|
|
```txt
|
|
Users
|
|
└─ CRM Roles
|
|
```
|
|
|
|
Recommended UI:
|
|
|
|
```txt
|
|
User
|
|
Assigned CRM Roles
|
|
Branch Scope
|
|
Product Type Scope
|
|
Primary Role
|
|
Status
|
|
Actions
|
|
```
|
|
|
|
Actions:
|
|
|
|
```txt
|
|
Assign Role
|
|
Edit Scope
|
|
Set Primary
|
|
Deactivate Assignment
|
|
Reactivate Assignment
|
|
Remove Assignment
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.5 Assignment Form
|
|
|
|
Fields:
|
|
|
|
```txt
|
|
User
|
|
Role Profile
|
|
Branch Scope Mode
|
|
Branches
|
|
Product Type Scope Mode
|
|
Product Types
|
|
Primary
|
|
Active
|
|
```
|
|
|
|
Behavior:
|
|
|
|
* role profile dropdown from active `crmRoleProfiles`
|
|
* branch options from `crm_branch`
|
|
* product type options from `crm_product_type`
|
|
* if scope mode = all, disable selected list
|
|
* if scope mode = selected, require at least one selected option
|
|
* if primary = true, unset other primary role assignments for same user/org
|
|
|
|
---
|
|
|
|
# Scope L1.6 API Routes
|
|
|
|
Add:
|
|
|
|
```txt
|
|
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]
|
|
```
|
|
|
|
Optional:
|
|
|
|
```txt
|
|
GET /api/crm/settings/users/[userId]/role-assignments
|
|
```
|
|
|
|
All APIs must:
|
|
|
|
```txt
|
|
requireOrganizationAccess
|
|
requirePermission("crm.role.assignment.manage")
|
|
filter by organizationId
|
|
audit mutations
|
|
return user-safe errors
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.7 Permissions
|
|
|
|
Add permissions:
|
|
|
|
```txt
|
|
crm.role.assignment.read
|
|
crm.role.assignment.create
|
|
crm.role.assignment.update
|
|
crm.role.assignment.delete
|
|
crm.role.assignment.manage
|
|
```
|
|
|
|
Grant default to:
|
|
|
|
```txt
|
|
system_admin
|
|
crm_admin
|
|
```
|
|
|
|
Optional read to:
|
|
|
|
```txt
|
|
sales_manager
|
|
department_manager
|
|
top_manager
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.8 Audit
|
|
|
|
Audit entity type:
|
|
|
|
```txt
|
|
crm_user_role_assignment
|
|
```
|
|
|
|
Actions:
|
|
|
|
```txt
|
|
create
|
|
update
|
|
delete
|
|
activate
|
|
deactivate
|
|
set_primary
|
|
scope_update
|
|
```
|
|
|
|
Audit payload should include:
|
|
|
|
```txt
|
|
userId
|
|
roleProfileId
|
|
branchScopeMode
|
|
branchScopeIds
|
|
productTypeScopeMode
|
|
productTypeScopeIds
|
|
isPrimary
|
|
isActive
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.9 Compatibility Layer
|
|
|
|
Keep `membership.businessRole` temporarily.
|
|
|
|
Rules:
|
|
|
|
* do not remove column yet
|
|
* do not rely on it when role assignments exist
|
|
* fallback to `membership.businessRole` only when user has no active CRM role assignment
|
|
* document deprecation
|
|
|
|
Add technical debt:
|
|
|
|
```txt
|
|
membership.businessRole is deprecated for CRM authorization after Task L.1.
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.10 Server-Side Enforcement Migration
|
|
|
|
Update all CRM modules to use resolved multi-role access:
|
|
|
|
```txt
|
|
Leads
|
|
Enquiries
|
|
Customers
|
|
Contacts
|
|
Quotations
|
|
Approvals
|
|
Dashboard
|
|
Settings
|
|
Reports if present
|
|
```
|
|
|
|
Priority:
|
|
|
|
1. Leads / Enquiries
|
|
2. Quotations
|
|
3. Dashboard
|
|
4. Approval
|
|
5. Customers / Contacts
|
|
6. Settings
|
|
|
|
Rules:
|
|
|
|
* no UI-only enforcement
|
|
* all list/detail/action routes must check resolved CRM access
|
|
* pricing visibility must remain permission-based
|
|
|
|
---
|
|
|
|
# Scope L1.11 Verification Matrix
|
|
|
|
Create verification cases:
|
|
|
|
## Marketing + Sales Support
|
|
|
|
Expected:
|
|
|
|
```txt
|
|
can view leads
|
|
can create support quotation if permission exists
|
|
cannot approve
|
|
cannot view revenue unless granted
|
|
```
|
|
|
|
## Sales + Sales Manager
|
|
|
|
Expected:
|
|
|
|
```txt
|
|
can see own sales records
|
|
can see team records if manager role scope allows
|
|
can create quotation
|
|
can approve only if role profile has authority
|
|
```
|
|
|
|
## CRM Admin + Department Manager
|
|
|
|
Expected:
|
|
|
|
```txt
|
|
can manage CRM settings
|
|
can approve department-level workflow
|
|
can view configured analytics
|
|
```
|
|
|
|
## Sales with Crane + Bangkok Scope
|
|
|
|
Expected:
|
|
|
|
```txt
|
|
can see Crane Bangkok records
|
|
cannot see Solar Rayong records
|
|
```
|
|
|
|
---
|
|
|
|
# Scope L1.12 Documentation / ADR
|
|
|
|
Create ADR:
|
|
|
|
```txt
|
|
docs/adr/0014-crm-multi-role-user-assignment.md
|
|
```
|
|
|
|
Document:
|
|
|
|
```txt
|
|
1 user = many CRM roles
|
|
membership is organization access
|
|
crm_user_role_assignments is CRM authorization
|
|
permission union rule
|
|
scope union rule
|
|
primary role display-only rule
|
|
businessRole deprecation
|
|
```
|
|
|
|
Update:
|
|
|
|
```txt
|
|
docs/implementation/technical-debt.md
|
|
```
|
|
|
|
---
|
|
|
|
# Explicit Non-Scope
|
|
|
|
Do NOT implement:
|
|
|
|
```txt
|
|
Keycloak group sync
|
|
External identity role sync
|
|
Team hierarchy module
|
|
Advanced ABAC rules
|
|
Approval delegation
|
|
Temporary role expiration
|
|
Role request workflow
|
|
```
|
|
|
|
---
|
|
|
|
# Output
|
|
|
|
After completion, summarize:
|
|
|
|
1. Files Added
|
|
2. Files Modified
|
|
3. Schema Added
|
|
4. Migration / Backfill
|
|
5. Resolver Changes
|
|
6. UI Added
|
|
7. API Added
|
|
8. Permissions Added
|
|
9. Audit Added
|
|
10. Verification Result
|
|
11. Remaining Risks
|
|
|
|
---
|
|
|
|
# Definition of Done
|
|
|
|
Task L.1 is complete when:
|
|
|
|
* user can have multiple CRM role assignments
|
|
* each assignment can have branch/product scope
|
|
* one primary role can be selected
|
|
* effective permissions are unioned from all active roles
|
|
* effective branch/product scope is resolved correctly
|
|
* membership.businessRole is no longer primary CRM authorization source
|
|
* role assignment UI works
|
|
* role assignment API works
|
|
* all CRM critical routes use multi-role resolver
|
|
* audit logs are created for assignment changes
|
|
* ADR 0014 exists
|