diff --git a/app/Config/Acl.php b/app/Config/Acl.php index 80f7a2bd..86760bb0 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]], @@ -28,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/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..d7353cb7 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 @@ -87,6 +88,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'); @@ -416,7 +418,7 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->get('getTheEmpDataForClaim/(:any)', 'ClientController::getTheEmpDataForClaim/$1'); // $routes->get('getTheEmpDataForClaimSearchByMobile/(:any)', 'ClientController::getTheEmpDataForClaimSearchByMobile/$1'); $routes->get('getTheEmpDataForClaimSearchByMobile', 'ClientController::getTheEmpDataForClaimSearchByMobile'); - $routes->get('getLeadNonEB/(:any)', 'LeadsController::getLeadNonEB/$1'); + $routes->get('getLeadNonEB/(:any)/(:any)', 'LeadsController::getLeadNonEB/$1/$2'); $routes->get('getPolicyTypeFields', 'LeadsController::getPolicyTypeFields'); $routes->get('removeMultiFile', 'LeadsController::removeMultiFile'); $routes->get('removeInstallments', 'LeadsController::removeInstallments'); @@ -669,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 @@ -706,6 +709,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"); @@ -908,6 +912,10 @@ $routes->group('sales', function($routes) { $routes->get('/', 'SalesController::index'); + $routes->get('activities', 'SalesController::activities'); + + $routes->get('page/(:segment)', 'SalesController::noPage/$1'); + // Get all leads with filters $routes->get('leads', 'SalesController::getLeads'); 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..5c87df6c 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(); } @@ -5498,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 @@ -5520,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 ?? []), ]); // } @@ -5530,6 +5541,73 @@ class EmployeeRestController extends AdminController 'metabaseUrl' => 'https://nsights.nhanceindia.in', ]); } + + public function getHrTpaDashboard($client_policy_id = null, $return_type = 'api') + { + $METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY'); + + 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') + ->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), + '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' => 'Dashboard fetched successfully.', + 'data' => [ + 'metabaseToken' => $token, + 'metabaseUrl' => 'https://nsights.nhanceindia.in' + ] + ]); + } // get policy files @@ -5648,5 +5726,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/Controllers/LeadsController.php b/app/Controllers/LeadsController.php index b4eaac06..2c2d9bcf 100644 --- a/app/Controllers/LeadsController.php +++ b/app/Controllers/LeadsController.php @@ -363,8 +363,9 @@ class LeadsController extends BaseController public function createLead() { - // print_r($this->request->getPost()); die; $id = $this->request->getPost('id'); + $actual_lead_id = $this->request->getPost('actual_lead_id'); + $actual_lead_id = !empty($actual_lead_id) ? $actual_lead_id : null; $postData = $this->request->getPost(); $data = $this->prepareLeadData(); $rules = [ @@ -4349,8 +4350,10 @@ class LeadsController extends BaseController } // get lead data for edit both EB and NON-EB - public function getLeadNonEB($type, $id = null) + public function getLeadNonEB($type, $actual_lead_id = null ,$id = null) { + // Convert '0' to null so it doesn't break your existing DB checks + $actual_lead_id = ($actual_lead_id == 0 || $actual_lead_id == '0') ? null : $actual_lead_id; // Set basic data $data = [ 'issuer' => $this->issuer, @@ -4365,6 +4368,7 @@ class LeadsController extends BaseController 'gpaClaimType' => $this->claim_type_for_gpa, 'causeOfDeath' => $this->cause_of_death, 'selected_lead_type' => $type, + 'actual_lead_id' => $actual_lead_id, ]; // Fetch sales team members who are active in team 5 diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php index f79fd170..904217b1 100644 --- a/app/Controllers/SalesController.php +++ b/app/Controllers/SalesController.php @@ -27,15 +27,107 @@ class SalesController extends BaseController $this->noteModel = new SalesLeadNoteModel(); } + public function index() { - $db = \Config\Database::connect(); - // Fetch users for the assignment dropdowns - $data['users'] = $db->table('user_profiles') - ->select('id, first_name, last_name') - ->where('is_active', 1) - ->get()->getResultArray(); + $data = $this->getSalesStaffData(); + + $data['tab_name'] = 'Leads'; + $data['page_name'] = 'Leads'; + + return $this->loadLayout('sales/tracker_view', $data); + } - $this->loadLayout('sales/tracker_view', $data); + public function activities(){ + $data = $this->getSalesStaffData(); + + $data['tab_name'] = 'Activities'; + $data['page_name'] = 'Activities'; + + $data['leads'] = $this->leadModel->select('lead_id, company_name') + ->orderBy('lead_id', 'DESC') + ->findAll(); + + return $this->loadLayout('sales/activity_view', $data); + } + + /** + * HELPER: Fetches Sales Managers based on the logged-in user's role and branch + */ + private function getSalesStaffData(): array + { + $db = \Config\Database::connect(); + $logged_user_id = get_session_userid(); + + $data = [ + 'users' => [], + 'sales_manager_ids' => [] + ]; + + // Get the Branch ID, Role, Team, and Name of the logged-in user + $row = $db->table('user_profiles up') + ->select('up.id, up.first_name, up.last_name, up.nhance_branch_id, up.role, ut.team_id') + ->join('user_teams ut', 'ut.user_id = up.id', 'left') + ->where('up.is_active', 1) + ->where('up.id', $logged_user_id) + ->get() + ->getRow(); + + $nhance_branch_id = $row ? $row->nhance_branch_id : null; + $role = $row ? $row->role : null; + $team_id = $row ? $row->team_id : null; + + // Is the logged-in user a Sales Manager? (Role 4, Team 5) + if ($role == 4 && $team_id == 5) { + + $data['sales_manager_ids'] = [$logged_user_id]; + + $data['users'] = [ + [ + 'id' => $row->id, + 'sales_manager' => trim($row->first_name . ' ' . $row->last_name), + 'nhance_branch_id' => $nhance_branch_id + ] + ]; + + } + // Otherwise fetch ALL sales managers in this branch + elseif ($nhance_branch_id) { + + $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(); + + $data['sales_manager_ids'] = array_column($data['users'], 'id'); + } + + return $data; + } + + public function noPage($type = null) + { + + $page_type = $type ?? 'Page'; + + $data['tab_name'] = 'Sales ' . $page_type; + $data['page_name'] = 'Sales ' . $page_type; + + echo view('layout/header', $data); + echo '
+
+
+

Oops! ' . esc($page_type) . ' is under construction.

+ Error Image +
+
+
'; + echo view('layout/footer', $data); } public function completeActivity($id) { @@ -672,7 +764,7 @@ class SalesController extends BaseController } - // ==================== Dashboard ==================== + // ==================== Dashboard ==================== public function branchLevelDashboard() { @@ -736,19 +828,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 +891,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 +905,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/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/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 467f0338..efc2842e 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,64 @@ 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; + } + } + + + if(!function_exists('add_google_calender_event')){ + function add_google_calender_event($data){ + + $calendar = new \App\Libraries\GoogleCalendarService(); + + // Check if user is authenticated without passing tokens manually + if (!$calendar->isReady()) { + return ['status' => 'failed', 'code' => '404' , 'message' => 'Google Access Token Expired']; + } + + try { + $response = $calendar->createEvent($data); + return ['status' => 'success', 'code' => '200' , 'message' => 'Follow-up Saved', 'response' => $response]; + } catch (\Exception $e) { + return ['status' => 'failed', 'code' => '500' , 'message' => 'Error: ' . $e->getMessage()]; + } + } + } + + 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([ diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php index 1c028645..72ac2f59 100644 --- a/app/Models/LeadsModel.php +++ b/app/Models/LeadsModel.php @@ -10,6 +10,7 @@ class LeadsModel extends Model protected $primaryKey = 'id'; protected $allowedFields = [ 'id', + 'actual_lead_id', 'lead_type', 'issuer', 'client_type', diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php index 80b8aef6..8d82097f 100644 --- a/app/Models/SalesActivityModel.php +++ b/app/Models/SalesActivityModel.php @@ -38,9 +38,9 @@ class SalesActivityModel extends Model // Validation protected $validationRules = [ 'lead_id' => 'required|integer', - 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo]', + 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo,Visit]', 'notes' => 'required', - 'scheduled_date' => 'required|valid_date', + 'scheduled_date' => 'required', 'assigned_to' => 'required|integer', 'status' => 'in_list[pending,completed]', ]; @@ -80,35 +80,63 @@ class SalesActivityModel extends Model */ public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0) { - $builder = $this->select('sales_activities.*, actual_leads.company_name, user_profiles.first_name as assigned_to_name') - ->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left') + $this->select('sales_activities.*, sales_actual_leads.company_name, user_profiles.first_name as assigned_to_name') + ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left') ->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left'); if (!empty($filters['status'])) { - $builder->where('sales_activities.status', $filters['status']); + $this->where('sales_activities.status', $filters['status']); } if (!empty($filters['activity_type'])) { - $builder->where('sales_activities.activity_type', $filters['activity_type']); + $this->where('sales_activities.activity_type', $filters['activity_type']); } + // if (!empty($filters['assigned_to'])) { + // $this->where('sales_activities.assigned_to', $filters['assigned_to']); + // } if (!empty($filters['assigned_to'])) { - $builder->where('sales_activities.assigned_to', $filters['assigned_to']); + $assigned_to = $filters['assigned_to']; + + // If it's already an array, use it. If it's a string, explode it into an array. + $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); } + + if (!empty($filters['date_from'])) { - $builder->where('sales_activities.scheduled_date >=', $filters['date_from']); + $this->where('sales_activities.scheduled_date >=', $filters['date_from']); } if (!empty($filters['date_to'])) { - $builder->where('sales_activities.scheduled_date <=', $filters['date_to']); + $this->where('sales_activities.scheduled_date <=', $filters['date_to']); } - return [ - 'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC') - ->limit($limit, $offset)->findAll(), - 'total' => $builder->countAllResults(false) - ]; + if (!empty($filters['search'])) { + $this->groupStart() + ->like('sales_actual_leads.company_name', $filters['search']) + ->orLike('sales_actual_leads.email', $filters['search']) + ->orLike('sales_actual_leads.phone', $filters['search']) + ->orLike('user_profiles.first_name', $filters['search']) + ->groupEnd(); + } + + $this->orderBy('sales_activities.created_at', 'DESC'); + + $total = $this->countAllResults(false); + + $data = $this->findAll($limit, $offset); + + return ['data' => $data,'total' => $total]; + + // return [ + // 'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC') + // ->limit($limit, $offset)->findAll(), + // 'total' => $builder->countAllResults(false) + // ]; } /** @@ -142,8 +170,8 @@ class SalesActivityModel extends Model { $endDate = date('Y-m-d H:i:s', strtotime("+{$days} days")); - return $this->select('sales_activities.*, actual_leads.company_name') - ->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left') + return $this->select('sales_activities.*, sales_actual_leads.company_name') + ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left') ->where('sales_activities.assigned_to', $userId) ->where('sales_activities.status', 'pending') ->where('sales_activities.scheduled_date <=', $endDate) diff --git a/app/Models/SalesActualLeadModel.php b/app/Models/SalesActualLeadModel.php index 25603740..22db6a2a 100644 --- a/app/Models/SalesActualLeadModel.php +++ b/app/Models/SalesActualLeadModel.php @@ -37,23 +37,37 @@ class SalesActualLeadModel extends Model // Validation protected $validationRules = [ - 'company_name' => 'required|min_length[2]|max_length[255]', - 'email' => 'required|valid_email|max_length[255]', - 'phone' => 'required|min_length[10]|max_length[20]', - 'status' => 'in_list[New,Potential,Prospects,Non prospects]', + 'company_name' => 'required|alpha_space|min_length[2]|max_length[255]', + 'email' => 'required_without[phone]|permit_empty|valid_email|max_length[255]', + 'phone' => 'required_without[email]|permit_empty|regex_match[/^[0-9+\s]+$/]|min_length[10]|max_length[20]', + 'status' => 'in_list[New,Potential,Prospects,Not a Prospects]', + 'assigned_to' => 'required', + 'gst_number' => 'permit_empty|exact_length[15]|regex_match[/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]Z[0-9A-Z]$/]', ]; protected $validationMessages = [ 'company_name' => [ - 'required' => 'Company name is required', + 'required' => 'Company Name is Missing', + 'alpha_space' => 'Company Name must contain only letters and spaces', + 'min_length' => 'Company Name must be at least 2 characters long', + 'max_length' => 'Company Name must be at most 255 characters long' ], 'email' => [ - 'required' => 'Email is required', + 'required_without' => 'Either Email or Mobile Number is Needed.', 'valid_email' => 'Please provide a valid email address', + 'max_length' => 'Email must be at most 255 characters long', ], 'phone' => [ - 'required' => 'Phone number is required', + 'required_without' => 'Either Email or Mobile Number is Needed.', + 'regex_match' => 'Phone number can contain only digits, + and spaces.', + 'min_length' => 'Mobile Number must be at least 10 characters long', + 'max_length' => 'Mobile Number must be at most 20 characters long', ], + 'status' => [ 'in_list' => 'Please select a valid Status'], + 'assigned_to'=> [ 'required' => 'Please select a valid User' ], + 'gst_number' => [ 'exact_length' => 'GST Number must be exactly 15 characters long.', + 'regex_match' => 'Please enter a valid GST Number (e.g., 22AAAAA0000A1Z5).' + ], ]; protected $skipValidation = false; @@ -74,29 +88,42 @@ class SalesActualLeadModel extends Model */ public function getLeadsWithFilters($filters = [], $limit = 10, $offset = 0) { - $builder = $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name') + $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name, user_profiles.email as assigned_to_email') ->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left'); if (!empty($filters['status'])) { - $builder->where('sales_actual_leads.status', $filters['status']); + $this->where('sales_actual_leads.status', $filters['status']); } + // if (!empty($filters['assigned_to'])) { + // $this->where('sales_actual_leads.assigned_to', $filters['assigned_to']); + // } if (!empty($filters['assigned_to'])) { - $builder->where('sales_actual_leads.assigned_to', $filters['assigned_to']); + $assigned_to = $filters['assigned_to']; + + // If it's already an array, use it. If it's a string, explode it into an array. + $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->orderBy('sales_actual_leads.created_at', 'DESC'); + if (!empty($filters['search'])) { - $builder->groupStart() + $this->groupStart() ->like('sales_actual_leads.company_name', $filters['search']) ->orLike('sales_actual_leads.email', $filters['search']) ->orLike('sales_actual_leads.phone', $filters['search']) + ->orLike('user_profiles.first_name', $filters['search']) ->groupEnd(); } - return [ - 'data' => $builder->limit($limit, $offset)->findAll(), - 'total' => $builder->countAllResults(false) - ]; + $total = $this->countAllResults(false); + + $data = $this->findAll($limit, $offset); + + return ['data' => $data,'total' => $total]; } /** @@ -113,9 +140,11 @@ class SalesActualLeadModel extends Model $contactModel = new SalesContactPersonModel(); $activityModel = new SalesActivityModel(); $noteModel = new SalesLeadNoteModel(); + $leadsModel = new LeadsModel(); $lead['contact_persons'] = $contactModel->where('lead_id', $leadId)->findAll(); $lead['activities'] = $activityModel->getActivitiesByLead($leadId); + $lead['opportunities'] = $leadsModel->where('actual_lead_id', $leadId)->findAll(); $lead['notes'] = $noteModel->getNotesByLead($leadId); return $lead; 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", ]; } diff --git a/app/Views/layout/header.php b/app/Views/layout/header.php index 57aff2c2..61e63b23 100755 --- a/app/Views/layout/header.php +++ b/app/Views/layout/header.php @@ -2077,6 +2077,37 @@ + +
  • + + /assets/images/sales_tracker_dark_sb.png" alt="Logo" height="20"> + Sales Tracker + +
    + +
    +
  • +
  • diff --git a/app/Views/leads_form.php b/app/Views/leads_form.php index ef30ba59..87868d39 100644 --- a/app/Views/leads_form.php +++ b/app/Views/leads_form.php @@ -131,6 +131,8 @@ enctype="multipart/form-data"> + +
    @@ -741,6 +743,7 @@ $('#page_title').text(page_title); $('#leads_primarykey').val(res.data.id); + $('#actual_lead_id').val(res.data.actual_lead_id || 0); $('#policy_start_date').val(res.data.policy_end_date); $('#lead_type').val(res.data.lead_type); $('#issuer').val(res.data.issuer); diff --git a/app/Views/leads_form_handler.php b/app/Views/leads_form_handler.php index 6677520d..01960410 100644 --- a/app/Views/leads_form_handler.php +++ b/app/Views/leads_form_handler.php @@ -956,6 +956,7 @@ if (isset($selected_lead_type)) { $('#page_title').text('Edit Lead'); $('#leads_primarykey').val(data.id || ''); + $('#actual_lead_id').val(data.actual_lead_id || 0); $('#policy_start_date').val(data.policy_end_date || ''); $('#lead_type').val(data.lead_type || ''); $('#issuer').val(data.issuer || ''); @@ -1130,6 +1131,7 @@ if (isset($selected_lead_type)) { $('#page_title').text('Edit Lead'); $('#leads_primarykey').val(data.id || ''); + $('#actual_lead_id').val(data.actual_lead_id || 0); $('#policy_start_date').val(data.policy_end_date || ''); $('#lead_type').val(data.lead_type || ''); $('#issuer').val(data.issuer || ''); diff --git a/app/Views/leads_list.php b/app/Views/leads_list.php index 896a1117..98b9f232 100644 --- a/app/Views/leads_list.php +++ b/app/Views/leads_list.php @@ -533,8 +533,11 @@ table.dataTable tbody td { console.log('lead_id', lead_id); console.log('lead_form_type', lead_form_type); - - let url = '' + lead_form_type + '/' + lead_id + let actual_lead_id = 0; // Hardcoded to 0 since this is an edit operation + //Here After Edit URL: /type / actual_lead_id (0) / lead_id + let url = '' + lead_form_type + '/' + actual_lead_id + '/' + lead_id; + // old URL + // let url = '' + lead_form_type + '/' + lead_id console.log('url ', url) window.location.href = url; diff --git a/app/Views/leads_non_eb.php b/app/Views/leads_non_eb.php index 6456935a..6d2049f0 100644 --- a/app/Views/leads_non_eb.php +++ b/app/Views/leads_non_eb.php @@ -74,6 +74,7 @@ +
    diff --git a/app/Views/policy_transaction_endorsement_form.php b/app/Views/policy_transaction_endorsement_form.php index 4b4ab0ba..0c9c579b 100644 --- a/app/Views/policy_transaction_endorsement_form.php +++ b/app/Views/policy_transaction_endorsement_form.php @@ -621,7 +621,7 @@ console.log('Business Team Logged in.'); } - getURLParamsForReport() + // getURLParamsForReport() }) $(document).ready(function(){ @@ -1086,10 +1086,10 @@ if(res.data.client_type == 2){ let data1 = policyListByClient[res.data.client_id]; - appendPolicies(data1, res.pt_id); + appendPolicies(data1, res.data.client_policy_id); }else{ let data2 = policy_list[res.data.client_branch_id]; - appendPolicies(data2, res.pt_id); + appendPolicies(data2, res.data.client_policy_id); } setTimeout(function(){ @@ -2957,9 +2957,9 @@ if (pt_id != 0) { hide_list_show_add(); - setTimeout(() => - getPolicyTransactionDataForEndorsementEdit(pt_id), - 1500); // Reduced timeout + // setTimeout(() => + getPolicyTransactionDataForEndorsementEdit(pt_id) + // 1500); // Reduced timeout } else { show_list_hide_add(); } diff --git a/app/Views/policy_transaction_endorsement_list.php b/app/Views/policy_transaction_endorsement_list.php index 0365496b..eed97902 100644 --- a/app/Views/policy_transaction_endorsement_list.php +++ b/app/Views/policy_transaction_endorsement_list.php @@ -460,7 +460,11 @@ table.dataTable thead th { // select2 document ready $(document).ready(function() { - getClientAndBranchAndPolicy() + // getClientAndBranchAndPolicy() + getClientAndBranchAndPolicy(function() { + getURLParamsForReport(); + }); + addHTMLInput(); has('create_failed')) : ?> @@ -632,7 +636,7 @@ table.dataTable thead th { } - function getClientAndBranchAndPolicy() { + function getClientAndBranchAndPolicy(callback) { $.ajax({ url: '', type: "GET", @@ -652,6 +656,11 @@ table.dataTable thead th { console.log('No data found'); } + // ✅ Data ellam set aanadhuku aparam callback call panrom + if (typeof callback === "function") { + callback(); + } + }, error: function(xhr, status, error) { console.error(xhr.responseText); diff --git a/app/Views/sales/activity_view.php b/app/Views/sales/activity_view.php new file mode 100644 index 00000000..4c5f939b --- /dev/null +++ b/app/Views/sales/activity_view.php @@ -0,0 +1,1744 @@ + + +
    +
    +
    + + +
    +
    All
    +
    Pending
    +
    Completed
    +
    + + +
    + + +
    + +
    + +
    + + + +
    + + + + + + + + + + + + + + \ No newline at end of file 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
    +
    New
    +
    Potential
    +
    Prospects
    +
    Not a Prospects
    +
    + + +
    + + +
    +
    + + +