MERGE_TEST_LIVE_ISSUES
This commit is contained in:
commit
02ec112b24
@ -42,6 +42,7 @@ class Acl
|
|||||||
'#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
'#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||||
'#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
'#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||||
'#^/sendDataToTPA#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID, MANAGER_ROLE_ID]],
|
'#^/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]],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ $routes->get('/chatbottest', 'ChatbotControllerNew::chatbotest');
|
|||||||
$routes->get('/chatbot', 'ChatbotControllerNew::chatbot');
|
$routes->get('/chatbot', 'ChatbotControllerNew::chatbot');
|
||||||
|
|
||||||
$routes->get('/swagger', 'SwaggerController::index', ['filter' => 'authMVC']);
|
$routes->get('/swagger', 'SwaggerController::index', ['filter' => 'authMVC']);
|
||||||
|
$routes->get('/swagger/tpa', 'SwaggerController::tpa', ['filter' => 'authMVC']);
|
||||||
|
$routes->get('/swagger/tpa-spec', 'SwaggerController::tpaSpec', ['filter' => 'authMVC']);
|
||||||
$routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authMVC']);
|
$routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authMVC']);
|
||||||
$routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']);
|
$routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']);
|
||||||
$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');
|
$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');
|
||||||
|
|||||||
@ -3785,6 +3785,14 @@ class EmployeeRestController extends AdminController
|
|||||||
|
|
||||||
$this->handleCliamFiles($file_data, $ticket_id, $ticket_message_id);
|
$this->handleCliamFiles($file_data, $ticket_id, $ticket_message_id);
|
||||||
|
|
||||||
|
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||||
|
try {
|
||||||
|
helper('merge_pdf');
|
||||||
|
merge_ticket_pdfs((int) $ticket_id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'EmployeeRestController::initiateClaim | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$mail_sent_status = ($this->ticketController->sendAutoMailTrigger($ticket_id));
|
$mail_sent_status = ($this->ticketController->sendAutoMailTrigger($ticket_id));
|
||||||
if (gettype($mail_sent_status) == 'array') {
|
if (gettype($mail_sent_status) == 'array') {
|
||||||
$message = 'Claim Iniated Successfully';
|
$message = 'Claim Iniated Successfully';
|
||||||
@ -5444,6 +5452,14 @@ class EmployeeRestController extends AdminController
|
|||||||
$result = $this->handleCliamFiles($file_data, $ticket_id, null, false, true);
|
$result = $this->handleCliamFiles($file_data, $ticket_id, null, false, true);
|
||||||
|
|
||||||
if (! empty($result)) {
|
if (! empty($result)) {
|
||||||
|
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||||
|
try {
|
||||||
|
helper('merge_pdf');
|
||||||
|
merge_ticket_pdfs((int) $ticket_id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'EmployeeRestController::uploadIRDocs | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// $this->ticketMaster->where('id', $ticket_id)->set(['required_docs', $required_docs])->update();
|
// $this->ticketMaster->where('id', $ticket_id)->set(['required_docs', $required_docs])->update();
|
||||||
db_connect()->query(
|
db_connect()->query(
|
||||||
"UPDATE ticket_master SET required_docs = ? WHERE id = ?",
|
"UPDATE ticket_master SET required_docs = ? WHERE id = ?",
|
||||||
|
|||||||
@ -92,7 +92,7 @@ class FhplApiController extends BaseController
|
|||||||
->join('employees e', 'e.id = tm.emp_id', 'left')
|
->join('employees e', 'e.id = tm.emp_id', 'left')
|
||||||
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
||||||
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
||||||
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 2 AND cf.mime_type='application/pdf'", '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)
|
->where('tm.id', $claimId)
|
||||||
->get()
|
->get()
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|||||||
@ -113,7 +113,7 @@ class HealthIndiaApiController extends BaseController
|
|||||||
->join('employees e', 'e.id = tm.emp_id', 'left')
|
->join('employees e', 'e.id = tm.emp_id', 'left')
|
||||||
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
||||||
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
||||||
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 2 AND cf.mime_type='application/pdf'", '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)
|
->where('tm.id', $claimId)
|
||||||
->get()
|
->get()
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|||||||
@ -67,7 +67,7 @@ class MediAssistApiController extends BaseController
|
|||||||
->join('employees e', 'e.id = tm.emp_id', 'left')
|
->join('employees e', 'e.id = tm.emp_id', 'left')
|
||||||
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
||||||
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
||||||
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 2 and cf.mime_type = 'application/pdf'", '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)
|
->where('tm.id', $claimId)
|
||||||
->get()
|
->get()
|
||||||
->getRowArray(); // single record
|
->getRowArray(); // single record
|
||||||
|
|||||||
@ -1,10 +1,84 @@
|
|||||||
<?php namespace App\Controllers;
|
<?php namespace App\Controllers;
|
||||||
|
|
||||||
use App\Controllers\BaseController;
|
use App\Controllers\BaseController;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
|
||||||
class SwaggerController extends BaseController
|
class SwaggerController extends BaseController
|
||||||
{
|
{
|
||||||
public function index(){
|
public function index()
|
||||||
return view('swagger/index');
|
{
|
||||||
|
return view('swagger/index', [
|
||||||
|
'specUrl' => base_url('assets/api.yaml'),
|
||||||
|
'pageTitle' => 'Swagger UI',
|
||||||
|
'swaggerPreauthorize' => [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger UI for TPA external integrations OpenAPI spec (openapi/tpa-external-integrations.yaml).
|
||||||
|
* Pre-fills Authorize from the same `.env` keys the API controllers use (`getenv`).
|
||||||
|
*/
|
||||||
|
public function tpa()
|
||||||
|
{
|
||||||
|
return view('swagger/index', [
|
||||||
|
'specUrl' => base_url('swagger/tpa-spec'),
|
||||||
|
'pageTitle' => 'TPA external APIs — Swagger UI',
|
||||||
|
'swaggerPreauthorize' => $this->buildTpaSwaggerPreauthorize(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values for Swagger UI Authorize (apiKey / basic). Uses runtime env — keep `/swagger/tpa` access-controlled.
|
||||||
|
*
|
||||||
|
* @return list<array<string, string>>
|
||||||
|
*/
|
||||||
|
protected function buildTpaSwaggerPreauthorize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['kind' => 'apiKey', 'scheme' => 'MediAssistUsername', 'value' => $this->readEnvString('MEDI_ASSIST_API_USERNAME')],
|
||||||
|
['kind' => 'apiKey', 'scheme' => 'MediAssistPassword', 'value' => $this->readEnvString('MEDI_ASSIST_API_PASSWORD')],
|
||||||
|
['kind' => 'apiKey', 'scheme' => 'VidalSubscriptionKey', 'value' => $this->readEnvString('VIDAL_API_SUBSCRIPTION_KEY')],
|
||||||
|
['kind' => 'apiKey', 'scheme' => 'VidalIrSubscriptionKey', 'value' => $this->readEnvString('VIDAL_SUBSCRIPTION_KEY')],
|
||||||
|
['kind' => 'apiKey', 'scheme' => 'VidalWellnessSubscriptionKey', 'value' => $this->readEnvString('VIDAL_WELLNESS_SUBSCRIPTION_KEY')],
|
||||||
|
[
|
||||||
|
'kind' => 'basic',
|
||||||
|
'scheme' => 'HealthIndiaTokenBasic',
|
||||||
|
'username' => $this->readEnvString('HEALTH_INDIA_USERNAME'),
|
||||||
|
'password' => $this->readEnvString('HEALTH_INDIA_PASSWORD'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read `.env` the same way as the rest of CodeIgniter (`env()` then `getenv()`).
|
||||||
|
*/
|
||||||
|
protected function readEnvString(string $key): string
|
||||||
|
{
|
||||||
|
if (function_exists('env')) {
|
||||||
|
$v = \env($key);
|
||||||
|
if ($v !== null) {
|
||||||
|
return \is_bool($v) ? '' : (string) $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$g = \getenv($key);
|
||||||
|
|
||||||
|
return $g === false ? '' : (string) $g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves the OpenAPI YAML from project root /openapi (not under public/).
|
||||||
|
*/
|
||||||
|
public function tpaSpec()
|
||||||
|
{
|
||||||
|
$path = ROOTPATH . 'openapi/tpa-external-integrations.yaml';
|
||||||
|
if (! is_readable($path)) {
|
||||||
|
throw PageNotFoundException::forPageNotFound('OpenAPI spec not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response
|
||||||
|
->setHeader('Content-Type', 'application/yaml; charset=UTF-8')
|
||||||
|
->setHeader('Cache-Control', 'public, max-age=300')
|
||||||
|
->setBody(file_get_contents($path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2403,7 +2403,7 @@ class TicketController extends BaseController
|
|||||||
$claimFiles = new ClaimFilesModel();
|
$claimFiles = new ClaimFilesModel();
|
||||||
$file_record = $claimFiles->where('id', $claim_file_id)->where('is_active', 1)->first();
|
$file_record = $claimFiles->where('id', $claim_file_id)->where('is_active', 1)->first();
|
||||||
|
|
||||||
if ($file_record === null || (int) ($file_record['file_type'] ?? 0) !== 2) {
|
if ($file_record === null || ! in_array((int) ($file_record['file_type'] ?? 0), [2, 4], true)) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3677,6 +3677,13 @@ class TicketController extends BaseController
|
|||||||
$employeeRest = new EmployeeRestController();
|
$employeeRest = new EmployeeRestController();
|
||||||
$result = $employeeRest->handleCliamFiles($file_data, $ticket_id);
|
$result = $employeeRest->handleCliamFiles($file_data, $ticket_id);
|
||||||
if(!empty($result)){
|
if(!empty($result)){
|
||||||
|
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||||
|
try {
|
||||||
|
helper('merge_pdf');
|
||||||
|
merge_ticket_pdfs((int) $ticket_id);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'TicketController::upload_url | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||||
|
}
|
||||||
return $this->respond(['status' => true, 'message' => 'File uploaded successfully ']);
|
return $this->respond(['status' => true, 'message' => 'File uploaded successfully ']);
|
||||||
}else{
|
}else{
|
||||||
return $this->respond(['status' => false, 'message' => 'Failed to upload file ']);
|
return $this->respond(['status' => false, 'message' => 'Failed to upload file ']);
|
||||||
@ -3704,7 +3711,7 @@ class TicketController extends BaseController
|
|||||||
->select("
|
->select("
|
||||||
*,
|
*,
|
||||||
CASE
|
CASE
|
||||||
WHEN file_type = 2 AND url IS NOT NULL AND url != ''
|
WHEN file_type IN (2, 4) AND url IS NOT NULL AND url != ''
|
||||||
THEN CONCAT('" . base_url('downloadClaimFile/') . "', id)
|
THEN CONCAT('" . base_url('downloadClaimFile/') . "', id)
|
||||||
ELSE url
|
ELSE url
|
||||||
END AS url
|
END AS url
|
||||||
|
|||||||
@ -168,7 +168,7 @@ class VidalApiController extends BaseController
|
|||||||
->join('policy_type pt', 'pt.id = cp.policy_type_id', 'left')
|
->join('policy_type pt', 'pt.id = cp.policy_type_id', 'left')
|
||||||
->join('employee_polices ep', 'ep.employee_id = e.id AND ep.client_policy_id = cp.id', 'left')
|
->join('employee_polices ep', 'ep.employee_id = e.id AND ep.client_policy_id = cp.id', 'left')
|
||||||
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
->join('ticket_notes tn', 'tn.ticket_id = tm.id', 'left')
|
||||||
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 2 and cf.mime_type = 'application/pdf'", '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)
|
->where('tm.id', $claimId)
|
||||||
->get()
|
->get()
|
||||||
->getRowArray(); // single record
|
->getRowArray(); // single record
|
||||||
|
|||||||
@ -696,7 +696,7 @@ class VoloApiController extends BaseController
|
|||||||
cf.url as filePath
|
cf.url as filePath
|
||||||
')
|
')
|
||||||
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
->join('client_policy cp', 'tm.client_policy_id = cp.id', 'left')
|
||||||
->join('claim_files cf', "cf.ticket_id = tm.id AND cf.file_type = 2 AND cf.mime_type = 'application/pdf'", '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)
|
->where('tm.id', $claimId)
|
||||||
->get()
|
->get()
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|||||||
@ -10,43 +10,62 @@ use finfo;
|
|||||||
|
|
||||||
class GlobalPostFileUploadGuard implements FilterInterface
|
class GlobalPostFileUploadGuard implements FilterInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Max file size (in bytes) → 25MB
|
|
||||||
*/
|
|
||||||
protected int $maxFileSize = 25 * 1024 * 1024;
|
protected int $maxFileSize = 25 * 1024 * 1024;
|
||||||
|
|
||||||
/**
|
|
||||||
* Allowed MIME types mapped to extensions
|
|
||||||
*/
|
|
||||||
protected array $allowedMimeMap = [
|
protected array $allowedMimeMap = [
|
||||||
'image/jpeg' => ['jpg', 'jpeg'],
|
'image/jpeg' => ['jpg', 'jpeg'],
|
||||||
'image/png' => ['png'],
|
'image/png' => ['png'],
|
||||||
'image/gif' => ['gif'],
|
'image/gif' => ['gif'],
|
||||||
'image/webp' => ['webp'],
|
'image/webp' => ['webp'],
|
||||||
'image/svg+xml' => ['svg'],
|
// SVG removed — dangerous without server-side sanitization
|
||||||
'application/pdf' => ['pdf'],
|
'application/pdf' => ['pdf'],
|
||||||
'application/msword' => ['doc'],
|
'application/msword' => ['doc'],
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
|
||||||
'application/vnd.oasis.opendocument.text' => ['odt'],
|
'application/vnd.oasis.opendocument.text' => ['odt'],
|
||||||
'text/rtf' => ['rtf'],
|
'text/rtf' => ['rtf'],
|
||||||
'application/rtf' => ['rtf'],
|
'application/rtf' => ['rtf'],
|
||||||
'application/vnd.ms-excel' => ['xls', 'xlsx'],
|
'application/vnd.ms-excel' => ['xls'], // removed xlsx from here — it has its own MIME
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
|
||||||
'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
|
'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
|
||||||
'text/csv' => ['csv'],
|
'text/csv' => ['csv'],
|
||||||
'application/csv' => ['csv'],
|
'application/csv' => ['csv'],
|
||||||
'text/plain' => ['txt', 'csv'],
|
'text/plain' => ['txt'], // separated csv out — txt only
|
||||||
|
];
|
||||||
|
|
||||||
|
protected array $magicBytes = [
|
||||||
|
'image/jpeg' => ["\xFF\xD8\xFF"],
|
||||||
|
'image/png' => ["\x89PNG\r\n\x1a\n"],
|
||||||
|
'image/gif' => ["GIF87a", "GIF89a"],
|
||||||
|
'image/webp' => ["RIFF"],
|
||||||
|
'application/pdf' => ["%PDF-"],
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ["PK\x03\x04"],
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ["PK\x03\x04"],
|
||||||
|
'application/vnd.oasis.opendocument.text' => ["PK\x03\x04"],
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet' => ["PK\x03\x04"],
|
||||||
|
'application/msword' => ["\xD0\xCF\x11\xE0"],
|
||||||
|
'application/vnd.ms-excel' => ["\xD0\xCF\x11\xE0"],
|
||||||
];
|
];
|
||||||
|
|
||||||
protected array $blockedExtensions = [
|
protected array $blockedExtensions = [
|
||||||
'php', 'phtml', 'html', 'pht', 'phar', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps',
|
'php', 'phtml', 'html', 'htm', 'pht', 'phar',
|
||||||
'cgi', 'fcgi', 'pl', 'py', 'rb', 'lua', 'tcl', 'go', 'rs', 'jar', 'class',
|
'php3', 'php4', 'php5', 'php7', 'php8', 'phps',
|
||||||
'exe', 'dll', 'com', 'bat', 'cmd', 'msi', 'vbs', 'ps1', 'scr',
|
'cgi', 'fcgi', 'pl', 'py', 'rb', 'lua', 'tcl',
|
||||||
'sh', 'bash', 'zsh', 'apk', 'app', 'deb', 'rpm', 'bin', 'run',
|
'go', 'rs', 'jar', 'class',
|
||||||
'js', 'mjs', 'jsp', 'asp', 'aspx', 'cer', 'swf',
|
'exe', 'dll', 'com', 'bat', 'cmd', 'msi',
|
||||||
'env', 'ini', 'user.ini', 'htaccess', 'htpasswd', 'conf', 'config', 'log', 'sql',
|
'vbs', 'ps1', 'ps2', 'scr', 'hta',
|
||||||
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso',
|
'sh', 'bash', 'zsh', 'fish',
|
||||||
'lnk', 'url', 'reg', 'sys', 'drv', 'vxd', 'tmp', 'bak', 'old', 'backup', 'key', 'pem'
|
'apk', 'app', 'deb', 'rpm',
|
||||||
|
'bin', 'run', 'elf',
|
||||||
|
'js', 'mjs', 'ts', 'jsx', 'tsx',
|
||||||
|
'jsp', 'jspx', 'asp', 'aspx', 'ascx', 'ashx', 'axd',
|
||||||
|
'cer', 'swf', 'xhtml',
|
||||||
|
'env', 'ini', 'user.ini', 'htaccess', 'htpasswd',
|
||||||
|
'conf', 'config', 'log', 'sql', 'db', 'sqlite',
|
||||||
|
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso', 'cab',
|
||||||
|
'lnk', 'url', 'reg', 'sys', 'drv', 'vxd',
|
||||||
|
'tmp', 'bak', 'old', 'backup',
|
||||||
|
'key', 'pem', 'p12', 'pfx', 'crt', 'csr',
|
||||||
|
'xml', 'xsl', 'xslt', 'svg',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function before(RequestInterface $request, $arguments = null)
|
public function before(RequestInterface $request, $arguments = null)
|
||||||
@ -68,8 +87,8 @@ class GlobalPostFileUploadGuard implements FilterInterface
|
|||||||
private function validateFileInput($fileData, string $inputName): void
|
private function validateFileInput($fileData, string $inputName): void
|
||||||
{
|
{
|
||||||
if (is_array($fileData)) {
|
if (is_array($fileData)) {
|
||||||
foreach ($fileData as $file) {
|
foreach ($fileData as $key => $item) {
|
||||||
$this->validateSingleFile($file, $inputName);
|
$this->validateFileInput($item, $inputName . '[' . $key . ']');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->validateSingleFile($fileData, $inputName);
|
$this->validateSingleFile($fileData, $inputName);
|
||||||
@ -78,7 +97,7 @@ class GlobalPostFileUploadGuard implements FilterInterface
|
|||||||
|
|
||||||
private function validateSingleFile($file, string $inputName): void
|
private function validateSingleFile($file, string $inputName): void
|
||||||
{
|
{
|
||||||
$request = Services::request();
|
$request = Services::request();
|
||||||
$clientIp = $request->getIPAddress();
|
$clientIp = $request->getIPAddress();
|
||||||
$uri = $request->getUri()->getPath();
|
$uri = $request->getUri()->getPath();
|
||||||
|
|
||||||
@ -89,42 +108,208 @@ class GlobalPostFileUploadGuard implements FilterInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$originalName = $file->getClientName();
|
$originalName = $file->getClientName();
|
||||||
$extension = strtolower($file->getExtension());
|
$clientExtension = strtolower($file->getClientExtension());
|
||||||
$mime = $file->getMimeType();
|
$tempPath = $file->getTempName();
|
||||||
$size = $file->getSize();
|
$size = $file->getSize();
|
||||||
|
|
||||||
// --- 1. Fixed Null Byte & Path Traversal Check ---
|
// echo $file->getExtension();
|
||||||
if (preg_match('/\0|[\/\\\]/', $originalName)) {
|
// echo '@@@@@@@@@@@@@@@@@@@@@@@';
|
||||||
$this->block("Malicious filename characters", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
// echo $file->getMimeType();
|
||||||
|
// --- 1. Null Byte & Path Traversal Check ---
|
||||||
|
if (preg_match('/\0|[\/\\\\]/', $originalName)) {
|
||||||
|
$this->block("Malicious filename characters", $clientIp, $uri, $inputName, $originalName, 'unknown', $clientExtension, $size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Double Extension Attack Check ---
|
// --- 2. Filename length check ---
|
||||||
if (preg_match('/\.(php|html|phtml|phar|exe|sh|bat|cmd|js|jsp|asp|aspx|py|pl)\./i', $originalName)) {
|
if (strlen($originalName) > 255) {
|
||||||
$this->block("Double extension attack", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
$this->block("Filename too long", $clientIp, $uri, $inputName, substr($originalName, 0, 100) . '...', 'unknown', $clientExtension, $size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. Forbidden Extension ---
|
// --- 3. Double/Multiple Extension Attack ---
|
||||||
if (in_array($extension, $this->blockedExtensions, true)) {
|
$nameParts = explode('.', $originalName);
|
||||||
$this->block("Forbidden extension", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
if (count($nameParts) > 2) {
|
||||||
|
$allExtensions = array_slice($nameParts, 1);
|
||||||
|
foreach ($allExtensions as $part) {
|
||||||
|
if (in_array(strtolower($part), $this->blockedExtensions, true)) {
|
||||||
|
$this->block("Double/multiple extension attack detected", $clientIp, $uri, $inputName, $originalName, 'unknown', $clientExtension, $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 4. File Size Limit ---
|
// --- 4. Forbidden Extension ---
|
||||||
|
if (in_array($clientExtension, $this->blockedExtensions, true)) {
|
||||||
|
$this->block("Forbidden extension", $clientIp, $uri, $inputName, $originalName, 'unknown', $clientExtension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. File Size Limit ---
|
||||||
if ($size > $this->maxFileSize) {
|
if ($size > $this->maxFileSize) {
|
||||||
$this->block("File too large", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
$this->block("File too large", $clientIp, $uri, $inputName, $originalName, 'unknown', $clientExtension, $size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 5. MIME Allow-list Check ---
|
// --- 6. Real MIME Detection via finfo on actual temp file ---
|
||||||
if (!array_key_exists($mime, $this->allowedMimeMap)) {
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
$this->block("MIME type not allowed ($mime)", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
$detectedMime = $finfo->file($tempPath);
|
||||||
|
|
||||||
|
// --- 7. Resolve Expected MIME From Extension ---
|
||||||
|
$expectedMime = null;
|
||||||
|
|
||||||
|
foreach ($this->allowedMimeMap as $mime => $extensions) {
|
||||||
|
|
||||||
|
if (in_array($clientExtension, $extensions, true)) {
|
||||||
|
$expectedMime = $mime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 6. MIME-Extension Consistency ---
|
if ($expectedMime === null) {
|
||||||
if (!in_array($extension, $this->allowedMimeMap[$mime], true)) {
|
$this->block(
|
||||||
$this->block("MIME-extension mismatch", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
"Extension not allowed",
|
||||||
|
$clientIp,
|
||||||
|
$uri,
|
||||||
|
$inputName,
|
||||||
|
$originalName,
|
||||||
|
$detectedMime,
|
||||||
|
$clientExtension,
|
||||||
|
$size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 8. Magic Bytes Deep Validation ---
|
||||||
|
// validate using EXPECTED MIME instead of detected MIME
|
||||||
|
$this->validateMagicBytes(
|
||||||
|
$tempPath,
|
||||||
|
$expectedMime,
|
||||||
|
$clientIp,
|
||||||
|
$uri,
|
||||||
|
$inputName,
|
||||||
|
$originalName,
|
||||||
|
$clientExtension,
|
||||||
|
$size
|
||||||
|
);
|
||||||
|
|
||||||
|
// // --- 9. Embedded Code Scan ---
|
||||||
|
// $this->scanForEmbeddedCode(
|
||||||
|
// $tempPath,
|
||||||
|
// $expectedMime,
|
||||||
|
// $clientIp,
|
||||||
|
// $uri,
|
||||||
|
// $inputName,
|
||||||
|
// $originalName,
|
||||||
|
// $clientExtension,
|
||||||
|
// $size
|
||||||
|
// );
|
||||||
|
|
||||||
|
// --- 10. Final MIME Validation ---
|
||||||
|
// strict match only: generic octet-stream is not accepted
|
||||||
|
if ($detectedMime !== $expectedMime) {
|
||||||
|
$this->block(
|
||||||
|
"MIME-extension mismatch",
|
||||||
|
$clientIp,
|
||||||
|
$uri,
|
||||||
|
$inputName,
|
||||||
|
$originalName,
|
||||||
|
$detectedMime,
|
||||||
|
$clientExtension,
|
||||||
|
$size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 10. Embedded Code Scan ---
|
||||||
|
// $this->scanForEmbeddedCode($tempPath, $detectedMime, $clientIp, $uri, $inputName, $originalName, $clientExtension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateMagicBytes(
|
||||||
|
string $tempPath,
|
||||||
|
string $mime,
|
||||||
|
string $ip,
|
||||||
|
string $uri,
|
||||||
|
string $field,
|
||||||
|
string $filename,
|
||||||
|
string $ext,
|
||||||
|
int $size
|
||||||
|
): void {
|
||||||
|
if (!isset($this->magicBytes[$mime])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// echo $mime;print_r($this->magicBytes[$mime]);
|
||||||
|
$handle = fopen($tempPath, 'rb');
|
||||||
|
if (!$handle) {
|
||||||
|
$this->block("Cannot read uploaded file", $ip, $uri, $field, $filename, $mime, $ext, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = fread($handle, 12);
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
$matched = false;
|
||||||
|
foreach ($this->magicBytes[$mime] as $signature) {
|
||||||
|
if (str_starts_with($header, $signature)) {
|
||||||
|
$matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// echo $matched;die;
|
||||||
|
// WebP special case: RIFF....WEBP
|
||||||
|
if ($mime === 'image/webp') {
|
||||||
|
$matched = (substr($header, 0, 4) === 'RIFF' && substr($header, 8, 4) === 'WEBP');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$matched) {
|
||||||
|
$this->block("Magic bytes mismatch for MIME: $mime", $ip, $uri, $field, $filename, $mime, $ext, $size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function scanForEmbeddedCode(
|
||||||
|
string $tempPath,
|
||||||
|
string $mime,
|
||||||
|
string $ip,
|
||||||
|
string $uri,
|
||||||
|
string $field,
|
||||||
|
string $filename,
|
||||||
|
string $ext,
|
||||||
|
int $size
|
||||||
|
): void {
|
||||||
|
$scanLimit = 1024 * 1024;
|
||||||
|
|
||||||
|
$content = '';
|
||||||
|
if ($size <= $scanLimit) {
|
||||||
|
$content = file_get_contents($tempPath);
|
||||||
|
} else {
|
||||||
|
$handle = fopen($tempPath, 'rb');
|
||||||
|
if ($handle) {
|
||||||
|
$content = fread($handle, 10240);
|
||||||
|
fseek($handle, -10240, SEEK_END);
|
||||||
|
$content .= fread($handle, 10240);
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($content)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dangerousPatterns = [
|
||||||
|
'/<\?php/i',
|
||||||
|
'/<\?=/i',
|
||||||
|
'/<\?/i',
|
||||||
|
'/<%/i',
|
||||||
|
'/<script\s+.*?runat\s*=\s*["\']?server/i',
|
||||||
|
'/eval\s*\(/i',
|
||||||
|
'/base64_decode\s*\(/i',
|
||||||
|
'/system\s*\(/i',
|
||||||
|
'/exec\s*\(/i',
|
||||||
|
'/passthru\s*\(/i',
|
||||||
|
'/shell_exec\s*\(/i',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($dangerousPatterns as $pattern) {
|
||||||
|
if (preg_match($pattern, $content)) {
|
||||||
|
$this->block("Embedded code pattern detected in file", $ip, $uri, $field, $filename, $mime, $ext, $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// block() signature and body kept EXACTLY as original — only $ext source changed upstream
|
||||||
private function block(string $reason, string $ip, string $uri, string $field, string $filename, string $mime, string $ext, int $size): void
|
private function block(string $reason, string $ip, string $uri, string $field, string $filename, string $mime, string $ext, int $size): void
|
||||||
{
|
{
|
||||||
log_message('critical',
|
log_message('critical',
|
||||||
|
|||||||
150
app/Filters/GlobalPostFileUploadGuard_OLD.php
Normal file
150
app/Filters/GlobalPostFileUploadGuard_OLD.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filters;
|
||||||
|
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use CodeIgniter\Filters\FilterInterface;
|
||||||
|
use Config\Services;
|
||||||
|
use finfo;
|
||||||
|
|
||||||
|
class GlobalPostFileUploadGuard implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Max file size (in bytes) → 25MB
|
||||||
|
*/
|
||||||
|
protected int $maxFileSize = 25 * 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed MIME types mapped to extensions
|
||||||
|
*/
|
||||||
|
protected array $allowedMimeMap = [
|
||||||
|
'image/jpeg' => ['jpg', 'jpeg'],
|
||||||
|
'image/png' => ['png'],
|
||||||
|
'image/gif' => ['gif'],
|
||||||
|
'image/webp' => ['webp'],
|
||||||
|
'image/svg+xml' => ['svg'],
|
||||||
|
'application/pdf' => ['pdf'],
|
||||||
|
'application/msword' => ['doc'],
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
|
||||||
|
'application/vnd.oasis.opendocument.text' => ['odt'],
|
||||||
|
'text/rtf' => ['rtf'],
|
||||||
|
'application/rtf' => ['rtf'],
|
||||||
|
'application/vnd.ms-excel' => ['xls', 'xlsx'],
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
|
||||||
|
'text/csv' => ['csv'],
|
||||||
|
'application/csv' => ['csv'],
|
||||||
|
'text/plain' => ['txt', 'csv'],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected array $blockedExtensions = [
|
||||||
|
'php', 'phtml', 'html', 'pht', 'phar', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps',
|
||||||
|
'cgi', 'fcgi', 'pl', 'py', 'rb', 'lua', 'tcl', 'go', 'rs', 'jar', 'class',
|
||||||
|
'exe', 'dll', 'com', 'bat', 'cmd', 'msi', 'vbs', 'ps1', 'scr',
|
||||||
|
'sh', 'bash', 'zsh', 'apk', 'app', 'deb', 'rpm', 'bin', 'run',
|
||||||
|
'js', 'mjs', 'jsp', 'asp', 'aspx', 'cer', 'swf',
|
||||||
|
'env', 'ini', 'user.ini', 'htaccess', 'htpasswd', 'conf', 'config', 'log', 'sql',
|
||||||
|
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso',
|
||||||
|
'lnk', 'url', 'reg', 'sys', 'drv', 'vxd', 'tmp', 'bak', 'old', 'backup', 'key', 'pem'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function before(RequestInterface $request, $arguments = null)
|
||||||
|
{
|
||||||
|
if ($request->getMethod() !== 'post') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $request->getFiles();
|
||||||
|
if (empty($files)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $inputName => $fileData) {
|
||||||
|
$this->validateFileInput($fileData, $inputName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateFileInput($fileData, string $inputName): void
|
||||||
|
{
|
||||||
|
if (is_array($fileData)) {
|
||||||
|
foreach ($fileData as $file) {
|
||||||
|
$this->validateSingleFile($file, $inputName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->validateSingleFile($fileData, $inputName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateSingleFile($file, string $inputName): void
|
||||||
|
{
|
||||||
|
$request = Services::request();
|
||||||
|
$clientIp = $request->getIPAddress();
|
||||||
|
$uri = $request->getUri()->getPath();
|
||||||
|
|
||||||
|
if (!$file->isValid()) {
|
||||||
|
if ($file->getError() === UPLOAD_ERR_INI_SIZE || $file->getError() === UPLOAD_ERR_FORM_SIZE) {
|
||||||
|
$this->block("File exceeds server-side size limit", $clientIp, $uri, $inputName, $file->getClientName(), 'unknown', 'unknown', 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = $file->getClientName();
|
||||||
|
$extension = strtolower($file->getExtension());
|
||||||
|
$client_extension = strtolower($file->getClientExtension());
|
||||||
|
$mime = $file->getMimeType();
|
||||||
|
$size = $file->getSize();
|
||||||
|
|
||||||
|
echo $extension.' - '.$client_extension;die;
|
||||||
|
|
||||||
|
// --- 1. Fixed Null Byte & Path Traversal Check ---
|
||||||
|
if (preg_match('/\0|[\/\\\]/', $originalName)) {
|
||||||
|
$this->block("Malicious filename characters", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. Double Extension Attack Check ---
|
||||||
|
if (preg_match('/\.(php|html|phtml|phar|exe|sh|bat|cmd|js|jsp|asp|aspx|py|pl)\./i', $originalName)) {
|
||||||
|
$this->block("Double extension attack", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Forbidden Extension ---
|
||||||
|
if (in_array($extension, $this->blockedExtensions, true)) {
|
||||||
|
$this->block("Forbidden extension", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. File Size Limit ---
|
||||||
|
if ($size > $this->maxFileSize) {
|
||||||
|
$this->block("File too large", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 5. MIME Allow-list Check ---
|
||||||
|
if (!array_key_exists($mime, $this->allowedMimeMap)) {
|
||||||
|
$this->block("MIME type not allowed ($mime)", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 6. MIME-Extension Consistency ---
|
||||||
|
if (!in_array($extension, $this->allowedMimeMap[$mime], true)) {
|
||||||
|
$this->block("MIME-extension mismatch", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function block(string $reason, string $ip, string $uri, string $field, string $filename, string $mime, string $ext, int $size): void
|
||||||
|
{
|
||||||
|
log_message('critical',
|
||||||
|
'[UPLOAD_BLOCKED] {reason} | IP: {ip} | URI: {uri} | Field: {field} | File: {file} | MIME: {mime} | EXT: {ext} | SIZE: {size}',
|
||||||
|
['reason'=>$reason, 'ip'=>$ip, 'uri'=>$uri, 'field'=>$field, 'file'=>$filename, 'mime'=>$mime, 'ext'=>$ext, 'size'=>$size]
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = Services::response();
|
||||||
|
$response->setStatusCode(403)
|
||||||
|
->setJSON([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'File upload rejected: Security policy violation.' . ($reason ? " Reason: $reason." : ''),
|
||||||
|
'debug' => (ENVIRONMENT === 'development') ? $reason : null
|
||||||
|
])
|
||||||
|
->send();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {}
|
||||||
|
}
|
||||||
194
app/Helpers/merge_pdf_helper.php
Normal file
194
app/Helpers/merge_pdf_helper.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\ClaimFilesModel;
|
||||||
|
use Mpdf\Mpdf;
|
||||||
|
use Mpdf\Output\Destination;
|
||||||
|
|
||||||
|
if (! defined('MERGED_CLAIM_FILE_TYPE')) {
|
||||||
|
// claim_files.file_type values currently in use:
|
||||||
|
// 1 = legacy letter, 2 = uploaded claim doc, 3 = approved/settle letter
|
||||||
|
// 4 = merged combined PDF (registered by this helper)
|
||||||
|
define('MERGED_CLAIM_FILE_TYPE', 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('merge_ticket_pdfs')) {
|
||||||
|
/**
|
||||||
|
* Merge all active PDF rows in claim_files for a given ticket_master id
|
||||||
|
* into one combined PDF and register that PDF as a new claim_files row
|
||||||
|
* with file_type = MERGED_CLAIM_FILE_TYPE.
|
||||||
|
*
|
||||||
|
* Source rows are picked from claim_files where:
|
||||||
|
* - ticket_id = $ticket_master_id
|
||||||
|
* - is_active = 1
|
||||||
|
* - mime_type = 'application/pdf'
|
||||||
|
* - file_type IN $opts['include_file_types'] (default [2, 3])
|
||||||
|
*
|
||||||
|
* @param int $ticket_master_id
|
||||||
|
* @param array $opts {
|
||||||
|
* @var bool $replace Default true. Soft-delete previous merged row before re-creating.
|
||||||
|
* @var int $created_by Override created_by user id on the inserted row.
|
||||||
|
* @var int $ticket_type Default 1. Stored on the inserted claim_files row.
|
||||||
|
* @var array $include_file_types Default [2, 3].
|
||||||
|
* }
|
||||||
|
* @return array {status, merged_file_id, file_name, pages, source_count, message}
|
||||||
|
*/
|
||||||
|
function merge_ticket_pdfs(int $ticket_master_id, array $opts = []): array
|
||||||
|
{
|
||||||
|
$opts += [
|
||||||
|
'replace' => true,
|
||||||
|
'created_by' => null,
|
||||||
|
'ticket_type' => 1,
|
||||||
|
'include_file_types' => [2, 3],
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'status' => false,
|
||||||
|
'merged_file_id' => null,
|
||||||
|
'file_name' => null,
|
||||||
|
'pages' => 0,
|
||||||
|
'source_count' => 0,
|
||||||
|
'message' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($ticket_master_id <= 0) {
|
||||||
|
$result['message'] = 'Invalid ticket_master_id';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$claimFiles = new ClaimFilesModel();
|
||||||
|
|
||||||
|
$rows = $claimFiles
|
||||||
|
->where('ticket_id', $ticket_master_id)
|
||||||
|
->where('is_active', 1)
|
||||||
|
->where('mime_type', 'application/pdf')
|
||||||
|
->whereIn('file_type', $opts['include_file_types'])
|
||||||
|
->orderBy('id', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
if (empty($rows)) {
|
||||||
|
$result['status'] = true;
|
||||||
|
$result['message'] = 'No PDF files to merge';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR
|
||||||
|
. 'uploads' . DIRECTORY_SEPARATOR
|
||||||
|
. 'claim_files' . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
$sourcePaths = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$name = ! empty($row['url']) ? $row['url'] : ($row['file_name'] ?? '');
|
||||||
|
if (empty($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// url column may sometimes hold a full URL; we only care about the file basename on disk.
|
||||||
|
$full = $uploadDir . basename($name);
|
||||||
|
if (is_file($full) && is_readable($full)) {
|
||||||
|
$sourcePaths[] = $full;
|
||||||
|
} else {
|
||||||
|
log_message('error', "merge_ticket_pdfs | missing PDF on disk | claim_file_id={$row['id']} | path={$full}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($sourcePaths)) {
|
||||||
|
$result['message'] = 'No readable PDF files on disk';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['source_count'] = count($sourcePaths);
|
||||||
|
|
||||||
|
$tempDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
|
||||||
|
if (! is_dir($tempDir)) {
|
||||||
|
@mkdir($tempDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mergedName = 'merged_' . $ticket_master_id . '_' . time() . '_' . bin2hex(random_bytes(5)) . '.pdf';
|
||||||
|
$mergedPath = $uploadDir . $mergedName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mpdf = new Mpdf([
|
||||||
|
'tempDir' => $tempDir,
|
||||||
|
'mode' => 'utf-8',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$totalPages = 0;
|
||||||
|
foreach ($sourcePaths as $src) {
|
||||||
|
try {
|
||||||
|
$pageCount = $mpdf->setSourceFile($src);
|
||||||
|
for ($p = 1; $p <= $pageCount; $p++) {
|
||||||
|
$tplId = $mpdf->importPage($p);
|
||||||
|
$size = $mpdf->getTemplateSize($tplId);
|
||||||
|
$mpdf->AddPageByArray([
|
||||||
|
'orientation' => ($size['width'] > $size['height']) ? 'L' : 'P',
|
||||||
|
'sheet-size' => [$size['width'], $size['height']],
|
||||||
|
]);
|
||||||
|
$mpdf->useTemplate($tplId);
|
||||||
|
$totalPages++;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', "merge_ticket_pdfs | failed to import {$src} | " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($totalPages === 0) {
|
||||||
|
$result['message'] = 'All source PDFs failed to import';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mpdf->Output($mergedPath, Destination::FILE);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'merge_ticket_pdfs | mpdf failure | ticket_id=' . $ticket_master_id . ' | ' . $e->getMessage());
|
||||||
|
$result['message'] = 'Merge failed: ' . $e->getMessage();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_file($mergedPath)) {
|
||||||
|
$result['message'] = 'Merged file was not created';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($opts['replace']) {
|
||||||
|
$claimFiles
|
||||||
|
->where('ticket_id', $ticket_master_id)
|
||||||
|
->where('file_type', MERGED_CLAIM_FILE_TYPE)
|
||||||
|
->where('is_active', 1)
|
||||||
|
->set(['is_active' => 0])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertData = [
|
||||||
|
'ticket_id' => $ticket_master_id,
|
||||||
|
'ticket_type' => $opts['ticket_type'],
|
||||||
|
'file_type' => MERGED_CLAIM_FILE_TYPE,
|
||||||
|
'doc_name' => 'MERGED_CLAIM_DOCS_PDF',
|
||||||
|
'file_name' => $mergedName,
|
||||||
|
'url' => $mergedName,
|
||||||
|
'mime_type' => 'application/pdf',
|
||||||
|
'is_active' => 1,
|
||||||
|
];
|
||||||
|
if (! empty($opts['created_by'])) {
|
||||||
|
$insertData['created_by'] = $opts['created_by'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertedId = $claimFiles->insert($insertData);
|
||||||
|
|
||||||
|
if (! $insertedId) {
|
||||||
|
log_message('error', 'merge_ticket_pdfs | DB insert failed for merged file | ticket_id=' . $ticket_master_id);
|
||||||
|
@unlink($mergedPath);
|
||||||
|
$result['message'] = 'Failed to register merged file in claim_files';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message(
|
||||||
|
'info',
|
||||||
|
"merge_ticket_pdfs | OK | ticket_id={$ticket_master_id} | sources={$result['source_count']} | pages={$totalPages} | claim_file_id={$insertedId}"
|
||||||
|
);
|
||||||
|
|
||||||
|
$result['status'] = true;
|
||||||
|
$result['merged_file_id'] = (int) $insertedId;
|
||||||
|
$result['file_name'] = $mergedName;
|
||||||
|
$result['pages'] = $totalPages;
|
||||||
|
$result['message'] = 'Merged successfully';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -185,6 +185,27 @@
|
|||||||
|
|
||||||
<?php if(in_array(get_role_id(), [1,2,3,4,5]) || (in_array(ENROLLMENT_TEAM_ID, user_team()) || in_array(CLAIMS_TEAM_ID, user_team()) || in_array(MANAGEMENT_TEAM_ID, user_team()) || in_array(FINANCE_TEAM_ID, user_team()) || in_array(BUSINESS_TEAM_ID, user_team()))) { ?>
|
<?php if(in_array(get_role_id(), [1,2,3,4,5]) || (in_array(ENROLLMENT_TEAM_ID, user_team()) || in_array(CLAIMS_TEAM_ID, user_team()) || in_array(MANAGEMENT_TEAM_ID, user_team()) || in_array(FINANCE_TEAM_ID, user_team()) || in_array(BUSINESS_TEAM_ID, user_team()))) { ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Exactly one tab-pane must be active initially; claims_dash/leads_dash used to each set
|
||||||
|
// $isActive so dual-team staff saw both panes (Bootstrap shows every .active pane).
|
||||||
|
$dash_show_pending_nav = in_array(get_role_id(), [1, 2, 3, 5]);
|
||||||
|
$dash_show_claims_nav = (get_role_id() == STAFF_ROLE_ID && in_array(CLAIMS_TEAM_ID, user_team())) || in_array(get_role_id(), [1, 5]);
|
||||||
|
$dash_show_leads_nav = (get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID, user_team())) || in_array(get_role_id(), [1, 5]);
|
||||||
|
|
||||||
|
$dash_default_pane = null;
|
||||||
|
if ($dash_show_pending_nav) {
|
||||||
|
$dash_default_pane = 'pending';
|
||||||
|
} elseif ($dash_show_claims_nav) {
|
||||||
|
$dash_default_pane = 'claims';
|
||||||
|
} elseif ($dash_show_leads_nav) {
|
||||||
|
$dash_default_pane = 'leads';
|
||||||
|
}
|
||||||
|
|
||||||
|
$dash_pending_pane_active = ($dash_default_pane === 'pending') ? 'active show' : '';
|
||||||
|
$dash_claims_pane_active = ($dash_default_pane === 'claims') ? 'active show' : '';
|
||||||
|
$dash_leads_pane_active = ($dash_default_pane === 'leads') ? 'active show' : '';
|
||||||
|
?>
|
||||||
|
|
||||||
<!-- <div class="row" id="client_add"> -->
|
<!-- <div class="row" id="client_add"> -->
|
||||||
<div class="row" id="client_add" style="margin-top:12px!important">
|
<div class="row" id="client_add" style="margin-top:12px!important">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -222,9 +243,6 @@
|
|||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<?php if ((get_role_id() == STAFF_ROLE_ID && in_array(CLAIMS_TEAM_ID,user_team())) || in_array(get_role_id(),[1,5])) :?>
|
<?php if ((get_role_id() == STAFF_ROLE_ID && in_array(CLAIMS_TEAM_ID,user_team())) || in_array(get_role_id(),[1,5])) :?>
|
||||||
<?php
|
|
||||||
$isActive = get_role_id() == STAFF_ROLE_ID && in_array(CLAIMS_TEAM_ID,user_team()) ? 'active show' : '';
|
|
||||||
?>
|
|
||||||
<li class="nav-item d-flex justify-content-center align-items-center">
|
<li class="nav-item d-flex justify-content-center align-items-center">
|
||||||
<a href="#claims-dash-tab" data-toggle="tab" aria-expanded="true" class="nav-link px-3 py-1 dash-anchor nav-dash" id="claims_tab">
|
<a href="#claims-dash-tab" data-toggle="tab" aria-expanded="true" class="nav-link px-3 py-1 dash-anchor nav-dash" id="claims_tab">
|
||||||
<img src="<?= base_url() . "public"; ?>/assets/images/inactive_claims.png" alt="Logo" height="14" class="inactive_claims">
|
<img src="<?= base_url() . "public"; ?>/assets/images/inactive_claims.png" alt="Logo" height="14" class="inactive_claims">
|
||||||
@ -237,9 +255,9 @@
|
|||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ((get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID,user_team())) || in_array(get_role_id(),[1,5])) :?>
|
<?php if ((get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID,user_team())) || in_array(get_role_id(),[1,5])) :?>
|
||||||
<?php
|
<!-- <?php
|
||||||
$isActive = get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID,user_team()) ? 'active show' : '';
|
// $isActive = get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID,user_team()) ? 'active show' : '';
|
||||||
?>
|
?> -->
|
||||||
<li class="nav-item d-flex justify-content-center align-items-center ">
|
<li class="nav-item d-flex justify-content-center align-items-center ">
|
||||||
<a href="#leads-dash-tab " data-toggle="tab" aria-expanded="true" class="nav-link px-3 py-1 dash-anchor nav-dash" id="leads_tab" >
|
<a href="#leads-dash-tab " data-toggle="tab" aria-expanded="true" class="nav-link px-3 py-1 dash-anchor nav-dash" id="leads_tab" >
|
||||||
<img src="<?= base_url() . "public"; ?>/assets/images/inactive_leads_and_bds_renewal.png" alt="Logo" height="14" class="inactive_leads">
|
<img src="<?= base_url() . "public"; ?>/assets/images/inactive_leads_and_bds_renewal.png" alt="Logo" height="14" class="inactive_leads">
|
||||||
|
|||||||
@ -475,13 +475,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function claimFileIsLocalUpload(item) {
|
||||||
|
// file_type 2 = uploaded claim doc, 4 = merged combined PDF (both stored on disk)
|
||||||
|
return item.file_type == 2 || item.file_type === '2'
|
||||||
|
|| item.file_type == 4 || item.file_type === '4';
|
||||||
|
}
|
||||||
|
|
||||||
function claimFileViewSupported(item) {
|
function claimFileViewSupported(item) {
|
||||||
var mime = String(item.mime_type || '').toLowerCase();
|
var mime = String(item.mime_type || '').toLowerCase();
|
||||||
if (mime === 'application/pdf' || mime === 'image/png' || mime === 'image/jpeg') {
|
if (mime === 'application/pdf' || mime === 'image/png' || mime === 'image/jpeg') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var ext = '';
|
var ext = '';
|
||||||
if (item.file_type == 2 || item.file_type === '2') {
|
if (claimFileIsLocalUpload(item)) {
|
||||||
ext = claimFileListExtension(item.file_name) || claimFileListExtension(item.doc_name);
|
ext = claimFileListExtension(item.file_name) || claimFileListExtension(item.doc_name);
|
||||||
} else {
|
} else {
|
||||||
ext = claimFileListExtensionFromUrl(item.url);
|
ext = claimFileListExtensionFromUrl(item.url);
|
||||||
@ -493,14 +499,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function claimFileViewOpenUrl(item, base_url) {
|
function claimFileViewOpenUrl(item, base_url) {
|
||||||
if (item.file_type == 2 || item.file_type === '2') {
|
if (claimFileIsLocalUpload(item)) {
|
||||||
return base_url + 'viewClaimFile/' + encodeURIComponent(item.id);
|
return base_url + 'viewClaimFile/' + encodeURIComponent(item.id);
|
||||||
}
|
}
|
||||||
return String(item.url || '');
|
return String(item.url || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function claimFileDownloadOpenUrl(item, base_url) {
|
function claimFileDownloadOpenUrl(item, base_url) {
|
||||||
if (item.file_type == 2 || item.file_type === '2') {
|
if (claimFileIsLocalUpload(item)) {
|
||||||
return base_url + 'downloadClaimFile/' + encodeURIComponent(item.id);
|
return base_url + 'downloadClaimFile/' + encodeURIComponent(item.id);
|
||||||
}
|
}
|
||||||
return String(item.url || '');
|
return String(item.url || '');
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Swagger UI</title>
|
<title><?= esc($pageTitle ?? 'Swagger UI') ?></title>
|
||||||
<link rel="stylesheet" type="text/css" href="<?= base_url('assets/swagger/swagger-ui.css') ?>">
|
<link rel="stylesheet" type="text/css" href="<?= base_url('assets/swagger/swagger-ui.css') ?>">
|
||||||
<link rel="icon" type="image/png" href="<?= base_url('assets/swagger/favicon-32x32.png') ?> sizes=" 32x32" />
|
<link rel="icon" type="image/png" href="<?= base_url('assets/swagger/favicon-32x32.png') ?> sizes=" 32x32" />
|
||||||
<link rel="icon" type="image/png" href="<?= base_url('assets/swagger/favicon-16x16.png') ?> sizes=" 16x16" />
|
<link rel="icon" type="image/png" href="<?= base_url('assets/swagger/favicon-16x16.png') ?> sizes=" 16x16" />
|
||||||
@ -31,15 +31,15 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="swagger-ui"></div>
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
<script src="<?= base_url('assets/swagger/swagger-ui-bundle.js') ?>">console.log(base_url); </script>
|
<script src="<?= base_url('assets/swagger/swagger-ui-bundle.js') ?>"></script>
|
||||||
<script src="<?= base_url('assets/swagger/swagger-ui-standalone-preset.js') ?>"> </script>
|
<script src="<?= base_url('assets/swagger/swagger-ui-standalone-preset.js') ?>"></script>
|
||||||
<script>
|
<script>
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Begin Swagger UI call region
|
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "<?= base_url('assets/api.yaml') ?>",
|
url: "<?= esc($specUrl ?? base_url('assets/api.yaml'), 'js') ?>",
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
|
persistAuthorization: true,
|
||||||
presets: [
|
presets: [
|
||||||
SwaggerUIBundle.presets.apis,
|
SwaggerUIBundle.presets.apis,
|
||||||
SwaggerUIStandalonePreset
|
SwaggerUIStandalonePreset
|
||||||
@ -48,12 +48,28 @@
|
|||||||
SwaggerUIBundle.plugins.DownloadUrl
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
],
|
],
|
||||||
layout: "StandaloneLayout"
|
layout: "StandaloneLayout"
|
||||||
})
|
});
|
||||||
// End Swagger UI call region
|
|
||||||
|
|
||||||
window.ui = ui
|
window.ui = ui;
|
||||||
}
|
|
||||||
|
|
||||||
|
<?php if (! empty($swaggerPreauthorize)): ?>
|
||||||
|
try {
|
||||||
|
<?php foreach ($swaggerPreauthorize as $row): ?>
|
||||||
|
<?php if (($row['kind'] ?? '') === 'apiKey'): ?>
|
||||||
|
if (typeof ui.preauthorizeApiKey === 'function') {
|
||||||
|
ui.preauthorizeApiKey(<?= json_encode($row['scheme'] ?? '') ?>, <?= json_encode($row['value'] ?? '') ?>);
|
||||||
|
}
|
||||||
|
<?php elseif (($row['kind'] ?? '') === 'basic'): ?>
|
||||||
|
if (typeof ui.preauthorizeBasic === 'function') {
|
||||||
|
ui.preauthorizeBasic(<?= json_encode($row['scheme'] ?? '') ?>, <?= json_encode($row['username'] ?? '') ?>, <?= json_encode($row['password'] ?? '') ?>);
|
||||||
|
}
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Swagger preauthorize', e);
|
||||||
|
}
|
||||||
|
<?php endif; ?>
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
922
openapi/tpa-external-integrations.yaml
Normal file
922
openapi/tpa-external-integrations.yaml
Normal file
@ -0,0 +1,922 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Nhance — TPA external API integrations
|
||||||
|
description: |
|
||||||
|
OpenAPI description of **outbound** HTTP calls made by Nhance to third-party TPA systems.
|
||||||
|
Base URLs and credentials come from environment variables (see **Authorize** / security schemes below).
|
||||||
|
This is **not** the public REST API of the Nhance application itself.
|
||||||
|
|
||||||
|
Controllers: `MediAssistApiController`, `VidalApiController`, `VoloApiController`,
|
||||||
|
`FhplApiController`, `HealthIndiaApiController`.
|
||||||
|
|
||||||
|
**Credentials (.env)** — use the same variable names as `getenv()` / `env()` in PHP:
|
||||||
|
MediAssist: `MEDI_ASSIST_API_USERNAME`, `MEDI_ASSIST_API_PASSWORD`; URLs: `MEDI_ASSIST_API_BASE_URL_*`.
|
||||||
|
Vidal: `VIDAL_API_SUBSCRIPTION_KEY`, `VIDAL_SUBSCRIPTION_KEY` (IR only), `VIDAL_WELLNESS_SUBSCRIPTION_KEY`; base `VIDAL_API_BASE_URL`.
|
||||||
|
Volo login: `VOLO_API_EMAIL`, `VOLO_API_PASSWORD`, `VOLO_LOGGED_IN_PORTAL`; bases `VOLO_API_ADMIN_BASE_URL`, `VOLO_API_CONSUMER_BASE_URL`.
|
||||||
|
FHPL: `FHPL_TOKEN_URL`, `FHPL_USER_NAME`, `FHPL_PASSWORD`, `FHPL_GRANT_TYPE`; API `FHPL_BASE_URL`.
|
||||||
|
Health India: `HEALTH_INDIA_TOKEN_URL`, `HEALTH_INDIA_USERNAME`, `HEALTH_INDIA_PASSWORD`; API `HEALTH_INDIA_BASE_URL`.
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: MediAssist
|
||||||
|
- name: Vidal
|
||||||
|
- name: Volo
|
||||||
|
- name: FHPL
|
||||||
|
- name: HealthIndia
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://example.invalid
|
||||||
|
description: >
|
||||||
|
Default placeholder only. Each TPA uses its own base URL from environment variables;
|
||||||
|
many MediAssist endpoints use a full URL per env var (see operation descriptions).
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# MediAssist — servers vary per env var (often full URL including path).
|
||||||
|
# Default hosts shown are illustrative; override via env.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
paths:
|
||||||
|
|
||||||
|
/mediassist/claim-submit:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: Submit claim (claim push)
|
||||||
|
description: |
|
||||||
|
URL from `MEDI_ASSIST_API_BASE_URL_CLAIMSUBMIT` (full URL).
|
||||||
|
Headers `Username` / `Password` from `MEDI_ASSIST_API_USERNAME` / `MEDI_ASSIST_API_PASSWORD`.
|
||||||
|
operationId: mediassistSubmitClaim
|
||||||
|
servers:
|
||||||
|
- url: https://apiintegration.mediassist.in
|
||||||
|
description: Example host — replace with env URL
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MediAssistSubmitClaimBody'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Wrapped by app helper; success includes `claimReferenceNo` in nested payload
|
||||||
|
|
||||||
|
/mediassist/ecard:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: E-card URL request
|
||||||
|
description: URL from `MEDI_ASSIST_API_BASE_URL_ECARDREQUEST`.
|
||||||
|
operationId: mediassistEcardRequest
|
||||||
|
servers:
|
||||||
|
- url: https://apiintegration.mediassist.in
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [employeeId, policyNo]
|
||||||
|
properties:
|
||||||
|
employeeId: { type: string }
|
||||||
|
policyNo: { type: string }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Response includes `ecardUrl` when successful
|
||||||
|
|
||||||
|
/mediassist/benef-details:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: Get beneficiary / enrollment (TPA ID pull)
|
||||||
|
description: URL from `MEDI_ASSIST_API_BASE_URL_FETCHTPA`. Paginated with `startIndex` / `range`.
|
||||||
|
operationId: mediassistGetBenefDetails
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [policyNo]
|
||||||
|
properties:
|
||||||
|
policyNo: { type: string }
|
||||||
|
startDate: { type: string, default: "" }
|
||||||
|
endDate: { type: string, default: "" }
|
||||||
|
isDeActivedata: { type: boolean, default: false }
|
||||||
|
startIndex: { type: integer }
|
||||||
|
range: { type: integer, example: 100 }
|
||||||
|
employeeId: { type: string, default: "" }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Expects `benefDetails` and `count` in data
|
||||||
|
|
||||||
|
/mediassist/claim-status:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: Claim status / claims data
|
||||||
|
description: URL from `MEDI_ASSIST_API_BASE_URL_CLAIMSTATUS`.
|
||||||
|
operationId: mediassistClaimStatus
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MediAssistClaimStatusBody'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Expects `claimsData` array in response data
|
||||||
|
|
||||||
|
/mediassist/ir-submission:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: IR (information) submission with attachment URLs
|
||||||
|
description: URL from `MEDI_ASSIST_API_BASE_URL_IRSUBMISSION`.
|
||||||
|
operationId: mediassistIRSubmission
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [ClaimID, Attachments]
|
||||||
|
properties:
|
||||||
|
ClaimID: { type: string }
|
||||||
|
Attachments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
AttachmentName: { type: string }
|
||||||
|
AttachmentPath: { type: string, description: Public download URL }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: App treats `status` from helper as success/failure
|
||||||
|
|
||||||
|
/mediassist/network-hospital:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: Network hospital list
|
||||||
|
description: |
|
||||||
|
Built as `{MEDI_ASSIST_API_BASE_URL}/NetworkHospital` (env base + path).
|
||||||
|
operationId: mediassistNetworkHospital
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
example:
|
||||||
|
startIndex: 0
|
||||||
|
endIndex: 10
|
||||||
|
policyNumber: "97000063250400000031"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Third-party response passed through
|
||||||
|
|
||||||
|
/mediassist/intimate-claim:
|
||||||
|
post:
|
||||||
|
tags: [MediAssist]
|
||||||
|
security:
|
||||||
|
- MediAssistUsername: []
|
||||||
|
MediAssistPassword: []
|
||||||
|
summary: Intimate claim (UAT sample in code)
|
||||||
|
description: |
|
||||||
|
**Note:** `IntimateClaim()` in code uses a hardcoded URL and test credentials;
|
||||||
|
production should use env-driven URL and secrets.
|
||||||
|
operationId: mediassistIntimateClaimDev
|
||||||
|
deprecated: true
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
Username: { type: string }
|
||||||
|
Password: { type: string }
|
||||||
|
policyNo: { type: string }
|
||||||
|
memberId: { type: number }
|
||||||
|
DateOfAdmisssion: { type: string, format: date }
|
||||||
|
HospitalName: { type: string }
|
||||||
|
AilmentDescription: { type: string }
|
||||||
|
ContactNo: { type: string }
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Vidal Health TPA (Azure APIM)
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
/partner-integration/api/files/upload-url:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalSubscriptionKey: []
|
||||||
|
summary: Get signed URL for document upload
|
||||||
|
description: |
|
||||||
|
`VIDAL_API_BASE_URL` + `/files/upload-url`.
|
||||||
|
Header `Ocp-Apim-Subscription-Key` = `VIDAL_API_SUBSCRIPTION_KEY`.
|
||||||
|
Second step uploads file via PUT to returned `signedUrl` (Azure Blob).
|
||||||
|
operationId: vidalFileUploadUrl
|
||||||
|
servers:
|
||||||
|
- url: https://devapigw.vidalhealthtpa.com
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
scope: { type: string, example: document type }
|
||||||
|
fileName: { type: string }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Expects `data.signedUrl`, `data.fileId`
|
||||||
|
|
||||||
|
/partner-integration/api/claims/submit:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalSubscriptionKey: []
|
||||||
|
summary: Submit claim
|
||||||
|
operationId: vidalClaimsSubmit
|
||||||
|
servers:
|
||||||
|
- url: https://devapigw.vidalhealthtpa.com
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/VidalClaimSubmitBody'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success path checks `data.status == SUCCESS` and nested claim numbers
|
||||||
|
|
||||||
|
/partner-integration/api/claims/claim-dependent-info:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalSubscriptionKey: []
|
||||||
|
summary: Claim dependent info / status poll
|
||||||
|
operationId: vidalClaimDependentInfo
|
||||||
|
servers:
|
||||||
|
- url: https://devapigw.vidalhealthtpa.com
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
empNO: { type: string, default: "" }
|
||||||
|
tpaCardID: { type: string, default: "" }
|
||||||
|
claimID: { type: string, description: Set when known }
|
||||||
|
emailID: { type: string, default: "" }
|
||||||
|
mobileNO: { type: string, default: "" }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Expects `data.data.claims[]`
|
||||||
|
|
||||||
|
/partner-integration/enrollment/info:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalSubscriptionKey: []
|
||||||
|
summary: Enrollment / beneficiary pull (Vidal V2)
|
||||||
|
description: |
|
||||||
|
URL from `vidalEnrollmentInfoApiUrl()`: strips trailing `/api` from `VIDAL_API_BASE_URL`
|
||||||
|
and appends `/enrollment/info`, or falls back to dev URL.
|
||||||
|
Paginates with `startIndex` / `endIndex`.
|
||||||
|
operationId: vidalEnrollmentInfo
|
||||||
|
servers:
|
||||||
|
- url: https://devapigw.vidalhealthtpa.com
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policyNo: { type: string }
|
||||||
|
startIndex: { type: integer, example: 1 }
|
||||||
|
endIndex: { type: integer, example: 100 }
|
||||||
|
|
||||||
|
/vidal/wellness-sso:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalWellnessSubscriptionKey: []
|
||||||
|
summary: Wellness SSO (encrypted payload)
|
||||||
|
description: |
|
||||||
|
`VIDAL_WELLNESS_BASE_URL`. Headers include `Ocp-Apim-Subscription-Key`,
|
||||||
|
`apiver`, `mode: encrypt`. Body uses AES-encrypted payload + `source` / `subPartnerId`.
|
||||||
|
operationId: vidalWellnessSSO
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
payload: { type: string, description: iv:ciphertext base64 format }
|
||||||
|
source: { type: string }
|
||||||
|
subPartnerId: { type: string }
|
||||||
|
|
||||||
|
/vidal/ir-submission:
|
||||||
|
post:
|
||||||
|
tags: [Vidal]
|
||||||
|
security:
|
||||||
|
- VidalIrSubscriptionKey: []
|
||||||
|
summary: IR submission (shortfall documents)
|
||||||
|
description: |
|
||||||
|
Full URL from `VIDAL_API_BASE_URL_IRSUBMISSION`.
|
||||||
|
Uses `VIDAL_SUBSCRIPTION_KEY` (note distinct from `VIDAL_API_SUBSCRIPTION_KEY` in code).
|
||||||
|
Body sent as JSON string in some call paths.
|
||||||
|
operationId: vidalIRSubmission
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
shortFallNo: { type: string }
|
||||||
|
fileId: { type: string, description: Single file }
|
||||||
|
fileIdList:
|
||||||
|
type: array
|
||||||
|
items: { type: string }
|
||||||
|
description: Multiple files
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Volo / TrueCover (admin + consumer bases)
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
/external/login:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security: []
|
||||||
|
summary: Login — obtain access token
|
||||||
|
description: |
|
||||||
|
`VOLO_API_ADMIN_BASE_URL` + `/external/login`.
|
||||||
|
Body uses `VOLO_API_EMAIL`, `VOLO_API_PASSWORD`, `VOLO_LOGGED_IN_PORTAL`.
|
||||||
|
operationId: voloLogin
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
emailId: { type: string }
|
||||||
|
password: { type: string }
|
||||||
|
loggedInPortal:
|
||||||
|
type: string
|
||||||
|
example: POLICY_CONFIGURATION_PORTAL
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Expects `accessToken` for Authorization header on subsequent calls
|
||||||
|
|
||||||
|
/trueclaim/tpa/get-Enrollment-dump:
|
||||||
|
get:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Enrollment dump by endorsement
|
||||||
|
operationId: voloGetEnrollmentDumpByEndorsement
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: insurerPolicyNumber
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
- in: query
|
||||||
|
name: endorsmentNo
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/tpa/getHospitalByInsurerName:
|
||||||
|
get:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Hospitals by insurer name
|
||||||
|
operationId: voloGetHospitalByInsurerName
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: insurerName
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/tpa/doc:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Upload document (body defined by TPA)
|
||||||
|
operationId: voloUploadDocument
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
/trueclaim/tpa/get-Enroll-dump:
|
||||||
|
get:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Enrollment dump by insurer policy number
|
||||||
|
operationId: voloGetEnrollmentDump
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: insurerPolicyNumber
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
|
||||||
|
/truecover/external-service/claim-status:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: External claim status (consumer base)
|
||||||
|
description: |
|
||||||
|
`VOLO_API_CONSUMER_BASE_URL` + `/truecover/external-service/claim-status`.
|
||||||
|
operationId: voloFetchExternalClaimStatus
|
||||||
|
servers:
|
||||||
|
- url: https://consumer.example.ewatpa.com
|
||||||
|
description: Replace with VOLO_API_CONSUMER_BASE_URL
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [tpaClaimNo]
|
||||||
|
properties:
|
||||||
|
tpaClaimNo: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/tpa/getTpaData:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: TPA data by date range and entity
|
||||||
|
operationId: voloGetTpaData
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [startDate, endDate, entityId]
|
||||||
|
properties:
|
||||||
|
startDate: { type: string }
|
||||||
|
endDate: { type: string }
|
||||||
|
entityId: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/create-new-all-member-id-card-pdf:
|
||||||
|
get:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: E-card PDF (base64 in response body)
|
||||||
|
operationId: voloGetEcardPdf
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: employeeId
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
- in: query
|
||||||
|
name: entityId
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/get-entity-from-policy:
|
||||||
|
get:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Resolve entity id from policy number
|
||||||
|
operationId: voloGetEntityFromPolicy
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: policyNumber
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
|
||||||
|
/trueclaim/policy-bazaar/intimate-claim:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Intimate claim (claim push)
|
||||||
|
description: |
|
||||||
|
Built payload includes `hospitalId` (`VOLO_DEFAULT_HOSPITAL_ID`), `memberId`,
|
||||||
|
dates, `potentialClaimAmount`, `claimDocuments` (pdf URL), `entityId`, `insurerPolicyNumber`.
|
||||||
|
operationId: voloIntimateClaim
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/VoloIntimateClaimBody'
|
||||||
|
|
||||||
|
/trueclaim/tpa/getClaimData:
|
||||||
|
post:
|
||||||
|
tags: [Volo]
|
||||||
|
security:
|
||||||
|
- VoloBearerToken: []
|
||||||
|
summary: Get claim data
|
||||||
|
operationId: voloGetClaimData
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: claimId
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
description: Empty JSON object sent as body
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# FHPL
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
/oauth/token:
|
||||||
|
get:
|
||||||
|
tags: [FHPL]
|
||||||
|
security: []
|
||||||
|
summary: Generate OAuth token
|
||||||
|
description: |
|
||||||
|
URL from `FHPL_TOKEN_URL`. **Note:** implementation uses GET with `application/x-www-form-urlencoded`
|
||||||
|
body (`UserName`, `Password`, `grant_type`) — align with FHPL spec / Postman.
|
||||||
|
operationId: fhplGenerateToken
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
UserName: { type: string }
|
||||||
|
Password: { type: string }
|
||||||
|
grant_type: { type: string }
|
||||||
|
|
||||||
|
/api/ClaimSubmission:
|
||||||
|
post:
|
||||||
|
tags: [FHPL]
|
||||||
|
summary: Claim submission
|
||||||
|
description: |
|
||||||
|
`{FHPL_BASE_URL}/api/ClaimSubmission` with Bearer token from token endpoint.
|
||||||
|
operationId: fhplClaimSubmission
|
||||||
|
security:
|
||||||
|
- FhplBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/FhplClaimSubmissionBody'
|
||||||
|
|
||||||
|
/api/GetTPA_ClaimsDetails:
|
||||||
|
post:
|
||||||
|
tags: [FHPL]
|
||||||
|
summary: TPA claim details / MIS (status sync)
|
||||||
|
operationId: fhplGetTpaClaimsDetails
|
||||||
|
security:
|
||||||
|
- FhplBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
UserName: { type: string }
|
||||||
|
Password: { type: string }
|
||||||
|
PolicyNumber: { type: string }
|
||||||
|
Fromdate: { type: string, format: date }
|
||||||
|
Todate: { type: string, format: date }
|
||||||
|
|
||||||
|
/api/GetEcard:
|
||||||
|
post:
|
||||||
|
tags: [FHPL]
|
||||||
|
summary: Get e-card URL
|
||||||
|
operationId: fhplGetEcard
|
||||||
|
security:
|
||||||
|
- FhplBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
UserName: { type: string }
|
||||||
|
Password: { type: string }
|
||||||
|
PolicyNumber: { type: string }
|
||||||
|
EmployeeID: { type: string }
|
||||||
|
|
||||||
|
/api/GetEnrollmentDetailsPolicy:
|
||||||
|
post:
|
||||||
|
tags: [FHPL]
|
||||||
|
summary: Enrollment details for policy (paginated)
|
||||||
|
operationId: fhplGetEnrollmentDetailsPolicy
|
||||||
|
security:
|
||||||
|
- FhplBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
UserName: { type: string }
|
||||||
|
Password: { type: string }
|
||||||
|
PolicyNumber: { type: string }
|
||||||
|
StartIndex: { type: integer }
|
||||||
|
Range: { type: integer, example: 100 }
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Health India TPA
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
/JWT/GenerateJWTAuth:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Generate JWT (Basic auth)
|
||||||
|
description: |
|
||||||
|
`HEALTH_INDIA_TOKEN_URL`. HTTP Basic with `HEALTH_INDIA_USERNAME` / `HEALTH_INDIA_PASSWORD`.
|
||||||
|
Body is `{}`.
|
||||||
|
operationId: healthIndiaGenerateJWT
|
||||||
|
security:
|
||||||
|
- HealthIndiaTokenBasic: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
/Intimation/GetClaimIntimation:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Claim intimation / reimbursement submission
|
||||||
|
description: >
|
||||||
|
HEALTH_INDIA_BASE_URL + /Intimation/GetClaimIntimation
|
||||||
|
operationId: healthIndiaClaimIntimation
|
||||||
|
security:
|
||||||
|
- HealthIndiaBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HealthIndiaClaimIntimationBody'
|
||||||
|
|
||||||
|
/Claims/GetClaims:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Get claim(s) by CCN
|
||||||
|
operationId: healthIndiaGetClaims
|
||||||
|
security:
|
||||||
|
- HealthIndiaBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policY_NUMBER: { type: string }
|
||||||
|
CCN: { type: string }
|
||||||
|
CCN_EXT: { type: string }
|
||||||
|
|
||||||
|
/Member/GetMemberEcard:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Member e-card
|
||||||
|
operationId: healthIndiaGetMemberEcard
|
||||||
|
security:
|
||||||
|
- HealthIndiaBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policY_NUMBER: { type: string }
|
||||||
|
employeE_CODE: { type: string }
|
||||||
|
membeR_ID: { type: string }
|
||||||
|
|
||||||
|
/Enrollment/GetEnrollmentData:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Bulk enrollment data (TPA ID pull)
|
||||||
|
operationId: healthIndiaGetEnrollmentData
|
||||||
|
security:
|
||||||
|
- HealthIndiaBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policY_NUMBER: { type: string }
|
||||||
|
|
||||||
|
/ClaimsMIS/GetClaimsMIS:
|
||||||
|
post:
|
||||||
|
tags: [HealthIndia]
|
||||||
|
summary: Claims MIS / sync
|
||||||
|
operationId: healthIndiaGetClaimsMIS
|
||||||
|
security:
|
||||||
|
- HealthIndiaBearerAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policY_NUMBER: { type: string }
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
MediAssistUsername:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Username
|
||||||
|
description: |
|
||||||
|
`.env`: `MEDI_ASSIST_API_USERNAME` — same value as `getenv('MEDI_ASSIST_API_USERNAME')` in PHP.
|
||||||
|
|
||||||
|
MediAssistPassword:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Password
|
||||||
|
description: |
|
||||||
|
`.env`: `MEDI_ASSIST_API_PASSWORD` — same value as `getenv('MEDI_ASSIST_API_PASSWORD')`.
|
||||||
|
|
||||||
|
VidalSubscriptionKey:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Ocp-Apim-Subscription-Key
|
||||||
|
description: |
|
||||||
|
`.env`: `VIDAL_API_SUBSCRIPTION_KEY` — used for partner-integration APIs (`VIDAL_API_BASE_URL`).
|
||||||
|
|
||||||
|
VidalIrSubscriptionKey:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Ocp-Apim-Subscription-Key
|
||||||
|
description: |
|
||||||
|
`.env`: `VIDAL_SUBSCRIPTION_KEY` — used **only** for IR submission (`VIDAL_API_BASE_URL_IRSUBMISSION`).
|
||||||
|
Same header name as main Vidal key but different secret.
|
||||||
|
|
||||||
|
VidalWellnessSubscriptionKey:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Ocp-Apim-Subscription-Key
|
||||||
|
description: |
|
||||||
|
`.env`: `VIDAL_WELLNESS_SUBSCRIPTION_KEY`. Wellness also uses `VIDAL_WELLNESS_BASE_URL`,
|
||||||
|
`VIDAL_WELLNESS_BASE64_KEY`, `VIDAL_WELLNESS_URL_IDENTIFIER`, `VIDAL_WELLNESS_SUB_PARTNER_ID`.
|
||||||
|
|
||||||
|
VoloBearerToken:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
Obtain token from `POST .../external/login` using body fields from `.env`:
|
||||||
|
`VOLO_API_EMAIL`, `VOLO_API_PASSWORD`, `VOLO_LOGGED_IN_PORTAL`.
|
||||||
|
Send the returned `accessToken` as the Bearer value (as required by the API, sometimes including a `Token ` prefix).
|
||||||
|
|
||||||
|
FhplBearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
Bearer `access_token` returned from `FHPL_TOKEN_URL` using form fields
|
||||||
|
`UserName`=`FHPL_USER_NAME`, `Password`=`FHPL_PASSWORD`, `grant_type`=`FHPL_GRANT_TYPE`.
|
||||||
|
|
||||||
|
HealthIndiaTokenBasic:
|
||||||
|
type: http
|
||||||
|
scheme: basic
|
||||||
|
description: |
|
||||||
|
For JWT generation call only. `.env`: `HEALTH_INDIA_USERNAME`, `HEALTH_INDIA_PASSWORD`
|
||||||
|
(HTTP Basic to `HEALTH_INDIA_TOKEN_URL`).
|
||||||
|
|
||||||
|
HealthIndiaBearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
Bearer JWT from the GenerateJWTAuth response (not a fixed `.env` value).
|
||||||
|
Use `result[0].access_token` after calling token URL with Basic credentials above.
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
MediAssistSubmitClaimBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policyNo: { type: string }
|
||||||
|
memberId: { type: string }
|
||||||
|
mobileNo: { type: string }
|
||||||
|
emailId: { type: string }
|
||||||
|
claimDateOfAdmission: { type: string }
|
||||||
|
claimDateOfDischarge: { type: string }
|
||||||
|
hospName: { type: string }
|
||||||
|
hospAddress: { type: string }
|
||||||
|
reasonForHospitalization: { type: string }
|
||||||
|
disease: { type: string }
|
||||||
|
claimAmount: { type: number }
|
||||||
|
claimType:
|
||||||
|
type: string
|
||||||
|
example: HOSPITALIZATION
|
||||||
|
claimSubmissionAttachments:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
fileName: { type: string }
|
||||||
|
filePath: { type: string, description: Public URL to PDF }
|
||||||
|
|
||||||
|
MediAssistClaimStatusBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policyNo: { type: string }
|
||||||
|
startDate: { type: string }
|
||||||
|
endDate: { type: string }
|
||||||
|
employeeCode: { type: string }
|
||||||
|
memberID: { type: string }
|
||||||
|
claimNo: { type: string }
|
||||||
|
claimRefNo: { type: string }
|
||||||
|
|
||||||
|
VidalClaimSubmitBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
policyNo: { type: string }
|
||||||
|
dependentUniqueId: { type: string }
|
||||||
|
typeOfClaim: { type: string, example: Main hospitalization claim }
|
||||||
|
claimSubType: { type: string }
|
||||||
|
requestedAmount: { type: string }
|
||||||
|
ailmentType: { type: string, example: Non covid }
|
||||||
|
admissionDate: { type: string, description: dd-mm-yyyy }
|
||||||
|
dischargeDate: { type: string }
|
||||||
|
hospitalName: { type: string }
|
||||||
|
empanelmentNo: { type: number }
|
||||||
|
ailmentName: { type: string }
|
||||||
|
hospitalAddress: { type: string, nullable: true }
|
||||||
|
hospitalState: { type: string, nullable: true }
|
||||||
|
hospitalCity: { type: string, nullable: true }
|
||||||
|
hospitalPinCode: { type: string, nullable: true }
|
||||||
|
hospitalPhoneNo: { type: string, nullable: true }
|
||||||
|
fileId: { type: string }
|
||||||
|
bankDetails:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
accountHolderName: { type: string, nullable: true }
|
||||||
|
accountType: { type: string, nullable: true }
|
||||||
|
accountNo: { type: string, nullable: true }
|
||||||
|
ifscCode: { type: string, nullable: true }
|
||||||
|
|
||||||
|
VoloIntimateClaimBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
hospitalId: { type: string }
|
||||||
|
memberId: { type: string }
|
||||||
|
dateOfAdmission: { type: string, format: date }
|
||||||
|
dateOfDischarge: { type: string, format: date }
|
||||||
|
potentialClaimAmount: { type: number }
|
||||||
|
claimDocuments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
documentName: { type: string }
|
||||||
|
documentType: { type: string, example: medical_report }
|
||||||
|
extension: { type: string, example: pdf }
|
||||||
|
url: { type: string, format: uri }
|
||||||
|
entityId: { type: string }
|
||||||
|
insurerPolicyNumber: { type: string }
|
||||||
|
|
||||||
|
FhplClaimSubmissionBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
IssueID: { type: string }
|
||||||
|
Userid:
|
||||||
|
type: string
|
||||||
|
description: Same as `.env` `FHPL_USER_NAME` where the app sends Userid.
|
||||||
|
PolicyNo: { type: string }
|
||||||
|
UhidNo: { type: string }
|
||||||
|
ClaimID: { type: string }
|
||||||
|
DOA: { type: string, format: date }
|
||||||
|
DateofDischarge: { type: string, format: date, nullable: true }
|
||||||
|
ClaimedAmount: { type: number }
|
||||||
|
DocumentType: { type: integer, example: 20, description: Fresh claim }
|
||||||
|
PayeeName: { type: string }
|
||||||
|
HospitalName: { type: string }
|
||||||
|
MobileNo: { type: string }
|
||||||
|
Documents:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
documentName: { type: string }
|
||||||
|
documentCategory: { type: string, example: IRR }
|
||||||
|
filecontent: { type: string, description: Base64 PDF }
|
||||||
|
|
||||||
|
HealthIndiaClaimIntimationBody:
|
||||||
|
type: object
|
||||||
|
description: Field casing matches Health India API (mixedCase keys).
|
||||||
|
properties:
|
||||||
|
policY_NUMBER: { type: string }
|
||||||
|
employeE_CODE: { type: string }
|
||||||
|
membeR_ID: { type: string }
|
||||||
|
claiM_TYPE: { type: string, example: Reimbursement }
|
||||||
|
benefiT_TYPE: { type: string, enum: [IPD, OPD] }
|
||||||
|
claimeD_AMOUNT: { type: string }
|
||||||
|
datE_OF_ADMISSION: { type: string, format: date }
|
||||||
|
ailmenT_DESCRIPTION: { type: string }
|
||||||
|
hospitaL_CODE: { type: string }
|
||||||
|
hospitaL_NAME: { type: string }
|
||||||
|
hospitaL_ADDRESS: { type: string }
|
||||||
|
hospitaL_NUMBER: { type: string }
|
||||||
|
pdF_BYTES:
|
||||||
|
type: array
|
||||||
|
items: { type: string, description: Base64-encoded PDF }
|
||||||
Loading…
Reference in New Issue
Block a user