MERGE_UAT_API_RATE_LIMIT_&OT_ISSUES
This commit is contained in:
commit
a3eacc1edf
@ -17,7 +17,10 @@ class Acl
|
||||
'#^/getVerifiedPosUserData#' => ['public' => true],
|
||||
'#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/getEmployeeActiveOrInactivePolicy#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sheet#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sendextraparam#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/test/testingquerys#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/test/viewrfq#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/fedeploy#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/visitOffBoardCheck#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/logs#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
|
||||
@ -20,6 +20,8 @@ use App\Filters\SecurityInputFilter;
|
||||
use App\Filters\GlobalPostFileUploadGuard;
|
||||
use App\Filters\AclFilter;
|
||||
use App\Filters\RateLimitFilter;
|
||||
use App\Filters\JwtApiRateLimitFilter;
|
||||
use App\Filters\AuthApiRateLimitFilter;
|
||||
|
||||
use App\Filters\AuthJWT;
|
||||
|
||||
@ -51,6 +53,8 @@ class Filters extends BaseConfig
|
||||
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
|
||||
'AclFilter' => AclFilter::class,
|
||||
'ratelimit' => RateLimitFilter::class,
|
||||
'AuthApiRateLimitFilter' => AuthApiRateLimitFilter::class,
|
||||
'JwtApiRateLimitFilter' => JwtApiRateLimitFilter::class,
|
||||
|
||||
];
|
||||
|
||||
|
||||
89
app/Config/RateLimiter.php
Normal file
89
app/Config/RateLimiter.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class RateLimiter extends BaseConfig
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| JWT / Authenticated API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $jwtApi = [
|
||||
'limit' => 60, // max requests
|
||||
'window' => 60, // window in seconds
|
||||
'violation_soft' => 3, // violations before soft block
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Auth API Routes (verifyMobile, verifyOTP, etc.)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $authApi = [
|
||||
'limit' => 10, // max requests per window
|
||||
'window' => 180, // window in seconds (3 min)
|
||||
'violation_soft' => 3, // failed attempts before soft block
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User-Level Progressive Block Durations (seconds)
|
||||
| 0 = permanent until manual unblock
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $userBlock = [
|
||||
'soft_duration' => 0, // permanent, manual unblock only
|
||||
'medium_duration' => 7200, // 2 hours
|
||||
'hard_duration' => 86400, // 24 hours
|
||||
// attempts while at a block level before escalating to next
|
||||
'medium_trigger' => 1, // attempts during soft → medium
|
||||
'hard_trigger' => 1, // attempts during medium → hard
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| IP-Level Throttle & Progressive Block (independent of user)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $ipBlock = [
|
||||
'limit' => 120, // max requests per window
|
||||
'window' => 60, // window in seconds
|
||||
'violation_soft' => 5, // violations before soft block
|
||||
'soft_duration' => 0, // permanent, manual unblock only
|
||||
'medium_duration' => 7200, // 2 hours
|
||||
'hard_duration' => 86400, // 24 hours
|
||||
'medium_trigger' => 1, // attempts during soft → medium
|
||||
'hard_trigger' => 1, // attempts during medium → hard
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefixes
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $cacheKeys = [
|
||||
'ip_count' => 'rl_ip_count_',
|
||||
'ip_violations' => 'rl_ip_viol_',
|
||||
'ip_block' => 'rl_ip_block_',
|
||||
'ip_block_hits' => 'rl_ip_blkhit_',
|
||||
'user_count' => 'rl_usr_count_',
|
||||
'user_violations' => 'rl_usr_viol_',
|
||||
'user_block' => 'rl_usr_block_',
|
||||
'user_block_hits' => 'rl_usr_blkhit_',
|
||||
];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Status Codes per block level
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
public array $statusCodes = [
|
||||
'throttle' => 429,
|
||||
'soft' => 429,
|
||||
'medium' => 403,
|
||||
'hard' => 451,
|
||||
];
|
||||
}
|
||||
@ -10,6 +10,15 @@ $routes->options('(:any)', function() {
|
||||
// But having this route ensures OPTIONS isn't rejected as 404
|
||||
});
|
||||
|
||||
$routes->group('sheet', function ($routes) {
|
||||
$routes->get('(:any)', 'GoogleSheetController::editor/$1');
|
||||
$routes->get('(:any)/fetch', 'GoogleSheetController::fetch/$1');
|
||||
$routes->post('(:any)/save', 'GoogleSheetController::save/$1');
|
||||
$routes->get('(:any)/download', 'GoogleSheetController::download/$1');
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @var RouteCollection $routes
|
||||
*/
|
||||
@ -787,8 +796,8 @@ $routes->get('FhplGetBenefDetails','FhplApiController::FhplGetBenefDetails');
|
||||
$routes->get('EcardRequest','HealthIndiaApiController::EcardRequest');
|
||||
$routes->get('HospitalNetwork','MediAssistApiController::HospitalNetwork');
|
||||
$routes->get('VidalGetBenefDetails','VidalApiController::VidalGetBenefDetails');
|
||||
$routes->get('ClaimDetail','HealthIndiaApiController::ClaimDetail');
|
||||
$routes->get('SubmitClaim','HealthIndiaApiController::SubmitClaim');
|
||||
$routes->get('ClaimDetail','VidalApiController::ClaimDetail');
|
||||
$routes->get('SubmitClaim','VidalApiController::SubmitClaim');
|
||||
$routes->get('IntimateClaim','MediAssistApiController::IntimateClaim');
|
||||
$routes->get('IRSubmission','MediAssistApiController::IRSubmission');
|
||||
$routes->get('ClaimStatusUpdate','MediAssistApiController::ClaimStatusUpdate');
|
||||
@ -896,6 +905,8 @@ $routes->group('logs', function($routes) {
|
||||
$routes->group('sales', function($routes) {
|
||||
|
||||
// ==================== LEAD ROUTES ====================
|
||||
|
||||
$routes->get('/', 'SalesController::index');
|
||||
|
||||
// Get all leads with filters
|
||||
$routes->get('leads', 'SalesController::getLeads');
|
||||
@ -974,6 +985,11 @@ $routes->group('sales', function($routes) {
|
||||
|
||||
// Delete note
|
||||
$routes->delete('notes/(:num)', 'SalesController::deleteNote/$1');
|
||||
|
||||
|
||||
//Dashboard
|
||||
$routes->get('branchLevelDashboard', 'SalesController::branchLevelDashboard');
|
||||
$routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -688,51 +688,4 @@ class ApiServiceController extends BaseController
|
||||
|
||||
|
||||
|
||||
// Get Claims
|
||||
public function getClaims($id)
|
||||
{
|
||||
if ($id == 1) {
|
||||
$controller = new VidalApiController();
|
||||
} elseif ($id == 2) {
|
||||
$controller = new ICICILombardController();
|
||||
} elseif ($id == 3) {
|
||||
$controller = new MediAssistApiController();
|
||||
}
|
||||
}
|
||||
|
||||
// Get UHID
|
||||
public function getUhid($id)
|
||||
{
|
||||
if ($id == 1) {
|
||||
$controller = new VidalApiController();
|
||||
} elseif ($id == 2) {
|
||||
$controller = new ICICILombardController();
|
||||
} elseif ($id == 3) {
|
||||
$controller = new MediAssistApiController();
|
||||
}
|
||||
}
|
||||
|
||||
// Push Enrollment
|
||||
public function pushEnrollment($id)
|
||||
{
|
||||
if ($id == 1) {
|
||||
$controller = new VidalApiController();
|
||||
} elseif ($id == 2) {
|
||||
$controller = new ICICILombardController();
|
||||
} elseif ($id == 3) {
|
||||
$controller = new MediAssistApiController();
|
||||
}
|
||||
}
|
||||
|
||||
// Get Hospital Network
|
||||
public function getHospitalNetwork($id)
|
||||
{
|
||||
if ($id == 1) {
|
||||
$controller = new VidalApiController();
|
||||
} elseif ($id == 2) {
|
||||
$controller = new ICICILombardController();
|
||||
} elseif ($id == 3) {
|
||||
$controller = new MediAssistApiController();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -783,19 +783,11 @@ class ClientController extends AdminController
|
||||
]
|
||||
],
|
||||
|
||||
// 'cd_ac_no' => [
|
||||
// 'rules' => 'required|regex_match[/^[a-zA-Z0-9\/_\\-]+$/]',
|
||||
// 'errors' => [
|
||||
// 'required' => 'Account number is required',
|
||||
// 'regex_match' => 'Account number can only contain letters, numbers, slashes (/), underscores (_), and hyphens (-)',
|
||||
// ]
|
||||
// ],
|
||||
|
||||
'cd_ac_no' => [
|
||||
'rules' => 'required|numeric',
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9\/\-_]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'CD Account number is required.',
|
||||
'numeric' => 'CD Account number must contain only numbers.',
|
||||
'required' => 'CD Account number is required.',
|
||||
'regex_match' => 'CD Account number can only contain letters, numbers, hyphens(-), underscores(_), and slashes(/).',
|
||||
]
|
||||
],
|
||||
|
||||
@ -6923,6 +6915,31 @@ class ClientController extends AdminController
|
||||
// $res = $medi_assist->MediAssistGetBenefDetails(['policy_no' => '97000034240400000030', 'file_id' => 389, 'return_type' => 'job', 'client_policy_id' => 6192 ]);
|
||||
// dd($res);
|
||||
|
||||
// // 1. Dummy JSON data create panrom (Temp file)
|
||||
// $tempJsonFile = tempnam(sys_get_temp_dir(), 'test_vidal_');
|
||||
// $dummyData = [
|
||||
// [
|
||||
// 'empNo' => 'EMP001',
|
||||
// 'name' => 'John Doe',
|
||||
// 'dob' => '01/01/1990',
|
||||
// 'relationship' => 'Self',
|
||||
// 'gender' => 'Male',
|
||||
// 'enrollmentId' => 'TPA123',
|
||||
// 'age' => 34
|
||||
// ]
|
||||
// ];
|
||||
|
||||
// file_put_contents($tempJsonFile, json_encode($dummyData));
|
||||
|
||||
// $inputArray = [
|
||||
// 'file_id' => 10,
|
||||
// 'json_file_path' => $tempJsonFile
|
||||
// ];
|
||||
|
||||
// $vidalController = new VidalApiController();
|
||||
// $res = $vidalController->saveVidalAPIData($inputArray);
|
||||
// dd($res);
|
||||
|
||||
$employeeController = new EmployeeController();
|
||||
// $response = $employeeController->getEmployeeEcardFromTmpFolderAndZipToS3(json_decode('{"batch_no":2,"last_emp_policy_id":"13218","folder_name":"bulk_ecards_IOCL-77448855996699885555_2026-02-05_09-32-22","processed_in_this_batch_data_count":7,"pdf_count":0,"hr_id":"1"}', true));
|
||||
// $response = $employeeController->bulkEcardDownloadAsZipFromS3(json_decode('{"client_policy_id":"6066","hr_id":"1"}', true));
|
||||
@ -6991,6 +7008,7 @@ class ClientController extends AdminController
|
||||
// $res = $policyTransactionController->validateInsurerStatement(['file_id' => '281']);
|
||||
// $res = $policyTransactionController->updateInsurerStatement(['file_id' => '62']);
|
||||
// $res = $policyTransactionController->bdsDumpExcelFileFormatValidation(['file_id' => '73']);
|
||||
// $res = $policyTransactionController->insertBulkBdsData(['file_id' => '78']);
|
||||
// dd($res);
|
||||
|
||||
// ----------EMP DATA SERVICE CONTROLLER--------------------------------------------------------------------------------
|
||||
|
||||
@ -336,9 +336,9 @@ class EmployeeController extends AdminController
|
||||
//endof validation process
|
||||
if (isset($result['error_summary']) && count($result['error_summary'])) {
|
||||
if(!empty($post_data)){
|
||||
return ['status' => false, 'message' => 'file rejected with errors', 'file_id' => $file_id];
|
||||
return ['status' => false, 'message' => 'File rejected with errors', 'file_id' => $file_id];
|
||||
}else{
|
||||
return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'file rejected with errors'], 200);
|
||||
return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'File rejected with errors'], 200);
|
||||
}
|
||||
}
|
||||
} else //if file size greater than 1 add the file as job
|
||||
@ -3669,7 +3669,7 @@ class EmployeeController extends AdminController
|
||||
"policyStartDate" => $policyStartDate,
|
||||
"policyEndDate" => $policyEndDate,
|
||||
"plan" => $primary["wellness_plan_id"] ?? null,
|
||||
"source" => $primary['short_name'] ?? null,
|
||||
"source" => 'NHANCE',
|
||||
"employer" => $primary['short_name'] ?? null,
|
||||
"employeeCode" => $empCode,
|
||||
"accountNumber" => "", // Fill from DB if available
|
||||
@ -3690,8 +3690,8 @@ class EmployeeController extends AdminController
|
||||
"name" => $row["name"],
|
||||
"phone" => $row["mobile"],
|
||||
"email" => $row["email_corporate"],
|
||||
"relationshipName" => strtoupper($row["relationship"] ?? ''),
|
||||
"gender" => $row["gender"],
|
||||
"relationshipName" => $this->mapRelationship($row["relationship"],$row["gender"]),
|
||||
"gender" => $row["gender"] == 'M' ? 'Male' : 'Female',
|
||||
"dob" => $row["dob"]
|
||||
];
|
||||
}
|
||||
@ -3700,6 +3700,42 @@ class EmployeeController extends AdminController
|
||||
}
|
||||
|
||||
|
||||
function mapRelationship(string $relationship, ?string $gender = null): string
|
||||
{
|
||||
// Normalize input
|
||||
$key = strtolower(trim($relationship));
|
||||
$key = str_replace(['-', '_'], ' ', $key);
|
||||
$key = preg_replace('/\s+/', ' ', $key);
|
||||
|
||||
// Base mapping
|
||||
$map = [
|
||||
'self' => 'self',
|
||||
'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',
|
||||
];
|
||||
|
||||
// Special handling for spouse
|
||||
if ($key === 'spouse') {
|
||||
if ($gender === 'M') {
|
||||
return 'husband';
|
||||
}
|
||||
if ($gender === 'F') {
|
||||
return 'wife';
|
||||
}
|
||||
|
||||
// fallback if gender missing (better to throw error)
|
||||
throw new \Exception("Gender required to map 'Spouse'");
|
||||
}
|
||||
|
||||
return $map[$key] ?? '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send each family payload to API and attach the response
|
||||
@ -4021,8 +4057,7 @@ class EmployeeController extends AdminController
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function reconcileDbWithTpa(array $db, array $tpaRows): array
|
||||
public function reconcileDbWithTpa(array $db, array $tpaRows): array
|
||||
{
|
||||
// Name normalization
|
||||
$normalizeName = function ($name) {
|
||||
@ -4076,12 +4111,7 @@ class EmployeeController extends AdminController
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function exportVariationReportExcel(
|
||||
array $notInTPA,
|
||||
array $notInNhance,
|
||||
array $reviewNeeded,
|
||||
string $filename = 'employee_review.xlsx')
|
||||
public function exportVariationReportExcel(array $notInTPA, array $notInNhance, array $reviewNeeded, string $filename = 'employee_review.xlsx')
|
||||
{
|
||||
|
||||
function setCell($sheet, int $col, int $row, $value)
|
||||
@ -4281,7 +4311,7 @@ class EmployeeController extends AdminController
|
||||
exit;
|
||||
}
|
||||
|
||||
public function bulkGenerateEcardAndStoreinS3(array $params = [])
|
||||
public function bulkGenerateEcardAndStoreinS3(array $params = [])
|
||||
{
|
||||
$request = \Config\Services::request();
|
||||
$isCli = is_cli();
|
||||
|
||||
@ -1673,10 +1673,16 @@ class EmployeeRestController extends AdminController
|
||||
{
|
||||
try {
|
||||
|
||||
$client = $this->clientModel->where('md5(id)', $this->request->getGet('client_id'))->first();
|
||||
$client_id = $this->request->getGet('client_id');
|
||||
if (is_string($client_id) && preg_match('/^[a-f0-9]{32}$/i', $client_id)) {
|
||||
$client = $this->clientModel->where('MD5(id)', $client_id)->first();
|
||||
}else{
|
||||
$client = $this->clientModel->where('id', $client_id)->first();
|
||||
}
|
||||
|
||||
if (!empty($client)) {
|
||||
$client['client_logo'] = base_url() . 'public/uploads/logo/' . $client['client_logo'];
|
||||
$clientPolicy = $this->clientPolicyModel->where('md5(client_id)', $this->request->getGet('client_id'))
|
||||
$clientPolicy = $this->clientPolicyModel->where('client_id', $client['id'])
|
||||
->where('client_branch_id', $this->request->getGet('client_branch_id'))->findAll();
|
||||
return $this->respond(['status' => 'success', 'code' => 200, 'data' => ['client' => $client, 'client_policy' => $clientPolicy]], 200);
|
||||
} else {
|
||||
@ -3763,7 +3769,11 @@ class EmployeeRestController extends AdminController
|
||||
|
||||
$fetchData['priority'] = 1;
|
||||
$fetchData['mode_of_intimation'] = 3;
|
||||
$fetchData['claim_type'] = 1;
|
||||
|
||||
if(!isset($received_data['claim_type']) || (isset($received_data['claim_type']) && empty($received_data['claim_type']))) {
|
||||
$received_data['claim_type'] = 1;
|
||||
}
|
||||
|
||||
$fetchData = array_merge($fetchData, $received_data);
|
||||
|
||||
// $fetchData['relationship'] = strtolower($fetchData['relationship']) ?? $fetchData['relationship'];
|
||||
@ -3987,8 +3997,12 @@ class EmployeeRestController extends AdminController
|
||||
// ])->setStatusCode(400);
|
||||
|
||||
$ticket_type = $this->ticketController->ticketType;
|
||||
$claim_type = $this->ticketController->claimType;
|
||||
unset($claim_type[1][2]);
|
||||
unset($claim_type[1][4]);
|
||||
|
||||
$ticket_type = array_map(fn($value, $key) => (object) ['id' => $key, 'name' => $value], array_values($ticket_type), array_keys($ticket_type));
|
||||
return $this->response->setJSON(['ticket_type' => $ticket_type])->setStatusCode(200);
|
||||
return $this->response->setJSON(['ticket_type' => $ticket_type, 'claim_type' => $claim_type])->setStatusCode(200);
|
||||
}
|
||||
|
||||
// Get policy type ids
|
||||
@ -4017,6 +4031,10 @@ class EmployeeRestController extends AdminController
|
||||
$this->myLogger->logme('error', "Policy type IDs fetched: " . json_encode($policy_type_ids));
|
||||
|
||||
$ticket_type = $this->ticketController->ticketType;
|
||||
$claim_type = $this->ticketController->claimType;
|
||||
unset($claim_type[1][2]);
|
||||
unset($claim_type[1][4]);
|
||||
|
||||
$filtered = [];
|
||||
|
||||
$mapping = [
|
||||
@ -4050,7 +4068,7 @@ class EmployeeRestController extends AdminController
|
||||
}
|
||||
|
||||
$filtered = array_values($filtered);
|
||||
return $this->response->setJSON(['status' => true, 'code' => 200, 'ticket_type' => $filtered])->setStatusCode(200);
|
||||
return $this->response->setJSON(['status' => true, 'code' => 200, 'ticket_type' => $filtered, 'claim_type' => $claim_type])->setStatusCode(200);
|
||||
} catch (\Throwable $e) {
|
||||
$this->myLogger->logme('error', "Error in get_ticket_type: " . $e->getMessage() . " Trace: " . $e->getTraceAsString());
|
||||
return $this->response->setJSON([
|
||||
|
||||
@ -98,7 +98,7 @@ class FhplApiController extends BaseController
|
||||
|
||||
|
||||
if (count($data) && $data['filePath'] == null) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED FHPL | claimId: '.$claimId.' - Claim or File Missing");
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - Claim or File Missing");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ class FhplApiController extends BaseController
|
||||
$pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename;
|
||||
|
||||
if (!file_exists($pdfPath)) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED FHPL | claimId: '.$claimId.' - PDF not found on server");
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - PDF not found on server");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ class FhplApiController extends BaseController
|
||||
// Generate FHPL Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['access_token'])) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED FHPL | claimId: '.$claimId.' - FHPL Token generation failed");
|
||||
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - FHPL Token generation failed");
|
||||
return;
|
||||
}
|
||||
$token = $tokenResponse['data']['access_token'];
|
||||
@ -152,14 +152,14 @@ class FhplApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH FHPL | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
log_message('error', 'FHPL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH FHPL RESPONSE | ' . json_encode($response));
|
||||
log_message('error', 'FHPL - Claim Push RESPONSE | ' . json_encode($response));
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'TPA CLAIM PUSH FAILED FHPL | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'FHPL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -184,10 +184,10 @@ class FhplApiController extends BaseController
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH SUCCESS FHPL | claimId: '.$claimId.' | claimNO: '.$fhplClaimNo);
|
||||
log_message('error', 'FHPL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$fhplClaimNo);
|
||||
|
||||
}else {
|
||||
log_message('error', 'TPA CLAIM PUSH API SUCCESS BUT claimsInfo EMPTY | response: '.json_encode($response));
|
||||
log_message('error', 'FHPL - Claim Push API SUCCESS BUT claimsInfo EMPTY | response: '.json_encode($response));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -235,21 +235,18 @@ class FhplApiController extends BaseController
|
||||
// dd($response);
|
||||
|
||||
if (empty($response['data'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'FHPL - Claim status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
return ['status' => false,'message' => 'API call failed.','data' => $response ];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
$claimData = $response['data'][0];
|
||||
$tpa_claim_no = $claimData['CLAIM_ID'] ?? '';
|
||||
$currentStatus = $claimData['CLAIM_STATUS'] ?? '';
|
||||
$tpa_claim_type = $claimData['CLAIM_TYPE'] ?? '';
|
||||
$tpa_ailments = $claimData['AILMENT'] ?? '';
|
||||
|
||||
$status = null;
|
||||
|
||||
// find this claim
|
||||
foreach($response['data'] as $row){
|
||||
if($row['CLAIM_ID']==$ticket['claimNo']){
|
||||
$status = $row['CLAIM_STATUS'];
|
||||
}
|
||||
}
|
||||
|
||||
$map = [
|
||||
$validStatuses = [
|
||||
"In-Progress" => 5,
|
||||
"Under Process" => 5,
|
||||
"Query" => 4,
|
||||
@ -259,14 +256,28 @@ class FhplApiController extends BaseController
|
||||
"Required Information" => 4,
|
||||
];
|
||||
|
||||
if( $status != null && isset($map[$status]))
|
||||
{
|
||||
$this->db->table('ticket_master')->where('id',$claimId)->update(['claim_status_id'=>$map[$status],'tpa_claim_status'=>$status]);
|
||||
log_message('error', "CLAIM STATUS SUCCESS | Updated ticket ID $claimId with claim status: $status");
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $status,'api_response' => $response];
|
||||
$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");
|
||||
|
||||
|
||||
|
||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||
|
||||
}
|
||||
|
||||
@ -297,7 +308,7 @@ class FhplApiController extends BaseController
|
||||
// Generate FHPL Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['access_token'])) {
|
||||
log_message('error', 'Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | Message: FHPL Token generation failed');
|
||||
log_message('error', 'FHPL - Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | Message: FHPL Token generation failed');
|
||||
return null;
|
||||
}
|
||||
$token = $tokenResponse['data']['access_token'];
|
||||
@ -317,12 +328,12 @@ class FhplApiController extends BaseController
|
||||
// dd($response);
|
||||
|
||||
if (($response['status'] ?? false) !== true) {
|
||||
log_message('error', 'Ecard Request FAILED | response: ' . json_encode($response));
|
||||
log_message('error', 'FHPL - Ecard Request FAILED | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($response['data'][0])) {
|
||||
log_message('error', 'Ecard Request FAILED | Empty data | response: ' . json_encode($response));
|
||||
log_message('error', 'FHPL - Ecard Request FAILED | Empty data | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -334,8 +345,8 @@ class FhplApiController extends BaseController
|
||||
|
||||
if (!empty($ecardUrl)) {
|
||||
log_message(
|
||||
'info',
|
||||
'Ecard Request PUSH SUCCESS | employeeId: ' . $employeeId .
|
||||
'error',
|
||||
'FHPL - Ecard Request PUSH SUCCESS | employeeId: ' . $employeeId .
|
||||
' | policyNo: ' . $policyNo .
|
||||
' | ecardUrl: ' . $ecardUrl
|
||||
);
|
||||
@ -343,7 +354,7 @@ class FhplApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', 'Ecard Request FAILED | response: ' . json_encode($response));
|
||||
log_message('error', 'FHPL - Ecard Request FAILED | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -360,7 +371,7 @@ class FhplApiController extends BaseController
|
||||
$client_policy_id = $requestData['client_policy_id'] ?? null;
|
||||
|
||||
if (empty($policyNo)) {
|
||||
log_message('error', 'TPA ID PULL | policy_no missing in request');
|
||||
log_message('error', 'FHPL - TPA ID Pull | policy_no missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'policy_no required'];
|
||||
}else{
|
||||
@ -369,7 +380,7 @@ class FhplApiController extends BaseController
|
||||
}
|
||||
|
||||
if (empty($client_policy_id)) {
|
||||
log_message('error', 'TPA ID PULL | client_policy_id missing in request');
|
||||
log_message('error', 'FHPL - TPA ID Pull | client_policy_id missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'client_policy_id required'];
|
||||
}else{
|
||||
@ -377,7 +388,7 @@ class FhplApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', "TPA ID PULL | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
log_message('error', "FHPL - TPA ID Pull | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
|
||||
// Fetch file download dates
|
||||
$batchFiles = $this->db->table('batch_files f')
|
||||
@ -389,7 +400,7 @@ class FhplApiController extends BaseController
|
||||
->getResultArray();
|
||||
|
||||
if (empty($batchFiles)) {
|
||||
log_message('error', 'TPA ID PULL FAILED | batchFiles is empty for this tpa id pull request');
|
||||
log_message('error', 'FHPL - TPA ID Pull FAILED | batchFiles is empty for this tpa id pull request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'batchFiles not found'];
|
||||
}else{
|
||||
@ -400,7 +411,7 @@ class FhplApiController extends BaseController
|
||||
// Generate FHPL Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['access_token'])) {
|
||||
log_message('error', 'TPA ID PULL | FHPL Token generation failed');
|
||||
log_message('error', 'FHPL - TPA ID Pull | FHPL Token generation failed');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'FHPL Token generation failed'];
|
||||
}else{
|
||||
@ -430,12 +441,12 @@ class FhplApiController extends BaseController
|
||||
"Range" => $range
|
||||
];
|
||||
|
||||
log_message('error', "TPA ID PULL | API parems " . json_encode([$url, 'POST', $headers, $body]));
|
||||
log_message('error', "FHPL - TPA ID Pull | API parems " . json_encode([$url, 'POST', $headers, $body]));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
if (($response['status'] ?? false) !== true) {
|
||||
log_message('error', 'FHPL API FAILED | response: ' . json_encode($response));
|
||||
log_message('error', 'FHPL - TPA ID Pull API FAILED | response: ' . json_encode($response));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -467,7 +478,7 @@ class FhplApiController extends BaseController
|
||||
log_message('error', "Failed to update file table status.");
|
||||
}
|
||||
|
||||
log_message('error', 'TPA ID PULL API FAILED | API failed: Empty menber data for this pull request');
|
||||
log_message('error', 'FHPL - TPA ID Pull API FAILED | API failed: Empty menber data for this pull request');
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => 'Empty menber data for this pull request'];
|
||||
@ -524,9 +535,9 @@ class FhplApiController extends BaseController
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "✅ Updated tpa_id={$m['TPA_TPADETAIL_ID']} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
log_message('error', "FHPL - TPA ID Pull Updated tpa_id={$m['TPA_TPADETAIL_ID']} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
} else {
|
||||
log_message('error', "⚠️ No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
log_message('error', "FHPL - TPA ID Pull No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -546,7 +557,7 @@ class FhplApiController extends BaseController
|
||||
|
||||
log_message(
|
||||
'error',
|
||||
"❌ No match for Nhance = " . json_encode($nhanceSideData)
|
||||
"FHPL - TPA ID Pull No match for Nhance = " . json_encode($nhanceSideData)
|
||||
);
|
||||
}
|
||||
|
||||
@ -568,7 +579,7 @@ class FhplApiController extends BaseController
|
||||
|
||||
$totalCount = count($allMembers);
|
||||
|
||||
log_message('error', "TPA ID PULL SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
log_message('error', "FHPL - TPA ID Pull SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return [
|
||||
@ -742,6 +753,7 @@ class FhplApiController extends BaseController
|
||||
$response = call_third_party_api($url,'POST',$headers,$body);
|
||||
|
||||
if(!empty($response['data'])){
|
||||
log_message('error', 'FHPL - Sync TPA Claims | Exception thrown while calling GetTPA_ClaimsDetails API: ' . json_encode($response));
|
||||
$finalResult = array_merge($finalResult,$response['data']);
|
||||
}
|
||||
}
|
||||
@ -778,6 +790,52 @@ class FhplApiController extends BaseController
|
||||
return ['status'=>true,'total'=>count($finalResult)];
|
||||
}
|
||||
|
||||
public function saveFhplAPIData($array)
|
||||
{
|
||||
$file_id = $array['file_id'];
|
||||
$json = file_get_contents($array['json_file_path']);
|
||||
$records = json_decode($json, true);
|
||||
|
||||
// log_message('error','FHPL - saveFhplAPIData' . json_encode($array));//die();
|
||||
$file_model = new BatchFileModel();
|
||||
$file_info = $file_model->where('id', $file_id)->find();
|
||||
|
||||
$tpaApiDataModel = new TpaApiDataModel();
|
||||
|
||||
//deactivate old data
|
||||
$tpaApiDataModel->set('is_active', 0)->where('file_id', $file_id)->update();
|
||||
|
||||
//covert tpa data to our model data
|
||||
$mappedRows = [];
|
||||
|
||||
foreach ($records as $row) {
|
||||
|
||||
$mappedRows[] = [
|
||||
'file_id' => $file_id, // ← pass from controller
|
||||
'emp_code' => trim($row['EMPLOYEE_ID'] ?? ''),
|
||||
|
||||
'name' => trim($row['EMPLOYEE_NAME'] ?? ''),
|
||||
'dob' => !empty($row['DATE_OF_BIRTH'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $row['DATE_OF_BIRTH']))) : null,
|
||||
|
||||
'relation' => trim(strtolower($row['RELATION'] ?? '')),
|
||||
'gender' => format_gender_v2($row['GENDER'] ?? null),
|
||||
'self' => strtolower($row['RELATION'] ?? '') === 'self' ? 1 : 0,
|
||||
|
||||
'tpa_id' => trim($row['TPA_TPADETAIL_ID'] ?? null),
|
||||
'age' => is_numeric($row['AGE'] ?? null) ? (int) $row['AGE'] : null,
|
||||
|
||||
'is_active' => 1,
|
||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// log_message('error','FHPL - COUNT' . count($mappedRows));
|
||||
$result = $tpaApiDataModel->insertBatchWithChunkLog($mappedRows,500,('FILE_ID_'.$file_id));
|
||||
// unlink($file_array['json_file_path']); // delete temp json file
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
66
app/Controllers/GoogleSheetController.php
Normal file
66
app/Controllers/GoogleSheetController.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php namespace App\Controllers;
|
||||
|
||||
use App\Libraries\GoogleSheetLib;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class GoogleSheetController extends Controller
|
||||
{
|
||||
protected GoogleSheetLib $sheetLib;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->sheetLib = new GoogleSheetLib();
|
||||
}
|
||||
|
||||
/* ---------- UI ---------- */
|
||||
public function editor(string $sheetId)
|
||||
{
|
||||
return view('gsheet_editor', [
|
||||
'sheetId' => $sheetId
|
||||
]);
|
||||
}
|
||||
|
||||
/* ---------- FETCH ---------- */
|
||||
public function fetch(string $sheetId)
|
||||
{
|
||||
// $data = $this->sheetLib->read($sheetId);
|
||||
// print_rr($data);die;
|
||||
// return $this->response->setJSON($data);
|
||||
echo 'Hi';
|
||||
}
|
||||
|
||||
/* ---------- SAVE ---------- */
|
||||
public function save(string $sheetId)
|
||||
{
|
||||
$rows = $this->request->getJSON(true);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return $this->response
|
||||
->setStatusCode(400)
|
||||
->setJSON(['error' => 'Invalid data']);
|
||||
}
|
||||
|
||||
$this->sheetLib->write($sheetId, $rows);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 'success'
|
||||
]);
|
||||
}
|
||||
|
||||
/* ---------- DOWNLOAD ---------- */
|
||||
public function download(string $sheetId)
|
||||
{
|
||||
$content = $this->sheetLib->downloadExcel($sheetId);
|
||||
|
||||
return $this->response
|
||||
->setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
->setHeader(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="sheet.xlsx"'
|
||||
)
|
||||
->setBody($content);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Controllers\TicketController;
|
||||
use App\Models\BatchFileModel;
|
||||
use App\Models\EmployeePolicyModel;
|
||||
use App\Models\TpaApiDataModel;
|
||||
@ -18,11 +19,15 @@ class HealthIndiaApiController extends BaseController
|
||||
use ResponseTrait;
|
||||
protected $db;
|
||||
protected $healthIndiaTpaId;
|
||||
protected $claim_type_array;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
$this->healthIndiaTpaId = getenv('HEALTH_INDIA_PRIMARY_KEY_CONSTANT');
|
||||
|
||||
$this->ticketController = new TicketController();
|
||||
$this->claim_type_array = $this->ticketController->claimType;
|
||||
}
|
||||
|
||||
public function generateAuthToken()
|
||||
@ -79,11 +84,11 @@ class HealthIndiaApiController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
public function SubmitClaim($claimId = 729)
|
||||
public function SubmitClaim($claimId = null)
|
||||
{
|
||||
helper('api');
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH HEALTH_INDIA | Started for claimId: ' . $claimId);
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push | Started for claimId: ' . $claimId);
|
||||
|
||||
$data = $this->db->table('ticket_master tm')
|
||||
->select('
|
||||
@ -117,12 +122,12 @@ class HealthIndiaApiController extends BaseController
|
||||
// dd($data);
|
||||
|
||||
if (!$data) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED HEALTH_INDIA | claimId: {$claimId} - Claim not found");
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Claim not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data['filePath'] == null) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED HEALTH_INDIA | claimId: {$claimId} - File Missing");
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - File Missing");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -131,7 +136,7 @@ class HealthIndiaApiController extends BaseController
|
||||
$pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename;
|
||||
|
||||
if (!file_exists($pdfPath)) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED HEALTH_INDIA | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}");
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -141,31 +146,31 @@ class HealthIndiaApiController extends BaseController
|
||||
// Generate Health India Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', "TPA CLAIM PUSH FAILED HEALTH_INDIA | claimId: {$claimId} - Token generation failed");
|
||||
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Token generation failed");
|
||||
return;
|
||||
}
|
||||
$token = $tokenResponse['data']['result'][0]['access_token'];
|
||||
|
||||
// Map claim type: Reimbursement or Cashless
|
||||
$claimTypeMap = [
|
||||
'reimbursement' => 'Reimbursement',
|
||||
'cashless' => 'Cashless',
|
||||
];
|
||||
$mappedClaimType = $claimTypeMap[strtolower($data['claimType'] ?? 'reimbursement')] ?? 'Reimbursement';
|
||||
|
||||
// Map benefit type: IPD or OPD
|
||||
// Map claim_type to benefit type (IPD / OPD)
|
||||
$claimTypeValue = $this->claim_type_array[1][$data['claim_type'] ?? null] ?? null;
|
||||
|
||||
$benefitTypeMap = [
|
||||
'ipd' => 'IPD',
|
||||
'opd' => 'OPD',
|
||||
'Main Hospitalization' => 'IPD',
|
||||
'Pre / Post' => 'IPD',
|
||||
'ReOpen' => 'IPD',
|
||||
'OPD' => 'OPD',
|
||||
];
|
||||
$mappedBenefitType = $benefitTypeMap[strtolower($data['benefitType'] ?? 'ipd')] ?? 'IPD';
|
||||
|
||||
$mappedBenefitType = $benefitTypeMap[$claimTypeValue] ?? 'IPD';
|
||||
|
||||
// Build Health India Claim Submission Request (API Section 5)
|
||||
$body = [
|
||||
"policY_NUMBER" => $data['policyNumber'],
|
||||
"employeE_CODE" => $data['employeeCode'],
|
||||
"membeR_ID" => $data['memberId'],
|
||||
"claiM_TYPE" => $mappedClaimType,
|
||||
"claiM_TYPE" => 'Reimbursement',
|
||||
"benefiT_TYPE" => $mappedBenefitType,
|
||||
"claimeD_AMOUNT" => (string) $data['claimedAmount'],
|
||||
"datE_OF_ADMISSION" => date('Y-m-d', strtotime($data['admissionDate'])),
|
||||
@ -201,16 +206,14 @@ class HealthIndiaApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH HEALTH_INDIA | claimId: ' . $claimId . ' | URL: ' . $url );
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push | claimId: ' . $claimId . ' | URL: ' . $url );
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH HEALTH_INDIA RESPONSE | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
|
||||
if ($response['status'] != true) {
|
||||
log_message('error', 'TPA CLAIM PUSH FAILED HEALTH_INDIA | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push FAILED | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update(['tpa_push_response' => json_encode($response)]);
|
||||
@ -224,35 +227,35 @@ class HealthIndiaApiController extends BaseController
|
||||
$this->db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update([
|
||||
'claim_number' => $ccn,
|
||||
'tpa_claim_id' => $ccn,
|
||||
'tpa_claim_push_reference_no' => $ccn . '(' . $ccnExt . ')',
|
||||
// 'claim_number' => $ccn,
|
||||
// 'tpa_claim_id' => $ccn,
|
||||
'tpa_claim_push_reference_no' => $ccn . '-' . $ccnExt,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH SUCCESS HEALTH_INDIA | claimId: ' . $claimId . ' | CCN: ' . $ccn . ' | CCN_EXT: ' . $ccnExt);
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push SUCCESS | claimId: ' . $claimId . ' | CCN: ' . $ccn . ' | CCN_EXT: ' . $ccnExt);
|
||||
} else {
|
||||
log_message('error', 'TPA CLAIM PUSH HEALTH_INDIA API SUCCESS BUT CCN EMPTY | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Claim Push API SUCCESS BUT CCN EMPTY | claimId: ' . $claimId . ' | response: ' . json_encode($response));
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function ClaimDetail($claimId = 729)
|
||||
public function ClaimDetail($claimId = null)
|
||||
{
|
||||
helper('api');
|
||||
|
||||
log_message('error', 'CLAIM STATUS HEALTH_INDIA | Started for claimId: ' . $claimId);
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status | Started for claimId: ' . $claimId);
|
||||
|
||||
$ticket = $this->db->table('ticket_master tm')
|
||||
->select("tm.id, tm.tpa_claim_id as ccn, cp.policy_no, cp.policy_start_date, cp.policy_end_date, tm.emp_code")
|
||||
->select("tm.id, tm.tpa_claim_push_reference_no, cp.policy_no, cp.policy_start_date, cp.policy_end_date, tm.emp_code")
|
||||
->join('client_policy cp', 'tm.client_policy_id=cp.id')
|
||||
->where('tm.id', $claimId)
|
||||
->get()->getRowArray();
|
||||
|
||||
if (!$ticket) {
|
||||
log_message('error', 'CLAIM STATUS FAILED HEALTH_INDIA | claimId: ' . $claimId . ' - Invalid claim');
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status FAILED | claimId: ' . $claimId . ' - Invalid claim');
|
||||
return ['status' => false, 'message' => 'Invalid claim'];
|
||||
}
|
||||
|
||||
@ -260,7 +263,7 @@ class HealthIndiaApiController extends BaseController
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED HEALTH_INDIA | claimId: ' . $claimId . ' - Token generation failed');
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status FAILED | claimId: ' . $claimId . ' - Token generation failed');
|
||||
return ['status' => false, 'message' => 'Token generation failed'];
|
||||
}
|
||||
|
||||
@ -269,10 +272,14 @@ class HealthIndiaApiController extends BaseController
|
||||
$url = getenv('HEALTH_INDIA_BASE_URL') . "/Claims/GetClaims";
|
||||
|
||||
// Using claim number wise approach (Section 7.3 - option 3)
|
||||
$reference = $ticket['tpa_claim_push_reference_no'];
|
||||
$parts = explode('-', $reference);
|
||||
$ccn = $parts[0] ?? null;
|
||||
$ccnExt = $parts[1] ?? null;
|
||||
$body = [
|
||||
"policY_NUMBER" => $ticket['policy_no'],
|
||||
"CCN" => $ticket['ccn'],
|
||||
"CCN_EXT" => "0"
|
||||
"CCN" => $ccn,
|
||||
"CCN_EXT" => $ccnExt
|
||||
];
|
||||
|
||||
$headers = [
|
||||
@ -280,49 +287,61 @@ class HealthIndiaApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'CLAIM STATUS HEALTH_INDIA | claimId: ' . $claimId . ' | Request: ' . json_encode($body));
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status | claimId: ' . $claimId . ' | Request: ' . json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
|
||||
log_message('error', 'CLAIM STATUS HEALTH_INDIA | claimId: ' . $claimId . ' | Response: ' . json_encode($response));
|
||||
|
||||
if (empty($response['data']['result'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED HEALTH_INDIA | claimId: ' . $claimId . ' | Empty response data');
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status FAILED | claimId: ' . $claimId . ' | Empty response data');
|
||||
return ['status' => false, 'message' => 'API call failed.', 'data' => $response];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
$claimData = $response['data']['result'][0];
|
||||
$status = $claimData['claiM_STATUS'] ?? null;
|
||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||
$currentStatus = $claimData['claiM_STATUS'] ?? '';
|
||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||
$tpa_ailments = $claimData['ailment'] ?? '';
|
||||
|
||||
// Status mapping based on API documentation
|
||||
$map = [
|
||||
$validStatuses = [
|
||||
"In-Progress" => 5,
|
||||
"Under Process" => 5,
|
||||
"Pending for Bill Entry" => 5,
|
||||
"Query" => 4,
|
||||
"Paid" => 11,
|
||||
"Rejected" => 8,
|
||||
"Approved" => 8,
|
||||
"Outstanding" => 5,
|
||||
"Required Information" => 4,
|
||||
"Intimated and File NOT received" => 4,
|
||||
];
|
||||
|
||||
if ($status != null && isset($map[$status])) {
|
||||
$this->db->table('ticket_master')
|
||||
->where('id', $claimId)
|
||||
->update([
|
||||
'claim_status_id' => $map[$status],
|
||||
'tpa_claim_status' => $status
|
||||
]);
|
||||
log_message('error', "CLAIM STATUS SUCCESS HEALTH_INDIA | Updated claimId: {$claimId} with status: {$status}");
|
||||
} else {
|
||||
log_message('error', "CLAIM STATUS HEALTH_INDIA | claimId: {$claimId} | Unknown status: {$status}");
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
'claim_number' => $tpa_claim_no,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$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");
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'message' => 'Claim status updated.',
|
||||
'updated_status' => $status,
|
||||
'updated_status' => $currentStatus,
|
||||
'api_response' => $response
|
||||
];
|
||||
}
|
||||
@ -331,7 +350,7 @@ class HealthIndiaApiController extends BaseController
|
||||
{
|
||||
helper('api');
|
||||
|
||||
log_message('error', 'CLAIM STATUS UPDATE BULK HEALTH_INDIA | Started');
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status UPDATE BULK | Started');
|
||||
|
||||
$tickets = $this->db->table('ticket_master tm')
|
||||
->select("tm.id, tm.tpa_claim_id, cp.policy_no")
|
||||
@ -347,7 +366,7 @@ class HealthIndiaApiController extends BaseController
|
||||
$count++;
|
||||
}
|
||||
|
||||
log_message('error', 'CLAIM STATUS UPDATE BULK HEALTH_INDIA | Completed | Updated: ' . $count);
|
||||
log_message('error', 'HEALTH_INDIA - Claim Status UPDATE BULK | Completed | Updated: ' . $count);
|
||||
|
||||
return $this->response->setJSON(['status' => true, 'updated' => $count]);
|
||||
}
|
||||
@ -356,12 +375,12 @@ class HealthIndiaApiController extends BaseController
|
||||
{
|
||||
helper('api');
|
||||
|
||||
log_message('error', "ECARD REQUEST HEALTH_INDIA | Started | employeeId: {$employeeId} | policyNo: {$policyNo} | memberId: {$memberId}");
|
||||
log_message('error', "HEALTH_INDIA - Ecard Request | Started | employeeId: {$employeeId} | policyNo: {$policyNo} | memberId: {$memberId}");
|
||||
|
||||
// Generate Health India Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', "ECARD REQUEST FAILED HEALTH_INDIA | employeeId: {$employeeId} | policyNo: {$policyNo} | Message: Token generation failed");
|
||||
log_message('error', "HEALTH_INDIA - Ecard Request | employeeId: {$employeeId} | policyNo: {$policyNo} | Message: Token generation failed");
|
||||
return null;
|
||||
}
|
||||
$token = $tokenResponse['data']['result'][0]['access_token'];
|
||||
@ -380,19 +399,19 @@ class HealthIndiaApiController extends BaseController
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
log_message('error', 'ECARD REQUEST HEALTH_INDIA | Request: ' . json_encode($body));
|
||||
log_message('error', 'HEALTH_INDIA - Ecard Request | Request: ' . json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
log_message('error', 'ECARD REQUEST HEALTH_INDIA | Response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Ecard Request | Response: ' . json_encode($response));
|
||||
|
||||
if (($response['status'] ?? false) !== true) {
|
||||
log_message('error', 'ECARD REQUEST FAILED HEALTH_INDIA | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Ecard Request | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($response['data']['result'][0])) {
|
||||
log_message('error', 'ECARD REQUEST FAILED HEALTH_INDIA | Empty data | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Ecard Request | Empty data | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -402,12 +421,12 @@ class HealthIndiaApiController extends BaseController
|
||||
$ecardUrl = $apiData['membeR_ECARD'] ?? '';
|
||||
|
||||
if (!empty($ecardUrl)) {
|
||||
log_message('error', "ECARD REQUEST SUCCESS HEALTH_INDIA | employeeId: {$employeeId} | policyNo: {$policyNo} | memberId: {$memberId} | ecardUrl: {$ecardUrl}");
|
||||
log_message('error', "HEALTH_INDIA - Ecard Request | employeeId: {$employeeId} | policyNo: {$policyNo} | memberId: {$memberId} | ecardUrl: {$ecardUrl}");
|
||||
return $ecardUrl;
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', 'ECARD REQUEST FAILED HEALTH_INDIA | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - Ecard Request | response: ' . json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -424,7 +443,7 @@ class HealthIndiaApiController extends BaseController
|
||||
|
||||
|
||||
if (empty($policyNo)) {
|
||||
log_message('error', 'TPA ID PULL HEALTH_INDIA | policy_no missing in request');
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull | policy_no missing in request');
|
||||
if ($function_calling_type == "job") {
|
||||
return ['status' => false, 'message' => 'policy_no required'];
|
||||
} else {
|
||||
@ -433,7 +452,7 @@ class HealthIndiaApiController extends BaseController
|
||||
}
|
||||
|
||||
if (empty($client_policy_id)) {
|
||||
log_message('error', 'TPA ID PULL HEALTH_INDIA | client_policy_id missing in request');
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull | client_policy_id missing in request');
|
||||
if ($function_calling_type == "job") {
|
||||
return ['status' => false, 'message' => 'client_policy_id required'];
|
||||
} else {
|
||||
@ -441,7 +460,7 @@ class HealthIndiaApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', "TPA ID PULL HEALTH_INDIA | called for policy_no: {$policyNo}, client_policy_id: {$client_policy_id}");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | called for policy_no: {$policyNo}, client_policy_id: {$client_policy_id}");
|
||||
|
||||
// Fetch file download dates
|
||||
$batchFiles = $this->db->table('batch_files f')
|
||||
@ -455,7 +474,7 @@ class HealthIndiaApiController extends BaseController
|
||||
|
||||
|
||||
if (empty($batchFiles)) {
|
||||
log_message('error', 'TPA ID PULL FAILED HEALTH_INDIA | batchFiles is empty for this tpa id pull request');
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull FAILED | batchFiles is empty for this tpa id pull request');
|
||||
if ($function_calling_type == "job") {
|
||||
return ['status' => false, 'message' => 'batchFiles not found'];
|
||||
} else {
|
||||
@ -466,7 +485,7 @@ class HealthIndiaApiController extends BaseController
|
||||
// Generate Health India Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', 'TPA ID PULL HEALTH_INDIA | Token generation failed');
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull | Token generation failed');
|
||||
if ($function_calling_type == "job") {
|
||||
return ['status' => false, 'message' => 'Token generation failed'];
|
||||
} else {
|
||||
@ -487,16 +506,16 @@ class HealthIndiaApiController extends BaseController
|
||||
"policY_NUMBER" => $policyNo
|
||||
];
|
||||
|
||||
log_message('error', "TPA ID PULL HEALTH_INDIA | API params: " . json_encode(['url' => $url, 'method' => 'POST', 'body' => $body]));
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | API params: " . json_encode(['url' => $url, 'method' => 'POST', 'body' => $body]));
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
|
||||
log_message('error', "TPA ID PULL HEALTH_INDIA | API Response Received ");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | API Response Received ");
|
||||
|
||||
if (($response['status'] ?? false) !== true) {
|
||||
log_message('error', 'HEALTH_INDIA API FAILED | response: ' . json_encode($response));
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull API FAILED | response: ' . json_encode($response));
|
||||
|
||||
// update file table status after the tpa id failed to update
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
@ -519,7 +538,7 @@ class HealthIndiaApiController extends BaseController
|
||||
!is_array($response['data']['result']) ||
|
||||
count($response['data']['result']) === 0
|
||||
) {
|
||||
log_message('error', 'TPA ID PULL API FAILED HEALTH_INDIA | Empty member data for this pull request');
|
||||
log_message('error', 'HEALTH_INDIA - TPA ID Pull API FAILED | Empty member data for this pull request');
|
||||
|
||||
// update file table status after the tpa id failed to update
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
@ -544,7 +563,7 @@ class HealthIndiaApiController extends BaseController
|
||||
$filePath = WRITEPATH . 'tmp/' . time() . '_' . $requestData['file_id'] . '.json';
|
||||
file_put_contents($filePath, $json);
|
||||
|
||||
log_message('error', "TPA ID PULL HEALTH_INDIA | Saved JSON to: {$filePath}");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | Saved JSON to: {$filePath}");
|
||||
|
||||
// Call a job for dump JSON data to DB
|
||||
$job_details = new Jobs();
|
||||
@ -556,7 +575,7 @@ class HealthIndiaApiController extends BaseController
|
||||
]
|
||||
]);
|
||||
|
||||
log_message('error', "TPA ID PULL HEALTH_INDIA | Job added for saving API data");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | Job added for saving API data");
|
||||
|
||||
// ================= MATCHING LOGIC =================
|
||||
|
||||
@ -610,9 +629,9 @@ class HealthIndiaApiController extends BaseController
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "✅ HEALTH_INDIA | Updated tpa_id={$m['memberId']} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | Updated tpa_id={$m['memberId']} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
} else {
|
||||
log_message('error', "⚠️ HEALTH_INDIA | No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -628,13 +647,13 @@ class HealthIndiaApiController extends BaseController
|
||||
];
|
||||
$batch_file_success = 'partially success';
|
||||
|
||||
log_message('error', "❌ HEALTH_INDIA | No match for Nhance = " . json_encode($nhanceSideData));
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | No match for Nhance = " . json_encode($nhanceSideData));
|
||||
}
|
||||
}
|
||||
|
||||
// Send e-card
|
||||
if (!empty($employee_policy_ids)) {
|
||||
log_message('error', "HEALTH_INDIA | sendMailForDownloadingECard JOB PUSHED.");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull | sendMailForDownloadingECard JOB PUSHED.");
|
||||
Jobs::addJob([
|
||||
'job_name' => 'sendMailForDownloadingECard',
|
||||
'payload' => [
|
||||
@ -655,7 +674,7 @@ class HealthIndiaApiController extends BaseController
|
||||
|
||||
$totalCount = count($allMembers);
|
||||
|
||||
log_message('error', "TPA ID PULL SUCCESS HEALTH_INDIA | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
log_message('error', "HEALTH_INDIA - TPA ID Pull SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
|
||||
if ($function_calling_type == "job") {
|
||||
return [
|
||||
@ -707,13 +726,13 @@ class HealthIndiaApiController extends BaseController
|
||||
{
|
||||
helper('api');
|
||||
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Started');
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Started');
|
||||
|
||||
// Generate Health India Token
|
||||
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
|
||||
|
||||
if (empty($tokenResponse['data']['result'][0]['access_token'])) {
|
||||
log_message('error', 'SYNC CLAIMS FAILED HEALTH_INDIA | Token generation failed');
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims FAILED | Token generation failed');
|
||||
return $this->response->setJSON(['status' => false, 'message' => 'Token generation failed']);
|
||||
}
|
||||
|
||||
@ -737,17 +756,17 @@ class HealthIndiaApiController extends BaseController
|
||||
"policY_NUMBER" => $policy['policy_no']
|
||||
];
|
||||
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Fetching for policy: ' . $policy['policy_no']);
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Fetching for policy: ' . $policy['policy_no']);
|
||||
|
||||
$response = call_third_party_api($url, 'POST', $headers, $body);
|
||||
|
||||
if (!empty($response['data']['result'])) {
|
||||
$finalResult = array_merge($finalResult, $response['data']['result']);
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Fetched ' . count($response['data']['result']) . ' claims for policy: ' . $policy['policy_no']);
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Fetched ' . count($response['data']['result']) . ' claims for policy: ' . $policy['policy_no']);
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Total claims fetched: ' . count($finalResult));
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Total claims fetched: ' . count($finalResult));
|
||||
|
||||
// Insert / update ticket_master
|
||||
$insertedCount = 0;
|
||||
@ -786,13 +805,13 @@ class HealthIndiaApiController extends BaseController
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
$insertedCount++;
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Inserted claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A'));
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Inserted claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A'));
|
||||
} else {
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Skipped existing claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A'));
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Skipped existing claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A'));
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', 'SYNC CLAIMS HEALTH_INDIA | Completed | Total: ' . count($finalResult) . ' | Inserted: ' . $insertedCount);
|
||||
log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Completed | Total: ' . count($finalResult) . ' | Inserted: ' . $insertedCount);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => true,
|
||||
@ -800,4 +819,48 @@ class HealthIndiaApiController extends BaseController
|
||||
'inserted' => $insertedCount
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveHealthIndiaAPIData($array)
|
||||
{
|
||||
$file_id = $array['file_id'];
|
||||
$json = file_get_contents($array['json_file_path']);
|
||||
$records = json_decode($json, true);
|
||||
// log_message('error','HEALTH_INDIA - saveHealthIndiaAPIData' . json_encode($array));//die();
|
||||
$file_model = new BatchFileModel();
|
||||
$file_info = $file_model->where('id', $file_id)->find();
|
||||
|
||||
$tpaApiDataModel = new TpaApiDataModel();
|
||||
|
||||
//deactivate old data
|
||||
$tpaApiDataModel->set('is_active', 0)->where('file_id', $file_id)->update();
|
||||
|
||||
//covert tpa data to our model data
|
||||
$mappedRows = [];
|
||||
|
||||
foreach ($records as $row) {
|
||||
|
||||
$mappedRows[] = [
|
||||
'file_id' => $file_id, // ← pass from controller
|
||||
'emp_code' => trim($row['employeeCode'] ?? ''),
|
||||
|
||||
'name' => trim($row['insured_Name'] ?? ''),
|
||||
'dob' => !empty($row['dateOfBirth'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $row['dateOfBirth']))) : null,
|
||||
|
||||
'relation' => map_relationship(trim($row['relation'] ?? null)),
|
||||
'gender' => strtoupper($row['gender'] ?? null),
|
||||
'self' => map_relationship(trim($row['relation'] ?? null)) === 'self' ? 1 : 0,
|
||||
|
||||
'tpa_id' => trim($row['memberId'] ?? null),
|
||||
'age' => is_numeric($row['age'] ?? null) ? (int) $row['age'] : null,
|
||||
|
||||
'is_active' => 1,
|
||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||
];
|
||||
}
|
||||
// log_message('error','HEALTH_INDIA - COUNT' . count($mappedRows));
|
||||
// print_rr($mappedRows);//die();
|
||||
$result = $tpaApiDataModel->insertBatchWithChunkLog($mappedRows,500,('FILE_ID_'.$file_id));
|
||||
// unlink($file_array['json_file_path']); // delete temp json file
|
||||
}
|
||||
|
||||
}
|
||||
@ -191,14 +191,26 @@ class JobWorker extends AdminController
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\VidalApiController',
|
||||
],
|
||||
'saveVidalAPIData' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\VidalApiController',
|
||||
],
|
||||
'FhplGetBenefDetails' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\FhplApiController',
|
||||
],
|
||||
'saveFhplAPIData' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\FhplApiController',
|
||||
],
|
||||
'HealthIndiaGetBenefDetails' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\HealthIndiaApiController',
|
||||
],
|
||||
'saveHealthIndiaAPIData' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\HealthIndiaApiController',
|
||||
],
|
||||
'bdsDumpExcelFileFormatValidation' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\PolicyTransactionController',
|
||||
@ -207,22 +219,23 @@ class JobWorker extends AdminController
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\PolicyTransactionController',
|
||||
],
|
||||
'initiateWellnessOnboardJob' => [
|
||||
'initiateWellnessOnboardJob' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\EmployeeController',
|
||||
], 'saveMediAssitAPIData' => [
|
||||
],
|
||||
'saveMediAssitAPIData' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\MediAssistApiController',
|
||||
],
|
||||
'bulkGenerateEcardAndStoreinS3' => [
|
||||
'bulkGenerateEcardAndStoreinS3' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\EmployeeController',
|
||||
],
|
||||
'bulkEcardDownloadAsZipFromS3' => [
|
||||
],
|
||||
'bulkEcardDownloadAsZipFromS3' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\EmployeeController',
|
||||
],
|
||||
'getEmployeeEcardFromTmpFolderAndZipToS3' => [
|
||||
],
|
||||
'getEmployeeEcardFromTmpFolderAndZipToS3' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\EmployeeController',
|
||||
]
|
||||
|
||||
@ -4319,27 +4319,27 @@ class LeadsController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastFiveFinancialYears()
|
||||
public function getLastFiveFinancialYears(): array
|
||||
{
|
||||
$currentYear = date('Y');
|
||||
$currentMonth = date('m');
|
||||
$year = (int) date('Y');
|
||||
$month = (int) date('m');
|
||||
// $month = (int) 5;
|
||||
|
||||
// In India, the financial year starts from April (04)
|
||||
if ($currentMonth < 4) {
|
||||
$currentYear--; // Adjust year if it's Jan-Mar
|
||||
}
|
||||
// Financial year starts in April
|
||||
$currentFYStart = ($month >= 4) ? $year : $year - 1;
|
||||
|
||||
$financialYears = [];
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$startYear = $currentYear - $i - 1;
|
||||
$endYear = $currentYear - $i;
|
||||
$financialYears[] = "$startYear-$endYear";
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$startYear = $currentFYStart - $i;
|
||||
$endYear = $startYear + 1;
|
||||
$financialYears[] = "{$startYear}-{$endYear}";
|
||||
}
|
||||
|
||||
return $financialYears;
|
||||
}
|
||||
|
||||
|
||||
// ------------ RFQ NON EB FUNCTIONS-----------------------------------------------------------------------------------------
|
||||
|
||||
public function rfqNonEB()
|
||||
|
||||
@ -1,259 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class LogController extends BaseController
|
||||
{
|
||||
private $logPath;
|
||||
public $dModel;
|
||||
public $session;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Path to log files
|
||||
$this->logPath = WRITEPATH . 'logs/';
|
||||
$this->session = session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display list of all log files
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
$logFiles = $this->getLogFiles();
|
||||
|
||||
$data = [
|
||||
'title' => 'Log Files',
|
||||
'logFiles' => $logFiles
|
||||
'title' => 'Log Files',
|
||||
'logFiles' => $this->getLogFiles()
|
||||
];
|
||||
|
||||
return $this->loadLayout('logs/index', $data);
|
||||
|
||||
// return view('logs/index', $data);
|
||||
return view('logs/index', $data);
|
||||
}
|
||||
|
||||
public function view($filename = null)
|
||||
{
|
||||
if (!$filename) return redirect()->to('/logs');
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) return redirect()->to('/logs');
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// 1. Fetch TPA List for the first row of tabs
|
||||
$tpaConfigs = $db->table('tpa_log_config')->get()->getResultArray();
|
||||
|
||||
// 2. Dynamically get Action buttons from table columns
|
||||
$allColumns = $db->getFieldNames('tpa_log_config');
|
||||
$dynamicKeys = [];
|
||||
foreach ($allColumns as $column) {
|
||||
if ($column !== 'tpa_name') {
|
||||
// Formatting: 'claim_push_key' -> 'Claim Push'
|
||||
$label = str_replace(['_key', '_'], ['', ' '], $column);
|
||||
$dynamicKeys[$column] = ucwords($label);
|
||||
}
|
||||
}
|
||||
|
||||
$selectedTpa = $this->request->getGet('tpa');
|
||||
$selectedKey = $this->request->getGet('key');
|
||||
$searchTerm = $this->request->getGet('search');
|
||||
|
||||
// Parse logs with current filters
|
||||
$logEntries = $this->parseLogFileOptimized($filePath, $tpaConfigs, $selectedTpa, $selectedKey, $searchTerm);
|
||||
|
||||
// Date Pagination
|
||||
$prevFile = $nextFile = null;
|
||||
if (preg_match('/log-(\d{4}-\d{2}-\d{2})\.log/', $filename, $match)) {
|
||||
$currentDate = $match[1];
|
||||
$prevD = date('Y-m-d', strtotime('-1 day', strtotime($currentDate)));
|
||||
$nextD = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
|
||||
if (file_exists($this->logPath . "log-$prevD.log")) $prevFile = "log-$prevD.log";
|
||||
if (file_exists($this->logPath . "log-$nextD.log")) $nextFile = "log-$nextD.log";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => 'TPA Logs: ' . $filename,
|
||||
'filename' => $filename,
|
||||
'logEntries' => $logEntries,
|
||||
'tpaConfigs' => $tpaConfigs,
|
||||
'dynamicKeys' => $dynamicKeys, // Dynamic Buttons
|
||||
'selectedTpa' => $selectedTpa,
|
||||
'selectedKey' => $selectedKey,
|
||||
'searchTerm' => $searchTerm,
|
||||
'prevFile' => $prevFile,
|
||||
'nextFile' => $nextFile
|
||||
];
|
||||
|
||||
return view('logs/view', $data);
|
||||
}
|
||||
|
||||
private function parseLogFileOptimized($path, $configs, $tpaName, $keyType, $searchTerm)
|
||||
{
|
||||
$entries = [];
|
||||
$handle = fopen($path, 'r');
|
||||
if (!$handle) return [];
|
||||
|
||||
$filters = [];
|
||||
if ($tpaName) {
|
||||
$filters[] = $tpaName;
|
||||
if ($keyType) {
|
||||
foreach ($configs as $conf) {
|
||||
if ($conf['tpa_name'] === $tpaName && isset($conf[$keyType])) {
|
||||
$filters[] = $conf[$keyType];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($searchTerm) $filters[] = $searchTerm;
|
||||
|
||||
$currentEntry = null;
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (preg_match('/^(\w+)\s*-\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s*-->\s*(.*)$/', $line, $matches)) {
|
||||
if ($currentEntry && $this->matchesFilters($currentEntry['message'], $filters)) {
|
||||
$entries[] = $currentEntry;
|
||||
}
|
||||
$currentEntry = ['level' => $matches[1], 'date' => $matches[2], 'message' => $matches[3]];
|
||||
} elseif ($currentEntry !== null && trim($line) !== '') {
|
||||
$currentEntry['message'] .= "\n" . $line;
|
||||
}
|
||||
}
|
||||
if ($currentEntry && $this->matchesFilters($currentEntry['message'], $filters)) $entries[] = $currentEntry;
|
||||
|
||||
fclose($handle);
|
||||
return array_reverse($entries);
|
||||
}
|
||||
|
||||
private function matchesFilters($message, $filters)
|
||||
{
|
||||
if (empty($filters)) return false;
|
||||
foreach ($filters as $f) {
|
||||
if (stripos($message, $f) === false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all log files sorted by date (latest first)
|
||||
*/
|
||||
private function getLogFiles()
|
||||
{
|
||||
$files = [];
|
||||
|
||||
if (!is_dir($this->logPath)) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
if (!is_dir($this->logPath)) return $files;
|
||||
$iterator = new \DirectoryIterator($this->logPath);
|
||||
|
||||
foreach ($iterator as $fileInfo) {
|
||||
if ($fileInfo->isFile() && $fileInfo->getExtension() === 'log') {
|
||||
$files[] = [
|
||||
'name' => $fileInfo->getFilename(),
|
||||
'path' => $fileInfo->getPathname(),
|
||||
'size' => $this->formatBytes($fileInfo->getSize()),
|
||||
'modified' => $fileInfo->getMTime(),
|
||||
'modified_date' => date('Y-m-d H:i:s', $fileInfo->getMTime())
|
||||
'size' => round($fileInfo->getSize() / 1024, 2) . ' KB',
|
||||
'modified_date' => date('Y-m-d H:i:s', $fileInfo->getMTime()),
|
||||
'ts' => $fileInfo->getMTime()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by modified time (latest first)
|
||||
usort($files, function($a, $b) {
|
||||
return $b['modified'] - $a['modified'];
|
||||
});
|
||||
|
||||
usort($files, fn($a, $b) => $b['ts'] - $a['ts']);
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* View specific log file content
|
||||
*/
|
||||
public function view($filename = null)
|
||||
{
|
||||
|
||||
|
||||
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
// Security: prevent directory traversal
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
// ✅ extract date from filename: log-YYYY-MM-DD.log
|
||||
if (preg_match('/log-(\d{4}-\d{2}-\d{2})\.log/', $filename, $match)) {
|
||||
$currentDate = $match[1];
|
||||
|
||||
$prevDate = date('Y-m-d', strtotime('-1 day', strtotime($currentDate)));
|
||||
$nextDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
|
||||
|
||||
$prevFile = "log-$prevDate.log";
|
||||
$nextFile = "log-$nextDate.log";
|
||||
|
||||
$prevExists = file_exists($this->logPath . $prevFile);
|
||||
$nextExists = file_exists($this->logPath . $nextFile);
|
||||
}
|
||||
|
||||
// Read log file content
|
||||
$content = file_get_contents($filePath);
|
||||
$logEntries = $this->parseLogFile($content);
|
||||
|
||||
$data = [
|
||||
'title' => 'View Log: ' . $filename,
|
||||
'filename' => $filename,
|
||||
'logEntries' => $logEntries,
|
||||
'prevFile' => $prevExists ? $prevFile : null,
|
||||
'nextFile' => $nextExists ? $nextFile : null,
|
||||
'fileSize' => $this->formatBytes(filesize($filePath)),
|
||||
'lastModified' => date('Y-m-d H:i:s', filemtime($filePath))
|
||||
];
|
||||
|
||||
// print_r( $data); die;
|
||||
|
||||
return $this->loadLayout('logs/view', $data);
|
||||
// return view('logs/view', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse log file into structured array
|
||||
*/
|
||||
|
||||
private function parseLogFile($content)
|
||||
{
|
||||
$entries = [];
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$currentEntry = null;
|
||||
|
||||
// Messages to filter out
|
||||
$skipPatterns = [
|
||||
'/Session: Class initialized using/',
|
||||
'/Session class already loaded/',
|
||||
];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Match CI4 log format: LEVEL - date --> message
|
||||
if (preg_match('/^(\w+)\s*-\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s*-->\s*(.*)$/', $line, $matches)) {
|
||||
|
||||
// Save previous entry if exists (before checking skip)
|
||||
if ($currentEntry !== null) {
|
||||
$entries[] = $currentEntry;
|
||||
$currentEntry = null;
|
||||
}
|
||||
|
||||
// Check if this message should be skipped
|
||||
$shouldSkip = false;
|
||||
foreach ($skipPatterns as $pattern) {
|
||||
if (preg_match($pattern, $matches[3])) {
|
||||
$shouldSkip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldSkip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start new entry
|
||||
$currentEntry = [
|
||||
'level' => $matches[1],
|
||||
'date' => $matches[2],
|
||||
'message' => $matches[3]
|
||||
];
|
||||
} elseif ($currentEntry !== null && trim($line) !== '') {
|
||||
// Continuation of previous message
|
||||
$currentEntry['message'] .= "\n" . $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Add last entry
|
||||
if ($currentEntry !== null) {
|
||||
$entries[] = $currentEntry;
|
||||
}
|
||||
|
||||
return array_reverse($entries); // Latest first
|
||||
}
|
||||
|
||||
/**
|
||||
* Download log file
|
||||
*/
|
||||
public function download($filename = null)
|
||||
{
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
return $this->response->download($filePath, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete log file
|
||||
*/
|
||||
public function delete($filename = null)
|
||||
{
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
if (unlink($filePath)) {
|
||||
return redirect()->to('/logs')->with('success', 'Log file deleted successfully');
|
||||
} else {
|
||||
return redirect()->to('/logs')->with('error', 'Failed to delete log file');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human readable format
|
||||
*/
|
||||
private function formatBytes($bytes, $precision = 2)
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all log files
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
$logFiles = $this->getLogFiles();
|
||||
$deleted = 0;
|
||||
|
||||
foreach ($logFiles as $file) {
|
||||
if (unlink($file['path'])) {
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('/logs')->with('success', $deleted . ' log file(s) deleted successfully');
|
||||
}
|
||||
}
|
||||
@ -130,14 +130,14 @@ class MediAssistApiController extends BaseController
|
||||
// ]
|
||||
// ];
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
log_message('error','MEDI_ASSIST - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
|
||||
if($response['status'] != true){
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -150,7 +150,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if(!empty($claimRef)){
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
log_message('error','MEDI_ASSIST - Claim Push SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
@ -159,7 +159,7 @@ class MediAssistApiController extends BaseController
|
||||
return;
|
||||
|
||||
} else {
|
||||
log_message('error', 'TPA CLAIM PUSH API SUCCESS BUT claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Push API Failed | claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -193,17 +193,17 @@ class MediAssistApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
$ecardUrl = $response['data']['ecardUrl'] ?? null;
|
||||
|
||||
if(!empty($ecardUrl)){
|
||||
log_message('error', 'Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
log_message('error','MEDI_ASSIST - Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
return $ecardUrl;
|
||||
} else {
|
||||
log_message('error', 'Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -242,7 +242,7 @@ class MediAssistApiController extends BaseController
|
||||
$client_policy_id = $requestData['client_policy_id'] ?? null;
|
||||
|
||||
if (empty($policyNo)) {
|
||||
log_message('error', 'TPA ID PULL | policy_no missing in request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull | policy_no missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'policy_no required'];
|
||||
}else{
|
||||
@ -251,7 +251,7 @@ class MediAssistApiController extends BaseController
|
||||
}
|
||||
|
||||
if (empty($client_policy_id)) {
|
||||
log_message('error', 'TPA ID PULL | client_policy_id missing in request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull | client_policy_id missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'client_policy_id required'];
|
||||
}else{
|
||||
@ -259,7 +259,7 @@ class MediAssistApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', "TPA ID PULL | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
|
||||
$employeePolicyModel = new EmployeePolicyModel();
|
||||
$employeePolicyData = $employeePolicyModel
|
||||
@ -278,7 +278,7 @@ class MediAssistApiController extends BaseController
|
||||
->findAll();
|
||||
|
||||
if (empty($employeePolicyData)) {
|
||||
log_message('error', 'TPA ID PULL FAILED | employeePolicyData is empty (tpa_id IS NULL from nhance) for this tpa id pull request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull FAILED | employeePolicyData is empty (tpa_id IS NULL from nhance) for this TPA ID Pull request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'employeePolicyData not found'];
|
||||
}else{
|
||||
@ -301,8 +301,8 @@ class MediAssistApiController extends BaseController
|
||||
"employeeId" => ""
|
||||
];
|
||||
|
||||
log_message('error', "TPA ID PULL | API Request (startIndex={$startIndex}): " . json_encode($body));
|
||||
log_message('error', "TPA ID PULL | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | API Request (startIndex={$startIndex}): " . json_encode($body));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
@ -312,12 +312,12 @@ class MediAssistApiController extends BaseController
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
$file_model = new BatchFileModel();
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
log_message('error', 'TPA ID PULL API FAILED | API failed: ' . json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull API FAILED | API failed: ' . json_encode($response));
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => $response];
|
||||
@ -329,7 +329,7 @@ class MediAssistApiController extends BaseController
|
||||
$data = $response['data'] ?? [];
|
||||
|
||||
if (!isset($data['benefDetails'])) {
|
||||
log_message('error', "TPA ID PULL FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ class MediAssistApiController extends BaseController
|
||||
$totalCount = $count;
|
||||
$fetchedCount = count($data['benefDetails']);
|
||||
|
||||
log_message('error', "Fetched {$fetchedCount} records (startIndex={$startIndex}) of total {$count}");
|
||||
log_message('error',"MEDI_ASSIST - Fetched {$fetchedCount} records (startIndex={$startIndex}) of total {$count}");
|
||||
|
||||
$allBenef = array_merge($allBenef, $data['benefDetails']);
|
||||
|
||||
@ -376,7 +376,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
$hasMatchForThisPolicy = true;
|
||||
|
||||
// log_message('error', "✅ Match found: emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
// log_message('error',"MEDI_ASSIST - ✅ Match found: emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
|
||||
$sql = "UPDATE employee_polices
|
||||
SET tpa_id = ?
|
||||
@ -390,9 +390,9 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "✅ Updated tpa_id={$row['benefMediAssistID']} for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
log_message('error',"MEDI_ASSIST - ✅ Updated tpa_id={$row['benefMediAssistID']} for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
} else {
|
||||
log_message('error', "⚠️ No update (already set or not matched) for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
log_message('error',"MEDI_ASSIST - ⚠️ No update (already set or not matched) for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
}
|
||||
|
||||
}
|
||||
@ -423,7 +423,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
// send e-card
|
||||
if(!empty($employee_policy_ids)){
|
||||
log_message('error', "sendMailForDownloadingECard JOB PUSHED.");
|
||||
log_message('error',"MEDI_ASSIST - sendMailForDownloadingECard JOB PUSHED.");
|
||||
Jobs::addJob(['job_name' => 'sendMailForDownloadingECard', 'payload' => ['ids' => $employee_policy_ids, 'client_policy_id' => $client_policy_id]]);
|
||||
}
|
||||
|
||||
@ -432,13 +432,13 @@ class MediAssistApiController extends BaseController
|
||||
$file_model = new BatchFileModel();
|
||||
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', $batch_file_success)->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
|
||||
log_message('error', "TPA ID PULL SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return [
|
||||
@ -462,9 +462,9 @@ class MediAssistApiController extends BaseController
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
$file_model = new BatchFileModel();
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
$errorData = [
|
||||
@ -478,7 +478,7 @@ class MediAssistApiController extends BaseController
|
||||
'class' => $th->getTrace()[0]['class'] ?? null,
|
||||
];
|
||||
|
||||
log_message('error', 'Exception thrown while calling GetBenefDetails API: ' . json_encode($errorData));
|
||||
log_message('error','MEDI_ASSIST - Exception thrown while calling GetBenefDetails API: ' . json_encode($errorData));
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => $errorData];
|
||||
}else{
|
||||
@ -506,6 +506,8 @@ class MediAssistApiController extends BaseController
|
||||
tm.id,
|
||||
tm.tpa_no as memberId,
|
||||
tm.tpa_claim_push_reference_no as claimRefNo,
|
||||
tm.tpa_claim_id,
|
||||
tm.claim_number,
|
||||
cp.policy_no as policyNo,
|
||||
cp.policy_start_date as startDate,
|
||||
cp.policy_end_date as endDate,
|
||||
@ -521,6 +523,7 @@ class MediAssistApiController extends BaseController
|
||||
return ['status' => false,'message' => 'Invalid Claim ID' ];
|
||||
}
|
||||
|
||||
|
||||
// REQUEST BODY
|
||||
if($ticket['claimRefNo'] != null)
|
||||
{
|
||||
@ -564,15 +567,17 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||
log_message('error', '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));
|
||||
|
||||
return ['status' => false,'message' => 'API call failed.','data' => $response ];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
// Extract Claim Status
|
||||
$claimData = $response['data']['claimsData'][0];
|
||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||
$tpa_ailments = ($claimData['ailment'] ?? '') . ' - ' . ($claimData['ailmenT_DESC'] ?? '');
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
@ -613,27 +618,40 @@ class MediAssistApiController extends BaseController
|
||||
"DENIAL REVIEW AWAITED" => 66,
|
||||
];
|
||||
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$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')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no, 'claim_number' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
if (isset($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)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
|
||||
// UPDATE ticket_master
|
||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "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");
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
public function IRSubmission($claimId = null) // 585 this id for test
|
||||
{
|
||||
log_message('error', "IRSubmission INIT for ticket_id={$claimId}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission | INIT for ticket_id={$claimId}");
|
||||
|
||||
// 1. FETCH TICKET DETAILS
|
||||
$ticket = $this->db->table('ticket_master tm')
|
||||
@ -654,7 +672,7 @@ class MediAssistApiController extends BaseController
|
||||
->getRowArray();
|
||||
|
||||
if (!$ticket || empty($ticket['ClaimID'])) {
|
||||
log_message('error', "IRSubmission FAILED → ClaimID NOT FOUND for ticket_id={$claimId}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission FAILED → ClaimID NOT FOUND for ticket_id={$claimId}");
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
@ -683,17 +701,17 @@ class MediAssistApiController extends BaseController
|
||||
$downloadUrl = base_url('fileDownload?file_path=') . $fileDir;
|
||||
} else {
|
||||
$downloadUrl = "";
|
||||
log_message('error', "File NOT FOUND on server → {$fileDir}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission File NOT FOUND on server → {$fileDir}");
|
||||
}
|
||||
|
||||
log_message('error', "IRSubmission Attachment Ready: {$filename} | URL={$downloadUrl}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Attachment Ready: {$filename} | URL={$downloadUrl}");
|
||||
|
||||
$Attachments[] = [
|
||||
"AttachmentName" => $filename,
|
||||
"AttachmentPath" => $downloadUrl
|
||||
];
|
||||
} else {
|
||||
log_message('error', "IRSubmission Missing File URL → file_id={$file['id']}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Missing File URL → file_id={$file['id']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -704,7 +722,7 @@ class MediAssistApiController extends BaseController
|
||||
"Attachments" => $Attachments
|
||||
];
|
||||
|
||||
log_message('error', "IRSubmission Request Body => " . json_encode($body));
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Request Body => " . json_encode($body));
|
||||
|
||||
// 4. SEND API CALL
|
||||
helper('api');
|
||||
@ -720,13 +738,13 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
log_message('error', "IRSubmission API Response => " . json_encode($response));
|
||||
log_message('error',"MEDI_ASSIST - IR Submission API Response => " . json_encode($response));
|
||||
|
||||
// 5. HANDLE RESPONSE
|
||||
if (!$response['status']) {
|
||||
log_message(
|
||||
'error',
|
||||
"IRSubmission FAILED for ClaimID={$ticket['ClaimID']} → Response=" . json_encode($response)
|
||||
"MEDI_ASSIST - IR Submission FAILED for ClaimID={$ticket['ClaimID']} → Response=" . json_encode($response)
|
||||
);
|
||||
|
||||
return [
|
||||
@ -736,7 +754,7 @@ class MediAssistApiController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
log_message('error', "IRSubmission SUCCESS → ClaimID={$ticket['ClaimID']}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission SUCCESS → ClaimID={$ticket['ClaimID']}");
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
@ -750,7 +768,7 @@ class MediAssistApiController extends BaseController
|
||||
$file_id = $array['file_id'];
|
||||
$json = file_get_contents($array['json_file_path']);
|
||||
$records = json_decode($json, true);
|
||||
// log_message('error','saveMediAssitAPIData' . json_encode($array));//die();
|
||||
// log_message('error','MEDI_ASSIST - saveMediAssitAPIData' . json_encode($array));//die();
|
||||
$file_model = new BatchFileModel();
|
||||
$file_info = $file_model->where('id', $file_id)->find();
|
||||
// dd($file_info);
|
||||
@ -787,7 +805,7 @@ class MediAssistApiController extends BaseController
|
||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||
];
|
||||
}
|
||||
// log_message('error','COUNT' . count($mappedRows));
|
||||
// log_message('error','MEDI_ASSIST - COUNT' . count($mappedRows));
|
||||
// print_rr($mappedRows);//die();
|
||||
$result = $tpaApiDataModel->insertBatchWithChunkLog($mappedRows,500,('FILE_ID_'.$file_id));
|
||||
// unlink($file_array['json_file_path']); // delete temp json file
|
||||
@ -829,7 +847,7 @@ class MediAssistApiController extends BaseController
|
||||
// dd($TicketData);
|
||||
|
||||
if (!$TicketData) {
|
||||
log_message('error', "Claims not found to update status");
|
||||
log_message('error',"MEDI_ASSIST - Claims not found to update Claim Status");
|
||||
return $this->response->setJSON(['status' => false,'message' => 'Claims not found' ]);
|
||||
}
|
||||
|
||||
@ -871,14 +889,16 @@ class MediAssistApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||
log_message('error', '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][['status' => false,'message' => 'API call failed.','data' => $response]];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
// Extract Claim Status
|
||||
$claimData = $response['data']['claimsData'][0];
|
||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||
$tpa_ailments = ($claimData['ailment'] ?? '') . ' - ' . ($claimData['ailmenT_DESC'] ?? '');
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
@ -920,12 +940,20 @@ class MediAssistApiController extends BaseController
|
||||
"DENIAL REVIEW AWAITED" => 66,
|
||||
];
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$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')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'claim_number' => $tpa_claim_no, 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
'claim_number' => $tpa_claim_no,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
// UPDATE ticket_master
|
||||
@ -933,13 +961,13 @@ class MediAssistApiController extends BaseController
|
||||
$status_updated_count ++;
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "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");
|
||||
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => true,
|
||||
'message' => 'Claim status updated.',
|
||||
'message' => 'Claim Status updated.',
|
||||
'updated_status' => $currentStatus,
|
||||
'api_response' => $response,
|
||||
'count' => $status_updated_count,
|
||||
@ -1002,7 +1030,7 @@ class MediAssistApiController extends BaseController
|
||||
->getResultArray();
|
||||
|
||||
if (empty($policies)) {
|
||||
log_message('error', 'No policies found for claim sync');
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | No policies found for claim sync');
|
||||
return $this->response->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Policies not found'
|
||||
@ -1067,7 +1095,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if (empty($response['status']) || empty($response['data']['claimsData'])) {
|
||||
|
||||
log_message('error','CLAIM STATUS FAILED | ' .'policyNo: ' . $policy['policyNo'] .' | ' . $chunkStart->format('Y-m-d') .' to ' . $chunkEnd->format('Y-m-d') .' | response: ' . json_encode($response) );
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | ' .'policyNo: ' . $policy['policyNo'] .' | ' . $chunkStart->format('Y-m-d') .' to ' . $chunkEnd->format('Y-m-d') .' | response: ' . json_encode($response) );
|
||||
|
||||
$errorData[] = [
|
||||
'policy_no' => $policy['policyNo'],
|
||||
@ -1153,7 +1181,7 @@ class MediAssistApiController extends BaseController
|
||||
$claimStatusId = $validStatuses[$currentStatus] ?? null;
|
||||
|
||||
if (!$claimStatusId) {
|
||||
log_message('error', 'Unknown claim status: '.$currentStatus);
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | Unknown Claim Status: '.$currentStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1243,13 +1271,18 @@ class MediAssistApiController extends BaseController
|
||||
// Payment
|
||||
'utr_details' => $value['banK_CHEQUE_NO'] ?? null,
|
||||
'settle_letter' => $value['settlement_LetterLink'] ?? null,
|
||||
|
||||
//others
|
||||
'tpa_claim_type' => $value['typE_OF_CLAIM'],
|
||||
'tpa_ailments' => ($value['ailment'] ?? '') . ' - ' . ($value['ailmenT_DESC'] ?? ''),
|
||||
|
||||
];
|
||||
|
||||
$this->db->table('ticket_master')->insert($claimData);
|
||||
|
||||
log_message(
|
||||
'error',
|
||||
'New claim created | Policy: '.$claimData['policy_no'].' | Claim: '.$claimData['claim_number']
|
||||
'MEDI_ASSIST - Sync TPA Claims | New claim created | Policy: '.$claimData['policy_no'].' | Claim: '.$claimData['claim_number']
|
||||
);
|
||||
|
||||
}
|
||||
@ -1259,7 +1292,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => true,
|
||||
'message' => 'TPA claim status sync completed',
|
||||
'message' => 'TPA Claim Status sync completed',
|
||||
'total_records' => count($finalResult),
|
||||
'result' => $finalResult,
|
||||
'errors' => $errorData
|
||||
|
||||
@ -5711,6 +5711,7 @@ class PolicyTransactionController extends BaseController
|
||||
// 'agent_code' => $current_agent_data['agent_code'] ?? null,
|
||||
'pos_id' => $current_agent_data['id'] ?? null,
|
||||
'file_id' => $params['file_id'],
|
||||
'created_by' => $file['created_by'] ?? null,
|
||||
'endorsement_no' => null,
|
||||
'client_branch_id' => null,
|
||||
'client_policy_id' => null,
|
||||
@ -5785,7 +5786,8 @@ class PolicyTransactionController extends BaseController
|
||||
|
||||
'pt_policy_issue_date' => change_date_format($policy_issue_date, 'd/M/Y', 'Y-m-d'),
|
||||
|
||||
'file_id' => $params['file_id']
|
||||
'file_id' => $params['file_id'],
|
||||
'created_by' => $file['created_by'] ?? null,
|
||||
];
|
||||
|
||||
if(!empty($vehicle_id) && !empty($client_id)){
|
||||
@ -6027,10 +6029,12 @@ class PolicyTransactionController extends BaseController
|
||||
|
||||
if (empty($vehicle_id) && empty($client_id)) {
|
||||
|
||||
foreach ($client_data as $c) {
|
||||
if (strcasecmp(trim($c['email']), $client_email) === 0) {
|
||||
$second_stage_client_id = $c['id'];
|
||||
break;
|
||||
if(strtolower(trim($vehicle_number)) != 'new'){
|
||||
foreach ($client_data as $c) {
|
||||
if (strcasecmp(trim($c['email']), $client_email) === 0) {
|
||||
$second_stage_client_id = $c['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6087,4 +6091,15 @@ class PolicyTransactionController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
public function truncateBdsBulkUploadData()
|
||||
{
|
||||
$file_id = $this->request->getGet('file_id') ?? null;
|
||||
if (empty($file_id)) {
|
||||
return array('status' => false, 'message' => 'file_id is required');
|
||||
}
|
||||
|
||||
$this->policyTransactionModel->select()->findAll();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\SalesActualLeadModel;
|
||||
@ -27,6 +27,73 @@ class SalesController extends BaseController
|
||||
$this->noteModel = new SalesLeadNoteModel();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
$db = \Config\Database::connect();
|
||||
// Fetch users for the assignment dropdowns
|
||||
$data['users'] = $db->table('user_profiles')
|
||||
->select('id, first_name, last_name')
|
||||
->where('is_active', 1)
|
||||
->get()->getResultArray();
|
||||
|
||||
$this->loadLayout('sales/tracker_view', $data);
|
||||
}
|
||||
|
||||
public function completeActivity($id) {
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
// 1. Mark current activity as completed
|
||||
$this->activityModel->completeActivity((int)$id, [
|
||||
'completion_notes' => $data['completion_notes'],
|
||||
'updated_by' => $data['updated_by']
|
||||
]);
|
||||
|
||||
// 2. Handle follow-up if requested
|
||||
if (!empty($data['schedule_followup']) && $data['schedule_followup'] === 'yes') {
|
||||
$activity = $this->activityModel->find($id);
|
||||
$this->activityModel->insert([
|
||||
'lead_id' => $activity['lead_id'],
|
||||
'activity_type' => $data['followup_type'],
|
||||
'notes' => $data['followup_notes'],
|
||||
'scheduled_date' => $data['followup_schedule'],
|
||||
'assigned_to' => $activity['assigned_to'],
|
||||
'status' => 'pending',
|
||||
'created_by' => $this->getUserId()
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->respond(['status' => 'success', 'message' => 'Activity updated']);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Corrected createLead to handle assigned_to as ID
|
||||
*/
|
||||
public function createLead()
|
||||
{
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['created_by'] = $this->getUserId();
|
||||
|
||||
// Ensure assigned_to is a valid integer from user_profiles
|
||||
if (empty($data['assigned_to'])) {
|
||||
return $this->fail('Please assign this lead to a user.');
|
||||
}
|
||||
|
||||
if (!$this->leadModel->insert($data)) {
|
||||
return $this->fail($this->leadModel->errors());
|
||||
}
|
||||
|
||||
return $this->respondCreated(['status' => 'success', 'id' => $this->leadModel->getInsertID()]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LEAD APIs ====================
|
||||
|
||||
/**
|
||||
@ -81,46 +148,6 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new lead
|
||||
* POST /api/sales/leads
|
||||
*/
|
||||
public function createLead()
|
||||
{
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
|
||||
// Set created_by and updated_by from authenticated user
|
||||
$data['created_by'] = $this->getUserId();
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
if (!$this->leadModel->insert($data)) {
|
||||
return $this->fail($this->leadModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$leadId = $this->leadModel->getInsertID();
|
||||
|
||||
// Insert contact persons if provided
|
||||
if (!empty($data['contact_persons'])) {
|
||||
foreach ($data['contact_persons'] as $contact) {
|
||||
$contact['lead_id'] = $leadId;
|
||||
$contact['created_by'] = $this->getUserId();
|
||||
$this->contactModel->insert($contact);
|
||||
}
|
||||
}
|
||||
|
||||
$lead = $this->leadModel->getLeadComplete($leadId);
|
||||
|
||||
return $this->respondCreated([
|
||||
'status' => 'success',
|
||||
'message' => 'Lead created successfully',
|
||||
'data' => $lead
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update lead
|
||||
* PUT /api/sales/leads/{id}
|
||||
@ -467,55 +494,6 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete activity
|
||||
* POST /api/sales/activities/{id}/complete
|
||||
*/
|
||||
public function completeActivity($id)
|
||||
{
|
||||
try {
|
||||
$activity = $this->activityModel->find((int)$id);
|
||||
|
||||
if (!$activity) {
|
||||
return $this->failNotFound('Activity not found');
|
||||
}
|
||||
|
||||
if ($activity['status'] === 'completed') {
|
||||
return $this->fail('Activity is already completed', ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
$this->activityModel->completeActivity((int)$id, $data);
|
||||
|
||||
// Create follow-up activity if requested
|
||||
if (!empty($data['create_followup']) && $data['create_followup'] === true) {
|
||||
$followupData = [
|
||||
'lead_id' => $activity['lead_id'],
|
||||
'activity_type' => $data['followup_type'] ?? 'Call',
|
||||
'notes' => $data['followup_notes'] ?? '',
|
||||
'scheduled_date' => $data['followup_date'] ?? null,
|
||||
'assigned_to' => $activity['assigned_to'],
|
||||
'parent_activity_id' => $id,
|
||||
'created_by' => $this->getUserId(),
|
||||
'updated_by' => $this->getUserId(),
|
||||
];
|
||||
|
||||
$this->activityModel->insert($followupData);
|
||||
}
|
||||
|
||||
$updatedActivity = $this->activityModel->find((int)$id);
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => 'Activity completed successfully',
|
||||
'data' => $updatedActivity
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete activity
|
||||
@ -692,4 +670,123 @@ class SalesController extends BaseController
|
||||
// For development/testing, you can return a default value
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// ==================== Dashboard ====================
|
||||
|
||||
public function branchLevelDashboard()
|
||||
{
|
||||
// Hardcoded branch ID as requested
|
||||
$branchId = 1;
|
||||
|
||||
try {
|
||||
// 1. Lead Statistics
|
||||
$stats = $this->leadModel->getLeadStats(); // Using existing model method
|
||||
|
||||
// 2. Activity Statistics
|
||||
$activityStats = [
|
||||
'total' => $this->activityModel->countAllResults(),
|
||||
'completed' => $this->activityModel->where('status', 'completed')->countAllResults(),
|
||||
'pending' => $this->activityModel->where('status', 'pending')->countAllResults(),
|
||||
];
|
||||
|
||||
// 3. Team Performance (Aggregating activity counts per user)
|
||||
$db = \Config\Database::connect();
|
||||
$teamPerformance = $db->table('user_profiles as u')
|
||||
->select('u.first_name, u.last_name, u.profile as role,
|
||||
(SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id) as total_acts,
|
||||
(SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = "completed") as done_acts')
|
||||
->where('u.nhance_branch_id', $branchId)
|
||||
->where('u.is_active', 1)
|
||||
->get()->getResultArray();
|
||||
|
||||
// 4. Recent Activities (Joining for Lead Names)
|
||||
$recentActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
|
||||
->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
|
||||
->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->limit(6)
|
||||
->findAll();
|
||||
|
||||
// 5. All Leads Overview
|
||||
$leadsOverview = $this->leadModel->select('sales_actual_leads.*, user_profiles.first_name, user_profiles.last_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left')
|
||||
->findAll();
|
||||
|
||||
$data = [
|
||||
'total_leads' => $stats['total'],
|
||||
'total_activities' => $activityStats['total'],
|
||||
'completed_acts' => $activityStats['completed'],
|
||||
'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
|
||||
'team' => $teamPerformance,
|
||||
'recent_acts' => $recentActivities,
|
||||
'leads_overview' => $leadsOverview
|
||||
];
|
||||
|
||||
// dd($data);
|
||||
|
||||
$this->loadLayout('sales/branch_level_dashboard_view', $data);
|
||||
|
||||
// return view('sales/dashboard_view', $data);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function salesManagerLevelDashboard()
|
||||
{
|
||||
$userId = 1;
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
try {
|
||||
$target = $db->table('sales_target')
|
||||
->where('user_id', $userId)
|
||||
->where('fy_year', '2025-2026')
|
||||
->get()->getRowArray();
|
||||
|
||||
$targetAmount = $target['target_amount'] ?? 2500000.00;
|
||||
$achievedAmount = 600000.00;
|
||||
$remainingAmount = $targetAmount - $achievedAmount;
|
||||
$achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0;
|
||||
|
||||
$activitySummary = [
|
||||
'total' => $this->activityModel->where('assigned_to', $userId)->countAllResults(),
|
||||
'pending' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'pending'])->countAllResults(),
|
||||
'completed' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'completed'])->countAllResults(),
|
||||
];
|
||||
|
||||
$myLeadsCount = $this->leadModel->where('assigned_to', $userId)->countAllResults();
|
||||
|
||||
$upcomingActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
|
||||
->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
|
||||
->where(['sales_activities.assigned_to' => $userId, 'sales_activities.status' => 'pending'])
|
||||
->orderBy('scheduled_date', 'ASC')
|
||||
->limit(3)
|
||||
->findAll();
|
||||
|
||||
$recentLeads = $this->leadModel->where('assigned_to', $userId)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->limit(5)
|
||||
->findAll();
|
||||
|
||||
$data = [
|
||||
'target' => $targetAmount,
|
||||
'achieved' => $achievedAmount,
|
||||
'remaining' => $remainingAmount,
|
||||
'percent' => $achievementPercent,
|
||||
'acts' => $activitySummary,
|
||||
'lead_count' => $myLeadsCount,
|
||||
'upcoming' => $upcomingActivities,
|
||||
'recent_leads' => $recentLeads,
|
||||
'user_name' => session()->get('first_name') ?? 'John Doe'
|
||||
];
|
||||
|
||||
// return view('sales/my_dashboard_view', $data);
|
||||
$this->loadLayout('sales/sales_manager_level_dashboard', $data);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -990,16 +990,24 @@ class TestingController extends BaseController
|
||||
|
||||
// 🔐 Move this to .env in real projects
|
||||
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
|
||||
$policy_id = $this->request->getGet('client_policy');
|
||||
$database_id = (int)$this->request->getGet('database_id') ?? 2;
|
||||
$policy_id = $this->request->getGet('client_policy') ?? null;
|
||||
$tpa_url = 'https://nsights.nhanceindia.in/public/dashboard/4babf324-6c1e-4c5a-adbb-1c80a0f545b1';
|
||||
$policy_id = $policy_id ? $policy_id : 4687;
|
||||
$payload = [
|
||||
'resource' => [
|
||||
// 'dashboard' => 1
|
||||
'dashboard' => 2
|
||||
'dashboard' => $database_id
|
||||
],
|
||||
'params' => (object)['client_policy' => $policy_id], // MUST be object for Metabase
|
||||
'exp' => time() + (10 * 60) // 10 minutes
|
||||
];
|
||||
|
||||
if(!empty($policy_id)){
|
||||
$payload['params'] = (object)['client_policy' => $policy_id]; // MUST be object for Metabase
|
||||
}else{
|
||||
$payload['params'] = (object)[]; // MUST be object for Metabase
|
||||
}
|
||||
|
||||
// dd($payload);
|
||||
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
|
||||
|
||||
@ -1025,6 +1033,31 @@ class TestingController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
public function testingquerys1(){
|
||||
|
||||
}
|
||||
|
||||
public function testingquerys()
|
||||
{
|
||||
$calendar = new \App\Libraries\GoogleCalendarService();
|
||||
|
||||
// Check if user is authenticated without passing tokens manually
|
||||
if (!$calendar->isReady()) {
|
||||
return $this->respond(['status' => 'failed', 'code' => '404' , 'message' => 'Google Access Token Expired'], 200);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'summary' => 'Client Follow up',
|
||||
'meeting_date' => '2026-02-22 10:00:00',
|
||||
'description' => 'Visit Client place',
|
||||
'emails' => ['surendarsuri30@gmail.com', 'vitvelz@gmail.com', 'venbalap2026@gmail.com', 'gowthamceline46@gmail.com']
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $calendar->createEvent($data);
|
||||
return $this->respond(['status' => 'success', 'code' => '200' , 'message' => 'Follow-up Saved', 'response' => $response], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond(['status' => 'failed', 'code' => '500' , 'message' => 'Error: ' . $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Models\EmployeePolicyModel;
|
||||
use App\Models\TpaApiDataModel;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Controllers\TicketController;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
@ -15,13 +16,20 @@ class VidalApiController extends BaseController
|
||||
use ResponseTrait;
|
||||
protected $db;
|
||||
protected $vidal_primary_key;
|
||||
protected $claim_type_array;
|
||||
protected $ticketController;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
$this->vidal_primary_key = getenv('VIDAL_PRIMARY_KEY_CONSTANT');
|
||||
|
||||
$this->ticketController = new TicketController();
|
||||
$this->claim_type_array = $this->ticketController->claimType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function uploadFileToVidal($filePath,$filename)
|
||||
{
|
||||
@ -29,7 +37,7 @@ class VidalApiController extends BaseController
|
||||
$apiUrl = getenv('VIDAL_API_BASE_URL').'/files/upload-url';
|
||||
$subscriptionKey = getenv('VIDAL_API_SUBSCRIPTION_KEY');
|
||||
|
||||
log_message('error', "TPA CLAIM PUSH | Starting file upload process for filename: $filename | Path: $filePath");
|
||||
log_message('error', "VIDAL - Claim Push | Starting file upload process for filename: $filename | Path: $filePath");
|
||||
|
||||
// Step 1: Get signed URL from Vidal API
|
||||
// $filePath = '/opt/lampp/htdocs/nhance/writable/uploads/claim_files/1760013019_73d94af7b96ddc2e3d51.png';
|
||||
@ -39,7 +47,7 @@ class VidalApiController extends BaseController
|
||||
"Ocp-Apim-Subscription-Key: $subscriptionKey"
|
||||
];
|
||||
|
||||
log_message('error', "TPA CLAIM PUSH | Requesting signed URL from Vidal API: $apiUrl | Payload: $payload");
|
||||
log_message('error', "VIDAL - Claim Push | Requesting signed URL from Vidal API: $apiUrl | Payload: $payload");
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@ -49,7 +57,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if (curl_errno($ch)) {
|
||||
log_message('error', "Curl error while requesting signed URL: " . curl_error($ch));
|
||||
log_message('error', "VIDAL - Claim Push Curl error while requesting signed URL: " . curl_error($ch));
|
||||
return ["status" => false, "message" => curl_error($ch)];
|
||||
}
|
||||
curl_close($ch);
|
||||
@ -57,14 +65,14 @@ class VidalApiController extends BaseController
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
if (!isset($responseData['data']['signedUrl']) || !isset($responseData['data']['fileId'])) {
|
||||
log_message('error', "TPA CLAIM PUSH | Invalid signed URL response received: " . json_encode($responseData));
|
||||
log_message('error', "VIDAL - Claim Push | Invalid signed URL response received: " . json_encode($responseData));
|
||||
return ["status" => false, "message" => "Invalid signed URL response", "response" => $responseData];
|
||||
}
|
||||
|
||||
$signedUrl = $responseData['data']['signedUrl'];
|
||||
$fileId = $responseData['data']['fileId'];
|
||||
|
||||
log_message('error', "Received signed URL & fileId. fileId: $fileId");
|
||||
log_message('error', "VIDAL - Claim Push | Received signed URL & fileId. fileId: $fileId");
|
||||
|
||||
// Step 2: Upload file to signed URL using PUT (Azure Blob)
|
||||
$fileSize = filesize($filePath);
|
||||
@ -89,12 +97,12 @@ class VidalApiController extends BaseController
|
||||
curl_close($ch2);
|
||||
|
||||
if ($curlErr) {
|
||||
log_message('error', "TPA CLAIM PUSH | Curl error during file upload: $curlErr");
|
||||
log_message('error', "VIDAL - Claim Push | Curl error during file upload: $curlErr");
|
||||
return ["status" => false, "message" => "File upload failed", "data" => $curlErr];
|
||||
}
|
||||
|
||||
if ($httpCode !== 200 && $httpCode !== 201) {
|
||||
log_message('error', "TPA CLAIM PUSH | File upload failed with HTTP Code: $httpCode | Response: $uploadResponse");
|
||||
log_message('error', "VIDAL - Claim Push | File upload failed with HTTP Code: $httpCode | Response: $uploadResponse");
|
||||
return [
|
||||
"status" => false,
|
||||
"message" => "File upload failed",
|
||||
@ -132,6 +140,7 @@ class VidalApiController extends BaseController
|
||||
tm.hospital_pin_code as hospitalPinCode,
|
||||
tm.hospital_phone_no as hospitalPhoneNo,
|
||||
tm.claim_amount as requestedAmount,
|
||||
tm.claim_type,
|
||||
tm.tpa_no as dependentUniqueId,
|
||||
cp.policy_no as policyNo,
|
||||
e.emp_code as memberId,
|
||||
@ -154,7 +163,7 @@ class VidalApiController extends BaseController
|
||||
->getRowArray(); // single record
|
||||
|
||||
if (count($data) && $data['filePath'] == null) {
|
||||
log_message('error', "TPA CLAIM PUSH | Submit claim failed - Claim or File Missing");
|
||||
log_message('error', "VIDAL - Claim Push | Submit claim failed - Claim or File Missing");
|
||||
return $this->response->setJSON(['status' => false,'message' => 'Claim or File Missing', ]);
|
||||
}
|
||||
|
||||
@ -167,7 +176,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
$upload = $this->uploadFileToVidal($filePath,$filename);
|
||||
if ($upload['status'] !== true) {
|
||||
log_message('error', "TPA CLAIM PUSH | Submit claim failed - File upload failed");
|
||||
log_message('error', "VIDAL - Claim Push | Submit claim failed - File upload failed");
|
||||
return $this->response->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'File upload failed',
|
||||
@ -185,11 +194,25 @@ class VidalApiController extends BaseController
|
||||
'Ocp-Apim-Subscription-Key:' .getenv('VIDAL_API_SUBSCRIPTION_KEY').'',
|
||||
];
|
||||
|
||||
$typeOfClaim = "Main hospitalization claim";
|
||||
$allowedSubTypes = [
|
||||
'Hospitalization',
|
||||
'OPD',
|
||||
'Health check-up',
|
||||
'Dental benefit',
|
||||
'Day care',
|
||||
'Domiciliary'
|
||||
];
|
||||
$mappedSubType = $this->claim_type_array[1][$data['claim_type'] ?? null] ?? null;
|
||||
$claimSubType = in_array($mappedSubType, $allowedSubTypes, true)
|
||||
? $mappedSubType
|
||||
: 'Hospitalization'; // safe default
|
||||
|
||||
$body = [
|
||||
'policyNo' => $data['policyNo'],
|
||||
'dependentUniqueId' => $data['dependentUniqueId'],
|
||||
'typeOfClaim' => "Main hospitalization claim",
|
||||
'claimSubType' => "OPD",
|
||||
'typeOfClaim' => $typeOfClaim,
|
||||
'claimSubType' => $claimSubType,
|
||||
'requestedAmount' => $data['requestedAmount'],
|
||||
'ailmentType' => "Non covid",
|
||||
'admissionDate' => change_date_format($data['admissionDate'], 'Y-m-d', 'd-m-Y'),
|
||||
@ -212,7 +235,7 @@ class VidalApiController extends BaseController
|
||||
];
|
||||
// dd($body);
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
log_message('error', 'VIDAL - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
// $body = [
|
||||
// 'policyNo' => "351500/D0534/PP/20-20/PC",
|
||||
@ -244,7 +267,7 @@ class VidalApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'TPA CLAIM PUSH FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -260,7 +283,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
if(!empty($claimNO) && !empty($claimInwardNO)){
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH SUCCESS | claimId: '.$claimId.' | claimNO: '.$claimNO.' | claimInwardNO: '.$claimInwardNO);
|
||||
log_message('error', 'VIDAL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$claimNO.' | claimInwardNO: '.$claimInwardNO);
|
||||
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
@ -269,11 +292,11 @@ class VidalApiController extends BaseController
|
||||
return;
|
||||
|
||||
} else {
|
||||
log_message('error', 'TPA CLAIM PUSH API SUCCESS BUT claimNO,claimInwardNO EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
log_message('error', 'TPA CLAIM PUSH API FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Claim Push API FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -463,22 +486,22 @@ class VidalApiController extends BaseController
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['data']['claims'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
|
||||
return ['status' => false, 'message' => 'API call failed.','data' => $response];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
$claimData = $response['data']['data']['claims'][0];
|
||||
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
||||
$currentStatus = $claimData['status'] ?? '';
|
||||
$tpa_claim_type = $claimData['claimType'] ?? '';
|
||||
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
|
||||
"In-Progress" => 5,
|
||||
"Required Information" => 4,
|
||||
"Paid" => 11,
|
||||
@ -486,20 +509,25 @@ class VidalApiController extends BaseController
|
||||
"Approved" => 8,
|
||||
];
|
||||
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
// 'claim_number' => $tpa_claim_no, // already updated
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
|
||||
|
||||
// UPDATE ticket_master
|
||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "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");
|
||||
|
||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||
}
|
||||
@ -540,7 +568,7 @@ class VidalApiController extends BaseController
|
||||
// dd($TicketData);
|
||||
|
||||
if (!$TicketData) {
|
||||
log_message('error', "Claims not found to update status");
|
||||
log_message('error', "VIDAL - Claim Status | Claims not found to update status");
|
||||
return $this->response->setJSON(['status' => false,'message' => 'Claims not found' ]);
|
||||
}
|
||||
|
||||
@ -570,18 +598,19 @@ class VidalApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['data']['claims'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
$error_data[$claimId][['status' => false,'message' => 'API call failed.','data' => $response]];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
$claimData = $response['data']['data']['claims'][0];
|
||||
$tpa_claim_no = $claimData['claimNumber'] ?? '';
|
||||
$currentStatus = $claimData['status'] ?? '';
|
||||
$tpa_claim_type = $claimData['claimType'] ?? '';
|
||||
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
|
||||
"In-Progress" => 5,
|
||||
"Required Information" => 4,
|
||||
"Paid" => 11,
|
||||
@ -589,20 +618,24 @@ class VidalApiController extends BaseController
|
||||
"Approved" => 8,
|
||||
];
|
||||
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
// 'claim_number' => $tpa_claim_no, // already updated
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
|
||||
// UPDATE ticket_master
|
||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "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 ++;
|
||||
|
||||
@ -654,17 +687,17 @@ class VidalApiController extends BaseController
|
||||
// dd($response);
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
$ecardUrl = $response['data']['data']['dependents'][0]['ecardLink'] ?? null;
|
||||
|
||||
if(!empty($ecardUrl)){
|
||||
log_message('error', 'Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
log_message('error', 'VIDAL - Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
return $ecardUrl;
|
||||
} else {
|
||||
log_message('error', 'Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error', 'VIDAL - Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -692,7 +725,7 @@ class VidalApiController extends BaseController
|
||||
$client_policy_id = $requestData['client_policy_id'] ?? null;
|
||||
|
||||
if (empty($policyNo)) {
|
||||
log_message('error', 'TPA ID PULL | policy_no missing in request');
|
||||
log_message('error', 'VIDAL - TPA ID Pull | policy_no missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'policy_no required'];
|
||||
}else{
|
||||
@ -701,7 +734,7 @@ class VidalApiController extends BaseController
|
||||
}
|
||||
|
||||
if (empty($client_policy_id)) {
|
||||
log_message('error', 'TPA ID PULL | client_policy_id missing in request');
|
||||
log_message('error', 'VIDAL - TPA ID Pull | client_policy_id missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'client_policy_id required'];
|
||||
}else{
|
||||
@ -709,7 +742,7 @@ class VidalApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', "TPA ID PULL | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
log_message('error', "VIDAL - TPA ID Pull | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
|
||||
// Fetch file download dates
|
||||
$batchFiles = $this->db->table('batch_files f')
|
||||
@ -723,7 +756,7 @@ class VidalApiController extends BaseController
|
||||
// dd($batchFiles);
|
||||
|
||||
if (empty($batchFiles)) {
|
||||
log_message('error', 'TPA ID PULL FAILED | batchFiles is empty for this tpa id pull request');
|
||||
log_message('error', 'VIDAL - TPA ID Pull FAILED | batchFiles is empty for this tpa id pull request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'batchFiles not found'];
|
||||
}else{
|
||||
@ -749,7 +782,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
// $body = [ 'empNO' => "Mem 1" ];
|
||||
|
||||
log_message('error', "TPA ID PULL | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
log_message('error', "VIDAL - TPA ID Pull | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
@ -759,12 +792,12 @@ class VidalApiController extends BaseController
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
$file_model = new BatchFileModel();
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error', "VIDAL - TPA ID Pull | Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error', "VIDAL - TPA ID Pull | Failed to update file table status.");
|
||||
}
|
||||
|
||||
log_message('error', 'TPA ID PULL API FAILED | API failed: ' . json_encode($response));
|
||||
log_message('error', 'VIDAL - TPA ID Pull API FAILED | API failed: ' . json_encode($response));
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => $response];
|
||||
@ -776,12 +809,12 @@ class VidalApiController extends BaseController
|
||||
$data = $response['data']['data'] ?? [];
|
||||
|
||||
if (!isset($data['dependents'])) {
|
||||
log_message('error', "TPA ID PULL FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
log_message('error', "VIDAL - TPA ID Pull FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
break;
|
||||
}
|
||||
|
||||
$fetchedCount = count($data['dependents']);
|
||||
log_message('error', "Fetched {$fetchedCount} records ");
|
||||
log_message('error', "VIDAL - TPA ID Pull | Fetched {$fetchedCount} records ");
|
||||
$allBenef = array_merge($allBenef, $data['dependents']);
|
||||
|
||||
}
|
||||
@ -848,9 +881,9 @@ class VidalApiController extends BaseController
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "✅ Updated tpa_id={$row['enrollmentId']} for emp_code={$row['empNo']} policy={$row['policyNumber']}");
|
||||
log_message('error', "VIDAL - TPA ID Pull | Updated tpa_id={$row['enrollmentId']} for emp_code={$row['empNo']} policy={$row['policyNumber']}");
|
||||
} else {
|
||||
log_message('error', "⚠️ No update (already set or not matched) for emp_code={$row['empNo']} policy={$row['policyNumber']}");
|
||||
log_message('error', "VIDAL - TPA ID Pull | No update (already set or not matched) for emp_code={$row['empNo']} policy={$row['policyNumber']}");
|
||||
}
|
||||
|
||||
}
|
||||
@ -871,7 +904,7 @@ class VidalApiController extends BaseController
|
||||
|
||||
log_message(
|
||||
'error',
|
||||
"❌ No match for Nhance = " . json_encode($nhanceSideData)
|
||||
"VIDAL - TPA ID Pull | No match for Nhance = " . json_encode($nhanceSideData)
|
||||
);
|
||||
}
|
||||
|
||||
@ -896,7 +929,7 @@ class VidalApiController extends BaseController
|
||||
}
|
||||
|
||||
|
||||
log_message('error', "TPA ID PULL SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
log_message('error', "VIDAL - TPA ID Pull SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return [
|
||||
@ -945,8 +978,54 @@ class VidalApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function saveVidalAPIData($array)
|
||||
{
|
||||
$file_id = $array['file_id'];
|
||||
$json = file_get_contents($array['json_file_path']);
|
||||
$records = json_decode($json, true);
|
||||
|
||||
// log_message('error','FHPL - saveFhplAPIData' . json_encode($array));//die();
|
||||
$file_model = new BatchFileModel();
|
||||
// $file_model = model(BatchFileModel::class);
|
||||
$file_info = $file_model->where('id', $file_id)->find();
|
||||
|
||||
// $tpaApiDataModel = new TpaApiDataModel();
|
||||
$tpaApiDataModel = model(TpaApiDataModel::class);
|
||||
|
||||
|
||||
//deactivate old data
|
||||
$tpaApiDataModel->set('is_active', 0)->where('file_id', $file_id)->update();
|
||||
|
||||
//covert tpa data to our model data
|
||||
$mappedRows = [];
|
||||
|
||||
foreach ($records as $row) {
|
||||
|
||||
$mappedRows[] = [
|
||||
'file_id' => $file_id, // ← pass from controller
|
||||
'emp_code' => trim($row['empNo'] ?? ''),
|
||||
|
||||
'name' => trim($row['name'] ?? ''),
|
||||
'dob' => !empty($row['dob'] ?? null) ? date('Y-m-d', strtotime(str_replace('/', '-', $row['dob']))) : null,
|
||||
|
||||
'relation' => trim(strtolower($row['relationship'] ?? '')),
|
||||
'gender' => format_gender_v2($row['gender'] ?? null),
|
||||
'self' => strtolower($row['relationship'] ?? '') === 'self' ? 1 : 0,
|
||||
|
||||
'tpa_id' => trim($row['enrollmentId'] ?? null),
|
||||
'age' => is_numeric($row['age'] ?? null) ? (int) $row['age'] : null,
|
||||
|
||||
'is_active' => 1,
|
||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// log_message('error','FHPL - COUNT' . count($mappedRows));
|
||||
$result = $tpaApiDataModel->insertBatchWithChunkLog($mappedRows,500,('FILE_ID_'.$file_id));
|
||||
// unlink($file_array['json_file_path']); // delete temp json file
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
143
app/Filters/AuthApiRateLimitFilter.php
Normal file
143
app/Filters/AuthApiRateLimitFilter.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Libraries\RateLimiterService;
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* AuthApiFilter
|
||||
*
|
||||
* Applied to API routes that do NOT use JWT — e.g. verifyMobileNumber, verifyOTP.
|
||||
* Identity is extracted from request params: 'email' or 'mobile_number'.
|
||||
*
|
||||
* Performs:
|
||||
* - IP-level throttle + progressive block check (via fingerprint)
|
||||
* - User-level block check (if identity present in params)
|
||||
*
|
||||
* Usage in Routes.php:
|
||||
* $routes->post('api/auth/verify-otp', 'AuthController::verifyOtp', ['filter' => 'authApiRateLimit']);
|
||||
*
|
||||
* Register in app/Config/Filters.php:
|
||||
* 'AuthApiRateLimitFilter' => \App\Filters\AuthApiRateLimitFilter::class
|
||||
*/
|
||||
class AuthApiRateLimitFilter implements FilterInterface
|
||||
{
|
||||
protected RateLimiterService $limiter;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->limiter = new RateLimiterService();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BEFORE — runs before the controller
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
$fingerprint = generateFingerprint(exclude_ua: true);
|
||||
|
||||
|
||||
// 1. IP-level check
|
||||
$ipResult = $this->limiter->checkIp($fingerprint, 'authApi');
|
||||
if ($ipResult) {
|
||||
return $this->jsonResponse($ipResult);
|
||||
}
|
||||
|
||||
// 2. User-level block check (identity may not be present yet on first hit)
|
||||
$identity = $this->resolveIdentity($request);
|
||||
if ($identity) {
|
||||
$userResult = $this->limiter->checkUser($identity);
|
||||
if ($userResult) {
|
||||
return $this->jsonResponse($userResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Store resolved identity in request for use in after()
|
||||
if ($identity) {
|
||||
$request->setGlobal('rateLimitIdentity', $identity);
|
||||
}
|
||||
$request->setGlobal('rateLimitFingerprint', $fingerprint);
|
||||
|
||||
return null; // pass through
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AFTER — runs after the controller; records failures on bad responses
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
// Only act on failed responses (4xx from auth failures)
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode < 400 || $statusCode === 429 || $statusCode === 403 || $statusCode === 451) {
|
||||
return; // 2xx/3xx = success; 429/403/451 already handled
|
||||
}
|
||||
|
||||
$fingerprint = $request->getGlobal('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true);
|
||||
|
||||
$identity = $request->getGlobal('rateLimitIdentity')
|
||||
?? $this->resolveIdentity($request);
|
||||
|
||||
// Record failure at IP level
|
||||
$this->limiter->recordIpFailure($fingerprint);
|
||||
|
||||
// Record failure at user level
|
||||
if ($identity) {
|
||||
$this->limiter->recordUserFailure($identity, 'authApi');
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HELPERS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Extract identity from POST body or GET params.
|
||||
* Looks for 'email' or 'mobile_number'.
|
||||
*/
|
||||
protected function resolveIdentity(RequestInterface $request): ?string
|
||||
{
|
||||
// Try POST body first
|
||||
$email = $request->getPost('email');
|
||||
$mobile = $request->getPost('mobile_number');
|
||||
|
||||
// Fallback to GET params
|
||||
if (! $email && ! $mobile) {
|
||||
$email = $request->getGet('email');
|
||||
$mobile = $request->getGet('mobile_number');
|
||||
}
|
||||
|
||||
if ($email) {
|
||||
return strtolower(trim($email));
|
||||
}
|
||||
|
||||
if ($mobile) {
|
||||
return trim($mobile);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return a JSON response for blocked/throttled requests.
|
||||
*/
|
||||
protected function jsonResponse(array $result): ResponseInterface
|
||||
{
|
||||
$response = service('response');
|
||||
$response->setStatusCode($result['status']);
|
||||
$response->setContentType('application/json');
|
||||
$response->setBody(json_encode([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'code' => strtoupper('RATE_LIMIT_' . $result['level']),
|
||||
'message' => $result['message'],
|
||||
'type' => $result['type'] ?? 'request',
|
||||
],
|
||||
]));
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ class GlobalPostFileUploadGuard implements FilterInterface
|
||||
'application/vnd.oasis.opendocument.text' => ['odt'],
|
||||
'text/rtf' => ['rtf'],
|
||||
'application/rtf' => ['rtf'],
|
||||
'application/vnd.ms-excel' => ['xls'],
|
||||
'application/vnd.ms-excel' => ['xls', 'xlsx'],
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
|
||||
'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
|
||||
'text/csv' => ['csv'],
|
||||
|
||||
153
app/Filters/JwtApiFilter.php
Normal file
153
app/Filters/JwtApiFilter.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Libraries\RateLimiterService;
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* JwtApiFilter
|
||||
*
|
||||
* Applied to API routes that require a valid JWT token.
|
||||
* Identity (email or mobile) is extracted from the JWT payload using
|
||||
* your existing helper functions: getEmailFromJWT() / getMobileFromJWT().
|
||||
*
|
||||
* Performs:
|
||||
* - IP-level throttle + progressive block check (via fingerprint)
|
||||
* - User-level throttle + progressive block check (by JWT identity)
|
||||
*
|
||||
* Usage in Routes.php:
|
||||
* $routes->get('api/profile', 'ProfileController::index', ['filter' => 'jwtApiRateLimit']);
|
||||
*
|
||||
* Register in app/Config/Filters.php:
|
||||
* 'JwtApiRateLimitFilter' => \App\Filters\JwtApiRateLimitFilter::class
|
||||
*/
|
||||
class JwtApiRateLimitFilter implements FilterInterface
|
||||
{
|
||||
protected RateLimiterService $limiter;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->limiter = new RateLimiterService();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BEFORE — runs before the controller
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
$fingerprint = generateFingerprint(exclude_ua: true);
|
||||
|
||||
|
||||
// 1. IP-level throttle + block check
|
||||
$ipResult = $this->limiter->checkIp($fingerprint, 'jwtApi');
|
||||
if ($ipResult) {
|
||||
return $this->jsonResponse($ipResult);
|
||||
}
|
||||
|
||||
// 2. Resolve user identity from JWT
|
||||
// Uses your existing JWT helper functions.
|
||||
// If neither returns a value, fall back to IP-only limiting.
|
||||
$identity = $this->resolveIdentityFromJwt();
|
||||
|
||||
if ($identity) {
|
||||
// User-level throttle (request count based for JWT routes)
|
||||
$userResult = $this->limiter->checkUserThrottle($identity, 'jwtApi');
|
||||
if ($userResult) {
|
||||
return $this->jsonResponse($userResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Stash for after() use
|
||||
$request->setGlobal('rateLimitFingerprint', $fingerprint);
|
||||
if ($identity) {
|
||||
$request->setGlobal('rateLimitIdentity', $identity);
|
||||
}
|
||||
|
||||
return null; // pass through
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AFTER — records IP failure on controller-level bad responses
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
// Only act on auth-related failures from the controller (401, 422, etc.)
|
||||
// 429/403/451 are already handled by before(); skip 2xx/3xx.
|
||||
if ($statusCode < 400 || in_array($statusCode, [429, 403, 451])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fingerprint = $request->getGlobal('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true);
|
||||
|
||||
$identity = $request->getGlobal('rateLimitIdentity') ?? $this->resolveIdentityFromJwt();
|
||||
|
||||
$this->limiter->recordIpFailure($fingerprint);
|
||||
|
||||
if ($identity) {
|
||||
$this->limiter->recordUserFailure($identity, 'jwtApi');
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HELPERS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolve user identity from JWT using your existing helper functions.
|
||||
* Tries email first, then mobile. Returns null if JWT is absent/invalid.
|
||||
*
|
||||
* IMPORTANT: Replace getEmailFromJWT() / getMobileFromJWT() with your
|
||||
* actual function names if they differ.
|
||||
*/
|
||||
protected function resolveIdentityFromJwt(): ?string
|
||||
{
|
||||
try {
|
||||
// Try email from JWT
|
||||
if (function_exists('getEmailFromJWT')) {
|
||||
$email = getEmailFromJWT();
|
||||
if ($email) {
|
||||
return strtolower(trim($email));
|
||||
}
|
||||
}
|
||||
|
||||
// Try mobile from JWT
|
||||
if (function_exists('getMobileFromJWT')) {
|
||||
$mobile = getMobileFromJWT();
|
||||
if ($mobile) {
|
||||
return trim($mobile);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// JWT invalid or expired — fall through to IP-only limiting
|
||||
log_message('debug', '[RateLimiter] JWT identity resolution failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return a JSON response for blocked/throttled requests.
|
||||
*/
|
||||
protected function jsonResponse(array $result): ResponseInterface
|
||||
{
|
||||
$response = service('response');
|
||||
$response->setStatusCode($result['status']);
|
||||
$response->setContentType('application/json');
|
||||
$response->setBody(json_encode([
|
||||
'success' => false,
|
||||
'error' => [
|
||||
'code' => strtoupper('RATE_LIMIT_' . $result['level']),
|
||||
'message' => $result['message'],
|
||||
'type' => $result['type'] ?? 'request',
|
||||
],
|
||||
]));
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ class HttpRequestHelper
|
||||
|
||||
$data = [
|
||||
'ip' => $request->getIPAddress(),
|
||||
'real_ip' => getRealClientIP(),
|
||||
'platform' => $platform,
|
||||
'browser' => $browser,
|
||||
'method' => strtoupper($request->getMethod()),
|
||||
|
||||
@ -1069,23 +1069,43 @@ function getRealClientIP()
|
||||
return $request->getIPAddress();
|
||||
}
|
||||
|
||||
function generateFingerprint()
|
||||
function generateFingerprint(bool $exclude_ua = false): string
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
$ua = $request->getUserAgent()->getAgentString();
|
||||
$ip = getRealClientIP();
|
||||
|
||||
// Use only subnet (first 3 blocks) to tolerate IP change
|
||||
$ipParts = explode('.', $ip);
|
||||
$ipSubnet = $ipParts[0] . '.' . $ipParts[1] . '.' . $ipParts[2];
|
||||
// Normalize localhost
|
||||
if ($ip === '127.0.0.1' || $ip === '::1') {
|
||||
$ipGroup = 'localhost';
|
||||
}
|
||||
// IPv4 handling
|
||||
elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$parts = explode('.', $ip);
|
||||
// Use /24 subnet (first 3 octets)
|
||||
$ipGroup = $parts[0] . '.' . $parts[1] . '.' . $parts[2];
|
||||
}
|
||||
// IPv6 handling
|
||||
elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
// Use first 4 blocks of IPv6 (rough /64 grouping)
|
||||
$blocks = explode(':', $ip);
|
||||
$ipGroup = implode(':', array_slice($blocks, 0, 4));
|
||||
}
|
||||
// Fallback
|
||||
else {
|
||||
$ipGroup = 'unknown';
|
||||
}
|
||||
|
||||
// $secret = env('app.sessionFingerprintSalt');
|
||||
|
||||
// return hash('sha256', $ua . '|' . $ipSubnet . '|' . $secret);
|
||||
return hash('sha256', $ua . '|' . $ipSubnet );
|
||||
if($exclude_ua){
|
||||
return hash('sha256', $ipGroup);
|
||||
}
|
||||
return hash('sha256', $ua . '|' . $ipGroup);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!function_exists('convertGoogleDriveToDownloadLink')) {
|
||||
function convertGoogleDriveToDownloadLink(?string $url): ?string
|
||||
{
|
||||
@ -1144,7 +1164,116 @@ if (!function_exists('clear_cd_balance_session')) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('format_gender_v2')) {
|
||||
function format_gender_v2($gender) {
|
||||
if (empty($gender)) return null;
|
||||
|
||||
$g = strtoupper(trim($gender));
|
||||
|
||||
// Direct-ah check pannuvom
|
||||
if (str_starts_with($g, 'M')) return 'M'; // Male, M
|
||||
if (str_starts_with($g, 'F')) return 'F'; // Female, F
|
||||
|
||||
// Others, Transgender, O - ivatrai 'O' ena return seiyum
|
||||
if (str_starts_with($g, 'O') || str_starts_with($g, 'T')) {
|
||||
return 'O';
|
||||
}
|
||||
|
||||
return $g; // Vera ethuvum illaiyengil original-aiye return pannum
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('map_relationship')) {
|
||||
/**
|
||||
* Employee -> self, WIFE -> spouse ena maatri return seiyum.
|
||||
*/
|
||||
function map_relationship($relation) {
|
||||
if (empty($relation)) return '';
|
||||
|
||||
// Case prechanai varaamal irukka lowercase-kku maatri check seivom
|
||||
$r = strtolower(trim($relation));
|
||||
|
||||
if ($r == 'employee') {
|
||||
return 'self';
|
||||
}
|
||||
else if ($r == 'wife') {
|
||||
return 'spouse';
|
||||
}
|
||||
|
||||
// Matra anaithu relationship-um iruppathu polave (Original-aga) return aagum
|
||||
return $relation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract identity from POST body or GET params.
|
||||
* Looks for 'email' or 'mobile_number'.
|
||||
*/
|
||||
function resolveIdentity($request): ?string
|
||||
{
|
||||
// Try POST body first
|
||||
$email = $request->getPost('email');
|
||||
// print_r($email);die;
|
||||
$mobile = $request->getPost('mobile_number');
|
||||
|
||||
// Fallback to GET params
|
||||
if (! $email && ! $mobile) {
|
||||
$email = $request->getGet('email');
|
||||
$mobile = $request->getGet('mobile_number');
|
||||
}
|
||||
// Fallback to JSON params
|
||||
if (! $email && ! $mobile) {
|
||||
$req_data = $request->getJSON();
|
||||
// print_r( $req_data);
|
||||
|
||||
$mobile = $req_data->mobile_number ?? null;
|
||||
// return trim($mobile_number);
|
||||
|
||||
$email = $req_data->email ?? null;
|
||||
|
||||
if (!$email)
|
||||
{
|
||||
$email = $req_data->email_id ?? null;
|
||||
}
|
||||
// return trim($email);
|
||||
}
|
||||
|
||||
if ($email) {
|
||||
return strtolower(trim($email));
|
||||
}
|
||||
|
||||
if ($mobile) {
|
||||
return trim($mobile);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function recordRateLimitFailure(string $context = 'authApi'): void
|
||||
{
|
||||
/** @var IncomingRequest $request */
|
||||
$request = \Config\Services::request();
|
||||
|
||||
$limiter = \Config\Services::limiter(); // or your custom limiter service
|
||||
|
||||
$fingerprint = $request->getVar('rateLimitFingerprint')
|
||||
?? generateFingerprint(exclude_ua: true);
|
||||
|
||||
|
||||
|
||||
$identity = $request->getVar('rateLimitIdentity')
|
||||
?? resolveIdentity($request);
|
||||
|
||||
// Record IP-level failure
|
||||
$limiter->recordIpFailure($fingerprint);
|
||||
|
||||
// Record user-level failure
|
||||
if (!empty($identity)) {
|
||||
$limiter->recordUserFailure($identity, $context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
205
app/Libraries/GoogleCalendarService.php
Normal file
205
app/Libraries/GoogleCalendarService.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use Google\Client;
|
||||
use Google\Service\Calendar;
|
||||
use Google\Service\Calendar\Event;
|
||||
|
||||
class GoogleCalendarService
|
||||
{
|
||||
protected $client;
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$appName = getenv('GOOGLE_OAUTH_APP_NAME');
|
||||
$clientID = getenv('GOOGLE_OAUTH_CLIENT_ID');
|
||||
$clientSecret = getenv('GOOGLE_OAUTH_CLIENT_SECRET');
|
||||
$redirectUri = getenv('GOOGLE_OAUTH_REDIRECT_URI');
|
||||
$scopes = getenv('GOOGLE_OAUTH_SCOPES');
|
||||
|
||||
$scopesArray = explode(',', $scopes);
|
||||
|
||||
$this->client = new Client();
|
||||
$this->client->setApplicationName($appName);
|
||||
$this->client->setClientId($clientID);
|
||||
$this->client->setClientSecret($clientSecret);
|
||||
$this->client->setRedirectUri($redirectUri);
|
||||
$this->client->addScope($scopesArray);
|
||||
$this->client->setPrompt('consent');
|
||||
$this->client->setAccessType('offline');
|
||||
|
||||
$accessToken = session()->get('access_token');
|
||||
$refreshToken = session()->get('refresh_token'); // Session-la irunthu refresh token-a edukkurom
|
||||
|
||||
if ($accessToken) {
|
||||
$this->client->setAccessToken($accessToken);
|
||||
|
||||
if ($this->client->isAccessTokenExpired()) {
|
||||
if ($refreshToken) {
|
||||
// Ippo session-la iruntha refresh token-a vechu puthu access token vaangurom
|
||||
$newToken = $this->client->fetchAccessTokenWithRefreshToken($refreshToken);
|
||||
|
||||
// Romba mukkiyam: Puthu token-la refresh token thirumba varaathu,
|
||||
// so pazhaya refresh token-aiye namma retain pannanum.
|
||||
if (!isset($newToken['refresh_token'])) {
|
||||
$newToken['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
session()->set('access_token', $newToken);
|
||||
$this->client->setAccessToken($newToken);
|
||||
} else {
|
||||
// Refresh token illana, user marubadiyum login panna sollanum
|
||||
return redirect()->to('/google-login');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isReady()
|
||||
{
|
||||
return $token = session()->get('access_token') && !$this->client->isAccessTokenExpired();
|
||||
}
|
||||
|
||||
public function createEventForOrganizerOnly(array $data)
|
||||
{
|
||||
$this->service = new Calendar($this->client);
|
||||
|
||||
// Date kuda '10:00' add panrathu safe
|
||||
$meeting_start = preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['meeting_date']) ? $data['meeting_date'] . ' 10:00:00' : $data['meeting_date'];
|
||||
|
||||
$event = new Event([
|
||||
'summary' => $data['summary'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'start' => [
|
||||
'dateTime' => date('c', strtotime($meeting_start)), // Ippo 10 AM-nu fix aagidum
|
||||
'timeZone' => 'Asia/Kolkata',
|
||||
],
|
||||
'end' => [
|
||||
'dateTime' => date('c', strtotime($meeting_start . ' +30 minutes')),
|
||||
'timeZone' => 'Asia/Kolkata',
|
||||
],
|
||||
'reminders' => [
|
||||
'useDefault' => false,
|
||||
'overrides' => [
|
||||
['method' => 'email', 'minutes' => 1440], // Munthuna naal 10 AM
|
||||
['method' => 'popup', 'minutes' => 60], // Event annaki 9 AM
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->service->events->insert('primary', $event);
|
||||
}
|
||||
|
||||
public function createEvent(array $data)
|
||||
{
|
||||
$this->service = new Calendar($this->client);
|
||||
$responses = [];
|
||||
|
||||
try {
|
||||
|
||||
$meetingStartRaw = (preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['meeting_date']))
|
||||
? $data['meeting_date'] . ' 10:00:00'
|
||||
: $data['meeting_date'];
|
||||
|
||||
$startTime = date('c', strtotime($meetingStartRaw));
|
||||
$endTime = date('c', strtotime(date('Y-m-d', strtotime($meetingStartRaw)) . ' 23:59:59'));
|
||||
|
||||
// 2. Prepare Attendees
|
||||
$attendees = [];
|
||||
if (!empty($data['emails']) && is_array($data['emails'])) {
|
||||
foreach ($data['emails'] as $email) {
|
||||
$attendees[] = ['email' => $email];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Define the Event
|
||||
$event = new Event([
|
||||
'summary' => $data['summary'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'start' => ['dateTime' => $startTime, 'timeZone' => 'Asia/Kolkata'],
|
||||
'end' => ['dateTime' => $endTime, 'timeZone' => 'Asia/Kolkata'],
|
||||
'attendees' => $attendees,
|
||||
'reminders' => [
|
||||
'useDefault' => false,
|
||||
'overrides' => [
|
||||
['method' => 'email', 'minutes' => 1440],
|
||||
['method' => 'popup', 'minutes' => 60],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$optParams = ['sendUpdates' => 'all'];
|
||||
|
||||
$calendarId = 'primary';
|
||||
$result = $this->service->events->insert($calendarId, $event, $optParams);
|
||||
|
||||
$responses['status'] = 'success';
|
||||
$responses['event_id'] = $result->getId();
|
||||
$responses['raw_response'] = $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$error_data = [
|
||||
'error' => $e->getMessage(),
|
||||
'data' => $data,
|
||||
'trace' => $e->getTraceAsString()
|
||||
];
|
||||
|
||||
// Log only on failure
|
||||
log_message('error', 'Google Calendar Event Creation Failed' . json_encode($error_data ?? []));
|
||||
|
||||
$responses['status'] = 'error';
|
||||
$responses['message'] = $e->getMessage();
|
||||
$responses['error_data'] = $error_data;
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
public function createStaffEvent(array $data)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
$meetingStartRaw = (preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['meeting_date']))
|
||||
? $data['meeting_date'] . ' 10:00:00'
|
||||
: $data['meeting_date'];
|
||||
|
||||
$startTime = date('c', strtotime($meetingStartRaw));
|
||||
$endTime = date('c', strtotime(date('Y-m-d', strtotime($meetingStartRaw)) . ' 23:59:59'));
|
||||
|
||||
|
||||
// Event Object create panrom (Common for all staff)
|
||||
$event = new Event([
|
||||
'summary' => $data['summary'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'start' => ['dateTime' => $startTime, 'timeZone' => 'Asia/Kolkata'],
|
||||
'end' => ['dateTime' => $endTime, 'timeZone' => 'Asia/Kolkata'],
|
||||
'reminders' => [
|
||||
'useDefault' => false,
|
||||
'overrides' => [
|
||||
['method' => 'email', 'minutes' => 1440],
|
||||
['method' => 'popup', 'minutes' => 60],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Loop through each staff email in the array
|
||||
foreach ($data['emails'] as $email) {
|
||||
try {
|
||||
|
||||
$this->client->setSubject($email);
|
||||
|
||||
$staffService = new Calendar($this->client);
|
||||
|
||||
$results[$email] = $staffService->events->insert('primary', $event);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$results[$email] = 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
80
app/Libraries/GoogleSheetLib.php
Normal file
80
app/Libraries/GoogleSheetLib.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php namespace App\Libraries;
|
||||
|
||||
use Google_Client;
|
||||
use Google_Service_Sheets;
|
||||
use Google_Service_Drive;
|
||||
use Google_Service_Sheets_ValueRange;
|
||||
|
||||
class GoogleSheetLib
|
||||
{
|
||||
protected Google_Client $client;
|
||||
protected Google_Service_Sheets $sheets;
|
||||
protected Google_Service_Drive $drive;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Google_Client();
|
||||
|
||||
// Service account JSON
|
||||
$this->client->setAuthConfig(
|
||||
ROOTPATH . 'gdrive-demo-394007-5b1d856b0c5b.json'
|
||||
);
|
||||
|
||||
// IMPORTANT for service account
|
||||
$this->client->useApplicationDefaultCredentials();
|
||||
|
||||
// Required scopes
|
||||
$this->client->addScope([
|
||||
Google_Service_Drive::DRIVE,
|
||||
Google_Service_Sheets::SPREADSHEETS
|
||||
]);
|
||||
|
||||
// Init services
|
||||
$this->sheets = new Google_Service_Sheets($this->client);
|
||||
$this->drive = new Google_Service_Drive($this->client);
|
||||
}
|
||||
|
||||
/* ===================== READ ===================== */
|
||||
|
||||
public function read(string $spreadsheetId, string $range = 'Sheet1')
|
||||
{
|
||||
$response = $this->sheets
|
||||
->spreadsheets_values
|
||||
->get($spreadsheetId, $range);
|
||||
|
||||
return $response->getValues() ?? [];
|
||||
}
|
||||
|
||||
/* ===================== WRITE ===================== */
|
||||
|
||||
public function write(string $spreadsheetId, array $values, string $range = 'Sheet1')
|
||||
{
|
||||
$body = new Google_Service_Sheets_ValueRange([
|
||||
'values' => $values
|
||||
]);
|
||||
|
||||
$this->sheets
|
||||
->spreadsheets_values
|
||||
->update(
|
||||
$spreadsheetId,
|
||||
$range,
|
||||
$body,
|
||||
['valueInputOption' => 'RAW']
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ===================== DOWNLOAD ===================== */
|
||||
|
||||
public function downloadExcel(string $spreadsheetId)
|
||||
{
|
||||
$response = $this->drive->files->export(
|
||||
$spreadsheetId,
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
['alt' => 'media']
|
||||
);
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
}
|
||||
383
app/Libraries/RateLimiterService.php
Normal file
383
app/Libraries/RateLimiterService.php
Normal file
@ -0,0 +1,383 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use Config\RateLimiter as RateLimiterConfig;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
* RateLimiterService
|
||||
*
|
||||
* Handles all rate limiting logic:
|
||||
* - IP-level throttle + progressive blocking (soft/medium/hard)
|
||||
* - User-level progressive blocking (soft/medium/hard) by email or mobile_number
|
||||
* - Manual block / unblock helpers callable from anywhere
|
||||
*
|
||||
* Block levels: 'soft' | 'medium' | 'hard'
|
||||
* All blocks are MANUAL UNBLOCK ONLY (no auto-expiry on block state).
|
||||
* Counters and violation counts use cache TTLs; block records do not expire.
|
||||
*/
|
||||
class RateLimiterService
|
||||
{
|
||||
protected RateLimiterConfig $config;
|
||||
protected CacheInterface $cache;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('RateLimiter');
|
||||
$this->cache = \Config\Services::cache();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PUBLIC — IP LEVEL
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Check & throttle by fingerprint (IP+UA based).
|
||||
* Returns null on pass, or an array ['level'=>..., 'message'=>...] on block.
|
||||
*/
|
||||
public function checkIp(string $fingerprint, string $routeType = 'jwtApi'): ?array
|
||||
{
|
||||
// 1. Is the IP already blocked?
|
||||
$blockInfo = $this->getIpBlock($fingerprint);
|
||||
if ($blockInfo) {
|
||||
// Count hit while blocked → maybe escalate
|
||||
$this->recordIpBlockHit($fingerprint, $blockInfo['level']);
|
||||
return $this->blockedResponse('ip', $blockInfo['level']);
|
||||
}
|
||||
|
||||
// 2. Throttle check
|
||||
$cfg = $this->config->ipBlock;
|
||||
$countKey = $this->config->cacheKeys['ip_count'] . $fingerprint;
|
||||
$count = (int) ($this->cache->get($countKey) ?? 0);
|
||||
|
||||
if ($count === 0) {
|
||||
$this->cache->save($countKey, 1, $cfg['window']);
|
||||
} else {
|
||||
$this->cache->save($countKey, $count + 1, $cfg['window']);
|
||||
}
|
||||
|
||||
if (($count + 1) > $cfg['limit']) {
|
||||
// Over limit → record violation
|
||||
$violated = $this->incrementIpViolation($fingerprint);
|
||||
if ($violated >= $cfg['violation_soft']) {
|
||||
$this->blockIp($fingerprint, 'soft');
|
||||
return $this->blockedResponse('ip', 'soft');
|
||||
}
|
||||
return [
|
||||
'level' => 'throttle',
|
||||
'message' => 'Too many requests. Please slow down.',
|
||||
'status' => $this->config->statusCodes['throttle'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a "bad outcome" for IP (e.g. controller calls this after failed auth).
|
||||
* Same escalation path as throttle violations.
|
||||
*/
|
||||
public function recordIpFailure(string $fingerprint): ?array
|
||||
{
|
||||
$blockInfo = $this->getIpBlock($fingerprint);
|
||||
if ($blockInfo) {
|
||||
$this->recordIpBlockHit($fingerprint, $blockInfo['level']);
|
||||
return $this->blockedResponse('ip', $blockInfo['level']);
|
||||
}
|
||||
|
||||
$violated = $this->incrementIpViolation($fingerprint);
|
||||
$cfg = $this->config->ipBlock;
|
||||
|
||||
if ($violated >= $cfg['violation_soft']) {
|
||||
$this->blockIp($fingerprint, 'soft');
|
||||
return $this->blockedResponse('ip', 'soft');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually block an IP at a given level.
|
||||
*/
|
||||
public function blockIp(string $fingerprint, string $level = 'soft'): void
|
||||
{
|
||||
$cfg = $this->config->ipBlock;
|
||||
$blockKey = $this->config->cacheKeys['ip_block'] . $fingerprint;
|
||||
|
||||
$duration = $this->blockDuration($cfg, $level);
|
||||
|
||||
$data = [
|
||||
'level' => $level,
|
||||
'blocked_at' => time(),
|
||||
'fingerprint'=> $fingerprint,
|
||||
'ip' => getRealClientIP(),
|
||||
];
|
||||
|
||||
// Duration 0 = store for 10 years (permanent until manual unblock)
|
||||
$ttl = $duration > 0 ? $duration : (10 * 365 * 24 * 3600);
|
||||
$this->cache->save($blockKey, $data, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually unblock an IP. Clears block, violations, and counters.
|
||||
*/
|
||||
public function unblockIp(string $fingerprint): void
|
||||
{
|
||||
$keys = $this->config->cacheKeys;
|
||||
$this->cache->delete($keys['ip_block'] . $fingerprint);
|
||||
$this->cache->delete($keys['ip_violations'] . $fingerprint);
|
||||
$this->cache->delete($keys['ip_count'] . $fingerprint);
|
||||
$this->cache->delete($keys['ip_block_hits'] . $fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current IP block info or null if not blocked.
|
||||
*/
|
||||
public function getIpBlock(string $fingerprint): ?array
|
||||
{
|
||||
$blockKey = $this->config->cacheKeys['ip_block'] . $fingerprint;
|
||||
$data = $this->cache->get($blockKey);
|
||||
return $data ?: null;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PUBLIC — USER LEVEL
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Check if a user (by email or mobile) is blocked.
|
||||
* Returns null on pass, or block response array on block.
|
||||
*/
|
||||
public function checkUser(string $identity): ?array
|
||||
{
|
||||
$blockInfo = $this->getUserBlock($identity);
|
||||
if ($blockInfo) {
|
||||
$this->recordUserBlockHit($identity, $blockInfo['level']);
|
||||
return $this->blockedResponse('user', $blockInfo['level']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a failed attempt for a user identity.
|
||||
* Called from controller after() or manually after a failed verification.
|
||||
* Handles escalation: free → soft → medium → hard
|
||||
*/
|
||||
public function recordUserFailure(string $identity, string $routeType = 'authApi'): ?array
|
||||
{
|
||||
$blockInfo = $this->getUserBlock($identity);
|
||||
|
||||
if ($blockInfo) {
|
||||
// Already blocked — count hit and maybe escalate
|
||||
$this->recordUserBlockHit($identity, $blockInfo['level']);
|
||||
return $this->blockedResponse('user', $blockInfo['level']);
|
||||
}
|
||||
|
||||
// Not blocked yet — increment violation count
|
||||
$violated = $this->incrementUserViolation($identity, $routeType);
|
||||
$cfg = $this->config->userBlock;
|
||||
$routeCfg = $this->config->{$routeType};
|
||||
|
||||
if ($violated >= $routeCfg['violation_soft']) {
|
||||
$this->blockUser($identity, 'soft');
|
||||
return $this->blockedResponse('user', 'soft');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually block a user identity at a given level.
|
||||
*/
|
||||
public function blockUser(string $identity, string $level = 'soft'): void
|
||||
{
|
||||
$cfg = $this->config->userBlock;
|
||||
$blockKey = $this->config->cacheKeys['user_block'] . $this->hashIdentity($identity);
|
||||
|
||||
$duration = $this->blockDuration($cfg, $level);
|
||||
$ttl = $duration > 0 ? $duration : (10 * 365 * 24 * 3600);
|
||||
|
||||
$data = [
|
||||
'level' => $level,
|
||||
'blocked_at' => time(),
|
||||
'identity' => $identity,
|
||||
];
|
||||
|
||||
$this->cache->save($blockKey, $data, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually unblock a user identity. Independent — does NOT touch IP block.
|
||||
*/
|
||||
public function unblockUser(string $identity): void
|
||||
{
|
||||
$keys = $this->config->cacheKeys;
|
||||
$hashed = $this->hashIdentity($identity);
|
||||
|
||||
$this->cache->delete($keys['user_block'] . $hashed);
|
||||
$this->cache->delete($keys['user_violations'] . $hashed);
|
||||
$this->cache->delete($keys['user_count'] . $hashed);
|
||||
$this->cache->delete($keys['user_block_hits'] . $hashed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user block info or null if not blocked.
|
||||
*/
|
||||
public function getUserBlock(string $identity): ?array
|
||||
{
|
||||
$blockKey = $this->config->cacheKeys['user_block'] . $this->hashIdentity($identity);
|
||||
$data = $this->cache->get($blockKey);
|
||||
return $data ?: null;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// USER THROTTLE (for JWT API routes — request count based)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Throttle check for a known user on JWT routes.
|
||||
* Increments request counter; if over limit records violation.
|
||||
*/
|
||||
public function checkUserThrottle(string $identity, string $routeType = 'jwtApi'): ?array
|
||||
{
|
||||
$blockCheck = $this->checkUser($identity);
|
||||
if ($blockCheck) {
|
||||
return $blockCheck;
|
||||
}
|
||||
|
||||
$cfg = $this->config->{$routeType};
|
||||
$hashed = $this->hashIdentity($identity);
|
||||
$countKey = $this->config->cacheKeys['user_count'] . $hashed;
|
||||
$count = (int) ($this->cache->get($countKey) ?? 0);
|
||||
|
||||
if ($count === 0) {
|
||||
$this->cache->save($countKey, 1, $cfg['window']);
|
||||
} else {
|
||||
$this->cache->save($countKey, $count + 1, $cfg['window']);
|
||||
}
|
||||
|
||||
if (($count + 1) > $cfg['limit']) {
|
||||
$violated = $this->incrementUserViolation($identity, $routeType);
|
||||
if ($violated >= $cfg['violation_soft']) {
|
||||
$this->blockUser($identity, 'soft');
|
||||
return $this->blockedResponse('user', 'soft');
|
||||
}
|
||||
return [
|
||||
'level' => 'throttle',
|
||||
'message' => 'Too many requests. Please slow down.',
|
||||
'status' => $this->config->statusCodes['throttle'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PRIVATE HELPERS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Increment IP violation counter and return new count.
|
||||
*/
|
||||
protected function incrementIpViolation(string $fingerprint): int
|
||||
{
|
||||
$key = $this->config->cacheKeys['ip_violations'] . $fingerprint;
|
||||
$count = (int) ($this->cache->get($key) ?? 0) + 1;
|
||||
// Keep violation record for the block window duration
|
||||
$this->cache->save($key, $count, $this->config->ipBlock['window'] * 10);
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a hit while IP is already blocked; escalate if thresholds met.
|
||||
*/
|
||||
protected function recordIpBlockHit(string $fingerprint, string $currentLevel): void
|
||||
{
|
||||
$cfg = $this->config->ipBlock;
|
||||
$hitKey = $this->config->cacheKeys['ip_block_hits'] . $fingerprint;
|
||||
$hits = (int) ($this->cache->get($hitKey) ?? 0) + 1;
|
||||
$this->cache->save($hitKey, $hits, 10 * 365 * 24 * 3600);
|
||||
|
||||
if ($currentLevel === 'soft' && $hits >= $cfg['medium_trigger']) {
|
||||
$this->cache->delete($hitKey);
|
||||
$this->blockIp($fingerprint, 'medium');
|
||||
} elseif ($currentLevel === 'medium' && $hits >= $cfg['hard_trigger']) {
|
||||
$this->cache->delete($hitKey);
|
||||
$this->blockIp($fingerprint, 'hard');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment user violation counter and return new count.
|
||||
*/
|
||||
protected function incrementUserViolation(string $identity, string $routeType): int
|
||||
{
|
||||
$hashed = $this->hashIdentity($identity);
|
||||
$key = $this->config->cacheKeys['user_violations'] . $hashed;
|
||||
$count = (int) ($this->cache->get($key) ?? 0) + 1;
|
||||
$window = $this->config->{$routeType}['window'] ?? 180;
|
||||
$this->cache->save($key, $count, $window * 10);
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a hit while user is already blocked; escalate if thresholds met.
|
||||
*/
|
||||
protected function recordUserBlockHit(string $identity, string $currentLevel): void
|
||||
{
|
||||
$cfg = $this->config->userBlock;
|
||||
$hashed = $this->hashIdentity($identity);
|
||||
$hitKey = $this->config->cacheKeys['user_block_hits'] . $hashed;
|
||||
$hits = (int) ($this->cache->get($hitKey) ?? 0) + 1;
|
||||
$this->cache->save($hitKey, $hits, 10 * 365 * 24 * 3600);
|
||||
|
||||
if ($currentLevel === 'soft' && $hits >= $cfg['medium_trigger']) {
|
||||
$this->cache->delete($hitKey);
|
||||
$this->blockUser($identity, 'medium');
|
||||
} elseif ($currentLevel === 'medium' && $hits >= $cfg['hard_trigger']) {
|
||||
$this->cache->delete($hitKey);
|
||||
$this->blockUser($identity, 'hard');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve block duration from config based on level.
|
||||
*/
|
||||
protected function blockDuration(array $cfg, string $level): int
|
||||
{
|
||||
return match ($level) {
|
||||
'soft' => $cfg['soft_duration'],
|
||||
'medium' => $cfg['medium_duration'],
|
||||
'hard' => $cfg['hard_duration'],
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a standardised blocked response array.
|
||||
*/
|
||||
protected function blockedResponse(string $type, string $level): array
|
||||
{
|
||||
$messages = [
|
||||
'soft' => 'Your access has been temporarily suspended. Please contact support.',
|
||||
'medium' => 'Your access has been restricted due to repeated violations.',
|
||||
'hard' => 'Your access has been permanently blocked. Please contact support.',
|
||||
];
|
||||
|
||||
return [
|
||||
'level' => $level,
|
||||
'type' => $type,
|
||||
'message' => $messages[$level] ?? 'Access denied.',
|
||||
'status' => $this->config->statusCodes[$level],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash user identity (email or mobile) for cache key safety.
|
||||
*/
|
||||
protected function hashIdentity(string $identity): string
|
||||
{
|
||||
return hash('sha256', strtolower(trim($identity)));
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
use Kint;
|
||||
|
||||
class PolicyTransactionModel extends Model
|
||||
{
|
||||
@ -3168,9 +3169,10 @@
|
||||
pt.installment,
|
||||
nhance_branch.branch_name as nhance_branch,
|
||||
|
||||
DATE_FORMAT(pt.policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(pcsd.pt_policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(pcsd.pt_policy_issue_date, '%b %Y') AS policy_issue_month,
|
||||
pt.month as statement_month,
|
||||
DATE_FORMAT(pcsd.pt_policy_issue_date, '%b %Y') AS statement_year_month,
|
||||
pcsd.pt_policy_issue_date as statement_month,
|
||||
|
||||
'no statement uploaded' AS statement_uploaded,
|
||||
|
||||
@ -3331,8 +3333,9 @@
|
||||
pt.installment,
|
||||
nhance_branch.branch_name as nhance_branch,
|
||||
|
||||
DATE_FORMAT(pt.policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(pcsd.pt_policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(IF(insq.month IS NULL, pcsd.pt_policy_issue_date, insq.month),'%b %Y') AS policy_issue_month,
|
||||
DATE_FORMAT(insq.month, '%b %Y') AS statement_year_month,
|
||||
insq.month as statement_month,
|
||||
|
||||
'statement uploaded' AS statement_uploaded,
|
||||
@ -3392,7 +3395,7 @@
|
||||
(pcsd.agreed_tp_per + pcsd.agreed_tep_per) AS agreed_tp_or_ter_per,
|
||||
|
||||
CASE
|
||||
WHEN insq.month = pt.month THEN pcsd.exp_amt
|
||||
WHEN DATE_FORMAT(insq.month, '%Y-%m') = DATE_FORMAT(pcsd.pt_policy_issue_date, '%Y-%m') THEN pcsd.exp_amt
|
||||
ELSE 0
|
||||
END AS total_irda_amt_2,
|
||||
|
||||
@ -3521,8 +3524,9 @@
|
||||
pt.installment,
|
||||
nhance_branch.branch_name as nhance_branch,
|
||||
|
||||
DATE_FORMAT(pt.policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(pcsd.pt_policy_issue_date, '%d %b %Y') AS policy_issue_date,
|
||||
DATE_FORMAT(IF(insq.month IS NULL, pcsd.pt_policy_issue_date, insq.month),'%b %Y') AS policy_issue_month,
|
||||
DATE_FORMAT(insq.month, '%b %Y') AS statement_year_month,
|
||||
insq.month as statement_month,
|
||||
|
||||
'statement uploaded' AS statement_uploaded,
|
||||
@ -3704,7 +3708,7 @@
|
||||
|
||||
|
||||
// $countofalldata = count($result);
|
||||
// dd($result);
|
||||
// Kint::dump($result);
|
||||
// dd($this->db->getLastQuery()->getQuery());
|
||||
|
||||
$keys = [];
|
||||
@ -3732,7 +3736,7 @@
|
||||
|
||||
// ✅ Stable key (NO reward flag)
|
||||
$key = implode('|', [
|
||||
$row['statement_month'],
|
||||
$row['statement_year_month'],
|
||||
$row['insurer_name'],
|
||||
$row['policy_no'],
|
||||
$endorsement_number,
|
||||
@ -3753,7 +3757,7 @@
|
||||
// Reindex final output
|
||||
$result = array_values($filtered);
|
||||
|
||||
// dd($countofalldata, $result);
|
||||
// dd($result);
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -3776,7 +3780,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// dd($totalIrdaMap);
|
||||
// dd($totalBilled, $totalIrdaMap);
|
||||
|
||||
$ptSeen = [];
|
||||
$final = [];
|
||||
@ -3785,8 +3789,10 @@
|
||||
$ptId = $row['pt_id'].'-'.$row['insurer_id'];
|
||||
if (!isset($ptSeen[$ptId])) {
|
||||
// First entry → set unbilled amount
|
||||
$row['unbilled_amount'] = (float) (($totalIrdaMap[$ptId] ?? 0) - ($totalBilled[$ptId] ?? 0)) ?? '0.00';
|
||||
|
||||
$row['unbilled_amount'] = round(
|
||||
(float) (($totalIrdaMap[$ptId] ?? 0) - ($totalBilled[$ptId] ?? 0)),
|
||||
2
|
||||
);
|
||||
$ptSeen[$ptId] = true;
|
||||
} else {
|
||||
// Other entries → zero
|
||||
|
||||
@ -60,52 +60,52 @@ class SalesActivityModel extends Model
|
||||
protected $skipValidation = false;
|
||||
|
||||
/**
|
||||
* Get activities by lead with user details
|
||||
* Get sales_activities by lead with user details
|
||||
*/
|
||||
public function getActivitiesByLead($leadId, $status = null)
|
||||
{
|
||||
$builder = $this->select('activities.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left')
|
||||
->where('activities.lead_id', $leadId);
|
||||
$builder = $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left')
|
||||
->where('sales_activities.lead_id', $leadId);
|
||||
|
||||
if ($status) {
|
||||
$builder->where('activities.status', $status);
|
||||
$builder->where('sales_activities.status', $status);
|
||||
}
|
||||
|
||||
return $builder->orderBy('activities.scheduled_date', 'DESC')->findAll();
|
||||
return $builder->orderBy('sales_activities.scheduled_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all activities with filters
|
||||
* Get all sales_activities with filters
|
||||
*/
|
||||
public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0)
|
||||
{
|
||||
$builder = $this->select('activities.*, actual_leads.company_name, user_profiles.username as assigned_to_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left');
|
||||
$builder = $this->select('sales_activities.*, actual_leads.company_name, user_profiles.first_name as assigned_to_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$builder->where('activities.status', $filters['status']);
|
||||
$builder->where('sales_activities.status', $filters['status']);
|
||||
}
|
||||
|
||||
if (!empty($filters['activity_type'])) {
|
||||
$builder->where('activities.activity_type', $filters['activity_type']);
|
||||
$builder->where('sales_activities.activity_type', $filters['activity_type']);
|
||||
}
|
||||
|
||||
if (!empty($filters['assigned_to'])) {
|
||||
$builder->where('activities.assigned_to', $filters['assigned_to']);
|
||||
$builder->where('sales_activities.assigned_to', $filters['assigned_to']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_from'])) {
|
||||
$builder->where('activities.scheduled_date >=', $filters['date_from']);
|
||||
$builder->where('sales_activities.scheduled_date >=', $filters['date_from']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_to'])) {
|
||||
$builder->where('activities.scheduled_date <=', $filters['date_to']);
|
||||
$builder->where('sales_activities.scheduled_date <=', $filters['date_to']);
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => $builder->orderBy('activities.scheduled_date', 'DESC')
|
||||
'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->limit($limit, $offset)->findAll(),
|
||||
'total' => $builder->countAllResults(false)
|
||||
];
|
||||
@ -125,7 +125,7 @@ class SalesActivityModel extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending activities count by user
|
||||
* Get pending sales_activities count by user
|
||||
*/
|
||||
public function getPendingActivitiesCount($userId)
|
||||
{
|
||||
@ -136,18 +136,18 @@ class SalesActivityModel extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upcoming activities for a user
|
||||
* Get upcoming sales_activities for a user
|
||||
*/
|
||||
public function getUpcomingActivities($userId, $days = 7, $limit = 10)
|
||||
{
|
||||
$endDate = date('Y-m-d H:i:s', strtotime("+{$days} days"));
|
||||
|
||||
return $this->select('activities.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left')
|
||||
->where('activities.assigned_to', $userId)
|
||||
->where('activities.status', 'pending')
|
||||
->where('activities.scheduled_date <=', $endDate)
|
||||
->orderBy('activities.scheduled_date', 'ASC')
|
||||
return $this->select('sales_activities.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left')
|
||||
->where('sales_activities.assigned_to', $userId)
|
||||
->where('sales_activities.status', 'pending')
|
||||
->where('sales_activities.scheduled_date <=', $endDate)
|
||||
->orderBy('sales_activities.scheduled_date', 'ASC')
|
||||
->limit($limit)
|
||||
->findAll();
|
||||
}
|
||||
@ -157,10 +157,10 @@ class SalesActivityModel extends Model
|
||||
*/
|
||||
public function getActivityTimeline($leadId)
|
||||
{
|
||||
return $this->select('activities.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left')
|
||||
->where('activities.lead_id', $leadId)
|
||||
->orderBy('activities.scheduled_date', 'DESC')
|
||||
return $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left')
|
||||
->where('sales_activities.lead_id', $leadId)
|
||||
->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Lead Model
|
||||
* Handles all operations related to actual_leads table
|
||||
* Handles all operations related to sales_actual_leads table
|
||||
*/
|
||||
class SalesActualLeadModel extends Model
|
||||
{
|
||||
@ -63,9 +63,9 @@ class SalesActualLeadModel extends Model
|
||||
*/
|
||||
public function getLeadWithUser($leadId)
|
||||
{
|
||||
return $this->select('actual_leads.*, user_profiles.username as assigned_to_name, user_profiles.email as assigned_to_email')
|
||||
->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left')
|
||||
->where('actual_leads.lead_id', $leadId)
|
||||
return $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name, user_profiles.email as assigned_to_email')
|
||||
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left')
|
||||
->where('sales_actual_leads.lead_id', $leadId)
|
||||
->first();
|
||||
}
|
||||
|
||||
@ -74,22 +74,22 @@ class SalesActualLeadModel extends Model
|
||||
*/
|
||||
public function getLeadsWithFilters($filters = [], $limit = 10, $offset = 0)
|
||||
{
|
||||
$builder = $this->select('actual_leads.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left');
|
||||
$builder = $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$builder->where('actual_leads.status', $filters['status']);
|
||||
$builder->where('sales_actual_leads.status', $filters['status']);
|
||||
}
|
||||
|
||||
if (!empty($filters['assigned_to'])) {
|
||||
$builder->where('actual_leads.assigned_to', $filters['assigned_to']);
|
||||
$builder->where('sales_actual_leads.assigned_to', $filters['assigned_to']);
|
||||
}
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$builder->groupStart()
|
||||
->like('actual_leads.company_name', $filters['search'])
|
||||
->orLike('actual_leads.email', $filters['search'])
|
||||
->orLike('actual_leads.phone', $filters['search'])
|
||||
->like('sales_actual_leads.company_name', $filters['search'])
|
||||
->orLike('sales_actual_leads.email', $filters['search'])
|
||||
->orLike('sales_actual_leads.phone', $filters['search'])
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
|
||||
@ -53,10 +53,10 @@ class SalesLeadNoteModel extends Model
|
||||
*/
|
||||
public function getNotesByLead($leadId)
|
||||
{
|
||||
return $this->select('lead_notes.*, user_profiles.username')
|
||||
->join('user_profiles', 'user_profiles.id = lead_notes.user_id', 'left')
|
||||
->where('lead_notes.lead_id', $leadId)
|
||||
->orderBy('lead_notes.created_at', 'DESC')
|
||||
return $this->select('sales_lead_notes.*, user_profiles.first_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_lead_notes.created_by', 'left')
|
||||
->where('sales_lead_notes.lead_id', $leadId)
|
||||
->orderBy('sales_lead_notes.created_at', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
@ -65,10 +65,10 @@ class SalesLeadNoteModel extends Model
|
||||
*/
|
||||
public function getNotesByUser($userId, $limit = 20, $offset = 0)
|
||||
{
|
||||
return $this->select('lead_notes.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = lead_notes.lead_id', 'left')
|
||||
->where('lead_notes.user_id', $userId)
|
||||
->orderBy('lead_notes.created_at', 'DESC')
|
||||
return $this->select('sales_lead_notes.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = sales_lead_notes.lead_id', 'left')
|
||||
->where('sales_lead_notes.created_by', $userId)
|
||||
->orderBy('sales_lead_notes.created_at', 'DESC')
|
||||
->limit($limit, $offset)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
12
app/Models/TpaConfigModel.php
Normal file
12
app/Models/TpaConfigModel.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class TpaConfigModel extends Model {
|
||||
protected $table = 'tpa_log_config';
|
||||
protected $returnType = 'array';
|
||||
}
|
||||
|
||||
?>
|
||||
@ -91,7 +91,7 @@
|
||||
|
||||
<div class="form-group text-right m-b-0">
|
||||
<button type="submit" class="btn btn-primary waves-effect waves-light mr-1"
|
||||
id="g_drive_file_upload_sbt_btn">Submit</button>
|
||||
id="g_drive_file_upload_sbt_btn">Save</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -287,6 +287,8 @@ input:checked + .slider:before {
|
||||
var message = (PrimaryKey === '') ? 'Client General Info Created successfully' : 'Client General Info Updated successfully';
|
||||
toastr.success(message, 'Success');
|
||||
$submitButton.prop('disabled', false);
|
||||
let client_name = res.data.client_name ? (' - ' + res.data.client_name) : '';
|
||||
$('#client_heading').text(client_name);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
@ -273,9 +273,9 @@
|
||||
<div class="card-body">
|
||||
<div class="row" style="padding-bottom: 10px;">
|
||||
<div class="col-6" style="align-self: center;">
|
||||
<h4 style="position: relative;">Client Onboarding <?php if (isset($client)) {
|
||||
<h4 style="position: relative;">Client Onboarding <span id="client_heading"><?php if (isset($client)) {
|
||||
echo ' - ' . $client['client_name'];
|
||||
} ?></h4>
|
||||
} ?></h4></span>
|
||||
</div>
|
||||
<div class="col-6" style="text-align: right;">
|
||||
<a href="<?= base_url("client/list"); ?>"><i class="mdi mdi-arrow-left" style="font-size: 17px;"></i></a>
|
||||
|
||||
@ -291,7 +291,7 @@ input:checked + .slider_blue::before {
|
||||
</div> -->
|
||||
|
||||
<div class="form-group col-md-12 EB">
|
||||
<label for="disclaimer">Disclaimer<span class="text-danger">*</span></label>
|
||||
<label for="disclaimer">Disclaimer<span class="text-danger"></span></label>
|
||||
<textarea class="form-control" placeholder="Enter Disclaimer" name="disclaimer" id="disclaimer" ></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
73
app/Views/gsheet_editor.php
Normal file
73
app/Views/gsheet_editor.php
Normal file
@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Google Sheet Editor</title>
|
||||
<style>
|
||||
body { font-family: Arial; padding: 10px; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
td { border: 1px solid #ccc; padding: 6px; min-width: 80px; }
|
||||
td[contenteditable] { background: #fffde7; }
|
||||
button { margin-right: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h3>Google Sheet Editor</h3>
|
||||
|
||||
<button onclick="save()">💾 Save</button>
|
||||
<button onclick="download()">⬇ Download</button>
|
||||
|
||||
<hr>
|
||||
|
||||
<table id="sheet"></table>
|
||||
|
||||
<script>
|
||||
alert('first');
|
||||
const sheetId = "<?= esc($sheetId) ?>";
|
||||
alert('second');
|
||||
/* ---------- LOAD ---------- */
|
||||
fetch('<?php echo base_url() ?>' + `/sheet/${sheetId}/fetch`)
|
||||
.then(r => r.json())
|
||||
.then(r => r.json())
|
||||
.then(render);
|
||||
|
||||
function render(data) {
|
||||
alert('data');
|
||||
console.log('data');
|
||||
console.log(data);
|
||||
const table = document.getElementById('sheet');
|
||||
table.innerHTML = '';
|
||||
|
||||
data.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
row.forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.contentEditable = true;
|
||||
td.innerText = cell ?? '';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
table.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- SAVE ---------- */
|
||||
function save() {
|
||||
const rows = [...document.querySelectorAll('tr')]
|
||||
.map(tr => [...tr.children].map(td => td.innerText));
|
||||
|
||||
fetch(`/sheet/${sheetId}/save`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(rows)
|
||||
})
|
||||
.then(() => alert('Saved successfully'));
|
||||
}
|
||||
|
||||
/* ---------- DOWNLOAD ---------- */
|
||||
function download() {
|
||||
window.location.href = '<?php echo base_url() ?>' + `/sheet/${sheetId}/download`;
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -2,482 +2,126 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= esc($title) ?></title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--dark: #1e293b;
|
||||
--bg: #f8fafc;
|
||||
}
|
||||
body { font-family: 'Inter', system-ui, sans-serif; background: var(--bg); padding: 20px; color: var(--dark); }
|
||||
.container { max-width: 1300px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden; }
|
||||
|
||||
/* Header & Navigation */
|
||||
.header { background: #fff; padding: 20px; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; }
|
||||
.filter-section { padding: 20px; background: #fff; border-bottom: 1px solid #e2e8f0; }
|
||||
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 15px; align-items: center; }
|
||||
|
||||
.btn { padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 13px; font-weight: 500; border: 1px solid #e2e8f0; background: #fff; color: #64748b; transition: all 0.2s; }
|
||||
.btn:hover { background: #f1f5f9; border-color: #cbd5e1; }
|
||||
.btn.active { background: var(--primary); color: white; border-color: var(--primary); }
|
||||
|
||||
/* body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
} */
|
||||
/* Search Box */
|
||||
.search-container { position: relative; margin-top: 10px; }
|
||||
.search-input { width: 100%; padding: 12px 15px; border-radius: 8px; border: 1px solid #e2e8f0; background: #f8fafc; outline: none; font-size: 14px; }
|
||||
.search-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); }
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
/* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); */
|
||||
color: black;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-top: 15px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.file-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 8px 16px;
|
||||
border: 2px solid #dee2e6;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
border-color: #667eea;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
width: 300px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.log-entries {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
background: white;
|
||||
border-left: 4px solid #6c757d;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.log-entry.critical {
|
||||
border-left-color: #dc3545;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
border-left-color: #fd7e14;
|
||||
background: #fff8f5;
|
||||
}
|
||||
|
||||
.log-entry.warning {
|
||||
border-left-color: #ffc107;
|
||||
background: #fffef5;
|
||||
}
|
||||
|
||||
.log-entry.info {
|
||||
border-left-color: #17a2b8;
|
||||
background: #f5fcfd;
|
||||
}
|
||||
|
||||
.log-entry.debug {
|
||||
border-left-color: #6c757d;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.level-critical {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-error {
|
||||
background: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-warning {
|
||||
background: #ffc107;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.level-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-debug {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.log-date {
|
||||
color: #6c757d;
|
||||
font-size: 13px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.no-logs {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 0px;
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
border-width: 1px;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-card p {
|
||||
opacity: 0.9;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
/* Log Console View */
|
||||
.log-viewport { background: #0f172a; padding: 20px; max-height: 650px; overflow-y: auto; }
|
||||
.log-card { margin-bottom: 12px; padding: 15px; border-radius: 8px; font-family: 'Fira Code', 'Courier New', monospace; font-size: 13px; border-left: 4px solid #475569; position: relative; background: #1e293b; }
|
||||
|
||||
.log-meta { display: flex; justify-content: space-between; margin-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; }
|
||||
.log-date { color: #94a3b8; font-size: 12px; }
|
||||
.log-level { font-weight: 700; text-transform: uppercase; font-size: 11px; padding: 2px 8px; border-radius: 4px; }
|
||||
|
||||
.level-critical { border-left-color: #ef4444; } .level-critical .log-level { background: #ef4444; color: #fff; }
|
||||
.level-error { border-left-color: #f87171; } .level-error .log-level { background: #f87171; color: #fff; }
|
||||
.level-info { border-left-color: #10b981; } .level-info .log-level { background: #10b981; color: #fff; }
|
||||
.level-warning { border-left-color: #f59e0b; } .level-warning .log-level { background: #f59e0b; color: #fff; }
|
||||
|
||||
.log-msg { color: #e2e8f0; line-height: 1.6; white-space: pre-wrap; word-break: break-all; }
|
||||
|
||||
.empty-state { text-align: center; color: #94a3b8; padding: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h1>
|
||||
📄 <?= esc($filename) ?>
|
||||
</h1>
|
||||
|
||||
<div style="display:flex; gap:10px; margin-bottom:15px;">
|
||||
<?php if ($prevFile): ?>
|
||||
<a href="<?= base_url('logs/view/' . $prevFile) ?>" class="btn btn-primary">Previous</a>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-secondary" disabled>⬅ Previous Day</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($nextFile): ?>
|
||||
<a href="<?= base_url('logs/view/' . $nextFile) ?>" class="btn btn-primary">Next</a>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-secondary" disabled>Next Day ➡</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="file-info">
|
||||
<div class="file-info-item">
|
||||
<strong>Size:</strong> <?= esc($fileSize) ?>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong>Last Modified:</strong> <?= esc($lastModified) ?>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong>Total Entries:</strong> <?= count($logEntries) ?>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="content">
|
||||
<div class="stats">
|
||||
|
||||
|
||||
|
||||
<!--<div class="stat-card">
|
||||
<h3 id="total-count"><?= count($logEntries) ?></h3>
|
||||
<p>Total Entries</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="critical-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'CRITICAL')) ?>
|
||||
</h3>
|
||||
<p>Critical</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="error-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'ERROR')) ?>
|
||||
</h3>
|
||||
<p>Errors</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="warning-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'WARNING')) ?>
|
||||
</h3>
|
||||
<p>Warnings</p>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA CLAIM PUSH SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA CLAIM PUSH FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA ID PULL SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Tpa no pull success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA ID PULL FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Tpa no pull Failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'CLAIM STATUS SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim status fetch success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'CLAIM STATUS FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim status fetch failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'Ecard Request PUSH SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Ecard Request</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="filter-group">
|
||||
<strong>Filter:</strong>
|
||||
<button class="filter-btn active" data-level="all">All</button>
|
||||
<button class="filter-btn" data-level="critical">Critical</button>
|
||||
<button class="filter-btn" data-level="error">Error</button>
|
||||
<button class="filter-btn" data-level="warning">Warning</button>
|
||||
<button class="filter-btn" data-level="info">Info</button>
|
||||
<button class="filter-btn" data-level="debug">Debug</button>
|
||||
</div>
|
||||
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search log messages...">
|
||||
</div>
|
||||
|
||||
<?php if (empty($logEntries)): ?>
|
||||
<div class="no-logs">
|
||||
<h3>No Log Entries Found</h3>
|
||||
<p>This log file is empty or couldn't be parsed.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="log-entries" id="logEntries">
|
||||
<?php foreach ($logEntries as $entry): ?>
|
||||
<?php
|
||||
$level = strtolower($entry['level']);
|
||||
$levelClass = 'level-' . $level;
|
||||
?>
|
||||
<div class="log-entry <?= $level ?>" data-level="<?= $level ?>">
|
||||
<div class="log-header">
|
||||
<span class="log-level <?= $levelClass ?>">
|
||||
<?= esc(strtoupper($entry['level'])) ?>
|
||||
</span>
|
||||
<span class="log-date"><?= esc($entry['date']) ?></span>
|
||||
</div>
|
||||
<div class="log-message"><?= esc($entry['message']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2 style="font-size: 1.25rem;">📄 <?= esc($filename) ?></h2>
|
||||
<div class="btn-group">
|
||||
<a href="<?= base_url('logs/view/'.$prevFile."?tpa=$selectedTpa&key=$selectedKey") ?>" class="btn <?= !$prevFile ? 'disabled' : '' ?>">← Previous Day</a>
|
||||
<a href="<?= base_url('logs/view/'.$nextFile."?tpa=$selectedTpa&key=$selectedKey") ?>" class="btn <?= !$nextFile ? 'disabled' : '' ?>">Next Day →</a>
|
||||
<a href="<?= base_url('logs') ?>" class="btn">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Filter functionality
|
||||
const filterBtns = document.querySelectorAll('.filter-btn');
|
||||
const logEntries = document.querySelectorAll('.log-entry');
|
||||
const searchBox = document.getElementById('searchBox');
|
||||
<div class="filter-section">
|
||||
<div class="btn-group">
|
||||
<span style="font-weight: 600; min-width: 80px;">Select TPA:</span>
|
||||
<?php foreach($tpaConfigs as $conf): ?>
|
||||
<a href="<?= base_url("logs/view/$filename?tpa=".$conf['tpa_name']) ?>"
|
||||
class="btn <?= $selectedTpa == $conf['tpa_name'] ? 'active' : '' ?>">
|
||||
<?= esc($conf['tpa_name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<a href="<?= base_url("logs/view/$filename") ?>" class="btn" style="color: #ef4444;">Clear Filters</a>
|
||||
</div>
|
||||
|
||||
let currentFilter = 'all';
|
||||
<?php if($selectedTpa): ?>
|
||||
<div class="btn-group">
|
||||
<span style="font-weight: 600; min-width: 80px;">Action:</span>
|
||||
<?php foreach($dynamicKeys as $columnName => $label): ?>
|
||||
<a href="<?= base_url("logs/view/$filename?tpa=$selectedTpa&key=$columnName") ?>"
|
||||
class="btn <?= $selectedKey == $columnName ? 'active' : '' ?>">
|
||||
<?= esc($label) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
filterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentFilter = btn.dataset.level;
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
<form action="<?= base_url("logs/view/$filename") ?>" method="get" class="search-container" style="margin-top: 15px;">
|
||||
<input type="hidden" name="tpa" value="<?= esc($selectedTpa) ?>">
|
||||
<input type="hidden" name="key" value="<?= esc($selectedKey) ?>">
|
||||
<input type="text" name="search" class="search-input"
|
||||
placeholder="🔍 Search in filtered logs..." value="<?= esc($searchTerm) ?>">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
searchBox.addEventListener('input', applyFilters);
|
||||
|
||||
function applyFilters() {
|
||||
const searchTerm = searchBox.value.toLowerCase();
|
||||
|
||||
logEntries.forEach(entry => {
|
||||
const level = entry.dataset.level;
|
||||
const message = entry.querySelector('.log-message').textContent.toLowerCase();
|
||||
|
||||
const matchesFilter = currentFilter === 'all' || level === currentFilter;
|
||||
const matchesSearch = message.includes(searchTerm);
|
||||
|
||||
if (matchesFilter && matchesSearch) {
|
||||
entry.classList.remove('hidden');
|
||||
} else {
|
||||
entry.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="log-viewport">
|
||||
<?php if(empty($logEntries)): ?>
|
||||
<div class="empty-state">
|
||||
<h3>No Logs Found</h3>
|
||||
<p>Try adjusting your TPA filters or search term.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach($logEntries as $entry): ?>
|
||||
<?php $lvl = strtolower($entry['level']); ?>
|
||||
<div class="log-card level-<?= $lvl ?>">
|
||||
<div class="log-meta">
|
||||
<span class="log-level"><?= esc($entry['level']) ?></span>
|
||||
<span class="log-date"><?= esc($entry['date']) ?></span>
|
||||
</div>
|
||||
<div class="log-msg"><?= esc($entry['message']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-submit search after typing (optional)
|
||||
let typingTimer;
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
searchInput.addEventListener('keyup', () => {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(() => {
|
||||
searchInput.closest('form').submit();
|
||||
}, 800);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -151,7 +151,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right m-b-0" id="hide_smbt_btn">
|
||||
<button type="submit" class="btn btn-primary" id="btnGridSubmit_2">Submit</button>
|
||||
<button type="submit" class="btn btn-primary savebuttonChange" id="btnGridSubmit_2">Save</button>
|
||||
<a class="btn btn-secondary waves-effect waves-light mr-1" id="btnGridrename" onclick="renameRackRateTab(this, null)">Rename</a>
|
||||
</div>
|
||||
</form>
|
||||
@ -371,9 +371,11 @@ $('body').on('click', '.btnPolicyModel', function()
|
||||
$('.loader-mask').fadeIn();
|
||||
|
||||
if (policy_type_string == 'GPA' || policy_type_id == 6 || policy_type_id == 7) {
|
||||
$('#add_new_rack_rate_tab_div').hide();
|
||||
$('.savebuttonChange').text('Submit');
|
||||
$('#add_new_rack_rate_tab_div').hide('');
|
||||
$('#rack_rate_one').hide();
|
||||
} else {
|
||||
$('.savebuttonChange').text('Save');
|
||||
$('#add_new_rack_rate_tab_div').show();
|
||||
$('#rack_rate_one').show();
|
||||
}
|
||||
@ -3413,7 +3415,7 @@ function appendNewTab(tabNameData = null, data = null)
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right m-b-0" id="hide_smbt_btn_${tabId}">
|
||||
<button type="submit" class="btn btn-primary" id="btnGridSubmit_2_${tabId}">Submit</button>
|
||||
<button type="submit" class="btn btn-primary" id="btnGridSubmit_2_${tabId}">Save</button>
|
||||
<a class="btn btn-danger waves-effect waves-light mr-1" id="btnGridremove_2_${tabId}" onclick="removeTab(this, '${tabId}', '${tabName}')">Delete</a>
|
||||
<a class="btn btn-secondary waves-effect waves-light mr-1" id="btnGridrename_${tabId}" onclick="renameRackRateTab(this, '${tabId}', '${tabName}')">Rename</a>
|
||||
</div>
|
||||
|
||||
@ -2713,13 +2713,10 @@
|
||||
if(allocg != 'EB'){
|
||||
tp = parseFloat($('#tp_premium_' + input).val()) || 0;
|
||||
}
|
||||
// do not remove this commented item
|
||||
// if(bap == 'Motor'){
|
||||
// tp = parseFloat($('#tp_premium_' + input).val()) || 0;
|
||||
// }
|
||||
|
||||
var tep = 0;
|
||||
if(bap == 'Fire' || bap == 'Marine Cargo' || bap == 'Marine Hull'){
|
||||
// if(bap == 'Fire' || bap == 'Marine Cargo' || bap == 'Marine Hull'){
|
||||
if(allocg != 'EB'){
|
||||
tep = parseFloat($('#ter_premium_' + input).val()) || 0;
|
||||
}
|
||||
|
||||
@ -2732,7 +2729,8 @@
|
||||
}
|
||||
|
||||
var cotep = 0;
|
||||
if(bap == 'Fire' || bap == 'Marine Cargo' || bap == 'Marine Hull'){
|
||||
// if(bap == 'Fire' || bap == 'Marine Cargo' || bap == 'Marine Hull'){
|
||||
if(allocg != 'EB'){
|
||||
cotep = parseFloat($('#co_ter_premium_' + input).val()) || 0;
|
||||
}
|
||||
|
||||
|
||||
177
app/Views/sales/branch_level_dashboard_view.php
Normal file
177
app/Views/sales/branch_level_dashboard_view.php
Normal file
@ -0,0 +1,177 @@
|
||||
<style>
|
||||
.dash-container { padding: 25px; background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||
.top-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; }
|
||||
|
||||
.stat-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
|
||||
.card { background: white; padding: 25px; border-radius: 12px; border: 1px solid #edf2f7; box-shadow: 0 2px 4px rgba(0,0,0,0.02); }
|
||||
.stat-val { font-size: 28px; font-weight: 700; color: #1a202c; }
|
||||
.stat-label { color: #718096; font-size: 14px; margin-top: 4px; font-weight: 500; }
|
||||
.stat-change { font-size: 11px; margin-top: 8px; font-weight: 600; }
|
||||
.text-success { color: #48bb78; }
|
||||
|
||||
.main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 30px; }
|
||||
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
|
||||
.team-member { display: flex; align-items: center; padding: 15px 0; border-bottom: 1px solid #f1f5f9; }
|
||||
.member-img { width: 40px; height: 40px; border-radius: 50%; background: #ff6b35; color: white; margin-right: 12px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 16px; }
|
||||
.act-stats { text-align: right; }
|
||||
.act-stats .total { font-weight: 700; font-size: 13px; color: #1a202c; }
|
||||
.act-stats .done { font-size: 11px; color: #38a169; font-weight: 600; margin-top: 2px; }
|
||||
|
||||
.breakdown-card { margin-top: 20px; }
|
||||
.breakdown-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 13px; }
|
||||
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 8px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th { text-align: left; padding: 12px; color: #718096; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid #edf2f7; }
|
||||
td { padding: 15px 12px; border-bottom: 1px solid #f1f5f9; font-size: 14px; vertical-align: middle; }
|
||||
|
||||
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; display: inline-block; }
|
||||
.status-completed { background: #f0fff4; color: #38a169; }
|
||||
.status-pending { background: #fffaf0; color: #dd6b20; }
|
||||
</style>
|
||||
|
||||
<div class="dash-container">
|
||||
<div class="top-header">
|
||||
<div>
|
||||
<h2 style="font-weight: 800; color: #1a202c; font-size: 24px;">Dashboard</h2>
|
||||
<p style="color: #718096; font-size: 14px; margin-top: 4px;">Overview of your branch</p>
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<input type="text" placeholder="Search leads, activities....." style="background:white; padding:12px 20px; border-radius:10px; border:1px solid #e2e8f0; width: 350px; font-size: 13px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-cards">
|
||||
<div class="card">
|
||||
<div class="stat-val">4</div>
|
||||
<div class="stat-label">Total Leads</div>
|
||||
<div class="stat-change text-success">↑ 12% this month</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="stat-val">6</div>
|
||||
<div class="stat-label">Total Activities</div>
|
||||
<div class="stat-change text-success">↑ 5% this month</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="stat-val">1</div>
|
||||
<div class="stat-label">Completed Activities</div>
|
||||
<div class="stat-change text-success">↑ 15% this month</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="stat-val">₹15.0L</div>
|
||||
<div class="stat-label">Pipeline Value</div>
|
||||
<div class="stat-change text-success">↑ 18% this month</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<div class="card">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">Recent Activities - All Team</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;">6 activities</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Activity & Assigned</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Venba Infotech</strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">CALL</div>
|
||||
<div style="font-size: 12px; color: #718096;">John Doe</div>
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;">20 Feb 2026</td>
|
||||
<td><span class="status-pill status-completed">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Acme Corporation</strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">EMAIL</div>
|
||||
<div style="font-size: 12px; color: #718096;">John Doe</div>
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;">20 Feb 2026</td>
|
||||
<td><span class="status-pill status-pending">Pending</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<h3 style="font-size: 16px; font-weight: 700; margin-bottom: 15px;">Sales Team Performance</h3>
|
||||
<div class="team-member">
|
||||
<div class="member-img" style="background: #ff6b35;">V</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 700; font-size: 14px;">Venkat 3.0</div>
|
||||
<div style="font-size: 11px; color: #718096;">Sales Manager</div>
|
||||
</div>
|
||||
<div class="act-stats">
|
||||
<div class="total">6 acts</div>
|
||||
<div class="done">1 done</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="team-member">
|
||||
<div class="member-img" style="background: #4a5568;">P</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 700; font-size: 14px;">Pavi_the_Staff V</div>
|
||||
<div style="font-size: 11px; color: #718096;">Sales Staff</div>
|
||||
</div>
|
||||
<div class="act-stats">
|
||||
<div class="total">0 acts</div>
|
||||
<div class="done">0 done</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card breakdown-card">
|
||||
<h3 style="font-size: 15px; font-weight: 700; margin-bottom: 20px;">Activity Breakdown</h3>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #ff6b35;"></span> Call</span><strong>42%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #48bb78;"></span> Email</span><strong>25%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #4299e1;"></span> Meeting</span><strong>18%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #ecc94b;"></span> Visit</span><strong>10%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #9f7aea;"></span> Demo</span><strong>5%</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">All Leads Overview</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Status</th>
|
||||
<th>Assigned To</th>
|
||||
<th>Activities</th>
|
||||
<th>Opportunities</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Venba Infotech</strong></td>
|
||||
<td><span style="background: #edf2f7; color: #4a5568; font-size: 10px; padding: 3px 10px; border-radius: 12px; font-weight: 700; text-transform: uppercase;">New</span></td>
|
||||
<td style="color: #4a5568;">John Doe</td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Acme Corporation</strong></td>
|
||||
<td><span style="background: #fff3e0; color: #f57c00; font-size: 10px; padding: 3px 10px; border-radius: 12px; font-weight: 700; text-transform: uppercase;">Potential</span></td>
|
||||
<td style="color: #4a5568;">John Doe</td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;">0</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
93
app/Views/sales/sales_manager_level_dashboard.php
Normal file
93
app/Views/sales/sales_manager_level_dashboard.php
Normal file
@ -0,0 +1,93 @@
|
||||
<style>
|
||||
.my-dash { padding: 25px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #fff; }
|
||||
.target-card { background: #1e252b; color: white; padding: 30px; border-radius: 15px; position: relative; margin-bottom: 30px; }
|
||||
.target-amount { font-size: 32px; font-weight: bold; margin: 10px 0; }
|
||||
.progress-container { background: #333; height: 8px; border-radius: 4px; margin: 20px 0; overflow: hidden; position: relative; }
|
||||
.progress-bar { background: #ff6b35; height: 100%; border-radius: 4px; }
|
||||
|
||||
.stats-row { display: flex; gap: 15px; margin-top: 25px; }
|
||||
.stat-box { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; flex: 1; text-align: left; border: 1px solid rgba(255,255,255,0.1); }
|
||||
.stat-box .num { font-size: 20px; font-weight: bold; display: block; margin-bottom: 2px; }
|
||||
.stat-box .lbl { font-size: 11px; color: #999; }
|
||||
|
||||
.chart-circle { position: absolute; right: 40px; top: 35px; width: 75px; height: 75px; border: 6px solid #333; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px; border-top-color: #ff6b35; }
|
||||
|
||||
.dashboard-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 25px; }
|
||||
.list-card { border: 1px solid #f0f0f0; border-radius: 12px; padding: 25px; background: #fff; }
|
||||
.list-item { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #f9f9f9; }
|
||||
.badge-done { background: #00c853; color: white; padding: 5px 15px; border-radius: 8px; font-size: 11px; font-weight: 600; cursor: pointer; border: none; }
|
||||
|
||||
.lead-initial { width: 35px; height: 35px; background: #f0f0f0; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #ff6b35; }
|
||||
.status-pill { font-size: 10px; padding: 2px 10px; border-radius: 12px; font-weight: 600; }
|
||||
</style>
|
||||
|
||||
<div class="my-dash">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:25px;">
|
||||
<div>
|
||||
<h1 style="font-size:22px; margin:0; font-weight: 700;">My Dashboard</h1>
|
||||
<p style="color:#666; font-size:13px; margin-top: 4px;">Welcome back, <?= $user_name ?></p>
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<input type="text" placeholder="Search leads, activities..." style="background:#f5f5f5; padding:10px 20px; border-radius:8px; border:none; width: 250px; font-size: 13px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="target-card">
|
||||
<div style="font-size:11px; color:#999; letter-spacing: 1px; font-weight: 600;">YEARLY TARGET 2025</div>
|
||||
<div class="target-amount">₹<?= number_format($target / 100000, 1) ?>L</div>
|
||||
<div style="font-size:12px; color:#777;">Apr 2025 - Mar 2026</div>
|
||||
|
||||
<div class="chart-circle">
|
||||
<span style="color:#ff6b35"><?= $percent ?>%</span>
|
||||
</div>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" style="width: <?= $percent ?>%"></div>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:12px; color: #999;">
|
||||
<span>Achieved <strong style="color:white">₹<?= number_format($achieved / 100000, 1) ?>L</strong></span>
|
||||
<span>Remaining <strong style="color:white">₹<?= number_format($remaining / 100000, 1) ?>L</strong></span>
|
||||
</div>
|
||||
|
||||
<div class="stats-row">
|
||||
<div class="stat-box"><span class="num"><?= $acts['total'] ?></span><span class="lbl">My Activities</span></div>
|
||||
<div class="stat-box"><span class="num"><?= $acts['pending'] ?></span><span class="lbl">Pending</span></div>
|
||||
<div class="stat-box"><span class="num"><?= $acts['completed'] ?></span><span class="lbl">Completed</span></div>
|
||||
<div class="stat-box"><span class="num"><?= $lead_count ?></span><span class="lbl">My Leads</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="list-card">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:15px; align-items: center;">
|
||||
<h3 style="font-size:16px; font-weight: 700; margin: 0;">My Upcoming Activities</h3>
|
||||
<span style="color:#ff6b35; font-size:12px; font-weight: 600;"><?= $acts['pending'] ?> pending</span>
|
||||
</div>
|
||||
<?php foreach($upcoming as $u): ?>
|
||||
<div class="list-item">
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:14px; color: #333;"><?= $u['company_name'] ?></div>
|
||||
<div style="font-size:11px; color:#999; margin-top: 3px;"><?= strtoupper($u['activity_type'] ?? 'EMAIL') ?> - <?= date('d Mar Y', strtotime($u['scheduled_date'])) ?></div>
|
||||
</div>
|
||||
<button class="badge-done">✓ Done</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="list-card">
|
||||
<h3 style="font-size:16px; font-weight: 700; margin-bottom:20px;">My Recent Leads</h3>
|
||||
<?php foreach($recent_leads as $rl): ?>
|
||||
<div class="list-item">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div class="lead-initial"><?= substr($rl['company_name'], 0, 1) ?></div>
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:14px; color: #333;"><?= $rl['company_name'] ?></div>
|
||||
<div style="font-size:11px; color:#999; margin-top: 2px;"><?= $rl['email'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-pill" style="background: #e3f2fd; color: #1976d2;"><?= $rl['status'] ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
356
app/Views/sales/tracker_view.php
Normal file
356
app/Views/sales/tracker_view.php
Normal file
@ -0,0 +1,356 @@
|
||||
<style>
|
||||
/* POC Exact Styling */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||||
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; height: 100vh; }
|
||||
.top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
||||
.search-input { width: 400px; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; }
|
||||
.search-input:focus { border-color: #ff6b35; }
|
||||
.btn-primary { background: #ff6b35; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||||
.btn-primary:hover { background: #ff5722; transform: translateY(-1px); }
|
||||
|
||||
/* Filter Tabs */
|
||||
.filter-tabs { display: flex; gap: 10px; padding: 20px 30px; background: #f5f5f5; }
|
||||
.tab { padding: 10px 20px; border-radius: 20px; cursor: pointer; font-size: 14px; background: white; color: #666; border: 1px solid #e0e0e0; transition: all 0.2s; }
|
||||
.tab.active { background: #ff6b35; color: white; border-color: #ff6b35; }
|
||||
|
||||
/* Leads Grid */
|
||||
.leads-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 0 30px 30px; overflow-y: auto; }
|
||||
.lead-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s; }
|
||||
.lead-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); transform: translateY(-2px); }
|
||||
.lead-status { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; margin-top: 5px; }
|
||||
.status-new { background: #e3f2fd; color: #1976d2; }
|
||||
.status-potential { background: #fff3e0; color: #f57c00; }
|
||||
.status-prospects { background: #e8f5e9; color: #388e3c; }
|
||||
|
||||
/* Modals */
|
||||
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; }
|
||||
.modal.active { display: flex; }
|
||||
.modal-content { background: white; border-radius: 12px; width: 90%; max-width: 800px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
.modal-header { padding: 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
||||
.modal-body { padding: 25px; flex: 1; }
|
||||
.form-group { margin-bottom: 20px; }
|
||||
.form-label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; }
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
|
||||
|
||||
/* Activity Type Buttons (POC Style) */
|
||||
.activity-types { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px; }
|
||||
.activity-type-btn { padding: 12px; border: 1px solid #e0e0e0; background: white; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; transition: 0.2s; }
|
||||
.activity-type-btn:hover { border-color: #ff6b35; background: #fff5f2; }
|
||||
.activity-type-btn.active { border-color: #ff6b35; background: #ff6b35; color: white; }
|
||||
|
||||
/* Timeline Styling */
|
||||
.timeline { position: relative; padding-left: 30px; margin-top: 20px; }
|
||||
.timeline-item { position: relative; padding-bottom: 25px; }
|
||||
.timeline-item::before { content: ''; position: absolute; left: -21px; top: 10px; width: 2px; height: 100%; background: #e0e0e0; }
|
||||
.timeline-dot { position: absolute; left: -26px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: #ff6b35; border: 2px solid white; box-shadow: 0 0 0 1px #ff6b35; }
|
||||
.timeline-dot.completed { background: #4caf50; box-shadow: 0 0 0 1px #4caf50; }
|
||||
.timeline-content { background: #f8f8f8; padding: 15px; border-radius: 8px; }
|
||||
.timeline-item {display : block !important;}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="top-bar">
|
||||
<input type="text" class="search-input" id="mainSearch" placeholder="Search leads..." onkeyup="fetchLeads()">
|
||||
<button class="btn-primary" onclick="openModal('addLeadModal')">+ Add Lead</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-tabs">
|
||||
<div class="tab active" data-filter="all" onclick="setFilter('all', this)">All</div>
|
||||
<div class="tab" data-filter="New" onclick="setFilter('New', this)">New</div>
|
||||
<div class="tab" data-filter="Potential" onclick="setFilter('Potential', this)">Potential</div>
|
||||
<div class="tab" data-filter="Prospects" onclick="setFilter('Prospects', this)">Prospects</div>
|
||||
</div>
|
||||
|
||||
<div class="leads-grid" id="leadsGrid"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="addLeadModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Add New Lead</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('addLeadModal')">×</button>
|
||||
</div>
|
||||
<form id="addLeadForm" class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Company Name</label>
|
||||
<input type="text" name="company_name" class="search-input" style="width:100%" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="search-input" style="width:100%" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" name="phone" class="search-input" style="width:100%" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Status</label>
|
||||
<select name="status" class="search-input" style="width:100%" required>
|
||||
<option value="New">New</option>
|
||||
<option value="Potential">Potential</option>
|
||||
<option value="Prospects">Prospects</option>
|
||||
<option value="Non prospects">Non prospects</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assign To</label>
|
||||
<select name="assigned_to" class="search-input" style="width:100%">
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?> </option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right; border-top:1px solid #eee; padding-top:20px;">
|
||||
<button type="button" class="btn-primary" style="background:#eee; color:#333; margin-right:10px;" onclick="closeModal('addLeadModal')">Cancel</button>
|
||||
<button type="submit" class="btn-primary">Create Lead</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="leadDetailModal">
|
||||
<div class="modal-content" style="max-width: 850px;">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h2 id="det_company">Lead Detail</h2>
|
||||
<span id="det_status_badge" class="lead-status"></span>
|
||||
</div>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('leadDetailModal')">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="background:#f8f8f8; padding:20px; border-radius:12px; margin-bottom:20px; display:grid; grid-template-columns: 1fr 1fr; gap:15px;">
|
||||
<div><small style="color:#999">Email</small><div id="det_email" style="font-weight:500"></div></div>
|
||||
<div><small style="color:#999">Phone</small><div id="det_phone" style="font-weight:500"></div></div>
|
||||
<div><small style="color:#999">Owner</small><div id="det_owner" style="font-weight:500"></div></div>
|
||||
<div><small style="color:#999">Address</small><div id="det_address" style="font-weight:500">N/A</div></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #eee; margin-bottom:20px;">
|
||||
<h3 style="padding-bottom:10px; border-bottom:2px solid #ff6b35;">Activity Timeline</h3>
|
||||
<button class="btn-primary" style="padding:6px 15px; font-size:12px;" onclick="openActivityModal()">+ Add Activity</button>
|
||||
</div>
|
||||
<div id="timelineContainer" class="timeline"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="activityModal">
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Add Activity</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('activityModal')">×</button>
|
||||
</div>
|
||||
<form id="activityForm" class="modal-body">
|
||||
<input type="hidden" id="act_lead_id">
|
||||
<label class="form-label">Activity Type</label>
|
||||
<div class="activity-types" id="typeButtons">
|
||||
<button type="button" class="activity-type-btn active" onclick="selectType('Call', this)">📞 Call</button>
|
||||
<button type="button" class="activity-type-btn" onclick="selectType('Email', this)">✉️ Email</button>
|
||||
<button type="button" class="activity-type-btn" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn" onclick="selectType('Demo', this)">🎬 Demo</button>
|
||||
<button type="button" class="activity-type-btn" onclick="selectType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn" onclick="selectType('Todo', this)">✓ To Do</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea id="act_notes" class="search-input" style="width:100%; height:100px;" placeholder="Add notes about this activity..." required></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Scheduled Date & Time</label>
|
||||
<input type="datetime-local" id="act_date" class="search-input" style="width:100%" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To</label>
|
||||
<select id="act_owner" class="search-input" style="width:100%">
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right; margin-top:10px;">
|
||||
<button type="submit" class="btn-primary" style="width:100%">Create Activity</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="completeModal">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header">
|
||||
<h3>Complete Activity</h3>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('completeModal')">×</button>
|
||||
</div>
|
||||
<form id="completeForm" class="modal-body">
|
||||
<input type="hidden" id="comp_id">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Outcome Notes</label>
|
||||
<textarea id="comp_notes" class="search-input" style="width:100%; height:80px;" required></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Next Follow-up?</label>
|
||||
<select id="do_follow" class="search-input" onchange="document.getElementById('f_up').style.display=this.value==='yes'?'block':'none'">
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="f_up" style="display:none; border-top:1px dashed #ccc; padding-top:15px;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Date</label>
|
||||
<input type="datetime-local" id="f_date" class="search-input">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" style="width:100%; margin-top:15px;">Submit Outcome</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
let filter = 'all';
|
||||
let lead_id = null;
|
||||
let selectedType = 'Call';
|
||||
|
||||
function openModal(id) { document.getElementById(id).classList.add('active'); }
|
||||
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
|
||||
|
||||
function selectType(val, el) {
|
||||
document.querySelectorAll('.activity-type-btn').forEach(b => b.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
selectedType = val;
|
||||
}
|
||||
|
||||
// 1. Leads Grid Logic
|
||||
async function fetchLeads() {
|
||||
const q = document.getElementById('mainSearch').value;
|
||||
const res = await fetch(`${API}/leads?status=${filter==='all'?'':filter}&search=${q}`);
|
||||
const json = await res.json();
|
||||
|
||||
document.getElementById('leadsGrid').innerHTML = json.data.map(l => `
|
||||
<div class="lead-card" onclick="viewDetail(${l.lead_id})">
|
||||
<div style="font-weight:600; font-size:17px;">${l.company_name}</div>
|
||||
<span class="lead-status status-${l.status.toLowerCase().replace(' ', '-')}">${l.status}</span>
|
||||
<div style="margin-top:12px; color:#666; font-size:13px;">
|
||||
<div>✉️ ${l.email}</div>
|
||||
<div>📞 ${l.phone}</div>
|
||||
</div>
|
||||
<div style="margin-top:15px; border-top:1px solid #f0f0f0; padding-top:10px; font-size:12px;">
|
||||
Assigned to: <b>${l.assigned_to_name || 'Unassigned'}</b>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 2. Detail Logic
|
||||
async function viewDetail(id) {
|
||||
lead_id = id;
|
||||
const res = await fetch(`${API}/leads/${id}`);
|
||||
const json = await res.json();
|
||||
const l = json.data;
|
||||
|
||||
document.getElementById('det_company').innerText = l.company_name;
|
||||
document.getElementById('det_email').innerText = l.email;
|
||||
document.getElementById('det_phone').innerText = l.phone;
|
||||
document.getElementById('det_owner').innerText = l.assigned_to_name;
|
||||
document.getElementById('det_address').innerText = l.address || 'N/A';
|
||||
|
||||
const badge = document.getElementById('det_status_badge');
|
||||
badge.innerText = l.status;
|
||||
badge.className = `lead-status status-${l.status.toLowerCase().replace(' ', '-')}`;
|
||||
|
||||
renderTimeline(l.activities);
|
||||
openModal('leadDetailModal');
|
||||
}
|
||||
|
||||
function renderTimeline(acts) {
|
||||
const cont = document.getElementById('timelineContainer');
|
||||
cont.innerHTML = acts.length ? acts.map(a => `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot ${a.status==='completed'?'completed':''}"></div>
|
||||
<div class="timeline-content">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
|
||||
<b>${a.activity_type}</b>
|
||||
<span style="font-size:11px; color:#999">${a.scheduled_date}</span>
|
||||
</div>
|
||||
<div style="font-size:13px; color:#444;">${a.notes}</div>
|
||||
${a.status === 'pending' ?
|
||||
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:8px;" onclick="openComp(${a.activity_id})">Mark Complete</button>` :
|
||||
`<div style="font-size:12px; color:#388e3c; margin-top:8px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<p style="color:#bbb; text-align:center;">No activities logged yet.</p>';
|
||||
}
|
||||
|
||||
function openActivityModal() {
|
||||
document.getElementById('act_lead_id').value = lead_id;
|
||||
openModal('activityModal');
|
||||
}
|
||||
|
||||
function openComp(id) {
|
||||
document.getElementById('comp_id').value = id;
|
||||
openModal('completeModal');
|
||||
}
|
||||
|
||||
// 3. Form Submissions
|
||||
document.getElementById('addLeadForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.target).entries());
|
||||
const res = await fetch(`${API}/leads`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if(res.ok) { closeModal('addLeadModal'); fetchLeads(); e.target.reset(); }
|
||||
else { const err = await res.json(); alert(err.messages.status || 'Error adding lead'); }
|
||||
};
|
||||
|
||||
document.getElementById('activityForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
lead_id: document.getElementById('act_lead_id').value,
|
||||
activity_type: selectedType,
|
||||
notes: document.getElementById('act_notes').value,
|
||||
scheduled_date: document.getElementById('act_date').value,
|
||||
assigned_to: document.getElementById('act_owner').value,
|
||||
status: 'pending'
|
||||
};
|
||||
const res = await fetch(`${API}/activities`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if(res.ok) { closeModal('activityModal'); viewDetail(lead_id); }
|
||||
};
|
||||
|
||||
document.getElementById('completeForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
completion_notes: document.getElementById('comp_notes').value,
|
||||
create_followup: document.getElementById('do_follow').value === 'yes',
|
||||
followup_date: document.getElementById('f_date').value
|
||||
};
|
||||
const res = await fetch(`${API}/activities/${document.getElementById('comp_id').value}/complete`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if(res.ok) { closeModal('completeModal'); viewDetail(lead_id); }
|
||||
};
|
||||
|
||||
function setFilter(val, el) {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
filter = val;
|
||||
fetchLeads();
|
||||
}
|
||||
|
||||
fetchLeads();
|
||||
</script>
|
||||
@ -49,7 +49,7 @@
|
||||
</div><input type="hidden" id="query_note_id">
|
||||
|
||||
<div class="form-group text-right">
|
||||
<button type="submit" class="btn btn-primary" id="ticket_auto_query_submit">Submit</button>
|
||||
<button type="submit" class="btn btn-primary" id="ticket_auto_query_submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -501,7 +501,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-12 text-right m-b-0" style="margin-top: 29px;">
|
||||
<button type="submit" class="btn btn-primary waves-effect waves-light mr-1" id="btnSubmit">Submit</button>
|
||||
<button type="submit" class="btn btn-primary waves-effect waves-light mr-1" id="btnSubmit"><?= !isset($ticket_data) ? "Submit" : "Save" ?></button>
|
||||
<?php if (get_role_id() == 5 && isset($ticket_data) && $ticket_data['claim_status_id'] == 8) { ?>
|
||||
<a class="btn btn-secondary waves-effect waves-light mr-1" onclick="showConfirmationModal(event)">Approve Claim Rejection</a>
|
||||
<?php } ?>
|
||||
|
||||
@ -326,7 +326,7 @@
|
||||
</div>
|
||||
<div class="form-group col-md-12 text-right m-b-0" style="margin-top: 29px;">
|
||||
<button type="submit" class="btn btn-primary waves-effect waves-light mr-1"
|
||||
id="btnSubmit">Submit</button>
|
||||
id="btnSubmit"><?= !isset($ticket_data) ? "Submit" : "Save" ?></button>
|
||||
<?php if (get_role_id() == 5 && isset($ticket_data) && in_array($ticket_data['claim_status_id'], [23, 33, 43])) { ?>
|
||||
<a class="btn btn-secondary waves-effect waves-light mr-1" onclick="showConfirmationModal(event)">Rejected Approve</a>
|
||||
<?php } ?>
|
||||
|
||||
@ -340,7 +340,7 @@
|
||||
</div>
|
||||
<div class="form-group col-md-12 text-right m-b-0" style="margin-top: 29px;">
|
||||
<button type="submit" class="btn btn-primary waves-effect waves-light mr-1"
|
||||
id="btnSubmit">Submit</button>
|
||||
id="btnSubmit"><?= !isset($ticket_data) ? "Submit" : "Save" ?></button>
|
||||
<?php if (get_role_id() == 5 && isset($ticket_data) && in_array($ticket_data['claim_status_id'], [23, 33, 43])) { ?>
|
||||
<a class="btn btn-secondary waves-effect waves-light mr-1" onclick="showConfirmationModal(event)">Rejected Approve</a>
|
||||
<?php } ?>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
</div><input type="hidden" id="note_id">
|
||||
<div class="form-group text-right m-b-0">
|
||||
<button type="submit" class="btn btn-primary" id="ticket_note_submit">Submit</button>
|
||||
<button type="submit" class="btn btn-primary" id="ticket_note_submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -461,9 +461,34 @@
|
||||
location.reload();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
toastr.error("Transaction save failed", "Error");
|
||||
|
||||
console.error(xhr.responseText);
|
||||
console.error(status, error);
|
||||
|
||||
if (xhr.status === 400) {
|
||||
let response = JSON.parse(xhr.responseText);
|
||||
let errorMessages = "";
|
||||
let seenMessages = []; // Array to store unique messages
|
||||
if (response.errors) {
|
||||
$.each(response.errors, function (field, message) {
|
||||
if (!seenMessages.includes(message)) {
|
||||
errorMessages += `• ${message}<br>`;
|
||||
seenMessages.push(message); // Mark this message as "seen"
|
||||
}
|
||||
});
|
||||
toastr.error(errorMessages, 'Validation Error', { "allowHtml": true });
|
||||
} else {
|
||||
toastr.warning(response.message || 'Validation failed', 'Warning');
|
||||
}
|
||||
} else if (xhr.status === 403) {
|
||||
let response = JSON.parse(xhr.responseText);
|
||||
toastr.error(response.message, 'Security Policy');
|
||||
} else if (xhr.status === 500) {
|
||||
toastr.error('Something went wrong . Please try again later.', 'Server Error');
|
||||
} else {
|
||||
toastr.error('An unexpected error occurred. Please try again later.', 'Error');
|
||||
}
|
||||
|
||||
location.reload(); // Uncomment if you need to reload the page on error
|
||||
}
|
||||
});
|
||||
|
||||
13
gdrive-demo-394007-5b1d856b0c5b.json
Normal file
13
gdrive-demo-394007-5b1d856b0c5b.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "gdrive-demo-394007",
|
||||
"private_key_id": "5b1d856b0c5b13e52b5210d381ce7ae02204f666",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC9tqkwe8XEuvDJ\ngDAKHn7FzFgkmor9sEZPkjofVJ1dK0RpD1mMVw38BzzMsEo8Y8aojNKj9FcgJPI+\nMiSwvoHDVGxPuyz2Q7o8BS7WdM83Sn69CBGn+0s+YjsyQ5pQBeu1YZ3erjckyA1M\nX0a0qXSFRm7dN0pwDIk0SP9/pl5iAKEtakuXn/Q+lSIpHz6OUgQy8bCjn91poMtF\nhL1YR/7k8ZttjQiFSCTzQ6e3IJmVbqY9UZ2hc4zVicLVSLM2x17M1CCVrsXoceOj\nQoYEeCqP2qJjGA29CB66xpXetuOFcll/ZHiNBhSekOBbJIKFG8WfTKaFn4hu2HX6\nzbJLse5FAgMBAAECggEAXK8qxWcS3eQ+0xLvZWI0qUoGHgvqr7o4/5L/FmNuZiBH\nUdSP+UJmsKSQjafq/Mn6OkpidntfPXMPbldtGXRZTSanq+RUORQpnj0h/uAehHK+\nrHeOuLTKs/Wl2g6xCzt5RqokSLBwfGXIKXG6x3SqWppoe2cR1OArAAJR4PlUzyeM\nP0HkZcVMDrXkshpbi/7yk1Yol5CTJjUrXT4cH2eFSih+eu5UxI/uEdxu86XnaB+V\nBDS94nSQffaMem3YLRSQpPWMHJts3NM2eoxpVy3NqbyHH0Jzr47T5+pdnk5AZX8v\nMu7L0FYgUmGli8W9/jV43lUi9z147EpNC1ugySICGQKBgQDeT3i9mBO1HQB+3y05\n4ao9YKWtR7RHTIiMBkekRs58xxWIq75+bsJtLMy0GsHOJHbpdfDU2AtcasYXVm/L\nc960AR7Qm0mdhQi5CG7XfFTvkc+RhsFoCAYOnbdInr1D+s7NsyOgdYvzh9HYIiJ/\nm8MzeGQia/4tlO+UNq7UK0sfowKBgQDadpe0OCynXEiziSY/aBT9mrjplXJSJUeR\nX5/pUrBV++mYt+LJU0Q+4op0Qf+PUJwp72O1v3T9h4ox2BcrYUMI17dJZ5HG46BG\n6Sjh+mZzLCTN9L6AVgRzK/5CUpsL+oPpClzGQK+1uJ+YKZD086YBuQi2JiG6uBxk\n7VLzATZ49wKBgQC3ZHgGb95SGoq+Hv4AMdluqLwEJpLh/pDmcofHTWIqLVHmXUfY\npSZfSgXUzf3zQMGX9mOmMlOs+ahQuE2hWQTvGb2B+ZjRCV4Yxowp17d5qp/BPZlv\naK8Wf6Ujk1AvNEhGCPHq/Q1m6TSDSCWNf8GYREjW3J/immrJqhKvlMd0YQKBgQCM\nf2CpQsdVCwCmljnG5YU6ZFsvvjE7q0YPtFP/lnJZmh1tXjW4DJkDaGZqxlc5MDp+\nrbqOlIcE1jqGO9cKyw51jWYPC1CxfIsDj8f/LS7eOzGgUxqBJtDN0SlANigI2CAl\nq8hmqAtY71eUYIcdQeUtjnaPzo46q1V3gzmplsoVmQKBgDCzaBbFY1bKW+xPPC98\nutYS7cGYtTq241zafSXx/qmpZB2QwFsR3rmZ9msCEBb3TY4Ew+hUi7+SP3ycSU1s\nkwIO/9DaZhFBRA9jbwbu4zdB2Niamfo79epqJ8vhJ6TA2d5xRSGbHWkGymWV/e0p\na74mh5hQ0lqI4MSeArFrJwwF\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "gsheet@gdrive-demo-394007.iam.gserviceaccount.com",
|
||||
"client_id": "108808196910972902964",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gsheet%40gdrive-demo-394007.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user