945 lines
36 KiB
PHP
945 lines
36 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Controllers\Jobs;
|
|
use App\Models\BatchFileModel;
|
|
use App\Models\EmployeePolicyModel;
|
|
use App\Models\TpaApiDataModel;
|
|
use CodeIgniter\API\ResponseTrait;
|
|
|
|
/**
|
|
* TrueCover / EWA TPA APIs (ewatpa.com) — Volo integration layer.
|
|
*
|
|
* @see Postman collection (login, enrollment, hospitals, documents, claim flows).
|
|
*/
|
|
class VoloApiController extends BaseController
|
|
{
|
|
use ResponseTrait;
|
|
|
|
protected $db;
|
|
protected $voloTpaId;
|
|
|
|
/** @var string|null Cached access token for the current request cycle */
|
|
private ?string $cachedAccessToken = null;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = \Config\Database::connect();
|
|
$this->voloTpaId = getenv('VOLO_PRIMARY_KEY_CONSTANT');
|
|
}
|
|
|
|
protected function adminBaseUrl(): string
|
|
{
|
|
return rtrim((string) getenv('VOLO_API_ADMIN_BASE_URL'), '/');
|
|
}
|
|
|
|
protected function consumerBaseUrl(): string
|
|
{
|
|
return rtrim((string) getenv('VOLO_API_CONSUMER_BASE_URL'), '/');
|
|
}
|
|
|
|
/**
|
|
* Obtain API access token (login).
|
|
*
|
|
* @return string|null Full accessToken value as returned by API (includes "Token " prefix when applicable)
|
|
*/
|
|
public function getAccessToken(): ?string
|
|
{
|
|
if ($this->cachedAccessToken !== null) {
|
|
return $this->cachedAccessToken;
|
|
}
|
|
|
|
helper('api');
|
|
|
|
$url = $this->adminBaseUrl() . '/external/login';
|
|
$body = [
|
|
'emailId' => getenv('VOLO_API_EMAIL'),
|
|
'password' => getenv('VOLO_API_PASSWORD'),
|
|
'loggedInPortal' => getenv('VOLO_LOGGED_IN_PORTAL') ?: 'POLICY_CONFIGURATION_PORTAL',
|
|
];
|
|
|
|
$headers = ['Content-Type: application/json'];
|
|
$response = call_third_party_api($url, 'POST', $headers, $body);
|
|
|
|
if (empty($response['status'])) {
|
|
log_message('error', 'VOLO - Login failed | ' . json_encode($response));
|
|
return null;
|
|
}
|
|
|
|
$data = $response['data'] ?? null;
|
|
if (!is_array($data)) {
|
|
log_message('error', 'VOLO - Login invalid response shape | ' . json_encode($response));
|
|
return null;
|
|
}
|
|
|
|
$token = $data['accessToken'] ?? null;
|
|
if ($token === null || $token === '' || $token === 'Token null') {
|
|
log_message('error', 'VOLO - Login rejected or empty token | ' . json_encode($data));
|
|
return null;
|
|
}
|
|
|
|
$this->cachedAccessToken = $token;
|
|
|
|
return $this->cachedAccessToken;
|
|
}
|
|
|
|
/**
|
|
* @return array{0: bool, 1: array<int, string>}
|
|
*/
|
|
protected function authorizedJsonHeaders(): array
|
|
{
|
|
$token = $this->getAccessToken();
|
|
if ($token === null) {
|
|
return [false, []];
|
|
}
|
|
|
|
return [true, [
|
|
'Content-Type: application/json',
|
|
'Authorization: ' . $token,
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* GET /trueclaim/tpa/get-Enrollment-dump
|
|
*/
|
|
public function getEnrollmentDumpByEndorsement(string $insurerPolicyNumber, string $endorsmentNo)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query([
|
|
'insurerPolicyNumber' => $insurerPolicyNumber,
|
|
'endorsmentNo' => $endorsmentNo,
|
|
]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/get-Enrollment-dump?' . $query;
|
|
|
|
return call_third_party_api($url, 'GET', $headers, []);
|
|
}
|
|
|
|
/**
|
|
* GET /trueclaim/tpa/getHospitalByInsurerName
|
|
*/
|
|
public function getHospitalByInsurerName(string $insurerName)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query(['insurerName' => $insurerName]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/getHospitalByInsurerName?' . $query;
|
|
|
|
return call_third_party_api($url, 'GET', $headers, []);
|
|
}
|
|
|
|
/**
|
|
* POST /trueclaim/tpa/doc
|
|
*
|
|
* @param array<string, mixed> $body
|
|
*/
|
|
public function uploadDocument(array $body)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/doc';
|
|
|
|
return call_third_party_api($url, 'POST', $headers, $body);
|
|
}
|
|
|
|
/**
|
|
* GET /trueclaim/tpa/get-Enroll-dump
|
|
*/
|
|
public function getEnrollmentDump(string $insurerPolicyNumber)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query(['insurerPolicyNumber' => $insurerPolicyNumber]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/get-Enroll-dump?' . $query;
|
|
|
|
return call_third_party_api($url, 'GET', $headers, []);
|
|
}
|
|
|
|
/**
|
|
* POST consumer — /truecover/external-service/claim-status
|
|
*/
|
|
public function fetchExternalClaimStatus(string $tpaClaimNo)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$url = $this->consumerBaseUrl() . '/truecover/external-service/claim-status';
|
|
$body = ['tpaClaimNo' => $tpaClaimNo];
|
|
|
|
return call_third_party_api($url, 'POST', $headers, $body);
|
|
}
|
|
|
|
/**
|
|
* POST /trueclaim/tpa/getTpaData
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function getTpaData(string $startDate, string $endDate, string $entityId)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/getTpaData';
|
|
$body = [
|
|
'startDate' => $startDate,
|
|
'endDate' => $endDate,
|
|
'entityId' => $entityId,
|
|
];
|
|
|
|
return call_third_party_api($url, 'POST', $headers, $body);
|
|
}
|
|
|
|
/**
|
|
* GET /trueclaim/create-new-all-member-id-card-pdf
|
|
* Returns raw API response; body may contain base64 PDF payload.
|
|
*/
|
|
public function getEcardPdfRequest(string $employeeId, string $entityId)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query([
|
|
'employeeId' => $employeeId,
|
|
'entityId' => $entityId,
|
|
]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/create-new-all-member-id-card-pdf?' . $query;
|
|
|
|
return call_third_party_api($url, 'GET', $headers, []);
|
|
}
|
|
|
|
/**
|
|
* GET /trueclaim/get-entity-from-policy
|
|
*/
|
|
public function getEntityFromPolicy(string $policyNumber)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query(['policyNumber' => $policyNumber]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/get-entity-from-policy?' . $query;
|
|
|
|
return call_third_party_api($url, 'GET', $headers, []);
|
|
}
|
|
|
|
/**
|
|
* POST /trueclaim/policy-bazaar/intimate-claim
|
|
*
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function intimateClaim(array $payload)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$url = $this->adminBaseUrl() . '/trueclaim/policy-bazaar/intimate-claim';
|
|
|
|
return call_third_party_api($url, 'POST', $headers, $payload);
|
|
}
|
|
|
|
/**
|
|
* POST /trueclaim/tpa/getClaimData?claimId=
|
|
*/
|
|
public function getClaimData(string $claimId)
|
|
{
|
|
helper('api');
|
|
[$ok, $headers] = $this->authorizedJsonHeaders();
|
|
if (!$ok) {
|
|
return ['status' => false, 'message' => 'VOLO token failed'];
|
|
}
|
|
$query = http_build_query(['claimId' => $claimId]);
|
|
$url = $this->adminBaseUrl() . '/trueclaim/tpa/getClaimData?' . $query;
|
|
|
|
return call_third_party_api($url, 'POST', $headers, new \stdClass());
|
|
}
|
|
|
|
/**
|
|
* Pull enrollment from Volo (get-Enroll-dump), match members, update employee_polices.tpa_id (memberId).
|
|
* Same flow as MediAssistApiController::MediAssistGetBenefDetails.
|
|
*
|
|
* @param array<string, mixed> $requestData
|
|
* @return array<string, mixed>|\CodeIgniter\HTTP\Response
|
|
*/
|
|
public function VoloGetBenefDetails($requestData)
|
|
{
|
|
$function_calling_type = $requestData['return_type'] ?? 'job';
|
|
|
|
try {
|
|
helper(['api', 'utility']);
|
|
|
|
$policyNo = $requestData['policy_no'] ?? null;
|
|
$client_policy_id = $requestData['client_policy_id'] ?? null;
|
|
|
|
if (empty($policyNo)) {
|
|
log_message('error', 'VOLO - TPA ID Pull | policy_no missing in request');
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'policy_no required'];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'policy_no required']);
|
|
}
|
|
|
|
if (empty($client_policy_id)) {
|
|
log_message('error', 'VOLO - TPA ID Pull | client_policy_id missing in request');
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'client_policy_id required'];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'client_policy_id required']);
|
|
}
|
|
|
|
log_message('error', "VOLO - TPA ID Pull | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
|
|
|
$employeePolicyModel = new EmployeePolicyModel();
|
|
$employeePolicyData = $employeePolicyModel
|
|
->select('
|
|
employees.*,
|
|
employee_polices.id as emp_policy_id,
|
|
employee_polices.client_policy_id,
|
|
')
|
|
->join('employees', 'employees.id = employee_polices.employee_id')
|
|
->where('employee_polices.is_active', 1)
|
|
->where('employee_polices.status', 'active')
|
|
->where('employees.is_active', 1)
|
|
->where('employees.emp_status', 'active')
|
|
->where('employee_polices.tpa_id IS NULL')
|
|
->where('employee_polices.client_policy_id', $client_policy_id)
|
|
->findAll();
|
|
|
|
if (empty($employeePolicyData)) {
|
|
log_message('error', 'VOLO - TPA ID Pull FAILED | employeePolicyData is empty (tpa_id IS NULL from nhance) for this TPA ID Pull request');
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'employeePolicyData not found'];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'employeePolicyData not found']);
|
|
}
|
|
|
|
$response = $this->getEnrollmentDump((string) $policyNo);
|
|
|
|
if (empty($response['status'])) {
|
|
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
|
$file_model = new BatchFileModel();
|
|
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
|
log_message('error', "VOLO - Files table status updated for the file id : {$requestData['file_id']}");
|
|
}
|
|
log_message('error', 'VOLO - TPA ID Pull API FAILED | ' . json_encode($response));
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'API call failed', 'data' => $response];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'API call failed', 'data' => $response]);
|
|
}
|
|
|
|
$apiPayload = $response['data'] ?? null;
|
|
if (!is_array($apiPayload)) {
|
|
log_message('error', 'VOLO - TPA ID Pull FAILED | invalid response shape | ' . json_encode($response));
|
|
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
|
(new BatchFileModel())->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
|
}
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'Invalid API response'];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'Invalid API response']);
|
|
}
|
|
|
|
$allRows = $this->extractVoloEnrollmentRows($apiPayload);
|
|
if ($allRows === []) {
|
|
log_message('error', 'VOLO - TPA ID Pull FAILED | enrollment body empty | ' . json_encode($apiPayload));
|
|
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
|
(new BatchFileModel())->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
|
}
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'enrollment body empty'];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'enrollment body empty']);
|
|
}
|
|
|
|
$totalCount = count($allRows);
|
|
log_message('error', "VOLO - TPA ID Pull | fetched {$totalCount} enrollment row(s)");
|
|
|
|
$json = json_encode($allRows, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
$filePath = WRITEPATH . 'tmp/' . time() . '_' . ($requestData['file_id'] ?? '0') . '_volo.json';
|
|
file_put_contents($filePath, $json);
|
|
|
|
if (!empty($requestData['file_id'])) {
|
|
Jobs::addJob([
|
|
'job_name' => 'saveVoloAPIData',
|
|
'payload' => [
|
|
'file_id' => $requestData['file_id'],
|
|
'json_file_path' => $filePath,
|
|
],
|
|
]);
|
|
}
|
|
|
|
$batch_file_success = 'success';
|
|
$updated = 0;
|
|
$employee_policy_ids = [];
|
|
|
|
foreach ($employeePolicyData as $policy_data) {
|
|
$hasMatchForThisPolicy = false;
|
|
|
|
foreach ($allRows as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$voloEmpCode = trim((string) ($row['memberEmployeeNo'] ?? $row['memberAltEmployeeNo'] ?? $row['empId'] ?? ''));
|
|
$voloMemberId = trim((string) ($row['memberId'] ?? ''));
|
|
if ($voloMemberId === '') {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
strtolower(trim((string) ($policy_data['name'] ?? ''))) === strtolower(trim((string) ($row['memberName'] ?? '')))
|
|
&& (string) ($policy_data['emp_code'] ?? '') === $voloEmpCode
|
|
&& $this->voloRelationsMatch((string) ($policy_data['relationship'] ?? ''), (string) ($row['relation'] ?? ''))
|
|
&& $this->voloGendersMatch((string) ($policy_data['gender'] ?? ''), (string) ($row['gender'] ?? ''))
|
|
&& $this->voloDobsMatch((string) ($policy_data['dob'] ?? ''), $row['DOB'] ?? $row['dob'] ?? null)
|
|
) {
|
|
$hasMatchForThisPolicy = true;
|
|
|
|
$sql = 'UPDATE employee_polices SET tpa_id = ? WHERE id = ?';
|
|
$this->db->query($sql, [$voloMemberId, $policy_data['emp_policy_id']]);
|
|
|
|
if (strtolower(trim((string) ($policy_data['relationship'] ?? ''))) === 'self') {
|
|
$employee_policy_ids[] = $policy_data['emp_policy_id'];
|
|
}
|
|
|
|
if ($this->db->affectedRows() > 0) {
|
|
$updated++;
|
|
log_message('error', "VOLO - Updated tpa_id={$voloMemberId} for emp_code={$voloEmpCode} policy={$policyNo}");
|
|
} else {
|
|
log_message('error', "VOLO - No update (already set or not matched) for emp_code={$voloEmpCode} policy={$policyNo}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$hasMatchForThisPolicy) {
|
|
$nhanceSideData = [
|
|
'name' => $policy_data['name'] ?? null,
|
|
'emp_code' => $policy_data['emp_code'] ?? null,
|
|
'relationship' => $policy_data['relationship'] ?? null,
|
|
'gender' => $policy_data['gender'] ?? null,
|
|
'dob' => $policy_data['dob'] ?? null,
|
|
];
|
|
$batch_file_success = 'partially success';
|
|
log_message('error', 'VOLO - No match for Nhance = ' . json_encode($nhanceSideData));
|
|
}
|
|
}
|
|
|
|
if ($employee_policy_ids !== []) {
|
|
log_message('error', 'VOLO - sendMailForDownloadingECard JOB PUSHED.');
|
|
Jobs::addJob(['job_name' => 'sendMailForDownloadingECard', 'payload' => ['ids' => $employee_policy_ids, 'client_policy_id' => $client_policy_id]]);
|
|
}
|
|
|
|
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
|
$file_model = new BatchFileModel();
|
|
$file_model->where('id', $requestData['file_id'])->set('status', $batch_file_success)->update();
|
|
log_message('error', "VOLO - Files table status updated for the file id : {$requestData['file_id']}");
|
|
}
|
|
|
|
log_message('error', "VOLO - TPA ID Pull SUCCESS | Total fetched={$totalCount}, updated={$updated}");
|
|
|
|
if ($function_calling_type === 'job') {
|
|
return [
|
|
'status' => true,
|
|
'message' => 'Updated successfully',
|
|
'total_fetched' => $totalCount,
|
|
'total_updated' => $updated,
|
|
];
|
|
}
|
|
|
|
return $this->respond([
|
|
'status' => true,
|
|
'message' => 'Updated successfully',
|
|
'total_fetched' => $totalCount,
|
|
'total_updated' => $updated,
|
|
]);
|
|
} catch (\Throwable $th) {
|
|
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
|
$file_model = new BatchFileModel();
|
|
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
|
log_message('error', "VOLO - Files table status updated for the file id : {$requestData['file_id']}");
|
|
}
|
|
|
|
$errorData = [
|
|
'message' => $th->getMessage(),
|
|
'file' => $th->getFile(),
|
|
'line' => $th->getLine(),
|
|
];
|
|
log_message('error', 'VOLO - Exception in VoloGetBenefDetails: ' . json_encode($errorData));
|
|
if ($function_calling_type === 'job') {
|
|
return ['status' => false, 'message' => 'API call failed', 'data' => $errorData];
|
|
}
|
|
|
|
return $this->respond(['status' => false, 'message' => 'API call failed', 'data' => $errorData]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Persist raw Volo enrollment rows into tpa_api_data (same pattern as saveMediAssitAPIData).
|
|
*
|
|
* @param array<string, mixed> $array
|
|
*/
|
|
public function saveVoloAPIData($array)
|
|
{
|
|
$file_id = $array['file_id'];
|
|
$json = file_get_contents($array['json_file_path']);
|
|
$records = json_decode($json, true);
|
|
if (!is_array($records)) {
|
|
return;
|
|
}
|
|
|
|
$file_model = new BatchFileModel();
|
|
$file_info = $file_model->where('id', $file_id)->find();
|
|
$tpaApiDataModel = new TpaApiDataModel();
|
|
$tpaApiDataModel->set('is_active', 0)->where('file_id', $file_id)->update();
|
|
|
|
$mappedRows = [];
|
|
foreach ($records as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$dobRaw = $row['DOB'] ?? $row['dob'] ?? null;
|
|
$dobYmd = null;
|
|
if ($dobRaw !== null && $dobRaw !== '') {
|
|
$dobYmd = $this->normalizeVoloDobToYmd($dobRaw);
|
|
}
|
|
|
|
$dojRaw = $row['DOJ'] ?? $row['doj'] ?? null;
|
|
$dojYmd = null;
|
|
if ($dojRaw !== null && $dojRaw !== '') {
|
|
$dojYmd = $this->normalizeVoloDobToYmd($dojRaw);
|
|
}
|
|
|
|
$rel = trim((string) ($row['relation'] ?? ''));
|
|
$mappedRows[] = [
|
|
'file_id' => $file_id,
|
|
'emp_code' => trim((string) ($row['memberEmployeeNo'] ?? $row['empId'] ?? '')),
|
|
'name' => trim((string) ($row['memberName'] ?? '')),
|
|
'dob' => $dobYmd,
|
|
'relation' => $rel,
|
|
'gender' => $this->voloGenderToMediStyle((string) ($row['gender'] ?? '')),
|
|
'self' => in_array(strtoupper($rel), ['EMPLOYEE', 'SELF'], true) ? 1 : 0,
|
|
'tpa_id' => trim((string) ($row['memberId'] ?? '')),
|
|
'age' => isset($row['age']) && is_numeric($row['age']) ? (int) $row['age'] : null,
|
|
'si' => $row['sumInsured'] ?? null,
|
|
'doj' => $dojYmd,
|
|
'is_active' => 1,
|
|
'created_by' => $file_info[0]['created_by'] ?? null,
|
|
];
|
|
}
|
|
|
|
if ($mappedRows !== []) {
|
|
$tpaApiDataModel->insertBatchWithChunkLog($mappedRows, 500, 'FILE_ID_' . $file_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $apiData Decoded JSON root from Volo get-Enroll-dump
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
protected function extractVoloEnrollmentRows(array $apiData): array
|
|
{
|
|
$body = $apiData['body'] ?? null;
|
|
if (is_array($body)) {
|
|
$out = [];
|
|
foreach ($body as $item) {
|
|
if (is_array($item)) {
|
|
$out[] = $item;
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
protected function voloRelationsMatch(string $nhanceRel, string $voloRel): bool
|
|
{
|
|
$n = strtolower(trim(str_replace('-', ' ', $nhanceRel)));
|
|
$v = strtoupper(trim(str_replace('-', ' ', $voloRel)));
|
|
if ($n === 'self' && in_array($v, ['EMPLOYEE', 'PRIMARY', 'INSURED'], true)) {
|
|
return true;
|
|
}
|
|
|
|
return strtolower($v) === $n || $n === strtolower($v);
|
|
}
|
|
|
|
protected function voloGendersMatch(string $nh, string $volo): bool
|
|
{
|
|
return $this->genderNorm($nh) === $this->genderNorm($volo);
|
|
}
|
|
|
|
protected function genderNorm(string $g): string
|
|
{
|
|
$g = strtolower(trim($g));
|
|
if ($g === '' || $g === 'm' || strpos($g, 'male') === 0) {
|
|
return 'm';
|
|
}
|
|
if ($g === 'f' || strpos($g, 'female') === 0) {
|
|
return 'f';
|
|
}
|
|
|
|
return $g;
|
|
}
|
|
|
|
protected function voloDobsMatch(string $nhanceDob, mixed $voloDob): bool
|
|
{
|
|
$v = $this->normalizeVoloDobToYmd($voloDob);
|
|
if ($v === null || $v === '') {
|
|
return false;
|
|
}
|
|
$tsN = strtotime($nhanceDob);
|
|
if ($tsN === false) {
|
|
return false;
|
|
}
|
|
$n = date('Y-m-d', $tsN);
|
|
|
|
return $n === $v;
|
|
}
|
|
|
|
protected function normalizeVoloDobToYmd(mixed $dob): ?string
|
|
{
|
|
if ($dob === null || $dob === '') {
|
|
return null;
|
|
}
|
|
if (is_numeric($dob) && (float) $dob > 1_000_000_000_000) {
|
|
return date('Y-m-d', (int) (((float) $dob) / 1000));
|
|
}
|
|
$ts = strtotime(str_replace('/', '-', (string) $dob));
|
|
|
|
return $ts ? date('Y-m-d', $ts) : null;
|
|
}
|
|
|
|
protected function voloGenderToMediStyle(string $g): string
|
|
{
|
|
return $this->genderNorm($g) === 'f' ? 'F' : 'M';
|
|
}
|
|
|
|
/**
|
|
* Resolve entity id for a policy number (uses get-entity-from-policy).
|
|
*/
|
|
protected function resolveEntityId(string $policyNo): ?string
|
|
{
|
|
$fallback = getenv('VOLO_DEFAULT_ENTITY_ID');
|
|
if (!empty($fallback)) {
|
|
return (string) $fallback;
|
|
}
|
|
|
|
$res = $this->getEntityFromPolicy($policyNo);
|
|
if (empty($res['status']) || !is_array($res['data'])) {
|
|
return null;
|
|
}
|
|
$body = $res['data']['body'] ?? null;
|
|
if ($body === null || $body === '') {
|
|
return null;
|
|
}
|
|
|
|
return (string) $body;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Push claim via intimate-claim (policy-bazaar) API.
|
|
*/
|
|
public function SubmitClaim($claimId = null)
|
|
{
|
|
helper(['api', 'tpa_claim_push_log', 'utility']);
|
|
|
|
$data = $this->db->table('ticket_master tm')
|
|
->select('
|
|
tm.id,
|
|
tm.doa as admissionDate,
|
|
tm.dod as dischargeDate,
|
|
tm.claim_amount as requestedAmount,
|
|
tm.tpa_no as memberId,
|
|
cp.policy_no as policyNo,
|
|
cf.id as fileId,
|
|
cf.url as filePath
|
|
')
|
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
|
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 4 AND cf.is_active = 1 AND cf.mime_type = 'application/pdf'", 'left')
|
|
->where('tm.id', $claimId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (!$data) {
|
|
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'])) {
|
|
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | file missing');
|
|
return ['status' => false, 'message' => 'Claim Push FAILED | File Missing'];
|
|
}
|
|
|
|
$file_id = $data['fileId'] ?? null;
|
|
$filename = basename($data['filePath']);
|
|
$publicUrl = storage_claim_file_download_url($filename, $file_id);
|
|
if ($publicUrl === '') {
|
|
tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | PDF not available on S3/local');
|
|
return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
|
|
}
|
|
|
|
$entityId = $this->resolveEntityId($data['policyNo'] ?? '');
|
|
if ($entityId === null) {
|
|
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 === '') {
|
|
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'];
|
|
}
|
|
|
|
$doa = !empty($data['admissionDate']) ? date('Y-m-d', strtotime($data['admissionDate'])) : '';
|
|
$dod = !empty($data['dischargeDate']) ? date('Y-m-d', strtotime($data['dischargeDate'])) : $doa;
|
|
|
|
$payload = [
|
|
'hospitalId' => (string) $hospitalId,
|
|
'memberId' => (string) ($data['memberId'] ?? ''),
|
|
'dateOfAdmission' => $doa,
|
|
'dateOfDischarge' => $dod,
|
|
'potentialClaimAmount' => (float) ($data['requestedAmount'] ?? 0),
|
|
'claimDocuments' => [
|
|
[
|
|
'documentName' => $filename,
|
|
'documentType' => 'medical_report',
|
|
'extension' => 'pdf',
|
|
'url' => $publicUrl,
|
|
],
|
|
],
|
|
'entityId' => (string) $entityId,
|
|
'insurerPolicyNumber' => (string) ($data['policyNo'] ?? ''),
|
|
];
|
|
|
|
tpa_claim_push_log($claimId, 'VOLO - Claim Push | claimId: ' . $claimId . ' | payload: ' . json_encode($payload));
|
|
|
|
$response = $this->intimateClaim($payload);
|
|
|
|
if (empty($response['status'])) {
|
|
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)]);
|
|
|
|
return ['status' => false, 'message' => 'Claim Push FAILED', 'response' => $response];
|
|
}
|
|
|
|
$apiData = $response['data'] ?? null;
|
|
$ref = null;
|
|
if (is_array($apiData)) {
|
|
$ref = $apiData['message'] ?? null;
|
|
}
|
|
|
|
if (!empty($ref)) {
|
|
$this->db->table('ticket_master')
|
|
->where('id', $claimId)
|
|
->update([
|
|
'tpa_claim_push_reference_no' => $ref,
|
|
'tpa_claim_id' => $ref,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
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)){
|
|
|
|
$this->db->table('claim_files')
|
|
->where('id', $file_id)
|
|
->update([ 'is_file_sent_to_tpa' => 1 ]);
|
|
|
|
tpa_claim_push_log($claimId, 'VOLO - Claim Push | Updating claim_files table for file_id: '.$file_id);
|
|
}else{
|
|
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];
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
/**
|
|
* Refresh claim status from consumer claim-status service.
|
|
*/
|
|
public function ClaimDetail($claimId = null)
|
|
{
|
|
helper('api');
|
|
|
|
$ticket = $this->db->table('ticket_master tm')
|
|
->select('tm.id, tm.tpa_claim_push_reference_no, tm.tpa_claim_id')
|
|
->where('tm.id', $claimId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (!$ticket) {
|
|
return ['status' => false, 'message' => 'Invalid claim'];
|
|
}
|
|
|
|
$tpaClaimNo = $ticket['tpa_claim_push_reference_no'] ?? $ticket['tpa_claim_id'] ?? null;
|
|
if (empty($tpaClaimNo)) {
|
|
log_message('error', 'VOLO - Claim status | missing TPA claim ref | ticket: ' . $claimId);
|
|
|
|
return ['status' => false, 'message' => 'TPA claim reference missing'];
|
|
}
|
|
|
|
$response = $this->fetchExternalClaimStatus((string) $tpaClaimNo);
|
|
|
|
if (empty($response['status']) || !is_array($response['data'])) {
|
|
log_message('error', 'VOLO - Claim status FAILED | ticket: ' . $claimId . ' | ' . json_encode($response));
|
|
|
|
return ['status' => false, 'message' => 'API call failed', 'data' => $response];
|
|
}
|
|
|
|
$payload = $response['data'];
|
|
$body = $payload['body'] ?? $payload;
|
|
|
|
$currentStatus = '';
|
|
if (is_array($body)) {
|
|
$currentStatus = (string) ($body['status'] ?? $body['claim_status'] ?? '');
|
|
}
|
|
|
|
if ($currentStatus === '') {
|
|
log_message('error', 'VOLO - Claim status empty | ticket: ' . $claimId . ' | ' . json_encode($response));
|
|
|
|
return ['status' => false, 'message' => 'Status not found in response', 'data' => $response];
|
|
}
|
|
|
|
$validStatuses = [
|
|
'READY_TO_PAY' => 11,
|
|
'PAID' => 11,
|
|
'SETTLED' => 11,
|
|
'REJECTED' => 8,
|
|
'DENIED' => 8,
|
|
'QUERY' => 4,
|
|
'PRE_AUTH_QUERY_RESPONDED' => 4,
|
|
'PRE_AUTH_QUERY' => 4,
|
|
'MEMBER_UNENDORSED' => 5,
|
|
'In-Progress' => 5,
|
|
'UNDER_PROCESS' => 5,
|
|
'UNDER PROCESS' => 5,
|
|
];
|
|
|
|
$normalized = strtoupper(str_replace(' ', '_', trim($currentStatus)));
|
|
$claimStatusId = $validStatuses[$currentStatus] ?? ($validStatuses[$normalized] ?? 5);
|
|
|
|
$updateArray = [
|
|
'tpa_claim_status' => $currentStatus,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
'claim_status_id' => $claimStatusId,
|
|
];
|
|
|
|
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
|
log_message('error', 'VOLO - Claim status SUCCESS | ticket: ' . $claimId . ' | status: ' . $currentStatus);
|
|
|
|
return [
|
|
'status' => true,
|
|
'message' => 'Claim status updated.',
|
|
'updated_status' => $currentStatus,
|
|
'api_response' => $response,
|
|
];
|
|
}
|
|
|
|
public function ClaimStatusUpdate()
|
|
{
|
|
helper('api');
|
|
|
|
$tickets = $this->db->table('ticket_master tm')
|
|
->select('tm.id')
|
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
|
->where('tm.tpa_claim_push_reference_no IS NOT NULL')
|
|
->whereNotIn('tm.claim_status_id', [8, 11, 12, 13, 66, 22, 24, 47, 49, 32, 34, 53, 55, 42, 44, 58, 60])
|
|
->where('tm.is_active', 1)
|
|
->where('cp.tpa_id', $this->voloTpaId)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$count = 0;
|
|
foreach ($tickets as $t) {
|
|
$this->ClaimDetail($t['id']);
|
|
$count++;
|
|
}
|
|
|
|
return $this->response->setJSON(['status' => true, 'updated' => $count]);
|
|
}
|
|
|
|
/**
|
|
* E-card PDF — returns a data URL the app can open, or null on failure.
|
|
*
|
|
* @param string|null $voloEmployeeId Volo/TPA employee or member id stored in employee_polices.tpa_id
|
|
*/
|
|
public function EcardRequest($emp_code = null, $policy_no = null, $voloEmployeeId = null)
|
|
{
|
|
if (empty($voloEmployeeId)) {
|
|
log_message('error', 'VOLO - Ecard | missing Volo employee/member id | emp_code: ' . ($emp_code ?? ''));
|
|
|
|
return null;
|
|
}
|
|
|
|
$entityId = $this->resolveEntityId((string) $policy_no);
|
|
if ($entityId === null) {
|
|
log_message('error', 'VOLO - Ecard | entity id not resolved | policy: ' . ($policy_no ?? ''));
|
|
|
|
return null;
|
|
}
|
|
|
|
$res = $this->getEcardPdfRequest((string) $voloEmployeeId, $entityId);
|
|
if (empty($res['status']) || !is_array($res['data'])) {
|
|
log_message('error', 'VOLO - Ecard FAILED | ' . json_encode($res));
|
|
|
|
return null;
|
|
}
|
|
|
|
$body = $res['data']['body'] ?? null;
|
|
if (!is_string($body) || $body === '') {
|
|
log_message('error', 'VOLO - Ecard FAILED | empty body | ' . json_encode($res));
|
|
|
|
return null;
|
|
}
|
|
|
|
return 'data:application/pdf;base64,' . $body;
|
|
}
|
|
}
|