This commit is contained in:
sanjeev.p 2026-04-04 15:06:51 +05:30
parent 80afc350eb
commit 1b1095643d
7 changed files with 559 additions and 25 deletions

View File

@ -64,8 +64,11 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
$routes->get('agent/agentList', 'AgentController::agentList');
$routes->get('agent/agentListForEnquiryCreationDropdown', 'AgentController::agentListForEnquiryCreationDropdown');
$routes->get('agent/findAgent', 'AgentController::findAgent');
$routes->get('agent/partnerVehicleTypeList', 'AgentController::partnerVehicleTypeList');
$routes->post('agent/createAgent', 'AgentController::createAgent');
$routes->post('agent/updateAgent', 'AgentController::updateAgent');
$routes->post('agent/updateAgentVehicleRetention', 'AgentController::updateAgentVehicleRetention');
$routes->post('agent/saveAgentRetentionRatesBulk', 'AgentController::saveAgentRetentionRatesBulk');
$routes->post('agent/updateDeviceToken', 'AgentController::updateDeviceToken');
$routes->post('agent/changeAgentStatus', 'AgentController::changeAgentStatus');
$routes->get('agent/downloadAgentCertificateFile', 'AgentController::downloadAgentCertificateFile');

View File

@ -5,16 +5,282 @@ use CodeIgniter\RESTful\ResourceController;
use App\Controllers\BaseController;
use App\Models\AgentModel;
use App\Models\AgentIncentiveFileModel;
use App\Models\PartnerRetentionRateModel;
use App\Models\PartnerVehicleTypeModel;
class AgentController extends ResourceController
{
protected $AgentModel;
protected $AgentIncentiveFileModel;
protected $PartnerRetentionRateModel;
protected $PartnerVehicleTypeModel;
public function __construct()
{
$this->AgentModel = new AgentModel();
$this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
$this->PartnerRetentionRateModel = new PartnerRetentionRateModel();
$this->PartnerVehicleTypeModel = new PartnerVehicleTypeModel();
}
/**
* @return list<array{vehicle_type_id:int|float, retention_rate:float}>
*/
protected function parseRetentionRatesFromRequest(): array
{
$raw = $this->request->getPost('retention_rates');
if ($raw === null || $raw === '') {
return [];
}
if (is_string($raw)) {
$decoded = json_decode($raw, true);
} else {
$decoded = $raw;
}
if (!is_array($decoded)) {
return [];
}
$out = [];
foreach ($decoded as $row) {
if (!is_array($row)) {
continue;
}
$vtId = $row['vehicle_type_id'] ?? $row['vehicleTypeId'] ?? null;
if ($vtId === null || $vtId === '') {
continue;
}
$rateRaw = $row['retention_rate'] ?? $row['retentionRate'] ?? null;
if ($rateRaw === null || $rateRaw === '') {
continue;
}
$rate = (float) $rateRaw;
if ($rate < 0 || $rate > 100) {
continue;
}
$out[] = [
'vehicle_type_id' => (int) $vtId,
'retention_rate' => round($rate, 2),
];
}
return $out;
}
/**
* Replace retention rows for an agent from the parsed list (main Save).
*/
protected function syncRetentionRatesForAgent(int $agentId, array $rates, ?int $userId): void
{
$this->PartnerRetentionRateModel->where('agent_id', $agentId)->delete();
$now = date('Y-m-d H:i:s');
foreach ($rates as $r) {
$this->PartnerRetentionRateModel->insert([
'agent_id' => $agentId,
'vehicle_type_id' => $r['vehicle_type_id'],
'retention_rate' => $r['retention_rate'],
'is_active' => 1,
'created_by' => $userId,
'created_on' => $now,
'updated_by' => null,
'updated_on' => null,
]);
}
}
/** Active vehicle types for partner retention UI */
public function partnerVehicleTypeList()
{
try {
$data = $this->PartnerVehicleTypeModel
->where('is_active', 1)
->orderBy('vehicle_type', 'ASC')
->findAll();
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
/**
* Upsert one vehicle-type retention row (inline edit on agent details).
*/
public function updateAgentVehicleRetention()
{
try {
$data = $this->request->getJSON(true);
if (!is_array($data) || $data === []) {
$raw = $this->request->getRawInput();
if (is_array($raw) && $raw !== []) {
$data = $raw;
} elseif (is_string($raw) && $raw !== '') {
$decoded = json_decode($raw, true);
if (is_array($decoded)) {
$data = $decoded;
}
}
}
if (!is_array($data) || $data === []) {
$data = $this->request->getPost();
}
if (!is_array($data)) {
$data = [];
}
if (!isset($data['agent_id'], $data['vehicle_type_id'], $data['retention_rate'])) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'agent_id, vehicle_type_id and retention_rate are required'], 200);
}
$agentId = (int) $data['agent_id'];
$vtId = (int) $data['vehicle_type_id'];
$rate = (float) $data['retention_rate'];
if ($rate < 0 || $rate > 100) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'retention_rate must be between 0 and 100'], 200);
}
$agent = $this->AgentModel->find($agentId);
if (!$agent) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Agent not found'], 200);
}
$updatedBy = isset($data['updated_by']) ? (int) $data['updated_by'] : null;
$now = date('Y-m-d H:i:s');
$existing = $this->PartnerRetentionRateModel
->where('agent_id', $agentId)
->where('vehicle_type_id', $vtId)
->first();
if ($existing) {
$this->PartnerRetentionRateModel->update((int) $existing['id'], [
'retention_rate' => round($rate, 2),
'is_active' => 1,
'updated_by' => $updatedBy,
'updated_on' => $now,
]);
} else {
$this->PartnerRetentionRateModel->insert([
'agent_id' => $agentId,
'vehicle_type_id' => $vtId,
'retention_rate' => round($rate, 2),
'is_active' => 1,
'created_by' => $updatedBy,
'created_on' => $now,
]);
}
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
/**
* Bulk upsert retention rows from the retention table (Save all).
* POST JSON or form: agent_id, updated_by, retention_rates (array of {vehicle_type_id, retention_rate}).
* Skips invalid entries; empty retention_rates array returns an error.
*/
public function saveAgentRetentionRatesBulk()
{
try {
$data = $this->request->getJSON(true);
if (!is_array($data) || $data === []) {
$raw = $this->request->getRawInput();
if (is_array($raw) && $raw !== []) {
$data = $raw;
} elseif (is_string($raw) && $raw !== '') {
$decoded = json_decode($raw, true);
if (is_array($decoded)) {
$data = $decoded;
}
}
}
if (!is_array($data) || $data === []) {
$data = $this->request->getPost();
}
if (!is_array($data)) {
$data = [];
}
if (!isset($data['agent_id'])) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'agent_id is required'], 200);
}
$agentId = (int) $data['agent_id'];
$agent = $this->AgentModel->find($agentId);
if (!$agent) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'Agent not found'], 200);
}
$ratesRaw = $data['retention_rates'] ?? null;
if ($ratesRaw === null || $ratesRaw === '') {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'retention_rates is required'], 200);
}
if (is_string($ratesRaw)) {
$rates = json_decode($ratesRaw, true);
} else {
$rates = $ratesRaw;
}
if (!is_array($rates) || $rates === []) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'retention_rates must be a non-empty array'], 200);
}
$updatedBy = isset($data['updated_by']) ? (int) $data['updated_by'] : null;
$now = date('Y-m-d H:i:s');
$saved = 0;
foreach ($rates as $row) {
if (!is_array($row)) {
continue;
}
$vtId = $row['vehicle_type_id'] ?? $row['vehicleTypeId'] ?? null;
if ($vtId === null || $vtId === '') {
continue;
}
$rateRaw = $row['retention_rate'] ?? $row['retentionRate'] ?? null;
if ($rateRaw === null || $rateRaw === '') {
continue;
}
$rate = (float) $rateRaw;
if ($rate < 0 || $rate > 100) {
continue;
}
$vtId = (int) $vtId;
$existing = $this->PartnerRetentionRateModel
->where('agent_id', $agentId)
->where('vehicle_type_id', $vtId)
->first();
if ($existing) {
$this->PartnerRetentionRateModel->update((int) $existing['id'], [
'retention_rate' => round($rate, 2),
'is_active' => 1,
'updated_by' => $updatedBy,
'updated_on' => $now,
]);
} else {
$this->PartnerRetentionRateModel->insert([
'agent_id' => $agentId,
'vehicle_type_id' => $vtId,
'retention_rate' => round($rate, 2),
'is_active' => 1,
'created_by' => $updatedBy,
'created_on' => $now,
]);
}
$saved++;
}
if ($saved === 0) {
return $this->respond(['status' => 'failed', 'code' => 200, 'data' => 'No valid retention rows to save'], 200);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => ['saved_count' => $saved],
], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
// List of all agents
@ -29,6 +295,29 @@ class AgentController extends ResourceController
->where('partner_agent.manager_id' , $id )
->findAll();
if ($data !== []) {
$ids = array_column($data, 'id');
$db = db_connect();
$avgs = $db->table('partner_retention_rate')
->select('agent_id, ROUND(AVG(retention_rate), 2) AS avg_retention', false)
->where('is_active', 1)
->whereIn('agent_id', $ids)
->groupBy('agent_id')
->get()
->getResultArray();
$avgMap = [];
foreach ($avgs as $a) {
$avgMap[(int) $a['agent_id']] = $a['avg_retention'];
}
foreach ($data as &$row) {
$aid = (int) $row['id'];
if (isset($avgMap[$aid])) {
$row['retention_rate'] = $avgMap[$aid];
}
}
unset($row);
}
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
} catch (\Exception $e) {
@ -66,6 +355,15 @@ class AgentController extends ResourceController
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200);
}
$rates = $this->PartnerRetentionRateModel
->select('partner_retention_rate.id as retention_row_id, partner_retention_rate.vehicle_type_id, partner_retention_rate.retention_rate, partner_vehicle_type.vehicle_type')
->join('partner_vehicle_type', 'partner_vehicle_type.id = partner_retention_rate.vehicle_type_id', 'left')
->where('partner_retention_rate.agent_id', (int) $id)
->where('partner_retention_rate.is_active', 1)
->orderBy('partner_vehicle_type.vehicle_type', 'ASC')
->findAll();
$record['retention_by_vehicle'] = $rates;
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $record], 200);
} catch (\Exception $e) {
@ -94,6 +392,8 @@ class AgentController extends ResourceController
$certificateFile->move($uploadPath, $certificateFileName);
}
$rates = $this->parseRetentionRatesFromRequest();
$insertData = [
'name' => $data['name'],
'email' => $data['email'],
@ -104,12 +404,18 @@ class AgentController extends ResourceController
'sales_executive_id' => $data['sales_executive_id'],
'certificate_file_name' => $certificateFileName,
'created_by' => $data['created_by'],
'retention_rate' => $data['retention_rate'] ?? 0
'retention_rate' => null,
];
$this->AgentModel->insert($insertData);
$newId = (int) $this->AgentModel->getInsertID();
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);
if ($rates !== [] && $newId > 0) {
$createdBy = isset($data['created_by']) ? (int) $data['created_by'] : null;
$this->syncRetentionRatesForAgent($newId, $rates, $createdBy);
}
return $this->respond(['status' => 'success', 'code' => 200, 'data' => ['agent_id' => $newId]], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
@ -145,7 +451,7 @@ class AgentController extends ResourceController
'agent_code' => $data['agent_code'] ?? null,
'sales_executive_id' => $data['sales_executive_id'] ?? null,
'updated_by' => $data['updated_by'] ?? null,
'retention_rate' => $data['retention_rate'] ?? $agent['retention_rate']
'retention_rate' => null,
];
if ($certificateFile && $certificateFile->isValid()) {
@ -158,6 +464,12 @@ class AgentController extends ResourceController
$updateData['certificate_file_name'] = $certificateFileName;
}
if (array_key_exists('retention_rates', $data)) {
$rates = $this->parseRetentionRatesFromRequest();
$updatedBy = isset($data['updated_by']) ? (int) $data['updated_by'] : null;
$this->syncRetentionRatesForAgent((int) $id, $rates, $updatedBy);
}
$this->AgentModel->update($id, $updateData);
return $this->respond(['status' => 'success', 'code' => 200, 'data' => []], 200);

View File

@ -9,6 +9,7 @@ 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;
@ -358,7 +359,16 @@ class AgentIncentiveController extends ResourceController
}
}
$gridResults = $builder->get()->getResult();
$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 = [

View File

@ -2,6 +2,7 @@
namespace App\Controllers;
use App\Libraries\PartnerPayoutGridRetention;
use CodeIgniter\RESTful\ResourceController;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
@ -2580,16 +2581,10 @@ class ExcelExportController extends ResourceController
$isAgent = strtolower($role) === 'agent';
// ✅ Get retention rate (only for Agent)
$retentionRate = 0;
// ✅ Pervehicle retention map (Agent only); partner_agent.retention_rate is unused
$retentionMap = [];
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;
$retentionMap = PartnerPayoutGridRetention::buildRetentionMap($this->db, (int) $loggedId);
}
// ✅ Main query
@ -2669,15 +2664,22 @@ class ExcelExportController extends ResourceController
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;
$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;
$compOut = $comp;
$tpOut = $tp;
$odOut = $od;
// ✅ Agent: subtract partner_retention_rate when grid vehicle_type matches partner_vehicle_type for this agent
if ($isAgent && $retentionMap !== []) {
$rate = PartnerPayoutGridRetention::retentionForRow($row, $retentionMap);
if ($rate !== null) {
$compOut = PartnerPayoutGridRetention::adjustPayoutValue($row['comp'] ?? 0, $rate);
$tpOut = PartnerPayoutGridRetention::adjustPayoutValue($row['tp'] ?? 0, $rate);
$odOut = PartnerPayoutGridRetention::adjustPayoutValue($row['od'] ?? 0, $rate);
}
}
$line = [
@ -2691,12 +2693,12 @@ class ExcelExportController extends ResourceController
// ✅ Dynamic columns (Agent only)
if ($isAgent) {
if ($planType === 'comp') {
$line[] = $comp;
$line[] = $compOut;
} elseif ($planType === 'tp') {
$line[] = $tp;
$line[] = $tpOut;
} elseif ($planType === 'od') {
$line[] = $od;
}
$line[] = $odOut;
}
} else {
// ✅ Manager / Accounts → always all
$line[] = $comp;

View File

@ -0,0 +1,166 @@
<?php
namespace App\Libraries;
use CodeIgniter\Database\BaseConnection;
/**
* Partner (agent) grid: match payout grid vehicle_type to partner_vehicle_type via
* partner_retention_rate (agent_id + vehicle_type_id), subtract retention from comp/tp/od.
* Values <= 0 after subtraction are returned as '-'.
*/
class PartnerPayoutGridRetention
{
public static function normalizeVehicleTypeLabel(?string $s): string
{
if ($s === null || $s === '') {
return '';
}
return strtolower(trim(preg_replace('/\s+/u', ' ', $s)));
}
/**
* @return array<string, float> normalized name => rate, and id:{vehicle_type_id} => rate
*/
public static function buildRetentionMap(BaseConnection $db, int $agentId): array
{
if ($agentId <= 0) {
return [];
}
$rows = $db->table('partner_retention_rate prr')
->select('prr.vehicle_type_id, prr.retention_rate, pvt.vehicle_type')
->join('partner_vehicle_type pvt', 'pvt.id = prr.vehicle_type_id', 'left')
->where('prr.agent_id', $agentId)
->where('prr.is_active', 1)
->get()
->getResultArray();
$map = [];
foreach ($rows as $r) {
if (! array_key_exists('retention_rate', $r) || $r['retention_rate'] === null || $r['retention_rate'] === '') {
continue;
}
$rate = (float) $r['retention_rate'];
$name = isset($r['vehicle_type']) ? trim((string) $r['vehicle_type']) : '';
if ($name !== '') {
$map[self::normalizeVehicleTypeLabel($name)] = $rate;
}
$vid = isset($r['vehicle_type_id']) ? (int) $r['vehicle_type_id'] : 0;
if ($vid > 0) {
$map['id:' . $vid] = $rate;
}
}
return $map;
}
/**
* @param array<int, array<string, mixed>> $rows
*
* @return array<int, array<string, mixed>>
*/
public static function applyToRows(array $rows, int $agentId, BaseConnection $db): array
{
if ($agentId <= 0 || $rows === []) {
return $rows;
}
$map = self::buildRetentionMap($db, $agentId);
if ($map === []) {
return $rows;
}
foreach ($rows as &$row) {
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
$rate = null;
if ($key !== '' && isset($map[$key])) {
$rate = $map[$key];
} else {
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
if ($vid > 0 && isset($map['id:' . $vid])) {
$rate = $map['id:' . $vid];
}
}
if ($rate === null) {
continue;
}
foreach (['comp', 'tp', 'od'] as $col) {
if (! array_key_exists($col, $row)) {
continue;
}
$raw = $row[$col];
if ($raw === null || $raw === '' || $raw === '-') {
continue;
}
$base = self::toFloat($raw);
if ($base === null) {
continue;
}
$adj = $base - $rate;
if ($adj <= 0) {
$row[$col] = '-';
} elseif (abs($adj - round($adj)) < 0.00001) {
$row[$col] = (string) (int) round($adj);
} else {
$row[$col] = rtrim(rtrim(number_format($adj, 2, '.', ''), '0'), '.');
}
}
}
unset($row);
return $rows;
}
public static function retentionForRow(array $row, array $map): ?float
{
$key = self::normalizeVehicleTypeLabel(isset($row['vehicle_type']) ? (string) $row['vehicle_type'] : '');
if ($key !== '' && isset($map[$key])) {
return $map[$key];
}
$vid = isset($row['vehicle_type_id']) ? (int) $row['vehicle_type_id'] : 0;
if ($vid > 0 && isset($map['id:' . $vid])) {
return $map['id:' . $vid];
}
return null;
}
/**
* @return float|int|string|null '-' when adjusted <= 0, else numeric display
*/
public static function adjustPayoutValue($raw, float $rate)
{
if ($raw === null || $raw === '' || $raw === '-') {
return $raw;
}
$base = self::toFloat($raw);
if ($base === null) {
return $raw;
}
$adj = $base - $rate;
if ($adj <= 0) {
return '-';
}
if (abs($adj - round($adj)) < 0.00001) {
return (int) round($adj);
}
return rtrim(rtrim(number_format($adj, 2, '.', ''), '0'), '.');
}
private static function toFloat($raw): ?float
{
if (is_numeric($raw)) {
return (float) $raw;
}
$clean = preg_replace('/[^0-9.\-]/', '', (string) $raw);
if ($clean === '' || $clean === '-' || $clean === '.') {
return null;
}
return (float) $clean;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class PartnerRetentionRateModel extends Model
{
protected $table = 'partner_retention_rate';
protected $primaryKey = 'id';
protected $allowedFields = [
'agent_id',
'vehicle_type_id',
'retention_rate',
'is_active',
'created_by',
'created_on',
'updated_by',
'updated_on',
];
protected $useTimestamps = false;
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class PartnerVehicleTypeModel extends Model
{
protected $table = 'partner_vehicle_type';
protected $primaryKey = 'id';
protected $allowedFields = [
'vehicle_type',
'is_active',
'created_at',
];
protected $useTimestamps = false;
}