diff --git a/app/Config/Routes.php b/app/Config/Routes.php index d22dd0b..a63928e 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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'); diff --git a/app/Controllers/AgentController.php b/app/Controllers/AgentController.php index 29f6658..834677b 100644 --- a/app/Controllers/AgentController.php +++ b/app/Controllers/AgentController.php @@ -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 + */ + 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); diff --git a/app/Controllers/AgentIncentiveController.php b/app/Controllers/AgentIncentiveController.php index 90697df..123c211 100644 --- a/app/Controllers/AgentIncentiveController.php +++ b/app/Controllers/AgentIncentiveController.php @@ -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: per–vehicle-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 = [ diff --git a/app/Controllers/ExcelExportController.php b/app/Controllers/ExcelExportController.php index f8b3860..9001fde 100644 --- a/app/Controllers/ExcelExportController.php +++ b/app/Controllers/ExcelExportController.php @@ -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; + // ✅ Per–vehicle 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; diff --git a/app/Libraries/PartnerPayoutGridRetention.php b/app/Libraries/PartnerPayoutGridRetention.php new file mode 100644 index 0000000..2e108bf --- /dev/null +++ b/app/Libraries/PartnerPayoutGridRetention.php @@ -0,0 +1,166 @@ + 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> $rows + * + * @return array> + */ + 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; + } +} diff --git a/app/Models/PartnerRetentionRateModel.php b/app/Models/PartnerRetentionRateModel.php new file mode 100644 index 0000000..28568a3 --- /dev/null +++ b/app/Models/PartnerRetentionRateModel.php @@ -0,0 +1,23 @@ +