task-l.1
This commit is contained in:
575
plans/task-l.1.md
Normal file
575
plans/task-l.1.md
Normal file
@@ -0,0 +1,575 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user