MERGE_TEST_NONEB_FLOW&OTRS
This commit is contained in:
commit
2277546932
10
.env.sample
10
.env.sample
@ -129,4 +129,12 @@ HEALTH_INDIA_TOKEN_URL =
|
||||
HEALTH_INDIA_USERNAME =
|
||||
HEALTH_INDIA_PASSWORD =
|
||||
|
||||
HEALTH_INDIA_PRIMARY_KEY_CONSTANT =
|
||||
HEALTH_INDIA_PRIMARY_KEY_CONSTANT =
|
||||
|
||||
#For sending mail for leads RFQ/QCR
|
||||
LEAD_INSURER_FROM_MAIL_ID =
|
||||
LEAD_CLIENT_FROM_MAIL_ID =
|
||||
|
||||
# BDS Daily Report Emails Configuration
|
||||
bds.dailyReportEmails =
|
||||
|
||||
|
||||
51
app/Config/RfqConfig.php
Normal file
51
app/Config/RfqConfig.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class RfqConfig extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Google Drive parent folder IDs for RFQ and QCR sheets.
|
||||
*/
|
||||
public string $rfqParentFolderId = '1MnYh5PTPDlc9mYMGsjmTv02y8EZ-BQf1';
|
||||
public string $qcrParentFolderId = '1MnYh5PTPDlc9mYMGsjmTv02y8EZ-BQf1';
|
||||
|
||||
/**
|
||||
* Default permissions for created sheets.
|
||||
* Copied from GoogleSheetController::$config.
|
||||
*/
|
||||
public array $permissions = [
|
||||
'editors' => [
|
||||
'vitvelz@gmail.com',
|
||||
'velz1990@gmail.com',
|
||||
'venkateshraman786@gmail.com',
|
||||
],
|
||||
'viewers' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Default protections for RFQ sheets.
|
||||
* Copied from GoogleSheetController::$config.
|
||||
*/
|
||||
public array $protections = [
|
||||
[
|
||||
'range' => 'RFQ Page!B12:C12',
|
||||
'users' => [
|
||||
'velz1990@gmail.com',
|
||||
'vitvelz@gmail.com',
|
||||
'firebase-adminsdk-mcdfe@nhance-ee8d1.iam.gserviceaccount.com',
|
||||
],
|
||||
'groups' => [],
|
||||
],
|
||||
[
|
||||
'range' => 'Claims Page!A1',
|
||||
'users' => [
|
||||
'velz1990@gmail.com',
|
||||
'venkateshraman786@gmail.com',
|
||||
'firebase-adminsdk-mcdfe@nhance-ee8d1.iam.gserviceaccount.com',
|
||||
],
|
||||
'groups' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -446,11 +446,15 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('generateDemographyDataTable', 'LeadsController::generateDemographyDataTable');
|
||||
$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) {
|
||||
|
||||
@ -498,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) {
|
||||
@ -505,6 +510,8 @@ $routes->group("leads", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->match(['get', 'post'],"list", "LeadsController::viewLeadsList");
|
||||
$routes->post("create", "LeadsController::createLead");
|
||||
$routes->get("list/(:any)", "LeadsController::getLeadDataForEdit/$1");
|
||||
$routes->get("createRfqSheet", "LeadsController::createRfqSheet");
|
||||
$routes->get("mailTemplate", "LeadsController::getLeadMailTemplate");
|
||||
$routes->get("sendMail", "LeadsController::sendMailWithAttachement");
|
||||
$routes->post("sendMail", "LeadsController::sendMailWithAttachement");
|
||||
$routes->get("exportQCRandRFQ/(:any)", "LeadsController::exportQCRandRFQ/$1");
|
||||
@ -518,6 +525,10 @@ $routes->group("rfq", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->post("createQCR", "LeadsController::createQCR");
|
||||
$routes->get("list/(:any)", "LeadsController::viewRFQ/$1");
|
||||
$routes->get("nonEB","LeadsController::rfqNonEB");
|
||||
// Non-EB dedicated RFQ/QCR endpoints (do not alter existing ones)
|
||||
$routes->get("nonEB/rfq/(:any)","LeadsController::viewNonEbRFQFromList/$1");
|
||||
$routes->get("nonEB/qcr/(:any)","LeadsController::viewNonEbQCRFromList/$1");
|
||||
$routes->get("placementData/(:num)", "LeadsController::getPlacementData/$1");
|
||||
});
|
||||
|
||||
|
||||
@ -547,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');
|
||||
|
||||
|
||||
@ -766,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) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -6975,7 +6975,9 @@ class ClientController extends AdminController
|
||||
// $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->tpaClaimDumpImporter(["file_id" => 48]); //vidal`
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 57]); //mediassist
|
||||
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 51]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 50]); //fhpl
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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'];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 = "<p>Please find attached the BDS report up to {$today}.</p>";
|
||||
$message .= "<p>Total records: " . count($reportList) . "</p>";
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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([
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<?php namespace App\Libraries;
|
||||
<?php
|
||||
namespace App\Libraries;
|
||||
|
||||
use Google_Client;
|
||||
use Google_Service_Sheets;
|
||||
use Google_Service_Drive;
|
||||
use Google_Service_Sheets_ValueRange;
|
||||
use Google_Service_Sheets;
|
||||
use Google_Service_Sheets_BatchUpdateSpreadsheetRequest;
|
||||
use Google_Service_Sheets_ValueRange;
|
||||
|
||||
class GoogleSheetLib
|
||||
{
|
||||
@ -12,7 +13,6 @@ class GoogleSheetLib
|
||||
protected Google_Service_Sheets $sheets;
|
||||
protected Google_Service_Drive $drive;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Google_Client();
|
||||
@ -28,7 +28,7 @@ class GoogleSheetLib
|
||||
// Required scopes
|
||||
$this->client->addScope([
|
||||
Google_Service_Drive::DRIVE,
|
||||
Google_Service_Sheets::SPREADSHEETS
|
||||
Google_Service_Sheets::SPREADSHEETS,
|
||||
]);
|
||||
|
||||
// Init services
|
||||
@ -53,7 +53,7 @@ class GoogleSheetLib
|
||||
public function write(string $spreadsheetId, array $values, string $range = 'Sheet1')
|
||||
{
|
||||
$body = new Google_Service_Sheets_ValueRange([
|
||||
'values' => $values
|
||||
'values' => $values,
|
||||
]);
|
||||
|
||||
$this->sheets
|
||||
@ -81,7 +81,6 @@ class GoogleSheetLib
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
|
||||
/* ================= COPY TEMPLATE ================= */
|
||||
|
||||
public function copyTemplate(string $templateId, string $name, string $folderId): string
|
||||
@ -92,10 +91,10 @@ class GoogleSheetLib
|
||||
'name' => $name,
|
||||
'parents' => [$folderId],
|
||||
|
||||
]),[
|
||||
'supportsAllDrives' => true,
|
||||
'fields' => 'id, name, parents'
|
||||
]
|
||||
]), [
|
||||
'supportsAllDrives' => true,
|
||||
'fields' => 'id, name, parents',
|
||||
]
|
||||
);
|
||||
|
||||
return $file->id;
|
||||
@ -116,7 +115,7 @@ class GoogleSheetLib
|
||||
|
||||
private function createPermission(string $fileId, string $email, string $role)
|
||||
{
|
||||
$type = str_starts_with($email, 'group:') ? 'group' : 'user';
|
||||
$type = str_starts_with($email, 'group:') ? 'group' : 'user';
|
||||
$email = str_replace('group:', '', $email);
|
||||
|
||||
$this->drive->permissions->create(
|
||||
@ -124,9 +123,9 @@ class GoogleSheetLib
|
||||
new \Google_Service_Drive_Permission([
|
||||
'type' => $type,
|
||||
'role' => $role,
|
||||
'emailAddress' => $email
|
||||
'emailAddress' => $email,
|
||||
]),
|
||||
['sendNotificationEmail' => false,'supportsAllDrives' => true]
|
||||
['sendNotificationEmail' => false, 'supportsAllDrives' => true]
|
||||
);
|
||||
}
|
||||
|
||||
@ -135,7 +134,7 @@ class GoogleSheetLib
|
||||
public function applyProtectionsold(string $spreadsheetId, array $ranges)
|
||||
{
|
||||
$spreadsheet = $this->sheets->spreadsheets->get($spreadsheetId);
|
||||
$sheetId = $spreadsheet->getSheets()[0]->getProperties()->getSheetId();
|
||||
$sheetId = $spreadsheet->getSheets()[0]->getProperties()->getSheetId();
|
||||
|
||||
$requests = [];
|
||||
|
||||
@ -145,96 +144,95 @@ class GoogleSheetLib
|
||||
$requests[] = [
|
||||
'addProtectedRange' => [
|
||||
'protectedRange' => [
|
||||
'range' => [
|
||||
'sheetId' => $sheetId
|
||||
'range' => [
|
||||
'sheetId' => $sheetId,
|
||||
],
|
||||
'warningOnly' => false
|
||||
]
|
||||
]
|
||||
'warningOnly' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->sheets->spreadsheets->batchUpdate(
|
||||
$spreadsheetId,
|
||||
new Google_Service_Sheets_BatchUpdateSpreadsheetRequest([
|
||||
'requests' => $requests
|
||||
'requests' => $requests,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function applyProtections(string $spreadsheetId, array $protections)
|
||||
{
|
||||
// Fetch spreadsheet metadata
|
||||
$spreadsheet = $this->sheets->spreadsheets->get(
|
||||
$spreadsheetId,
|
||||
['fields' => 'sheets(properties(sheetId,title,gridProperties))']
|
||||
);
|
||||
|
||||
// Map sheet names
|
||||
$sheetMap = [];
|
||||
foreach ($spreadsheet->getSheets() as $sheet) {
|
||||
$props = $sheet->getProperties();
|
||||
$sheetMap[$props->getTitle()] = [
|
||||
'sheetId' => $props->getSheetId(),
|
||||
'rowCount' => $props->getGridProperties()->getRowCount(),
|
||||
'colCount' => $props->getGridProperties()->getColumnCount(),
|
||||
];
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
|
||||
foreach ($protections as $protection) {
|
||||
|
||||
$rangeStr = $protection['range'];
|
||||
|
||||
if (!str_contains($rangeStr, '!')) {
|
||||
throw new \Exception("Invalid range format: {$rangeStr}");
|
||||
}
|
||||
|
||||
[$sheetName, $a1] = explode('!', $rangeStr, 2);
|
||||
|
||||
if (!isset($sheetMap[$sheetName])) {
|
||||
throw new \Exception("Sheet not found: {$sheetName}");
|
||||
}
|
||||
|
||||
$sheetMeta = $sheetMap[$sheetName];
|
||||
|
||||
$gridRange = $this->convertA1ToGridRange(
|
||||
$a1,
|
||||
$sheetMeta['sheetId'],
|
||||
$sheetMeta['rowCount'],
|
||||
$sheetMeta['colCount']
|
||||
{
|
||||
// Fetch spreadsheet metadata
|
||||
$spreadsheet = $this->sheets->spreadsheets->get(
|
||||
$spreadsheetId,
|
||||
['fields' => 'sheets(properties(sheetId,title,gridProperties))']
|
||||
);
|
||||
|
||||
$protectedRange = [
|
||||
'range' => $gridRange,
|
||||
'description' => 'RFQ Protected Area',
|
||||
'warningOnly' => false,
|
||||
'editors' => [
|
||||
'users' => $protection['users'] ?? [],
|
||||
'groups' => $protection['groups'] ?? []
|
||||
]
|
||||
];
|
||||
// Map sheet names
|
||||
$sheetMap = [];
|
||||
foreach ($spreadsheet->getSheets() as $sheet) {
|
||||
$props = $sheet->getProperties();
|
||||
$sheetMap[$props->getTitle()] = [
|
||||
'sheetId' => $props->getSheetId(),
|
||||
'rowCount' => $props->getGridProperties()->getRowCount(),
|
||||
'colCount' => $props->getGridProperties()->getColumnCount(),
|
||||
];
|
||||
}
|
||||
|
||||
$requests[] = [
|
||||
'addProtectedRange' => [
|
||||
'protectedRange' => $protectedRange
|
||||
]
|
||||
];
|
||||
$requests = [];
|
||||
|
||||
foreach ($protections as $protection) {
|
||||
|
||||
$rangeStr = $protection['range'];
|
||||
|
||||
if (! str_contains($rangeStr, '!')) {
|
||||
throw new \Exception("Invalid range format: {$rangeStr}");
|
||||
}
|
||||
|
||||
[$sheetName, $a1] = explode('!', $rangeStr, 2);
|
||||
|
||||
if (! isset($sheetMap[$sheetName])) {
|
||||
throw new \Exception("Sheet not found: {$sheetName}");
|
||||
}
|
||||
|
||||
$sheetMeta = $sheetMap[$sheetName];
|
||||
|
||||
$gridRange = $this->convertA1ToGridRange(
|
||||
$a1,
|
||||
$sheetMeta['sheetId'],
|
||||
$sheetMeta['rowCount'],
|
||||
$sheetMeta['colCount']
|
||||
);
|
||||
|
||||
$protectedRange = [
|
||||
'range' => $gridRange,
|
||||
'description' => 'RFQ Protected Area',
|
||||
'warningOnly' => false,
|
||||
'editors' => [
|
||||
'users' => $protection['users'] ?? [],
|
||||
'groups' => $protection['groups'] ?? [],
|
||||
],
|
||||
];
|
||||
|
||||
$requests[] = [
|
||||
'addProtectedRange' => [
|
||||
'protectedRange' => $protectedRange,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($requests)) {
|
||||
$batch = new \Google_Service_Sheets_BatchUpdateSpreadsheetRequest([
|
||||
'requests' => $requests,
|
||||
]);
|
||||
|
||||
$this->sheets->spreadsheets->batchUpdate($spreadsheetId, $batch);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($requests)) {
|
||||
$batch = new \Google_Service_Sheets_BatchUpdateSpreadsheetRequest([
|
||||
'requests' => $requests
|
||||
]);
|
||||
|
||||
$this->sheets->spreadsheets->batchUpdate($spreadsheetId, $batch);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ================= URL ================= */
|
||||
|
||||
public function sheetUrl(string $sheetId): string
|
||||
@ -242,43 +240,41 @@ class GoogleSheetLib
|
||||
return "https://docs.google.com/spreadsheets/d/{$sheetId}/edit";
|
||||
}
|
||||
|
||||
private function convertA1ToGridRange($a1, $sheetId, $maxRows, $maxCols)
|
||||
{
|
||||
if (preg_match('/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i', $a1, $m)) {
|
||||
|
||||
private function convertA1ToGridRange($a1, $sheetId, $maxRows, $maxCols)
|
||||
{
|
||||
if (preg_match('/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i', $a1, $m)) {
|
||||
$startCol = $this->colToIndex($m[1]);
|
||||
$startRow = intval($m[2]) - 1;
|
||||
|
||||
$startCol = $this->colToIndex($m[1]);
|
||||
$startRow = intval($m[2]) - 1;
|
||||
if (! empty($m[3])) {
|
||||
$endCol = $this->colToIndex($m[3]) + 1;
|
||||
$endRow = intval($m[4]);
|
||||
} else {
|
||||
$endCol = $startCol + 1;
|
||||
$endRow = $startRow + 1;
|
||||
}
|
||||
|
||||
if (!empty($m[3])) {
|
||||
$endCol = $this->colToIndex($m[3]) + 1;
|
||||
$endRow = intval($m[4]);
|
||||
} else {
|
||||
$endCol = $startCol + 1;
|
||||
$endRow = $startRow + 1;
|
||||
return [
|
||||
'sheetId' => $sheetId,
|
||||
'startRowIndex' => $startRow,
|
||||
'endRowIndex' => $endRow,
|
||||
'startColumnIndex' => $startCol,
|
||||
'endColumnIndex' => $endCol,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'sheetId' => $sheetId,
|
||||
'startRowIndex' => $startRow,
|
||||
'endRowIndex' => $endRow,
|
||||
'startColumnIndex' => $startCol,
|
||||
'endColumnIndex' => $endCol
|
||||
];
|
||||
throw new \Exception("Unsupported A1 format: {$a1}");
|
||||
}
|
||||
|
||||
throw new \Exception("Unsupported A1 format: {$a1}");
|
||||
}
|
||||
|
||||
private function colToIndex($letters)
|
||||
{
|
||||
$letters = strtoupper($letters);
|
||||
$index = 0;
|
||||
for ($i = 0; $i < strlen($letters); $i++) {
|
||||
$index = $index * 26 + (ord($letters[$i]) - 64);
|
||||
private function colToIndex($letters)
|
||||
{
|
||||
$letters = strtoupper($letters);
|
||||
$index = 0;
|
||||
for ($i = 0; $i < strlen($letters); $i++) {
|
||||
$index = $index * 26 + (ord($letters[$i]) - 64);
|
||||
}
|
||||
return $index - 1;
|
||||
}
|
||||
return $index - 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class LeadsModel extends Model
|
||||
{
|
||||
protected $table = 'leads';
|
||||
protected $primaryKey = 'id';
|
||||
protected $table = 'leads';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'actual_lead_id',
|
||||
@ -47,6 +46,7 @@ class LeadsModel extends Model
|
||||
'proposel_data',
|
||||
'status',
|
||||
'notes',
|
||||
'lost_reason',
|
||||
'created_at',
|
||||
'created_by',
|
||||
'updated_at',
|
||||
@ -99,7 +99,7 @@ class LeadsModel extends Model
|
||||
'no_of_installment',
|
||||
'is_policy_created',
|
||||
'claim_history',
|
||||
|
||||
|
||||
'total_lives_at_incept',
|
||||
'premium_at_incept',
|
||||
|
||||
@ -108,20 +108,19 @@ class LeadsModel extends Model
|
||||
'quote_received_insurer',
|
||||
'acm_id',
|
||||
'policy_with_correction',
|
||||
'agreed_percentage',
|
||||
'agreed_percentage', 'misc',
|
||||
];
|
||||
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ["checkAndADDCreatedByValue"];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ["checkAndUpdateUpdatedByValue"];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ["checkAndADDCreatedByValue"];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ["checkAndUpdateUpdatedByValue"];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
protected function checkAndADDCreatedByValue(array $data)
|
||||
{
|
||||
@ -160,7 +159,7 @@ class LeadsModel extends Model
|
||||
FROM rfq
|
||||
WHERE type = 2
|
||||
AND is_active = 1 and lead_id = leads.id
|
||||
) AS qcr_count,
|
||||
) AS qcr_count,
|
||||
|
||||
(
|
||||
SELECT COUNT(*) AS rfq_count
|
||||
@ -176,7 +175,7 @@ class LeadsModel extends Model
|
||||
->join('lead_files', 'leads.id = lead_files.lead_id AND lead_files.type = 2 AND lead_files.is_active = 1', 'left')
|
||||
->where('leads.is_active', 1);
|
||||
|
||||
if (!empty($where)) {
|
||||
if (! empty($where)) {
|
||||
$data->where($where);
|
||||
}
|
||||
|
||||
@ -185,7 +184,7 @@ class LeadsModel extends Model
|
||||
|
||||
public function getLeadForInsertClientList($type = null, $client_id = null)
|
||||
{
|
||||
$query = $this->db->table('leads')
|
||||
$query = $this->db->table('leads')
|
||||
->select('leads.*, user_profiles.first_name as user_name')
|
||||
->join('user_profiles', 'leads.created_by = user_profiles.id')
|
||||
->where('leads.is_active', 1)
|
||||
@ -194,8 +193,6 @@ class LeadsModel extends Model
|
||||
->where("(leads.is_client_created = '' OR leads.is_client_created IS NULL)")
|
||||
->where("(leads.is_policy_created = '' OR leads.is_policy_created IS NULL)");
|
||||
|
||||
|
||||
|
||||
if ($type) {
|
||||
$query->where('leads.lead_type', $type);
|
||||
}
|
||||
@ -242,8 +239,8 @@ class LeadsModel extends Model
|
||||
// $builder->select($select);
|
||||
|
||||
// // Auditing subquery
|
||||
// $subquery = "(SELECT pk, MAX(created_at) AS last_claim_status_change
|
||||
// FROM auditing_history
|
||||
// $subquery = "(SELECT pk, MAX(created_at) AS last_claim_status_change
|
||||
// FROM auditing_history
|
||||
// WHERE table_name = 'leads' AND field_name = 'status'
|
||||
// GROUP BY pk)";
|
||||
|
||||
@ -281,7 +278,7 @@ class LeadsModel extends Model
|
||||
// // if (!str_ends_with($key, '_ids') && $key != "won") {
|
||||
// // $total += (int) $value;
|
||||
// // }
|
||||
|
||||
|
||||
// if (!str_ends_with($key, '_ids')) {
|
||||
// $total += (int) $value;
|
||||
// }
|
||||
@ -291,12 +288,10 @@ class LeadsModel extends Model
|
||||
// $lead_data[0]['total'] = $total;
|
||||
// $lead_data[0]['policy_with_correction'] = $policy_with_correction_count;
|
||||
|
||||
|
||||
// // dd($lead_data[0]);
|
||||
// return $lead_data[0];
|
||||
// }
|
||||
|
||||
|
||||
public function getDashData()
|
||||
{
|
||||
// Get all unique statuses
|
||||
@ -321,7 +316,7 @@ class LeadsModel extends Model
|
||||
$selectParts = [];
|
||||
foreach ($statuses as $row) {
|
||||
$status = $row['status'];
|
||||
$alias = strtolower(str_replace(' ', '_', $status));
|
||||
$alias = strtolower(str_replace(' ', '_', $status));
|
||||
|
||||
$selectParts[] = "SUM(CASE WHEN status = '{$status}' THEN 1 ELSE 0 END) AS `{$alias}`";
|
||||
$selectParts[] = "GROUP_CONCAT(CASE WHEN status = '{$status}' THEN id END) AS `{$alias}_ids`";
|
||||
@ -339,19 +334,17 @@ class LeadsModel extends Model
|
||||
// Calculate total
|
||||
$total = 0;
|
||||
foreach ($lead_data as $key => $val) {
|
||||
if (!str_ends_with($key, '_ids')) {
|
||||
if (! str_ends_with($key, '_ids')) {
|
||||
$total += (int) $val;
|
||||
}
|
||||
}
|
||||
|
||||
$lead_data['total'] = $total;
|
||||
$lead_data['policy_with_correction'] = $policy_with_correction_data['count'] ?? 0;
|
||||
$lead_data['total'] = $total;
|
||||
$lead_data['policy_with_correction'] = $policy_with_correction_data['count'] ?? 0;
|
||||
$lead_data['policy_with_correction_ids'] = $policy_with_correction_data['ids'] ?? null;
|
||||
|
||||
// dd($lead_data);
|
||||
return $lead_data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -3098,6 +3098,8 @@
|
||||
if ($date_type === "policy_issue_date") {
|
||||
$conditions .= " AND pcsd.pt_policy_issue_date >= '$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' ";
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PolicyTypeModel extends Model
|
||||
{
|
||||
protected $table = 'policy_type';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
protected $table = 'policy_type';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
"id",
|
||||
"policy_type",
|
||||
"bap",
|
||||
@ -22,6 +21,6 @@ class PolicyTypeModel extends Model
|
||||
"etp",
|
||||
"iep",
|
||||
"itp",
|
||||
"question_json",'itep','etep', 'policy_category',
|
||||
"question_json", 'itep', 'etep', 'policy_category', 'misc',
|
||||
];
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ class SalesActivityModel extends Model
|
||||
'completion_notes',
|
||||
'completed_date',
|
||||
'parent_activity_id',
|
||||
'additional_assigned_ids',
|
||||
'created_by',
|
||||
'updated_by'
|
||||
];
|
||||
@ -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')
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
}
|
||||
|
||||
347
app/Views/daily_report_email_template.php
Normal file
347
app/Views/daily_report_email_template.php
Normal file
@ -0,0 +1,347 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
/* Base */
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f7f6;
|
||||
color: #333;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1f618d, #21618c, #117a65);
|
||||
color: #ffffff;
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.header-subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
.report-date {
|
||||
color: #7f8c8d;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 17px;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
display: inline-block;
|
||||
margin: 18px 0 12px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Stats Grid - stacked for better email support */
|
||||
.stats-grid {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.stat-card {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #f8f9fa;
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3498db;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: #7f8c8d;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
color: #2c3e50;
|
||||
margin-top: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stat-note {
|
||||
font-size: 11px;
|
||||
color: #95a5a6;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
}
|
||||
.status-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.status-table th {
|
||||
background-color: #ecf0f1;
|
||||
text-align: left;
|
||||
padding: 10px 8px;
|
||||
font-size: 13px;
|
||||
border-bottom: 2px solid #bdc3c7;
|
||||
}
|
||||
.status-table td {
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
background: #ecf0f1;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: #f8f9fa;
|
||||
padding: 14px 18px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media only screen and (max-width: 480px) {
|
||||
.content {
|
||||
padding: 14px;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.header-subtitle {
|
||||
font-size: 12px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
.stat-card {
|
||||
min-width: 100%;
|
||||
}
|
||||
.report-date {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="width:100%;max-width:680px;margin:0 auto;background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,0.06);">
|
||||
<div class="header" style="background:#1f618d;color:#ffffff;padding:24px 16px;text-align:center;">
|
||||
<h1>Daily Activity Report</h1>
|
||||
<div class="header-subtitle">
|
||||
Key operations, sales and claim metrics for the day
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content" style="padding:20px;">
|
||||
<div class="report-date" style="color:#7f8c8d;font-size:13px;margin-bottom:16px;text-align:right;">Date: <?= $today ?></div>
|
||||
|
||||
<!-- File Operations -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Inception & Endorsement (File Upload)</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #3498db;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Inception</div>
|
||||
<div class="stat-value"><?= $total_inception_count ?></div>
|
||||
<div class="stat-note">Employee file uploads marked as inception</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #3498db;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Endorsement</div>
|
||||
<div class="stat-value"><?= $total_endorsement_count ?></div>
|
||||
<div class="stat-note">All non‑inception successful uploads</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Employee Enrollment (new data points as cards only) -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Employee Enrollment</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #3498db;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Draft / Enrolled </div>
|
||||
<div class="stat-value"><?= $total_employee_draft_count ?? 0 ?> / <?= $total_employee_enrolled_count ?? 0 ?></div>
|
||||
<div class="stat-note">Employees in draft / enrolled status for the day</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #9b59b6;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Open for Enrollment Policies</div>
|
||||
<div class="stat-value"><?= $total_open_for_enrollemnt_policy_count ?? 0 ?></div>
|
||||
<div class="stat-note">Policies currently open for enrollment</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TPA / Insurer -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">TPA & Insurer Batch Summary</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #e67e22;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">TPA (Inception / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_tpa_incetion_count ?> / <?= $total_tpa_endorsement_count ?></div>
|
||||
<div class="stat-note">Successful TPA events</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #9b59b6;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Insurer (Inception / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_insurer_incetion_count ?> / <?= $total_insurer_endorsement_count ?></div>
|
||||
<div class="stat-note">Successful insurer events</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sales Funnel -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Sales Funnel & Leads</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #27ae60;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Opportunities & Won</div>
|
||||
<div class="stat-value"><?= $total_opportunity_count ?> / <?= $total_placement_count ?></div>
|
||||
<div class="stat-note">Total opportunities created vs converted</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #16a085;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">RFQ (Created / Sent to Insurer)</div>
|
||||
<div class="stat-value"><?= $total_rfq_created_count ?> / <?= $total_rfq_insurer_send_count ?></div>
|
||||
<div class="stat-note">Movement from opportunity to RFQ</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #2980b9;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">QCR (Created / Sent to Client)</div>
|
||||
<div class="stat-value"><?= $total_qcr_created_count ?> / <?= $total_qcr_client_send_count ?></div>
|
||||
<div class="stat-note">Quotes prepared and shared</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #8e44ad;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Leads & Activities</div>
|
||||
<div class="stat-value"><?= $total_lead_count ?> / <?= $total_activity_count ?></div>
|
||||
<div class="stat-note">Lead entries and logged touchpoints</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BDS / Policy Transactions -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">BDS Policy Transactions</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #d35400;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total BDS Transactions</div>
|
||||
<div class="stat-value"><?= $total_bds_count ?></div>
|
||||
<div class="stat-note">All policy transactions processed</div>
|
||||
</div>
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #c0392b;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">BDS (Policy / Endorsement)</div>
|
||||
<div class="stat-value"><?= $total_bds_policy_wise_count ?> / <?= $total_bds_endorsement_wise_count ?></div>
|
||||
<div class="stat-note">Split of inceptions vs endorsements</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Claims -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Claims</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" style="width:100%;display:block;background:#f8f9fa;padding:12px 14px;border-radius:6px;border-left:4px solid #d35400;box-sizing:border-box;margin-bottom:10px;">
|
||||
<div class="stat-label">Total Claims Registered for the day</div>
|
||||
<div class="stat-value"><?= $total_claim_count ?></div>
|
||||
<div class="stat-note"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BDS by Policy Type -->
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">BDS by Policy Type</h3>
|
||||
<div class="table-container" style="width:100%;">
|
||||
<table class="status-table" style="width:100%;border-collapse:collapse;margin-top:8px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="background-color:#ecf0f1;text-align:left;padding:10px 8px;font-size:13px;border-bottom:2px solid #bdc3c7;">Policy Type</th>
|
||||
<th style="background-color:#ecf0f1;text-align:right;padding:10px 8px;font-size:13px;border-bottom:2px solid #bdc3c7;">Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($total_bds_policy_type_wise_count)): ?>
|
||||
<?php foreach ($total_bds_policy_type_wise_count as $row): ?>
|
||||
<tr>
|
||||
<td style="padding:10px 8px;border-bottom:1px solid #eee;font-size:13px;">
|
||||
<span class="badge" style="display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;background:#ecf0f1;color:#2c3e50;"><?= esc($row['policy_type']) ?></span>
|
||||
</td>
|
||||
<td style="padding:10px 8px;border-bottom:1px solid #eee;font-size:13px;text-align:right;font-weight:bold;"><?= $row['count'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="2" style="padding:10px 8px;border-bottom:1px solid #eee;font-size:12px;text-align:center;color:#7f8c8d;">
|
||||
No BDS activity recorded for today.
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 class="section-title" style="font-size:17px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:18px 0 12px;padding-bottom:4px;">Claim Status Breakdown</h3>
|
||||
<div style="padding:10px 8px;border:1px solid #eee;border-radius:4px;font-size:12px;text-align:center;color:#7f8c8d;margin-top:8px;">
|
||||
The total claims received so far are listed ticket type-wise.
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$claimSections = [
|
||||
'GMC Claims' => $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;
|
||||
?>
|
||||
|
||||
<?php foreach ($claimSections as $sectionTitle => $rows): ?>
|
||||
<?php if (!empty($rows)): ?>
|
||||
<?php $hasAnyClaimData = true; ?>
|
||||
<h4 style="font-size:14px;color:#34495e;margin:14px 0 6px;"><?= esc($sectionTitle) ?></h4>
|
||||
<div class="table-container" style="width:100%;margin-bottom:10px;">
|
||||
<table class="status-table" style="width:100%;border-collapse:collapse;margin-top:4px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="background-color:#ecf0f1;text-align:left;padding:10px 8px;font-size:13px;border-bottom:2px solid #bdc3c7;">Status</th>
|
||||
<th style="background-color:#ecf0f1;text-align:right;padding:10px 8px;font-size:13px;border-bottom:2px solid #bdc3c7;">Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<tr>
|
||||
<td style="padding:10px 8px;border-bottom:1px solid #eee;font-size:13px;">
|
||||
<span class="badge" style="display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;background:#ecf0f1;color:#2c3e50;"><?= esc($row['claim_status']) ?></span>
|
||||
</td>
|
||||
<td style="padding:10px 8px;border-bottom:1px solid #eee;font-size:13px;text-align:right;font-weight:bold;"><?= $row['count'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer" style="background:#f8f9fa;padding:14px 18px;text-align:center;font-size:11px;color:#95a5a6;">
|
||||
This is an automated system generated report. Please do not reply to this email.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -12,7 +12,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h3>Google Sheet Editor</h3>
|
||||
<h3>Edit RFQ </h3>
|
||||
|
||||
<button onclick="save()">💾 Save</button>
|
||||
<button onclick="download()">⬇ Download</button>
|
||||
@ -44,9 +44,9 @@ function createRfq() {
|
||||
<table id="sheet"></table>
|
||||
|
||||
<script>
|
||||
alert('first');
|
||||
// alert('first');
|
||||
const sheetId = "<?= esc($sheetId) ?>";
|
||||
alert('second');
|
||||
// alert('second');
|
||||
/* ---------- LOAD ---------- */
|
||||
fetch('<?php echo base_url() ?>' + `sheet/${sheetId}/fetch`)
|
||||
.then(r => r.json())
|
||||
@ -54,7 +54,7 @@ fetch('<?php echo base_url() ?>' + `sheet/${sheetId}/fetch`)
|
||||
.then(render);
|
||||
|
||||
function render(data) {
|
||||
alert('data');
|
||||
// alert('data');
|
||||
console.log('data');
|
||||
console.log(data);
|
||||
const table = document.getElementById('sheet');
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
|
||||
<!-- Sweet Alert CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-multiselect@1.1.0/dist/js/bootstrap-multiselect.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.13/css/bootstrap-multiselect.css" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/toastr.min.js"></script>
|
||||
@ -146,7 +146,7 @@
|
||||
|
||||
|
||||
<script src="https://cdn.datatables.net/plug-ins/2.0.8/sorting/scientific.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.8.0/jszip.min.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
@ -401,6 +401,12 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-row" id="lost_reason_div" style="display: none;">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="lost_reason">Lost Reason <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="lost_reason" name="lost_reason" rows="4" placeholder="Please specify why this opportunity was lost"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="notes">Remarks</label>
|
||||
@ -778,7 +784,8 @@
|
||||
$('#client_name').val(res.data.client_name);
|
||||
$('#client_short_name').val(res.data.client_short_name);
|
||||
$('#entity_type_id').val(res.data.entity_type_id);
|
||||
$('#lead_status').val(res.data.status);
|
||||
$('#lead_status').val(res.data.status).trigger('change');
|
||||
$('#lost_reason').val(res.data.lost_reason || '');
|
||||
$('#notes').val(res.data.notes);
|
||||
|
||||
setTimeout(function() {
|
||||
@ -2431,5 +2438,66 @@
|
||||
});
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var actual_lead_client_details = <?= json_encode($actual_lead_client_details ?? null) ?>;
|
||||
var actual_lead_contact_person_details = <?= json_encode($actual_lead_contact_person_details ?? null) ?>;
|
||||
|
||||
// -------------------------------
|
||||
// CLIENT DETAILS AUTO FILL
|
||||
// -------------------------------
|
||||
if (actual_lead_client_details) {
|
||||
|
||||
$('#client_name').val(actual_lead_client_details.company_name || '');
|
||||
|
||||
$('#gst').val(actual_lead_client_details.gst_number || '');
|
||||
|
||||
// 🔥 Important:
|
||||
// Only auto-generate short name IF empty (avoid overwrite in edit)
|
||||
if (!$('#client_short_name').val()) {
|
||||
$('#client_name').trigger('input');
|
||||
} else {
|
||||
// Run duplicate validation once
|
||||
validateInput($('#client_short_name')[0], "clients", "short_name");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// CONTACT PERSON AUTO FILL
|
||||
// -------------------------------
|
||||
if (actual_lead_contact_person_details) {
|
||||
|
||||
$('#contact_person_name').val(actual_lead_contact_person_details.name || '');
|
||||
|
||||
$('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || '');
|
||||
|
||||
$('#contact_person_email').val(actual_lead_contact_person_details.email || '');
|
||||
}
|
||||
|
||||
|
||||
$('#lead_status').change(function() {
|
||||
if ($(this).val() === 'lost') {
|
||||
$('#lost_reason_div').show();
|
||||
$('#lost_reason').attr('required', 'required');
|
||||
} else {
|
||||
$('#lost_reason_div').hide();
|
||||
$('#lost_reason').val("");
|
||||
$('#lost_reason').removeAttr('required');
|
||||
}
|
||||
});
|
||||
|
||||
// Check on page load
|
||||
if ($('#lead_status').val() === 'lost') {
|
||||
$('#lost_reason_div').show();
|
||||
$('#lost_reason').attr('required', 'required');
|
||||
} else {
|
||||
// Ensure it's hidden and not required if the initial value is not 'lost'
|
||||
$('#lost_reason_div').hide();
|
||||
$('#lost_reason').val("");
|
||||
$('#lost_reason').removeAttr('required');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@ -410,6 +410,12 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="lost_reason_div" style="display: none;">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="lost_reason">Lost Reason <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="lost_reason" name="lost_reason" rows="4" placeholder="Please specify why this opportunity was lost"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="notes">Remarks</label>
|
||||
@ -1258,4 +1264,63 @@
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var actual_lead_client_details = <?= json_encode($actual_lead_client_details ?? null) ?>;
|
||||
var actual_lead_contact_person_details = <?= json_encode($actual_lead_contact_person_details ?? null) ?>;
|
||||
|
||||
// -------------------------------
|
||||
// CLIENT DETAILS AUTO FILL
|
||||
// -------------------------------
|
||||
if (actual_lead_client_details) {
|
||||
|
||||
$('#client_name').val(actual_lead_client_details.company_name || '');
|
||||
|
||||
$('#gst').val(actual_lead_client_details.gst_number || '');
|
||||
|
||||
// 🔥 Important:
|
||||
// Only auto-generate short name IF empty (avoid overwrite in edit)
|
||||
if (!$('#client_short_name').val()) {
|
||||
$('#client_name').trigger('input');
|
||||
} else {
|
||||
// Run duplicate validation once
|
||||
validateInput($('#client_short_name')[0], "clients", "short_name");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// CONTACT PERSON AUTO FILL
|
||||
// -------------------------------
|
||||
if (actual_lead_contact_person_details) {
|
||||
|
||||
$('#contact_person_name').val(actual_lead_contact_person_details.name || '');
|
||||
|
||||
$('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || '');
|
||||
|
||||
$('#contact_person_email').val(actual_lead_contact_person_details.email || '');
|
||||
}
|
||||
|
||||
|
||||
$('#lead_status').change(function() {
|
||||
if ($(this).val() === 'lost') {
|
||||
$('#lost_reason_div').show();
|
||||
$('#lost_reason').attr('required', 'required');
|
||||
} else {
|
||||
$('#lost_reason_div').hide();
|
||||
$('#lost_reason').val("");
|
||||
$('#lost_reason').removeAttr('required');
|
||||
}
|
||||
});
|
||||
|
||||
// Check on page load
|
||||
if ($('#lead_status').val() === 'lost') {
|
||||
$('#lost_reason_div').show();
|
||||
$('#lost_reason').attr('required', 'required');
|
||||
} else {
|
||||
// Ensure it's hidden and not required if the initial value is not 'lost'
|
||||
$('#lost_reason_div').hide();
|
||||
$('#lost_reason').val("");
|
||||
$('#lost_reason').removeAttr('required');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -10,6 +10,8 @@
|
||||
.btn-complete:hover { background: #4caf50; transform: translateY(-1px); }
|
||||
.btn-view { background: #f0f0f0; color: #666; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;}
|
||||
.btn-view:hover { background: #f0f0f0; 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; }
|
||||
@ -68,6 +70,7 @@
|
||||
.activity-meta { display: flex; gap: 20px; font-size: 13px; color: #999; margin-top: 10px;}
|
||||
|
||||
.lead-status { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; margin-top: 5px; }
|
||||
.opportunity-status-badge { padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500;}
|
||||
|
||||
.status-new { background: #e3f2fd; color: #1976d2; }
|
||||
.status-potential { background: #fff3e0; color: #f57c00; }
|
||||
@ -76,6 +79,10 @@
|
||||
.status-pending { background: #fff3e0; color: #f57c00; }
|
||||
.status-completed { background: #e8f5e9; color: #388e3c; }
|
||||
.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; }
|
||||
@ -124,6 +131,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; }
|
||||
@ -142,6 +152,21 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@ -159,7 +184,7 @@
|
||||
<!-- RIGHT SIDE -->
|
||||
<div class="lead-actions">
|
||||
<input type="text" class="search-input" id="mainSearch"
|
||||
placeholder="Search activities..." onkeyup="fetchActivities(false)">
|
||||
placeholder="Search activities..." onkeyup="fetchActivities(false)" style="width: 300px !important;">
|
||||
<button class="btn-primary" onclick="openMainActivityModal()">
|
||||
+ Add Activity
|
||||
</button>
|
||||
@ -191,7 +216,7 @@
|
||||
<h2 id="det_company">Lead Detail</h2>
|
||||
<span id="det_status_badge" class="lead-status" style="margin-top: unset;"></span>
|
||||
</div>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('leadDetailModal')">×</button>
|
||||
<button class="btn-close" title="Close" onclick="closeModal('leadDetailModal')">×</button>
|
||||
</div>
|
||||
<hr class="my-0">
|
||||
<div class="modal-body">
|
||||
@ -233,7 +258,7 @@
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Add Activity</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('activityModal')">×</button>
|
||||
<button class="btn-close" title="Close" onclick="closeModal('activityModal')">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="activityForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -276,11 +301,19 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To <span class="text-danger">*</span></label>
|
||||
<select id="act_owner" class="search-input searchable" style="width:100%" required>
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Additional Assigner</label>
|
||||
<select id="act_multiple_owner" class="search-input searchable multi-searchable" style="width:100%;" multiple>
|
||||
<?php foreach($sales_team as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -297,7 +330,7 @@
|
||||
<div class="modal-content" style="max-width: 675px;">
|
||||
<div class="modal-header">
|
||||
<h3>Complete Activity</h3>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('completeModal')">×</button>
|
||||
<button class="btn-close" title="Close" onclick="closeModal('completeModal')">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="completeForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -346,11 +379,19 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To <span class="text-danger">*</span></label>
|
||||
<select id="f_assigned_to" class="search-input searchable" style="width:100%">
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Additional Assigner <span class="text-danger">*</span></label>
|
||||
<select id="f_multiple_owner" class="search-input searchable multi-searchable" style="width:100%" multiple>
|
||||
<?php foreach($sales_team as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -368,7 +409,7 @@
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Select Opportunity Type</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('opportunityModal')">×</button>
|
||||
<button class="btn-close" title="Close" onclick="closeModal('opportunityModal')">×</button>
|
||||
</div>
|
||||
<hr class="my-0">
|
||||
<div class="modal-body p-4">
|
||||
@ -401,18 +442,45 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
resetFlatpicker();
|
||||
$('.searchable').each(function() {
|
||||
let parentModal = $(this).closest('.modal');
|
||||
$(this).select2({
|
||||
|
||||
// Single selects
|
||||
document.querySelectorAll('.searchable:not(.multi-searchable)').forEach(function(el) {
|
||||
let parentModal = el.closest('.modal');
|
||||
$(el).select2({
|
||||
placeholder: "Select..",
|
||||
dropdownParent: parentModal.length ? parentModal : $(document.body)
|
||||
allowClear: false,
|
||||
minimumResultsForSearch: 0,
|
||||
dropdownParent: parentModal ? $(parentModal) : $(document.body)
|
||||
});
|
||||
});
|
||||
|
||||
// Multi selects
|
||||
document.querySelectorAll('.multi-searchable').forEach(function(el) {
|
||||
let parentModal = el.closest('.modal');
|
||||
$(el).select2({
|
||||
placeholder: "Search and select members...",
|
||||
allowClear: false,
|
||||
closeOnSelect: false,
|
||||
minimumResultsForSearch: 0,
|
||||
dropdownParent: parentModal ? $(parentModal) : $(document.body),
|
||||
width: '100%'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// $(document).ready(function() {
|
||||
// resetFlatpicker();
|
||||
// $('.searchable').each(function() {
|
||||
// let parentModal = $(this).closest('.modal');
|
||||
// $(this).select2({
|
||||
// placeholder: "Select..",
|
||||
// dropdownParent: parentModal.length ? parentModal : $(document.body)
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
function resetFlatpicker(){
|
||||
document.querySelectorAll(".forschedule").forEach(el => {
|
||||
if (el._flatpickr) {
|
||||
@ -438,7 +506,7 @@ const activityIcons = {
|
||||
"Share Docs": "📄",
|
||||
"To Do": "✓"
|
||||
};
|
||||
const salesManagerIds = <?= json_encode($sales_manager_ids ?? []) ?>;
|
||||
const salesManagerWithHeadIds = <?= json_encode($sales_manager_with_head_ids ?? []) ?>;
|
||||
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
let filter = 'all';
|
||||
@ -535,10 +603,10 @@ async function fetchActivities(isLoadMore = false) {
|
||||
</div>`;
|
||||
|
||||
console.log("function here");
|
||||
console.log(salesManagerIds);
|
||||
console.log(salesManagerWithHeadIds);
|
||||
// return;
|
||||
// 2. 🛑 SHORT-CIRCUIT: If no sales manager IDs exist, show empty state and stop!
|
||||
if (typeof salesManagerIds === 'undefined' || salesManagerIds.length === 0) {
|
||||
if (typeof salesManagerWithHeadIds === 'undefined' || salesManagerWithHeadIds.length === 0) {
|
||||
console.log("No Sales Manager IDs found. Skipping API call.");
|
||||
grid.innerHTML = emptyStateHTML;
|
||||
btnLoadMore.style.display = 'none'; // Hide the load more button
|
||||
@ -565,8 +633,8 @@ async function fetchActivities(isLoadMore = false) {
|
||||
// 5. Build URL with dynamic offset
|
||||
let url = `${API}/activities?status=${filter === 'all' ? '' : filter}&search=${q}&limit=${limit}&offset=${currentOffset}`;
|
||||
// url += `&assigned_to=${salesManagerIds.join(',')}`; // We already proved it exists above!
|
||||
if (typeof salesManagerIds !== 'undefined' && salesManagerIds.length > 0) {
|
||||
url += `&assigned_to=${salesManagerIds.join(',')}`;
|
||||
if (typeof salesManagerWithHeadIds !== 'undefined' && salesManagerWithHeadIds.length > 0) {
|
||||
url += `&assigned_to=${salesManagerWithHeadIds.join(',')}`;
|
||||
}
|
||||
|
||||
console.log("Fetching API:", url);
|
||||
@ -578,6 +646,15 @@ async function fetchActivities(isLoadMore = false) {
|
||||
const activityData = json.data || [];
|
||||
const totalrecords = json.total || 0;
|
||||
console.log("API Response:", activityData);
|
||||
|
||||
const tabLeadsCount = json.counts || {};
|
||||
|
||||
// ✅ Update tab counts
|
||||
if (tabLeadsCount) {
|
||||
document.querySelector('[data-filter="all"]').innerText = `All (${tabLeadsCount.all ?? 0})`;
|
||||
document.querySelector('[data-filter="Pending"]').innerText = `Pending (${tabLeadsCount.pending ?? 0})`;
|
||||
document.querySelector('[data-filter="Completed"]').innerText = `Completed (${tabLeadsCount.completed ?? 0})`;
|
||||
}
|
||||
|
||||
const html = activityData
|
||||
.sort((a, b) => Number(b.activity_id) - Number(a.activity_id))
|
||||
@ -613,9 +690,18 @@ async function fetchActivities(isLoadMore = false) {
|
||||
<div class="activity-lead-name">${a.company_name}</div>
|
||||
<div class="activity-notes" title="${displayNote}">${displayNote}</div>
|
||||
<div class="activity-meta">
|
||||
<span>📅 ${formattedCreatedDate}</span>
|
||||
<span>🗓️ ${formattedCreatedDate}</span>
|
||||
<span>👤 ${a.assigned_to_name}</span>
|
||||
</div>
|
||||
${a.additional_assigned_names ? `
|
||||
<div style="display:flex; justify-content:flex-start; margin-bottom:5px;">
|
||||
<span style="font-size:11px; color:#999; text-align: right;
|
||||
max-width: 75%; /* Forces long lists to wrap cleanly on the right side */
|
||||
display: inline-block;">
|
||||
👥 ${a.additional_assigned_names}
|
||||
</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="activity-actions">
|
||||
@ -706,10 +792,17 @@ async function viewDetail(id) {
|
||||
}
|
||||
|
||||
function renderCard(opps) {
|
||||
const cont = document.getElementById('opportunitiesContainer');
|
||||
|
||||
cont.innerHTML = opps.length ? opps.map(o => {
|
||||
const opp_cont = document.getElementById('opportunitiesContainer');
|
||||
opp_cont.innerHTML = opps.length ? opps.map(o => {
|
||||
let lead_type = o.lead_type == 1 ? 'EB' : 'Non-EB';
|
||||
let status = o.status?.toLowerCase();
|
||||
let statusClass = {
|
||||
won: 'status-text-won',
|
||||
lost: 'status-text-lost'
|
||||
}[status] || 'status-text-unknown';
|
||||
|
||||
let opp_status_value = o.status?.replace(/[-_']/g, ' ').toUpperCase();
|
||||
|
||||
let formattedDate = new Date(o.created_at).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
@ -736,11 +829,21 @@ function renderCard(opps) {
|
||||
<strong>Created At:</strong> ${formattedDate}
|
||||
</div>
|
||||
<div class="opportunity-detail-item">
|
||||
<strong>Status:</strong> ${o.status}
|
||||
<strong>Status:</strong> <span class="${statusClass}">${opp_status_value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opportunity-footer">
|
||||
${o.notes || 'N/A'}
|
||||
${ `${o.status === 'lost' ? `
|
||||
<div class="lost-reason">
|
||||
<strong>Reason for Loss:</strong> ${o.lost_reason || 'N/A'}
|
||||
</div>
|
||||
<div class="footer-divider"></div>
|
||||
` : ``}
|
||||
<div class="notes">
|
||||
<strong>Remarks:</strong> ${o.notes || 'N/A'}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@ -774,24 +877,35 @@ function renderTimeline(acts) {
|
||||
// <span style="font-size:11px; color:#999">${formattedDate}</span>
|
||||
|
||||
return `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot ${a.status==='completed'?'completed':''}"></div>
|
||||
<div class="timeline-content">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
|
||||
<b>${icon} ${formattedType}</b>
|
||||
<span style="font-size:11px; color:#999"> 👤 ${a.assigned_to_name} | 🗓 ${formattedDate}</span>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:2px;">
|
||||
<b style="white-space: nowrap; margin-right: 15px;">${icon} ${formattedType}</b>
|
||||
<span style="font-size:11px; color:#999; text-align:right;"> 👤 ${a.assigned_to_name} | 🗓️ ${formattedDate}</span>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:13px; color:#444;">
|
||||
|
||||
${a.additional_assigned_names ? `
|
||||
<div style="display:flex; justify-content:flex-end; margin-bottom:5px;">
|
||||
<span style="font-size:11px; color:#999; text-align: right;
|
||||
max-width: 75%; /* Forces long lists to wrap cleanly on the right side */
|
||||
display: inline-block;">
|
||||
👥 ${a.additional_assigned_names}
|
||||
</span>
|
||||
</div>
|
||||
` : '<div style="margin-bottom:8px;"></div>'}
|
||||
|
||||
<div style="font-size:13px; color:#444; margin-bottom: 8px;">
|
||||
${a.notes}
|
||||
</div>
|
||||
<div style="font-size:13px; color:#444;"></div>
|
||||
|
||||
${a.status === 'pending' ?
|
||||
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:8px;" onclick="openComp(${a.activity_id},${a.assigned_to},${a.lead_id},'frompopup')">Mark Complete</button>` :
|
||||
`<div style="font-size:12px; color:#388e3c; margin-top:8px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
||||
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:4px;" onclick="openComp(${a.activity_id},${a.assigned_to},${a.lead_id})">Mark Complete</button>` :
|
||||
`<div style="font-size:12px; color:#388e3c; margin-top:4px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</div>`;
|
||||
}).join('') : `<div class="empty-state"><div class="empty-icon">✓</div> No activities yet. Add one to get started!</div>`;
|
||||
}
|
||||
|
||||
@ -906,7 +1020,8 @@ document.getElementById('activityForm').onsubmit = async (e) => {
|
||||
notes: actNotes, // Reused the trimmed variable from above
|
||||
scheduled_date: dbFormat,
|
||||
assigned_to: actOwner, // Reused the trimmed variable from above
|
||||
status: 'pending'
|
||||
status: 'pending',
|
||||
additional_assigned_ids : Array.from(document.getElementById('act_multiple_owner').selectedOptions).map(o => o.value)
|
||||
};
|
||||
|
||||
const res = await fetch(`${API}/activities`, {
|
||||
@ -1023,7 +1138,8 @@ document.getElementById('completeForm').onsubmit = async (e) => {
|
||||
notes: document.getElementById('f_notes').value,
|
||||
scheduled_date: dbFormat,
|
||||
assigned_to: document.getElementById('f_assigned_to').value,
|
||||
status: 'pending'
|
||||
status: 'pending',
|
||||
additional_assigned_ids : Array.from(document.getElementById('f_multiple_owner').selectedOptions).map(o => o.value)
|
||||
};
|
||||
|
||||
const res2 = await fetch(`${API}/activities`, {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
294
app/Views/sales/branch_level_dashboard_view_1mar.php
Normal file
294
app/Views/sales/branch_level_dashboard_view_1mar.php
Normal file
@ -0,0 +1,294 @@
|
||||
<style>
|
||||
.dash-container { padding: 25px; background: #f8f9fa; font-family: 'Segoe UI', sans-serif; }
|
||||
.top-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; }
|
||||
|
||||
.stat-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
|
||||
.card { background: white; padding: 25px; border-radius: 12px; border: 1px solid #edf2f7; box-shadow: 0 2px 4px rgba(0,0,0,0.02); }
|
||||
.stat-val { font-size: 28px; font-weight: 700; color: #1a202c; }
|
||||
.stat-label { color: #718096; font-size: 14px; margin-top: 4px; font-weight: 500; }
|
||||
.stat-change { font-size: 11px; margin-top: 8px; font-weight: 600; }
|
||||
.text-success { color: #48bb78; }
|
||||
|
||||
.card-hero {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #edf2f7;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* The Gradient Overlay Effect */
|
||||
.card-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px; /* Thin line at the top */
|
||||
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
.card-hero:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Optional: Subtle Background Gradient */
|
||||
.card-hero.gradient-bg {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
|
||||
}
|
||||
|
||||
.main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 30px; }
|
||||
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
|
||||
.team-member { display: flex; align-items: center; padding: 15px 0; border-bottom: 1px solid #f1f5f9; }
|
||||
.member-img { width: 40px; height: 40px; border-radius: 50%; background: #ff6b35; color: white; margin-right: 12px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 16px; }
|
||||
.act-stats { text-align: right; }
|
||||
.act-stats .total { font-weight: 700; font-size: 13px; color: #1a202c; }
|
||||
.act-stats .done { font-size: 11px; color: #38a169; font-weight: 600; margin-top: 2px; }
|
||||
|
||||
.breakdown-card { margin-top: 20px; }
|
||||
.breakdown-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 13px; }
|
||||
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 8px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th { text-align: left; padding: 12px; color: #718096; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid #edf2f7; }
|
||||
td { padding: 15px 12px; border-bottom: 1px solid #f1f5f9; font-size: 14px; vertical-align: middle; }
|
||||
|
||||
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; display: inline-block; }
|
||||
.status-completed { background: #f0fff4; color: #38a169; }
|
||||
.status-pending { background: #fffaf0; color: #dd6b20; }
|
||||
.status-new { background: #e3f2fd; color: #1976d2; }
|
||||
.status-potential { background: #fff3e0; color: #f57c00; }
|
||||
.status-prospects { background: #e8f5e9; color: #388e3c; }
|
||||
.status-not-a-prospects { background: #ffebee; color: #d32f2f; }
|
||||
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: #999; }
|
||||
.empty-icon { width: 80px; height: 80px; margin: 0 auto 20px; background: #f5f5f5; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36px; }
|
||||
|
||||
</style>
|
||||
|
||||
<div class="dash-container">
|
||||
<!-- <div class="top-header">
|
||||
<div>
|
||||
<h2 style="font-weight: 800; color: #1a202c; font-size: 24px;">Dashboard</h2>
|
||||
<p style="color: #718096; font-size: 14px; margin-top: 4px;">Overview of your branch</p>
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<input type="text" placeholder="Search leads, activities....." style="background:white; padding:12px 20px; border-radius:10px; border:1px solid #e2e8f0; width: 350px; font-size: 13px;">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="stat-cards">
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_leads; ?></div>
|
||||
<div class="stat-label">Total Leads</div>
|
||||
<!-- <div class="stat-change text-success">↑ 12% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_acts ?></div>
|
||||
<div class="stat-label">Total Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 5% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_pending_acts ?></div>
|
||||
<div class="stat-label">Pending Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 15% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_completed_acts ?></div>
|
||||
<div class="stat-label">Completed Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 15% this month</div> -->
|
||||
</div>
|
||||
<!-- <div class="card">
|
||||
<div class="stat-val">₹15.0L</div>
|
||||
<div class="stat-label">Pipeline Value</div>
|
||||
<div class="stat-change text-success">↑ 18% this month</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<div class="card">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">Pending Activities - All Team</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;"><?= count($pending_acts) . ' ' . (count($pending_acts) == 1 ? 'activity' : 'activities') ?></span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Activity & Assigned</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($pending_acts)): ?>
|
||||
<?php foreach ($pending_acts as $a): ?>
|
||||
<?php
|
||||
$activityIcons = [
|
||||
'Call' => '📞',
|
||||
'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']));
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= esc($a['company_name']) ?></strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">
|
||||
<?= $icon ?> <?= strtoupper(esc($a['activity_type'])) ?>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #718096;">
|
||||
<?= esc($a['assigned_to_name'] ?? 'ID: ' . $a['assigned_to']) ?>
|
||||
</div>
|
||||
<!-- <div style="font-size: 12px; color: #718096;">
|
||||
<?= esc($a['notes'] ?? 'ID: ' . $a['assigned_to']) ?>
|
||||
</div> -->
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;"><?= $formattedscheduledDate ?></td>
|
||||
<td><span class="status-pill status-<?= $statusClass ?>"><?= ucfirst(esc($a['status'])) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">✓</div> No activities found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
|
||||
<h3 style="font-size: 16px; font-weight: 700; margin-bottom: 15px;">Sales Team Performance</h3>
|
||||
<?php foreach ($team as $member) {
|
||||
$firstLetter = strtoupper(substr($member['first_name'], 0, 1));
|
||||
$fullName = $member['first_name'] . ' ' . $member['last_name'];
|
||||
$totalActs = $member['total_acts'];
|
||||
$doneActs = $member['done_acts'];
|
||||
$role = $member['role']; // fix your DB first
|
||||
|
||||
// Random or consistent color based on name
|
||||
$colors = ['#ff6b35', '#667eea', '#48bb78', '#ed8936', '#9f7aea'];
|
||||
$colorIndex = abs(crc32($member['first_name'])) % count($colors);
|
||||
$color = $colors[$colorIndex];
|
||||
|
||||
echo "
|
||||
<div class='team-member'>
|
||||
<div class='member-img' style='background: {$color};'>{$firstLetter}</div>
|
||||
<div style='flex: 1;'>
|
||||
<div style='font-weight: 700; font-size: 14px;'>{$fullName}</div>
|
||||
<div style='font-size: 11px; color: #718096;'>{$role}</div>
|
||||
</div>
|
||||
<div class='act-stats'>
|
||||
<div class='total'>{$totalActs} acts</div>
|
||||
<div class='done'>{$doneActs} done</div>
|
||||
</div>
|
||||
</div>";
|
||||
} ?>
|
||||
|
||||
|
||||
<div class="card breakdown-card">
|
||||
<h3 style="font-size: 15px; font-weight: 700; margin-bottom: 20px;">Activity Breakdown</h3>
|
||||
<!-- <div class="breakdown-item"><span><span class="dot" style="background: #ff6b35;"></span> Call</span><strong>42%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #48bb78;"></span> Email</span><strong>25%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #4299e1;"></span> Meeting</span><strong>18%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #ecc94b;"></span> Visit</span><strong>10%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #9f7aea;"></span> Demo</span><strong>5%</strong></div> -->
|
||||
<?php
|
||||
$activityConfig = [
|
||||
'Call' => ['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' => '✓'],
|
||||
];
|
||||
?>
|
||||
|
||||
<?php if (!empty($activity_breakdown)): ?>
|
||||
<?php foreach ($activity_breakdown as $item): ?>
|
||||
<?php
|
||||
$type = $item['activity_type'];
|
||||
$color = $activityConfig[$type]['color'] ?? '#718096';
|
||||
$icon = $activityConfig[$type]['icon'] ?? '📌';
|
||||
?>
|
||||
<div class="breakdown-item">
|
||||
<span>
|
||||
<span style="background: <?= $color ?>;"></span>
|
||||
<?= $icon ?> <?= esc($type) ?>
|
||||
</span>
|
||||
<strong><?= $item['percentage'] ?>%</strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">✓</div> No activities found
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="grid-column: 1 / -1; width: 100%; box-sizing: border-box;">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">All Leads Overview </h3>
|
||||
<!-- <span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span>
|
||||
<a href="<?= base_url('sales') ?>"
|
||||
style="color: #718096; font-size: 12px; font-weight: 600; text-decoration: none; padding: 4px 8px; transition: color 0.2s ease; display: inline-block; cursor: pointer;"
|
||||
onmouseover="this.style.color='#ff6b35';"
|
||||
onmouseout="this.style.color='#718096';">
|
||||
Click a lead to see details
|
||||
</a> -->
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Status</th>
|
||||
<th>Assigned To</th>
|
||||
<th style="text-align: center !important;">Activities</th>
|
||||
<th style="text-align: center !important;">Opportunities</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($leads_overview)): ?>
|
||||
<?php foreach ($leads_overview as $lead): ?>
|
||||
<?php $statusClass = strtolower(str_replace(' ', '-', $lead['status'])); ?>
|
||||
<tr>
|
||||
<td><strong><?= esc($lead['company_name']) ?></strong></td>
|
||||
<td><span class="status-pill status-<?= $statusClass ?>"><?= esc($lead['status']) ?></span></td>
|
||||
<td><?= esc($lead['assigned_to'] ?? 'Unassigned') ?></td>
|
||||
<td><div style="text-align: center; font-weight: 700;"><?= $lead['activities'] ?></div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;"><?= $lead['opportunities'] ?></div></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">👤</div> No leads found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,7 +74,7 @@
|
||||
<p style="color:#666; font-size:13px; margin-top: 4px;">Welcome back, <?= $user_name ?></p> -->
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<select id="financial_year" name="financial_year" onchange="onFinancialYearChange(this)" style="background:#f5f5f5; padding:10px 20px; border-radius:8px; border:none; width: 250px; font-size: 13px; cursor: pointer;">
|
||||
<select id="financial_year" name="financial_year" onchange="onFinancialYearChange(this)" style="width: 250px; font-size: 13px; cursor: pointer;">
|
||||
<?php if(!empty($fin_years)): ?>
|
||||
<?php foreach($fin_years as $year): ?>
|
||||
<option value="<?= $year; ?>"><?= $year; ?></option>
|
||||
@ -133,6 +133,9 @@
|
||||
<button class="badge-done" onclick="openComp(<?= $u['activity_id'] ?>,<?= $u['lead_id'] ?>)">✓ Mark as completed</button>
|
||||
</div>
|
||||
<?php endforeach; ?> -->
|
||||
<?php if (!empty($upcoming)) : ?>
|
||||
|
||||
|
||||
<?php foreach($upcoming as $u):
|
||||
$activityIcons = [ 'Call' => '📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share Docs' => '📄', 'To Do' => '✓' ];
|
||||
$icon = $activityIcons[$u['activity_type']] ?? '📌';
|
||||
@ -160,11 +163,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
|
||||
<div class="text-center text-muted py-3">
|
||||
No Upcoming Activities for this Financial Year.
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="list-card">
|
||||
<h3 style="font-size:16px; font-weight: 700; margin-bottom:20px;">My Recent Leads</h3>
|
||||
<?php if (!empty($recent_leads)) : ?>
|
||||
<?php foreach($recent_leads as $rl): ?>
|
||||
<!-- <div class="list-item">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
@ -206,6 +217,13 @@
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
|
||||
<div class="text-center text-muted py-3">
|
||||
No Leads Found for this Financial Year.
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -307,12 +307,12 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const users = <?= json_encode($users ?? []) ?>;
|
||||
const sales_manager = <?= json_encode($sales_manager ?? []) ?>;
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
DATA
|
||||
────────────────────────────────────────── */
|
||||
const teamMembers = users.map(user => ({
|
||||
const teamMembers = sales_manager.map(user => ({
|
||||
id: user.id,
|
||||
name: user.first_name + ' ' + user.last_name
|
||||
}));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="main-content">
|
||||
@ -137,7 +160,7 @@
|
||||
<!-- RIGHT SIDE -->
|
||||
<div class="lead-actions">
|
||||
<input type="text" class="search-input" id="mainSearch"
|
||||
placeholder="Search leads..." onkeyup="fetchLeads(false)">
|
||||
placeholder="Search leads..." onkeyup="fetchLeads(false)" style="width: 300px !important;">
|
||||
<button class="btn-primary" onclick="openModal('addLeadModal')">
|
||||
+ Add Lead
|
||||
</button>
|
||||
@ -166,7 +189,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Add Lead</h4>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('addLeadModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('addLeadModal')" title="Close">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="addLeadForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -203,8 +226,8 @@
|
||||
<label class="form-label">Assign To<span class="text-danger">*</span></label>
|
||||
<select name="assigned_to" class="form-control searchable" style="width: 100%;" required>
|
||||
<option value="">Select User</option>
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?> </option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?> </option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@ -228,7 +251,7 @@
|
||||
<h2 id="det_company">Lead Detail</h2>
|
||||
<span id="det_status_badge" class="lead-status" style="margin-top: unset;"></span>
|
||||
</div>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('leadDetailModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('leadDetailModal')" title="Close">×</button>
|
||||
</div>
|
||||
<hr class="my-0">
|
||||
<div class="modal-body">
|
||||
@ -270,7 +293,7 @@
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Add Activity</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('activityModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('activityModal')" title="Close">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="activityForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -305,12 +328,20 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To</label>
|
||||
<select id="act_owner" class="search-input searchable" style="width:100%">
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Additional Assigner</label>
|
||||
<select id="act_multiple_owner" class="search-input searchable multi-searchable" style="width:100%;" multiple>
|
||||
<?php foreach($sales_team as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="form-group text-right mb-0">
|
||||
@ -326,7 +357,7 @@
|
||||
<div class="modal-content" style="max-width: 675px;">
|
||||
<div class="modal-header">
|
||||
<h3>Complete Activity</h3>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('completeModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('completeModal')" title="Close">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="completeForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -374,12 +405,20 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To <span class="text-danger">*</span></label>
|
||||
<select id="f_assigned_to" class="search-input searchable" style="width:100%">
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Additional Assigner <span class="text-danger">*</span></label>
|
||||
<select id="f_multiple_owner" class="search-input searchable multi-searchable" style="width:100%" multiple>
|
||||
<?php foreach($sales_team as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@ -397,7 +436,7 @@
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Select Opportunity Type</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('opportunityModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('opportunityModal')" title="Close">×</button>
|
||||
</div>
|
||||
<hr class="my-0">
|
||||
<div class="modal-body p-4">
|
||||
@ -429,61 +468,12 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="modal" id="opportunityModal">
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2>Add Opportunity</h2>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('opportunityModal')">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="OpportunityForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
<div class="modal-body p-4">
|
||||
|
||||
<input type="hidden" id="opp_lead_id">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Opportunity Title</label>
|
||||
<input type="text" name="title" class="search-input" style="width:100%">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group ml-1">
|
||||
<label class="form-label">Expected Worth Amount</label>
|
||||
<input type="number" name="amount" class="search-input" style="width:100%">
|
||||
</div>
|
||||
<div class="form-group mr-1">
|
||||
<label class="form-label">Policy Type</label>
|
||||
<input type="text" name="policy_type" class="search-input" style="width:100%">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Employee Count</label>
|
||||
<input type="number" name="count" class="search-input" style="width:100%">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Expected Close Date</label>
|
||||
<input type="datetime-local" id="e_date" class="search-input" style="width:100%">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea id="opp_notes" class="search-input" style="width:100%; height:100px;" placeholder="Add notes about this opportunity..." required></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="form-group text-right mb-0">
|
||||
<button type="button" class="btn-primary" style="background:#eee; color:#333; margin-right:10px;" onclick="closeModal('opportunityModal')">Cancel</button>
|
||||
<button type="submit" class="btn-primary" >Create Opportunity</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="modal" id="editLeadModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Edit Lead</h4>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('editLeadModal')">×</button>
|
||||
<button class="btn-close" onclick="closeModal('editLeadModal')" title="Close">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="editLeadForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
@ -525,46 +515,75 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-1" style="padding-left: 25px;">
|
||||
<label class="form-label">Contact Persons</label>
|
||||
<div id="contactPersonsContainer">
|
||||
<div id="savedContactsContainer"> <span id="NoData"> <center><i> No contact persons added yet </i> </center> </span> </div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.g-0-5 { --bs-gutter-x: 0.3rem; }
|
||||
.g-0-5 > * { padding-right: calc(var(--bs-gutter-x) * .5); padding-left: calc(var(--bs-gutter-x) * .5); }
|
||||
|
||||
<div class="col-12 mb-1 ml-1 ">
|
||||
|
||||
<div class="row g-2 align-items-center">
|
||||
|
||||
<div class="col-md-5" style="padding-left: 20px;">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="contact_name"
|
||||
placeholder="Contact Person Name"
|
||||
oninput="this.value=this.value.replace(/[^A-Za-z\s]/g,'')">
|
||||
.primary-container {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* Ensures the checkbox container height matches the standard input height */
|
||||
height: 31px;
|
||||
}
|
||||
.primary-label {
|
||||
font-size: 12px !important;
|
||||
font-weight: bold;
|
||||
/* Forces the text to take up exactly its own height */
|
||||
line-height: 1;
|
||||
margin-top: 2px;
|
||||
color: #888;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* This is the most important part */
|
||||
.primary-container .form-check-input {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
cursor: pointer;
|
||||
/* Reset Bootstrap's default position: absolute if present */
|
||||
position: static;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="col-12 ml-1">
|
||||
<label class="form-label fw-bold small ml-1">Contact Persons</label>
|
||||
<input type="hidden" id="editing_contact_id" value="">
|
||||
<div class="row g-0-5 align-items-end ml-1">
|
||||
<div class="col-md-3 col-12 mb-2 mb-md-0">
|
||||
<input type="text" class="form-control form-control-sm" id="contact_name" placeholder="Person Name" oninput="this.value = this.value.replace(/[^A-Za-z\s]/g, '')">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="contact_mobile"
|
||||
placeholder="Contact Mobile Number"
|
||||
maxlength="10"
|
||||
oninput="this.value=this.value.replace(/[^0-9]/g,'')">
|
||||
<div class="col-md-3 col-6">
|
||||
<input type="text" class="form-control form-control-sm" id="contact_mobile" placeholder="Mobile" maxlength="10" oninput="this.value = this.value.replace(/[^0-9]/g, '').substring(0, 10);">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3" style="padding-right: 4%;">
|
||||
<button type="button"
|
||||
id="btnSaveContact"
|
||||
class="btn btn-sm w-100"
|
||||
style="background:#ff6a3d;border:none;color:white;">
|
||||
+ Save Contact
|
||||
<div class="col-md-3 col-6">
|
||||
<input type="text" class="form-control form-control-sm" id="contact_designation" placeholder="Designation" oninput="this.value = this.value.replace(/[^A-Za-z\s]/g, '')">
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 col-3">
|
||||
<div class="primary-container">
|
||||
<label for="contact_is_primary" class="primary-label">Primary</label>
|
||||
<input class="form-check-input" type="checkbox" id="contact_is_primary">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 col-6">
|
||||
<button type="button" id="btnSaveContact" class="btn btn-sm btn-info text-white" style="width:85%">
|
||||
✔ Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="contactPersonsList" class="p-1">
|
||||
<div id="noContactPersonsData" class="py-1 text-center">
|
||||
<small class="text-muted fst-italic">No contact persons added yet</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row mb-1">
|
||||
<div class="col-md-12 ml-3" style="width: 97%;">
|
||||
@ -580,8 +599,8 @@
|
||||
<label class="form-label">Assign To <span class="text-danger">*</span> </label>
|
||||
<select name="assigned_to" class="form-control searchable" style="width: 100%;" required>
|
||||
<option value="">Select User</option>
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?> </option>
|
||||
<?php foreach($sales_manager_with_head as $sm): ?>
|
||||
<option value="<?= $sm['id'] ?>"><?= $sm['first_name'] ?> </option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@ -599,16 +618,32 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
resetFlatpicker();
|
||||
$('.searchable').each(function() {
|
||||
let parentModal = $(this).closest('.modal');
|
||||
$(this).select2({
|
||||
|
||||
// Single selects
|
||||
document.querySelectorAll('.searchable:not(.multi-searchable)').forEach(function(el) {
|
||||
let parentModal = el.closest('.modal');
|
||||
$(el).select2({
|
||||
placeholder: "Select..",
|
||||
dropdownParent: parentModal.length ? parentModal : $(document.body)
|
||||
allowClear: false,
|
||||
minimumResultsForSearch: 0,
|
||||
dropdownParent: parentModal ? $(parentModal) : $(document.body)
|
||||
});
|
||||
});
|
||||
|
||||
// Multi selects
|
||||
document.querySelectorAll('.multi-searchable').forEach(function(el) {
|
||||
let parentModal = el.closest('.modal');
|
||||
$(el).select2({
|
||||
placeholder: "Search and select members...",
|
||||
allowClear: false,
|
||||
closeOnSelect: false,
|
||||
minimumResultsForSearch: 0,
|
||||
dropdownParent: parentModal ? $(parentModal) : $(document.body),
|
||||
width: '100%'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function resetFlatpicker(){
|
||||
@ -626,7 +661,7 @@ function resetFlatpicker(){
|
||||
allowInput: false,
|
||||
});
|
||||
}
|
||||
const salesManagerIds = <?= json_encode($sales_manager_ids ?? []) ?>;
|
||||
const salesManagerHeadIds = <?= json_encode($sales_manager_with_head_ids ?? []) ?>;
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
let filter = 'all';
|
||||
let lead_id = null;
|
||||
@ -648,7 +683,12 @@ function closeModal(id) {
|
||||
const form = modal.querySelector('form'); // Find the form inside this specific modal
|
||||
if (form) {
|
||||
form.reset(); // Resets standard inputs (text, email, select)
|
||||
$(form).find('.searchable').val('').trigger('change'); // Clear jQuery/Select2/Searchable dropdowns if you use them
|
||||
form.querySelectorAll('.searchable').forEach(function(el) {
|
||||
// Reset native select
|
||||
Array.from(el.options).forEach(o => o.selected = false);
|
||||
// Notify Select2 to refresh its UI
|
||||
$(el).trigger('change');
|
||||
});
|
||||
$(form).find('.is-invalid').removeClass('is-invalid'); // Remove any "is-invalid" red borders from previous validation errors
|
||||
}
|
||||
|
||||
@ -672,6 +712,15 @@ function closeModal(id) {
|
||||
switchTab('activity'); // Reset the tab back to 'activity'
|
||||
document.getElementById('btn_add_opportunity').style.display = 'none';
|
||||
}
|
||||
|
||||
if (id === 'editLeadModal') {
|
||||
// const Eform = document.getElementById('editLeadForm');
|
||||
// if (!Eform) return; // safety check
|
||||
// Eform.reset();
|
||||
form.querySelectorAll('[name="status"] option')
|
||||
.forEach(opt => opt.hidden = false);
|
||||
form.querySelector('[name="status"]').value = 'New';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -733,7 +782,7 @@ async function fetchLeads(isLoadMore = false) {
|
||||
</div>`;
|
||||
|
||||
// 2. 🛑 SHORT-CIRCUIT: If no sales manager IDs exist, show empty state and stop!
|
||||
if (typeof salesManagerIds === 'undefined' || salesManagerIds.length === 0) {
|
||||
if (typeof salesManagerHeadIds === 'undefined' || salesManagerHeadIds.length === 0) {
|
||||
console.log("No Sales Manager IDs found. Skipping API call.");
|
||||
grid.innerHTML = emptyStateHTML;
|
||||
btnLoadMore.style.display = 'none'; // Hide the load more button
|
||||
@ -759,9 +808,9 @@ async function fetchLeads(isLoadMore = false) {
|
||||
|
||||
// 5. Build URL with dynamic offset
|
||||
let url = `${API}/leads?status=${filter === 'all' ? '' : filter}&search=${q}&limit=${limit}&offset=${currentOffset}`;
|
||||
// url += `&assigned_to=${salesManagerIds.join(',')}`; // We already proved it exists above!
|
||||
if (typeof salesManagerIds !== 'undefined' && salesManagerIds.length > 0) {
|
||||
url += `&assigned_to=${salesManagerIds.join(',')}`;
|
||||
// url += `&assigned_to=${salesManagerHeadIds.join(',')}`; // We already proved it exists above!
|
||||
if (typeof salesManagerHeadIds !== 'undefined' && salesManagerHeadIds.length > 0) {
|
||||
url += `&assigned_to=${salesManagerHeadIds.join(',')}`;
|
||||
}
|
||||
|
||||
console.log("Fetching API:", url);
|
||||
@ -773,6 +822,17 @@ async function fetchLeads(isLoadMore = false) {
|
||||
const leadsData = json.data || [];
|
||||
const totalrecords = json.total || 0;
|
||||
console.log("API Response:", leadsData);
|
||||
|
||||
const tabLeadsCount = json.counts || {};
|
||||
|
||||
// ✅ Update tab counts
|
||||
if (tabLeadsCount) {
|
||||
document.querySelector('[data-filter="all"]').innerText = `All (${tabLeadsCount.all ?? 0})`;
|
||||
document.querySelector('[data-filter="New"]').innerText = `New (${tabLeadsCount.New ?? 0})`;
|
||||
document.querySelector('[data-filter="Potential"]').innerText = `Potential (${tabLeadsCount.Potential ?? 0})`;
|
||||
document.querySelector('[data-filter="Prospects"]').innerText = `Prospects (${tabLeadsCount.Prospects ?? 0})`;
|
||||
document.querySelector('[data-filter="Not a Prospects"]').innerText = `Not a Prospects (${tabLeadsCount['Not a Prospects'] ?? 0})`;
|
||||
}
|
||||
|
||||
const html = leadsData
|
||||
.sort((a, b) => Number(b.lead_id) - Number(a.lead_id))
|
||||
@ -801,7 +861,7 @@ async function fetchLeads(isLoadMore = false) {
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<a class="text-body dropdown-toggle" href="#" data-toggle="dropdown">
|
||||
<i class="mdi mdi-dots-vertical font-20" title="Options"></i>
|
||||
<i class="mdi mdi-dots-vertical font-20 btn-close" title="Options"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="viewDetail(${l.lead_id})"> 👁 View</a>
|
||||
@ -894,10 +954,21 @@ async function viewDetail(id) {
|
||||
}
|
||||
|
||||
function renderCard(opps) {
|
||||
const cont = document.getElementById('opportunitiesContainer');
|
||||
const opp_cont = document.getElementById('opportunitiesContainer');
|
||||
|
||||
cont.innerHTML = opps.length ? opps.map(o => {
|
||||
opp_cont.innerHTML = opps.length ? opps.map(o => {
|
||||
let lead_type = o.lead_type == 1 ? 'EB' : 'Non-EB';
|
||||
let status = o.status?.toLowerCase();
|
||||
let statusClass = {
|
||||
won: 'status-text-won',
|
||||
lost: 'status-text-lost'
|
||||
}[status] || 'status-text-unknown';
|
||||
|
||||
let opp_status_value = o.status?.replace(/[-_']/g, ' ').toUpperCase();
|
||||
|
||||
|
||||
|
||||
|
||||
let formattedDate = new Date(o.created_at).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
@ -924,11 +995,21 @@ function renderCard(opps) {
|
||||
<strong>Created At:</strong> ${formattedDate}
|
||||
</div>
|
||||
<div class="opportunity-detail-item">
|
||||
<strong>Status:</strong> ${o.status}
|
||||
<strong>Status:</strong> <span class="${statusClass}">${opp_status_value}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opportunity-footer">
|
||||
${o.notes || 'N/A'}
|
||||
${ `${o.status === 'lost' ? `
|
||||
<div class="lost-reason">
|
||||
<strong>Reason for Loss:</strong> ${o.lost_reason || 'N/A'}
|
||||
</div>
|
||||
<div class="footer-divider"></div>
|
||||
` : ``}
|
||||
<div class="notes">
|
||||
<strong>Remarks:</strong> ${o.notes || 'N/A'}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@ -936,15 +1017,7 @@ function renderCard(opps) {
|
||||
}
|
||||
function renderTimeline(acts) {
|
||||
const cont = document.getElementById('timelineContainer');
|
||||
const activityIcons = {
|
||||
"Call": "📞",
|
||||
"Email": "✉️",
|
||||
"Meeting": "📅",
|
||||
"Visit": "🚗",
|
||||
"Demo": "🖥️",
|
||||
"Share Docs": "📄",
|
||||
"To Do": "✓"
|
||||
};
|
||||
const activityIcons = { "Call": "📞", "Email": "✉️", "Meeting": "📅", "Visit": "🚗", "Demo": "🖥️", "Share Docs": "📄", "To Do": "✓"};
|
||||
|
||||
if (acts.length === 0) {
|
||||
cont.classList.add('no-line');
|
||||
@ -969,29 +1042,41 @@ function renderTimeline(acts) {
|
||||
// <span style="font-size:11px; color:#999">${formattedDate}</span>
|
||||
|
||||
return `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot ${a.status==='completed'?'completed':''}"></div>
|
||||
<div class="timeline-content">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
|
||||
<b>${icon} ${formattedType}</b>
|
||||
<span style="font-size:11px; color:#999"> 👤 ${a.assigned_to_name} | 🗓 ${formattedDate}</span>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:2px;">
|
||||
<b style="white-space: nowrap; margin-right: 15px;">${icon} ${formattedType}</b>
|
||||
<span style="font-size:11px; color:#999; text-align:right;"> 👤 ${a.assigned_to_name} | 🗓️ ${formattedDate}</span>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:13px; color:#444;">
|
||||
|
||||
${a.additional_assigned_names ? `
|
||||
<div style="display:flex; justify-content:flex-end; margin-bottom:5px;">
|
||||
<span style="font-size:11px; color:#999; text-align: right;
|
||||
max-width: 75%; /* Forces long lists to wrap cleanly on the right side */
|
||||
display: inline-block;">
|
||||
👥 ${a.additional_assigned_names}
|
||||
</span>
|
||||
</div>
|
||||
` : '<div style="margin-bottom:8px;"></div>'}
|
||||
|
||||
<div style="font-size:13px; color:#444; margin-bottom: 8px;">
|
||||
${a.notes}
|
||||
</div>
|
||||
<div style="font-size:13px; color:#444;"></div>
|
||||
|
||||
${a.status === 'pending' ?
|
||||
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:8px;" onclick="openComp(${a.activity_id},${a.assigned_to},${a.lead_id})">Mark Complete</button>` :
|
||||
`<div style="font-size:12px; color:#388e3c; margin-top:8px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
||||
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:4px;" onclick="openComp(${a.activity_id},${a.assigned_to},${a.lead_id})">Mark Complete</button>` :
|
||||
`<div style="font-size:12px; color:#388e3c; margin-top:4px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</div>`;
|
||||
}).join('') : `<div class="empty-state"><div class="empty-icon">✓</div> No activities yet. Add one to get started!</div>`;
|
||||
}
|
||||
function openActivityModal() {
|
||||
document.getElementById('act_owner').value = global_lead_assigned_to;
|
||||
document.getElementById('act_owner').dispatchEvent(new Event('change'));
|
||||
$(document.getElementById('act_owner')).trigger('change'); // tells Select2 to update display
|
||||
// document.getElementById('act_owner').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_lead_id').value = lead_id;
|
||||
openModal('activityModal');
|
||||
}
|
||||
@ -1004,7 +1089,8 @@ function openComp(id, assigned_to, lead_id) {
|
||||
document.getElementById('comp_id').value = id;
|
||||
document.getElementById('lead_id').value = lead_id;
|
||||
document.getElementById('f_assigned_to').value = assigned_to;
|
||||
document.getElementById('f_assigned_to').dispatchEvent(new Event('change'));
|
||||
$(document.getElementById('f_assigned_to')).trigger('change'); // tells Select2 to update display
|
||||
// document.getElementById('f_assigned_to').dispatchEvent(new Event('change'));
|
||||
document.getElementById('f_typeButtons').querySelectorAll('.f_activity_type')
|
||||
.forEach(btn => btn.classList.remove('active'));
|
||||
selectedFollowUpActivityType = '';
|
||||
@ -1250,11 +1336,14 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
|
||||
|
||||
if(res.ok) {
|
||||
toastr.success('Leads Updated Successfully');
|
||||
e.target.reset();
|
||||
closeModal('editLeadModal');
|
||||
fetchLeads();
|
||||
e.target.reset();
|
||||
$('#savedContactsContainer').empty();
|
||||
$('#NoData').show();
|
||||
// CLEANUP: Reset the contact UI for the next time it opens
|
||||
$('#contactPersonsList').find('.contact-card').remove();
|
||||
$('#noContactPersonsData').show();
|
||||
document.getElementById('editing_contact_id').value = '';
|
||||
document.getElementById('btnSaveContact').innerHTML = "✔ Save";
|
||||
} else {
|
||||
let err = await res.json();
|
||||
if (res.status === 400) {
|
||||
@ -1346,7 +1435,9 @@ document.getElementById('activityForm').onsubmit = async (e) => {
|
||||
notes: actNotes, // Reused the trimmed variable from above
|
||||
scheduled_date: dbFormat,
|
||||
assigned_to: actOwner, // Reused the trimmed variable from above
|
||||
status: 'pending'
|
||||
status: 'pending',
|
||||
additional_assigned_ids : Array.from(document.getElementById('act_multiple_owner').selectedOptions).map(o => o.value)
|
||||
|
||||
};
|
||||
|
||||
const res = await fetch(`${API}/activities`, {
|
||||
@ -1483,7 +1574,8 @@ document.getElementById('completeForm').onsubmit = async (e) => {
|
||||
notes: document.getElementById('f_notes').value,
|
||||
scheduled_date: dbFormat,
|
||||
assigned_to: document.getElementById('f_assigned_to').value,
|
||||
status: 'pending'
|
||||
status: 'pending',
|
||||
additional_assigned_ids : Array.from(document.getElementById('f_multiple_owner').selectedOptions).map(o => o.value)
|
||||
};
|
||||
|
||||
const res2 = await fetch(`${API}/activities`, {
|
||||
@ -1594,146 +1686,11 @@ document.getElementById('do_follow').addEventListener('change', function () {
|
||||
}
|
||||
});
|
||||
|
||||
const btnSaveContact = document.getElementById('btnSaveContact');
|
||||
if (btnSaveContact) {
|
||||
btnSaveContact.onclick = async (e) => {
|
||||
// Use .value instead of .val()
|
||||
let name = document.getElementById('contact_name').value.trim();
|
||||
let mobile = document.getElementById('contact_mobile').value.trim();
|
||||
let lead_id = document.getElementById('hidden_lead_id').value.trim();
|
||||
|
||||
if (!name || !mobile) {
|
||||
return toastr.warning('Please enter both contact person name and mobile number.');
|
||||
}
|
||||
|
||||
let payload = { lead_id: lead_id, name: name, mobile: mobile };
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/contacts`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const contactResult = await res.json(); // Get the response body
|
||||
|
||||
if (res.ok) {
|
||||
// Clear inputs
|
||||
document.getElementById('contact_name').value = '';
|
||||
document.getElementById('contact_mobile').value = '';
|
||||
|
||||
// Hide "No Data" message
|
||||
const noData = document.getElementById('NoData');
|
||||
if(noData) noData.style.display = 'none';
|
||||
|
||||
// Create the HTML string
|
||||
let contactHtml = `
|
||||
<div class="contact-card d-flex justify-content-between align-items-center mb-2 p-3"
|
||||
style="background: #f8f8f8; border-radius: 8px;"
|
||||
data-id="${contactResult.data.contact_id}">
|
||||
<div>
|
||||
<div class="fw-bold">${contactResult.data.name}</div>
|
||||
<div class="text-muted">${contactResult.data.mobile}</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-sm btnRemoveContact"
|
||||
data-id="${contactResult.data.contact_id}">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// <button type="button"
|
||||
// class="btn btn-secondary btn-sm btnEditContact"
|
||||
// data-id="${contactResult.data.contact_id}">
|
||||
// Update
|
||||
// </button>
|
||||
// Append to container using Vanilla JS
|
||||
document.getElementById('savedContactsContainer').insertAdjacentHTML('beforeend', contactHtml);
|
||||
toastr.success('Contact saved successfully');
|
||||
} else {
|
||||
|
||||
if (res.status === 400) {
|
||||
let errorMessages = "";
|
||||
let seenMessages = [];
|
||||
if (contactResult.messages) {
|
||||
Object.entries(contactResult.messages).forEach(([field, message]) => {
|
||||
let inputElement = $('[name="' + field + '"]');
|
||||
|
||||
if (inputElement.length > 0) {
|
||||
// Make the field box turn red so the user sees it immediately
|
||||
inputElement.addClass('is-invalid');
|
||||
|
||||
// Focus on the first error field
|
||||
if (isFirstError) {
|
||||
// The 100ms delay safely bypasses Bootstrap's modal focus block
|
||||
setTimeout(function() {
|
||||
inputElement.focus();
|
||||
}, 100);
|
||||
|
||||
isFirstError = false;
|
||||
}
|
||||
}
|
||||
if (!seenMessages.includes(message)) {
|
||||
errorMessages += `• ${message}<br>`;
|
||||
seenMessages.push(message);
|
||||
}
|
||||
});
|
||||
toastr.error(errorMessages, 'Validation Error', { allowHtml: true });
|
||||
} else {
|
||||
toastr.warning(contactResult.message || 'Validation failed', 'Warning');
|
||||
}
|
||||
}
|
||||
else {
|
||||
toastr.error(contactResult.message || 'Error adding lead');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error('An error occurred');
|
||||
}
|
||||
};
|
||||
}
|
||||
const savedContactsContainer = document.getElementById('savedContactsContainer');
|
||||
if (savedContactsContainer) {
|
||||
savedContactsContainer.onclick = async (e) => {
|
||||
|
||||
// Check if the clicked element is a Remove button
|
||||
if (e.target.classList.contains('btnRemoveContact')) {
|
||||
const btn = e.target;
|
||||
const contactId = btn.dataset.id; // Get data-id
|
||||
const card = btn.closest('.contact-card'); // Find the parent card
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/contacts/${contactId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
card.remove(); // Remove from DOM
|
||||
toastr.success('Contact removed successfully');
|
||||
|
||||
// Check if container is empty to show "No Data"
|
||||
const container = document.getElementById('savedContactsContainer');
|
||||
if (container.querySelectorAll('.contact-card').length === 0) {
|
||||
const noData = document.getElementById('NoData');
|
||||
if(noData) noData.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
toastr.error('Failed to remove contact');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error('An error occurred');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function openEditLeadModal(id) {
|
||||
// 1. Reset UI State
|
||||
document.getElementById('hidden_lead_id').value = id;
|
||||
const container = $('#savedContactsContainer');
|
||||
const container = $('#contactPersonsList');
|
||||
// Remove only previous contact cards, keep the NoData span for now
|
||||
container.find('.contact-card').remove();
|
||||
|
||||
@ -1757,35 +1714,49 @@ async function openEditLeadModal(id) {
|
||||
form.querySelector('[name="address"]').value = lead.address || '';
|
||||
form.querySelector('[name="website"]').value = lead.website || '';
|
||||
form.querySelector('[name="gst_number"]').value = lead.gst_number || '';
|
||||
form.querySelector('[name="status"]').value = lead.status || 'New';
|
||||
// form.querySelector('[name="status"]').value = lead.status || 'New';
|
||||
form.querySelector('[name="assigned_to"]').value = lead.assigned_to || '';
|
||||
form.querySelector('[name="assigned_to"]').value = lead.assigned_to || '';
|
||||
$(form.querySelector('[name="assigned_to"]')).trigger('change');
|
||||
const statusSelect = form.querySelector('[name="status"]');
|
||||
statusSelect.value = lead.status || 'New';
|
||||
updateStatusOptions(statusSelect.value);
|
||||
|
||||
// 4. Handle Contact Persons (Looping through the nested array)
|
||||
const contacts = lead.contact_persons; // Array from your JSON
|
||||
|
||||
if (contacts && contacts.length > 0) {
|
||||
$('#NoData').hide();
|
||||
|
||||
$('#noContactPersonsData').hide();
|
||||
contacts.forEach(contact => {
|
||||
const isPrimary = contact.is_primary == 1;
|
||||
const primaryBadge = isPrimary ? `<div style="margin-top:2px;"><span class="badge bg-warning text-dark ml-1" style="font-size:9px;">PRIMARY</span></div>` : '';
|
||||
|
||||
let contactHtml = `
|
||||
<div class="contact-card d-flex justify-content-between align-items-center mb-2 p-3"
|
||||
style="background: #f8f8f8; border-radius: 8px;"
|
||||
data-id="${contact.contact_id}">
|
||||
<div>
|
||||
<div class="fw-bold">${contact.name}</div>
|
||||
<div class="text-muted">${contact.mobile}</div>
|
||||
<div class="contact-card d-flex align-items-center mb-2 p-2 px-3"
|
||||
style="background: ${isPrimary ? '#fff9c4' : '#f8f8f8'}; border: 1px solid ${isPrimary ? '#fbc02d' : '#eee'}; border-radius: 8px; gap: 12px;">
|
||||
|
||||
<div style="flex: 2; min-width: 0;">
|
||||
<div class="fw-bold text-truncate" style="font-size:14px;">${contact.name} ${primaryBadge}</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-sm btnRemoveContact"
|
||||
data-id="${contact.contact_id}">
|
||||
Remove
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
<div style="flex: 2; min-width: 0; color:#666; font-size:13px;">${contact.mobile}</div>
|
||||
<div style="flex: 2; min-width: 0; color:#666; font-size:13px;"><div class="text-truncate">${contact.designation}</div></div>
|
||||
|
||||
<div class="d-flex" style="gap:5px;">
|
||||
<button type="button" class="btnEditContact" data-id="${contact.contact_id}" data-info='${JSON.stringify(contact)}' title="Edit Contact Person"
|
||||
style="background:none; border:none; cursor:pointer; color:#1976d2; font-size:18px; flex-shrink:0; padding:4px 6px; border-radius:6px; transition:background 0.2s;"
|
||||
onmouseover="this.style.background='#e3f2fd'" onmouseout="this.style.background='none'">✏️</button>
|
||||
<button type="button" class="btnRemoveContact" data-id="${contact.contact_id}" title="Remove Contact Person"
|
||||
style="background:none; border:none; cursor:pointer; color:#1976d2; font-size:18px; flex-shrink:0; padding:4px 6px; border-radius:6px; transition:background 0.2s;"
|
||||
onmouseover="this.style.background='#ffebee'" onmouseout="this.style.background='none'">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.append(contactHtml);
|
||||
});
|
||||
} else {
|
||||
$('#NoData').show();
|
||||
}
|
||||
else {
|
||||
$('#noContactPersonsData').show();
|
||||
}
|
||||
|
||||
// 5. Open the Modal
|
||||
@ -1797,6 +1768,125 @@ async function openEditLeadModal(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 1. HANDLE EDIT & REMOVE (Event Delegation) ---
|
||||
document.getElementById('contactPersonsList').onclick = async (e) => {
|
||||
const btnEdit = e.target.closest('.btnEditContact');
|
||||
const btnRemove = e.target.closest('.btnRemoveContact');
|
||||
|
||||
// EDIT LOGIC
|
||||
if (btnEdit) {
|
||||
const card = btnEdit.closest('.contact-card');
|
||||
const contactData = JSON.parse(btnEdit.dataset.info);
|
||||
|
||||
// Fill Form
|
||||
document.getElementById('editing_contact_id').value = contactData.contact_id;
|
||||
document.getElementById('contact_name').value = contactData.name;
|
||||
document.getElementById('contact_mobile').value = contactData.mobile;
|
||||
document.getElementById('contact_designation').value = contactData.designation;
|
||||
document.getElementById('contact_is_primary').checked = contactData.is_primary == 1;
|
||||
|
||||
// Change Button UI
|
||||
const saveBtn = document.getElementById('btnSaveContact');
|
||||
saveBtn.innerHTML = "Update";
|
||||
|
||||
document.getElementById('contact_name').focus();
|
||||
}
|
||||
|
||||
// REMOVE LOGIC
|
||||
if (btnRemove) {
|
||||
const contactId = btnRemove.dataset.id;
|
||||
if (!confirm('Are you sure you want to remove this contact?')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/contacts/${contactId}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
toastr.success('Removed successfully');
|
||||
openEditLeadModal(document.getElementById('hidden_lead_id').value); // Refresh
|
||||
}
|
||||
} catch (err) { console.error(err); }
|
||||
}
|
||||
};
|
||||
|
||||
// --- 2. SAVE / UPDATE LOGIC ---
|
||||
const btnSaveContact = document.getElementById('btnSaveContact');
|
||||
if (btnSaveContact) {
|
||||
btnSaveContact.onclick = async (e) => {
|
||||
const editingId = document.getElementById('editing_contact_id').value;
|
||||
const leadId = document.getElementById('hidden_lead_id').value;
|
||||
|
||||
let payload = {
|
||||
lead_id: leadId,
|
||||
name: document.getElementById('contact_name').value.trim(),
|
||||
mobile: document.getElementById('contact_mobile').value.trim(),
|
||||
designation: document.getElementById('contact_designation').value.trim(),
|
||||
is_primary: document.getElementById('contact_is_primary').checked ? 1 : 0
|
||||
};
|
||||
|
||||
if (!payload.name || !payload.mobile) {
|
||||
return toastr.warning('Name and Mobile are required');
|
||||
}
|
||||
|
||||
// Determine if we POST (new) or PUT (edit)
|
||||
const url = editingId ? `${API}/contacts/${editingId}` : `${API}/contacts`;
|
||||
const method = editingId ? 'PUT' : 'POST';
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success(editingId ? 'Contact Updated' : 'Contact Saved');
|
||||
|
||||
// Reset Form UI
|
||||
document.getElementById('editing_contact_id').value = '';
|
||||
document.getElementById('contact_name').value = '';
|
||||
document.getElementById('contact_mobile').value = '';
|
||||
document.getElementById('contact_designation').value = '';
|
||||
document.getElementById('contact_is_primary').checked = false;
|
||||
|
||||
const saveBtn = document.getElementById('btnSaveContact');
|
||||
saveBtn.innerHTML = "✔ Save";
|
||||
// Refresh List
|
||||
openEditLeadModal(leadId);
|
||||
} else {
|
||||
const err = await res.json();
|
||||
toastr.error(err.message || 'Error processing request');
|
||||
}
|
||||
} catch (error) { console.error(error); }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const statusOrder = ['New', 'Potential', 'Prospects', 'Not a Prospects'];
|
||||
|
||||
function updateStatusOptions(currentStatus) {
|
||||
|
||||
let Eform = document.getElementById('editLeadForm');
|
||||
if (!Eform) return; // safety check
|
||||
|
||||
|
||||
let select = Eform.querySelector('[name="status"]');
|
||||
let currentIndex = statusOrder.indexOf(currentStatus);
|
||||
|
||||
// First show all
|
||||
select.querySelectorAll('option').forEach(opt => {
|
||||
opt.hidden = false;
|
||||
});
|
||||
|
||||
// Hide previous ones
|
||||
if (currentIndex > -1) {
|
||||
statusOrder.slice(0, currentIndex).forEach(status => {
|
||||
const opt = select.querySelector(`option[value="${status}"]`);
|
||||
if (opt) opt.hidden = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function convertDBFormatted(input) {
|
||||
|
||||
if (!input) return null;
|
||||
|
||||
@ -46,6 +46,17 @@
|
||||
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
|
||||
|
||||
<?php if (
|
||||
$ticket_data['is_tpa_api_service_enabled'] == true &&
|
||||
empty($ticket_data['tpa_claim_push_reference_no']) &&
|
||||
empty($ticket_data['claim_number'])
|
||||
) : ?>
|
||||
<a href="#" class="btn btn-success mr-2" onclick="manualTpaClaimPush(); return false;">
|
||||
Manual TPA Claim Push
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if(isset($ticket_data['tpa_claim_push_reference_no']) && !empty($ticket_data['tpa_claim_push_reference_no'])) : ?>
|
||||
<a href="#" class="btn btn-success mr-2" onclick="fetchTpaClaimStatus()">
|
||||
Fetch Claim Status
|
||||
@ -385,6 +396,57 @@
|
||||
});
|
||||
}
|
||||
|
||||
function manualTpaClaimPush() {
|
||||
let ticket_master_id = $('#ticket_master_id').val();
|
||||
if (!ticket_master_id) {
|
||||
toastr.warning('Claim ID not found. Please ensure the form is loaded.', 'Warning');
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: 'Manual TPA Claim Push',
|
||||
text: 'Do you want to push this claim to the TPA now?',
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#00999E',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Yes, push claim',
|
||||
cancelButtonText: 'Cancel'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$('.loader').fadeIn();
|
||||
$('.loader-mask').fadeIn();
|
||||
|
||||
let requestData = { claim_id: ticket_master_id };
|
||||
let url = '<?= base_url('ticket/manualTpaClaimPush') ?>';
|
||||
|
||||
sendAjaxRequestForGlobal(url, 'POST', requestData, function(response) {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
console.log('Manual TPA claim push response:', response);
|
||||
if (response.status) {
|
||||
toastr.success(response.message || 'Claim pushed to TPA successfully', 'Success');
|
||||
window.location.reload(true);
|
||||
} else {
|
||||
toastr.warning(response.message || 'Claim push failed', 'Warning');
|
||||
}
|
||||
}, function(xhr, status, error) {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
console.error('Error pushing claim:', error);
|
||||
console.error(xhr.responseText);
|
||||
let msg = 'An error occurred while pushing the claim to TPA.';
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
msg = xhr.responseJSON.message;
|
||||
} else if (xhr.status === 404) {
|
||||
msg = 'Manual TPA Claim Push endpoint is not configured. Please contact support.';
|
||||
}
|
||||
toastr.error(msg, 'Error');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchTpaClaimStatus(){
|
||||
|
||||
let ticket_master_id = $('#ticket_master_id').val();
|
||||
|
||||
278
tests/unit/PolicyTransactionControllerTest.php
Normal file
278
tests/unit/PolicyTransactionControllerTest.php
Normal file
@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\unit;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use App\Controllers\PolicyTransactionController;
|
||||
use App\Models\PolicyTransactionModel;
|
||||
|
||||
class PolicyTransactionControllerTest extends CIUnitTestCase
|
||||
{
|
||||
/**
|
||||
* Helper to build a fully initialised controller instance.
|
||||
*/
|
||||
protected function makeController(): PolicyTransactionController
|
||||
{
|
||||
$controller = new PolicyTransactionController();
|
||||
|
||||
$request = \Config\Services::request();
|
||||
$response = \Config\Services::response();
|
||||
$logger = \Config\Services::logger();
|
||||
|
||||
$controller->initController($request, $response, $logger);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to inject a stubbed PolicyTransactionModel into the controller.
|
||||
*
|
||||
* @param PolicyTransactionController $controller
|
||||
* @param array $stubData
|
||||
*/
|
||||
protected function injectBdsStubModel(PolicyTransactionController $controller, array $stubData): void
|
||||
{
|
||||
$stubModel = new class($stubData)
|
||||
{
|
||||
private array $data;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getBDSReportList(
|
||||
$start_date = 0,
|
||||
$end_date = 0,
|
||||
$client_id = 0,
|
||||
$insurer_id = 0,
|
||||
$policy_type_id = 0,
|
||||
$date_type = 0,
|
||||
$issuer = 0,
|
||||
$client_branch_id = 0,
|
||||
$insurer_branch_id = 0,
|
||||
$client_policy_id = 0,
|
||||
$user_id = 0,
|
||||
$where = [],
|
||||
) {
|
||||
return $this->data;
|
||||
}
|
||||
};
|
||||
|
||||
$refClass = new \ReflectionClass($controller);
|
||||
$prop = $refClass->getProperty('policyTransactionModel');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue($controller, $stubModel);
|
||||
}
|
||||
|
||||
public function testCronDailyBDSReportReturnsUnauthorizedWithoutKey(): void
|
||||
{
|
||||
putenv('cron.secretKey=unit-test-secret');
|
||||
|
||||
$_GET = [];
|
||||
|
||||
$controller = $this->makeController();
|
||||
|
||||
$response = $controller->cronDailyBDSReport();
|
||||
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
|
||||
$body = json_decode($response->getBody(), true);
|
||||
$this->assertIsArray($body);
|
||||
$this->assertSame('error', $body['status'] ?? null);
|
||||
}
|
||||
|
||||
public function testCronDailyBDSReportNoRecordsReturnsSuccessMessage(): void
|
||||
{
|
||||
putenv('cron.secretKey=unit-test-secret');
|
||||
putenv('bds.reportEmails='); // no recipients for this test
|
||||
|
||||
$_GET['key'] = 'unit-test-secret';
|
||||
|
||||
$controller = $this->makeController();
|
||||
|
||||
$this->injectBdsStubModel($controller, []);
|
||||
|
||||
$response = $controller->cronDailyBDSReport();
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
|
||||
$body = json_decode($response->getBody(), true);
|
||||
$this->assertIsArray($body);
|
||||
$this->assertSame('success', $body['status'] ?? null);
|
||||
$this->assertStringContainsString(
|
||||
'No BDS records for today',
|
||||
$body['message'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Integration-style test: generate an Excel file for today's BDS data
|
||||
* and store it in the user's Downloads folder as BDS_DAILY_REPORT_TEST_CASE.xlsx.
|
||||
*
|
||||
* This reuses the same transformation logic as cronDailyBDSReport.
|
||||
*/
|
||||
public function testGenerateDailyBdsReportExcelToDownloads(): void
|
||||
{
|
||||
helper(['excel_import_export_helper', 'utility_helper']);
|
||||
|
||||
// Sample BDS row to generate a test Excel (no DB dependency)
|
||||
$today = date('Y-m-d');
|
||||
$reportList = [
|
||||
[
|
||||
'user_name' => 'Test User',
|
||||
'policy_issue_month' => 'Mar 2026',
|
||||
'revenue_type' => 'Fresh',
|
||||
'client_type' => 'Group',
|
||||
'client_name' => 'Test Client',
|
||||
'action_type' => 'Policy',
|
||||
'policy_type' => 'Health',
|
||||
'bap' => 'BAP-GRP',
|
||||
'vehicle_no' => 'TN01AB1234',
|
||||
'policy_no' => 'P-TEST-001',
|
||||
'endorsement_no' => 'E-TEST-001',
|
||||
'insurer_branch_name' => 'Chennai Branch',
|
||||
'endorse_eff_date' => $today,
|
||||
'policy_start_date' => $today,
|
||||
'policy_end_date' => $today,
|
||||
'ref' => 'REF-001',
|
||||
'remarks' => 'Test remark',
|
||||
'bp_amt' => 1000,
|
||||
'tp_or_ter' => 500,
|
||||
'premium_wo_gst' => 1500,
|
||||
'total_premium' => 1770,
|
||||
'agreed_bp_per' => 10,
|
||||
'agreed_tp_or_ter_per' => 5,
|
||||
'reward' => 100,
|
||||
'total_irda_amt' => 800,
|
||||
'billed_amt' => 300,
|
||||
'salse_person_name' => 'Sales Person',
|
||||
'service_person_name' => 'Service Person',
|
||||
'nhance_branch' => 'Nhance Chennai',
|
||||
'installment' => '1',
|
||||
'data_received_date' => $today,
|
||||
'renewal_date' => $today,
|
||||
'co_share' => 'No',
|
||||
'bro_payable_by' => 'No',
|
||||
'salse_manager_name' => 'Sales Manager',
|
||||
'service_manager_name' => 'Service Manager',
|
||||
'service_branch' => 'Service Branch',
|
||||
'rollover_date' => $today,
|
||||
'policy_holder_name' => 'Holder Name',
|
||||
'same_as_proposer' => 'Yes',
|
||||
'follower_policy_no' => 'F-TEST-001',
|
||||
'co_share_per' => 0,
|
||||
'non_comm_per_amt' => 0,
|
||||
'bp_cgst' => 0,
|
||||
'bp_sgst' => 0,
|
||||
'bp_igst' => 0,
|
||||
'stamp_duty' => 0,
|
||||
'standerd_bp_per' => 0,
|
||||
'standerd_tp_per' => 0,
|
||||
'actual_bp_amt' => 0,
|
||||
'actual_tp_amt' => 0,
|
||||
'actual_bp_per' => 0,
|
||||
'actual_tp_per' => 0,
|
||||
'actual_tep_brokerage_amt' => 0,
|
||||
'actual_tp_brokerage_amt' => 0,
|
||||
'cd_ac_no' => 'CD-001',
|
||||
],
|
||||
];
|
||||
|
||||
$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'
|
||||
];
|
||||
}
|
||||
|
||||
$filePath = '/home/venkat/Downloads/BDS_DAILY_REPORT_TEST_CASE.xlsx';
|
||||
|
||||
$generated = generate_excel($headers, $excelData, $filePath);
|
||||
|
||||
$this->assertTrue($generated, 'Failed to generate BDS_DAILY_REPORT_TEST_CASE.xlsx in Downloads');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user