1094 lines
42 KiB
PHP
1094 lines
42 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
use CodeIgniter\RESTful\ResourceController;
|
|
use App\Controllers\BaseController;
|
|
use App\Models\AgentModel;
|
|
use App\Models\AgentIncentiveFileModel;
|
|
use App\Models\PartnerGridDetailsModel;
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
|
|
class AgentController extends ResourceController
|
|
{
|
|
protected $AgentModel;
|
|
protected $AgentIncentiveFileModel;
|
|
protected $PartnerGridDetailsModel;
|
|
|
|
public function __construct()
|
|
{
|
|
helper('jwt_helper');
|
|
$this->AgentModel = new AgentModel();
|
|
$this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
|
|
$this->PartnerGridDetailsModel = new PartnerGridDetailsModel();
|
|
}
|
|
|
|
private function getAuthenticatedUserData(): ?object
|
|
{
|
|
$header = $this->request->getHeaderLine('Authorization');
|
|
if (!$header || !preg_match('/Bearer\s(\S+)/', $header, $matches)) {
|
|
return null;
|
|
}
|
|
|
|
$decodedToken = validateJWT($matches[1]);
|
|
if (!$decodedToken || !isset($decodedToken['data'])) {
|
|
return null;
|
|
}
|
|
|
|
return $decodedToken['data'];
|
|
}
|
|
|
|
private function canManagePartnerGrid(?string $role): bool
|
|
{
|
|
return in_array((string) $role, ['1', '4'], true);
|
|
}
|
|
|
|
// List of all agents
|
|
public function agentList()
|
|
{
|
|
try{
|
|
|
|
$id = $this->request->getGet('manager_id');
|
|
|
|
$data = $this->AgentModel->select('partner_agent.*,partner_sales_executive.name as sales_executive_name')
|
|
->join('partner_sales_executive', 'partner_sales_executive.id = partner_agent.sales_executive_id', 'left')
|
|
->where('partner_agent.manager_id' , $id )
|
|
->findAll();
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
|
|
}
|
|
|
|
public function agentListForEnquiryCreationDropdown()
|
|
{
|
|
try{
|
|
|
|
$id = $this->request->getGet('manager_id');
|
|
|
|
$data = $this->AgentModel->where('manager_id' , $id )->where('is_active',1)->findAll();
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
|
|
}
|
|
|
|
// Find a agent
|
|
public function findAgent()
|
|
{
|
|
try{
|
|
|
|
$id = $this->request->getGet('id');
|
|
|
|
$record = $this->AgentModel->find((int)$id);
|
|
|
|
if (!$record) {
|
|
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200);
|
|
}
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $record], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// CREATE agent
|
|
public function createAgent()
|
|
{
|
|
try{
|
|
$data = $this->request->getPost();
|
|
|
|
// handle file uploads
|
|
$certificateFile = $this->request->getFile('certificate_file_name');
|
|
|
|
$certificateFileName = null;
|
|
|
|
// certificate upload
|
|
if ($certificateFile && $certificateFile->isValid()) {
|
|
$uploadPath = WRITEPATH . 'uploads/agent/certificate_file/';
|
|
if (!is_dir($uploadPath)) {
|
|
mkdir($uploadPath, 0777, true);
|
|
}
|
|
$certificateFileName = time() . '_' . $certificateFile->getRandomName();
|
|
$certificateFile->move($uploadPath, $certificateFileName);
|
|
}
|
|
|
|
$insertData = [
|
|
'name' => $data['name'],
|
|
'email' => $data['email'],
|
|
'mobile' => $data['mobile'],
|
|
'agent_code' => $data['agent_code'],
|
|
'address' => $data['address'],
|
|
'manager_id' => $data['manager_id'],
|
|
'sales_executive_id' => $data['sales_executive_id'],
|
|
'certificate_file_name' => $certificateFileName,
|
|
'created_by' => $data['created_by'],
|
|
'retention_rate' => $data['retention_rate'] ?? 0
|
|
];
|
|
|
|
$this->AgentModel->insert($insertData);
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// UPDATE agent
|
|
public function updateAgent()
|
|
{
|
|
try{
|
|
|
|
$data = $this->request->getPost();
|
|
if (!isset($data['id'])) {
|
|
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'ID Required'], 200);
|
|
}
|
|
|
|
$id = $data['id'];
|
|
|
|
// check if agent exists
|
|
$agent = $this->AgentModel->find((int)$id);
|
|
if (!$agent) {
|
|
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Date Not Found'], 200);
|
|
}
|
|
|
|
// handle file uploads
|
|
$certificateFile = $this->request->getFile('certificate_file_name');
|
|
|
|
$updateData = [
|
|
'name' => $data['name'] ?? null,
|
|
'email' => $data['email'] ?? null,
|
|
'mobile' => $data['mobile'] ?? null,
|
|
'address' => $data['address'] ?? null,
|
|
'agent_code' => $data['agent_code'] ?? null,
|
|
'sales_executive_id' => $data['sales_executive_id'] ?? null,
|
|
'updated_by' => $data['updated_by'] ?? null,
|
|
'retention_rate' => $data['retention_rate'] ?? $agent['retention_rate']
|
|
];
|
|
|
|
if ($certificateFile && $certificateFile->isValid()) {
|
|
$uploadPath = WRITEPATH . 'uploads/agent/certificate_file/';
|
|
if (!is_dir($uploadPath)) {
|
|
mkdir($uploadPath, 0777, true);
|
|
}
|
|
$certificateFileName = time() . '_' . $certificateFile->getRandomName();
|
|
$certificateFile->move($uploadPath, $certificateFileName);
|
|
$updateData['certificate_file_name'] = $certificateFileName;
|
|
}
|
|
|
|
$this->AgentModel->update($id, $updateData);
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
//update device token
|
|
public function updateDeviceToken()
|
|
{
|
|
try{
|
|
|
|
$data = $this->request->getPost();
|
|
if (!isset($data['id'])) {
|
|
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'ID Required'], 200);
|
|
}
|
|
|
|
$id = $data['id'];
|
|
|
|
// check if agent exists
|
|
$agent = $this->AgentModel->find((int)$id);
|
|
if (!$agent) {
|
|
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Date Not Found'], 200);
|
|
}
|
|
|
|
|
|
$updateData = [
|
|
'firebase_device_token' => $data['firebase_device_token'] ?? null,
|
|
];
|
|
|
|
$this->AgentModel->update($id, $updateData);
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// ACTIVATE / DEACTIVATE agent
|
|
public function changeAgentStatus()
|
|
{
|
|
try {
|
|
$data = $this->request->getJSON(true);
|
|
|
|
// validate inputs
|
|
if (!isset($data['id']) || !isset($data['is_active'])) {
|
|
return $this->respond(['status' => 'failed', 'code' => 200,'data' => 'ID and Status are required'], 200);
|
|
}
|
|
|
|
$id = $data['id'];
|
|
$is_active = (int) $data['is_active']; // 0 or 1
|
|
|
|
$status = ($is_active == 1) ? 0 : 1;
|
|
|
|
// check if agent exists
|
|
$agent = $this->AgentModel->find((int)$id);
|
|
if (!$agent) {
|
|
return $this->respond(['status' => 'failed','code' => 200, 'data' => 'Agent not found'], 200);
|
|
}
|
|
|
|
// update status
|
|
$this->AgentModel->update($id, ['is_active' => $status]);
|
|
|
|
return $this->respond([
|
|
'status' => 'success', 'code' => 200,'data' => "Agent status updated"], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed','code' => 500,'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// Download agent certificate file
|
|
public function downloadAgentCertificateFile()
|
|
{
|
|
try {
|
|
$id = $this->request->getGet('agent_id');
|
|
|
|
if (!$id) {
|
|
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200);
|
|
}
|
|
|
|
// Fetch record from DB
|
|
$fileRecord = $this->AgentModel->where('is_active',1)->find((int)$id);
|
|
|
|
if (!$fileRecord) {
|
|
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200);
|
|
}
|
|
|
|
$filePath = WRITEPATH . 'uploads/agent/certificate_file/' . $fileRecord['certificate_file_name'];
|
|
|
|
if (!file_exists($filePath)) {
|
|
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 200);
|
|
}
|
|
|
|
// Force file download
|
|
return $this->response->download($filePath, null);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'message' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// List of all agents incentive files
|
|
public function agentIncentiveFileList()
|
|
{
|
|
try{
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$roleId = (string) ($authUser->role_id ?? 'agent');
|
|
$agentIdFromRequest = $this->request->getGet('agent_id');
|
|
$fileType = trim((string) ($this->request->getGet('file_type') ?? ''));
|
|
|
|
// Partner can only view own files; manager/accounts can view all.
|
|
$agentId = $this->canManagePartnerGrid($roleId) ? $agentIdFromRequest : ($authUser->id ?? null);
|
|
if (empty($agentId)) {
|
|
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'agent_id is required'], 200);
|
|
}
|
|
|
|
$builder = $this->AgentIncentiveFileModel
|
|
->where('agent_id', (int) $agentId)
|
|
->where('is_active', 1);
|
|
|
|
if ($fileType !== '') {
|
|
$builder->where('file_type', $fileType);
|
|
}
|
|
|
|
$data = $builder->orderBy('id', 'DESC')->findAll();
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
|
|
}
|
|
|
|
// Upload agent incentive file
|
|
public function uploadAgentIncentiveFile()
|
|
{
|
|
try{
|
|
$data = $this->request->getPost();
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$roleId = (string) ($authUser->role_id ?? 'agent');
|
|
$isGridUpload = isset($data['file_type']) && $data['file_type'] === 'grid';
|
|
|
|
if ($isGridUpload && !$this->canManagePartnerGrid($roleId)) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 403,
|
|
'data' => 'Only Manager and Accounts can upload/edit partner grid files'
|
|
], 403);
|
|
}
|
|
|
|
if ($isGridUpload) {
|
|
$this->uploadGridFile($data);
|
|
} else {
|
|
|
|
$duplicateData = $this->AgentIncentiveFileModel
|
|
->where('agent_id', $data['agent_id'])
|
|
->where('incentive_month', $data['incentive_month'])
|
|
->first();
|
|
|
|
if (!empty($duplicateData)) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 200,
|
|
'data' => 'Duplicate Entry.'
|
|
], 200);
|
|
}
|
|
}
|
|
// handle file uploads
|
|
$incentiveFile = $this->request->getFile('incentive_file_name');
|
|
|
|
$incentiveFileName = null;
|
|
|
|
// incentive upload
|
|
if ($incentiveFile && $incentiveFile->isValid()) {
|
|
$uploadPath = WRITEPATH . 'uploads/agent/incentive_file/';
|
|
if (!is_dir($uploadPath)) {
|
|
mkdir($uploadPath, 0777, true);
|
|
}
|
|
$incentiveFileName = time() . '_' . $incentiveFile->getRandomName();
|
|
$incentiveFile->move($uploadPath, $incentiveFileName);
|
|
}
|
|
|
|
$insertData = [
|
|
'agent_id' => $data['agent_id'],
|
|
'incentive_month' => $data['incentive_month'],
|
|
'incentive_file_name' => $incentiveFileName,
|
|
'file_type' => $data['file_type'] ?? 'incentive',
|
|
'created_by' => $data['created_by'] ?? null
|
|
];
|
|
|
|
$this->AgentIncentiveFileModel->insert($insertData);
|
|
|
|
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────
|
|
// Grid Functionality
|
|
// ─────────────────────────────────────────
|
|
public function uploadGridFile($data){
|
|
$gridFile = $this->request->getFile('incentive_file_name');
|
|
|
|
if (!$gridFile || !$gridFile->isValid()) {
|
|
throw new \RuntimeException('Valid grid file is required');
|
|
}
|
|
|
|
$extension = strtolower((string) $gridFile->getExtension());
|
|
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
|
|
throw new \RuntimeException('Only xlsx, xls, csv grid files are allowed');
|
|
}
|
|
|
|
$spreadsheet = IOFactory::load($gridFile->getTempName());
|
|
$rows = $spreadsheet->getActiveSheet()->toArray(null, true, true, false);
|
|
|
|
if (empty($rows)) {
|
|
throw new \RuntimeException('Grid file is empty');
|
|
}
|
|
|
|
$normalize = static function ($value): string {
|
|
$value = strtolower(trim((string) $value));
|
|
return preg_replace('/[^a-z0-9]+/', '', $value) ?? '';
|
|
};
|
|
$toNumber = static function ($value): float {
|
|
if (is_numeric($value)) {
|
|
return (float) $value;
|
|
}
|
|
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
|
return (float) $matches[0];
|
|
}
|
|
return 0.0;
|
|
};
|
|
$toNullable = static function ($value): ?string {
|
|
$value = trim((string) $value);
|
|
return $value === '' ? null : $value;
|
|
};
|
|
$formatNumber = static function (float $value): string {
|
|
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
|
};
|
|
$parsePremium = static function ($value) use ($formatNumber): ?string {
|
|
$source = trim((string) $value);
|
|
if ($source === '') {
|
|
return null;
|
|
}
|
|
if (preg_match('/-?\d+(?:\.\d+)?/', $source, $matches) !== 1) {
|
|
return null;
|
|
}
|
|
return $formatNumber((float) $matches[0]);
|
|
};
|
|
|
|
// Fixed format: first row is header and data starts from row 2.
|
|
$headerRowIndex = 0;
|
|
$headerMap = [];
|
|
$headerRow = $rows[$headerRowIndex] ?? [];
|
|
foreach ($headerRow as $colIndex => $cell) {
|
|
$normalizedHeader = $normalize($cell);
|
|
if ($normalizedHeader === 'type') {
|
|
$headerMap['vehicle_type_id'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'insurer') {
|
|
$headerMap['insurer_id'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'rto') {
|
|
$headerMap['rto_id'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'segment') {
|
|
$headerMap['segment_id'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'comp') {
|
|
$headerMap['comp'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'tp') {
|
|
$headerMap['tp'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'fuel') {
|
|
$headerMap['fuel'] = $colIndex;
|
|
} elseif ($normalizedHeader === 'remarks') {
|
|
$headerMap['remarks'] = $colIndex;
|
|
}
|
|
}
|
|
|
|
if (!isset($headerMap['insurer_id'], $headerMap['rto_id'], $headerMap['segment_id'], $headerMap['vehicle_type_id'])) {
|
|
throw new \RuntimeException('Invalid grid header. Required: TYPE, INSURER, RTO, SEGMENT');
|
|
}
|
|
|
|
$defaultPartnerId = !empty($data['agent_id']) ? (int) $data['agent_id'] : null;
|
|
$createdBy = $data['created_by'] ?? null;
|
|
|
|
$db = \Config\Database::connect();
|
|
|
|
$vehicleTypeMaster = $db->table('vehicle_type')->select('id, vehicle_type')->where('is_active', 1)->get()->getResultArray();
|
|
|
|
$insurerMaster = $db->table('insurers')->select('id, name, short_name')->where('is_active', 1)->get()->getResultArray();
|
|
|
|
$rtoMaster = $db->table('rto_master')->select('id, rto_code, rto_name')->where('is_active', 1)->get()->getResultArray();
|
|
|
|
$partnerMaster = $db->table('partner_agent')->select('id, name, agent_code, retention_rate')->where('is_active', 1)->get()->getResultArray();
|
|
|
|
|
|
$insurerMap = [];
|
|
|
|
foreach ($insurerMaster as $item) {
|
|
$insurerMap[$normalize($item['name'])] = (int) $item['id'];
|
|
$insurerMap[$normalize($item['short_name'])] = (int) $item['id'];
|
|
$insurerMap[(string) $item['id']] = (int) $item['id'];
|
|
}
|
|
|
|
$rtoMap = [];
|
|
foreach ($rtoMaster as $item) {
|
|
$rtoMap[$normalize($item['rto_code'])] = (int) $item['id'];
|
|
$rtoMap[$normalize($item['rto_name'])] = (int) $item['id'];
|
|
$rtoMap[(string) $item['id']] = (int) $item['id'];
|
|
}
|
|
|
|
$partnerMap = [];
|
|
$partnerRetentionMap = [];
|
|
foreach ($partnerMaster as $item) {
|
|
$id = (int) $item['id'];
|
|
$partnerMap[$normalize($item['name'])] = $id;
|
|
$partnerMap[$normalize($item['agent_code'])] = $id;
|
|
$partnerMap[(string) $id] = $id;
|
|
$partnerRetentionMap[$id] = $toNumber($item['retention_rate'] ?? 0);
|
|
}
|
|
|
|
$insertCount = 0;
|
|
$updateCount = 0;
|
|
$skipStats = [
|
|
'empty_row' => 0,
|
|
'missing_required_columns' => 0,
|
|
'master_mapping_failed' => 0,
|
|
'partner_not_found' => 0,
|
|
];
|
|
$skipSamples = [];
|
|
|
|
for ($i = $headerRowIndex + 1, $count = count($rows); $i < $count; $i++) {
|
|
$row = $rows[$i];
|
|
$hasAnyData = false;
|
|
|
|
foreach ($row as $cellValue) {
|
|
if (trim((string) $cellValue) !== '') {
|
|
$hasAnyData = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$hasAnyData) {
|
|
$skipStats['empty_row']++;
|
|
continue;
|
|
}
|
|
|
|
$insurerRaw = trim((string) ($row[$headerMap['insurer_id']] ?? ''));
|
|
$rtoRaw = trim((string) ($row[$headerMap['rto_id']] ?? ''));
|
|
$segmentRaw = trim((string) ($row[$headerMap['segment_id']] ?? ''));
|
|
$vehicleTypeRaw = trim((string) ($row[$headerMap['vehicle_type_id']] ?? $segmentRaw));
|
|
|
|
if ($insurerRaw === '' || $rtoRaw === '' || $segmentRaw === '' || $vehicleTypeRaw === '') {
|
|
$skipStats['missing_required_columns']++;
|
|
if (count($skipSamples) < 15) {
|
|
$skipSamples[] = [
|
|
'row' => $i + 1,
|
|
'reason' => 'missing_required_columns',
|
|
'insurer' => $insurerRaw,
|
|
'rto' => $rtoRaw,
|
|
'segment' => $segmentRaw,
|
|
'vehicle_type' => $vehicleTypeRaw,
|
|
];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
$insurerId = $insurerMap[$normalize($insurerRaw)] ?? null;
|
|
if ($insurerId === null && ctype_digit($insurerRaw) && isset($insurerMap[$insurerRaw])) {
|
|
$insurerId = $insurerMap[$insurerRaw];
|
|
}
|
|
|
|
$rtoId = $rtoMap[$normalize($rtoRaw)] ?? null;
|
|
if ($rtoId === null && ctype_digit($rtoRaw) && isset($rtoMap[$rtoRaw])) {
|
|
$rtoId = $rtoMap[$rtoRaw];
|
|
}
|
|
|
|
if (empty($insurerId) || empty($rtoId)) {
|
|
$skipStats['master_mapping_failed']++;
|
|
if (count($skipSamples) < 15) {
|
|
$skipSamples[] = [
|
|
'row' => $i + 1,
|
|
'reason' => 'master_mapping_failed',
|
|
'insurer' => $insurerRaw,
|
|
'rto' => $rtoRaw,
|
|
'segment' => $segmentRaw,
|
|
'vehicle_type' => $vehicleTypeRaw,
|
|
];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$comp = trim((string) ($row[$headerMap['comp']] ?? ''));
|
|
$tp = trim((string) ($row[$headerMap['tp']] ?? ''));
|
|
$remarks = trim((string) ($row[$headerMap['remarks']] ?? ''));
|
|
$fuelRaw = trim((string) ($row[$headerMap['fuel']] ?? ''));
|
|
$fuel = $toNullable($fuelRaw);
|
|
$partnerRaw = trim((string) ($row[$headerMap['partner_id']] ?? ''));
|
|
|
|
$partnerId = null;
|
|
if ($partnerRaw !== '') {
|
|
$partnerId = $partnerMap[$normalize($partnerRaw)] ?? null;
|
|
if ($partnerId === null && ctype_digit($partnerRaw) && isset($partnerMap[$partnerRaw])) {
|
|
$partnerId = $partnerMap[$partnerRaw];
|
|
}
|
|
}
|
|
|
|
if (empty($partnerId) && !empty($defaultPartnerId)) {
|
|
$partnerId = $defaultPartnerId;
|
|
}
|
|
|
|
if (empty($partnerId)) {
|
|
$skipStats['partner_not_found']++;
|
|
if (count($skipSamples) < 15) {
|
|
$skipSamples[] = [
|
|
'row' => $i + 1,
|
|
'reason' => 'partner_not_found',
|
|
'partner' => $partnerRaw,
|
|
];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$retentionRate = $partnerRetentionMap[(int) $partnerId] ?? 0.0;
|
|
$compSanitized = $parsePremium($comp);
|
|
$tpSanitized = $parsePremium($tp);
|
|
$partnerComp = $compSanitized !== null ? $formatNumber($retentionRate + (float) $compSanitized) : null;
|
|
$partnerTp = $tpSanitized !== null ? $formatNumber($retentionRate + (float) $tpSanitized) : null;
|
|
|
|
$recordData = [
|
|
'vehicle_type_id' => $vehicleTypeRaw,
|
|
'insurer_id' => (string) $insurerId,
|
|
'rto_id' => (string) $rtoId,
|
|
'segment_id' => $segmentRaw,
|
|
'comp' => $compSanitized,
|
|
'tp' => $tpSanitized,
|
|
'fuel' => $fuel,
|
|
'remarks' => $toNullable($remarks),
|
|
'partner_id' => (string) $partnerId,
|
|
'partner_comp' => $partnerComp,
|
|
'partner_tp' => $partnerTp,
|
|
];
|
|
|
|
$existing = $this->PartnerGridDetailsModel
|
|
->where('vehicle_type_id', $vehicleTypeRaw)
|
|
->where('insurer_id', (string) $insurerId)
|
|
->where('rto_id', (string) $rtoId)
|
|
->where('segment_id', $segmentRaw)
|
|
->where('partner_id', (string) $partnerId)
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$recordData['updated_by'] = $createdBy;
|
|
$this->PartnerGridDetailsModel->update((int) $existing['id'], $recordData);
|
|
$updateCount++;
|
|
} else {
|
|
$recordData['created_by'] = $createdBy;
|
|
$this->PartnerGridDetailsModel->insert($recordData);
|
|
$insertCount++;
|
|
}
|
|
}
|
|
|
|
if ($insertCount === 0 && $updateCount === 0) {
|
|
$debugData = [
|
|
'message' => 'No valid rows found in grid file',
|
|
'header_row' => $headerRowIndex + 1,
|
|
'detected_headers' => array_keys($headerMap),
|
|
'skip_stats' => $skipStats,
|
|
'skip_samples' => $skipSamples,
|
|
];
|
|
throw new \RuntimeException(json_encode($debugData, JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Delete agent incentive file
|
|
public function deleteAgentIncentiveFile()
|
|
{
|
|
try {
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
$roleId = (string) ($authUser->role_id ?? 'agent');
|
|
|
|
$id = $this->request->getGet('id');
|
|
|
|
|
|
// check if agent exists
|
|
$file = $this->AgentIncentiveFileModel->find((int)$id);
|
|
if (!$file) {
|
|
return $this->respond(['status' => 'failed','code' => 200, 'data' => 'File not found'], 200);
|
|
}
|
|
|
|
if (($file['file_type'] ?? '') === 'grid' && !$this->canManagePartnerGrid($roleId)) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 403,
|
|
'data' => 'Only Manager and Accounts can edit/delete partner grid files'
|
|
], 403);
|
|
}
|
|
|
|
// update status
|
|
$this->AgentIncentiveFileModel->update($id, ['is_active' => 0]);
|
|
|
|
return $this->respond([
|
|
'status' => 'success', 'code' => 200,'data' => "File status updated"], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed','code' => 500,'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// Download agent incentive file
|
|
public function downloadAgentIncentiveFile()
|
|
{
|
|
try {
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
$roleId = (string) ($authUser->role_id ?? 'agent');
|
|
$id = $this->request->getGet('id');
|
|
|
|
if (!$id) {
|
|
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200);
|
|
}
|
|
|
|
// Fetch record from DB
|
|
$fileRecord = $this->AgentIncentiveFileModel->where('is_active',1)->find((int)$id);
|
|
|
|
if (!$fileRecord) {
|
|
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200);
|
|
}
|
|
|
|
if (!$this->canManagePartnerGrid($roleId) && (int) $fileRecord['agent_id'] !== (int) ($authUser->id ?? 0)) {
|
|
return $this->respond(['status' => 'failed', 'code' => 403, 'data' => 'Access denied'], 403);
|
|
}
|
|
|
|
$filePath = WRITEPATH . 'uploads/agent/incentive_file/' . $fileRecord['incentive_file_name'];
|
|
|
|
if (!file_exists($filePath)) {
|
|
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 200);
|
|
}
|
|
|
|
// Force file download
|
|
return $this->response->download($filePath, null);
|
|
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'message' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// Download sample partner grid excel
|
|
public function downloadSamplePartnerGridExcel()
|
|
{
|
|
try {
|
|
$filePath = WRITEPATH . 'uploads/sample_partner_grid_file.xlsx';
|
|
|
|
if (!file_exists($filePath)) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 404,
|
|
'data' => 'Sample partner grid file not found on server',
|
|
], 200);
|
|
}
|
|
|
|
return $this->response->download($filePath, null);
|
|
} catch (\Exception $e) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 500,
|
|
'data' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
// Monthly commission grid filters
|
|
public function monthlyCommissionGridFilters()
|
|
{
|
|
try {
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$db = \Config\Database::connect();
|
|
|
|
$rtoMaster = $db->table('rto_master')
|
|
->select('id, rto_code, rto_name')
|
|
->where('is_active', 1)
|
|
->orderBy('rto_code', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$segmentMaster = $db->table('vehicle_type')
|
|
->select('id, vehicle_type')
|
|
->where('is_active', 1)
|
|
->orderBy('vehicle_type', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$planMaster = $db->table('partner_insurance_plan_type_master')
|
|
->select('id, insurance_plan_type')
|
|
->where('is_active', 1)
|
|
->orderBy('insurance_plan_type', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$partnerMaster = $db->table('partner_agent')
|
|
->select('id, name, agent_code, retention_rate')
|
|
->where('is_active', 1)
|
|
->orderBy('name', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$monthRows = $db->table('partner_grid_details')
|
|
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
|
->where('created_at IS NOT NULL', null, false)
|
|
->groupBy("DATE_FORMAT(created_at, '%Y-%m')")
|
|
->orderBy('month_key', 'DESC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$months = array_values(array_filter(array_map(static function ($row) {
|
|
return $row['month_key'] ?? null;
|
|
}, $monthRows)));
|
|
|
|
$latestMonth = !empty($months) ? $months[0] : date('Y-m');
|
|
|
|
return $this->respond([
|
|
'status' => 'success',
|
|
'code' => 200,
|
|
'data' => [
|
|
'rto_master' => $rtoMaster,
|
|
'segment_master' => $segmentMaster,
|
|
'plan_master' => $planMaster,
|
|
'partner_master' => $partnerMaster,
|
|
'months' => $months,
|
|
'default_month' => $latestMonth,
|
|
],
|
|
], 200);
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// Monthly commission grid list
|
|
public function monthlyCommissionGridList()
|
|
{
|
|
try {
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$partnerId = (int) ($this->request->getGet('partner_id') ?? 0);
|
|
$rtoId = (int) ($this->request->getGet('rto_id') ?? 0);
|
|
$segmentId = (int) ($this->request->getGet('segment_id') ?? 0);
|
|
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
|
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
|
|
|
if ($partnerId <= 0 || $rtoId <= 0 || $segmentId <= 0 || $planType === '') {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 200,
|
|
'data' => 'partner_id, rto_id, segment_id and insurance_plan_type are required',
|
|
], 200);
|
|
}
|
|
|
|
$normalizedPlan = strtolower($planType);
|
|
|
|
$db = \Config\Database::connect();
|
|
$latestRow = $db->table('partner_grid_details')
|
|
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
|
->where('created_at IS NOT NULL', null, false)
|
|
->orderBy('created_at', 'DESC')
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
$effectiveMonth = $month !== '' ? $month : (($latestRow['month_key'] ?? '') ?: date('Y-m'));
|
|
|
|
$rows = $db->table('partner_grid_details pgd')
|
|
->select("
|
|
pgd.id,
|
|
pgd.partner_id,
|
|
pa.name AS partner_name,
|
|
pa.agent_code,
|
|
COALESCE(pa.retention_rate, 0) AS retention_rate,
|
|
pgd.rto_id,
|
|
rm.rto_code,
|
|
rm.rto_name,
|
|
pgd.segment,
|
|
pgd.vehicle_type_id,
|
|
vt.vehicle_type AS vehicle_type_name,
|
|
pgd.comp,
|
|
pgd.tp,
|
|
pgd.fuel,
|
|
pgd.remarks,
|
|
DATE_FORMAT(pgd.created_at, '%Y-%m') AS month_key
|
|
")
|
|
->join('partner_agent pa', 'pa.id = pgd.partner_id', 'left')
|
|
->join('rto_master rm', 'rm.id = pgd.rto_id', 'left')
|
|
->join('vehicle_type vt', 'vt.id = pgd.vehicle_type_id', 'left')
|
|
->where('pgd.partner_id', $partnerId)
|
|
->where('pgd.rto_id', $rtoId)
|
|
->where('pgd.segment_id', $segmentId)
|
|
->where("DATE_FORMAT(pgd.created_at, '%Y-%m') =", $effectiveMonth)
|
|
->orderBy('pgd.id', 'DESC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$toPercent = static function ($value): float {
|
|
if ($value === null) {
|
|
return 0.0;
|
|
}
|
|
if (is_numeric($value)) {
|
|
return (float) $value;
|
|
}
|
|
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
|
return (float) $matches[0];
|
|
}
|
|
return 0.0;
|
|
};
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$retention = $toPercent($row['retention_rate'] ?? 0);
|
|
$compRate = $toPercent($row['comp'] ?? 0);
|
|
$tpRate = $toPercent($row['tp'] ?? 0);
|
|
|
|
if (strpos($normalizedPlan, 'third') !== false || $normalizedPlan === 'tp') {
|
|
$gridRate = $tpRate;
|
|
} else {
|
|
// Comprehensive + Own Damage use COMP column.
|
|
$gridRate = $compRate;
|
|
}
|
|
|
|
$netRate = $gridRate - $retention;
|
|
|
|
$row['insurance_plan_type'] = $planType;
|
|
$row['month'] = $effectiveMonth;
|
|
$row['grid_rate_percentage'] = round($gridRate, 2);
|
|
$row['retention_rate_percentage'] = round($retention, 2);
|
|
$row['final_commission_percentage'] = round($netRate, 2);
|
|
$result[] = $row;
|
|
}
|
|
|
|
return $this->respond([
|
|
'status' => 'success',
|
|
'code' => 200,
|
|
'data' => $result,
|
|
'meta' => [
|
|
'month' => $effectiveMonth,
|
|
'insurance_plan_type' => $planType,
|
|
],
|
|
], 200);
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
// Monthly commission grid download
|
|
public function downloadMonthlyCommissionGrid()
|
|
{
|
|
try {
|
|
$authUser = $this->getAuthenticatedUserData();
|
|
if (!$authUser) {
|
|
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$partnerId = (int) ($this->request->getGet('partner_id') ?? 0);
|
|
$rtoId = (int) ($this->request->getGet('rto_id') ?? 0);
|
|
$segmentId = (int) ($this->request->getGet('segment_id') ?? 0);
|
|
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
|
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
|
|
|
if ($partnerId <= 0 || $rtoId <= 0 || $segmentId <= 0 || $planType === '') {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 200,
|
|
'data' => 'partner_id, rto_id, segment_id and insurance_plan_type are required',
|
|
], 200);
|
|
}
|
|
|
|
$normalizedPlan = strtolower($planType);
|
|
$db = \Config\Database::connect();
|
|
$latestRow = $db->table('partner_grid_details')
|
|
->select("DATE_FORMAT(created_at, '%Y-%m') AS month_key")
|
|
->where('created_at IS NOT NULL', null, false)
|
|
->orderBy('created_at', 'DESC')
|
|
->get(1)
|
|
->getRowArray();
|
|
$effectiveMonth = $month !== '' ? $month : (($latestRow['month_key'] ?? '') ?: date('Y-m'));
|
|
|
|
$sourceRows = $db->table('partner_grid_details pgd')
|
|
->select("
|
|
pa.name AS partner_name,
|
|
pa.agent_code,
|
|
COALESCE(pa.retention_rate, 0) AS retention_rate,
|
|
rm.rto_code,
|
|
rm.rto_name,
|
|
vt.vehicle_type AS segment_name,
|
|
pgd.comp,
|
|
pgd.tp
|
|
")
|
|
->join('partner_agent pa', 'pa.id = pgd.partner_id', 'left')
|
|
->join('rto_master rm', 'rm.id = pgd.rto_id', 'left')
|
|
->join('vehicle_type vt', 'vt.id = pgd.segment_id', 'left')
|
|
->where('pgd.partner_id', $partnerId)
|
|
->where('pgd.rto_id', $rtoId)
|
|
->where('pgd.segment_id', $segmentId)
|
|
->where("DATE_FORMAT(pgd.created_at, '%Y-%m') =", $effectiveMonth)
|
|
->orderBy('pgd.id', 'DESC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$toPercent = static function ($value): float {
|
|
if ($value === null) {
|
|
return 0.0;
|
|
}
|
|
if (is_numeric($value)) {
|
|
return (float) $value;
|
|
}
|
|
if (preg_match('/-?\d+(\.\d+)?/', (string) $value, $matches)) {
|
|
return (float) $matches[0];
|
|
}
|
|
return 0.0;
|
|
};
|
|
|
|
$rows = [];
|
|
foreach ($sourceRows as $row) {
|
|
$retention = $toPercent($row['retention_rate'] ?? 0);
|
|
$compRate = $toPercent($row['comp'] ?? 0);
|
|
$tpRate = $toPercent($row['tp'] ?? 0);
|
|
$gridRate = (strpos($normalizedPlan, 'third') !== false || $normalizedPlan === 'tp') ? $tpRate : $compRate;
|
|
|
|
$row['month'] = $effectiveMonth;
|
|
$row['insurance_plan_type'] = $planType;
|
|
$row['grid_rate_percentage'] = round($gridRate, 2);
|
|
$row['retention_rate_percentage'] = round($retention, 2);
|
|
$row['final_commission_percentage'] = round($gridRate - $retention, 2);
|
|
$rows[] = $row;
|
|
}
|
|
|
|
if (empty($rows)) {
|
|
return $this->respond([
|
|
'status' => 'failed',
|
|
'code' => 404,
|
|
'data' => 'No data found for the selected filters',
|
|
], 200);
|
|
}
|
|
|
|
$safePlan = preg_replace('/[^a-zA-Z0-9]+/', '_', strtolower($planType)) ?: 'plan';
|
|
$fileName = "monthly_commission_{$effectiveMonth}_{$safePlan}.csv";
|
|
$tmpFile = WRITEPATH . 'uploads/temp/' . $fileName;
|
|
if (!is_dir(dirname($tmpFile))) {
|
|
mkdir(dirname($tmpFile), 0777, true);
|
|
}
|
|
|
|
$fp = fopen($tmpFile, 'w');
|
|
fputcsv($fp, [
|
|
'Month',
|
|
'Partner',
|
|
'Partner Code',
|
|
'RTO',
|
|
'Segment',
|
|
'Insurance Plan',
|
|
'Grid %',
|
|
'Retention %',
|
|
'Final Commission %',
|
|
]);
|
|
|
|
foreach ($rows as $row) {
|
|
fputcsv($fp, [
|
|
$row['month'] ?? $effectiveMonth,
|
|
$row['partner_name'] ?? '',
|
|
$row['agent_code'] ?? '',
|
|
trim((string) (($row['rto_code'] ?? '') . ' - ' . ($row['rto_name'] ?? '')), ' -'),
|
|
$row['segment_name'] ?? '',
|
|
$row['insurance_plan_type'] ?? $planType,
|
|
$row['grid_rate_percentage'] ?? 0,
|
|
$row['retention_rate_percentage'] ?? 0,
|
|
$row['final_commission_percentage'] ?? 0,
|
|
]);
|
|
}
|
|
|
|
fclose($fp);
|
|
return $this->response->download($tmpFile, null)->setFileName($fileName);
|
|
} catch (\Exception $e) {
|
|
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
|
|
}
|
|
}
|
|
}
|