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
$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
$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/downloadAgentIncentiveFile', 'AgentController::downloadAgentIncentiveFile');
$routes->get('grid', 'AgentIncentiveController::getGridData');
$routes->post('grid/upload', 'AgentIncentiveController::uploadGridFile');
$routes->get('grid/fileList', 'AgentIncentiveController::gridFileList');
//Staff
$routes->get('staff/staffList', 'StaffController::staffList');
@ -123,6 +126,7 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('policy/findPolicy', 'PolicyController::findPolicy');
$routes->post('policy/createPolicy', 'PolicyController::createPolicy');
$routes->post('policy/updatePolicy', 'PolicyController::updatePolicy');
$routes->post('policy/updatePolicyCommission', 'PolicyController::updatePolicyCommission');
$routes->get('policy/downloadPolicyFile', 'PolicyController::downloadPolicyFile');
$routes->post('policy/uploadPolicyFile', 'PolicyController::uploadPolicyFile');
$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/updateEndorsement', 'EndorsementController::updateEndorsement');
$routes->get('endorsement/downloadEndorsementCompletionFile', 'EndorsementController::downloadEndorsementCompletionFile');
$routes->post('endorsement/uploadEndorsementFile', 'EndorsementController::uploadEndorsementFile');
$routes->get('endorsement/deleteEndorsement', 'EndorsementController::deleteEndorsement');
//dashboard
$routes->get('dashboard/managerDashboard', 'DashboardController::managerDashboard');
$routes->get('dashboard/handlerDashboard', 'DashboardController::handlerDashboard');
@ -178,15 +184,43 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$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
$routes->get('invoice/list', 'InvoiceController::invoiceList');
$routes->get('invoice/details', 'InvoiceController::findInvoiceWithItems');
$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->post('invoice/commission-rate-list', 'InvoiceController::getCommissionRateList');
$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
@ -203,7 +237,7 @@ $routes->group('api', ['filter' => 'appSignature'], function ($routes) {
$routes->get('audit/history', 'MasterController::getHistory');
});
});
// common
@ -688,15 +722,3 @@ $routes->get("processjob", "JobWorker::processJob");

View File

@ -269,8 +269,10 @@ class AgentController extends ResourceController
try{
$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);
@ -287,7 +289,7 @@ class AgentController extends ResourceController
$data = $this->request->getPost();
//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)){
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Duplicate Entry.'], 200);
}
@ -311,6 +313,7 @@ class AgentController extends ResourceController
'agent_id' => $data['agent_id'],
'incentive_month' => $data['incentive_month'],
'incentive_file_name' => $incentiveFileName,
'file_type' => 'incentive',
'created_by' => $data['created_by'] ?? null
];
@ -332,7 +335,7 @@ class AgentController extends ResourceController
// 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);
}
@ -353,13 +356,15 @@ class AgentController extends ResourceController
{
try {
$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);
@ -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);
}
// ─────────────────────────────────────────────────────────────────────────────
// 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
->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.short_name as insurer_short_name,
ib.branch_name as insurer_branch,
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,
pp.enquiry_id,
etm.endorsement_type as endorsement_type_value,
@ -125,8 +139,14 @@ class EndorsementController extends ResourceController
foreach ($data as $key => $row) {
$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]['policy_start_date'] = date('d-m-Y', strtotime($row['policy_start_date']));
$data[$key]['policy_end_date'] = date('d-m-Y', strtotime($row['policy_end_date']));
// ✅ NULL-safe date formatting
$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);
@ -268,26 +288,58 @@ class EndorsementController extends ResourceController
if (!$endorsement) { return $this->respond([ 'status' => 'failed', 'code' => 404,'data' => 'Endorsement not found' ], 404); }
// 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/';
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0777, true);
$uploadOriginalPath = WRITEPATH . 'uploads/endorsement/';
if (!is_dir($uploadOriginalPath)) {
mkdir($uploadOriginalPath, 0777, true);
}
$uploadedCompletionFile = time() . '_' . $uploadFile->getRandomName();
$uploadFile->move($uploadPath, $uploadedCompletionFile);
$uploadedOriginalCompletionFile = time() . '_' . $uploadOriginalFile->getRandomName();
$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 = [];
// 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'])) {
$updateData['endorsement_type'] = $reqData['endorsement_type'];
}
@ -316,17 +368,26 @@ class EndorsementController extends ResourceController
$updateData['endorsement_premium'] = $reqData['endorsement_premium'];
}
if (isset($reqData['commission_amount'])) {
$updateData['commission_amount'] = $reqData['commission_amount'];
}
if (isset($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
if ($endorsement['policy_from'] === 'External') {
/*
* External Policy Editable Fields
* Use incoming policy_from first (if provided), fallback to stored value.
* This prevents missing updates when stored value casing differs (external/External).
*/
$effectivePolicyFrom = $reqData['policy_from'] ?? $endorsement['policy_from'] ?? '';
if (strcasecmp(trim((string)$effectivePolicyFrom), 'External') === 0) {
if (isset($reqData['insurer_id'])) {
$updateData['insurer_id'] = $reqData['insurer_id'] ?? null;
@ -372,6 +433,7 @@ class EndorsementController extends ResourceController
if (!empty($staff) && $staff['role_id'] == 4) {
$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()
// {
@ -556,6 +642,7 @@ class EndorsementController extends ResourceController
{
try {
$id = $this->request->getGet('id');
$type = $this->request->getGet('type'); // 'completion' or 'original'
if (!$id) {
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);
}
$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)) {
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 {
$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
$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('vehicle_type VT', 'VT.id = partner_enquiry.vehicle_type_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_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.is_active', 1)
->first();
@ -834,7 +836,7 @@ class EnquiryController extends ResourceController
try {
$data = $this->request->getJSON(true);
print_r($data);die;
// print_r($data);die;
if (!isset($data['id'])) {
return $this->respond(['status' => 'failed', 'code' => 200, 'error' => 'ID Required'], 200);

View File

@ -1546,11 +1546,12 @@ class ExcelExportController extends ResourceController
'Insured Name',
'Insurer',
'Broker',
'Partner Code And Name',
'Contact Person',
'Remarks',
'Is Financial',
'Endorsement Premium Amount',
'Commission Amount'
'Commission Amount',
];
$finalData = [];
@ -2479,6 +2480,7 @@ class ExcelExportController extends ResourceController
per.insured_name,
I.name AS insurer_name,
pb.name AS broker_name,
CONCAT(pa.agent_code, ' - ', pa.name) AS agent_details,
per.contact_person,
per.endorsement_description,
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()
{
try {

View File

@ -181,8 +181,9 @@ if (!function_exists('createBDS')) {
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);
if ($dt) {
return $dt->format('Y-m-d');

View File

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

Binary file not shown.