Merge branch 'main' of bitbucket.org:jubilian/nhance_partner_be

This commit is contained in:
Gowtham M 2026-04-03 10:25:16 +05:30
commit aaf0c81d11
14 changed files with 2611 additions and 59 deletions

View File

@ -28,7 +28,7 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
//api's with token //api's with token
$routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], function ($routes) { $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], function ($routes) {
//logout //logout
$routes->get('auth/logout', 'StaffAuthController::logout'); $routes->get('auth/logout', 'StaffAuthController::logout');
@ -74,6 +74,9 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile'); $routes->get('agent/deleteAgentIncentiveFile', 'AgentController::deleteAgentIncentiveFile');
$routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile'); $routes->get('agent/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
$routes->get('grid', 'AgentIncentiveController::getGridData');
$routes->post('grid/upload', 'AgentIncentiveController::uploadGridFile');
$routes->get('grid/fileList', 'AgentIncentiveController::gridFileList');
//Staff //Staff
$routes->get('staff/staffList', 'StaffController::staffList'); $routes->get('staff/staffList', 'StaffController::staffList');
@ -123,6 +126,7 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('policy/findPolicy', 'PolicyController::findPolicy'); $routes->get('policy/findPolicy', 'PolicyController::findPolicy');
$routes->post('policy/createPolicy', 'PolicyController::createPolicy'); $routes->post('policy/createPolicy', 'PolicyController::createPolicy');
$routes->post('policy/updatePolicy', 'PolicyController::updatePolicy'); $routes->post('policy/updatePolicy', 'PolicyController::updatePolicy');
$routes->post('policy/updatePolicyCommission', 'PolicyController::updatePolicyCommission');
$routes->get('policy/downloadPolicyFile', 'PolicyController::downloadPolicyFile'); $routes->get('policy/downloadPolicyFile', 'PolicyController::downloadPolicyFile');
$routes->post('policy/uploadPolicyFile', 'PolicyController::uploadPolicyFile'); $routes->post('policy/uploadPolicyFile', 'PolicyController::uploadPolicyFile');
$routes->get("policy/searchThePolicies", "PolicyController::searchThePolicies"); $routes->get("policy/searchThePolicies", "PolicyController::searchThePolicies");
@ -143,7 +147,9 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->post('endorsement/createEndorsement', 'EndorsementController::createEndorsement'); $routes->post('endorsement/createEndorsement', 'EndorsementController::createEndorsement');
$routes->post('endorsement/updateEndorsement', 'EndorsementController::updateEndorsement'); $routes->post('endorsement/updateEndorsement', 'EndorsementController::updateEndorsement');
$routes->get('endorsement/downloadEndorsementCompletionFile', 'EndorsementController::downloadEndorsementCompletionFile'); $routes->get('endorsement/downloadEndorsementCompletionFile', 'EndorsementController::downloadEndorsementCompletionFile');
$routes->post('endorsement/uploadEndorsementFile', 'EndorsementController::uploadEndorsementFile');
$routes->get('endorsement/deleteEndorsement', 'EndorsementController::deleteEndorsement');
//dashboard //dashboard
$routes->get('dashboard/managerDashboard', 'DashboardController::managerDashboard'); $routes->get('dashboard/managerDashboard', 'DashboardController::managerDashboard');
$routes->get('dashboard/handlerDashboard', 'DashboardController::handlerDashboard'); $routes->get('dashboard/handlerDashboard', 'DashboardController::handlerDashboard');
@ -178,15 +184,43 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('reports/endorsement-excel', 'ExcelExportController::downloadExcelEndorsement'); $routes->get('reports/endorsement-excel', 'ExcelExportController::downloadExcelEndorsement');
// DASHBOARD Season 6 — Partner Portal
// GET /partner/{id}/details
$routes->get('partner/(:num)/details', 'DashboardController::partnerDetails/$1');
// GET /partner/{id}/policies
$routes->get('partner/(:num)/policies', 'DashboardController::partnerPolicies/$1');
// GET /partner/{id}/renewals?days=N
$routes->get('partner/(:num)/renewals', 'DashboardController::partnerRenewals/$1');
// GET /partner/{id}/earnings
$routes->get('partner/(:num)/earnings', 'DashboardController::partnerEarnings/$1');
// DASHBOARD Season 7 — Partner Portal
$routes->get('grid/download', 'ExcelExportController::downloadExcelGrid');
//invoice //invoice
$routes->get('invoice/list', 'InvoiceController::invoiceList'); $routes->get('invoice/list', 'InvoiceController::invoiceList');
$routes->get('invoice/details', 'InvoiceController::findInvoiceWithItems'); $routes->get('invoice/details', 'InvoiceController::findInvoiceWithItems');
$routes->post('invoice/create-or-update', 'InvoiceController::createOrUpdateInvoice'); $routes->post('invoice/create-or-update', 'InvoiceController::createOrUpdateInvoice');
$routes->get('invoice/utrDetails', 'InvoiceController::utrDetails');
$routes->post('invoice/add-payment', 'InvoiceController::addInvoicePayment'); // tempo
$routes->post('invoice/updateUtrDetails', 'InvoiceController::updateUtrDetails');
$routes->get('invoice/add-payment-history', 'InvoiceController::addPaymentHistory'); // tempo
$routes->post('invoice/add-payment-history', 'InvoiceController::addPaymentHistory'); // tempo
$routes->get('invoice/listUtrDetails', 'InvoiceController::listUtrDetails');
$routes->post('invoice/listUtrDetails', 'InvoiceController::listUtrDetails');
$routes->get('invoice/delete', 'InvoiceController::deleteInvoice'); $routes->get('invoice/delete', 'InvoiceController::deleteInvoice');
$routes->post('invoice/commission-rate-list', 'InvoiceController::getCommissionRateList'); $routes->post('invoice/commission-rate-list', 'InvoiceController::getCommissionRateList');
$routes->get('invoice/getAgentUnusedCommissionList', 'InvoiceController::getAgentUnusedCommissionList'); $routes->get('invoice/getAgentUnusedCommissionList', 'InvoiceController::getAgentUnusedCommissionList');
$routes->post('invoice/bulk-upload-commission', 'InvoiceController::bulkUploadCommission');
$routes->post('invoice/bulk-upload-commission/proceed', 'InvoiceController::bulkUploadCommissionProceed');
$routes->post('invoice/bulk-upload-commission', 'InvoiceController::bulkUploadCommission');
$routes->post('invoice/bulk-upload-commission/proceed', 'InvoiceController::bulkUploadCommissionProceed');
// SALES EXECUTIVE // SALES EXECUTIVE
@ -203,7 +237,7 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('audit/history', 'MasterController::getHistory'); $routes->get('audit/history', 'MasterController::getHistory');
}); });
// common // common
@ -688,15 +722,3 @@ $routes->get("processjob", "JobWorker::processJob");

View File

@ -269,8 +269,10 @@ class AgentController extends ResourceController
try{ try{
$agent_id = $this->request->getGet('agent_id'); $agent_id = $this->request->getGet('agent_id');
$type = $this->request->getGet('type');
$data = $this->AgentIncentiveFileModel->where('agent_id',$agent_id)->where('is_active',1)->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); return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
@ -287,7 +289,7 @@ class AgentController extends ResourceController
$data = $this->request->getPost(); $data = $this->request->getPost();
//duplicate check //duplicate check
$duplicateData = $this->AgentIncentiveFileModel->where('agent_id',$data['agent_id'])->where('incentive_month',$data['incentive_month'])->first(); $duplicateData = $this->AgentIncentiveFileModel->where('agent_id',$data['agent_id'])->where('incentive_month',$data['incentive_month'])->where('file_type','incentive')->first();
if(!empty($duplicateData)){ if(!empty($duplicateData)){
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Duplicate Entry.'], 200); return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Duplicate Entry.'], 200);
} }
@ -311,6 +313,7 @@ class AgentController extends ResourceController
'agent_id' => $data['agent_id'], 'agent_id' => $data['agent_id'],
'incentive_month' => $data['incentive_month'], 'incentive_month' => $data['incentive_month'],
'incentive_file_name' => $incentiveFileName, 'incentive_file_name' => $incentiveFileName,
'file_type' => 'incentive',
'created_by' => $data['created_by'] ?? null 'created_by' => $data['created_by'] ?? null
]; ];
@ -332,7 +335,7 @@ class AgentController extends ResourceController
// check if agent exists // check if agent exists
$file = $this->AgentIncentiveFileModel->find((int)$id); $file = $this->AgentIncentiveFileModel->where('file_type','incentive')->find((int)$id);
if (!$file) { if (!$file) {
return $this->respond(['status' => 'failed','code' => 200, 'data' => 'File not found'], 200); return $this->respond(['status' => 'failed','code' => 200, 'data' => 'File not found'], 200);
} }
@ -353,13 +356,15 @@ class AgentController extends ResourceController
{ {
try { try {
$id = $this->request->getGet('id'); $id = $this->request->getGet('id');
$type = $this->request->getGet('type'); // 'incentive' or 'grid'
if (!$id) { if (!$id) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200);
} }
$fileType = (!empty($type) && $type === 'grid') ? 'grid' : 'incentive';
// Fetch record from DB // 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) { if (!$fileRecord) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200); return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200);
@ -379,10 +384,6 @@ class AgentController extends ResourceController
} }
} }
//Empty Commit
} }

View File

@ -0,0 +1,614 @@
<?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();
}
// GET /agent/gridFileList
public function gridFileList()
{
try {
$builder = $this->AgentIncentiveFileModel->builder();
// 1. Explicitly select and format dates in the SQL layer (Faster + Indian Format)
$builder->select("
paif.id,
paif.agent_id,
paif.incentive_month,
DATE_FORMAT(paif.incentive_month, '%d-%m-%Y') as vaild_from,
paif.incentive_file_name,
ps.name as created_by_name,
paif.created_on,
DATE_FORMAT(paif.created_on, '%d-%m-%Y %h:%i %p') as created_date
");
$builder->from('partner_agent_incentive_file paif');
// 2. The Join
$builder->join('partner_staff ps', 'ps.id = paif.created_by', 'left');
// 3. Filters
$builder->where('paif.is_active', 1);
$builder->where('paif.file_type', 'grid');
// 4. THE FIX: Group by the primary ID to stop the "5 rows" duplication
$builder->groupBy('paif.id');
// 5. Order
$builder->orderBy('paif.id', 'DESC');
$query = $builder->get();
$result = $query->getResult();
// Use $this->respond to maintain consistency with your other API methods
return $this->respond([
'status' => 'success', // Changed to 'success' to match your other methods
'code' => 200,
'data' => $result
], 200);
} catch (\Exception $e) {
return $this->respond([
'status' => 'failed',
'code' => 500,
'data' => $e->getMessage()
], 500);
}
}
// -------------------------------------------------------------------------
// Upload Grid File (file_type = 'grid')
// Parses Excel and UPSERTs rows into partner_insurance_payout_grid
// -------------------------------------------------------------------------
public function uploadGridFile()
{
try {
/* ====================================================================
* STEP 1 Validate POST input (Agent ID removed)
* ==================================================================== */
$data = $this->request->getPost();
$month = $data['incentive_month'] ?? null;
$createdBy = $data['created_by'] ?? null;
/* ====================================================================
* STEP 2 Validation now only checks for month
* ==================================================================== */
if (empty($month)) {
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => 'Incentive month is required.',
], 200);
}
/* ====================================================================
* STEP 3 Validate uploaded file
* ==================================================================== */
$gridFile = $this->request->getFile('incentive_file_name');
if (!$gridFile || !$gridFile->isValid()) {
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => 'No valid file uploaded.',
], 200);
}
/* ====================================================================
* STEP 4 Move file
* ==================================================================== */
$uploadPath = WRITEPATH . 'uploads/agent/incentive_file/';
if (!is_dir($uploadPath)) mkdir($uploadPath, 0777, true);
$gridFileName = time() . '_' . $gridFile->getRandomName();
$gridFile->move($uploadPath, $gridFileName);
/* ====================================================================
* STEP 5 Save File record & GET THE ID
* ==================================================================== */
$fileData = [
'incentive_month' => $month,
'incentive_file_name' => $gridFileName,
'file_type' => 'grid',
'is_active' => 1,
'created_by' => $createdBy,
];
// insert() with returnID enabled returns the inserted primary key in CI4.
$fileId = $this->AgentIncentiveFileModel->insert($fileData, true);
if (empty($fileId) || (int)$fileId <= 0) {
$modelErrors = $this->AgentIncentiveFileModel->errors();
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => !empty($modelErrors) ? $modelErrors : 'Unable to create incentive file record.',
], 200);
}
/* ====================================================================
* STEP 6 Parse Excel
* ==================================================================== */
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($uploadPath . $gridFileName);
$rows = $spreadsheet->getActiveSheet()->toArray(null, true, true, false);
if (empty($rows)) {
return $this->respond(['status' => 'success', 'code' => 200, 'data' => 'File empty.'], 200);
}
/* ====================================================================
* STEP 7 Clean Numeric Helper (Modified to store only numbers)
* ==================================================================== */
$extractNumeric = static function ($value): ?float {
$str = strtolower(trim((string) $value));
if ($str === '' || $str === 'nan') return null;
if (str_contains($str, '+')) {
$sum = 0.0; $hasValid = false;
foreach (explode('+', $str) as $part) {
$cleanPart = preg_replace('/[^0-9.]/', '', $part);
if (is_numeric($cleanPart)) { $sum += (float) $cleanPart; $hasValid = true; }
}
return $hasValid ? $sum : null;
}
// Strip everything except numbers and decimals
$str = preg_replace('/[^0-9.]/', '', $str);
return is_numeric($str) ? (float) $str : null;
};
/* ====================================================================
* STEP 8 Process Rows
* ==================================================================== */
$currentVehicleType = null;
$layoutHasComp = true;
$insertedCount = 0;
foreach ($rows as $row) {
while (count($row) < 9) $row[] = null;
$col0 = trim((string) $row[0]);
$col1 = trim((string) $row[1]);
// Section Header Detection
if ($col0 !== '' && ($col1 === '' || strtolower($col1) === 'nan') && strtoupper($col0) !== 'INSURER') {
$currentVehicleType = strtoupper($col0);
continue;
}
// Column Header Detection (Layout A vs B)
if (strtoupper($col0) === 'INSURER') {
$col4Upper = strtoupper((string)$row[4]);
$layoutHasComp = !preg_match('/(FUEL|PETROL|DIESEL|TP)/', $col4Upper);
continue;
}
if (empty($col0) || $currentVehicleType === null) continue;
// Mapping with Clean Numbers
if ($layoutHasComp) {
$insurer = strtoupper($col0);
$rto = strtoupper((string)$row[1]);
$segment = strtoupper((string)$row[2]);
// We extract numeric values only for the storage
$comp = $extractNumeric($row[3]);
$tp = $extractNumeric($row[4]);
$od = null;
$fuel = null;
$brokerName = trim((string)$row[6]);
$brokerExcelComp = $extractNumeric($row[7]);
$brokerExcelTp = $extractNumeric($row[8]);
$brokerExcelOd = null;
} else {
$insurer = strtoupper($col0);
$rto = strtoupper((string)$row[1]);
$segment = strtoupper((string)$row[2]);
$comp = $extractNumeric($row[3]);
$tp = null;
$od = null;
$fuel = strtoupper((string)$row[4]);
$brokerName = trim((string)$row[6]);
$brokerExcelComp = null;
$brokerExcelTp = $extractNumeric($row[7]);
$brokerExcelOd = null;
}
// OD logic (if COMP contains an OD value)
if ($comp !== null && stripos((string)$row[3], 'OD') !== false && $tp === null) {
$od = $comp; $comp = null;
$brokerExcelOd = $brokerExcelComp; $brokerExcelComp = null;
}
$gridRow = [
'partner_agent_incentive_file_id' => $fileId, // STORE THE FILE ID HERE
'vehicle_type' => $currentVehicleType,
'fuel' => $fuel,
'insurer' => $insurer,
'rto' => $rto,
'broker_name' => $brokerName,
'segment' => $segment,
'comp' => $comp,
'tp' => $tp,
'od' => $od,
'remarks' => trim((string)($row[5] ?? '')),
'broker_comp' => $brokerExcelComp,
'broker_tp' => $brokerExcelTp,
'broker_od' => $brokerExcelOd,
];
$gridRow['created_by'] = $createdBy;
$this->PayoutGridModel->insert($gridRow);
$insertedCount++;
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [
'file_id' => $fileId,
'inserted' => $insertedCount,
'message' => "Processed: {$insertedCount} new",
],
], 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 getGridData()
{
try {
$request = $this->request;
$role = $request->getGet('role');
$file_id = $request->getGet('file_id');
// 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,fuel,broker_name,comp,tp,od,remarks,broker_comp,
broker_tp,broker_od,created_by,created_at,updated_by,updated_at");
} else {
// Default selection if no plan_type or invalid plan_type
$builder->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, fuel,broker_name,comp,tp,od,remarks,broker_comp,
broker_tp,broker_od,created_by,created_at,updated_by,updated_at');
}
}
if ($file_id) {
$builder->where('partner_agent_incentive_file_id', $file_id);
} else {
$maxRow = $this->PayoutGridModel
->selectMax('partner_agent_incentive_file_id')
->first();
// Model may return array or object based on global returnType.
$maxFileId = null;
if (is_array($maxRow)) {
$maxFileId = $maxRow['partner_agent_incentive_file_id'] ?? null;
} elseif (is_object($maxRow)) {
$maxFileId = $maxRow->partner_agent_incentive_file_id ?? null;
}
if (!empty($maxFileId)) {
$builder->where('partner_agent_incentive_file_id', $maxFileId);
} else {
// No data case
return $this->respond([
'status' => 'failed',
'code' => 404,
'data' => 'No data found'
], 404);
}
}
$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);
}
// -------------------------------------------------------------------------
// Update grid row by id
// Route: POST /agent/updateGrid
// -------------------------------------------------------------------------
public function updateGrid()
{
try {
$jsonData = (array) ($this->request->getJSON(true) ?? []);
$postData = $this->request->getPost() ?? [];
$data = !empty($jsonData) ? $jsonData : $postData;
$id = isset($data['id']) ? (int) $data['id'] : 0;
if ($id <= 0) {
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => 'id is required.',
], 200);
}
$existing = $this->PayoutGridModel->find($id);
if (empty($existing)) {
return $this->respond([
'status' => 'failed',
'code' => 404,
'data' => 'Grid record not found.',
], 200);
}
$updateData = [
'vehicle_type' => array_key_exists('vehicle_type', $data) ? $data['vehicle_type'] : null,
'fuel' => array_key_exists('fuel', $data) ? $data['fuel'] : null,
'rto' => array_key_exists('rto', $data) ? $data['rto'] : null,
'segment' => array_key_exists('segment', $data) ? $data['segment'] : null,
'broker_name' => array_key_exists('broker_name', $data) ? $data['broker_name'] : null,
'comp' => array_key_exists('comp', $data) ? $data['comp'] : null,
'tp' => array_key_exists('tp', $data) ? $data['tp'] : null,
'od' => array_key_exists('od', $data) ? $data['od'] : null,
'broker_comp' => array_key_exists('broker_comp', $data) ? $data['broker_comp'] : null,
'broker_tp' => array_key_exists('broker_tp', $data) ? $data['broker_tp'] : null,
'broker_od' => array_key_exists('broker_od', $data) ? $data['broker_od'] : null,
'remarks' => array_key_exists('remarks', $data) ? $data['remarks'] : null,
];
$hasAnyField = false;
foreach (array_keys($updateData) as $field) {
if (array_key_exists($field, $data)) {
$hasAnyField = true;
break;
}
}
if (!$hasAnyField) {
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => 'No update fields provided.',
], 200);
}
if (array_key_exists('updated_by', $data)) {
$updateData['updated_by'] = $data['updated_by'];
}
$this->PayoutGridModel->updateGridById($id, $updateData);
$updated = $this->PayoutGridModel->find($id);
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => $updated,
], 200);
} catch (\Exception $e) {
return $this->respond([
'status' => 'failed',
'code' => 500,
'data' => $e->getMessage(),
], 500);
}
}
// -------------------------------------------------------------------------
// 2. Download Filtered Grid in Excel
// Route: $routes->get('agent/downloadGrid', 'AgentIncentiveController::downloadGridInExcel');
// Purpose: Applies the exact same filters as getGridData/(:num)(), 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']);
} elseif ($plan_type === 'tp') {
$sheet->setCellValue('F' . $rowNumber, $row['tp']);
} elseif ($plan_type === 'od') {
$sheet->setCellValue('F' . $rowNumber, $row['od']);
} else {
$sheet->setCellValue('F' . $rowNumber, $row['comp']);
$sheet->setCellValue('G' . $rowNumber, $row['tp']);
$sheet->setCellValue('H' . $rowNumber, $row['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);
}
}
}

View File

@ -1172,5 +1172,413 @@ class DashboardController extends ResourceController
return array_values($final); return array_values($final);
} }
// ─────────────────────────────────────────────────────────────────────────────
// DashboardController.php — Partner Portal API methods
// Routes:
// GET partner/(:num)/details → partnerDetails($id)
// GET partner/(:num)/policies → partnerPolicies($id)
// GET partner/(:num)/renewals → partnerRenewals($id) ?days=20
// GET partner/(:num)/earnings → partnerEarnings($id)
// ─────────────────────────────────────────────────────────────────────────────
// ══════════════════════════════════════════════════════════════════════════════
// GET partner/{id}/details
// agent_id = partner_agent.id
// Joins: partner_policy (agent_id), partner_enquiry (agent_id),
// partner_endorsement_request (agent_id)
// ══════════════════════════════════════════════════════════════════════════════
public function partnerDetails($id)
{
$ref = [];
try {
if (empty($id)) {
return $this->respond([
'status' => 'error',
'code' => 400,
'data' => [],
'message' => 'Missing required parameter: id',
], 200);
}
// ── 1. Agent profile (partner_agent.id = $id)
$agent = $this->db->table('partner_agent pa')
->select('
pa.id,
pa.name AS agent_name,
pa.agent_code,
pa.mobile,
pa.email,
pa.is_active,
ps.name AS manager_name
')
->join('partner_staff ps', 'ps.id = pa.manager_id', 'left')
->where('pa.id', $id)
->get()->getRowArray();
if (empty($agent)) {
throw new \RuntimeException('Partner not found.', 404);
}
// -- 2. Policy counts + premium + commission
// mapped_policies = ALL policies under this agent (226)
// issued_policies = policy_number IS NOT NULL AND is_active = 1 (217)
// pending_policies = policy_number IS NULL (9 — raised but not yet issued)
// total_premium / commission = from issued policies only
$policyStats = $this->db->table('partner_policy pp')
->select('
COUNT(pp.id) AS mapped_policies,
SUM(CASE WHEN pp.policy_number IS NOT NULL AND pp.is_active = 1 AND pp.premium_amount IS NOT NULL THEN 1 ELSE 0 END) AS issued_policies,
SUM(CASE WHEN pp.policy_number IS NULL THEN 1 ELSE 0 END) AS pending_policies,
COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL THEN pp.premium_amount ELSE 0 END), 0) AS total_premium,
COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL THEN pp.premium_amount * 0.15 ELSE 0 END), 0) AS commission_earned
')
->where('pp.agent_id', $id)
->get()->getRowArray();
// ── 3. Enquiry counts
// enquiry_status enum: To be assigned | Assigned | In progress | Completed
$enquiryStats = $this->db->table('partner_enquiry pe')
->select('
COUNT(pe.id) AS enquiry_total,
SUM(CASE WHEN pe.enquiry_status = "Completed" THEN 1 ELSE 0 END) AS enquiry_completed,
SUM(CASE WHEN pe.enquiry_status != "Completed" THEN 1 ELSE 0 END) AS enquiry_pending
')
->where('pe.agent_id', $id)
->where('pe.is_active', 1)
->get()->getRowArray();
// ── 4. Endorsement counts
// status is varchar(20) — adjust "Completed" to match your actual values
$endorseStats = $this->db->table('partner_endorsement_request per')
->select('
COUNT(per.id) AS endorsement_total,
SUM(CASE WHEN per.status = "Completed" THEN 1 ELSE 0 END) AS endorsement_done,
SUM(CASE WHEN per.status != "Completed" THEN 1 ELSE 0 END) AS endorsement_pending
')
->where('per.agent_id', $id)
->where('per.is_active', 1)
->get()->getRowArray();
// ── Merge everything
$data = array_merge(
$agent,
[
'status' => $agent['is_active'] ? 'Active' : 'Inactive',
'commission_rate' => 15,
],
$policyStats ?? [],
$enquiryStats ?? [],
$endorseStats ?? [],
);
$ref['message'] = 'Partner details retrieved successfully.';
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => $data,
'ref' => $ref,
], 200);
} catch (\Throwable $e) {
if ($e instanceof \RuntimeException && $e->getCode() === 404) {
$ref['message'] = 'No Data Found';
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [],
'message' => 'No Data Found',
'ref' => $ref,
], 200);
}
$isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException
|| $e instanceof \mysqli_sql_exception
|| $e instanceof \PDOException;
if ($isDbError) {
$ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine();
$ref['message'] = 'Database Error Occurred.';
} else {
$ref['message'] = 'An unexpected error occurred: ' . $e->getMessage();
}
return $this->respond([
'status' => 'error',
'code' => 500,
'data' => [],
'ref' => $ref,
], 200);
}
} }
// ══════════════════════════════════════════════════════════════════════════════
// GET partner/{id}/policies
// partner_policy.agent_id = $id
// holder_name → pp.insured_name (the actual insured person, NOT agent)
// product → pp.product (varchar 50), falls back to pp.vehicle_type
// policy_no → pp.policy_number
// ══════════════════════════════════════════════════════════════════════════════
public function partnerPolicies($id)
{
$ref = [];
try {
if (empty($id)) {
return $this->respond([
'status' => 'error',
'code' => 400,
'data' => [],
'message' => 'Missing required parameter: id',
], 200);
}
$results = $this->db->table('partner_policy pp')
->select('
pp.policy_number AS policy_no,
pp.insured_name AS holder_name,
COALESCE(NULLIF(pp.product, ""), pp.vehicle_type) AS product,
pp.premium_amount AS premium,
DATE_FORMAT(pp.issued_date, "%d-%m-%Y") AS issued_date,
DATE_FORMAT(pp.end_date, "%d-%m-%Y") AS expiry_date
')
->where('pp.agent_id', $id)
->where('pp.policy_number IS NOT NULL')
->where('pp.premium_amount IS NOT NULL')
->where('pp.is_active', 1)
->orderBy('pp.issued_date', 'DESC')
->get()->getResultArray();
$ref['total_records'] = count($results);
if (empty($results)) {
throw new \RuntimeException('No policies found for this partner.', 404);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => $results,
'ref' => $ref,
], 200);
} catch (\Throwable $e) {
if ($e instanceof \RuntimeException && $e->getCode() === 404) {
$ref['message'] = 'No Data Found';
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [],
'message' => 'No Data Found',
'ref' => $ref,
], 200);
}
$isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException
|| $e instanceof \mysqli_sql_exception
|| $e instanceof \PDOException;
if ($isDbError) {
$ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine();
$ref['message'] = 'Database Error Occurred.';
} else {
$ref['message'] = 'An unexpected error occurred: ' . $e->getMessage();
}
return $this->respond([
'status' => 'error',
'code' => 500,
'data' => [],
'ref' => $ref,
], 200);
}
}
// ══════════════════════════════════════════════════════════════════════════════
// GET partner/{id}/renewals?days=20
// partner_policy.agent_id = $id
// holder_name → pp.insured_name
// premium → pp.premium_amount
// ══════════════════════════════════════════════════════════════════════════════
public function partnerRenewals($id)
{
$ref = [];
$days = (int) ($this->request->getGet('days') ?? 20);
try {
if (empty($id)) {
return $this->respond([
'status' => 'error',
'code' => 400,
'data' => [],
'message' => 'Missing required parameter: id',
], 200);
}
$results = $this->db->table('partner_policy pp')
->select('
pp.policy_number AS policy_no,
pp.insured_name AS holder_name,
pp.premium_amount AS premium,
DATE_FORMAT(pp.end_date, "%d-%m-%Y") AS end_date,
DATEDIFF(pp.end_date, CURDATE()) AS days_left
')
->where('pp.agent_id', $id)
->where('pp.is_active', 1)
->where('pp.policy_number IS NOT NULL')
->where('pp.premium_amount IS NOT NULL')
->where('pp.end_date >= CURDATE()', null, false)
->where("pp.end_date <= DATE_ADD(CURDATE(), INTERVAL {$days} DAY)", null, false)
->orderBy('pp.end_date', 'ASC')
->get()->getResultArray();
$ref['days_filter'] = $days;
$ref['total_records'] = count($results);
if (empty($results)) {
throw new \RuntimeException('No renewals due within ' . $days . ' days.', 404);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => $results,
'ref' => $ref,
], 200);
} catch (\Throwable $e) {
if ($e instanceof \RuntimeException && $e->getCode() === 404) {
$ref['message'] = 'No Data Found';
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [],
'message' => 'No Data Found',
'ref' => $ref,
], 200);
}
$isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException
|| $e instanceof \mysqli_sql_exception
|| $e instanceof \PDOException;
if ($isDbError) {
$ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine();
$ref['message'] = 'Database Error Occurred.';
} else {
$ref['message'] = 'An unexpected error occurred: ' . $e->getMessage();
}
return $this->respond([
'status' => 'error',
'code' => 500,
'data' => [],
'ref' => $ref,
], 200);
}
}
// ══════════════════════════════════════════════════════════════════════════════
// GET partner/{id}/earnings
// partner_policy.agent_id = $id
// Groups by issued_date month → month_key (YYYY-MM), month_label (Month YYYY)
// paid = MAX(is_data_accuracy_checked) — 1 if all policies in month are checked
// No month_key param → returns ALL months (FY filtering done client-side in Dart)
// ══════════════════════════════════════════════════════════════════════════════
public function partnerEarnings($id)
{
$ref = [];
$monthKey = $this->request->getGet('month_key') ?? null;
try {
if (empty($id)) {
return $this->respond([
'status' => 'error',
'code' => 400,
'data' => [],
'message' => 'Missing required parameter: id',
], 200);
}
$builder = $this->db->table('partner_policy pp');
$builder->select("
DATE_FORMAT(pp.issued_date, '%Y-%m') AS month_key,
DATE_FORMAT(pp.issued_date, '%M %Y') AS month_label,
COUNT(pp.id) AS policies,
COALESCE(SUM(pp.premium_amount), 0) AS premium,
COALESCE(SUM(pp.premium_amount * 0.15), 0) AS commission,
COALESCE(SUM(pp.premium_amount * 0.15 * 0.10), 0) AS tds,
COALESCE(SUM(pp.premium_amount * 0.15 * 0.90), 0) AS net_payout,
MAX(pp.is_data_accuracy_checked) AS paid
");
$builder->where('pp.agent_id', $id);
$builder->where('pp.is_active', 1);
$builder->where('pp.policy_number IS NOT NULL');
$builder->where('pp.premium_amount IS NOT NULL');
if (!empty($monthKey)) {
$builder->where("DATE_FORMAT(pp.issued_date, '%Y-%m')", $monthKey);
}
$builder->groupBy("DATE_FORMAT(pp.issued_date, '%Y-%m')");
$builder->orderBy('month_key', 'ASC');
$results = $builder->get()->getResultArray();
$ref['month_filter'] = $monthKey ?? 'all';
$ref['total_records'] = count($results);
if (empty($results)) {
throw new \RuntimeException('No earning data found for this partner.', 404);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => $results,
'ref' => $ref,
], 200);
} catch (\Throwable $e) {
if ($e instanceof \RuntimeException && $e->getCode() === 404) {
$ref['message'] = 'No Data Found';
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [],
'message' => 'No Data Found',
'ref' => $ref,
], 200);
}
$isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException
|| $e instanceof \mysqli_sql_exception
|| $e instanceof \PDOException;
if ($isDbError) {
$ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine();
$ref['message'] = 'Database Error Occurred.';
} else {
$ref['message'] = 'An unexpected error occurred: ' . $e->getMessage();
}
return $this->respond([
'status' => 'error',
'code' => 500,
'data' => [],
'ref' => $ref,
], 200);
}
}
}

View File

@ -47,10 +47,24 @@ class EndorsementController extends ResourceController
$builder = $this->EndorsementModel $builder = $this->EndorsementModel
->select('partner_endorsement_request.*, ->select('partner_endorsement_request.*,
CASE
WHEN partner_endorsement_request.policy_start_date = "0000-00-00"
OR YEAR(partner_endorsement_request.policy_start_date) < 2000
THEN NULL
ELSE DATE_FORMAT(partner_endorsement_request.policy_start_date, "%Y-%m-%d")
END as policy_start_date,
CASE
WHEN partner_endorsement_request.policy_end_date = "0000-00-00"
OR YEAR(partner_endorsement_request.policy_end_date) < 2000
THEN NULL
ELSE DATE_FORMAT(partner_endorsement_request.policy_end_date, "%Y-%m-%d")
END as policy_end_date,
i.name as insurer_name, i.name as insurer_name,
i.short_name as insurer_short_name, i.short_name as insurer_short_name,
ib.branch_name as insurer_branch, ib.branch_name as insurer_branch,
c.client_name, c.phone as client_phone, c.email as client_email, c.client_name, c.phone as client_phone, c.email as client_email,
partner_endorsement_request.agent_id,
pa.agent_code as agent_code,
pa.name as agent_name, pa.name as agent_name,
pp.enquiry_id, pp.enquiry_id,
etm.endorsement_type as endorsement_type_value, etm.endorsement_type as endorsement_type_value,
@ -125,8 +139,14 @@ class EndorsementController extends ResourceController
foreach ($data as $key => $row) { foreach ($data as $key => $row) {
$data[$key]['created_at'] = date('d-m-Y h:i A', strtotime($row['created_at'])); $data[$key]['created_at'] = date('d-m-Y h:i A', strtotime($row['created_at']));
$data[$key]['updated_at'] = date('d-m-Y h:i A', strtotime($row['updated_at'])); $data[$key]['updated_at'] = date('d-m-Y h:i A', strtotime($row['updated_at']));
$data[$key]['policy_start_date'] = date('d-m-Y', strtotime($row['policy_start_date'])); // ✅ NULL-safe date formatting
$data[$key]['policy_end_date'] = date('d-m-Y', strtotime($row['policy_end_date'])); $data[$key]['policy_start_date'] = (!empty($row['policy_start_date']) && $row['policy_start_date'] !== '0000-00-00')
? date('d-m-Y', strtotime($row['policy_start_date']))
: null;
$data[$key]['policy_end_date'] = (!empty($row['policy_end_date']) && $row['policy_end_date'] !== '0000-00-00')
? date('d-m-Y', strtotime($row['policy_end_date']))
: null;
} }
return $this->respond(['status' => 'success','code' => 200,'data' => $data], 200); return $this->respond(['status' => 'success','code' => 200,'data' => $data], 200);
@ -268,26 +288,58 @@ class EndorsementController extends ResourceController
if (!$endorsement) { return $this->respond([ 'status' => 'failed', 'code' => 404,'data' => 'Endorsement not found' ], 404); } if (!$endorsement) { return $this->respond([ 'status' => 'failed', 'code' => 404,'data' => 'Endorsement not found' ], 404); }
// FILE UPLOAD // FILE UPLOAD
$uploadedCompletionFile = $endorsement['endorsement_file_name']; $uploadedOriginalCompletionFile = $endorsement['endorsement_file_name'];
$uploadFile = $this->request->getFile('endorsement_file_name'); $uploadOriginalFile = $this->request->getFile('endorsement_file_name');
if ($uploadFile && $uploadFile->isValid()) { if ($uploadOriginalFile && $uploadOriginalFile->isValid()) {
$uploadPath = WRITEPATH . 'uploads/endorsement/'; $uploadOriginalPath = WRITEPATH . 'uploads/endorsement/';
if (!is_dir($uploadPath)) { if (!is_dir($uploadOriginalPath)) {
mkdir($uploadPath, 0777, true); mkdir($uploadOriginalPath, 0777, true);
} }
$uploadedCompletionFile = time() . '_' . $uploadFile->getRandomName(); $uploadedOriginalCompletionFile = time() . '_' . $uploadOriginalFile->getRandomName();
$uploadFile->move($uploadPath, $uploadedCompletionFile); $uploadOriginalFile->move($uploadOriginalPath, $uploadedOriginalCompletionFile); // ✅ fixed variable
} }
//COMMON UPDATE FIELDS // FILE REVISED UPLOAD
$uploadedRevisedCompletionFile = $endorsement['endorsement_completion_file'];
$uploadRevisedFile = $this->request->getFile('endorsement_completion_file');
if ($uploadRevisedFile && $uploadRevisedFile->isValid()) {
$uploadRevisedPath = WRITEPATH . 'uploads/endorsement/endorsement_pdf/';
if (!is_dir($uploadRevisedPath)) {
mkdir($uploadRevisedPath, 0777, true);
}
$uploadedRevisedCompletionFile = time() . '_' . $uploadRevisedFile->getRandomName();
$uploadRevisedFile->move($uploadRevisedPath, $uploadedRevisedCompletionFile);
}
/*
* COMMON UPDATE FIELDS
* Keep update mapping explicit so fields sent by frontend are not silently ignored.
*/
$updateData = []; $updateData = [];
// Update only if provided (avoid null overwrite) // Update only if provided (avoid null overwrite)
if (isset($reqData['policy_from'])) {
$updateData['policy_from'] = $reqData['policy_from'];
}
if (isset($reqData['policy_number'])) {
$updateData['policy_number'] = $reqData['policy_number'];
}
if (isset($reqData['manager_id'])) {
$updateData['manager_id'] = $reqData['manager_id'];
}
if (isset($reqData['agent_id'])) {
$updateData['agent_id'] = $reqData['agent_id'];
}
if (isset($reqData['endorsement_type'])) { if (isset($reqData['endorsement_type'])) {
$updateData['endorsement_type'] = $reqData['endorsement_type']; $updateData['endorsement_type'] = $reqData['endorsement_type'];
} }
@ -316,17 +368,26 @@ class EndorsementController extends ResourceController
$updateData['endorsement_premium'] = $reqData['endorsement_premium']; $updateData['endorsement_premium'] = $reqData['endorsement_premium'];
} }
if (isset($reqData['commission_amount'])) {
$updateData['commission_amount'] = $reqData['commission_amount'];
}
if (isset($reqData['pending_days'])) { if (isset($reqData['pending_days'])) {
$updateData['pending_days'] = $reqData['pending_days']; $updateData['pending_days'] = $reqData['pending_days'];
} }
// Always update both files (new or existing)
$updateData['endorsement_file_name'] = $uploadedOriginalCompletionFile;
$updateData['endorsement_completion_file'] = $uploadedRevisedCompletionFile; // ✅ fixed variable
// Always update file (either old or new) /*
$updateData['endorsement_file_name'] = $uploadedCompletionFile; * External Policy Editable Fields
* Use incoming policy_from first (if provided), fallback to stored value.
// External Policy Editable Fields * This prevents missing updates when stored value casing differs (external/External).
if ($endorsement['policy_from'] === 'External') { */
$effectivePolicyFrom = $reqData['policy_from'] ?? $endorsement['policy_from'] ?? '';
if (strcasecmp(trim((string)$effectivePolicyFrom), 'External') === 0) {
if (isset($reqData['insurer_id'])) { if (isset($reqData['insurer_id'])) {
$updateData['insurer_id'] = $reqData['insurer_id'] ?? null; $updateData['insurer_id'] = $reqData['insurer_id'] ?? null;
@ -372,6 +433,7 @@ class EndorsementController extends ResourceController
if (!empty($staff) && $staff['role_id'] == 4) { if (!empty($staff) && $staff['role_id'] == 4) {
$updateData['is_data_accuracy_checked'] = 1; $updateData['is_data_accuracy_checked'] = 1;
$updateData['status'] = "Closed" ;
} }
} }
@ -408,6 +470,30 @@ class EndorsementController extends ResourceController
} }
} }
public function deleteEndorsement()
{
try {
$id = $this->request->getGet('id');
// check if EndorsementModel exists
$file = $this->EndorsementModel->find((int)$id);
if (!$file) {
return $this->respond(['status' => 'failed','code' => 200, 'data' => 'Endorsement not found'], 200);
}
// update status
$this->EndorsementModel->update($id, ['is_active' => 0]);
return $this->respond([
'status' => 'success', 'code' => 200,'data' => "Endorsement Deleted"], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed','code' => 500,'data' => $e->getMessage()], 500);
}
}
// public function createEndorsement() // public function createEndorsement()
// { // {
@ -556,6 +642,7 @@ class EndorsementController extends ResourceController
{ {
try { try {
$id = $this->request->getGet('id'); $id = $this->request->getGet('id');
$type = $this->request->getGet('type'); // 'completion' or 'original'
if (!$id) { if (!$id) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 200);
@ -568,7 +655,23 @@ class EndorsementController extends ResourceController
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200); return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 200);
} }
$filePath = WRITEPATH . 'uploads/endorsement/' . $fileRecord['endorsement_file_name']; // $filePath = WRITEPATH . 'uploads/endorsement/' . $fileRecord['endorsement_file_name'];
// ✅ If type is null/empty → original (default)
// ✅ If type = 'completion' → endorsement_pdf/ subfolder
if (!$type || $type === 'original') {
$fileName = $fileRecord['endorsement_file_name'] ?? null;
$subPath = '';
} else if ($type === 'completion') {
$fileName = $fileRecord['endorsement_completion_file'] ?? null;
$subPath = 'endorsement_pdf/';
}
if (!$fileName) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'No file uploaded yet'], 200);
}
$filePath = WRITEPATH . 'uploads/endorsement/' . $subPath . $fileName;
if (!file_exists($filePath)) { if (!file_exists($filePath)) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 200); return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 200);
@ -582,7 +685,7 @@ class EndorsementController extends ResourceController
} }
} }
public function EndorsementFilePath() public function EndorsementFilePathGOWTHAM()
{ {
try { try {
$endorsementId = $this->request->getGet('endorsement_id'); $endorsementId = $this->request->getGet('endorsement_id');
@ -623,11 +726,138 @@ class EndorsementController extends ResourceController
} }
} }
public function EndorsementFilePath()
{
try {
$endorsementId = $this->request->getGet('endorsement_id');
if (!$endorsementId) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'endorsement_id is required'], 200);
}
// Fetch record from DB
$fileRecord = $this->EndorsementModel->where('is_active', 1)->find((int)$endorsementId);
if (!$fileRecord) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found in database'], 200);
}
// ✅ STEP 1: Check completion file first
$completionFile = $fileRecord['endorsement_completion_file'] ?? null;
$originalFile = $fileRecord['endorsement_file_name'] ?? null;
if (!empty($completionFile)) {
// ✅ Use completion file path
$fileName = $completionFile;
$filePath = WRITEPATH . 'uploads/endorsement/endorsement_pdf/' . $fileName;
} elseif (!empty($originalFile)) {
// ✅ Fallback to original file path
$fileName = $originalFile;
$filePath = WRITEPATH . 'uploads/endorsement/' . $fileName;
} else {
// ❌ Neither file exists in DB
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'No file uploaded yet'], 200);
}
// ✅ STEP 2: Check file exists on disk
if (!file_exists($filePath)) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 200);
}
// ✅ STEP 3: Stream file to browser / Flutter
$mime = mime_content_type($filePath);
return $this->response
->setHeader('Content-Type', $mime)
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
->setBody(file_get_contents($filePath));
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'message' => $e->getMessage()], 500);
}
}
public function uploadEndorsementFile()
{
try {
$data = $this->request->getPost();
// ✅ Validate required POST fields first
if (empty($data['id']) || empty($data['updated_by'])) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Missing required fields'], 400);
}
$uploadPath = WRITEPATH . 'uploads/endorsement/';
$allowedTypes = ['application/pdf'];
$pdfPath = $uploadPath . 'endorsement_pdf/';
$endorsementPdf = $this->request->getFile('endorsement_completion_file');
// ✅ Check file existence and validity BEFORE accessing its properties
if (!$endorsementPdf || !$endorsementPdf->isValid()) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'No valid file received'], 400);
}
// ✅ MIME validation only after confirming file exists
if (!in_array($endorsementPdf->getMimeType(), $allowedTypes)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Only PDF files are allowed'], 400);
}
// ✅ Fetch existing record to get old file name BEFORE uploading
$existingRecord = $this->EndorsementModel->find($data['id']);
$oldFileName = $existingRecord['endorsement_completion_file'] ?? null;
// ✅ Ensure upload directory exists
if (!is_dir($pdfPath)) {
mkdir($pdfPath, 0777, true);
}
// ✅ Upload new file
$pdfFileName = time() . '_' . $endorsementPdf->getRandomName();
$endorsementPdf->move($pdfPath, $pdfFileName);
// ✅ Guard: ensure file was actually saved on disk
if (!$pdfFileName || !file_exists($pdfPath . $pdfFileName)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'File upload failed'], 400);
}
// ✅ Update DB with new file name
$updateData = [
'endorsement_completion_file' => $pdfFileName,
'updated_by' => $data['updated_by'],
];
$updated = $this->EndorsementModel->update($data['id'], $updateData);
// ✅ DB update failed → delete the newly uploaded file to avoid orphan files
if (!$updated) {
if (file_exists($pdfPath . $pdfFileName)) {
unlink($pdfPath . $pdfFileName);
}
log_message('error', 'DB update failed for endorsement ID: ' . $data['id'] . '. New file removed.');
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => 'DB update failed, file not saved'], 500);
}
// ✅ DB success → NOW safe to delete old file
if ($oldFileName) {
$oldFilePath = $pdfPath . $oldFileName;
if (file_exists($oldFilePath)) {
if (!unlink($oldFilePath)) {
log_message('warning', 'DB updated but failed to delete old file: ' . $oldFilePath);
} else {
log_message('info', 'Old file deleted after successful DB update: ' . $oldFilePath);
}
}
}
log_message('info', 'Endorsement PDF uploaded successfully. ID: ' . $data['id'] . ', File: ' . $pdfFileName);
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data['id']], 200);
} catch (\Exception $e) {
log_message('error', 'uploadEndorsementFile error: ' . $e->getMessage());
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
} }

View File

@ -186,11 +186,13 @@ class EnquiryController extends ResourceController
} }
//Get enquiry details //Get enquiry details
$enquiry = $this->enquiryModel->select('partner_enquiry.* , I.name as insurer_name , I.short_name , VT.vehicle_type as vehicle_type ,PB.name as broker_name ,PA.name as agent_name,PA.agent_code') $enquiry = $this->enquiryModel->select('partner_enquiry.* , I.name as insurer_name , I.short_name , VT.vehicle_type as vehicle_type ,PB.name as broker_name ,PA.name as agent_name,PA.agent_code,PPMM.value as payment_mode_value')
->join('insurers I', 'I.id = partner_enquiry.insurer_id', 'left') ->join('insurers I', 'I.id = partner_enquiry.insurer_id', 'left')
->join('vehicle_type VT', 'VT.id = partner_enquiry.vehicle_type_id', 'left') ->join('vehicle_type VT', 'VT.id = partner_enquiry.vehicle_type_id', 'left')
->join('partner_brokers PB', 'PB.id = partner_enquiry.broker_id', 'left') ->join('partner_brokers PB', 'PB.id = partner_enquiry.broker_id', 'left')
->join('partner_agent PA', 'PA.id = partner_enquiry.agent_id', 'left') ->join('partner_agent PA', 'PA.id = partner_enquiry.agent_id', 'left')
->join('partner_quotation PQ', 'PQ.enquiry_id = partner_enquiry.id', 'left')
->join('partner_payment_mode_master PPMM', 'PPMM.id = PQ.payment_mode_id', 'left')
->where('partner_enquiry.id', $enquiry_id) ->where('partner_enquiry.id', $enquiry_id)
->where('partner_enquiry.is_active', 1) ->where('partner_enquiry.is_active', 1)
->first(); ->first();
@ -834,7 +836,7 @@ class EnquiryController extends ResourceController
try { try {
$data = $this->request->getJSON(true); $data = $this->request->getJSON(true);
print_r($data);die; // print_r($data);die;
if (!isset($data['id'])) { if (!isset($data['id'])) {
return $this->respond(['status' => 'failed', 'code' => 200, 'error' => 'ID Required'], 200); return $this->respond(['status' => 'failed', 'code' => 200, 'error' => 'ID Required'], 200);

View File

@ -1546,11 +1546,12 @@ class ExcelExportController extends ResourceController
'Insured Name', 'Insured Name',
'Insurer', 'Insurer',
'Broker', 'Broker',
'Partner Code And Name',
'Contact Person', 'Contact Person',
'Remarks', 'Remarks',
'Is Financial', 'Is Financial',
'Endorsement Premium Amount', 'Endorsement Premium Amount',
'Commission Amount' 'Commission Amount',
]; ];
$finalData = []; $finalData = [];
@ -2479,6 +2480,7 @@ class ExcelExportController extends ResourceController
per.insured_name, per.insured_name,
I.name AS insurer_name, I.name AS insurer_name,
pb.name AS broker_name, pb.name AS broker_name,
CONCAT(pa.agent_code, ' - ', pa.name) AS agent_details,
per.contact_person, per.contact_person,
per.endorsement_description, per.endorsement_description,
per.financia_or_non_financial, per.financia_or_non_financial,
@ -2548,4 +2550,179 @@ class ExcelExportController extends ResourceController
} }
} }
// grid/download?role=Manager means all list
// grid/download?role=Manager&insurer=HDFC&vehicle_type=TWO%20WHEELER&plan_type=comp
// grid/download?role=Manager&insurer=HDFC&rto=TN&segment=BIKE&plan_type=comp
// grid/download?role=Manager&insurer=HDFC&segment=BIKE&vehicle_type=TWO%20WHEELER&plan_type=comp
public function downloadExcelGrid()
{
try {
$request = $this->request;
// ✅ Inputs
$role = trim((string) ($request->getGet('role') ?? ''));
$fileId = trim((string) ($request->getGet('file_id') ?? ''));
$insurer = trim((string) ($request->getGet('insurer') ?? ''));
$rto = trim((string) ($request->getGet('rto') ?? ''));
$segment = trim((string) ($request->getGet('segment') ?? ''));
$vehicleType = trim((string) ($request->getGet('vehicle_type') ?? ''));
$planType = strtolower(trim((string) ($request->getGet('plan_type') ?? '')));
$search = trim((string) ($request->getGet('search') ?? ''));
$loggedId = trim((string) ($request->getGet('logged_id') ?? ''));
if ($role === '') {
return $this->response->setJSON([
'status' => 'failed',
'code' => 400,
'data' => 'Role is required to export payout grid.',
]);
}
$isAgent = strtolower($role) === 'agent';
// ✅ Get retention rate (only for Agent)
$retentionRate = 0;
if ($isAgent && $loggedId !== '') {
$agent = $this->db->table('partner_agent')
->select('retention_rate')
->where('id', $loggedId)
->get()
->getRowArray();
$retentionRate = isset($agent['retention_rate']) ? (float)$agent['retention_rate'] : 0;
}
// ✅ Main query
$builder = $this->db->table('partner_insurance_payout_grid');
if ($fileId !== '') {
$builder->where('partner_agent_incentive_file_id', $fileId);
} else {
$maxFileId = $this->db->table('partner_insurance_payout_grid')
->selectMax('partner_agent_incentive_file_id')
->get()
->getRow()
->partner_agent_incentive_file_id ?? null;
if (!empty($maxFileId)) {
$builder->where('partner_agent_incentive_file_id', $maxFileId);
}
}
// ✅ Filters
if ($insurer !== '') $builder->where('insurer', $insurer);
if ($rto !== '') $builder->where('rto', $rto);
if ($segment !== '') $builder->where('segment', $segment);
if ($vehicleType !== '') $builder->where('vehicle_type', $vehicleType);
if (in_array($planType, ['comp', 'tp', 'od'])) {
$builder->where("$planType IS NOT NULL", null, false)
->where("$planType !=", '')
->where("$planType !=", '0');
}
// ✅ Search
if ($search !== '') {
$builder->groupStart()
->like('insurer', $search)
->orLike('vehicle_type', $search)
->orLike('segment', $search)
->orLike('rto', $search)
->orLike('remarks', $search)
->groupEnd();
}
// ✅ Fetch rows
$rows = $builder
->select('id, insurer, vehicle_type, segment, rto, comp, tp, od, remarks')
->orderBy('id', 'DESC')
->get()
->getResultArray();
// =========================
// ✅ HEADERS
// =========================
$headers = ['S.No', 'Insurer', 'Vehicle Type', 'Segment', 'RTO'];
if ($isAgent) {
if ($planType === 'comp') {
$headers[] = 'Comp';
} elseif ($planType === 'tp') {
$headers[] = 'TP';
} elseif ($planType === 'od') {
$headers[] = 'OD';
}
} else {
// ✅ Manager / Accounts
$headers[] = 'Comp';
$headers[] = 'TP';
$headers[] = 'OD';
}
$headers[] = 'Remarks';
// =========================
// ✅ DATA
// =========================
$data = [];
$sNo = 1;
foreach ($rows as $row) {
$comp = isset($row['comp']) ? (float)$row['comp'] : 0;
$tp = isset($row['tp']) ? (float)$row['tp'] : 0;
$od = isset($row['od']) ? (float)$row['od'] : 0;
// ✅ Apply retention only for Agent
if ($isAgent) {
$comp -= $retentionRate;
$tp -= $retentionRate;
$od -= $retentionRate;
}
$line = [
$sNo++,
$row['insurer'] ?? '-',
$row['vehicle_type'] ?? '-',
$row['segment'] ?? '-',
$row['rto'] ?? '-',
];
// ✅ Dynamic columns (Agent only)
if ($isAgent) {
if ($planType === 'comp') {
$line[] = $comp;
} elseif ($planType === 'tp') {
$line[] = $tp;
} elseif ($planType === 'od') {
$line[] = $od;
}
} else {
// ✅ Manager / Accounts → always all
$line[] = $comp;
$line[] = $tp;
$line[] = $od;
}
$line[] = $row['remarks'] ?? '-';
$data[] = $line;
}
// ✅ Export
$fileName = "grid_" . date('Ymd_His') . ".xlsx";
return $this->streamExcelFile($headers, $data, 'Payout Grid', $fileName, false);
} catch (\Throwable $e) {
return $this->response->setJSON([
'status' => 'failed',
'code' => 500,
'data' => $e->getMessage(),
]);
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -413,6 +413,82 @@ class PolicyController extends ResourceController
} }
} }
public function updatePolicyCommission()
{
try {
$data = $this->request->getJSON(true);
if (empty($data['id'])) {
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => 'Policy id is required',
], 422);
}
if (!isset($data['commission_amount']) || $data['commission_amount'] === '') {
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => 'commission_amount is required',
], 422);
}
$policyId = (int) $data['id'];
$policy = $this->PolicyModel->find($policyId);
if (!$policy) {
return $this->respond([
'status' => 'failed',
'code' => 404,
'data' => 'Policy not found',
], 404);
}
$commission = (float) $data['commission_amount'];
if ($commission < 0) {
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => 'commission_amount must be positive',
], 422);
}
/*
* Commission-only update endpoint.
* Keeps the existing policy workflow unchanged and updates only required fields.
*/
$updateData = [
'commission_amount' => number_format($commission, 2, '.', ''),
'updated_by' => $data['updated_by'] ?? null,
'updated_on' => date('Y-m-d H:i:s'),
];
if (!$this->PolicyModel->update($policyId, $updateData)) {
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => $this->PolicyModel->errors(),
], 422);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [
'policy_id' => (string) $policyId,
'commission_amount' => $updateData['commission_amount'],
'message' => 'Commission updated successfully',
],
], 200);
} catch (\Exception $e) {
return $this->respond([
'status' => 'failed',
'code' => 500,
'data' => $e->getMessage(),
], 500);
}
}
public function uploadPolicyFile() public function uploadPolicyFile()
{ {
try { try {

View File

@ -181,8 +181,9 @@ if (!function_exists('createBDS')) {
if (!function_exists('format_date_for_database')) { if (!function_exists('format_date_for_database')) {
function format_date_for_database(string $date): ?string function format_date_for_database(?string $date): ?string
{ {
if (empty($date)) return null; // ✅ handle null/empty
$dt = DateTime::createFromFormat('d-m-Y', $date); $dt = DateTime::createFromFormat('d-m-Y', $date);
if ($dt) { if ($dt) {
return $dt->format('Y-m-d'); return $dt->format('Y-m-d');

View File

@ -15,6 +15,7 @@ class AgentIncentiveFileModel extends Model
'agent_id', 'agent_id',
'incentive_month', 'incentive_month',
'incentive_file_name', 'incentive_file_name',
'file_type',
'is_active', 'is_active',
'created_by', 'created_by',
'created_on', 'created_on',
@ -32,8 +33,9 @@ class AgentIncentiveFileModel extends Model
// Validation rules (optional, add as per your need) // Validation rules (optional, add as per your need)
protected $validationRules = [ protected $validationRules = [
'agent_id' => 'required|integer', 'agent_id' => 'permit_empty|integer',
'incentive_month' => 'required|valid_date', 'incentive_month' => 'required|valid_date',
'incentive_file_name'=> 'required|string|max_length[150]' 'incentive_file_name' => 'required|string|max_length[150]',
'file_type' => 'permit_empty|max_length[50]'
]; ];
} }

View File

@ -0,0 +1,78 @@
<?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_comp',
'broker_tp',
'broker_od',
'partner_agent_incentive_file_id',
'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;
// -------------------------------------------------------------------------
// 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();
}
}

View File

@ -30,6 +30,7 @@ class PolicyModel extends Model
'pt_oc_share_details_id', 'pt_oc_share_details_id',
'created_by', 'created_by',
'updated_by', 'updated_by',
'updated_on',
// newly added // newly added
'tp', 'tp',

Binary file not shown.