diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 4764e7e1..360c6b8c 100755
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -213,6 +213,7 @@ $routes->group("/employee", ["filter" => "authMVC"], function ($routes) {
$routes->post('get_emp_history','EmployeeController::getEmpHistory');
$routes->get("retail-endorsement-list", "EmployeeController::retailendorsementlist");
$routes->post("retail-endorsement-save", "EmployeeController::retailendorsementsave");
+ $routes->get("getTPADataVariationReport/(:num)", "EmployeeController::getTPADataVariationReport/$1");
});
diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php
index dc72314e..32217c91 100755
--- a/app/Controllers/EmployeeController.php
+++ b/app/Controllers/EmployeeController.php
@@ -28,6 +28,7 @@ use App\Models\PolicyPremium2Model;
use App\Models\AuditHistoryModel;
use App\Models\UserModel;
use App\Models\PartnerEndorsementRequestModel;
+use App\Models\TpaApiDataModel;
use App\Controllers\Jobs;
@@ -42,6 +43,8 @@ use CodeIgniter\API\ResponseTrait;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
use Dompdf\Dompdf;
use Dompdf\Options;
@@ -3579,5 +3582,388 @@ class EmployeeController extends AdminController
}
}
+ public function getTPADataVariationReport($file_id)
+ {
+ $file_info = $this->batchFileModel->where('id', $file_id)->find();
+ $client_id = $file_info[0]['client_id'];
+ $client_policy_id = $file_info[0]['client_policy_id'];
+ $TpaApiDataModel = new TpaApiDataModel();
+ $emp_data_wo_tpa_id = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id);
+
+
+ //loop emp data with TPA data for matches
+ foreach ($emp_data_wo_tpa_id as $db_key => $db_row)
+ {
+ //get TPA API data from table for current DB ep code
+ $tpa_temp_data = $TpaApiDataModel->select('*')
+ ->where('emp_code', $db_row['emp_code'])
+ ->where('file_id', $file_id)
+ ->where('is_active', 1)
+ ->findAll();
+ $match= $this->reconcileDbWithTpa($db_row,$tpa_temp_data);
+ $emp_data_wo_tpa_id[$db_key]['match'] = $match;
+ }
+
+ // d($emp_data_wo_tpa_id);
+
+ // die();
+ //not_in_tpa
+ $tpa_emp_codes = $TpaApiDataModel->select('emp_code')
+ ->where('file_id', $file_id)
+ ->where('is_active', 1)
+ ->groupBy('emp_code')
+ ->findAll();
+ $tpa_emp_codes = array_column($tpa_emp_codes, 'emp_code');
+
+ $not_in_tpa = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id,$tpa_emp_codes);
+ // d($not_in_tpa);die();
+
+ // not_in_nhance
+ $master_emp_codes = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id,[],true);
+ $master_emp_codes = array_column($master_emp_codes, 'emp_code');
+
+ $not_in_nhance = $TpaApiDataModel->select('*')
+ ->where('is_active',1)
+ ->where('file_id',$file_id)
+ ->whereNotIn('emp_code',$master_emp_codes)
+ ->findAll();
+ // d($not_in_nhance);die();
+
+ if( !empty($not_in_tpa) || !empty($not_in_nhance) || !empty($emp_data_wo_tpa_id) )
+ {
+ $this->exportVariationReportExcel($not_in_tpa,$not_in_nhance,$emp_data_wo_tpa_id);
+ }
+ else
+ {
+ return false;
+ }
+ // $this->exportVariationReportExcel([],[],[]);
+
+
+ }
+
+
+ // not in use once all functionality workes well in this funciton then remvoe this function
+ function compareDbWithTpa(array $db, array $tpaRows): array
+ {
+ $partialMatches = [];
+
+ // Normalize helper
+ $normalizeName = function ($name) {
+ return strtolower(
+ preg_replace('/[.\s_]+/', '', trim($name))
+ );
+ };
+
+ foreach ($tpaRows as $tpa) {
+
+ // 0️⃣ emp_code must match
+ if (($db['emp_code'] ?? '') !== ($tpa['emp_code'] ?? '')) {
+ continue;
+ }
+
+ $relationMatch = strtolower($db['relation'] ?? '') === strtolower($tpa['relation'] ?? '');
+ $genderMatch = strtoupper($db['gender'] ?? '') === strtoupper($tpa['gender'] ?? '');
+ $dobMatch = ($db['dob'] ?? '') === ($tpa['dob'] ?? '');
+
+ $nameMatch = $normalizeName($db['name'] ?? '') ===
+ $normalizeName($tpa['name'] ?? '');
+
+ // ✅ FULL MATCH
+ if ($nameMatch && $relationMatch && $genderMatch && $dobMatch) {
+ return [
+ 'match' => 'full_match',
+ 'record'=> $tpa
+ ];
+ }
+
+ // ⚠️ PARTIAL MATCH
+ if ($relationMatch || $genderMatch || $dobMatch) {
+ $partialMatches[] = [
+ 'record' => $tpa,
+ 'matched_on' => [
+ 'relation' => $relationMatch,
+ 'gender' => $genderMatch,
+ 'dob' => $dobMatch
+ ]
+ ];
+ }
+ }
+
+ // If no full match but partial exists
+ if (!empty($partialMatches)) {
+ return [
+ 'match' => 'partial_match',
+ 'candidates' => $partialMatches
+ ];
+ }
+
+ // Nothing matched
+ return [
+ 'match' => 'no_match'
+ ];
+ }
+
+
+ function reconcileDbWithTpa(array $db, array $tpaRows): array
+ {
+ // Name normalization
+ $normalizeName = function ($name) {
+ return strtolower(
+ preg_replace('/[.\s_]+/', '', trim($name))
+ );
+ };
+
+ foreach ($tpaRows as $tpa) {
+
+ // 1️⃣ emp_code + relation must match
+ if (
+ // ($db['emp_code'] ?? '') !== ($tpa['emp_code'] ?? '') ||
+ strtolower($db['relationship']) !== strtolower($tpa['relation'])
+ ) {
+ continue;
+ }
+
+ // 2️⃣ Field comparison
+ $diff = [];
+
+ if (
+ ($db['name'] ?? '') !==
+ ($tpa['name'] ?? '')
+ ) {
+ $diff[] = 'name';
+ }
+
+ if (($db['dob'] ?? '') !== ($tpa['dob'] ?? '')) {
+ $diff[] = 'dob';
+ }
+
+ if (
+ strtoupper($db['gender'] ?? '') !==
+ strtoupper($tpa['gender'] ?? '')
+ ) {
+ $diff[] = 'gender';
+ }
+
+ // 3️⃣ Match found
+ return [
+ 'status' => 'matched',
+ 'tpa_record' => $tpa,
+ 'not_matching' => $diff // empty = perfect match
+ ];
+ }
+
+ // 4️⃣ No match found
+ return [
+ 'status' => 'no_match'
+ ];
+ }
+
+
+ function exportVariationReportExcel(
+ array $notInTPA,
+ array $notInNhance,
+ array $reviewNeeded,
+ string $filename = 'employee_review.xlsx'
+) {
+
+ function setCell($sheet, int $col, int $row, $value)
+{
+ $cell = Coordinate::stringFromColumnIndex($col) . $row;
+ $sheet->setCellValue($cell, $value);
+}
+
+$EXPORT_COLUMNS = [
+
+ // Sheet 1 — Not in TPA
+ 'not_in_tpa' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relationship' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'mobile' => 'Mobile No',
+ 'email_corporate' => 'Corporate Email',
+ 'policy_no' => 'Policy No',
+ 'tpa_name' => 'TPA Name',
+ 'change_event' => 'Change Event',
+ ],
+
+ // Sheet 2 — Not in Nhance
+ 'not_in_nhance' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relation' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'age' => 'Age',
+ 'tpa_id' => 'TPA Member ID',
+ ],
+
+ // Sheet 3 — Review Needed (DB side)
+ 'review_main' => [
+ 'emp_code' => 'Employee Code',
+ 'name' => 'Employee Name',
+ 'relationship' => 'Relation',
+ 'dob' => 'Date of Birth',
+ 'gender' => 'Gender',
+ 'policy_no' => 'Policy No',
+ 'uhid' => 'UHID',
+ 'change_event' => 'Change event'
+ ],
+
+ // Sheet 3 — Review Needed (TPA side)
+ 'review_tpa' => [
+ 'emp_code' => 'TPA Employee Code',
+ 'name' => 'TPA Name',
+ 'relation' => 'TPA Relation',
+ 'dob' => 'TPA DOB',
+ 'gender' => 'TPA Gender',
+ 'tpa_id' => 'TPA Member ID',
+ 'age' => 'TPA Age',
+ ],
+];
+
+
+ $spreadsheet = new Spreadsheet();
+
+ /* =========================================================
+ * SHEET 1 — NOT IN TPA
+ * ========================================================= */
+ $sheet1 = $spreadsheet->getActiveSheet();
+ $sheet1->setTitle('Not in TPA');
+
+ $cols = $EXPORT_COLUMNS['not_in_tpa'];
+
+ $colNo = 1;
+ foreach ($cols as $label) {
+ setCell($sheet1, $colNo++, 1, $label);
+ }
+
+ $rowNo = 2;
+ foreach ($notInTPA as $row) {
+ $colNo = 1;
+ foreach ($cols as $key => $label) {
+ setCell($sheet1, $colNo++, $rowNo, $row[$key] ?? '');
+ }
+ $rowNo++;
+ }
+
+ foreach (range(1, count($cols)) as $c) {
+ $sheet1->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
+
+ /* =========================================================
+ * SHEET 2 — NOT IN NHANCE
+ * ========================================================= */
+ $sheet2 = $spreadsheet->createSheet();
+ $sheet2->setTitle('Not in Nhance');
+
+ $cols = $EXPORT_COLUMNS['not_in_nhance'];
+
+ $colNo = 1;
+ foreach ($cols as $label) {
+ setCell($sheet2, $colNo++, 1, $label);
+ }
+
+ $rowNo = 2;
+ foreach ($notInNhance as $row) {
+ $colNo = 1;
+ foreach ($cols as $key => $label) {
+ setCell($sheet2, $colNo++, $rowNo, $row[$key] ?? '');
+ }
+ $rowNo++;
+ }
+
+ foreach (range(1, count($cols)) as $c) {
+ $sheet2->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
+
+ /* =========================================================
+ * SHEET 3 — REVIEW NEEDED
+ * ========================================================= */
+ $sheet3 = $spreadsheet->createSheet();
+ $sheet3->setTitle('Review Needed');
+
+ $mainCols = $EXPORT_COLUMNS['review_main'];
+ $tpaCols = $EXPORT_COLUMNS['review_tpa'];
+
+ // headers
+ $colNo = 1;
+ foreach ($mainCols as $label) {
+ setCell($sheet3, $colNo++, 1, $label);
+ }
+ foreach ($tpaCols as $label) {
+ setCell($sheet3, $colNo++, 1, $label);
+ }
+
+ // rows
+ $rowNo = 2;
+ foreach ($reviewNeeded as $row) {
+
+ // main (DB)
+ $colNo = 1;
+ foreach ($mainCols as $key => $label) {
+ setCell($sheet3, $colNo++, $rowNo, $row[$key] ?? '');
+ }
+
+ $tpaData = [];
+ $notMatching = [];
+
+ if (($row['match']['status'] ?? '') === 'matched') {
+ $tpaData = $row['match']['tpa_record'] ?? [];
+ $notMatching = $row['match']['not_matching'] ?? [];
+ }
+
+ // TPA
+ foreach ($tpaCols as $key => $label) {
+ setCell($sheet3, $colNo++, $rowNo, $tpaData[$key] ?? '');
+ }
+
+ // highlight mismatches
+ foreach ($notMatching as $field) {
+
+ if (isset($mainCols[$field])) {
+ $idx = array_keys($mainCols);
+ $pos = array_search($field, $idx);
+ $cell = Coordinate::stringFromColumnIndex($pos + 1) . $rowNo;
+ $sheet3->getStyle($cell)->getFill()
+ ->setFillType(Fill::FILL_SOLID)
+ ->getStartColor()->setARGB('FFFFFF00');
+ }
+
+ if (isset($tpaCols[$field])) {
+ $idx = array_keys($tpaCols);
+ $pos = array_search($field, $idx);
+ $cell = Coordinate::stringFromColumnIndex(count($mainCols) + $pos + 1) . $rowNo;
+ $sheet3->getStyle($cell)->getFill()
+ ->setFillType(Fill::FILL_SOLID)
+ ->getStartColor()->setARGB('FFFFFF00');
+ }
+ }
+
+ $rowNo++;
+ }
+
+ foreach (range(1, count($mainCols) + count($tpaCols)) as $c) {
+ $sheet3->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setAutoSize(true);
+ }
+
+ /* =========================================================
+ * OUTPUT
+ * ========================================================= */
+ $writer = new Xlsx($spreadsheet);
+
+ if (ob_get_length()) ob_end_clean();
+
+ header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Cache-Control: max-age=0');
+
+ $writer->save('php://output');
+ exit;
+}
+
+
}
diff --git a/app/Controllers/MediAssistApiController.php b/app/Controllers/MediAssistApiController.php
index ba21f694..476cf2f7 100644
--- a/app/Controllers/MediAssistApiController.php
+++ b/app/Controllers/MediAssistApiController.php
@@ -351,6 +351,7 @@ class MediAssistApiController extends BaseController
// now update DB
+ $batch_file_success = 'success';
$updated = 0;
$employee_policy_ids = [];
foreach ($employeePolicyData as $policy_data) {
@@ -401,6 +402,7 @@ class MediAssistApiController extends BaseController
'gender' => $policy_data['gender'] ?? null,
'dob' => $policy_data['dob'] ?? null,
];
+ $batch_file_success = 'partially success';
log_message(
'error',
@@ -421,7 +423,8 @@ class MediAssistApiController extends BaseController
// update file table status after the tpa id successfully updated
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
$file_model = new BatchFileModel();
- $file_model->where('id', $requestData['file_id'])->set('status', 'success')->update();
+
+ $file_model->where('id', $requestData['file_id'])->set('status', $batch_file_success)->update();
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
} else {
log_message('error', "Failed to update file table status.");
diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php
index 16d4a815..0e86d94a 100755
--- a/app/Models/EmployeePolicyModel.php
+++ b/app/Models/EmployeePolicyModel.php
@@ -2139,5 +2139,60 @@ class EmployeePolicyModel extends Model
return $data;
}
+ public function getTPADataVariationReport($client_id, $client_policy_id, $file_id,$emp_codes = [],$all = false)
+ {
+ $result = $this->select([
+ 'employee_polices.*',
+ 'tpam.name as tpa_name',
+ 'tpam.short_name as tpa_short_name',
+ 'emp.relationship',
+ 'emp.relationship_code',
+ 'emp.change_event',
+ 'emp.emp_code',
+ 'emp.name',
+ 'emp.email_corporate',
+ 'emp.dob',
+ 'emp.gender',
+ 'emp.emp_status',
+ 'emp.is_active as emp_is_active',
+ 'emp.mobile as mobile',
+ 'emp.doj',
+ 'emp.basic_pay',
+ 'emp.band as grade',
+ 'cp.policy_no',
+ 'cp.policy_type_id',
+
+ ])
+ ->join('employees emp', 'employee_polices.employee_id = emp.id')
+ ->join('client_policy cp', 'employee_polices.client_policy_id = cp.id')
+ ->join('tpa tpam', 'cp.tpa_id = tpam.id', 'left') //tpam - tpa master
+ // ->orderBy('emp.emp_code', 'ASC')
+ // ->orderBy('employee_polices.employee_id', 'ASC');
+ ->where('employee_polices.status', 'active')
+ ->where('employee_polices.is_active', 1)
+ ->where('emp.is_active', 1)
+ ->where('employee_polices.status !=', 'inactive')
+ ->where('cp.id', $client_policy_id)
+ ->where('employee_polices.client_policy_id', $client_policy_id)
+ ->where('emp.client_id', $client_id);
+
+ if(count($emp_codes) == 0 && $all == false)
+ {
+ $result->where('employee_polices.tpa_id IS NULL');
+ }
+ else if(count($emp_codes) > 0 && $all == false)
+ {
+ $result->whereNotIn('emp.emp_code',$emp_codes);
+ }
+ else if(count($emp_codes) == 0 && $all == true)
+ {
+ $result->groupBy('emp.emp_code');
+ }
+
+ return $result->findAll();
+ // print_r($result);
+ // return $result;
+ }
+
}
\ No newline at end of file
diff --git a/app/Views/batch_list.php b/app/Views/batch_list.php
index 123950fb..aa2aa0da 100755
--- a/app/Views/batch_list.php
+++ b/app/Views/batch_list.php
@@ -176,6 +176,12 @@
Download
+
+ Download TPA Variation report
+
+
+
+
diff --git a/app/Views/insurer_or_tpa_data.php b/app/Views/insurer_or_tpa_data.php
index 60efbcb4..70296812 100755
--- a/app/Views/insurer_or_tpa_data.php
+++ b/app/Views/insurer_or_tpa_data.php
@@ -813,6 +813,11 @@
}
function fetchTpaIdFromTpa(){
+ let user_confirm = confirm('Are you sure you want to initiate the TPA fetch? This may take a while.');
+ if(!user_confirm)
+ {
+ return false;
+ }
let event = $('#event_type').val();
let client_id = $('#client').val();