nhance_partner_be/app/Controllers/AgentIncentiveController.php
2026-04-04 15:06:51 +05:30

625 lines
27 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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\Libraries\PartnerPayoutGridRetention;
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()->getResultArray();
// Partner (Agent) login: pervehicle-type retention from partner_retention_rate (+ partner_vehicle_type name match to grid vehicle_type)
if (strtolower(trim((string) $role)) === 'agent') {
$agentId = (int) trim((string) ($request->getGet('logged_id') ?? $request->getGet('agent_id') ?? 0));
if ($agentId > 0) {
$db = \Config\Database::connect();
$gridResults = PartnerPayoutGridRetention::applyToRows($gridResults, $agentId, $db);
}
}
// 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);
}
}
}