diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index d9f3ea9c..90b426ce 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -9021,9 +9021,10 @@ class ClientController extends AdminController // Now process any remaining HRs that didn't have access records foreach ($hrMap as $hr) { - $temp_arr_key = $hr['hr_mobile'] . $hr['hr_mail']; $post_hr_id = $hr['post_hr_id']; $pre_hr_id = $hr['pre_hr_id']; + // Keep each HR entry unique; mobile/email can repeat across branches. + $temp_arr_key = $hr['hr_mobile'] . $hr['hr_mail'] . '_' . ($post_hr_id ?? 'null') . '_' . ($pre_hr_id ?? 'null'); if (empty($post_hr_id) && !empty($pre_hr_id)) { $type_of_access_data = "PRE"; @@ -9057,8 +9058,14 @@ class ClientController extends AdminController } } + // Sort by HR name for consistent display order. + $resultList = array_values($result); + usort($resultList, function ($a, $b) { + return strcasecmp((string)($a['hr_name'] ?? ''), (string)($b['hr_name'] ?? '')); + }); + // dd($result); - $resultData['hr_access_data'] = array_values($result); + $resultData['hr_access_data'] = $resultList; $resultData['pre_policy_data'] = $data['pre_policy_data']; $resultData['post_policy_data'] = $data['post_policy_data']; $resultData['post_cd_data'] = $data['post_cd_data']; diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index ee3caadd..606460f0 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -2586,8 +2586,8 @@ class EmployeeRestController extends AdminController } $claim_type = $this->ticketController->claimType; - unset($claim_type[1][2]); - unset($claim_type[1][4]); + // unset($claim_type[1][2]); + // unset($claim_type[1][4]); $data['claim_type'] = $claim_type; return $this->respond(['status' => (count($data) ? 'success' : 'failed'), 'code' => (count($data) ? 200 : 404), 'data' => $data], 200); @@ -4109,8 +4109,8 @@ class EmployeeRestController extends AdminController $ticket_type = $this->ticketController->ticketType; $claim_type = $this->ticketController->claimType; - unset($claim_type[1][2]); - unset($claim_type[1][4]); + // unset($claim_type[1][2]); + // unset($claim_type[1][4]); $ticket_type = array_map(fn($value, $key) => (object) ['id' => $key, 'name' => $value], array_values($ticket_type), array_keys($ticket_type)); return $this->response->setJSON(['ticket_type' => $ticket_type, 'claim_type' => $claim_type])->setStatusCode(200); @@ -4143,8 +4143,8 @@ class EmployeeRestController extends AdminController $ticket_type = $this->ticketController->ticketType; $claim_type = $this->ticketController->claimType; - unset($claim_type[1][2]); - unset($claim_type[1][4]); + // unset($claim_type[1][2]); + // unset($claim_type[1][4]); $filtered = []; diff --git a/app/Controllers/TicketController.php b/app/Controllers/TicketController.php index 8da4c33d..0200523b 100644 --- a/app/Controllers/TicketController.php +++ b/app/Controllers/TicketController.php @@ -822,7 +822,7 @@ class TicketController extends BaseController $data['extra_fields'] = $this->getExtraFields($data['claim_status']); $data['extra_fields_array_for_validate'] = $this->getExtraFields($data['claim_status'], 1); - if ($ticket_type == 1) { + if ($ticket_type == 1 || $ticket_type == 72) { $data['claim_type'] = $this->claimType[1]; } else { $data['claim_type'] = $this->claimType[2]; diff --git a/app/Models/ClaimsCollectionV2DashboardModel.php b/app/Models/ClaimsCollectionV2DashboardModel.php index c32b1014..0e686a53 100644 --- a/app/Models/ClaimsCollectionV2DashboardModel.php +++ b/app/Models/ClaimsCollectionV2DashboardModel.php @@ -144,8 +144,8 @@ SQL; return $this->runKpiQuery($sql, $policyId); } - /** Metabase #185: PREMIUM AS ON DATE */ - public function premium_as_on_date(int $policyId): array + /** Metabase #185: PREMIUM AS ON DATE (legacy — not in KPI_MAP; kept for reference) */ + public function premium_as_on_date_legacy(int $policyId): array { $sql = <<<'SQL' SELECT @@ -165,7 +165,7 @@ SELECT END ), 0) , 0)) AS premium_as_on_date - + FROM employee_polices ep JOIN employees e ON e.id = ep.employee_id WHERE ep.client_policy_id = :policy_id: @@ -174,6 +174,43 @@ SQL; return $this->runKpiQuery($sql, $policyId); } + /** Metabase #185: PREMIUM AS ON DATE */ + public function premium_as_on_date(int $policyId): array + { + $sql = <<<'SQL' +SELECT + CONCAT('💰 ', FORMAT( + COALESCE(SUM( + CASE + WHEN ep.date_of_exit IS NOT NULL + AND ep.date_of_exit != '' + AND (ep.claim_status = 0 OR ep.claim_status IS NULL) + AND ep.date_coverage IS NOT NULL + AND ep.date_coverage != '' + AND ep.policy_end_date IS NOT NULL + AND ep.policy_end_date != '' + THEN (ep.rata_premimum + COALESCE(ep.gst, 0)) + * ( + (DATEDIFF(ep.policy_end_date, ep.date_coverage) + 1) + - (DATEDIFF(ep.policy_end_date, ep.date_of_exit) - 1) + ) + / NULLIF(DATEDIFF(ep.policy_end_date, ep.date_coverage) + 1, 0) + ELSE (ep.rata_premimum + COALESCE(ep.gst, 0)) + END + ), 0) + , 0)) AS premium_as_on_date + +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +WHERE ep.client_policy_id = :policy_id: + AND ep.is_active = 1 + AND ep.status = 'active' + AND e.is_active = 1 + AND e.emp_status = 'active'; +SQL; + return $this->runKpiQuery($sql, $policyId); + } + /** Metabase #186: CLAIMS EXPERIENCE SUMMARY */ public function claims_experience_summary(int $policyId): array { @@ -183,15 +220,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ @@ -687,15 +724,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ @@ -852,15 +889,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ @@ -1017,15 +1054,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ @@ -1182,15 +1219,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ @@ -1348,15 +1385,15 @@ SELECT DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) AS run_days, + ) + 1 AS run_days, /* earned premium */ FORMAT( (prem.premium_as_on_date / 365) - * DATEDIFF( + * (DATEDIFF( MAX(COALESCE(tm.claim_dump_date, cp.policy_end_date)), cp.policy_start_date - ) + ) + 1) , 0) AS earned_premium, /* claims reported */ diff --git a/tests/debug_premium_as_on_date.php b/tests/debug_premium_as_on_date.php new file mode 100644 index 00000000..f4e9beb6 --- /dev/null +++ b/tests/debug_premium_as_on_date.php @@ -0,0 +1,208 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +use App\Models\ClaimsCollectionV2DashboardModel; +use Config\Database; + +$policyId = isset($argv[1]) ? (int) $argv[1] : 4687; +$model = new ClaimsCollectionV2DashboardModel(); +$db = Database::connect(); + +echo "=== premium_as_on_date debug (policy_id={$policyId}) ===" . PHP_EOL . PHP_EOL; + +try { + $new = $model->premium_as_on_date($policyId); + echo 'NEW premium_as_on_date: ' . json_encode($new, JSON_UNESCAPED_UNICODE) . PHP_EOL; +} catch (Throwable $e) { + echo 'NEW ERROR: ' . $e->getMessage() . PHP_EOL; +} + +try { + $old = $model->premium_as_on_date_legacy($policyId); + echo 'LEGACY premium_as_on_date: ' . json_encode($old, JSON_UNESCAPED_UNICODE) . PHP_EOL; +} catch (Throwable $e) { + echo 'LEGACY ERROR: ' . $e->getMessage() . PHP_EOL; +} + +echo PHP_EOL . '=== Active filter counts ===' . PHP_EOL; +$checks = [ + 'all is_active ep' => "SELECT COUNT(*) c FROM employee_polices ep WHERE ep.client_policy_id = ? AND ep.is_active = 1", + "ep.status = 'active'" => "SELECT COUNT(*) c FROM employee_polices ep JOIN employees e ON e.id = ep.employee_id WHERE ep.client_policy_id = ? AND ep.is_active = 1 AND ep.status = 'active' AND e.is_active = 1 AND e.emp_status = 'active'", + "ep.status = 'Active'" => "SELECT COUNT(*) c FROM employee_polices ep JOIN employees e ON e.id = ep.employee_id WHERE ep.client_policy_id = ? AND ep.is_active = 1 AND ep.status = 'Active' AND e.is_active = 1 AND e.emp_status = 'Active'", + 'distinct ep.status' => "SELECT DISTINCT ep.status FROM employee_polices ep WHERE ep.client_policy_id = ? AND ep.is_active = 1 LIMIT 10", + 'distinct e.emp_status' => "SELECT DISTINCT e.emp_status FROM employee_polices ep JOIN employees e ON e.id = ep.employee_id WHERE ep.client_policy_id = ? AND ep.is_active = 1 LIMIT 10", + 'with date_of_exit' => "SELECT COUNT(*) c FROM employee_polices ep JOIN employees e ON e.id = ep.employee_id WHERE ep.client_policy_id = ? AND ep.is_active = 1 AND ep.date_of_exit IS NOT NULL AND ep.date_of_exit != ''", +]; + +foreach ($checks as $label => $sql) { + try { + $q = $db->query($sql, [$policyId]); + $rows = $q->getResultArray(); + if (count($rows) === 1 && isset($rows[0]['c'])) { + echo "{$label}: {$rows[0]['c']}" . PHP_EOL; + } else { + echo "{$label}: " . json_encode($rows) . PHP_EOL; + } + } catch (Throwable $e) { + echo "{$label}: ERROR " . $e->getMessage() . PHP_EOL; + } +} + +echo PHP_EOL . '=== Sum breakdown (new logic parts) ===' . PHP_EOL; +$breakdown = <<<'SQL' +SELECT + COUNT(*) AS member_rows, + SUM(ep.rata_premimum + COALESCE(ep.gst, 0)) AS gross_sum, + SUM( + CASE + WHEN ep.date_of_exit IS NOT NULL + AND ep.date_of_exit != '' + AND (ep.claim_status = 0 OR ep.claim_status IS NULL) + THEN (ep.rata_premimum + COALESCE(ep.gst, 0)) + * (DATEDIFF(ep.policy_end_date, ep.date_of_exit) + 1) / 365 + ELSE 0 + END + ) AS remaining_deducted, + SUM( + CASE + WHEN ep.date_of_exit IS NOT NULL + AND ep.date_of_exit != '' + AND (ep.claim_status = 0 OR ep.claim_status IS NULL) + THEN (ep.rata_premimum + COALESCE(ep.gst, 0)) + - ((ep.rata_premimum + COALESCE(ep.gst, 0)) + * (DATEDIFF(ep.policy_end_date, ep.date_of_exit) + 1) / 365) + ELSE (ep.rata_premimum + COALESCE(ep.gst, 0)) + END + ) AS net_sum +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +WHERE ep.client_policy_id = ? + AND ep.is_active = 1 + AND ep.status = 'active' + AND e.is_active = 1 + AND e.emp_status = 'active' +SQL; + +try { + $row = $db->query($breakdown, [$policyId])->getRowArray(); + echo json_encode($row, JSON_PRETTY_PRINT) . PHP_EOL; +} catch (Throwable $e) { + echo 'BREAKDOWN ERROR: ' . $e->getMessage() . PHP_EOL; +} + +// Same breakdown with Active casing +$breakdownActive = str_replace(["ep.status = 'active'", "e.emp_status = 'active'"], ["ep.status = 'Active'", "e.emp_status = 'Active'"], $breakdown); +try { + $row = $db->query($breakdownActive, [$policyId])->getRowArray(); + echo PHP_EOL . 'With Active casing:' . PHP_EOL; + echo json_encode($row, JSON_PRETTY_PRINT) . PHP_EOL; +} catch (Throwable $e) { + echo 'ACTIVE CASING ERROR: ' . $e->getMessage() . PHP_EOL; +} + +echo PHP_EOL . '=== Exited members (any active flag) ===' . PHP_EOL; +$exitSql = <<<'SQL' +SELECT + ep.is_active, + ep.status, + e.emp_status, + COUNT(*) AS c, + SUM(ep.rata_premimum + COALESCE(ep.gst, 0)) AS premium_sum +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +WHERE ep.client_policy_id = ? + AND ep.date_of_exit IS NOT NULL + AND ep.date_of_exit != '' +GROUP BY ep.is_active, ep.status, e.emp_status +SQL; +try { + foreach ($db->query($exitSql, [$policyId])->getResultArray() as $row) { + echo json_encode($row) . PHP_EOL; + } +} catch (Throwable $e) { + echo 'EXIT ERROR: ' . $e->getMessage() . PHP_EOL; +} + +echo PHP_EOL . '=== Compare enrollment net_premium ===' . PHP_EOL; +try { + $enroll = new \App\Models\EnrollmentCollectionV1DashboardModel(); + echo json_encode($enroll->net_premium($policyId), JSON_UNESCAPED_UNICODE) . PHP_EOL; +} catch (Throwable $e) { + echo 'ENROLL ERROR: ' . $e->getMessage() . PHP_EOL; +} + +echo PHP_EOL . '=== earned_premium (#203) uses old premium subquery ===' . PHP_EOL; +try { + echo json_encode($model->earned_premium($policyId), JSON_UNESCAPED_UNICODE) . PHP_EOL; + $oldPrem = $db->query(<<<'SQL' +SELECT COALESCE(SUM( + CASE + WHEN e2.change_event = 'inception' THEN ep2.rata_premimum + WHEN e2.change_event != 'inception' AND (ep2.date_of_exit IS NULL OR ep2.date_of_exit = '') THEN ep2.rata_premimum + WHEN ep2.date_of_exit IS NOT NULL AND ep2.date_of_exit != '' AND ep2.claim_status = 0 THEN -ep2.rata_premimum + ELSE 0 + END +), 0) AS old_subquery_premium +FROM employee_polices ep2 +JOIN employees e2 ON e2.id = ep2.employee_id +WHERE ep2.client_policy_id = ? + AND ep2.is_active = 1 +SQL, [$policyId])->getRowArray(); + echo 'old subquery base (rata only, no gst): ' . json_encode($oldPrem) . PHP_EOL; +} catch (Throwable $e) { + echo 'EARNED ERROR: ' . $e->getMessage() . PHP_EOL; +} + +echo PHP_EOL . '=== Find policy with exits for exit-logic test ===' . PHP_EOL; +$findExit = <<<'SQL' +SELECT ep.client_policy_id, + SUM(CASE WHEN ep.is_active=1 AND ep.status='active' AND e.is_active=1 AND e.emp_status='active' THEN 1 ELSE 0 END) AS active_both, + SUM(CASE WHEN ep.is_active=1 AND ep.status='active' AND e.is_active=1 AND e.emp_status='active' + AND ep.date_of_exit IS NOT NULL AND ep.date_of_exit != '' THEN 1 ELSE 0 END) AS active_with_exit +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +GROUP BY ep.client_policy_id +HAVING active_with_exit > 0 +ORDER BY active_with_exit DESC +LIMIT 5 +SQL; +try { + $policies = $db->query($findExit)->getResultArray(); + echo json_encode($policies, JSON_PRETTY_PRINT) . PHP_EOL; + if (! empty($policies[0]['client_policy_id'])) { + $testPid = (int) $policies[0]['client_policy_id']; + echo PHP_EOL . "Testing policy {$testPid} with exits..." . PHP_EOL; + echo 'NEW: ' . json_encode($model->premium_as_on_date($testPid), JSON_UNESCAPED_UNICODE) . PHP_EOL; + echo 'net_premium: ' . json_encode((new \App\Models\EnrollmentCollectionV1DashboardModel())->net_premium($testPid), JSON_UNESCAPED_UNICODE) . PHP_EOL; + + $diag = $db->query(<<<'SQL' +SELECT + COUNT(*) AS total_ep, + SUM(CASE WHEN ep.is_active=1 AND ep.status='active' AND e.is_active=1 AND e.emp_status='active' THEN 1 ELSE 0 END) AS active_both, + SUM(CASE WHEN ep.date_of_exit IS NOT NULL AND ep.date_of_exit != '' THEN 1 ELSE 0 END) AS has_exit, + SUM(CASE WHEN ep.is_active=1 AND ep.status='active' AND e.is_active=1 AND e.emp_status='active' + AND ep.date_of_exit IS NOT NULL AND ep.date_of_exit != '' THEN 1 ELSE 0 END) AS active_with_exit +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +WHERE ep.client_policy_id = ? +SQL, [$testPid])->getRowArray(); + echo 'policy diag: ' . json_encode($diag) . PHP_EOL; + } +} catch (Throwable $e) { + echo 'FIND EXIT ERROR: ' . $e->getMessage() . PHP_EOL; +} diff --git a/tests/debug_premium_exit_row.php b/tests/debug_premium_exit_row.php new file mode 100644 index 00000000..1f100944 --- /dev/null +++ b/tests/debug_premium_exit_row.php @@ -0,0 +1,31 @@ +systemDirectory, '\\/ ') . '/bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) require_once $boot; + +$db = Config\Database::connect(); +$row = $db->query(<<<'SQL' +SELECT ep.id, ep.rata_premimum, ep.gst, ep.policy_end_date, ep.date_of_exit, ep.date_coverage, + ep.claim_status, ep.status, + (ep.rata_premimum + COALESCE(ep.gst,0)) AS gross, + (DATEDIFF(ep.policy_end_date, ep.date_of_exit) - 1) AS unused_days, + (DATEDIFF(ep.policy_end_date, ep.date_coverage) + 1) AS total_coverage_days, + (ep.rata_premimum + COALESCE(ep.gst,0)) + * ((DATEDIFF(ep.policy_end_date, ep.date_coverage) + 1) - (DATEDIFF(ep.policy_end_date, ep.date_of_exit) - 1)) + / NULLIF(DATEDIFF(ep.policy_end_date, ep.date_coverage) + 1, 0) AS net_member +FROM employee_polices ep +JOIN employees e ON e.id = ep.employee_id +WHERE ep.client_policy_id = 5889 + AND ep.is_active = 1 AND ep.status = 'active' AND e.is_active = 1 AND e.emp_status = 'active' + AND ep.date_of_exit IS NOT NULL AND ep.date_of_exit != '' +LIMIT 3 +SQL)->getResultArray(); +echo json_encode($row, JSON_PRETTY_PRINT); diff --git a/tests/smoke_claims_collection_v2.php b/tests/smoke_claims_collection_v2.php index 5b10923d..3f540dc7 100644 --- a/tests/smoke_claims_collection_v2.php +++ b/tests/smoke_claims_collection_v2.php @@ -76,6 +76,21 @@ try { ok('model getKpi(incurred_ratio)', false, $e->getMessage()); } +try { + $rows = $model->getKpi('premium_as_on_date', $policyId); + $val = is_array($rows) && isset($rows[0]['premium_as_on_date']) ? (string) $rows[0]['premium_as_on_date'] : ''; + ok('model premium_as_on_date (#185)', count($rows) === 1 && str_starts_with($val, '💰'), $val ?: 'empty'); +} catch (Throwable $e) { + ok('model premium_as_on_date (#185)', false, $e->getMessage()); +} + +try { + $rows = $model->premium_as_on_date_legacy($policyId); + ok('model premium_as_on_date_legacy callable', is_array($rows), 'rows=' . count($rows)); +} catch (Throwable $e) { + ok('model premium_as_on_date_legacy callable', false, $e->getMessage()); +} + $request = Services::request(null, false); $response = Services::response(); $request->setGlobal('get', ['client_policy' => (string) $policyId]); @@ -89,6 +104,9 @@ ok('controller kpi by slug', ($slugResp['status'] ?? false) === true && ($slugRe $idResp = json_decode($controller->kpi('207')->getJSON(), true); ok('controller kpi by id 207', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 207); +$premResp = json_decode($controller->kpi('185')->getJSON(), true); +ok('controller kpi by id 185', ($premResp['status'] ?? false) === true && ($premResp['kpi'] ?? '') === 'premium_as_on_date' && ! empty($premResp['rows'][0]['premium_as_on_date'] ?? '')); + $badResp = json_decode($controller->kpi('not_a_kpi')->getJSON(), true); ok('controller unknown kpi 404', ($badResp['status'] ?? true) === false);