# 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