Active Directory & HR Integration API Documentation

Comprehensive documentation for integrating with Mock AD Server and HR APIs. This documentation provides implementation examples for both Laravel and Node.js environments.

Note: Response structures may differ from actual production APIs. It's recommended to use a component-based approach for integration to ensure consistency across projects.

List of Features

  • Active Directory Integration
  • HR Management
  • Case Management
  • Recovery Management

Configuration

.env

Environment Configuration


# Active Directory Configuration
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_VERSION=3
LDAP_BASE_DN=dc=corporate,dc=local

# HR API Configuration
HR_API_BASE_URL=http://127.0.0.1:8000
HR_API_KEY=your_api_key_here
HR_API_TIMEOUT=30

# CASE MANAGER API Configuration
CASE_MANAGER_API_BASE_URL=http://127.0.0.1:8000
CASE_MANAGER_API_KEY=your_api_key_here
CASE_MANAGER_API_TIMEOUT=30
                    
config/ldap.php

LDAP Configuration File


<?php

return [
    'default' => [
        'host' => env('LDAP_HOST', 'localhost'),
        'port' => env('LDAP_PORT', 389),
        'version' => env('LDAP_VERSION', 3),
        'base_dn' => env('LDAP_BASE_DN', 'dc=corporate,dc=local'),
        'timeout' => 5,
        'use_ssl' => false,
        'use_tls' => false,
    ],
];
                    
.env

Environment Configuration


# Active Directory Configuration
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_VERSION=3
LDAP_BASE_DN=dc=corporate,dc=local

# HR API Configuration
HR_API_BASE_URL=http://127.0.0.1:8000
HR_API_KEY=your_api_key_here
HR_API_TIMEOUT=30000

# CASE MANAGER API Configuration
CASE_MANAGER_API_BASE_URL=http://127.0.0.1:8000
CASE_MANAGER_API_KEY=your_api_key_here
CASE_MANAGER_API_TIMEOUT=30000
                    
config/config.js

Configuration File


require('dotenv').config();

module.exports = {
    ldap: {
        host: process.env.LDAP_HOST || 'localhost',
        port: parseInt(process.env.LDAP_PORT) || 389,
        version: parseInt(process.env.LDAP_VERSION) || 3,
        baseDN: process.env.LDAP_BASE_DN || 'dc=corporate,dc=local',
        timeout: 5000
    },
    hrApi: {
        baseURL: process.env.HR_API_BASE_URL || 'http://127.0.0.1:8000',
        apiKey: process.env.HR_API_KEY,
        timeout: parseInt(process.env.HR_API_TIMEOUT) || 30000
    },
    caseManagerApi: {
        baseURL: process.env.CASE_MANAGER_API_BASE_URL || 'http://127.0.0.1:8000',
        apiKey: process.env.CASE_MANAGER_API_KEY,
        timeout: parseInt(process.env.CASE_MANAGER_API_TIMEOUT) || 30000
    }
};
                    

Active Directory Connection

POST

Connect to Mock AD Server

LDAP Connection

Original PHP Code:


php -r '$conn = ldap_connect("localhost", 389); 
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); 
ldap_bind($conn, "admins@corporate.local", "Admin123!"); 
echo $bind ? "Bind successful\n" : "Bind failed: " . ldap_error($conn) . "\n";'
                    

Laravel Implementation:


<?php

namespace App\Services;

use Exception;

class ActiveDirectoryService
{
    private $connection;
    private $config;

    public function __construct(array $config = null)
    {
        $this->config = $config ?? config('ldap.default');
    }

    /**
     * Connect to Active Directory
     */
    public function connect($username,$password): bool
    {
        try {
            $this->connection = ldap_connect(
                $this->config['host'], 
                $this->config['port']
            );

            if (!$this->connection) {
                throw new Exception('Could not connect to LDAP server');
            }

            // Set protocol version
            ldap_set_option(
                $this->connection, 
                LDAP_OPT_PROTOCOL_VERSION, 
                $this->config['version']
            );

            // Set referral option
            ldap_set_option(
                $this->connection, 
                LDAP_OPT_REFERRALS, 
                0
            );

            // Bind to LDAP server
            $bind = ldap_bind(
                $this->connection,
                $username,
                $password
            );

            if (!$bind) {
                throw new Exception(
                    'LDAP bind failed: ' . ldap_error($this->connection)
                );
            }

            return true;
        } catch (Exception $e) {
            logger()->error('LDAP Connection Error: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Disconnect from Active Directory
     */
    public function disconnect(): void
    {
        if ($this->connection) {
            ldap_unbind($this->connection);
            $this->connection = null;
        }
    }

    
    public function __destruct()
    {
        $this->disconnect();
    }
}
                    
Usage Example:

// In Controller
$adService = new ActiveDirectoryService();
$connected = $adService->connect('admins@corporate.local', 'Admin123!');

if ($connected) {
   //Do your own authentication logic here
}
                        

Node.js Implementation:


const ldap = require('ldapjs');
const config = require('../config/config');

class ActiveDirectoryService {
    constructor() {
        this.client = null;
        this.config = config.ldap;
    }

/**
 * Connect to Active Directory with provided credentials
 * @param {string} username - LDAP username (e.g., admins@corporate.local)
 * @param {string} password - LDAP password
 */
async connect(username, password) {
    return new Promise((resolve, reject) => {
        if (!username || !password) {
            reject(new Error('Username and password are required'));
            return;
        }

        this.client = ldap.createClient({
            url: `ldap://${this.config.host}:${this.config.port}`,
            timeout: this.config.timeout,
            reconnect: false, // Disable auto-reconnect when using dynamic credentials
            tlsOptions: this.config.useTls ? {
                rejectUnauthorized: false // Allow self-signed certificates
            } : undefined
        });

        this.client.on('error', (err) => {
            console.error('LDAP client error:', err.message);
            this.client.destroy(); // Clean up failed connection
            reject(new Error(`LDAP connection failed: ${err.message}`));
        });

        this.client.on('connect', () => {
            console.log('LDAP connection established');
        });

        this.client.on('close', () => {
            console.log('LDAP connection closed');
        });

        // Bind to LDAP server with provided credentials
        this.client.bind(
            username,
            password,
            (err) => {
                if (err) {
                    console.error('LDAP bind error:', err.message);
                    this.client.destroy(); // Clean up failed bind
                    
                    // Provide more specific error messages
                    if (err.code === 49) {
                        reject(new Error('Invalid credentials - Authentication failed'));
                    } else if (err.code === 81) {
                        reject(new Error('Server not available - Connection refused'));
                    } else if (err.code === -1) {
                        reject(new Error('Connection timeout'));
                    } else {
                        reject(new Error(`LDAP bind failed: ${err.message}`));
                    }
                } else {
                    console.log('LDAP bind successful');
                    
                    //Do your own authentication logic here
                    
                    resolve({
                        success: true,
                        message: 'Authentication successful',
                        user: username,
                        timestamp: new Date().toISOString()
                    });
                }
            }
        );
    });
}
    /**
     * Disconnect from Active Directory
     */
    async disconnect() {
        return new Promise((resolve) => {
            if (this.client) {
                this.client.unbind(() => {
                    this.client = null;
                    resolve();
                });
            } else {
                resolve();
            }
        });
    }

   
    /**
     * Test connection
     */
    async testConnection() {
        try {
            await this.connect();
            await this.disconnect();
            return { success: true, message: 'Connection successful' };
        } catch (error) {
            return { 
                success: false, 
                message: `Connection failed: ${error.message}` 
            };
        }
    }
}

module.exports = ActiveDirectoryService;
                    

HR/Case Manager API Services

app/Services/HrApiService.php

HR/Case Manager API Service Class


<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Exception;

class HrApiService
{
    private $baseUrl;
    private $apiKey;
    private $timeout;

    public function __construct()
    {
        $this->baseUrl = config('services.hr_api.base_url');
        $this->apiKey = config('services.hr_api.api_key');
        $this->timeout = config('services.hr_api.timeout', 30);
    }

    /**
     * Make authenticated request to HR API
     */
    private function makeRequest(string $method, string $endpoint, array $params = [])
    {
        try {
            $response = Http::withHeaders([
                'X-API-KEY' => $this->apiKey,
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ])
            ->timeout($this->timeout)
            ->$method("{$this->baseUrl}{$endpoint}", $method === 'get' ? $params : []);

            if ($response->successful()) {
                return $response->json();
            }

            throw new Exception(
                "HR API Error: {$response->status()} - " . $response->body()
            );
        } catch (Exception $e) {
            logger()->error('HR API Request Failed: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Get employees list
     */
    public function getEmployees(array $filters = [])
    {
        $defaultFilters = [
            'search' => null,  //The value here can be employee ID, first name, last name, personal email, personal phone
            'status' => null, //The value here can be active, inactive
            'department_id' => null, //The value here can be department ID
            'per_page' => 20,
            'sort' => 'asc'
        ];

        $filters = array_merge($defaultFilters, $filters);
        
        return $this->makeRequest('get', '/api/v1/employees', $filters);
    }

    /**
     * Get departments
     */
    public function getDepartments(bool $activeOnly = true, bool $withHierarchy = false)
    {
        return $this->makeRequest('get', '/api/v1/departments', [
            'active_only' => $activeOnly, //The value here is either true or false
            'with_hierarchy' => $withHierarchy //The value here is either true or false
        ]);
    }

    /**
     * Get zones/branches
     */
    public function getZones(bool $isActive = true)
    {
        return $this->makeRequest('get', '/api/v1/zones', [
            'is_active' => $isActive //The value here is either true or false
        ]);
    }

    /**
     * Cache departments for 1 hour
     */
    public function getCachedDepartments(): array
    {
        return Cache::remember('hr_departments', 3600, function () {
            return $this->getDepartments();
        });
    }

    /**
     * Cache zones for 1 hour
     */
    public function getCachedZones(): array
    {
        return Cache::remember('hr_zones', 3600, function () {
            return $this->getZones();
        });
    }
}
                    
app/Services/CaseManagerApiService.php

Case Manager API Service Class


 <?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Exception;

class CaseManagerApiService
{
    private $baseUrl;
    private $apiKey;
    private $timeout;

    public function __construct()
    {
        $this->baseUrl = config('services.case_manager_api.base_url');
        $this->apiKey = config('services.case_manager_api.api_key');
        $this->timeout = config('services.case_manager_api.timeout', 30);
    }

    /**
     * Make authenticated request to Case Manager API
     */
    private function makeRequest(string $method, string $endpoint, array $params = [])
    {
        try {
            $response = Http::withHeaders([
                'X-API-KEY' => $this->apiKey,
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ])
            ->timeout($this->timeout)
            ->$method("{$this->baseUrl}{$endpoint}", $method === 'get' ? $params : []);

            if ($response->successful()) {
                return $response->json();
            }

            throw new Exception(
                "HR API Error: {$response->status()} - " . $response->body()
            );
        } catch (Exception $e) {
            logger()->error('HR API Request Failed: ' . $e->getMessage());
            throw $e;
        }
    }

                     /**
     * Get cases
     */
    public function getCases(array $filters = [])
    {
        $defaultFilters = [
            'status' => null, //Expected value is either active or inactive
            'priority' => null, //Expected value is either low, medium, high
            'assigned_to' => null, //Expected value is the user id
            'date_from' => null, //Expected value is the date from
            'date_to' => null, //Expected value is the date to
            'search' => null, //Expected value are case number, title, suspects first name or last name, organization name
            'sort_order' => 'asc' //Expected value is either asc or desc
        ];

        $filters = array_merge($defaultFilters, $filters);
        
        return $this->makeRequest('get', '/api/v1/cases', $filters);
    }

    /**
     * Get suspects
     */
    public function getSuspects(array $filters = [])
    {
        $defaultFilters = [
            'type' => 'individual', //Expected value is either individual or organisation
            'status' => null, //Expected value is either active or inactive
            'risk_level' => null, //Expected value is either low, medium, high
            'search' => null, //Expected value are first name, last name, identity number, organization name, tax identification number
            'case_number' => null //Expected value is the case number
        ];

        $filters = array_merge($defaultFilters, $filters);
        
        return $this->makeRequest('get', '/api/v1/suspects', $filters);
    }

    /**
     * Get case recovery for a single case
     */
    public function getCaseRecovery(int $caseId)
    {
        return $this->makeRequest('get', "/api/v1/case/{$caseId}/recovery");
    }

    /**
     * Get recovery summary
     */
    public function getRecoverySummary(array $filters = [])
    {
        $defaultFilters = [
            'zone_id' => null, //Expected value is the zone id
            'type' => null, //Expected value is either individual or organisation
            'status' => null, //Expected value is either active or inactive
            'date_from' => null, //Expected value is the date from
            'date_to' => null, //Expected value is the date to
            'case_id' => null, //Expected value is the case id
            'suspect_id' => null //Expected value is the suspect id
        ];

        $filters = array_merge($defaultFilters, $filters);
        
        return $this->makeRequest('get', '/api/v1/case/recovery/summary', $filters);
    }
}
                    
app/Http/Controllers/Api/IntegrationController.php

Sample Controller


<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Services\ActiveDirectoryService;
use App\Services\HrApiService;
use App\Services\CaseManagerApiService;
use Illuminate\Http\Request;

class IntegrationController extends Controller
{
    protected $adService;
    protected $hrService;
    protected $caseManagerApiService;

    public function __construct(ActiveDirectoryService $adService, HrApiService $hrService, CaseManagerApiService $caseManagerApiService)
    {
        $this->adService = $adService;
        $this->hrService = $hrService;
        $this->caseManagerApiService = $caseManagerApiService;
    }

    /**
     * Test AD connection
     */
    public function testAdConnection()
    {
        try {
            $connected = $this->adService->connect();
            
            return response()->json([
                'success' => true,
                'message' => 'AD Connection successful',
                'data' => [
                    'connection_status' => $connected
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Get employees with search
     */
    public function getEmployees(Request $request)
    {
        $validated = $request->validate([
            'search' => 'nullable|string|max:255',
            'status' => 'nullable|string',
            'department_id' => 'nullable|integer',
            'per_page' => 'nullable|integer|min:1|max:100',
            'sort' => 'nullable|in:asc,desc'
        ]);

        try {
            $employees = $this->hrService->getEmployees($validated);
            
            return response()->json([
                'success' => true,
                'data' => $employees,
                'meta' => [
                    'total' => $employees['total'] ?? count($employees),
                    'filters' => $validated
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to fetch employees: ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * Get departments
     */
    public function getDepartments()
    {
        try {
            $departments = $this->hrService->getCachedDepartments();
            
            return response()->json([
                'success' => true,
                'data' => $departments,
                'meta' => [
                    'count' => count($departments),
                    'source' => 'cached'
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to fetch departments: ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * Get cases with filters
     */
    public function getCases(Request $request)
    {
        $validated = $request->validate([
            'status' => 'nullable|string',
            'priority' => 'nullable|string',
            'assigned_to' => 'nullable|integer',
            'date_from' => 'nullable|date',
            'date_to' => 'nullable|date|after_or_equal:date_from',
            'search' => 'nullable|string',
            'sort_order' => 'nullable|in:asc,desc'
        ]);

        try {
            $cases = $this->caseManagerApiService->getCases($validated);
            
            return response()->json([
                'success' => true,
                'data' => $cases,
                'meta' => [
                    'filters_applied' => array_filter($validated),
                    'results_count' => count($cases)
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to fetch cases: ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * Get recovery summary
     */
    public function getRecoverySummary(Request $request)
    {
        $validated = $request->validate([
            'zone_id' => 'nullable|integer',
            'type' => 'nullable|string',
            'status' => 'nullable|string',
            'date_from' => 'nullable|date',
            'date_to' => 'nullable|date|after_or_equal:date_from',
            'case_id' => 'nullable|integer',
            'suspect_id' => 'nullable|integer'
        ]);

        try {
            $summary = $this->caseManagerApiService->getRecoverySummary($validated);
            
            return response()->json([
                'success' => true,
                'data' => $summary,
                'meta' => [
                    'filters' => $validated,
                    'generated_at' => now()->toDateTimeString()
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to fetch recovery summary: ' . $e->getMessage()
            ], 500);
        }
    }

    /**
     * Get suspect details
     */
    public function getSuspectDetails($id)
    {
        try {
            $suspects = $this->caseManagerApiService->getSuspects(['case_number' => "CASE-{$id}"]);
            
            return response()->json([
                'success' => true,
                'data' => $suspects,
                'meta' => [
                    'case_number' => "CASE-{$id}",
                    'suspects_count' => count($suspects)
                ]
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to fetch suspect details: ' . $e->getMessage()
            ], 500);
        }
    }
}
                    
services/HrApiService.js

HR API Service Class


const axios = require('axios');
const NodeCache = require('node-cache');
const config = require('../config/config');

class HrApiService {
    constructor() {
        this.baseURL = config.hrApi.baseURL;
        this.apiKey = config.hrApi.apiKey;
        this.timeout = config.hrApi.timeout;
        this.cache = new NodeCache({ stdTTL: 3600 }); // 1 hour cache
        
        this.client = axios.create({
            baseURL: this.baseURL,
            timeout: this.timeout,
            headers: {
                'X-API-KEY': this.apiKey,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        });
    }

    /**
     * Make authenticated request to HR API
     */
    async makeRequest(method, endpoint, params = {}) {
        try {
            const response = await this.client({
                method,
                url: endpoint,
                [method.toLowerCase() === 'get' ? 'params' : 'data']: params
            });

            return response.data;
        } catch (error) {
            console.error('HR API Request Failed:', error.message);
            
            if (error.response) {
                throw new Error(
                    `HR API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
                );
            } else if (error.request) {
                throw new Error('HR API Error: No response received');
            } else {
                throw new Error(`HR API Error: ${error.message}`);
            }
        }
    }

    /**
     * Get employees list
     */
    async getEmployees(filters = {}) {
        const defaultFilters = {
            search: null, // Search by employee ID, first name, last name, personal email, personal phone
            status: null, // Active or Inactive
            department_id: null, // Department ID
            per_page: 20, // Number of items per page
            sort: 'asc' // Sort order
        };

        const mergedFilters = { ...defaultFilters, ...filters };
        
        return this.makeRequest('get', '/api/v1/employees', mergedFilters);
    }

    /**
     * Get departments
     */
    async getDepartments(activeOnly = true, withHierarchy = false) {
        return this.makeRequest('get', '/api/v1/departments', {
            active_only: activeOnly, // true or false
            with_hierarchy: withHierarchy // true or false
        });
    }

   
    /**
     * Get cached departments
     */
    async getCachedDepartments() {
        const cacheKey = 'hr_departments';
        let departments = this.cache.get(cacheKey);

        if (!departments) {
            departments = await this.getDepartments();
            this.cache.set(cacheKey, departments);
        }

        return departments;
    }

    /**
     * Get cached zones
     */
    async getCachedZones() {
        const cacheKey = 'hr_zones';
        let zones = this.cache.get(cacheKey);

        if (!zones) {
            zones = await this.getZones();
            this.cache.set(cacheKey, zones);
        }

        return zones;
    }

    /**
     * Clear cache
     */
    clearCache() {
        this.cache.flushAll();
        console.log('HR API cache cleared');
    }
}

module.exports = HrApiService;
                    
services/CaseManagerApiService.js

Case Manager API Service Class


const axios = require('axios');
const NodeCache = require('node-cache');
const config = require('../config/config');

class CaseManagerApiService {
    constructor() {
        this.baseURL = config.caseManagerApi.baseURL;
        this.apiKey = config.caseManagerApi.apiKey;
        this.timeout = config.caseManagerApi.timeout;
        this.cache = new NodeCache({ stdTTL: 3600 }); // 1 hour cache
        
        this.client = axios.create({
            baseURL: this.baseURL,
            timeout: this.timeout,
            headers: {
                'X-API-KEY': this.apiKey,
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        });
    }

    /**
     * Make authenticated request to HR API
     */
    async makeRequest(method, endpoint, params = {}) {
        try {
            const response = await this.client({
                method,
                url: endpoint,
                [method.toLowerCase() === 'get' ? 'params' : 'data']: params
            });

            return response.data;
        } catch (error) {
            console.error('HR API Request Failed:', error.message);
            
            if (error.response) {
                throw new Error(
                    `HR API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
                );
            } else if (error.request) {
                throw new Error('HR API Error: No response received');
            } else {
                throw new Error(`HR API Error: ${error.message}`);
            }
        }
    }


    /**
     * Get cases
     */
    async getCases(filters = {}) {
        const defaultFilters = {
            status: null, // active or inactive
            priority: null, // high, medium, low
            assigned_to: null, // user id
            date_from: null, // date from
            date_to: null, // date to
            search: null, // search by case number, title, suspects first name or last name, organization name
            sort_order: 'asc' // asc or desc
        };

        const mergedFilters = { ...defaultFilters, ...filters };
        
        return this.makeRequest('get', '/api/v1/cases', mergedFilters);
    }

    /**
     * Get suspects
     */
    async getSuspects(filters = {}) {
        const defaultFilters = {
            type: 'individual', // individual or organization
            status: null, // active or inactive
            risk_level: null, // high, medium, low
            search: null, // search by first name, last name, identity number, organization name, tax identification number
            case_number: null // case number
        };

        const mergedFilters = { ...defaultFilters, ...filters };
        
        return this.makeRequest('get', '/api/v1/suspects', mergedFilters);
    }

    /**
     * Get case recovery for a single case
     */
    async getCaseRecovery(caseId) {
        return this.makeRequest('get', `/api/v1/case/${caseId}/recovery`);
    }

    /**
     * Get recovery summary
     */
    async getRecoverySummary(filters = {}) {
        const defaultFilters = {
            zone_id: null, //Zone ID
            type: null, //Case or Suspect
            status: null, //Active or Inactive
            date_from: null, //Date from
            date_to: null, //Date to
            case_id: null, //Case ID
            suspect_id: null //Suspect ID
        };

        const mergedFilters = { ...defaultFilters, ...filters };
        
        return this.makeRequest('get', '/api/v1/case/recovery/summary', mergedFilters);
    }

    /**
     * Clear cache
     */
    clearCache() {
        this.cache.flushAll();
        console.log('Case Manager API cache cleared');
    }
}

module.exports = CaseManagerApiService;
                    
routes/api.js

Sample Route Handlers


const express = require('express');
const router = express.Router();
const ActiveDirectoryService = require('../services/ActiveDirectoryService');
const HrApiService = require('../services/HrApiService');
const CaseManagerApiService = require('../services/CaseManagerApiService');

const adService = new ActiveDirectoryService();
const hrService = new HrApiService();
const caseManagerService = new CaseManagerApiService();

/**
 * Test AD connection
 */
router.get('/test-ad-connection', async (req, res) => {
    try {
        await adService.connect();
        
        res.json({
            success: true,
            message: 'AD Connection successful',
            data: {
                connection_status: true,
                timestamp: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: error.message
        });
    }
});

/**
 * Get employees with search
 */
router.get('/employees', async (req, res) => {
    try {
        const filters = {
            search: req.query.search || null,
            status: req.query.status || null,
            department_id: req.query.department_id || null,
            per_page: req.query.per_page || 20,
            sort: req.query.sort || 'asc'
        };

        const employees = await hrService.getEmployees(filters);
        
        res.json({
            success: true,
            data: employees,
            meta: {
                total: employees.total || employees.length,
                filters: filters,
                timestamp: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: `Failed to fetch employees: ${error.message}`
        });
    }
});

/**
 * Get departments
 */
router.get('/departments', async (req, res) => {
    try {
        const departments = await hrService.getCachedDepartments();
        
        res.json({
            success: true,
            data: departments,
            meta: {
                count: departments.length,
                source: 'cached',
                timestamp: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: `Failed to fetch departments: ${error.message}`
        });
    }
});

/**
 * Get cases with filters
 */
router.get('/cases', async (req, res) => {
    try {
        const filters = {
            status: req.query.status || null,
            priority: req.query.priority || null,
            assigned_to: req.query.assigned_to || null,
            date_from: req.query.date_from || null,
            date_to: req.query.date_to || null,
            search: req.query.search || null,
            sort_order: req.query.sort_order || 'asc'
        };

        const cases = await caseManagerService.getCases(filters);
        
        res.json({
            success: true,
            data: cases,
            meta: {
                filters_applied: Object.fromEntries(
                    Object.entries(filters).filter(([_, v]) => v != null)
                ),
                results_count: cases.length,
                timestamp: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: `Failed to fetch cases: ${error.message}`
        });
    }
});

/**
 * Get recovery summary
 */
router.get('/recovery-summary', async (req, res) => {
    try {
        const filters = {
            zone_id: req.query.zone_id || null,
            type: req.query.type || null,
            status: req.query.status || null,
            date_from: req.query.date_from || null,
            date_to: req.query.date_to || null,
            case_id: req.query.case_id || null,
            suspect_id: req.query.suspect_id || null
        };

        const summary = await caseManagerService.getRecoverySummary(filters);
        
        res.json({
            success: true,
            data: summary,
            meta: {
                filters: filters,
                generated_at: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: `Failed to fetch recovery summary: ${error.message}`
        });
    }
});

/**
 * Get suspect details
 */
router.get('/suspects/:caseId', async (req, res) => {
    try {
        const suspects = await caseManagerService.getSuspects({
            case_number: `CASE-${req.params.caseId}`
        });
        
        res.json({
            success: true,
            data: suspects,
            meta: {
                case_number: `CASE-${req.params.caseId}`,
                suspects_count: suspects.length,
                timestamp: new Date().toISOString()
            }
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            message: `Failed to fetch suspect details: ${error.message}`
        });
    }
});

module.exports = router;
                    

Sample API Responses

GET

Employees Response


{
    "success": true,
    "data": {
        "employees": [
            {
                "id": 2,
                "employee_id": "EMP20260001",
                "first_name": "Joda",
                "middle_name": null,
                "last_name": "Gbage",
                "gender": "Male",
                "personal_email": null,
                "personal_phone": null,
                "signature": "https://onlinepngtools.com/images/examples-onlinepngtools/george-walker-bush-signature.jpg",
                "employment_status": "active",
                "position": {
                    "id": 4,
                    "code": "JL-02",
                    "title": "Junior Staff 2",
                    "job_level_id": 1,
                    "cadre_id": 2,
                    "job_level": {
                        "id": 1,
                        "code": "lv-8",
                        "name": "Level 8",
                        "level": 8
                    },
                    "cadre": {
                        "id": 2,
                        "code": "cad-02",
                        "name": "Cadre 02"
                    }
                },
                "reports_to": null
            }
        ],
        "pagination": {
            "total": 1,
            "per_page": 20,
            "current_page": 1,
            "last_page": 1
        }
    }
}
                    
GET

Cases Response


{
    "success": true,
    "data": [
        {
            "id": 201,
            "case_number": "CASE-4266233",
            "title": "Quasi minus eius eveniet voluptatem ratione labore laudantium.",
            "description": "Vero corrupti eius voluptate tenetur vel et occaecati. Aut provident veritatis deserunt labore excepturi ex.\n\nRerum consectetur nulla doloremque et aut enim labore. Molestiae est voluptatem iusto dolores officia error. Ullam aspernatur necessitatibus et et. Quasi laudantium quo cupiditate rerum nam magnam. Vitae temporibus tempore maiores voluptatem.\n\nAliquam ea alias itaque necessitatibus ut non qui. Fugiat voluptatem in occaecati omnis. Accusamus quo soluta et minima molestias.",
            "status": "closed",
            "priority": "medium",
            "confidential_level": "secret",
            "amount_involved": null,
            "formatted_amount": "NGN0.00",
            "currency": "NGN",
            "date_reported": "2023-06-05",
            "date_opened": "2023-04-05",
            "date_closed": "2023-06-05",
            "estimated_completion_date": "2023-06-05",
            "actual_completion_date": "2023-06-05",
            "assigned_to": {
                "id": 1,
                "name": "Test User",
                "badge_number": "23425678",
                "role": "Investigator"
            },
            "created_by": {
                "id": 1,
                "name": "Test User",
                "badge_number": "23425678"
            },
            "suspects": [
                {
                    "id": 39,
                    "type": "organization",
                    "first_name": null,
                    "last_name": null,
                    "full_name": "Raynor Inc",
                    "identity_number": "9005766531",
                    "organization_name": "Raynor Inc",
                    "gender": null,
                    "nationality": "ZW",
                    "occupation": null,
                    "status": "wanted",
                    "risk_level": "high",
                    "notes": null
                },
                {
                    "id": 112,
                    "type": "individual",
                    "first_name": "Caleigh",
                    "last_name": "Lubowitz",
                    "full_name": "Caleigh Lubowitz",
                    "identity_number": "0267976094",
                    "organization_name": null,
                    "gender": "female",
                    "nationality": "KN",
                    "occupation": "Therapist",
                    "status": "under_surveillance",
                    "risk_level": "high",
                    "notes": null
                }
            ],
            "is_active": false,
            "days_open": 2.049283914664352,
            "reporting_lag": null,
            "investigation_duration": null,
            "created_at": "2026-01-07 21:24:21",
            "updated_at": "2026-01-07 21:24:21"
        }
    ],
    "meta": {
        "total": 1,
        "per_page": 20,
        "current_page": 1,
        "last_page": 1
    }
}
                    
GET

Recovery Summary


{
    "success": true,
    "summary": {
        "total_recoveries": 85,
        "total_estimated_value": "1901280133.15",
        "total_actual_value": "386508666.87",
        "recoveries_by_zone": [
            {
                "id": 1,
                "title": "Head Office Zone 2, Abuja",
                "code": "2343567",
                "region": "FCT",
                "country": "Nigeria",
                "description": "Head Office",
                "requirements": null,
                "is_active": true,
                "created_at": null,
                "updated_at": null,
                "deleted_at": null,
                "count": 85,
                "total_estimated_value": "1901280133.15",
                "total_actual_value": "386508666.87"
            }
        ],
        "recoveries_by_type": [
            {
                "type": "cash",
                "count": 19,
                "total_estimated_value": "124967998.36",
                "total_actual_value": "5297114.36",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "type": "property",
                "count": 9,
                "total_estimated_value": "100787713.98",
                "total_actual_value": null,
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "type": "vehicle",
                "count": 15,
                "total_estimated_value": "408238642.74",
                "total_actual_value": "51672146.65",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "type": "real_estate",
                "count": 18,
                "total_estimated_value": "944342021.86",
                "total_actual_value": "313329460.64",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "type": "financial_instrument",
                "count": 11,
                "total_estimated_value": "168379595.46",
                "total_actual_value": "4884157.65",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "type": "digital_asset",
                "count": 13,
                "total_estimated_value": "154564160.75",
                "total_actual_value": "11325787.57",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            }
        ],
        "recoveries_by_status": [
            {
                "status": "identified",
                "count": 17,
                "total_estimated_value": "390643543.82",
                "total_actual_value": null,
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 25,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "status": "frozen",
                "count": 14,
                "total_estimated_value": "349036580.45",
                "total_actual_value": null,
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 50,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "status": "seized",
                "count": 20,
                "total_estimated_value": "463553451.01",
                "total_actual_value": "79200928.00",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 75,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "status": "forfeited",
                "count": 23,
                "total_estimated_value": "372845505.68",
                "total_actual_value": null,
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 90,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "status": "auctioned",
                "count": 11,
                "total_estimated_value": "325201052.19",
                "total_actual_value": "307307738.87",
                "is_active": false,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 100,
                "case": null,
                "suspect": null,
                "zone": null
            }
        ],
        "recoveries_by_month": [
            {
                "month": "2025-07",
                "count": 6,
                "total_estimated_value": "49702476.46",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-06",
                "count": 14,
                "total_estimated_value": "309052561.02",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-05",
                "count": 16,
                "total_estimated_value": "468135261.83",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-04",
                "count": 8,
                "total_estimated_value": "192978860.70",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-03",
                "count": 16,
                "total_estimated_value": "503156400.45",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-02",
                "count": 14,
                "total_estimated_value": "239761610.73",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            },
            {
                "month": "2025-01",
                "count": 11,
                "total_estimated_value": "138492961.96",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            }
        ],
        "top_cases": [
            {
                "id": 223,
                "case_number": "CASE-3119121",
                "title": "Qui quaerat nobis pariatur nam perferendis dolores et.",
                "recovery_count": 4,
                "total_estimated_value": "166932767.70",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            }
        ],
        "top_suspects": [
            {
                "id": 37,
                "first_name": "Rory",
                "last_name": "Gibson",
                "organization_name": null,
                "recovery_count": 2,
                "total_estimated_value": "118053493.43",
                "is_active": true,
                "days_in_custody": 0,
                "formatted_estimated_value": "N/A",
                "formatted_actual_value": "Not yet valued",
                "recovery_progress": 0,
                "case": null,
                "suspect": null,
                "zone": null
            }
        ]
    },
    "latest_recoveries": [
        {
            "id": 147,
            "recovery_number": "REC-2026-000062",
            "title": "Cryptocurrency Wallet",
            "type": "digital_asset",
            "status": "seized",
            "estimated_value": "6498554.45",
            "actual_value": null,
            "formatted_estimated_value": "₦6,498,554.45",
            "formatted_actual_value": "Not yet valued",
            "currency": "NGN",
            "description": "Qui facere qui molestias excepturi tempora vero rerum molestiae. Voluptas id officiis deserunt incidunt. Earum inventore dolore hic explicabo sunt accusantium.",
            "location_found": "60461 Gislason Green\nCloydberg, NE 69554",
            "date_identified": "2025-07-09",
            "date_frozen": "2025-10-08",
            "date_seized": "2025-11-15",
            "date_forfeited": null,
            "date_disposed": null,
            "disposal_method": null,
            "disposal_notes": null,
            "legal_basis": null,
            "court_order_number": null,
            "zone": {
                "id": 1,
                "name": "Head Office Zone 2, Abuja",
                "code": "2343567",
                "region": "FCT"
            },
            "zone_id": 1,
            "case": {
                "id": 231,
                "case_number": "CASE-7920069",
                "title": "Necessitatibus aspernatur sint et vel voluptatem maiores.",
                "status": "pending"
            },
            "case_id": 231,
            "suspect": {
                "id": 91,
                "full_name": "Charlene Bins",
                "type": "individual",
                "organization_name": null,
                "status": "under_surveillance"
            },
            "suspect_id": 91,
            "storage_location": null,
            "is_active": true,
            "days_in_custody": 55.96164080956019,
            "recovery_progress": 75,
            "metadata": {
                "source": "data_analysis",
                "risk_level": "low"
            },
            "status_timeline": {
                "identified": "2025-07-09",
                "frozen": "2025-10-08",
                "seized": "2025-11-15",
                "forfeited": null,
                "disposed": null
            },
            "value_difference": null,
            "value_percentage_change": null,
            "created_at": "2026-01-09 14:18:51",
            "updated_at": "2026-01-09 14:18:51"
        }
    ]
}
                    
GET

Suspects Response


{
    "success": true,
    "data": [
        {
            "id": 112,
            "type": "individual",
            "first_name": "Caleigh",
            "last_name": "Lubowitz",
            "full_name": "Caleigh Lubowitz",
            "identity_number": "0267976094",
            "organization_name": null,
            "gender": "female",
            "nationality": "KN",
            "occupation": "Therapist",
            "status": "under_surveillance",
            "risk_level": "high",
            "notes": null,
            "cases": [
                {
                    "id": 201,
                    "case_number": "CASE-4266233",
                    "title": "Quasi minus eius eveniet voluptatem ratione labore laudantium.",
                    "status": "closed",
                    "priority": "medium",
                    "pivot": {
                        "role": "beneficiary",
                        "involvement_description": "Description for suspect 112 in case 201"
                    }
                },
                {
                    "id": 219,
                    "case_number": "CASE-0915165",
                    "title": "Aut distinctio et quibusdam recusandae est.",
                    "status": "under_investigation",
                    "priority": "high",
                    "pivot": {
                        "role": "beneficiary",
                        "involvement_description": "Description for suspect 112 in case 219"
                    }
                },
                {
                    "id": 260,
                    "case_number": "CASE-0240581",
                    "title": "Quam laudantium assumenda maxime possimus.",
                    "status": "pending",
                    "priority": "medium",
                    "pivot": {
                        "role": "accomplice",
                        "involvement_description": "Description for suspect 112 in case 260"
                    }
                }
            ],
            "cases_count": 3,
            "recoveries_count": 3,
            "active_cases": 2
        },
        {
            "id": 54,
            "type": "individual",
            "first_name": "Landen",
            "last_name": "Cartwright",
            "full_name": "Landen Cartwright",
            "identity_number": "9103966922",
            "organization_name": null,
            "gender": "female",
            "nationality": "AS",
            "occupation": "Assessor",
            "status": "in_custody",
            "risk_level": "low",
            "notes": null,
            "cases": [
                {
                    "id": 260,
                    "case_number": "CASE-0240581",
                    "title": "Quam laudantium assumenda maxime possimus.",
                    "status": "pending",
                    "priority": "medium",
                    "pivot": {
                        "role": "witness",
                        "involvement_description": "Description for suspect 54 in case 260"
                    }
                },
                {
                    "id": 292,
                    "case_number": "CASE-7866361",
                    "title": "Quod at perspiciatis inventore incidunt ad velit beatae.",
                    "status": "closed",
                    "priority": "high",
                    "pivot": {
                        "role": "other",
                        "involvement_description": "Description for suspect 54 in case 292"
                    }
                },
                {
                    "id": 285,
                    "case_number": "CASE-4250116",
                    "title": "Inventore in accusamus qui veritatis ullam sit et.",
                    "status": "pending",
                    "priority": "critical",
                    "pivot": {
                        "role": "beneficiary",
                        "involvement_description": "Description for suspect 54 in case 285"
                    }
                }
            ],
            "cases_count": 3,
            "recoveries_count": 0,
            "active_cases": 2
        }
    ],
    "meta": {
        "total": 2,
        "per_page": 20,
        "current_page": 1,
        "last_page": 1
    }
}