Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev

This commit is contained in:
sanjeev.p 2026-06-03 17:42:19 +05:30
commit 1bdce5cca8
49 changed files with 1053708 additions and 224 deletions

View File

@ -44,7 +44,7 @@ class Acl
'#^/sendDataToTPA#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID, MANAGER_ROLE_ID]],
'#^/swagger#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID, MANAGER_ROLE_ID]],
'#^/viewClaimFile#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID, MANAGER_ROLE_ID, STAFF_ROLE_ID]],
'#^/employee/tpaReportsDashboard#' => ['roles' => [ACCOUNT_MANAGER_ROLE_ID]],

View File

@ -35,6 +35,7 @@ $routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authM
$routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']);
$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');
$routes->get('/metaDashboardDemo', 'TestingController::metaDashboardDemo');
$routes->get('/apacheSuperSetDemo', 'TestingController::apacheSuperSetDemo');
$routes->get('/metaTpaDashboardDemo', 'TestingController::metaTpaDashboardDemo');
// Reminder Mail Notification
@ -137,6 +138,7 @@ $routes->group("/client", ["filter" => "authMVC"], function ($routes) {
$routes->get('view_deposit/(:num)', 'ClientController::view_Deposit/$1');
$routes->get('createtransaction', 'ClientController::createtransaction');
$routes->post('save_deposit', 'ClientController::saveDeposit');
$routes->post('update_deposit_amount', 'ClientController::updateDepositAmount');
$routes->post("saveApiData", "ClientController::saveApiData");
$routes->get("generateToken","ClientController::sendToken");
// $routes->get('view_Deposit/(:num)/(:num)','ClientController/view_Deposit/$1/$2');
@ -240,7 +242,7 @@ $routes->group("/employee", ["filter" => "authMVC"], function ($routes) {
$routes->get("bulkGenerateEcardAndStoreinS3", "EmployeeController::bulkGenerateEcardAndStoreinS3");
$routes->get('clearCdSession', 'EmployeeController::clearCdSession');
$routes->get('checkSessionStatus', 'EmployeeController::checkSessionStatus');
$routes->get("tpaReportsDashboard", "EmployeeController::tpaReportsDashboard");
});
@ -460,6 +462,15 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
$routes->get('insertSampleTpaApiData/(:any)', 'TestingController::insertSampleTpaApiData/$1');
$routes->get('listEmployeeCountByClientPolicy', 'TestingController::listEmployeeCountByClientPolicy');
$routes->get('testMediAssistWellness','TestingController::testMediAssistWellness');
$routes->group('claims-collection-v2', static function ($routes) {
$routes->get('preview', 'ClaimsCollectionV2DashboardController::preview');
$routes->get('preview/(:num)', 'ClaimsCollectionV2DashboardController::preview/$1');
$routes->get('kpi/(:segment)', 'ClaimsCollectionV2DashboardController::kpi/$1');
$routes->get('all', 'ClaimsCollectionV2DashboardController::all');
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
});
});
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
@ -767,6 +778,15 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
$routes->get("downloadPolicyFiles", "EmployeeRestController::downloadPolicyFiles");
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");
$routes->get("downloadSampleExcel/(:any)", "EmployeeController::downloadSampleExcelFile/$1");
$routes->group('claims-collection-v2', static function ($routes) {
$routes->get('preview', 'ClaimsCollectionV2DashboardController::preview');
$routes->get('preview/(:num)', 'ClaimsCollectionV2DashboardController::preview/$1');
$routes->get('kpi/(:segment)', 'ClaimsCollectionV2DashboardController::kpi/$1');
$routes->get('all', 'ClaimsCollectionV2DashboardController::all');
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
});
});
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");
@ -804,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');

View File

@ -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}");
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace App\Controllers;
use App\Models\ClaimsCollectionV2DashboardModel;
use CodeIgniter\API\ResponseTrait;
/**
* Claims Collection V2 KPI API (Metabase SQL as model methods).
*/
class ClaimsCollectionV2DashboardController extends BaseController
{
use ResponseTrait;
/**
* Resolve policy id from request; optional $fallback (e.g. route default for debug only).
*/
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;
}
/**
* Resolve KPI by Metabase numeric id or method slug.
*/
protected function resolveKpiMethod(string $kpiKey): ?string
{
$kpiKey = trim($kpiKey);
if ($kpiKey === '') {
return null;
}
if (in_array($kpiKey, ClaimsCollectionV2DashboardModel::KPI_MAP, true)) {
return $kpiKey;
}
if (ctype_digit($kpiKey)) {
$id = (int) $kpiKey;
return ClaimsCollectionV2DashboardModel::KPI_MAP[$id] ?? null;
}
return null;
}
/**
* JSON: single KPI by slug (e.g. incurred_ratio) or Metabase id (e.g. 207).
* Requires client_policy or client_policy_id.
*/
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' => ClaimsCollectionV2DashboardModel::KPI_MAP,
], 404);
}
$model = new ClaimsCollectionV2DashboardModel();
$metabaseId = array_search($kpiMethod, ClaimsCollectionV2DashboardModel::KPI_MAP, true);
return $this->respond([
'status' => true,
'policy_id' => $policyId,
'kpi_id' => $metabaseId !== false ? (int) $metabaseId : null,
'kpi' => $kpiMethod,
'label' => ClaimsCollectionV2DashboardModel::KPI_LABELS[$kpiMethod] ?? $kpiMethod,
'rows' => $model->getKpi($kpiMethod, $policyId),
]);
}
/**
* JSON: all KPIs. Requires client_policy or client_policy_id.
*/
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 ClaimsCollectionV2DashboardModel();
return $this->respond([
'status' => true,
'policy_id' => $policyId,
'data' => $model->getAllKpis($policyId),
]);
}
/**
* Admin preview UI (authMVC only) KPI grid for manual testing.
* Default policy id only in method signature; override via query or /preview/{id}.
*/
public function preview(int $policyId = 4687)
{
$policyId = $this->resolvePolicyId($policyId);
$path = $this->request->getUri()->getPath();
$isJwt = stripos($path, 'employeeRest') !== false;
$prefix = $isJwt ? 'employeeRest/claims-collection-v2' : 'util/claims-collection-v2';
return view('claims_collection_v2_dashboard', [
'policy_id' => $policyId,
'kpi_map' => ClaimsCollectionV2DashboardModel::KPI_MAP,
'kpi_labels' => ClaimsCollectionV2DashboardModel::KPI_LABELS,
'api_all_url' => base_url($prefix . '/all'),
'api_kpi_url' => base_url($prefix . '/kpi'),
]);
}
/**
* Admin check only: raw JSON on screen (no dashboard UI).
* Default policy id only here; override via ?client_policy= or /debug/{id}.
*/
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 ClaimsCollectionV2DashboardModel();
$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);
}
}

View File

@ -902,6 +902,109 @@ class ClientController extends AdminController
return $this->response->setJSON(['success' => $response['success']]);
}
public function updateDepositAmount()
{
$rules = [
'deposit_id' => 'required|is_natural_no_zero',
'amount' => 'required|numeric|greater_than_equal_to[0]',
'transaction_type' => 'required|in_list[Credit,Debit]',
'client_id' => 'required',
'insurer_id' => 'required|is_natural_no_zero',
'cd_ac_pk' => 'required|is_natural_no_zero',
];
if (! $this->validate($rules)) {
return $this->response
->setStatusCode(400)
->setJSON([
'status' => 'error',
'message' => 'Input validation failed',
'errors' => $this->validator->getErrors(),
]);
}
$postData = sanitizeInputArrayAdvanced($this->request->getPost());
$depositId = (int) ($postData['deposit_id'] ?? 0);
$clientId = $postData['client_id'] ?? null;
$insurerId = (int) ($postData['insurer_id'] ?? 0);
$cdAcPk = (int) ($postData['cd_ac_pk'] ?? 0);
$amount = (float) ($postData['amount'] ?? 0);
$transactionType = $postData['transaction_type'] ?? 'Credit';
$loggedInUserID = get_session_userid();
$depositModel = new ClientDepositModel();
$targetRow = $depositModel
->where('id', $depositId)
->where('client_id', $clientId)
->where('insurer_id', $insurerId)
->where('cd_ac_pk', $cdAcPk)
->where('is_active', 1)
->first();
if (empty($targetRow)) {
return $this->response
->setStatusCode(404)
->setJSON([
'status' => 'error',
'message' => 'Transaction not found for this account.',
]);
}
$existingType = $targetRow['transaction_type'] ?? '';
if ($existingType !== $transactionType) {
return $this->response
->setStatusCode(400)
->setJSON([
'status' => 'error',
'message' => 'Only ' . $existingType . ' value can be edited for this transaction.',
]);
}
$db = \Config\Database::connect();
$db->transBegin();
$depositModel->update($depositId, [
'amount' => $amount,
'transaction_type' => $transactionType,
'updated_by' => $loggedInUserID,
]);
$rows = $depositModel
->where('client_id', $clientId)
->where('insurer_id', $insurerId)
->where('cd_ac_pk', $cdAcPk)
->where('is_active', 1)
->orderBy('id', 'ASC')
->findAll();
$runningBalance = 0.0;
foreach ($rows as $row) {
$rowAmount = (float) ($row['amount'] ?? 0);
$runningBalance += (($row['transaction_type'] ?? '') === 'Credit') ? $rowAmount : -$rowAmount;
$depositModel->update((int) $row['id'], [
'balance' => $runningBalance,
'updated_by' => $loggedInUserID,
]);
}
if ($db->transStatus() === false) {
$db->transRollback();
return $this->response
->setStatusCode(500)
->setJSON([
'status' => 'error',
'message' => 'Failed to update transaction.',
]);
}
$db->transCommit();
return $this->response->setJSON([
'status' => 'success',
'message' => 'Transaction updated successfully.',
]);
}
public function editClientOnboarding($id = null)
{
@ -6652,7 +6755,7 @@ class ClientController extends AdminController
// Check in Enrollment (client_policy)
$client_policy_data = $this->clientPolicyModel
->where('is_active', 1)
->where('policy_status', 1)
// ->where('policy_status', 1)
->where('TRIM(policy_no)', $policy_no)
->first();

View File

@ -55,6 +55,8 @@ use Dompdf\Dompdf;
use Dompdf\Options;
use Kint;
use Firebase\JWT\JWT;
class EmployeeController extends AdminController
{
@ -7339,4 +7341,75 @@ class EmployeeController extends AdminController
}
}
public function tpaReportsDashboard()
{
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$policy_id = $this->request->getGet('client_policy') ?? null;
if ($this->request->getGet('api') != 1) {
$data = [
'tab_name' => 'TPA Reports',
'page_name' => 'TPA Reports',
'metabaseUrl' => 'https://nsights.nhanceindia.in',
'dashboardApiUrl' => base_url('/employee/tpaReportsDashboard'),
];
return $this->loadLayout('meta_dashboard_demo_one', $data);
}
if (empty($policy_id)) {
return $this->respond([
'status' => 'failed',
'message' => 'Client policy is required.',
'data' => [],
]);
}
$client_policy_data = $this->clientPolicyModel
->select('tpa.dashboard_id')
->join('tpa', 'client_policy.tpa_id = tpa.id')
->where('client_policy.id', $policy_id)
->first();
$database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null;
if (empty($database_id)) {
if ($this->request->getGet('api') == 1) {
return $this->respond([
'status' => 'failed',
'message' => 'There is no dashboard for this TPA.',
'data' => []
]);
}
return view('errors/404', [
'message' => 'There is no dashboard for this TPA.'
]);
}
$payload = [
'resource' => [
'dashboard' => $database_id
],
'exp' => time() + (10 * 60), // 10 minutes
'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase
];
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
// dd($token);
return $this->respond([
'status' => 'success',
'message' => 'Form data received successfully!',
'data' => [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
],
]);
}
}

View File

@ -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'];
}
}

View File

@ -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'];
}

View File

@ -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];
}

View File

@ -365,7 +365,7 @@ class TestingController extends BaseController
$insurerBranchModel = new InsurerBranchModel();
$insurer_branch = $insurerBranchModel->getInsurerBranchesWithInsurerNames();
$dataArray = $data['pt_co_share_details']; // Example
$dataArray = $data['pt_co_share_details'] ?? []; // Example
$cd_ac_pk = 'CD12345';
$role_id = 1;
$team_id = ['6'];
@ -1099,6 +1099,64 @@ class TestingController extends BaseController
public function metaDashboardDemo()
{
// 🔐 Move this to .env in real projects
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$database_id = (int) ($this->request->getGet('database_id') ?? 2);
$policy_id = $this->request->getGet('client_policy') ?? null;
$payload = [
'resource' => [
// 'dashboard' => 1
'dashboard' => $database_id
],
'exp' => time() + (10 * 60), // 10 minutes
];
if(!empty($policy_id)){
$payload['params'] = (object)['client_policy' => $policy_id]; // MUST be object for Metabase
}else{
$payload['params'] = (object)[]; // MUST be object for Metabase
}
// dd($payload);
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
// // You can either return token only
// return $this->response->setJSON([
// 'token' => $token,
// 'iframe_url' => "https://your-metabase-domain/embed/dashboard/{$token}#bordered=true&titled=true"
// ]);
if ($this->request->getGet('api') == 1) {
if (empty($policy_id)) {
return $this->respond([
'status' => 'failed',
'message' => 'Client policy is required.',
'data' => [],
]);
}
return $this->respond([
'status' => 'success',
'message' => 'Dashboard token generated.',
'data' => [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
],
]);
}
$data = [
'tab_name' => 'Metabase Dashboard',
'page_name' => 'Metabase Dashboard',
'metabaseUrl' => 'https://nsights.nhanceindia.in',
'dashboardApiUrl' => base_url('/metaDashboardDemo'),
];
return $this->loadLayout('meta_dashboard_demo_one', $data);
}
public function apacheSuperSetDemo()
{
// echo 'Dta';die;
// 🔐 Move this to .env in real projects
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$database_id = (int)$this->request->getGet('database_id') ?? 2;
@ -1108,7 +1166,7 @@ class TestingController extends BaseController
$payload = [
'resource' => [
// 'dashboard' => 1
'dashboard' => $database_id
'dashboard' => 2
],
'exp' => time() + (10 * 60), // 10 minutes
];
@ -1138,16 +1196,11 @@ class TestingController extends BaseController
]);
}
return view('meta_dashboard_demo_one', [
return view('apache_dashboard_demo_one', [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
]);
}
public function testingquerys1(){
}
public function testingquerys()
{
$calendar = new \App\Libraries\GoogleCalendarService();
@ -1175,7 +1228,26 @@ class TestingController extends BaseController
public function metaTpaDashboardDemo()
{
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$policy_id = $this->request->getGet('client_policy') ?? 4687;
$policy_id = $this->request->getGet('client_policy') ?? null;
if ($this->request->getGet('api') != 1) {
$data = [
'tab_name' => 'TPA Reports',
'page_name' => 'TPA Reports',
'metabaseUrl' => 'https://nsights.nhanceindia.in',
'dashboardApiUrl' => base_url('/metaTpaDashboardDemo'),
];
return $this->loadLayout('meta_dashboard_demo_one', $data);
}
if (empty($policy_id)) {
return $this->respond([
'status' => 'failed',
'message' => 'Client policy is required.',
'data' => [],
]);
}
$client_policy_data = $this->ClientPolicyModel
->select('tpa.dashboard_id')
@ -1212,20 +1284,13 @@ class TestingController extends BaseController
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
// dd($token);
if ($this->request->getGet('api') == 1) {
return $this->respond([
'status' => 'success',
'message' => 'Form data received successfully!',
'data' => [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in'
]
]);
}
return view('meta_dashboard_demo_one', [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
return $this->respond([
'status' => 'success',
'message' => 'Form data received successfully!',
'data' => [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
],
]);
}

View File

@ -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)

View File

@ -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'];
}

View File

@ -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];
}

View File

@ -183,12 +183,24 @@ use App\Models\JobModel;
class MailHelper
{
private const SYSTEM_EMAIL_FOOTER = '<div style="
text-align:center;
font-size:9px;
color:#999999;
border-top:1px solid #eeeeee;
margin-top:2px;
padding-top:1px;
line-height:1;
">
This is a system-generated email. Please do not reply.
</div>';
public static function send_email_smtp($params)
{
$myLogger = \Config\Services::mylogger();
$emaill = $params['mail'];
$subject = $params['subject'];
$message = $params['message'];
$message .= self::SYSTEM_EMAIL_FOOTER;
if (isset($params['bcc'])) {
$bcc = $params['bcc'];
} else {
@ -206,7 +218,8 @@ class MailHelper
$curl = curl_init();
$postData = [
'from' => [
'address' => $from_address
'address' => $from_address,
'name' => 'Nhance India Insurance'
],
'to' => [
[
@ -315,6 +328,9 @@ class MailHelper
$subject = $params['subject'];
$message = $params['message'];
$message .= self::SYSTEM_EMAIL_FOOTER;
// echo $message;die;
$attachments = isset($params['attachments']) ? $params['attachments'] : [];
$common = isset($params['common']) ? $params['common'] : '';
$bcc = isset($params['bcc']) ? $params['bcc'] : '';
@ -328,7 +344,8 @@ class MailHelper
$postData = [
'from' => [
'address' => $from_address
'address' => $from_address,
'name' => "Nhance India Insurance"
],
'to' => $emails,
'subject' => $subject,

View 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]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -311,6 +311,24 @@ class ClientPolicyModel extends Model
// Fetch the deposit data based on client and insurer IDs
$query = $this->db->table('cash_deposit')
->select('cash_deposit.*')
->select("
(select old_value from auditing_history where pk = cash_deposit.id and field_name = 'amount' and table_name = 'cash_deposit' order by id desc limit 1) as old_amt,
(select new_value from auditing_history where pk = cash_deposit.id and field_name = 'amount' and table_name = 'cash_deposit' order by id desc limit 1) as new_amt,
(select created_by from auditing_history where pk = cash_deposit.id and field_name = 'amount' and table_name = 'cash_deposit' order by id desc limit 1) as amt_updated_by,
(select created_at from auditing_history where pk = cash_deposit.id and field_name = 'amount' and table_name = 'cash_deposit' order by id desc limit 1) as amt_updated_at,
(
select up.first_name
from user_profiles up
where up.id = (
select created_by
from auditing_history
where pk = cash_deposit.id and field_name = 'amount' and table_name = 'cash_deposit'
order by id desc
limit 1
)
limit 1
) as amt_updated_by_name
")
->select('insurers.name as insurer_name, insurers.short_name as insurer_short')
->select('clients.client_name as clientname, clients.short_name as clientshort, client_policy.policy_no')
->select('cd_master.cd_ac_no as cd_master_account_no')

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claims Overview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: sans-serif; background: #f5f5f5; }
#dashboard-container {
width: 100%;
height: 100vh;
}
#dashboard-container iframe {
width: 100%;
height: 100%;
border: none;
}
#error-msg {
display: none;
padding: 2rem;
color: #c0392b;
font-size: 14px;
text-align: center;
}
</style>
</head>
<body>
<div id="dashboard-container"></div>
<iframe src="https://venbait.in/chartBoard/public/share/8e130e4faa821740183feaa805f3ba0c7935841a64c5461ba6ed255f5b419f93?embed=1" width="1200" height="800" style="border:0;" loading="lazy" title="Chart-Board"></iframe>
<div id="error-msg"></div>
<script src="https://unpkg.com/@superset-ui/embedded-sdk"></script>
<script>
const SUPERSET_DOMAIN = "https://demo.venbait.in";
const DASHBOARD_ID = "aaff42c8-1d7a-4e28-8beb-f6a7b9e08661";
const USERNAME = "admin";
const PASSWORD = "$tr0n9pa55w0rd ";
async function getGuestToken() {
// Step 1: Login to get access token
const loginRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: USERNAME,
password: PASSWORD,
provider: "db",
refresh: true
})
});
if (!loginRes.ok) throw new Error("Login failed: " + loginRes.status);
const { access_token } = await loginRes.json();
// Step 2: Get CSRF token
const csrfRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/csrf_token/", {
method: "GET",
headers: { "Authorization": "Bearer " + access_token }
});
if (!csrfRes.ok) throw new Error("CSRF fetch failed: " + csrfRes.status);
const { result: csrf_token } = await csrfRes.json();
// Step 3: Get guest token
const guestRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/guest_token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token,
"X-CSRFToken": csrf_token
},
body: JSON.stringify({
resources: [{ type: "dashboard", id: DASHBOARD_ID }],
rls: [],
user: { username: "guest", first_name: "Guest", last_name: "User" }
})
});
if (!guestRes.ok) throw new Error("Guest token failed: " + guestRes.status);
const { token } = await guestRes.json();
return token;
}
async function init() {
alert();
return true;
try {
await supersetEmbeddedSdk.embedDashboard({
id: DASHBOARD_ID,
supersetDomain: SUPERSET_DOMAIN,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: () => getGuestToken(),
dashboardUiConfig: {
hideTitle: true,
filters: { expanded: true }
}
});
} catch (err) {
const el = document.getElementById("error-msg");
el.style.display = "block";
el.textContent = "Error loading dashboard: " + err.message;
console.error(err);
}
}
init();
</script>
</body>
</html>

View File

@ -34,7 +34,7 @@ if (!empty($batch_list) && is_array($batch_list)) {
$countStr = ($file['count'] ?? null) === null ? '-' : (string) $file['count'];
$batch_col_max_len[9] = max($batch_col_max_len[9], mb_strlen($countStr));
$batch_col_max_len[10] = max($batch_col_max_len[10], mb_strlen((string) format_indian_number($file['amount'])));
$userTime = change_date_format($file['created_at'] ?? '', 'Y-m-d H:i:s', 'd M Y h:i a')
$userTime = change_date_format($file['created_at'] ?? '', 'Y-m-d H:i:s', 'd/m/Y h:i a')
. ' by '
. get_username($file['created_by'] ?? '');
$batch_col_max_len[11] = max($batch_col_max_len[11], mb_strlen($userTime));
@ -350,7 +350,7 @@ for ($i = 0; $i < $batch_col_count; $i++) {
<td><?php echo format_indian_number($file['amount'])?></td>
<td class="reload">
<?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd M Y h:i a') ?> by <?php echo get_username($file['created_by']) ?>
<?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd/m/Y h:i a') ?> by <?php echo get_username($file['created_by']) ?>
</td>

View File

@ -49,7 +49,7 @@
<td style="overflow: hidden;" class="reload truncate" data-toggle="tooltip" data-placement="top" title="<?php echo $file['file_name'] ?>">
<?php echo $file['file_name'] ?>
</td>
<td><?php echo change_date_format($file['created_at'], 'Y-m-d H:i:s', 'd M Y h:i A') . ' by <strong>' . $file['user_name'] . '</strong>' ?> </td>
<td><?php echo change_date_format($file['created_at'], 'Y-m-d H:i:s', 'd/m/Y h:i A') . ' by <strong>' . $file['user_name'] . '</strong>' ?> </td>
<td>
<?php if ($file['status'] == "failed") { ?>
<span> <?= $file['status'] ?> </span>

View File

@ -107,13 +107,50 @@
var isCdMasterPage = <?php echo isset($CD_Master_Data) ? 'true' : 'false'; ?>;
console.log("isCdMasterPage", isCdMasterPage);
function formatCdOpeningDateForInput(inputDate) {
if (!inputDate || String(inputDate).trim() === '') {
return '';
}
var str = String(inputDate).trim();
var iso = str.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (iso) {
return iso[3] + '/' + iso[2] + '/' + iso[1];
}
var dmy = str.match(/^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})$/);
if (dmy) {
var day = ('0' + dmy[1]).slice(-2);
var month = ('0' + dmy[2]).slice(-2);
return day + '/' + month + '/' + dmy[3];
}
return str;
}
function cdOpeningDateForSubmit(inputDate) {
var formatted = formatCdOpeningDateForInput(inputDate);
if (!formatted) {
return inputDate || '';
}
var parts = formatted.split('/');
return parts[0] + '-' + parts[1] + '-' + parts[2];
}
var openingDatePicker;
$(document).ready(function(){
var openingDatePicker = flatpickr("#opening_date", {
dateFormat: "d-m-Y",
openingDatePicker = flatpickr("#opening_date", {
dateFormat: "d/m/Y",
allowInput: false
});
$('#con-close-modal').on('shown.bs.modal', function() {
var val = $('#opening_date').val();
if (val) {
var formatted = formatCdOpeningDateForInput(val);
openingDatePicker.setDate(formatted, false);
}
});
if(isCdMasterPage == true){
$('#cd_client_id').select2();
$('#insurer_id_for_cd').select2();
@ -172,6 +209,10 @@
}
let formData = new FormData($('#CDMasterForm')[0]);
var openingDate = formData.get('opening_date');
if (openingDate) {
formData.set('opening_date', cdOpeningDateForSubmit(openingDate));
}
console.log("formData", formData);
let url = '<?= base_url('master/cash_deposite/create') ?>';

View File

@ -15,12 +15,12 @@ foreach ($CD_Master_Data as $index => $row) {
$cdm_col_max_len[1] = max($cdm_col_max_len[1], mb_strlen($clientCell));
$cdm_col_max_len[2] = max($cdm_col_max_len[2], mb_strlen((string) ($row['insurer_name'] ?? '')));
$cdm_col_max_len[3] = max($cdm_col_max_len[3], mb_strlen((string) ($row['insurer_branch_name'] ?? '')));
$od = ! empty($row['opening_date']) ? date('d-m-Y', strtotime((string) $row['opening_date'])) : '';
$od = ! empty($row['opening_date']) ? date('d/m/Y', strtotime((string) $row['opening_date'])) : '';
$cdm_col_max_len[4] = max($cdm_col_max_len[4], mb_strlen($od));
$cdm_col_max_len[5] = max($cdm_col_max_len[5], mb_strlen((string) ($row['cd_ac_no'] ?? '')));
$cdm_col_max_len[6] = max($cdm_col_max_len[6], mb_strlen((string) ($row['opening_bal'] ?? '')));
$du = ! empty($row['created_at'])
? (date('d-M-Y h:i A', strtotime((string) $row['created_at'])) . ' by ' . ($row['user_name'] ?? ''))
? (date('d/m/Y h:i A', strtotime((string) $row['created_at'])) . ' by ' . ($row['user_name'] ?? ''))
: '';
$cdm_col_max_len[7] = max($cdm_col_max_len[7], mb_strlen($du));
$cdm_col_max_len[8] = max($cdm_col_max_len[8], 4);
@ -118,10 +118,10 @@ $cdm_col_width_px = nhance_dt_column_widths_px($cdm_header_labels, $cdm_col_max_
<td><?php echo $row['client_name']; ?>( <?= $row['short_name'] ?> )</td>
<td><?php echo $row['insurer_name']; ?></td>
<td><?php echo $row['insurer_branch_name']; ?></td>
<td><?php echo date('d-m-Y', strtotime($row['opening_date'])); ?></td>
<td><?php echo date('d/m/Y', strtotime($row['opening_date'])); ?></td>
<td><?php echo $row['cd_ac_no']; ?></td>
<td><?php echo $row['opening_bal']; ?></td>
<td><?php echo date('d-M-Y h:i A', strtotime($row['created_at'])) ?> <br>by <?php echo $row['user_name']; ?></td>
<td><?php echo date('d/m/Y h:i A', strtotime($row['created_at'])) ?> <br>by <?php echo $row['user_name']; ?></td>
<td class="text-center table-action-cell">
<div class="btn-group dropdown">
<a href="javascript: void(0);"class="dropdown-toggle arrow-none btn btn-light btn-sm" data-toggle="dropdown" aria-expanded="false"><i class="mdi mdi-dots-horizontal"></i></a>

View File

@ -161,7 +161,7 @@
<td style="overflow: hidden;" class="reload truncate" data-toggle="tooltip" data-placement="top" title="<?php echo $file['file_name'] ?>">
<?php echo $file['file_name'] ?>
</td>
<td><?php echo change_date_format($file['created_at'], 'Y-m-d H:i:s', 'd M Y h:i A') . ' by <strong>' . $file['user_name'] . '</strong>' ?> </td>
<td><?php echo change_date_format($file['created_at'], 'Y-m-d H:i:s', 'd/m/Y h:i A') . ' by <strong>' . $file['user_name'] . '</strong>' ?> </td>
<td>
<?php if ($file['status'] == "failed") { ?>
<span style="color : #BD0707 ;"> <?= $file['status'] ?> </span>
@ -262,7 +262,7 @@
<label for="tpa">TPA<span id="tpa_danger" class="text-danger"></span></label>
<select class="form-control readonly-select" id="tpa_id" name="tpa_id">
<option value="">Select TPA</option>
<?php foreach ($tpa_list as $value) { ?>
<?php foreach ($tpa_list ?? [] as $value) { ?>
<option value="<?= $value['id'] ?>"><?= $value['short_name'] ?></option>
<?php } ?>
</select>

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claims Collection V2 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: #2563eb; 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>Claims Collection V2</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 baseUrl = <?= json_encode(rtrim(base_url(), '/')) ?>;
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/claims-collection-v2/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>

View File

@ -236,7 +236,7 @@ input:checked + .slider:before {
class="text-danger">*</span></label>
<select class="form-control" id="entity_type" name="entity_type_id" required>
<option value="">Select Entity</option>
<?php foreach($entity as $value) { ?>
<?php foreach($entity ?? [] as $value) { ?>
<?php if(isset($client['entity_type_id'])){ ?>
<?php if($value['id'] == $client['entity_type_id']) { ?>
<option value="<?= $value['id'] ?>" selected><?= $value['name'] ?></option>
@ -285,7 +285,7 @@ input:checked + .slider:before {
<label for="parent_client_id">Parent Group<span class="text-danger"></span></label>
<select class="form-control" id="parent_client_id" name="parent_client_id">
<option value="" >Select Parent</option>
<?php foreach($clients as $value) { ?>
<?php foreach($clients ?? [] as $value) { ?>
<option value="<?= $value['id'] ?>" <?= isset($client['parent_client_id']) && $client['parent_client_id'] == $value['id'] ? "selected" : '' ?>><?= $value['client_name'] ?></option>
<?php } ?>
</select>
@ -293,7 +293,7 @@ input:checked + .slider:before {
<div class="form-group col-md-4">
<label>HR File Processed By <span class="text-danger">*</span></label>
<label>Enrollment file processed by <span class="text-danger">*</span></label>
<div class="hr-processed-options">
<div class="hr-processed-option">
<input class="hr-radio-input"

View File

@ -138,7 +138,7 @@ $cd_col_width_px = nhance_dt_column_widths_px($cd_header_labels, $cd_col_max_len
<td>
<a class="view-deposit-link"
href="<?= base_url("client/view_deposit/{$value->insurer_id}?client_id={$value->client_id}&cd_ac_pk={$value->cd_ac_pk}"); ?>">
<i class="mdi mdi-eye text-muted font-18 vertical-middle"></i><span>View Deposit</span>
<i class="mdi mdi-eye text-muted font-18 vertical-middle"></i><span>View Details</span>
</a>
</td>
</tr>

View File

@ -209,7 +209,7 @@ input:checked + .slider_blue::before {
data-parsley-pattern="^[1-9][0-9]*$"
data-parsley-pattern-message="Please select a valid Policy Type.">
<option value="0">Select Policy Type</option>
<?php foreach ($policy_types as $value) { ?>
<?php foreach ($policy_types ?? [] as $value) { ?>
<option value="<?= $value['id']?>"><?= $value['policy_type'] ?></option>
<?php } ?>
</select>
@ -230,7 +230,7 @@ input:checked + .slider_blue::before {
<label for="insurer">Insurer<span class="text-danger">*</span></label>
<select class="form-control" id="insurer" name="insurer" onchange="setInsuerAndBranchValue(this)" required>
<option value="">Select Insurer</option>
<?php foreach ($insurer as $value) { ?>
<?php foreach ($insurer ?? [] as $value) { ?>
<option data-id="<?= $value['insurer_id']?>" value="<?= $value['id'] . '-' . $value['insurer_id'] ?>">
<?= $value['insurer_name'] . '-' . $value['branch_code'] ?></option>
<?php } ?>
@ -241,7 +241,7 @@ input:checked + .slider_blue::before {
<label for="tpa">TPA<span id="tpa_danger" class="text-danger">*</span></label>
<select class="form-control" id="tpa" name="tpa" required>
<option value="">Select TPA</option>
<?php foreach ($tpa as $value) { ?>
<?php foreach ($tpa ?? [] as $value) { ?>
<option value="<?= $value['id'] . '-' . $value['tpa_id'] ?>">
<?= $value['tpa_short_name'] . '-' . $value['branch_code'] ?></option>
<?php } ?>
@ -454,7 +454,7 @@ input:checked + .slider_blue::before {
const showExpired = $('#chk-show-expired').is(':checked');
var startDatePicker = flatpickr("#start_date", {
dateFormat: "d-m-Y",
dateFormat: "d/m/Y",
defaultDate: today,
allowInput: false,
onChange: function(selectedDates, dateStr, instance) {
@ -467,7 +467,7 @@ input:checked + .slider_blue::before {
// Initialize Flatpickr for the start date with today's date
var openDatePicker = flatpickr("#open_date", {
dateFormat: "d-m-Y",
dateFormat: "d/m/Y",
defaultDate: today,
allowInput: false,
});
@ -478,14 +478,14 @@ input:checked + .slider_blue::before {
closeDate.setDate(closeDate.getDate() - 1); // Set end date to last day of next year
var endDatePicker = flatpickr("#end_date", {
dateFormat: "d-m-Y",
dateFormat: "d/m/Y",
defaultDate: closeDate,
allowInput: false
});
// Initialize Flatpickr for the end date with the calculated end date
var closeDatePicker = flatpickr("#close_date", {
dateFormat: "d-m-Y",
dateFormat: "d/m/Y",
defaultDate: today,
allowInput: false
});
@ -566,7 +566,7 @@ input:checked + .slider_blue::before {
<td>${policy_name_data}</td>
<td>${item.branch_name ? item.branch_name : ' - '}</td>
<td>${tpaValue}</td>
<td>${(item.policy_start_date)} / ${(item.policy_end_date)}</td>
<td>${formatPolicyDisplayDate(item.policy_start_date)} - ${formatPolicyDisplayDate(item.policy_end_date)}</td>
<td>${checkDateStatus(item.policy_end_date, 1)}</td>
<td>
@ -751,6 +751,12 @@ input:checked + .slider_blue::before {
$('.loader-mask').fadeIn();
var formData = new FormData($('#policy_form')[0]);
['policy_start_date', 'policy_end_date', 'open_date', 'close_date'].forEach(function(fieldName) {
var val = formData.get(fieldName);
if (val) {
formData.set(fieldName, policyFormDateForSubmit(val));
}
});
var policy_form_action = $('#policy_form_action').val();
console.log(formData+'form data');
$.ajax({
@ -853,7 +859,7 @@ input:checked + .slider_blue::before {
<td>${item.branch_name ? item.branch_name : ' - '}</td>
<td>${tpaValue}</td>
<td>${(item.policy_start_date)} / ${(item.policy_end_date)}</td>
<td>${formatPolicyDisplayDate(item.policy_start_date)} - ${formatPolicyDisplayDate(item.policy_end_date)}</td>
<td>${checkDateStatus(item.policy_end_date, 1)}</td>
<td>
<div class="btn-group dropdown">
@ -1512,31 +1518,74 @@ input:checked + .slider_blue::before {
});
//for date convert to indian formate like this 'yyyy-mm-dd' to this 'dd-mm-yyyy'
function rearrangeDateFormat(inputDate) {
console.log('inputDate', inputDate)
// Check if inputDate is a string and not empty
if (typeof inputDate === 'string' && inputDate.trim() !== '') {
var dateComponents = inputDate.split("-");
// Check if dateComponents has the expected number of parts
if (dateComponents.length === 3) {
var rearrangedDate = dateComponents[2] + "-" + dateComponents[1] + "-" + dateComponents[0];
return rearrangedDate;
} else {
// Handle unexpected date format
return '';
}
} else {
// Handle the case where inputDate is not a valid string
return '';
function parsePolicyDate(inputDate) {
if (!inputDate || (typeof inputDate === 'string' && inputDate.trim() === '')) {
return null;
}
var str = String(inputDate).trim();
var dmyMon = str.match(/^(\d{1,2})\/([A-Za-z]{3})\/(\d{4})$/i);
if (dmyMon) {
return new Date(str);
}
var dmy = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (dmy) {
return new Date(parseInt(dmy[3], 10), parseInt(dmy[2], 10) - 1, parseInt(dmy[1], 10));
}
var iso = str.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (iso) {
return new Date(parseInt(iso[1], 10), parseInt(iso[2], 10) - 1, parseInt(iso[3], 10));
}
var dmyDash = str.match(/^(\d{1,2})-([A-Za-z]{3}|\d{1,2})-(\d{4})$/i);
if (dmyDash) {
if (/^[A-Za-z]{3}$/i.test(dmyDash[2])) {
return new Date(str);
}
return new Date(parseInt(dmyDash[3], 10), parseInt(dmyDash[2], 10) - 1, parseInt(dmyDash[1], 10));
}
var parsed = new Date(str);
return isNaN(parsed.getTime()) ? null : parsed;
}
var policyMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
function formatPolicyDisplayDate(inputDate) {
var d = parsePolicyDate(inputDate);
if (!d) {
return (inputDate && String(inputDate).trim() !== '') ? String(inputDate) : '';
}
return d.getDate() + '/' + policyMonthNames[d.getMonth()] + '/' + d.getFullYear();
}
function formatPolicyFormDate(inputDate) {
var d = parsePolicyDate(inputDate);
if (!d) {
return (inputDate && String(inputDate).trim() !== '') ? String(inputDate) : '';
}
var day = ('0' + d.getDate()).slice(-2);
var month = ('0' + (d.getMonth() + 1)).slice(-2);
return day + '/' + month + '/' + d.getFullYear();
}
function policyFormDateForSubmit(inputDate) {
var formatted = formatPolicyFormDate(inputDate);
if (!formatted) {
return inputDate || '';
}
var parts = formatted.split('/');
return parts[0] + '-' + parts[1] + '-' + parts[2];
}
// Convert API/storage dates to d/m/Y for form inputs
function rearrangeDateFormat(inputDate) {
return formatPolicyFormDate(inputDate);
}
function checkDateStatus(inputDate, bg = false) {
var givenDate = new Date(inputDate);
var givenDate = parsePolicyDate(inputDate);
if (!givenDate) {
return bg ? '' : '';
}
var currentDate = new Date();
if (bg != false) {
@ -2763,7 +2812,7 @@ $(document).ready(function () {
<td>${policy_name_data}</td>
<td>${item.branch_name ? item.branch_name : ' - '}</td>
<td>${tpaValue}</td>
<td>${(item.policy_start_date)} / ${(item.policy_end_date)}</td>
<td>${formatPolicyDisplayDate(item.policy_start_date)} - ${formatPolicyDisplayDate(item.policy_end_date)}</td>
<td>${checkDateStatus(item.policy_end_date, 1)}</td>
<td>
@ -2809,7 +2858,7 @@ $(document).ready(function () {
<td>${policy_name_data}</td>
<td>${item.branch_name ? item.branch_name : ' - '}</td>
<td>${tpaValue}</td>
<td>${(item.policy_start_date)} / ${(item.policy_end_date)}</td>
<td>${formatPolicyDisplayDate(item.policy_start_date)} - ${formatPolicyDisplayDate(item.policy_end_date)}</td>
<td>${checkDateStatus(item.policy_end_date, 1)}</td>
<td>

View File

@ -182,7 +182,7 @@
<td><?php echo ucfirst($row['department']) ?> </td>
<td><?php echo $row['rules_count'] ?></td>
<td><?php echo ucfirst($row['file_status']) ?> </td>
<td><?php echo change_date_format($row['created_at'],'Y-m-d H:i:s', 'd M Y h:i A') . ' by <strong>' . $row['created_user_name'] . '</strong>' ?>
<td><?php echo change_date_format($row['created_at'],'Y-m-d H:i:s', 'd/m/Y h:i A') . ' by <strong>' . $row['created_user_name'] . '</strong>' ?>
<td>
<div class="btn-group dropdown">
<a href="javascript: void(0);" class="dropdown-toggle arrow-none btn btn-light btn-sm" data-toggle="dropdown" aria-expanded="false"><i class="mdi mdi-dots-horizontal"></i></a>

View File

@ -252,7 +252,7 @@ $ex_col_width_px = nhance_dt_column_widths_px($ex_header_labels, $ex_col_max_len
<td><?= number_format((float) ($row['amount'] ?? 0), 2); ?></td>
<td>
<?php if (! empty($row['expense_date'])): ?>
<?= date('d-m-Y', strtotime($row['expense_date'])); ?>
<?= date('d/m/Y', strtotime($row['expense_date'])); ?>
<?php endif; ?>
</td>
<td>

View File

@ -23,7 +23,7 @@ if (!empty($fileList) && is_array($fileList)) {
);
$fl_col_max_len[4] = max($fl_col_max_len[4], mb_strlen($policy_display));
$fl_col_max_len[5] = max($fl_col_max_len[5], mb_strlen((string) ($file['action'] ?? '')));
$userTime = change_date_format($file['created_at'] ?? '', 'Y-m-d H:i:s', 'd M Y h:i a')
$userTime = change_date_format($file['created_at'] ?? '', 'Y-m-d H:i:s', 'd/m/Y h:i a')
. ' by '
. ($file['first_name'] ?? '');
$fl_col_max_len[6] = max($fl_col_max_len[6], mb_strlen($userTime));
@ -203,7 +203,7 @@ $fl_col_width_px = nhance_dt_column_widths_px($fl_header_labels, $fl_col_max_len
<!-- <td><?php echo isset($file['policy_name']) ? $file['policy_name'] : '' ?> - <?php echo isset($file['policy_no']) ? $file['policy_no'] : '' ?> - <?php echo isset($file['policy_type']) ? $file['policy_type'] : '' ?></td> -->
<td><?php echo isset($file['policy_type']) ? $file['policy_type'] : '' ?> - <?php echo isset($file['policy_no']) ? $file['policy_no'] : '' ?></td>
<td><?php echo $file['action'] ?></td>
<td><?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd M Y h:i a') . ' by <strong>' . $file['first_name'] . '</strong>' ?>
<td><?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd/m/Y h:i a') . ' by <strong>' . $file['first_name'] . '</strong>' ?>
</td>
<td>
<?php

View File

@ -255,7 +255,7 @@ $hr_col_width_px = nhance_dt_column_widths_px($hr_header_labels, $hr_col_max_len
<td><?php echo $file['branch_name'] ?></td>
<td><?php echo $file['policy_no'] ?></td>
<td><?php echo $file['file_action'] ?></td>
<td><?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd M Y h:i A') . ' by <strong>' . $file['first_name'] . '</strong>' ?></td>
<td><?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd/m/Y h:i A') . ' by <strong>' . $file['first_name'] . '</strong>' ?></td>
<td><?php echo $file['status']; ?> </td>
<td> </td>
</tr>

View File

@ -2017,6 +2017,11 @@ body[data-sidebar-size="condensed"] .footer {
<li>
<a href="<?= base_url('/employee/retail-endorsement-list') ?>">Retail Endorsement</a>
</li>
<?php if(in_array(get_role_id(), [ACCOUNT_MANAGER_ROLE_ID])) { ?>
<li>
<a href="<?= base_url('/employee/tpaReportsDashboard') ?>">TPA Reports</a>
</li>
<?php } ?>
</ul>
</div>
</li>

View File

@ -1,33 +1,345 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Metabase Dashboard</title>
<style>
.meta-dashboard-demo .filter-bar {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
align-items: flex-end;
background: #eef5fa;
border-radius: 10px;
padding: 10px 14px;
margin-bottom: 10px;
}
.meta-dashboard-demo .filter-field {
flex: 1 1 140px;
min-width: 120px;
max-width: 220px;
}
.meta-dashboard-demo .filter-field label {
display: block;
font-size: 12px;
font-weight: 700;
margin-bottom: 4px;
color: #111;
}
.meta-dashboard-demo .filter-field .select2-container {
width: 100% !important;
}
.meta-dashboard-demo .filter-field .select2-selection--single {
height: 32px !important;
min-height: 32px;
border: 1px solid #d8e2ea !important;
border-radius: 8px !important;
font-size: 12px;
}
.meta-dashboard-demo .filter-field .select2-selection__rendered {
line-height: 30px !important;
padding-left: 10px !important;
color: #333;
}
.meta-dashboard-demo .filter-field .select2-selection__arrow {
height: 30px !important;
}
.meta-dashboard-demo .filter-field .select2-container--disabled .select2-selection--single {
background-color: #f5f7f9 !important;
cursor: not-allowed;
opacity: 0.85;
}
.meta-dashboard-demo .dashboard-status {
font-size: 12px;
color: #5c6b7a;
padding: 4px 0 10px;
min-height: 20px;
}
.meta-dashboard-demo .dashboard-status.is-error { color: #c0392b; }
.meta-dashboard-demo .dashboard-status.is-loading { color: #0a8794; }
.meta-dashboard-demo #dashboard-container {
min-height: 480px;
}
.meta-dashboard-demo #dashboard-container metabase-dashboard {
display: block;
width: 100%;
min-height: 480px;
}
.meta-dashboard-demo .dashboard-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
color: #7a8a99;
font-size: 13px;
background: #fff;
border: 1px dashed #d0dce6;
border-radius: 8px;
}
<!-- Metabase embed script -->
<script defer src="<?= esc($metabaseUrl) ?>/app/embed.js"></script>
.emotion-65tole {
display: none !important;
}
</style>
<script>
function defineMetabaseConfig(config) {
window.metabaseConfig = config;
<div class="row meta-dashboard-demo">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<!-- <h4 class="mb-3"><?= esc($page_name ?? 'Metabase Dashboard') ?></h4> -->
<div class="filter-bar">
<div class="filter-field">
<label for="filter-client">Client</label>
<select id="filter-client" class="form-control form-control-sm meta-filter-select2">
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-branch">Branch</label>
<select id="filter-branch" class="form-control form-control-sm meta-filter-select2" disabled>
<option value="">Select</option>
</select>
</div>
<div class="filter-field">
<label for="filter-policy">Policy</label>
<select id="filter-policy" class="form-control form-control-sm meta-filter-select2" disabled>
<option value="">Select</option>
</select>
</div>
</div>
<div id="dashboard-status" class="dashboard-status"></div>
<div id="dashboard-container">
<div class="dashboard-placeholder" id="dashboard-placeholder">
Select client, branch, and policy to load the dashboard.
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function defineMetabaseConfig(config) {
window.metabaseConfig = config;
}
defineMetabaseConfig({
theme: { preset: "light" },
isGuest: true,
instanceUrl: "<?= esc($metabaseUrl ?? '') ?>"
});
</script>
<script>
$(function () {
const CLIENTS_API = <?= json_encode(base_url('/util/getClientAndBranchAndPolicy')) ?>;
const DASHBOARD_API = <?= json_encode($dashboardApiUrl ?? base_url('/metaTpaDashboardDemo')) ?>;
const METABASE_URL = <?= json_encode($metabaseUrl ?? '') ?>;
const select2Options = {
placeholder: 'Select',
allowClear: true,
width: '100%',
minimumResultsForSearch: 0,
dropdownParent: $(document.body)
};
let branchByClient = {};
let policyByClient = {};
let embedScriptPromise = null;
const $elClient = $('#filter-client');
const $elBranch = $('#filter-branch');
const $elPolicy = $('#filter-policy');
const elStatus = document.getElementById('dashboard-status');
const elContainer = document.getElementById('dashboard-container');
const elPlaceholder = document.getElementById('dashboard-placeholder');
function setStatus(message, type) {
elStatus.textContent = message || '';
elStatus.className = 'dashboard-status' + (type ? ' is-' + type : '');
}
defineMetabaseConfig({
theme: {
preset: "light"
},
isGuest: true,
instanceUrl: "<?= esc($metabaseUrl) ?>"
function initSelect2($select, disabled) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.prop('disabled', false);
$select.select2(select2Options);
if (disabled) {
$select.prop('disabled', true);
}
}
function rebuildSelect($select, placeholder, disabled, items, getValue, getLabel) {
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.empty().append($('<option>', { value: '', text: placeholder }));
(items || []).forEach(function (item) {
$select.append($('<option>', {
value: String(getValue(item)),
text: getLabel(item)
}));
});
$select.val('');
initSelect2($select, disabled);
}
function filtersComplete() {
return !!($elClient.val() && $elBranch.val() && $elPolicy.val());
}
function loadMetabaseEmbedScript() {
if (window.customElements && window.customElements.get('metabase-dashboard')) {
return Promise.resolve();
}
if (embedScriptPromise) {
return embedScriptPromise;
}
embedScriptPromise = new Promise(function (resolve, reject) {
const existing = document.querySelector('script[data-metabase-embed]');
if (existing) {
existing.addEventListener('load', function () { resolve(); }, { once: true });
existing.addEventListener('error', reject, { once: true });
return;
}
const script = document.createElement('script');
script.src = METABASE_URL + '/app/embed.js';
script.defer = true;
script.dataset.metabaseEmbed = '1';
script.onload = function () { resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
return embedScriptPromise;
}
function clearDashboard() {
const existing = elContainer.querySelector('metabase-dashboard');
if (existing) {
existing.remove();
}
if (elPlaceholder) {
elPlaceholder.style.display = '';
}
}
async function renderDashboard(token) {
clearDashboard();
if (!token) {
return;
}
await loadMetabaseEmbedScript();
if (elPlaceholder) {
elPlaceholder.style.display = 'none';
}
const dash = document.createElement('metabase-dashboard');
dash.setAttribute('token', token);
dash.setAttribute('with-title', 'true');
dash.setAttribute('with-downloads', 'true');
elContainer.appendChild(dash);
}
async function loadFilterData() {
setStatus('Loading clients…', 'loading');
try {
const res = await fetch(CLIENTS_API, { credentials: 'same-origin' });
const json = await res.json();
if (!json.status) {
setStatus('Could not load client list.', 'error');
return;
}
branchByClient = json.branch_data || {};
policyByClient = json.policyListByClient || {};
rebuildSelect($elClient, 'Select', false, json.client_data || [], function (c) { return c.id; }, function (c) {
return c.client_name || c.client_short_name || ('Client ' + c.id);
});
rebuildSelect($elBranch, 'Select', true);
rebuildSelect($elPolicy, 'Select', true);
setStatus('');
} catch (e) {
setStatus('Failed to load filter options.', 'error');
}
}
async function fetchDashboard(policyId) {
if (!filtersComplete()) {
clearDashboard();
setStatus('Select client, branch, and policy to load the dashboard.');
return;
}
if (!policyId) {
clearDashboard();
setStatus('Select client, branch, and policy to load the dashboard.');
return;
}
setStatus('Loading dashboard…', 'loading');
try {
const url = DASHBOARD_API + '?api=1&client_policy=' + encodeURIComponent(policyId);
const res = await fetch(url, { credentials: 'same-origin' });
const json = await res.json();
if (json.status !== 'success' || !json.data || !json.data.metabaseToken) {
clearDashboard();
setStatus(json.message || 'No dashboard available for this policy.', 'error');
return;
}
renderDashboard(json.data.metabaseToken);
setStatus('');
} catch (e) {
clearDashboard();
setStatus('Failed to load dashboard.', 'error');
}
}
$elClient.on('change', function () {
const clientId = $(this).val();
rebuildSelect($elBranch, 'Select', !clientId);
rebuildSelect($elPolicy, 'Select', true);
clearDashboard();
setStatus(clientId ? '' : 'Select client, branch, and policy to load the dashboard.');
if (!clientId) {
return;
}
const branches = branchByClient[clientId] || [];
rebuildSelect($elBranch, 'Select', branches.length === 0, branches, function (b) { return b.id; }, function (b) {
return b.branch_name || ('Branch ' + b.id);
});
});
</script>
</head>
<body>
<metabase-dashboard
token="<?= esc($metabaseToken) ?>"
with-title="true"
with-downloads="true">
</metabase-dashboard>
$elBranch.on('change', function () {
const branchId = $(this).val();
const clientId = $elClient.val();
rebuildSelect($elPolicy, 'Select', !branchId);
clearDashboard();
setStatus(branchId ? '' : 'Select branch and policy to load the dashboard.');
</body>
</html>
if (!branchId || !clientId) {
return;
}
const policiesForClient = policyByClient[clientId] || [];
const policies = policiesForClient.filter(function (p) {
return String(p.client_branch_id) === String(branchId);
});
rebuildSelect($elPolicy, 'Select', policies.length === 0, policies, function (p) { return p.id; }, function (p) {
const type = p.policy_type || '';
const no = p.policy_no || '';
return (type && no) ? (type + ' - ' + no) : (no || type || ('Policy ' + p.id));
});
if (policies.length === 0) {
setStatus('No policies found for this branch.', 'error');
}
});
$elPolicy.on('change', function () {
fetchDashboard($(this).val());
});
initSelect2($elClient, false);
initSelect2($elBranch, true);
initSelect2($elPolicy, true);
loadFilterData();
});
</script>

View File

@ -64,7 +64,7 @@
<td><?= $row['nhance_claim_ref_no'] ?: 'N/A'; ?></td>
<td><?= $row['short_name'] ?: ($row['client_name'] ?? 'N/A'); ?></td>
<td><?= $row['insurer_short_name'] ?: ($row['insurer_name'] ?: 'N/A'); ?></td>
<td><?= !empty($row['loss_date']) ? date('d-m-Y', strtotime($row['loss_date'])) : 'N/A'; ?></td>
<td><?= !empty($row['loss_date']) ? date('d/m/Y', strtotime($row['loss_date'])) : 'N/A'; ?></td>
<td><?= $row['nature_of_loss'] ?: 'N/A'; ?></td>
<td style="display:none;"><?= $row['acm_name'] ?? ''; ?></td>

View File

@ -181,7 +181,7 @@
}
</style>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_endorsement_form_validation.js') ?>"></script>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_endorsement_form_validation.js') ?>?v=20260529"></script>
<div class="tab-pane fade active show" id="form">
<div class="row" id="endorsement_form">
@ -297,7 +297,7 @@
<div class="form-group col-md-3">
<label for="addon_policy">Policy No<span id="base_danger"class="text-danger"></span></label>
<input type="text" class="form-control" id="policy_no" name="policy_no"placeholder="Enter Policy No" readonly>
<input type="text" class="form-control" id="policy_no" name="policy_no" placeholder="Enter Policy No" readonly data-parsley-validate="false" data-parsley-exclude-charset="true">
</div>
<div class="form-group col-md-3">
@ -326,7 +326,7 @@
<div class="form-group col-md-3">
<label for="addon_policy">Endorsement No<span id="base_danger" class="text-danger"></span></label>
<input type="text" class="form-control" id="endorsement_no" name="endorsement_no" placeholder="Enter Endorsement No" onchange="validateInput(this, 'policy_transaction', 'endorsement_no')">
<input type="text" class="form-control" id="endorsement_no" name="endorsement_no" placeholder="Enter Endorsement No" data-parsley-validate="false" data-parsley-exclude-charset="true">
</div>
<div class="form-group col-md-3">
@ -757,6 +757,9 @@
// console.log('tpa',tpa);
$('#policy_no').val(policy_no);
if (typeof window.clearEndorsementExcludedFieldsValidation === 'function') {
window.clearEndorsementExcludedFieldsValidation('#endorsement_form_id');
}
$('#insurer_id').val(insurer);
$('#tpa').val(tpa).change();
// $('#cd_ac_no').val(cd_ac_no);
@ -1206,6 +1209,9 @@
$('#policy_no').val(res.data.policy_no);
$('#action_type').val(res.data.action_type);
$('#endorsement_no').val(res.data.endorsement_no);
if (typeof window.clearEndorsementExcludedFieldsValidation === 'function') {
window.clearEndorsementExcludedFieldsValidation('#endorsement_form_id');
}
$('#data_received_date').val(res.data.data_received_date);
$('#policy_issue_date').val(res.data.policy_issue_date);
$('#emp_count').val(res.data.emp_count);

View File

@ -195,7 +195,7 @@
}
</style>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_endorsement_form_validation.js') ?>"></script>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_endorsement_form_validation.js') ?>?v=20260529"></script>
<div class="row" id="endorsement_form" style="display: none;">
<div class="col-12">
@ -310,7 +310,7 @@
<div class="form-group col-md-3">
<label for="addon_policy">Policy No<span id="base_danger"class="text-danger"></span></label>
<input type="text" class="form-control" id="policy_no" name="policy_no"placeholder="Enter Policy No" readonly>
<input type="text" class="form-control" id="policy_no" name="policy_no" placeholder="Enter Policy No" readonly data-parsley-validate="false" data-parsley-exclude-charset="true">
</div>
<div class="form-group col-md-3">
@ -339,7 +339,7 @@
<div class="form-group col-md-3">
<label for="addon_policy">Endorsement No<span id="base_danger" class="text-danger"></span></label>
<input type="text" class="form-control" id="endorsement_no" name="endorsement_no" placeholder="Enter Endorsement No" onchange="validateInput(this, 'policy_transaction', 'endorsement_no')">
<input type="text" class="form-control" id="endorsement_no" name="endorsement_no" placeholder="Enter Endorsement No" data-parsley-validate="false" data-parsley-exclude-charset="true">
</div>
<div class="form-group col-md-3">
@ -613,6 +613,9 @@
// console.log('tpa',tpa);
$('#policy_no').val(policy_no);
if (typeof window.clearEndorsementExcludedFieldsValidation === 'function') {
window.clearEndorsementExcludedFieldsValidation('#endorsement_form_id');
}
$('#insurer_id').val(insurer);
$('#tpa').val(tpa).change();
// $('#cd_ac_no').val(cd_ac_no);
@ -1037,6 +1040,9 @@
$('#policy_no').val(res.data.policy_no);
$('#action_type').val(res.data.action_type);
$('#endorsement_no').val(res.data.endorsement_no);
if (typeof window.clearEndorsementExcludedFieldsValidation === 'function') {
window.clearEndorsementExcludedFieldsValidation('#endorsement_form_id');
}
$('#data_received_date').val(res.data.data_received_date);
$('#policy_issue_date').val(res.data.policy_issue_date);
$('#emp_count').val(res.data.emp_count);

View File

@ -1,11 +1,14 @@
<style>
.table th,
.table td {
padding: 8px;
#scroll-horizontal-datatable thead th,
#scroll-horizontal-datatable tbody td {
padding: 4px 4px !important;
box-sizing: border-box;
}
table.dataTable tbody td {
#scroll-horizontal-datatable_wrapper .dataTables_scrollHead table thead th,
#scroll-horizontal-datatable_wrapper .dataTables_scrollBody table tbody td {
padding: 4px 4px !important;
box-sizing: border-box;
}
.col-12 {
@ -76,7 +79,7 @@ table.dataTable tbody td {
</div>
<div class="badge-container">
Total Policy Count: <span class="text-primary"><?= $policy_count ?></span>
Total Policy Count: <span class="text-primary"><?= $policy_count ?? 0 ?></span>
</div>
</div>
@ -111,7 +114,7 @@ table.dataTable tbody td {
<th style="display: none;">Total Premium</th>
<th>Agreed BP %</th>
<th>Agreed TP %</th>
<th style="display: true;">Rewards</th>
<th>Rewards</th>
<th>Agreed Amount</th>
<th>Invoiced Amount</th>
<th>Outstanding Amount</th>
@ -199,7 +202,7 @@ table.dataTable tbody td {
<td class="right-align-input" style="display: none;"><?php echo ($row['total_irda_amt'] != 0.00) ? ($row['total_premium'] ?: '0.00') : '0.00'; ?></td>
<td class="right-align-input"><?php echo ($row['total_irda_amt'] != 0.00) ? ($row['agreed_bp_per'] ?: '0.00') : '0.00'; ?>%</td>
<td class="right-align-input"><?php echo ($row['total_irda_amt'] != 0.00) ? ($row['agreed_tp_or_ter_per'] ?: '0.00') : '0.00'; ?>%</td>
<td class="right-align-input" style="display: true;"><?php echo isset($row['reward']) ? $row['reward'] : '0.00'; ?></td>
<td class="right-align-input"><?php echo isset($row['reward']) ? $row['reward'] : '0.00'; ?></td>
<?php
// Below Line its old version i am removed. reason no value ['total_irda_amt'] means taken as ['exp_amt'] so.
@ -335,8 +338,9 @@ $(document).ready(function() {
var ticketsTable = $('#scroll-horizontal-datatable');
if (ticketsTable.length) {
ticketsTable.DataTable({
scrollX: true,
nhanceListDataTableBeforeInit();
var nhBdsReportTable = ticketsTable.DataTable(nhanceMergeListDataTableOptions({
autoWidth: false,
// dom: "<'row'<'col-sm-6'f><'col-sm-6 text-right'B>>" + // Filter left, buttons right
// "<'row'<'col-sm-12'tr>>" +
// "<'row'<'col-sm-5'i><'col-sm-7'p>>", // Pagination at the bottom, without page length selector
@ -711,7 +715,9 @@ $(document).ready(function() {
// var totalUnbilled = getUniqueUnbilled(27);
// $('#total_unbilled').text(totalUnbilled.toFixed(2));
}
});
}));
nhanceListDataTableAfterInit();
nhanceListDataTableBindAdjust(nhBdsReportTable);
} else {
console.error("Table atet found.");
}

View File

@ -257,7 +257,7 @@ $ret_col_width_px = nhance_dt_column_widths_px($ret_header_labels, $ret_col_max_
<td><?php echo $employee['agent_name'] ?? 'N/A' ?></td>
<td><?php echo $employee['manager_name'] ?? 'N/A' ?></td>
<td><?php if (!empty($employee['created_at'])):
$cd = date("j F Y", strtotime($employee['created_at']));
$cd = date("j/F/Y", strtotime($employee['created_at']));
$ct = date("h:i A", strtotime($employee['created_at']));
echo $cd . "<br><span class='time'> " . $ct . "</span>";
endif; ?></td>

View File

@ -901,7 +901,7 @@ async function viewDetail(id) {
function renderCard(opps) {
const opp_cont = document.getElementById('opportunitiesContainer');
opp_cont.innerHTML = opps.length ? opps.map(o => {
let lead_type = o.lead_type == 1 ? 'EB' : 'Non-EB';
let lead_type = o.lead_form_type == 1 ? 'EB' : 'Non-EB';
let status = o.status?.toLowerCase();
let statusClass = {
won: 'status-text-won',

View File

@ -269,7 +269,7 @@ $gst_total = 0;
<td><?php echo $employee['emp_code']; ?></td>
<td><?php echo $employee['relationship']; ?></td>
<td><?php echo $employee['gender']; ?></td>
<td><?php echo date('d/M/Y', strtotime($employee['dob'])); ?></td>
<td><?php echo date('d/m/Y', strtotime($employee['dob'])); ?></td>
<?php /*
<!-- <td><?php //echo isset($employee['policy_type']) ? $employee['policy_type'] : ''; ?> - <?php echo isset($employee['policy_no']) ? $employee['policy_no'] : ''; ?></td>

View File

@ -188,7 +188,7 @@ beccause = dataTables_length and dataTables_paginate need in same line thats why
<td class="text-left"><?php echo $row['assignee_name'] ? $row['assignee_name'] : 'N/A'; ?></td>
<td class="text-left">
<?php if (!empty($row['created_at'])):
$cd = date("j M Y", strtotime($row['created_at']));
$cd = date("j/m/Y", strtotime($row['created_at']));
$ct = date("h:i A", strtotime($row['created_at']));
echo $cd . "<br><span class='time'> " . $ct . "</span>";
endif;
@ -196,7 +196,7 @@ beccause = dataTables_length and dataTables_paginate need in same line thats why
</td>
<td class="text-left">
<?php if (!empty($row['updated_at'])):
$ud = date("j M Y", strtotime($row['updated_at']));
$ud = date("j/m/Y", strtotime($row['updated_at']));
$ut = date("h:i A", strtotime($row['updated_at']));
echo $ud . "<br><span class='time'> " . $ut . "</span>";
endif;

View File

@ -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) => {

View File

@ -80,7 +80,7 @@
var end = moment(end, 'DD/MM/YYYY');
function cb(start, end) {
$('#reportrange span').html(start.format('D-MM-YYYY') + ' - ' + end.format('D-MM-YYYY'));
$('#reportrange span').html(start.format('D/MM/YYYY') + ' - ' + end.format('D/MM/YYYY'));
$('#startDate').val(start.format('DD-MM-YYYY'));
$('#endDate').val(end.format('DD-MM-YYYY'));
}

View File

@ -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">&times;</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 -->

View File

@ -1,12 +1,79 @@
<?php
helper('datatable_view');
$vd_header_labels = [
'Date', 'Record Date', 'Unit', 'Policy', 'Endorsement No', 'Sub Type',
'Credit', 'Debit', 'Balance', 'Description', 'User',
];
$vd_col_count = count($vd_header_labels);
$vd_col_max_len = array_fill(0, $vd_col_count, 0);
for ($i = 0; $i < $vd_col_count; $i++) {
$vd_col_max_len[$i] = mb_strlen($vd_header_labels[$i]);
}
$depositdata = $depositdata ?? [];
$subTypeOptions = $subTypeOptions ?? [];
foreach ($depositdata as $row) {
$vd_col_max_len[0] = max($vd_col_max_len[0], mb_strlen(date('d-M-Y h:i A', strtotime((string) $row->created_at))));
$rec = ! empty($row->record_date) ? date('d-M-Y', strtotime((string) $row->record_date)) : '-';
$vd_col_max_len[1] = max($vd_col_max_len[1], mb_strlen($rec));
$vd_col_max_len[2] = max($vd_col_max_len[2], mb_strlen(trim((string) ($row->unit ?? ' - '))));
$policyType = $row->policy_type ?? '';
$policyNo = $row->policy_no ?? '';
$policyCell = ($policyType || $policyNo) ? trim($policyType . ' - ' . $policyNo, ' -') : '-';
$vd_col_max_len[3] = max($vd_col_max_len[3], mb_strlen($policyCell));
$vd_col_max_len[4] = max($vd_col_max_len[4], mb_strlen(strip_tags((string) ($row->endorsement_no ?? '-'))));
$subLabel = isset($subTypeOptions[$row->sub_type]) ? (string) $subTypeOptions[$row->sub_type] : '';
$vd_col_max_len[5] = max($vd_col_max_len[5], mb_strlen($subLabel));
$credit = ($row->transaction_type ?? '') === 'Credit' ? (string) ($row->amount ?? '-') : '-';
$debit = ($row->transaction_type ?? '') === 'Debit' ? (string) ($row->amount ?? '-') : '-';
$vd_col_max_len[6] = max($vd_col_max_len[6], mb_strlen($credit));
$vd_col_max_len[7] = max($vd_col_max_len[7], mb_strlen($debit));
$vd_col_max_len[8] = max($vd_col_max_len[8], mb_strlen((string) ($row->balance ?? '')));
$vd_col_max_len[9] = max($vd_col_max_len[9], mb_strlen((string) ($row->description ?? '')));
$vd_col_max_len[10] = max($vd_col_max_len[10], mb_strlen((string) ($row->username ?? '')));
}
$vd_col_min_px = [120, 100, 72, 160, 110, 88, 150, 150, 170, 140, 100];
$vd_col_max_px = [200, 120, 120, 360, 160, 140, 180, 180, 220, 400, 200];
$vd_col_width_px = nhance_dt_column_widths_px($vd_header_labels, $vd_col_max_len, $vd_col_min_px, $vd_col_max_px);
?>
<style>
.table th,
.table td {
padding: 8px;
}
table.dataTable tbody td {
padding: 4px 4px !important;
}
<?php for ($i = 0; $i < $vd_col_count; $i++) : ?>
#scroll-horizontal-datatable_wrapper .dataTables_scrollHead table thead th:nth-child(<?= $i + 1 ?>),
#scroll-horizontal-datatable thead th:nth-child(<?= $i + 1 ?>) {
min-width: <?= (int) $vd_col_width_px[$i] ?>px;
width: <?= (int) $vd_col_width_px[$i] ?>px;
box-sizing: border-box;
vertical-align: middle;
overflow: visible !important;
}
#scroll-horizontal-datatable tbody td:nth-child(<?= $i + 1 ?>) {
width: <?= (int) $vd_col_width_px[$i] ?>px;
max-width: <?= (int) $vd_col_width_px[$i] ?>px;
min-width: <?= (int) $vd_col_width_px[$i] ?>px;
box-sizing: border-box;
vertical-align: middle;
}
<?php endfor; ?>
#scroll-horizontal-datatable tbody td {
padding: 5px 11px !important;
font-size: 13px;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
}
#scroll-horizontal-datatable_wrapper .dataTables_scrollHead table thead th,
#scroll-horizontal-datatable thead th {
padding: 5px 11px !important;
font-size: 13px;
line-height: 1.25;
}
#scroll-horizontal-datatable_wrapper .dataTables_scrollHead table,
#scroll-horizontal-datatable_wrapper .dataTables_scrollBody table {
table-layout: fixed;
width: 100% !important;
}
#List-page .card-body > .table-responsive {
overflow-x: auto;
}
.deposit-header {
display: flex;
align-items: center;
@ -31,6 +98,165 @@
}
.dataTables_length label {height: 21px !important;}
#scroll-horizontal-datatable tbody tr:hover { background-color: #e0e0e0; }
.deposit-inline-cell {
display: flex;
align-items: center;
gap: 6px;
}
.deposit-inline-input {
width: 150px;
max-width: 100%;
}
.deposit-inline-input-disabled {
background-color: #f4f5f7 !important;
cursor: not-allowed;
}
.deposit-balance-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
}
.deposit-history-icon {
color: #0ea5b7;
cursor: pointer;
font-size: 16px;
line-height: 1;
}
#moreInfoModal .modal-dialog {
max-width: 520px;
}
#moreInfoModal .modal-content {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 14px;
color: #111827;
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.12);
}
#moreInfoModal .modal-header,
#moreInfoModal .modal-footer {
border-color: #e5e7eb;
padding: 14px 18px;
background: #ffffff;
}
#moreInfoModal .modal-body {
padding: 0 18px 16px;
background: #ffffff;
}
#moreInfoModal .deposit-history-title-wrap {
display: flex;
align-items: center;
gap: 10px;
}
#moreInfoModal .deposit-history-title-icon {
width: 34px;
height: 34px;
border-radius: 8px;
background: #0ea5b7;
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
#moreInfoModal .modal-title {
font-size: 18px;
font-weight: 600;
color: #111827;
margin: 0;
}
#moreInfoModal .deposit-history-close-btn {
width: 34px;
height: 34px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #ffffff;
color: #6b7280;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
}
#moreInfoModal .deposit-history-close-btn:hover {
background: #f3f4f6;
color: #111827;
}
#moreInfoModal .deposit-history-amount-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 14px;
}
#moreInfoModal .deposit-history-amount-card {
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 12px 14px;
}
#moreInfoModal .deposit-history-amount-label {
font-size: 11px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #6b7280;
margin-bottom: 6px;
font-weight: 600;
}
#moreInfoModal .deposit-history-amount-value {
font-size: 24px;
line-height: 1.2;
font-weight: 700;
color: #111827;
word-break: break-word;
}
#moreInfoModal .deposit-history-meta-box {
border: 1px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
background: #f9fafb;
}
#moreInfoModal .deposit-history-meta-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 14px;
background: #ffffff;
}
#moreInfoModal .deposit-history-meta-row + .deposit-history-meta-row {
border-top: 1px solid #e5e7eb;
}
#moreInfoModal .deposit-history-meta-label {
display: inline-flex;
align-items: center;
gap: 8px;
color: #6b7280;
font-size: 14px;
}
#moreInfoModal .deposit-history-meta-value {
color: #111827;
font-size: 14px;
font-weight: 500;
text-align: right;
}
#moreInfoModal .deposit-history-footer-btn {
border: 1px solid #d1d5db;
border-radius: 8px;
background: #ffffff;
color: #374151;
padding: 6px 16px;
font-size: 14px;
}
#moreInfoModal .deposit-history-footer-btn:hover {
background: #f3f4f6;
color: #111827;
}
.deposit-inline-actions {
display: inline-flex;
gap: 4px;
}
.deposit-inline-actions .btn {
padding: 2px 6px;
line-height: 1.2;
}
@media (max-width: 767.98px) {
.deposit-title {
font-size: 22px;
@ -123,7 +349,7 @@
</div> -->
<div class="table-responsive">
<table data-custom-table-css="table" class="table mb-0 nowrap w-100 table-centered" cellspacing="0" id="scroll-horizontal-datatable">
<table data-custom-table-css="table" class="table mb-0 nowrap w-100" cellspacing="0" id="scroll-horizontal-datatable">
<thead class="bg-light">
<tr>
<th class="font-weight-medium">Date</th>
@ -141,7 +367,26 @@
</thead>
<tbody>
<?php foreach($depositdata as $row) { ?>
<tr id="<?php echo $row->id;?>">
<?php
$isCreditRow = ($row->transaction_type == 'Credit');
$isDebitRow = ($row->transaction_type == 'Debit');
$creditAmount = $isCreditRow ? (float) $row->amount : 0;
$debitAmount = $isDebitRow ? (float) $row->amount : 0;
$hasAmtHistory = isset($row->new_amt) && trim((string) $row->new_amt) !== '';
$historyOldAmt = trim((string) ($row->old_amt ?? '-')) !== '' ? (string) $row->old_amt : '-';
$historyNewAmt = trim((string) ($row->new_amt ?? '-')) !== '' ? (string) $row->new_amt : '-';
$historyUpdatedBy = trim((string) ($row->amt_updated_by_name ?? '')) !== ''
? (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))
: '-';
?>
<tr id="<?php echo $row->id;?>"
data-row-id="<?= (int) $row->id ?>"
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 isset($row->record_date)? date('d-M-Y', strtotime($row->record_date)):'-' ?></td>
<td><?php echo $row->unit ?? ' - '; ?></td>
@ -154,17 +399,46 @@
if ($policyType || $policyNo) {
echo trim($policyType . ' - ' . $policyNo, ' -');
} else {
echo '<center> - </center>';
echo '-';
}
?>
</td>
<td><?php echo $row->endorsement_no ?? '<center> - </center>'; ?></td>
<td><?php echo $row->endorsement_no ?? '-'; ?></td>
<td><?php echo isset($subTypeOptions[$row->sub_type]) ? $subTypeOptions[$row->sub_type] : ''; ?>
</td>
<td><?php echo ($row->transaction_type == 'Credit') ? $row->amount : '-'; ?></td>
<td><?php echo ($row->transaction_type == 'Debit') ? $row->amount : '-'; ?></td>
<td><?php echo $row->balance; ?></td>
<td><?php echo $row->description; ?></td>
<td class="deposit-inline-edit-cell" data-type="credit">
<div class="deposit-inline-cell">
<input type="text" class="form-control form-control-sm deposit-inline-input <?= $isCreditRow ? '' : 'deposit-inline-input-disabled' ?>" placeholder="-" value="<?= $isCreditRow ? esc((string) $row->amount, 'attr') : '' ?>" <?= $isCreditRow ? '' : 'disabled' ?>>
</div>
</td>
<td class="deposit-inline-edit-cell" data-type="debit">
<div class="deposit-inline-cell">
<input type="text" class="form-control form-control-sm deposit-inline-input <?= $isDebitRow ? '' : 'deposit-inline-input-disabled' ?>" placeholder="-" value="<?= $isDebitRow ? esc((string) $row->amount, 'attr') : '' ?>" <?= $isDebitRow ? '' : 'disabled' ?>>
</div>
</td>
<td class="deposit-balance-cell">
<span class="deposit-balance-wrap">
<span><?php echo $row->balance; ?></span>
<?php if ($hasAmtHistory) : ?>
<i class="mdi mdi-information-outline deposit-history-icon js-deposit-history-trigger"
data-old-amt="<?= esc($historyOldAmt, 'attr') ?>"
data-new-amt="<?= esc($historyNewAmt, 'attr') ?>"
data-updated-by="<?= esc($historyUpdatedBy, 'attr') ?>"
data-updated-at="<?= esc($historyUpdatedAt, 'attr') ?>"
onclick="openDepositHistoryModal(this); return false;"
aria-label="Amount change history"
tabindex="0"></i>
<?php endif; ?>
</span>
</td>
<td class="deposit-description-cell"
data-toggle="tooltip"
data-bs-toggle="tooltip"
data-placement="top"
data-bs-placement="top"
title="<?= esc((string) ($row->description ?? ''), 'attr') ?>">
<?php echo $row->description; ?>
</td>
<td><?php echo $row->username; ?></td>
</tr>
<?php } ?>
@ -178,6 +452,95 @@
<!-- end col -->
</div>
</div>
<div class="modal fade" id="moreInfoModal" tabindex="-1" aria-labelledby="depositHistoryModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div class="deposit-history-title-wrap">
<span class="deposit-history-title-icon" aria-hidden="true">
<i class="mdi mdi-history"></i>
</span>
<h5 class="modal-title" id="depositHistoryModalLabel">Amount change history</h5>
</div>
<button type="button" class="deposit-history-close-btn" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close">
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="modal-body">
<div class="deposit-history-amount-grid">
<div class="deposit-history-amount-card">
<div class="deposit-history-amount-label">Old amount</div>
<div class="deposit-history-amount-value" id="history_old_amt">-</div>
</div>
<div class="deposit-history-amount-card">
<div class="deposit-history-amount-label">New amount</div>
<div class="deposit-history-amount-value" id="history_new_amt">-</div>
</div>
</div>
<div class="deposit-history-meta-box">
<div class="deposit-history-meta-row">
<div class="deposit-history-meta-label">
<i class="mdi mdi-account-outline"></i>
<span>Changed 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>
</div>
<div class="deposit-history-meta-value" id="history_updated_at">-</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="deposit-history-footer-btn" data-dismiss="modal" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
function formatDepositHistoryAmount(value) {
if (value === null || value === undefined) {
return '-';
}
var raw = String(value).trim();
if (raw === '' || raw === '-') {
return '-';
}
var num = parseFloat(raw.replace(/,/g, ''));
if (isNaN(num)) {
return raw;
}
return '₹' + num.toLocaleString('en-IN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
function openDepositHistoryModal(iconEl) {
var $icon = $(iconEl);
$('#history_old_amt').text(formatDepositHistoryAmount($icon.data('old-amt')));
$('#history_new_amt').text(formatDepositHistoryAmount($icon.data('new-amt')));
$('#history_updated_by').text($icon.data('updated-by') || '-');
$('#history_updated_at').text($icon.data('updated-at') || '-');
var modalElement = document.getElementById('moreInfoModal');
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal !== 'undefined' && modalElement) {
var myModal = new bootstrap.Modal(document.getElementById('moreInfoModal'));
myModal.show();
return;
}
if (typeof $('#moreInfoModal').modal === 'function') {
$('#moreInfoModal').modal('show');
return;
}
toastr.warning('History view is not available right now.');
}
</script>
<!-- Add this modal markup at the end of your HTML body -->
<!-- Button to trigger modal -->
@ -304,8 +667,42 @@
allowInput: false,
});
$('#scroll-horizontal-datatable').DataTable({
scrollX: true,
nhanceListDataTableBeforeInit();
var depositPageStorageKey = 'view_deposit_datatable_page_' + window.location.pathname + window.location.search;
function getStoredDepositPage() {
var page = parseInt(sessionStorage.getItem(depositPageStorageKey), 10);
return isNaN(page) || page < 0 ? 0 : page;
}
function storeDepositPage(pageNumber) {
sessionStorage.setItem(depositPageStorageKey, String(pageNumber));
}
window.storeCurrentDepositTablePage = function () {
try {
var table = $('#scroll-horizontal-datatable').DataTable();
storeDepositPage(table.page());
} catch (e) {
// No-op when table is not available.
}
};
function initDepositDescriptionTooltips() {
var selector = '#scroll-horizontal-datatable [data-bs-toggle="tooltip"]';
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Tooltip !== 'undefined') {
document.querySelectorAll(selector).forEach(function (el) {
var existing = bootstrap.Tooltip.getInstance(el);
if (existing) {
existing.dispose();
}
new bootstrap.Tooltip(el, { container: 'body' });
});
return;
}
if (typeof $.fn.tooltip === 'function') {
var $targets = $('#scroll-horizontal-datatable [data-toggle="tooltip"]');
$targets.tooltip('dispose');
$targets.tooltip({ container: 'body' });
}
}
var vdDepositTable = $('#scroll-horizontal-datatable').DataTable(nhanceMergeListDataTableOptions({
// dom: "<'row'<'col-sm-2'f><'col-sm-10 text-right'B>>" +
// "<'row'<'col-sm-12'tr>>" +
// "<'row'<'col-sm-5'i><'col-sm-7'p>>",
@ -363,6 +760,17 @@
myModal.show();
}
},
{
text: '<i class="mdi mdi-content-save"></i><span class=" btn-custom"> Save </span>',
className: 'btn app-btn-primary mr-2 deposit-top-save-btn d-none',
action: function () {
if (typeof window.saveDepositInlineChanges === 'function') {
window.saveDepositInlineChanges();
} else {
toastr.warning('Save handler is not ready yet.');
}
}
},
{
extend: 'collection',
text: '<span class=" btn-custom"> Export </span><i class="mdi mdi-menu-down"></i>',
@ -400,8 +808,35 @@
emptyTable: '<div class="text-center text-muted">No Data found</div>'
},
ordering: false,
paging: true
});
paging: true,
autoWidth: false,
columnDefs: [
<?php for ($i = 0; $i < $vd_col_count; $i++) : ?>
{ targets: <?= $i ?>, width: '<?= (int) $vd_col_width_px[$i] ?>px' },
<?php endfor; ?>
],
}));
vdDepositTable.on('page.dt', function () {
storeDepositPage(vdDepositTable.page());
});
initDepositDescriptionTooltips();
vdDepositTable.on('draw', function () {
initDepositDescriptionTooltips();
});
var selectedPage = getStoredDepositPage();
if (selectedPage > 0 && selectedPage < vdDepositTable.page.info().pages) {
vdDepositTable.page(selectedPage).draw('page');
}
nhanceListDataTableAfterInit();
nhanceListDataTableBindAdjust(vdDepositTable);
$(document).on('click keydown', '.js-deposit-history-trigger', function (e) {
if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') {
return;
}
e.preventDefault();
openDepositHistoryModal(this);
});
});
</script>
@ -509,6 +944,9 @@
// Handle success response
toastr.success("Transaction saved successfully", "Success");
// Reload the page or perform other actions as needed
if (typeof window.storeCurrentDepositTablePage === 'function') {
window.storeCurrentDepositTablePage();
}
location.reload();
},
error: function(xhr, status, error) {
@ -540,6 +978,9 @@
toastr.error('An unexpected error occurred. Please try again later.', 'Error');
}
if (typeof window.storeCurrentDepositTablePage === 'function') {
window.storeCurrentDepositTablePage();
}
location.reload(); // Uncomment if you need to reload the page on error
}
});
@ -566,4 +1007,237 @@
$(this).removeClass('is-invalid');
}
});
</script>
<script>
$(document).ready(function () {
var numericRegex = /^[0-9]+(\.[0-9]{1,2})?$/;
function formatAmount(value) {
var num = parseFloat(value);
if (isNaN(num)) {
return '0.00';
}
return num.toFixed(2);
}
function normalizeInput($input) {
var value = ($input.val() || '').replace(/[^0-9.]/g, '');
var parts = value.split('.');
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('');
parts = value.split('.');
}
if (parts[1] && parts[1].length > 2) {
value = parts[0] + '.' + parts[1].slice(0, 2);
}
$input.val(value);
}
function setRowDisplay($row, creditAmount, debitAmount) {
var $creditCell = $row.find('td[data-type="credit"]');
var $debitCell = $row.find('td[data-type="debit"]');
var creditNum = parseFloat(creditAmount) || 0;
var debitNum = parseFloat(debitAmount) || 0;
$row.attr('data-credit', creditNum);
$row.attr('data-debit', debitNum);
$creditCell.find('.deposit-inline-input').val(creditNum > 0 ? formatAmount(creditNum) : '');
$debitCell.find('.deposit-inline-input').val(debitNum > 0 ? formatAmount(debitNum) : '');
}
function recalculateVisibleBalances() {
var running = 0;
var rows = $('#scroll-horizontal-datatable tbody tr').get().reverse();
$(rows).each(function () {
var $row = $(this);
var credit = parseFloat($row.attr('data-credit')) || 0;
var debit = parseFloat($row.attr('data-debit')) || 0;
running += credit;
running -= debit;
$row.find('.deposit-balance-cell').text(formatAmount(running));
});
}
function getTopSaveButton() {
return $('.deposit-top-save-btn');
}
function hasPendingChanges() {
var pending = false;
$('#scroll-horizontal-datatable tbody tr').each(function () {
var $row = $(this);
var creditInputVal = ($row.find('td[data-type="credit"] .deposit-inline-input').val() || '').trim();
var debitInputVal = ($row.find('td[data-type="debit"] .deposit-inline-input').val() || '').trim();
var originalCredit = parseFloat($row.attr('data-credit')) || 0;
var originalDebit = parseFloat($row.attr('data-debit')) || 0;
var inputCredit = creditInputVal === '' ? 0 : parseFloat(creditInputVal);
var inputDebit = debitInputVal === '' ? 0 : parseFloat(debitInputVal);
if (isNaN(inputCredit) || isNaN(inputDebit)) {
return;
}
if (Math.abs(inputCredit - originalCredit) > 0.0001 || Math.abs(inputDebit - originalDebit) > 0.0001) {
pending = true;
return false;
}
});
return pending;
}
function updateTopSaveButtonVisibility() {
var $saveBtn = getTopSaveButton();
if (!$saveBtn.length) {
return;
}
$saveBtn.toggleClass('d-none', !hasPendingChanges());
}
$(document).on('input', '.deposit-inline-input', function () {
var $input = $(this);
normalizeInput($input);
updateTopSaveButtonVisibility();
});
function collectChanges() {
var changes = [];
var hasValidationError = false;
$('#scroll-horizontal-datatable tbody tr').each(function () {
var $row = $(this);
var creditInputVal = ($row.find('td[data-type="credit"] .deposit-inline-input').val() || '').trim();
var debitInputVal = ($row.find('td[data-type="debit"] .deposit-inline-input').val() || '').trim();
var originalCredit = parseFloat($row.attr('data-credit')) || 0;
var originalDebit = parseFloat($row.attr('data-debit')) || 0;
var hasCredit = creditInputVal !== '';
var hasDebit = debitInputVal !== '';
if (!hasCredit && !hasDebit) {
return;
}
if (hasCredit && hasDebit) {
toastr.warning('Please fill only one of Credit or Debit per row.');
hasValidationError = true;
return false;
}
var type = hasCredit ? 'credit' : 'debit';
var value = hasCredit ? creditInputVal : debitInputVal;
var amount = parseFloat(value);
var isChanged = (type === 'credit')
? (Math.abs(amount - originalCredit) > 0.0001)
: (Math.abs(amount - originalDebit) > 0.0001);
// Skip untouched rows (important for legacy 0/-ve values)
if (!isChanged) {
return;
}
if (!numericRegex.test(value)) {
toastr.warning('Enter a valid amount (up to 2 decimals).');
hasValidationError = true;
return false;
}
if (amount <= 0) {
toastr.warning('Amount must be greater than 0.');
hasValidationError = true;
return false;
}
changes.push({
rowId: $row.data('row-id'),
type: type,
amount: amount,
$row: $row
});
});
if (hasValidationError) {
return false;
}
return changes;
}
window.saveDepositInlineChanges = function () {
var changes = collectChanges();
if (!Array.isArray(changes) || changes.length === 0) {
if (changes !== false) {
toastr.info('No changes to save.');
}
return;
}
var clientId = '<?= esc((string) $clientData->id, 'js') ?>';
var insurerId = '<?= esc((string) $insurerName->id, 'js') ?>';
var cdAcPk = $('#cd_ac_pk').val();
var completed = 0;
function saveNext(index) {
if (index >= changes.length) {
toastr.success('Saved ' + completed + ' change(s) successfully.');
setTimeout(function () {
if (typeof window.storeCurrentDepositTablePage === 'function') {
window.storeCurrentDepositTablePage();
}
location.reload();
}, 250);
return;
}
var change = changes[index];
var transactionType = (change.type === 'credit') ? 'Credit' : 'Debit';
$.ajax({
type: 'POST',
url: '<?= base_url() . "client/update_deposit_amount" ?>',
data: {
deposit_id: change.rowId,
amount: change.amount,
transaction_type: transactionType,
client_id: clientId,
insurer_id: insurerId,
cd_ac_pk: cdAcPk
},
success: function () {
if (transactionType === 'Credit') {
setRowDisplay(change.$row, change.amount, 0);
} else {
setRowDisplay(change.$row, 0, change.amount);
}
completed++;
saveNext(index + 1);
},
error: function (xhr) {
if (xhr.status === 400 && xhr.responseText) {
try {
var response = JSON.parse(xhr.responseText);
toastr.error(response.message || 'Validation failed.');
} catch (e) {
toastr.error('Validation failed.');
}
} else if (xhr.status === 404) {
toastr.error('Transaction not found for this account.');
} else {
toastr.error('Unable to update transaction right now.');
}
}
});
}
saveNext(0);
};
$(document).on('keydown', '.deposit-inline-input', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
if (typeof window.saveDepositInlineChanges === 'function') {
window.saveDepositInlineChanges();
}
}
});
updateTopSaveButtonVisibility();
});
</script>

363
hr-dashboard.md Normal file
View File

@ -0,0 +1,363 @@
# 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` (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 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 (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
(or route segment for `debug/{id}` / `preview/{id}`).
---
## Route stacks
### 1. Admin — `authMVC` (session login required)
Prefix: `util/claims-collection-v2`
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `util/claims-collection-v2/preview` | `::preview` | UI debug grid in browser |
| 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 34 KPIs |
| GET | `util/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
**Filters applied:** `authMVC`, `AclFilter`, `HttpRequestLog`, `Cors`, `SecurityInputFilter`
---
### 2. Frontend / HR App — `authJWT` (Bearer token required)
Prefix: `employeeRest/claims-collection-v2`
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| 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 |
| GET | `employeeRest/claims-collection-v2/debug` | `::debug` | Raw JSON (FE debug) |
| GET | `employeeRest/claims-collection-v2/debug/{policy_id}` | `::debug` | Same, policy in URL |
**Filters applied:** `GlobalPostFileUploadGuard`, `ratelimit`, `appSignature`, `authJWT`
---
## Parameters
| Param | Location | Type | Required | Notes |
|-------|----------|------|----------|-------|
| `client_policy` | query string | int | **Yes** (API) | Policy ID |
| `client_policy_id` | query string | int | **Yes** (API) | Alias for above |
| `{policy_id}` | URL segment | int | No | Only on `preview/{id}` and `debug/{id}`; falls back to 4687 if omitted |
| `{slug\|id}` | URL segment | string/int | Yes (kpi only) | KPI method slug OR Metabase question id |
> **Default policy ID (4687)** is only the method-signature default for `preview` and `debug`.
> Change it by passing the query param or URL segment — never hardcoded elsewhere.
---
## How to call — Admin (browser / Postman, session cookie)
Open in browser while logged in to admin:
```
# UI preview page (loads grid, click "Load all KPIs")
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 34 KPIs
GET /index.php/util/claims-collection-v2/debug?client_policy=4687
# Raw JSON — single KPI by slug
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
```
---
## How to call — Frontend / HR App (JWT)
```http
GET /index.php/employeeRest/claims-collection-v2/all?client_policy=4687
Authorization: Bearer <jwt_token>
X-App-Signature: <app_signature>
Content-Type: application/json
```
```http
GET /index.php/employeeRest/claims-collection-v2/kpi/incurred_ratio?client_policy=4687
Authorization: Bearer <jwt_token>
X-App-Signature: <app_signature>
```
```http
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>
```
---
## Sample responses
### `kpi/{slug}` — single-row KPI
```json
{
"status": true,
"policy_id": 4687,
"kpi_id": 207,
"kpi": "incurred_ratio",
"label": "Incurred Ratio",
"rows": [
{ "incurred_ratio": "72.34%" }
]
}
```
### `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
{
"status": true,
"policy_id": 4687,
"data": {
"policy_exposure_summary": {
"id": 181,
"label": "POLICY & EXPOSURE SUMMARY",
"rows": [
{
"policy_start_date": "2024-01-01",
"policy_end_date": "2024-12-31",
"insurer_name": "HDFC Ergo",
"tpa_name": "Medi Assist"
}
]
},
"incurred_ratio": {
"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" }
]
}
}
}
```
### Error — missing policy id
```json
{
"status": false,
"message": "client_policy or client_policy_id is required."
}
```
**HTTP 422**
### Error — unknown KPI
```json
{
"status": false,
"message": "Unknown KPI. Pass Metabase id or method slug.",
"allowed": {
"181": "policy_exposure_summary",
"207": "incurred_ratio",
"232": "inception_emp_lives",
"233": "current_emp_lives",
"234": "claim_value_by_month"
}
}
```
**HTTP 404** — `allowed` lists the full `KPI_MAP` (34 entries).
---
## All 34 KPIs
| 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 **232234** 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 232234
| 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` | 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 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 (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 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, 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 (expects `KPI_MAP` count === 34).

1048639
metabase_raw_queries.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -47,12 +47,61 @@
return true;
}
function isExcludedCharsetField(el) {
if (!el) {
return false;
}
if ($(el).attr('data-parsley-exclude-charset') === 'true') {
return true;
}
var id = (el.id || '').toLowerCase();
var name = (el.name || '').toLowerCase();
return id === 'policy_no' || id === 'endorsement_no' ||
name === 'policy_no' || name === 'endorsement_no';
}
function clearExcludedFieldValidation($form) {
if (!$form || !$form.length) {
return;
}
$form.find('input, textarea, select').each(function () {
var el = this;
if (!isExcludedCharsetField(el)) {
return;
}
var $el = $(el);
$el.removeAttr('data-parsley-endorsementcharset');
$el.attr('data-parsley-validate', 'false');
$el.removeClass('parsley-error parsley-success');
$el.siblings('ul.parsley-errors-list').remove();
$el.closest('.form-group').find('ul.parsley-errors-list').remove();
if (typeof $el.parsley === 'function') {
try {
var fieldInstance = $el.parsley();
if (fieldInstance && typeof fieldInstance.reset === 'function') {
fieldInstance.reset();
}
if (fieldInstance && typeof fieldInstance.destroy === 'function') {
fieldInstance.destroy();
}
} catch (e) {
// no-op
}
}
});
refreshParsley($form);
}
function registerValidator() {
if (!isParsleyReady() || window.Parsley.__endorsementFormValidatorRegistered) {
return;
}
window.Parsley.addValidator('endorsementcharset', {
validateString: function (value) {
var el = this.$element && this.$element.length ? this.$element[0] : null;
if (isExcludedCharsetField(el)) {
return true;
}
if (!value || String(value).trim() === '') {
return true;
}
@ -85,6 +134,12 @@
return;
}
if (isExcludedCharsetField(el)) {
$el.removeAttr('data-parsley-endorsementcharset');
$el.attr('data-parsley-validate', 'false');
return;
}
if (isCharsetCandidate(el) || isDocumentNameField(el)) {
if (!$el.attr('data-parsley-endorsementcharset')) {
$el.attr('data-parsley-endorsementcharset', 'true');
@ -144,7 +199,7 @@
}
function validateField(field) {
if (!field || isSkippableField(field) || !isParsleyReady()) {
if (!field || isSkippableField(field) || isExcludedCharsetField(field) || !isParsleyReady()) {
return;
}
var $field = $(field);
@ -181,6 +236,7 @@
registerValidator();
applyConstraints($form);
clearExcludedFieldValidation($form);
refreshParsley($form);
stripParsleyFormSubmitHandlers($form);
@ -237,18 +293,14 @@
initWithRetry(40);
});
$(window).on('load', function () {
if (!isParsleyReady()) {
window.clearEndorsementExcludedFieldsValidation = function (formSelector) {
var sel = formSelector || '#endorsement_form_id';
var $form = $(sel);
if (!$form.length || !isParsleyReady()) {
return;
}
FORM_SELECTORS.forEach(function (selector) {
var $f = $(selector);
if ($f.length) {
refreshParsley($f);
stripParsleyFormSubmitHandlers($f);
}
});
});
clearExcludedFieldValidation($form);
};
window.refreshEndorsementFormValidation = function (formSelector) {
if (!formSelector || !isParsleyReady()) {
@ -271,12 +323,32 @@
stripOrphanParsleyUi($form);
$form.removeData(LIVE_VALIDATE_DATA);
try {
clearExcludedFieldValidation($form);
refreshParsley($form);
stripParsleyFormSubmitHandlers($form);
} catch (e2) {
// no-op
}
};
// form-validation.init.js binds all .parsley-examples after this script loads
$(window).on('load', function () {
window.setTimeout(function () {
if (!isParsleyReady()) {
return;
}
FORM_SELECTORS.forEach(function (selector) {
var $f = $(selector);
if (!$f.length) {
return;
}
applyConstraints($f);
clearExcludedFieldValidation($f);
refreshParsley($f);
stripParsleyFormSubmitHandlers($f);
});
}, 0);
});
})(function () {
return window.jQuery;
});

View File

@ -0,0 +1,156 @@
<?php
/**
* One-off smoke test: Claims Collection V2 dashboard.
* Run: php tests/smoke_claims_collection_v2.php [policy_id]
*/
declare(strict_types=1);
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;
}
// Load autoloaded services without full HTTP stack.
helper('url');
use App\Controllers\ClaimsCollectionV2DashboardController;
use App\Models\ClaimsCollectionV2DashboardModel;
use Config\Services;
$policyId = isset($argv[1]) ? (int) $argv[1] : 4687;
$pass = 0;
$fail = 0;
$results = [];
function ok(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 ClaimsCollectionV2DashboardModel();
$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP;
ok('KPI_MAP count', count($kpiMap) === 34, (string) count($kpiMap));
$uniqueMethods = array_unique(array_values($kpiMap));
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));
}
ok('id 207 maps to incurred_ratio', ($kpiMap[207] ?? '') === 'incurred_ratio');
ok('id 181 maps to policy_exposure_summary', ($kpiMap[181] ?? '') === 'policy_exposure_summary');
try {
$rows = $model->policy_exposure_summary($policyId);
ok('model policy_exposure_summary', is_array($rows), 'rows=' . count($rows));
} catch (Throwable $e) {
ok('model policy_exposure_summary', false, $e->getMessage());
}
try {
$rows = $model->getKpi('incurred_ratio', $policyId);
ok('model getKpi(incurred_ratio)', is_array($rows), 'rows=' . count($rows));
} catch (Throwable $e) {
ok('model getKpi(incurred_ratio)', false, $e->getMessage());
}
$request = Services::request(null, false);
$response = Services::response();
$request->setGlobal('get', ['client_policy' => (string) $policyId]);
$controller = new ClaimsCollectionV2DashboardController();
$controller->initController($request, $response, service('logger'));
$slugResp = json_decode($controller->kpi('incurred_ratio')->getJSON(), true);
ok('controller kpi by slug', ($slugResp['status'] ?? false) === true && ($slugResp['kpi'] ?? '') === 'incurred_ratio');
$idResp = json_decode($controller->kpi('207')->getJSON(), true);
ok('controller kpi by id 207', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 207);
$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'] ?? []) === 34);
$debugOut = $controller->debug($policyId);
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
$debugJson = json_decode($debugBody, true);
ok('controller debug JSON', ($debugJson['status'] ?? false) === true && isset($debugJson['data']));
$previewOut = $controller->preview($policyId);
$previewHtml = is_string($previewOut) ? $previewOut : $previewOut->getBody();
ok('controller preview HTML', str_contains($previewHtml, 'Claims Collection V2') && str_contains($previewHtml, 'kpi-grid'));
$routeCollection = Services::routes(true);
$mvcRoutes = [];
$jwtRoutes = [];
foreach ($routeCollection->getRoutes('get') as $pattern => $handler) {
if (! str_contains($pattern, 'claims-collection-v2')) {
continue;
}
if (str_starts_with($pattern, 'util/')) {
$mvcRoutes[] = $pattern;
}
if (str_starts_with($pattern, 'employeeRest/')) {
$jwtRoutes[] = $pattern;
}
}
ok('MVC claims-collection-v2 routes registered', count($mvcRoutes) >= 5, implode(', ', $mvcRoutes) ?: 'none');
ok('JWT claims-collection-v2 routes registered', count($jwtRoutes) >= 5, implode(', ', $jwtRoutes) ?: 'none');
$baseUrl = rtrim((string) env('app.baseURL', ''), '/');
if ($baseUrl !== '') {
$results[] = '';
$results[] = '=== HTTP auth gate checks (no session/token) ===';
$urls = [
'MVC preview' => $baseUrl . '/util/claims-collection-v2/preview?client_policy=' . $policyId,
'MVC kpi slug' => $baseUrl . '/util/claims-collection-v2/kpi/incurred_ratio?client_policy=' . $policyId,
'MVC kpi id' => $baseUrl . '/util/claims-collection-v2/kpi/207?client_policy=' . $policyId,
'MVC debug' => $baseUrl . '/util/claims-collection-v2/debug?client_policy=' . $policyId,
'JWT kpi slug' => $baseUrl . '/employeeRest/claims-collection-v2/kpi/incurred_ratio?client_policy=' . $policyId,
'JWT debug' => $baseUrl . '/employeeRest/claims-collection-v2/debug?client_policy=' . $policyId,
];
foreach ($urls as $label => $url) {
$ctx = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]);
$body = @file_get_contents($url, false, $ctx);
$code = 0;
if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) {
$code = (int) $m[1];
}
$blocked = in_array($code, [401, 403, 302, 303], true);
$reachable = $code >= 200 && $code < 500;
ok("HTTP {$label} (" . ($code ?: 'no connection') . ')', $blocked || $reachable, $url);
}
} else {
$results[] = '[SKIP] HTTP checks — app.baseURL not set in .env';
}
ob_end_clean();
echo '=== Claims Collection V2 smoke test (policy_id=' . $policyId . ') ===' . PHP_EOL . PHP_EOL;
echo implode(PHP_EOL, $results) . PHP_EOL;
echo PHP_EOL . "=== Summary: {$pass} passed, {$fail} failed ===" . PHP_EOL;
exit($fail > 0 ? 1 : 0);