MERGE_TEST_DASHBOARD_KPIS
This commit is contained in:
commit
153daf8524
@ -471,6 +471,15 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
|
||||
});
|
||||
|
||||
$routes->group('enrollment-collection-v1', static function ($routes) {
|
||||
$routes->get('preview', 'EnrollmentCollectionV1DashboardController::preview');
|
||||
$routes->get('preview/(:num)', 'EnrollmentCollectionV1DashboardController::preview/$1');
|
||||
$routes->get('kpi/(:segment)', 'EnrollmentCollectionV1DashboardController::kpi/$1');
|
||||
$routes->get('all', 'EnrollmentCollectionV1DashboardController::all');
|
||||
$routes->get('debug', 'EnrollmentCollectionV1DashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'EnrollmentCollectionV1DashboardController::debug/$1');
|
||||
});
|
||||
});
|
||||
|
||||
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
@ -787,6 +796,15 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
|
||||
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
|
||||
});
|
||||
|
||||
$routes->group('enrollment-collection-v1', static function ($routes) {
|
||||
$routes->get('preview', 'EnrollmentCollectionV1DashboardController::preview');
|
||||
$routes->get('preview/(:num)', 'EnrollmentCollectionV1DashboardController::preview/$1');
|
||||
$routes->get('kpi/(:segment)', 'EnrollmentCollectionV1DashboardController::kpi/$1');
|
||||
$routes->get('all', 'EnrollmentCollectionV1DashboardController::all');
|
||||
$routes->get('debug', 'EnrollmentCollectionV1DashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'EnrollmentCollectionV1DashboardController::debug/$1');
|
||||
});
|
||||
});
|
||||
|
||||
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");
|
||||
|
||||
@ -1015,34 +1015,27 @@ class DashboardController extends AdminController
|
||||
|
||||
$db2 = \Config\Database::connect('preDB');
|
||||
|
||||
$builder = $db2->table('employees');
|
||||
$total_employee_draft_count = $builder->join('employee_polices', 'employees.id = employee_polices.employee_id')
|
||||
->select('employee_polices.status, count(*) as count')
|
||||
$total_employee_draft_count = $db2->table('employee_polices')
|
||||
->join('employees', 'employees.id = employee_polices.employee_id')
|
||||
->where('DATE(employee_polices.created_at)', $today)
|
||||
->where('employee_polices.is_active', 1)
|
||||
->where('employees.is_active', 1)
|
||||
->whereIn('employee_polices.status', ['draft'])
|
||||
->groupBy('employee_polices.status')
|
||||
->where('employee_polices.status', 'draft')
|
||||
->countAllResults();
|
||||
|
||||
|
||||
$builder1 = $db2->table('employees');
|
||||
$total_employee_enrolled_count = $builder1->join('employee_polices', 'employees.id = employee_polices.employee_id')
|
||||
->select('employee_polices.status, count(*) as count')
|
||||
$total_employee_enrolled_count = $db2->table('employee_polices')
|
||||
->join('employees', 'employees.id = employee_polices.employee_id')
|
||||
->where('DATE(employee_polices.created_at)', $today)
|
||||
->where('employee_polices.is_active', 1)
|
||||
->where('employees.is_active', 1)
|
||||
->whereIn('employee_polices.status', ['enrolled'])
|
||||
->groupBy('employee_polices.status')
|
||||
->where('employee_polices.status', 'enrolled')
|
||||
->countAllResults();
|
||||
|
||||
$builder2 = $db2->table('files');
|
||||
$total_open_for_enrollemnt_policy_count = $builder2
|
||||
->select('COUNT(*) as count')
|
||||
->where('enrollment_open_date <=', $today)
|
||||
->where('enrollment_close_date >=', $today)
|
||||
|
||||
$total_open_for_enrollemnt_policy_count = $db2->table('client_policy')
|
||||
->where('open_date <=', $today)
|
||||
->where('close_date >=', $today)
|
||||
->where('is_active', 1)
|
||||
->where('status', 'success')
|
||||
->where('policy_status', 1)
|
||||
->countAllResults();
|
||||
|
||||
$data = [
|
||||
|
||||
@ -2266,6 +2266,7 @@ class EmployeeRestController extends AdminController
|
||||
client_policy.insurer_id as insurer_id,
|
||||
DATE_FORMAT(client_policy.policy_start_date, '%d-%m-%Y') AS policy_start_date,
|
||||
DATE_FORMAT(client_policy.policy_end_date, '%d-%m-%Y') AS policy_expiry_date,
|
||||
clients.client_logo as client_logo,
|
||||
(
|
||||
select COALESCE(ROUND(SUM(rata_premimum + gst)), 0)
|
||||
from employee_polices
|
||||
@ -2275,6 +2276,7 @@ class EmployeeRestController extends AdminController
|
||||
) as total_premium,
|
||||
CASE WHEN policy_type.allocg IN ('Non-EB', 'Marine') THEN 'Non-EB' ELSE 'EB' END as allocg
|
||||
", false)
|
||||
->join('clients', 'clients.id = client_policy.client_id', 'left')
|
||||
->join('policy_type', 'policy_type.id = client_policy.policy_type_id', 'left')
|
||||
->where('md5(client_policy.client_id)', $this->request->getGet('client_id'))
|
||||
->where('client_policy.client_branch_id', $this->request->getGet('client_branch_id'))
|
||||
@ -2293,6 +2295,7 @@ class EmployeeRestController extends AdminController
|
||||
$value['policy_name'] = $policyTypeData->long_name;
|
||||
$value['insurer_name'] = $insurerData->name;
|
||||
$value['insurer_short_name'] = $insurerData->short_name;
|
||||
$value['client_logo'] = ! empty($value['client_logo']) ? base_url() . 'public/uploads/logo/' . $value['client_logo'] : '';
|
||||
$employeeDetails = $this->employeePolicyModel->getEmployeePolicy(client_id: $value['client_id'], policy_id: $value['client_policy_id'], status: 0, branch_id: $this->request->getGet('client_branch_id'), status_type: 'inactive');
|
||||
// dd($employeeDetails);
|
||||
$activeCount = 0;
|
||||
|
||||
146
app/Controllers/EnrollmentCollectionV1DashboardController.php
Normal file
146
app/Controllers/EnrollmentCollectionV1DashboardController.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\EnrollmentCollectionV1DashboardModel;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
/**
|
||||
* Enrollment Collection V1 KPI API (Metabase SQL as model methods).
|
||||
*/
|
||||
class EnrollmentCollectionV1DashboardController extends BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected function resolvePolicyId(?int $fallback = null): int
|
||||
{
|
||||
$id = (int) (
|
||||
$this->request->getGet('client_policy')
|
||||
?? $this->request->getGet('client_policy_id')
|
||||
?? $this->request->getPost('client_policy')
|
||||
?? $this->request->getPost('client_policy_id')
|
||||
?? 0
|
||||
);
|
||||
|
||||
if ($id > 0) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
if ($fallback !== null && $fallback > 0) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function resolveKpiMethod(string $kpiKey): ?string
|
||||
{
|
||||
$kpiKey = trim($kpiKey);
|
||||
if ($kpiKey === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (in_array($kpiKey, EnrollmentCollectionV1DashboardModel::KPI_MAP, true)) {
|
||||
return $kpiKey;
|
||||
}
|
||||
|
||||
if (ctype_digit($kpiKey)) {
|
||||
$id = (int) $kpiKey;
|
||||
|
||||
return EnrollmentCollectionV1DashboardModel::KPI_MAP[$id] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function kpi(string $kpiMethod = '')
|
||||
{
|
||||
$policyId = $this->resolvePolicyId();
|
||||
|
||||
if ($policyId <= 0) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'client_policy or client_policy_id is required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$kpiMethod = $this->resolveKpiMethod($kpiMethod);
|
||||
if ($kpiMethod === null) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Unknown KPI. Pass Metabase id or method slug.',
|
||||
'allowed' => EnrollmentCollectionV1DashboardModel::KPI_MAP,
|
||||
], 404);
|
||||
}
|
||||
|
||||
$model = new EnrollmentCollectionV1DashboardModel();
|
||||
$metabaseId = array_search($kpiMethod, EnrollmentCollectionV1DashboardModel::KPI_MAP, true);
|
||||
|
||||
return $this->respond([
|
||||
'status' => true,
|
||||
'policy_id' => $policyId,
|
||||
'kpi_id' => $metabaseId !== false ? (int) $metabaseId : null,
|
||||
'kpi' => $kpiMethod,
|
||||
'label' => EnrollmentCollectionV1DashboardModel::KPI_LABELS[$kpiMethod] ?? $kpiMethod,
|
||||
'rows' => $model->getKpi($kpiMethod, $policyId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$policyId = $this->resolvePolicyId();
|
||||
|
||||
if ($policyId <= 0) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'client_policy or client_policy_id is required.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$model = new EnrollmentCollectionV1DashboardModel();
|
||||
|
||||
return $this->respond([
|
||||
'status' => true,
|
||||
'policy_id' => $policyId,
|
||||
'data' => $model->getAllKpis($policyId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function preview(int $policyId = 4687)
|
||||
{
|
||||
$policyId = $this->resolvePolicyId($policyId);
|
||||
$path = $this->request->getUri()->getPath();
|
||||
$isJwt = stripos($path, 'employeeRest') !== false;
|
||||
$prefix = $isJwt ? 'employeeRest/enrollment-collection-v1' : 'util/enrollment-collection-v1';
|
||||
|
||||
return view('enrollment_collection_v1_dashboard', [
|
||||
'policy_id' => $policyId,
|
||||
'kpi_map' => EnrollmentCollectionV1DashboardModel::KPI_MAP,
|
||||
'kpi_labels' => EnrollmentCollectionV1DashboardModel::KPI_LABELS,
|
||||
'api_all_url' => base_url($prefix . '/all'),
|
||||
'api_kpi_url' => base_url($prefix . '/kpi'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function debug(int $policyId = 4687)
|
||||
{
|
||||
$policyId = $this->resolvePolicyId($policyId);
|
||||
|
||||
if ($policyId <= 0) {
|
||||
return $this->response
|
||||
->setStatusCode(422)
|
||||
->setBody('client_policy or client_policy_id is required.');
|
||||
}
|
||||
|
||||
$model = new EnrollmentCollectionV1DashboardModel();
|
||||
$body = json_encode([
|
||||
'status' => true,
|
||||
'policy_id' => $policyId,
|
||||
'data' => $model->getAllKpis($policyId),
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/json; charset=UTF-8')
|
||||
->setBody($body);
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ use App\Helpers\ExcelSanitizeHelper;
|
||||
use App\Models\NhanceBranchModel;
|
||||
use App\Models\BDSDumpModel;
|
||||
use App\Models\VehicleModel;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use Exception;
|
||||
|
||||
class PolicyTransactionController extends BaseController
|
||||
@ -5324,21 +5325,18 @@ class PolicyTransactionController extends BaseController
|
||||
$bdsInstallmentData = [];
|
||||
|
||||
try {
|
||||
|
||||
$bdsInstallmentData = $this->BdsPlacementModel->getClientInstallmentDetails();
|
||||
$bdsInstallmentData = $this->BdsPlacementModel->getLeadInstallmentDetails();
|
||||
} catch (Exception $e) {
|
||||
|
||||
$this->myLogger->logme('error', 'Exception: ' . $e->getMessage() . 'Page: ' . $e->getFile() . ' Line: ' . $e->getLine());
|
||||
}
|
||||
|
||||
$this->myLogger->logme("error", "Data for BDS Installment " . json_encode($bdsInstallmentData));
|
||||
// dd($bdsInstallmentData);
|
||||
// print_r($bdsInstallmentData);die();
|
||||
|
||||
if (empty($bdsInstallmentData)) {
|
||||
|
||||
$this->myLogger->logme("error", "No Client Installment is Due in the 15th Day");
|
||||
|
||||
return false;
|
||||
CLI::write("No Client Installment is Due in the 15th Day");
|
||||
return ['status' => false, 'message' => 'No data found', 'response' => []];
|
||||
}
|
||||
|
||||
foreach ($bdsInstallmentData as $installmentData) {
|
||||
@ -5358,13 +5356,13 @@ class PolicyTransactionController extends BaseController
|
||||
$this->myLogger->logme('error', 'res: ' . json_encode($res));
|
||||
|
||||
if ($res->status == 'success') {
|
||||
return true;
|
||||
return ['status' => true, 'message' => 'Mail sent successfully', 'response' => $res];
|
||||
} else {
|
||||
return false;
|
||||
return ['status' => false, 'message' => 'Mail sent failed', 'response' => $res];
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->myLogger->logme('error', 'Exception: ' . $e->getMessage() . 'Page: ' . $e->getFile() . ' Line: ' . $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use App\Models\SalesLeadNoteModel;
|
||||
use App\Models\SalesTargetModel;
|
||||
use App\Models\UserModel;
|
||||
use App\Models\ClientModel;
|
||||
use App\Helpers\MailHelper;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
@ -330,7 +331,10 @@ class SalesController extends BaseController
|
||||
return $this->fail($this->leadModel->errors());
|
||||
}
|
||||
|
||||
return $this->respondCreated(['status' => 'success', 'id' => $this->leadModel->getInsertID()]);
|
||||
$leadId = $this->leadModel->getInsertID();
|
||||
$this->sendLeadCreateMail($leadId, $data);
|
||||
|
||||
return $this->respondCreated(['status' => 'success', 'id' => $leadId]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
@ -2595,4 +2599,132 @@ public function salesManagerLevelDashboard($userId, $current_fin_year = null, $f
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send lead-create notification email when the logged-in user's branch is Chennai.
|
||||
*/
|
||||
private function sendLeadCreateMail(int $leadId, array $leadData): void
|
||||
{
|
||||
try {
|
||||
if (!$this->isLoggedInUserChennaiBranch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$toMail = trim((string) getenv('LEAD_CREATE_MAIL'));
|
||||
if ($toMail === '') {
|
||||
log_message('error', 'sendLeadCreateMail: LEAD_CREATE_MAIL is not configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
$recipientEmails = array_filter(array_map('trim', explode(',', $toMail)));
|
||||
if (empty($recipientEmails)) {
|
||||
log_message('error', 'sendLeadCreateMail: No valid recipients in LEAD_CREATE_MAIL.');
|
||||
return;
|
||||
}
|
||||
|
||||
$creator = $this->getLeadCreateMailCreatorDetails();
|
||||
$assignedUser = $this->userModel->where('id', $leadData['assigned_to'] ?? 0)->first() ?? [];
|
||||
|
||||
$companyName = $leadData['company_name'] ?? '';
|
||||
$subject = 'New Lead Created - ' . $companyName;
|
||||
$message = $this->buildLeadCreateMailMessage(
|
||||
$companyName,
|
||||
$leadData['email'] ?? '',
|
||||
$leadData['phone'] ?? '',
|
||||
$leadData['status'] ?? '',
|
||||
$creator['name'] ?? '',
|
||||
$creator['email'] ?? '',
|
||||
$creator['branch'] ?? '',
|
||||
$assignedUser['first_name'] ?? '',
|
||||
$assignedUser['email'] ?? '',
|
||||
date('d/m/Y h:i A')
|
||||
);
|
||||
|
||||
$res = MailHelper::send_email([
|
||||
'mail' => $recipientEmails,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'common' => ['module' => 'sales', 'pk' => $leadId, 'mail_type' => 'lead_create'],
|
||||
]);
|
||||
|
||||
$resDecoded = is_string($res) ? json_decode($res, true) : $res;
|
||||
if (!isset($resDecoded['status']) || $resDecoded['status'] !== 'success') {
|
||||
log_message('error', 'sendLeadCreateMail: Email send failed - ' . json_encode($resDecoded));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'sendLeadCreateMail: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function isLoggedInUserChennaiBranch(): bool
|
||||
{
|
||||
$userId = get_session_userid();
|
||||
if (empty($userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = $this->userModel
|
||||
->select('id')
|
||||
->where('id', $userId)
|
||||
->where('nhance_branch_id', 1)
|
||||
->first();
|
||||
|
||||
return !empty($row);
|
||||
}
|
||||
|
||||
private function getLeadCreateMailCreatorDetails(): array
|
||||
{
|
||||
$userId = get_session_userid();
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$row = $db->table('user_profiles up')
|
||||
->select('up.first_name, up.email, nb.branch_name')
|
||||
->join('nhance_branch nb', 'nb.id = up.nhance_branch_id', 'left')
|
||||
->where('up.id', $userId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (!$row) {
|
||||
return ['name' => '', 'email' => '', 'branch' => ''];
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $row['first_name'] ?? '',
|
||||
'email' => $row['email'] ?? '',
|
||||
'branch' => $row['branch_name'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
private function buildLeadCreateMailMessage(
|
||||
string $companyName,
|
||||
string $leadEmail,
|
||||
string $leadMobile,
|
||||
string $leadStatus,
|
||||
string $createdByName,
|
||||
string $createdByEmail,
|
||||
string $createdByBranch,
|
||||
string $assignedUserName,
|
||||
string $assignedUserEmail,
|
||||
string $createdDateTime
|
||||
): string {
|
||||
$e = static fn (?string $value): string => htmlspecialchars((string) ($value ?? ''), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return '<p>Hello Team,</p>'
|
||||
. '<p>A new lead has been created</p>'
|
||||
. '<h3>Lead Details</h3>'
|
||||
. '<p><strong>Company Name :</strong> ' . $e($companyName) . '</p>'
|
||||
. '<p><strong>Email :</strong> ' . $e($leadEmail) . '</p>'
|
||||
. '<p><strong>Mobile :</strong> ' . $e($leadMobile) . '</p>'
|
||||
. '<p><strong>Status :</strong> ' . $e($leadStatus) . '</p>'
|
||||
. '<h3>Created By</h3>'
|
||||
. '<p><strong>Name :</strong> ' . $e($createdByName) . '</p>'
|
||||
. '<p><strong>Email :</strong> ' . $e($createdByEmail) . '</p>'
|
||||
. '<p><strong>Branch :</strong> ' . $e($createdByBranch) . '</p>'
|
||||
. '<h3>Assigned To</h3>'
|
||||
. '<p><strong>Name :</strong> ' . $e($assignedUserName) . '</p>'
|
||||
. '<p><strong>Email :</strong> ' . $e($assignedUserEmail) . '</p>'
|
||||
. '<h3>Created On</h3>'
|
||||
. '<p><strong>Date & Time :</strong> ' . $e($createdDateTime) . '</p>'
|
||||
. '<p>Regards,<br>NHANCE INDIA INSURANCE BROKING PRIVATE LIMITED</p>';
|
||||
}
|
||||
}
|
||||
@ -114,6 +114,59 @@ class BdsPlacementModel extends Model
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getLeadInstallmentDetails()
|
||||
{
|
||||
$heads = $this->db->table('user_profiles')
|
||||
->select('email')->where(['role' => 5, 'is_active' => 1])
|
||||
->get()->getResultArray();
|
||||
|
||||
$admins = $this->db->table('user_profiles')
|
||||
->select('email')->where(['role' => 1, 'is_active' => 1])
|
||||
->get()->getResultArray();
|
||||
|
||||
$businessTeam = $this->db->table("user_teams ut")
|
||||
->select("up.email")
|
||||
->join('user_profiles up', 'up.id = ut.user_id AND up.is_active = 1')
|
||||
->where(["ut.team_id" => 7, "ut.is_active" => 1])
|
||||
->get()->getResultArray();
|
||||
|
||||
$builder = $this->db->table("lead_installment_payment_details lipd")
|
||||
->select("lipd.*, COALESCE(ct.client_name, leads.client_name) as client_name, COALESCE(ct.short_name, leads.client_short_name) as short_name, COALESCE(cb.branch_name, leads.branch_name) as branch_name, leads.salse_person_id, cp.policy_no")
|
||||
->join("leads", "leads.id = lipd.lead_id")
|
||||
->join("clients ct", "ct.id = leads.client_id", "left")
|
||||
->join("client_branch cb", "cb.id = leads.client_branch_id", "left")
|
||||
->join("client_policy cp", "cp.id = leads.source_policy_id", "left")
|
||||
->where("lipd.is_active", 1)
|
||||
->where("lipd.utr_no IS NULL")
|
||||
->where("lipd.payment_date", date('Y-m-d', strtotime('+15 days')));
|
||||
|
||||
$data = $builder->get()->getResultArray();
|
||||
|
||||
foreach ($data as &$row) {
|
||||
$sales_person_ids = json_decode($row['salse_person_id'], true);
|
||||
$sales_person_id = $sales_person_ids[0] ?? null;
|
||||
|
||||
if ($sales_person_id) {
|
||||
$user = $this->db->table('user_profiles')
|
||||
->select('email')
|
||||
->where('id', (int) $sales_person_id)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$row['sales_person'] = $user['email'] ?? 'N/A';
|
||||
} else {
|
||||
$row['sales_person'] = 'Not Assigned';
|
||||
}
|
||||
|
||||
$row['heads'] = $heads;
|
||||
$row['admins'] = $admins;
|
||||
$row['buisness_team'] = $businessTeam;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getClientInstallmentDetails_olds()
|
||||
{
|
||||
|
||||
|
||||
290
app/Models/EnrollmentCollectionV1DashboardModel.php
Normal file
290
app/Models/EnrollmentCollectionV1DashboardModel.php
Normal file
@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Enrollment Collection V1 dashboard KPIs (from metabase_raw_queries.csv).
|
||||
*/
|
||||
class EnrollmentCollectionV1DashboardModel extends Model
|
||||
{
|
||||
protected $DBGroup = 'default';
|
||||
|
||||
/**
|
||||
* Metabase question id => PHP method name.
|
||||
*/
|
||||
public const KPI_MAP = [
|
||||
115 => 'originally_enrolled',
|
||||
116 => 'added_subsequently',
|
||||
117 => 'gender_split',
|
||||
119 => 'enrollment_relationship',
|
||||
122 => 'average_age_by_enrollment_month',
|
||||
123 => 'original_base_premium',
|
||||
124 => 'overall_active',
|
||||
125 => 'enrollment_age_group',
|
||||
126 => 'additions_premium',
|
||||
127 => 'net_premium',
|
||||
];
|
||||
|
||||
/**
|
||||
* Human-readable KPI labels (Metabase name).
|
||||
*/
|
||||
public const KPI_LABELS = [
|
||||
'originally_enrolled' => 'Originally Enrolled',
|
||||
'added_subsequently' => 'Added Subsequently',
|
||||
'gender_split' => 'Gender Split',
|
||||
'enrollment_relationship' => 'Relationship',
|
||||
'average_age_by_enrollment_month' => 'Average age by Enrolment month',
|
||||
'original_base_premium' => 'Original Base Premium',
|
||||
'overall_active' => 'Overall Active',
|
||||
'enrollment_age_group' => 'Age Group',
|
||||
'additions_premium' => 'Additions Premium',
|
||||
'net_premium' => 'Net Premium',
|
||||
];
|
||||
|
||||
protected function runKpiQuery(string $sql, int $policyId): array
|
||||
{
|
||||
$sql = str_replace(['\\t', '\\n', '\\r'], ["\t", "\n", "\r"], $sql);
|
||||
|
||||
$db = \Config\Database::connect($this->DBGroup);
|
||||
$query = $db->query($sql, ['client_policy_id' => $policyId]);
|
||||
|
||||
return $query->getResultArray();
|
||||
}
|
||||
|
||||
public function getKpi(string $method, int $policyId): array
|
||||
{
|
||||
if (! method_exists($this, $method)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->{$method}($policyId);
|
||||
}
|
||||
|
||||
public function getAllKpis(int $policyId): array
|
||||
{
|
||||
$out = [];
|
||||
foreach (self::KPI_MAP as $id => $method) {
|
||||
$out[$method] = [
|
||||
'id' => (int) $id,
|
||||
'label' => self::KPI_LABELS[$method] ?? $method,
|
||||
'rows' => $this->getKpi($method, $policyId),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Metabase #115: Originally Enrolled */
|
||||
public function originally_enrolled(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT ep.employee_id) AS `Originally Enrolled`
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id AND e.change_event = 'Inception'
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status = 'Active'
|
||||
AND e.emp_status = 'Active'
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #116: Added Subsequently */
|
||||
public function added_subsequently(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT ep.employee_id) AS `Subsequent Additions`
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
AND LOWER(e.change_event) IN ('dependent addition', 'addition', 'missed inception')
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status = 'Active'
|
||||
AND e.emp_status = 'Active'
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #117: Gender Split */
|
||||
public function gender_split(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
e.gender,
|
||||
COUNT(DISTINCT ep.employee_id) AS member_count,
|
||||
ROUND((COUNT(DISTINCT ep.employee_id) * 100.0) /
|
||||
SUM(COUNT(DISTINCT ep.employee_id)) OVER(), 2) AS percentage
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status != 'Truncated'
|
||||
AND e.is_active = 1
|
||||
AND e.emp_status != 'Truncated'
|
||||
AND e.gender IS NOT NULL
|
||||
GROUP BY e.gender
|
||||
ORDER BY member_count DESC;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #119: Relationship */
|
||||
public function enrollment_relationship(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
e.relationship,
|
||||
COUNT(DISTINCT ep.employee_id) AS member_count,
|
||||
ROUND((COUNT(DISTINCT ep.employee_id) * 100.0) /
|
||||
SUM(COUNT(DISTINCT ep.employee_id)) OVER(), 2) AS percentage
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status != 'Truncated'
|
||||
AND e.is_active = 1
|
||||
AND e.emp_status != 'Truncated'
|
||||
AND e.relationship IS NOT NULL
|
||||
GROUP BY e.relationship
|
||||
ORDER BY member_count DESC;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #122: Average age by Enrolment month */
|
||||
public function average_age_by_enrollment_month(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
DATE_FORMAT(ep.date_coverage, '%Y-%m') AS enrollment_month,
|
||||
DATE_FORMAT(ep.date_coverage, '%b %Y') AS month_label,
|
||||
ROUND(AVG(TIMESTAMPDIFF(YEAR, e.dob, ep.date_coverage)), 2) AS average_age,
|
||||
COUNT(DISTINCT ep.employee_id) AS enrollments
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND e.is_active = 1
|
||||
AND e.dob IS NOT NULL
|
||||
AND ep.date_coverage IS NOT NULL
|
||||
GROUP BY DATE_FORMAT(ep.date_coverage, '%Y-%m')
|
||||
ORDER BY enrollment_month;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #123: Original Base Premium */
|
||||
public function original_base_premium(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
ROUND(IFNULL(SUM(ep.rata_premimum + COALESCE(ep.gst, 0)), 0), 2) AS opening_premium
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id AND e.change_event = 'Inception'
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status = 'Active'
|
||||
AND e.emp_status = 'Active'
|
||||
AND e.is_active = 1;
|
||||
-- AND e.relationship = 'Self';
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #124: Overall Active */
|
||||
public function overall_active(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT ep.employee_id) AS `Overall Active`
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status = 'Active'
|
||||
AND e.emp_status = 'Active'
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #125: Age Group */
|
||||
public function enrollment_age_group(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
CASE
|
||||
WHEN TIMESTAMPDIFF(YEAR, e.dob, CURDATE()) BETWEEN 0 AND 18 THEN '0-18'
|
||||
WHEN TIMESTAMPDIFF(YEAR, e.dob, CURDATE()) BETWEEN 19 AND 30 THEN '19-30'
|
||||
WHEN TIMESTAMPDIFF(YEAR, e.dob, CURDATE()) BETWEEN 31 AND 40 THEN '31-40'
|
||||
WHEN TIMESTAMPDIFF(YEAR, e.dob, CURDATE()) BETWEEN 41 AND 50 THEN '41-50'
|
||||
WHEN TIMESTAMPDIFF(YEAR, e.dob, CURDATE()) BETWEEN 51 AND 60 THEN '51-60'
|
||||
ELSE '60+'
|
||||
END AS age_group,
|
||||
COUNT(DISTINCT ep.employee_id) AS member_count,
|
||||
ROUND((COUNT(DISTINCT ep.employee_id) * 100.0) /
|
||||
SUM(COUNT(DISTINCT ep.employee_id)) OVER(), 2) AS percentage
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status != 'Truncated'
|
||||
AND e.is_active = 1
|
||||
AND e.emp_status != 'Truncated'
|
||||
AND e.dob IS NOT NULL
|
||||
GROUP BY age_group
|
||||
ORDER BY age_group;
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #126: Additions Premium */
|
||||
public function additions_premium(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
ROUND(IFNULL(SUM(ep.rata_premimum + COALESCE(ep.gst, 0)), 0), 2) AS additions_premium
|
||||
FROM employee_polices ep
|
||||
INNER JOIN employees e ON ep.employee_id = e.id
|
||||
AND LOWER(e.change_event) IN ('dependent addition', 'addition', 'missed inception')
|
||||
WHERE ep.client_policy_id = :client_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 #127: Net Premium */
|
||||
public function net_premium(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
ROUND(IFNULL(SUM(ep.rata_premimum + COALESCE(ep.gst, 0)), 0), 2) AS net_premium
|
||||
-- ROUND(SUM(ep.rata_premimum), 2) as base_premium,
|
||||
-- ROUND(SUM(COALESCE(ep.gst, 0)), 2) as total_gst
|
||||
FROM employee_polices ep
|
||||
WHERE ep.client_policy_id = :client_policy_id:
|
||||
AND ep.is_active = 1
|
||||
AND ep.status = 'Active';
|
||||
SQL;
|
||||
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
}
|
||||
@ -166,19 +166,19 @@
|
||||
</div>
|
||||
|
||||
<div class="content" style="padding:20px;">
|
||||
<div class="report-date" style="color:#7f8c8d;font-size:13px;margin-bottom:16px;text-align:right;">Date: <?= $today ?></div>
|
||||
<div class="report-date" style="color:#7f8c8d;font-size:13px;margin-bottom:16px;text-align:right;">Date: <?= $today ?? "" ?></div>
|
||||
|
||||
<!-- File Operations -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Inception & Endorsement (File Upload)</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #3498db;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Inception</div>
|
||||
<div class="stat-value"><?= $total_inception_count ?></div>
|
||||
<div class="stat-value"><?= $total_inception_count ?? 0 ?></div>
|
||||
<div class="stat-note">Employee file uploads marked as inception</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #3498db;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Endorsement</div>
|
||||
<div class="stat-value"><?= $total_endorsement_count ?></div>
|
||||
<div class="stat-value"><?= $total_endorsement_count ?? 0 ?></div>
|
||||
<div class="stat-note">All non‑inception successful uploads</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -203,12 +203,12 @@
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #e67e22;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">TPA (Inception / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_tpa_incetion_count ?> / <?= $total_tpa_endorsement_count ?></div>
|
||||
<div class="stat-value"><?= $total_tpa_incetion_count ?? 0 ?> / <?= $total_tpa_endorsement_count ?? 0 ?></div>
|
||||
<div class="stat-note">Successful TPA events</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #9b59b6;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Insurer (Inception / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_insurer_incetion_count ?> / <?= $total_insurer_endorsement_count ?></div>
|
||||
<div class="stat-value"><?= $total_insurer_incetion_count ?? 0 ?> / <?= $total_insurer_endorsement_count ?? 0 ?></div>
|
||||
<div class="stat-note">Successful insurer events</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,22 +218,22 @@
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #27ae60;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Opportunities & Won</div>
|
||||
<div class="stat-value"><?= $total_opportunity_count ?> / <?= $total_placement_count ?></div>
|
||||
<div class="stat-value"><?= $total_opportunity_count ?? 0 ?> / <?= $total_placement_count ?? 0 ?></div>
|
||||
<div class="stat-note">Total opportunities created vs converted</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #16a085;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">RFQ (Created / Sent to Insurer)</div>
|
||||
<div class="stat-value"><?= $total_rfq_created_count ?> / <?= $total_rfq_insurer_send_count ?></div>
|
||||
<div class="stat-value"><?= $total_rfq_created_count ?? 0 ?> / <?= $total_rfq_insurer_send_count ?? 0 ?></div>
|
||||
<div class="stat-note">Movement from opportunity to RFQ</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #2980b9;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">QCR (Created / Sent to Client)</div>
|
||||
<div class="stat-value"><?= $total_qcr_created_count ?> / <?= $total_qcr_client_send_count ?></div>
|
||||
<div class="stat-value"><?= $total_qcr_created_count ?? 0 ?> / <?= $total_qcr_client_send_count ?? 0 ?></div>
|
||||
<div class="stat-note">Quotes prepared and shared</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #8e44ad;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Leads & Activities</div>
|
||||
<div class="stat-value"><?= $total_lead_count ?> / <?= $total_activity_count ?></div>
|
||||
<div class="stat-value"><?= $total_lead_count ?? 0 ?> / <?= $total_activity_count ?? 0 ?></div>
|
||||
<div class="stat-note">Lead entries and logged touchpoints</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -243,12 +243,12 @@
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #d35400;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total BDS Transactions</div>
|
||||
<div class="stat-value"><?= $total_bds_count ?></div>
|
||||
<div class="stat-value"><?= $total_bds_count ?? 0 ?></div>
|
||||
<div class="stat-note">All policy transactions processed</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #c0392b;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">BDS (Policy / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_bds_policy_wise_count ?> / <?= $total_bds_endorsement_wise_count ?></div>
|
||||
<div class="stat-value"><?= $total_bds_policy_wise_count ?? 0?> / <?= $total_bds_endorsement_wise_count ?? 0 ?></div>
|
||||
<div class="stat-note">Split of inceptions vs endorsements</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -289,7 +289,7 @@
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #d35400;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Claims Registered for the day</div>
|
||||
<div class="stat-value"><?= $total_claim_count ?></div>
|
||||
<div class="stat-value"><?= $total_claim_count ?? 0 ?></div>
|
||||
<div class="stat-note"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
102
app/Views/enrollment_collection_v1_dashboard.php
Normal file
102
app/Views/enrollment_collection_v1_dashboard.php
Normal file
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enrollment Collection V1 Dashboard</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { font-family: system-ui, sans-serif; margin: 0; padding: 1.5rem; background: #f0f2f5; color: #1a1a1a; }
|
||||
h1 { font-size: 1.35rem; margin: 0 0 1rem; }
|
||||
.toolbar { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin-bottom: 1.25rem; }
|
||||
.toolbar input { padding: 0.45rem 0.6rem; border: 1px solid #ccc; border-radius: 6px; width: 140px; }
|
||||
.toolbar button { padding: 0.5rem 1rem; border: none; border-radius: 6px; background: #059669; color: #fff; cursor: pointer; }
|
||||
.toolbar button.secondary { background: #64748b; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem; }
|
||||
.card { background: #fff; border-radius: 8px; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
||||
.card h2 { font-size: 0.85rem; margin: 0 0 0.5rem; color: #475569; font-weight: 600; }
|
||||
.card pre { font-size: 0.72rem; margin: 0; max-height: 200px; overflow: auto; background: #f8fafc; padding: 0.5rem; border-radius: 4px; white-space: pre-wrap; word-break: break-word; }
|
||||
.card .status { font-size: 0.75rem; color: #94a3b8; margin-bottom: 0.35rem; }
|
||||
.card.loading pre { color: #94a3b8; }
|
||||
.card.error pre { color: #b91c1c; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Enrollment Collection V1</h1>
|
||||
|
||||
<div class="toolbar">
|
||||
<label>Policy ID <input type="number" id="policy-id" value="<?= (int) $policy_id ?>" min="1"></label>
|
||||
<button type="button" id="btn-load-all">Load all KPIs</button>
|
||||
<button type="button" class="secondary" id="btn-clear">Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="grid" id="kpi-grid">
|
||||
<?php foreach ($kpi_map as $metabaseId => $method): ?>
|
||||
<div class="card" id="card-<?= esc($method) ?>" data-kpi="<?= esc($method) ?>">
|
||||
<div class="status">#<?= (int) $metabaseId ?> · <?= esc($kpi_labels[$method] ?? $method) ?></div>
|
||||
<h2><?= esc($method) ?></h2>
|
||||
<pre>Click “Load all KPIs” or open single KPI API.</pre>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const policyInput = document.getElementById('policy-id');
|
||||
const grid = document.getElementById('kpi-grid');
|
||||
|
||||
function setCardState(method, state, text) {
|
||||
const card = document.getElementById('card-' + method);
|
||||
if (!card) return;
|
||||
card.classList.remove('loading', 'error');
|
||||
if (state) card.classList.add(state);
|
||||
card.querySelector('pre').textContent = text;
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
const policyId = policyInput.value;
|
||||
if (!policyId) {
|
||||
alert('Enter a policy ID');
|
||||
return;
|
||||
}
|
||||
grid.querySelectorAll('.card').forEach(function (c) {
|
||||
c.classList.add('loading');
|
||||
c.querySelector('pre').textContent = 'Loading…';
|
||||
});
|
||||
|
||||
try {
|
||||
const apiAllUrl = <?= json_encode($api_all_url ?? base_url('util/enrollment-collection-v1/all')) ?>;
|
||||
const res = await fetch(apiAllUrl + '?client_policy=' + encodeURIComponent(policyId));
|
||||
const json = await res.json();
|
||||
if (!json.status) {
|
||||
throw new Error(json.message || 'Request failed');
|
||||
}
|
||||
Object.keys(json.data || {}).forEach(function (method) {
|
||||
const block = json.data[method];
|
||||
setCardState(method, '', JSON.stringify(block.rows, null, 2));
|
||||
});
|
||||
} catch (e) {
|
||||
grid.querySelectorAll('.card').forEach(function (c) {
|
||||
c.classList.remove('loading');
|
||||
c.classList.add('error');
|
||||
c.querySelector('pre').textContent = e.message;
|
||||
});
|
||||
} finally {
|
||||
grid.querySelectorAll('.card.loading').forEach(function (c) {
|
||||
c.classList.remove('loading');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btn-load-all').addEventListener('click', loadAll);
|
||||
document.getElementById('btn-clear').addEventListener('click', function () {
|
||||
grid.querySelectorAll('.card').forEach(function (c) {
|
||||
c.classList.remove('error');
|
||||
c.querySelector('pre').textContent = '—';
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -379,7 +379,7 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
? (string) $row->amt_updated_by_name
|
||||
: (!empty($row->amt_updated_by) ? ('User #' . $row->amt_updated_by) : '-');
|
||||
$historyUpdatedAt = !empty($row->amt_updated_at)
|
||||
? date('d M Y, h:i A', strtotime((string) $row->amt_updated_at))
|
||||
? date('d/m/Y h:i A', strtotime((string) $row->amt_updated_at))
|
||||
: '-';
|
||||
?>
|
||||
<tr id="<?php echo $row->id;?>"
|
||||
@ -387,7 +387,7 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
data-transaction-type="<?= esc((string) $row->transaction_type, 'attr') ?>"
|
||||
data-credit="<?= esc((string) $creditAmount, 'attr') ?>"
|
||||
data-debit="<?= esc((string) $debitAmount, 'attr') ?>">
|
||||
<td><?php echo date('d-M-Y h:i A', strtotime($row->created_at)); ?></td>
|
||||
<td><?php echo date('d/m/Y h:i A', strtotime($row->created_at)); ?></td>
|
||||
<td><?php echo isset($row->record_date)? date('d-M-Y', strtotime($row->record_date)):'-' ?></td>
|
||||
<td><?php echo $row->unit ?? ' - '; ?></td>
|
||||
<!-- <td><?php echo $row->policy_name ?? '<center> - </center>'; ?></td> -->
|
||||
@ -481,14 +481,14 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
<div class="deposit-history-meta-row">
|
||||
<div class="deposit-history-meta-label">
|
||||
<i class="mdi mdi-account-outline"></i>
|
||||
<span>Changed by</span>
|
||||
<span>Modified by</span>
|
||||
</div>
|
||||
<div class="deposit-history-meta-value" id="history_updated_by">-</div>
|
||||
</div>
|
||||
<div class="deposit-history-meta-row">
|
||||
<div class="deposit-history-meta-label">
|
||||
<i class="mdi mdi-calendar-month-outline"></i>
|
||||
<span>Changed at</span>
|
||||
<span>Modified at</span>
|
||||
</div>
|
||||
<div class="deposit-history-meta-value" id="history_updated_at">-</div>
|
||||
</div>
|
||||
@ -668,6 +668,38 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
|
||||
});
|
||||
nhanceListDataTableBeforeInit();
|
||||
function depositExportFormatBody(data, row, column, node) {
|
||||
if (column === 6 || column === 7) {
|
||||
var $input = $(node).find('.deposit-inline-input');
|
||||
if ($input.length) {
|
||||
var val = ($input.val() || '').trim();
|
||||
return val !== '' ? val : '-';
|
||||
}
|
||||
var $tr = $(node).closest('tr');
|
||||
var credit = parseFloat($tr.attr('data-credit')) || 0;
|
||||
var debit = parseFloat($tr.attr('data-debit')) || 0;
|
||||
if (column === 6) {
|
||||
return credit > 0 ? String(credit) : '-';
|
||||
}
|
||||
return debit > 0 ? String(debit) : '-';
|
||||
}
|
||||
if (column === 8) {
|
||||
var $balanceSpan = $(node).find('.deposit-balance-wrap > span').first();
|
||||
if ($balanceSpan.length) {
|
||||
return ($balanceSpan.text() || '').trim();
|
||||
}
|
||||
return ($(node).text() || '').trim();
|
||||
}
|
||||
if (typeof data === 'string' && data.indexOf('<') !== -1) {
|
||||
return $('<div>').html(data).text().trim();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
var depositExportOptions = {
|
||||
format: {
|
||||
body: depositExportFormatBody
|
||||
}
|
||||
};
|
||||
var depositPageStorageKey = 'view_deposit_datatable_page_' + window.location.pathname + window.location.search;
|
||||
function getStoredDepositPage() {
|
||||
var page = parseInt(sessionStorage.getItem(depositPageStorageKey), 10);
|
||||
@ -780,7 +812,7 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
extend: 'csv',
|
||||
text: '<i class="mdi mdi-file-delimited " ></i><span class=" btn-custom"> CSV </span>',
|
||||
title: 'CD TransactionDetails',
|
||||
exportOptions: { columns: '' },
|
||||
exportOptions: $.extend(true, { columns: '' }, depositExportOptions),
|
||||
className: 'app-btn-primary ',
|
||||
},
|
||||
{
|
||||
@ -788,9 +820,7 @@ $vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len
|
||||
text: '<i class="mdi mdi-file-excel " ></i><span class=" btn-custom"> EXCEL </span>',
|
||||
title: 'CD TransactionDetails',
|
||||
sheetName: 'CD TransactionDetails',
|
||||
exportOptions: {
|
||||
orthogonal: 'sort'
|
||||
}
|
||||
exportOptions: $.extend(true, { orthogonal: 'sort' }, depositExportOptions)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
154
enrollment-dashboard.md
Normal file
154
enrollment-dashboard.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Enrollment Collection V1 — HR Dashboard API
|
||||
|
||||
**Created:** 2026-06-03
|
||||
**Controller:** `App\Controllers\EnrollmentCollectionV1DashboardController`
|
||||
**Model:** `App\Models\EnrollmentCollectionV1DashboardModel`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Enrollment Collection V1`
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Total KPIs:** 10
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
All Enrollment Collection V1 KPIs from **Enrollment Collection V1** are implemented as PHP model methods.
|
||||
SQL uses Metabase param `{{client_policy_id}}` → CodeIgniter bind `:client_policy_id:`.
|
||||
|
||||
| What | URL fragment | Use case |
|
||||
|------|-------------|----------|
|
||||
| **Single KPI** | `kpi/{slug or Metabase id}` | One card at a time |
|
||||
| **All KPIs** | `all` | Full enrollment dashboard (10 KPIs) |
|
||||
| **Debug / preview** | `debug` / `preview` | Admin / FE testing |
|
||||
|
||||
Query param: `client_policy` or `client_policy_id` (required for `kpi` / `all`).
|
||||
|
||||
---
|
||||
|
||||
## Route stacks
|
||||
|
||||
### 1. Admin — `authMVC`
|
||||
|
||||
Prefix: `util/enrollment-collection-v1`
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `util/enrollment-collection-v1/preview` | UI debug grid |
|
||||
| GET | `util/enrollment-collection-v1/preview/{policy_id}` | Same, policy in URL |
|
||||
| GET | `util/enrollment-collection-v1/debug` | Raw JSON (all KPIs) |
|
||||
| GET | `util/enrollment-collection-v1/debug/{policy_id}` | Same, policy in URL |
|
||||
| GET | `util/enrollment-collection-v1/all` | JSON — all 6 KPIs |
|
||||
| GET | `util/enrollment-collection-v1/kpi/{slug\|id}` | JSON — single KPI |
|
||||
|
||||
### 2. Frontend / HR App — `authJWT`
|
||||
|
||||
Prefix: `employeeRest/enrollment-collection-v1`
|
||||
|
||||
Same paths as above under `employeeRest/` (Bearer + `X-App-Signature`).
|
||||
|
||||
---
|
||||
|
||||
## How to call
|
||||
|
||||
> **Important:** Admin routes live under the **`util/`** prefix (same as `util/claims-collection-v2`).
|
||||
> `enrollment-collection-v1/preview` without `util/` will return **404**.
|
||||
|
||||
```
|
||||
GET /index.php/util/enrollment-collection-v1/preview?client_policy=4687
|
||||
GET /index.php/util/enrollment-collection-v1/all?client_policy=4687
|
||||
GET /index.php/util/enrollment-collection-v1/kpi/originally_enrolled?client_policy=4687
|
||||
GET /index.php/util/enrollment-collection-v1/kpi/117?client_policy=4687
|
||||
GET /index.php/util/enrollment-collection-v1/debug?client_policy=4687
|
||||
```
|
||||
|
||||
Full local example:
|
||||
|
||||
```
|
||||
https://localhost/PHP828APPS/ruc/nhance/index.php/util/enrollment-collection-v1/preview?client_policy=4687
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## All 10 KPIs
|
||||
|
||||
| Metabase ID | Method slug | Label | Rows |
|
||||
|-------------|-------------|-------|------|
|
||||
| 115 | `originally_enrolled` | Originally Enrolled | single |
|
||||
| 116 | `added_subsequently` | Added Subsequently | single |
|
||||
| 117 | `gender_split` | Gender Split | multi |
|
||||
| 119 | `enrollment_relationship` | Relationship | multi |
|
||||
| 122 | `average_age_by_enrollment_month` | Average age by Enrolment month | multi |
|
||||
| 123 | `original_base_premium` | Original Base Premium | single |
|
||||
| 124 | `overall_active` | Overall Active | single |
|
||||
| 125 | `enrollment_age_group` | Age Group | multi |
|
||||
| 126 | `additions_premium` | Additions Premium | single |
|
||||
| 127 | `net_premium` | Net Premium | single |
|
||||
|
||||
> Slug `enrollment_relationship` avoids clashing with Claims V2 `claim_amount_by_relationship` (ticket-level).
|
||||
|
||||
---
|
||||
|
||||
## Output columns
|
||||
|
||||
| Slug | Key row fields |
|
||||
|------|----------------|
|
||||
| `originally_enrolled` | `Originally Enrolled` |
|
||||
| `added_subsequently` | `Subsequent Additions` |
|
||||
| `gender_split` | `gender`, `member_count`, `percentage` |
|
||||
| `enrollment_relationship` | `relationship`, `member_count`, `percentage` |
|
||||
| `average_age_by_enrollment_month` | `enrollment_month`, `month_label`, `average_age`, `enrollments` |
|
||||
| `original_base_premium` | `opening_premium` |
|
||||
| `overall_active` | `Overall Active` |
|
||||
| `enrollment_age_group` | `age_group`, `member_count`, `percentage` |
|
||||
| `additions_premium` | `additions_premium` |
|
||||
| `net_premium` | `net_premium` |
|
||||
|
||||
---
|
||||
|
||||
## Sample `all` response (excerpt)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"data": {
|
||||
"originally_enrolled": {
|
||||
"id": 115,
|
||||
"label": "Originally Enrolled",
|
||||
"rows": [{ "Originally Enrolled": "320" }]
|
||||
},
|
||||
"gender_split": {
|
||||
"id": 117,
|
||||
"label": "Gender Split",
|
||||
"rows": [
|
||||
{ "gender": "Male", "member_count": "180", "percentage": "56.25" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/Models/EnrollmentCollectionV1DashboardModel.php` | KPI SQL, `KPI_MAP`, `KPI_LABELS` |
|
||||
| `app/Controllers/EnrollmentCollectionV1DashboardController.php` | `kpi()`, `all()`, `preview()`, `debug()` |
|
||||
| `app/Views/enrollment_collection_v1_dashboard.php` | Preview UI |
|
||||
| `app/Config/Routes.php` | `enrollment-collection-v1` groups (util + employeeRest) |
|
||||
| `tests/smoke_enrollment_collection_v1.php` | `php tests/smoke_enrollment_collection_v1.php 4687` |
|
||||
| `enrollment-dashboard.md` | This document |
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- Claims dashboard: `hr-dashboard.md` → `claims-collection-v2` (36 KPIs)
|
||||
|
||||
---
|
||||
|
||||
## BE notes
|
||||
|
||||
- Run `php tests/smoke_enrollment_collection_v1.php {policy_id}` after model changes.
|
||||
- Status filters in CSV use `'Active'` (capital A) for some KPIs; kept as in Metabase export.
|
||||
@ -407,3 +407,9 @@ X-App-Signature: <app_signature>
|
||||
- All queries use named binding `:policy_id:` (CodeIgniter style, replaces Metabase `{{policy_id}}`).
|
||||
- Literal `\t` / `\n` in CSV SQL is normalized in `runKpiQuery()` — safe to re-generate from CSV.
|
||||
- Run `php tests/smoke_claims_collection_v2.php {policy_id}` after any model change (expects `KPI_MAP` count === 36).
|
||||
|
||||
---
|
||||
|
||||
## Related dashboards
|
||||
|
||||
- **Enrollment Collection V1** (10 KPIs): see `enrollment-dashboard.md` — prefix `enrollment-collection-v1`
|
||||
|
||||
105
tests/smoke_enrollment_collection_v1.php
Normal file
105
tests/smoke_enrollment_collection_v1.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* Smoke test: Enrollment Collection V1 dashboard.
|
||||
* Run: php tests/smoke_enrollment_collection_v1.php [policy_id]
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Controllers\EnrollmentCollectionV1DashboardController;
|
||||
use App\Models\EnrollmentCollectionV1DashboardModel;
|
||||
use Config\Services;
|
||||
|
||||
ob_start();
|
||||
|
||||
define('FCPATH', __DIR__ . '/../public/');
|
||||
chdir(FCPATH);
|
||||
|
||||
require FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require rtrim($paths->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;
|
||||
}
|
||||
|
||||
helper('url');
|
||||
|
||||
$policyId = isset($argv[1]) ? (int) $argv[1] : 4687;
|
||||
$pass = 0;
|
||||
$fail = 0;
|
||||
$results = [];
|
||||
|
||||
function ok_enrollment(string $label, bool $cond, string $detail = ''): void
|
||||
{
|
||||
global $pass, $fail, $results;
|
||||
if ($cond) {
|
||||
$pass++;
|
||||
$results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
} else {
|
||||
$fail++;
|
||||
$results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
}
|
||||
}
|
||||
|
||||
$model = new EnrollmentCollectionV1DashboardModel();
|
||||
$kpiMap = EnrollmentCollectionV1DashboardModel::KPI_MAP;
|
||||
$expectedCount = 10;
|
||||
|
||||
ok_enrollment('KPI_MAP count', count($kpiMap) === $expectedCount, (string) count($kpiMap));
|
||||
|
||||
$uniqueMethods = array_unique(array_values($kpiMap));
|
||||
ok_enrollment('unique KPI method names', count($uniqueMethods) === $expectedCount, count($uniqueMethods) . ' methods');
|
||||
|
||||
ok_enrollment('id 115 maps to originally_enrolled', ($kpiMap[115] ?? '') === 'originally_enrolled');
|
||||
ok_enrollment('id 119 maps to enrollment_relationship', ($kpiMap[119] ?? '') === 'enrollment_relationship');
|
||||
ok_enrollment('id 124 maps to overall_active', ($kpiMap[124] ?? '') === 'overall_active');
|
||||
ok_enrollment('id 125 maps to enrollment_age_group', ($kpiMap[125] ?? '') === 'enrollment_age_group');
|
||||
ok_enrollment('id 126 maps to additions_premium', ($kpiMap[126] ?? '') === 'additions_premium');
|
||||
ok_enrollment('id 127 maps to net_premium', ($kpiMap[127] ?? '') === 'net_premium');
|
||||
|
||||
try {
|
||||
$rows = $model->originally_enrolled($policyId);
|
||||
ok_enrollment('model originally_enrolled', is_array($rows), 'rows=' . count($rows));
|
||||
} catch (Throwable $e) {
|
||||
ok_enrollment('model originally_enrolled', false, $e->getMessage());
|
||||
}
|
||||
|
||||
$request = Services::request(null, false);
|
||||
$response = Services::response();
|
||||
$request->setGlobal('get', ['client_policy' => (string) $policyId]);
|
||||
|
||||
$controller = new EnrollmentCollectionV1DashboardController();
|
||||
$controller->initController($request, $response, service('logger'));
|
||||
|
||||
$slugResp = json_decode($controller->kpi('gender_split')->getJSON(), true);
|
||||
ok_enrollment('controller kpi by slug', ($slugResp['status'] ?? false) === true && ($slugResp['kpi'] ?? '') === 'gender_split');
|
||||
|
||||
$idResp = json_decode($controller->kpi('115')->getJSON(), true);
|
||||
ok_enrollment('controller kpi by id 115', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 115);
|
||||
|
||||
$allResp = json_decode($controller->all()->getJSON(), true);
|
||||
ok_enrollment('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === $expectedCount);
|
||||
|
||||
$previewOut = $controller->preview($policyId);
|
||||
$previewHtml = is_string($previewOut) ? $previewOut : $previewOut->getBody();
|
||||
ok_enrollment('controller preview HTML', str_contains($previewHtml, 'Enrollment Collection V1') && str_contains($previewHtml, 'kpi-grid'));
|
||||
|
||||
$sparkRoutes = (string) shell_exec('php ' . escapeshellarg(ROOTPATH . 'spark') . ' routes 2>&1');
|
||||
ok_enrollment('spark lists util preview route', str_contains($sparkRoutes, 'util/enrollment-collection-v1/preview'));
|
||||
ok_enrollment('spark lists employeeRest preview route', str_contains($sparkRoutes, 'employeeRest/enrollment-collection-v1/preview'));
|
||||
|
||||
$previewUrl = rtrim((string) base_url(), '/') . '/util/enrollment-collection-v1/preview?client_policy=' . $policyId;
|
||||
|
||||
ob_end_clean();
|
||||
echo '=== Enrollment Collection V1 smoke test (policy_id=' . $policyId . ') ===' . PHP_EOL . PHP_EOL;
|
||||
echo implode(PHP_EOL, $results) . PHP_EOL;
|
||||
echo PHP_EOL . '=== Admin preview URL (note util/ prefix) ===' . PHP_EOL;
|
||||
echo $previewUrl . PHP_EOL;
|
||||
echo PHP_EOL . "=== Summary: {$pass} passed, {$fail} failed ===" . PHP_EOL;
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
Loading…
Reference in New Issue
Block a user