Administrator — Approver Management Documentation

Version: 1.0
Last updated: December 10, 2025

Table of Contents


1. Overview

Approver Management is the core administrative module for managing approval workflows in the DTR System. Administrators can:

  • Assign approvers for leave requests
  • Assign approvers for overtime requests
  • Set approvers by workplace and approval type
  • Manage approver status (active/inactive)
  • Delegate approval authority to backup approvers
  • Track approval history and audit trails
  • Monitor approver workload and pending approvals

Key files involved:

  • Admin views: resources/views/users/admin/approver/ (index, create, edit)
  • Controller: App\Http\Controllers\Web\Admin\ApproverController.php
  • Model: App\Models\Approver, App\Models\User, App\Models\WorkPlace
  • Database: approvers table

2. Purpose & Scope

Purpose:

  • Centralized management of approval workflows
  • Flexible approver assignment by workplace and request type
  • Support for delegation and backup approvers
  • Track who approves what and when
  • Ensure proper authorization hierarchy
  • Monitor approval turnaround times

Scope:

  • Create approvers (assign users as approvers)
  • Read / view approvers and their details
  • Update approver assignments
  • Delete approvers (remove approval authority)
  • Filter approvers by type (leave, overtime, all)
  • Search approvers by user, workplace
  • Track approver creation and modifications
  • Manage delegate approvers

Out of scope:

  • Approval workflow execution (documented separately)
  • Leave request processing (documented separately)
  • Overtime request processing (documented separately)
  • Notification delivery (documented separately)

3. Access & Permissions

Required permissions:

  • view_approvers — view approver list and details
  • create_approvers — create new approver assignments
  • edit_approvers — modify existing approver assignments
  • delete_approvers — remove approver assignments

Recommended roles:

  • Super Admin — full CRUD on approvers, can assign/remove approvers
  • Admin — create/edit/delete approvers, manage across all workplaces
  • HR Manager — view approvers, assign approvers for own workplace(s)
  • Workplace Manager — view approvers, assign approvers for own workplace
  • Team Lead — view approvers assigned to own team (read-only)
  • Employee — view own approver (read-only)

Implementation note:

  • Enforce RBAC server-side via middleware or gate checks
  • UI hides approver management actions (create, edit, delete) for non-admin roles
  • Restrict approver creation to users with hr, admin, or tl roles
  • Use authorize() in controller methods

4. How to Access

Admin URLs:

  • Approvers index: /admin/approvers
  • Filter by type: /admin/approvers?type=leave or /admin/approvers?type=overtime
  • Create approver: /admin/approvers/create
  • Edit approver: /admin/approvers/{id}/edit (via inline edit or modal)
  • Delete approver: /admin/approvers/{id}/delete (POST with confirmation)
  • Search approvers: /admin/approvers?search={query}

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


5. Interface Layout & Views

5.1 Index (Approvers List)

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

View file: resources/views/users/admin/approver/index.blade.php

UI elements:

Top filters & search:

  • Type filter (dropdown):

    • All (default)
    • Leave Approvers
    • Overtime Approvers
  • Search bar: filter by approver name, workplace, email

    • Placeholder: "Search by approver name, workplace..."
    • Real-time AJAX search (optional)
  • Quick actions:

    • Create Approver (button)
    • Export List (CSV/Excel)
    • Refresh

Approvers table:

  • Columns:

    • Approver Name (linked to user profile)
    • Email
    • Workplace (location/office)
    • Approval Type (Leave, Overtime, or Both)
    • Delegate Approver (backup approver name, if assigned)
    • Status (Active/Inactive badge)
    • Created By (admin who created)
    • Created At (timestamp)
    • Actions (Edit, Delete)
  • Pagination: 10 approvers per page (configurable)

  • Sorting: by workplace, creation date (newest first default)

  • Row status indicator:

    • Green badge: Active approver
    • Gray badge: Inactive approver

Key features:

  • Filter by approval type (leave, overtime, all)
  • Quick search via name or workplace
  • Status toggle (inline or modal confirmation)
  • Delete confirmation modal
  • Inline edit link or dedicated edit page
  • Export approvers list to CSV/Excel
  • Color-coded status badges

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

public function index(Request $request)
{
    $type = $request->get('type', 'all');

    $query = Approver::with(['user.profile', 'workPlace', 'delegateApprover.profile'])
        ->when($type !== 'all', function ($query) use ($type) {
            $query->where('approver_for', $type);
        })
        ->orderBy('work_place_id')
        ->orderBy('created_at', 'desc');

    $approvers = $query->paginate(10);

    return view('users.admin.approver.index', compact('approvers'));
}

5.2 Create Approver

Purpose: Assign a user as an approver for a specific workplace and request type.

Trigger: "Create Approver" button on index view.

View file: resources/views/users/admin/approver/create.blade.php

Form fields:

  • User (required, dropdown):

    • List of users with hr, admin, or tl roles
    • Search by name/email (if list is long)
    • Display: "Name (email)" format
    • Validation: Must exist in users table
  • Workplace (required, dropdown):

    • List of all workplaces/locations
    • Display: Workplace name
    • Validation: Must exist in work_places table
  • Approval Type (required, radio or dropdown):

    • Leave — approves leave requests
    • Overtime — approves overtime requests
    • Validation: Must be leave or overtime
  • Delegate Approver (optional, dropdown):

    • List of users with hr, admin, or tl roles
    • Backup/delegate approver (for when primary is absent)
    • Display: "Name (email)" format
    • Validation: Must exist, must have appropriate role, different from primary user
  • Active (optional, checkbox):

    • Toggle approver status (default: checked/active)
    • Validation: Boolean

Form submission (POST /admin/approvers):

  1. Validate all fields
  2. Verify primary user has appropriate role
  3. Verify delegate user (if provided) has appropriate role
  4. Create Approver record with:
    • user_id (primary approver)
    • work_place_id
    • approver_for (leave or overtime)
    • delegate_approver_id (optional)
    • active (default true)
    • created_by (admin user name)
  5. Create audit log entry
  6. Send notification to new approver (email): "You've been assigned as approver"
  7. Redirect to index with success message

Validation:

$validated = $request->validate([
    'user_id' => 'required|exists:users,id',
    'work_place_id' => 'required|exists:work_places,id',
    'approver_for' => 'required|in:leave,overtime',
    'active' => 'boolean',
    'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
]);

// Check if delegate approver has appropriate roles
if ($request->delegate_approver_id) {
    $delegateUser = User::find($request->delegate_approver_id);
    if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
        return back()->withErrors([
            'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
        ])->withInput();
    }
}

Controller method:

public function store(Request $request)
{
    $validated = $request->validate([
        'user_id' => 'required|exists:users,id',
        'work_place_id' => 'required|exists:work_places,id',
        'approver_for' => 'required|in:leave,overtime',
        'active' => 'boolean',
        'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
    ]);

    // Check if delegate approver has appropriate roles
    if ($request->delegate_approver_id) {
        $delegateUser = User::find($request->delegate_approver_id);
        if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
            return back()->withErrors([
                'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
            ])->withInput();
        }
    }

    Approver::create([
        ...$validated,
        'created_by' => Auth::user()->name,
    ]);

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver added successfully');
}

5.3 Edit Approver

Purpose: Modify approver assignment (change workplace, type, delegate, or status).

Trigger: Click "Edit" button next to approver in index view.

View file: resources/views/users/admin/approver/edit.blade.php

Form fields:

Same as create form, except:

  • User (read-only or disabled):

    • Cannot change the primary approver user
    • Display only
  • Workplace (required, dropdown):

    • Can change workplace assignment
    • Current selection highlighted
  • Approval Type (required, radio/dropdown):

    • Can change from leave to overtime or vice versa
    • Current selection highlighted
  • Delegate Approver (optional, dropdown):

    • Can update or remove delegate
    • Current selection highlighted
    • Validation: Must have appropriate role
  • Active (optional, checkbox):

    • Toggle approver status
    • Current state shown

Form submission (PUT /admin/approvers/{id}):

  1. Validate fields
  2. Verify delegate user (if provided) has appropriate role
  3. Update Approver record
  4. Create audit log entry with changes
  5. Send notification to approver of any role/responsibility changes
  6. Redirect to index with success message

Validation:

$validated = $request->validate([
    'work_place_id' => 'required|exists:work_places,id',
    'approver_for' => 'required|in:leave,overtime',
    'active' => 'boolean',
    'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
]);

if ($request->delegate_approver_id) {
    $delegateUser = User::find($request->delegate_approver_id);
    if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
        return back()->withErrors([
            'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
        ])->withInput();
    }
}

Controller method:

public function update(Request $request, Approver $approver)
{
    $validated = $request->validate([
        'work_place_id' => 'required|exists:work_places,id',
        'approver_for' => 'required|in:leave,overtime',
        'active' => 'boolean',
        'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
    ]);

    if ($request->delegate_approver_id) {
        $delegateUser = User::find($request->delegate_approver_id);
        if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
            return back()->withErrors([
                'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
            ])->withInput();
        }
    }

    $approver->update($validated);

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver updated successfully');
}

Delete Approver:

Trigger: Click "Delete" button or link on edit page.

Flow:

  1. Show confirmation modal: "Are you sure you want to remove this approver?"
  2. List pending approvals (count) if any
  3. Option to reassign pending requests or auto-reject
  4. On confirmation:
    • Delete Approver record
    • Create audit log: "Approver removed for {User Name}"
    • Reassign or fail pending requests per admin choice
    • Redirect to index with success message

Controller method:

public function destroy(Approver $approver)
{
    // Optional: Handle pending approvals
    // $pending = LeaveRequest::where('approver_id', $approver->user_id)
    //     ->where('status', 'pending')
    //     ->count();
    // if ($pending > 0) {
    //     // Notify or reassign
    // }

    $approver->delete();

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver removed successfully');
}

6. Key Features

  • Flexible approver assignment: Assign approvers per workplace and request type
  • Delegation support: Assign backup approvers for when primary is unavailable
  • Type filtering: Filter by leave, overtime, or all approvers
  • Status management: Activate/deactivate approvers without deleting
  • Role validation: Ensure approvers have appropriate permissions (hr, admin, tl)
  • Audit trail: Track who created/modified approver assignments
  • Multi-type support: Single user can approve leaves and overtime in different workplaces
  • Backup approver: Workflow can fallback to delegate if primary unavailable
  • Search & filter: Find approvers by name, workplace, type
  • Export capability: Export approvers list to CSV/Excel
  • Status badges: Visual indicators for active/inactive approvers
  • Workplace scoping: Approvers tied to specific workplace/location

7. Data Model & Database

Approver table fields:

id                 — Primary key
user_id            — Foreign key to users (primary approver)
work_place_id      — Foreign key to work_places
approver_for       — Enum: 'leave' or 'overtime'
delegate_approver_id — Foreign key to users (nullable, backup approver)
active             — Boolean (default true)
created_by         — String (admin user name who created)
created_at         — Timestamp
updated_at         — Timestamp
deleted_at         — Timestamp (soft delete, nullable)

Sample records:

Approver 1:

id: 1
user_id: 5 (Jane Doe - HR Manager)
work_place_id: 1 (New York Office)
approver_for: 'leave'
delegate_approver_id: 8 (John Smith - HR Backup)
active: true
created_by: 'Admin'
created_at: '2025-01-01 09:00:00'

Approver 2:

id: 2
user_id: 6 (Alice Johnson - Team Lead)
work_place_id: 2 (San Francisco Office)
approver_for: 'overtime'
delegate_approver_id: null
active: true
created_by: 'Admin'
created_at: '2025-01-05 10:30:00'

Approver 3:

id: 3
user_id: 7 (Bob Wilson - HR Manager)
work_place_id: 1 (New York Office)
approver_for: 'overtime'
delegate_approver_id: 9 (Carol White - HR Backup)
active: false
created_by: 'Admin'
created_at: '2024-12-20 14:00:00'

Relationships:

  • Approver belongsTo User (primary approver)
  • Approver belongsTo User as delegateApprover (backup)
  • Approver belongsTo WorkPlace
  • User hasMany Approver (user can be approver in multiple workplaces)
  • WorkPlace hasMany Approver (workplace can have multiple approvers)

8. Approver Types & Hierarchy

Leave Request Approvers:

Approver Type: leave
├─ Responsibility: Review and approve employee leave requests
├─ Approval Authority: Accept/Reject leave requests
├─ Scope: Specific workplace
└─ Typical Users: HR Manager, HR Officer, Team Lead

Example:
  Jane Doe (HR Manager) approves leave for New York Office
  - Handles all leave requests from NY employees
  - Can delegate to John Smith (HR backup)

Overtime Request Approvers:

Approver Type: overtime
├─ Responsibility: Review and approve overtime requests
├─ Approval Authority: Accept/Reject overtime requests
├─ Scope: Specific workplace
└─ Typical Users: HR Manager, Department Manager, Team Lead

Example:
  Alice Johnson (Team Lead) approves overtime for SF Office
  - Handles overtime requests from her team
  - Ensures budget compliance

Hierarchy by Workplace:

New York Office:
├─ Leave Approver: Jane Doe (HR Manager)
│  └─ Delegate: John Smith (HR Backup)
├─ Overtime Approver: Bob Wilson (HR Manager)
│  └─ Delegate: Carol White (HR Backup)

San Francisco Office:
├─ Leave Approver: Alice Johnson (Team Lead)
│  └─ No delegate
└─ Overtime Approver: Alice Johnson (Team Lead)
   └─ No delegate

9. Approver Assignment

9.1 Single Approver Assignment

Scenario: Assign Jane Doe as leave approver for New York Office.

Steps:

  1. Navigate: Admin → Approvers → Create
  2. Fill form:
    • User: Jane Doe (select from dropdown)
    • Workplace: New York Office
    • Approval Type: Leave
    • Delegate Approver: John Smith (optional)
    • Active: Checked
  3. Click "Create"

Server-side processing:

  1. Validate all inputs
  2. Verify Jane Doe has hr, admin, or tl role
  3. Verify John Smith (delegate) has appropriate role
  4. Create Approver record
  5. Create audit log: "Leave approver assigned: Jane Doe for New York Office"
  6. Send email to Jane: "You've been assigned as a leave approver for New York Office"
  7. Redirect with success message

9.2 Multiple Approval Types

Scenario: Alice Johnson approves both leave and overtime for San Francisco Office.

Steps:

  1. First assignment: Create approver (Alice → Leave → SF)
  2. Second assignment: Create approver (Alice → Overtime → SF)
  3. Result: Alice appears in index twice (once per type) or consolidated view

9.3 With Delegation

Scenario: Assign John Smith as HR backup when Jane is unavailable.

Steps:

  1. Navigate: Admin → Approvers → Create (or Edit existing)
  2. Fill form:
    • User: Jane Doe
    • Workplace: New York Office
    • Approval Type: Leave
    • Delegate Approver: John Smith
    • Active: Checked
  3. Click "Create"

Workflow impact:

  • Primary workflow: Request goes to Jane Doe
  • If Jane unavailable (set status inactive): Request goes to John Smith
  • Fallback logic: System checks primary approver status, routes to delegate if needed

9.4 Removing Approver

Scenario: Remove Bob Wilson as overtime approver (leaving company or role change).

Steps:

  1. Navigate: Admin → Approvers
  2. Find: Bob Wilson, Overtime, New York Office
  3. Click "Edit" or select row
  4. Click "Delete"
  5. Confirmation modal:
    • "Remove Bob Wilson as overtime approver?"
    • "Pending requests: 3" (if any)
    • Options: Reassign to delegate, Auto-reject, Manual resolution
  6. Confirm

Server-side processing:

  1. Check for pending approvals
  2. Handle pending requests per admin choice
  3. Delete Approver record
  4. Create audit log: "Overtime approver removed: Bob Wilson"
  5. Notify Bob: "Your approver status has been revoked"
  6. Redirect with success message

10. Delegation & Backup Approvers

Purpose of delegation:

  • Cover for absent approvers (vacation, sick leave, etc.)
  • Ensure continuity of approval workflow
  • Reduce request bottlenecks
  • Support approver workload distribution

How delegation works:

Scenario 1: Primary approver active
Leave Request → Routed to Jane Doe (primary)

Scenario 2: Primary approver inactive (status = false)
Leave Request → Routed to John Smith (delegate)

Scenario 3: No delegate assigned
Leave Request → Queues or auto-rejects (configurable)

Setting up delegation:

  1. During creation:

    • User: Jane Doe
    • Delegate: John Smith
    • Leave requests go to Jane, fallback to John
  2. Adding delegate to existing:

    • Edit Jane's approver record
    • Add delegate: John Smith
    • Save
  3. Removing delegate:

    • Edit approver record
    • Clear delegate field
    • Save

Temporary approval authority:

If primary approver on leave:

  1. Admin disables primary approver (uncheck "Active")
  2. System automatically routes to delegate
  3. When primary returns:
    • Admin re-enables primary approver
    • System routes back to primary
    • Delegate no longer handles new requests

11. API Endpoints

GET /admin/approvers

  • Returns index view with approvers list
  • Query params: type (leave, overtime, all), search, page
  • Response: HTML view with:
    • $approvers — paginated approvers
    • $type — current filter
public function index(Request $request)
{
    $type = $request->get('type', 'all');

    $query = Approver::with(['user.profile', 'workPlace', 'delegateApprover.profile'])
        ->when($type !== 'all', function ($query) use ($type) {
            $query->where('approver_for', $type);
        })
        ->orderBy('work_place_id')
        ->orderBy('created_at', 'desc');

    $approvers = $query->paginate(10);

    return view('users.admin.approver.index', compact('approvers'));
}

GET /admin/approvers/create

  • Show approver creation form
  • Returns view with:
    • $users — eligible users for approver role
    • $workPlaces — all workplaces
public function create()
{
    $users = User::role(['hr', 'admin', 'tl'])->get();
    $workPlaces = WorkPlace::all();

    return view('users.admin.approver.create', compact('users', 'workPlaces'));
}

POST /admin/approvers

  • Create new approver
  • Body:
    {
        "user_id": 5,
        "work_place_id": 1,
        "approver_for": "leave",
        "delegate_approver_id": 8,
        "active": true
    }
  • Response: Redirect to index with success message

GET /admin/approvers/{id}/edit

  • Show approver edit form
  • Returns view with:
    • $approver — approver record
    • $workPlaces — all workplaces
    • $users — eligible users for delegate role
public function edit(Approver $approver)
{
    $workPlaces = WorkPlace::all();
    $users = User::whereHas('roles', function ($query) {
        $query->whereIn('name', ['hr', 'admin', 'tl']);
    })->get();

    return view('users.admin.approver.edit', compact('approver', 'workPlaces', 'users'));
}

PUT /admin/approvers/{id}

  • Update approver
  • Body: (any of create fields except user_id)
  • Response: Redirect with success message
public function update(Request $request, Approver $approver)
{
    $validated = $request->validate([
        'work_place_id' => 'required|exists:work_places,id',
        'approver_for' => 'required|in:leave,overtime',
        'active' => 'boolean',
        'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
    ]);

    if ($request->delegate_approver_id) {
        $delegateUser = User::find($request->delegate_approver_id);
        if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
            return back()->withErrors([
                'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
            ])->withInput();
        }
    }

    $approver->update($validated);

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver updated successfully');
}

DELETE /admin/approvers/{id}

  • Delete approver
  • Response: Redirect with success message
public function destroy(Approver $approver)
{
    $approver->delete();

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver removed successfully');
}

GET /api/admin/approvers

  • Returns JSON list of approvers (with pagination)
  • Query params: type, workplace_id, search, page
  • Response:
    {
        "data": [
            {
                "id": 1,
                "user": {
                    "id": 5,
                    "name": "Jane Doe",
                    "email": "jane@company.com"
                },
                "workplace": {
                    "id": 1,
                    "name": "New York Office"
                },
                "approver_for": "leave",
                "delegate_approver": {
                    "id": 8,
                    "name": "John Smith"
                },
                "active": true,
                "created_at": "2025-01-01T09:00:00Z"
            }
        ],
        "pagination": { "total": 10, "per_page": 25, "current_page": 1 }
    }

GET /api/admin/approvers/by-workplace/{workplace_id}

  • Get approvers for specific workplace
  • Response:
    {
        "workplace_id": 1,
        "leave_approver": { "id": 5, "name": "Jane Doe", ... },
        "overtime_approver": { "id": 6, "name": "Alice Johnson", ... }
    }

GET /api/admin/approvers/for-request

  • Get approver for specific request type
  • Query params: type (leave/overtime), workplace_id
  • Response:
    {
        "approver": { "id": 5, "name": "Jane Doe", ... },
        "delegate": { "id": 8, "name": "John Smith", ... }
    }

12. Controller Implementation Details

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

index() — List all approvers

public function index(Request $request)
{
    $type = $request->get('type', 'all');

    $query = Approver::with(['user.profile', 'workPlace', 'delegateApprover.profile'])
        ->when($type !== 'all', function ($query) use ($type) {
            $query->where('approver_for', $type);
        })
        ->orderBy('work_place_id')
        ->orderBy('created_at', 'desc');

    $approvers = $query->paginate(10);

    return view('users.admin.approver.index', compact('approvers'));
}

create() — Show create form

public function create()
{
    $users = User::role(['hr', 'admin', 'tl'])->get();
    $workPlaces = WorkPlace::all();

    return view('users.admin.approver.create', compact('users', 'workPlaces'));
}

store() — Create new approver

public function store(Request $request)
{
    $validated = $request->validate([
        'user_id' => 'required|exists:users,id',
        'work_place_id' => 'required|exists:work_places,id',
        'approver_for' => 'required|in:leave,overtime',
        'active' => 'boolean',
        'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
    ]);

    // Check if delegate approver has appropriate roles
    if ($request->delegate_approver_id) {
        $delegateUser = User::find($request->delegate_approver_id);
        if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
            return back()->withErrors([
                'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
            ])->withInput();
        }
    }

    Approver::create([
        ...$validated,
        'created_by' => Auth::user()->name,
    ]);

    // Audit log
    activity()
        ->causedBy(auth()->user())
        ->withProperties([
            'approver_name' => User::find($validated['user_id'])->name,
            'workplace' => WorkPlace::find($validated['work_place_id'])->name,
            'approver_for' => $validated['approver_for']
        ])
        ->log('approver_created');

    // Notification (optional)
    // Mail::queue(new ApproverAssigned($user));

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver added successfully');
}

edit() — Show edit form

public function edit(Approver $approver)
{
    $workPlaces = WorkPlace::all();
    $users = User::whereHas('roles', function ($query) {
        $query->whereIn('name', ['hr', 'admin', 'tl']);
    })->get();

    return view('users.admin.approver.edit', compact('approver', 'workPlaces', 'users'));
}

update() — Update approver

public function update(Request $request, Approver $approver)
{
    $validated = $request->validate([
        'work_place_id' => 'required|exists:work_places,id',
        'approver_for' => 'required|in:leave,overtime',
        'active' => 'boolean',
        'delegate_approver_id' => 'nullable|exists:users,id|different:user_id'
    ]);

    if ($request->delegate_approver_id) {
        $delegateUser = User::find($request->delegate_approver_id);
        if (!$delegateUser->hasAnyRole(['hr', 'admin', 'tl'])) {
            return back()->withErrors([
                'delegate_approver_id' => 'The delegate approver must have appropriate permissions.'
            ])->withInput();
        }
    }

    $approver->update($validated);

    // Audit log
    activity()
        ->causedBy(auth()->user())
        ->performedOn($approver)
        ->withProperties([
            'old' => array_diff_key($approver->getOriginal(), $validated),
            'new' => $validated
        ])
        ->log('approver_updated');

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver updated successfully');
}

destroy() — Delete approver

public function destroy(Approver $approver)
{
    // Audit log
    activity()
        ->causedBy(auth()->user())
        ->performedOn($approver)
        ->withProperties(['approver_name' => $approver->user->name])
        ->log('approver_deleted');

    $approver->delete();

    return redirect()->route('admin.approvers.index')
        ->with('success', 'Approver removed successfully');
}

13. Filters, Search & Reports

Quick filters:

  • All approvers
  • Leave approvers only
  • Overtime approvers only

Search functionality:

  • Search by approver name
  • Search by workplace name
  • Search by email
  • Real-time AJAX search (optional)

Advanced filters (optional):

  • Filter by active status
  • Filter by workplace
  • Filter by user role
  • Filter by creation date range

Reports:

  • Approver workload: Number of pending requests per approver
  • Coverage report: Approvers by workplace and request type
  • Delegation report: Primary and delegate approvers
  • Audit trail: Approver assignment history (who created, when, changes)
  • Gap analysis: Workplaces missing approvers

Export formats: CSV, Excel


14. Integrations (Leave / Overtime)

Leave Request Integration:

  • When leave request submitted: System finds approver for that workplace + leave type
  • Approver determined by: Workplace + approver_for='leave'
  • If primary inactive: Routes to delegate approver
  • Approver receives notification: New leave request pending
  • Approver can approve/reject: Updates leave request status

Overtime Request Integration:

  • When overtime submitted: System finds approver for that workplace + overtime type
  • Approver determined by: Workplace + approver_for='overtime'
  • If primary inactive: Routes to delegate
  • Approver receives notification: New overtime request pending
  • Approver can approve/reject: Updates overtime request status

Auto-routing logic:

// Find approver for request
$approver = Approver::where('work_place_id', $request->workplace_id)
    ->where('approver_for', 'leave') // or 'overtime'
    ->where('active', true)
    ->first();

if (!$approver) {
    // Check for delegate
    $approver = Approver::where('work_place_id', $request->workplace_id)
        ->where('approver_for', 'leave')
        ->with('delegateApprover')
        ->first();

    if ($approver && $approver->delegateApprover) {
        $approver = $approver->delegateApprover;
    } else {
        // No approver found - queue or auto-reject
        $request->status = 'no_approver';
    }
}

$request->approver_id = $approver->user_id;
$request->save();

15. Common Tasks & Workflows

Task 1: Assign Leave Approver

  1. Navigate: Admin → Approvers → Create
  2. User: Jane Doe
  3. Workplace: New York Office
  4. Type: Leave
  5. Delegate: John Smith
  6. Click "Create"
  7. Jane assigned as leave approver; John is backup ✓

Task 2: Assign Multiple Types

  1. Create approver: Alice → San Francisco → Leave
  2. Create approver: Alice → San Francisco → Overtime
  3. Alice now approves both types in SF ✓

Task 3: Update Delegate

  1. Navigate: Admin → Approvers
  2. Find: Jane Doe, Leave, NY
  3. Click "Edit"
  4. Change Delegate: John Smith → Carol White
  5. Click "Update"
  6. Carol now backs up Jane ✓

Task 4: Deactivate Approver

  1. Navigate: Admin → Approvers
  2. Find: Bob Wilson (Overtime, NY)
  3. Click "Edit"
  4. Uncheck "Active"
  5. Click "Update"
  6. Requests route to delegate Carol White ✓

Task 5: Remove Approver

  1. Navigate: Admin → Approvers
  2. Find: Bob Wilson (Overtime, NY)
  3. Click "Delete"
  4. Confirm: "Remove as approver?"
  5. Handle pending requests (reassign or auto-reject)
  6. Bob removed as approver ✓

Task 6: View All Leave Approvers

  1. Navigate: Admin → Approvers
  2. Filter: Type = "Leave"
  3. View all leave approvers by workplace
  4. See coverage for each office ✓

16. Audit, Notifications & Compliance

Audit logging:

  • Log all approver assignments with: user, approver, workplace, type, created by, timestamp
  • Log all approver updates with: changes, updated by, timestamp
  • Log all approver deletions with: who deleted, when, approver details
  • Maintain immutable audit trail (1 year retention minimum)

Notifications:

  • Approver assigned → notify user: "You've been assigned as {type} approver for {workplace}"
  • Approver updated → notify user of role/responsibility changes
  • Approver removed → notify user: "Your approver status has been revoked"
  • Delegate assigned → notify delegate: "You're backup approver for {primary user}"

Compliance:

  • Approver changes logged for audit purposes
  • Ensure primary and delegate have appropriate roles
  • Prevent conflicts of interest (optional: don't allow self-delegation)
  • Track approver assignment history
  • Generate coverage reports for compliance audits

17. Practical Examples

Example 1: Multi-Location Approver Setup

Company with 3 offices:

New York Office:
├─ Leave Approver: Jane Doe (HR Manager)
│  └─ Delegate: John Smith (HR Specialist)
├─ Overtime Approver: Bob Wilson (Operations Manager)
│  └─ No delegate

San Francisco Office:
├─ Leave Approver: Alice Johnson (HR Manager)
│  └─ Delegate: Carol White (HR Assistant)
├─ Overtime Approver: David Lee (Operations Manager)
│  └─ Delegate: Emma Brown (Ops Specialist)

Chicago Office:
├─ Leave Approver: Frank Martinez (HR Manager)
│  └─ Delegate: Grace Kim (HR Assistant)
└─ Overtime Approver: Frank Martinez (dual role)
   └─ Delegate: Grace Kim

Example 2: Approver with Delegation

Jane Doe (Primary Leave Approver for NY)
├─ Role: HR Manager
├─ Approves: Leave requests for NY office
├─ Pending requests: 5
├─ Avg response time: 24 hours
└─ Delegate: John Smith
   ├─ Role: HR Specialist
   ├─ Takes over when Jane unavailable
   └─ Current status: Active

Workflow:
1. Employee submits leave request for NY
2. Request routed to Jane Doe
3. If Jane inactive: Request routed to John Smith
4. John approves/rejects on Jane's behalf

Example 3: Approver Transition

Scenario: Jane Doe retiring, transitioning to Alice Johnson

Action Plan:
1. Create new approver: Alice Johnson → Leave → NY
2. Set Jane as Alice's delegate (temporary, during transition)
3. Announce change to employees
4. Jane handles remaining requests with Alice learning
5. After transition period: Remove Jane as approver
6. Alice becomes sole leave approver for NY

18. Testing & QA Checklist

Unit tests:

  • ✓ Approver::create validates required fields
  • ✓ Approver::update prevents invalid roles
  • ✓ Delegate must have appropriate role
  • ✓ Delegate cannot be same as primary user
  • ✓ Approver::delete soft-deletes record

Integration tests:

  • ✓ Create approver → record created in DB
  • ✓ Update approver → changes saved
  • ✓ Delete approver → soft delete applied
  • ✓ Filter by type → returns correct approvers
  • ✓ Leave request routes to correct approver
  • ✓ Overtime request routes to correct approver
  • ✓ Fallback to delegate when primary inactive

E2E tests (manual):

  • ✓ Admin creates approver → appears in index
  • ✓ Admin filters by type → only selected type shown
  • ✓ Admin updates approver → changes reflected
  • ✓ Admin deletes approver → removed from list
  • ✓ Employee submits leave → routed to approver
  • ✓ Approver receives notification
  • ✓ Approver can approve/reject request
  • ✓ Delegate handles request when primary inactive

Permission tests:

  • ✓ Non-admin cannot create approvers
  • ✓ HR/admin/tl can be selected as approver
  • ✓ Other roles cannot be selected
  • ✓ Only authorized roles can access approver management

19. Troubleshooting & FAQs

Q: "Approver not found for leave request"

  • A:
    • Check if approver exists for employee's workplace
    • Verify approver type is set to 'leave'
    • Verify approver is active (status = true)
    • Check if delegate exists if primary is inactive
    • Create missing approver via Admin → Approvers → Create

Q: "Cannot save approver - delegate has wrong role"

  • A:
    • Delegate user must have hr, admin, or tl role
    • Assign appropriate role to delegate user first
    • Or select different delegate with proper role

Q: "Requests going to wrong approver"

  • A:
    • Verify employee's workplace is correct
    • Verify approver is assigned to correct workplace
    • Check if multiple approvers exist for same type/workplace
    • Verify approver is active (not inactive)
    • Check system logs for routing logic

Q: "Delegate approver not receiving requests"

  • A:
    • Primary approver may still be active (check status)
    • Set primary approver to inactive to trigger fallback
    • Verify delegate is properly linked to primary
    • Check if delegate has permissions to approve

Q: "Cannot edit approver - user_id shows as read-only"

  • A:
    • By design, cannot change primary approver user
    • Create new approver with different user if needed
    • Delete old approver and create new one

Q: "Search not returning expected approvers"

  • A:
    • Search only filters by name, workplace, email
    • Try searching by different fields
    • Check approver is active (inactive ones not shown)
    • Clear filters and try again

Q: "Approver appears twice in list"

  • A:
    • Normal behavior - same user can be approver for multiple types
    • For example: Jane Doe as leave approver and overtime approver
    • Each approval type creates separate record

20. Appendix: SQL & Code Examples

SQL: Find approvers for specific workplace

SELECT a.*, u.first_name, u.last_name, w.name as workplace_name
FROM approvers a
JOIN users u ON a.user_id = u.id
JOIN work_places w ON a.work_place_id = w.id
WHERE a.work_place_id = 1 AND a.active = 1
ORDER BY a.approver_for;

SQL: Find leave approvers by workplace

SELECT a.id, u.name, u.email, w.name as workplace,
       da.name as delegate_name, a.active
FROM approvers a
JOIN users u ON a.user_id = u.id
JOIN work_places w ON a.work_place_id = w.id
LEFT JOIN users da ON a.delegate_approver_id = da.id
WHERE a.approver_for = 'leave'
ORDER BY w.name, a.created_at DESC;

SQL: Count pending requests per approver

SELECT a.user_id, u.name, COUNT(lr.id) as pending_count
FROM approvers a
JOIN users u ON a.user_id = u.id
LEFT JOIN leave_requests lr ON a.user_id = lr.approver_id
    AND lr.status = 'pending'
WHERE a.approver_for = 'leave' AND a.active = 1
GROUP BY a.user_id
ORDER BY pending_count DESC;

SQL: Find approvers with delegates

SELECT a.id, u.name as primary_approver, da.name as delegate,
       w.name as workplace, a.approver_for
FROM approvers a
JOIN users u ON a.user_id = u.id
JOIN users da ON a.delegate_approver_id = da.id
JOIN work_places w ON a.work_place_id = w.id
WHERE a.delegate_approver_id IS NOT NULL
ORDER BY w.name;

Laravel Model: Approver with relationships

namespace App\Models;

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

class Approver extends Model
{
    protected $fillable = [
        'user_id', 'work_place_id', 'approver_for',
        'delegate_approver_id', 'active', 'created_by'
    ];

    protected $casts = [
        'active' => 'boolean',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function workPlace(): BelongsTo
    {
        return $this->belongsTo(WorkPlace::class);
    }

    public function delegateApprover(): BelongsTo
    {
        return $this->belongsTo(User::class, 'delegate_approver_id');
    }

    // Scopes
    public function scopeActive($query)
    {
        return $query->where('active', true);
    }

    public function scopeForLeave($query)
    {
        return $query->where('approver_for', 'leave');
    }

    public function scopeForOvertime($query)
    {
        return $query->where('approver_for', 'overtime');
    }
}

Laravel: Find approver for request

// In LeaveRequest or OvertimeRequest model/service

public function findApprover($workplaceId, $type = 'leave')
{
    $approver = Approver::where('work_place_id', $workplaceId)
        ->where('approver_for', $type)
        ->where('active', true)
        ->with('delegateApprover')
        ->first();

    if (!$approver) {
        return null;
    }

    // Return delegate if primary inactive
    if ($approver->active === false && $approver->delegateApprover) {
        return $approver->delegateApprover;
    }

    return $approver->user;
}

Blade template: Approvers table

@foreach($approvers as $approver)
    <tr>
        <td>{{ $approver->user->name }}</td>
        <td>{{ $approver->user->email }}</td>
        <td>{{ $approver->workPlace->name }}</td>
        <td>
            <span class="badge badge-{{ $approver->approver_for === 'leave' ? 'info' : 'warning' }}">
                {{ ucfirst($approver->approver_for) }}
            </span>
        </td>
        <td>
            @if($approver->delegateApprover)
                {{ $approver->delegateApprover->name }}
            @else
                <span class="text-muted">—</span>
            @endif
        </td>
        <td>
            <span class="badge badge-{{ $approver->active ? 'success' : 'secondary' }}">
                {{ $approver->active ? 'Active' : 'Inactive' }}
            </span>
        </td>
        <td>
            <a href="{{ route('admin.approvers.edit', $approver->id) }}" class="btn btn-sm btn-primary">Edit</a>
            <form action="{{ route('admin.approvers.destroy', $approver->id) }}" method="POST" style="display:inline;">
                @csrf @method('DELETE')
                <button type="submit" class="btn btn-sm btn-danger"
                    onclick="return confirm('Are you sure?')">Delete</button>
            </form>
        </td>
    </tr>
@endforeach

Blade template: Create form

<form action="{{ route('admin.approvers.store') }}" method="POST">
    @csrf

    <div class="form-group">
        <label for="user_id">Approver</label>
        <select name="user_id" id="user_id" class="form-control" required>
            <option value="">— Select Approver —</option>
            @foreach($users as $user)
                <option value="{{ $user->id }}">{{ $user->name }} ({{ $user->email }})</option>
            @endforeach
        </select>
        @error('user_id')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <div class="form-group">
        <label for="work_place_id">Workplace</label>
        <select name="work_place_id" id="work_place_id" class="form-control" required>
            <option value="">— Select Workplace —</option>
            @foreach($workPlaces as $workplace)
                <option value="{{ $workplace->id }}">{{ $workplace->name }}</option>
            @endforeach
        </select>
        @error('work_place_id')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <div class="form-group">
        <label for="approver_for">Approval Type</label>
        <select name="approver_for" id="approver_for" class="form-control" required>
            <option value="">— Select Type —</option>
            <option value="leave">Leave</option>
            <option value="overtime">Overtime</option>
        </select>
        @error('approver_for')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <div class="form-group">
        <label for="delegate_approver_id">Delegate Approver (Optional)</label>
        <select name="delegate_approver_id" id="delegate_approver_id" class="form-control">
            <option value="">— None —</option>
            @foreach($users as $user)
                <option value="{{ $user->id }}">{{ $user->name }}</option>
            @endforeach
        </select>
        @error('delegate_approver_id')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <div class="form-group">
        <label for="active">
            <input type="checkbox" name="active" id="active" value="1" checked>
            Active
        </label>
        @error('active')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <button type="submit" class="btn btn-primary">Create Approver</button>
    <a href="{{ route('admin.approvers.index') }}" class="btn btn-secondary">Cancel</a>
</form>

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