From 80afc350ebe7d8937098b95af9686f36ab3b6d71 Mon Sep 17 00:00:00 2001 From: "sanjeev.p" Date: Fri, 3 Apr 2026 18:57:36 +0530 Subject: [PATCH 1/3] FIX_dashboard api --- app/Controllers/DashboardController.php | 198 ++++++++++++++++++++---- 1 file changed, 169 insertions(+), 29 deletions(-) diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index e4a42a9..2bc7a8c 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -1221,24 +1221,57 @@ public function partnerDetails($id) 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 + // -- 2. Overview = current Indian financial year (Apr–Mar, Asia/Kolkata) + $fy = $this->indianFinancialYearBounds(null); + $fyStart = $fy['start']; + $fyEnd = $fy['end']; + $overviewFy = $fy['label']; + $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 - ') + ->select(" + SUM(CASE WHEN pp.policy_number IS NOT NULL AND pp.issued_date >= '{$fyStart}' AND pp.issued_date <= '{$fyEnd}' THEN 1 ELSE 0 END) AS issued_policies, + SUM(CASE WHEN pp.policy_number IS NULL AND DATE(pp.created_on) >= '{$fyStart}' AND DATE(pp.created_on) <= '{$fyEnd}' THEN 1 ELSE 0 END) AS pending_policies, + COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL AND pp.issued_date >= '{$fyStart}' AND pp.issued_date <= '{$fyEnd}' THEN pp.premium_amount ELSE 0 END), 0) AS total_premium, + COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL AND pp.issued_date >= '{$fyStart}' AND pp.issued_date <= '{$fyEnd}' THEN COALESCE(pp.commission_amount, 0) ELSE 0 END), 0) AS commission_earned + ", false) ->where('pp.agent_id', $id) ->get()->getRowArray(); - // ── 3. Enquiry counts - // enquiry_status enum: To be assigned | Assigned | In progress | Completed + $policyStats['mapped_policies'] = (int) ($policyStats['issued_policies'] ?? 0) + (int) ($policyStats['pending_policies'] ?? 0); + + $agentId = (int) $id; + + $paidRow = $this->db->table('partner_invoice_items pii') + ->select('COALESCE(SUM(COALESCE(pii.commission_amount, 0)), 0) AS commission_paid', false) + ->join('partner_policy pp', 'pp.id = pii.policy_id', 'inner') + ->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'inner') + ->where('pp.agent_id', $agentId) + ->where('pii.is_active', 1) + ->where('pp.is_active', 1) + ->where('pp.policy_number IS NOT NULL') + ->where('pp.issued_date >=', $fyStart) + ->where('pp.issued_date <=', $fyEnd) + ->where( + 'JSON_SEARCH(pi.agent_id, \'one\', CAST(' . $agentId . ' AS CHAR)) IS NOT NULL', + null, + false + ) + ->where( + 'EXISTS (SELECT 1 FROM partner_invoice_utr piu WHERE piu.invoice_id = pi.id AND piu.is_active = 1 AND piu.amount > 0)', + null, + false + ) + ->get()->getRowArray(); + + $commissionEarned = (float) ($policyStats['commission_earned'] ?? 0); + $commissionPaidRaw = (float) ($paidRow['commission_paid'] ?? 0); + $commissionPaid = min($commissionPaidRaw, $commissionEarned); + $commissionUnpaid = max(0, $commissionEarned - $commissionPaid); + + $policyStats['commission_paid'] = $commissionPaid; + $policyStats['commission_unpaid'] = $commissionUnpaid; + + // ── 3. Enquiry counts (created in current FY) $enquiryStats = $this->db->table('partner_enquiry pe') ->select(' COUNT(pe.id) AS enquiry_total, @@ -1247,10 +1280,11 @@ public function partnerDetails($id) ') ->where('pe.agent_id', $id) ->where('pe.is_active', 1) + ->where('pe.created_on >=', $fyStart . ' 00:00:00') + ->where('pe.created_on <=', $fyEnd . ' 23:59:59') ->get()->getRowArray(); - // ── 4. Endorsement counts - // status is varchar(20) — adjust "Completed" to match your actual values + // ── 4. Endorsement counts (created in current FY) $endorseStats = $this->db->table('partner_endorsement_request per') ->select(' COUNT(per.id) AS endorsement_total, @@ -1259,14 +1293,16 @@ public function partnerDetails($id) ') ->where('per.agent_id', $id) ->where('per.is_active', 1) + ->where('per.created_at >=', $fyStart . ' 00:00:00') + ->where('per.created_at <=', $fyEnd . ' 23:59:59') ->get()->getRowArray(); // ── Merge everything $data = array_merge( $agent, [ - 'status' => $agent['is_active'] ? 'Active' : 'Inactive', - 'commission_rate' => 15, + 'status' => $agent['is_active'] ? 'Active' : 'Inactive', + 'overview_financial_year' => $overviewFy, ], $policyStats ?? [], $enquiryStats ?? [], @@ -1490,13 +1526,17 @@ public function partnerRenewals($id) // 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) +// payout = SUM(partner_policy.commission_amount) — total commission for the month +// payout_paid = SUM(partner_invoice_items.commission_amount) where policy belongs to agent, +// invoice lists this agent in agent_id JSON, and invoice has UTR payment (amount > 0) +// payout_unpaid = payout − payout_paid (floored at 0) +// FY filter is client-side // ══════════════════════════════════════════════════════════════════════════════ public function partnerEarnings($id) { $ref = []; $monthKey = $this->request->getGet('month_key') ?? null; + $fyParam = $this->request->getGet('financial_year') ?? null; try { if (empty($id)) { @@ -1508,6 +1548,13 @@ public function partnerEarnings($id) ], 200); } + $agentId = (int) $id; + + $fyBounds = null; + if (!empty($fyParam)) { + $fyBounds = $this->indianFinancialYearBounds($fyParam); + } + $builder = $this->db->table('partner_policy pp'); $builder->select(" @@ -1515,17 +1562,19 @@ public function partnerEarnings($id) 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 + COALESCE(SUM(COALESCE(pp.commission_amount, 0)), 0) AS payout "); - $builder->where('pp.agent_id', $id); + $builder->where('pp.agent_id', $agentId); $builder->where('pp.is_active', 1); $builder->where('pp.policy_number IS NOT NULL'); $builder->where('pp.premium_amount IS NOT NULL'); + if ($fyBounds !== null) { + $builder->where('pp.issued_date >=', $fyBounds['start']); + $builder->where('pp.issued_date <=', $fyBounds['end']); + } + if (!empty($monthKey)) { $builder->where("DATE_FORMAT(pp.issued_date, '%Y-%m')", $monthKey); } @@ -1535,13 +1584,63 @@ public function partnerEarnings($id) $results = $builder->get()->getResultArray(); - $ref['month_filter'] = $monthKey ?? 'all'; - $ref['total_records'] = count($results); + // Paid commission: invoice line items tied to this agent’s policies, invoice includes agent, UTR settled + $paidBuilder = $this->db->table('partner_invoice_items pii'); + $paidBuilder->select(" + DATE_FORMAT(pp.issued_date, '%Y-%m') AS month_key, + COALESCE(SUM(COALESCE(pii.commission_amount, 0)), 0) AS payout_paid + ", false); + $paidBuilder->join('partner_policy pp', 'pp.id = pii.policy_id', 'inner'); + $paidBuilder->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'inner'); + $paidBuilder->where('pp.agent_id', $agentId); + $paidBuilder->where('pii.is_active', 1); + $paidBuilder->where('pp.is_active', 1); + $paidBuilder->where('pp.policy_number IS NOT NULL'); + $paidBuilder->where('pp.premium_amount IS NOT NULL'); + $paidBuilder->where( + 'JSON_SEARCH(pi.agent_id, \'one\', CAST(' . $agentId . ' AS CHAR)) IS NOT NULL', + null, + false + ); + $paidBuilder->where( + 'EXISTS (SELECT 1 FROM partner_invoice_utr piu WHERE piu.invoice_id = pi.id AND piu.is_active = 1 AND piu.amount > 0)', + null, + false + ); - if (empty($results)) { - throw new \RuntimeException('No earning data found for this partner.', 404); + if ($fyBounds !== null) { + $paidBuilder->where('pp.issued_date >=', $fyBounds['start']); + $paidBuilder->where('pp.issued_date <=', $fyBounds['end']); } + if (!empty($monthKey)) { + $paidBuilder->where("DATE_FORMAT(pp.issued_date, '%Y-%m')", $monthKey); + } + + $paidBuilder->groupBy("DATE_FORMAT(pp.issued_date, '%Y-%m')"); + $paidRows = $paidBuilder->get()->getResultArray(); + + $paidMap = []; + foreach ($paidRows as $pr) { + $paidMap[$pr['month_key']] = (float) $pr['payout_paid']; + } + + foreach ($results as &$row) { + $mk = $row['month_key']; + $payout = (float) $row['payout']; + $payoutPaid = min((float) ($paidMap[$mk] ?? 0), $payout); + $row['payout'] = $payout; + $row['payout_paid'] = $payoutPaid; + $row['payout_unpaid'] = max(0, $payout - $payoutPaid); + $row['premium'] = (float) $row['premium']; + $row['policies'] = (int) $row['policies']; + } + unset($row); + + $ref['month_filter'] = $monthKey ?? 'all'; + $ref['financial_year'] = ($fyBounds !== null) ? $fyBounds['label'] : 'all'; + $ref['total_records'] = count($results); + return $this->respond([ 'status' => 'success', 'code' => 200, @@ -1581,4 +1680,45 @@ public function partnerEarnings($id) ], 200); } } + + /** + * Indian financial year Apr 1 (Y) → Mar 31 (Y+1). + * + * @param string|null $fyStr e.g. "2025-2026" (first year is the April year) + * @return array{start: string, end: string, label: string} Y-m-d bounds + label "2025-2026" + */ + private function indianFinancialYearBounds(?string $fyStr = null): array + { + if ($fyStr !== null && preg_match('/^(\d{4})-(\d{4})$/', trim($fyStr), $m)) { + $y1 = (int) $m[1]; + $y2 = (int) $m[2]; + if ($y2 !== $y1 + 1) { + $y1 = $y1 - 1; + $y2 = $y1 + 1; + } + $start = sprintf('%04d-04-01', $y1); + $end = sprintf('%04d-03-31', $y2); + + return [ + 'start' => $start, + 'end' => $end, + 'label' => $y1 . '-' . $y2, + ]; + } + + $tz = new \DateTimeZone('Asia/Kolkata'); + $now = new \DateTime('now', $tz); + $y = (int) $now->format('Y'); + $mo = (int) $now->format('n'); + $y1 = $mo >= 4 ? $y : $y - 1; + $y2 = $y1 + 1; + $start = sprintf('%04d-04-01', $y1); + $end = sprintf('%04d-03-31', $y2); + + return [ + 'start' => $start, + 'end' => $end, + 'label' => $y1 . '-' . $y2, + ]; + } } \ No newline at end of file From 1b1095643d0f7a078e8f0acb55998e078d155b4b Mon Sep 17 00:00:00 2001 From: "sanjeev.p" Date: Sat, 4 Apr 2026 15:06:51 +0530 Subject: [PATCH 2/3] FIX_RATE --- app/Config/Routes.php | 3 + app/Controllers/AgentController.php | 318 ++++++++++++++++++- app/Controllers/AgentIncentiveController.php | 12 +- app/Controllers/ExcelExportController.php | 44 +-- app/Libraries/PartnerPayoutGridRetention.php | 166 ++++++++++ app/Models/PartnerRetentionRateModel.php | 23 ++ app/Models/PartnerVehicleTypeModel.php | 18 ++ 7 files changed, 559 insertions(+), 25 deletions(-) create mode 100644 app/Libraries/PartnerPayoutGridRetention.php create mode 100644 app/Models/PartnerRetentionRateModel.php create mode 100644 app/Models/PartnerVehicleTypeModel.php 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 @@ + Date: Sat, 4 Apr 2026 15:41:05 +0530 Subject: [PATCH 3/3] FIX_Dashboardmobileemail --- app/Controllers/DashboardController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 2bc7a8c..4153445 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -1211,7 +1211,9 @@ public function partnerDetails($id) pa.mobile, pa.email, pa.is_active, - ps.name AS manager_name + ps.name AS manager_name, + ps.mobile AS manager_mobile, + ps.email AS manager_email ') ->join('partner_staff ps', 'ps.id = pa.manager_id', 'left') ->where('pa.id', $id)