Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
1332841bcb
@ -824,6 +824,7 @@ $routes->group("/ticket", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('feedback-list','TicketController::feedbackList');
|
||||
$routes->match(['get', 'post'], 'claim-upload','TicketController::claimDumpUpload');
|
||||
$routes->get('remove','TicketController::removeTicket');
|
||||
$routes->get('tpa-claim-push-logs','TicketController::getTpaClaimPushLogs');
|
||||
$routes->get('new/(:any)','TicketController::ticket_form/$1');
|
||||
$routes->post('create','TicketController::createTicket');
|
||||
$routes->post('update','TicketController::updateTicket');
|
||||
|
||||
@ -50,7 +50,9 @@ class ApiServiceController extends BaseController
|
||||
// Push Claims
|
||||
public function pushClaims($claimId)
|
||||
{
|
||||
|
||||
helper('tpa_claim_push_log');
|
||||
init_tpa_claim_push_logs($claimId);
|
||||
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
->select('tm.id,tm.tpa_id ')->where('tm.id', $claimId)->get()->getRowArray(); // single record
|
||||
|
||||
@ -79,7 +81,7 @@ class ApiServiceController extends BaseController
|
||||
$voloApiController = new VoloApiController();
|
||||
return $voloApiController->SubmitClaim($claimId);
|
||||
}else{
|
||||
log_message('error', "This ticket id ( {$claimId} ) TPA has no API service enabled. TPA ID : {$tpaID}");
|
||||
tpa_claim_push_log($claimId, "This ticket id ( {$claimId} ) TPA has no API service enabled. TPA ID : {$tpaID}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ class FhplApiController extends BaseController
|
||||
|
||||
public function SubmitClaim($claimId = null) // 515
|
||||
{
|
||||
helper('api');
|
||||
helper(['api', 'tpa_claim_push_log']);
|
||||
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
->select('
|
||||
@ -99,7 +99,7 @@ class FhplApiController extends BaseController
|
||||
|
||||
|
||||
if (count($data) && $data['filePath'] == null) {
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - Claim or File Missing");
|
||||
tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - Claim or File Missing");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | Claim or File Missing'];
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ class FhplApiController extends BaseController
|
||||
$pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename;
|
||||
|
||||
if (!file_exists($pdfPath)) {
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - PDF not found on server");
|
||||
tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - PDF not found on server");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ class FhplApiController extends BaseController
|
||||
// Generate FHPL Token
|
||||
$tokenResponse = $this->generateAuthToken();
|
||||
if (empty($tokenResponse['data']['access_token'])) {
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - FHPL Token generation failed");
|
||||
tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - FHPL Token generation failed");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | FHPL Token generation failed'];
|
||||
}
|
||||
$token = $tokenResponse['data']['access_token'];
|
||||
@ -154,14 +154,14 @@ class FhplApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'FHPL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
log_message('error', 'FHPL - Claim Push RESPONSE | ' . json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push RESPONSE | ' . json_encode($response));
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'FHPL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -193,15 +193,15 @@ class FhplApiController extends BaseController
|
||||
->where('id', $file_id)
|
||||
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
||||
|
||||
log_message('error','HEALTH_INDIA - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
}else{
|
||||
log_message('error','HEALTH_INDIA - Claim Push | No file_id found to update claim_files table.');
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push | No file_id found to update claim_files table.');
|
||||
}
|
||||
|
||||
log_message('error', 'FHPL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$fhplClaimNo);
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$fhplClaimNo);
|
||||
return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
|
||||
}else {
|
||||
log_message('error', 'FHPL - Claim Push API SUCCESS BUT claimsInfo EMPTY | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'FHPL - Claim Push API SUCCESS BUT claimsInfo EMPTY | response: '.json_encode($response));
|
||||
return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT claimsInfo EMPTY'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,9 +84,9 @@ class HealthIndiaApiController extends BaseController
|
||||
|
||||
public function SubmitClaim($claimId = null)
|
||||
{
|
||||
helper('api');
|
||||
helper(['api', 'tpa_claim_push_log']);
|
||||
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push | Started for claimId: ' . $claimId);
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push | Started for claimId: ' . $claimId);
|
||||
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
->select('
|
||||
@ -121,12 +121,12 @@ class HealthIndiaApiController extends BaseController
|
||||
// dd($data);
|
||||
|
||||
if (!$data) {
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Claim not found");
|
||||
tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Claim not found");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | Claim not found'];
|
||||
}
|
||||
|
||||
if ($data['filePath'] == null) {
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - File Missing");
|
||||
tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - File Missing");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | File Missing'];
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ class HealthIndiaApiController extends BaseController
|
||||
$pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename;
|
||||
|
||||
if (!file_exists($pdfPath)) {
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}");
|
||||
tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ class HealthIndiaApiController extends BaseController
|
||||
// Generate Health India Token
|
||||
$tokenResponse = $this->generateAuthToken();
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Token generation failed");
|
||||
tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Token generation failed");
|
||||
return;
|
||||
}
|
||||
$token = $tokenResponse['data']['result'][0]['access_token'];
|
||||
@ -206,14 +206,14 @@ class HealthIndiaApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push | claimId: ' . $claimId . ' | URL: ' . $url );
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push | claimId: ' . $claimId . ' | URL: ' . $url);
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
|
||||
if ($response['status'] != true) {
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push FAILED | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push FAILED | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update(['tpa_push_response' => json_encode($response)]);
|
||||
@ -240,15 +240,15 @@ class HealthIndiaApiController extends BaseController
|
||||
->where('id', $file_id)
|
||||
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
||||
|
||||
log_message('error','HEALTH_INDIA - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
}else{
|
||||
log_message('error','HEALTH_INDIA - Claim Push | No file_id found to update claim_files table.');
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push | No file_id found to update claim_files table.');
|
||||
}
|
||||
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push SUCCESS | claimId: ' . $claimId . ' | CCN: ' . $ccn . ' | CCN_EXT: ' . $ccnExt);
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push SUCCESS | claimId: ' . $claimId . ' | CCN: ' . $ccn . ' | CCN_EXT: ' . $ccnExt);
|
||||
return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
|
||||
} else {
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push API SUCCESS BUT CCN EMPTY | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push API SUCCESS BUT CCN EMPTY | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT CCN EMPTY'];
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ class MediAssistApiController extends BaseController
|
||||
public function SubmitClaim ($claimId = null)
|
||||
{
|
||||
|
||||
helper('api');
|
||||
helper(['api', 'tpa_claim_push_log']);
|
||||
|
||||
// $url = ''. getenv('MEDI_ASSIST_API_BASE_URL').'/SubmitClaim';
|
||||
$url = getenv('MEDI_ASSIST_API_BASE_URL_CLAIMSUBMIT');
|
||||
@ -133,14 +133,14 @@ class MediAssistApiController extends BaseController
|
||||
// ]
|
||||
// ];
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
|
||||
if($response['status'] != true){
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -153,7 +153,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if(!empty($claimRef)){
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Push SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
|
||||
// Update claim reference number in ticket_master
|
||||
$this->db->table('ticket_master')
|
||||
@ -167,15 +167,15 @@ class MediAssistApiController extends BaseController
|
||||
->where('id', $file_id)
|
||||
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
}else{
|
||||
log_message('error','MEDI_ASSIST - Claim Push | No file_id found to update claim_files table.');
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push | No file_id found to update claim_files table.');
|
||||
}
|
||||
|
||||
return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
|
||||
|
||||
} else {
|
||||
log_message('error','MEDI_ASSIST - Claim Push API Failed | claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'MEDI_ASSIST - Claim Push API Failed | claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return ['status' => false, 'message' => 'Claim Push API Failed', 'response' => $response];
|
||||
}
|
||||
|
||||
|
||||
@ -2028,6 +2028,35 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
|
||||
ORDER BY l.created_at DESC
|
||||
", [$uid, $fyStart, $fyEnd])->getResultArray();
|
||||
|
||||
// ── Lost Leads for this user in FY (Opportunities tab — toggle off) ──
|
||||
$lossLeads = $db->query("
|
||||
SELECT
|
||||
l.id AS opportunities_id,
|
||||
l.actual_lead_id,
|
||||
COALESCE(l.lead_form_type, 1) AS lead_form_type_id,
|
||||
l.lead_type AS lead_type_id,
|
||||
sal.company_name AS company,
|
||||
l.client_name AS client_name,
|
||||
CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type,
|
||||
CASE
|
||||
WHEN l.lead_type = 1 THEN 'Fresh'
|
||||
WHEN l.lead_type = 2 THEN 'Renewal'
|
||||
WHEN l.lead_type = 3 THEN 'Roll Over'
|
||||
ELSE ''
|
||||
END AS lead_type,
|
||||
l.created_at AS created_at,
|
||||
l.status,
|
||||
l.lost_reason AS lost_reason
|
||||
FROM leads l
|
||||
INNER JOIN sales_actual_leads sal
|
||||
ON sal.lead_id = l.actual_lead_id
|
||||
WHERE l.status = 'lost'
|
||||
AND sal.assigned_to = ?
|
||||
AND l.created_at >= ?
|
||||
AND l.created_at <= ?
|
||||
ORDER BY l.created_at DESC
|
||||
", [$uid, $fyStart, $fyEnd])->getResultArray();
|
||||
|
||||
// -- NOTE: AND TRIM(sal.company_name) = TRIM(l.client_name)
|
||||
$result[$uid] = [
|
||||
'total_policies' => $totalPolicies,
|
||||
@ -2035,6 +2064,7 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
|
||||
'target_amt' => $targetAmt,
|
||||
'policies' => $policies,
|
||||
'won_leads' => $wonLeads, // for Table 2 in modal Tab 2
|
||||
'loss_leads' => $lossLeads,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -509,6 +509,7 @@ class TicketController extends BaseController
|
||||
'tm.is_head_approved',
|
||||
'tcs.claim_status AS status',
|
||||
'tm.claim_number AS claim_no',
|
||||
'tm.tpa_claim_push_reference_no',
|
||||
'tm.tpa_id',
|
||||
'tm.tpa_no',
|
||||
'tm.emp_name',
|
||||
@ -650,6 +651,7 @@ class TicketController extends BaseController
|
||||
'tm.ticket_type_id',
|
||||
'tcs.claim_status AS status',
|
||||
'tm.claim_number AS claim_no',
|
||||
'tm.tpa_claim_push_reference_no',
|
||||
'tm.claim_status_id',
|
||||
'tm.claim_created_by',
|
||||
'tm.is_head_approved',
|
||||
@ -2535,6 +2537,43 @@ class TicketController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
public function getTpaClaimPushLogs()
|
||||
{
|
||||
$ticketId = $this->request->getGet('ticket_id');
|
||||
|
||||
if (empty($ticketId)) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'ticket_id is required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$ticket = $this->ticketMasterModel
|
||||
->select('id, tpa_claim_push_logs')
|
||||
->where('id', $ticketId)
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
|
||||
if (!$ticket) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Claim not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$logs = trim((string) ($ticket['tpa_claim_push_logs'] ?? ''));
|
||||
|
||||
return $this->respond([
|
||||
'status' => true,
|
||||
'message' => 'TPA claim push logs fetched successfully',
|
||||
'data' => [
|
||||
'ticket_id' => (int) $ticketId,
|
||||
'logs' => $logs,
|
||||
'has_logs' => $logs !== '',
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
|
||||
//---- Email Trigger Part----------------------------------------------------------------------------------------------
|
||||
|
||||
public function constructMailContent($ticket_id, $status_id = null)
|
||||
|
||||
@ -41,13 +41,14 @@ class VidalApiController extends BaseController
|
||||
|
||||
|
||||
|
||||
function uploadFileToVidal($filePath,$filename)
|
||||
function uploadFileToVidal($filePath, $filename, $claimId = null)
|
||||
{
|
||||
helper('tpa_claim_push_log');
|
||||
// $apiUrl = "https://devapigw.vidalhealthtpa.com/partner-integration/api/files/upload-url";
|
||||
$apiUrl = getenv('VIDAL_API_BASE_URL').'/files/upload-url';
|
||||
$subscriptionKey = getenv('VIDAL_API_SUBSCRIPTION_KEY');
|
||||
|
||||
log_message('error', "VIDAL - Claim Push | Starting file upload process for filename: $filename | Path: $filePath");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Starting file upload process for filename: $filename | Path: $filePath");
|
||||
|
||||
// Step 1: Get signed URL from Vidal API
|
||||
// $filePath = '/opt/lampp/htdocs/nhance/writable/uploads/claim_files/1760013019_73d94af7b96ddc2e3d51.png';
|
||||
@ -57,7 +58,7 @@ class VidalApiController extends BaseController
|
||||
"Ocp-Apim-Subscription-Key: $subscriptionKey"
|
||||
];
|
||||
|
||||
log_message('error', "VIDAL - Claim Push | Requesting signed URL from Vidal API: $apiUrl | Payload: $payload");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Requesting signed URL from Vidal API: $apiUrl | Payload: $payload");
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@ -67,7 +68,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if (curl_errno($ch)) {
|
||||
log_message('error', "VIDAL - Claim Push Curl error while requesting signed URL: " . curl_error($ch));
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push Curl error while requesting signed URL: " . curl_error($ch));
|
||||
return ["status" => false, "message" => curl_error($ch)];
|
||||
}
|
||||
curl_close($ch);
|
||||
@ -75,14 +76,14 @@ class VidalApiController extends BaseController
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
if (!isset($responseData['data']['signedUrl']) || !isset($responseData['data']['fileId'])) {
|
||||
log_message('error', "VIDAL - Claim Push | Invalid signed URL response received: " . json_encode($responseData));
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Invalid signed URL response received: " . json_encode($responseData));
|
||||
return ["status" => false, "message" => "Invalid signed URL response", "response" => $responseData];
|
||||
}
|
||||
|
||||
$signedUrl = $responseData['data']['signedUrl'];
|
||||
$fileId = $responseData['data']['fileId'];
|
||||
|
||||
log_message('error', "VIDAL - Claim Push | Received signed URL & fileId. fileId: $fileId");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Received signed URL & fileId. fileId: $fileId");
|
||||
|
||||
// Step 2: Upload file to signed URL using PUT (Azure Blob)
|
||||
$fileSize = filesize($filePath);
|
||||
@ -107,12 +108,12 @@ class VidalApiController extends BaseController
|
||||
curl_close($ch2);
|
||||
|
||||
if ($curlErr) {
|
||||
log_message('error', "VIDAL - Claim Push | Curl error during file upload: $curlErr");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Curl error during file upload: $curlErr");
|
||||
return ["status" => false, "message" => "File upload failed", "data" => $curlErr];
|
||||
}
|
||||
|
||||
if ($httpCode !== 200 && $httpCode !== 201) {
|
||||
log_message('error', "VIDAL - Claim Push | File upload failed with HTTP Code: $httpCode | Response: $uploadResponse");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | File upload failed with HTTP Code: $httpCode | Response: $uploadResponse");
|
||||
return [
|
||||
"status" => false,
|
||||
"message" => "File upload failed",
|
||||
@ -133,7 +134,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
public function SubmitClaim ($claimId = null) //515
|
||||
{
|
||||
helper('api');
|
||||
helper(['api', 'tpa_claim_push_log']);
|
||||
|
||||
// Fetch the data from DB
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
@ -174,7 +175,7 @@ class VidalApiController extends BaseController
|
||||
->getRowArray(); // single record
|
||||
|
||||
if (count($data) && $data['filePath'] == null) {
|
||||
log_message('error', "VIDAL - Claim Push | Submit claim failed - Claim or File Missing");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Submit claim failed - Claim or File Missing");
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | Claim or File Missing'];
|
||||
}
|
||||
|
||||
@ -185,9 +186,9 @@ class VidalApiController extends BaseController
|
||||
// dd($data);
|
||||
// Upload file first
|
||||
|
||||
$upload = $this->uploadFileToVidal($filePath,$filename);
|
||||
$upload = $this->uploadFileToVidal($filePath, $filename, $claimId);
|
||||
if ($upload['status'] !== true) {
|
||||
log_message('error', "VIDAL - Claim Push | Submit claim failed - File upload failed");
|
||||
tpa_claim_push_log($claimId, "VIDAL - Claim Push | Submit claim failed - File upload failed");
|
||||
return $this->response->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'File upload failed',
|
||||
@ -247,7 +248,7 @@ class VidalApiController extends BaseController
|
||||
];
|
||||
// dd($body);
|
||||
|
||||
log_message('error', 'VIDAL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
// $body = [
|
||||
// 'policyNo' => "351500/D0534/PP/20-20/PC",
|
||||
@ -279,7 +280,7 @@ class VidalApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'VIDAL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -295,7 +296,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
if(!empty($claimNO) && !empty($claimInwardNO)){
|
||||
|
||||
log_message('error', 'VIDAL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$claimNO.' | claimInwardNO: '.$claimInwardNO);
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$claimNO.' | claimInwardNO: '.$claimInwardNO);
|
||||
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
@ -308,19 +309,19 @@ class VidalApiController extends BaseController
|
||||
->where('id', $tpa_sent_file_id)
|
||||
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Push | Updating claim_files table for file_id: '.$tpa_sent_file_id);
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push | Updating claim_files table for file_id: '.$tpa_sent_file_id);
|
||||
}else{
|
||||
log_message('error','MEDI_ASSIST - Claim Push | No file_id found to update claim_files table.');
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push | No file_id found to update claim_files table.');
|
||||
}
|
||||
|
||||
return ['status' => true, 'message' => 'Claim Push SUCCESS'];
|
||||
|
||||
} else {
|
||||
log_message('error', 'VIDAL - Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY'];
|
||||
}
|
||||
}else{
|
||||
log_message('error', 'VIDAL - Claim Push API FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'VIDAL - Claim Push API FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return ['status' => false, 'message' => 'Claim Push API FAILED'];
|
||||
}
|
||||
|
||||
|
||||
@ -682,7 +682,7 @@ class VoloApiController extends BaseController
|
||||
*/
|
||||
public function SubmitClaim($claimId = null)
|
||||
{
|
||||
helper('api');
|
||||
helper(['api', 'tpa_claim_push_log']);
|
||||
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
->select('
|
||||
@ -702,12 +702,12 @@ class VoloApiController extends BaseController
|
||||
->getRowArray();
|
||||
|
||||
if (!$data) {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | claim not found');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | claim not found');
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | Claim not found'];
|
||||
}
|
||||
|
||||
if (empty($data['filePath'])) {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | file missing');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | file missing');
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | File Missing'];
|
||||
}
|
||||
|
||||
@ -715,7 +715,7 @@ class VoloApiController extends BaseController
|
||||
$filename = basename($data['filePath']);
|
||||
$pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename;
|
||||
if (!is_readable($pdfPath)) {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | PDF not readable');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | PDF not readable');
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
|
||||
}
|
||||
|
||||
@ -723,13 +723,13 @@ class VoloApiController extends BaseController
|
||||
|
||||
$entityId = $this->resolveEntityId($data['policyNo'] ?? '');
|
||||
if ($entityId === null) {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | entity id not resolved');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | entity id not resolved');
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | entity id not resolved'];
|
||||
}
|
||||
|
||||
$hospitalId = getenv('VOLO_DEFAULT_HOSPITAL_ID');
|
||||
if ($hospitalId === false || $hospitalId === '') {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | VOLO_DEFAULT_HOSPITAL_ID not set');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | VOLO_DEFAULT_HOSPITAL_ID not set');
|
||||
return ['status' => false, 'message' => 'Claim Push FAILED | hospital id not configured'];
|
||||
}
|
||||
|
||||
@ -754,12 +754,12 @@ class VoloApiController extends BaseController
|
||||
'insurerPolicyNumber' => (string) ($data['policyNo'] ?? ''),
|
||||
];
|
||||
|
||||
log_message('error', 'VOLO - Claim Push | claimId: ' . $claimId . ' | payload: ' . json_encode($payload));
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push | claimId: ' . $claimId . ' | payload: ' . json_encode($payload));
|
||||
|
||||
$response = $this->intimateClaim($payload);
|
||||
|
||||
if (empty($response['status'])) {
|
||||
log_message('error', 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | ' . json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | ' . json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update(['tpa_push_response' => json_encode($response)]);
|
||||
@ -781,7 +781,7 @@ class VoloApiController extends BaseController
|
||||
'tpa_claim_id' => $ref,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
log_message('error', 'VOLO - Claim Push SUCCESS | claimId: ' . $claimId . ' | ref: ' . $ref);
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push SUCCESS | claimId: ' . $claimId . ' | ref: ' . $ref);
|
||||
|
||||
// Update claim files table that file is sent to tpa for this claim
|
||||
if(!empty($file_id)){
|
||||
@ -790,15 +790,15 @@ class VoloApiController extends BaseController
|
||||
->where('id', $file_id)
|
||||
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
||||
|
||||
log_message('error','VOLO - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
||||
}else{
|
||||
log_message('error','VOLO - Claim Push | No file_id found to update claim_files table.');
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push | No file_id found to update claim_files table.');
|
||||
}
|
||||
|
||||
return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
|
||||
}
|
||||
|
||||
log_message('error', 'VOLO - Claim Push empty reference | claimId: ' . $claimId . ' | ' . json_encode($response));
|
||||
tpa_claim_push_log($claimId, 'VOLO - Claim Push empty reference | claimId: ' . $claimId . ' | ' . json_encode($response));
|
||||
|
||||
return ['status' => false, 'message' => 'Claim Push API response missing reference', 'response' => $response];
|
||||
}
|
||||
|
||||
48
app/Helpers/tpa_claim_push_log_helper.php
Normal file
48
app/Helpers/tpa_claim_push_log_helper.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
if (!function_exists('init_tpa_claim_push_logs')) {
|
||||
/**
|
||||
* Reset claim push logs for a ticket before a new push attempt.
|
||||
*/
|
||||
function init_tpa_claim_push_logs($claimId): void
|
||||
{
|
||||
if ($claimId === null || $claimId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$started = date('Y-m-d H:i:s');
|
||||
\Config\Database::connect()
|
||||
->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update(['tpa_claim_push_logs' => "[{$started}] Claim push started\n"]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('tpa_claim_push_log')) {
|
||||
/**
|
||||
* Write to application log and append the same line to ticket_master.tpa_claim_push_logs.
|
||||
*/
|
||||
function tpa_claim_push_log($claimId, string $message, string $level = 'error'): void
|
||||
{
|
||||
log_message($level, $message);
|
||||
|
||||
if ($claimId === null || $claimId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$line = "[{$timestamp}] {$message}\n";
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$row = $db->table('ticket_master')
|
||||
->select('tpa_claim_push_logs')
|
||||
->where('id', $claimId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$existing = $row['tpa_claim_push_logs'] ?? '';
|
||||
$db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update(['tpa_claim_push_logs' => $existing . $line]);
|
||||
}
|
||||
}
|
||||
@ -46,6 +46,9 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
229 => 'cataract_avg_exceeded_amount',
|
||||
230 => 'hospital_city_wise_si_limit_cataract',
|
||||
231 => 'total_incurred_by_cliam_status',
|
||||
232 => 'inception_emp_lives',
|
||||
233 => 'current_emp_lives',
|
||||
234 => 'claim_value_by_month',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -83,6 +86,9 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
'cataract_avg_exceeded_amount' => 'Cataract avg exceeded amount',
|
||||
'hospital_city_wise_si_limit_cataract' => 'Hospital city wise SI Limit - Cataract',
|
||||
'total_incurred_by_cliam_status' => 'Total Incurred by Cliam Status ',
|
||||
'inception_emp_lives' => 'Inception Employees & Lives',
|
||||
'current_emp_lives' => 'Current Employees & Lives',
|
||||
'claim_value_by_month' => 'Claim Value by Month',
|
||||
];
|
||||
|
||||
protected function runKpiQuery(string $sql, int $policyId): array
|
||||
@ -2083,6 +2089,92 @@ WHERE tm.client_policy_id = :policy_id:
|
||||
AND tm.is_active = 1
|
||||
GROUP BY tcs.display_name, totals.total_count, totals.total_value
|
||||
-- ORDER BY claim_count DESC;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #232: Inception Employees & Lives */
|
||||
public function inception_emp_lives(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END) AS inception_emp,
|
||||
|
||||
COUNT(DISTINCT e.id) AS inception_lives
|
||||
|
||||
FROM employee_polices ep
|
||||
JOIN employees e ON e.id = ep.employee_id
|
||||
WHERE ep.client_policy_id = :policy_id:
|
||||
AND LOWER(e.change_event) LIKE '%inception%'
|
||||
AND ep.is_active = 1
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #233: Current Employees & Lives */
|
||||
public function current_emp_lives(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END) AS current_emp,
|
||||
|
||||
COUNT(DISTINCT e.id) AS current_lives,
|
||||
|
||||
ROUND(
|
||||
COUNT(DISTINCT e.id)
|
||||
/ NULLIF(COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END), 0)
|
||||
, 2) AS avg_family_size
|
||||
|
||||
FROM employee_polices ep
|
||||
JOIN employees e ON e.id = ep.employee_id
|
||||
WHERE ep.client_policy_id = :policy_id:
|
||||
AND ep.status = 'active'
|
||||
AND ep.is_active = 1
|
||||
AND e.emp_status = 'active'
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #234: Claim Value by Month */
|
||||
public function claim_value_by_month(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
DATE_FORMAT(tm.created_at, '%b %Y') AS claim_month,
|
||||
DATE_FORMAT(tm.created_at, '%Y-%m') AS month_sort,
|
||||
-- COUNT(tm.id) AS claim_count,
|
||||
-- CONCAT(ROUND(COUNT(tm.id) / totals.total_count * 100, 2), '%') AS count_pct,
|
||||
|
||||
COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0)
|
||||
AS claim_value
|
||||
-- CONCAT(ROUND(
|
||||
-- COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0)
|
||||
-- / NULLIF(totals.total_value, 0) * 100
|
||||
-- , 2), '%') AS value_pct
|
||||
FROM ticket_master tm
|
||||
JOIN (
|
||||
SELECT
|
||||
COUNT(id) AS total_count,
|
||||
COALESCE(SUM(CAST(NULLIF(claim_amount, '') AS DECIMAL(15,2))), 0) AS total_value
|
||||
FROM ticket_master
|
||||
WHERE client_policy_id = :policy_id: AND is_active = 1
|
||||
) totals ON 1=1
|
||||
WHERE tm.client_policy_id = :policy_id:
|
||||
AND tm.is_active = 1
|
||||
GROUP BY
|
||||
DATE_FORMAT(tm.created_at, '%b %Y'),
|
||||
DATE_FORMAT(tm.created_at, '%Y-%m'),
|
||||
totals.total_count,
|
||||
totals.total_value
|
||||
ORDER BY month_sort;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
@ -230,6 +230,17 @@
|
||||
.opp-card { background: #f8fafd; border-radius: 12px; padding: 16px; border: 1px solid #e4eaf5; text-align: center; }
|
||||
.opp-card-val { font-size: 22px; font-weight: 800; color: #0f172a; }
|
||||
.opp-card-lbl { font-size: 11px; color: #7c8db0; font-weight: 700; margin-top: 4px; letter-spacing: .04em; }
|
||||
.opp-section-head { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 12px; gap: 12px; flex-wrap: wrap; }
|
||||
.opp-section-head .split-section-title { margin: 0; }
|
||||
.opp-toggle-wrap { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
||||
.opp-toggle-lbl { font-size: 12px; font-weight: 700; color: #94a3b8; transition: color .2s; }
|
||||
.opp-toggle-lbl.active { color: #0f172a; }
|
||||
.opp-toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
|
||||
.opp-toggle-switch input { opacity: 0; width: 0; height: 0; }
|
||||
.opp-toggle-slider { position: absolute; cursor: pointer; inset: 0; background: #cbd5e1; border-radius: 99px; transition: background .2s; }
|
||||
.opp-toggle-slider::before { content: ""; position: absolute; height: 18px; width: 18px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: transform .2s; box-shadow: 0 1px 3px rgba(15,23,42,0.15); }
|
||||
.opp-toggle-switch input:checked + .opp-toggle-slider { background: #4f46e5; }
|
||||
.opp-toggle-switch input:checked + .opp-toggle-slider::before { transform: translateX(20px); }
|
||||
|
||||
@media(max-width:900px) { .list-col-head,.achieve-row { grid-template-columns: 220px 1fr 110px 110px 110px; } .col-lbl:nth-child(6),.col-lbl:nth-child(7),.achieve-row>*:nth-child(6),.achieve-row>*:nth-child(7) { display: none; } }
|
||||
@media(max-width:640px) { .list-col-head { display: none; } .achieve-row { grid-template-columns: 1fr auto; gap: 12px; padding: 14px 16px; } .achieve-row>*:not(:nth-child(1)):not(:nth-child(7)) { display: none; } .section-head { flex-direction: column; align-items: flex-start; gap: 12px; } .modal-summary { grid-template-columns: repeat(2,1fr); } .target-edit-panel { grid-template-columns: 1fr; } .opp-summary-cards { grid-template-columns: 1fr 1fr; } }
|
||||
@ -502,7 +513,9 @@ const TEAM = <?php echo json_encode($team_achievement ?? []); ?>;
|
||||
const OPP_DATA = <?php echo json_encode($opp_achievement ?? []); ?>;
|
||||
/* OPP_DATA shape per userId:
|
||||
{ total_policies, total_exp_amt, target_amt,
|
||||
policies: [{ policy_no, issue_date, amount, created_at }] } */
|
||||
policies: [{ policy_no, issue_date, amount, created_at }],
|
||||
won_leads: [...], loss_leads: [...] } */
|
||||
let currentModalOppUserId = null;
|
||||
|
||||
/* ── JS Helpers ── */
|
||||
const ACT_ICONS = {Call:'📞',Email:'✉️',Meeting:'📅',Visit:'🚗',Demo:'🖥️','Share Docs':'📄','To Do':'✓'};
|
||||
@ -545,6 +558,89 @@ function redirectToOpportunity(leadTypeId, actualLeadId, opportunityId) {
|
||||
+ encodeURIComponent(opportunityId);
|
||||
}
|
||||
|
||||
function escOppCell(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function updateOppTableHeader(showWon) {
|
||||
const headRow = document.getElementById('oppTableHead');
|
||||
if (!headRow) return;
|
||||
|
||||
if (showWon) {
|
||||
headRow.innerHTML =
|
||||
'<th>Company</th>'
|
||||
+ '<th>Opportunity Type</th>'
|
||||
+ '<th>Date</th>';
|
||||
} else {
|
||||
headRow.innerHTML =
|
||||
'<th>Company</th>'
|
||||
+ '<th>Opportunity Type</th>'
|
||||
+ '<th>Lost Reason</th>'
|
||||
+ '<th>Date</th>';
|
||||
}
|
||||
}
|
||||
|
||||
function buildOpportunityTableRows(leads, showWon) {
|
||||
return (leads || []).map(function(l) {
|
||||
const opportunityType = [
|
||||
l.lead_form_type || '',
|
||||
].filter(Boolean).join('').toUpperCase();
|
||||
const typeBadgeClass = l.lead_form_type === 'EB'
|
||||
? 'style="background:#eff6ff;color:#2563eb;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"'
|
||||
: 'style="background:#f0fdf4;color:#16a34a;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"';
|
||||
const canRedirect = l.lead_form_type_id && l.actual_lead_id && l.opportunities_id;
|
||||
const rowAttrs = canRedirect
|
||||
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
|
||||
+ JSON.stringify(String(l.lead_form_type_id)) + ','
|
||||
+ JSON.stringify(String(l.actual_lead_id)) + ','
|
||||
+ JSON.stringify(String(l.opportunities_id)) + ')\''
|
||||
: '';
|
||||
|
||||
const lostReasonCell = showWon ? '' :
|
||||
'<td style="font-size:12px;color:#64748b;max-width:220px;white-space:normal;">'
|
||||
+ escOppCell((l.lost_reason || '').trim() || '—')
|
||||
+ '</td>';
|
||||
|
||||
return '<tr' + rowAttrs + '>'
|
||||
+ '<td style="font-weight:700;color:#0f172a;">' + escOppCell(l.company || '—') + '</td>'
|
||||
+ '<td><span ' + typeBadgeClass + '>' + (opportunityType || '—') + '</span></td>'
|
||||
+ lostReasonCell
|
||||
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
|
||||
+ '</tr>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateOppToggleLabels(showWon) {
|
||||
const lostLbl = document.getElementById('oppToggleLblLost');
|
||||
const wonLbl = document.getElementById('oppToggleLblWon');
|
||||
if (lostLbl) lostLbl.classList.toggle('active', !showWon);
|
||||
if (wonLbl) wonLbl.classList.toggle('active', showWon);
|
||||
}
|
||||
|
||||
function renderOpportunitiesTable(userId, showWon) {
|
||||
const opp = OPP_DATA[userId];
|
||||
const tbody = document.getElementById('oppTableBody');
|
||||
if (!tbody || !opp) return;
|
||||
|
||||
const leads = showWon ? (opp.won_leads || []) : (opp.loss_leads || []);
|
||||
const colSpan = showWon ? 3 : 4;
|
||||
updateOppTableHeader(showWon);
|
||||
const rows = buildOpportunityTableRows(leads, showWon);
|
||||
const emptyMsg = showWon ? 'No won opportunities found' : 'No lost opportunities found';
|
||||
tbody.innerHTML = rows || '<tr><td colspan="' + colSpan + '" style="text-align:center;color:#aaa;padding:24px;">' + emptyMsg + '</td></tr>';
|
||||
updateOppToggleLabels(showWon);
|
||||
}
|
||||
|
||||
function onOppStatusToggle(checked) {
|
||||
if (currentModalOppUserId) {
|
||||
renderOpportunitiesTable(currentModalOppUserId, checked);
|
||||
}
|
||||
}
|
||||
|
||||
function goSalesPage(page, status, memberId) {
|
||||
const url = new URL(page === 'activities' ? SALES_ACTIVITIES_URL : SALES_LEADS_URL, window.location.origin);
|
||||
url.searchParams.set('fy', getSelectedDashboardFY());
|
||||
@ -939,48 +1035,33 @@ function openModal(id) {
|
||||
// + '</table>'
|
||||
// + '</div>';
|
||||
|
||||
/* ── Won Leads Table (Table 2) ── */
|
||||
const wonLeads = opp.won_leads || [];
|
||||
const wonRows = wonLeads.map(function(l) {
|
||||
// const opportunityType = [
|
||||
// l.lead_form_type || '',
|
||||
// l.lead_type || ''
|
||||
// ].filter(Boolean).join('/').toUpperCase();
|
||||
const opportunityType = [
|
||||
l.lead_form_type || '',
|
||||
].filter(Boolean).join('').toUpperCase();
|
||||
const typeBadgeClass = l.lead_form_type === 'EB'
|
||||
? 'style="background:#eff6ff;color:#2563eb;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"'
|
||||
: 'style="background:#f0fdf4;color:#16a34a;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"';
|
||||
const canRedirect = l.lead_form_type_id && l.actual_lead_id && l.opportunities_id;
|
||||
const rowAttrs = canRedirect
|
||||
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
|
||||
+ JSON.stringify(String(l.lead_form_type_id)) + ','
|
||||
+ JSON.stringify(String(l.actual_lead_id)) + ','
|
||||
+ JSON.stringify(String(l.opportunities_id)) + ')\''
|
||||
: '';
|
||||
|
||||
return '<tr' + rowAttrs + '>'
|
||||
+ '<td style="font-weight:700;color:#0f172a;">' + (l.company || '—') + '</td>'
|
||||
+ '<td><span ' + typeBadgeClass + '>' + (opportunityType || '—') + '</span></td>'
|
||||
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
|
||||
+ '</tr>';
|
||||
}).join('');
|
||||
currentModalOppUserId = id;
|
||||
|
||||
tab2Html +=
|
||||
'<div class="split-section-title" style="margin-top:24px;"> Opportunities </div>'
|
||||
'<div class="opp-section-head">'
|
||||
+ '<div class="split-section-title">Opportunities</div>'
|
||||
+ '<div class="opp-toggle-wrap">'
|
||||
+ '<span class="opp-toggle-lbl" id="oppToggleLblLost">Lost</span>'
|
||||
+ '<label class="opp-toggle-switch" title="Toggle between won and lost opportunities">'
|
||||
+ '<input type="checkbox" id="oppStatusToggle" checked onchange="onOppStatusToggle(this.checked)">'
|
||||
+ '<span class="opp-toggle-slider"></span>'
|
||||
+ '</label>'
|
||||
+ '<span class="opp-toggle-lbl active" id="oppToggleLblWon">Won</span>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div style="overflow-x:auto;">'
|
||||
+ '<table class="split-table">'
|
||||
+ '<thead><tr>'
|
||||
+ '<thead><tr id="oppTableHead">'
|
||||
+ '<th>Company</th>'
|
||||
+ '<th>Opportunity Type</th>'
|
||||
+ '<th>Date</th>'
|
||||
+ '</tr></thead>'
|
||||
+ '<tbody>' + (wonRows || '<tr><td colspan="3" style="text-align:center;color:#aaa;padding:24px;">No leads found</td></tr>') + '</tbody>'
|
||||
+ '<tbody id="oppTableBody"></tbody>'
|
||||
+ '</table>'
|
||||
+ '</div>';
|
||||
|
||||
} else {
|
||||
currentModalOppUserId = null;
|
||||
tab2Html = '<div class="empty-state"><div class="empty-icon">📋</div>No opportunities data available</div>';
|
||||
}
|
||||
|
||||
@ -996,6 +1077,14 @@ function openModal(id) {
|
||||
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
if (currentModalOppUserId && OPP_DATA[currentModalOppUserId]) {
|
||||
const toggle = document.getElementById('oppStatusToggle');
|
||||
if (toggle) toggle.checked = true;
|
||||
renderOpportunitiesTable(currentModalOppUserId, true);
|
||||
} else {
|
||||
currentModalOppUserId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(idx, btn) {
|
||||
@ -1007,6 +1096,7 @@ function switchTab(idx, btn) {
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
currentModalOppUserId = null;
|
||||
}
|
||||
|
||||
document.getElementById('modalOverlay').addEventListener('click', function(e) { if (e.target === e.currentTarget) closeModal(); });
|
||||
|
||||
@ -86,6 +86,13 @@ table.dataTable tbody td {
|
||||
color: #9d174d;
|
||||
}
|
||||
|
||||
.tpa-push-status-item {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: default;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.table th:nth-child(1),
|
||||
@ -328,6 +335,13 @@ table.dataTable tbody td {
|
||||
<a class="dropdown-item delete" data-id="<?= $row['id'];?>" onclick="removeClaim(this, '<?= htmlspecialchars($row['id'], ENT_QUOTES) ?>')">
|
||||
<i class="mdi mdi-delete mr-2 text-muted font-18 vertical-middle"></i>Delete
|
||||
</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="showTpaClaimPushLogs(event, '<?= htmlspecialchars($row['id'], ENT_QUOTES) ?>')">
|
||||
<i class="mdi mdi-text-box-search-outline mr-2 text-muted font-18 vertical-middle"></i>TPA Push Logs
|
||||
</a>
|
||||
<?php $tpaPushSuccess = !empty($row['tpa_claim_push_reference_no']); ?>
|
||||
<span class="dropdown-item-text tpa-push-status-item px-3 py-2 <?= $tpaPushSuccess ? 'text-success' : 'text-danger' ?>">
|
||||
TPA PUSH : <?= $tpaPushSuccess ? 'Success.' : 'Failed.' ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@ -346,8 +360,6 @@ table.dataTable tbody td {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-------------------------------------------------------------------------------------------------->
|
||||
|
||||
<script>
|
||||
@ -434,6 +446,14 @@ function getStatusTooltip(row) {
|
||||
return status;
|
||||
}
|
||||
|
||||
function getTpaPushStatusMenuItem(row) {
|
||||
var ref = row.tpa_claim_push_reference_no;
|
||||
var isSuccess = ref !== null && ref !== undefined && String(ref).trim() !== '';
|
||||
var statusClass = isSuccess ? 'text-success' : 'text-danger';
|
||||
var statusLabel = isSuccess ? 'TPA PUSH : Success.' : 'TPA PUSH : Failed.';
|
||||
return '<span class="dropdown-item-text tpa-push-status-item px-3 py-2 ' + statusClass + '">' + escapeHtml(statusLabel) + '</span>';
|
||||
}
|
||||
|
||||
function getActionCell(row) {
|
||||
if (!window.ticketTableIsDeleteEnabled) return '';
|
||||
var id = escapeHtml(row.id || '');
|
||||
@ -445,6 +465,10 @@ function getActionCell(row) {
|
||||
' <a class="dropdown-item delete" data-id="' + id + '" onclick="removeClaim(this, \'' + id + '\')">' +
|
||||
' <i class="mdi mdi-delete mr-2 text-muted font-18 vertical-middle"></i>Delete' +
|
||||
' </a>' +
|
||||
' <a class="dropdown-item" href="javascript:void(0);" onclick="showTpaClaimPushLogs(event, \'' + id + '\')">' +
|
||||
' <i class="mdi mdi-text-box-search-outline mr-2 text-muted font-18 vertical-middle"></i>TPA Push Logs' +
|
||||
' </a>' +
|
||||
getTpaPushStatusMenuItem(row) +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</td>';
|
||||
@ -649,6 +673,133 @@ function viewTicket(ticket_id){
|
||||
|
||||
}
|
||||
|
||||
function forceShowTpaClaimPushLogsModal() {
|
||||
var modalEl = document.getElementById('tpaClaimPushLogsModal');
|
||||
if (!modalEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modalEl.parentNode !== document.body) {
|
||||
document.body.appendChild(modalEl);
|
||||
}
|
||||
|
||||
modalEl.style.display = 'block';
|
||||
modalEl.classList.add('show', 'in');
|
||||
modalEl.removeAttribute('aria-hidden');
|
||||
modalEl.setAttribute('aria-modal', 'true');
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
if (!document.querySelector('.modal-backdrop.tpa-claim-push-logs-backdrop')) {
|
||||
var backdrop = document.createElement('div');
|
||||
backdrop.className = 'modal-backdrop fade show in tpa-claim-push-logs-backdrop';
|
||||
document.body.appendChild(backdrop);
|
||||
}
|
||||
}
|
||||
|
||||
function hideTpaClaimPushLogsModal() {
|
||||
var modalEl = document.getElementById('tpaClaimPushLogsModal');
|
||||
if (!modalEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof jQuery !== 'undefined' && jQuery.fn && typeof jQuery.fn.modal === 'function') {
|
||||
$('#tpaClaimPushLogsModal').modal('hide');
|
||||
}
|
||||
|
||||
modalEl.classList.remove('show', 'in');
|
||||
modalEl.style.display = 'none';
|
||||
modalEl.setAttribute('aria-hidden', 'true');
|
||||
modalEl.removeAttribute('aria-modal');
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.removeProperty('padding-right');
|
||||
$('.modal-backdrop').remove();
|
||||
}
|
||||
|
||||
function openTpaClaimPushLogsModal() {
|
||||
var $modal = $('#tpaClaimPushLogsModal');
|
||||
if (!$modal.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($modal.parent()[0] !== document.body) {
|
||||
$modal.appendTo('body');
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof jQuery !== 'undefined' && jQuery.fn && typeof jQuery.fn.modal === 'function') {
|
||||
$modal.off('shown.bs.modal.tpaLogs').on('shown.bs.modal.tpaLogs', function() {
|
||||
$('.modal-backdrop').last().addClass('tpa-claim-push-logs-backdrop');
|
||||
});
|
||||
$modal.modal('show');
|
||||
setTimeout(function() {
|
||||
if ((!$modal.hasClass('show') && !$modal.hasClass('in')) || !$modal.is(':visible')) {
|
||||
forceShowTpaClaimPushLogsModal();
|
||||
}
|
||||
}, 120);
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.bootstrap && bootstrap.Modal) {
|
||||
var modalEl = document.getElementById('tpaClaimPushLogsModal');
|
||||
if (modalEl && typeof bootstrap.Modal.getOrCreateInstance === 'function') {
|
||||
bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('TPA claim push logs modal open failed:', e);
|
||||
}
|
||||
|
||||
forceShowTpaClaimPushLogsModal();
|
||||
}
|
||||
|
||||
function showTpaClaimPushLogs(event, ticketId) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
var $content = $('#tpaClaimPushLogsContent');
|
||||
var $titleMeta = $('#tpaClaimPushLogsTicketId');
|
||||
|
||||
$titleMeta.text('Claim ID: ' + ticketId);
|
||||
$content.text('Loading...');
|
||||
openTpaClaimPushLogsModal();
|
||||
|
||||
var url = '<?= base_url('ticket/tpa-claim-push-logs') ?>';
|
||||
sendAjaxRequestForGlobal(url, 'GET', { ticket_id: ticketId }, function(response) {
|
||||
if (response.status === true && response.data) {
|
||||
if (response.data.has_logs) {
|
||||
$content.text(response.data.logs);
|
||||
} else {
|
||||
$content.text('No TPA claim push logs found for this claim.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
$content.text(response.message || 'Failed to load TPA claim push logs.');
|
||||
toastr.warning(response.message || 'Failed to load TPA claim push logs.', 'Warning');
|
||||
}, function(xhr) {
|
||||
var message = 'Failed to load TPA claim push logs.';
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
message = xhr.responseJSON.message;
|
||||
}
|
||||
$content.text(message);
|
||||
toastr.error(message, 'Error');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var $modal = $('#tpaClaimPushLogsModal');
|
||||
if ($modal.length && $modal.parent()[0] !== document.body) {
|
||||
$modal.appendTo('body');
|
||||
}
|
||||
|
||||
$(document).on('click', '#tpaClaimPushLogsModal [data-dismiss="modal"]', function(e) {
|
||||
e.preventDefault();
|
||||
hideTpaClaimPushLogsModal();
|
||||
});
|
||||
});
|
||||
|
||||
function removeClaim(input, ticket_id) {
|
||||
|
||||
confirmActionSweertAlert("Do you want to delete this Claim?", "Yes, Proceed!", "No, Cancel").then((confirmed) => {
|
||||
|
||||
@ -201,6 +201,59 @@
|
||||
<?php include('ticket_list.php'); ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#tpaClaimPushLogsModal.show,
|
||||
#tpaClaimPushLogsModal.in {
|
||||
display: block !important;
|
||||
z-index: 1055;
|
||||
}
|
||||
#tpaClaimPushLogsModal .modal-dialog {
|
||||
z-index: 1056;
|
||||
}
|
||||
#tpaClaimPushLogsModal .modal-content {
|
||||
background: #ffffff;
|
||||
border: 1px solid #c5cdd8;
|
||||
border-radius: 8px;
|
||||
box-shadow:
|
||||
0 12px 28px rgba(15, 23, 42, 0.18),
|
||||
0 4px 12px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
#tpaClaimPushLogsModal .modal-header {
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
#tpaClaimPushLogsModal .modal-footer {
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
.modal-backdrop.tpa-claim-push-logs-backdrop {
|
||||
background-color: rgba(15, 23, 42, 0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal fade" id="tpaClaimPushLogsModal" tabindex="-1" role="dialog" aria-labelledby="tpaClaimPushLogsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="tpaClaimPushLogsModalLabel">TPA Claim Push Logs</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-muted mb-2" id="tpaClaimPushLogsTicketId"></p>
|
||||
<pre id="tpaClaimPushLogsContent" class="mb-0 p-3 bg-light border rounded" style="white-space: pre-wrap; word-break: break-word; max-height: 60vh; overflow-y: auto; font-size: 12px;">Loading...</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- end page title -->
|
||||
|
||||
|
||||
205
hr-dashboard.md
205
hr-dashboard.md
@ -1,22 +1,25 @@
|
||||
# Claims Collection V2 — HR Dashboard API
|
||||
|
||||
**Created:** 2026-06-02
|
||||
**Updated:** 2026-06-03
|
||||
**Controller:** `App\Controllers\ClaimsCollectionV2DashboardController`
|
||||
**Model:** `App\Models\ClaimsCollectionV2DashboardModel`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2`
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2` (31 original) + 3 HR exposure/trend KPIs added in-model
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Total KPIs:** 34
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Each Metabase question in the CSV is a separate PHP method on the model.
|
||||
Each KPI is a separate PHP method on the model, registered in `KPI_MAP` (Metabase question id → slug) and `KPI_LABELS` (slug → display label).
|
||||
|
||||
The API exposes them in three ways:
|
||||
|
||||
| What | URL fragment | Use case |
|
||||
|------|-------------|----------|
|
||||
| **Single KPI** | `kpi/{slug or Metabase id}` | FE loads one card at a time |
|
||||
| **All KPIs** | `all` | FE loads entire dashboard in one call |
|
||||
| **All KPIs** | `all` | FE loads entire dashboard in one call (34 KPIs) |
|
||||
| **Debug / preview** | `debug` / `preview` | Admin checks raw output in browser |
|
||||
|
||||
All endpoints require `client_policy` or `client_policy_id` as a query param
|
||||
@ -36,7 +39,7 @@ Prefix: `util/claims-collection-v2`
|
||||
| GET | `util/claims-collection-v2/preview/{policy_id}` | `::preview` | Same, policy in URL |
|
||||
| GET | `util/claims-collection-v2/debug` | `::debug` | Raw JSON dump (all KPIs) |
|
||||
| GET | `util/claims-collection-v2/debug/{policy_id}` | `::debug` | Same, policy in URL |
|
||||
| GET | `util/claims-collection-v2/all` | `::all` | JSON — all 31 KPIs |
|
||||
| GET | `util/claims-collection-v2/all` | `::all` | JSON — all 34 KPIs |
|
||||
| GET | `util/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
|
||||
|
||||
**Filters applied:** `authMVC`, `AclFilter`, `HttpRequestLog`, `Cors`, `SecurityInputFilter`
|
||||
@ -49,7 +52,7 @@ Prefix: `employeeRest/claims-collection-v2`
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 31 KPIs |
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 34 KPIs |
|
||||
| GET | `employeeRest/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
|
||||
| GET | `employeeRest/claims-collection-v2/preview` | `::preview` | UI grid (FE debug) |
|
||||
| GET | `employeeRest/claims-collection-v2/preview/{policy_id}` | `::preview` | Same, policy in URL |
|
||||
@ -85,7 +88,7 @@ GET /index.php/util/claims-collection-v2/preview?client_policy=4687
|
||||
# Preview with policy in URL
|
||||
GET /index.php/util/claims-collection-v2/preview/4687
|
||||
|
||||
# Raw JSON — all 31 KPIs
|
||||
# Raw JSON — all 34 KPIs
|
||||
GET /index.php/util/claims-collection-v2/debug?client_policy=4687
|
||||
|
||||
# Raw JSON — single KPI by slug
|
||||
@ -93,6 +96,11 @@ GET /index.php/util/claims-collection-v2/kpi/incurred_ratio?client_policy=4687
|
||||
|
||||
# Raw JSON — single KPI by Metabase question id
|
||||
GET /index.php/util/claims-collection-v2/kpi/207?client_policy=4687
|
||||
|
||||
# Exposure KPIs (added 2026-06-03)
|
||||
GET /index.php/util/claims-collection-v2/kpi/inception_emp_lives?client_policy=4687
|
||||
GET /index.php/util/claims-collection-v2/kpi/current_emp_lives?client_policy=4687
|
||||
GET /index.php/util/claims-collection-v2/kpi/claim_value_by_month?client_policy=4687
|
||||
```
|
||||
|
||||
---
|
||||
@ -113,7 +121,13 @@ X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/207?client_policy=4687
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/inception_emp_lives?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/234?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
@ -122,7 +136,7 @@ X-App-Signature: <app_signature>
|
||||
|
||||
## Sample responses
|
||||
|
||||
### `kpi/{slug}` or `kpi/{id}`
|
||||
### `kpi/{slug}` — single-row KPI
|
||||
|
||||
```json
|
||||
{
|
||||
@ -137,6 +151,54 @@ X-App-Signature: <app_signature>
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/inception_emp_lives` — exposure (single row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 232,
|
||||
"kpi": "inception_emp_lives",
|
||||
"label": "Inception Employees & Lives",
|
||||
"rows": [
|
||||
{ "inception_emp": "150", "inception_lives": "420" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/current_emp_lives` — exposure (single row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 233,
|
||||
"kpi": "current_emp_lives",
|
||||
"label": "Current Employees & Lives",
|
||||
"rows": [
|
||||
{ "current_emp": "145", "current_lives": "410", "avg_family_size": "2.83" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/claim_value_by_month` — time series (multi-row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 234,
|
||||
"kpi": "claim_value_by_month",
|
||||
"label": "Claim Value by Month",
|
||||
"rows": [
|
||||
{ "claim_month": "Jan 2024", "month_sort": "2024-01", "claim_value": "125000.00" },
|
||||
{ "claim_month": "Feb 2024", "month_sort": "2024-02", "claim_value": "98000.50" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Sort charts by `month_sort` (ISO `YYYY-MM`), not `claim_month` (display label).
|
||||
|
||||
### `all`
|
||||
|
||||
```json
|
||||
@ -160,6 +222,23 @@ X-App-Signature: <app_signature>
|
||||
"id": 207,
|
||||
"label": "Incurred Ratio",
|
||||
"rows": [{ "incurred_ratio": "72.34%" }]
|
||||
},
|
||||
"inception_emp_lives": {
|
||||
"id": 232,
|
||||
"label": "Inception Employees & Lives",
|
||||
"rows": [{ "inception_emp": "150", "inception_lives": "420" }]
|
||||
},
|
||||
"current_emp_lives": {
|
||||
"id": 233,
|
||||
"label": "Current Employees & Lives",
|
||||
"rows": [{ "current_emp": "145", "current_lives": "410", "avg_family_size": "2.83" }]
|
||||
},
|
||||
"claim_value_by_month": {
|
||||
"id": 234,
|
||||
"label": "Claim Value by Month",
|
||||
"rows": [
|
||||
{ "claim_month": "Jan 2024", "month_sort": "2024-01", "claim_value": "125000.00" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,80 +262,102 @@ X-App-Signature: <app_signature>
|
||||
"message": "Unknown KPI. Pass Metabase id or method slug.",
|
||||
"allowed": {
|
||||
"181": "policy_exposure_summary",
|
||||
"207": "incurred_ratio"
|
||||
"207": "incurred_ratio",
|
||||
"232": "inception_emp_lives",
|
||||
"233": "current_emp_lives",
|
||||
"234": "claim_value_by_month"
|
||||
}
|
||||
}
|
||||
```
|
||||
**HTTP 404**
|
||||
**HTTP 404** — `allowed` lists the full `KPI_MAP` (34 entries).
|
||||
|
||||
---
|
||||
|
||||
## All 31 KPIs
|
||||
## All 34 KPIs
|
||||
|
||||
| Metabase ID | Method slug | Label |
|
||||
|-------------|-------------|-------|
|
||||
| 181 | `policy_exposure_summary` | POLICY & EXPOSURE SUMMARY |
|
||||
| 185 | `premium_as_on_date` | PREMIUM AS ON DATE |
|
||||
| 186 | `claims_experience_summary` | CLAIMS EXPERIENCE SUMMARY |
|
||||
| 190 | `claim_amount_by_gender` | Claim Amount by Gender |
|
||||
| 191 | `age_band` | Age Band |
|
||||
| 194 | `top_5_hospitals_by_incurred_amount` | Top 5 Hospitals by Incurred amount |
|
||||
| 197 | `claims_incidence_rate` | Claims Incidence Rate |
|
||||
| 198 | `policy_start_date` | Policy Start Date |
|
||||
| 199 | `policy_end_date` | Policy End Date |
|
||||
| 200 | `insurer` | Insurer |
|
||||
| 201 | `tpa` | TPA |
|
||||
| 203 | `earned_premium` | Earned Premium |
|
||||
| 204 | `total_claims` | Total Claims |
|
||||
| 206 | `incurred_amount` | Incurred Amount |
|
||||
| 207 | `incurred_ratio` | Incurred Ratio |
|
||||
| 208 | `projected_claims` | Projected Claims |
|
||||
| 209 | `projected_ratio` | Projected Ratio |
|
||||
| 213 | `total_reimbursement_amount` | Total Reimbursement Amount |
|
||||
| 214 | `total_reimbursement_amt_pct` | Total Reimbursement Amt % |
|
||||
| 215 | `cashless_claim_amt` | Cashless Claim Amt |
|
||||
| 216 | `cashless_claim_amt_pct` | Cashless Claim Amt % |
|
||||
| 217 | `total_incurred_by_city` | Total Incurred by city |
|
||||
| 219 | `claim_amount_by_claim_status` | Claim Amount by Claim Status |
|
||||
| 220 | `hospitals_in_detail` | Hospitals in detail |
|
||||
| 221 | `hospital_city_wise_si_limit_pregnancy` | Hospital city wise SI Limit - Pregnancy |
|
||||
| 224 | `s_pregnancy_normal_delivery_exceeded_amt` | S-PREGNANCY - NORMAL DELIVERY Exceeded Amt |
|
||||
| 225 | `s_pregnancy_c_sec_avg_exceeded_amt` | S-Pregnancy C-Sec avg exceeded amt |
|
||||
| 228 | `cataract_exceeded_claim_amount` | Cataract exceeded claim amount |
|
||||
| 229 | `cataract_avg_exceeded_amount` | Cataract avg exceeded amount |
|
||||
| 230 | `hospital_city_wise_si_limit_cataract` | Hospital city wise SI Limit - Cataract |
|
||||
| 231 | `total_incurred_by_cliam_status` | Total Incurred by Cliam Status |
|
||||
| Metabase ID | Method slug | Label | Rows |
|
||||
|-------------|-------------|-------|------|
|
||||
| 181 | `policy_exposure_summary` | POLICY & EXPOSURE SUMMARY | single |
|
||||
| 185 | `premium_as_on_date` | PREMIUM AS ON DATE | single |
|
||||
| 186 | `claims_experience_summary` | CLAIMS EXPERIENCE SUMMARY | single |
|
||||
| 190 | `claim_amount_by_gender` | Claim Amount by Gender | multi |
|
||||
| 191 | `age_band` | Age Band | multi |
|
||||
| 194 | `top_5_hospitals_by_incurred_amount` | Top 5 Hospitals by Incurred amount | multi |
|
||||
| 197 | `claims_incidence_rate` | Claims Incidence Rate | single |
|
||||
| 198 | `policy_start_date` | Policy Start Date | single |
|
||||
| 199 | `policy_end_date` | Policy End Date | single |
|
||||
| 200 | `insurer` | Insurer | single |
|
||||
| 201 | `tpa` | TPA | single |
|
||||
| 203 | `earned_premium` | Earned Premium | single |
|
||||
| 204 | `total_claims` | Total Claims | single |
|
||||
| 206 | `incurred_amount` | Incurred Amount | single |
|
||||
| 207 | `incurred_ratio` | Incurred Ratio | single |
|
||||
| 208 | `projected_claims` | Projected Claims | single |
|
||||
| 209 | `projected_ratio` | Projected Ratio | single |
|
||||
| 213 | `total_reimbursement_amount` | Total Reimbursement Amount | single |
|
||||
| 214 | `total_reimbursement_amt_pct` | Total Reimbursement Amt % | single |
|
||||
| 215 | `cashless_claim_amt` | Cashless Claim Amt | single |
|
||||
| 216 | `cashless_claim_amt_pct` | Cashless Claim Amt % | single |
|
||||
| 217 | `total_incurred_by_city` | Total Incurred by city | multi |
|
||||
| 219 | `claim_amount_by_claim_status` | Claim Amount by Claim Status | multi |
|
||||
| 220 | `hospitals_in_detail` | Hospitals in detail | multi |
|
||||
| 221 | `hospital_city_wise_si_limit_pregnancy` | Hospital city wise SI Limit - Pregnancy | multi |
|
||||
| 224 | `s_pregnancy_normal_delivery_exceeded_amt` | S-PREGNANCY - NORMAL DELIVERY Exceeded Amt | multi |
|
||||
| 225 | `s_pregnancy_c_sec_avg_exceeded_amt` | S-Pregnancy C-Sec avg exceeded amt | multi |
|
||||
| 228 | `cataract_exceeded_claim_amount` | Cataract exceeded claim amount | multi |
|
||||
| 229 | `cataract_avg_exceeded_amount` | Cataract avg exceeded amount | multi |
|
||||
| 230 | `hospital_city_wise_si_limit_cataract` | Hospital city wise SI Limit - Cataract | multi |
|
||||
| 231 | `total_incurred_by_cliam_status` | Total Incurred by Cliam Status | multi |
|
||||
| 232 | `inception_emp_lives` | Inception Employees & Lives | single |
|
||||
| 233 | `current_emp_lives` | Current Employees & Lives | single |
|
||||
| 234 | `claim_value_by_month` | Claim Value by Month | multi |
|
||||
|
||||
> **Note:** KPIs 214 and 216 were renamed from the auto-generated slug to avoid collision with 213 and 215.
|
||||
|
||||
> **Note:** IDs **232–234** were assigned in-app for KPIs added outside the original Metabase CSV export. Confirm real Metabase question IDs and update `KPI_MAP` if they differ.
|
||||
|
||||
---
|
||||
|
||||
## Output columns — KPIs 232–234
|
||||
|
||||
| Slug | Row fields | Description |
|
||||
|------|------------|-------------|
|
||||
| `inception_emp_lives` | `inception_emp`, `inception_lives` | Distinct Self employees and all lives at inception (`change_event` contains inception) |
|
||||
| `current_emp_lives` | `current_emp`, `current_lives`, `avg_family_size` | Active employees/lives; `avg_family_size` = lives ÷ employees (2 dp) |
|
||||
| `claim_value_by_month` | `claim_month`, `month_sort`, `claim_value` | Monthly sum of `claim_amount`; ordered by `month_sort` |
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 31 KPI query methods, `KPI_MAP`, `KPI_LABELS`, `getAllKpis()`, `getKpi()` |
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 34 KPI query methods, `KPI_MAP`, `KPI_LABELS`, `getAllKpis()`, `getKpi()` |
|
||||
| `app/Controllers/ClaimsCollectionV2DashboardController.php` | `kpi()`, `all()`, `preview()`, `debug()` |
|
||||
| `app/Views/claims_collection_v2_dashboard.php` | Admin/FE debug preview UI (KPI card grid) |
|
||||
| `app/Config/Routes.php` | Both route groups (search `claims-collection-v2`) |
|
||||
| `tests/smoke_claims_collection_v2.php` | CLI smoke test — run: `php tests/smoke_claims_collection_v2.php 4687` |
|
||||
| `metabase_raw_queries.csv` | Source of truth for all SQL queries |
|
||||
| `metabase_raw_queries.csv` | Source of truth for original Metabase SQL queries |
|
||||
| `hr-dashboard.md` | This document |
|
||||
|
||||
---
|
||||
|
||||
## FE integration notes
|
||||
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card.
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card (34 keys under `data`).
|
||||
- Call `kpi/{slug}` for lazy/on-demand loading of individual cards.
|
||||
- `policy_id` should come from the HR session / selected policy context — never hardcoded.
|
||||
- All rows are raw arrays; formatting (currency, %, dates) is already applied inside the SQL (`FORMAT()`, `CONCAT()`).
|
||||
- `rows` may be empty `[]` if no claims exist for that policy — handle gracefully in UI.
|
||||
- All rows are raw arrays; formatting (currency, %, dates) is already applied inside the SQL where applicable (`FORMAT()`, `CONCAT()`, `DATE_FORMAT()`).
|
||||
- `rows` may be empty `[]` if no data exists for that policy — handle gracefully in UI.
|
||||
- **Single-row KPIs** (e.g. `incurred_ratio`, `inception_emp_lives`): use `rows[0]`.
|
||||
- **Multi-row KPIs** (e.g. `claim_value_by_month`, `age_band`): iterate `rows`; for time series use `month_sort` for sort order.
|
||||
- **Exposure block:** `inception_emp_lives` + `current_emp_lives` pair for inception vs current headcount.
|
||||
|
||||
---
|
||||
|
||||
## BE notes
|
||||
|
||||
- To add a new KPI: add an entry to `KPI_MAP` + `KPI_LABELS` in the model and write the corresponding method `public function my_kpi(int $policyId): array`.
|
||||
- To add a new KPI: add an entry to `KPI_MAP` + `KPI_LABELS` in the model, write `public function my_kpi(int $policyId): array`, update this doc and bump the smoke test KPI count.
|
||||
- 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.
|
||||
- Run `php tests/smoke_claims_collection_v2.php {policy_id}` after any model change (expects `KPI_MAP` count === 34).
|
||||
|
||||
@ -51,10 +51,10 @@ function ok(string $label, bool $cond, string $detail = ''): void
|
||||
$model = new ClaimsCollectionV2DashboardModel();
|
||||
$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP;
|
||||
|
||||
ok('KPI_MAP count', count($kpiMap) === 31, (string) count($kpiMap));
|
||||
ok('KPI_MAP count', count($kpiMap) === 34, (string) count($kpiMap));
|
||||
|
||||
$uniqueMethods = array_unique(array_values($kpiMap));
|
||||
ok('unique KPI method names', count($uniqueMethods) === 31, count($uniqueMethods) . ' methods');
|
||||
ok('unique KPI method names', count($uniqueMethods) === 34, count($uniqueMethods) . ' methods');
|
||||
|
||||
foreach (['policy_exposure_summary', 'incurred_ratio'] as $slug) {
|
||||
ok("slug map contains {$slug}", in_array($slug, $kpiMap, true));
|
||||
@ -93,7 +93,7 @@ $badResp = json_decode($controller->kpi('not_a_kpi')->getJSON(), true);
|
||||
ok('controller unknown kpi 404', ($badResp['status'] ?? true) === false);
|
||||
|
||||
$allResp = json_decode($controller->all()->getJSON(), true);
|
||||
ok('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === 31);
|
||||
ok('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === 34);
|
||||
|
||||
$debugOut = $controller->debug($policyId);
|
||||
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user