diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 87957c5..8e974d1 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -178,6 +178,23 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f $routes->get('reports/endorsement-excel', 'ExcelExportController::downloadExcelEndorsement'); + + // DASHBOARD Season 6 — Partner Portal + + // GET /partner/{id}/details + $routes->get('partner/(:num)/details', 'DashboardController::partnerDetails/$1'); + + // GET /partner/{id}/policies + $routes->get('partner/(:num)/policies', 'DashboardController::partnerPolicies/$1'); + + // GET /partner/{id}/renewals?days=N + $routes->get('partner/(:num)/renewals', 'DashboardController::partnerRenewals/$1'); + + // GET /partner/{id}/earnings + $routes->get('partner/(:num)/earnings', 'DashboardController::partnerEarnings/$1'); + + + //invoice @@ -203,7 +220,7 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f $routes->get('audit/history', 'MasterController::getHistory'); - }); +}); // common diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 941b7ee..e4a42a9 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -1172,5 +1172,413 @@ class DashboardController extends ResourceController return array_values($final); } +// ───────────────────────────────────────────────────────────────────────────── +// DashboardController.php — Partner Portal API methods +// Routes: +// GET partner/(:num)/details → partnerDetails($id) +// GET partner/(:num)/policies → partnerPolicies($id) +// GET partner/(:num)/renewals → partnerRenewals($id) ?days=20 +// GET partner/(:num)/earnings → partnerEarnings($id) +// ───────────────────────────────────────────────────────────────────────────── + +// ══════════════════════════════════════════════════════════════════════════════ +// GET partner/{id}/details +// agent_id = partner_agent.id +// Joins: partner_policy (agent_id), partner_enquiry (agent_id), +// partner_endorsement_request (agent_id) +// ══════════════════════════════════════════════════════════════════════════════ +public function partnerDetails($id) +{ + $ref = []; + + try { + if (empty($id)) { + return $this->respond([ + 'status' => 'error', + 'code' => 400, + 'data' => [], + 'message' => 'Missing required parameter: id', + ], 200); + } + + // ── 1. Agent profile (partner_agent.id = $id) + $agent = $this->db->table('partner_agent pa') + ->select(' + pa.id, + pa.name AS agent_name, + pa.agent_code, + pa.mobile, + pa.email, + pa.is_active, + ps.name AS manager_name + ') + ->join('partner_staff ps', 'ps.id = pa.manager_id', 'left') + ->where('pa.id', $id) + ->get()->getRowArray(); + + if (empty($agent)) { + throw new \RuntimeException('Partner not found.', 404); + } + + // -- 2. Policy counts + premium + commission + // mapped_policies = ALL policies under this agent (226) + // issued_policies = policy_number IS NOT NULL AND is_active = 1 (217) + // pending_policies = policy_number IS NULL (9 — raised but not yet issued) + // total_premium / commission = from issued policies only + $policyStats = $this->db->table('partner_policy pp') + ->select(' + COUNT(pp.id) AS mapped_policies, + SUM(CASE WHEN pp.policy_number IS NOT NULL AND pp.is_active = 1 AND pp.premium_amount IS NOT NULL THEN 1 ELSE 0 END) AS issued_policies, + SUM(CASE WHEN pp.policy_number IS NULL THEN 1 ELSE 0 END) AS pending_policies, + COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL THEN pp.premium_amount ELSE 0 END), 0) AS total_premium, + COALESCE(SUM(CASE WHEN pp.policy_number IS NOT NULL THEN pp.premium_amount * 0.15 ELSE 0 END), 0) AS commission_earned + ') + ->where('pp.agent_id', $id) + ->get()->getRowArray(); + + // ── 3. Enquiry counts + // enquiry_status enum: To be assigned | Assigned | In progress | Completed + $enquiryStats = $this->db->table('partner_enquiry pe') + ->select(' + COUNT(pe.id) AS enquiry_total, + SUM(CASE WHEN pe.enquiry_status = "Completed" THEN 1 ELSE 0 END) AS enquiry_completed, + SUM(CASE WHEN pe.enquiry_status != "Completed" THEN 1 ELSE 0 END) AS enquiry_pending + ') + ->where('pe.agent_id', $id) + ->where('pe.is_active', 1) + ->get()->getRowArray(); + + // ── 4. Endorsement counts + // status is varchar(20) — adjust "Completed" to match your actual values + $endorseStats = $this->db->table('partner_endorsement_request per') + ->select(' + COUNT(per.id) AS endorsement_total, + SUM(CASE WHEN per.status = "Completed" THEN 1 ELSE 0 END) AS endorsement_done, + SUM(CASE WHEN per.status != "Completed" THEN 1 ELSE 0 END) AS endorsement_pending + ') + ->where('per.agent_id', $id) + ->where('per.is_active', 1) + ->get()->getRowArray(); + + // ── Merge everything + $data = array_merge( + $agent, + [ + 'status' => $agent['is_active'] ? 'Active' : 'Inactive', + 'commission_rate' => 15, + ], + $policyStats ?? [], + $enquiryStats ?? [], + $endorseStats ?? [], + ); + + $ref['message'] = 'Partner details retrieved successfully.'; + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => $data, + 'ref' => $ref, + ], 200); + + } catch (\Throwable $e) { + + if ($e instanceof \RuntimeException && $e->getCode() === 404) { + $ref['message'] = 'No Data Found'; + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [], + 'message' => 'No Data Found', + 'ref' => $ref, + ], 200); + } + + $isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException + || $e instanceof \mysqli_sql_exception + || $e instanceof \PDOException; + + if ($isDbError) { + $ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine(); + $ref['message'] = 'Database Error Occurred.'; + } else { + $ref['message'] = 'An unexpected error occurred: ' . $e->getMessage(); + } + + return $this->respond([ + 'status' => 'error', + 'code' => 500, + 'data' => [], + 'ref' => $ref, + ], 200); + } } + + +// ══════════════════════════════════════════════════════════════════════════════ +// GET partner/{id}/policies +// partner_policy.agent_id = $id +// holder_name → pp.insured_name (the actual insured person, NOT agent) +// product → pp.product (varchar 50), falls back to pp.vehicle_type +// policy_no → pp.policy_number +// ══════════════════════════════════════════════════════════════════════════════ +public function partnerPolicies($id) +{ + $ref = []; + + try { + if (empty($id)) { + return $this->respond([ + 'status' => 'error', + 'code' => 400, + 'data' => [], + 'message' => 'Missing required parameter: id', + ], 200); + } + + $results = $this->db->table('partner_policy pp') + ->select(' + pp.policy_number AS policy_no, + pp.insured_name AS holder_name, + COALESCE(NULLIF(pp.product, ""), pp.vehicle_type) AS product, + pp.premium_amount AS premium, + DATE_FORMAT(pp.issued_date, "%d-%m-%Y") AS issued_date, + DATE_FORMAT(pp.end_date, "%d-%m-%Y") AS expiry_date + ') + ->where('pp.agent_id', $id) + ->where('pp.policy_number IS NOT NULL') + ->where('pp.premium_amount IS NOT NULL') + ->where('pp.is_active', 1) + ->orderBy('pp.issued_date', 'DESC') + ->get()->getResultArray(); + + $ref['total_records'] = count($results); + + if (empty($results)) { + throw new \RuntimeException('No policies found for this partner.', 404); + } + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => $results, + 'ref' => $ref, + ], 200); + + } catch (\Throwable $e) { + + if ($e instanceof \RuntimeException && $e->getCode() === 404) { + $ref['message'] = 'No Data Found'; + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [], + 'message' => 'No Data Found', + 'ref' => $ref, + ], 200); + } + + $isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException + || $e instanceof \mysqli_sql_exception + || $e instanceof \PDOException; + + if ($isDbError) { + $ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine(); + $ref['message'] = 'Database Error Occurred.'; + } else { + $ref['message'] = 'An unexpected error occurred: ' . $e->getMessage(); + } + + return $this->respond([ + 'status' => 'error', + 'code' => 500, + 'data' => [], + 'ref' => $ref, + ], 200); + } +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// GET partner/{id}/renewals?days=20 +// partner_policy.agent_id = $id +// holder_name → pp.insured_name +// premium → pp.premium_amount +// ══════════════════════════════════════════════════════════════════════════════ +public function partnerRenewals($id) +{ + $ref = []; + $days = (int) ($this->request->getGet('days') ?? 20); + + try { + if (empty($id)) { + return $this->respond([ + 'status' => 'error', + 'code' => 400, + 'data' => [], + 'message' => 'Missing required parameter: id', + ], 200); + } + + $results = $this->db->table('partner_policy pp') + ->select(' + pp.policy_number AS policy_no, + pp.insured_name AS holder_name, + pp.premium_amount AS premium, + DATE_FORMAT(pp.end_date, "%d-%m-%Y") AS end_date, + DATEDIFF(pp.end_date, CURDATE()) AS days_left + ') + ->where('pp.agent_id', $id) + ->where('pp.is_active', 1) + ->where('pp.policy_number IS NOT NULL') + ->where('pp.premium_amount IS NOT NULL') + ->where('pp.end_date >= CURDATE()', null, false) + ->where("pp.end_date <= DATE_ADD(CURDATE(), INTERVAL {$days} DAY)", null, false) + ->orderBy('pp.end_date', 'ASC') + ->get()->getResultArray(); + + $ref['days_filter'] = $days; + $ref['total_records'] = count($results); + + if (empty($results)) { + throw new \RuntimeException('No renewals due within ' . $days . ' days.', 404); + } + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => $results, + 'ref' => $ref, + ], 200); + + } catch (\Throwable $e) { + + if ($e instanceof \RuntimeException && $e->getCode() === 404) { + $ref['message'] = 'No Data Found'; + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [], + 'message' => 'No Data Found', + 'ref' => $ref, + ], 200); + } + + $isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException + || $e instanceof \mysqli_sql_exception + || $e instanceof \PDOException; + + if ($isDbError) { + $ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine(); + $ref['message'] = 'Database Error Occurred.'; + } else { + $ref['message'] = 'An unexpected error occurred: ' . $e->getMessage(); + } + + return $this->respond([ + 'status' => 'error', + 'code' => 500, + 'data' => [], + 'ref' => $ref, + ], 200); + } +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// GET partner/{id}/earnings +// partner_policy.agent_id = $id +// Groups by issued_date month → month_key (YYYY-MM), month_label (Month YYYY) +// paid = MAX(is_data_accuracy_checked) — 1 if all policies in month are checked +// No month_key param → returns ALL months (FY filtering done client-side in Dart) +// ══════════════════════════════════════════════════════════════════════════════ +public function partnerEarnings($id) +{ + $ref = []; + $monthKey = $this->request->getGet('month_key') ?? null; + + try { + if (empty($id)) { + return $this->respond([ + 'status' => 'error', + 'code' => 400, + 'data' => [], + 'message' => 'Missing required parameter: id', + ], 200); + } + + $builder = $this->db->table('partner_policy pp'); + + $builder->select(" + DATE_FORMAT(pp.issued_date, '%Y-%m') AS month_key, + DATE_FORMAT(pp.issued_date, '%M %Y') AS month_label, + COUNT(pp.id) AS policies, + COALESCE(SUM(pp.premium_amount), 0) AS premium, + COALESCE(SUM(pp.premium_amount * 0.15), 0) AS commission, + COALESCE(SUM(pp.premium_amount * 0.15 * 0.10), 0) AS tds, + COALESCE(SUM(pp.premium_amount * 0.15 * 0.90), 0) AS net_payout, + MAX(pp.is_data_accuracy_checked) AS paid + "); + + $builder->where('pp.agent_id', $id); + $builder->where('pp.is_active', 1); + $builder->where('pp.policy_number IS NOT NULL'); + $builder->where('pp.premium_amount IS NOT NULL'); + + if (!empty($monthKey)) { + $builder->where("DATE_FORMAT(pp.issued_date, '%Y-%m')", $monthKey); + } + + $builder->groupBy("DATE_FORMAT(pp.issued_date, '%Y-%m')"); + $builder->orderBy('month_key', 'ASC'); + + $results = $builder->get()->getResultArray(); + + $ref['month_filter'] = $monthKey ?? 'all'; + $ref['total_records'] = count($results); + + if (empty($results)) { + throw new \RuntimeException('No earning data found for this partner.', 404); + } + + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => $results, + 'ref' => $ref, + ], 200); + + } catch (\Throwable $e) { + + if ($e instanceof \RuntimeException && $e->getCode() === 404) { + $ref['message'] = 'No Data Found'; + return $this->respond([ + 'status' => 'success', + 'code' => 200, + 'data' => [], + 'message' => 'No Data Found', + 'ref' => $ref, + ], 200); + } + + $isDbError = $e instanceof \CodeIgniter\Database\Exceptions\DatabaseException + || $e instanceof \mysqli_sql_exception + || $e instanceof \PDOException; + + if ($isDbError) { + $ref['debug_info'] = $e->getFile() . ' / LN : ' . $e->getLine(); + $ref['message'] = 'Database Error Occurred.'; + } else { + $ref['message'] = 'An unexpected error occurred: ' . $e->getMessage(); + } + + return $this->respond([ + 'status' => 'error', + 'code' => 500, + 'data' => [], + 'ref' => $ref, + ], 200); + } + } +} \ No newline at end of file diff --git a/app/Controllers/EndorsementController.php b/app/Controllers/EndorsementController.php index 6dfcd8b..a3d16de 100644 --- a/app/Controllers/EndorsementController.php +++ b/app/Controllers/EndorsementController.php @@ -139,8 +139,14 @@ class EndorsementController extends ResourceController foreach ($data as $key => $row) { $data[$key]['created_at'] = date('d-m-Y h:i A', strtotime($row['created_at'])); $data[$key]['updated_at'] = date('d-m-Y h:i A', strtotime($row['updated_at'])); - $data[$key]['policy_start_date'] = date('d-m-Y', strtotime($row['policy_start_date'])); - $data[$key]['policy_end_date'] = date('d-m-Y', strtotime($row['policy_end_date'])); + // ✅ NULL-safe date formatting + $data[$key]['policy_start_date'] = (!empty($row['policy_start_date']) && $row['policy_start_date'] !== '0000-00-00') + ? date('d-m-Y', strtotime($row['policy_start_date'])) + : null; + + $data[$key]['policy_end_date'] = (!empty($row['policy_end_date']) && $row['policy_end_date'] !== '0000-00-00') + ? date('d-m-Y', strtotime($row['policy_end_date'])) + : null; } return $this->respond(['status' => 'success','code' => 200,'data' => $data], 200); diff --git a/app/Controllers/EnquiryController.php b/app/Controllers/EnquiryController.php index 22c2454..fcd535a 100644 --- a/app/Controllers/EnquiryController.php +++ b/app/Controllers/EnquiryController.php @@ -186,11 +186,13 @@ class EnquiryController extends ResourceController } //Get enquiry details - $enquiry = $this->enquiryModel->select('partner_enquiry.* , I.name as insurer_name , I.short_name , VT.vehicle_type as vehicle_type ,PB.name as broker_name ,PA.name as agent_name,PA.agent_code') + $enquiry = $this->enquiryModel->select('partner_enquiry.* , I.name as insurer_name , I.short_name , VT.vehicle_type as vehicle_type ,PB.name as broker_name ,PA.name as agent_name,PA.agent_code,PPMM.value as payment_mode_value') ->join('insurers I', 'I.id = partner_enquiry.insurer_id', 'left') ->join('vehicle_type VT', 'VT.id = partner_enquiry.vehicle_type_id', 'left') ->join('partner_brokers PB', 'PB.id = partner_enquiry.broker_id', 'left') ->join('partner_agent PA', 'PA.id = partner_enquiry.agent_id', 'left') + ->join('partner_quotation PQ', 'PQ.enquiry_id = partner_enquiry.id', 'left') + ->join('partner_payment_mode_master PPMM', 'PPMM.id = PQ.payment_mode_id', 'left') ->where('partner_enquiry.id', $enquiry_id) ->where('partner_enquiry.is_active', 1) ->first(); @@ -834,7 +836,7 @@ class EnquiryController extends ResourceController try { $data = $this->request->getJSON(true); - print_r($data);die; + // print_r($data);die; if (!isset($data['id'])) { return $this->respond(['status' => 'failed', 'code' => 200, 'error' => 'ID Required'], 200); diff --git a/app/Controllers/InvoiceController.php b/app/Controllers/InvoiceController.php index 0f9d45d..5fb26df 100644 --- a/app/Controllers/InvoiceController.php +++ b/app/Controllers/InvoiceController.php @@ -41,7 +41,33 @@ class InvoiceController extends ResourceController pos.name as pos_name, FORMAT(partner_invoice.invoice_amount, 2, "en_IN") AS invoice_amount_indian_format, DATE_FORMAT(partner_invoice.invoice_date, "%d-%m-%Y") AS invoice_date_ui_format, - ') + ( + SELECT GROUP_CONCAT(pa.name ORDER BY pa.id SEPARATOR ", ") + FROM partner_agent pa + WHERE JSON_SEARCH(partner_invoice.agent_id, "one", CAST(pa.id AS CHAR)) IS NOT NULL + ) AS partner_names, + ( + SELECT GROUP_CONCAT(piu.utr_no ORDER BY piu.id SEPARATOR ", ") + FROM partner_invoice_utr piu + WHERE piu.invoice_id = partner_invoice.id + AND piu.is_active = 1 + ) AS utr_numbers, + partner_invoice.invoice_amount AS invoiced_amount, + COALESCE(( + SELECT SUM(piu.amount) + FROM partner_invoice_utr piu + WHERE piu.invoice_id = partner_invoice.id + AND piu.is_active = 1 + ), 0.00) AS payout_amount, + ( + partner_invoice.invoice_amount - COALESCE(( + SELECT SUM(piu.amount) + FROM partner_invoice_utr piu + WHERE piu.invoice_id = partner_invoice.id + AND piu.is_active = 1 + ), 0.00) + ) AS balance_amount', + false) ->join('partner_brokers PB', 'PB.id = partner_invoice.broker_id', 'left') ->join('partner_pos pos', 'pos.id = partner_invoice.pos_id', 'left') ->where('partner_invoice.is_active', 1) @@ -193,6 +219,7 @@ class InvoiceController extends ResourceController private function generateInvoiceNo($pos_id) { + $pos_id = !empty($pos_id) ? $pos_id : 0; $year = date('Y'); $prefix = "NIIB/$pos_id/$year/"; diff --git a/app/Helpers/common_helper.php b/app/Helpers/common_helper.php index 7f3ae14..6d84858 100644 --- a/app/Helpers/common_helper.php +++ b/app/Helpers/common_helper.php @@ -181,8 +181,9 @@ if (!function_exists('createBDS')) { if (!function_exists('format_date_for_database')) { - function format_date_for_database(string $date): ?string + function format_date_for_database(?string $date): ?string { + if (empty($date)) return null; // ✅ handle null/empty $dt = DateTime::createFromFormat('d-m-Y', $date); if ($dt) { return $dt->format('Y-m-d');