diff --git a/.env.sample b/.env.sample
index 4faec3e2..5c3f1bf8 100755
--- a/.env.sample
+++ b/.env.sample
@@ -131,5 +131,10 @@ HEALTH_INDIA_PASSWORD =
HEALTH_INDIA_PRIMARY_KEY_CONSTANT =
+#For sending mail for leads RFQ/QCR
LEAD_INSURER_FROM_MAIL_ID =
-LEAD_CLIENT_FROM_MAIL_ID =
\ No newline at end of file
+LEAD_CLIENT_FROM_MAIL_ID =
+
+# BDS Daily Report Emails Configuration
+bds.dailyReportEmails =
+
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 4996d7f7..09d117f3 100755
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -444,11 +444,17 @@ $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->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
});
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
$routes->cli("cli/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
+$routes->cli("cli/cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport");
+$routes->cli('cli/croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
+
+
$routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
@@ -496,6 +502,7 @@ $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
//$routes->post('failedStatement',"PolicyTransactionController::failedStatementList");
});
+ $routes->get("cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport");
});
$routes->group("leads", ["filter" => "authMVC"], function ($routes) {
@@ -551,9 +558,18 @@ $routes->cli("cli/sendCroneRemainderMail", "DashboardController::sendCroneRemain
$routes->cli('cli/update-emp-policy-status', 'ClientController::updateEmpAndPolicyStatus');
$routes->cli('cli/insurerRFQRemainder', 'LeadsController::remainderForQcr');
$routes->cli('cli/sendMailWithAutoQuery','TicketController::sendMailWithAutoQuery');
+
+// Claim Status Update every 2 hours
$routes->cli('cli/MediAssit-ClaimStatusUpdate','MediAssistApiController::ClaimStatusUpdate');
$routes->cli('cli/Vidal-ClaimStatusUpdate','VidalApiController::ClaimStatusUpdate');
+$routes->cli('cli/Fhpl-ClaimStatusUpdate','FhplApiController::ClaimStatusUpdate');
+$routes->cli('cli/HealthIndia-ClaimStatusUpdate','HealthIndiaApiController::ClaimStatusUpdate');
+
+// Sync TPA Claims to Nhance
$routes->cli('cli/MediAssit-syncTpaClaimToNhance','MediAssistApiController::syncTpaClaimToNhance');
+$routes->cli('cli/Fhpl-syncTpaClaimToNhance','FhplApiController::syncFhplClaimsToNhance');
+$routes->cli('cli/HealthIndia-syncTpaClaimToNhance','HealthIndiaApiController::syncHealthIndiaClaimsToNhance');
+
$routes->cli('cli/thzReminderCrone','ThzController::getOpenTicketsOlderThan24HoursAndAssignNextLevel');
@@ -770,6 +786,7 @@ $routes->group("/ticket", ["filter" => "authMVC"], function ($routes) {
$routes->get('fetchVehiclePolicy/(:any)','TicketController::fetchVehiclePolicy/$1');
$routes->post('saveIRDocsJson',"TicketController::saveIRDocsJson");
$routes->get('getTpaClaimStatus',"ApiServiceController::getClaimStatus");
+ $routes->post('manualTpaClaimPush',"ApiServiceController::manualTpaClaimPush");
});
$routes->group("/claim_mis", ["filter" => "authMVC"], function ($routes) {
@@ -920,7 +937,7 @@ $routes->group('sales', function($routes) {
$routes->get('/', 'SalesController::index');
$routes->get('loadactivities', 'SalesController::loadactivities');
-
+
$routes->get('loadtargets', 'SalesController::loadtargets');
$routes->get('page/(:segment)', 'SalesController::noPage/$1');
@@ -1003,6 +1020,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');
@@ -1010,4 +1034,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/ApiServiceController.php b/app/Controllers/ApiServiceController.php
index 28b2aec6..16a650a0 100644
--- a/app/Controllers/ApiServiceController.php
+++ b/app/Controllers/ApiServiceController.php
@@ -476,6 +476,35 @@ class ApiServiceController extends BaseController
}
}
+ /**
+ * Manual TPA Claim Push - accepts claim_id via POST and delegates to pushClaims().
+ * Returns the response from pushClaims() as the API response.
+ */
+ public function manualTpaClaimPush()
+ {
+ $claimId = $this->request->getPost('claim_id');
+
+ if (empty($claimId)) {
+ return $this->response->setJSON([
+ 'status' => false,
+ 'message' => 'claim_id is required'
+ ]);
+ }
+
+ $result = $this->pushClaims($claimId);
+
+ if ($result !== null && is_array($result)) {
+ return $this->response->setJSON([
+ 'status' => $result['status'] ?? false,
+ 'message' => $result['message'] ?? ($result['status'] ? 'Claim pushed successfully' : 'Claim push failed')
+ ]);
+ }
+
+ return $this->response->setJSON([
+ 'status' => false,
+ 'message' => 'Claim push failed or TPA has no API service enabled for this ticket.'
+ ]);
+ }
// public function getWellnessUrl()
diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php
index 24c0f2ec..ee3a8098 100755
--- a/app/Controllers/ClientController.php
+++ b/app/Controllers/ClientController.php
@@ -6969,16 +6969,23 @@ 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->tpaClaimDumpImporter(["file_id" => 48]); //vidal`
+ // $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 57]); //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/DashboardController.php b/app/Controllers/DashboardController.php
index 1dab325b..b4d224fe 100755
--- a/app/Controllers/DashboardController.php
+++ b/app/Controllers/DashboardController.php
@@ -7,6 +7,7 @@ use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use App\Helpers\sendMailNotification;
+use App\Helpers\MailHelper;
use CodeIgniter\API\ResponseTrait;
@@ -24,6 +25,11 @@ use App\Controllers\EmpDataServiceController;
use App\Models\TicketMasterModel;
use App\Models\LeadsModel;
use App\Models\TicketClaimStatusModel;
+use App\Models\FileModel;
+use App\Models\BatchFileModel;
+use App\Models\SalesActivityModel;
+use App\Models\SalesActualLeadModel;
+
class DashboardController extends AdminController
@@ -43,6 +49,13 @@ class DashboardController extends AdminController
protected $policyStatus;
protected $colorShades;
protected $claimDashLimit;
+ protected $filesModel;
+ protected $batchFilesModel;
+ protected $leadsModel;
+ protected $ticketMasterModel;
+ protected $ticketClaimStatusModel;
+ protected $salesActivityModel;
+ protected $salesActualModel;
protected $myLogger;
@@ -59,6 +72,13 @@ class DashboardController extends AdminController
$this->ticketModel = new TicketMasterModel();
$this->leadModel = new LeadsModel();
$this->ticketStatusModel = new TicketClaimStatusModel();
+ $this->filesModel = new FileModel();
+ $this->batchFilesModel = new BatchFileModel();
+ $this->leadsModel = new LeadsModel();
+ $this->ticketMasterModel = new TicketMasterModel();
+ $this->ticketClaimStatusModel = new TicketClaimStatusModel();
+ $this->salesActivityModel = new SalesActivityModel();
+ $this->salesActualModel = new SalesActualLeadModel();
$this->myLogger = \Config\Services::mylogger();
@@ -787,4 +807,298 @@ class DashboardController extends AdminController
$data['ticket_type_id'] = $ticketTypeId;
return $this->respond(['status' => "success", "data" => $data], 200);
}
+
+ public function croneDailyActivityReport()
+ {
+
+ // $today = date('Y-m-d');
+ $today = date('Y-m-d', strtotime('-1 day'));
+
+ // Inception & endorsement counts (file uploads)
+ $total_inception_count = $this->filesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('action', 'inception')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ $total_endorsement_count = $this->filesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('action !=', 'inception')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ // TPA & Insurer batch file counts
+ $total_tpa_incetion_count = $this->batchFilesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('event_type', 'inception')
+ ->where('insurer_or_tpa', 'tpa')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ $total_tpa_endorsement_count = $this->batchFilesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('event_type !=', 'inception')
+ ->where('insurer_or_tpa', 'tpa')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ $total_insurer_incetion_count = $this->batchFilesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('event_type', 'inception')
+ ->where('insurer_or_tpa', 'insurer')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ $total_insurer_endorsement_count = $this->batchFilesModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('event_type !=', 'inception')
+ ->where('insurer_or_tpa', 'insurer')
+ ->where('status', 'success')
+ ->countAllResults();
+
+ // Claim counts
+ $total_claim_count = $this->ticketMasterModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->countAllResults();
+
+ $total_gmc_status_wise_claim_count = $this->ticketMasterModel
+ ->select('tcs.claim_status, count(*) as count')
+ ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left')
+ ->where('ticket_master.is_active', 1)
+ ->where('ticket_master.ticket_type_id', 1)
+ // ->where('DATE(ticket_master.created_at)', $today)
+ ->where('tcs.is_active', 1)
+ ->groupBy('tcs.claim_status')
+ ->findAll();
+
+ $total_gpa_status_wise_claim_count = $this->ticketMasterModel
+ ->select('tcs.claim_status, count(*) as count')
+ ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left')
+ ->where('ticket_master.is_active', 1)
+ ->where('ticket_master.ticket_type_id', 2)
+ // ->where('DATE(ticket_master.created_at)', $today)
+ ->where('tcs.is_active', 1)
+ ->groupBy('tcs.claim_status')
+ ->findAll();
+
+ $total_edli_status_wise_claim_count = $this->ticketMasterModel
+ ->select('tcs.claim_status, count(*) as count')
+ ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left')
+ ->where('ticket_master.is_active', 1)
+ ->where('ticket_master.ticket_type_id', 3)
+ // ->where('DATE(ticket_master.created_at)', $today)
+ ->where('tcs.is_active', 1)
+ ->groupBy('tcs.claim_status')
+ ->findAll();
+
+ $total_gtli_status_wise_claim_count = $this->ticketMasterModel
+ ->select('tcs.claim_status, count(*) as count')
+ ->join('ticket_claim_status tcs', 'tcs.id = ticket_master.claim_status_id', 'left')
+ ->where('ticket_master.is_active', 1)
+ ->where('ticket_master.ticket_type_id', 4)
+ // ->where('DATE(ticket_master.created_at)', $today)
+ ->where('tcs.is_active', 1)
+ ->groupBy('tcs.claim_status')
+ ->findAll();
+
+ // Sales / lead counts
+ $total_opportunity_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->countAllResults();
+
+ $total_rfq_created_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('status', 'rfq_created')
+ ->countAllResults();
+
+ $total_rfq_insurer_send_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('status', 'rfq_sent')
+ ->countAllResults();
+
+ $total_qcr_created_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('status', 'qcr_created')
+ ->countAllResults();
+
+ $total_qcr_client_send_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('status', 'qcr_sent')
+ ->countAllResults();
+
+ $total_placement_count = $this->leadsModel
+ ->where('is_active', 1)
+ ->where('DATE(created_at)', $today)
+ ->where('status', 'won')
+ ->countAllResults();
+
+ $total_activity_count = $this->salesActivityModel
+ ->where('DATE(created_at)', $today)
+ ->countAllResults();
+
+ $total_lead_count = $this->salesActualModel
+ ->where('DATE(created_at)', $today)
+ ->countAllResults();
+
+ $total_bds_count = $this->policyTransactionModel
+ ->select('pt_co_share_details.*')
+ ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id')
+ ->where('DATE(policy_transaction.created_at)', $today)
+ ->where('pt_co_share_details.is_active', 1)
+ ->where('policy_transaction.is_active', 1)
+ ->countAllResults();
+
+ $total_bds_policy_wise_count = $this->policyTransactionModel
+ ->select('pt_co_share_details.*')
+ ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id')
+ ->where('DATE(policy_transaction.created_at)', $today)
+ ->where('pt_co_share_details.is_active', 1)
+ ->where('policy_transaction.action_type', 'inception')
+ ->where('policy_transaction.is_active', 1)
+ ->countAllResults();
+
+
+ $total_bds_endorsement_wise_count = $this->policyTransactionModel
+ ->select('pt_co_share_details.*')
+ ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id')
+ ->where('DATE(policy_transaction.created_at)', $today)
+ ->where('pt_co_share_details.is_active', 1)
+ ->where('policy_transaction.action_type !=', 'inception')
+ ->where('policy_transaction.is_active', 1)
+ ->countAllResults();
+
+ $total_bds_policy_type_wise_count = $this->policyTransactionModel
+ ->select('policy_type.policy_type, count(*) as count')
+ ->join('pt_co_share_details', 'policy_transaction.id = pt_co_share_details.pt_id')
+ ->join('policy_type', 'policy_transaction.policy_type_id = policy_type.id')
+ ->where('DATE(policy_transaction.created_at)', $today)
+ ->where('pt_co_share_details.is_active', 1)
+ ->where('policy_transaction.is_active', 1)
+ ->groupBy('policy_transaction.policy_type_id')
+ ->findAll();
+
+ // Connect Pre DB for Employee Enrollment Count
+
+ $db2 = \Config\Database::connect('preDB');
+
+ $builder = $db2->table('employees');
+ $total_employee_draft_count = $builder->join('employee_polices', 'employees.id = employee_polices.employee_id')
+ ->select('employee_polices.status, count(*) as count')
+ ->where('DATE(employee_polices.created_at)', $today)
+ ->where('employee_polices.is_active', 1)
+ ->where('employees.is_active', 1)
+ ->whereIn('employee_polices.status', ['draft'])
+ ->groupBy('employee_polices.status')
+ ->countAllResults();
+
+
+ $builder1 = $db2->table('employees');
+ $total_employee_enrolled_count = $builder1->join('employee_polices', 'employees.id = employee_polices.employee_id')
+ ->select('employee_polices.status, count(*) as count')
+ ->where('DATE(employee_polices.created_at)', $today)
+ ->where('employee_polices.is_active', 1)
+ ->where('employees.is_active', 1)
+ ->whereIn('employee_polices.status', ['enrolled'])
+ ->groupBy('employee_polices.status')
+ ->countAllResults();
+
+ $builder2 = $db2->table('files');
+ $total_open_for_enrollemnt_policy_count = $builder2
+ ->select('COUNT(*) as count')
+ ->where('enrollment_open_date <=', $today)
+ ->where('enrollment_close_date >=', $today)
+ ->where('is_active', 1)
+ ->where('status', 'success')
+ ->countAllResults();
+
+ $data = [
+ 'total_inception_count' => $total_inception_count,
+ 'total_endorsement_count' => $total_endorsement_count,
+ 'total_tpa_incetion_count' => $total_tpa_incetion_count,
+ 'total_tpa_endorsement_count' => $total_tpa_endorsement_count,
+ 'total_insurer_incetion_count' => $total_insurer_incetion_count,
+ 'total_insurer_endorsement_count' => $total_insurer_endorsement_count,
+ 'total_claim_count' => $total_claim_count,
+ 'total_gmc_status_wise_claim_count' => $total_gmc_status_wise_claim_count,
+ 'total_gpa_status_wise_claim_count' => $total_gpa_status_wise_claim_count,
+ 'total_edli_status_wise_claim_count' => $total_edli_status_wise_claim_count,
+ 'total_gtli_status_wise_claim_count' => $total_gtli_status_wise_claim_count,
+ 'total_opportunity_count' => $total_opportunity_count,
+ 'total_rfq_created_count' => $total_rfq_created_count,
+ 'total_rfq_insurer_send_count' => $total_rfq_insurer_send_count,
+ 'total_qcr_created_count' => $total_qcr_created_count,
+ 'total_qcr_client_send_count' => $total_qcr_client_send_count,
+ 'total_placement_count' => $total_placement_count,
+ 'total_activity_count' => $total_activity_count,
+ 'total_lead_count' => $total_lead_count,
+ 'total_bds_count' => $total_bds_count,
+ 'total_bds_policy_wise_count' => $total_bds_policy_wise_count,
+ 'total_bds_endorsement_wise_count' => $total_bds_endorsement_wise_count,
+ 'total_bds_policy_type_wise_count' => $total_bds_policy_type_wise_count,
+ 'total_employee_draft_count' => $total_employee_draft_count,
+ 'total_employee_enrolled_count' => $total_employee_enrolled_count,
+ 'total_open_for_enrollemnt_policy_count' => $total_open_for_enrollemnt_policy_count,
+ ];
+
+ // dd($data);
+
+ $today = date('d-m-Y', strtotime($today));
+ $data['today'] = $today;
+
+ // Render the HTML email using the dedicated view
+ $message = view('daily_report_email_template', $data);
+ // return $message;
+
+ // Recipients: prefer dedicated env, fallback to BDS report emails if not set
+ $emailList = getenv('activity.dailyReportEmails') ?: getenv('bds.dailyReportEmails') ?: '';
+ $recipientEmails = array_filter(array_map('trim', explode(',', $emailList)));
+
+ if (empty($recipientEmails)) {
+ $this->myLogger->logme('error', 'croneDailyActivityReport: No recipients configured (set activity.dailyReportEmails in .env).');
+ return $this->respond([
+ 'status' => 'success',
+ 'message' => 'Daily activity data prepared but no recipients configured.',
+ 'data' => $data,
+ ], 200);
+ }
+
+ $subject = "Daily Activity Report - {$today}";
+
+ $res = MailHelper::send_email([
+ 'mail' => $recipientEmails,
+ 'subject' => $subject,
+ 'message' => $message,
+ ]);
+
+ $resDecoded = is_string($res) ? json_decode($res, true) : $res;
+
+ if (isset($resDecoded['status']) && $resDecoded['status'] === 'success') {
+ $this->myLogger->logme('error', 'croneDailyActivityReport: Report email sent to ' . count($recipientEmails) . ' recipients');
+ return $this->respond([
+ 'status' => true,
+ 'message' => 'Daily activity report emailed successfully.',
+ 'recipients' => count($recipientEmails),
+ ], 200);
+ }
+
+ $this->myLogger->logme('error', 'croneDailyActivityReport: Email send failed - ' . json_encode($resDecoded));
+ return $this->respond([
+ 'status' => false,
+ 'message' => 'Daily activity data prepared but email send failed.',
+ 'data' => $data,
+ ], 500);
+ }
+
}
diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php
index 3a0bea5b..df5dcfab 100755
--- a/app/Controllers/EmployeeRestController.php
+++ b/app/Controllers/EmployeeRestController.php
@@ -2418,6 +2418,11 @@ class EmployeeRestController extends AdminController
["ticket_type" => "4", "type_name" => "GTLI"],
];
+ $claim_type = $this->ticketController->claimType;
+ unset($claim_type[1][2]);
+ unset($claim_type[1][4]);
+ $data['claim_type'] = $claim_type;
+
return $this->respond(['status' => (count($data) ? 'success' : 'failed'), 'code' => (count($data) ? 200 : 404), 'data' => $data], 200);
}
@@ -2581,61 +2586,90 @@ class EmployeeRestController extends AdminController
$required_docs = $this->ticketMaster->select('required_docs')->where('id', $ticket_id)->first();
$data['required_docs'] = json_decode($required_docs['required_docs'] ?? '{}', true) ?? [];
- // $ticketData = $data['ticket_data'];
- // $ticketHistory = $data['ticket_history'];
- // print_r($ticketHistory); die;
+ // print_rr($data['ticket_history']); die;
+
+ $filteredArray = array_filter($data['ticket_history'], function($item) {
+ return $item['field_name'] == 'claim_status_id';
+ });
+
+ $filteredArray = array_reverse(array_values($filteredArray));
$currentClaimStatus = $this->claimStatusModel->select("claim_status")->where('id', $data['claims_data']['claim_status_id'])->where('is_active', 1)->first();
$ticketClaimStatus = $this->claimStatusModel->select('claim_status, display_name')->where('display_name is not null')->where('ticket_type', $data['claims_data']['ticket_type_id'])->where('is_active', 1)->findAll();
$status_list = array_column($ticketClaimStatus, 'display_name', 'claim_status');
- // print_r($currentClaimStatus); die;
+ // dd($filteredArray, $status_list); die;
- $data['ticket_data'] = array_fill_keys(array_keys($data['ticket_data']), []);
+ $filtered_history = [];
+ $counter = 0;
+
+ foreach ($filteredArray as $key => $value) {
+ foreach ($status_list as $status => $display_name) {
+ if ($value['new_value'] == $status) {
- // Loop through ticket_data
- foreach ($data['ticket_data'] as $status => &$fields) {
- // Search ticket_history for matching old_status_value
- foreach ($data['ticket_history'] as $history) {
+ if($display_name == 'Under Process'){
+ $unique_key = $display_name . str_repeat("\u{200B}", $counter++);
+ }else{
+ $unique_key = $display_name;
+ }
- if ($history['old_status_value'] === $status) {
- // Attach modified_by and created_at
- // $fields['modified_by'] = $history['modified_by'];
- $fields['modified_by'] = "";
- $fields['modified_at'] = date('d-m-Y h:i A', strtotime($history['created_at']));
- // Break after first match (assuming latest entry is enough)
- break;
+ $filtered_history[$unique_key] = [
+ 'modified_by' => "",
+ 'modified_at' => date('d-m-Y h:i A', strtotime($value['created_at'])),
+ ];
}
}
}
+ // dd($filteredArray, $status_list, $filtered_history); die;
- $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_by'] = "";
- $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_at'] = $formatted = date('d-m-Y h:i A', strtotime($data['claims_data']['updated_at']));
- $new_ticket_data = [];
- foreach ($data['ticket_data'] as $oldKey => $value) {
+ // $data['ticket_data'] = array_fill_keys(array_keys($data['ticket_data']), []);
- // Only process if key exists in status_list
- if (! isset($status_list[$oldKey])) {
- continue; // skip and do NOT add to new array
- }
+ // // Loop through ticket_data
+ // foreach ($data['ticket_data'] as $status => &$fields) {
+ // // Search ticket_history for matching old_status_value
+ // foreach ($data['ticket_history'] as $history) {
- // Get new key based on mapping
- $newKey = $status_list[$oldKey];
+ // if ($history['old_status_value'] === $status) {
+ // // Attach modified_by and created_at
+ // // $fields['modified_by'] = $history['modified_by'];
+ // $fields['modified_by'] = "";
+ // $fields['modified_at'] = date('d-m-Y h:i A', strtotime($history['created_at']));
+ // // Break after first match (assuming latest entry is enough)
+ // break;
+ // }
+ // }
+ // }
- // Avoid duplicates
- if (! isset($new_ticket_data[$newKey])) {
- $new_ticket_data[$newKey] = $value;
- }
- }
+ // $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_by'] = "";
+ // $data['ticket_data'][$currentClaimStatus['claim_status']]['modified_at'] = $formatted = date('d-m-Y h:i A', strtotime($data['claims_data']['updated_at']));
- uasort($new_ticket_data, function ($a, $b) {
- $timeA = \DateTime::createFromFormat('d-m-Y h:i A', $a['modified_at']);
- $timeB = \DateTime::createFromFormat('d-m-Y h:i A', $b['modified_at']);
- return $timeA <=> $timeB; // Ascending
- });
+ // $new_ticket_data = [];
- $data['ticket_data'] = $new_ticket_data;
+ // foreach ($data['ticket_data'] as $oldKey => $value) {
+
+ // // Only process if key exists in status_list
+ // if (! isset($status_list[$oldKey])) {
+ // continue; // skip and do NOT add to new array
+ // }
+
+ // // Get new key based on mapping
+ // $newKey = $status_list[$oldKey];
+
+ // // Avoid duplicates
+ // if (! isset($new_ticket_data[$newKey])) {
+ // $new_ticket_data[$newKey] = $value;
+ // }
+ // }
+
+ // uasort($new_ticket_data, function ($a, $b) {
+ // $timeA = \DateTime::createFromFormat('d-m-Y h:i A', $a['modified_at']);
+ // $timeB = \DateTime::createFromFormat('d-m-Y h:i A', $b['modified_at']);
+ // return $timeA <=> $timeB; // Ascending
+ // });
+
+ // $data['ticket_data'] = $new_ticket_data;
+ $data['ticket_data'] = $filtered_history;
$ticketMesssageModel = new TicketMessageModel();
$ticket_message = $ticketMesssageModel
@@ -5451,7 +5485,7 @@ class EmployeeRestController extends AdminController
'dashboard' => $database_id,
],
'exp' => time() + (10 * 60),
- 'params' => (object) [],
+ 'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase
];
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
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/FhplApiController.php b/app/Controllers/FhplApiController.php
index 734bb438..8c3e76e1 100644
--- a/app/Controllers/FhplApiController.php
+++ b/app/Controllers/FhplApiController.php
@@ -99,7 +99,7 @@ class FhplApiController extends BaseController
if (count($data) && $data['filePath'] == null) {
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - Claim or File Missing");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | Claim or File Missing'];
}
// Build absolute file path
@@ -108,7 +108,7 @@ class FhplApiController extends BaseController
if (!file_exists($pdfPath)) {
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - PDF not found on server");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
}
// Convert PDF to Base64
@@ -118,7 +118,7 @@ class FhplApiController extends BaseController
$tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
if (empty($tokenResponse['data']['access_token'])) {
log_message('error', "FHPL - Claim Push FAILED | claimId: '.$claimId.' - FHPL Token generation failed");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | FHPL Token generation failed'];
}
$token = $tokenResponse['data']['access_token'];
@@ -163,7 +163,7 @@ class FhplApiController extends BaseController
$this->db->table('ticket_master')
->where('id',$claimId)
->update([ 'tpa_push_response' => json_encode($response) ]);
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | API call failed'];
}
@@ -185,14 +185,14 @@ class FhplApiController extends BaseController
]);
log_message('error', 'FHPL - Claim Push SUCCESS | claimId: '.$claimId.' | claimNO: '.$fhplClaimNo);
-
+ return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
}else {
log_message('error', 'FHPL - Claim Push API SUCCESS BUT claimsInfo EMPTY | response: '.json_encode($response));
- return;
+ return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT claimsInfo EMPTY'];
}
}
- return;
+ return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT claimsInfo EMPTY'];
// return $this->response->setJSON($response);
}
@@ -288,7 +288,10 @@ class FhplApiController extends BaseController
$tickets = $this->db->table('ticket_master tm')
->select("tm.id,tm.tpa_claim_id,cp.policy_no")
->join('client_policy cp','tm.client_policy_id=cp.id')
- ->where('tm.tpa_claim_id IS NOT NULL')
+ ->where('tm.tpa_claim_push_reference_no IS NOT NULL')
+ ->whereNotIn('tm.claim_status_id', [8, 11, 12, 13, 66, 22, 24, 47, 49, 32, 34, 53, 55, 42, 44, 58, 60])
+ ->where('tm.is_active', 1)
+ ->where('cp.tpa_id', $this->fhplTpaId)
->get()->getResultArray();
$count=0;
@@ -714,7 +717,7 @@ class FhplApiController extends BaseController
// ];
// }
- public function syncFhplClaimsToNhance()
+ public function syncFhplClaimsToNhanceOld()
{
helper('api');
@@ -790,6 +793,201 @@ class FhplApiController extends BaseController
return ['status'=>true,'total'=>count($finalResult)];
}
+ public function syncFhplClaimsToNhance()
+ {
+ helper('api');
+
+ try {
+
+ // Generate FHPL Token
+ $tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
+
+ if (empty($tokenResponse['data']['access_token'])) {
+ return $this->response->setJSON(['status' => false,'message' => 'FHPL Token generation failed']);
+ }
+
+ $token = $tokenResponse['data']['access_token'];
+
+ $url = getenv('FHPL_BASE_URL')."/api/GetTPA_ClaimsDetails";
+
+ $headers = [
+ "Authorization: Bearer ".$token,
+ "Content-Type: application/json"
+ ];
+
+ $policies = $this->db->table('client_policy')
+ ->where('tpa_id',$this->fhplTpaId)
+ ->get()->getResultArray();
+
+ $finalResult=[];
+ foreach($policies as $policy){
+
+ $body = [
+ "UserName" => getenv('FHPL_USER_NAME'),
+ "Password" => getenv('FHPL_PASSWORD'),
+ "PolicyNumber" => $policy['policy_no'],
+ "Fromdate" => $policy['policy_start_date'],
+ "Todate" => $policy['policy_end_date']
+ ];
+
+ $response = call_third_party_api($url,'POST',$headers,$body);
+
+ if(!empty($response['data'])){
+ log_message('error', 'FHPL - Sync TPA Claims | Exception thrown while calling GetTPA_ClaimsDetails API: ' . json_encode($response));
+ $finalResult = array_merge($finalResult,$response['data']);
+ }
+ }
+
+ $insertedCount = 0;
+
+ // Insert into ticket_master with mandatory columns (reference: MediAssist syncTpaClaimToNhance)
+ foreach($finalResult as $row){
+
+ $status = $row['CLAIM_STATUS'] ?? null;
+
+ $map = [
+ "Under Process"=>5,
+ "Paid"=>11,
+ "Rejected"=>8,
+ "Approved"=>8
+ ];
+
+ $claimStatus = $map[$status] ?? 61;
+
+ // Derive relationship (default to self)
+ $relationship = map_relationship(trim($row['RELATION'] ?? 'self'));
+
+ // Fetch client policy details
+ $clientpolicy = $this->db->table('client_policy cp')
+ ->select("
+ cp.id as client_policy_id,
+ cp.client_id ,
+ cp.insurer_id ,
+ cp.tpa_id ,
+ client_rm.id as acm_id
+ ")
+ ->join('client_rm', 'client_rm.client_id = cp.client_id AND client_rm.level = 3', 'left')
+ ->where('cp.policy_no', $row['POLICY_NO'] ?? null)
+ ->orderBy('client_rm.id','DESC')
+ ->get()
+ ->getRowArray();
+
+ if (!$clientpolicy) {
+ log_message(
+ 'error',
+ 'FHPL - Sync TPA Claims | Client policy not found for policy_no: ' . ($row['POLICY_NO'] ?? 'N/A')
+ );
+ continue;
+ }
+
+ // Fetch employee / insured details
+ $employee = $this->db->table('employees e')
+ ->select("
+ e.id as emp_id,
+ e.emp_code ,
+ e.name as emp_name,
+ e2.id as insured_emp_id,
+ e2.name as insured_emp_name,
+ ep.tpa_id as tpa_no
+ ")
+ ->join(
+ 'employees e2',
+ "e2.emp_code = e.emp_code AND e2.relationship = ".$this->db->escape($relationship),
+ 'left'
+ )
+ ->join( 'employee_polices ep', "ep.employee_id = e2.id ", 'left' )
+ ->where('e.emp_code', $row['EMPLOYEE_NO'] ?? null)
+ ->where('e.client_id', $clientpolicy['client_id'] ?? null)
+ ->where('e.relationship', 'self')
+ ->where('e.is_active', 1)
+ ->where('e2.is_active', 1)
+ ->where('ep.is_active', 1)
+ ->get()
+ ->getRowArray();
+
+ if (!$employee) {
+ log_message(
+ 'error',
+ 'FHPL - Sync TPA Claims | Employee data not found for emp_code: ' . ($row['EMPLOYEE_NO'] ?? 'N/A') .
+ ' | policy_no: ' . ($row['POLICY_NO'] ?? 'N/A') .
+ ' | relationship: ' . $relationship
+ );
+ continue;
+ }
+
+ $claimData = [
+ // Core
+ 'ticket_type_id' => 1,
+ 'claim_status_id' => $claimStatus,
+ 'policy_no' => $row['POLICY_NO'] ?? null,
+ 'claim_number' => $row['CLAIM_ID'] ?? null,
+ 'tpa_claim_id' => $row['CLAIM_ID'] ?? null,
+
+ // local primary ids
+ 'tpa_id' => $clientpolicy['tpa_id'] ?? $this->fhplTpaId,
+ 'insurer_id' => $clientpolicy['insurer_id'] ?? null,
+ 'client_policy_id' => $clientpolicy['client_policy_id'] ?? null,
+ 'client_id' => $clientpolicy['client_id'] ?? null,
+ 'acm_id' => $clientpolicy['acm_id'] ?? null,
+
+ // Employee / Insured
+ 'emp_id' => $employee['emp_id'] ?? null,
+ 'insured_emp_id' => $employee['insured_emp_id'] ?? null,
+ 'tpa_no' => $employee['tpa_no'] ?? null,
+ 'emp_code' => $row['EMPLOYEE_NO'] ?? null,
+ 'emp_name' => $employee['emp_name'] ?? null,
+ 'insured_name' => $row['insured_emp_name'] ?? null,
+ 'relationship' => $relationship,
+
+ // Claim info
+ 'claim_type' => 1,
+ 'mode_of_intimation' => 5,
+ 'claim_amount' => $row['CLAIM_AMOUNT'] ?? null,
+
+ // Dates
+ 'doa' => change_date_format($row['DATE_OF_ADMISSION'] ?? '', null, 'Y-m-d') ?? null,
+ 'dod' => change_date_format($row['DATE_OF_DISCHARGE'] ?? '', null, 'Y-m-d') ?? null,
+
+ // Hospital
+ 'hospital_name' => $row['HOSPITAL_NAME'] ?? null,
+ 'hospital_state' => $row['HOSPITAL_STATE'] ?? null,
+ 'hospital_city' => $row['HOSPITAL_CITY'] ?? null,
+ 'hospital_address' => $row['Hospital Address'] ?? null,
+ 'hospital_pincode' => $row['Hospital Pincode'] ?? null,
+
+ 'registration_date' => $row['CLAIM_REGISTERED_DATE'] ?? null,
+
+ // Others
+ 'tpa_claim_type' => $row['CLAIM_TYPE'] ?? null,
+ 'tpa_ailments' => $row['AILMENT'] ?? null,
+
+ 'created_at' => date('Y-m-d H:i:s'),
+ ];
+
+ $this->db->table('ticket_master')->insert($claimData);
+
+ $insertedCount++;
+ }
+
+ log_message('error', 'FHPL - Sync TPA Claims | Fetched Data | Inserted Data: ' . count($finalResult) . ' | Inserted: ' . $insertedCount);
+ return ['status'=>true,'total'=>count($finalResult), 'inserted'=>$insertedCount];
+
+ } catch (\Throwable $th) {
+ $errorData = [
+ 'message' => $th->getMessage(),
+ 'file' => $th->getFile(),
+ 'line' => $th->getLine(),
+ 'code' => $th->getCode(),
+ 'trace' => $th->getTraceAsString(),
+ 'trace_array' => $th->getTrace(), // full array version (optional)
+ 'function' => $th->getTrace()[0]['function'] ?? null,
+ 'class' => $th->getTrace()[0]['class'] ?? null,
+ ];
+ log_message('error', 'FHPL - Sync TPA Claims | Exception thrown: ' . json_encode($errorData));
+ return ['status'=>false,'message'=>$th->getMessage()];
+ }
+ }
+
public function saveFhplAPIData($array)
{
$file_id = $array['file_id'];
diff --git a/app/Controllers/HealthIndiaApiController.php b/app/Controllers/HealthIndiaApiController.php
index 13bc10c3..66e6d543 100644
--- a/app/Controllers/HealthIndiaApiController.php
+++ b/app/Controllers/HealthIndiaApiController.php
@@ -123,12 +123,12 @@ class HealthIndiaApiController extends BaseController
if (!$data) {
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Claim not found");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | Claim not found'];
}
if ($data['filePath'] == null) {
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - File Missing");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | File Missing'];
}
// Build absolute file path
@@ -137,7 +137,7 @@ class HealthIndiaApiController extends BaseController
if (!file_exists($pdfPath)) {
log_message('error', "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}");
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server'];
}
// Convert PDF to Base64
@@ -217,7 +217,7 @@ class HealthIndiaApiController extends BaseController
$this->db->table('ticket_master')
->where('id', $claimId)
->update(['tpa_push_response' => json_encode($response)]);
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | API call failed'];
}
if ($response['status'] === true && !empty($response['data']['result'][0]['ccn'])) {
@@ -234,12 +234,13 @@ class HealthIndiaApiController extends BaseController
]);
log_message('error', 'HEALTH_INDIA - Claim Push SUCCESS | claimId: ' . $claimId . ' | CCN: ' . $ccn . ' | CCN_EXT: ' . $ccnExt);
+ return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
} else {
log_message('error', 'HEALTH_INDIA - Claim Push API SUCCESS BUT CCN EMPTY | claimId: ' . $claimId . ' | response: ' . json_encode($response));
- return;
+ return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT CCN EMPTY'];
}
- return;
+ return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT CCN EMPTY'];
}
public function ClaimDetail($claimId = null)
@@ -355,7 +356,9 @@ class HealthIndiaApiController extends BaseController
$tickets = $this->db->table('ticket_master tm')
->select("tm.id, tm.tpa_claim_id, cp.policy_no")
->join('client_policy cp', 'tm.client_policy_id=cp.id')
- ->where('tm.tpa_claim_id IS NOT NULL')
+ ->where('tm.tpa_claim_push_reference_no IS NOT NULL')
+ ->whereNotIn('tm.claim_status_id', [8, 11, 12, 13, 66, 22, 24, 47, 49, 32, 34, 53, 55, 42, 44, 58, 60])
+ ->where('tm.is_active', 1)
->where('cp.tpa_id', $this->healthIndiaTpaId)
->get()->getResultArray();
@@ -722,7 +725,7 @@ class HealthIndiaApiController extends BaseController
}
}
- public function syncHealthIndiaClaimsToNhance()
+ public function syncHealthIndiaClaimsToNhanceOld()
{
helper('api');
@@ -820,6 +823,217 @@ class HealthIndiaApiController extends BaseController
]);
}
+ public function syncHealthIndiaClaimsToNhance()
+ {
+ helper('api');
+
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Started');
+
+ // Generate Health India Token
+ $tokenResponse = json_decode($this->generateAuthToken()->getBody(), true);
+
+ if (empty($tokenResponse['data']['result'][0]['access_token'])) {
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims FAILED | Token generation failed');
+ return $this->response->setJSON(['status' => false, 'message' => 'Token generation failed']);
+ }
+
+ $token = $tokenResponse['data']['result'][0]['access_token'];
+
+ $url = getenv('HEALTH_INDIA_BASE_URL') . "/ClaimsMIS/GetClaimsMIS";
+
+ $headers = [
+ "Authorization: Bearer " . $token,
+ "Content-Type: application/json"
+ ];
+
+ $policies = $this->db->table('client_policy')
+ ->where('tpa_id', $this->healthIndiaTpaId)
+ ->get()->getResultArray();
+
+ $finalResult = [];
+
+ foreach ($policies as $policy) {
+ $body = [
+ "policY_NUMBER" => $policy['policy_no']
+ ];
+
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Fetching for policy: ' . $policy['policy_no']);
+
+ $response = call_third_party_api($url, 'POST', $headers, $body);
+
+ if (!empty($response['data']['result'])) {
+ $finalResult = array_merge($finalResult, $response['data']['result']);
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Fetched ' . count($response['data']['result']) . ' claims for policy: ' . $policy['policy_no']);
+ }
+ }
+
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Total claims fetched: ' . count($finalResult));
+
+ $insertedCount = 0;
+
+ foreach ($finalResult as $row) {
+ $status = $row['Claim_Status'] ?? 'Under Process';
+
+ $map = [
+ "Under Process" => 5,
+ "Pending for Bill Entry" => 5,
+ "Paid" => 11,
+ "Rejected" => 8,
+ "Approved" => 8,
+ "Outstanding" => 5,
+ ];
+
+ $claimStatus = $map[$status] ?? 1;
+
+ // Check if claim already exists
+ $existing = $this->db->table('ticket_master')
+ ->where('tpa_claim_id', $row['CLAIM_NUMBER'])
+ ->get()->getRowArray();
+
+ if ($existing) {
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Skipped existing claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A'));
+ continue;
+ }
+
+ // Derive relationship (default to self)
+ $relationship = 'self';
+ $rawRelation = $row['RELATION_NAME'] ?? null;
+ if (!empty($rawRelation)) {
+ if ($rawRelation === 'Employee') {
+ $relationship = 'self';
+ } elseif (strtoupper($rawRelation) === 'WIFE') {
+ $relationship = 'spouse';
+ } else {
+ $relationship = strtolower($rawRelation);
+ }
+ }
+
+ // Fetch client policy details
+ $clientpolicy = $this->db->table('client_policy cp')
+ ->select("
+ cp.id as client_policy_id,
+ cp.client_id,
+ cp.insurer_id,
+ cp.tpa_id,
+ client_rm.id as acm_id
+ ")
+ ->join('client_rm', 'client_rm.client_id = cp.client_id AND client_rm.level = 3', 'left')
+ ->where('cp.policy_no', $row['Policy_No'] ?? null)
+ ->orderBy('client_rm.id', 'DESC')
+ ->get()
+ ->getRowArray();
+
+ if (!$clientpolicy) {
+ log_message(
+ 'error',
+ 'HEALTH_INDIA - Sync TPA Claims | Client policy not found for policy_no: ' . ($row['Policy_No'] ?? 'N/A')
+ );
+ continue;
+ }
+
+ // Fetch employee / insured details
+ $employee = $this->db->table('employees e')
+ ->select("
+ e.id as emp_id,
+ e.emp_code,
+ e.name as emp_name,
+ e2.id as insured_emp_id,
+ e2.name as insured_emp_name,
+ ep.tpa_id as tpa_no,
+ e.mobile as emp_mobile,
+ e.email_corporate as emp_mail
+ ")
+ ->join(
+ 'employees e2',
+ "e2.emp_code = e.emp_code AND e2.relationship = " . $this->db->escape($relationship),
+ 'left'
+ )
+ ->join('employee_polices ep', "ep.employee_id = e2.id ", 'left')
+ ->where('e.emp_code', $row['Employee_Code'] ?? null)
+ ->where('e.client_id', $clientpolicy['client_id'] ?? null)
+ ->where('e.relationship', 'self')
+ ->where('e.is_active', 1)
+ ->where('e2.is_active', 1)
+ ->where('ep.is_active', 1)
+ ->get()
+ ->getRowArray();
+
+ if (!$employee) {
+ log_message(
+ 'error',
+ 'HEALTH_INDIA - Sync TPA Claims | Employee data not found for emp_code: ' . ($row['Employee_Code'] ?? 'N/A') .
+ ' | policy_no: ' . ($row['Policy_No'] ?? 'N/A') .
+ ' | relationship: ' . $relationship
+ );
+ continue;
+ }
+
+ $claimData = [
+ // Core
+ 'ticket_type_id' => 1,
+ 'claim_status_id' => $claimStatus,
+ 'policy_no' => $row['Policy_No'] ?? null,
+ 'claim_number' => $row['CLAIM_NUMBER'] ?? null,
+ 'tpa_claim_id' => $row['CLAIM_NUMBER'] ?? null,
+
+ // Local primary/foreign keys
+ 'tpa_id' => $clientpolicy['tpa_id'] ?? $this->healthIndiaTpaId,
+ 'insurer_id' => $clientpolicy['insurer_id'] ?? null,
+ 'client_policy_id' => $clientpolicy['client_policy_id'] ?? null,
+ 'client_id' => $clientpolicy['client_id'] ?? null,
+ 'acm_id' => $clientpolicy['acm_id'] ?? null,
+
+ // Employee / Insured
+ 'emp_id' => $employee['emp_id'] ?? null,
+ 'insured_emp_id' => $employee['insured_emp_id'] ?? null,
+ 'tpa_no' => $employee['tpa_no'] ?? null,
+ 'emp_code' => $row['Employee_Code'] ?? null,
+ 'emp_name' => $employee['emp_name'] ?? null,
+ 'insured_name' => $employee['insured_emp_name'] ?? ($row['PATIENT_NAME'] ?? null),
+ 'relationship' => $relationship,
+ 'emp_mobile' => $employee['emp_mobile'] ?? null,
+ 'emp_mail' => $employee['emp_mail'] ?? null,
+
+ // Claim info
+ 'claim_type' => 1,
+ 'mode_of_intimation' => 5,
+ 'claim_amount' => $row['INTIMATED_AMOUNT'] ?? 0,
+
+ // Dates
+ 'doa' => !empty($row['DATEOF_ADMISSION']) ? date('Y-m-d', strtotime($row['DATEOF_ADMISSION'])) : null,
+ 'dod' => !empty($row['DATEOF_DISCHARGE']) ? date('Y-m-d', strtotime($row['DATEOF_DISCHARGE'])) : null,
+
+ // Hospital
+ 'hospital_name' => $row['HOSPITAL_NAME'] ?? null,
+ 'hospital_address' => $row['Hospital_address'] ?? null,
+ 'hospital_pincode' => $row['HOSPITAL_Pincode'] ?? null,
+ 'hospital_state' => $row['HOSPITAL_STATE'] ?? null,
+ 'hospital_city' => $row['HOSPITAL_CITY'] ?? null,
+
+ // TPA extras
+ 'tpa_claim_status' => $status,
+
+ 'created_at' => date('Y-m-d H:i:s'),
+ ];
+
+ $this->db->table('ticket_master')->insert($claimData);
+ $insertedCount++;
+
+ log_message(
+ 'error',
+ 'HEALTH_INDIA - Sync TPA Claims | Inserted claim: ' . ($row['CLAIM_NUMBER'] ?? 'N/A')
+ );
+ }
+
+ log_message('error', 'HEALTH_INDIA - Sync TPA Claims | Completed | Total: ' . count($finalResult) . ' | Inserted: ' . $insertedCount);
+
+ return $this->response->setJSON([
+ 'status' => true,
+ 'total' => count($finalResult),
+ 'inserted' => $insertedCount
+ ]);
+ }
+
public function saveHealthIndiaAPIData($array)
{
$file_id = $array['file_id'];
diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php
index af699248..41739b6a 100644
--- a/app/Controllers/LeadsController.php
+++ b/app/Controllers/LeadsController.php
@@ -1,7 +1,44 @@
occupancyModel = new OccupancyMasterModel();
$this->leadFilesModel = new LeadFilesModel();
$this->leadInstallmentPaymentDetails = new LeadInstallmentPaymentDetails();
- $this->gmailSentHistoryModel = new GmailSentHistoryModel();
+ $this->gmailSentHistoryModel = new GmailSentHistoryModel();
+ $this->leadModel = new SalesActualLeadModel();
+ $this->contactModel = new SalesContactPersonModel();
+
$this->issuer = [1 => 'JIBS', 2 => 'Nhance'];
$this->clientType = [1 => 'Group', 2 => 'Individual'];
@@ -616,7 +658,6 @@ class LeadsController extends BaseController
$request_data = $this->request->getPost();
$data = sanitizeInputArrayAdvanced($request_data);
-
$data['client_type'] = 1;
$data['pan'] = "";
@@ -812,28 +853,28 @@ class LeadsController extends BaseController
$last_3_years_claims = $data['finyear'];
$processedData[] = [
- 'actual_lead_id' => $data['actual_lead_id'] ?? null,
- 'lead_type' => $data['lead_type'],
- 'issuer' => $data['issuer'],
- 'client_type' => $data['client_type'],
- 'client_name' => $data['client_name'],
- 'client_short_name' => $data['client_short_name'],
- 'entity_type_id' => $data['entity_type_id'],
- 'client_code' => $data['client_code'],
- 'pan' => $data['pan'],
- 'gst' => $data['gst'],
- 'branch_name' => $data['branch_name'],
- 'branch_code' => $data['branch_code'],
- 'contact_person_name' => $data['contact_person_name'],
- 'contact_person_mobile' => $data['contact_person_mobile'],
- 'contact_person_email' => $data['contact_person_email'],
- 'client_id' => $data['client_id'] ?? 0,
- 'client_branch_id' => $data['client_branch_id'] ?? 0,
- 'is_client_created' => $data['is_client_created'] ?? null,
- 'source_policy_id' => $data['source_policy_id'] ?? 0,
- 'policy_type_id' => $value,
- 'salse_person_id' => $data['salse_person_id'] ?? 0,
-
+ 'lost_reason' => $data['lost_reason'] ?? null,
+ 'actual_lead_id' => $data['actual_lead_id'] ?? null,
+ 'lead_type' => $data['lead_type'],
+ 'issuer' => $data['issuer'],
+ 'client_type' => $data['client_type'],
+ 'client_name' => $data['client_name'],
+ 'client_short_name' => $data['client_short_name'],
+ 'entity_type_id' => $data['entity_type_id'],
+ 'client_code' => $data['client_code'],
+ 'pan' => $data['pan'],
+ 'gst' => $data['gst'],
+ 'branch_name' => $data['branch_name'],
+ 'branch_code' => $data['branch_code'],
+ 'contact_person_name' => $data['contact_person_name'],
+ 'contact_person_mobile' => $data['contact_person_mobile'],
+ 'contact_person_email' => $data['contact_person_email'],
+ 'client_id' => $data['client_id'] ?? 0,
+ 'client_branch_id' => $data['client_branch_id'] ?? 0,
+ 'is_client_created' => $data['is_client_created'] ?? null,
+ 'source_policy_id' => $data['source_policy_id'] ?? 0,
+ 'policy_type_id' => $value,
+ 'salse_person_id' => $data['salse_person_id'] ?? 0,
'insurer_id' => $insurer_id ?? 0,
'insurer_branch_id' => $insurer_branch_id ?? 0,
'tpa_id' => $tpa_id ?? 0,
@@ -4607,6 +4648,28 @@ class LeadsController extends BaseController
'actual_lead_id' => $actual_lead_id,
];
+ if ($actual_lead_id > 0) {
+
+ // 🔹 Client Details (Single Row)
+ $data['actual_lead_client_details'] = $this->leadModel
+ ->select('company_name, email, phone, address, website, gst_number, status, assigned_to')
+ ->where('lead_id', $actual_lead_id)
+ ->first(); // first row only
+
+
+ // 🔹 Contact Person Details (First Row Only)
+ $data['actual_lead_contact_person_details'] = $this->contactModel
+ ->select('contact_id, name, mobile, designation, email, is_primary')
+ ->where('lead_id', $actual_lead_id)
+ ->where('is_primary',1)
+ ->orderBy('is_primary', 'DESC') // optional (primary first)
+ ->first(); // only first row
+ }
+ else {
+ $data['actual_lead_client_details'] = null;
+ $data['actual_lead_contact_person_details'] = null;
+ }
+
// Fetch sales team members who are active in team 5
$data['salse_team'] = $this->userModel
->select('user_profiles.*')
diff --git a/app/Controllers/MediAssistApiController.php b/app/Controllers/MediAssistApiController.php
index a59c2637..05770e6f 100644
--- a/app/Controllers/MediAssistApiController.php
+++ b/app/Controllers/MediAssistApiController.php
@@ -141,7 +141,7 @@ class MediAssistApiController extends BaseController
$this->db->table('ticket_master')
->where('id',$claimId)
->update([ 'tpa_push_response' => json_encode($response) ]);
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED', 'response' => $response];
}
// return $this->response->setJSON($response);
@@ -156,11 +156,11 @@ class MediAssistApiController extends BaseController
->where('id',$claimId)
->update([ 'tpa_claim_push_reference_no' => $claimRef ]);
- return;
+ return ['status' => true, 'message' => 'Claim Push SUCCESS', 'response' => $response];
} else {
log_message('error','MEDI_ASSIST - Claim Push API Failed | claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
- return;
+ return ['status' => false, 'message' => 'Claim Push API Failed', 'response' => $response];
}
}
diff --git a/app/Controllers/PolicyTransactionController.php b/app/Controllers/PolicyTransactionController.php
index 59c115fe..c762866e 100644
--- a/app/Controllers/PolicyTransactionController.php
+++ b/app/Controllers/PolicyTransactionController.php
@@ -317,8 +317,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'Q',
'col_name' => 'Base Premium',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -329,8 +329,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'R',
'col_name' => 'Non commission permium Amount',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -341,8 +341,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'S',
'col_name' => 'TP Premium',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -353,8 +353,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'T',
'col_name' => 'IGST',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => 'check_gst_percentage',
'params' => ['row']
@@ -365,8 +365,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'U',
'col_name' => 'CGST',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => 'check_gst_percentage',
'params' => ['row']
@@ -377,8 +377,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'V',
'col_name' => 'SGST',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => 'check_gst_percentage',
'params' => ['row']
@@ -390,8 +390,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'W',
'col_name' => 'Stamp Duty',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -414,8 +414,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'X',
'col_name' => 'Agreed Amount',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -426,8 +426,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'Y',
'col_name' => 'Agreed BP Percentage',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -438,8 +438,8 @@ class PolicyTransactionController extends BaseController
'col_cell_name' => 'Z',
'col_name' => 'Agreed TP Percentage',
'is_mandatory' => false,
- 'data_type' => '',
- 'format' => null,
+ 'data_type' => 'positive_number',
+ 'format' => 'positive_number',
'allowed_values' => null,
'custom' => null,
'params' => null
@@ -6098,8 +6098,189 @@ class PolicyTransactionController extends BaseController
return array('status' => false, 'message' => 'file_id is required');
}
- $this->policyTransactionModel->select()->findAll();
+ $data['is_active'] = 0;
+ $this->policyTransactionModel->where('file_id', $file_id)->set($data)->update();
+ $this->PTCOShareDetailsModel->where('file_id', $file_id)->set($data)->update();
+ return $this->respond(['status' => true, 'code' => 200, 'message' => 'BDS bulk upload data truncated successfully'], 200);
+ }
+
+ // ------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Cron job: Daily BDS Report
+ * Fetches today's BDS entries, generates Excel (matching report_bds.php column order), and emails to recipients from .env.
+ * Recipients: comma-separated emails in bds.reportEmails
+ * File stored temporarily in writable/tmp/ and deleted after sending.
+ */
+ public function cronDailyBDSReport()
+ {
+
+ helper('excel_import_export_helper');
+
+ $filePath = null;
+ try {
+
+ $today = date('Y-m-d', strtotime('-1 day'));
+ $reportList = $this->policyTransactionModel->getBDSReportList($today, $today,0,0,0,'created_at',0,0,0,0,0,[]);
+
+ if (empty($reportList)) {
+ $this->myLogger->logme('error', "cronDailyBDSReport: No BDS records for {$today}");
+ return $this->respond(['status' => 'success', 'message' => 'No BDS records for today. No report sent.'], 200);
+ }
+
+ // Column headers exactly matching report_bds.php order (including display:none columns)
+ $headers = [
+ 'S. No', 'User', 'Month', 'Business Type', 'Client Type', 'Insured Name', 'Transaction Type',
+ 'Policy Type', 'BAP Group', 'Vehicle Number', 'Policy No', 'Endorsement No', 'Insurer Branch',
+ 'Endorsement Effective Date', 'Policy Effective Date', 'Policy Expiry Date', 'Reference', 'Remarks',
+ 'BP Premium', 'TP Premium', 'Premium (without GST)', 'Total Premium', 'Agreed BP %', 'Agreed TP %',
+ 'Rewards', 'Agreed Amount', 'Invoiced Amount', 'Outstanding Amount',
+ 'Salse Person', 'Service Person', 'Salse Person Branch', 'Installment', 'Data Received Date', 'Renewal Date',
+ 'Co-Premium', 'Remuneration Pay By Leader', 'Salse Person Manager', 'Service Person Manager',
+ 'Service Person Branch', 'Rollover Date', 'Policyholder Name', 'Insured (Same as Proposer)',
+ 'Follower Policy No', 'Co-Share %', 'Non Commissional Premium Amount', 'CGST', 'SGST', 'IGST',
+ 'Stamp Duty', 'Standard BP %', 'Standard TP %', 'Actual BP Amount', 'Actual TP Amount',
+ 'Actual BP %', 'Actual TP %', 'Actual BP Remuneration Amount', 'Actual TP Remuneration Amount',
+ 'CD Account No'
+ ];
+
+ $excelData = [];
+ foreach ($reportList as $idx => $row) {
+ $totalIrda = (float)($row['total_irda_amt'] ?? 0);
+ $billedAmt = (float)($row['billed_amt'] ?? 0);
+ $unbilledAmt = $totalIrda - $billedAmt;
+ if ($totalIrda == 0) {
+ $unbilledAmt = abs($unbilledAmt);
+ }
+ $unbilledAmt = ($unbilledAmt == 0 && $billedAmt == 0) ? $totalIrda : $unbilledAmt;
+
+ $hasIrda = ($totalIrda != 0);
+
+ $excelData[] = [
+ $idx + 1,
+ $row['user_name'] ?? 'N/A',
+ $row['policy_issue_month'] ?? 'N/A',
+ $row['revenue_type'] ?? 'N/A',
+ $row['client_type'] ?? 'N/A',
+ $row['client_name'] ?? 'N/A',
+ $row['action_type'] ?? 'N/A',
+ $row['policy_type'] ?? 'N/A',
+ $row['bap'] ?? 'N/A',
+ $row['vehicle_no'] ?? 'N/A',
+ $row['policy_no'] ?? 'N/A',
+ $row['endorsement_no'] ?? 'N/A',
+ $row['insurer_branch_name'] ?? 'N/A',
+ !empty($row['endorse_eff_date']) ? change_date_format($row['endorse_eff_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ !empty($row['policy_start_date']) ? change_date_format($row['policy_start_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ !empty($row['policy_end_date']) ? change_date_format($row['policy_end_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ $row['ref'] ?? 'N/A',
+ $row['remarks'] ?? 'N/A',
+ $hasIrda ? ($row['bp_amt'] ?? '0.00') : '0.00',
+ $hasIrda ? ($row['tp_or_ter'] ?? '0.00') : '0.00',
+ $hasIrda ? ($row['premium_wo_gst'] ?? '0.00') : '0.00',
+ $hasIrda ? ($row['total_premium'] ?? '0.00') : '0.00',
+ $hasIrda ? ($row['agreed_bp_per'] ?? '0.00') . '%' : '0.00%',
+ $hasIrda ? ($row['agreed_tp_or_ter_per'] ?? '0.00') . '%' : '0.00%',
+ $row['reward'] ?? '0.00',
+ $row['total_irda_amt'] ?? '0.00',
+ !empty($row['billed_amt']) ? $row['billed_amt'] : '0.00',
+ number_format((float)$unbilledAmt, 2, '.', ''),
+ $row['salse_person_name'] ?? 'N/A',
+ $row['service_person_name'] ?? 'N/A',
+ $row['nhance_branch'] ?? 'N/A',
+ $row['installment'] ?? 'N/A',
+ !empty($row['data_received_date']) ? change_date_format($row['data_received_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ !empty($row['renewal_date']) ? change_date_format($row['renewal_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ $row['co_share'] ?? 'No',
+ $row['bro_payable_by'] ?? 'No',
+ $row['salse_manager_name'] ?? 'N/A',
+ $row['service_manager_name'] ?? 'N/A',
+ $row['service_branch'] ?? 'N/A',
+ !empty($row['rollover_date']) ? change_date_format($row['rollover_date'], 'Y-m-d', 'd/m/Y') : 'N/A',
+ $row['policy_holder_name'] ?? 'N/A',
+ $row['same_as_proposer'] ?? 'No',
+ $row['follower_policy_no'] ?? 'N/A',
+ number_format((float)($row['co_share_per'] ?? 0), 2),
+ number_format((float)($row['non_comm_per_amt'] ?? 0), 2),
+ number_format((float)($row['bp_cgst'] ?? 0), 2),
+ number_format((float)($row['bp_sgst'] ?? 0), 2),
+ number_format((float)($row['bp_igst'] ?? 0), 2),
+ number_format((float)($row['stamp_duty'] ?? 0), 2),
+ number_format((float)($row['standerd_bp_per'] ?? 0), 2),
+ number_format((float)($row['standerd_tp_per'] ?? 0), 2),
+ number_format((float)($row['actual_bp_amt'] ?? 0), 2),
+ number_format((float)($row['actual_tp_amt'] ?? 0), 2),
+ number_format((float)($row['actual_bp_per'] ?? 0), 2),
+ number_format((float)($row['actual_tp_per'] ?? 0), 2),
+ number_format((float)($row['actual_tep_brokerage_amt'] ?? 0), 2),
+ number_format((float)($row['actual_tp_brokerage_amt'] ?? 0), 2),
+ $row['cd_ac_no'] ?? 'N/A'
+ ];
+ }
+
+ $today = date('d-m-Y', strtotime($today));
+ $fileName = 'BDS_Daily_Report_' . $today . '.xlsx';
+ $tmpDir = WRITEPATH . 'tmp' . DIRECTORY_SEPARATOR;
+ if (!is_dir($tmpDir)) {
+ mkdir($tmpDir, 0755, true);
+ }
+ $filePath = $tmpDir . $fileName;
+
+ $generated = generate_excel($headers, $excelData, $filePath);
+ if (!$generated) {
+ $this->myLogger->logme('error', 'cronDailyBDSReport: Excel generation failed');
+ return $this->respond(['status' => false, 'message' => 'Excel generation failed'], 500);
+ }
+
+ $emailList = getenv('bds.dailyReportEmails') ?: '';
+ $recipientEmails = array_filter(array_map('trim', explode(',', $emailList)));
+ if (empty($recipientEmails)) {
+ @unlink($filePath);
+ $this->myLogger->logme('error', 'cronDailyBDSReport: No recipients in bds.dailyReportEmails. Report generated but not sent.');
+ return $this->respond([
+ 'status' => 'success',
+ 'message' => 'Report generated. No recipients configured (set bds.dailyReportEmails in .env).',
+ 'file' => $fileName
+ ], 200);
+ }
+
+ $subject = "BDS Report Up to - {$today}";
+ $message = "
Please find attached the BDS report up to {$today}.
";
+ $message .= "Total records: " . count($reportList) . "
";
+
+ $attachments = [
+ ['filePath' => $filePath, 'fileName' => $fileName]
+ ];
+
+ $res = MailHelper::send_email([
+ 'mail' => $recipientEmails,
+ 'subject' => $subject,
+ 'message' => $message,
+ 'attachments' => $attachments
+ ]);
+
+ @unlink($filePath);
+
+ $resDecoded = is_string($res) ? json_decode($res, true) : $res;
+ if (isset($resDecoded['status']) && $resDecoded['status'] === 'success') {
+ $this->myLogger->logme('error', "cronDailyBDSReport: Report sent to " . count($recipientEmails) . " recipients");
+ return $this->respond([
+ 'status' => true,
+ 'message' => 'Report generated and emailed successfully.',
+ 'recipients' => count($recipientEmails)
+ ], 200);
+ }
+
+ $this->myLogger->logme('error', 'cronDailyBDSReport: Email send failed - ' . json_encode($resDecoded));
+ return $this->respond(['status' => false, 'message' => 'Report generated but email send failed'], 500);
+ } catch (Exception $e) {
+ if ($filePath && is_file($filePath)) {
+ @unlink($filePath);
+ }
+ $this->myLogger->logme('error', 'cronDailyBDSReport Exception: ' . $e->getMessage());
+ return $this->respond(['status' => false, 'message' => $e->getMessage()], 500);
+ }
}
}
diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php
index dccd9d91..39a0f672 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();
}
@@ -49,68 +53,145 @@ class SalesController extends BaseController
->orderBy('lead_id', 'DESC')
->findAll();
-
return $this->loadLayout('sales/activity_view', $data);
}
public function loadtargets(){
$data = $this->getSalesStaffData();
- $data['tab_name'] = 'Team Target';
- $data['page_name'] = 'Team Target';
+ $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
- */
+ /**
+ * HELPER: Fetches Sales Managers based on the logged-in user's role and branch
+ * sales_manager Current branch | Role 4 + Team 5 | Assign To dropdown
+ * sales_manager_ids Current branch | Role 4 + Team 5 | Query filter IDs
+ * sales_manager_with_head Current branch | Role 1,4,5 | Branch reporting dropdown
+ * sales_manager_with_head_ids Current branch | Role 1,4,5 | Branch reporting filter
+ * sales_team All branches | Role 1,4,5 | Admin/global reporting
+ */
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();
+ // ── Get logged-in user's profile ────────────────────────────────
+ $row = $db->table('user_profiles')
+ ->where('is_active', 1)
+ ->where('id', $logged_user_id)
+ ->get()->getRow();
+
+ $nhance_branch_id = $row ? $row->nhance_branch_id : null;
+
+ // ── Base result structure ────────────────────────────────────────
$data = [
- 'users' => [],
- 'sales_manager_ids' => []
+ 'sales_role' => '',
+ 'sales_manager' => [],
+ 'sales_manager_ids' => [],
+ 'sales_manager_with_head' => [],
+ 'sales_manager_with_head_ids' => [],
+ 'sales_team' => [],
+ 'nhance_branch_id' => $nhance_branch_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;
-
- // Is the logged-in user a Sales Manager? (Role 4, Team 5)
- if ($role == 4 && in_array(5, $team_id)) {
+ // ================================================================
+ // QUERY 1: Get all MANAGERS in current branch
+ // Role = 4 AND Team = 5 AND same branch
+ // ================================================================
+ $branch_managers = $db->table('user_profiles up')
+ ->select('up.id, up.first_name, up.role, 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) // Sales Manager role
+ ->where('ut.team_id', 5) // Sales team
+ ->where('up.nhance_branch_id', $nhance_branch_id) // same branch
+ ->groupBy('up.id')
+ ->get()->getResultArray();
+
+ // ================================================================
+ // QUERY 2: Get all HEADS in current branch
+ // Role = 1 or 5 AND same branch
+ // ================================================================
+ $branch_heads = $db->table('user_profiles')
+ ->select('id, first_name, role, nhance_branch_id')
+ ->where('is_active', 1)
+ ->whereIn('role', [1, 5]) // Sales Head roles
+ ->where('nhance_branch_id', $nhance_branch_id) // same branch
+ ->get()->getResultArray();
+
+ // Add "(Head)" label to heads so dropdown is clear
+ foreach ($branch_heads as &$head) {
+ $head['first_name'] = $head['first_name'] . ' (Head)';
+ }
+ unset($head);
+
+ // ================================================================
+ // QUERY 3: Get ALL MANAGERS across ALL branches
+ // Role = 4 AND Team = 5 (no branch filter)
+ // ================================================================
+ $all_managers = $db->table('user_profiles up')
+ ->select('up.id, up.first_name, up.role, 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) // Sales Manager role
+ ->where('ut.team_id', 5) // Sales team
+ ->groupBy('up.id')
+ ->get()->getResultArray();
+
+ // ================================================================
+ // QUERY 4: Get ALL HEADS across ALL branches
+ // Role = 1 or 5 (no branch filter)
+ // ================================================================
+ $all_heads = $db->table('user_profiles')
+ ->select('id, first_name, role, nhance_branch_id')
+ ->where('is_active', 1)
+ ->whereIn('role', [1, 5]) // Sales Head roles
+ ->get()->getResultArray();
+
+ // Add "(Head)" label to all heads
+ foreach ($all_heads as &$head) {
+ $head['first_name'] = $head['first_name'] . ' (Head)';
+ }
+ unset($head);
+
+ // ================================================================
+ // BUILD: sales_manager_with_head = branch heads + branch managers
+ // ================================================================
+ $data['sales_manager_with_head'] = array_merge($branch_heads, $branch_managers);
+ $data['sales_manager_with_head_ids'] = array_column($data['sales_manager_with_head'], 'id');
+
+ // ================================================================
+ // BUILD: sales_team = all heads + all managers (every branch)
+ // ================================================================
+ $data['sales_team'] = array_merge($all_heads, $all_managers);
+
+ // ── Sales Manager (Role 4, Team 5) ──────────────────────────────
+ if ($role == 4 && in_array(5, $team_id)) {
+
+ $data['sales_role'] = 'Sales Manager';
+ $data['sales_manager'] = [[ // only himself
+ 'id' => $row->id,
+ 'first_name' => $row->first_name,
+ 'role' => $role,
+ 'nhance_branch_id' => $nhance_branch_id,
+ ]];
$data['sales_manager_ids'] = [$logged_user_id];
-
- $data['users'] = [
- [
- 'id' => $row->id,
- 'first_name' => $row->first_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['sales_manager'] = $branch_managers; // reuse QUERY 1 result
+ $data['sales_manager_ids'] = array_column($branch_managers, 'id');
+ $data['sales_manager_with_head'] = array_merge($branch_heads, $branch_managers); // reuse
+ $data['sales_manager_with_head_ids'] = array_column($data['sales_manager_with_head'], 'id');
}
return $data;
@@ -136,6 +217,9 @@ class SalesController extends BaseController
echo view('layout/footer', $data);
}
+ /**
+ * GET /api/sales/activities/(:num)/complete
+ */
public function completeActivity($id) {
try {
$data = $this->request->getJSON(true);
@@ -223,7 +307,8 @@ class SalesController extends BaseController
'data' => $result['data'],
'total' => $result['total'],
'limit' => $limit,
- 'offset' => $offset
+ 'offset' => $offset,
+ 'counts' => $result['counts'],
]);
} catch (\Exception $e) {
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
@@ -385,6 +470,15 @@ class SalesController extends BaseController
$data = $this->request->getJSON(true);
$data['updated_by'] = $this->getUserId();
+ $contact = $this->contactModel->find($id);
+
+ if (isset($data['is_primary']) && $data['is_primary'] == 1) {
+ // Reset all contacts for this lead to 0 primary
+ $this->contactModel->where('lead_id', $contact['lead_id'])
+ ->set(['is_primary' => 0])
+ ->update();
+ }
+
if (!$this->contactModel->update((int)$id, $data)) {
return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
}
@@ -474,7 +568,8 @@ class SalesController extends BaseController
'data' => $result['data'],
'total' => $result['total'],
'limit' => $limit,
- 'offset' => $offset
+ 'offset' => $offset,
+ 'counts' => $result['counts'],
], 200);
} catch (\Exception $e) {
@@ -552,6 +647,11 @@ class SalesController extends BaseController
$data['created_by'] = $this->getUserId();
$data['updated_by'] = $this->getUserId();
+ // FIX: Convert the array to a JSON string so it fits in the VARCHAR column
+ if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) {
+ $data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']);
+ }
+
if (!$this->activityModel->insert($data)) {
return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
}
@@ -562,6 +662,11 @@ class SalesController extends BaseController
$activityId = $this->activityModel->getInsertID();
$activity = $this->activityModel->find((int)$activityId);
+ // OPTIONAL: Decode it back to an array for the API response so the frontend gets a clean array
+ if (isset($activity['additional_assigned_ids'])) {
+ $activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true);
+ }
+
return $this->respondCreated([
'status' => 'success',
'message' => 'Activity created successfully',
@@ -587,12 +692,22 @@ class SalesController extends BaseController
$data = $this->request->getJSON(true);
$data['updated_by'] = $this->getUserId();
+ // FIX: Convert the array to a JSON string for updating
+ if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) {
+ $data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']);
+ }
+
if (!$this->activityModel->update($id, $data)) {
return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
}
$activity = $this->activityModel->find((int)$id);
+ // OPTIONAL: Decode it back to an array for the API response
+ if (isset($activity['additional_assigned_ids'])) {
+ $activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true);
+ }
+
return $this->respond([
'status' => 'success',
'message' => 'Activity updated successfully',
@@ -760,6 +875,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 ====================
/**
@@ -782,281 +1029,896 @@ class SalesController extends BaseController
// ==================== Dashboard ====================
- public function dashboard(){
- $logged_user_id = get_session_userid();
- $role = get_role_id();
- $team_id = user_team();
- $payload = $this->request->getGet();
+// ─────────────────────────────────────────────
+// HELPER: Build FY date range from fy_year string
+// e.g. "2024-2025" → ['2024-04-01 00:00:00', '2025-03-31 23:59:59']
+// ─────────────────────────────────────────────
+private function getFYDateRange(string $financialYear): array
+{
+ // Format: "2025-2026" — split on last hyphen to get start=2025, end=2026
+ $pos = strrpos($financialYear, '-');
+ $startYear = substr($financialYear, 0, $pos); // "2025"
+ $endYear = substr($financialYear, $pos + 1); // "2026"
- $db = \Config\Database::connect();
+ return [
+ 'start' => $startYear . '-04-01 00:00:00', // 2025-04-01 00:00:00
+ 'end' => $endYear . '-03-31 23:59:59', // 2026-03-31 23:59:59
+ ];
+}
- $row = $db->table('user_profiles')
- ->select('*')
- ->where('is_active', 1)
- ->where('id', $logged_user_id)
- ->get()
- ->getRowArray();
+// ─────────────────────────────────────────────
+// HELPER: FY quarters (Apr-Jun / Jul-Sep / Oct-Dec / Jan-Mar)
+// ─────────────────────────────────────────────
+private function getFYQuarters(string $financialYear): array
+{
+ $pos = strrpos($financialYear, '-');
+ $sy = (int)substr($financialYear, 0, $pos); // 2025
+ $ey = (int)substr($financialYear, $pos + 1); // 2026
- $nhance_branch_id = $row ? $row['nhance_branch_id'] : null;
+ return [
+ ['name' => 'Q1', 'label' => "Q1 (Apr–Jun {$sy})", 'start' => "{$sy}-04-01", 'end' => "{$sy}-06-30"],
+ ['name' => 'Q2', 'label' => "Q2 (Jul–Sep {$sy})", 'start' => "{$sy}-07-01", 'end' => "{$sy}-09-30"],
+ ['name' => 'Q3', 'label' => "Q3 (Oct–Dec {$sy})", 'start' => "{$sy}-10-01", 'end' => "{$sy}-12-31"],
+ ['name' => 'Q4', 'label' => "Q4 (Jan–Mar {$ey})", 'start' => "{$ey}-01-01", 'end' => "{$ey}-03-31"],
+ ];
+}
- // dd($logged_user_id, $nhance_branch_id, $role, $team_id );
+// ─────────────────────────────────────────────
+// dashboard() — entry point
+// ─────────────────────────────────────────────
+public function dashboard()
+{
+ $payload = $this->request->getGet();
+ $base = $this->getSalesStaffData();
+ $salesRole = $base['sales_role'];
+ $salesManagerIds = $base['sales_manager_ids'];
+ $userId = get_session_userid();
- if (in_array($role,[1,5])) {
+ // Get branch id
+ $nhanceBranchId = $base['sales_manager'][0]['nhance_branch_id'] ?? null;
- $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);
- }
-
+ $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear();
+
+ $db = \Config\Database::connect();
+
+ // Available FY years for dropdown
+ $fin_years_raw = $db->table('sales_target')
+ ->select('fy_year', false) // false = no backtick escaping
+ ->distinct()
+ ->orderBy('fy_year', 'DESC')
+ ->get()
+ ->getResultArray();
+
+ $fin_years = array_column($fin_years_raw, 'fy_year');
+
+ if (empty($fin_years)) {
+ $fin_years[] = $current_fin_year;
}
- public function branchLevelDashboard($branchId,$sales_manager_ids)
- {
- // Hardcoded branch ID as requested
- // $branchId = 1;
+ // Ensure current FY is available in list
+ if (!in_array($current_fin_year, $fin_years)) {
+ array_unshift($fin_years, $current_fin_year);
+ }
- try {
- $sales_manager_ids = array_values(array_map('intval', $sales_manager_ids));
-
- // Final safe check
- if (empty($sales_manager_ids)) {
- // No valid IDs — skip queries or return empty
- $total_leads = 0;
- $total_activity = 0;
- $total_completed_activity = 0;
- $total_pending_activity = 0;
- $pending_activities = [];
- $recent_activities = [];
- $teamPerformance = [];
-
- $leadsOverview = [];
- } else {
+ // Route by role
+ if ($salesRole === 'Sales Head') {
+ $this->branchLevelDashboard($nhanceBranchId, $salesManagerIds, $current_fin_year, $fin_years);
+ } elseif ($salesRole === 'Sales Manager') {
+ $this->salesManagerLevelDashboard($userId, $current_fin_year, $fin_years);
+ }
+}
- if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) {
- $sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0
- }
-
- // 1. Lead Statistics
- $total_leads = $this->leadModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults(); // Use countAllResults, NOT countAll
- // echo $this->leadModel->getLastQuery();die();
-
- // 2. Total activity
- $total_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults();
+// ─────────────────────────────────────────────
+// branchLevelDashboard()
+// ─────────────────────────────────────────────
+public function branchLevelDashboard($branchId, $sales_manager_ids, $current_fin_year, $fin_years)
+{
+ try {
+ $sales_manager_ids = array_values(array_filter(array_map('intval', $sales_manager_ids)));
- // 3. Completed activity
- $total_completed_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'completed')->countAllResults();
+ $db = \Config\Database::connect();
+ $fyRange = $this->getFYDateRange($current_fin_year);
+ $fyStart = $fyRange['start'];
+ $fyEnd = $fyRange['end'];
- // 4. Pending activity
- $total_pending_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'pending')->countAllResults();
-
- $db = \Config\Database::connect();
-
- // 5. Team Performance
- $teamPerformance = $db->table('user_profiles as u')
- ->select('u.first_name, u.last_name, r.role,
- (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id) as total_acts,
- (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = "completed") as done_acts')
- ->join('roles r', 'r.id = u.role')
- ->where('u.nhance_branch_id', $branchId)
- ->whereIn('u.id', $sales_manager_ids)
- ->where('u.is_active', 1)
- ->get()->getResultArray();
-
- // 6. Recent Activities (Joining for Lead Names)
- $recent_activities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
- ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
- ->orderBy('sales_activities.scheduled_date', 'DESC')
- ->limit(6)
- ->findAll();
-
- // 7. Pending Activities (List)
- $pending_activities = $db->table('sales_activities sa')
- ->select('sa.activity_id,sa.lead_id,sa.activity_type,sa.scheduled_date,sa.status,sa.assigned_to,sal.company_name,up.first_name AS assigned_to_name,sa.notes')
- ->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
- ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') // ✅ ADD THIS
- ->orderBy('sa.scheduled_date', 'DESC')
- ->whereIn('sa.assigned_to', $sales_manager_ids)
- ->where('sa.status', 'pending')
- ->get()->getResultArray();
-
- // 8. All Leads Overview
- $leadsOverview = $db->table('sales_actual_leads sal')
- ->select('sal.lead_id,sal.company_name,sal.status,up.first_name AS assigned_to,
- COUNT(DISTINCT sa.activity_id) AS activities,
- COUNT(DISTINCT l.id) AS opportunities
- ')
- ->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
- ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
- ->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left')
- ->groupBy('sal.lead_id, sal.company_name, sal.status, up.first_name')
- // ->having('COUNT(DISTINCT sa.activity_id) + COUNT(DISTINCT l.id) >', 0) // ← this line
- ->orderBy('sal.created_at', 'DESC')
- ->whereIn('sal.assigned_to', $sales_manager_ids)
- ->get()
- ->getResultArray();
-
- // 9. Activity BrakDown
- $activityBreakdown = $db->table('sales_activities')
- ->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total_activity}, 0) AS percentage", false)
- ->whereIn('assigned_to', $sales_manager_ids)
- ->groupBy('activity_type')
- ->orderBy('total', 'DESC')
- ->get()
- ->getResultArray();
- }
+ if (empty($sales_manager_ids)) {
+ // ── No team members — return empty dashboard ──
$data = [
- 'total_leads' => $total_leads,
- 'total_acts' => $total_activity,
- 'total_completed_acts' => $total_completed_activity,
- 'total_pending_acts'=> $total_pending_activity,
- 'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
- 'team' => $teamPerformance,
- 'recent_acts' => $recent_activities,
- 'pending_acts' => $pending_activities,
- 'leads_overview' => $leadsOverview,
- 'activity_breakdown'=> $activityBreakdown,
- 'tab_name' => "Sales Dashboard",
- 'page_name' => "Sales Dashboard"
+ 'total_leads' => 0,
+ 'total_acts' => 0,
+ 'total_completed_acts' => 0,
+ 'total_pending_acts' => 0,
+ 'team' => [],
+ 'pending_acts' => [],
+ 'leads_overview' => [],
+ 'activity_breakdown' => [],
+ 'team_achievement' => [],
+ 'opp_achievement' => [],
+ 'fin_years' => $fin_years,
+ 'current_fin_year' => $current_fin_year,
+ 'tab_name' => 'Sales Dashboard',
+ 'page_name' => 'Sales Dashboard',
];
-
- // dd($data);
-
$this->loadLayout('sales/branch_level_dashboard_view', $data);
-
- // return view('sales/dashboard_view', $data);
-
- } catch (\Exception $e) {
- return $this->failServerError($e->getMessage());
+ return;
}
+
+ // 1. Lead count — FY filtered by created_at
+ $total_leads = $this->leadModel
+ ->whereIn('assigned_to', $sales_manager_ids)
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->countAllResults();
+
+ // 2. Total activities — FY filtered by scheduled_date
+ $total_activity = $this->activityModel
+ ->whereIn('assigned_to', $sales_manager_ids)
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->countAllResults();
+
+ // 3. Completed activities — FY filtered
+ $total_completed_activity = $this->activityModel
+ ->whereIn('assigned_to', $sales_manager_ids)
+ ->where('status', 'completed')
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->countAllResults();
+
+ // 4. Pending activities — FY filtered
+ $total_pending_activity = $this->activityModel
+ ->whereIn('assigned_to', $sales_manager_ids)
+ ->where('status', 'pending')
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->countAllResults();
+
+ // 5. Team Performance — subqueries FY filtered by scheduled_date
+ $teamPerformance = $db->table('user_profiles as u')
+ ->select("u.id, u.first_name, u.last_name, r.role,
+ (SELECT COUNT(*) FROM sales_activities
+ WHERE assigned_to = u.id
+ AND scheduled_date >= '{$fyStart}'
+ AND scheduled_date <= '{$fyEnd}') as total_acts,
+ (SELECT COUNT(*) FROM sales_activities
+ WHERE assigned_to = u.id AND status = 'completed'
+ AND scheduled_date >= '{$fyStart}'
+ AND scheduled_date <= '{$fyEnd}') as done_acts", false)
+ ->join('roles r', 'r.id = u.role')
+ ->where('u.nhance_branch_id', $branchId)
+ ->whereIn('u.id', $sales_manager_ids)
+ ->where('u.is_active', 1)
+ ->get()
+ ->getResultArray();
+
+ // 6. Pending Activities list — FY filtered by scheduled_date
+ $pending_activities = $db->table('sales_activities sa')
+ ->select('sa.activity_id, sa.lead_id, sa.activity_type, sa.scheduled_date,
+ sa.status, sa.assigned_to, sal.company_name,
+ up.first_name AS assigned_to_name, sa.notes')
+ ->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
+ ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left')
+ ->whereIn('sa.assigned_to', $sales_manager_ids)
+ ->where('sa.status', 'pending')
+ ->where('sa.scheduled_date >=', $fyStart)
+ ->where('sa.scheduled_date <=', $fyEnd)
+ ->orderBy('sa.scheduled_date', 'DESC')
+ ->get()
+ ->getResultArray();
+
+ // 7. All Leads Overview — FY filtered by sal.created_at
+ // Activities & opportunities also scoped to FY via CASE WHEN
+ $leadsOverview = $db->table('sales_actual_leads sal')
+ ->select("sal.lead_id, sal.company_name, sal.status,
+ up.first_name AS assigned_to,
+ COUNT(DISTINCT CASE WHEN sa.scheduled_date >= '{$fyStart}'
+ AND sa.scheduled_date <= '{$fyEnd}' THEN sa.activity_id END) AS activities,
+ COUNT(DISTINCT CASE WHEN l.updated_at >= '{$fyStart}'
+ AND l.updated_at <= '{$fyEnd}' THEN l.id END) AS opportunities", false)
+ ->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
+ ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
+ ->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left')
+ ->whereIn('sal.assigned_to', $sales_manager_ids)
+ ->where('sal.created_at >=', $fyStart)
+ ->where('sal.created_at <=', $fyEnd)
+ ->groupBy('sal.lead_id, sal.company_name, sal.status, up.first_name')
+ ->orderBy('sal.created_at', 'DESC')
+ ->get()
+ ->getResultArray();
+
+ // Protect against division by zero in query #8
+ $total_activity_safe = $total_activity > 0 ? $total_activity : 1;
+
+ // 8. Activity Breakdown — FY filtered by scheduled_date
+ $activityBreakdown = $db->table('sales_activities')
+ ->select("activity_type,
+ COUNT(*) AS total,
+ ROUND(COUNT(*) * 100.0 / {$total_activity_safe}, 0) AS percentage", false)
+ ->whereIn('assigned_to', $sales_manager_ids)
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->groupBy('activity_type')
+ ->orderBy('total', 'DESC')
+ ->get()
+ ->getResultArray();
+
+ // 9. Team Achievement (for achievement list + modal)
+ $teamAchievement = $this->buildTeamAchievement(
+ $db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd
+ );
+
+ // 10. Opportunities Achievement per member
+ $oppAchievement = $this->buildOppAchievement(
+ $db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd
+ );
+
+ $data = [
+ 'total_leads' => $total_leads,
+ 'total_acts' => $total_activity,
+ 'total_completed_acts' => $total_completed_activity,
+ 'total_pending_acts' => $total_pending_activity,
+ 'team' => $teamPerformance,
+ 'pending_acts' => $pending_activities,
+ 'leads_overview' => $leadsOverview,
+ 'activity_breakdown' => $activityBreakdown,
+ 'team_achievement' => $teamAchievement, // used by JS TEAM constant
+ 'opp_achievement' => $oppAchievement, // used by JS OPP_DATA constant
+ 'fin_years' => $fin_years,
+ 'current_fin_year' => $current_fin_year,
+ 'tab_name' => 'Sales Dashboard',
+ 'page_name' => 'Sales Dashboard',
+ ];
+
+ $this->loadLayout('sales/branch_level_dashboard_view', $data);
+
+ } catch (\Exception $e) {
+ return $this->failServerError($e->getMessage());
+ }
+}
+
+// ─────────────────────────────────────────────
+// buildTeamAchievement()
+// Builds the TEAM array for the achievement list
+// ─────────────────────────────────────────────
+private function buildTeamAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array
+{
+ $quarters = $this->getFYQuarters($fy);
+
+ // Gradient palette (cycles)
+ $gradients = [
+ ['grad' => 'linear-gradient(135deg,#10b981,#34d399)', 'color' => '#10b981'],
+ ['grad' => 'linear-gradient(135deg,#06b6d4,#67e8f9)', 'color' => '#06b6d4'],
+ ['grad' => 'linear-gradient(135deg,#4f46e5,#818cf8)', 'color' => '#4f46e5'],
+ ['grad' => 'linear-gradient(135deg,#ec4899,#f9a8d4)', 'color' => '#ec4899'],
+ ['grad' => 'linear-gradient(135deg,#f97316,#fbbf24)', 'color' => '#f97316'],
+ ];
+
+ $members = $db->table('user_profiles as u')
+ ->select('u.id, u.first_name, u.last_name, r.role')
+ ->join('roles r', 'r.id = u.role')
+ ->where('u.nhance_branch_id', $branchId)
+ ->whereIn('u.id', $sales_manager_ids)
+ ->where('u.is_active', 1)
+ ->get()
+ ->getResultArray();
+
+ $result = [];
+
+ foreach ($members as $idx => $m) {
+ $uid = (int)$m['id'];
+
+ // Target from sales_target
+ $targetRow = $db->table('sales_target')
+ ->where('user_id', $uid)
+ ->where('fy_year', $fy)
+ ->get()
+ ->getRowArray();
+ $targetAmt = (float)($targetRow['target_amount'] ?? 0);
+
+ // Achieved (won leads in FY)
+ $achievedAmt = (float)$this->getUserAchievedAmount($fy, $uid);
+
+ // Activities — FY filtered by scheduled_date
+ $totalActs = $db->table('sales_activities')
+ ->where('assigned_to', $uid)
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->countAllResults();
+ $doneActs = $db->table('sales_activities')
+ ->where('assigned_to', $uid)
+ ->where('status', 'completed')
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->countAllResults();
+
+ // Activity breakdown — FY filtered
+ $actRows = $db->table('sales_activities')
+ ->select('activity_type, COUNT(*) as cnt')
+ ->where('assigned_to', $uid)
+ ->where('scheduled_date >=', $fyStart)
+ ->where('scheduled_date <=', $fyEnd)
+ ->groupBy('activity_type')
+ ->get()->getResultArray();
+ $activities = [];
+ foreach ($actRows as $ar) {
+ $activities[$ar['activity_type']] = (int)$ar['cnt'];
+ }
+
+ // Quarter splits
+ $splits = [];
+ foreach ($quarters as $q) {
+ $qStart = $q['start'] . ' 00:00:00';
+ $qEnd = $q['end'] . ' 23:59:59';
+
+ // Achievement = SUM(exp_amt) for won policies in this quarter
+ $qAchievedRow = $db->query("
+ SELECT COALESCE(SUM(ptcs.exp_amt), 0) AS total
+ FROM policy_transaction pt
+ LEFT JOIN pt_co_share_details ptcs
+ ON ptcs.pt_id = pt.id
+ AND ptcs.is_active = 1
+ WHERE pt.sales_generated_by = ?
+ AND pt.issuer_branch = ?
+ AND pt.created_at >= ?
+ AND pt.created_at <= ?
+ ", [$uid, $branchId, $qStart, $qEnd])->getRowArray();
+ $qAchieved = (float)($qAchievedRow['total'] ?? 0);
+
+ $qTarget = $targetAmt > 0 ? round($targetAmt / 4, 2) : 0;
+
+ $qActs = $db->table('sales_activities')
+ ->where('assigned_to', $uid)
+ ->where('scheduled_date >=', $qStart)
+ ->where('scheduled_date <=', $qEnd)
+ ->countAllResults();
+
+ $qDone = $db->table('sales_activities')
+ ->where('assigned_to', $uid)
+ ->where('status', 'completed')
+ ->where('scheduled_date >=', $qStart)
+ ->where('scheduled_date <=', $qEnd)
+ ->countAllResults();
+
+ $qLeads = $db->table('sales_actual_leads')
+ ->where('assigned_to', $uid)
+ ->where('created_at >=', $qStart)
+ ->where('created_at <=', $qEnd)
+ ->countAllResults();
+
+ $splits[] = [
+ 'name' => $q['label'],
+ 'start' => date('M Y', strtotime($q['start'])),
+ 'end' => date('M Y', strtotime($q['end'])),
+ 'target' => $qTarget,
+ 'achieved' => $qAchieved,
+ 'acts' => $qActs,
+ 'done' => $qDone,
+ 'leads' => $qLeads,
+ ];
+ }
+
+ $palette = $gradients[$idx % count($gradients)];
+
+ $result[] = [
+ 'id' => $uid,
+ 'first_name' => $m['first_name'],
+ 'last_name' => $m['last_name'],
+ 'role' => $m['role'],
+ 'total_acts' => $totalActs,
+ 'done_acts' => $doneActs,
+ 'target_amt' => $targetAmt,
+ 'achieved_amt' => $achievedAmt,
+ 'grad' => $palette['grad'],
+ 'color' => $palette['color'],
+ 'splits' => $splits,
+ 'activities' => $activities,
+ ];
}
- public function salesManagerLevelDashboard($userId,$sales_manager_ids, $payload = [])
- {
- // $userId = get_session_userid();
- // $userId = 1;
- $db = \Config\Database::connect();
+ return $result;
+}
- try {
+// ─────────────────────────────────────────────
+// buildOppAchievement()
+// Opportunities via policy_transaction + pt_co_share_details
+// Returns per-member: totals + flat policy list (no quarterly grouping)
+// ─────────────────────────────────────────────
+private function buildOppAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array
+{
+ $result = [];
- // $payload = $this->request->getGet();
- $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear();
+ foreach ($sales_manager_ids as $uid) {
-
- $fin_years = $db->table('sales_target')
- ->select('fy_year')
- ->where('user_id', $userId)
- ->orderBy('fy_year', 'desc')
- ->get()
- ->getResultArray();
+ // ── Target ──
+ $targetRow = $db->table('sales_target')
+ ->where('user_id', $uid)
+ ->where('fy_year', $fy)
+ ->get()
+ ->getRowArray();
+ $targetAmt = (float)($targetRow['target_amount'] ?? 0);
- $fin_years = array_column($fin_years, 'fy_year');
+ // ── All policy rows for this user in FY ──
+ // policy_no, issue_date (from pt), amount (exp_amt from child), created_at
+ // If no matching pt_co_share_details row exists, exp_amt = 0
+ // Policy list for Tab 2: policy_transaction + exp_amt from pt_co_share_details
+ $policyRows = $db->query("
+ SELECT
+ pt.id,
+ pt.policy_no,
+ pt.created_at AS issue_date,
+ COALESCE(ptcs.exp_amt, 0) AS amount,
+ pt.created_at AS created_at
+ FROM policy_transaction pt
+ LEFT JOIN pt_co_share_details ptcs
+ ON ptcs.pt_id = pt.id
+ AND ptcs.is_active = 1
+ WHERE pt.sales_generated_by = ?
+ AND pt.issuer_branch = ?
+ AND pt.created_at >= ?
+ AND pt.created_at <= ?
+ ORDER BY pt.created_at DESC
+ ", [$uid, $branchId, $fyStart, $fyEnd])->getResultArray();
- if(empty($fin_years)){
- $fin_years[] = $current_fin_year;
- }
+ // ── Totals derived from policy_transaction ──
+ $totalPolicies = count($policyRows);
+ $totalExpAmt = array_sum(array_column($policyRows, 'amount'));
- $target = $db->table('sales_target')
- ->where('user_id', $userId)
- ->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);
-
- $remainingAmount = $targetAmount - $achievedAmount;
- // $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(),
- 'pending' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'pending'])->countAllResults(),
- 'completed' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'completed'])->countAllResults(),
+ // Clean policy list for JS
+ $policies = array_map(function($row) {
+ return [
+ 'policy_no' => $row['policy_no'],
+ 'issue_date' => $row['issue_date'],
+ 'amount' => (float)$row['amount'],
+ 'created_at' => $row['created_at'],
];
+ }, $policyRows);
- $myLeadsCount = $this->leadModel->where('assigned_to', $userId)->countAllResults();
+ // ── Won Leads for this user in FY (Table 2 in modal) ──
+ // leads.actual_lead_id maps to sales_actual_leads.id
+ // leads.type: 1 = EB, else = Non-EB
+ $wonLeads = $db->query("
+ SELECT
+ sal.lead_id,
+ sal.company_name AS company,
+ CASE WHEN l.lead_type = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_type,
+ l.created_at AS created_at,
+ l.status
+ FROM leads l
+ INNER JOIN sales_actual_leads sal
+ ON sal.lead_id = l.actual_lead_id
+ WHERE l.status = 'won'
+ AND sal.assigned_to = ?
+ AND l.created_at >= ?
+ AND l.created_at <= ?
+ ORDER BY l.created_at DESC
+ ", [$uid, $fyStart, $fyEnd])->getResultArray();
- $upcomingActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
- ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
- ->where(['sales_activities.assigned_to' => $userId, 'sales_activities.status' => 'pending'])
- ->orderBy('scheduled_date', 'ASC')
- ->limit(3)
- ->findAll();
-
- $recentLeads = $this->leadModel->where('assigned_to', $userId)
- ->orderBy('created_at', 'DESC')
- ->limit(5)
- ->findAll();
-
- $data = [
- 'target_amt' => $targetAmount,
- 'achieved' => $achievedAmount,
- 'remaining' => $remainingAmount,
- 'percent' => $achievementPercent,
- 'acts' => $activitySummary,
- 'lead_count' => $myLeadsCount,
- 'upcoming' => $upcomingActivities,
- 'recent_leads' => $recentLeads,
- 'fin_years' => $fin_years,
- 'display_fin_years' => format_financial_year($current_fin_year),
- 'user_name' => get_session_userdata()->first_namee ?? '',
- 'tab_name' => "Sales Dashboard",
- 'page_name' => "Sales Dashboard"
- ];
-
- // dd($data);
-
- // return view('sales/my_dashboard_view', $data);
- $this->loadLayout('sales/sales_manager_level_dashboard', $data);
-
- } catch (\Exception $e) {
- return $this->failServerError($e->getMessage());
- }
+ $result[$uid] = [
+ 'total_policies' => $totalPolicies,
+ 'total_exp_amt' => $totalExpAmt,
+ 'target_amt' => $targetAmt,
+ 'policies' => $policies,
+ 'won_leads' => $wonLeads, // for Table 2 in modal Tab 2
+ ];
}
- public function getUserAchievedAmount($financialYear, $userId)
- {
- // Split the string into two years
- $years = explode('-', $financialYear);
- $startYear = $years[0]; // 2025
- $endYear = $years[1]; // 2026
+ return $result;
+}
- // Create the timestamps
- $startFY = $startYear . '-04-01 00:00:00';
- $endFY = $endYear . '-03-31 23:59:59';
+// ─────────────────────────────────────────────
+// getUserAchievedAmount()
+// ─────────────────────────────────────────────
+public function getUserAchievedAmount($financialYear, $userId)
+{
+ // "2025-2026" → strrpos splits correctly into 2025 / 2026
+ $pos = strrpos($financialYear, '-');
+ $startYear = substr($financialYear, 0, $pos); // "2025"
+ $endYear = substr($financialYear, $pos + 1); // "2026"
- $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)
+ $startFY = $startYear . '-04-01 00:00:00'; // 2025-04-01
+ $endFY = $endYear . '-03-31 23:59:59'; // 2026-03-31
+
+ // Achievement = SUM(exp_amt) from policy_transaction + pt_co_share_details
+ $db = \Config\Database::connect();
+ $row = $db->query("
+ SELECT COALESCE(SUM(ptcs.exp_amt), 0) AS achieved_amount
+ FROM policy_transaction pt
+ LEFT JOIN pt_co_share_details ptcs
+ ON ptcs.pt_id = pt.id
+ AND ptcs.is_active = 1
+ WHERE pt.sales_generated_by = ?
+ AND pt.created_at >= ?
+ AND pt.created_at <= ?
+ ", [$userId, $startFY, $endFY])->getRowArray();
+
+ return (float)($row['achieved_amount'] ?? 0.00);
+}
+
+// ─────────────────────────────────────────────
+// salesManagerLevelDashboard()
+// ─────────────────────────────────────────────
+public function salesManagerLevelDashboard($userId, $current_fin_year = null, $fin_years = [])
+{
+ $db = \Config\Database::connect();
+
+ try {
+
+ // -------------------------------
+ // Financial Year Handling
+ // -------------------------------
+ if (empty($current_fin_year)) {
+ $current_fin_year = getCurrentFinancialYear();
+ }
+
+ $fyRange = $this->getFYDateRange($current_fin_year);
+ $fyStart = $fyRange['start'];
+ $fyEnd = $fyRange['end'];
+
+ // -------------------------------
+ // Target (FY Based)
+ // -------------------------------
+ $target = $db->table('sales_target')
+ ->where('user_id', $userId)
+ ->where('fy_year', $current_fin_year)
+ ->get()
+ ->getRowArray();
+
+ $targetAmount = (float)($target['target_amount'] ?? 0);
+
+ // -------------------------------
+ // Achieved (FY Based)
+ // -------------------------------
+ $achievedAmount = (float)$this->getUserAchievedAmount($current_fin_year, $userId);
+
+ $remainingAmount = $targetAmount - $achievedAmount;
+ $achievementPercent = ($targetAmount > 0)
+ ? min(100, round(($achievedAmount / $targetAmount) * 100))
+ : 0;
+
+ // -------------------------------
+ // Activity Summary (FY Based using created_at)
+ // -------------------------------
+ $activitySummary = [
+ 'total' => $this->activityModel
+ ->where('assigned_to', $userId)
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->countAllResults(),
+
+ 'pending' => $this->activityModel
+ ->where([
+ 'assigned_to' => $userId,
+ 'status' => 'pending'
+ ])
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->countAllResults(),
+
+ 'completed' => $this->activityModel
+ ->where([
+ 'assigned_to' => $userId,
+ 'status' => 'completed'
+ ])
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->countAllResults(),
+ ];
+
+ // -------------------------------
+ // Leads Count (FY Based)
+ // -------------------------------
+ $myLeadsCount = $this->leadModel
+ ->where('assigned_to', $userId)
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->countAllResults();
+
+ // -------------------------------
+ // Upcoming Activities (FY Based)
+ // -------------------------------
+ $upcomingActivities = $this->activityModel
+ ->select('sales_activities.*, sales_actual_leads.company_name')
+ ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
+ ->where([
+ 'sales_activities.assigned_to' => $userId,
+ 'sales_activities.status' => 'pending'
+ ])
+ ->where('sales_activities.created_at >=', $fyStart)
+ ->where('sales_activities.created_at <=', $fyEnd)
+ ->orderBy('scheduled_date', 'ASC')
+ ->limit(3)
->findAll();
- return $achievedAmountData[0]['achieved_amount'] ?? 0.00;
+ // -------------------------------
+ // Recent Leads (FY Based)
+ // -------------------------------
+ $recentLeads = $this->leadModel
+ ->where('assigned_to', $userId)
+ ->where('created_at >=', $fyStart)
+ ->where('created_at <=', $fyEnd)
+ ->orderBy('created_at', 'DESC')
+ ->limit(5)
+ ->findAll();
+
+ // -------------------------------
+ // Final Data
+ // -------------------------------
+ $data = [
+ 'target_amt' => $targetAmount,
+ 'achieved' => $achievedAmount,
+ 'remaining' => $remainingAmount,
+ 'percent' => $achievementPercent,
+ 'acts' => $activitySummary,
+ 'lead_count' => $myLeadsCount,
+ 'upcoming' => $upcomingActivities,
+ 'recent_leads' => $recentLeads,
+ 'fin_years' => $fin_years,
+ 'current_fin_year' => $current_fin_year,
+ 'display_fin_years' => format_financial_year($current_fin_year),
+ 'user_name' => get_session_userdata()->first_name ?? '',
+ 'tab_name' => 'Sales Dashboard',
+ 'page_name' => 'Sales Dashboard',
+ ];
+
+ return $this->loadLayout('sales/sales_manager_level_dashboard', $data);
+
+ } catch (\Exception $e) {
+ return $this->failServerError($e->getMessage());
}
+}
+ // public function dashboard()
+ // {
+ // $payload = $this->request->getGet();
+ // $base = $this->getSalesStaffData();
+ // $salesRole = $base['sales_role'];
+ // $salesManagerIds = $base['sales_manager_ids'];
+ // $userId = get_session_userid();
+
+ // // Get branch id from users array
+ // $nhanceBranchId = $base['users'][0]['nhance_branch_id'] ?? null;
+
+ // if ($salesRole === 'Sales Head') {
+ // $this->branchLevelDashboard($nhanceBranchId, $salesManagerIds);
+ // // $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload);
+ // } elseif ($salesRole === 'Sales Manager') {
+ // $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload);
+ // }
+ // }
+
+ // public function branchLevelDashboard($branchId,$sales_manager_ids)
+ // {
+ // // Hardcoded branch ID as requested
+ // // $branchId = 1;
+
+ // try {
+ // $sales_manager_ids = array_values(array_map('intval', $sales_manager_ids));
+
+ // // Final safe check
+ // if (empty($sales_manager_ids)) {
+ // // No valid IDs — skip queries or return empty
+ // $total_leads = 0;
+ // $total_activity = 0;
+ // $total_completed_activity = 0;
+ // $total_pending_activity = 0;
+ // $pending_activities = [];
+ // $recent_activities = [];
+ // $teamPerformance = [];
+
+ // $leadsOverview = [];
+ // } else {
+
+ // if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) {
+ // $sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0
+ // }
+
+ // // 1. Lead Statistics
+ // $total_leads = $this->leadModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults(); // Use countAllResults, NOT countAll
+ // // echo $this->leadModel->getLastQuery();die();
+
+ // // 2. Total activity
+ // $total_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults();
+
+ // // 3. Completed activity
+ // $total_completed_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'completed')->countAllResults();
+
+ // // 4. Pending activity
+ // $total_pending_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'pending')->countAllResults();
+
+ // $db = \Config\Database::connect();
+
+ // // 5. Team Performance
+ // $teamPerformance = $db->table('user_profiles as u')
+ // ->select('u.first_name, u.last_name, r.role,
+ // (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id) as total_acts,
+ // (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = "completed") as done_acts')
+ // ->join('roles r', 'r.id = u.role')
+ // ->where('u.nhance_branch_id', $branchId)
+ // ->whereIn('u.id', $sales_manager_ids)
+ // ->where('u.is_active', 1)
+ // ->get()->getResultArray();
+
+ // // 6. Recent Activities (Joining for Lead Names)
+ // $recent_activities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
+ // ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
+ // ->orderBy('sales_activities.scheduled_date', 'DESC')
+ // ->limit(6)
+ // ->findAll();
+
+ // // 7. Pending Activities (List)
+ // $pending_activities = $db->table('sales_activities sa')
+ // ->select('sa.activity_id,sa.lead_id,sa.activity_type,sa.scheduled_date,sa.status,sa.assigned_to,sal.company_name,up.first_name AS assigned_to_name,sa.notes')
+ // ->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
+ // ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') // ✅ ADD THIS
+ // ->orderBy('sa.scheduled_date', 'DESC')
+ // ->whereIn('sa.assigned_to', $sales_manager_ids)
+ // ->where('sa.status', 'pending')
+ // ->get()->getResultArray();
+
+ // // 8. All Leads Overview
+ // $leadsOverview = $db->table('sales_actual_leads sal')
+ // ->select('sal.lead_id,sal.company_name,sal.status,up.first_name AS assigned_to,
+ // COUNT(DISTINCT sa.activity_id) AS activities,
+ // COUNT(DISTINCT l.id) AS opportunities
+ // ')
+ // ->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
+ // ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
+ // ->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left')
+ // ->groupBy('sal.lead_id, sal.company_name, sal.status, up.first_name')
+ // // ->having('COUNT(DISTINCT sa.activity_id) + COUNT(DISTINCT l.id) >', 0) // ← this line
+ // ->orderBy('sal.created_at', 'DESC')
+ // ->whereIn('sal.assigned_to', $sales_manager_ids)
+ // ->get()
+ // ->getResultArray();
+
+ // // 9. Activity BrakDown
+ // $activityBreakdown = $db->table('sales_activities')
+ // ->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total_activity}, 0) AS percentage", false)
+ // ->whereIn('assigned_to', $sales_manager_ids)
+ // ->groupBy('activity_type')
+ // ->orderBy('total', 'DESC')
+ // ->get()
+ // ->getResultArray();
+ // }
+ // $data = [
+ // 'total_leads' => $total_leads,
+ // 'total_acts' => $total_activity,
+ // 'total_completed_acts' => $total_completed_activity,
+ // 'total_pending_acts'=> $total_pending_activity,
+ // 'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
+ // 'display_fin_years' => format_financial_year($current_fin_year),
+ // 'team' => $teamPerformance,
+ // 'recent_acts' => $recent_activities,
+ // 'pending_acts' => $pending_activities,
+ // 'leads_overview' => $leadsOverview,
+ // 'activity_breakdown'=> $activityBreakdown,
+ // 'tab_name' => "Sales Dashboard",
+ // 'page_name' => "Sales Dashboard"
+ // ];
+
+ // // dd($data);
+
+ // $this->loadLayout('sales/branch_level_dashboard_view', $data);
+
+ // // return view('sales/dashboard_view', $data);
+
+ // } catch (\Exception $e) {
+ // return $this->failServerError($e->getMessage());
+ // }
+ // }
+
+ // public function salesManagerLevelDashboard($userId,$sales_manager_ids, $payload = [])
+ // {
+ // // $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');
+
+ // if(empty($fin_years)){
+ // $fin_years[] = $current_fin_year;
+ // }
+
+ // $target = $db->table('sales_target')
+ // ->where('user_id', $userId)
+ // ->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);
+
+ // $remainingAmount = $targetAmount - $achievedAmount;
+ // // $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(),
+ // 'pending' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'pending'])->countAllResults(),
+ // 'completed' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'completed'])->countAllResults(),
+ // ];
+
+ // $myLeadsCount = $this->leadModel->where('assigned_to', $userId)->countAllResults();
+
+ // $upcomingActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
+ // ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
+ // ->where(['sales_activities.assigned_to' => $userId, 'sales_activities.status' => 'pending'])
+ // ->orderBy('scheduled_date', 'ASC')
+ // ->limit(3)
+ // ->findAll();
+
+ // $recentLeads = $this->leadModel->where('assigned_to', $userId)
+ // ->orderBy('created_at', 'DESC')
+ // ->limit(5)
+ // ->findAll();
+
+ // $data = [
+ // 'target_amt' => $targetAmount,
+ // 'achieved' => $achievedAmount,
+ // 'remaining' => $remainingAmount,
+ // 'percent' => $achievementPercent,
+ // 'acts' => $activitySummary,
+ // 'lead_count' => $myLeadsCount,
+ // 'upcoming' => $upcomingActivities,
+ // 'recent_leads' => $recentLeads,
+ // 'fin_years' => $fin_years,
+ // 'display_fin_years' => format_financial_year($current_fin_year),
+ // 'user_name' => get_session_userdata()->first_namee ?? '',
+ // 'tab_name' => "Sales Dashboard",
+ // 'page_name' => "Sales Dashboard",
+ // 'splits' => [],
+ // 'activity_breakdown'=> [],
+ // ];
+
+
+
+ // // dd($data);
+
+ // // return view('sales/my_dashboard_view', $data);
+ // $this->loadLayout('sales/sales_manager_level_dashboard', $data);
+
+ // } catch (\Exception $e) {
+ // 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;
+ // }
public function addCalenderEvent($input)
{
@@ -1102,9 +1964,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/TestingController.php b/app/Controllers/TestingController.php
index daae4293..c83fe172 100644
--- a/app/Controllers/TestingController.php
+++ b/app/Controllers/TestingController.php
@@ -1097,10 +1097,12 @@ class TestingController extends BaseController
'dashboard' => $database_id
],
'exp' => time() + (10 * 60), // 10 minutes
- 'params' => (object)[]
+ 'params' => (object) ['client_policy' => $policy_id ], // MUST be object for Metabase
+
];
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
+ // dd($token);
if ($this->request->getGet('api') == 1) {
return $this->respond([
diff --git a/app/Controllers/TicketController.php b/app/Controllers/TicketController.php
index 01109672..c3d5e86e 100644
--- a/app/Controllers/TicketController.php
+++ b/app/Controllers/TicketController.php
@@ -959,6 +959,8 @@ class TicketController extends BaseController
// Redirect or show a 404 to prevent "Undefined array key" errors
return redirect()->to(base_url('ticket/list'))->with('error', 'Ticket not found');
}
+
+ $ticket_data['is_tpa_api_service_enabled'] = $this->ticketMasterModel->isTpaApiServiceEnabled($ticket_id);
$ticket_data = $this->formatDateForClaim($ticket_data, 'd/m/Y');
@@ -1173,9 +1175,13 @@ class TicketController extends BaseController
'regex_match' => 'Policy Number can only contain letters, numbers, spaces, hyphens(-) underscores(_), and slashes(/).'
]
],
- 'tpa_no' => ['label' => 'TPA ID','rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9]+$/]','errors' => [
- 'regex_match' => 'TPA ID can only contain letters and numbers.'
- ]],
+ 'tpa_no' => [
+ 'label' => 'TPA ID',
+ 'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]',
+ 'errors' => [
+ 'regex_match' => 'TPA ID can only contain letters, numbers, and /.'
+ ]
+ ],
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
'errors' => [
'required' => 'Mobile number is required',
@@ -1522,9 +1528,13 @@ class TicketController extends BaseController
'regex_match' => 'Policy Number can only contain letters, numbers, spaces, hyphens(-) underscores(_), and slashes(/).'
]
],
- 'tpa_no' => ['label' => 'TPA ID','rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9]+$/]','errors' => [
- 'regex_match' => 'TPA ID can only contain letters and numbers.'
- ]],
+ 'tpa_no' => [
+ 'label' => 'TPA ID',
+ 'rules' => 'permit_empty|regex_match[/^[a-zA-Z0-9\/]+$/]',
+ 'errors' => [
+ 'regex_match' => 'TPA ID can only contain letters, numbers, and /.'
+ ]
+ ],
'emp_mobile' => ['label' => 'Employee Mobile No','rules' => 'required|numeric|exact_length[10]',
'errors' => [
'required' => 'Mobile number is required',
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 3836cb4b..d4674302 100644
--- a/app/Controllers/VidalApiController.php
+++ b/app/Controllers/VidalApiController.php
@@ -164,7 +164,7 @@ class VidalApiController extends BaseController
if (count($data) && $data['filePath'] == null) {
log_message('error', "VIDAL - Claim Push | Submit claim failed - Claim or File Missing");
- return $this->response->setJSON(['status' => false,'message' => 'Claim or File Missing', ]);
+ return ['status' => false, 'message' => 'Claim Push FAILED | Claim or File Missing'];
}
$filePath = $data['filePath'] ?? '';
@@ -271,7 +271,7 @@ class VidalApiController extends BaseController
$this->db->table('ticket_master')
->where('id',$claimId)
->update([ 'tpa_push_response' => json_encode($response) ]);
- return;
+ return ['status' => false, 'message' => 'Claim Push FAILED | API call failed'];
}
// return $this->response->setJSON($response);
@@ -289,16 +289,17 @@ class VidalApiController extends BaseController
->where('id',$claimId)
->update([ 'tpa_claim_push_reference_no' => $claimInwardNO , 'tpa_claim_id' => $claimNO , 'claim_number' => $claimNO ]);
- return;
+ return ['status' => true, 'message' => 'Claim Push SUCCESS'];
} else {
log_message('error', 'VIDAL - Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
- return;
+ return ['status' => false, 'message' => 'Claim Push API SUCCESS BUT claimNO,claimInwardNO EMPTY'];
}
}else{
log_message('error', 'VIDAL - Claim Push API FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
- return;
+ return ['status' => false, 'message' => 'Claim Push API FAILED'];
}
+
}
function getWellnessSSORedirectUrl($email = 'test@getvisitapp.com')
diff --git a/app/Helpers/excel_util_helper.php b/app/Helpers/excel_util_helper.php
index 5b5d6b7b..9419d6e1 100755
--- a/app/Helpers/excel_util_helper.php
+++ b/app/Helpers/excel_util_helper.php
@@ -3023,6 +3023,9 @@ if (!function_exists('validate_excel_value')) {
case 'vehicle':
return validate_indian_vehicle_number($value);
+ case 'positive_number':
+ return validate_positive_number_value($value);
+
default:
return [
'status' => true,
@@ -3066,6 +3069,24 @@ if (!function_exists('validate_mobile_value')) {
}
}
+if (!function_exists('validate_positive_number_value')) {
+ function validate_positive_number_value($value)
+ {
+ if ($value === "" || $value === null) {
+ return ['status' => true, 'error' => null];
+ }
+
+ if (is_numeric($value) && $value >= 0) {
+ return ['status' => true, 'error' => null];
+ }
+
+ return [
+ 'status' => false,
+ 'error' => "Value must be a positive number"
+ ];
+ }
+}
+
if (!function_exists('validate_email_value')) {
function validate_email_value($value)
{
diff --git a/app/Libraries/MyGoogleDrive.php b/app/Libraries/MyGoogleDrive.php
index 566098eb..2fea0f9e 100644
--- a/app/Libraries/MyGoogleDrive.php
+++ b/app/Libraries/MyGoogleDrive.php
@@ -16,7 +16,7 @@ class MyGoogleDrive
{
$this->myLogger = \Config\Services::mylogger();
$this->client = new Google_Client();
- $this->client->setAuthConfig(ROOTPATH . 'nhance-app-google-drive.json'); // App credentials
+ $this->client->setAuthConfig(ROOTPATH . 'nhance-ee8d1-e3c5269b1ec7.json'); // App credentials
// putenv('GOOGLE_APPLICATION_CREDENTIALS=' . ROOTPATH . 'nhance-app-google-drive.json');
$this->client->useApplicationDefaultCredentials();
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..3ccbcadd 100644
--- a/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php
+++ b/app/Libraries/TPAClaimsImportServices/BaseTpaClaimImportService.php
@@ -9,17 +9,30 @@ use App\Models\TicketMasterModel;
use App\Models\EmployeeModel;
use App\Models\ClaimDumpFileModel;
use App\Models\ClaimsDumpFhplModel;
+use App\Models\ClientPolicyModel;
+
use RuntimeException;
abstract class BaseTpaClaimImportService
{
protected BaseConnection $db;
protected $claimDumpFileModel;
+ protected $clientPolicyModel;
+ protected $policyNumberMapping;
public function __construct()
{
$this->db = db_connect();
$this->claimDumpFileModel = new ClaimDumpFileModel();
+ $this->clientPolicyModel = new ClientPolicyModel();
+ $this->policyNumberMapping = [
+ (int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'Insurer Policy Number',
+ (int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'Policy Number',
+ (int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'policy_no',
+ (int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'Policy No',
+ (int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'Policy Number',
+ (int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'POLICY_NO',
+ ];
}
/**
@@ -33,6 +46,8 @@ abstract class BaseTpaClaimImportService
try {
$fileData = $this->claimDumpFileModel->where('id', $fileId)->first();
+ $client_policy_data = $this->clientPolicyModel->where('id', $fileData['client_policy_id'])->first();
+
// Determine sheet name logic...
if (env('FHPL_PRIMARY_KEY_CONSTANT') == $fileData['tpa_id']) {
$rows = $this->readExcelBySheetName($filePath, 'Claims&Preauth');
@@ -47,6 +62,18 @@ abstract class BaseTpaClaimImportService
return ['status' => false, 'message' => 'Excel file contains no data or wrong file upload'];
}
+ if(isset($this->policyNumberMapping[$fileData['tpa_id']]) && !empty($this->policyNumberMapping[$fileData['tpa_id']])){
+ $policy_number_column = $this->policyNumberMapping[$fileData['tpa_id']];
+ }else{
+ $policy_number_column = 'policy_no';
+ }
+
+
+ if($client_policy_data['policy_no'] != ($rows[0][$policy_number_column] ?? '')){
+ $this->db->transRollback();
+ return ['status' => false, 'message' => 'Policy number mismatch in the file and in the system'];
+ }
+
$tpaInsertData = $this->mapTPAData($rows, $fileId);
if (empty($tpaInsertData)) {
@@ -81,7 +108,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 +128,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 +361,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 +392,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/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 @@
+= '$startDate' ";
$conditions .= " AND pcsd.pt_policy_issue_date <= '$endDate' ";
+ } else if ($date_type === "created_at") {
+ $conditions .= " AND pt.created_at <= '$endDate' ";
} else {
$conditions .= " AND pt.$date_type >= '$startDate' ";
$conditions .= " AND pt.$date_type <= '$endDate' ";
diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php
index 8d82097f..a51713dd 100644
--- a/app/Models/SalesActivityModel.php
+++ b/app/Models/SalesActivityModel.php
@@ -25,6 +25,7 @@ class SalesActivityModel extends Model
'completion_notes',
'completed_date',
'parent_activity_id',
+ 'additional_assigned_ids',
'created_by',
'updated_by'
];
@@ -38,7 +39,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',
@@ -64,8 +65,14 @@ class SalesActivityModel extends Model
*/
public function getActivitiesByLead($leadId, $status = null)
{
- $builder = $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name')
- ->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left')
+ // Convert the INT id to a string, then wrap it in JSON quotes to match ["5", "11"]
+ $subQuery = "(SELECT GROUP_CONCAT(up2.first_name SEPARATOR ', ')
+ FROM user_profiles up2
+ WHERE JSON_CONTAINS(sales_activities.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR)))
+ ) as additional_assigned_names";
+
+ $builder = $this->select("sales_activities.*, up1.first_name as assigned_to_name, $subQuery")
+ ->join('user_profiles as up1', 'up1.id = sales_activities.assigned_to', 'left')
->where('sales_activities.lead_id', $leadId);
if ($status) {
@@ -78,7 +85,7 @@ class SalesActivityModel extends Model
/**
* Get all sales_activities with filters
*/
- public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0)
+ public function getActivitiesWithFiltersOLD($filters = [], $limit = 10, $offset = 0)
{
$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')
@@ -102,7 +109,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);
}
@@ -139,6 +146,90 @@ class SalesActivityModel extends Model
// ];
}
+ public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0)
+ {
+ // 1. Initial Selection
+ $builder = $this->select("
+ sales_activities.*,
+ sales_actual_leads.company_name,
+ up1.first_name as assigned_to_name,
+ GROUP_CONCAT(DISTINCT up2.first_name SEPARATOR ', ') as additional_assigned_names
+ ")
+ ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left')
+ ->join('user_profiles as up1', 'up1.id = sales_activities.assigned_to', 'left');
+
+ // 2. The JSON Join for additional names
+ // Only attempts join if the string looks like a JSON array
+ $builder->join('user_profiles as up2', "
+ sales_activities.additional_assigned_ids IS NOT NULL
+ AND sales_activities.additional_assigned_ids != ''
+ AND sales_activities.additional_assigned_ids != '[]'
+ AND JSON_VALID(sales_activities.additional_assigned_ids)
+ AND JSON_CONTAINS(sales_activities.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR)))
+ ", 'left');
+
+ // 3. Apply Filters
+ if (!empty($filters['status'])) {
+ $builder->where('sales_activities.status', $filters['status']);
+ }
+
+ if (!empty($filters['activity_type'])) {
+ $builder->where('sales_activities.activity_type', $filters['activity_type']);
+ }
+
+ if (!empty($filters['assigned_to'])) {
+ $assignedToIds = is_array($filters['assigned_to']) ? $filters['assigned_to'] : explode(',', $filters['assigned_to']);
+ $builder->whereIn('sales_activities.assigned_to', $assignedToIds);
+ }
+
+ if (!empty($filters['search'])) {
+ $builder->groupStart()
+ ->like('sales_actual_leads.company_name', $filters['search'])
+ ->orLike('up1.first_name', $filters['search'])
+ ->groupEnd();
+ }
+
+ // 4. Grouping & Ordering
+ $builder->groupBy('sales_activities.activity_id');
+ $builder->orderBy('sales_activities.created_at', 'DESC');
+
+ // 5. Calculate Counts (using a clean builder to avoid the syntax error)
+ $counts = $this->getActivityStatusCounts($filters);
+
+ // 6. Get Data and Total
+ // Use true for countAllResults to get an accurate count of grouped rows
+ $totalCountQuery = clone $builder;
+ $total = $totalCountQuery->countAllResults(false);
+
+ $data = $builder->findAll($limit, $offset);
+
+ return [
+ 'data' => $data,
+ 'total' => $total,
+ 'counts' => $counts
+ ];
+ }
+
+ /**
+ * Helper function to get counts without breaking the main query syntax
+ */
+ private function getActivityStatusCounts($filters)
+ {
+ $validStatuses = ['pending', 'completed'];
+ $counts = ['all' => 0, 'pending' => 0, 'completed' => 0];
+
+ foreach ($validStatuses as $status) {
+ $query = $this->db->table('sales_activities')->where('status', $status);
+ if (!empty($filters['assigned_to'])) {
+ $ids = is_array($filters['assigned_to']) ? $filters['assigned_to'] : explode(',', $filters['assigned_to']);
+ $query->whereIn('assigned_to', $ids);
+ }
+ $counts[$status] = $query->countAllResults();
+ }
+ $counts['all'] = $counts['pending'] + $counts['completed'];
+ return $counts;
+ }
+
/**
* Complete an activity
*/
diff --git a/app/Models/SalesActualLeadModel.php b/app/Models/SalesActualLeadModel.php
index 22db6a2a..a5ca1026 100644
--- a/app/Models/SalesActualLeadModel.php
+++ b/app/Models/SalesActualLeadModel.php
@@ -123,7 +123,38 @@ class SalesActualLeadModel extends Model
$data = $this->findAll($limit, $offset);
- return ['data' => $data,'total' => $total];
+ $counts = $this->getLeadStatusCounts($filters);
+
+ return ['data' => $data,'total' => $total,'counts' => $counts];
+ }
+
+ /**
+ * Helper function to get counts without breaking the main query syntax
+ */
+ private function getLeadStatusCounts($filters)
+ {
+
+ $validStatuses = ['New', 'Potential', 'Prospects', 'Not a Prospects'];
+ $counts = ['all' => 0, 'New' => 0, 'Potential' => 0, 'Prospects' => 0, 'Not a Prospects' => 0];
+
+ foreach ($validStatuses as $status) {
+ $countQuery = $this->db->table('sales_actual_leads')
+ ->whereIn('status', $validStatuses);
+
+ // Apply assigned_to filter to counts too
+ if (!empty($filters['assigned_to'])) {
+ $assignedToIds = is_array($filters['assigned_to'])
+ ? $filters['assigned_to']
+ : explode(',', $filters['assigned_to']);
+ $countQuery->whereIn('assigned_to', $assignedToIds);
+ }
+
+ $counts[$status] = $countQuery->where('status', $status)->countAllResults();
+ }
+
+ $counts['all'] = array_sum($counts);
+
+ return $counts;
}
/**
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 14101188..13682edb 100644
--- a/app/Models/TicketMasterModel.php
+++ b/app/Models/TicketMasterModel.php
@@ -1218,6 +1218,21 @@ class TicketMasterModel extends Model
}
+ public function isTpaApiServiceEnabled($ticket_id)
+ {
+ $result = $this->db->table('ticket_master tm')
+ ->select('tas.*')
+ ->join('client_policy cp', 'tm.client_policy_id = cp.id')
+ ->join('tpa_api_services tas', 'cp.tpa_id = tas.tpa_id')
+ ->where('tm.id', $ticket_id)
+ ->where('tm.is_active', 1)
+ ->where('cp.is_active', 1)
+ ->where('tas.is_active', 1)
+ ->get()->getRowArray();
+
+ return count($result ?? []) > 0 ? true : false; // true if any API service is enabled, false if no API service is enabled
+ }
+
// -----------------------------------------------------------------------------------------------------
}
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 @@
@@ -219,6 +298,20 @@
let policyListByClient = null;
let client_list = null;
+ $(function () {
+ $('[data-toggle="tooltip"]').tooltip();
+ });
+
+ document.querySelectorAll('.custom-tooltip').forEach(function(tooltip) {
+ tooltip.addEventListener('mouseenter', function() {
+ var icon = this.querySelector('.info-icon');
+ var tip = this.querySelector('.tooltiptext');
+ var rect = icon.getBoundingClientRect();
+ tip.style.left = rect.left + 'px';
+ tip.style.top = (rect.top - tip.offsetHeight - 10) + 'px';
+ });
+ });
+
$(document).ready(function() {
getClientAndBranchAndPolicy();
@@ -232,10 +325,17 @@
let client_id = $('#client_id').val() ?? null;
let client_policy_id = $('#client_policy_id').val() ?? null;
+ let tpa_id = $('#tpa_id').val() ?? null;
if(client_id){
+
if(!client_policy_id){
- toastr.warning('Please select the client policy', 'Warning');
+ toastr.warning('Please select the client policy', 'WARNING');
+ return;
+ }
+
+ if(!tpa_id){
+ toastr.warning('TPA is required', 'WARNING');
return;
}
}
@@ -251,9 +351,15 @@
'application/vnd.oasis.opendocument.spreadsheet'
];
+ if (!fileInput.files.length) {
+ toastr.warning('Please select an Excel file', 'WARNING');
+ return false;
+ }
+
var selectedFile = fileInput.files[0];
+
if (!allowedTypes.includes(selectedFile.type)) {
- alert('Please upload a valid Excel file (.xlsx, .xls, .ods)');
+ toastr.warning('Please upload a valid Excel file (.xlsx, .xls, .ods)', 'WARNING');
return false;
}
@@ -500,6 +606,70 @@
}
+ function downloadTpaErrorFile(file_id) {
+ if (!file_id) {
+ toastr.error('Invalid file reference for error download', 'Error');
+ return;
+ }
+
+ var url = '= base_url("util/getTpaClaimDumpErrorData"); ?>/' + file_id;
+
+ $.ajax({
+ url: url,
+ type: 'GET',
+ xhrFields: { responseType: 'blob' },
+ success: function (data, status, xhr) {
+ var disposition = xhr.getResponseHeader('Content-Disposition') || '';
+ var contentType = xhr.getResponseHeader('Content-Type') || '';
+
+ // If server sent JSON (error), parse it and show via Toastr
+ if (contentType.indexOf('application/json') !== -1) {
+ var reader = new FileReader();
+ reader.onload = function () {
+ try {
+ var json = JSON.parse(reader.result);
+ var msg = json.message || 'Unable to download error file.';
+ toastr.error(msg, 'Error');
+ } catch (e) {
+ toastr.error('Unable to download error file.', 'Error');
+ }
+ };
+ reader.readAsText(data);
+ return;
+ }
+
+ // Otherwise assume it is Excel and trigger a download
+ var filename = 'tpa_claim_dump_errors_' + file_id + '.xlsx';
+ var match = /filename="?([^"]+)"?/.exec(disposition);
+ if (match && match[1]) {
+ filename = match[1];
+ }
+
+ var blob = new Blob([data], {
+ type: contentType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ });
+ var link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ toastr.success('Error file download started', 'Success');
+ },
+ error: function (xhr) {
+ var msg = 'Unable to download error file.';
+ try {
+ var json = JSON.parse(xhr.responseText);
+ if (json.message) {
+ msg = json.message;
+ }
+ } catch (e) {}
+ toastr.error(msg, 'Error');
+ }
+ });
+ }
+
$('.close').click(function(){
$('#modal_body').empty()
let html = `
`
diff --git a/app/Views/daily_report_email_template.php b/app/Views/daily_report_email_template.php
new file mode 100644
index 00000000..0ba7c8ff
--- /dev/null
+++ b/app/Views/daily_report_email_template.php
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
Date: = $today ?>
+
+
+
Inception & Endorsement (File Upload)
+
+
+
Total Inception
+
= $total_inception_count ?>
+
Employee file uploads marked as inception
+
+
+
Total Endorsement
+
= $total_endorsement_count ?>
+
All non‑inception successful uploads
+
+
+
+
+
Employee Enrollment
+
+
+
Draft / Enrolled
+
= $total_employee_draft_count ?? 0 ?> / = $total_employee_enrolled_count ?? 0 ?>
+
Employees in draft / enrolled status for the day
+
+
+
Open for Enrollment Policies
+
= $total_open_for_enrollemnt_policy_count ?? 0 ?>
+
Policies currently open for enrollment
+
+
+
+
+
TPA & Insurer Batch Summary
+
+
+
TPA (Inception / Endorsement)
+
= $total_tpa_incetion_count ?> / = $total_tpa_endorsement_count ?>
+
Successful TPA events
+
+
+
Insurer (Inception / Endorsement)
+
= $total_insurer_incetion_count ?> / = $total_insurer_endorsement_count ?>
+
Successful insurer events
+
+
+
+
+
Sales Funnel & Leads
+
+
+
Opportunities & Won
+
= $total_opportunity_count ?> / = $total_placement_count ?>
+
Total opportunities created vs converted
+
+
+
RFQ (Created / Sent to Insurer)
+
= $total_rfq_created_count ?> / = $total_rfq_insurer_send_count ?>
+
Movement from opportunity to RFQ
+
+
+
QCR (Created / Sent to Client)
+
= $total_qcr_created_count ?> / = $total_qcr_client_send_count ?>
+
Quotes prepared and shared
+
+
+
Total Leads & Activities
+
= $total_lead_count ?> / = $total_activity_count ?>
+
Lead entries and logged touchpoints
+
+
+
+
+
BDS Policy Transactions
+
+
+
Total BDS Transactions
+
= $total_bds_count ?>
+
All policy transactions processed
+
+
+
BDS (Policy / Endorsement)
+
= $total_bds_policy_wise_count ?> / = $total_bds_endorsement_wise_count ?>
+
Split of inceptions vs endorsements
+
+
+
+
+
Claims
+
+
+
Total Claims Registered for the day
+
= $total_claim_count ?>
+
+
+
+
+
+
BDS by Policy Type
+
+
+
+
+ Policy Type
+ Count
+
+
+
+
+
+
+
+ = esc($row['policy_type']) ?>
+
+ = $row['count'] ?>
+
+
+
+
+
+ No BDS activity recorded for today.
+
+
+
+
+
+
+
+
+
Claim Status Breakdown
+
+ The total claims received so far are listed ticket type-wise.
+
+
+ $total_gmc_status_wise_claim_count ?? [],
+ 'GPA Claims' => $total_gpa_status_wise_claim_count ?? [],
+ 'EDLI Claims' => $total_edli_status_wise_claim_count ?? [],
+ 'GTLI Claims' => $total_gtli_status_wise_claim_count ?? [],
+ ];
+ $hasAnyClaimData = false;
+ ?>
+
+ $rows): ?>
+
+
+
= esc($sectionTitle) ?>
+
+
+
+
+ Status
+ Count
+
+
+
+
+
+
+ = esc($row['claim_status']) ?>
+
+ = $row['count'] ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/expense_list.php b/app/Views/expense_list.php
new file mode 100644
index 00000000..2dedb059
--- /dev/null
+++ b/app/Views/expense_list.php
@@ -0,0 +1,586 @@
+
+
+
+
+
+
+
+
+
+
+
+ S.No
+ Client
+ Policy No
+ Description
+ Approved By
+ Amount
+ Expense Date
+ Action
+
+
+
+
+ $row) : ?>
+
+ = $index + 1; ?>
+
+ = esc($row['client_name'] ?? ''); ?>
+ = ! empty($row['short_name']) ? ' (' . esc($row['short_name']) . ')' : ''; ?>
+
+ = esc($row['policy_no'] ?? ''); ?>
+ = esc($row['description'] ?? ''); ?>
+ = esc($row['approved_by_name'] ?? ''); ?>
+ = number_format((float) ($row['amount'] ?? 0), 2); ?>
+
+
+ = date('d-m-Y', strtotime($row['expense_date'])); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/Views/layout/header.php b/app/Views/layout/header.php
index 224a7bd8..0086c4f5 100755
--- a/app/Views/layout/header.php
+++ b/app/Views/layout/header.php
@@ -1876,6 +1876,17 @@
+
+
+
+
+ /assets/images/expense_icon.png" alt="Logo" height="24">
+ Expense
+
+
+
+
@@ -2106,7 +2117,7 @@
-->
- Team Targets
+ Sales Team Targets
diff --git a/app/Views/leads_dash.php b/app/Views/leads_dash.php
index 0833c740..7fb07fb6 100644
--- a/app/Views/leads_dash.php
+++ b/app/Views/leads_dash.php
@@ -347,7 +347,7 @@ $isActive = get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID, user_
if (type == 1 && leadType != null && leadType == 1) {
$('.leadStatusTitle_1').show();
- $('#main_tile').text(" Leads");
+ $('#main_tile').text("Opportunities");
}
if (type == 1 && leadType != null && leadType == 2) {
diff --git a/app/Views/leads_form.php b/app/Views/leads_form.php
index eeef3d1b..089bff40 100644
--- a/app/Views/leads_form.php
+++ b/app/Views/leads_form.php
@@ -401,6 +401,12 @@
+
@@ -368,7 +409,7 @@
@@ -401,18 +442,45 @@
\ No newline at end of file
diff --git a/app/Views/sales/branch_level_dashboard_view_1mar.php b/app/Views/sales/branch_level_dashboard_view_1mar.php
new file mode 100644
index 00000000..efc8d3cb
--- /dev/null
+++ b/app/Views/sales/branch_level_dashboard_view_1mar.php
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+
+
+
Pending Activities
+
+
+
+
+
Completed Activities
+
+
+
+
+
+
+
+
+
+
+
+ Company
+ Activity & Assigned
+ Date
+ Status
+
+
+
+
+
+ '📞',
+ 'Email' => '✉️',
+ 'Meeting' => '📅',
+ 'Visit' => '🚗',
+ 'Demo' => '🖥️',
+ 'Share Docs' => '📄',
+ 'To Do' => '✓'
+ ];
+ $icon = $activityIcons[$a['activity_type']] ?? '📌';
+ $statusClass = strtolower(str_replace(' ', '-', $a['status']));
+ $formattedscheduledDate = date('M d, Y, h:i A', strtotime($a['scheduled_date']));
+ ?>
+
+ = esc($a['company_name']) ?>
+
+
+ = $icon ?> = strtoupper(esc($a['activity_type'])) ?>
+
+
+ = esc($a['assigned_to_name'] ?? 'ID: ' . $a['assigned_to']) ?>
+
+
+
+ = $formattedscheduledDate ?>
+ = ucfirst(esc($a['status'])) ?>
+
+
+
+
+
+
+
✓
No activities found
+
+
+
+
+
+
+
+
+
+
+
+
Sales Team Performance
+
+
{$firstLetter}
+
+
{$fullName}
+
{$role}
+
+
+
{$totalActs} acts
+
{$doneActs} done
+
+
";
+ } ?>
+
+
+
+
Activity Breakdown
+
+ ['color' => '#ff6b35', 'icon' => '📞'],
+ 'Email' => ['color' => '#48bb78', 'icon' => '✉️'],
+ 'Meeting' => ['color' => '#4299e1', 'icon' => '📅'],
+ 'Visit' => ['color' => '#ecc94b', 'icon' => '🚗'],
+ 'Demo' => ['color' => '#9f7aea', 'icon' => '🖥️'],
+ 'Share Docs' => ['color' => '#ed8936', 'icon' => '📄'],
+ 'To Do' => ['color' => '#718096', 'icon' => '✓'],
+ ];
+ ?>
+
+
+
+
+
+
+
+ = $icon ?> = esc($type) ?>
+
+ = $item['percentage'] ?>%
+
+
+
+
+
✓
No activities found
+
+
+
+
+
+
+
+
+
+
+
+ Company
+ Status
+ Assigned To
+ Activities
+ Opportunities
+
+
+
+
+
+
+
+ = esc($lead['company_name']) ?>
+ = esc($lead['status']) ?>
+ = esc($lead['assigned_to'] ?? 'Unassigned') ?>
+ = $lead['activities'] ?>
+ = $lead['opportunities'] ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 cfca7b7b..dcf9906d 100644
--- a/app/Views/sales/sales_manager_level_dashboard.php
+++ b/app/Views/sales/sales_manager_level_dashboard.php
@@ -74,7 +74,7 @@
Welcome back, = $user_name ?>
-->
-
+
= $year; ?>
@@ -133,8 +133,11 @@
✓ Mark as completed
-->
+
+
+
'📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share' => '📄', 'Todo' => '✓' ];
+ $activityIcons = [ 'Call' => '📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share Docs' => '📄', 'To Do' => '✓' ];
$icon = $activityIcons[$u['activity_type']] ?? '📌';
$formattedscheduledDate = date('M d, Y, h:i A', strtotime($u['scheduled_date']));
?>
@@ -160,11 +163,19 @@
+
+
+
+ No Upcoming Activities for this Financial Year.
+
+
+
My Recent Leads
+
+
+
+
+
+
+
+ Sales Manager
+ Action
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Financial Year
+ Amount
+ Action
+
+
+
+ No targets added yet.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/sales/tracker_view.php b/app/Views/sales/tracker_view.php
index 7e8c4a92..2f56569d 100644
--- a/app/Views/sales/tracker_view.php
+++ b/app/Views/sales/tracker_view.php
@@ -7,6 +7,8 @@
.btn-primary { background: #02a8b5; 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: #02a8b5; transform: translateY(-1px); }
+ .btn-close { background: none; border: none; font-size: 22px; cursor: pointer; color: #888; width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: background .2s; }
+ .btn-close:hover { background: #f0f0f0; color: #333; }
/* Filter Tabs */
.filter-tabs { display: flex; gap: 10px; padding: 20px 30px; }
@@ -24,6 +26,11 @@
.status-potential { background: #fff3e0; color: #f57c00; }
.status-prospects { background: #e8f5e9; color: #388e3c; }
.status-not-a-prospects { background: #ffebee; color: #d32f2f; }
+ .status-unknown { background: #000; color: #fff; }
+ .status-text-unknown { color: #000; font-weight: bold; }
+ .status-text-lost { color: #d32f2f; font-weight: bold; }
+ .status-text-won { color: #388e3c; font-weight: bold; }
+
/* 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; }
@@ -72,6 +79,9 @@
.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; }
+ .lost-reason { margin-bottom: 8px; color: #c0392b; /* soft red for lost */ }
+ .footer-divider { border-top: 1px dashed #ddd; margin: 8px 0; }
+ .notes { color: #555; }
/* 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; }
@@ -105,7 +115,20 @@
border-color: #ff4d4d !important;
}
-
+.select2-container--default
+.select2-selection--multiple
+.select2-selection__choice {
+ background-color: #02a8b5 !important;
+ border: none !important;
+ border-color: #fff !important;
+ color: #fff !important;
+}
+.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+ color: #fff !important;
+}
+.modal .select2-container--default .select2-selection--multiple {
+ background-color: #fff !important;
+}
@@ -137,7 +160,7 @@
+ placeholder="Search leads..." onkeyup="fetchLeads(false)" style="width: 300px !important;">
+ Add Lead
@@ -166,7 +189,7 @@
@@ -203,8 +226,8 @@
Assign To*
Select User
-
- = $user['first_name'] ?>
+
+ = $sm['first_name'] ?>
@@ -228,7 +251,7 @@
Lead Detail
-
×
+
×
@@ -270,7 +293,7 @@
@@ -285,8 +308,8 @@
📅 Meeting
🚗 Visit
🖥️ Demo
- 📄 Share Docs
- ✓ To Do
+ 📄 Share Docs
+ ✓ To Do
+
+ Additional Assigner
+
+
+ = $sm['first_name'] ?>
+
+
+