MERGE_TEST_TPA_API_ISSUES&MINOR&SECURITY&LIVE
This commit is contained in:
commit
51c3e1eaf7
@ -449,6 +449,8 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
|||||||
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
||||||
$routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1');
|
$routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1');
|
||||||
$routes->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
|
$routes->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
|
||||||
|
$routes->get('insertSampleTpaApiData/(:any)', 'TestingController::insertSampleTpaApiData/$1');
|
||||||
|
$routes->get('listEmployeeCountByClientPolicy', 'TestingController::listEmployeeCountByClientPolicy');
|
||||||
});
|
});
|
||||||
|
|
||||||
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||||
|
|||||||
@ -226,6 +226,14 @@ class AppContentManagementController extends AdminController
|
|||||||
|
|
||||||
if ($this->request->getMethod() === 'post') {
|
if ($this->request->getMethod() === 'post') {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* STEP 1: INITIAL VALIDATION
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* These are the basic validation rules. For 'content' and 'notes', we only
|
||||||
|
* check if they are provided and within the allowed length.
|
||||||
|
* The more advanced security check for script tags happens next.
|
||||||
|
*/
|
||||||
$rules = [
|
$rules = [
|
||||||
'fe_id' => [
|
'fe_id' => [
|
||||||
'rules' => 'permit_empty|integer|is_natural',
|
'rules' => 'permit_empty|integer|is_natural',
|
||||||
@ -259,21 +267,19 @@ class AppContentManagementController extends AdminController
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'content' => [
|
'content' => [
|
||||||
'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
'rules' => 'required|max_length[5000]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'required' => 'Content is required',
|
'required' => 'Content is required',
|
||||||
'max_length' => 'Content cannot exceed 5000 characters',
|
'max_length' => 'Content cannot exceed 5000 characters',
|
||||||
'regex_match' => 'Content contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'notes' => [
|
'notes' => [
|
||||||
'rules' => 'required|max_length[1500]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
'rules' => 'required|max_length[1500]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'required' => 'Notes are required',
|
'required' => 'Notes are required',
|
||||||
'max_length' => 'Notes cannot exceed 1500 characters',
|
'max_length' => 'Notes cannot exceed 1500 characters',
|
||||||
'regex_match' => 'Notes contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!$this->validate($rules)) {
|
if (!$this->validate($rules)) {
|
||||||
@ -284,8 +290,59 @@ class AppContentManagementController extends AdminController
|
|||||||
'errors' => $this->validator->getErrors()
|
'errors' => $this->validator->getErrors()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$request_post_data = $this->request->getPost();
|
|
||||||
$data = sanitizeInputArrayAdvanced($request_post_data);
|
/**************************************************************************
|
||||||
|
* REFACTORED SANITIZATION LOGIC (XSS Protection)
|
||||||
|
**************************************************************************
|
||||||
|
*
|
||||||
|
* Per the user's request, we are avoiding the generic `sanitizeInputArrayAdvanced`
|
||||||
|
* on the `content` and `notes` fields, as they require special HTML
|
||||||
|
* handling.
|
||||||
|
*
|
||||||
|
* The new process is:
|
||||||
|
* 1. Get the raw `content` and `notes` directly from the POST request.
|
||||||
|
* 2. Perform the critical XSS validation on this raw content using `hasXssTags()`.
|
||||||
|
* If it fails, the request is rejected immediately. This satisfies all
|
||||||
|
* the failure test cases (Tests 4-9).
|
||||||
|
* 3. Take all *other* POST data and sanitize it using the generic
|
||||||
|
* `sanitizeInputArrayAdvanced` function.
|
||||||
|
* 4. Sanitize the now-validated `content` and `notes` using our specific
|
||||||
|
* `sanitizeHtml()` function, which allows safe HTML.
|
||||||
|
* 5. Combine the sanitized data into a final array for database insertion.
|
||||||
|
*
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
// Step 1: Get raw `content` and `notes`.
|
||||||
|
$rawContent = $this->request->getPost('content');
|
||||||
|
$rawNotes = $this->request->getPost('notes');
|
||||||
|
|
||||||
|
// Step 2: Perform critical XSS validation on raw input.
|
||||||
|
$xssErrors = [];
|
||||||
|
if ($this->hasXssTags($rawContent)) {
|
||||||
|
$xssErrors['content'] = 'Content contains restricted tags. Script, iframe and event handlers are not allowed';
|
||||||
|
}
|
||||||
|
if ($this->hasXssTags($rawNotes)) {
|
||||||
|
$xssErrors['notes'] = 'Notes contains restricted tags. Script, iframe and event handlers are not allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($xssErrors)) {
|
||||||
|
return $this->response->setStatusCode(400)->setJSON([
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Input validation failed',
|
||||||
|
'code' => 400,
|
||||||
|
'errors' => $xssErrors
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Sanitize all *other* POST data.
|
||||||
|
$otherPostData = $this->request->getPost();
|
||||||
|
unset($otherPostData['content'], $otherPostData['notes']);
|
||||||
|
$data = sanitizeInputArrayAdvanced($otherPostData);
|
||||||
|
|
||||||
|
// Step 4 & 5: Sanitize and re-combine `content` and `notes`.
|
||||||
|
$data['content'] = $this->sanitizeHtml($rawContent);
|
||||||
|
$data['notes'] = $this->sanitizeHtml($rawNotes);
|
||||||
|
|
||||||
$id = $data['fe_id'] ?? null;
|
$id = $data['fe_id'] ?? null;
|
||||||
|
|
||||||
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
||||||
@ -416,19 +473,17 @@ class AppContentManagementController extends AdminController
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'question' => [
|
'question' => [
|
||||||
'rules' => 'required|max_length[1000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
'rules' => 'required|max_length[1000]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'required' => 'Question is required',
|
'required' => 'Question is required',
|
||||||
'max_length' => 'Question cannot exceed 1000 characters',
|
'max_length' => 'Question cannot exceed 1000 characters',
|
||||||
'regex_match' => 'Question contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'answer' => [
|
'answer' => [
|
||||||
'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
'rules' => 'required|max_length[5000]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'required' => 'Answer is required',
|
'required' => 'Answer is required',
|
||||||
'max_length' => 'Answer cannot exceed 5000 characters',
|
'max_length' => 'Answer cannot exceed 5000 characters',
|
||||||
'regex_match' => 'Answer contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
@ -443,9 +498,50 @@ class AppContentManagementController extends AdminController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$request_post_data = $this->request->getPost();
|
/**************************************************************************
|
||||||
$sanitized_post_data = sanitizeInputArrayAdvanced($request_post_data);
|
* XSS PROTECTION FOR 'question' and 'answer'
|
||||||
$data = array_filter($sanitized_post_data, fn($v) => $v !== '' && $v !== null);
|
**************************************************************************
|
||||||
|
*
|
||||||
|
* Applying the same security model as `frontend_content`.
|
||||||
|
*
|
||||||
|
* 1. Validate raw `question` and `answer` for malicious tags using `hasXssTags()`.
|
||||||
|
* If found, reject the request immediately.
|
||||||
|
* 2. Sanitize all *other* fields using the generic `sanitizeInputArrayAdvanced`.
|
||||||
|
* 3. Sanitize the `question` and `answer` using the HTML-aware `sanitizeHtml()`
|
||||||
|
* function to allow safe tags before saving.
|
||||||
|
*
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
// Step 1: Validate raw input for XSS threats.
|
||||||
|
$rawQuestion = $this->request->getPost('question');
|
||||||
|
$rawAnswer = $this->request->getPost('answer');
|
||||||
|
$xssErrors = [];
|
||||||
|
|
||||||
|
if ($this->hasXssTags($rawQuestion)) {
|
||||||
|
$xssErrors['question'] = 'Question contains restricted tags. Script, iframe and event handlers are not allowed';
|
||||||
|
}
|
||||||
|
if ($this->hasXssTags($rawAnswer)) {
|
||||||
|
$xssErrors['answer'] = 'Answer contains restricted tags. Script, iframe and event handlers are not allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($xssErrors)) {
|
||||||
|
return $this->response->setStatusCode(400)->setJSON([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Input validation failed',
|
||||||
|
'code' => 400,
|
||||||
|
'errors' => $xssErrors,
|
||||||
|
'ref' => $ref
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2 & 3: Sanitize and combine data.
|
||||||
|
$otherPostData = $this->request->getPost();
|
||||||
|
unset($otherPostData['question'], $otherPostData['answer']);
|
||||||
|
$data = sanitizeInputArrayAdvanced($otherPostData);
|
||||||
|
|
||||||
|
$data['question'] = $this->sanitizeHtml($rawQuestion);
|
||||||
|
$data['answer'] = $this->sanitizeHtml($rawAnswer);
|
||||||
|
|
||||||
$id = $data['faq_id'] ?? null;
|
$id = $data['faq_id'] ?? null;
|
||||||
|
|
||||||
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
||||||
@ -470,12 +566,6 @@ class AppContentManagementController extends AdminController
|
|||||||
$msg = "Updated";
|
$msg = "Updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ($returnType === 'web') {
|
|
||||||
// return redirect()->back()->with($status ? 'success' : 'error', "FAQ $msg " . ($status ? 'successfully' : 'failed'));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return $this->response->setJSON([
|
|
||||||
// ])->setStatusCode($result ? 200 : 400);
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'status' => $status ? 'success' : 'error',
|
'status' => $status ? 'success' : 'error',
|
||||||
'message' => "FAQ $msg " . ($status ? 'successfully' : 'failed'),
|
'message' => "FAQ $msg " . ($status ? 'successfully' : 'failed'),
|
||||||
@ -611,4 +701,104 @@ class AppContentManagementController extends AdminController
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// Add these two private methods inside AppContentManagementController
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =================================================================================
|
||||||
|
* HTML SANITIZATION & VALIDATION HELPER METHODS
|
||||||
|
* =================================================================================
|
||||||
|
* The following two methods are the core of the XSS protection logic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sanitizeHtml()
|
||||||
|
*
|
||||||
|
* This function cleans a string of HTML, ensuring it is safe to display in a browser.
|
||||||
|
* It allows a specific set of safe HTML tags and removes any dangerous attributes
|
||||||
|
* from those tags.
|
||||||
|
*
|
||||||
|
* @param string $input The raw HTML string from user input.
|
||||||
|
* @return string The cleaned, safe HTML string.
|
||||||
|
*/
|
||||||
|
private function sanitizeHtml(string $input): string
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define a whitelist of allowed HTML tags. Any tag not in this list will be
|
||||||
|
* completely removed. We are allowing basic formatting, lists, tables, etc.
|
||||||
|
*/
|
||||||
|
// ✅ Added <s>, <u>, <h1>-<h6>, <blockquote>, <pre>, <code>, <hr> for Jodit support
|
||||||
|
$allowed_tags = '<p><b><i><s><u><strong><em><ul><ol><li><br><a><img><table><thead><tbody><tr><th><td><span><div><h1><h2><h3><h4><h5><h6><blockquote><pre><code><hr><sub><sup>';
|
||||||
|
|
||||||
|
// Use strip_tags() to remove all tags that are not in our whitelist.
|
||||||
|
$clean = strip_tags($input, $allowed_tags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a blacklist of dangerous attributes. These are often used for XSS
|
||||||
|
* attacks (e.g., `onclick`, `onmouseover`). We search for and remove these
|
||||||
|
* attributes from any remaining tags.
|
||||||
|
*/
|
||||||
|
$dangerous_attrs = [
|
||||||
|
'/\s*on\w+\s*=\s*["\'][^"\']*["\']/i', // e.g., onclick="..."
|
||||||
|
'/\s*on\w+\s*=\s*[^\s>]*/i', // e.g., onclick=...
|
||||||
|
'/\s*javascript\s*:[^"\'"]*/i', // e.g., href="javascript:..."
|
||||||
|
'/\s*vbscript\s*:[^"\'"]*/i', // e.g., href="vbscript:..."
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use preg_replace to find and remove the dangerous attributes.
|
||||||
|
foreach ($dangerous_attrs as $pattern) {
|
||||||
|
$clean = preg_replace($pattern, '', $clean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hasXssTags()
|
||||||
|
*
|
||||||
|
* This function scans a string for common XSS-related tags, protocols, and event
|
||||||
|
* handlers. It is used as a primary check to quickly reject any input that is
|
||||||
|
* clearly malicious.
|
||||||
|
*
|
||||||
|
* @param string $str The raw string from user input.
|
||||||
|
* @return bool Returns `true` if a dangerous pattern is found, `false` otherwise.
|
||||||
|
*/
|
||||||
|
private function hasXssTags(string $str): bool
|
||||||
|
{
|
||||||
|
// Decode the string to handle entities (e.g., `%3Cscript%3E`) and prevent evasion.
|
||||||
|
$decoded = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
|
||||||
|
$decoded = urldecode($decoded);
|
||||||
|
$decoded = str_replace(["\0", "\x00"], '', $decoded); // Remove null bytes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a blacklist of dangerous patterns. This includes tags like `<script>`
|
||||||
|
* and `<iframe>`, as well as patterns like `javascript:` and `onclick=`.
|
||||||
|
* The `/i` flag makes the search case-insensitive.
|
||||||
|
*/
|
||||||
|
$dangerous_patterns = [
|
||||||
|
'/<\s*script/i', // <script
|
||||||
|
'/<\s*\/\s*script/i', // </script
|
||||||
|
'/javascript\s*:/i', // javascript:
|
||||||
|
'/vbscript\s*:/i', // vbscript:
|
||||||
|
'/<\s*iframe/i', // <iframe>
|
||||||
|
'/<\s*object/i', // <object>
|
||||||
|
'/<\s*embed/i', // <embed>
|
||||||
|
'/<\s*applet/i', // <applet>
|
||||||
|
'/on\w+\s*=/i', // on...= (e.g., onclick=)
|
||||||
|
'/data\s*:\s*text\/html/i', // data:text/html
|
||||||
|
'/expression\s*\(/i', // CSS expression()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Loop through the patterns and check if any of them exist in the decoded string.
|
||||||
|
foreach ($dangerous_patterns as $pattern) {
|
||||||
|
if (preg_match($pattern, $decoded)) return true; // Found a threat
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, no threats were found.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -721,21 +721,43 @@ class DashboardController extends AdminController
|
|||||||
return $this->respond(['status' => false, 'code' => 400, 'message' => 'No template found', 'message2' => 'Failed'], 200);
|
return $this->respond(['status' => false, 'code' => 400, 'message' => 'No template found', 'message2' => 'Failed'], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$emp_data = $this->employeePolicyModel->getEmployeePolicyForEcard($policy_id);
|
$emp_data = $this->employeePolicyModel->getEmployeePolicyForEcard($policy_id);
|
||||||
if (count($emp_data) == 0) {
|
if (count($emp_data) == 0) {
|
||||||
return $this->respond(['status' => false, 'code' => 400, 'message' => 'No employees found', 'message2' => 'Failed'], 200);
|
return $this->respond(['status' => false, 'code' => 400, 'message' => 'No employees found', 'message2' => 'Failed'], 200);
|
||||||
}
|
}
|
||||||
$ids = array_column($emp_data, 'id');
|
$ids = array_column($emp_data, 'id');
|
||||||
|
|
||||||
|
|
||||||
|
$client_policy_data = $this->clientPolicyModel->where('id', $policy_id)->first();
|
||||||
|
$sendMail = false;
|
||||||
|
|
||||||
|
if ($client_policy_data['policy_type_id'] == 3 && $client_policy_data['is_addon'] == 3) {
|
||||||
|
$sendMail = true;
|
||||||
|
} else {
|
||||||
|
$relationships = array_column($emp_data, 'relationship');
|
||||||
|
if (in_array('Self', $relationships)) {
|
||||||
|
$sendMail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sendMail) {
|
||||||
|
$message = 'Mail Queued';
|
||||||
|
Jobs::addJob(['job_name' => 'sendMailForDownloadingECard','payload' => ['ids' => $ids,'client_policy_id' => $policy_id]]);
|
||||||
|
$this->myLogger->logme('error', 'sendManualEcard - Mail Queued');
|
||||||
|
} else {
|
||||||
|
$message = 'E-card has already been sent to those employees.';
|
||||||
|
$this->myLogger->logme('error', 'sendManualEcard - E-card has already been sent to those employees.');
|
||||||
|
}
|
||||||
|
|
||||||
// print_r(($ids)); die;
|
// print_r(($ids)); die;
|
||||||
// print_r($this->clientPolicyModel->getLastQuery()); die;
|
// print_r($this->clientPolicyModel->getLastQuery()); die;
|
||||||
// $this->myLogger->logme('error', "Policy details fetched: " . json_encode($client_policy_data));
|
// $this->myLogger->logme('error', "Policy details fetched: " . json_encode($client_policy_data));
|
||||||
Jobs::addJob(['job_name' => 'sendMailForDownloadingECard', 'payload' => ['ids' => $ids, 'client_policy_id' => $policy_id]]);
|
// Jobs::addJob(['job_name' => 'sendMailForDownloadingECard', 'payload' => ['ids' => $ids, 'client_policy_id' => $policy_id]]);
|
||||||
// $empEmpDataServiceController = new EmpDataServiceController();
|
// $empEmpDataServiceController = new EmpDataServiceController();
|
||||||
// $empEmpDataServiceController->sendMailForDownloadingECard($ids);
|
// $empEmpDataServiceController->sendMailForDownloadingECard($ids);
|
||||||
|
|
||||||
|
|
||||||
return $this->respond(['status' => true, 'code' => 200, 'message' => 'Mail Queued'], 200);
|
return $this->respond(['status' => true, 'code' => 200, 'message' => $message], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function data_construct_for_bds($data)
|
public function data_construct_for_bds($data)
|
||||||
|
|||||||
@ -4055,12 +4055,50 @@ class EmployeeController extends AdminController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tab = $this->request->getGet('tab');
|
||||||
|
|
||||||
|
// If the user is proceeding from the "Not in Nhance" tab,
|
||||||
|
// generate an Employee Upload with Events compatible Excel file
|
||||||
|
// and trigger the usual upload pipeline.
|
||||||
|
if ($tab === 'not_in_nhance') {
|
||||||
|
$generationResult = $this->generateEmployeeUploadFromNotInNhance((int) $file_id, $file);
|
||||||
|
|
||||||
|
if (!$generationResult['status']) {
|
||||||
|
return $this->respond(
|
||||||
|
[
|
||||||
|
'status' => false,
|
||||||
|
'code' => 422,
|
||||||
|
'message' => $generationResult['message'] ?? 'Unable to generate employee upload file from Not in Nhance data.',
|
||||||
|
'data' => $generationResult['data'] ?? [],
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif ($tab === 'need_to_review') {
|
||||||
|
// For the "Need to Review" tab, generate a Correction Excel
|
||||||
|
// using the same overall pipeline as the Not in Nhance implementation.
|
||||||
|
$generationResult = $this->generateCorrectionUploadFromNeedToReview((int) $file_id, $file);
|
||||||
|
|
||||||
|
if (!$generationResult['status']) {
|
||||||
|
return $this->respond(
|
||||||
|
[
|
||||||
|
'status' => false,
|
||||||
|
'code' => 422,
|
||||||
|
'message' => $generationResult['message'] ?? 'Unable to generate correction upload file from Need to Review data.',
|
||||||
|
'data' => $generationResult['data'] ?? [],
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->myLogger->logme(
|
$this->myLogger->logme(
|
||||||
'error',
|
'error',
|
||||||
'TPA variation review completed and proceed to next clicked',
|
'TPA variation review completed and proceed to next clicked',
|
||||||
[
|
[
|
||||||
'file_id' => $file_id,
|
'file_id' => $file_id,
|
||||||
'user_id' => get_session_userid(),
|
'user_id' => get_session_userid(),
|
||||||
|
'tab' => $tab,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -4068,7 +4106,11 @@ class EmployeeController extends AdminController
|
|||||||
[
|
[
|
||||||
'status' => true,
|
'status' => true,
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'message' => 'Proceed to next step recorded successfully.',
|
'message' => $tab === 'not_in_nhance'
|
||||||
|
? 'Employee upload file generated from Not in Nhance data and queued for processing.'
|
||||||
|
: ($tab === 'need_to_review'
|
||||||
|
? 'Correction upload file generated from Need to Review data and queued for processing.'
|
||||||
|
: 'Proceed to next step recorded successfully.'),
|
||||||
'data' => [],
|
'data' => [],
|
||||||
],
|
],
|
||||||
200
|
200
|
||||||
@ -4092,6 +4134,551 @@ class EmployeeController extends AdminController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an Employee Upload with Events compatible Excel file
|
||||||
|
* from the Not in Nhance TPA variation data and push it into the
|
||||||
|
* existing employee upload pipeline.
|
||||||
|
*
|
||||||
|
* @param int $batchFileId Batch file id used for TPA variation report.
|
||||||
|
* @param array $batchFile Batch file row from DB.
|
||||||
|
*
|
||||||
|
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
||||||
|
*/
|
||||||
|
protected function generateEmployeeUploadFromNotInNhance(int $batchFileId, array $batchFile): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$clientId = (int) ($batchFile['client_id'] ?? 0);
|
||||||
|
$clientPolicyId = (int) ($batchFile['client_policy_id'] ?? 0);
|
||||||
|
$clientBranchId = (int) ($batchFile['client_branch_id'] ?? 0);
|
||||||
|
|
||||||
|
if (!$clientId || !$clientPolicyId || !$clientBranchId) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Incomplete batch file information. Client / policy / branch missing.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$TpaApiDataModel = new TpaApiDataModel();
|
||||||
|
|
||||||
|
// Get master emp codes from Nhance for this client & policy
|
||||||
|
$masterEmpRows = $this->employeePolicyModel->getTPADataVariationReport(
|
||||||
|
$clientId,
|
||||||
|
$clientPolicyId,
|
||||||
|
$batchFileId,
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (!is_array($masterEmpRows)) {
|
||||||
|
$masterEmpRows = [];
|
||||||
|
}
|
||||||
|
$masterEmpCodes = array_column($masterEmpRows, 'emp_code');
|
||||||
|
|
||||||
|
// Fetch Not in Nhance rows for this batch file
|
||||||
|
$notInNhance = $TpaApiDataModel->select('*')
|
||||||
|
->where('is_active', 1)
|
||||||
|
->where('file_id', $batchFileId)
|
||||||
|
->whereNotIn('emp_code', $masterEmpCodes)
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
if (empty($notInNhance)) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'No "Not in Nhance" records found for this file.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$empServiceController = new EmployeeServiceController();
|
||||||
|
$inceptionColumns = $empServiceController->getInceptionExcelColumns();
|
||||||
|
|
||||||
|
if (empty($inceptionColumns)) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unable to load inception Excel column configuration.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$sheet->setTitle('Employees');
|
||||||
|
|
||||||
|
// Header row from EmployeeServiceController column definitions
|
||||||
|
$colIndex = 1;
|
||||||
|
foreach ($inceptionColumns as $columnDef) {
|
||||||
|
$headerText = $columnDef['col_name'] ?? '';
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||||
|
$sheet->setCellValue($columnLetter . '1', $headerText);
|
||||||
|
$colIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to safely format dates as d-M-Y when possible
|
||||||
|
$formatDate = static function ($value): string {
|
||||||
|
if (empty($value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ts = strtotime($value);
|
||||||
|
if ($ts === false) {
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date('d-M-Y', $ts);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map Not in Nhance TPA rows into the inception Excel structure
|
||||||
|
$rowIndex = 2;
|
||||||
|
$sno = 1;
|
||||||
|
|
||||||
|
foreach ($notInNhance as $tpaRow) {
|
||||||
|
$colIndex = 1;
|
||||||
|
|
||||||
|
foreach ($inceptionColumns as $key => $columnDef) {
|
||||||
|
$value = '';
|
||||||
|
|
||||||
|
switch ($key) {
|
||||||
|
case 'sno':
|
||||||
|
$value = $sno;
|
||||||
|
break;
|
||||||
|
case 'emp_id':
|
||||||
|
$value = $tpaRow['emp_code'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'name_of_emp_dep':
|
||||||
|
$value = $tpaRow['name'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'dob':
|
||||||
|
$value = $formatDate($tpaRow['dob'] ?? '');
|
||||||
|
break;
|
||||||
|
case 'gender':
|
||||||
|
$value = $tpaRow['gender'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'relationship':
|
||||||
|
// Normalize relation text to match allowed values
|
||||||
|
$relation = (string) ($tpaRow['relation'] ?? '');
|
||||||
|
$relation = trim(strtolower($relation));
|
||||||
|
$map = [
|
||||||
|
'self' => 'Self',
|
||||||
|
'employee' => 'Self',
|
||||||
|
'spouse' => 'Spouse',
|
||||||
|
'wife' => 'Spouse',
|
||||||
|
'husband' => 'Spouse',
|
||||||
|
'son' => 'Son',
|
||||||
|
'daughter' => 'Daughter',
|
||||||
|
'father' => 'Father',
|
||||||
|
'mother' => 'Mother',
|
||||||
|
'father-in-law' => 'Father in Law',
|
||||||
|
'father in law' => 'Father in Law',
|
||||||
|
'mother-in-law' => 'Mother in Law',
|
||||||
|
'mother in law' => 'Mother in Law',
|
||||||
|
];
|
||||||
|
$value = $map[$relation] ?? ($tpaRow['relation'] ?? '');
|
||||||
|
break;
|
||||||
|
case 'basic_cover_si':
|
||||||
|
$value = $tpaRow['si'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'doc':
|
||||||
|
// Use DOJ from TPA data as Date of Coverage best-effort
|
||||||
|
$value = $formatDate($tpaRow['doj'] ?? '');
|
||||||
|
break;
|
||||||
|
case 'doj':
|
||||||
|
$value = $formatDate($tpaRow['doj'] ?? '');
|
||||||
|
break;
|
||||||
|
case 'pre_existing_ailments':
|
||||||
|
// Default to "0" (No) so validation passes for mandatory field
|
||||||
|
$value = '0';
|
||||||
|
break;
|
||||||
|
case 'change_event':
|
||||||
|
// For addition / dependent_addition, this is mandatory.
|
||||||
|
$value = 'addition';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Non-mapped columns (phone, email, etc.) left blank by default.
|
||||||
|
$value = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||||
|
$sheet->setCellValue($columnLetter . $rowIndex, $value);
|
||||||
|
$colIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowIndex++;
|
||||||
|
$sno++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-size columns
|
||||||
|
$totalColumns = count($inceptionColumns);
|
||||||
|
for ($c = 1; $c <= $totalColumns; $c++) {
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($c);
|
||||||
|
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the Excel file to the same folder used by manual uploads
|
||||||
|
$fileName = sprintf(
|
||||||
|
'not_in_nhance_employee_upload_%d_%s.xlsx',
|
||||||
|
$batchFileId,
|
||||||
|
date('Ymd_His')
|
||||||
|
);
|
||||||
|
$filePath = WRITEPATH . 'uploads/excel/' . $fileName;
|
||||||
|
|
||||||
|
$writer = new Xlsx($spreadsheet);
|
||||||
|
$writer->save($filePath);
|
||||||
|
|
||||||
|
// Create a new entry in the files table so that the
|
||||||
|
// existing Employee Upload with Events pipeline can process it.
|
||||||
|
$loggedInUserId = $batchFile['created_by'] ?? get_session_userid();
|
||||||
|
$action = 'addition';
|
||||||
|
|
||||||
|
$newFileId = $this->fileModel->insert([
|
||||||
|
'file_name' => $fileName,
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'policy_id' => $clientPolicyId,
|
||||||
|
'created_by' => $loggedInUserId,
|
||||||
|
'status' => 'inprogress',
|
||||||
|
'action' => $action,
|
||||||
|
'client_branch_id'=> $clientBranchId,
|
||||||
|
'uploaded_by' => 1,
|
||||||
|
'hr_file_id' => null,
|
||||||
|
'hr_id' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$newFileId || !is_numeric($newFileId)) {
|
||||||
|
$this->myLogger->logme(
|
||||||
|
'error',
|
||||||
|
'Failed to insert generated Not in Nhance employee upload file into files table',
|
||||||
|
[
|
||||||
|
'batch_file_id' => $batchFileId,
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'client_policy_id' => $clientPolicyId,
|
||||||
|
'client_branch_id' => $clientBranchId,
|
||||||
|
'file_name' => $fileName,
|
||||||
|
'insert_result' => $newFileId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unable to create file record for generated employee upload.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run the same format validation used for manual uploads.
|
||||||
|
$validationResult = $empServiceController->excelFileFormatValidation(['file_id' => $newFileId]);
|
||||||
|
|
||||||
|
if (isset($validationResult['error_summary']) && count($validationResult['error_summary'])) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'File upload was successful, but file format validation failed. Please review the error report.',
|
||||||
|
'data' => ['file_id' => $newFileId],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => true,
|
||||||
|
'message' => 'Employee upload file generated from Not in Nhance data and queued for processing.',
|
||||||
|
'data' => ['file_id' => $newFileId],
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->myLogger->logme(
|
||||||
|
'error',
|
||||||
|
'Error while generating employee upload from Not in Nhance'.
|
||||||
|
json_encode( [
|
||||||
|
'batch_file_id' => $batchFileId,
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_file' => $e->getFile(),
|
||||||
|
'exception_line' => $e->getLine(),
|
||||||
|
'exception_trace' => $e->getTraceAsString(),
|
||||||
|
'client_id' => $batchFile['client_id'] ?? null,
|
||||||
|
'client_policy_id' => $batchFile['client_policy_id'] ?? null,
|
||||||
|
'client_branch_id' => $batchFile['client_branch_id'] ?? null,
|
||||||
|
], JSON_PRETTY_PRINT)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unexpected error while generating employee upload file.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Correction Excel file from the Need to Review
|
||||||
|
* TPA variation data and push it into the existing correction
|
||||||
|
* upload pipeline.
|
||||||
|
*
|
||||||
|
* Each mismatched field (name, dob, relationship, email_corporate)
|
||||||
|
* becomes a separate row in the Excel, using the correction
|
||||||
|
* headers defined in EmployeeServiceController::$correction_excel_columns.
|
||||||
|
*
|
||||||
|
* @param int $batchFileId Batch file id used for TPA variation report.
|
||||||
|
* @param array $batchFile Batch file row from DB.
|
||||||
|
*
|
||||||
|
* @return array ['status' => bool, 'message' => string, 'data' => array]
|
||||||
|
*/
|
||||||
|
protected function generateCorrectionUploadFromNeedToReview(int $batchFileId, array $batchFile): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$clientId = (int) ($batchFile['client_id'] ?? 0);
|
||||||
|
$clientPolicyId = (int) ($batchFile['client_policy_id'] ?? 0);
|
||||||
|
$clientBranchId = (int) ($batchFile['client_branch_id'] ?? 0);
|
||||||
|
|
||||||
|
if (!$clientId || !$clientPolicyId || !$clientBranchId) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Incomplete batch file information. Client / policy / branch missing.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$TpaApiDataModel = new TpaApiDataModel();
|
||||||
|
|
||||||
|
// Reuse the same DB + TPA reconciliation used in getTPADataVariationReport
|
||||||
|
$dbRows = $this->employeePolicyModel->getTPADataVariationReport(
|
||||||
|
$clientId,
|
||||||
|
$clientPolicyId,
|
||||||
|
$batchFileId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!is_array($dbRows) || !count($dbRows)) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'No employee data found for Need to Review.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mismatchRows = [];
|
||||||
|
|
||||||
|
foreach ($dbRows as $dbRow) {
|
||||||
|
$tpaRows = $TpaApiDataModel->select('*')
|
||||||
|
->where('emp_code', $dbRow['emp_code'])
|
||||||
|
->where('file_id', $batchFileId)
|
||||||
|
->where('is_active', 1)
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
if (!count($tpaRows)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$match = $this->reconcileDbWithTpa($dbRow, $tpaRows);
|
||||||
|
|
||||||
|
if (($match['status'] ?? '') !== 'matched') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tpaRecord = $match['tpa_record'] ?? [];
|
||||||
|
$notMatching = $match['not_matching'] ?? [];
|
||||||
|
|
||||||
|
if (!is_array($notMatching) || !count($notMatching)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only consider fields that are supported by the correction Excel headers
|
||||||
|
$allowedFields = ['name', 'dob', 'relationship', 'email_corporate'];
|
||||||
|
|
||||||
|
foreach ($notMatching as $field) {
|
||||||
|
if (!in_array($field, $allowedFields, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mismatchRows[] = [
|
||||||
|
'emp_code' => $dbRow['emp_code'] ?? '',
|
||||||
|
'name' => $dbRow['name'] ?? '',
|
||||||
|
'field' => $field,
|
||||||
|
// Use TPA value as the corrected value to be applied in Nhance
|
||||||
|
'value' => $tpaRecord[$field] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!count($mismatchRows)) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'No mismatched records found to generate correction upload.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$empServiceController = new EmployeeServiceController();
|
||||||
|
$correctionColumns = $empServiceController->getCorrectionExcelColumns();
|
||||||
|
|
||||||
|
if (empty($correctionColumns)) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unable to load correction Excel column configuration.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$sheet->setTitle('Correction');
|
||||||
|
|
||||||
|
// Header row from EmployeeServiceController column definitions
|
||||||
|
$colIndex = 1;
|
||||||
|
foreach ($correctionColumns as $columnDef) {
|
||||||
|
$headerText = $columnDef['col_name'] ?? '';
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||||
|
$sheet->setCellValue($columnLetter . '1', $headerText);
|
||||||
|
$colIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$todayDisplay = date('d-M-Y');
|
||||||
|
|
||||||
|
$rowIndex = 2;
|
||||||
|
$sno = 1;
|
||||||
|
|
||||||
|
foreach ($mismatchRows as $row) {
|
||||||
|
$colIndex = 1;
|
||||||
|
|
||||||
|
$field_name = $row['field'] ?? '';
|
||||||
|
$field_value = ($field_name == 'dob' ? change_date_format($row['value'] ?? '', 'Y-m-d', 'd-M-Y') : $row['value'] ?? '' );
|
||||||
|
|
||||||
|
foreach ($correctionColumns as $key => $columnDef) {
|
||||||
|
$value = '';
|
||||||
|
|
||||||
|
switch ($key) {
|
||||||
|
case 'sno':
|
||||||
|
$value = $sno;
|
||||||
|
break;
|
||||||
|
case 'emp_id':
|
||||||
|
$value = $row['emp_code'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'name_of_emp_dep':
|
||||||
|
$value = $row['name'] ?? '';
|
||||||
|
break;
|
||||||
|
case 'field':
|
||||||
|
$value = $field_name;
|
||||||
|
break;
|
||||||
|
case 'value':
|
||||||
|
$value = $field_value ?? '';
|
||||||
|
break;
|
||||||
|
case 'date_of_correction':
|
||||||
|
$value = $todayDisplay;
|
||||||
|
break;
|
||||||
|
case 'change_event':
|
||||||
|
$value = 'correction';
|
||||||
|
break;
|
||||||
|
case 'remarks':
|
||||||
|
$value = '';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$value = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||||
|
$sheet->setCellValue($columnLetter . $rowIndex, $value);
|
||||||
|
$colIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowIndex++;
|
||||||
|
$sno++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-size columns
|
||||||
|
$totalColumns = count($correctionColumns);
|
||||||
|
for ($c = 1; $c <= $totalColumns; $c++) {
|
||||||
|
$columnLetter = Coordinate::stringFromColumnIndex($c);
|
||||||
|
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the Excel file to the same folder used by manual uploads
|
||||||
|
$fileName = sprintf(
|
||||||
|
'need_to_review_correction_upload_%d_%s.xlsx',
|
||||||
|
$batchFileId,
|
||||||
|
date('Ymd_His')
|
||||||
|
);
|
||||||
|
$filePath = WRITEPATH . 'uploads/excel/' . $fileName;
|
||||||
|
|
||||||
|
$writer = new Xlsx($spreadsheet);
|
||||||
|
$writer->save($filePath);
|
||||||
|
|
||||||
|
// Insert into files table so the existing correction pipeline can process it.
|
||||||
|
$loggedInUserId = $batchFile['created_by'] ?? get_session_userid();
|
||||||
|
|
||||||
|
$newFileId = $this->fileModel->insert([
|
||||||
|
'file_name' => $fileName,
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'policy_id' => $clientPolicyId,
|
||||||
|
'created_by' => $loggedInUserId,
|
||||||
|
'status' => 'inprogress',
|
||||||
|
'action' => 'correction',
|
||||||
|
'client_branch_id'=> $clientBranchId,
|
||||||
|
'uploaded_by' => 1,
|
||||||
|
'hr_file_id' => null,
|
||||||
|
'hr_id' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$newFileId || !is_numeric($newFileId)) {
|
||||||
|
$this->myLogger->logme(
|
||||||
|
'error',
|
||||||
|
'Failed to insert generated Need to Review correction upload file into files table',
|
||||||
|
[
|
||||||
|
'batch_file_id' => $batchFileId,
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'client_policy_id' => $clientPolicyId,
|
||||||
|
'client_branch_id' => $clientBranchId,
|
||||||
|
'file_name' => $fileName,
|
||||||
|
'insert_result' => $newFileId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unable to create file record for generated correction upload.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the same format validation used for manual uploads so that
|
||||||
|
// the correction file enters the normal processing pipeline.
|
||||||
|
$validationResult = $empServiceController->excelFileFormatValidation(['file_id' => $newFileId]);
|
||||||
|
|
||||||
|
if (isset($validationResult['error_summary']) && count($validationResult['error_summary'])) {
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'File upload was successful, but correction file format validation failed. Please review the error report.',
|
||||||
|
'data' => ['file_id' => $newFileId],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => true,
|
||||||
|
'message' => 'Correction upload file generated from Need to Review data and queued for processing.',
|
||||||
|
'data' => ['file_id' => $newFileId],
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->myLogger->logme(
|
||||||
|
'error',
|
||||||
|
'Error while generating correction upload from Need to Review'.
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
'batch_file_id' => $batchFileId,
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_file' => $e->getFile(),
|
||||||
|
'exception_line' => $e->getLine(),
|
||||||
|
'exception_trace' => $e->getTraceAsString(),
|
||||||
|
'client_id' => $batchFile['client_id'] ?? null,
|
||||||
|
'client_policy_id' => $batchFile['client_policy_id'] ?? null,
|
||||||
|
'client_branch_id' => $batchFile['client_branch_id'] ?? null,
|
||||||
|
],
|
||||||
|
JSON_PRETTY_PRINT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unexpected error while generating correction upload file.',
|
||||||
|
'data' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// not in use once all functionality workes well in this funciton then remvoe this function
|
// not in use once all functionality workes well in this funciton then remvoe this function
|
||||||
function compareDbWithTpa(array $db, array $tpaRows): array
|
function compareDbWithTpa(array $db, array $tpaRows): array
|
||||||
|
|||||||
@ -2404,6 +2404,9 @@ class EmployeeRestController extends AdminController
|
|||||||
|
|
||||||
if ($this->request->is('get')) {
|
if ($this->request->is('get')) {
|
||||||
|
|
||||||
|
$client_id = $this->request->getGet('client_id') ?? null;
|
||||||
|
|
||||||
|
|
||||||
$data['claim_status'] = $this->claimStatusModel
|
$data['claim_status'] = $this->claimStatusModel
|
||||||
->select('id,ticket_type, display_name as claim_status')
|
->select('id,ticket_type, display_name as claim_status')
|
||||||
->where('is_active', 1)
|
->where('is_active', 1)
|
||||||
@ -2411,12 +2414,57 @@ class EmployeeRestController extends AdminController
|
|||||||
->groupBy('display_name')
|
->groupBy('display_name')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
|
if (!empty($client_id)) {
|
||||||
|
|
||||||
|
if (is_string($client_id) && preg_match('/^[a-f0-9]{32}$/i', $client_id)) {
|
||||||
|
$client_data = $this->clientModel->where('MD5(id)', $client_id)->first();
|
||||||
|
$client_id = $client_data['id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_policy_data = $this->clientPolicyModel
|
||||||
|
->where('client_id', $client_id)
|
||||||
|
->where('is_active', 1)
|
||||||
|
->groupBy('policy_type_id')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
$data['ticket_type'] = [];
|
||||||
|
$addedTypes = [];
|
||||||
|
|
||||||
|
foreach ($client_policy_data as $value) {
|
||||||
|
|
||||||
|
if (in_array($value['policy_type_id'], [2,3,4,5]) && !in_array('1', $addedTypes)) {
|
||||||
|
$data['ticket_type'][] = ["ticket_type" => "1", "type_name" => "Claim-GMC"];
|
||||||
|
$addedTypes[] = '1';
|
||||||
|
|
||||||
|
} elseif (in_array($value['policy_type_id'], [1]) && !in_array('2', $addedTypes)) {
|
||||||
|
$data['ticket_type'][] = ["ticket_type" => "2", "type_name" => "Claim-GPA"];
|
||||||
|
$addedTypes[] = '2';
|
||||||
|
|
||||||
|
} elseif (in_array($value['policy_type_id'], [6]) && !in_array('3', $addedTypes)) {
|
||||||
|
$data['ticket_type'][] = ["ticket_type" => "3", "type_name" => "EDLI"];
|
||||||
|
$addedTypes[] = '3';
|
||||||
|
|
||||||
|
} elseif (in_array($value['policy_type_id'], [7]) && !in_array('4', $addedTypes)) {
|
||||||
|
$data['ticket_type'][] = ["ticket_type" => "4", "type_name" => "GTLI"];
|
||||||
|
$addedTypes[] = '4';
|
||||||
|
|
||||||
|
} elseif (in_array($value['policy_type_id'], [72]) && !in_array('72', $addedTypes)) {
|
||||||
|
$data['ticket_type'][] = ["ticket_type" => "72", "type_name" => "OPD"];
|
||||||
|
$addedTypes[] = '72';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($data['ticket_type'], fn($a,$b) => $a['ticket_type'] <=> $b['ticket_type']);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
$data['ticket_type'] = [
|
$data['ticket_type'] = [
|
||||||
["ticket_type" => "1", "type_name" => "Claim-GMC"],
|
["ticket_type" => "1", "type_name" => "Claim-GMC"],
|
||||||
["ticket_type" => "2", "type_name" => "Claim-GPA"],
|
["ticket_type" => "2", "type_name" => "Claim-GPA"],
|
||||||
["ticket_type" => "3", "type_name" => "EDLI"],
|
["ticket_type" => "3", "type_name" => "EDLI"],
|
||||||
["ticket_type" => "4", "type_name" => "GTLI"],
|
["ticket_type" => "4", "type_name" => "GTLI"],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$claim_type = $this->ticketController->claimType;
|
$claim_type = $this->ticketController->claimType;
|
||||||
unset($claim_type[1][2]);
|
unset($claim_type[1][2]);
|
||||||
|
|||||||
@ -2575,4 +2575,29 @@ class EmployeeServiceController extends AdminController
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the inception Excel column configuration used for
|
||||||
|
* validating and processing Employee Upload with Events files.
|
||||||
|
* This allows other controllers to generate compatible Excel files.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getInceptionExcelColumns(): array
|
||||||
|
{
|
||||||
|
return $this->inception_excel_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the correction Excel column configuration used for
|
||||||
|
* validating and processing Employee correction files.
|
||||||
|
* This is reused by other controllers when they need to generate
|
||||||
|
* a correction-compatible Excel programmatically.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getCorrectionExcelColumns(): array
|
||||||
|
{
|
||||||
|
return $this->correction_excel_columns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -34,8 +34,7 @@ class ExpenseController extends AdminController
|
|||||||
try {
|
try {
|
||||||
$data['tab_name'] = 'Expense';
|
$data['tab_name'] = 'Expense';
|
||||||
$data['page_name'] = 'Expense';
|
$data['page_name'] = 'Expense';
|
||||||
$descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/u';
|
$descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+\'"]+$/u';
|
||||||
|
|
||||||
// Raw GET filters
|
// Raw GET filters
|
||||||
$rawFilters = $this->request->getGet() ?? [];
|
$rawFilters = $this->request->getGet() ?? [];
|
||||||
$rawFilters = is_array($rawFilters) ? $rawFilters : [];
|
$rawFilters = is_array($rawFilters) ? $rawFilters : [];
|
||||||
@ -97,6 +96,7 @@ class ExpenseController extends AdminController
|
|||||||
// Clients for dropdown
|
// Clients for dropdown
|
||||||
$data['clients'] = $this->clientModel
|
$data['clients'] = $this->clientModel
|
||||||
->select('id, client_name, short_name')
|
->select('id, client_name, short_name')
|
||||||
|
->where('client_type', 1)
|
||||||
->where('is_active', 1)
|
->where('is_active', 1)
|
||||||
->orderBy('client_name', 'ASC')
|
->orderBy('client_name', 'ASC')
|
||||||
->findAll();
|
->findAll();
|
||||||
@ -228,11 +228,12 @@ class ExpenseController extends AdminController
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'amount' => [
|
'amount' => [
|
||||||
'rules' => 'required|numeric|greater_than_equal_to[0]',
|
'rules' => 'required|numeric|greater_than_equal_to[0]|less_than_equal_to[100000000]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'required' => 'Amount is required',
|
'required' => 'Amount is required',
|
||||||
'numeric' => 'Amount must be numeric',
|
'numeric' => 'Amount must be numeric',
|
||||||
'greater_than_equal_to' => 'Amount cannot be negative',
|
'greater_than_equal_to' => 'Amount cannot be negative',
|
||||||
|
'less_than_equal_to' => 'Amount cannot be greater than 100 Cr.',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -240,11 +240,18 @@ class FhplApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract claim status
|
// Extract claim status
|
||||||
$claimData = $response['data'][0];
|
// $claimData = $response['data'][0];
|
||||||
|
$allClaimData = $response['data'];
|
||||||
|
|
||||||
|
$currentStatus = "";
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$tpa_claim_no = $claimData['CLAIM_ID'] ?? '';
|
$tpa_claim_no = $claimData['CLAIM_ID'] ?? '';
|
||||||
$currentStatus = $claimData['CLAIM_STATUS'] ?? '';
|
$currentStatus = $claimData['CLAIM_STATUS'] ?? '';
|
||||||
$tpa_claim_type = $claimData['CLAIM_TYPE'] ?? '';
|
$tpa_claim_type = $claimData['CLAIM_TYPE'] ?? '';
|
||||||
$tpa_ailments = $claimData['AILMENT'] ?? '';
|
$tpa_ailments = $claimData['AILMENT'] ?? '';
|
||||||
|
$doa = !empty($claimData['DATE_OF_ADMISSION'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $claimData['DATE_OF_ADMISSION']))) : null;
|
||||||
|
|
||||||
|
|
||||||
$validStatuses = [
|
$validStatuses = [
|
||||||
"In-Progress" => 5,
|
"In-Progress" => 5,
|
||||||
@ -259,6 +266,7 @@ class FhplApiController extends BaseController
|
|||||||
$updateArray = [
|
$updateArray = [
|
||||||
'tpa_claim_status' => $currentStatus,
|
'tpa_claim_status' => $currentStatus,
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||||
@ -269,13 +277,20 @@ class FhplApiController extends BaseController
|
|||||||
if (!empty($tpa_ailments)) {
|
if (!empty($tpa_ailments)) {
|
||||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||||
}
|
}
|
||||||
|
if (empty($ticket['tpa_claim_id']) || $ticket['tpa_claim_id'] === null) {
|
||||||
|
$updateArray['tpa_claim_id'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
if (empty($ticket['claim_number']) || $ticket['claim_number'] === null) {
|
||||||
|
$updateArray['claim_number'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($ticket['doa'] == $doa || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
|
|
||||||
$this->db->table('ticket_master')->where('id',$claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id',$claimId)->update($updateArray);
|
||||||
log_message('error', "FHPL - Claim status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
log_message('error', "FHPL - Claim status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||||
|
}else{
|
||||||
|
log_message('error', "FHPL - Claim status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$doa} | Status={$currentStatus}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||||
|
|
||||||
@ -1022,6 +1037,10 @@ class FhplApiController extends BaseController
|
|||||||
'tpa_id' => trim($row['TPA_TPADETAIL_ID'] ?? null),
|
'tpa_id' => trim($row['TPA_TPADETAIL_ID'] ?? null),
|
||||||
'age' => is_numeric($row['AGE'] ?? null) ? (int) $row['AGE'] : null,
|
'age' => is_numeric($row['AGE'] ?? null) ? (int) $row['AGE'] : null,
|
||||||
|
|
||||||
|
'si' => $row['BASE_SUMINSURED'] ?? null,
|
||||||
|
'doj' => !empty($row['DATE_OF_JOINING'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $row['DATE_OF_JOINING']))) : null,
|
||||||
|
|
||||||
|
|
||||||
'is_active' => 1,
|
'is_active' => 1,
|
||||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -300,11 +300,17 @@ class HealthIndiaApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract claim status
|
// Extract claim status
|
||||||
$claimData = $response['data']['result'][0];
|
// $claimData = $response['data']['result'][0];
|
||||||
|
$allClaimData = $response['data']['result'];
|
||||||
|
|
||||||
|
$currentStatus = "";
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||||
$currentStatus = $claimData['claiM_STATUS'] ?? '';
|
$currentStatus = $claimData['claiM_STATUS'] ?? '';
|
||||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||||
$tpa_ailments = $claimData['ailment'] ?? '';
|
$tpa_ailments = $claimData['ailment'] ?? '';
|
||||||
|
$doa = !empty($claimData['datE_OF_ADMISSION'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $claimData['datE_OF_ADMISSION']))) : null;
|
||||||
|
|
||||||
$validStatuses = [
|
$validStatuses = [
|
||||||
"In-Progress" => 5,
|
"In-Progress" => 5,
|
||||||
@ -319,9 +325,10 @@ class HealthIndiaApiController extends BaseController
|
|||||||
|
|
||||||
$updateArray = [
|
$updateArray = [
|
||||||
'tpa_claim_status' => $currentStatus,
|
'tpa_claim_status' => $currentStatus,
|
||||||
'tpa_claim_id' => $tpa_claim_no,
|
// 'tpa_claim_id' => $tpa_claim_no,
|
||||||
'claim_number' => $tpa_claim_no,
|
// 'claim_number' => $tpa_claim_no,
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||||
@ -332,13 +339,20 @@ class HealthIndiaApiController extends BaseController
|
|||||||
if (!empty($tpa_ailments)) {
|
if (!empty($tpa_ailments)) {
|
||||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||||
}
|
}
|
||||||
|
if (empty($ticket['tpa_claim_id']) || $ticket['tpa_claim_id'] === null) {
|
||||||
|
$updateArray['tpa_claim_id'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
if (empty($ticket['claim_number']) || $ticket['claim_number'] === null) {
|
||||||
|
$updateArray['claim_number'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($ticket['doa'] == $doa || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
|
|
||||||
$this->db->table('ticket_master')->where('id',$claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id',$claimId)->update($updateArray);
|
||||||
|
|
||||||
log_message('error', "HEALTH_INDIA - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
log_message('error', "HEALTH_INDIA - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||||
|
}else{
|
||||||
|
log_message('error', "HEALTH_INDIA - Claim status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$doa} | Status={$currentStatus}");
|
||||||
|
}
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
'status' => true,
|
'status' => true,
|
||||||
'message' => 'Claim status updated.',
|
'message' => 'Claim status updated.',
|
||||||
@ -1067,6 +1081,9 @@ class HealthIndiaApiController extends BaseController
|
|||||||
'tpa_id' => trim($row['memberId'] ?? null),
|
'tpa_id' => trim($row['memberId'] ?? null),
|
||||||
'age' => is_numeric($row['age'] ?? null) ? (int) $row['age'] : null,
|
'age' => is_numeric($row['age'] ?? null) ? (int) $row['age'] : null,
|
||||||
|
|
||||||
|
'si' => $row['baseSumInsured'] ?? null,
|
||||||
|
'doj' => !empty($row['dateofPolicyJoining'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $row['dateofPolicyJoining']))) : null,
|
||||||
|
|
||||||
'is_active' => 1,
|
'is_active' => 1,
|
||||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -573,7 +573,11 @@ class MediAssistApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract Claim Status
|
// Extract Claim Status
|
||||||
$claimData = $response['data']['claimsData'][0];
|
$allClaimData = $response['data']['claimsData'];
|
||||||
|
|
||||||
|
$currentStatus = "";
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||||
@ -621,6 +625,7 @@ class MediAssistApiController extends BaseController
|
|||||||
$updateArray = [
|
$updateArray = [
|
||||||
'tpa_claim_status' => $currentStatus,
|
'tpa_claim_status' => $currentStatus,
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
@ -640,11 +645,17 @@ class MediAssistApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if($ticket['doa'] == $this->mediDate($claimData['datE_OF_ADMISSION'] ?? null) || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
// UPDATE ticket_master
|
// UPDATE ticket_master
|
||||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||||
|
|
||||||
// LOG UPDATE
|
// LOG UPDATE
|
||||||
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
||||||
|
}else{
|
||||||
|
log_message('error', "MEDI_ASSIST | Fetch Claim Status | Claim Status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$claimData['datE_OF_ADMISSION']} | Status={$currentStatus}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return ['status' => true,'message' => 'Claim Status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
return ['status' => true,'message' => 'Claim Status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||||
}
|
}
|
||||||
@ -796,6 +807,10 @@ class MediAssistApiController extends BaseController
|
|||||||
'gender' => strtoupper($row['benefSex'] ?? null),
|
'gender' => strtoupper($row['benefSex'] ?? null),
|
||||||
'self' => strtolower($row['relName'] ?? '') === 'self' ? 1 : 0,
|
'self' => strtolower($row['relName'] ?? '') === 'self' ? 1 : 0,
|
||||||
|
|
||||||
|
'si' => $row['sum_insured'] ?? null,
|
||||||
|
'doj' => $this->mediDate($row['benefWEF'] ?? null),
|
||||||
|
|
||||||
|
|
||||||
'tpa_id' => trim($row['benefMediAssistID'] ?? null),
|
'tpa_id' => trim($row['benefMediAssistID'] ?? null),
|
||||||
'age' => is_numeric($row['benefAge'] ?? null)
|
'age' => is_numeric($row['benefAge'] ?? null)
|
||||||
? (int) $row['benefAge']
|
? (int) $row['benefAge']
|
||||||
@ -890,6 +905,9 @@ class MediAssistApiController extends BaseController
|
|||||||
// CALL API
|
// CALL API
|
||||||
$response = call_third_party_api($url, $method, $headers, $body);
|
$response = call_third_party_api($url, $method, $headers, $body);
|
||||||
|
|
||||||
|
log_message('error','MEDI_ASSIST - 2 hours Claim Status API Response: ' . json_encode($response));
|
||||||
|
|
||||||
|
|
||||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||||
log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||||
$error_data[$claimId] = [
|
$error_data[$claimId] = [
|
||||||
@ -900,7 +918,12 @@ class MediAssistApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract Claim Status
|
// Extract Claim Status
|
||||||
$claimData = $response['data']['claimsData'][0];
|
// $allClaimData = $response['data']['claimsData'][0];
|
||||||
|
$allClaimData = $response['data']['claimsData'];
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||||
@ -948,13 +971,19 @@ class MediAssistApiController extends BaseController
|
|||||||
|
|
||||||
$updateArray = [
|
$updateArray = [
|
||||||
'tpa_claim_status' => $currentStatus,
|
'tpa_claim_status' => $currentStatus,
|
||||||
'tpa_claim_id' => $tpa_claim_no,
|
|
||||||
'claim_number' => $tpa_claim_no,
|
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||||
}
|
}
|
||||||
|
if (empty($ticket['tpa_claim_id']) || $ticket['tpa_claim_id'] === null) {
|
||||||
|
$updateArray['tpa_claim_id'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
if (empty($ticket['claim_number']) || $ticket['claim_number'] === null) {
|
||||||
|
$updateArray['claim_number'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
if (!empty($tpa_claim_type)) {
|
if (!empty($tpa_claim_type)) {
|
||||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||||
}
|
}
|
||||||
@ -962,14 +991,20 @@ class MediAssistApiController extends BaseController
|
|||||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($ticket['doa'] == $this->mediDate($claimData['datE_OF_ADMISSION'] ?? null) || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
// UPDATE ticket_master
|
// UPDATE ticket_master
|
||||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||||
$status_updated_count ++;
|
$status_updated_count ++;
|
||||||
|
|
||||||
// LOG UPDATE
|
// LOG UPDATE
|
||||||
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
||||||
|
}else{
|
||||||
|
log_message('error', "MEDI_ASSIST | 2 hours | Claim Status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$claimData['datE_OF_ADMISSION']} | Status={$currentStatus}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message('error',"MEDI_ASSIST - Claim Status ENDED | 2 hours | Total Tickets={".count($TicketData)."} | Status Updated={$status_updated_count} | Errors={".count($error_data)."}");
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'status' => true,
|
'status' => true,
|
||||||
|
|||||||
@ -5618,17 +5618,18 @@ class PolicyTransactionController extends BaseController
|
|||||||
$policy_end_date = trim($row[11]);
|
$policy_end_date = trim($row[11]);
|
||||||
$revenue_type = trim($row[12]);
|
$revenue_type = trim($row[12]);
|
||||||
|
|
||||||
$base_premium = trim($row[16]);
|
$base_premium = cleanNumber($row[16]);
|
||||||
$non_commission_premium_amount = trim($row[17]);
|
$non_commission_premium_amount = cleanNumber($row[17]);
|
||||||
$tp_premium = trim($row[18]);
|
$tp_premium = cleanNumber($row[18]);
|
||||||
$igst = trim($row[19]);
|
$igst = cleanNumber($row[19]);
|
||||||
$cgst = trim($row[20]);
|
$cgst = cleanNumber($row[20]);
|
||||||
$sgst = trim($row[21]);
|
$sgst = cleanNumber($row[21]);
|
||||||
$stamp_duty = trim($row[22]);
|
$stamp_duty = cleanNumber($row[22]);
|
||||||
|
$agreed_amount = cleanNumber($row[23]);
|
||||||
|
$agreed_bp_percentage = cleanNumber($row[24]);
|
||||||
|
$agreed_tp_percentage = cleanNumber($row[25]);
|
||||||
|
|
||||||
// $total = trim($row[23]);
|
// $total = trim($row[23]);
|
||||||
$agreed_amount = trim($row[23]);
|
|
||||||
$agreed_bp_percentage = trim($row[24]);
|
|
||||||
$agreed_tp_percentage = trim($row[25]);
|
|
||||||
// $actual_bp_amount = trim($row[27]);
|
// $actual_bp_amount = trim($row[27]);
|
||||||
// $actual_tp_amount = trim($row[28]);
|
// $actual_tp_amount = trim($row[28]);
|
||||||
// $actual_bp_percentage = trim($row[29]);
|
// $actual_bp_percentage = trim($row[29]);
|
||||||
@ -5647,16 +5648,16 @@ class PolicyTransactionController extends BaseController
|
|||||||
$rewards = 0;
|
$rewards = 0;
|
||||||
|
|
||||||
$calculation = calculateMotorPolicyAmounts([
|
$calculation = calculateMotorPolicyAmounts([
|
||||||
'base_premium' => trim($row[16]),
|
'base_premium' => $base_premium,
|
||||||
'non_commission_premium_amount' => trim($row[17]),
|
'non_commission_premium_amount' => $non_commission_premium_amount,
|
||||||
'tp_premium' => trim($row[18]),
|
'tp_premium' => $tp_premium,
|
||||||
'igst' => trim($row[19]),
|
'igst' => $igst,
|
||||||
'cgst' => trim($row[20]),
|
'cgst' => $cgst,
|
||||||
'sgst' => trim($row[21]),
|
'sgst' => $sgst,
|
||||||
'stamp_duty' => trim($row[22]),
|
'stamp_duty' => $stamp_duty,
|
||||||
'agreed_amount' => trim($row[23]),
|
'agreed_amount' => $agreed_amount,
|
||||||
'agreed_bp_percentage' => trim($row[24]),
|
'agreed_bp_percentage' => $agreed_bp_percentage,
|
||||||
'agreed_tp_percentage' => trim($row[25]),
|
'agreed_tp_percentage' => $agreed_tp_percentage,
|
||||||
'standard_bp_percentage' => 15.00,
|
'standard_bp_percentage' => 15.00,
|
||||||
'standard_tp_percentage' => 2.5
|
'standard_tp_percentage' => 2.5
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -5,8 +5,11 @@ namespace App\Controllers;
|
|||||||
use App\Controllers\BaseController;
|
use App\Controllers\BaseController;
|
||||||
use App\Models\EmployeePolicyModel;
|
use App\Models\EmployeePolicyModel;
|
||||||
use App\Models\ClientPolicyModel;
|
use App\Models\ClientPolicyModel;
|
||||||
|
use App\Models\TpaApiDataModel;
|
||||||
|
use App\Models\BatchFileModel;
|
||||||
use App\Models\InsurerBranchModel;
|
use App\Models\InsurerBranchModel;
|
||||||
use App\Models\RFQModel;
|
use App\Models\RFQModel;
|
||||||
|
use App\Models\EmployeeModel;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use CodeIgniter\API\ResponseTrait;
|
use CodeIgniter\API\ResponseTrait;
|
||||||
use Dompdf\Dompdf;
|
use Dompdf\Dompdf;
|
||||||
@ -1120,4 +1123,185 @@ class TestingController extends BaseController
|
|||||||
'metabaseUrl' => 'https://nsights.nhanceindia.in',
|
'metabaseUrl' => 'https://nsights.nhanceindia.in',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert sample data into tpa_api_data for testing variance report (Not in NHANCE, Not in TPA, Need to Review).
|
||||||
|
* Uses client_policy (policy_status=1, is_active=1), employee_policies (active), and employees.
|
||||||
|
*
|
||||||
|
* @param int|null $client_policy_id Optional. If not provided, first eligible policy is used.
|
||||||
|
* @return \CodeIgniter\HTTP\ResponseInterface
|
||||||
|
*/
|
||||||
|
public function insertSampleTpaApiData($client_policy_id = null)
|
||||||
|
{
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$clientPolicyModel = new ClientPolicyModel();
|
||||||
|
$employeePolicyModel = new EmployeePolicyModel();
|
||||||
|
$employeeModel = new EmployeeModel();
|
||||||
|
$tpaApiDataModel = new TpaApiDataModel();
|
||||||
|
$batchFileModel = new BatchFileModel();
|
||||||
|
|
||||||
|
// 1. Get policies: policy_status = 1, is_active = 1
|
||||||
|
$policyBuilder = $clientPolicyModel
|
||||||
|
->where('policy_status', 1)
|
||||||
|
->where('is_active', 1);
|
||||||
|
if ($client_policy_id !== null && $client_policy_id !== '') {
|
||||||
|
$policyBuilder->where('id', (int) $client_policy_id);
|
||||||
|
}
|
||||||
|
$policies = $policyBuilder->orderBy('id', 'ASC')->findAll();
|
||||||
|
if (empty($policies)) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'No active client policy found (policy_status=1, is_active=1).',
|
||||||
|
'data' => [],
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
$policy = $policies[0];
|
||||||
|
$client_policy_id = (int) $policy['id'];
|
||||||
|
$client_id = (int) $policy['client_id'];
|
||||||
|
$client_branch_id = !empty($policy['client_branch_id']) ? (int) $policy['client_branch_id'] : 0;
|
||||||
|
|
||||||
|
// 2. Get related employees from employee_policies (active) + employees
|
||||||
|
$empPolicies = $db->table('employee_polices ep')
|
||||||
|
->select('ep.id AS emp_policy_id, ep.employee_id, ep.tpa_id, e.emp_code, e.name, e.dob, e.gender, e.relationship')
|
||||||
|
->join('employees e', 'e.id = ep.employee_id')
|
||||||
|
->where('ep.client_policy_id', $client_policy_id)
|
||||||
|
->where('ep.is_active', 1)
|
||||||
|
->whereIn('ep.status', ['active', 'expired'])
|
||||||
|
->where('e.is_active', 1)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
if (empty($empPolicies)) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'No active employee policies found for this client policy.',
|
||||||
|
'data' => ['client_policy_id' => $client_policy_id],
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create a test batch file so we have a file_id for tpa_api_data
|
||||||
|
$createdBy = function_exists('get_session_userid') ? get_session_userid() : 1;
|
||||||
|
$batchCode = 'TPA_SAMPLE_' . date('YmdHis') . '_' . bin2hex(random_bytes(4));
|
||||||
|
$batchFileId = $batchFileModel->insert([
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_policy_id' => $client_policy_id,
|
||||||
|
'client_branch_id' => $client_branch_id,
|
||||||
|
'batch_code' => $batchCode,
|
||||||
|
'file_name' => 'sample_tpa_data_test_' . date('Y-m-d_His') . '.xlsx',
|
||||||
|
'insurer_or_tpa' => 'tpa',
|
||||||
|
'event_type' => 'api',
|
||||||
|
'actions' => 'fetch',
|
||||||
|
'status' => 'partially success',
|
||||||
|
'count' => 0,
|
||||||
|
'created_by' => $createdBy,
|
||||||
|
'is_active' => 1,
|
||||||
|
]);
|
||||||
|
if (!$batchFileId) {
|
||||||
|
return $this->respond([
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Failed to create test batch file.',
|
||||||
|
'data' => [],
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
$file_id = (int) $batchFileId;
|
||||||
|
|
||||||
|
$tpaApiDataModel->skipValidation(true);
|
||||||
|
$inserted = ['not_in_nhance' => 0, 'need_to_review' => 0];
|
||||||
|
$toInsert = [];
|
||||||
|
|
||||||
|
// 4. Not in NHANCE: insert TPA records with emp_codes that do NOT exist in NHANCE for this policy
|
||||||
|
$fakeEmpCodes = ['TPA_SAMPLE_NOTINNHANCE_1', 'TPA_SAMPLE_NOTINNHANCE_2'];
|
||||||
|
foreach ($fakeEmpCodes as $i => $empCode) {
|
||||||
|
$toInsert[] = [
|
||||||
|
'file_id' => $file_id,
|
||||||
|
'emp_code' => $empCode,
|
||||||
|
'name' => 'Sample TPA Only ' . ($i + 1),
|
||||||
|
'dob' => '1990-01-' . str_pad((string)(15 + $i), 2, '0', STR_PAD_LEFT),
|
||||||
|
'relation' => 'Self',
|
||||||
|
'gender' => ($i % 2 === 0) ? 'M' : 'F',
|
||||||
|
'self' => 'Sample TPA Only ' . ($i + 1),
|
||||||
|
'tpa_id' => 'TPA' . (1000 + $i),
|
||||||
|
'age' => 32 + $i,
|
||||||
|
'is_active' => 1,
|
||||||
|
'desc' => 'Sample data – Not in NHANCE',
|
||||||
|
'created_by'=> $createdBy,
|
||||||
|
];
|
||||||
|
$inserted['not_in_nhance']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Need to Review: same employee as in NHANCE but with different name/dob/gender
|
||||||
|
$needReview = array_slice($empPolicies, 0, min(2, count($empPolicies)));
|
||||||
|
foreach ($needReview as $emp) {
|
||||||
|
$dob = $emp['dob'];
|
||||||
|
if (is_string($dob) && preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $dob, $m)) {
|
||||||
|
$altDob = $m[1] . '-' . $m[2] . '-' . str_pad((string)((int)$m[3] + 1), 2, '0', STR_PAD_LEFT);
|
||||||
|
} else {
|
||||||
|
$altDob = '1995-06-15';
|
||||||
|
}
|
||||||
|
$toInsert[] = [
|
||||||
|
'file_id' => $file_id,
|
||||||
|
'emp_code' => $emp['emp_code'],
|
||||||
|
'name' => '[TPA Altered] ' . ($emp['name'] ?? 'Unknown'),
|
||||||
|
'dob' => $altDob,
|
||||||
|
'relation' => $emp['relationship'] ?? 'Self',
|
||||||
|
'gender' => (strtoupper($emp['gender'] ?? 'M') === 'M') ? 'F' : 'M',
|
||||||
|
'self' => $emp['name'] ?? 'Unknown',
|
||||||
|
'tpa_id' => $emp['tpa_id'] ?? ('T' . $emp['employee_id']),
|
||||||
|
'age' => 30,
|
||||||
|
'is_active' => 1,
|
||||||
|
'desc' => 'Sample data – Need to Review (mismatch)',
|
||||||
|
'created_by'=> $createdBy,
|
||||||
|
];
|
||||||
|
$inserted['need_to_review']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($toInsert as $row) {
|
||||||
|
$tpaApiDataModel->insert($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in TPA: we do NOT insert those into tpa_api_data; NHANCE already has employees. So any employee
|
||||||
|
// we did not add to tpa_api_data will appear as "Not in TPA". We added only "Need to Review" and
|
||||||
|
// "Not in NHANCE" rows; the rest of NHANCE employees remain without TPA rows => they show as Not in TPA.
|
||||||
|
|
||||||
|
return $this->respond([
|
||||||
|
'status' => true,
|
||||||
|
'message' => 'Sample TPA API data inserted successfully.',
|
||||||
|
'data' => [
|
||||||
|
'file_id' => $file_id,
|
||||||
|
'client_policy_id' => $client_policy_id,
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'batch_code' => $batchCode,
|
||||||
|
'inserted' => $inserted,
|
||||||
|
'not_in_tpa_note' => 'Employees in NHANCE that were not added to TPA data will appear as "Not in TPA" when you run the variance report for this file.',
|
||||||
|
],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List employee count per client policy.
|
||||||
|
* No input parameters. Checks all client_policy records and counts linked employee_policies per policy.
|
||||||
|
*
|
||||||
|
* @return \CodeIgniter\HTTP\ResponseInterface
|
||||||
|
*/
|
||||||
|
public function listEmployeeCountByClientPolicy()
|
||||||
|
{
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$rows = $db->table('client_policy cp')
|
||||||
|
->select('cp.id AS client_policy_id, COUNT(ep.id) AS employee_policy_count', false)
|
||||||
|
->join('employee_polices ep', 'ep.client_policy_id = cp.id', 'left')
|
||||||
|
->groupBy('cp.id')
|
||||||
|
->orderBy('cp.id', 'ASC')
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
$list = array_map(function ($row) {
|
||||||
|
return [
|
||||||
|
'client_policy_id' => (int) $row['client_policy_id'],
|
||||||
|
'employee_policy_count' => (int) $row['employee_policy_count'],
|
||||||
|
];
|
||||||
|
}, $rows);
|
||||||
|
return $this->respond([
|
||||||
|
'status' => true,
|
||||||
|
'message' => 'Employee count per client policy.',
|
||||||
|
'data' => $list,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1178,9 +1178,9 @@ class TicketController extends BaseController
|
|||||||
],
|
],
|
||||||
'tpa_no' => [
|
'tpa_no' => [
|
||||||
'label' => 'TPA ID',
|
'label' => 'TPA ID',
|
||||||
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]',
|
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/\-_]+$/]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'regex_match' => 'TPA ID can only contain letters, numbers, and /.'
|
'regex_match' => 'TPA ID can only contain letters, numbers, /, -, and _.'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
|
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
|
||||||
@ -1233,9 +1233,9 @@ class TicketController extends BaseController
|
|||||||
],
|
],
|
||||||
'hospital_address' => [
|
'hospital_address' => [
|
||||||
'label' => 'Hospital Address',
|
'label' => 'Hospital Address',
|
||||||
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s\-_.,#\/]+$/]',
|
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s.,#\/_-]+$/]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'regex_match' => 'The {field} contains invalid characters (Allowed: letters, numbers, spaces, dashes, commas, dots, # and /).'
|
'regex_match' => 'The {field} contains invalid characters (Allowed: letters, numbers, spaces, -, _, ., ,, # and /).'
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'hospital_state' => [
|
'hospital_state' => [
|
||||||
@ -1531,9 +1531,9 @@ class TicketController extends BaseController
|
|||||||
],
|
],
|
||||||
'tpa_no' => [
|
'tpa_no' => [
|
||||||
'label' => 'TPA ID',
|
'label' => 'TPA ID',
|
||||||
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]',
|
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/\-_]+$/]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'regex_match' => 'TPA ID can only contain letters, numbers, and /.'
|
'regex_match' => 'TPA ID can only contain letters, numbers, /, -, and _.'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
|
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
|
||||||
@ -1586,9 +1586,9 @@ class TicketController extends BaseController
|
|||||||
],
|
],
|
||||||
'hospital_address' => [
|
'hospital_address' => [
|
||||||
'label' => 'Hospital Address',
|
'label' => 'Hospital Address',
|
||||||
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s\-_.,#\/]+$/]',
|
'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s.,#\/_-]+$/]',
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'regex_match' => 'The {field} contains invalid characters (Allowed: letters, numbers, spaces, dashes, commas, dots, # and /).'
|
'regex_match' => 'The {field} contains invalid characters (Allowed: letters, numbers, spaces, -, _, ., ,, # and /).'
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'hospital_state' => [
|
'hospital_state' => [
|
||||||
@ -1777,8 +1777,12 @@ class TicketController extends BaseController
|
|||||||
$ticket_id = $this->request->getPost('ticket_master_id');
|
$ticket_id = $this->request->getPost('ticket_master_id');
|
||||||
$old_ticket_data = $this->ticketMasterModel->where('id', $ticket_id)->where('is_active', 1)->first();
|
$old_ticket_data = $this->ticketMasterModel->where('id', $ticket_id)->where('is_active', 1)->first();
|
||||||
$ticket_data['claim_status_id'] = $this->getLastMatchedStatus($ticket_data, $old_ticket_data);
|
$ticket_data['claim_status_id'] = $this->getLastMatchedStatus($ticket_data, $old_ticket_data);
|
||||||
|
$ticket_data['last_updated_by'] = 'USER';
|
||||||
// print_rr($ticket_data); die;
|
// print_rr($ticket_data); die;
|
||||||
|
|
||||||
|
$this->myLogger->logme('error', "[UPDATE_CLAIM] Ticket Master ID: {data}", ['data' => $ticket_id]);
|
||||||
|
$this->myLogger->logme('error', "[UPDATE_CLAIM] Old Ticket Data: {data}", ['data' => json_encode($old_ticket_data, JSON_PRETTY_PRINT)]);
|
||||||
|
$this->myLogger->logme('error', "[UPDATE_CLAIM] New Ticket Data: {data}", ['data' => json_encode($ticket_data, JSON_PRETTY_PRINT)]);
|
||||||
|
|
||||||
if ($ticket_data) {
|
if ($ticket_data) {
|
||||||
$return_value = $this->ticketMasterModel->where('id', $ticket_id)->set($ticket_data)->update();
|
$return_value = $this->ticketMasterModel->where('id', $ticket_id)->set($ticket_data)->update();
|
||||||
@ -2400,6 +2404,8 @@ class TicketController extends BaseController
|
|||||||
th.old_value,
|
th.old_value,
|
||||||
th.new_value,
|
th.new_value,
|
||||||
th.created_at,
|
th.created_at,
|
||||||
|
th.updated_by,
|
||||||
|
|
||||||
CONCAT_WS(' ', creator.first_name, creator.last_name) AS modified_by,
|
CONCAT_WS(' ', creator.first_name, creator.last_name) AS modified_by,
|
||||||
-- Claim Status
|
-- Claim Status
|
||||||
old_status.claim_status as old_status_value,
|
old_status.claim_status as old_status_value,
|
||||||
|
|||||||
@ -495,10 +495,17 @@ class VidalApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract claim status
|
// Extract claim status
|
||||||
$claimData = $response['data']['data']['claims'][0];
|
// $claimData = $response['data']['data']['claims'][0];
|
||||||
|
$allClaimData = $response['data']['data']['claims'];
|
||||||
|
|
||||||
|
$currentStatus = "";
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
||||||
$currentStatus = $claimData['status'] ?? '';
|
$currentStatus = $claimData['status'] ?? '';
|
||||||
$tpa_claim_type = $claimData['claimType'] ?? '';
|
$tpa_claim_type = $claimData['claimType'] ?? '';
|
||||||
|
$doa = !empty($claimData['doa'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $claimData['doa']))) : null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// VALID STATUS LIST
|
// VALID STATUS LIST
|
||||||
@ -515,6 +522,7 @@ class VidalApiController extends BaseController
|
|||||||
'tpa_claim_id' => $tpa_claim_no,
|
'tpa_claim_id' => $tpa_claim_no,
|
||||||
// 'claim_number' => $tpa_claim_no, // already updated
|
// 'claim_number' => $tpa_claim_no, // already updated
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||||
@ -523,12 +531,16 @@ class VidalApiController extends BaseController
|
|||||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($ticket['doa'] == $doa || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
// UPDATE ticket_master
|
// UPDATE ticket_master
|
||||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||||
|
|
||||||
// LOG UPDATE
|
// LOG UPDATE
|
||||||
log_message('error', "VIDAL - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
log_message('error', "VIDAL - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||||
|
}else{
|
||||||
|
log_message('error', "VIDAL - Claim Status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$doa} | Status={$currentStatus}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||||
}
|
}
|
||||||
@ -611,10 +623,15 @@ class VidalApiController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract claim status
|
// Extract claim status
|
||||||
$claimData = $response['data']['data']['claims'][0];
|
// $claimData = $response['data']['data']['claims'][0];
|
||||||
|
$allClaimData = $response['data']['data']['claims'];
|
||||||
|
|
||||||
|
foreach ($allClaimData as $claimData) {
|
||||||
|
|
||||||
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
||||||
$currentStatus = $claimData['status'] ?? '';
|
$currentStatus = $claimData['status'] ?? '';
|
||||||
$tpa_claim_type = $claimData['claimType'] ?? '';
|
$tpa_claim_type = $claimData['claimType'] ?? '';
|
||||||
|
$doa = !empty($claimData['doa'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $claimData['doa']))) : null;
|
||||||
|
|
||||||
|
|
||||||
// VALID STATUS LIST
|
// VALID STATUS LIST
|
||||||
@ -628,26 +645,39 @@ class VidalApiController extends BaseController
|
|||||||
|
|
||||||
$updateArray = [
|
$updateArray = [
|
||||||
'tpa_claim_status' => $currentStatus,
|
'tpa_claim_status' => $currentStatus,
|
||||||
'tpa_claim_id' => $tpa_claim_no,
|
|
||||||
// 'claim_number' => $tpa_claim_no, // already updated
|
// 'claim_number' => $tpa_claim_no, // already updated
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'last_updated_by' => 'API',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($validStatuses[$currentStatus])) {
|
if (isset($validStatuses[$currentStatus])) {
|
||||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||||
}
|
}
|
||||||
if (!empty($tpa_claim_type)) {
|
if (!empty($tpa_claim_type)) {
|
||||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||||
}
|
}
|
||||||
|
if (empty($ticket['tpa_claim_id']) || $ticket['tpa_claim_id'] === null) {
|
||||||
|
$updateArray['tpa_claim_id'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
if (empty($ticket['claim_number']) || $ticket['claim_number'] === null) {
|
||||||
|
$updateArray['claim_number'] = $tpa_claim_no;
|
||||||
|
}
|
||||||
|
|
||||||
// UPDATE ticket_master
|
// UPDATE ticket_master
|
||||||
|
if($ticket['doa'] == $doa || $ticket['claim_number'] == $tpa_claim_no){
|
||||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||||
|
|
||||||
// LOG UPDATE
|
// LOG UPDATE
|
||||||
log_message('error', "VIDAL - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
log_message('error', "VIDAL - Claim Status SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||||
|
|
||||||
$status_updated_count ++;
|
$status_updated_count ++;
|
||||||
|
}else{
|
||||||
|
log_message('error', "VIDAL - Claim Status NOT UPDATED | TicketID={$claimId} | TicketDOA={$ticket['doa']} | ClaimDOA={$doa} | Status={$currentStatus}");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message('error', "VIDAL - Claim Status ENDED | Total Tickets={".count($TicketData)."} | Status Updated={$status_updated_count} | Errors={".count($error_data)."}");
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'status' => true,
|
'status' => true,
|
||||||
|
|||||||
@ -3072,6 +3072,8 @@ if (!function_exists('validate_mobile_value')) {
|
|||||||
if (!function_exists('validate_positive_number_value')) {
|
if (!function_exists('validate_positive_number_value')) {
|
||||||
function validate_positive_number_value($value)
|
function validate_positive_number_value($value)
|
||||||
{
|
{
|
||||||
|
$value = cleanNumber($value);
|
||||||
|
|
||||||
if ($value === "" || $value === null) {
|
if ($value === "" || $value === null) {
|
||||||
return ['status' => true, 'error' => null];
|
return ['status' => true, 'error' => null];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1382,3 +1382,16 @@ if (! function_exists('add_google_calender_event')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('cleanNumber')) {
|
||||||
|
|
||||||
|
function cleanNumber($value){
|
||||||
|
if (empty($value)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = str_replace(',', '', trim($value));
|
||||||
|
return is_numeric($value) ? (float)$value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -368,7 +368,8 @@ abstract class BaseTpaClaimImportService
|
|||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return 61;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -374,7 +374,8 @@ class EmployeePolicyModel extends Model
|
|||||||
public function getEmployeePolicyForEcard($policy_id = 0)
|
public function getEmployeePolicyForEcard($policy_id = 0)
|
||||||
{
|
{
|
||||||
$result = $this->select([
|
$result = $this->select([
|
||||||
'employee_polices.id'
|
'employee_polices.id',
|
||||||
|
'emp.relationship',
|
||||||
])
|
])
|
||||||
->join('employees emp', 'employee_polices.employee_id = emp.id');
|
->join('employees emp', 'employee_polices.employee_id = emp.id');
|
||||||
if ($policy_id !=0 && !empty($policy_id)) {
|
if ($policy_id !=0 && !empty($policy_id)) {
|
||||||
|
|||||||
@ -3785,7 +3785,7 @@
|
|||||||
$totalBilled[$key] = ($totalBilled[$key] ?? 0) + (float) ($row['billed_amt'] ?? 0);
|
$totalBilled[$key] = ($totalBilled[$key] ?? 0) + (float) ($row['billed_amt'] ?? 0);
|
||||||
|
|
||||||
// Store total_irda_amt once
|
// Store total_irda_amt once
|
||||||
if (!isset($totalIrdaMap[$key]) && ($row['total_irda_amt'] ?? 0) > 0) {
|
if (!isset($totalIrdaMap[$key])) {
|
||||||
$totalIrdaMap[$key] = (float) $row['total_irda_amt'];
|
$totalIrdaMap[$key] = (float) $row['total_irda_amt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3802,11 +3802,24 @@
|
|||||||
foreach ($result as $row) {
|
foreach ($result as $row) {
|
||||||
$ptId = $row['pt_id'].'-'.$row['insurer_id'];
|
$ptId = $row['pt_id'].'-'.$row['insurer_id'];
|
||||||
if (!isset($ptSeen[$ptId])) {
|
if (!isset($ptSeen[$ptId])) {
|
||||||
|
|
||||||
|
$totalIrdaVal = $totalIrdaMap[$ptId] ?? 0;
|
||||||
|
$totalBilledVal = $totalBilled[$ptId] ?? 0;
|
||||||
|
$addMinus = false;
|
||||||
|
|
||||||
|
if($totalIrdaVal < 0){
|
||||||
|
$totalIrdaVal = abs($totalIrdaVal);
|
||||||
|
$totalBilledVal = abs($totalBilledVal);
|
||||||
|
$addMinus = true;
|
||||||
|
}
|
||||||
|
|
||||||
// First entry → set unbilled amount
|
// First entry → set unbilled amount
|
||||||
$row['unbilled_amount'] = round(
|
$row['unbilled_amount'] = round((float) ($totalIrdaVal - $totalBilledVal),2 );
|
||||||
(float) (($totalIrdaMap[$ptId] ?? 0) - ($totalBilled[$ptId] ?? 0)),
|
|
||||||
2
|
if($addMinus){
|
||||||
);
|
$row['unbilled_amount'] = ($row['unbilled_amount'] * -1);
|
||||||
|
}
|
||||||
|
|
||||||
$ptSeen[$ptId] = true;
|
$ptSeen[$ptId] = true;
|
||||||
} else {
|
} else {
|
||||||
// Other entries → zero
|
// Other entries → zero
|
||||||
|
|||||||
@ -94,6 +94,7 @@ class TicketMasterModel extends Model
|
|||||||
'tpa_claim_type',
|
'tpa_claim_type',
|
||||||
'tpa_ailments',
|
'tpa_ailments',
|
||||||
'claim_dump_ref_id',
|
'claim_dump_ref_id',
|
||||||
|
'last_updated_by',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,9 @@ class TpaApiDataModel extends Model
|
|||||||
'age',
|
'age',
|
||||||
'is_active',
|
'is_active',
|
||||||
'desc',
|
'desc',
|
||||||
'created_by'
|
'created_by',
|
||||||
|
'si',
|
||||||
|
'doj'
|
||||||
];
|
];
|
||||||
|
|
||||||
// protected $useTimestamps = true;
|
// protected $useTimestamps = true;
|
||||||
|
|||||||
@ -13,7 +13,67 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.dataTables_length label {height: 21px !important;}
|
|
||||||
|
.dataTables_length label {
|
||||||
|
height: 21px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TPA variation modal layout */
|
||||||
|
#tpa_variation_modal .modal-dialog {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .modal-content {
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .modal-body {
|
||||||
|
max-height: calc(85vh - 50px);
|
||||||
|
overflow-y: auto;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal table {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal table thead th,
|
||||||
|
#tpa_variation_modal table tbody td {
|
||||||
|
padding: 2px 6px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs spacing & styling */
|
||||||
|
#tpa_variation_modal .nav-tabs {
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .nav-tabs .nav-item {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .nav-tabs .nav-link {
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tpa_variation_modal .nav-tabs .nav-link.active {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #dee2e6 #dee2e6 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal footer buttons size */
|
||||||
|
#tpa_variation_modal .modal-footer .btn {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="col-12" id="second_page">
|
<div class="col-12" id="second_page">
|
||||||
@ -72,6 +132,7 @@
|
|||||||
<?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd M Y h:i a') ?> by <?php echo get_username($file['created_by']) ?>
|
<?php echo change_date_format($file['created_at'],'Y-m-d H:i:s', 'd M Y h:i a') ?> by <?php echo get_username($file['created_by']) ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php if ($file['status'] == 'failed') { ?>
|
<?php if ($file['status'] == 'failed') { ?>
|
||||||
<?php echo $file['status']; ?>
|
<?php echo $file['status']; ?>
|
||||||
@ -157,6 +218,8 @@
|
|||||||
<a href="javascript: void(0);" class="dropdown-toggle arrow-none btn btn-light btn-sm"
|
<a href="javascript: void(0);" class="dropdown-toggle arrow-none btn btn-light btn-sm"
|
||||||
data-toggle="dropdown" aria-expanded="false"><i class="mdi mdi-dots-horizontal"></i></a>
|
data-toggle="dropdown" aria-expanded="false"><i class="mdi mdi-dots-horizontal"></i></a>
|
||||||
|
|
||||||
|
<?php if($file['actions'] != 'export') { ?>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
|
||||||
<?php if (str_starts_with($file['status'], 'failed')) { ?>
|
<?php if (str_starts_with($file['status'], 'failed')) { ?>
|
||||||
@ -179,14 +242,15 @@
|
|||||||
|
|
||||||
<?php if ($file['actions'] == "fetch" && $file['status'] == 'partially success') { ?>
|
<?php if ($file['actions'] == "fetch" && $file['status'] == 'partially success') { ?>
|
||||||
<a href="<?= base_url('employee/getTPADataVariationReport/') . $file['id'] ?>" class="dropdown-item" style="color: #000;" aria-hidden="true" target="_blank" ><i class="mdi mdi-download mr-2 text-muted font-18 vertical-middle"></i>Download TPA Variation report</a>
|
<a href="<?= base_url('employee/getTPADataVariationReport/') . $file['id'] ?>" class="dropdown-item" style="color: #000;" aria-hidden="true" target="_blank" ><i class="mdi mdi-download mr-2 text-muted font-18 vertical-middle"></i>Download TPA Variation report</a>
|
||||||
|
<a href="#" class="dropdown-item" style="color: #000;" aria-hidden="true" onclick="getTPADataVariationReport(<?= $file['id'] ?>)"><i class="mdi mdi-eye mr-2 text-muted font-18 vertical-middle"></i>View TPA Variation report</a>
|
||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php }
|
<?php }
|
||||||
} ?>
|
} ?>
|
||||||
@ -228,10 +292,252 @@
|
|||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
<!-- TPA Data Variation Modal -->
|
||||||
|
<div class="modal fade" id="tpa_variation_modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">TPA Data Variation Report</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ul class="nav nav-tabs" id="tpaVariationTabs" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" id="not-in-nhance-tab" data-toggle="tab" href="#not_in_nhance_tab" role="tab" aria-controls="not_in_nhance_tab" aria-selected="true">Not in Nhance</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="not-in-tpa-tab" data-toggle="tab" href="#not_in_tpa_tab" role="tab" aria-controls="not_in_tpa_tab" aria-selected="false">Not in TPA</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="need-to-review-tab" data-toggle="tab" href="#need_to_review_tab" role="tab" aria-controls="need_to_review_tab" aria-selected="false">Need to Review</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content pt-3" id="tpaVariationTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="not_in_nhance_tab" role="tabpanel" aria-labelledby="not-in-nhance-tab">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm" id="not_in_nhance_table">
|
||||||
|
<thead></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="not_in_tpa_tab" role="tabpanel" aria-labelledby="not-in-tpa-tab">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm" id="not_in_tpa_table">
|
||||||
|
<thead></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="need_to_review_tab" role="tabpanel" aria-labelledby="need-to-review-tab">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm" id="need_to_review_table">
|
||||||
|
<thead></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" id="tpaProceedButton" class="btn btn-primary btn-sm" onclick="handleTPAProceed()">Proceed</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
let currentTPAVariationFileId = null;
|
||||||
|
|
||||||
|
const tpaVariationColumns = {
|
||||||
|
not_in_nhance: [
|
||||||
|
{ key: 'emp_code', label: 'Employee Code' },
|
||||||
|
{ key: 'name', label: 'Employee Name' },
|
||||||
|
{ key: 'relation', label: 'Relation' },
|
||||||
|
{ key: 'dob', label: 'Date of Birth' },
|
||||||
|
{ key: 'gender', label: 'Gender' },
|
||||||
|
{ key: 'age', label: 'Age' },
|
||||||
|
{ key: 'tpa_id', label: 'TPA Member ID' },
|
||||||
|
],
|
||||||
|
not_in_tpa: [
|
||||||
|
{ key: 'emp_code', label: 'Employee Code' },
|
||||||
|
{ key: 'name', label: 'Employee Name' },
|
||||||
|
{ key: 'relationship', label: 'Relation' },
|
||||||
|
{ key: 'dob', label: 'Date of Birth' },
|
||||||
|
{ key: 'gender', label: 'Gender' },
|
||||||
|
{ key: 'mobile', label: 'Mobile No' },
|
||||||
|
{ key: 'email_corporate', label: 'Corporate Email' },
|
||||||
|
{ key: 'policy_no', label: 'Policy No' },
|
||||||
|
{ key: 'tpa_name', label: 'TPA Name' },
|
||||||
|
{ key: 'change_event', label: 'Change Event' },
|
||||||
|
],
|
||||||
|
need_to_review: [
|
||||||
|
// DB side
|
||||||
|
{ key: 'emp_code', label: 'Employee Code' },
|
||||||
|
{ key: 'name', label: 'Employee Name' },
|
||||||
|
{ key: 'relationship', label: 'Relation' },
|
||||||
|
{ key: 'dob', label: 'Date of Birth' },
|
||||||
|
{ key: 'gender', label: 'Gender' },
|
||||||
|
{ key: 'policy_no', label: 'Policy No' },
|
||||||
|
{ key: 'uhid', label: 'UHID' },
|
||||||
|
{ key: 'change_event', label: 'Change Event' },
|
||||||
|
// TPA side
|
||||||
|
{ key: 'tpa_emp_code', label: 'TPA Employee Code' },
|
||||||
|
{ key: 'tpa_name', label: 'TPA Name' },
|
||||||
|
{ key: 'tpa_relation', label: 'TPA Relation' },
|
||||||
|
{ key: 'tpa_dob', label: 'TPA Date of Birth' },
|
||||||
|
{ key: 'tpa_gender', label: 'TPA Gender' },
|
||||||
|
{ key: 'tpa_tpa_id', label: 'TPA Member ID' },
|
||||||
|
{ key: 'tpa_age', label: 'TPA Age' },
|
||||||
|
// Mismatch info
|
||||||
|
{ key: 'mismatch_fields', label: 'Mismatch Fields' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderVariationTable(tableSelector, rows, columns) {
|
||||||
|
const $table = $(tableSelector);
|
||||||
|
const $thead = $table.find('thead');
|
||||||
|
const $tbody = $table.find('tbody');
|
||||||
|
|
||||||
|
$thead.empty();
|
||||||
|
$tbody.empty();
|
||||||
|
|
||||||
|
const headerRow = $('<tr></tr>');
|
||||||
|
columns.forEach(col => {
|
||||||
|
headerRow.append($('<th></th>').text(col.label));
|
||||||
|
});
|
||||||
|
$thead.append(headerRow);
|
||||||
|
|
||||||
|
if (!rows || !rows.length) {
|
||||||
|
const emptyRow = $('<tr></tr>');
|
||||||
|
columns.forEach((col, index) => {
|
||||||
|
const td = $('<td></td>');
|
||||||
|
if (index === 0) {
|
||||||
|
td.addClass('text-center text-muted').text('No data found');
|
||||||
|
}
|
||||||
|
emptyRow.append(td);
|
||||||
|
});
|
||||||
|
$tbody.append(emptyRow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const tr = $('<tr></tr>');
|
||||||
|
columns.forEach(col => {
|
||||||
|
let value = row[col.key];
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
tr.append($('<td></td>').text(value));
|
||||||
|
});
|
||||||
|
$tbody.append(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateTPAVariationModal(data) {
|
||||||
|
const notInNhance = data.not_in_nhance || [];
|
||||||
|
const notInTpa = data.not_in_tpa || [];
|
||||||
|
const reviewData = (data.mismatch_data || []).map(row => {
|
||||||
|
let mismatchFields = '';
|
||||||
|
const match = row.match || {};
|
||||||
|
|
||||||
|
const notMatching = match.not_matching || [];
|
||||||
|
if (Array.isArray(notMatching) && notMatching.length) {
|
||||||
|
mismatchFields = notMatching.join(', ');
|
||||||
|
} else if (match.status && match.status !== 'matched') {
|
||||||
|
mismatchFields = match.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tpaRecord = match.tpa_record || {};
|
||||||
|
|
||||||
|
return Object.assign({}, row, {
|
||||||
|
mismatch_fields: mismatchFields,
|
||||||
|
tpa_emp_code: tpaRecord.emp_code || '',
|
||||||
|
tpa_name: tpaRecord.name || '',
|
||||||
|
tpa_relation: tpaRecord.relation || '',
|
||||||
|
tpa_dob: tpaRecord.dob || '',
|
||||||
|
tpa_gender: tpaRecord.gender || '',
|
||||||
|
tpa_tpa_id: tpaRecord.tpa_id || '',
|
||||||
|
tpa_age: tpaRecord.age || '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
destroyVariationDataTable('#not_in_nhance_table');
|
||||||
|
destroyVariationDataTable('#not_in_tpa_table');
|
||||||
|
destroyVariationDataTable('#need_to_review_table');
|
||||||
|
|
||||||
|
renderVariationTable('#not_in_nhance_table', notInNhance, tpaVariationColumns.not_in_nhance);
|
||||||
|
renderVariationTable('#not_in_tpa_table', notInTpa, tpaVariationColumns.not_in_tpa);
|
||||||
|
renderVariationTable('#need_to_review_table', reviewData, tpaVariationColumns.need_to_review);
|
||||||
|
|
||||||
|
initVariationDataTable('#not_in_nhance_table');
|
||||||
|
initVariationDataTable('#not_in_tpa_table');
|
||||||
|
initVariationDataTable('#need_to_review_table');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTPAVariationModal() {
|
||||||
|
const modalElement = document.getElementById('tpa_variation_modal');
|
||||||
|
if (!modalElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const modal = new bootstrap.Modal(modalElement);
|
||||||
|
modal.show();
|
||||||
|
// Ensure proceed button reflects the currently active tab when modal opens
|
||||||
|
const $activeTab = $('#tpaVariationTabs .nav-link.active');
|
||||||
|
updateTPAProceedButton($activeTab.attr('id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initVariationDataTable(tableSelector) {
|
||||||
|
const $table = $(tableSelector);
|
||||||
|
|
||||||
|
$table.DataTable({
|
||||||
|
pageLength: 20,
|
||||||
|
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
|
||||||
|
searching: true,
|
||||||
|
paging: true,
|
||||||
|
ordering: false,
|
||||||
|
info: true,
|
||||||
|
autoWidth: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyVariationDataTable(tableSelector) {
|
||||||
|
const $table = $(tableSelector);
|
||||||
|
if ($.fn.DataTable.isDataTable($table)) {
|
||||||
|
$table.DataTable().clear().destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTPAProceedButton(activeId) {
|
||||||
|
const $button = $('#tpaProceedButton');
|
||||||
|
if (!$button.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: visible
|
||||||
|
$button.removeClass('d-none');
|
||||||
|
|
||||||
|
if (activeId === 'not-in-nhance-tab') {
|
||||||
|
$button.text('Proceed - Not in Nhance');
|
||||||
|
} else if (activeId === 'need-to-review-tab') {
|
||||||
|
$button.text('Proceed - Need to Review');
|
||||||
|
} else if (activeId === 'not-in-tpa-tab') {
|
||||||
|
// Hide button for "Not in TPA" section
|
||||||
|
$button.addClass('d-none');
|
||||||
|
} else {
|
||||||
|
$button.text('Proceed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Update proceed button label/visibility on tab change
|
||||||
|
$('#tpaVariationTabs a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||||
|
const activeId = $(e.target).attr('id');
|
||||||
|
updateTPAProceedButton(activeId);
|
||||||
|
});
|
||||||
|
|
||||||
$('#datatable-buttons').DataTable({
|
$('#datatable-buttons').DataTable({
|
||||||
// dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
// dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
||||||
// "<'row'<'col-sm-12'tr>>" +
|
// "<'row'<'col-sm-12'tr>>" +
|
||||||
@ -281,7 +587,6 @@
|
|||||||
paging: true
|
paging: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to format a number in Indian Rupees format
|
// Function to format a number in Indian Rupees format
|
||||||
@ -393,5 +698,109 @@
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTPADataVariationReport(file_id) {
|
||||||
|
|
||||||
|
currentTPAVariationFileId = file_id;
|
||||||
|
|
||||||
|
$('.loader').fadeIn();
|
||||||
|
$('.loader-mask').fadeIn();
|
||||||
|
|
||||||
|
let url = '<?= base_url('employee/getTPADataVariationReportView') ?>/' + file_id;
|
||||||
|
|
||||||
|
sendAjaxRequestForGlobal(url, 'GET', {}, function (response) {
|
||||||
|
|
||||||
|
$('.loader').fadeOut();
|
||||||
|
$('.loader-mask').delay(350).fadeOut('slow');
|
||||||
|
|
||||||
|
if (response && response.status && response.data) {
|
||||||
|
populateTPAVariationModal(response.data);
|
||||||
|
showTPAVariationModal();
|
||||||
|
} else {
|
||||||
|
toastr.warning((response && response.message) || 'No data found for variation report.', 'WARNING');
|
||||||
|
}
|
||||||
|
|
||||||
|
}, function (xhr, status, error) {
|
||||||
|
$('.loader').fadeOut();
|
||||||
|
$('.loader-mask').delay(350).fadeOut('slow');
|
||||||
|
console.error('Error fetching TPA variation data:', error);
|
||||||
|
toastr.error('An error occurred while fetching the variation report.', 'ERROR');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function proceedTPADataVariationNextStep(tabKey) {
|
||||||
|
if (!currentTPAVariationFileId) {
|
||||||
|
toastr.warning('Unable to identify the selected file.', 'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.loader').fadeIn();
|
||||||
|
$('.loader-mask').fadeIn();
|
||||||
|
|
||||||
|
const url = '<?= base_url('employee/proceedTPADataVariationNextStep') ?>/' + currentTPAVariationFileId + '?tab=' + encodeURIComponent(tabKey);
|
||||||
|
|
||||||
|
sendAjaxRequestForGlobal(url, 'GET', {}, function (response) {
|
||||||
|
$('.loader').fadeOut();
|
||||||
|
$('.loader-mask').delay(350).fadeOut('slow');
|
||||||
|
|
||||||
|
if (response && response.status) {
|
||||||
|
toastr.success(response.message || 'Proceed to next step initiated successfully.', 'SUCCESS');
|
||||||
|
} else {
|
||||||
|
toastr.warning(response.message || 'Unable to proceed to next step.', 'WARNING');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
}, function (xhr, status, error) {
|
||||||
|
$('.loader').fadeOut();
|
||||||
|
$('.loader-mask').delay(350).fadeOut('slow');
|
||||||
|
toastr.error('An error occurred while proceeding to the next step.', 'ERROR');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function proceedNotInNhance() {
|
||||||
|
proceedTPADataVariationNextStep('not_in_nhance');
|
||||||
|
}
|
||||||
|
|
||||||
|
function proceedNotInTPA() {
|
||||||
|
proceedTPADataVariationNextStep('not_in_tpa');
|
||||||
|
}
|
||||||
|
|
||||||
|
function proceedNeedToReview() {
|
||||||
|
proceedTPADataVariationNextStep('need_to_review');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTPAProceed() {
|
||||||
|
const $activeTab = $('#tpaVariationTabs .nav-link.active');
|
||||||
|
const activeId = $activeTab.attr('id');
|
||||||
|
|
||||||
|
if (!activeId) {
|
||||||
|
toastr.warning('No active tab selected.', 'WARNING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabLabel = ($activeTab.text() || '').trim() || 'this tab';
|
||||||
|
|
||||||
|
confirmActionSweertAlert(
|
||||||
|
"Are you sure you want to proceed with the data in this tab?",
|
||||||
|
"Yes, Proceed",
|
||||||
|
"Cancel",
|
||||||
|
"warning"
|
||||||
|
).then(function(isConfirmed) {
|
||||||
|
if (!isConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeId === 'not-in-nhance-tab') {
|
||||||
|
proceedNotInNhance();
|
||||||
|
} else if (activeId === 'not-in-tpa-tab') {
|
||||||
|
proceedNotInTPA();
|
||||||
|
} else if (activeId === 'need-to-review-tab') {
|
||||||
|
proceedNeedToReview();
|
||||||
|
} else {
|
||||||
|
toastr.warning('Unknown tab selected.', 'WARNING');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -273,7 +273,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<script>
|
<script>
|
||||||
var pageSubTitle = 'Client Onboarding <span id="client_heading"><?php if (isset($client)) { echo ' - ' . addslashes($client['client_name']); } ?></span>';
|
var pageSubTitle = 'Client Onboarding <span id="client_heading"><?php if (isset($client)) { echo ' - ' . addslashes($client['client_name']); } ?></span>';
|
||||||
var pageBackButton = '<a href="<?= base_url("ticket/list"); ?>" aria-label="Back to list"><i class="mdi mdi-arrow-left" style="font-size: 17px;"></i></a>';
|
var pageBackButton = '<a href="<?= base_url("client/list"); ?>" aria-label="Back to list"><i class="mdi mdi-arrow-left" style="font-size: 17px;"></i></a>';
|
||||||
</script>
|
</script>
|
||||||
<!-- <div class="row" style="padding-bottom: 10px;">
|
<!-- <div class="row" style="padding-bottom: 10px;">
|
||||||
<div class="col-6" style="align-self: center;">
|
<div class="col-6" style="align-self: center;">
|
||||||
|
|||||||
@ -1,3 +1,35 @@
|
|||||||
|
<style>
|
||||||
|
|
||||||
|
.truncate {
|
||||||
|
max-width: 80px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#expense-desc-popup {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 320px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="row" id="expense_module">
|
<div class="row" id="expense_module">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -80,7 +112,18 @@
|
|||||||
|
|
||||||
<div class="form-group col-md-4">
|
<div class="form-group col-md-4">
|
||||||
<label for="amount">Amount <span class="text-danger">*</span></label>
|
<label for="amount">Amount <span class="text-danger">*</span></label>
|
||||||
<input type="number" step="0.01" min="0" class="form-control" id="amount" name="amount" placeholder="Enter amount" value="<?= isset($filters['amount']) ? esc($filters['amount']) : ''; ?>" required>
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="100000000"
|
||||||
|
class="form-control"
|
||||||
|
id="amount"
|
||||||
|
name="amount"
|
||||||
|
placeholder="Enter amount"
|
||||||
|
value="<?= isset($filters['amount']) ? esc($filters['amount']) : ''; ?>"
|
||||||
|
required
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group col-md-4">
|
<div class="form-group col-md-4">
|
||||||
@ -126,7 +169,16 @@
|
|||||||
<?= ! empty($row['short_name']) ? ' (' . esc($row['short_name']) . ')' : ''; ?>
|
<?= ! empty($row['short_name']) ? ' (' . esc($row['short_name']) . ')' : ''; ?>
|
||||||
</td>
|
</td>
|
||||||
<td><?= esc($row['policy_no'] ?? ''); ?></td>
|
<td><?= esc($row['policy_no'] ?? ''); ?></td>
|
||||||
<td><?= esc($row['description'] ?? ''); ?></td>
|
<td class="desc-cell">
|
||||||
|
<i
|
||||||
|
class="mdi mdi-information-outline text-muted expense-desc-icon"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
data-description="<?= esc($row['description'] ?? ''); ?>"
|
||||||
|
></i>
|
||||||
|
<span class="truncate">
|
||||||
|
<?= esc($row['description'] ?? ''); ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td><?= esc($row['approved_by_name'] ?? ''); ?></td>
|
<td><?= esc($row['approved_by_name'] ?? ''); ?></td>
|
||||||
<td><?= number_format((float) ($row['amount'] ?? 0), 2); ?></td>
|
<td><?= number_format((float) ($row['amount'] ?? 0), 2); ?></td>
|
||||||
<td>
|
<td>
|
||||||
@ -213,8 +265,13 @@
|
|||||||
|
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
errors.push('Amount is required.');
|
errors.push('Amount is required.');
|
||||||
} else if (isNaN(amount) || Number(amount) < 0) {
|
} else {
|
||||||
|
const amountNum = Number(amount);
|
||||||
|
if (isNaN(amountNum) || amountNum < 0) {
|
||||||
errors.push('Amount must be a non-negative number.');
|
errors.push('Amount must be a non-negative number.');
|
||||||
|
} else if (amountNum > 100000000) {
|
||||||
|
errors.push('Amount cannot be greater than 100 Cr.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expenseDate) {
|
if (!expenseDate) {
|
||||||
@ -261,8 +318,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (amount) {
|
if (amount) {
|
||||||
if (isNaN(amount) || Number(amount) < 0) {
|
const amountNum = Number(amount);
|
||||||
|
if (isNaN(amountNum) || amountNum < 0) {
|
||||||
errors.push('Amount filter must be a non-negative number.');
|
errors.push('Amount filter must be a non-negative number.');
|
||||||
|
} else if (amountNum > 100000000) {
|
||||||
|
errors.push('Amount filter cannot be greater than 100 Cr.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,10 +490,12 @@
|
|||||||
|
|
||||||
window.expenseDatePicker = flatpickr('#expense_date', {
|
window.expenseDatePicker = flatpickr('#expense_date', {
|
||||||
dateFormat: 'd-m-Y',
|
dateFormat: 'd-m-Y',
|
||||||
allowInput: true
|
allowInput: false,
|
||||||
|
maxDate: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#expense-table').DataTable({
|
$('#expense-table').DataTable({
|
||||||
|
scrollX: true,
|
||||||
dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
||||||
"<'row'<'col-sm-12'tr>>" +
|
"<'row'<'col-sm-12'tr>>" +
|
||||||
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
|
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
|
||||||
@ -450,7 +512,18 @@
|
|||||||
title: 'Expense List',
|
title: 'Expense List',
|
||||||
className: 'app-btn-primary ',
|
className: 'app-btn-primary ',
|
||||||
exportOptions: {
|
exportOptions: {
|
||||||
columns: ':not(:last-child)'
|
columns: ':not(:last-child)',
|
||||||
|
format: {
|
||||||
|
body: function (data, row, column, node) {
|
||||||
|
// Column index 2 is "Policy No" (S.No=0, Client=1, Policy No=2)
|
||||||
|
if (column === 2) {
|
||||||
|
// Prefix with apostrophe to force Excel treat as text and preserve long numbers
|
||||||
|
var text = $(node).text ? $(node).text() : data;
|
||||||
|
return "'" + text;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -461,7 +534,16 @@
|
|||||||
className: 'app-btn-primary ',
|
className: 'app-btn-primary ',
|
||||||
exportOptions: {
|
exportOptions: {
|
||||||
orthogonal: 'sort',
|
orthogonal: 'sort',
|
||||||
columns: ':not(:last-child)'
|
columns: ':not(:last-child)',
|
||||||
|
format: {
|
||||||
|
body: function (data, row, column, node) {
|
||||||
|
if (column === 2) {
|
||||||
|
var text = $(node).text ? $(node).text() : data;
|
||||||
|
return "'" + text;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -581,6 +663,34 @@
|
|||||||
$('#btnResetExpense, #btnCancelExpense').on('click', function () {
|
$('#btnResetExpense, #btnCancelExpense').on('click', function () {
|
||||||
resetExpenseForm();
|
resetExpenseForm();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show small popup near the info icon on hover (no tooltip)
|
||||||
|
let $popup = $('#expense-desc-popup');
|
||||||
|
if (!$popup.length) {
|
||||||
|
$popup = $('<div id="expense-desc-popup"></div>').appendTo('body');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('mouseenter', '.expense-desc-icon', function () {
|
||||||
|
const desc = $(this).data('description') || '';
|
||||||
|
if (!desc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$popup.text(desc);
|
||||||
|
|
||||||
|
const offset = $(this).offset();
|
||||||
|
const iconHeight = $(this).outerHeight() || 16;
|
||||||
|
|
||||||
|
$popup.css({
|
||||||
|
top: offset.top + iconHeight + 4,
|
||||||
|
left: offset.left,
|
||||||
|
display: 'block'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mouseleave', '.expense-desc-icon', function () {
|
||||||
|
$popup.hide();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -60,10 +60,11 @@
|
|||||||
? htmlspecialchars(substr($category, 0, 50)) . "..."
|
? htmlspecialchars(substr($category, 0, 50)) . "..."
|
||||||
: htmlspecialchars($category);
|
: htmlspecialchars($category);
|
||||||
?></td>
|
?></td>
|
||||||
<td><?php $question = $row['question'] ? $row['question'] : 'N/A';
|
<td><?php
|
||||||
echo (strlen($question) > 50)
|
$question_text = $row['question'] ? strip_tags($row['question']) : 'N/A';
|
||||||
? htmlspecialchars(substr($question, 0, 50)) . "..."
|
echo (strlen($question_text) > 50)
|
||||||
: htmlspecialchars($question);
|
? htmlspecialchars(substr($question_text, 0, 50)) . "..."
|
||||||
|
: htmlspecialchars($question_text);
|
||||||
?></td>
|
?></td>
|
||||||
<!-- <td><?php $answer = $row['answer'] ? $row['answer'] : 'N/A';
|
<!-- <td><?php $answer = $row['answer'] ? $row['answer'] : 'N/A';
|
||||||
echo (strlen($answer) > 50)
|
echo (strlen($answer) > 50)
|
||||||
@ -324,7 +325,7 @@
|
|||||||
}
|
}
|
||||||
$('.loader, .loader-mask').fadeOut();
|
$('.loader, .loader-mask').fadeOut();
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function (xhr, status, error) {
|
||||||
$('.loader, .loader-mask').fadeOut();
|
$('.loader, .loader-mask').fadeOut();
|
||||||
if (xhr.status === 400) {
|
if (xhr.status === 400) {
|
||||||
let response = JSON.parse(xhr.responseText);
|
let response = JSON.parse(xhr.responseText);
|
||||||
@ -366,7 +367,8 @@
|
|||||||
$('#FAQForm')[0].reset();
|
$('#FAQForm')[0].reset();
|
||||||
$('#faq_id').val(data.id);
|
$('#faq_id').val(data.id);
|
||||||
$('#category').val(data.category);
|
$('#category').val(data.category);
|
||||||
$('#question').val(data.question);
|
// Safely set the question value by stripping any HTML tags
|
||||||
|
$('#question').val($('<div/>').html(data.question || '').text());
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.value = data.answer || '';
|
editor.value = data.answer || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -447,7 +447,7 @@
|
|||||||
}
|
}
|
||||||
$('.loader, .loader-mask').fadeOut();
|
$('.loader, .loader-mask').fadeOut();
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function (xhr, status, error) {
|
||||||
$('.loader, .loader-mask').fadeOut();
|
$('.loader, .loader-mask').fadeOut();
|
||||||
if (xhr.status === 400) {
|
if (xhr.status === 400) {
|
||||||
let response = JSON.parse(xhr.responseText);
|
let response = JSON.parse(xhr.responseText);
|
||||||
@ -500,8 +500,29 @@
|
|||||||
$('#content_section').val(data.content_section);
|
$('#content_section').val(data.content_section);
|
||||||
}
|
}
|
||||||
$('#heading').val(data.heading);
|
$('#heading').val(data.heading);
|
||||||
// $('#content').val(data.content);
|
|
||||||
// $('#notes').val(data.notes);
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* FIX & EXPLANATION: Safely Loading HTML into the Editor
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* ISSUE:
|
||||||
|
* Loading HTML directly from an AJAX response into a webpage can be a
|
||||||
|
* major security risk (XSS). One might think `data.content` is unsafe.
|
||||||
|
*
|
||||||
|
* RESOLUTION:
|
||||||
|
* This operation is SAFE and CORRECT because of the "defense-in-depth"
|
||||||
|
* strategy we implemented in the `AppContentManagementController`.
|
||||||
|
*
|
||||||
|
* 1. The `data.content` and `data.notes` being received here have already
|
||||||
|
* been validated and sanitized on the server *before* they were ever
|
||||||
|
* saved to the database.
|
||||||
|
* 2. The controller's `sanitizeHtml()` function removed all dangerous tags
|
||||||
|
* (like <script>) and attributes (like `onclick`).
|
||||||
|
*
|
||||||
|
* Therefore, the HTML in `data.content` is trusted. We can safely assign it
|
||||||
|
* directly to the Jodit editor's `.value` property, which will correctly
|
||||||
|
* render the allowed HTML for the user to edit.
|
||||||
|
*/
|
||||||
if (content_editor) {
|
if (content_editor) {
|
||||||
content_editor.value = data.content || '';
|
content_editor.value = data.content || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -293,7 +293,10 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
var pageSubTitle = undefined;
|
||||||
|
var pageBackButton = undefined;
|
||||||
|
</script>
|
||||||
<div class="tab-pane fade active show" id="form">
|
<div class="tab-pane fade active show" id="form">
|
||||||
<input type="hidden" id="entity_type_id">
|
<input type="hidden" id="entity_type_id">
|
||||||
<div class="row" id="inception_form">
|
<div class="row" id="inception_form">
|
||||||
@ -302,16 +305,16 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row" style="margin-bottom:1rem;">
|
<div class="row" style="margin-bottom:1rem;">
|
||||||
|
|
||||||
<div class="col-6" style="align-self: center;">
|
<!-- <div class="col-6" style="align-self: center;">
|
||||||
<h4 id="page_title" style="position: relative;">Add Policy</h4>
|
<h4 id="page_title" style="position: relative;">Add Policy</h4>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="col-3" style="text-align: right; position: relative; left: 211px;">
|
<!-- <div class="col-3" style="text-align: right; position: relative; left: 211px;"> -->
|
||||||
<!-- <button class="btn btn-primary waves-effect waves-light" onclick="fileupload(this)">file upload</button> -->
|
<!-- <button class="btn btn-primary waves-effect waves-light" onclick="fileupload(this)">file upload</button> -->
|
||||||
</div>
|
<!-- </div> -->
|
||||||
<div class="col-1" style="text-align: right; position: relative; left: 193px;">
|
<!-- <div class="col-1" style="text-align: right; position: relative; left: 193px;">
|
||||||
<a href="<?= base_url('policy_tranction/inception/list') ?>" type="button" id="btnAdd" class="btn btn-primary waves-effect waves-light"
|
<a href="<?= base_url('policy_tranction/inception/list') ?>" type="button" id="btnAdd" class="btn btn-primary waves-effect waves-light"
|
||||||
>Back</a>
|
>Back</a>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<form role="form" class="parsley-examples" method="post" id="inception_form_id" enctype="multipart/form-data">
|
<form role="form" class="parsley-examples" method="post" id="inception_form_id" enctype="multipart/form-data">
|
||||||
|
|
||||||
@ -2255,8 +2258,7 @@
|
|||||||
var page_title = 'Edit Policy' + (res.data.client_short_name || res.data.policy_type || res.data.policy_no ?
|
var page_title = 'Edit Policy' + (res.data.client_short_name || res.data.policy_type || res.data.policy_no ?
|
||||||
' - ' + [res.data.client_short_name, res.data.policy_type, res.data.policy_no]
|
' - ' + [res.data.client_short_name, res.data.policy_type, res.data.policy_no]
|
||||||
.filter(Boolean).join('-') : '');
|
.filter(Boolean).join('-') : '');
|
||||||
|
pageSubTitle = page_title;
|
||||||
$('#page_title').text(page_title);
|
|
||||||
$('#client_id_kyc').val(res.data.client_id);
|
$('#client_id_kyc').val(res.data.client_id);
|
||||||
$('#policy_tranction_primarykey_for_file_upload').val(res.data.id);
|
$('#policy_tranction_primarykey_for_file_upload').val(res.data.id);
|
||||||
$('#client_id_for_vehicle_file_upload').val(res.data.client_id);
|
$('#client_id_for_vehicle_file_upload').val(res.data.client_id);
|
||||||
|
|||||||
@ -723,7 +723,9 @@ $(document).ready(function(){
|
|||||||
|
|
||||||
function hide_list_show_add()
|
function hide_list_show_add()
|
||||||
{
|
{
|
||||||
$('#page_title').text('Add Policy')
|
// $('#page_title').text('Add Policy')
|
||||||
|
pageSubTitle = 'Add Policy';
|
||||||
|
pageBackButton = '<a href="<?= base_url("policy_tranction/inception/list"); ?>" aria-label="Back to list"><i class="mdi mdi-arrow-left" style="font-size: 17px;"></i></a>';
|
||||||
$('#inception_form_id')[0].reset();
|
$('#inception_form_id')[0].reset();
|
||||||
$('#client_id').val('').change().prop('disabled', false);
|
$('#client_id').val('').change().prop('disabled', false);
|
||||||
$('#tpa').val('').change().prop('disabled', false);
|
$('#tpa').val('').change().prop('disabled', false);
|
||||||
@ -754,6 +756,8 @@ function hide_list_show_add()
|
|||||||
|
|
||||||
function show_list_hide_add()
|
function show_list_hide_add()
|
||||||
{
|
{
|
||||||
|
pageSubTitle = undefined;
|
||||||
|
pageBackButton = undefined;
|
||||||
$('#pt_onboarding').hide()
|
$('#pt_onboarding').hide()
|
||||||
$('#inception_list').show()
|
$('#inception_list').show()
|
||||||
$('#inception_filter').show();
|
$('#inception_filter').show();
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<td><?php echo $row['display_name']; ?></td>
|
<td><?php echo $row['display_name']; ?></td>
|
||||||
<td><?php echo $row['old_value'].' => '.$row['new_value']; ?></td>
|
<td><?php echo $row['old_value'].' => '.$row['new_value']; ?></td>
|
||||||
<!-- <td><?php //echo $row['new_value']; ?></td> -->
|
<!-- <td><?php //echo $row['new_value']; ?></td> -->
|
||||||
<td><?php echo !empty($row['modified_by'])? $row['modified_by'] : "Created by employee"; ?></td>
|
<td><?php echo !empty($row['modified_by'])? $row['modified_by'] : "Created by employee"; ?> <?= $row['updated_by'] == "0" ? "(API)" : ""; ?></td>
|
||||||
<!-- <td><?php //echo $row['created_at']; ?></td> -->
|
<!-- <td><?php //echo $row['created_at']; ?></td> -->
|
||||||
<td><?php echo date("d-m-Y h:i:s A", strtotime($row['created_at'])) ?? " - "; ?></td>
|
<td><?php echo date("d-m-Y h:i:s A", strtotime($row['created_at'])) ?? " - "; ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
BIN
public/assets/images/sales_tracker_light_sb.png
Normal file
BIN
public/assets/images/sales_tracker_light_sb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Loading…
Reference in New Issue
Block a user