diff --git a/app/Config/Acl.php b/app/Config/Acl.php index 2960595f..b4d85eac 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -32,6 +32,8 @@ class Acl '#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/sales/dashboard#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], '#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], + '#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]], + diff --git a/app/Config/Routes.php b/app/Config/Routes.php index d9a05733..cf88f2b9 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -444,7 +444,9 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->get('proceedExcelFileDataValidation', 'EmployeeController::proceedExcelFileDataValidation'); $routes->get('checkTpaApiEnable', 'EmployeeRestController::checkTpaApiEnable'); $routes->get('generateDemographyDataTable', 'LeadsController::generateDemographyDataTable'); - $routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend'); + $routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend'); + $routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1'); + }); $routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail"); @@ -915,6 +917,8 @@ $routes->group('sales', function($routes) { $routes->get('loadactivities', 'SalesController::loadactivities'); + $routes->get('loadtargets', 'SalesController::loadtargets'); + $routes->get('page/(:segment)', 'SalesController::noPage/$1'); // Get all leads with filters @@ -995,6 +999,13 @@ $routes->group('sales', function($routes) { // Delete note $routes->delete('notes/(:num)', 'SalesController::deleteNote/$1'); + // ==================== TARGETS ROUTES ==================== + $routes->get('targets', 'SalesController::getTargets'); + $routes->get('targets/user/(:num)', 'SalesController::getTargetByUser/$1'); + $routes->get('targets/fy/(:segment)','SalesController::getTargetByFY/$1'); + $routes->post('targets', 'SalesController::createTarget'); + $routes->put('targets/(:num)', 'SalesController::updateTarget/$1'); + $routes->delete('targets/(:num)', 'SalesController::deleteTarget/$1'); //Dashboard $routes->get('dashboard', 'SalesController::dashboard'); @@ -1002,4 +1013,13 @@ $routes->group('sales', function($routes) { $routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard'); }); +// Expence Module Route Group +$routes->group('expense', ["filter" => "authMVC", 'namespace' => 'App\Controllers'], static function($routes) { + $routes->get('/', 'ExpenseController::index'); + $routes->post('save', 'ExpenseController::save'); + $routes->get('get/(:num)', 'ExpenseController::getExpense/$1'); + $routes->post('delete/(:num)', 'ExpenseController::delete/$1'); + $routes->get('client-policies', 'ExpenseController::clientPolicies'); +}); + diff --git a/app/Controllers/AppContentManagementController.php b/app/Controllers/AppContentManagementController.php index a53d90d0..c3bcde67 100755 --- a/app/Controllers/AppContentManagementController.php +++ b/app/Controllers/AppContentManagementController.php @@ -62,32 +62,40 @@ class AppContentManagementController extends AdminController $data = $this->request->getPost(); $sanitized_post_data = sanitizeInputArrayAdvanced($data); - $id = ((int) $sanitized_post_data['add_image_id']) ?? null; + $id = (int)($sanitized_post_data['add_image_id'] ?? 0); $rules = [ - 'client_id' => [ - 'rules' => 'required|integer', + 'add_image_id' => [ + 'rules' => 'permit_empty|integer|is_natural', 'errors' => [ - 'required' => 'Client is required', - 'integer' => 'Invalid client selected' + 'integer' => 'Image ID must be a valid number', + 'is_natural' => 'Image ID must be a non-negative number' + ] + ], + 'client_id' => [ + 'rules' => 'required|integer|is_natural_no_zero', + 'errors' => [ + 'required' => 'Client is required', + 'integer' => 'Invalid client selected', + 'is_natural_no_zero' => 'Invalid client selected' + ] + ], + 'advertise_image' => [ + 'rules' => ($id === 0 ? 'uploaded[advertise_image]|' : '') + . 'is_image[advertise_image]' + . '|mime_in[advertise_image,image/jpg,image/jpeg,image/png]' + . '|max_size[advertise_image,200]' + . '|min_dims[advertise_image,1640,664]' + . '|max_dims[advertise_image,1640,664]', + 'errors' => [ + 'uploaded' => 'Image is required', + 'is_image' => 'File must be an image', + 'mime_in' => 'Only JPG, JPEG, PNG allowed', + 'max_size' => 'Image size must not exceed 200 KB', + 'min_dims' => 'Image dimensions must be exactly 1640x664 pixels', + 'max_dims' => 'Image dimensions must be exactly 1640x664 pixels', ] ], - ]; - $rules['advertise_image'] = [ - 'rules' => ($id === 0 ? 'uploaded[advertise_image]|' : '') // required only for ADD - . 'is_image[advertise_image]' - . '|mime_in[advertise_image,image/jpg,image/jpeg,image/png]' - . '|max_size[advertise_image,200]' - . '|min_dims[advertise_image,1640,664]' - . '|max_dims[advertise_image,1640,664]', - 'errors' => [ - 'uploaded' => 'Image is required', - 'is_image' => 'File must be an image', - 'mime_in' => 'Only JPG, JPEG, PNG allowed', - 'max_size' => 'Image size must not exceed 200 KB', - 'min_dims' => 'Image dimensions must be exactly 1640x664 pixels', - 'max_dims' => 'Image dimensions must be exactly 1640x664 pixels', - ] ]; if (!$this->validate($rules)) { return $this->response->setStatusCode(400)->setJSON([ @@ -100,7 +108,12 @@ class AppContentManagementController extends AdminController $file = $this->request->getFile('advertise_image'); - $client_id = $sanitized_post_data['client_id'] ?? null; + $client_id = (int)($sanitized_post_data['client_id'] ?? 0); + + $clientExists = $this->clientModel->where('id', $client_id)->where('is_active', 1)->first(); + if (!$clientExists) { + return $this->respond(['status' => false, 'message' => 'Invalid client selected.'], 400); + } if (!$file || !$file->isValid()) { return $this->respond(['status' => false, 'message' => 'No file uploaded or invalid file.'], 400); @@ -119,12 +132,15 @@ class AppContentManagementController extends AdminController $file->move($uploadPath, $fileName); - $id = $sanitized_post_data['add_image_id'] ?? null; $details = ['name' => $fileName,'client_id'=>$client_id]; - if ($id == 0) { + if ($id === 0) { $this->addImgModel->insert($details); } else { + $existingRecord = $this->addImgModel->find($id); + if (!$existingRecord) { + return $this->respond(['status' => false, 'message' => 'Record not found for update.'], 404); + } $this->addImgModel->update($id, $details); } @@ -138,14 +154,34 @@ class AppContentManagementController extends AdminController public function remove_advertise_image() { try { - $id = $this->request->getPost('add_image_id'); + $rules = [ + 'add_image_id' => [ + 'rules' => 'required|integer|is_natural_no_zero', + 'errors' => [ + 'required' => 'Image ID is required', + 'integer' => 'Image ID must be a valid number', + 'is_natural_no_zero' => 'Image ID must be a positive number' + ] + ] + ]; - if (!$id) { - return $this->respond(['status' => false, 'message' => 'ID missing'], 400); + if (!$this->validate($rules)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Input validation failed', + 'code' => 400, + 'errors' => $this->validator->getErrors() + ]); } - $data = ['is_active' => 0]; - $this->addImgModel->update($id, $data); + $id = (int)$this->request->getPost('add_image_id'); + + $existing = $this->addImgModel->find($id); + if (!$existing) { + return $this->respond(['status' => false, 'message' => 'Record not found'], 404); + } + + $this->addImgModel->update($id, ['is_active' => 0]); return $this->respond(['status' => true, 'message' => 'Deleted successfully']); @@ -158,19 +194,27 @@ class AppContentManagementController extends AdminController // Preview image Went Edit. public function showAdvertiseImage($filename) { - // $path = WRITEPATH . 'uploads/advertiseImage/' . $filename; - $path = ROOTPATH . 'public/uploads/add_image_upload/' . $filename; + $filename = basename($filename); - if (!file_exists($path)) { + if (!preg_match('/^[a-zA-Z0-9_\-]+\.(jpg|jpeg|png|gif|webp)$/i', $filename)) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); - // return $this->response->setStatusCode(404, 'File not found'); } - $mime = mime_content_type($path); - // header('Content-Type: ' . $mimeType); - // readfile($path); - // exit; - return $this->response->setHeader('Content-Type', $mime)->setBody(file_get_contents($path)); + $path = ROOTPATH . 'public/uploads/add_image_upload/' . $filename; + $realPath = realpath($path); + $allowedDir = realpath(ROOTPATH . 'public/uploads/add_image_upload'); + + if (!$realPath || strpos($realPath, $allowedDir) !== 0 || !file_exists($realPath)) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); + } + + $mime = mime_content_type($realPath); + $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + if (!in_array($mime, $allowedMimes, true)) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); + } + + return $this->response->setHeader('Content-Type', $mime)->setBody(file_get_contents($realPath)); } @@ -183,39 +227,51 @@ class AppContentManagementController extends AdminController if ($this->request->getMethod() === 'post') { $rules = [ - 'type' => [ - 'rules' => 'required|max_length[255]', + 'fe_id' => [ + 'rules' => 'permit_empty|integer|is_natural', 'errors' => [ - 'required' => 'Type is required', - 'max_length' => 'Type cannot exceed 255 characters' + 'integer' => 'ID must be a valid number', + 'is_natural' => 'ID must be a non-negative number' + ] + ], + 'type' => [ + 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9_ \-]+$/]', + 'errors' => [ + 'required' => 'Type is required', + 'max_length' => 'Type cannot exceed 255 characters', + 'regex_match' => 'Type contains invalid characters' ] ], 'content_section' => [ - 'rules' => 'required|max_length[255]', + 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9_ \-]+$/]', 'errors' => [ - 'required' => 'Content Section is required', - 'max_length' => 'Content Section cannot exceed 255 characters' + 'required' => 'Content Section is required', + 'max_length' => 'Content Section cannot exceed 255 characters', + 'regex_match' => 'Content Section contains invalid characters' ] ], 'heading' => [ - 'rules' => 'required|max_length[255]', + 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/]+$/]', 'errors' => [ - 'required' => 'Heading is required', - 'max_length' => 'Heading cannot exceed 255 characters' + 'required' => 'Heading is required', + 'max_length' => 'Heading cannot exceed 255 characters', + 'regex_match' => 'Heading contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ], 'content' => [ - 'rules' => 'required|max_length[5000]', + 'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', 'errors' => [ - 'required' => 'Content is required', - 'max_length' => 'Content cannot exceed 5000 characters' + 'required' => 'Content is required', + 'max_length' => 'Content cannot exceed 5000 characters', + 'regex_match' => 'Content contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ], 'notes' => [ - 'rules' => 'required|max_length[1500]', + 'rules' => 'required|max_length[1500]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', 'errors' => [ - 'required' => 'Notes are required', - 'max_length' => 'Notes cannot exceed 1500 characters' + 'required' => 'Notes are required', + 'max_length' => 'Notes cannot exceed 1500 characters', + 'regex_match' => 'Notes contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ] ]; @@ -230,8 +286,14 @@ class AppContentManagementController extends AdminController } $request_post_data = $this->request->getPost(); $data = sanitizeInputArrayAdvanced($request_post_data); - $id = $data['fe_id']; - + $id = $data['fe_id'] ?? null; + + if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, 'message' => 'Invalid ID format', 'code' => 400 + ]); + } + $id = !empty($id) ? (int)$id : null; unset($data['fe_id']); @@ -244,6 +306,12 @@ class AppContentManagementController extends AdminController $status = $this->feContentModel->insert($data); $text = "Created"; } else { + $existing = $this->feContentModel->find($id); + if (!$existing) { + return $this->response->setStatusCode(404)->setJSON([ + 'status' => false, 'message' => 'Record not found', 'code' => 404 + ]); + } $status = $this->feContentModel->update($id, $data); $text = "Updated"; } @@ -260,6 +328,13 @@ class AppContentManagementController extends AdminController $id = $this->request->getGet('fe_id') ?? null; if (!empty($id)) { + + if (!ctype_digit((string)$id) || (int)$id <= 0) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, 'code' => 400, 'message' => 'Invalid ID format' + ]); + } + $id = (int)$id; $data['fe_list'] = $this->feContentModel->where('id', $id)->orderBy('id', 'DESC')->findAll(); @@ -276,16 +351,24 @@ class AppContentManagementController extends AdminController } elseif ($method === 'delete') { - // $input = $this->request->getRawInput(); - $id = $this->request->getGet('fe_id'); // ✅ THIS - $id = $id ?? null; + $id = $this->request->getGet('fe_id') ?? null; - if (empty($id)) { + if (empty($id) || !ctype_digit((string)$id) || (int)$id <= 0) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'Valid numeric ID is required for deletion' + ], 400); + } + $id = (int)$id; + + $existing = $this->feContentModel->find($id); + if (!$existing) { return $this->respond([ 'status' => false, 'code' => 404, - 'message' => 'No ID provided for deletion' - ], 200); + 'message' => 'Record not found' + ], 404); } $update_status = $this->feContentModel->where('id', $id)->set(['is_active' => 0])->update(); @@ -309,10 +392,15 @@ class AppContentManagementController extends AdminController } + private const ALLOWED_RETURN_TYPES = ['api', 'web']; + public function FAQ() { $method = strtolower($this->request->getMethod()); $returnType = strtolower($this->request->getGet('return_type') ?? 'api'); + if (!in_array($returnType, self::ALLOWED_RETURN_TYPES, true)) { + $returnType = 'api'; + } $ref = ['timestamp' => date('Y-m-d H:i:s')]; try { @@ -320,33 +408,64 @@ class AppContentManagementController extends AdminController if ($method === 'post') { $rules = [ 'category' => [ - 'rules' => 'required', + 'rules' => 'required|max_length[100]|alpha_numeric_space', 'errors' => [ - 'required' => 'Category is required' + 'required' => 'Category is required', + 'max_length' => 'Category cannot exceed 100 characters', + 'alpha_numeric_space' => 'Category contains invalid characters' ] ], 'question' => [ - 'rules' => 'required', + 'rules' => 'required|max_length[1000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', 'errors' => [ - 'required' => 'Question is required' + 'required' => 'Question is required', + 'max_length' => 'Question cannot exceed 1000 characters', + 'regex_match' => 'Question contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ], 'answer' => [ - 'rules' => 'required', + 'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', 'errors' => [ - 'required' => 'Answer is required' + 'required' => 'Answer is required', + 'max_length' => 'Answer cannot exceed 5000 characters', + 'regex_match' => 'Answer contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ] ]; + + if (!$this->validate($rules)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => 'error', + 'message' => 'Input validation failed', + 'code' => 400, + 'errors' => $this->validator->getErrors(), + 'ref' => $ref + ]); + } + $request_post_data = $this->request->getPost(); $sanitized_post_data = sanitizeInputArrayAdvanced($request_post_data); $data = array_filter($sanitized_post_data, fn($v) => $v !== '' && $v !== null); - $id = $data['faq_id']; + $id = $data['faq_id'] ?? null; + + if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => 'error', 'message' => 'Invalid FAQ ID format', 'code' => 400, 'ref' => $ref + ]); + } + $id = !empty($id) ? (int)$id : null; + unset($data['faq_id']); if (empty($id)) { $status = $this->faqModel->insert($data); $msg = "Created"; } else { + $existing = $this->faqModel->find($id); + if (!$existing) { + return $this->response->setStatusCode(404)->setJSON([ + 'status' => 'error', 'message' => 'FAQ not found', 'code' => 404, 'ref' => $ref + ]); + } $status = $this->faqModel->update($id, $data); $msg = "Updated"; } @@ -372,10 +491,17 @@ class AppContentManagementController extends AdminController $id = $this->request->getGet('faq_id'); if (!empty($id)) { + if (!ctype_digit((string)$id) || (int)$id <= 0) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => 'error', 'message' => 'Invalid FAQ ID format', 'code' => 400, 'ref' => $ref + ]); + } + $id = (int)$id; + if($returnType === 'web'){ - $row = $this->faqModel->find((int)$id); + $row = $this->faqModel->find($id); }else{ - $row = $this->faqModel->where('is_active', 1)->find((int)$id); + $row = $this->faqModel->where('is_active', 1)->find($id); } $data['faq_list'] = $row ? [$row] : []; @@ -417,7 +543,15 @@ class AppContentManagementController extends AdminController // --- 3. DELETE: SOFT DELETE --- elseif ($method === 'delete') { $id = $this->request->getGet('faq_id'); - $status = (!empty($id)) ? $this->faqModel->update($id, ['is_active' => 0]) : false; + + if (empty($id) || !ctype_digit((string)$id) || (int)$id <= 0) { + return $this->response->setJSON([ + 'status' => 'error', 'message' => 'Valid numeric FAQ ID is required', 'code' => 400, 'ref' => $ref + ])->setStatusCode(400); + } + $id = (int)$id; + + $status = $this->faqModel->find($id) ? $this->faqModel->update($id, ['is_active' => 0]) : false; return $this->response->setJSON([ 'status' => $status ? ($returnType === 'web' ? true : 'success') : ($returnType === 'web' ? false : 'error'), @@ -429,12 +563,12 @@ class AppContentManagementController extends AdminController } } catch (\Throwable $e) { - $msg = $e->getMessage(); + log_message('error', 'FAQ error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); return $this->response->setJSON([ 'status' => $returnType === 'web' ? false : 'error', 'code' => 500, - 'message' => $msg, - 'ref' => ['file' => $e->getFile(), 'line' => $e->getLine()] + 'message' => 'An internal error occurred. Please try again later.', + 'ref' => $ref ])->setStatusCode(500); } } diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index 62989198..6d2064fa 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -142,24 +142,38 @@ class ClientController extends AdminController return $this->response->setJSON(['isDuplicate' => $isDuplicate]); } + private const ALLOWED_DUPLICATE_CHECK_TABLES = [ + 'clients', 'client_branch', 'level_contacts', 'insurers', 'insurer_branch', + 'tpa', 'tpa_branch', 'policies', 'client_policy', 'user_profiles', + ]; + public function checkDuplicateTableFieldValue() { $table = $this->request->getPost('table'); $field = $this->request->getPost('field'); $value = $this->request->getPost('value'); - // Load the database if not already loaded - $db = db_connect(); + if (!in_array($table, self::ALLOWED_DUPLICATE_CHECK_TABLES, true)) { + return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid table']); + } + + $db = db_connect(); + $tableFields = $db->getFieldNames($table); - // Perform the query $builder = $db->table($table); if (is_array($value)) { - $isDuplicate = $builder->where($value)->where('is_active', 1)->countAllResults() > 0; + $filteredValue = array_intersect_key($value, array_flip($tableFields)); + if (empty($filteredValue)) { + return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid fields']); + } + $isDuplicate = $builder->where($filteredValue)->where('is_active', 1)->countAllResults() > 0; } else { + if (!in_array($field, $tableFields, true)) { + return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid field']); + } $isDuplicate = $builder->where($field, $value)->where('is_active', 1)->countAllResults() > 0; } - // Return the result return $this->response->setJSON(['isDuplicate' => $isDuplicate]); } @@ -6955,16 +6969,21 @@ class ClientController extends AdminController // $response = $ticketServiceController->getClaimExcelErrorData(["file_id" => 41]); // $response = $ticketServiceController->claimDumpOnBoardProcess(["file_id" => 17]); // $response = $ticketServiceController->extractExcelData("claims_dump_form_client.xlsx"); + // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 51]); //abhi // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 50]); //fhpl // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 53]); //icici // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 54]); //mediassist // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 52]); //reliance // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 48]); //vidal - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist - // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 4]); //abhi + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 51]); //abhi + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 50]); //fhpl + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 53]); //icici + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 52]); //reliance + // $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal + // $response = $ticketServiceController->getTpaClaimDumpErrorData(["file_id" => 48]); // dd($response); // ---------- TICKET CONTROLLER -------------------------------------------------------------------------------- diff --git a/app/Controllers/EmpDataServiceController.php b/app/Controllers/EmpDataServiceController.php index 4a4e9faf..3ea1af3a 100755 --- a/app/Controllers/EmpDataServiceController.php +++ b/app/Controllers/EmpDataServiceController.php @@ -4360,13 +4360,15 @@ class EmpDataServiceController extends BaseController { $client_policy_id = $arrayData['client_policy_id']; $cd_ac_pk = $this->clientPolicyModel->select('cd_ac_pk')->where('id',$client_policy_id)->first(); + $sanitizedIds = array_map('intval', $arrayData['employeeIds']); + $placeholders = implode(',', array_fill(0, count($sanitizedIds), '?')); $amount = $this->employeePolicyModel->query(" SELECT SUM(rata_premimum + gst) AS total_sum FROM employee_polices JOIN employees ON employees.id = employee_polices.employee_id - WHERE employees.unit = '$unit' - AND employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ") - ")->getRow(); + WHERE employees.unit = ? + AND employee_polices.id IN ($placeholders) + ", array_merge([$unit], $sanitizedIds))->getRow(); if($amount){ @@ -4625,11 +4627,14 @@ class EmpDataServiceController extends BaseController // AND employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ") // ")->getRow(); + $sanitizedIds = array_map('intval', $arrayData['employeeIds']); + $placeholders = implode(',', array_fill(0, count($sanitizedIds), '?')); + $safeAddOneDay = (int)$add_one_day; $amount = $this->employeePolicyModel->query(" SELECT SUM(ROUND( - ((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + '$add_one_day')) / 365) + - (((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + '$add_one_day')) / 365) * 0.18), + ((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + ?)) / 365) + + (((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + ?)) / 365) * 0.18), 2 )) AS total_sum FROM @@ -4639,14 +4644,14 @@ class EmpDataServiceController extends BaseController JOIN emp_endorsement ON emp_endorsement.pk = employee_polices.id WHERE - employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ") - AND emp_endorsement.pk IN (" . implode(',', $arrayData['employeeIds']) . ") + employee_polices.id IN ($placeholders) + AND emp_endorsement.pk IN ($placeholders) AND employee_polices.claim_status = 0 AND emp_endorsement.field_name = 'date_of_exit' AND emp_endorsement.is_active = 1 AND emp_endorsement.actions = 'd' - AND emp_endorsement.status != 'truncated'; - ")->getRow(); + AND emp_endorsement.status != 'truncated' + ", array_merge([$safeAddOneDay, $safeAddOneDay], $sanitizedIds, $sanitizedIds))->getRow(); // dd(db_connect()->getLastQuery(), $amount); diff --git a/app/Controllers/ExpenseController.php b/app/Controllers/ExpenseController.php new file mode 100644 index 00000000..c734284d --- /dev/null +++ b/app/Controllers/ExpenseController.php @@ -0,0 +1,412 @@ +myLogger = \Config\Services::mylogger(); + $this->expenseModel = new ExpenseModel(); + $this->clientModel = new ClientModel(); + $this->clientPolicyModel = new ClientPolicyModel(); + } + + /** + * Web list + form view + */ + public function index() + { + try { + $data['tab_name'] = 'Expense'; + $data['page_name'] = 'Expense'; + $descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/u'; + + // Raw GET filters + $rawFilters = $this->request->getGet() ?? []; + $rawFilters = is_array($rawFilters) ? $rawFilters : []; + + // Sanitize input array using existing helper + $sanitized = sanitizeInputArrayAdvanced($rawFilters); + + $filters = [ + 'client_id' => trim($sanitized['client_id'] ?? ''), + 'client_policy_id' => trim($sanitized['client_policy_id'] ?? ''), + 'approved_by' => trim($sanitized['approved_by'] ?? ''), + 'description' => trim($sanitized['description'] ?? ''), + 'amount' => trim($sanitized['amount'] ?? ''), + 'expense_date' => trim($sanitized['expense_date'] ?? ''), + ]; + + $validationErrors = []; + + // Basic type / format validation for filters + if ($filters['client_id'] !== '' && ! ctype_digit($filters['client_id'])) { + $validationErrors[] = 'Invalid client selected for search.'; + $filters['client_id'] = ''; + } + + if ($filters['client_policy_id'] !== '' && ! ctype_digit($filters['client_policy_id'])) { + $validationErrors[] = 'Invalid policy selected for search.'; + $filters['client_policy_id'] = ''; + } + + if ($filters['approved_by'] !== '' && ! ctype_digit($filters['approved_by'])) { + $validationErrors[] = 'Invalid approver selected for search.'; + $filters['approved_by'] = ''; + } + + if ($filters['description'] !== '' && ! preg_match($descriptionPattern, $filters['description'])) { + $validationErrors[] = 'Description filter contains invalid characters.'; + $filters['description'] = ''; + } + + if ($filters['amount'] !== '') { + if (! is_numeric($filters['amount']) || (float) $filters['amount'] < 0) { + $validationErrors[] = 'Amount filter must be a non-negative number.'; + $filters['amount'] = ''; + } + } + + if ($filters['expense_date'] !== '') { + $dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']); + $errors = $dt ? \DateTime::getLastErrors() : ['warning_count' => 1, 'error_count' => 1]; + if (! $dt || ! empty($errors['warning_count']) || ! empty($errors['error_count'])) { + $validationErrors[] = 'Expense Date filter must be in DD-MM-YYYY format.'; + $filters['expense_date'] = ''; + } + } + + $data['filters'] = $filters; + $data['validation_errors'] = $validationErrors; + + // Clients for dropdown + $data['clients'] = $this->clientModel + ->select('id, client_name, short_name') + ->where('is_active', 1) + ->orderBy('client_name', 'ASC') + ->findAll(); + + // Approved by (users) dropdown + $db = db_connect(); + $data['approved_users'] = $db->table('user_profiles') + ->select('id, first_name') + ->where('is_active', 1) + ->whereIn('id', [7, 8]) + ->orderBy('first_name', 'ASC') + ->get() + ->getResultArray(); + + // Policies for filter dropdown (when client filter is selected) + $data['policies_for_filter'] = []; + if ($filters['client_id'] !== '') { + $data['policies_for_filter'] = $this->clientPolicyModel + ->select('id, policy_no') + ->where('client_id', (int) $filters['client_id']) + ->where('is_active', 1) + ->orderBy('id', 'ASC') + ->findAll(); + } + + // Existing expenses with optional filters + $builder = $this->expenseModel + ->select(' + expenses.*, + clients.client_name, + clients.short_name, + client_policy.policy_no, + user_profiles.first_name AS approved_by_name + ') + ->join('clients', 'clients.id = expenses.client_id') + ->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left') + ->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left') + ->where('expenses.is_active', 1); + + if ($filters['client_id'] !== '') { + $builder->where('expenses.client_id', (int) $filters['client_id']); + } + + if ($filters['client_policy_id'] !== '') { + $builder->where('expenses.client_policy_id', (int) $filters['client_policy_id']); + } + + if ($filters['approved_by'] !== '') { + $builder->where('expenses.approved_by', (int) $filters['approved_by']); + } + + if ($filters['description'] !== '') { + $builder->like('expenses.description', (string) $filters['description']); + } + + if ($filters['amount'] !== '') { + $builder->where('expenses.amount', (float) $filters['amount']); + } + + if ($filters['expense_date'] !== '') { + $dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']); + if ($dt) { + $builder->where('expenses.expense_date', $dt->format('Y-m-d')); + } + } + + $data['expenses'] = $builder + ->orderBy('expenses.id', 'DESC') + ->findAll(); + + return $this->loadLayout('expense_list', $data); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Create / update expense (AJAX) + */ + public function save() + { + try { + if ($this->request->getMethod() !== 'post') { + return $this->response + ->setStatusCode(405) + ->setJSON([ + 'status' => false, + 'message' => 'Invalid request method', + ]); + } + + $rawData = $this->request->getPost(); + $data = sanitizeInputArrayAdvanced($rawData); + + $id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : null; + + $rules = [ + 'client_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Client is required', + ], + ], + 'client_policy_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Policy is required', + ], + ], + 'description' => [ + 'rules' => 'required|string|min_length[1]|max_length[2000]|regex_match[/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/]', + 'errors' => [ + 'required' => 'Description is required', + 'min_length' => 'Description cannot be empty', + 'regex_match' => 'Description contains invalid characters.', + ], + ], + 'expense_date' => [ + 'rules' => 'required|valid_date[d-m-Y]', + 'errors' => [ + 'required' => 'Expense Date is required', + 'valid_date' => 'Expense Date must be in DD-MM-YYYY format', + ], + ], + 'approved_by' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Approved By is required', + ], + ], + 'amount' => [ + 'rules' => 'required|numeric|greater_than_equal_to[0]', + 'errors' => [ + 'required' => 'Amount is required', + 'numeric' => 'Amount must be numeric', + 'greater_than_equal_to' => 'Amount cannot be negative', + ], + ], + ]; + + if (! $this->validate($rules)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Input validation failed', + 'errors' => $this->validator->getErrors(), + ]); + } + + $payload = [ + 'client_id' => (int) ($data['client_id'] ?? 0), + 'client_policy_id' => (int) ($data['client_policy_id'] ?? 0), + 'description' => $data['description'] ?? null, + 'approved_by' => (int) ($data['approved_by'] ?? 0), + 'amount' => $data['amount'] ?? null, + 'is_active' => 1, + ]; + + $expenseDate = $data['expense_date'] ?? null; + if (! empty($expenseDate)) { + $dt = \DateTime::createFromFormat('d-m-Y', $expenseDate); + $payload['expense_date'] = $dt ? $dt->format('Y-m-d') : null; + } else { + $payload['expense_date'] = null; + } + + if ($id === null) { + $insertId = $this->expenseModel->insert($payload, true); + $success = ! empty($insertId); + $id = $insertId; + $message = $success + ? 'Expense created successfully' + : 'Unable to create expense. Please try again.'; + } else { + $success = $this->expenseModel->update($id, $payload); + $message = $success + ? 'Expense updated successfully' + : 'Unable to update expense. Please try again.'; + } + + return $this->response + ->setStatusCode($success ? 200 : 400) + ->setJSON([ + 'status' => (bool) $success, + 'message' => $message, + 'id' => $id, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Get single expense (AJAX) + */ + public function getExpense($id = null) + { + try { + $id = (int) $id; + + if (empty($id)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Invalid expense id', + ]); + } + + $expense = $this->expenseModel + ->select(' + expenses.*, + clients.client_name, + clients.short_name, + client_policy.policy_no, + user_profiles.first_name AS approved_by_name + ') + ->join('clients', 'clients.id = expenses.client_id', 'left') + ->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left') + ->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left') + ->where('expenses.id', $id) + ->where('expenses.is_active', 1) + ->first(); + + if (empty($expense)) { + return $this->response->setStatusCode(404)->setJSON([ + 'status' => false, + 'message' => 'Expense not found', + ]); + } + + return $this->response->setStatusCode(200)->setJSON([ + 'status' => true, + 'data' => $expense, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Soft delete expense (AJAX) + */ + public function delete($id = null) + { + try { + if ($this->request->getMethod() !== 'post') { + return $this->response + ->setStatusCode(405) + ->setJSON([ + 'status' => false, + 'message' => 'Invalid request method', + ]); + } + + $id = (int) $id; + + if (empty($id)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Invalid expense id', + ]); + } + + $payload = [ + 'is_active' => 0, + ]; + + $success = $this->expenseModel->update($id, $payload); + + return $this->response + ->setStatusCode($success ? 200 : 400) + ->setJSON([ + 'status' => (bool) $success, + 'message' => $success + ? 'Expense deleted successfully' + : 'Unable to delete expense. Please try again.', + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } + + /** + * Get policies by client for dropdown (AJAX) + */ + public function clientPolicies() + { + try { + $clientId = (int) ($this->request->getGet('client_id') ?? 0); + + if (empty($clientId)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Client is required', + ]); + } + + $policies = $this->clientPolicyModel + ->select('client_policy.id, client_policy.policy_no, policy_type.policy_type') + ->join('policy_type', 'policy_type.id = client_policy.policy_type_id AND policy_type.is_active = 1') + ->where('client_policy.client_id', $clientId) + ->where('client_policy.is_active', 1) + ->orderBy('client_policy.id', 'ASC') + ->findAll(); + + return $this->response->setStatusCode(200)->setJSON([ + 'status' => true, + 'data' => $policies, + ]); + } catch (\Throwable $e) { + return handle_exception($e, $this->myLogger, $this->response); + } + } +} + diff --git a/app/Controllers/MediAssistApiController.php b/app/Controllers/MediAssistApiController.php index 26b82ce1..a59c2637 100644 --- a/app/Controllers/MediAssistApiController.php +++ b/app/Controllers/MediAssistApiController.php @@ -844,6 +844,8 @@ class MediAssistApiController extends BaseController ->get() ->getResultArray(); + log_message('error','MEDI_ASSIST - Claim Status started | for ticket count: ' . count($TicketData)); + // dd($TicketData); if (!$TicketData) { @@ -890,7 +892,11 @@ class MediAssistApiController extends BaseController if ($response['status'] != true || empty($response['data']['claimsData'][0])) { log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response)); - $error_data[$claimId][['status' => false,'message' => 'API call failed.','data' => $response]]; + $error_data[$claimId] = [ + 'response' => $response, + 'body' => $body + ]; + continue; // skip to next ticket } // Extract Claim Status diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php index c3f870c5..164444e9 100644 --- a/app/Controllers/SalesController.php +++ b/app/Controllers/SalesController.php @@ -7,6 +7,7 @@ use App\Models\SalesActualLeadModel; use App\Models\SalesContactPersonModel; use App\Models\SalesActivityModel; use App\Models\SalesLeadNoteModel; +use App\Models\SalesTargetModel; use App\Models\UserModel; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\API\ResponseTrait; @@ -20,6 +21,8 @@ class SalesController extends BaseController protected $activityModel; protected $noteModel; protected $userModel; + protected $targetModel; + public function __construct() { @@ -28,6 +31,7 @@ class SalesController extends BaseController $this->activityModel = new SalesActivityModel(); $this->noteModel = new SalesLeadNoteModel(); $this->userModel = new UserModel(); + $this->targetModel = new SalesTargetModel(); } @@ -53,55 +57,73 @@ class SalesController extends BaseController return $this->loadLayout('sales/activity_view', $data); } + public function loadtargets(){ + $data = $this->getSalesStaffData(); + + $data['tab_name'] = 'Sales Team Targets'; + $data['page_name'] = 'Sales Team Targets'; + + return $this->loadLayout('sales/target_view', $data); + } + /** * HELPER: Fetches Sales Managers based on the logged-in user's role and branch */ private function getSalesStaffData(): array { - $db = \Config\Database::connect(); + $db = \Config\Database::connect(); $logged_user_id = get_session_userid(); - $role = get_role_id(); - $team_id = user_team(); + $role = get_role_id(); + $team_id = user_team(); $data = [ - 'users' => [], - 'sales_manager_ids' => [] + 'users' => [], + 'sales_manager_ids'=> [], + 'sales_role' => '', + 'nhance_branch_id' => null, + 'assigned_ids' => [], ]; - $row = $db->table('user_profiles')->select('*') - ->where('is_active', 1)->where('id', $logged_user_id) - ->get()->getRow(); - - $nhance_branch_id = $row ? $row->nhance_branch_id : null; - - // Is the logged-in user a Sales Manager? (Role 4, Team 5) - if ($role == 4 && in_array(5, $team_id)) { + $row = $db->table('user_profiles')->select('*') + ->where('is_active', 1)->where('id', $logged_user_id) + ->get()->getRow(); + + $nhance_branch_id = $row ? $row->nhance_branch_id : null; + $data['nhance_branch_id']= $nhance_branch_id; + + // ── Sales Manager (Role 4, Team 5) ────────────────────────── + if ($role == 4 && in_array(5, $team_id)) { + + $data['sales_role'] = 'Sales Manager'; $data['sales_manager_ids'] = [$logged_user_id]; - - $data['users'] = [ + $data['assigned_ids'] = [$logged_user_id]; + $data['users'] = [ [ 'id' => $row->id, - 'first_name' => $row->first_name, - 'nhance_branch_id' => $nhance_branch_id + 'first_name' => $row->first_name, + 'last_name' => $row->last_name ?? '', + 'nhance_branch_id' => $nhance_branch_id, ] ]; - } - // Otherwise fetch ALL sales managers in this branch - elseif (in_array($role,[1,5])) { - - $data['users'] = $db->table('user_profiles up') - ->select('up.id, up.first_name, up.last_name, up.nhance_branch_id') - ->join('user_teams ut', 'ut.user_id = up.id') - ->where('up.is_active', 1) - ->where('ut.is_active', 1) - ->where('up.role', 4) - ->where('ut.team_id', 5) - ->where('up.nhance_branch_id', $nhance_branch_id) - ->get() - ->getResultArray(); + // ── Sales Head (Role 1 or 5) ───────────────────────────────── + } elseif (in_array($role, [1, 5])) { - $data['sales_manager_ids'] = array_column($data['users'], 'id'); + $data['sales_role'] = 'Sales Head'; + $data['users'] = $db->table('user_profiles up') + ->select('up.id, up.first_name, up.last_name, up.nhance_branch_id') + ->join('user_teams ut', 'ut.user_id = up.id') + ->where('up.is_active', 1) + ->where('ut.is_active', 1) + ->where('up.role', 4) + ->where('ut.team_id', 5) + ->where('up.nhance_branch_id', $nhance_branch_id) + ->get() + ->getResultArray(); + + $ids = array_column($data['users'], 'id'); + $data['sales_manager_ids'] = $ids; + $data['assigned_ids'] = $ids; // same value, both available } return $data; @@ -751,6 +773,138 @@ class SalesController extends BaseController } } + // ==================== SALES TARGET APIs ==================== + /** + * Get all sales targets + * GET /api/sales/targets + */ + public function getTargets() + { + try { + $targets = $this->targetModel->findAll(); + return $this->respond([ + 'status' => 'success', + 'data' => $targets + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Get sales target by user + * GET /api/sales/targets/user/{userId} + */ + public function getTargetByUser($userId) + { + try { + $targets = $this->targetModel->where('user_id', $userId)->findAll(); + return $this->respond([ + 'status' => 'success', + 'data' => $targets + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Get sales target by FY year + * GET /api/sales/targets/fy/{fyYear} + */ + public function getTargetByFY($fyYear) + { + try { + $targets = $this->targetModel->where('fy_year', $fyYear)->findAll(); + return $this->respond([ + 'status' => 'success', + 'data' => $targets + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Create sales target + * POST /api/sales/targets + */ + public function createTarget() + { + try { + $data = $this->request->getJSON(true); + $data['created_by'] = $this->getUserId(); + $data['updated_by'] = $this->getUserId(); + + if (!$this->targetModel->insert($data)) { + return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $targetId = $this->targetModel->getInsertID(); + $target = $this->targetModel->find((int)$targetId); + + return $this->respondCreated([ + 'status' => 'success', + 'message' => 'Sales target created successfully', + 'data' => $target + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Update sales target + * PUT /api/sales/targets/{id} + */ + public function updateTarget($id) + { + try { + $target = $this->targetModel->find((int)$id); + if (!$target) { + return $this->failNotFound('Sales target not found'); + } + + $data = $this->request->getJSON(true); + $data['updated_by'] = $this->getUserId(); + + if (!$this->targetModel->update($id, $data)) { + return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $updatedTarget = $this->targetModel->find((int)$id); + return $this->respond([ + 'status' => 'success', + 'message' => 'Sales target updated successfully', + 'data' => $updatedTarget + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Delete sales target + * DELETE /api/sales/targets/{id} + */ + public function deleteTarget($id) + { + try { + $target = $this->targetModel->find((int)$id); + if (!$target) { + return $this->failNotFound('Sales target not found'); + } + + $this->targetModel->delete((int)$id); + return $this->respondDeleted([ + 'status' => 'success', + 'message' => 'Sales target deleted successfully' + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + // ==================== HELPER METHODS ==================== /** @@ -773,52 +927,23 @@ class SalesController extends BaseController // ==================== Dashboard ==================== - public function dashboard(){ - - $logged_user_id = get_session_userid(); - $role = get_role_id(); - $team_id = user_team(); + public function dashboard() + { $payload = $this->request->getGet(); + $base = $this->getSalesStaffData(); + $salesRole = $base['sales_role']; + $salesManagerIds = $base['sales_manager_ids']; + $userId = get_session_userid(); - $db = \Config\Database::connect(); + // Get branch id from users array + $nhanceBranchId = $base['users'][0]['nhance_branch_id'] ?? null; - $row = $db->table('user_profiles') - ->select('*') - ->where('is_active', 1) - ->where('id', $logged_user_id) - ->get() - ->getRowArray(); - - $nhance_branch_id = $row ? $row['nhance_branch_id'] : null; - - // dd($logged_user_id, $nhance_branch_id, $role, $team_id ); - - if (in_array($role,[1,5])) { - - $sales_manager_ids = array_column( - $db->table('user_profiles up') - ->select('up.id') - ->join('user_teams ut', 'ut.user_id = up.id') - ->where([ - 'up.is_active' => 1, - 'ut.is_active' => 1, - 'up.role' => 4, - 'ut.team_id' => 5, - 'up.nhance_branch_id' => $nhance_branch_id - ]) - ->get() - ->getResultArray(), - 'id' - ); - $this->branchLevelDashboard($nhance_branch_id,$sales_manager_ids); - - } - elseif ($role == 4 && in_array(5, $team_id)) { - $sales_manager_ids = [$logged_user_id]; - $this->salesManagerLevelDashboard($logged_user_id,$sales_manager_ids, $payload); + if ($salesRole === 'Sales Head') { + $this->branchLevelDashboard($nhanceBranchId, $salesManagerIds); + } elseif ($salesRole === 'Sales Manager') { + $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload); } - - } + } public function branchLevelDashboard($branchId,$sales_manager_ids) { @@ -1001,7 +1126,7 @@ class SalesController extends BaseController ->findAll(); $data = [ - 'target' => $targetAmount, + 'target_amt' => $targetAmount, 'achieved' => $achievedAmount, 'remaining' => $remainingAmount, 'percent' => $achievementPercent, @@ -1093,9 +1218,23 @@ class SalesController extends BaseController /* ---------------- SUMMARY ---------------- */ - $summary = ucfirst($input['activity_type']) . - ' with ' . - $lead_data['company_name']; + $activityType = ucfirst($input['activity_type']); + + $prepositionMap = [ + 'Email' => 'to', + 'Call' => 'with', + 'Meeting' => 'with', + 'Visit' => 'to', + 'Demo' => 'with', + 'Share Docs' => 'to', + 'To Do' => 'for' + ]; + + $preposition = $prepositionMap[$activityType] ?? 'with'; + + $summary = "Activity scheduled : {$activityType} {$preposition} {$lead_data['company_name']}"; + + /* ---------------- GOOGLE PAYLOAD ---------------- */ diff --git a/app/Controllers/TicketServiceController.php b/app/Controllers/TicketServiceController.php index f95f547d..8e96a780 100644 --- a/app/Controllers/TicketServiceController.php +++ b/app/Controllers/TicketServiceController.php @@ -30,8 +30,9 @@ use App\Models\TicketMasterModel; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Fill; -class TicketServiceController extends BaseController +class TicketServiceController extends AdminController { use ResponseTrait; @@ -1840,39 +1841,86 @@ class TicketServiceController extends BaseController { } - - + + // -------------------------------------------------------------------------------------------------------------------------------- - - public function tpaClaimDumpImporter($params) + /** + * Resolve and validate Claim Dump file metadata and physical file for TPA imports. + * + * @param int|null $fileId + * @return array{status:bool,message?:string,fileData?:array,filePath?:string} + */ + private function resolveTpaClaimDumpFile(?int $fileId): array { - $file_id = $params['file_id'] ?? null; // Move outside try to ensure catch can see it + if (empty($fileId)) { + return [ + 'status' => false, + 'message' => 'File ID is missing', + ]; + } + + $fileData = $this->claimDumpFileModel->find((int) $fileId); + if (!$fileData) { + return [ + 'status' => false, + 'message' => 'Invalid file ID. No file data found', + ]; + } + + $filePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR . $fileData['file_name']; + + if (!is_file($filePath)) { + return [ + 'status' => false, + 'message' => 'Claim dump file not found', + ]; + } + + return [ + 'status' => true, + 'fileData' => $fileData, + 'filePath' => $filePath, + ]; + } + + /** + * First TPA-wise job – parse Excel and push data into TPA staging table. + * + * @param array $params + * @return array + */ + public function tpaClaimDumpImporter(array $params) + { + $file_id = isset($params['file_id']) ? (int) $params['file_id'] : null; try { - $file_path = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR; + $resolved = $this->resolveTpaClaimDumpFile($file_id); - if (!$file_id) { - return ['status' => false, 'message' => 'File ID is missing']; + if ($resolved['status'] === false) { + return [ + 'status' => false, + 'message' => $resolved['message'] ?? 'Unable to resolve claim dump file', + ]; } - $fileData = $this->claimDumpFileModel->find((int)$file_id); - if (!$fileData) { - return ['status' => false, 'message' => 'Invalid file ID. No file data found']; - } - - $file_full_path = $file_path . $fileData['file_name']; - if (!is_file($file_full_path)) { - return ['status' => false, 'message' => 'Claim dump file not found']; - } + $fileData = $resolved['fileData']; + $filePath = $resolved['filePath']; $handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0); - $result = $handler->runTpaClaimDumpInsert($file_full_path, $file_id); + $result = $handler->runTpaClaimDumpInsert($filePath, $file_id); if (!empty($result['status']) && $result['status'] === true) { - Jobs::addJob(['job_name' => 'tpaClaimDumpToTicketMasterImporters', 'payload' => ['file_id' => $file_id]]); + Jobs::addJob([ + 'job_name' => 'tpaClaimDumpToTicketMasterImporters', + 'payload' => ['file_id' => $file_id], + ]); } else { // FORCE FAIL LOGIC - $this->markAsFailed($file_id, $result['message'] ?? 'System error contact admin', $fileData['created_by'] ?? null); + $this->markAsFailed( + $file_id, + $result['message'] ?? 'System error contact admin', + $fileData['created_by'] ?? null + ); } return $result; @@ -1884,9 +1932,9 @@ class TicketServiceController extends BaseController } return [ - 'status' => false, - 'message' => 'TPA Claim dump import failed', - 'error_data' => $th->getMessage() + 'status' => false, + 'message' => 'TPA Claim dump import failed', + 'error_data' => $th->getMessage(), ]; } } @@ -1908,30 +1956,37 @@ class TicketServiceController extends BaseController return $this->claimDumpFileModel->update($file_id, $data); } - public function tpaClaimDumpToTicketMasterImporters($params) + /** + * Second TPA-wise job – move data from TPA staging into ticket_master. + * + * @param array $params + * @return array + */ + public function tpaClaimDumpToTicketMasterImporters(array $params) { - $file_id = $params['file_id'] ?? null; + $file_id = isset($params['file_id']) ? (int) $params['file_id'] : null; $fileData = null; try { - if (!$file_id) { - return ['status' => false, 'message' => 'File ID is missing']; + $resolved = $this->resolveTpaClaimDumpFile($file_id); + + if ($resolved['status'] === false) { + return [ + 'status' => false, + 'message' => $resolved['message'] ?? 'Unable to resolve claim dump file', + ]; } - $fileData = $this->claimDumpFileModel->where('id', $file_id)->first(); - - if (!$fileData) { - return ['status' => false, 'message' => 'Invalid file ID. No file data found to import']; - } + $fileData = $resolved['fileData']; $handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0); - $result = $handler->runTicketMasterInsert($params); + $result = $handler->runTicketMasterInsert($params); if (!empty($result['status']) && $result['status'] === true) { // Success: Update the status to success $this->claimDumpFileModel->update($file_id, [ 'status' => 'success', - 'reason' => null + 'reason' => null, ]); } else { // Logic failure: The runTicketMasterInsert returned status false @@ -1957,8 +2012,8 @@ class TicketServiceController extends BaseController 'message' => $th->getMessage(), 'error_data' => [ 'line' => $th->getLine(), - 'file' => $th->getFile() - ] + 'file' => $th->getFile(), + ], ]; } } @@ -2011,5 +2066,176 @@ class TicketServiceController extends BaseController return $data; } - + public function getTpaClaimDumpErrorData($file_id) + { + $file_id = (int) $file_id; + + // Ensure we always have a Response object, even if controller was instantiated manually + $response = $this->response ?? service('response'); + + if ($file_id <= 0) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'Invalid file id']); + } + + $fileData = $this->claimDumpFileModel->find($file_id); + + if (!$fileData) { + return $response + ->setStatusCode(ResponseInterface::HTTP_NOT_FOUND) + ->setJSON(['status' => false, 'message' => 'File record not found']); + } + + $tpaId = (int) ($fileData['tpa_id'] ?? 0); + + if ($tpaId <= 0) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'TPA not linked with this file']); + } + + // Resolve TPA staging table based on configured TPA IDs + $tableName = match ($tpaId) { + (int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal', + (int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi', + (int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist', + (int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl', + (int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance', + (int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici', + default => null, + }; + + if ($tableName === null) { + return $response + ->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST) + ->setJSON(['status' => false, 'message' => 'Unsupported TPA for error dump export']); + } + + $db = \Config\Database::connect(); + $builder = $db->table($tableName); + + // Fetch only records belonging to this file and having a rejection reason + $rows = $builder + ->where('file_id', $file_id) + ->where('is_active', 1) + ->where('master_reject_reason IS NOT NULL', null, false) + ->get() + ->getResultArray(); + + if (empty($rows)) { + // No error records – return a small Excel file with just a message + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Errors'); + $sheet->setCellValue('A1', 'Message'); + $sheet->setCellValue('A2', 'No rejected records found for this file.'); + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + ob_start(); + $writer->save('php://output'); + $excelOutput = ob_get_clean(); + + $filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx'; + + return $response + ->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->setHeader('Cache-Control', 'max-age=0') + ->setBody($excelOutput); + } + + // Columns that must NOT be included in the Excel export + $excludedColumns = [ + 'id', + 'client_id', + 'client_policy_id', + 'ticket_id', + 'file_id', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', + 'is_active', + ]; + + $firstRow = $rows[0]; + + // Build header mapping and track the "Rejected Reason" column index + $dbColumnOrder = []; + $displayHeaderLabels = []; + $rejectedReasonColIdx = null; // 1-based index for Excel column + + foreach ($firstRow as $columnName => $_) { + if (in_array($columnName, $excludedColumns, true)) { + continue; + } + + $dbColumnOrder[] = $columnName; + + if ($columnName === 'master_reject_reason' || $columnName === 'master_rejection_reason_key') { + $displayHeaderLabels[] = 'Rejected Reason'; + $rejectedReasonColIdx = count($displayHeaderLabels); // current column index (1-based) + } else { + $displayHeaderLabels[] = $columnName; + } + } + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Errors'); + + // Header row + $rowIndex = 1; + foreach ($displayHeaderLabels as $colIndex => $headerLabel) { + $columnLetter = Coordinate::stringFromColumnIndex($colIndex + 1); + $cellAddress = $columnLetter . $rowIndex; + $sheet->setCellValue($cellAddress, $headerLabel); + } + + // Data rows + $rowIndex = 2; + foreach ($rows as $row) { + foreach ($dbColumnOrder as $i => $columnName) { + $colIndex = $i + 1; + $columnLetter = Coordinate::stringFromColumnIndex($colIndex); + $cellAddress = $columnLetter . $rowIndex; + $value = $row[$columnName] ?? null; + + $sheet->setCellValue($cellAddress, $value); + + // Highlight the "Rejected Reason" values + if ($rejectedReasonColIdx !== null && $colIndex === $rejectedReasonColIdx) { + $sheet->getStyle($cellAddress) + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setARGB('FFFFF4B2'); // light yellow + } + } + + $rowIndex++; + } + + // Autosize columns for better readability + $highestColumnIndex = count($displayHeaderLabels); + for ($col = 1; $col <= $highestColumnIndex; $col++) { + $columnLetter = Coordinate::stringFromColumnIndex($col); + $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + } + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + ob_start(); + $writer->save('php://output'); + $excelOutput = ob_get_clean(); + + $filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx'; + + return $response + ->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->setHeader('Cache-Control', 'max-age=0') + ->setBody($excelOutput); + } + } diff --git a/app/Controllers/VidalApiController.php b/app/Controllers/VidalApiController.php index 04be9976..3836cb4b 100644 --- a/app/Controllers/VidalApiController.php +++ b/app/Controllers/VidalApiController.php @@ -567,6 +567,8 @@ class VidalApiController extends BaseController // dd($TicketData); + log_message('error', "VIDAL - Claim Status | Total tickets fetched for status update: " . count($TicketData)); + if (!$TicketData) { log_message('error', "VIDAL - Claim Status | Claims not found to update status"); return $this->response->setJSON(['status' => false,'message' => 'Claims not found' ]); @@ -599,7 +601,12 @@ class VidalApiController extends BaseController if ($response['status'] != true || empty($response['data']['data']['claims'][0])) { 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]]; + $error_data[$claimId] = [ + 'status' => false, + 'message' => 'API call failed.', + 'data' => $response + ]; + continue; } // Extract claim status diff --git a/app/Helpers/excel_import_export_helper.php b/app/Helpers/excel_import_export_helper.php index c7ba2982..dcffd660 100755 --- a/app/Helpers/excel_import_export_helper.php +++ b/app/Helpers/excel_import_export_helper.php @@ -766,18 +766,18 @@ if(!function_exists('query_construct_for_emp_policy_id')){ $gst_alise = ''; } $subQuery .= "SELECT " . intval($index) . $row_id_alise; - $subQuery .= "'" . $employee[$col_index['emp_name']] . "'" . $emp_name_alise; - $subQuery .= "'" . $employee[$col_index['emp_code']] . "'" . $emp_code_alise; - $subQuery .= "'" . $employee[$col_index['emp_dob']] . "'" . $emp_dob_alise; - $subQuery .= "'" . $employee[$col_index['emp_gender']] . "'" . $emp_gender_alise; - $subQuery .= "'" . $employee[$col_index['pre_existing_alignments']] . "'" . $pre_existing_alignments_alise; - $subQuery .= "'" . $employee[$col_index['basic_cover_si']] . "'" . $basic_cover_si_alise; - $subQuery .= "'" . $employee[$col_index['emp_relationship']] . "'" . $emp_relationship_alise; - $subQuery .= "'" . $employee[$col_index['policy_end_date']] . "'" . $policy_end_date_alise; - $subQuery .= "'" . $employee[$col_index['days']] . "'" . $days_alise; - $subQuery .= "'" . $employee[$col_index['premium']] . "'" . $premium_alise; - $subQuery .= "'" . $employee[$col_index['rata_premimum']] . "'" . $rata_premimum_alise; - $subQuery .= "'" . $employee[$col_index['gst']] . "'" . $gst_alise; + $subQuery .= $db->escape($employee[$col_index['emp_name']]) . $emp_name_alise; + $subQuery .= $db->escape($employee[$col_index['emp_code']]) . $emp_code_alise; + $subQuery .= $db->escape($employee[$col_index['emp_dob']]) . $emp_dob_alise; + $subQuery .= $db->escape($employee[$col_index['emp_gender']]) . $emp_gender_alise; + $subQuery .= $db->escape($employee[$col_index['pre_existing_alignments']]) . $pre_existing_alignments_alise; + $subQuery .= $db->escape($employee[$col_index['basic_cover_si']]) . $basic_cover_si_alise; + $subQuery .= $db->escape($employee[$col_index['emp_relationship']]) . $emp_relationship_alise; + $subQuery .= $db->escape($employee[$col_index['policy_end_date']]) . $policy_end_date_alise; + $subQuery .= $db->escape($employee[$col_index['days']]) . $days_alise; + $subQuery .= $db->escape($employee[$col_index['premium']]) . $premium_alise; + $subQuery .= $db->escape($employee[$col_index['rata_premimum']]) . $rata_premimum_alise; + $subQuery .= $db->escape($employee[$col_index['gst']]) . $gst_alise; } // Complete the query @@ -893,18 +893,18 @@ if(!function_exists('query_construct_second_stage')){ $gst_alise = ''; } $subQuery .= "SELECT " . intval($index) . $row_id_alise; - $subQuery .= "'" . $employee[$col_index['emp_name']] . "'" . $emp_name_alise; - $subQuery .= "'" . $employee[$col_index['emp_code']] . "'" . $emp_code_alise; - $subQuery .= "'" . $employee[$col_index['emp_dob']] . "'" . $emp_dob_alise; - $subQuery .= "'" . $employee[$col_index['emp_gender']] . "'" . $emp_gender_alise; - $subQuery .= "'" . $employee[$col_index['pre_existing_alignments']] . "'" . $pre_existing_alignments_alise; - $subQuery .= "'" . $employee[$col_index['basic_cover_si']] . "'" . $basic_cover_si_alise; - $subQuery .= "'" . $employee[$col_index['emp_relationship']] . "'" . $emp_relationship_alise; - $subQuery .= "'" . $employee[$col_index['policy_end_date']] . "'" . $policy_end_date_alise; - $subQuery .= "'" . $employee[$col_index['days']] . "'" . $days_alise; - $subQuery .= "'" . $employee[$col_index['premium']] . "'" . $premium_alise; - $subQuery .= "'" . $employee[$col_index['rata_premimum']] . "'" . $rata_premimum_alise; - $subQuery .= "'" . $employee[$col_index['gst']] . "'" . $gst_alise; + $subQuery .= $db->escape($employee[$col_index['emp_name']]) . $emp_name_alise; + $subQuery .= $db->escape($employee[$col_index['emp_code']]) . $emp_code_alise; + $subQuery .= $db->escape($employee[$col_index['emp_dob']]) . $emp_dob_alise; + $subQuery .= $db->escape($employee[$col_index['emp_gender']]) . $emp_gender_alise; + $subQuery .= $db->escape($employee[$col_index['pre_existing_alignments']]) . $pre_existing_alignments_alise; + $subQuery .= $db->escape($employee[$col_index['basic_cover_si']]) . $basic_cover_si_alise; + $subQuery .= $db->escape($employee[$col_index['emp_relationship']]) . $emp_relationship_alise; + $subQuery .= $db->escape($employee[$col_index['policy_end_date']]) . $policy_end_date_alise; + $subQuery .= $db->escape($employee[$col_index['days']]) . $days_alise; + $subQuery .= $db->escape($employee[$col_index['premium']]) . $premium_alise; + $subQuery .= $db->escape($employee[$col_index['rata_premimum']]) . $rata_premimum_alise; + $subQuery .= $db->escape($employee[$col_index['gst']]) . $gst_alise; } // Complete the query diff --git a/app/Libraries/DataServiceSqlite.php b/app/Libraries/DataServiceSqlite.php index 226cba32..442efc63 100755 --- a/app/Libraries/DataServiceSqlite.php +++ b/app/Libraries/DataServiceSqlite.php @@ -65,12 +65,23 @@ class DataServiceSqlite $this->sqliteDb->exec($sql); } + private const ALLOWED_TABLES = ['http', 'log']; + public function insertData(array $data, string $tableName): bool { log_message('error', 'SQLite insert called: '); + + if (!in_array($tableName, self::ALLOWED_TABLES, true)) { + log_message('error', 'SQLite insert rejected: invalid table name: ' . $tableName); + return false; + } + unset($data['context']); - $columns = implode(', ', array_keys($data)); - // print_r($columns); + + $safeColumns = array_map(function ($col) { + return preg_replace('/[^a-zA-Z0-9_]/', '', $col); + }, array_keys($data)); + $columns = implode(', ', $safeColumns); $placeholders = implode(', ', array_fill(0, count($data), '?')); $sql = "INSERT INTO $tableName ($columns) VALUES ($placeholders)"; diff --git a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php index ffb4b970..103989bf 100644 --- a/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/AbhiClaimImportService.php @@ -119,6 +119,19 @@ class AbhiClaimImportService extends BaseTpaClaimImportService 'Settled' => 11, 'Rejected' => 8, 'Cancelled' => 13, + 'Approved' => 9, + 'Closed' => 12, + 'Query' => 4, + 'Required Information' => 4, + 'In-Progress' => 5, + 'Paid' => 11, + 'Denied' => 8, + 'Cancelled' => 13, + 'Processed' => 61, + 'Information Awaited' => 4, + 'Denied Letter Sent' => 66, + 'Cashless Document Awaited' => 3, + 'RI Cancelled' => 13, ]; protected $dateColumns = [ @@ -164,14 +177,30 @@ class AbhiClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_abhi cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - $builder = $this->db->table('claims_dump_abhi'); - return $builder->updateBatch($data, 'id'); + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_abhi')->updateBatch($updateData, 'id') !== false; } @@ -329,7 +358,7 @@ class AbhiClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']); $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php index 3a6a5777..137caf25 100644 --- a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php @@ -81,7 +81,7 @@ abstract class BaseTpaClaimImportService $this->db->transBegin(); try { - $file_id = $params['file_id']; + $file_id = $params['file_id']; $ticketMasterData = $this->mapClaimMasterData($file_id); // Check if mapping failed @@ -101,6 +101,12 @@ abstract class BaseTpaClaimImportService $this->db->transRollback(); return ['status' => false, 'message' => 'Ticket Master Claim bulk insert failed']; } + // Map newly created ticket IDs back to the TPA staging table + if (!$this->updateTicketIdInTPATable($file_id)) { + $this->db->transRollback(); + return ['status' => false, 'message' => 'Updating ticket_id in TPA table failed']; + } + $message .= 'Ticket Master Claim bulk insert success. '; $hasExecutedTask = true; }else{ @@ -328,6 +334,16 @@ abstract class BaseTpaClaimImportService return $employeeData ?? []; } + public function checkStatusMapping($statusArray, $statusString) + { + foreach ($statusArray as $key => $value) { + if (strtolower($statusString) == strtolower($key) || strtolower($statusString) == strtolower(trim($key))) { + return $value; + } + } + return null; + } + /** * Map Excel rows to TPA table structure */ @@ -349,9 +365,9 @@ abstract class BaseTpaClaimImportService abstract protected function importClaimMaster(array $data): bool; /** - * Update TPA table with ticket_master primary key + * Update TPA table with ticket_master primary key for a given file. */ - abstract protected function updateTicketIdInTPATable(): bool; + abstract protected function updateTicketIdInTPATable(int $fileId): bool; /** * Update TPA table with ticket_master insert rejected reason diff --git a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php index 324fd3a7..7123230c 100644 --- a/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/FhplClaimImportService.php @@ -181,6 +181,13 @@ class FhplClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, + 'Paid' => 11, + 'Approved' => 9, + 'Closed' => 12, + 'Under Process' => 5, + 'Query' => 4, + 'Required Information' => 4, + 'In-Progress' => 5, ]; protected $dateColumns = [ @@ -235,14 +242,31 @@ class FhplClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + // Join ticket_master with FHPL staging on file_id + claim_dump_ref_id -> id + $rows = $this->db->table('claims_dump_fhpl cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_fhpl'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_fhpl')->updateBatch($updateData, 'id') !== false; } @@ -400,7 +424,7 @@ class FhplClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['current_claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['current_claim_status']) ?? 61; $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php index 132986a2..6e055139 100644 --- a/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/IciciClaimImportService.php @@ -106,7 +106,13 @@ class IciciClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'PAID' => 11, + 'SETTLED' => 11, 'REJECTED' => 8, + 'APPROVED' => 9, + 'CLOSED' => 12, + 'QUERY' => 4, + 'REQUIRED INFORMATION' => 4, + 'IN-PROGRESS' => 5, ]; protected $dateColumns = [ @@ -152,14 +158,30 @@ class IciciClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_icici cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_icici'); - return $builder->upsertBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_icici')->updateBatch($updateData, 'id') !== false; } @@ -287,7 +309,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService $item['tpa_id'] = $client_policy_data['tpa_id'] ?? null; $item['acm_id'] = $client_policy_data['acm_id'] ?? null; $item['policy_no'] = $client_policy_data['policy_no'] ?? null; - $item['relationship'] = $this->convertRelation($row['relation_group'] ?? ''); + $item['relationship'] = $this->convertRelation($row['relation'] ?? ''); $employee_data = $this->getEmployeeDetails($file_data['client_id'], $file_data['client_policy_id'], $row['employee_member_id'], $item['relationship']); @@ -317,7 +339,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['updated_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['updated_status']) ?? 61; $item['claim_dump_ref_id'] = $row['id']; $item['file_id'] = $file_id; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php index d052c9f5..4a2952fb 100644 --- a/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/MediAssistClaimImportService.php @@ -158,12 +158,15 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService protected $statusMapping = [ 'Settled' => 11, 'Rejected' => 8, + 'Paid' => 11, 'Denied' => 8, 'Cancelled' => 13, 'Processed' => 61, 'Information Awaited' => 4, 'Denied Letter Sent' => 66, 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, ]; /** @@ -192,16 +195,30 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; - } - - $builder = $this->db->table('claims_dump_medi_assist'); - $builder->insertBatch($data); + $rows = $this->db->table('claims_dump_medi_assist cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); - return true; + if (empty($rows)) { + return true; + } + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_medi_assist')->updateBatch($updateData, 'id') !== false; } @@ -356,8 +373,8 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService // Meta fields $item['claim_dump_ref_id'] = $row['id']; - $item['file_id'] = $file_id; - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['file_id'] = $file_id; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61; $item['created_by'] = $file_data['created_by'] ?? null; $item['claim_type'] = 1; $item['priority'] = 1; diff --git a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php index 770caadd..afe95c4f 100644 --- a/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/RcareClaimImportService.php @@ -96,14 +96,15 @@ class RcareClaimImportService extends BaseTpaClaimImportService ]; protected $statusMapping = [ + 'CL Paid with Settlement Letter' => 11, 'Settled' => 11, + 'Paid' => 11, 'Rejected' => 8, - 'Denied' => 8, - 'Cancelled' => 13, - 'Processed' => 61, - 'Information Awaited' => 4, - 'Denied Letter Sent' => 66, - 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, + 'CL Rejected' => 8, + 'CL Approved' => 9, + 'AL Closed' => 12, ]; protected $dateColumns = [ @@ -143,14 +144,30 @@ class RcareClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_reliance cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_reliance'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_reliance')->updateBatch($updateData, 'id') !== false; } @@ -307,7 +324,7 @@ class RcareClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['final_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['final_status']) ?? 61; $item['file_id'] = $file_id; $item['created_by'] = $file_data['created_by'] ?? null; $item['claim_dump_ref_id'] = $row['id']; diff --git a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php index c90d580b..70a1340c 100644 --- a/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php +++ b/app/Libraries/TPAClaimsImportServices/VidalClaimImportService.php @@ -349,10 +349,17 @@ class VidalClaimImportService extends BaseTpaClaimImportService ]; protected $statusMapping = [ - 'CL Paid with Settlement Letter' => 11, - 'CL Rejected' => 8, - 'CL Approved' => 9, - 'AL Closed' => 12, + 'Settled' => 11, + 'Rejected' => 8, + 'Paid' => 11, + 'Denied' => 8, + 'Cancelled' => 13, + 'Processed' => 61, + 'Information Awaited' => 4, + 'Denied Letter Sent' => 66, + 'Cashless Document Awaited' => 3, + 'Approved' => 9, + 'Closed' => 12, ]; @@ -382,14 +389,30 @@ class VidalClaimImportService extends BaseTpaClaimImportService } - public function updateTicketIdInTPATable(): bool + public function updateTicketIdInTPATable(int $fileId): bool { - if (empty($data)) { - return false; + $rows = $this->db->table('claims_dump_vidal cd') + ->select('cd.id, tm.id AS ticket_id') + ->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner') + ->where('cd.is_active', 1) + ->where('cd.file_id', $fileId) + ->where('cd.ticket_id IS NULL') + ->get() + ->getResultArray(); + + if (empty($rows)) { + return true; } - - $builder = $this->db->table('claims_dump_vidal'); - return $builder->updateBatch($data, 'id'); + + $updateData = []; + foreach ($rows as $row) { + $updateData[] = [ + 'id' => $row['id'], + 'ticket_id' => $row['ticket_id'], + ]; + } + + return $this->db->table('claims_dump_vidal')->updateBatch($updateData, 'id') !== false; } @@ -542,7 +565,7 @@ class VidalClaimImportService extends BaseTpaClaimImportService } // Meta fields - $item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61; + $item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61; $item['file_id'] = $file_id; $item['claim_dump_ref_id'] = $row['id']; $item['created_by'] = $file_data['created_by'] ?? null; diff --git a/app/Models/ClientModel.php b/app/Models/ClientModel.php index c99a50f4..0a291e7c 100755 --- a/app/Models/ClientModel.php +++ b/app/Models/ClientModel.php @@ -228,8 +228,14 @@ class ClientModel extends Model return $result; } + private const ALLOWED_CONTACT_FIELDS = ['email', 'mobile', 'phone', 'name', 'contact_name', 'contact_email', 'contact_mobile']; + public function isDuplicateByClientBranch($value, $field, $clientId, $branchId) { + if (!in_array($field, self::ALLOWED_CONTACT_FIELDS, true)) { + throw new \InvalidArgumentException("Invalid field name: {$field}"); + } + $builder = $this->db->table('level_contacts lc') ->select('lc.id') ->join('client_branch cb', 'lc.ref_id = cb.id', 'left') diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php index 8171df0c..92efedf8 100755 --- a/app/Models/EmployeePolicyModel.php +++ b/app/Models/EmployeePolicyModel.php @@ -1146,6 +1146,8 @@ class EmployeePolicyModel extends Model $endorsement_condition = "{$insurer_or_tpa}" === 'tpa' ? "AND (a.endorsement_id IS NULL OR a.endorsement_id = '')" : "AND (a.endorsement_id IS NULL OR a.endorsement_id = '')"; + $add_one_day = (int)$add_one_day; + $query = $this->db->query(" SELECT DISTINCT a.id as endorsement_primarykey, @@ -1688,30 +1690,29 @@ class EmployeePolicyModel extends Model public function bulkUpdate($emp_details) { // Extract IDs, tpa_ids, and uhids - $ids = array_column($emp_details, 'id'); + $ids = array_map('intval', array_column($emp_details, 'id')); $tpa_ids = array_column($emp_details, 'tpa_id'); $uhids = array_column($emp_details, 'uhid'); - // Escape values for SQL - $escapedIds = array_map([$this->db, 'escape'], $ids); + // Escape string values for SQL $escapedTpaIds = array_map([$this->db, 'escape'], $tpa_ids); $escapedUhids = array_map([$this->db, 'escape'], $uhids); - // Construct the CASE statements + // Construct the CASE statements with int-cast IDs $caseTpaId = array_map(function($id, $tpa_id) { - return "WHEN id = $id THEN $tpa_id"; - }, $escapedIds, $escapedTpaIds); + return "WHEN id = {$id} THEN {$tpa_id}"; + }, $ids, $escapedTpaIds); $caseUhid = array_map(function($id, $uhid) { - return "WHEN id = $id THEN $uhid"; - }, $escapedIds, $escapedUhids); + return "WHEN id = {$id} THEN {$uhid}"; + }, $ids, $escapedUhids); // Convert cases to a string $caseTpaIdString = implode(' ', $caseTpaId); $caseUhidString = implode(' ', $caseUhid); // Convert ids to a string - $idsString = implode(', ', $escapedIds); + $idsString = implode(', ', $ids); // Construct the SQL query $sql = " @@ -1837,18 +1838,19 @@ class EmployeePolicyModel extends Model $groupKeys = []; foreach ($endorsement_details as $row) { - $groupKey = $this->db->escape($row['group_key']); + $groupKey = (int)$row['group_key']; $endorsementId = $this->db->escape($row['endorsement_id']); $caseParts[] = "WHEN group_key = {$groupKey} THEN {$endorsementId}"; $groupKeys[] = $groupKey; } + $groupKeysStr = implode(', ', $groupKeys); $sql = " UPDATE emp_endorsement SET endorsement_id = CASE " . implode(' ', $caseParts) . " END, status = 'complete' - WHERE group_key IN (" . implode(', ', $groupKeys) . ") + WHERE group_key IN ({$groupKeysStr}) "; $this->db->query($sql); @@ -1866,26 +1868,37 @@ class EmployeePolicyModel extends Model public function bulkUpdateForCorrection($emp_details){ + $ids = []; + $caseStatements = []; + + $employeeTableFields = $this->db->getFieldNames('employees'); + foreach ($emp_details as $employee) { - $id = $this->db->escape($employee['id']); + $id = (int)$employee['id']; $ids[] = $id; foreach ($employee as $field => $value) { if ($field === 'id') continue; + + $safeField = preg_replace('/[^a-zA-Z0-9_]/', '', $field); + if (!in_array($safeField, $employeeTableFields, true)) { + continue; + } + $escapedValue = $this->db->escape($value); - if (!isset($caseStatements[$field])) { - $caseStatements[$field] = []; + if (!isset($caseStatements[$safeField])) { + $caseStatements[$safeField] = []; } - $caseStatements[$field][] = "WHEN id = $id THEN $escapedValue"; + $caseStatements[$safeField][] = "WHEN id = {$id} THEN {$escapedValue}"; } } // Construct the CASE strings $caseStrings = []; foreach ($caseStatements as $field => $cases) { - $caseStrings[] = "$field = CASE " . implode(' ', $cases) . " END"; + $caseStrings[] = "{$field} = CASE " . implode(' ', $cases) . " END"; } // Convert ids to a string @@ -1941,7 +1954,7 @@ class EmployeePolicyModel extends Model ->getRowArray(); // Determine if one day should be added for deletion - $add_one_day = (!empty($result) && $result['deletion_add_day'] == 1) ? 1 : 0; + $add_one_day = (int)((!empty($result) && $result['deletion_add_day'] == 1) ? 1 : 0); $query = $this->db->query(" @@ -2130,14 +2143,17 @@ class EmployeePolicyModel extends Model $caseEndorsementId = "CASE "; $ids = []; foreach ($array as $item) { - $caseStatus .= "WHEN id = {$item['id']} THEN '{$item['status']}' "; - $caseEndorsementId .= "WHEN id = {$item['id']} THEN '{$item['endorsement_id']}' "; - $ids[] = $item['id']; + $safeId = (int)$item['id']; + $safeStatus = $this->db->escape($item['status']); + $safeEndorsementId = $this->db->escape($item['endorsement_id']); + $caseStatus .= "WHEN id = {$safeId} THEN {$safeStatus} "; + $caseEndorsementId .= "WHEN id = {$safeId} THEN {$safeEndorsementId} "; + $ids[] = $safeId; } $caseStatus .= "END"; $caseEndorsementId .= "END"; $ids = implode(',', $ids); - $query = "UPDATE emp_endorsement SET status = $caseStatus, endorsement_id = $caseEndorsementId WHERE id IN ($ids);"; // Execute the query + $query = "UPDATE emp_endorsement SET status = $caseStatus, endorsement_id = $caseEndorsementId WHERE id IN ($ids);"; $this->db->query($query); } diff --git a/app/Models/ExpenseModel.php b/app/Models/ExpenseModel.php new file mode 100644 index 00000000..7d4a37c1 --- /dev/null +++ b/app/Models/ExpenseModel.php @@ -0,0 +1,73 @@ +validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } else { // $fromDate = date('Y-m-d', strtotime('-30 days')); @@ -835,18 +852,16 @@ // Check if the start date and end date are provided if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - // $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - // ->where('policy_transaction.' . $date_type . '<=', $endDate); - if($date_type == "policy_issue_date"){ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } @@ -1064,6 +1079,7 @@ // Optimize Date Filtering if (!empty($start_date) && !empty($end_date) && !empty($date_type)) { + $this->validateDateType($date_type); $startDate = change_date_format($start_date, 'd/m/Y', 'Y-m-d 00:00:00'); $endDate = change_date_format($end_date, 'd/m/Y', 'Y-m-d 23:59:59'); @@ -1165,6 +1181,7 @@ if ($start_date != 0 && $end_date != 0 && $date_type != 0) { + $this->validateDateType($date_type); $startDate = change_date_format($start_date, 'd/m/Y', 'Y-m-d 00:00:00'); $endDate = change_date_format($end_date, 'd/m/Y', 'Y-m-d 23:59:59'); @@ -1172,8 +1189,8 @@ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } else { @@ -1349,18 +1366,16 @@ if ($start_date != 0 && $end_date != 0 && $date_type != 0) { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - // $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - // ->where('policy_transaction.' . $date_type . '<=', $endDate); - if($date_type == "policy_issue_date"){ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } else { @@ -1463,18 +1478,16 @@ if ($start_date != 0 && $end_date != 0 && $date_type != 0) { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - // $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - // ->where('policy_transaction.' . $date_type . '<=', $endDate); - if($date_type == "policy_issue_date"){ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } else { @@ -1574,18 +1587,16 @@ if ($start_date != 0 && $end_date != 0 && $date_type != 0) { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - // $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - // ->where('policy_transaction.' . $date_type . '<=', $endDate); - if($date_type == "policy_issue_date"){ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } else { @@ -1689,18 +1700,16 @@ // Date range filtering if ($start_date != 0 && $end_date != 0 && $date_type != 0) { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); - // $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - // ->where('policy_transaction.' . $date_type . '<=', $endDate); - if($date_type == "policy_issue_date"){ $builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate) ->where("pt_co_share_details.pt_policy_issue_date <=", $endDate); }else{ - $builder->where('policy_transaction.' . $date_type . '>=', $startDate) - ->where('policy_transaction.' . $date_type . '<=', $endDate); + $builder->where('policy_transaction.' . $date_type . ' >=', $startDate) + ->where('policy_transaction.' . $date_type . ' <=', $endDate); } } else { $fromDate = date('Y-m-d', strtotime('-90 days')); @@ -2364,6 +2373,7 @@ } if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); @@ -2489,6 +2499,7 @@ // 3. Date condition (except statement_month) if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); @@ -3080,6 +3091,7 @@ // 3. Date condition (except statement_month) if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') { + $this->validateDateType($date_type); $startDate = date('Y-m-d 00:00:00', strtotime($start_date)); $endDate = date('Y-m-d 23:59:59', strtotime($end_date)); diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php index 8d82097f..e9a3801d 100644 --- a/app/Models/SalesActivityModel.php +++ b/app/Models/SalesActivityModel.php @@ -38,7 +38,7 @@ class SalesActivityModel extends Model // Validation protected $validationRules = [ 'lead_id' => 'required|integer', - 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo,Visit]', + 'activity_type' => 'required|in_list[Call,Email,Meeting,Visit,Demo,Share Docs,To Do]', 'notes' => 'required', 'scheduled_date' => 'required', 'assigned_to' => 'required|integer', @@ -102,7 +102,7 @@ class SalesActivityModel extends Model $assignedToIds = is_array($assigned_to) ? $assigned_to : explode(',', $assigned_to); // Now it is guaranteed to be an array, making whereIn perfectly safe - $this->whereIn('sales_actual_leads.assigned_to', $assignedToIds); + $this->whereIn('sales_activities.assigned_to', $assignedToIds); } diff --git a/app/Models/SalesTargetModel.php b/app/Models/SalesTargetModel.php new file mode 100644 index 00000000..f23193f6 --- /dev/null +++ b/app/Models/SalesTargetModel.php @@ -0,0 +1,38 @@ + 'required|integer', + 'fy_year' => 'required|max_length[9]', + 'target_amount' => 'required|decimal', + ]; + + protected $validationMessages = [ + 'user_id' => ['required' => 'User is required'], + 'fy_year' => ['required' => 'Financial year is required'], + 'target_amount' => ['required' => 'Target amount is required', 'decimal' => 'Target amount must be a valid number'], + ]; +} \ No newline at end of file diff --git a/app/Models/TicketMasterModel.php b/app/Models/TicketMasterModel.php index 324a18db..14101188 100644 --- a/app/Models/TicketMasterModel.php +++ b/app/Models/TicketMasterModel.php @@ -1,5 +1,4 @@ select("ticket_mail_template.*, CASE - WHEN employees.email_corporate IS NULL OR employees.email_corporate = '' - THEN ticket_master.emp_mail - ELSE employees.email_corporate + ->select("ticket_mail_template.*, CASE + WHEN employees.email_corporate IS NULL OR employees.email_corporate = '' + THEN ticket_master.emp_mail + ELSE employees.email_corporate END as emp_mail, ,ticket_master.claim_status_id") ->join('ticket_claim_status', 'ticket_master.claim_status_id = ticket_claim_status.id and ticket_claim_status.is_active = 1') ->join('ticket_mail_template', ' - ticket_claim_status.trigger_type = ticket_mail_template.trigger_type - and ticket_claim_status.ticket_type = ticket_mail_template.ticket_type + ticket_claim_status.trigger_type = ticket_mail_template.trigger_type + and ticket_claim_status.ticket_type = ticket_mail_template.ticket_type and ticket_mail_template.is_active = 1') ->join('employees', 'employees.id = ticket_master.emp_id', 'left') ->where('ticket_master.id', $ticket_id) @@ -173,11 +171,11 @@ class TicketMasterModel extends Model { $ticket_data = $this->select(" ticket_master.*, - CASE - WHEN employees.email_corporate IS NULL OR employees.email_corporate = '' - THEN ticket_master.emp_mail - ELSE employees.email_corporate - END as emp_mail, + CASE + WHEN employees.email_corporate IS NULL OR employees.email_corporate = '' + THEN ticket_master.emp_mail + ELSE employees.email_corporate + END as emp_mail, user_profiles.first_name as acm, user_profiles.mobile as acm_mobile, @@ -205,7 +203,7 @@ class TicketMasterModel extends Model //get TAT report BAND wise Data // public function getTATReport($ticket_type = null, $start_date = null, $end_date = null) - // { + // { // //set default last 3 months data date // $fromDate = date('Y-m-d', strtotime('-90 days')); // $toDate = date('Y-m-d 23:59:59'); @@ -222,9 +220,9 @@ class TicketMasterModel extends Model // } // // Fetch claim statuses from the `ticket_claim_status` table - // $statusQuery = " SELECT + // $statusQuery = " SELECT // id, claim_status, ticket_type - // FROM ticket_claim_status + // FROM ticket_claim_status // Where is_active = 1 // $ticket_type_data_1 // "; @@ -273,7 +271,6 @@ class TicketMasterModel extends Model // $dynamicSelect = rtrim($dynamicSelect, ', '); // // dd($dynamicSelect); - // // Construct the full SQL query // $sql = " // SELECT @@ -326,7 +323,7 @@ class TicketMasterModel extends Model // WHERE // tm.ticket_type_id = tcs.id // $ticket_type_data_2 - // AND tm.created_at >= '$fromDate' + // AND tm.created_at >= '$fromDate' // AND tm.created_at <= '$toDate' // AND tm.is_active = 1 // AND th.is_active = 1 @@ -371,9 +368,9 @@ class TicketMasterModel extends Model // } // // Fetch claim statuses from the `ticket_claim_status` table - // $statusQuery = " SELECT + // $statusQuery = " SELECT // id, claim_status, ticket_type - // FROM ticket_claim_status + // FROM ticket_claim_status // Where is_active = 1 // $ticket_type_data_1 // "; @@ -422,7 +419,6 @@ class TicketMasterModel extends Model // $dynamicSelect = rtrim($dynamicSelect, ', '); // // dd($dynamicSelect); - // // Construct the full SQL query // $sql = " // SELECT @@ -451,20 +447,20 @@ class TicketMasterModel extends Model // FROM // ticket_master tm // LEFT JOIN ( - // SELECT - // th.ticket_id, + // SELECT + // th.ticket_id, // th.new_value AS claim_status_id, // MAX(th.created_at) AS change_date - // FROM - // ticket_history th - // WHERE + // FROM + // ticket_history th + // WHERE // th.field_name = 'claim_status_id' // AND th.is_active = 1 - // GROUP BY + // GROUP BY // th.ticket_id, th.new_value // ) AS latest_status ON latest_status.ticket_id = tm.id // WHERE tm.is_active = 1 - // AND tm.created_at >= '$fromDate' + // AND tm.created_at >= '$fromDate' // AND tm.created_at <= '$toDate' // " . (!empty($ticket_type) ? " AND tm.ticket_type_id = $ticket_type" : "") . " // GROUP BY tm.id, latest_status.claim_status_id, latest_status.change_date, tm.created_at @@ -493,33 +489,33 @@ class TicketMasterModel extends Model { // Set default date range (last 90 days) $fromDate = date('Y-m-d', strtotime('-90 days')); - $toDate = date('Y-m-d 23:59:59'); - - if (!empty($start_date) && !empty($end_date)) { + $toDate = date('Y-m-d 23:59:59'); + + if (! empty($start_date) && ! empty($end_date)) { $fromDate = change_date_format($start_date); - $toDate = change_date_format($end_date); + $toDate = change_date_format($end_date); } - + // Get all active claim statuses $statusQuery = $this->db->table('ticket_claim_status') ->select('id, claim_status') ->where('is_active', 1) ->orderBy('id', 'ASC'); - - if (!empty($ticket_type)) { + + if (! empty($ticket_type)) { $statusQuery->where('ticket_type', $ticket_type); } - + $statusResult = $statusQuery->get()->getResultArray(); - + // Create a list of all TAT categories we want in the output $tatCategories = [ 'Above 20 Days', - '13-20 Days', + '13-20 Days', '7-12 Days', - '0-6 Days' + '0-6 Days', ]; - + // Initialize the result array with all TAT categories $result = []; foreach ($tatCategories as $category) { @@ -529,39 +525,39 @@ class TicketMasterModel extends Model } $result[] = $row; } - + // Get the base query for ticket counts by status and TAT category $query = $this->db->table('ticket_master tm') ->select([ 'tcs.claim_status', 'CASE WHEN DATEDIFF(CURDATE(), COALESCE( - (SELECT MAX(th.created_at) - FROM ticket_history th - WHERE th.ticket_id = tm.id + (SELECT MAX(th.created_at) + FROM ticket_history th + WHERE th.ticket_id = tm.id AND th.field_name = "claim_status_id" AND th.is_active = 1), tm.created_at )) BETWEEN 0 AND 6 THEN "0-6 Days" WHEN DATEDIFF(CURDATE(), COALESCE( - (SELECT MAX(th.created_at) - FROM ticket_history th - WHERE th.ticket_id = tm.id + (SELECT MAX(th.created_at) + FROM ticket_history th + WHERE th.ticket_id = tm.id AND th.field_name = "claim_status_id" AND th.is_active = 1), tm.created_at )) BETWEEN 7 AND 12 THEN "7-12 Days" WHEN DATEDIFF(CURDATE(), COALESCE( - (SELECT MAX(th.created_at) - FROM ticket_history th - WHERE th.ticket_id = tm.id + (SELECT MAX(th.created_at) + FROM ticket_history th + WHERE th.ticket_id = tm.id AND th.field_name = "claim_status_id" AND th.is_active = 1), tm.created_at )) BETWEEN 13 AND 20 THEN "13-20 Days" ELSE "Above 20 Days" END AS tat_category', - 'COUNT(*) AS count' + 'COUNT(*) AS count', ]) ->join('ticket_claim_status tcs', 'tcs.id = tm.claim_status_id AND tcs.is_active = 1') ->where('tm.is_active', 1) @@ -569,23 +565,23 @@ class TicketMasterModel extends Model ->where('tm.created_at <=', $toDate) ->groupBy('tcs.claim_status, tat_category') ->orderBy('FIELD(tat_category, "Above 20 Days", "13-20 Days", "7-12 Days", "0-6 Days")'); - - if (!empty($ticket_type)) { + + if (! empty($ticket_type)) { $query->where('tm.ticket_type_id', $ticket_type); } - + $countResults = $query->get()->getResultArray(); - + // Populate the result array with actual counts foreach ($countResults as $row) { foreach ($result as &$categoryRow) { if ($categoryRow['TAT_Category'] === $row['tat_category']) { - $categoryRow[$row['claim_status']] = (int)$row['count']; + $categoryRow[$row['claim_status']] = (int) $row['count']; break; } } } - + return $result; } @@ -593,21 +589,21 @@ class TicketMasterModel extends Model { $ticket_type_data_1 = ""; $ticket_type_data_2 = ""; - $statusBinds = []; + $statusBinds = []; - if (!empty($policy_type)) { - $ticket_type_data_1 = "AND ticket_type = :policy_type:"; - $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; + if (! empty($policy_type)) { + $ticket_type_data_1 = "AND ticket_type = :policy_type:"; + $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; $statusBinds['policy_type'] = $policy_type; } // Fetch claim statuses dynamically - $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; + $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; $statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray(); // Initialize dynamic query parts - $dynamicSelect = ''; - $dynamicTotal = ''; + $dynamicSelect = ''; + $dynamicTotal = ''; $dynamicClosedTotal = ''; // Loop through each claim status and generate the CASE statements @@ -624,7 +620,7 @@ class TicketMasterModel extends Model 'UNDER PROCESS - INVESTIGATION STATUS', 'UNDER PROCESS - QUERY DOCUMENT RECEIVED', 'APPROVED', - 'PAYMENT INITIATED' + 'PAYMENT INITIATED', ])) { $dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + "; } @@ -638,34 +634,34 @@ class TicketMasterModel extends Model } // Remove the trailing commas and `+` signs - $dynamicSelect = rtrim($dynamicSelect, ', '); - $dynamicTotal = rtrim($dynamicTotal, ' +'); + $dynamicSelect = rtrim($dynamicSelect, ', '); + $dynamicTotal = rtrim($dynamicTotal, ' +'); $dynamicClosedTotal = rtrim($dynamicClosedTotal, ' +'); // print_rr($dynamicSelect); // Construct the final SQL query $sql = " - SELECT + SELECT tpa.id as TPA_ID, tpa.name AS TPA_NAME, $dynamicSelect, ($dynamicTotal) AS TOTAL, ($dynamicClosedTotal) AS CLEARED_TOTAL - FROM + FROM ticket_master master - JOIN + JOIN tpa ON master.tpa_id = tpa.id AND tpa.is_active = 1 - WHERE + WHERE master.created_at BETWEEN :start_date: AND :end_date: AND master.is_active = 1 $ticket_type_data_2 - GROUP BY + GROUP BY tpa.id; "; // Execute the query - $binds = ["start_date"=>$start_date,"end_date"=>$end_date]; + $binds = ["start_date" => $start_date, "end_date" => $end_date]; // Merge policy_type binds if present - if (!empty($statusBinds)) { + if (! empty($statusBinds)) { $binds = array_merge($binds, $statusBinds); } $result = $this->db->query($sql, $binds)->getResultArray(); @@ -680,28 +676,27 @@ class TicketMasterModel extends Model if ($policy_type == 1) { $ticket_type_data_1 = ""; $ticket_type_data_2 = ""; - $statusBinds = []; + $statusBinds = []; - if (!empty($policy_type)) { - $ticket_type_data_1 = "AND ticket_type = :policy_type:"; - $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; + if (! empty($policy_type)) { + $ticket_type_data_1 = "AND ticket_type = :policy_type:"; + $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; $statusBinds['policy_type'] = $policy_type; } // Fetch claim statuses dynamically - $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; - $statusResult = $this->db->query($statusQuery,$statusBinds)->getResultArray(); + $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; + $statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray(); // Initialize dynamic query parts $dynamicSelect = ''; - $dynamicTotal = ''; + $dynamicTotal = ''; // Loop through each claim status and generate the CASE statements foreach ($statusResult as $status) { $dynamicSelect .= " COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) AS `{$status['claim_status']}`, "; - // Include in total count $dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + "; @@ -711,54 +706,54 @@ class TicketMasterModel extends Model // Remove the trailing commas and `+` signs $dynamicSelect = rtrim($dynamicSelect, ', '); - $dynamicTotal = rtrim($dynamicTotal, ' +'); + $dynamicTotal = rtrim($dynamicTotal, ' +'); // print_rr($dynamicSelect); // Construct the final SQL query $sql = " - SELECT + SELECT user_profiles.id as ACM_ID, user_profiles.first_name AS ACM_NAME, $dynamicSelect, ($dynamicTotal) AS TOTAL - FROM + FROM ticket_master master - JOIN + JOIN user_profiles ON master.acm_id = user_profiles.id AND user_profiles.is_active = 1 - WHERE + WHERE master.created_at BETWEEN :start_date: AND :end_date: AND master.is_active = 1 $ticket_type_data_2 - GROUP BY + GROUP BY user_profiles.id; "; - $binds = ["start_date"=>$start_date,"end_date"=>$end_date]; - // Merge policy_type binds if present - if (!empty($statusBinds)) { + $binds = ["start_date" => $start_date, "end_date" => $end_date]; + // Merge policy_type binds if present + if (! empty($statusBinds)) { $binds = array_merge($binds, $statusBinds); } - $result = $this->db->query($sql,$binds)->getResultArray(); + $result = $this->db->query($sql, $binds)->getResultArray(); // print_rr(count(($result))); // print_rr($this->db->lastQuery);die(); return $result; } else { $ticket_type_data_1 = ""; $ticket_type_data_2 = ""; - $statusBinds = []; + $statusBinds = []; - if (!empty($policy_type)) { - $ticket_type_data_1 = "AND ticket_type = :policy_type:"; - $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; + if (! empty($policy_type)) { + $ticket_type_data_1 = "AND ticket_type = :policy_type:"; + $ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:"; $statusBinds['policy_type'] = $policy_type; } // Fetch claim statuses dynamically - $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; - $statusResult = $this->db->query($statusQuery,$statusBinds)->getResultArray(); + $statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1"; + $statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray(); // Initialize dynamic query parts - $dynamicSelect = ''; - $dynamicTotal = ''; + $dynamicSelect = ''; + $dynamicTotal = ''; $dynamicClosedTotal = ''; // Loop through each claim status and generate the CASE statements @@ -773,7 +768,7 @@ class TicketMasterModel extends Model 'INSURER PENDING', 'INVESTIGATION', 'APPROVED', - 'ON HOLD' + 'ON HOLD', ])) { $dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + "; } @@ -787,52 +782,51 @@ class TicketMasterModel extends Model } // Remove the trailing commas and `+` signs - $dynamicSelect = rtrim($dynamicSelect, ', '); - $dynamicTotal = rtrim($dynamicTotal, ' +'); + $dynamicSelect = rtrim($dynamicSelect, ', '); + $dynamicTotal = rtrim($dynamicTotal, ' +'); $dynamicClosedTotal = rtrim($dynamicClosedTotal, ' +'); // print_rr($dynamicSelect); // Construct the final SQL query $sql = " - SELECT + SELECT user_profiles.id as ACM_ID, user_profiles.first_name AS ACM_NAME, $dynamicSelect, ($dynamicTotal) AS TOTAL, ($dynamicClosedTotal) AS CLEARED_TOTAL - FROM + FROM ticket_master master - JOIN + JOIN user_profiles ON master.acm_id = user_profiles.id AND user_profiles.is_active = 1 - WHERE + WHERE master.created_at BETWEEN :start_date: AND :end_date: AND master.is_active = 1 $ticket_type_data_2 - GROUP BY + GROUP BY user_profiles.id; "; - $binds = ["start_date"=>$start_date,"end_date"=>$end_date]; + $binds = ["start_date" => $start_date, "end_date" => $end_date]; // Execute the query - // Merge policy_type binds if present - if (!empty($statusBinds)) { + // Merge policy_type binds if present + if (! empty($statusBinds)) { $binds = array_merge($binds, $statusBinds); } - $result = $this->db->query($sql,$binds)->getResultArray(); + $result = $this->db->query($sql, $binds)->getResultArray(); // print_rr($this->db->lastQuery);die(); return $result; - } } //api - public function get_ticket_data($emp_id, $returnType,$ticket_type = null, $ticket_id = null) + public function get_ticket_data($emp_id, $returnType, $ticket_type = null, $ticket_id = null) { $query = $this->select(" - ticket_master.*, - tms.mail_subject as subject, + ticket_master.*, + tms.mail_subject as subject, tms.id as ticket_message_id, ( SELECT th1.old_value @@ -854,11 +848,11 @@ class TicketMasterModel extends Model $query->whereIn('sender', ['staff', 'user']); $query->where('ticket_master.emp_id', $emp_id); - if (!empty($ticket_id)) { + if (! empty($ticket_id)) { $query->where('ticket_master.id', $ticket_id); } - if (!empty($ticket_type)) { + if (! empty($ticket_type)) { $query->where('ticket_master.ticket_type_id', $ticket_type); } @@ -879,9 +873,9 @@ class TicketMasterModel extends Model ->select('tm.id, tm.ticket_type_id, tcs.claim_status, th.last_claim_status_change') ->join('ticket_claim_status tcs', 'tm.claim_status_id = tcs.id', 'left') ->join( - '(SELECT ticket_id, MAX(created_at) AS last_claim_status_change - FROM ticket_history - WHERE field_name = \'claim_status_id\' + '(SELECT ticket_id, MAX(created_at) AS last_claim_status_change + FROM ticket_history + WHERE field_name = \'claim_status_id\' GROUP BY ticket_id) th', 'tm.id = th.ticket_id', 'left' @@ -898,8 +892,8 @@ class TicketMasterModel extends Model $total = 0; foreach ($statuses as $status) { - $alias = strtolower(str_replace(' ', '_', $status)); - $summary[$alias] = 0; + $alias = strtolower(str_replace(' ', '_', $status)); + $summary[$alias] = 0; $summary[$alias . '_ids'] = ''; $dateLimit = $limit[$typeId][$status] ?? 3; @@ -911,19 +905,18 @@ class TicketMasterModel extends Model if ( $row['claim_status'] === $status && ( - !$row['last_claim_status_change'] || + ! $row['last_claim_status_change'] || $row['last_claim_status_change'] <= $threshold ) ) { $summary[$alias]++; $ticketIds[] = $row['id']; - if ($status == "APPROVED"|| $status == "SETTLED"|| $status == "CLOSED" ){ + if ($status == "APPROVED" || $status == "SETTLED" || $status == "CLOSED") { continue; } - + $total++; - } } @@ -934,8 +927,8 @@ class TicketMasterModel extends Model $summary['total'] = $total; // Add approved but not settled IDs and count - $approvedNotSettled = $this->getNotSettledbutApprovedCount($typeId); - $summary['approved_not_settled'] = $approvedNotSettled['count']; + $approvedNotSettled = $this->getNotSettledbutApprovedCount($typeId); + $summary['approved_not_settled'] = $approvedNotSettled['count']; $summary['approved_not_settled_ids'] = $approvedNotSettled['ticket_ids']; $finalResults[$typeId] = $summary; @@ -960,13 +953,13 @@ class TicketMasterModel extends Model $ids = $builder->get()->getRowArray(); // If no matching status IDs are found, return empty - if (!$ids) { + if (! $ids) { return ['count' => 0, 'ticket_ids' => '']; } // Extract approved and settled status IDs $approved_id = $ids['approved_id']; - $settled_id = $ids['settled_id']; + $settled_id = $ids['settled_id']; // Subquery to get the latest approval time for each ticket $subquery = $this->db->table('ticket_history') @@ -983,12 +976,12 @@ class TicketMasterModel extends Model // Left join to find if there is a settled status for each ticket after approval $builder->join( 'ticket_history th_settled', - "th_settled.ticket_id = tm.id - AND th_settled.field_name = 'claim_status_id' - AND th_settled.new_value = {$settled_id} + "th_settled.ticket_id = tm.id + AND th_settled.field_name = 'claim_status_id' + AND th_settled.new_value = {$settled_id} AND th_settled.created_at > latest_approval.approved_time", 'left', - false // Important for raw ON condition + false// Important for raw ON condition ); // Filtering conditions: not settled and approved time older than 12 days @@ -1001,7 +994,7 @@ class TicketMasterModel extends Model $builder->select('tm.id'); // Execute the query - $query = $builder->get(); + $query = $builder->get(); $ticketIds = array_column($query->getResultArray(), 'id'); // Extract the IDs // Convert ticket IDs array to a comma-separated string @@ -1009,8 +1002,8 @@ class TicketMasterModel extends Model // Return the count and comma-separated ticket IDs return [ - 'count' => count($ticketIds), - 'ticket_ids' => $ticketIdsString + 'count' => count($ticketIds), + 'ticket_ids' => $ticketIdsString, ]; } @@ -1024,8 +1017,8 @@ class TicketMasterModel extends Model AND cp.is_active = 1 "; - $binds = ["ticket_id"=>$ticket_id]; - $query = $this->db->query($sql,$binds); + $binds = ["ticket_id" => $ticket_id]; + $query = $this->db->query($sql, $binds); if ($query && $query->getNumRows() > 0) { $row = $query->getRowArray(); @@ -1039,11 +1032,10 @@ class TicketMasterModel extends Model public function getPolicyData($params) { - $client_name = $params['client_name'] ?? null; + $client_name = $params['client_name'] ?? null; $insurer_short_name = $params['insurer_short_name'] ?? null; - $tpa_short_name = $params['tpa_short_name'] ?? null; - $policy_no = $params['policy_no'] ?? null; - + $tpa_short_name = $params['tpa_short_name'] ?? null; + $policy_no = $params['policy_no'] ?? null; $builder = $this->db->table('client_policy'); $builder->select('client_policy.*'); @@ -1055,19 +1047,19 @@ class TicketMasterModel extends Model $builder->where('i.is_active', 1); $builder->where('c.client_name', trim($client_name)); $builder->where('i.short_name', trim($insurer_short_name)); - if(!empty($tpa_short_name)){ - $builder->where('tpa.short_name', trim($tpa_short_name)); + if (! empty($tpa_short_name)) { + $builder->where('tpa.short_name', trim($tpa_short_name)); } $builder->where('client_policy.policy_no', trim($policy_no)); - $query = $builder->get(); + $query = $builder->get(); $result = $query->getResultArray(); // dd($this->db->getLastQuery()); return $result; } public function getTheClaimDetails($params) - { + { $client_name = $params['client_name'] ?? null; $policy_no = $params['policy_no'] ?? null; $insurer_short_name = $params['insurer_short_name'] ?? null; @@ -1099,19 +1091,19 @@ class TicketMasterModel extends Model $builder->where('ticket_master.claim_number', trim($claim_no)); $builder->where('ticket_master.relationship', trim($relationship)); - if(!empty($tpa_short_name)){ - $builder->where('tpa.short_name', trim($tpa_short_name)); + if (! empty($tpa_short_name)) { + $builder->where('tpa.short_name', trim($tpa_short_name)); } - $query = $builder->get(); + $query = $builder->get(); $result = $query->getResultArray(); return $result; - } + } public function getEmployeeAndEmployeePolicyDetails($params) - { + { $client_name = $params['client_name'] ?? null; $emp_code = $params['emp_code'] ?? null; $emp_name = $params['emp_name'] ?? null; @@ -1124,7 +1116,6 @@ class TicketMasterModel extends Model // $emp_name = "Lokesh"; // $policy_no = "GMC-1999/2000/2021/2022"; - $builder = $this->db->table('employees e'); $builder->select(" e.id as emp_id, @@ -1143,7 +1134,7 @@ class TicketMasterModel extends Model cp.insurer_id, cp.tpa_id, cp.policy_no, - CASE + CASE WHEN cp.policy_type_id = 2 THEN 1 WHEN cp.policy_type_id = 1 THEN 2 WHEN cp.policy_type_id = 6 THEN 3 @@ -1165,7 +1156,7 @@ class TicketMasterModel extends Model $builder->where('cp.policy_no', trim($policy_no)); $builder->where('LOWER(e.relationship)', strtolower('self')); - $query = $builder->get(); + $query = $builder->get(); $result = $query->getRowArray(); return $result; @@ -1181,8 +1172,8 @@ class TicketMasterModel extends Model $builder = $this->db->table('user_profiles'); $builder->select("user_profiles.*"); $builder->where('user_profiles.is_active', 1); - $builder->where('user_profiles.mobile', trim($acm_mobile)); - $query = $builder->get(); + $builder->where('user_profiles.mobile', trim($acm_mobile)); + $query = $builder->get(); $result = $query->getRowArray(); return $result; @@ -1193,9 +1184,9 @@ class TicketMasterModel extends Model public function getInsuredDetails($params) { - $client_id = $params['client_id'] ?? null; + $client_id = $params['client_id'] ?? null; $emp_code = $params['emp_code'] ?? null; - $policy_id = $params['client_policy_id'] ?? null; + $policy_id = $params['client_policy_id'] ?? null; $insured_name = $params['insured_name'] ?? null; $builder = $this->db->table('employees e'); @@ -1220,7 +1211,7 @@ class TicketMasterModel extends Model $builder->where('e.client_id', trim($client_id)); $builder->where('ep.client_policy_id', trim($policy_id)); - $query = $builder->get(); + $query = $builder->get(); $result = $query->getRowArray(); // dd($this->db->getLastQuery()); return $result; diff --git a/app/Views/DashBoard.php b/app/Views/DashBoard.php index e71d67ff..0eff67a2 100755 --- a/app/Views/DashBoard.php +++ b/app/Views/DashBoard.php @@ -245,7 +245,7 @@ /assets/images/active_leads_and_bds_renewal.png" alt="Logo" height="14" class="active_leads " style="display:none;">    - Leads and BDS Renewals + Opportunities and BDS Renewals      diff --git a/app/Views/claim_dump_file_list.php b/app/Views/claim_dump_file_list.php index 1330cded..9ba0451b 100644 --- a/app/Views/claim_dump_file_list.php +++ b/app/Views/claim_dump_file_list.php @@ -24,6 +24,66 @@ .dataTables_length label {height: 21px !important;} .readonly-select { background-color: #f3f3f3 !important; cursor: not-allowed; pointer-events: none; } + +.custom-tooltip { + position: relative; + display: inline-block; + cursor: pointer; +} + +.info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background-color: #007bff; + color: #fff; + border-radius: 50%; + font-size: 14px; + font-style: italic; + font-weight: bold; + font-family: Georgia, serif; + line-height: 1; + vertical-align: middle; +} + +.tooltiptext { + visibility: hidden; + opacity: 0; + position: fixed; /* fixed instead of absolute to escape modal overflow */ + background: #333; + color: #fff; + padding: 10px 14px; + border-radius: 6px; + white-space: normal; /* allow text wrapping */ + width: 320px; /* fixed width for multiline */ + font-size: 12px; + font-weight: normal; + font-style: normal; + line-height: 1.6; + z-index: 99999; + transition: opacity 0.2s ease; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + top: 20px !important; + left: 155px !important; +} + +.tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 20px; + border: 6px solid transparent; + border-top-color: #333; +} + +.custom-tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + @@ -151,7 +221,16 @@