FIX_Changes and Additional Requirements ( GRID XL Backend )
This commit is contained in:
parent
48a76a54ea
commit
0fb92252dc
@ -73,10 +73,12 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
|
||||
$routes->post('agent/uploadAgentIncentiveFile', 'AgentController::uploadAgentIncentiveFile');
|
||||
$routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile');
|
||||
$routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
|
||||
$routes->get('agent/downloadSamplePartnerGridExcel', 'AgentController::downloadSamplePartnerGridExcel');
|
||||
$routes->get('agent/monthlyCommissionGridFilters', 'AgentController::monthlyCommissionGridFilters');
|
||||
$routes->get('agent/monthlyCommissionGridList', 'AgentController::monthlyCommissionGridList');
|
||||
$routes->get('agent/downloadMonthlyCommissionGrid', 'AgentController::downloadMonthlyCommissionGrid');
|
||||
|
||||
$routes->post('agent/uploadGrid', 'AgentIncentiveController::uploadPayoutGridFile');
|
||||
$routes->get('agent/payoutGrid', 'AgentIncentiveController::getPayoutGrid');
|
||||
$routes->get('agent/downloadGrid', 'AgentIncentiveController::downloadGridInExcel');
|
||||
$routes->get('agent/loadGrid', 'AgentIncentiveController::loadGrid');
|
||||
$routes->get('agent/downloadExistGrid', 'AgentIncentiveController::downloadLastestGrid');
|
||||
|
||||
|
||||
//Staff
|
||||
@ -714,15 +716,3 @@ $routes->get("processjob", "JobWorker::processJob");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -5,41 +5,16 @@ 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
|
||||
@ -292,30 +267,12 @@ class AgentController extends ResourceController
|
||||
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') ?? ''));
|
||||
$agent_id = $this->request->getGet('agent_id');
|
||||
$type = $this->request->getGet('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();
|
||||
$fileType = (!empty($type) && $type === 'grid') ? 'grid' : 'incentive';
|
||||
$data = $this->AgentIncentiveFileModel->where('agent_id',$agent_id)->where('is_active',1)->where('file_type', $fileType)->findAll();
|
||||
|
||||
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
|
||||
|
||||
@ -330,39 +287,13 @@ class AgentController extends ResourceController
|
||||
{
|
||||
try{
|
||||
$data = $this->request->getPost();
|
||||
$authUser = $this->getAuthenticatedUserData();
|
||||
if (!$authUser) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 401, 'data' => 'Unauthorized'], 401);
|
||||
|
||||
//duplicate check
|
||||
$duplicateData = $this->AgentIncentiveFileModel->where('agent_id',$data['agent_id'])->where('incentive_month',$data['incentive_month'])->where('file_type','incentive')->first();
|
||||
if(!empty($duplicateData)){
|
||||
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Duplicate Entry.'], 200);
|
||||
}
|
||||
|
||||
$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');
|
||||
|
||||
@ -382,7 +313,7 @@ class AgentController extends ResourceController
|
||||
'agent_id' => $data['agent_id'],
|
||||
'incentive_month' => $data['incentive_month'],
|
||||
'incentive_file_name' => $incentiveFileName,
|
||||
'file_type' => $data['file_type'] ?? 'incentive',
|
||||
'file_type' => 'incentive',
|
||||
'created_by' => $data['created_by'] ?? null
|
||||
];
|
||||
|
||||
@ -395,310 +326,20 @@ class AgentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// 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'] = $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'], $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']] ?? ''));
|
||||
$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' => $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', $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);
|
||||
$file = $this->AgentIncentiveFileModel->where('file_type','incentive')->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]);
|
||||
|
||||
@ -714,28 +355,21 @@ class AgentController extends ResourceController
|
||||
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');
|
||||
$type = $this->request->getGet('type'); // 'incentive' or 'grid'
|
||||
|
||||
if (!$id) {
|
||||
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200);
|
||||
}
|
||||
|
||||
$fileType = (!empty($type) && $type === 'grid') ? 'grid' : 'incentive';
|
||||
// Fetch record from DB
|
||||
$fileRecord = $this->AgentIncentiveFileModel->where('is_active',1)->find((int)$id);
|
||||
$fileRecord = $this->AgentIncentiveFileModel->where('is_active',1)->where('file_type',$fileType)->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)) {
|
||||
@ -750,352 +384,4 @@ class AgentController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
$vehicleTypeMaster = $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,
|
||||
'vehicle_type_master' => $vehicleTypeMaster,
|
||||
'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);
|
||||
$vehicleTypeId = (int) ($this->request->getGet('vehicle_type_id') ?? 0);
|
||||
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
||||
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
||||
$segment = trim((string) ($this->request->getGet('segment') ?? ''));
|
||||
|
||||
|
||||
if ($partnerId <= 0 || $rtoId <= 0 || $vehicleTypeId <= 0 || $planType === '' || $segment === '') {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'partner_id, rto_id, vehicle_type_id, segment 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', $segment)
|
||||
->where('pgd.vehicle_type_id', $vehicleTypeId)
|
||||
->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);
|
||||
$segment = (int) ($this->request->getGet('segment') ?? 0);
|
||||
$vehicleTypeId = (int) ($this->request->getGet('vehicle_type_id') ?? 0);
|
||||
$planType = trim((string) ($this->request->getGet('insurance_plan_type') ?? ''));
|
||||
$month = trim((string) ($this->request->getGet('month') ?? ''));
|
||||
|
||||
if ($partnerId <= 0 || $rtoId <= 0 || $vehicleTypeId <= 0 || $planType === '' || $segment === '') {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'partner_id, rto_id, vehicle_type_id, segment 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 vehicle_type_name,
|
||||
pdg.segment,
|
||||
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.vehicle_type_id', 'left')
|
||||
->where('pgd.partner_id', $partnerId)
|
||||
->where('pgd.rto_id', $rtoId)
|
||||
->where('pgd.segment', $segment)
|
||||
->where('pgd.vehicle_type_id', $vehicleTypeId)
|
||||
->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',
|
||||
'Type',
|
||||
'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['vehicle_type_name'] ?? '',
|
||||
$row['segment'] ?? '',
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
757
app/Controllers/AgentIncentiveController.php
Normal file
757
app/Controllers/AgentIncentiveController.php
Normal file
@ -0,0 +1,757 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\RESTful\ResourceController;
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
use App\Models\AgentIncentiveFileModel;
|
||||
use App\Models\PartnerInsurancePayoutGridModel;
|
||||
use App\Models\AgentModel;
|
||||
|
||||
|
||||
class AgentIncentiveController extends ResourceController
|
||||
{
|
||||
protected $AgentIncentiveFileModel;
|
||||
protected $PayoutGridModel;
|
||||
protected $PartnerAgentModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
|
||||
$this->PayoutGridModel = new PartnerInsurancePayoutGridModel();
|
||||
$this->PartnerAgentModel = new AgentModel();
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Upload Grid File (file_type = 'grid')
|
||||
// Parses Excel and UPSERTs rows into partner_insurance_payout_grid
|
||||
// -------------------------------------------------------------------------
|
||||
public function uploadPayoutGridFile()
|
||||
{
|
||||
try {
|
||||
/* ====================================================================
|
||||
* STEP 1 — Validate POST input
|
||||
* ==================================================================== */
|
||||
$data = $this->request->getPost();
|
||||
$agentId = $data['agent_id'] ?? null;
|
||||
$month = $data['incentive_month'] ?? null;
|
||||
$createdBy = $data['created_by'] ?? null;
|
||||
|
||||
if (empty($agentId) || empty($month)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'agent_id and incentive_month are required.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 2 — Duplicate check on partner_agent_incentive_file
|
||||
* ==================================================================== */
|
||||
$duplicate = $this->AgentIncentiveFileModel
|
||||
->where('agent_id', $agentId)
|
||||
->where('incentive_month', $month)
|
||||
->where('file_type', 'grid')
|
||||
->first();
|
||||
|
||||
if (!empty($duplicate)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 200,
|
||||
'data' => 'Duplicate Entry.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 3 — Fetch retention_rate from partner_agent using agent_id
|
||||
*
|
||||
* retention_rate is stored as decimal(4,2) e.g. 2.50
|
||||
* Used later to calculate partner_comp / partner_tp / partner_od
|
||||
* by subtracting from broker_excel values.
|
||||
* If agent not found or retention_rate is NULL → partner fields = null
|
||||
* ==================================================================== */
|
||||
$agent = $this->PartnerAgentModel->find($agentId);
|
||||
$retentionRate = (!empty($agent) && $agent['retention_rate'] !== null)
|
||||
? (float) $agent['retention_rate']
|
||||
: null;
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 4 — Validate uploaded file (extension check)
|
||||
* ==================================================================== */
|
||||
$gridFile = $this->request->getFile('incentive_file_name');
|
||||
|
||||
if (!$gridFile || !$gridFile->isValid()) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'No valid file uploaded.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$extension = strtolower($gridFile->getClientExtension());
|
||||
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'Only xlsx, xls, csv grid files are allowed.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 5 — Move file to upload directory
|
||||
* ==================================================================== */
|
||||
$uploadPath = WRITEPATH . 'uploads/agent/incentive_file/';
|
||||
if (!is_dir($uploadPath)) {
|
||||
mkdir($uploadPath, 0777, true);
|
||||
}
|
||||
|
||||
$gridFileName = time() . '_' . $gridFile->getRandomName();
|
||||
$gridFile->move($uploadPath, $gridFileName);
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 6 — Insert record into partner_agent_incentive_file
|
||||
* (same as uploadAgentIncentiveFile but file_type = 'grid')
|
||||
* ==================================================================== */
|
||||
$this->AgentIncentiveFileModel->insert([
|
||||
'agent_id' => $agentId,
|
||||
'incentive_month' => $month,
|
||||
'incentive_file_name' => $gridFileName,
|
||||
'file_type' => 'grid',
|
||||
'is_active' => 1,
|
||||
'created_by' => $createdBy,
|
||||
]);
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 7 — Parse Excel sheet into a flat array of rows
|
||||
* ==================================================================== */
|
||||
$spreadsheet = IOFactory::load($uploadPath . $gridFileName);
|
||||
$rows = $spreadsheet->getActiveSheet()
|
||||
->toArray(null, true, true, false);
|
||||
|
||||
if (empty($rows)) {
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'message' => 'File saved but grid sheet is empty — no rows processed.',
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 8 — Helper: normalise a cell value
|
||||
* Strips spaces & non-alphanumeric chars, returns lowercase.
|
||||
* Used to safely compare header/section values.
|
||||
* ==================================================================== */
|
||||
$normalize = static function ($value): string {
|
||||
$value = strtolower(trim((string) $value));
|
||||
return preg_replace('/[^a-z0-9]+/', '', $value) ?? '';
|
||||
};
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 9 — Helper: extract numeric value from broker_excel cell
|
||||
*
|
||||
* Broker excel cells come in mixed formats:
|
||||
* "22" → 22.0 (plain number)
|
||||
* "4.8" → 4.8
|
||||
* "5.5X" → 5.5 (strip trailing X)
|
||||
* "NET 1.9X" → 1.9 (strip prefix text + X)
|
||||
* "OD 1.5X" → 1.5 (strip OD prefix + X)
|
||||
* "1.5 X" → 1.5 (space before X)
|
||||
* "OD25+TP10" → 35.0 (compound format, splits and sums up)
|
||||
* "OD 20+TP 15"→ 35.0 (compound format, splits and sums up)
|
||||
* "" / null → null
|
||||
*
|
||||
* Returns float|null
|
||||
* ==================================================================== */
|
||||
$extractNumeric = static function ($value): ?float {
|
||||
$str = strtolower(trim((string) $value));
|
||||
|
||||
// Blank or nan → null
|
||||
if ($str === '' || $str === 'nan') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Compound values like "OD25+TP10" or "OD 20+TP 15"
|
||||
if (str_contains($str, '+')) {
|
||||
$sum = 0.0;
|
||||
$hasValid = false;
|
||||
foreach (explode('+', $str) as $part) {
|
||||
$cleanPart = preg_replace('/^(net|od|tp)\s*/i', '', trim($part));
|
||||
$cleanPart = rtrim(trim($cleanPart), 'xX ');
|
||||
if (is_numeric($cleanPart)) {
|
||||
$sum += (float) $cleanPart;
|
||||
$hasValid = true;
|
||||
}
|
||||
}
|
||||
return $hasValid ? $sum : null;
|
||||
}
|
||||
|
||||
// Strip known text prefixes: "net", "od", "tp", spaces
|
||||
$str = preg_replace('/^(net|od|tp)\s*/i', '', $str);
|
||||
|
||||
// Strip trailing "x" or "X" and any surrounding spaces
|
||||
$str = rtrim(trim($str), 'xX ');
|
||||
|
||||
// Now try to parse as float
|
||||
if (is_numeric($str)) {
|
||||
return (float) $str;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 10 — Helper: calculate partner rate
|
||||
*
|
||||
* Formula: partner_value = broker_excel_value - retention_rate
|
||||
*
|
||||
* Rules:
|
||||
* - If broker_excel_value is null → return null
|
||||
* - If retention_rate is null → return null
|
||||
* - Result rounded to 2 decimal places
|
||||
* - Stored as string to match varchar column type
|
||||
* ==================================================================== */
|
||||
$calcPartnerRate = static function (
|
||||
?string $brokerExcelRaw,
|
||||
?float $retentionRate,
|
||||
callable $extractNumeric
|
||||
): ?string {
|
||||
// Either side missing → cannot compute partner rate
|
||||
if ($brokerExcelRaw === null || $retentionRate === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$brokerValue = $extractNumeric($brokerExcelRaw);
|
||||
|
||||
// Could not parse a clean number from the broker value
|
||||
if ($brokerValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// partner rate = broker excel value − retention rate
|
||||
$partnerValue = round($brokerValue - $retentionRate, 2);
|
||||
|
||||
return (string) $partnerValue;
|
||||
};
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 11 — Walk rows: detect section headers → column headers → data
|
||||
*
|
||||
* Excel has two column layouts depending on section:
|
||||
*
|
||||
* Layout A — TWO WHEELER / PCV / GCV / LCV / HCV / BUS / TAXI etc.
|
||||
* [0] INSURER | [1] RTO | [2] SEGMENT | [3] COMP | [4] TP
|
||||
* [5] REMARKS | [6] BROKER | [7] BROKER_EXCEL_COMP | [8] BROKER_EXCEL_TP
|
||||
*
|
||||
* Layout B — PRIVATE CAR-TP / fuel-separated sections
|
||||
* [0] INSURER | [1] RTO | [2] SEGMENT | [3] TP | [4] FUEL
|
||||
* [5] REMARKS | [6] BROKER | [7] BROKER_EXCEL_TP
|
||||
* ==================================================================== */
|
||||
$currentVehicleType = null;
|
||||
$layoutHasComp = true; // true = Layout A, false = Layout B
|
||||
$insertedCount = 0;
|
||||
$updatedCount = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
|
||||
// Pad row to 9 columns so every index access is always safe
|
||||
while (count($row) < 9) {
|
||||
$row[] = null;
|
||||
}
|
||||
|
||||
$col0 = trim((string) ($row[0] ?? ''));
|
||||
$col1 = trim((string) ($row[1] ?? ''));
|
||||
$col2 = trim((string) ($row[2] ?? ''));
|
||||
$col3 = trim((string) ($row[3] ?? ''));
|
||||
$col4 = trim((string) ($row[4] ?? ''));
|
||||
|
||||
// Consider col1 empty when it is blank or the string "nan"
|
||||
$isCol1Empty = ($col1 === '' || strtolower($col1) === 'nan');
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Row type A — Date row (very first row of the sheet) → skip
|
||||
* ------------------------------------------------------------------ */
|
||||
if ($col0 !== '' && $isCol1Empty && strtotime($col0) !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Row type B — Section-header row
|
||||
* Condition: col0 has text, col1 is empty, col0 is NOT "INSURER"
|
||||
* Action: set currentVehicleType, reset layout flag
|
||||
* ------------------------------------------------------------------ */
|
||||
if (
|
||||
$col0 !== ''
|
||||
&& $isCol1Empty
|
||||
&& strtoupper($col0) !== 'INSURER'
|
||||
&& $normalize($col0) !== 'nan'
|
||||
) {
|
||||
$currentVehicleType = strtoupper($col0);
|
||||
$layoutHasComp = true; // will be re-detected from next header row
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Row type C — Column-header row (col0 == "INSURER")
|
||||
* Detect layout from col[4]:
|
||||
* FUEL / PETROL / DIESEL / TP → Layout B (no comp column)
|
||||
* anything else → Layout A (has comp column)
|
||||
* ------------------------------------------------------------------ */
|
||||
if (strtoupper($col0) === 'INSURER') {
|
||||
$col4Upper = strtoupper($col4);
|
||||
$layoutHasComp = !(
|
||||
str_contains($col4Upper, 'FUEL')
|
||||
|| str_contains($col4Upper, 'PETROL')
|
||||
|| str_contains($col4Upper, 'DIESEL')
|
||||
|| $col4Upper === 'TP'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Row type D — Completely blank row → skip
|
||||
* ------------------------------------------------------------------ */
|
||||
if ($col0 === '' && $col1 === '' && $col2 === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Row type E — Data row before any section header was seen → skip
|
||||
* ------------------------------------------------------------------ */
|
||||
if ($currentVehicleType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* MAP COLUMNS TO FIELDS based on detected layout
|
||||
* ================================================================== */
|
||||
if ($layoutHasComp) {
|
||||
/* ----------------------------------------------------------------
|
||||
* LAYOUT A (COMP + TP both present)
|
||||
* col[3] = COMP (broker rate for comprehensive)
|
||||
* col[4] = TP (broker rate for third-party)
|
||||
* col[7] = BROKER_EXCEL_COMP
|
||||
* col[8] = BROKER_EXCEL_TP
|
||||
* ---------------------------------------------------------------- */
|
||||
$insurer = $col0 !== '' ? strtoupper($col0) : null;
|
||||
$rto = $col1 !== '' ? strtoupper($col1) : null;
|
||||
$segment = $col2 !== '' ? strtoupper($col2) : null;
|
||||
$comp = $col3 !== '' ? strtoupper($col3) : null;
|
||||
$tp = $col4 !== '' ? strtoupper($col4) : null;
|
||||
$fuel = null;
|
||||
$remarks = trim((string) ($row[5] ?? '')) ?: null;
|
||||
$brokerName = trim((string) ($row[6] ?? '')) ?: null;
|
||||
$brokerExcelComp = trim((string) ($row[7] ?? '')) ?: null;
|
||||
$brokerExcelTp = trim((string) ($row[8] ?? '')) ?: null;
|
||||
$brokerExcelOd = null;
|
||||
$od = null;
|
||||
|
||||
/* partner_comp = broker_excel_comp − retention_rate
|
||||
partner_tp = broker_excel_tp − retention_rate
|
||||
partner_od = null (no OD broker value in Layout A)
|
||||
Any of these will be null if broker_excel or retention_rate is null */
|
||||
$partnerComp = $calcPartnerRate($brokerExcelComp, $retentionRate, $extractNumeric);
|
||||
$partnerTp = $calcPartnerRate($brokerExcelTp, $retentionRate, $extractNumeric);
|
||||
$partnerOd = null;
|
||||
|
||||
} else {
|
||||
/* ----------------------------------------------------------------
|
||||
* LAYOUT B (TP only, with FUEL column)
|
||||
* col[3] = TP (broker rate for third-party)
|
||||
* col[4] = FUEL type
|
||||
* col[7] = BROKER_EXCEL_TP
|
||||
* No COMP or OD broker excel column in this layout
|
||||
* ---------------------------------------------------------------- */
|
||||
$insurer = $col0 !== '' ? strtoupper($col0) : null;
|
||||
$rto = $col1 !== '' ? strtoupper($col1) : null;
|
||||
$segment = $col2 !== '' ? strtoupper($col2) : null;
|
||||
$comp = null;
|
||||
$tp = $col3 !== '' ? strtoupper($col3) : null;
|
||||
$fuel = $col4 !== '' ? strtoupper($col4) : null;
|
||||
$remarks = trim((string) ($row[5] ?? '')) ?: null;
|
||||
$brokerName = trim((string) ($row[6] ?? '')) ?: null;
|
||||
$brokerExcelComp = null;
|
||||
$brokerExcelTp = trim((string) ($row[7] ?? '')) ?: null;
|
||||
$brokerExcelOd = null;
|
||||
$od = null;
|
||||
|
||||
/* partner_comp = null (no comp in Layout B)
|
||||
partner_tp = broker_excel_tp − retention_rate
|
||||
partner_od = null (no OD broker value in Layout B) */
|
||||
$partnerComp = null;
|
||||
$partnerTp = $calcPartnerRate($brokerExcelTp, $retentionRate, $extractNumeric);
|
||||
$partnerOd = null;
|
||||
}
|
||||
|
||||
// Skip rows that have no essential identifiers
|
||||
if (empty($insurer) || empty($segment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* OD-only row detection
|
||||
* Some rows encode OD rate inside the comp column (e.g. "OD 1.5X")
|
||||
* when tp is empty. Promote comp → od and clear comp.
|
||||
* Recalculate partner_od from broker_excel_comp in this case.
|
||||
* ------------------------------------------------------------------ */
|
||||
if ($comp !== null && stripos($comp, 'OD') === 0 && $tp === null) {
|
||||
$od = $comp;
|
||||
$comp = null;
|
||||
$brokerExcelOd = $brokerExcelComp; // broker excel comp was OD value
|
||||
$brokerExcelComp = null;
|
||||
|
||||
// partner_od = broker_excel_od − retention_rate
|
||||
$partnerOd = $calcPartnerRate($brokerExcelOd, $retentionRate, $extractNumeric);
|
||||
$partnerComp = null;
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* UPSERT into partner_insurance_payout_grid
|
||||
* Natural key: vehicle_type + insurer + rto + segment
|
||||
* UPDATE if record exists, INSERT otherwise.
|
||||
* ================================================================== */
|
||||
$existing = $this->PayoutGridModel
|
||||
->where('vehicle_type', $currentVehicleType)
|
||||
->where('insurer', $insurer)
|
||||
->where('rto', $rto ?? '')
|
||||
->where('segment', $segment)
|
||||
->first();
|
||||
|
||||
$gridRow = [
|
||||
'vehicle_type' => $currentVehicleType,
|
||||
'fuel' => $fuel,
|
||||
'insurer' => $insurer,
|
||||
'rto' => $rto,
|
||||
'broker_name' => $brokerName,
|
||||
'segment' => $segment,
|
||||
'comp' => $comp,
|
||||
'tp' => $tp,
|
||||
'od' => $od,
|
||||
'remarks' => $remarks,
|
||||
'broker_excel_comp' => $brokerExcelComp,
|
||||
'broker_excel_tp' => $brokerExcelTp,
|
||||
'broker_excel_od' => $brokerExcelOd,
|
||||
// partner_* = broker_excel_* − retention_rate
|
||||
// null when either broker value or retention_rate is missing
|
||||
'partner_comp' => $partnerComp,
|
||||
'partner_tp' => $partnerTp,
|
||||
'partner_od' => $partnerOd,
|
||||
];
|
||||
|
||||
if (!empty($existing)) {
|
||||
// Record already exists → UPDATE, stamp updated_by
|
||||
$gridRow['updated_by'] = $createdBy;
|
||||
$this->PayoutGridModel->update($existing['id'], $gridRow);
|
||||
$updatedCount++;
|
||||
} else {
|
||||
// New record → INSERT, stamp created_by
|
||||
$gridRow['created_by'] = $createdBy;
|
||||
$this->PayoutGridModel->insert($gridRow);
|
||||
$insertedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* STEP 12 — Return summary response
|
||||
* ==================================================================== */
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'file' => $gridFileName,
|
||||
'inserted' => $insertedCount,
|
||||
'updated' => $updatedCount,
|
||||
'retention_rate' => $retentionRate,
|
||||
'message' => "Grid processed: {$insertedCount} inserted, {$updatedCount} updated.",
|
||||
],
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// List all grid records (optional utility endpoint)
|
||||
// -------------------------------------------------------------------------
|
||||
// GET /agent/payoutGrid?role=Manager means all list
|
||||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&vehicle_type=TWO%20WHEELER&plan_type=comp
|
||||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&rto=TN&segment=BIKE&plan_type=comp
|
||||
// GET /agent/payoutGrid?role=Manager&insurer=HDFC&segment=BIKE&vehicle_type=TWO%20WHEELER&plan_type=comp
|
||||
public function getPayoutGrid()
|
||||
{
|
||||
try {
|
||||
$request = $this->request;
|
||||
$role = $request->getGet('role');
|
||||
|
||||
// 1. Check if Role is provided
|
||||
if (!$role) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'Role is required to fetch payout data.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// 2. Extract Filters
|
||||
$insurer = $request->getGet('insurer');
|
||||
$rto = $request->getGet('rto');
|
||||
$segment = $request->getGet('segment');
|
||||
$vehicle_type = $request->getGet('vehicle_type');
|
||||
$plan_type = $request->getGet('plan_type');
|
||||
|
||||
// 3. Build the Grid Query
|
||||
$builder = $this->PayoutGridModel->builder();
|
||||
$builder->orderBy('vehicle_type', 'ASC')->orderBy('insurer', 'ASC');
|
||||
|
||||
// Apply filters only for authorized roles
|
||||
if (in_array($role, ['Manager', 'Accounts'])) {
|
||||
if (!empty($insurer)) $builder->where('insurer', $insurer);
|
||||
if (!empty($rto)) $builder->where('rto', $rto);
|
||||
if (!empty($segment)) $builder->where('segment', $segment);
|
||||
if (!empty($vehicle_type)) $builder->where('vehicle_type', $vehicle_type);
|
||||
|
||||
// Handle dynamic column selection (comp/tp/od)
|
||||
if (!empty($plan_type) && in_array($plan_type, ['comp', 'tp', 'od'])) {
|
||||
$builder->select("id, insurer, vehicle_type, segment, rto, $plan_type as payout");
|
||||
} else {
|
||||
// Default selection if no plan_type or invalid plan_type
|
||||
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od');
|
||||
}
|
||||
}
|
||||
|
||||
$gridResults = $builder->get()->getResult();
|
||||
|
||||
// 4. Combine Grid Results with Dropdown Meta-data
|
||||
$responseData = [
|
||||
'grid' => $gridResults,
|
||||
'rtos' => $this->getUniqueColumnValues('rto'),
|
||||
'segments' => $this->getUniqueColumnValues('segment'),
|
||||
'vehicle_types' => $this->getUniqueColumnValues('vehicle_type'),
|
||||
'insurers' => $this->getUniqueColumnValues('insurer'),
|
||||
'plan_types' => [
|
||||
['value' => 'comp', 'label' => 'Comprehensive'],
|
||||
['value' => 'tp', 'label' => 'Third Party'],
|
||||
['value' => 'od', 'label' => 'Own Damage']
|
||||
]
|
||||
];
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => $responseData
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to fetch unique non-empty values for a column
|
||||
*/
|
||||
private function getUniqueColumnValues($column)
|
||||
{
|
||||
$results = $this->PayoutGridModel->select($column)
|
||||
->distinct()
|
||||
->where("$column IS NOT NULL")
|
||||
->where("$column !=", '')
|
||||
->orderBy($column, 'ASC')
|
||||
->findAll();
|
||||
|
||||
return array_column($results, $column);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Download Filtered Grid in Excel
|
||||
// Route: $routes->get('agent/downloadGrid', 'AgentIncentiveController::downloadGridInExcel');
|
||||
// Purpose: Applies the exact same filters as getPayoutGrid(), but instead
|
||||
// of returning JSON, it generates and downloads an Excel file.
|
||||
// -------------------------------------------------------------------------
|
||||
public function downloadGridInExcel()
|
||||
{
|
||||
try {
|
||||
$request = $this->request;
|
||||
|
||||
// Get GET parameters for filtering
|
||||
$role = $request->getGet('role');
|
||||
$insurer = $request->getGet('insurer');
|
||||
$rto = $request->getGet('rto');
|
||||
$segment = $request->getGet('segment');
|
||||
$vehicle_type = $request->getGet('vehicle_type');
|
||||
$plan_type = $request->getGet('plan_type'); // comp, tp, od
|
||||
|
||||
$builder = $this->PayoutGridModel->builder();
|
||||
$builder->orderBy('vehicle_type', 'ASC')->orderBy('insurer', 'ASC');
|
||||
|
||||
// Apply filters if the user is a Manager or Accounts role
|
||||
if (in_array($role, ['Manager', 'Accounts'])) {
|
||||
if (!empty($insurer)) $builder->where('insurer', $insurer);
|
||||
if (!empty($rto)) $builder->where('rto', $rto);
|
||||
if (!empty($segment)) $builder->where('segment', $segment);
|
||||
if (!empty($vehicle_type)) $builder->where('vehicle_type', $vehicle_type);
|
||||
}
|
||||
|
||||
// Fetch the filtered records
|
||||
$records = $builder->get()->getResultArray();
|
||||
|
||||
if (empty($records)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'No records found to export based on your filters.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
// --- Start Excel Generation ---
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
// Setup Header Row
|
||||
$sheet->setCellValue('A1', 'Vehicle Type');
|
||||
$sheet->setCellValue('B1', 'Insurer');
|
||||
$sheet->setCellValue('C1', 'RTO');
|
||||
$sheet->setCellValue('D1', 'Segment');
|
||||
$sheet->setCellValue('E1', 'Broker Name'); // Changed from 'ID' for clarity
|
||||
|
||||
// Set the dynamic column header based on the selected plan type
|
||||
if ($plan_type === 'comp') {
|
||||
$sheet->setCellValue('F1', 'Comp');
|
||||
$sheet->setCellValue('G1', 'Comp');
|
||||
} elseif ($plan_type === 'tp') {
|
||||
$sheet->setCellValue('F1', 'TP');
|
||||
$sheet->setCellValue('G1', 'TP');
|
||||
} elseif ($plan_type === 'od') {
|
||||
$sheet->setCellValue('F1', 'OD');
|
||||
$sheet->setCellValue('G1', 'OD');
|
||||
} else {
|
||||
// If no specific plan type is selected, show all
|
||||
$sheet->setCellValue('F1', 'Comp');
|
||||
$sheet->setCellValue('G1', 'TP');
|
||||
$sheet->setCellValue('H1', 'OD');
|
||||
|
||||
$sheet->setCellValue('I1', 'Comp');
|
||||
$sheet->setCellValue('J1', 'TP');
|
||||
$sheet->setCellValue('K1', 'OD');
|
||||
}
|
||||
|
||||
// Populate Excel Rows
|
||||
$rowNumber = 2; // Start on row 2 (row 1 is headers)
|
||||
foreach ($records as $row) {
|
||||
$sheet->setCellValue('A' . $rowNumber, $row['vehicle_type']);
|
||||
$sheet->setCellValue('B' . $rowNumber, $row['insurer']);
|
||||
$sheet->setCellValue('C' . $rowNumber, $row['rto']);
|
||||
$sheet->setCellValue('D' . $rowNumber, $row['segment']);
|
||||
$sheet->setCellValue('E' . $rowNumber, $row['broker_name']);
|
||||
|
||||
// Output dynamic columns based on selected plan type
|
||||
if ($plan_type === 'comp') {
|
||||
$sheet->setCellValue('F' . $rowNumber, $row['comp']);
|
||||
$sheet->setCellValue('G' . $rowNumber, $row['partner_comp']);
|
||||
} elseif ($plan_type === 'tp') {
|
||||
$sheet->setCellValue('F' . $rowNumber, $row['tp']);
|
||||
$sheet->setCellValue('G' . $rowNumber, $row['partner_tp']);
|
||||
} elseif ($plan_type === 'od') {
|
||||
$sheet->setCellValue('F' . $rowNumber, $row['od']);
|
||||
$sheet->setCellValue('G' . $rowNumber, $row['partner_od']);
|
||||
} else {
|
||||
$sheet->setCellValue('F' . $rowNumber, $row['comp']);
|
||||
$sheet->setCellValue('G' . $rowNumber, $row['tp']);
|
||||
$sheet->setCellValue('H' . $rowNumber, $row['od']);
|
||||
$sheet->setCellValue('I' . $rowNumber, $row['partner_comp']);
|
||||
$sheet->setCellValue('J' . $rowNumber, $row['partner_tp']);
|
||||
$sheet->setCellValue('K' . $rowNumber, $row['partner_od']);
|
||||
}
|
||||
$rowNumber++;
|
||||
}
|
||||
|
||||
// Auto-size columns for better readability
|
||||
foreach (range('A', $sheet->getHighestColumn()) as $columnID) {
|
||||
$sheet->getColumnDimension($columnID)->setAutoSize(true);
|
||||
}
|
||||
|
||||
// Write the file to a temporary location
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$fileName = 'Filtered_Payout_Grid_' . date('Y-m-d_H-i') . '.xlsx';
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'grid_export');
|
||||
$writer->save($tempFile);
|
||||
|
||||
// Return the file as a direct download response
|
||||
return $this->response->download($tempFile, null)->setFileName($fileName);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => 'Failed to generate Excel file: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Download the Originally Uploaded Reference Grid
|
||||
// Route: $routes->get('agent/downloadExistGrid', 'AgentIncentiveController::downloadLastestGrid');
|
||||
// Purpose: Finds the most recently uploaded physical Excel file (where file_type='grid')
|
||||
// from the partner_agent_incentive_file table and initiates a download.
|
||||
// -------------------------------------------------------------------------
|
||||
public function downloadLastestGrid()
|
||||
{
|
||||
try {
|
||||
// Find the latest file entry in the database where file_type is 'grid'
|
||||
$latestFileRecord = $this->AgentIncentiveFileModel
|
||||
->where('file_type', 'grid')
|
||||
->orderBy('created_on', 'DESC')
|
||||
->first();
|
||||
|
||||
if (empty($latestFileRecord)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'No uploaded reference grid file found in the database.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Construct the exact file path where it was saved during upload
|
||||
$fileName = $latestFileRecord['incentive_file_name'];
|
||||
$filePath = WRITEPATH . 'uploads/agent/incentive_file/' . $fileName;
|
||||
|
||||
// Check if the physical file actually exists on the server
|
||||
if (!file_exists($filePath)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'The file record exists, but the physical file is missing from the server.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Initiate the download of the physical file
|
||||
return $this->response->download($filePath, null)->setFileName('Reference_Grid_' . $fileName);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => 'Error attempting to download file: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,202 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PartnerGridDetailsModel extends Model
|
||||
{
|
||||
// ─────────────────────────────────────────
|
||||
// Table Configuration
|
||||
// ─────────────────────────────────────────
|
||||
protected $table = 'partner_grid_details';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Allowed Fields
|
||||
// ─────────────────────────────────────────
|
||||
protected $allowedFields = [
|
||||
'vehicle_type_id',
|
||||
'insurer_id',
|
||||
'rto_id',
|
||||
'segment',
|
||||
'comp',
|
||||
'tp',
|
||||
'fuel',
|
||||
'remarks',
|
||||
'partner_id',
|
||||
'partner_comp',
|
||||
'partner_tp',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Timestamps
|
||||
// ─────────────────────────────────────────
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Validation Rules
|
||||
// ─────────────────────────────────────────
|
||||
protected $validationRules = [
|
||||
'vehicle_type_id' => 'permit_empty|max_length[50]',
|
||||
'insurer_id' => 'required|max_length[50]',
|
||||
'rto_id' => 'required|max_length[50]',
|
||||
'segment' => 'required|max_length[100]',
|
||||
'comp' => 'permit_empty|max_length[50]',
|
||||
'tp' => 'permit_empty|max_length[50]',
|
||||
'remarks' => 'permit_empty|max_length[255]',
|
||||
'partner_id' => 'required|max_length[50]',
|
||||
'fuel' => 'permit_empty|max_length[50]',
|
||||
'partner_comp' => 'permit_empty|max_length[20]',
|
||||
'partner_tp' => 'permit_empty|max_length[20]',
|
||||
'created_by' => 'permit_empty|integer',
|
||||
'updated_by' => 'permit_empty|integer',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'insurer_id' => [
|
||||
'required' => 'Insurer is required.',
|
||||
'max_length' => 'Insurer name must not exceed 50 characters.',
|
||||
],
|
||||
'rto_id' => [
|
||||
'required' => 'RTO is required.',
|
||||
'max_length' => 'RTO code must not exceed 50 characters.',
|
||||
],
|
||||
'vehicle_type_id' => [
|
||||
'required' => 'Type is required.',
|
||||
'max_length' => 'Type must not exceed 50 characters.',
|
||||
],
|
||||
'segment' => [
|
||||
'required' => 'Segment is required.',
|
||||
'max_length' => 'Segment must not exceed 100 characters.',
|
||||
],
|
||||
'partner_id' => [
|
||||
'required' => 'Partner is required.',
|
||||
'max_length' => 'Partner ID must not exceed 50 characters.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $skipValidation = false;
|
||||
|
||||
// ─────────────────────────────────────────
|
||||
// Custom Methods
|
||||
// ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get all active partner grid records
|
||||
*/
|
||||
public function getAllRecords()
|
||||
{
|
||||
return $this->orderBy('id', 'ASC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Insurer ID
|
||||
*/
|
||||
public function getByInsurer(string $insurerId)
|
||||
{
|
||||
return $this->where('insurer_id', $insurerId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by RTO ID
|
||||
*/
|
||||
public function getByRTO(string $rtoId)
|
||||
{
|
||||
return $this->where('rto_id', $rtoId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Segment
|
||||
*/
|
||||
public function getBySegment(string $segment)
|
||||
{
|
||||
return $this->where('segment', $segment)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Vehicle Type ID
|
||||
*/
|
||||
public function getByType(string $vehicleTypeId)
|
||||
{
|
||||
return $this->where('vehicle_type_id', $vehicleTypeId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Partner ID
|
||||
*/
|
||||
public function getByPartner(string $partnerId)
|
||||
{
|
||||
return $this->where('partner_id', $partnerId)->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records by Insurer and RTO
|
||||
*/
|
||||
public function getByInsurerAndRTO(string $insurerId, string $rtoId)
|
||||
{
|
||||
return $this->where('insurer_id', $insurerId)
|
||||
->where('rto_id', $rtoId)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search with multiple filters
|
||||
*/
|
||||
public function search(array $filters = [])
|
||||
{
|
||||
$builder = $this->builder();
|
||||
|
||||
if (!empty($filters['insurer_id'])) {
|
||||
$builder->where('insurer_id', $filters['insurer_id']);
|
||||
}
|
||||
if (!empty($filters['rto_id'])) {
|
||||
$builder->where('rto_id', $filters['rto_id']);
|
||||
}
|
||||
if (!empty($filters['vehicle_type_id'])) {
|
||||
$builder->where('vehicle_type_id', $filters['vehicle_type_id']);
|
||||
}
|
||||
if (!empty($filters['segment'])) {
|
||||
$builder->where('segment', $filters['segment']);
|
||||
}
|
||||
if (!empty($filters['partner_id'])) {
|
||||
$builder->where('partner_id', $filters['partner_id']);
|
||||
}
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert with created_by
|
||||
*/
|
||||
public function insertRecord(array $data, int $userId)
|
||||
{
|
||||
$data['created_by'] = $userId;
|
||||
$data['updated_by'] = $userId;
|
||||
return $this->insert($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update with updated_by
|
||||
*/
|
||||
public function updateRecord(int $id, array $data, int $userId)
|
||||
{
|
||||
$data['updated_by'] = $userId;
|
||||
return $this->update($id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a record by ID
|
||||
*/
|
||||
public function deleteRecord(int $id)
|
||||
{
|
||||
return $this->delete($id);
|
||||
}
|
||||
}
|
||||
129
app/Models/PartnerInsurancePayoutGridModel.php
Normal file
129
app/Models/PartnerInsurancePayoutGridModel.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PartnerInsurancePayoutGridModel extends Model
|
||||
{
|
||||
protected $table = 'partner_insurance_payout_grid';
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
protected $allowedFields = [
|
||||
'vehicle_type',
|
||||
'fuel',
|
||||
'insurer',
|
||||
'rto',
|
||||
'broker_name',
|
||||
'segment',
|
||||
'comp',
|
||||
'tp',
|
||||
'od',
|
||||
'remarks',
|
||||
'broker_excel_comp',
|
||||
'broker_excel_tp',
|
||||
'broker_excel_od',
|
||||
'partner_comp',
|
||||
'partner_tp',
|
||||
'partner_od',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
// Timestamps — map to the column names in your DDL
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
// Validation
|
||||
protected $validationRules = [
|
||||
'vehicle_type' => 'required|max_length[100]',
|
||||
'insurer' => 'required|max_length[45]',
|
||||
'segment' => 'required|max_length[100]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'vehicle_type' => ['required' => 'Vehicle type is required.'],
|
||||
'insurer' => ['required' => 'Insurer is required.'],
|
||||
'segment' => ['required' => 'Segment is required.'],
|
||||
];
|
||||
|
||||
protected $skipValidation = false;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Find by the natural UPSERT key
|
||||
// -------------------------------------------------------------------------
|
||||
public function findByUpsertKey(
|
||||
string $vehicleType,
|
||||
string $insurer,
|
||||
?string $rto,
|
||||
string $segment
|
||||
): ?array {
|
||||
return $this
|
||||
->where('vehicle_type', $vehicleType)
|
||||
->where('insurer', $insurer)
|
||||
->where('rto', $rto ?? '')
|
||||
->where('segment', $segment)
|
||||
->first();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Bulk UPSERT helper
|
||||
// Accepts an array of grid rows and inserts or updates each one.
|
||||
// Returns ['inserted' => int, 'updated' => int]
|
||||
// -------------------------------------------------------------------------
|
||||
public function bulkUpsert(array $gridRows, ?int $userId = null): array
|
||||
{
|
||||
$inserted = 0;
|
||||
$updated = 0;
|
||||
|
||||
foreach ($gridRows as $row) {
|
||||
$existing = $this->findByUpsertKey(
|
||||
$row['vehicle_type'],
|
||||
$row['insurer'],
|
||||
$row['rto'] ?? null,
|
||||
$row['segment']
|
||||
);
|
||||
|
||||
$row['updated_by'] = $userId;
|
||||
|
||||
if (!empty($existing)) {
|
||||
$this->update($existing['id'], $row);
|
||||
$updated++;
|
||||
} else {
|
||||
$row['created_by'] = $userId;
|
||||
$this->insert($row);
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
|
||||
return ['inserted' => $inserted, 'updated' => $updated];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fetch grid filtered by vehicle_type
|
||||
// -------------------------------------------------------------------------
|
||||
public function getByVehicleType(string $vehicleType): array
|
||||
{
|
||||
return $this
|
||||
->where('vehicle_type', $vehicleType)
|
||||
->orderBy('insurer', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fetch grid filtered by insurer + rto
|
||||
// -------------------------------------------------------------------------
|
||||
public function getByInsurerAndRto(string $insurer, string $rto): array
|
||||
{
|
||||
return $this
|
||||
->where('insurer', $insurer)
|
||||
->where('rto', $rto)
|
||||
->orderBy('vehicle_type', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user