Administrator — Role Management Documentation

Version: 1.0
Last updated: December 10, 2025

Table of Contents


1. Overview

Role Management is the core administrative module for managing user roles and permissions in the DTR System. Administrators can:

  • Create and configure roles (admin, employee, manager, scheduler, etc.)
  • Assign roles to individual employees
  • Remove roles from users
  • Track role assignments and changes
  • Manage role permissions (via Spatie Laravel-Permission)
  • Control user access based on assigned roles
  • Audit role assignment history

Key files involved:

  • Admin views: resources/views/users/admin/role-management/ (index)
  • Controller: App\Http\Controllers\Web\Admin\RoleController.php
  • Model: Spatie\Permission\Models\Role, App\Models\User
  • Database: roles, model_has_roles tables

2. Purpose & Scope

Purpose:

  • Centralized role creation and management
  • Flexible role assignment to employees
  • Control user access and permissions via roles
  • Track role assignment history and audit trail
  • Integrate with DTR timekeeping and payroll systems
  • Enable fine-grained access control (RBAC)

Scope:

  • Create / read / update / delete roles
  • Assign roles to users (single or bulk)
  • Remove roles from users
  • Search and filter users and roles
  • Track role assignment history
  • Generate role assignment reports
  • Validate role compatibility

Out of scope:

  • Permission management (documented separately, via permissions)
  • Advanced role inheritance/hierarchy (documented separately)
  • Custom role creation by non-admins (documented separately)

3. Access & Permissions

Required permissions:

  • view_roles — view role list and details
  • create_roles — create new roles
  • edit_roles — modify existing roles
  • delete_roles — delete role records
  • assign_roles — assign roles to users
  • view_role_assignments — view user role history

Recommended roles:

  • Super Admin — full CRUD on roles, assign/remove roles, audit access
  • Admin — create/edit/delete roles, assign/remove roles from users
  • HR Manager — view roles, assign/remove roles for employees, view reports
  • Manager — view roles, request role changes for own team (requires approval)
  • Employee — view own role and permissions (read-only)

Implementation note:

  • Enforce RBAC server-side via middleware or gate checks
  • UI hides role management actions (create, edit, delete) for non-admin roles
  • Use authorize() in controller methods or Gate policies
  • Prevent removal of core roles (admin, employee) without confirmation

4. How to Access

Admin URLs:

  • Roles & users index: /admin/role-management
  • Search users: /admin/role-management?search={query}
  • Assign role to user: /admin/role-management/{userId}/assign-role (POST)
  • Remove role from user: /admin/role-management/{userId}/remove-role (POST)

Browser requirements: Modern Chromium-based browsers, recent Safari/Firefox. Mobile viewing supported; recommend desktop for full role management.


5. Interface Layout & Views

5.1 Index (Roles & Users)

Purpose: Display all roles and users with filtering, search and bulk actions.

View file: resources/views/users/admin/role-management/index.blade.php

UI elements:

Left Panel — Roles List:

  • Title: "Available Roles"
  • Role cards / rows:
    • Role Name (e.g., "Team Lead", "Scheduler", "HR Manager")
    • Role Description (if available)
    • User Count (how many users have this role)
    • Actions:
      • Edit Role (pencil icon)
      • Delete Role (trash icon, disabled if users assigned)
      • View Permissions (show attached permissions)
  • Filters / Controls:
    • Show system roles: toggle (admin, employee) — hidden by default
    • Filter by department (if role is department-specific)
    • Create Role (button, admin only)

Right Panel — Users & Role Assignment:

  • Search bar: filter by user name or email
  • Table columns:
    • Avatar / User Name
    • Email / Employee ID
    • Current Role(s) (badge list)
    • Department / Position
    • Status (active, inactive)
    • Assigned Date
    • Actions:
      • Add Role (plus icon, dropdown to select available role)
      • Remove Role (if multiple roles) (X icon)
      • View Role History (timeline)
  • Pagination: 10 users per page (configurable)
  • Bulk actions: Assign role to multiple users, Export user-role matrix

Key features:

  • Inline role assignment via dropdown
  • Quick role removal via confirmation modal
  • Real-time search via AJAX (/admin/role-management?search={query})
  • Color-coded role badges (different color per role type)
  • Role count indicators (e.g., "3 users have this role")
  • Disabled state for system roles (admin, employee)

Controller method: RoleController::index(Request $request)

public function index(Request $request)
{
    $search = $request->search;

    // Get all roles except admin and employee (system roles)
    $roles = Role::whereNotIn('name', ['admin', 'employee'])->get();

    // Query employees with role filter
    $query = User::role('employee');

    // Search by name or email
    if($search){
        $query = $query->where('name', 'like', '%' . $search . '%')
            ->orWhere('email', 'like', '%' . $search . '%');
    }

    $users = $query->latest()->paginate(10);

    return view('users.admin.role-management.index', compact(['roles', 'users']));
}

5.2 Assign Role

Purpose: Assign a new role to a user.

Trigger: "Add Role" button/action next to user in index view.

Flow:

  1. Modal / Inline Form opens:

    • User name display (read-only)
    • Current roles (display)
    • Available roles dropdown (list of roles NOT already assigned)
    • Confirmation checkbox: "I confirm this role assignment"
    • Assign button
  2. Form submission (POST /admin/role-management/{userId}/assign-role):

    • Validate role_id is provided and exists
    • Verify user is not already assigned this role
    • Call User::assignRole(Role)
    • Create audit log entry
    • Send notification to user (optional): "You've been assigned to {Role Name}"
    • Notify managers/admins if role grants significant permissions
    • Redirect back with success message

Form fields:

  • User ID (hidden) — from URL parameter {id}
  • Role ID (required, dropdown) — list of available roles
    • Exclude system roles (admin, employee)
    • Exclude already assigned roles
    • Sort: custom role order or alphabetical
  • Notes (optional, text) — reason for role assignment
    • "Promoted to team lead"
    • "Temporary cover for John (on leave)"

Validation:

  • role_id required, must exist in roles table
  • User must not already have this role
  • Admin must have assign_roles permission

Server-side processing:

public function assignRole(Request $request, string $id)
{
    // Validate
    $request->validate([
        'role_id' => 'required|exists:roles,id'
    ]);

    $user = User::findOrFail($id);
    $role = Role::findOrFail($request->role_id);

    // Check user doesn't already have role
    if ($user->hasRole($role)) {
        return back()->with('error', 'User already has this role');
    }

    // Assign role
    $user->assignRole($role);

    // Audit log
    activity()
        ->causedBy(auth()->user())
        ->performedOn($user)
        ->withProperties(['role' => $role->name, 'notes' => $request->notes ?? null])
        ->log('role_assigned');

    // Notification
    // Mail::queue(new RoleAssigned($user, $role));

    return back()->with(['message' => 'New Role Assign!']);
}

Controller method:

public function assignRole(Request $request, string $id)
{
    $user = User::find($id);
    $role_id = $request->role_id;
    $role = Role::find($role_id);

    $user->assignRole($role);

    return back()->with(['message' => 'New Role Assign!']);
}

5.3 Remove Role

Purpose: Remove an assigned role from a user.

Trigger: "Remove" button/action (X icon) next to role badge in user row.

Flow:

  1. Confirmation modal opens:

    • Message: "Remove {Role Name} from {User Name}?"
    • Warning: "This user will lose all permissions tied to this role."
    • Reason dropdown (optional):
      • "User left the role"
      • "Role reassigned"
      • "User terminated"
      • "Custom: (text field)"
    • Cancel / Confirm buttons
  2. Form submission (POST /admin/role-management/{userId}/remove-role):

    • Validate role_id is provided and user has this role
    • Prevent removal of all roles (user must have at least one)
    • Call User::removeRole(Role)
    • Create audit log entry with reason
    • Send notification to user: "Your {Role Name} role has been removed"
    • Notify managers if role removal affects team responsibilities
    • Redirect back with success message

Form fields:

  • User ID (hidden) — from URL parameter {id}
  • Role ID (hidden or dropdown) — role to remove
  • Reason (optional, dropdown/text) — why role is being removed
    • "Left the role"
    • "Demotion"
    • "Reassignment"
    • "Termination"
    • "Custom"

Validation:

  • role_id required, must exist in roles table
  • User must currently have this role
  • User must have at least one role after removal (cannot remove all)
  • Admin must have assign_roles permission

Server-side processing:

public function removeRole(string $id, Request $request)
{
    // Validate
    $request->validate([
        'role_id' => 'required|exists:roles,id'
    ]);

    $user = User::findOrFail($id);
    $role = Role::findOrFail($request->role_id);

    // Check user has this role
    if (!$user->hasRole($role)) {
        return back()->with('error', 'User does not have this role');
    }

    // Check user won't have zero roles
    if ($user->roles()->count() <= 1) {
        return back()->with('error', 'User must have at least one role');
    }

    // Remove role
    $user->removeRole($role);

    // Audit log
    activity()
        ->causedBy(auth()->user())
        ->performedOn($user)
        ->withProperties(['role' => $role->name, 'reason' => $request->reason ?? null])
        ->log('role_removed');

    // Notification
    // Mail::queue(new RoleRemoved($user, $role));

    return back()->with(['message' => 'Role is Remove!']);
}

Controller method:

public function removeRole(string $id, Request $request)
{
    $role = Role::find($request->role_id);
    $user = User::find($id);

    $user->removeRole($role);

    return back()->with(['message' => 'Role is Remove!']);
}

6. Key Features

  • Flexible role assignment: Support multiple roles per user
  • Role filtering: Hide system roles (admin, employee) by default
  • Search & filter: Find users by name or email
  • Inline assignment: Assign/remove roles directly from index view
  • Conflict detection: Prevent duplicate role assignments
  • Audit trail: Log all role changes with who/when/reason
  • Integration with permissions: Roles carry permissions (via Spatie)
  • Bulk operations: Assign roles to multiple users at once (future feature)
  • Role hierarchy: System roles (admin, employee) separate from custom roles
  • Validation: Ensure users always have at least one role
  • Notifications: Alert users of role assignment/removal

7. Data Model & Database

Role model fields (Spatie Laravel-Permission):

id                 — Primary key
name               — Unique identifier (string, e.g., "team-lead", "scheduler")
guard_name         — Guard name (string, default 'web')
created_at         — Timestamp
updated_at         — Timestamp

model_has_roles table (pivot):

role_id            — Foreign key to roles
model_id           — Foreign key to users
model_type         — Model class name (App\Models\User)

Sample records:

Role:

id: 1
name: "admin"
guard_name: "web"

id: 2
name: "employee"
guard_name: "web"

id: 3
name: "team-lead"
guard_name: "web"

id: 4
name: "scheduler"
guard_name: "web"

id: 5
name: "hr-manager"
guard_name: "web"

model_has_roles:

role_id: 3 (team-lead)
model_id: 42 (Jane Doe)
model_type: App\Models\User

role_id: 5 (hr-manager)
model_id: 43 (John Smith)
model_type: App\Models\User

8. Role Types & Hierarchy

System Roles (Built-in, cannot be deleted):

Admin
├─ Description: System administrator with full access
├─ Permissions: All
├─ Users: Limited (1-2 super admins)
└─ Can manage: users, roles, permissions, system settings

Employee
├─ Description: Default role for all regular employees
├─ Permissions: Basic (view own profile, apply leave, view shifts)
├─ Users: Majority of users
└─ Cannot manage: other users, roles, system

Custom Roles (Created by Admin):

Team Lead
├─ Description: Team leader with management capabilities
├─ Permissions: Approve leaves, assign shifts, view team reports, manage team
├─ Users: ~5-10 per department
└─ Scope: Own team members

HR Manager
├─ Description: Human Resources manager
├─ Permissions: Manage users, view reports, approve leaves, manage roles
├─ Users: 2-5
└─ Scope: All employees

Scheduler
├─ Description: Scheduling specialist
├─ Permissions: Create/edit shifts, manage schedules, bulk assign shifts
├─ Users: 1-3
└─ Scope: All shifts and schedules

Manager / Supervisor
├─ Description: Department manager
├─ Permissions: Approve timesheets, manage attendance, view payroll summaries
├─ Users: ~3-10
└─ Scope: Own department

Payroll Administrator
├─ Description: Payroll processor
├─ Permissions: View payroll data, process payroll, generate reports
├─ Users: 1-2
└─ Scope: All employees

IT Admin
├─ Description: IT system administrator
├─ Permissions: Manage SSO clients, system logs, user accounts
├─ Users: 1-3
└─ Scope: System administration

Role Hierarchy Example:

Super Admin
├── Admin
│   ├── HR Manager
│   │   ├── Team Lead
│   │   └── Scheduler
│   └── IT Admin
└── Employee (base level)

9. User Role Assignment

9.1 Single Role Assignment

Scenario: Assign "Team Lead" role to Jane Doe.

Steps:

  1. Navigate: Admin → Role Management
  2. Search for Jane Doe in the users list
  3. Click "Add Role" button next to Jane's row
  4. Modal opens:
    • User: Jane Doe (display)
    • Current roles: Employee (display)
    • Select role: Team Lead (dropdown)
    • Optional notes: "Promoted to team lead"
  5. Click "Assign"

Server-side processing:

  1. Validate role_id provided and exists
  2. Verify Jane doesn't already have "Team Lead" role
  3. Call User::assignRole(Role)
  4. Update model_has_roles table
  5. Create audit log: "Team Lead role assigned to Jane Doe by Admin"
  6. Send email to Jane: "Congratulations! You've been assigned to Team Lead role"
  7. Redirect back with success message

Permissions granted:

  • view_team_members
  • approve_leaves
  • assign_shifts
  • view_team_reports

9.2 Multiple Role Assignment

Scenario: User can have multiple roles (e.g., "Team Lead" + "Scheduler").

Steps:

  1. Navigate: Admin → Role Management
  2. Search for John Smith
  3. Add first role: "Team Lead" (click "Add Role" → select → assign)
  4. Add second role: "Scheduler" (click "Add Role" → select → assign)
  5. Result: John now has both roles and combined permissions

Validation:

  • User can have 2-3 roles maximum (configurable)
  • Prevent conflicting roles (e.g., admin + employee)
  • Warn if user already has similar permissions

9.3 Removing a Role

Scenario: Remove "Team Lead" role from Jane Doe.

Steps:

  1. Navigate: Admin → Role Management
  2. Find Jane Doe in users list
  3. Locate "Team Lead" badge in her current roles
  4. Click "X" or "Remove" button
  5. Confirmation modal:
    • "Remove Team Lead from Jane Doe?"
    • "She will lose permissions: view_team_members, approve_leaves, assign_shifts"
    • Reason dropdown (optional): "Demotion" / "Left role" / "Reassignment"
  6. Click "Confirm"

Server-side processing:

  1. Validate role_id provided
  2. Verify Jane has this role
  3. Check Jane won't have zero roles (error if last role)
  4. Call User::removeRole(Role)
  5. Update model_has_roles table
  6. Create audit log: "Team Lead role removed from Jane Doe by Admin (Reason: Demotion)"
  7. Send email to Jane: "Your Team Lead role has been removed"
  8. Redirect back with success message

10. API Endpoints

GET /admin/role-management

  • Returns index view with roles and users
  • Query params: search (user name/email), role_id (filter by role)
  • Response: HTML view with:
    • $roles — all custom roles (excluding admin, employee)
    • $users — paginated employees with their roles

POST /admin/role-management/{id}/assign-role

  • Assign role to user
  • Body:
    {
        "role_id": 3
    }
  • Response: Redirect with flash message "New Role Assign!"

POST /admin/role-management/{id}/remove-role

  • Remove role from user
  • Body:
    {
        "role_id": 3,
        "reason": "Demotion"
    }
  • Response: Redirect with flash message "Role is Remove!"

GET /api/admin/roles

  • Returns all roles (paginated)
  • Query params: search, guard_name
  • Response:
    {
        "data": [
            {
                "id": 3,
                "name": "team-lead",
                "guard_name": "web",
                "permissions_count": 5,
                "users_count": 8,
                "created_at": "2025-01-01T00:00:00Z"
            }
        ],
        "pagination": { "total": 10, "per_page": 25, "current_page": 1 }
    }

GET /api/admin/roles/{id}

  • Returns single role with details
  • Response:
    {
        "id": 3,
        "name": "team-lead",
        "guard_name": "web",
        "permissions": [
            { "id": 1, "name": "view_team_members" },
            { "id": 2, "name": "approve_leaves" }
        ],
        "users": [{ "id": 42, "name": "Jane Doe", "email": "jane@example.com" }]
    }

POST /api/admin/roles

  • Create new role
  • Body:
    {
        "name": "new-role",
        "description": "Role description"
    }
  • Response: 201 Created + role record

PUT /api/admin/roles/{id}

  • Update role
  • Body: (any of create fields)
  • Response: 200 OK + updated role

DELETE /api/admin/roles/{id}

  • Delete role (cannot delete system roles)
  • Response: 200 OK

GET /api/admin/users/{id}/roles

  • Get all roles assigned to a user
  • Response:
    {
        "user_id": 42,
        "roles": [
            { "id": 3, "name": "team-lead" },
            { "id": 5, "name": "hr-manager" }
        ]
    }

11. Controller Implementation Details

11.1 RoleController Methods

Location: app/Http/Controllers/Web/Admin/RoleController.php

index() — List all roles and users

public function index(Request $request)
{
    $search = $request->search;

    // Get all custom roles (exclude system roles)
    $roles = Role::whereNotIn('name', ['admin', 'employee'])->get();

    // Query users with 'employee' role
    $query = User::role('employee');

    // Search by name or email
    if($search){
        $query = $query->where('name', 'like', '%' . $search . '%')
            ->orWhere('email', 'like', '%' . $search . '%');
    }

    // Paginate results
    $users = $query->latest()->paginate(10);

    return view('users.admin.role-management.index', compact(['roles', 'users']));
}

assignRole() — Assign role to user

public function assignRole(Request $request, string $id)
{
    // Validate request
    $request->validate([
        'role_id' => 'required|exists:roles,id'
    ]);

    // Find user and role
    $user = User::find($id);
    $role_id = $request->role_id;
    $role = Role::find($role_id);

    // Check user doesn't already have role
    if ($user->hasRole($role)) {
        return back()->with('error', 'User already has this role');
    }

    // Assign role
    $user->assignRole($role);

    // Audit log (optional)
    // activity()
    //     ->causedBy(auth()->user())
    //     ->performedOn($user)
    //     ->withProperties(['role' => $role->name])
    //     ->log('role_assigned');

    // Notification (optional)
    // Mail::queue(new RoleAssigned($user, $role));

    return back()->with(['message' => 'New Role Assign!']);
}

removeRole() — Remove role from user

public function removeRole(string $id, Request $request)
{
    // Validate request
    $request->validate([
        'role_id' => 'required|exists:roles,id'
    ]);

    // Find user and role
    $user = User::find($id);
    $role = Role::find($request->role_id);

    // Check user has this role
    if (!$user->hasRole($role)) {
        return back()->with('error', 'User does not have this role');
    }

    // Check user won't have zero roles
    if ($user->roles()->count() <= 1) {
        return back()->with('error', 'User must have at least one role');
    }

    // Remove role
    $user->removeRole($role);

    // Audit log (optional)
    // activity()
    //     ->causedBy(auth()->user())
    //     ->performedOn($user)
    //     ->withProperties(['role' => $role->name, 'reason' => $request->reason ?? null])
    //     ->log('role_removed');

    // Notification (optional)
    // Mail::queue(new RoleRemoved($user, $role));

    return back()->with(['message' => 'Role is Remove!']);
}

11.2 Role Services (if applicable)

Location: app/Services/RoleServices.php (recommended, not yet implemented)

Suggested methods:

namespace App\Services;

use Spatie\Permission\Models\Role;
use App\Models\User;

class RoleServices
{
    /**
     * Get all custom roles (excluding system roles)
     */
    public function getCustomRoles()
    {
        return Role::whereNotIn('name', ['admin', 'employee'])->get();
    }

    /**
     * Assign role to user with validation
     */
    public function assignRoleToUser(User $user, Role $role, $reason = null)
    {
        if ($user->hasRole($role)) {
            throw new \Exception("User already has {$role->name} role");
        }

        $user->assignRole($role);

        // Log activity
        activity()
            ->causedBy(auth()->user())
            ->performedOn($user)
            ->withProperties(['role' => $role->name, 'reason' => $reason])
            ->log('role_assigned');

        return $user;
    }

    /**
     * Remove role from user with validation
     */
    public function removeRoleFromUser(User $user, Role $role, $reason = null)
    {
        if (!$user->hasRole($role)) {
            throw new \Exception("User does not have {$role->name} role");
        }

        if ($user->roles()->count() <= 1) {
            throw new \Exception('User must have at least one role');
        }

        $user->removeRole($role);

        // Log activity
        activity()
            ->causedBy(auth()->user())
            ->performedOn($user)
            ->withProperties(['role' => $role->name, 'reason' => $reason])
            ->log('role_removed');

        return $user;
    }

    /**
     * Get all users with a specific role
     */
    public function getUsersByRole(Role $role)
    {
        return $role->users()->paginate(25);
    }

    /**
     * Get role assignment history for a user
     */
    public function getRoleHistory(User $user)
    {
        return activity()
            ->performedOn($user)
            ->where('log_name', 'role_assigned')
            ->orWhere('log_name', 'role_removed')
            ->latest()
            ->paginate(10);
    }

    /**
     * Bulk assign role to multiple users
     */
    public function bulkAssignRole(array $userIds, Role $role)
    {
        $results = ['success' => 0, 'failed' => 0, 'errors' => []];

        foreach ($userIds as $userId) {
            try {
                $user = User::findOrFail($userId);
                $this->assignRoleToUser($user, $role);
                $results['success']++;
            } catch (\Exception $e) {
                $results['failed']++;
                $results['errors'][] = "User {$userId}: {$e->getMessage()}";
            }
        }

        return $results;
    }
}

12. Filters, Search & Reports

Index filters:

  • Search: user name, email
  • Role filter: view users by specific role (dropdown)
  • Status: active, inactive, suspended, terminated
  • Department: filter by department

Search functionality:

  • Real-time AJAX search via /admin/role-management?search={term}
  • Searches: first name, last name, email, employee ID
  • Returns matching users with their current roles

Reports:

  • User-role matrix: all users by role assignment (Excel export)
  • Role assignment history: who assigned/removed roles when
  • Unused roles: roles with no users assigned
  • Permission coverage: which permissions are covered by active roles
  • Audit trail: role changes by user and date

Export formats: CSV, Excel, PDF (role matrix)


13. Integrations (DTR / Scheduling / Payroll)

DTR Integration:

  • Role determines DTR access level
    • "Employee" can only view own DTR
    • "Team Lead" can view/approve team DTR
    • "Manager" can view department DTR
  • Role changes trigger DTR permission update

Scheduling Integration:

  • "Scheduler" role can create/edit shifts
  • "Team Lead" can assign shifts to own team
  • "HR Manager" can assign shifts to all employees
  • Role change updates scheduling permissions

Payroll Integration:

  • "Payroll Admin" role can view payroll data
  • "HR Manager" can view payroll summaries
  • "Manager" can view own team payroll summaries
  • Role change updates payroll access

Sync behavior:

  • On role assignment: update DTR, Scheduling, Payroll systems
  • On role removal: revoke access in DTR, Scheduling, Payroll systems
  • Use background jobs for heavy operations
  • Idempotent payloads with role ID and user ID

14. Common Tasks & Workflows

Task 1: Promote Employee to Team Lead

  1. Navigate: Admin → Role Management
  2. Search for employee "Alice Johnson"
  3. Click "Add Role" next to Alice
  4. Select "Team Lead" from dropdown
  5. Click "Assign"
  6. Alice now has Team Lead role; gains all associated permissions ✓

Task 2: Remove Role from User

  1. Navigate: Admin → Role Management
  2. Find user "John Smith" in list
  3. Locate "Scheduler" badge in his roles
  4. Click "Remove" next to Scheduler
  5. Modal confirms: "Remove Scheduler from John Smith?"
  6. Select reason: "Reassignment"
  7. Click "Confirm"
  8. John loses Scheduler role; loses scheduler permissions ✓

Task 3: Assign Multiple Roles to User

  1. Navigate: Admin → Role Management
  2. Find user "Sarah Davis"
  3. Click "Add Role" → Select "HR Manager" → Assign
  4. Click "Add Role" → Select "Team Lead" → Assign
  5. Sarah now has both roles + combined permissions ✓

Task 4: Search and Filter Users by Role

  1. Navigate: Admin → Role Management
  2. Filter: Select "Team Lead" from role dropdown
  3. System shows all users with Team Lead role
  4. Can further search by name within results ✓

Task 5: Bulk Assign Role to Multiple Users

  1. Navigate: Admin → Role Management
  2. Click "Bulk Assign" button
  3. Upload CSV with user IDs:
    user_id,role_id
    42,3
    43,3
    44,3
  4. System validates and assigns role to all users
  5. Confirmation: "3 users assigned to Team Lead" ✓

Task 6: View Role Assignment History

  1. Navigate: Admin → Role Management
  2. Find user "Jane Doe"
  3. Click "View History" link
  4. Shows:
    • Jan 1, 2025: Team Lead assigned by Admin
    • Dec 15, 2024: Scheduler removed by Admin
    • Nov 1, 2024: Scheduler assigned by Admin
  5. Each entry shows who made change, when, and reason ✓

15. Audit, Notifications & Compliance

Audit logging:

  • Log all role assignments with: user, role, assigned by, timestamp, reason
  • Log all role removals with: user, role, removed by, timestamp, reason
  • Maintain immutable audit trail (1 year retention minimum)
  • Include IP address for assignment/removal actions

Notifications:

  • Role assigned → notify user + manager
  • Role removed → notify user + manager
  • Multiple roles to same user → warn if conflicting
  • System role attempted to remove → prevent + notify admin

Compliance:

  • Role changes logged for audit purposes
  • Prevent removal of all roles from user
  • Prevent assignment of conflicting roles
  • Track role changes for access control compliance
  • Generate role change reports for HR/Compliance

16. Practical Examples

Example 1: Department Leads and Permissions

Department: Sales

Users:
- Alice Johnson: Team Lead
  - Roles: Team Lead, Employee
  - Permissions: view_team_members, approve_leaves, assign_shifts, view_team_reports, + basic employee permissions

- Bob Smith: Team Lead
  - Roles: Team Lead, Employee
  - Permissions: (same as Alice)

- Carol Davis: Employee
  - Roles: Employee
  - Permissions: view_own_profile, apply_leave, view_assigned_shifts

- David Wilson: Employee
  - Roles: Employee
  - Permissions: (same as Carol)

Example 2: Multi-Role User (HR Manager who schedules)

User: Sarah Johnson

Roles:
- HR Manager
  - Permissions: manage_users, view_users, approve_leaves, view_payroll_summary

- Scheduler
  - Permissions: create_shifts, edit_shifts, bulk_assign_shifts, view_schedules

Combined Permissions:
- manage_users, view_users, approve_leaves, view_payroll_summary, create_shifts, edit_shifts, bulk_assign_shifts, view_schedules

Use Case: Sarah manages HR operations and handles shift scheduling

Example 3: Role Hierarchy in Action

Organization Structure:

Super Admin (1 user)
├── Admin (2 users)
│   ├── HR Manager (3 users)
│   │   ├── Team Lead (8 users)
│   │   └── Scheduler (2 users)
│   └── IT Admin (1 user)
└── Employees (150 users)

17. Testing & QA Checklist

Unit tests:

  • User::assignRole() assigns role correctly
  • User::removeRole() removes role correctly
  • User::hasRole() checks role existence
  • ✓ Prevent duplicate role assignment (throws error)
  • ✓ Prevent removing last role (throws error)

Integration tests:

  • ✓ Assign role via form → model_has_roles record created
  • ✓ Remove role via form → model_has_roles record deleted
  • ✓ Role assignment → audit log entry created
  • ✓ Role removal → audit log entry created
  • ✓ Search filters users correctly
  • ✓ Pagination works (10 users per page)

E2E tests (manual):

  • ✓ Admin creates user → assigns "Team Lead" role → user sees team management options
  • ✓ Admin removes "Team Lead" role → user loses management options
  • ✓ Admin assigns multiple roles → user has combined permissions
  • ✓ Admin removes role → user still has other roles
  • ✓ Search returns matching users
  • ✓ System roles (admin, employee) hidden by default

Validation tests:

  • ✓ Cannot assign role that user already has (error message)
  • ✓ Cannot remove user's last role (error message)
  • ✓ Cannot delete role with active users (disabled button)
  • ✓ Role dropdown excludes already-assigned roles

Performance tests:

  • ✓ Index page (1000 users) loads < 1 sec
  • ✓ Search filters < 200ms
  • ✓ Bulk assign (100 users) completes < 10 sec
  • ✓ Role assignment (single) < 100ms

18. Troubleshooting & FAQs

Q: "Cannot assign role — User already has this role"

  • A:
    • Check user's current roles (badge list)
    • Select different role from dropdown
    • Or remove existing role first, then assign new one

Q: "Cannot remove role — User must have at least one role"

  • A:
    • User needs to have at least one role at all times
    • Assign a new role first, then remove the old one
    • Or use "Replace Role" feature (if available)

Q: "Role not appearing in dropdown"

  • A:
    • Role may already be assigned to user
    • Verify role exists in roles table
    • Check role is not a system role (admin, employee)
    • Clear permission cache: php artisan cache:clear

Q: "User assigned role but permissions not taking effect"

  • A:
    • Clear permission cache: php artisan cache:clear
    • User may need to re-login to refresh token
    • Verify role-permission link exists in DB
    • Check role is active (not soft-deleted)

Q: "Search not returning expected results"

  • A:
    • Search only filters by name or email
    • Try searching by different fields
    • Check user status is "active" (inactive users excluded)
    • Verify search term is spelled correctly

Q: "Audit log shows role changes but permissions not updating in DTR"

  • A:
    • DTR sync job may not have run
    • Check queue status: php artisan queue:failed
    • Manually trigger sync: php artisan dtr:sync-permissions {userId}
    • Check DTR service logs for errors

Q: "Cannot delete system role (admin, employee)"

  • A:
    • System roles are protected and cannot be deleted
    • These are core roles required for system operation
    • Custom roles can be deleted if no users assigned

19. Appendix: SQL & Code Examples

SQL: Find all users with a specific role

SELECT u.* FROM users u
JOIN model_has_roles mhr ON u.id = mhr.model_id
JOIN roles r ON mhr.role_id = r.id
WHERE r.name = 'team-lead'
  AND mhr.model_type = 'App\Models\User'
  AND u.status = 'active'
ORDER BY u.last_name;

SQL: Find role assignment history

SELECT al.* FROM activity_log al
WHERE al.log_name IN ('role_assigned', 'role_removed')
  AND al.model_type = 'App\Models\User'
ORDER BY al.created_at DESC
LIMIT 100;

SQL: Count users per role

SELECT r.name, COUNT(mhr.model_id) as user_count
FROM roles r
LEFT JOIN model_has_roles mhr ON r.id = mhr.role_id
WHERE mhr.model_type = 'App\Models\User' OR mhr.model_type IS NULL
GROUP BY r.id
ORDER BY user_count DESC;

SQL: Find roles with no users

SELECT r.* FROM roles r
LEFT JOIN model_has_roles mhr ON r.id = mhr.role_id
WHERE mhr.id IS NULL
ORDER BY r.name;

Laravel Model: User with roles relationship

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;

class User extends Model
{
    use HasRoles;

    protected $fillable = ['first_name', 'last_name', 'email', 'password', 'status'];

    // Spatie provides: roles(), hasRole(), assignRole(), removeRole(), syncRoles()

    public function getRolesAttribute()
    {
        return $this->roles()->pluck('name');
    }

    public function getPermissionsAttribute()
    {
        return $this->getAllPermissions()->pluck('name');
    }
}

Laravel Model: Role with users relationship

namespace Spatie\Permission\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    protected $fillable = ['name', 'guard_name'];

    public function users(): BelongsToMany
    {
        return $this->morphedByMany(
            \App\Models\User::class,
            'model',
            'model_has_roles'
        );
    }

    public function permissions(): BelongsToMany
    {
        return $this->belongsToMany(Permission::class);
    }
}

Blade template: Role assignment modal

<!-- Add Role Modal -->
<div class="modal fade" id="assignRoleModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <form action="{{ route('admin.role.assign', $user->id) }}" method="POST">
                @csrf
                <div class="modal-header">
                    <h5 class="modal-title">Assign Role to {{ $user->name }}</h5>
                    <button type="button" class="close" data-dismiss="modal">
                        <span>&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label>Current Roles</label>
                        <div>
                            @forelse($user->roles as $role)
                                <span class="badge badge-primary">{{ $role->name }}</span>
                            @empty
                                <span class="text-muted">No roles assigned</span>
                            @endforelse
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="role_id">Select Role</label>
                        <select name="role_id" id="role_id" class="form-control" required>
                            <option value="">-- Select a role --</option>
                            @foreach($roles as $role)
                                @unless($user->hasRole($role->name))
                                    <option value="{{ $role->id }}">{{ $role->name }}</option>
                                @endunless
                            @endforeach
                        </select>
                    </div>

                    <div class="form-group">
                        <label for="notes">Notes (optional)</label>
                        <textarea name="notes" id="notes" class="form-control" rows="2"></textarea>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
                    <button type="submit" class="btn btn-primary">Assign Role</button>
                </div>
            </form>
        </div>
    </div>
</div>

Blade template: User roles display with remove button

@foreach($user->roles as $role)
    <span class="badge badge-success">
        {{ $role->name }}
        <form action="{{ route('admin.role.remove', $user->id) }}" method="POST" style="display:inline;">
            @csrf
            <input type="hidden" name="role_id" value="{{ $role->id }}">
            <button type="submit" class="btn-close" title="Remove role"
                onclick="return confirm('Remove {{ $role->name }}?')">
                &times;
            </button>
        </form>
    </span>
@endforeach

Seeder: Create default roles and permissions

namespace Database\Seeders;

use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Illuminate\Database\Seeder;

class RoleAndPermissionSeeder extends Seeder
{
    public function run()
    {
        // Create permissions
        $permissions = [
            'view_users', 'create_users', 'edit_users', 'delete_users',
            'view_roles', 'create_roles', 'edit_roles', 'delete_roles',
            'assign_roles', 'view_shifts', 'create_shifts', 'edit_shifts',
            'assign_shifts', 'approve_leaves', 'view_payroll', 'edit_payroll'
        ];

        foreach ($permissions as $permission) {
            Permission::findOrCreate($permission);
        }

        // Create roles
        $admin = Role::firstOrCreate(['name' => 'admin']);
        $admin->syncPermissions(Permission::all());

        $employee = Role::firstOrCreate(['name' => 'employee']);
        $employee->syncPermissions(['view_own_profile', 'apply_leave', 'view_assigned_shifts']);

        $teamLead = Role::firstOrCreate(['name' => 'team-lead']);
        $teamLead->syncPermissions([
            'view_users', 'view_team_members', 'approve_leaves',
            'assign_shifts', 'view_team_reports'
        ]);

        $hrManager = Role::firstOrCreate(['name' => 'hr-manager']);
        $hrManager->syncPermissions([
            'view_users', 'create_users', 'edit_users', 'delete_users',
            'assign_roles', 'approve_leaves', 'view_payroll'
        ]);
    }
}

Document version: 1.0
Maintainers: HR / Admin / Engineering
Last updated: December 10, 2025