From 4da0b60d8e44bb77d0cdc0d8a93609e141b65c55 Mon Sep 17 00:00:00 2001 From: "venkatesh.r" Date: Tue, 24 Feb 2026 12:43:53 +0530 Subject: [PATCH 1/6] CHANGE_ECARD_ZIP_TO_TABLE --- app/Config/Acl.php | 1 + app/Config/Filters.php | 2 +- app/Config/Routes.php | 2 + app/Controllers/EmployeeController.php | 130 ++++++++++++++++++++- app/Controllers/EmployeeRestController.php | 53 +++++++++ app/Models/EmployeePolicyModel.php | 5 +- 6 files changed, 190 insertions(+), 3 deletions(-) diff --git a/app/Config/Acl.php b/app/Config/Acl.php index 80f7a2bd..af8b94f3 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -17,6 +17,7 @@ class Acl '#^/getVerifiedPosUserData#' => ['public' => true], '#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]], '#^/getEmployeeActiveOrInactivePolicy#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], + '#^/bulkEcardDownloadAsZip#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/sheet#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/sendextraparam#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/test/testingquerys#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], diff --git a/app/Config/Filters.php b/app/Config/Filters.php index 6c031ae7..b219e60e 100755 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -69,7 +69,7 @@ class Filters extends BaseConfig 'before' => [ 'HttpRequestLog' => ['except' => 'cli/*'], 'Cors', - 'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','employeeRest/*','processjob','getCommission']], + 'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','employeeRest/*','processjob','getCommission','downloadEmployeeEcardZip']], 'SecurityInputFilter' => ['except' => ['/client/notification/create','/ticket/crud_mail_template/*','test_mail','leads/sendMail'] ], 'GlobalPostFileUploadGuard' // 'csrf', diff --git a/app/Config/Routes.php b/app/Config/Routes.php index efde3147..0600dbb8 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -87,6 +87,7 @@ $routes->get('claim-form-download/(:any)', 'TicketController::downloadClaimForm/ $routes->get('downloadClaimFile/(:any)', 'TicketController::downloadClaimFile/$1'); $routes->match (['get','post'],"claims-feedback-form/(:any)/(:any)", "TicketController::viewClaimFeedbackForm/$1/$2"); $routes->match (['get','post'],"claims-feedback-form/(:any)", "TicketController::viewClaimFeedbackForm/$1"); +$routes->get('downloadEmployeeEcardZip/(:any)', 'EmployeeController::downloadEmployeeEcardZip/$1'); @@ -706,6 +707,7 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel $routes->get("downloadSampleExcel/(:any)", "EmployeeController::downloadSampleExcelFile/$1"); }); +$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip"); $routes->get("hrFileDownload", "EmployeeRestController::hrFileDownload"); $routes->post("hrFileList", "EmployeeRestController::hrFileList"); $routes->get("getEmployeeActiveOrInactivePolicy", "EmployeeRestController::getEmployeeActiveOrInactivePolicy"); diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php index 4e7ddef7..01664e76 100755 --- a/app/Controllers/EmployeeController.php +++ b/app/Controllers/EmployeeController.php @@ -41,6 +41,7 @@ use App\Controllers\EmployeeServiceController; use App\Controllers\EmpDataServiceController; use App\Models\LeadsModel; use App\Models\ThzMasterModel; +use App\Models\UserActivityHistoryModel; use CodeIgniter\API\ResponseTrait; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -78,6 +79,7 @@ class EmployeeController extends AdminController protected $userModel; protected $partnerEndorsementRequestModel; protected $LevelContactModel; + protected $UserActivityHistoryModel; public function __construct() { @@ -102,6 +104,7 @@ class EmployeeController extends AdminController $this->userModel = new userModel(); $this->partnerEndorsementRequestModel = new PartnerEndorsementRequestModel(); $this->LevelContactModel = new LevelContactModel(); + $this->UserActivityHistoryModel = new UserActivityHistoryModel(); } public function list() @@ -4862,8 +4865,133 @@ class EmployeeController extends AdminController return $mail_response; } - public function downloadZip(){ + + public function bulkEcardDownloadAsZipFromS3New($params) + { + try { + + $limit = $params['limit'] ?? 100; + $last_id = $params['last_id'] ?? 0; + $zip_id = $params['zip_id']; + + $employee_data = $this->employeePolicyModel->getEmployeeDataWithPolicyUsingClientPolicyIdOrEmployeePolicyIds($params, $limit, $last_id); + + if (empty($employee_data)) { + // No more records, move to Zipping phase + return Jobs::addJob(['job_name' => 'getEmployeeEcardFromTmpFolderAndZipToS3', 'payload' => $params]); + } + + $tempPath = FCPATH . 'tmp/' . $params['folder_name'] . '/'; + if (!is_dir($tempPath)) mkdir($tempPath, 0777, true); + + $s3 = \Config\Services::getS3Service(); + $current_last_id = $last_id; + $pdf_count = 0; + + foreach ($employee_data as $emp) { + $current_last_id = $emp['emp_policy_id']; + + + $s3_key = 'ecard_' . $emp['name'] . '(' . $emp['emp_code'] . ')' . '_' . $emp['tpa_id'] . '.pdf'; + $s3_key = $this->sanitizeFilePart($s3_key); + + if ($s3->exists($s3_key)) { + $s3_url = $s3->getPresignedUrl($s3_key); + $pdf_content = file_get_contents($s3_url['url']); + + if ($pdf_content !== false) { + file_put_contents($tempPath . $s3_key, $pdf_content); + $pdf_count++; + } + } + } + + // Check if we need more batches + $params['last_id'] = $current_last_id; + $params['pdf_count'] = $pdf_count; + $params['batch_no'] = ($params['batch_no'] ?? 1) + 1; + + if (count($employee_data) == $limit) { + return Jobs::addJob(['job_name' => 'bulkEcardDownloadAsZipFromS3', 'payload' => $params]); + } else { + return Jobs::addJob(['job_name' => 'getEmployeeEcardFromTmpFolderAndZipToS3', 'payload' => $params]); + } + + } catch (\Throwable $e) { + $this->logAndNotifyError($e, $params); + } + } + + public function getEmployeeEcardFromTmpFolderAndZipToS3New($params) + { + try { + $zipService = new \App\Libraries\ZipService(); + $source = FCPATH . 'tmp/' . $params['folder_name']; + $zipName = $params['folder_name'] . '.zip'; + $s3_dest_key = 'exports/zips/' . $zipName; + + // 1. Create Zip and Upload + $result = $zipService->zipAndUploadS3($source, $s3_dest_key); + + if ($result['status']) { + // 2. IMPORTANT: Update the Activity Record with the S3 Key + $history = $this->UserActivityHistoryModel->find($params['zip_id']); + $misc = json_decode($history['misc_data'], true); + $misc['status'] = 'completed'; + $misc['s3_key'] = $s3_dest_key; + + $this->UserActivityHistoryModel->update($params['zip_id'], [ + 'misc_data' => json_encode($misc) + ]); + + $params['url'] = base_url('downloadEmployeeEcardZip/' . md5($params['zip_id'])); + } + + // 3. Cleanup local files + $this->recursiveRemoveDir($source); + + // 4. Email HR + return $this->sendMailToHrWithZipAttachments($params); + + } catch (\Throwable $e) { + $this->logAndNotifyError($e, $params); + } + } + + public function downloadEmployeeEcardZip($md5Id) + { + // Search by MD5 of ID + $data = $this->UserActivityHistoryModel->where('MD5(CAST(id AS CHAR))', $md5Id)->first(); + + if (!$data) return "File link expired or invalid."; + + $misc = json_decode($data['misc_data'], true); + $s3Key = $misc['s3_key'] ?? null; + + if (!$s3Key) return "File is still processing or failed to generate."; + + $s3 = \Config\Services::getS3Service(); + $presigned = $s3->getPresignedUrl($s3Key, 15); // 15 mins expiry + + return redirect()->to($presigned['url']); + } + + private function logAndNotifyError($e, $params) + { + $this->myLogger->logme('error', "[ECARD_ZIP]: " . $e->getMessage()); + $params['url'] = null; // Forces "Failed" email + $this->sendMailToHrWithZipAttachments($params); + } + + private function recursiveRemoveDir($dir) + { + if (!is_dir($dir)) return; + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? $this->recursiveRemoveDir("$dir/$file") : unlink("$dir/$file"); + } + return rmdir($dir); } } diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index fb17d59a..6d91d543 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -63,6 +63,7 @@ use App\Models\ClaimFilesModel; use App\Models\PolicyTransactionModel; use App\Models\TicketMailTemplateModel; use App\Models\TpaApiSeviceModel; +use App\Models\UserActivityHistoryModel; use Composer\Pcre\Preg; use Kreait\Firebase\Factory; use Kreait\Firebase\Messaging\CloudMessage; @@ -106,6 +107,7 @@ class EmployeeRestController extends AdminController protected $ticketMailTemplateModel; protected $employeeRetailPolicy; protected $batchFileModel; + protected $UserActivityHistoryModel; public function __construct() @@ -141,6 +143,7 @@ class EmployeeRestController extends AdminController $this->hrFileUploadModel = new HrFileUploadModel(); $this->ticketMailTemplateModel = new TicketMailTemplateModel(); $this->batchFileModel = new BatchFileModel(); + $this->UserActivityHistoryModel = new UserActivityHistoryModel(); } @@ -5648,5 +5651,55 @@ class EmployeeRestController extends AdminController } + public function bulkEcardDownloadAsZipNew() + { + $received_data = $this->request->getJSON(true) ?? []; + + if (empty($received_data['client_policy_id']) && empty($received_data['emp_policy_ids'])) { + return $this->respond(['status' => false, 'message' => 'client_policy_id or employee_policy_ids is required'], 400); + } + + if (empty($received_data['hr_id'])) { + return $this->respond(['status' => false, 'message' => 'hr_id is required'], 400); + } + + $employee_data = $this->employeePolicyModel->getEmployeeDataWithPolicyUsingClientPolicyIdOrEmployeePolicyIds($received_data, 1); + + if (empty($employee_data)) { + return $this->respond(['status' => false, 'message' => 'No employee data found'], 404); + } + + // Define naming convention + $folderName = 'bulk_ecards_' . ($employee_data[0]['client_short_name'] ?? 'export') . '_' . date('Ymd_His'); + + $insert_data = [ + 'user_id' => $received_data['hr_id'], + 'user_type' => 'hr', + 'activity' => 'download_employees_ecard_zip', + 'misc_data' => json_encode([ + 'folder_name' => $folderName, + 'status' => 'pending', + 's3_key' => null + ]) + ]; + + $zip_id = $this->UserActivityHistoryModel->insert($insert_data); + + $payload = array_merge($received_data, [ + 'zip_id' => $zip_id, + 'folder_name' => $folderName, + 'batch_no' => 1, + 'last_id' => 0, + 'limit' => 100 + ]); + + // Dispatch job + Jobs::addJob(['job_name' => 'bulkEcardDownloadAsZipFromS3', 'payload' => $payload]); + + return $this->respond([ + 'status' => true, + 'message' => 'Process started. You will receive an email with the download link shortly.' + ], 200); + } } diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php index 908d1698..8171df0c 100755 --- a/app/Models/EmployeePolicyModel.php +++ b/app/Models/EmployeePolicyModel.php @@ -2260,9 +2260,12 @@ class EmployeePolicyModel extends Model employees.id as emp_id, tpa.short_name, employee_polices.tpa_id, - client_policy.policy_no + client_policy.policy_no, + clients.client_name as client_name, + clients.short_name as client_short_name ') ->join('client_policy', 'client_policy.id = employee_polices.client_policy_id') + ->join('clients', 'clients.id = client_policy.client_id') ->join('employees', 'employees.id = employee_polices.employee_id') ->join('tpa', 'tpa.id = client_policy.tpa_id') ->where([ From cfddad848577e47ef5a1a9286825aec31bc7deff Mon Sep 17 00:00:00 2001 From: "venkatesh.r" Date: Tue, 24 Feb 2026 14:52:28 +0530 Subject: [PATCH 2/6] FEAT_HR_TPA_DASHBOARD --- app/Config/Acl.php | 1 + app/Config/Routes.php | 2 + app/Controllers/EmployeeRestController.php | 43 ++++++++++++++++ app/Controllers/TestingController.php | 58 ++++++++++++++++++++++ app/Models/TPAModel.php | 1 + 5 files changed, 105 insertions(+) diff --git a/app/Config/Acl.php b/app/Config/Acl.php index af8b94f3..86760bb0 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -29,6 +29,7 @@ class Acl '#^/util/view_log#' => ['roles' => [ADMIN_ROLE_ID]], '#^/util/download_log#' => ['roles' => [ADMIN_ROLE_ID]], '#^/metaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], + '#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], // ===================== PUBLIC DOWNLOADS / FORMS ===================== '#^/download-#' => ['public' => true], diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 0600dbb8..387c3dfd 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -32,6 +32,7 @@ $routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authM $routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']); $routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck'); $routes->get('/metaDashboardDemo', 'TestingController::metaDashboardDemo'); +$routes->get('/metaTpaDashboardDemo', 'TestingController::metaTpaDashboardDemo'); // Reminder Mail Notification @@ -670,6 +671,7 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel $routes->post("hrFileUpload", "EmployeeRestController::hrFileUpload"); $routes->post("updateHrFileUploadData", "EmployeeRestController::updateHrFileUploadData"); $routes->post("getHrDashboad", "EmployeeRestController::getHrDashboad"); + $routes->post("getHrTpaDashboard", "EmployeeRestController::getHrTpaDashboard"); //thz_master's diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 6d91d543..50398782 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -5533,6 +5533,49 @@ class EmployeeRestController extends AdminController 'metabaseUrl' => 'https://nsights.nhanceindia.in', ]); } + + public function getHrTpaDashboard() + { + $METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY'); + $received_data = $this->request->getJSON(true) ?? null; + + $policy_id = $received_data['client_policy_id'] ?? 4687; + + $client_policy_data = $this->clientPolicyModel + ->select('tpa.dashboard_id') + ->join('tpa', 'client_policy.tpa_id = tpa.id') + ->where('client_policy.id', $policy_id) + ->first(); + + $database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null; + + if (empty($database_id)) { + return $this->respond([ + 'status' => 'failed', + 'message' => 'There is no dashboard for this TPA.', + 'data' => [] + ]); + } + + $payload = [ + 'resource' => [ + 'dashboard' => $database_id + ], + 'exp' => time() + (10 * 60), // 10 minutes + 'params' => (object)[] + ]; + + $token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256'); + + return $this->respond([ + 'status' => 'success', + 'message' => 'Form data received successfully!', + 'data' => [ + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in' + ] + ]); + } // get policy files diff --git a/app/Controllers/TestingController.php b/app/Controllers/TestingController.php index 66957ea8..aaf62878 100644 --- a/app/Controllers/TestingController.php +++ b/app/Controllers/TestingController.php @@ -4,6 +4,7 @@ namespace App\Controllers; use App\Controllers\BaseController; use App\Models\EmployeePolicyModel; +use App\Models\ClientPolicyModel; use App\Models\InsurerBranchModel; use App\Models\RFQModel; use CodeIgniter\HTTP\ResponseInterface; @@ -23,10 +24,12 @@ class TestingController extends BaseController { use ResponseTrait; protected $myLogger; + protected $ClientPolicyModel; public function __construct() { $this->myLogger = \Config\Services::mylogger(); + $this->ClientPolicyModel = new ClientPolicyModel(); } public function saveForm() @@ -1060,4 +1063,59 @@ class TestingController extends BaseController return $this->respond(['status' => 'failed', 'code' => '500' , 'message' => 'Error: ' . $e->getMessage()], 200); } } + + public function metaTpaDashboardDemo() + { + $METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY'); + $policy_id = $this->request->getGet('client_policy') ?? 4687; + + $client_policy_data = $this->ClientPolicyModel + ->select('tpa.dashboard_id') + ->join('tpa', 'client_policy.tpa_id = tpa.id') + ->where('client_policy.id', $policy_id) + ->first(); + + $database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null; + + if (empty($database_id)) { + + if ($this->request->getGet('api') == 1) { + return $this->respond([ + 'status' => 'failed', + 'message' => 'There is no dashboard for this TPA.', + 'data' => [] + ]); + } + + return view('errors/404', [ + 'message' => 'There is no dashboard for this TPA.' + ]); + } + + $payload = [ + 'resource' => [ + 'dashboard' => $database_id + ], + 'exp' => time() + (10 * 60), // 10 minutes + 'params' => (object)[] + ]; + + $token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256'); + + if ($this->request->getGet('api') == 1) { + return $this->respond([ + 'status' => 'success', + 'message' => 'Form data received successfully!', + 'data' => [ + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in' + ] + ]); + } + + return view('meta_dashboard_demo_one', [ + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in', + ]); + } } diff --git a/app/Models/TPAModel.php b/app/Models/TPAModel.php index fe3f30cd..679cae2a 100755 --- a/app/Models/TPAModel.php +++ b/app/Models/TPAModel.php @@ -19,5 +19,6 @@ class TPAModel extends Model "created_by", "updated_by", "is_active", + "dashboard_id", ]; } From 6a1e3519d263b5bb2dd6469426a209a6651a7bd1 Mon Sep 17 00:00:00 2001 From: "venkatesh.r" Date: Tue, 24 Feb 2026 15:29:30 +0530 Subject: [PATCH 3/6] FIX_ISSUE --- app/Controllers/EmployeeRestController.php | 48 ++++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 50398782..5c87df6c 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -5501,9 +5501,15 @@ class EmployeeRestController extends AdminController // 🔐 Move this to .env in real projects $METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY'); + $is_tpa_dashboard_enable = $this->clientPolicyModel + ->select('tpa.dashboard_id') + ->join('tpa', 'client_policy.tpa_id = tpa.id') + ->where('client_policy.id', $received_data['client_policy_id']) + ->where('tpa.dashboard_id IS NOT NULL') + ->first(); + $payload = [ 'resource' => [ - // 'dashboard' => 1 'dashboard' => 2 ], 'params' => (object)['client_policy' => $received_data['client_policy_id']], // MUST be object for Metabase @@ -5523,8 +5529,10 @@ class EmployeeRestController extends AdminController 'status' => 'success', 'message' => 'Form data received successfully!', 'data' => [ - 'metabaseToken' => $token, - 'metabaseUrl' => 'https://nsights.nhanceindia.in'] + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in' + ], + 'is_tpa_dashboard_enable' => !empty($is_tpa_dashboard_enable ?? []), ]); // } @@ -5534,12 +5542,27 @@ class EmployeeRestController extends AdminController ]); } - public function getHrTpaDashboard() + public function getHrTpaDashboard($client_policy_id = null, $return_type = 'api') { $METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY'); - $received_data = $this->request->getJSON(true) ?? null; - $policy_id = $received_data['client_policy_id'] ?? 4687; + if ($return_type === 'api') { + + $received_data = $this->request->getJSON(true) ?? []; + $policy_id = $received_data['client_policy_id'] ?? null; + + } else { + // Internal call + $policy_id = $client_policy_id; + } + + if (empty($policy_id)) { + return $this->respond([ + 'status' => 'failed', + 'message' => 'Client Policy ID is required.', + 'data' => [] + ]); + } $client_policy_data = $this->clientPolicyModel ->select('tpa.dashboard_id') @@ -5561,15 +5584,24 @@ class EmployeeRestController extends AdminController 'resource' => [ 'dashboard' => $database_id ], - 'exp' => time() + (10 * 60), // 10 minutes + 'exp' => time() + (10 * 60), 'params' => (object)[] ]; $token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256'); + if ($return_type === 'internal') { + + // Return raw data for internal usage + return [ + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in' + ]; + } + return $this->respond([ 'status' => 'success', - 'message' => 'Form data received successfully!', + 'message' => 'Dashboard fetched successfully.', 'data' => [ 'metabaseToken' => $token, 'metabaseUrl' => 'https://nsights.nhanceindia.in' From 0540cd65851e82ca9eba22b802cb484ec19898c9 Mon Sep 17 00:00:00 2001 From: "venkatesh.r" Date: Wed, 25 Feb 2026 14:10:52 +0530 Subject: [PATCH 4/6] CHANGE_SALES_DASHBOARD --- app/Controllers/SalesController.php | 63 ++++++++++++++++--- app/Helpers/utility_helper.php | 44 ++++++++++++- .../sales/sales_manager_level_dashboard.php | 53 ++++++++++++---- 3 files changed, 139 insertions(+), 21 deletions(-) diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php index f79fd170..047be5b3 100644 --- a/app/Controllers/SalesController.php +++ b/app/Controllers/SalesController.php @@ -672,7 +672,7 @@ class SalesController extends BaseController } - // ==================== Dashboard ==================== + // ==================== Dashboard ==================== public function branchLevelDashboard() { @@ -736,19 +736,39 @@ class SalesController extends BaseController public function salesManagerLevelDashboard() { - $userId = 1; + $userId = get_session_userid(); + // $userId = 1; $db = \Config\Database::connect(); try { + + $payload = $this->request->getGet(); + $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear(); + + + $fin_years = $db->table('sales_target') + ->select('fy_year') + ->where('user_id', $userId) + ->orderBy('fy_year', 'desc') + ->get() + ->getResultArray(); + $fin_years = array_column($fin_years, 'fy_year'); + $fin_years[] = '2024-2025'; + $target = $db->table('sales_target') ->where('user_id', $userId) - ->where('fy_year', '2025-2026') - ->get()->getRowArray(); + ->where('fy_year', $current_fin_year) + ->get() + ->getRowArray(); + + $targetAmount = $target['target_amount'] ?? 0.00; + + // get achieved amount from leads table + $achievedAmount = $this->getUserAchievedAmount($current_fin_year, $userId); - $targetAmount = $target['target_amount'] ?? 2500000.00; - $achievedAmount = 600000.00; $remainingAmount = $targetAmount - $achievedAmount; - $achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0; + // $achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0; + $achievementPercent = ($targetAmount > 0) ? min(100, round(($achievedAmount / $targetAmount) * 100)) : 0; $activitySummary = [ 'total' => $this->activityModel->where('assigned_to', $userId)->countAllResults(), @@ -779,9 +799,13 @@ class SalesController extends BaseController 'lead_count' => $myLeadsCount, 'upcoming' => $upcomingActivities, 'recent_leads' => $recentLeads, - 'user_name' => session()->get('first_name') ?? 'John Doe' + 'fin_years' => $fin_years, + 'display_fin_years' => format_financial_year($current_fin_year), + 'user_name' => get_session_userdata()->first_namee ?? '' ]; + // dd($data); + // return view('sales/my_dashboard_view', $data); $this->loadLayout('sales/sales_manager_level_dashboard', $data); @@ -789,4 +813,27 @@ class SalesController extends BaseController return $this->failServerError($e->getMessage()); } } + + public function getUserAchievedAmount($financialYear, $userId) + { + // Split the string into two years + $years = explode('-', $financialYear); + $startYear = $years[0]; // 2025 + $endYear = $years[1]; // 2026 + + // Create the timestamps + $startFY = $startYear . '-04-01 00:00:00'; + $endFY = $endYear . '-03-31 23:59:59'; + + $achievedAmountData = $this->leadModel + ->select('SUM(leads.premium_amount) as achieved_amount') + ->join('leads', 'sales_actual_leads.lead_id = leads.actual_lead_id') + ->where('sales_actual_leads.assigned_to', $userId) + ->where('leads.status', 'won') + ->where('leads.updated_at >=', $startFY) + ->where('leads.updated_at <=', $endFY) + ->findAll(); + + return $achievedAmountData[0]['achieved_amount'] ?? 0.00; + } } \ No newline at end of file diff --git a/app/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 467f0338..ce6e6e4a 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -1210,7 +1210,7 @@ if (!function_exists('map_relationship')) { * Extract identity from POST body or GET params. * Looks for 'email' or 'mobile_number'. */ - function resolveIdentity($request): ?string + function resolveIdentity($request): ?string { // Try POST body first $email = $request->getPost('email'); @@ -1251,7 +1251,7 @@ if (!function_exists('map_relationship')) { } - function recordRateLimitFailure(string $context = 'authApi'): void + function recordRateLimitFailure(string $context = 'authApi'): void { /** @var IncomingRequest $request */ $request = \Config\Services::request(); @@ -1276,4 +1276,44 @@ if (!function_exists('map_relationship')) { } + if (!function_exists('getCurrentFinancialYear')) { + function getCurrentFinancialYear() + { + // Get current year and month + $date = new DateTime(); + $currentYear = (int)$date->format('Y'); + $currentMonth = (int)$date->format('m'); + + // If month is Jan, Feb, or March, we are still in the previous year's FY + if ($currentMonth < 4) { + $startYear = $currentYear - 1; + $endYear = $currentYear; + } else { + $startYear = $currentYear; + $endYear = $currentYear + 1; + } + + return $startYear . '-' . $endYear; + } + } + + + if (!function_exists('format_financial_year')) { + function format_financial_year(string $financialYear): string + { + if (empty($financialYear) || !str_contains($financialYear, '-')) { + return $financialYear; + } + + $years = explode('-', $financialYear); + + // Ensure we have both parts + $startYear = $years[0] ?? ''; + $endYear = $years[1] ?? ''; + + return "APR " . $startYear . " - MAR " . $endYear; + } + } + + diff --git a/app/Views/sales/sales_manager_level_dashboard.php b/app/Views/sales/sales_manager_level_dashboard.php index 5b75fc42..e103759c 100644 --- a/app/Views/sales/sales_manager_level_dashboard.php +++ b/app/Views/sales/sales_manager_level_dashboard.php @@ -1,5 +1,5 @@ + +
+
+
+ + +
+
All
+
Pending
+
Completed
+
+ + +
+ + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/Views/sales/tracker_view.php b/app/Views/sales/tracker_view.php index b317712c..d2c3085d 100644 --- a/app/Views/sales/tracker_view.php +++ b/app/Views/sales/tracker_view.php @@ -2,19 +2,20 @@ /* POC Exact Styling */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; } - .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; height: 100vh; } - .top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } - .search-input { width: 400px; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; } - .search-input:focus { border-color: #ff6b35; } + .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; } + /* .top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } */ + .btn-primary { background: #ff6b35; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; } .btn-primary:hover { background: #ff5722; transform: translateY(-1px); } /* Filter Tabs */ - .filter-tabs { display: flex; gap: 10px; padding: 20px 30px; background: #f5f5f5; } + .filter-tabs { display: flex; gap: 10px; padding: 20px 30px; } .tab { padding: 10px 20px; border-radius: 20px; cursor: pointer; font-size: 14px; background: white; color: #666; border: 1px solid #e0e0e0; transition: all 0.2s; } .tab.active { background: #ff6b35; color: white; border-color: #ff6b35; } /* Leads Grid */ + .lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; } + .lead-actions { display: flex; align-items: center; gap: 10px; } .leads-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 0 30px 30px; overflow-y: auto; } .lead-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s; } .lead-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); transform: translateY(-2px); } @@ -22,12 +23,13 @@ .status-new { background: #e3f2fd; color: #1976d2; } .status-potential { background: #fff3e0; color: #f57c00; } .status-prospects { background: #e8f5e9; color: #388e3c; } + .status-not-a-prospects { background: #ffebee; color: #d32f2f; } /* Modals */ .modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; } .modal.active { display: flex; } .modal-content { background: white; border-radius: 12px; width: 90%; max-width: 800px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; } - .modal-header { padding: 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } + .modal-header { padding: 5px 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } .modal-body { padding: 25px; flex: 1; } .form-group { margin-bottom: 20px; } .form-label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; } @@ -39,6 +41,14 @@ .activity-type-btn:hover { border-color: #ff6b35; background: #fff5f2; } .activity-type-btn.active { border-color: #ff6b35; background: #ff6b35; color: white; } + .activity-types { display: grid; grid-template-columns: repeat(12, 1fr); /* 12-column grid */ gap: 10px; margin-bottom: 20px; } + + /* First 3 buttons → 4 columns each (3 × 4 = 12) */ + .activity-types button:nth-child(-n+3) { grid-column: span 4; } + + /* Next 4 buttons → 3 columns each (4 × 3 = 12) */ + .activity-types button:nth-child(n+4) { grid-column: span 3; } + /* Timeline Styling */ .timeline { position: relative; padding-left: 30px; margin-top: 20px; } .timeline-item { position: relative; padding-bottom: 25px; } @@ -47,71 +57,167 @@ .timeline-dot.completed { background: #4caf50; box-shadow: 0 0 0 1px #4caf50; } .timeline-content { background: #f8f8f8; padding: 15px; border-radius: 8px; } .timeline-item {display : block !important;} + .timeline::before { display: none !important; content: none !important; } + .timeline.no-line::before { display: none; } + .timeline { border-left: none !important; } + .timeline-item:last-child { border-left: none !important;} + .timeline-item:last-child::before { display: none !important;} + + /* opportunities Styling */ + .opportunities-list { display: grid; gap: 15px;} + .opportunity-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e0e0e0; transition: all 0.2s;} + .opportunity-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px;} + .opportunity-title { font-weight: 600; font-size: 16px; margin-bottom: 5px;} + .opportunity-amount { font-size: 20px; font-weight: 700; color: #4caf50;} + .opportunity-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px;} + .opportunity-detail-item { font-size: 13px; color: #666;} + .opportunity-footer { margin-top: 15px; padding-top: 15px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #666; } + + /* Base style for both tabs */ + .tab-item { cursor: pointer; padding-bottom: 10px; margin: 0; font-size: 16px; color: #999; /* Default grey for unselected */ border-bottom: 2px solid transparent; transition: all 0.2s ease; } + + /* Style for the SELECTED tab */ + .tab-item.active { color: #333; /* Darker text */ border-bottom: 2px solid #ff6b35; /* Orange underline */ } + + .empty-state { text-align: center; padding: 60px 20px; color: #999; } + .empty-icon { width: 80px; height: 80px; margin: 0 auto 20px; background: #f5f5f5; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36px; } + input, select, textarea { background-color: white !important; border-radius: 6px !important; box-shadow: none !important; border-color: #ddd !important; } + input, select, textarea { width: 400px; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; } + input:focus, select:focus, textarea:focus { border-color: #ff6b35; outline: none; /* removes blue browser outline */ } + .modal .form-control { transition: 0.2s ease; } + + .modal .form-control:focus { border-color: #ff6b35; box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2); + outline: none; + } + + /* The animation definition */ + @keyframes validation-blink { + 0% { transform: translateX(0); box-shadow: 0 0 0px red; } + 25% { transform: translateX(-5px); box-shadow: 0 0 10px red; } + 50% { transform: translateX(5px); box-shadow: 0 0 10px red; } + 75% { transform: translateX(-5px); box-shadow: 0 0 10px red; } + 100% { transform: translateX(0); box-shadow: 0 0 0px red; } + } + + /* The class we will add via JavaScript */ + .blink-error { + animation: validation-blink 0.3s ease-in-out; + border-color: #ff4d4d !important; + } +
-
+ +
+
+ + +
+
All
+
New
+
Potential
+
Prospects
+
Not a Prospects
+
+ + +
+ + +
+
+ + +