Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
543cfced62
@ -129,4 +129,7 @@ HEALTH_INDIA_TOKEN_URL =
|
||||
HEALTH_INDIA_USERNAME =
|
||||
HEALTH_INDIA_PASSWORD =
|
||||
|
||||
HEALTH_INDIA_PRIMARY_KEY_CONSTANT =
|
||||
HEALTH_INDIA_PRIMARY_KEY_CONSTANT =
|
||||
|
||||
# BDS Daily Report Emails Configuration
|
||||
bds.dailyReportEmails =
|
||||
|
||||
@ -32,6 +32,8 @@ class Acl
|
||||
'#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sales/dashboard#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
'#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
'#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -444,11 +444,17 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('proceedExcelFileDataValidation', 'EmployeeController::proceedExcelFileDataValidation');
|
||||
$routes->get('checkTpaApiEnable', 'EmployeeRestController::checkTpaApiEnable');
|
||||
$routes->get('generateDemographyDataTable', 'LeadsController::generateDemographyDataTable');
|
||||
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
||||
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
||||
$routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1');
|
||||
$routes->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
|
||||
});
|
||||
|
||||
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
$routes->cli("cli/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
$routes->cli("cli/cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport");
|
||||
$routes->cli('cli/croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
|
||||
|
||||
|
||||
|
||||
$routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
|
||||
|
||||
@ -496,6 +502,7 @@ $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
|
||||
//$routes->post('failedStatement',"PolicyTransactionController::failedStatementList");
|
||||
});
|
||||
|
||||
$routes->get("cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport");
|
||||
});
|
||||
|
||||
$routes->group("leads", ["filter" => "authMVC"], function ($routes) {
|
||||
@ -1011,4 +1018,13 @@ $routes->group('sales', function($routes) {
|
||||
$routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard');
|
||||
});
|
||||
|
||||
// Expence Module Route Group
|
||||
$routes->group('expense', ["filter" => "authMVC", 'namespace' => 'App\Controllers'], static function($routes) {
|
||||
$routes->get('/', 'ExpenseController::index');
|
||||
$routes->post('save', 'ExpenseController::save');
|
||||
$routes->get('get/(:num)', 'ExpenseController::getExpense/$1');
|
||||
$routes->post('delete/(:num)', 'ExpenseController::delete/$1');
|
||||
$routes->get('client-policies', 'ExpenseController::clientPolicies');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -6969,16 +6969,23 @@ class ClientController extends AdminController
|
||||
// $response = $ticketServiceController->getClaimExcelErrorData(["file_id" => 41]);
|
||||
// $response = $ticketServiceController->claimDumpOnBoardProcess(["file_id" => 17]);
|
||||
// $response = $ticketServiceController->extractExcelData("claims_dump_form_client.xlsx");
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 51]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 50]); //fhpl
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 53]); //icici
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 54]); //mediassist
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 52]); //reliance
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 48]); //vidal`
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 57]); //mediassist
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 4]); //abhi
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 51]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 50]); //fhpl
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 53]); //icici
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 52]); //reliance
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->getTpaClaimDumpErrorData(["file_id" => 48]);
|
||||
// dd($response);
|
||||
|
||||
// ---------- TICKET CONTROLLER --------------------------------------------------------------------------------
|
||||
|
||||
@ -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');
|
||||
|
||||
412
app/Controllers/ExpenseController.php
Normal file
412
app/Controllers/ExpenseController.php
Normal file
@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\ExpenseModel;
|
||||
use App\Models\ClientModel;
|
||||
use App\Models\ClientPolicyModel;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
class ExpenseController extends AdminController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected $myLogger;
|
||||
protected $expenseModel;
|
||||
protected $clientModel;
|
||||
protected $clientPolicyModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
set_session_context('Expense');
|
||||
|
||||
$this->myLogger = \Config\Services::mylogger();
|
||||
$this->expenseModel = new ExpenseModel();
|
||||
$this->clientModel = new ClientModel();
|
||||
$this->clientPolicyModel = new ClientPolicyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Web list + form view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$data['tab_name'] = 'Expense';
|
||||
$data['page_name'] = 'Expense';
|
||||
$descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/u';
|
||||
|
||||
// Raw GET filters
|
||||
$rawFilters = $this->request->getGet() ?? [];
|
||||
$rawFilters = is_array($rawFilters) ? $rawFilters : [];
|
||||
|
||||
// Sanitize input array using existing helper
|
||||
$sanitized = sanitizeInputArrayAdvanced($rawFilters);
|
||||
|
||||
$filters = [
|
||||
'client_id' => trim($sanitized['client_id'] ?? ''),
|
||||
'client_policy_id' => trim($sanitized['client_policy_id'] ?? ''),
|
||||
'approved_by' => trim($sanitized['approved_by'] ?? ''),
|
||||
'description' => trim($sanitized['description'] ?? ''),
|
||||
'amount' => trim($sanitized['amount'] ?? ''),
|
||||
'expense_date' => trim($sanitized['expense_date'] ?? ''),
|
||||
];
|
||||
|
||||
$validationErrors = [];
|
||||
|
||||
// Basic type / format validation for filters
|
||||
if ($filters['client_id'] !== '' && ! ctype_digit($filters['client_id'])) {
|
||||
$validationErrors[] = 'Invalid client selected for search.';
|
||||
$filters['client_id'] = '';
|
||||
}
|
||||
|
||||
if ($filters['client_policy_id'] !== '' && ! ctype_digit($filters['client_policy_id'])) {
|
||||
$validationErrors[] = 'Invalid policy selected for search.';
|
||||
$filters['client_policy_id'] = '';
|
||||
}
|
||||
|
||||
if ($filters['approved_by'] !== '' && ! ctype_digit($filters['approved_by'])) {
|
||||
$validationErrors[] = 'Invalid approver selected for search.';
|
||||
$filters['approved_by'] = '';
|
||||
}
|
||||
|
||||
if ($filters['description'] !== '' && ! preg_match($descriptionPattern, $filters['description'])) {
|
||||
$validationErrors[] = 'Description filter contains invalid characters.';
|
||||
$filters['description'] = '';
|
||||
}
|
||||
|
||||
if ($filters['amount'] !== '') {
|
||||
if (! is_numeric($filters['amount']) || (float) $filters['amount'] < 0) {
|
||||
$validationErrors[] = 'Amount filter must be a non-negative number.';
|
||||
$filters['amount'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($filters['expense_date'] !== '') {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']);
|
||||
$errors = $dt ? \DateTime::getLastErrors() : ['warning_count' => 1, 'error_count' => 1];
|
||||
if (! $dt || ! empty($errors['warning_count']) || ! empty($errors['error_count'])) {
|
||||
$validationErrors[] = 'Expense Date filter must be in DD-MM-YYYY format.';
|
||||
$filters['expense_date'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$data['filters'] = $filters;
|
||||
$data['validation_errors'] = $validationErrors;
|
||||
|
||||
// Clients for dropdown
|
||||
$data['clients'] = $this->clientModel
|
||||
->select('id, client_name, short_name')
|
||||
->where('is_active', 1)
|
||||
->orderBy('client_name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
// Approved by (users) dropdown
|
||||
$db = db_connect();
|
||||
$data['approved_users'] = $db->table('user_profiles')
|
||||
->select('id, first_name')
|
||||
->where('is_active', 1)
|
||||
->whereIn('id', [7, 8])
|
||||
->orderBy('first_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// Policies for filter dropdown (when client filter is selected)
|
||||
$data['policies_for_filter'] = [];
|
||||
if ($filters['client_id'] !== '') {
|
||||
$data['policies_for_filter'] = $this->clientPolicyModel
|
||||
->select('id, policy_no')
|
||||
->where('client_id', (int) $filters['client_id'])
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
// Existing expenses with optional filters
|
||||
$builder = $this->expenseModel
|
||||
->select('
|
||||
expenses.*,
|
||||
clients.client_name,
|
||||
clients.short_name,
|
||||
client_policy.policy_no,
|
||||
user_profiles.first_name AS approved_by_name
|
||||
')
|
||||
->join('clients', 'clients.id = expenses.client_id')
|
||||
->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left')
|
||||
->where('expenses.is_active', 1);
|
||||
|
||||
if ($filters['client_id'] !== '') {
|
||||
$builder->where('expenses.client_id', (int) $filters['client_id']);
|
||||
}
|
||||
|
||||
if ($filters['client_policy_id'] !== '') {
|
||||
$builder->where('expenses.client_policy_id', (int) $filters['client_policy_id']);
|
||||
}
|
||||
|
||||
if ($filters['approved_by'] !== '') {
|
||||
$builder->where('expenses.approved_by', (int) $filters['approved_by']);
|
||||
}
|
||||
|
||||
if ($filters['description'] !== '') {
|
||||
$builder->like('expenses.description', (string) $filters['description']);
|
||||
}
|
||||
|
||||
if ($filters['amount'] !== '') {
|
||||
$builder->where('expenses.amount', (float) $filters['amount']);
|
||||
}
|
||||
|
||||
if ($filters['expense_date'] !== '') {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']);
|
||||
if ($dt) {
|
||||
$builder->where('expenses.expense_date', $dt->format('Y-m-d'));
|
||||
}
|
||||
}
|
||||
|
||||
$data['expenses'] = $builder
|
||||
->orderBy('expenses.id', 'DESC')
|
||||
->findAll();
|
||||
|
||||
return $this->loadLayout('expense_list', $data);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create / update expense (AJAX)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
try {
|
||||
if ($this->request->getMethod() !== 'post') {
|
||||
return $this->response
|
||||
->setStatusCode(405)
|
||||
->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid request method',
|
||||
]);
|
||||
}
|
||||
|
||||
$rawData = $this->request->getPost();
|
||||
$data = sanitizeInputArrayAdvanced($rawData);
|
||||
|
||||
$id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : null;
|
||||
|
||||
$rules = [
|
||||
'client_id' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Client is required',
|
||||
],
|
||||
],
|
||||
'client_policy_id' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Policy is required',
|
||||
],
|
||||
],
|
||||
'description' => [
|
||||
'rules' => 'required|string|min_length[1]|max_length[2000]|regex_match[/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Description is required',
|
||||
'min_length' => 'Description cannot be empty',
|
||||
'regex_match' => 'Description contains invalid characters.',
|
||||
],
|
||||
],
|
||||
'expense_date' => [
|
||||
'rules' => 'required|valid_date[d-m-Y]',
|
||||
'errors' => [
|
||||
'required' => 'Expense Date is required',
|
||||
'valid_date' => 'Expense Date must be in DD-MM-YYYY format',
|
||||
],
|
||||
],
|
||||
'approved_by' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Approved By is required',
|
||||
],
|
||||
],
|
||||
'amount' => [
|
||||
'rules' => 'required|numeric|greater_than_equal_to[0]',
|
||||
'errors' => [
|
||||
'required' => 'Amount is required',
|
||||
'numeric' => 'Amount must be numeric',
|
||||
'greater_than_equal_to' => 'Amount cannot be negative',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Input validation failed',
|
||||
'errors' => $this->validator->getErrors(),
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'client_id' => (int) ($data['client_id'] ?? 0),
|
||||
'client_policy_id' => (int) ($data['client_policy_id'] ?? 0),
|
||||
'description' => $data['description'] ?? null,
|
||||
'approved_by' => (int) ($data['approved_by'] ?? 0),
|
||||
'amount' => $data['amount'] ?? null,
|
||||
'is_active' => 1,
|
||||
];
|
||||
|
||||
$expenseDate = $data['expense_date'] ?? null;
|
||||
if (! empty($expenseDate)) {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $expenseDate);
|
||||
$payload['expense_date'] = $dt ? $dt->format('Y-m-d') : null;
|
||||
} else {
|
||||
$payload['expense_date'] = null;
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
$insertId = $this->expenseModel->insert($payload, true);
|
||||
$success = ! empty($insertId);
|
||||
$id = $insertId;
|
||||
$message = $success
|
||||
? 'Expense created successfully'
|
||||
: 'Unable to create expense. Please try again.';
|
||||
} else {
|
||||
$success = $this->expenseModel->update($id, $payload);
|
||||
$message = $success
|
||||
? 'Expense updated successfully'
|
||||
: 'Unable to update expense. Please try again.';
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setStatusCode($success ? 200 : 400)
|
||||
->setJSON([
|
||||
'status' => (bool) $success,
|
||||
'message' => $message,
|
||||
'id' => $id,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single expense (AJAX)
|
||||
*/
|
||||
public function getExpense($id = null)
|
||||
{
|
||||
try {
|
||||
$id = (int) $id;
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid expense id',
|
||||
]);
|
||||
}
|
||||
|
||||
$expense = $this->expenseModel
|
||||
->select('
|
||||
expenses.*,
|
||||
clients.client_name,
|
||||
clients.short_name,
|
||||
client_policy.policy_no,
|
||||
user_profiles.first_name AS approved_by_name
|
||||
')
|
||||
->join('clients', 'clients.id = expenses.client_id', 'left')
|
||||
->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left')
|
||||
->where('expenses.id', $id)
|
||||
->where('expenses.is_active', 1)
|
||||
->first();
|
||||
|
||||
if (empty($expense)) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Expense not found',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)->setJSON([
|
||||
'status' => true,
|
||||
'data' => $expense,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete expense (AJAX)
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
try {
|
||||
if ($this->request->getMethod() !== 'post') {
|
||||
return $this->response
|
||||
->setStatusCode(405)
|
||||
->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid request method',
|
||||
]);
|
||||
}
|
||||
|
||||
$id = (int) $id;
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid expense id',
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'is_active' => 0,
|
||||
];
|
||||
|
||||
$success = $this->expenseModel->update($id, $payload);
|
||||
|
||||
return $this->response
|
||||
->setStatusCode($success ? 200 : 400)
|
||||
->setJSON([
|
||||
'status' => (bool) $success,
|
||||
'message' => $success
|
||||
? 'Expense deleted successfully'
|
||||
: 'Unable to delete expense. Please try again.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get policies by client for dropdown (AJAX)
|
||||
*/
|
||||
public function clientPolicies()
|
||||
{
|
||||
try {
|
||||
$clientId = (int) ($this->request->getGet('client_id') ?? 0);
|
||||
|
||||
if (empty($clientId)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Client is required',
|
||||
]);
|
||||
}
|
||||
|
||||
$policies = $this->clientPolicyModel
|
||||
->select('client_policy.id, client_policy.policy_no, policy_type.policy_type')
|
||||
->join('policy_type', 'policy_type.id = client_policy.policy_type_id AND policy_type.is_active = 1')
|
||||
->where('client_policy.client_id', $clientId)
|
||||
->where('client_policy.is_active', 1)
|
||||
->orderBy('client_policy.id', 'ASC')
|
||||
->findAll();
|
||||
|
||||
return $this->response->setStatusCode(200)->setJSON([
|
||||
'status' => true,
|
||||
'data' => $policies,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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([
|
||||
|
||||
@ -1173,9 +1173,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 +1526,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',
|
||||
|
||||
@ -30,8 +30,9 @@ use App\Models\TicketMasterModel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
|
||||
class TicketServiceController extends BaseController
|
||||
class TicketServiceController extends AdminController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
@ -1840,39 +1841,86 @@ class TicketServiceController extends BaseController
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public function tpaClaimDumpImporter($params)
|
||||
/**
|
||||
* Resolve and validate Claim Dump file metadata and physical file for TPA imports.
|
||||
*
|
||||
* @param int|null $fileId
|
||||
* @return array{status:bool,message?:string,fileData?:array,filePath?:string}
|
||||
*/
|
||||
private function resolveTpaClaimDumpFile(?int $fileId): array
|
||||
{
|
||||
$file_id = $params['file_id'] ?? null; // Move outside try to ensure catch can see it
|
||||
if (empty($fileId)) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'File ID is missing',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find((int) $fileId);
|
||||
if (!$fileData) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Invalid file ID. No file data found',
|
||||
];
|
||||
}
|
||||
|
||||
$filePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR . $fileData['file_name'];
|
||||
|
||||
if (!is_file($filePath)) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Claim dump file not found',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'fileData' => $fileData,
|
||||
'filePath' => $filePath,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* First TPA-wise job – parse Excel and push data into TPA staging table.
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function tpaClaimDumpImporter(array $params)
|
||||
{
|
||||
$file_id = isset($params['file_id']) ? (int) $params['file_id'] : null;
|
||||
|
||||
try {
|
||||
$file_path = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR;
|
||||
$resolved = $this->resolveTpaClaimDumpFile($file_id);
|
||||
|
||||
if (!$file_id) {
|
||||
return ['status' => false, 'message' => 'File ID is missing'];
|
||||
if ($resolved['status'] === false) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => $resolved['message'] ?? 'Unable to resolve claim dump file',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find((int)$file_id);
|
||||
if (!$fileData) {
|
||||
return ['status' => false, 'message' => 'Invalid file ID. No file data found'];
|
||||
}
|
||||
|
||||
$file_full_path = $file_path . $fileData['file_name'];
|
||||
if (!is_file($file_full_path)) {
|
||||
return ['status' => false, 'message' => 'Claim dump file not found'];
|
||||
}
|
||||
$fileData = $resolved['fileData'];
|
||||
$filePath = $resolved['filePath'];
|
||||
|
||||
$handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0);
|
||||
$result = $handler->runTpaClaimDumpInsert($file_full_path, $file_id);
|
||||
$result = $handler->runTpaClaimDumpInsert($filePath, $file_id);
|
||||
|
||||
if (!empty($result['status']) && $result['status'] === true) {
|
||||
Jobs::addJob(['job_name' => 'tpaClaimDumpToTicketMasterImporters', 'payload' => ['file_id' => $file_id]]);
|
||||
Jobs::addJob([
|
||||
'job_name' => 'tpaClaimDumpToTicketMasterImporters',
|
||||
'payload' => ['file_id' => $file_id],
|
||||
]);
|
||||
} else {
|
||||
// FORCE FAIL LOGIC
|
||||
$this->markAsFailed($file_id, $result['message'] ?? 'System error contact admin', $fileData['created_by'] ?? null);
|
||||
$this->markAsFailed(
|
||||
$file_id,
|
||||
$result['message'] ?? 'System error contact admin',
|
||||
$fileData['created_by'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -1884,9 +1932,9 @@ class TicketServiceController extends BaseController
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'TPA Claim dump import failed',
|
||||
'error_data' => $th->getMessage()
|
||||
'status' => false,
|
||||
'message' => 'TPA Claim dump import failed',
|
||||
'error_data' => $th->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1908,30 +1956,37 @@ class TicketServiceController extends BaseController
|
||||
return $this->claimDumpFileModel->update($file_id, $data);
|
||||
}
|
||||
|
||||
public function tpaClaimDumpToTicketMasterImporters($params)
|
||||
/**
|
||||
* Second TPA-wise job – move data from TPA staging into ticket_master.
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function tpaClaimDumpToTicketMasterImporters(array $params)
|
||||
{
|
||||
$file_id = $params['file_id'] ?? null;
|
||||
$file_id = isset($params['file_id']) ? (int) $params['file_id'] : null;
|
||||
$fileData = null;
|
||||
|
||||
try {
|
||||
if (!$file_id) {
|
||||
return ['status' => false, 'message' => 'File ID is missing'];
|
||||
$resolved = $this->resolveTpaClaimDumpFile($file_id);
|
||||
|
||||
if ($resolved['status'] === false) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => $resolved['message'] ?? 'Unable to resolve claim dump file',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->where('id', $file_id)->first();
|
||||
|
||||
if (!$fileData) {
|
||||
return ['status' => false, 'message' => 'Invalid file ID. No file data found to import'];
|
||||
}
|
||||
$fileData = $resolved['fileData'];
|
||||
|
||||
$handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0);
|
||||
$result = $handler->runTicketMasterInsert($params);
|
||||
$result = $handler->runTicketMasterInsert($params);
|
||||
|
||||
if (!empty($result['status']) && $result['status'] === true) {
|
||||
// Success: Update the status to success
|
||||
$this->claimDumpFileModel->update($file_id, [
|
||||
'status' => 'success',
|
||||
'reason' => null
|
||||
'reason' => null,
|
||||
]);
|
||||
} else {
|
||||
// Logic failure: The runTicketMasterInsert returned status false
|
||||
@ -1957,8 +2012,8 @@ class TicketServiceController extends BaseController
|
||||
'message' => $th->getMessage(),
|
||||
'error_data' => [
|
||||
'line' => $th->getLine(),
|
||||
'file' => $th->getFile()
|
||||
]
|
||||
'file' => $th->getFile(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -2011,5 +2066,176 @@ class TicketServiceController extends BaseController
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function getTpaClaimDumpErrorData($file_id)
|
||||
{
|
||||
$file_id = (int) $file_id;
|
||||
|
||||
// Ensure we always have a Response object, even if controller was instantiated manually
|
||||
$response = $this->response ?? service('response');
|
||||
|
||||
if ($file_id <= 0) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'Invalid file id']);
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find($file_id);
|
||||
|
||||
if (!$fileData) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_NOT_FOUND)
|
||||
->setJSON(['status' => false, 'message' => 'File record not found']);
|
||||
}
|
||||
|
||||
$tpaId = (int) ($fileData['tpa_id'] ?? 0);
|
||||
|
||||
if ($tpaId <= 0) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'TPA not linked with this file']);
|
||||
}
|
||||
|
||||
// Resolve TPA staging table based on configured TPA IDs
|
||||
$tableName = match ($tpaId) {
|
||||
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal',
|
||||
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi',
|
||||
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist',
|
||||
(int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl',
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($tableName === null) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'Unsupported TPA for error dump export']);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$builder = $db->table($tableName);
|
||||
|
||||
// Fetch only records belonging to this file and having a rejection reason
|
||||
$rows = $builder
|
||||
->where('file_id', $file_id)
|
||||
->where('is_active', 1)
|
||||
->where('master_reject_reason IS NOT NULL', null, false)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
// No error records – return a small Excel file with just a message
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Errors');
|
||||
$sheet->setCellValue('A1', 'Message');
|
||||
$sheet->setCellValue('A2', 'No rejected records found for this file.');
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
ob_start();
|
||||
$writer->save('php://output');
|
||||
$excelOutput = ob_get_clean();
|
||||
|
||||
$filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx';
|
||||
|
||||
return $response
|
||||
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
||||
->setHeader('Cache-Control', 'max-age=0')
|
||||
->setBody($excelOutput);
|
||||
}
|
||||
|
||||
// Columns that must NOT be included in the Excel export
|
||||
$excludedColumns = [
|
||||
'id',
|
||||
'client_id',
|
||||
'client_policy_id',
|
||||
'ticket_id',
|
||||
'file_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
$firstRow = $rows[0];
|
||||
|
||||
// Build header mapping and track the "Rejected Reason" column index
|
||||
$dbColumnOrder = [];
|
||||
$displayHeaderLabels = [];
|
||||
$rejectedReasonColIdx = null; // 1-based index for Excel column
|
||||
|
||||
foreach ($firstRow as $columnName => $_) {
|
||||
if (in_array($columnName, $excludedColumns, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dbColumnOrder[] = $columnName;
|
||||
|
||||
if ($columnName === 'master_reject_reason' || $columnName === 'master_rejection_reason_key') {
|
||||
$displayHeaderLabels[] = 'Rejected Reason';
|
||||
$rejectedReasonColIdx = count($displayHeaderLabels); // current column index (1-based)
|
||||
} else {
|
||||
$displayHeaderLabels[] = $columnName;
|
||||
}
|
||||
}
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Errors');
|
||||
|
||||
// Header row
|
||||
$rowIndex = 1;
|
||||
foreach ($displayHeaderLabels as $colIndex => $headerLabel) {
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($colIndex + 1);
|
||||
$cellAddress = $columnLetter . $rowIndex;
|
||||
$sheet->setCellValue($cellAddress, $headerLabel);
|
||||
}
|
||||
|
||||
// Data rows
|
||||
$rowIndex = 2;
|
||||
foreach ($rows as $row) {
|
||||
foreach ($dbColumnOrder as $i => $columnName) {
|
||||
$colIndex = $i + 1;
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||
$cellAddress = $columnLetter . $rowIndex;
|
||||
$value = $row[$columnName] ?? null;
|
||||
|
||||
$sheet->setCellValue($cellAddress, $value);
|
||||
|
||||
// Highlight the "Rejected Reason" values
|
||||
if ($rejectedReasonColIdx !== null && $colIndex === $rejectedReasonColIdx) {
|
||||
$sheet->getStyle($cellAddress)
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('FFFFF4B2'); // light yellow
|
||||
}
|
||||
}
|
||||
|
||||
$rowIndex++;
|
||||
}
|
||||
|
||||
// Autosize columns for better readability
|
||||
$highestColumnIndex = count($displayHeaderLabels);
|
||||
for ($col = 1; $col <= $highestColumnIndex; $col++) {
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($col);
|
||||
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
ob_start();
|
||||
$writer->save('php://output');
|
||||
$excelOutput = ob_get_clean();
|
||||
|
||||
$filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx';
|
||||
|
||||
return $response
|
||||
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
||||
->setHeader('Cache-Control', 'max-age=0')
|
||||
->setBody($excelOutput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -119,6 +119,19 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'Query' => 4,
|
||||
'Required Information' => 4,
|
||||
'In-Progress' => 5,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'RI Cancelled' => 13,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -164,14 +177,30 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_abhi cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_abhi');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_abhi')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -329,7 +358,7 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']);
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -9,17 +9,30 @@ use App\Models\TicketMasterModel;
|
||||
use App\Models\EmployeeModel;
|
||||
use App\Models\ClaimDumpFileModel;
|
||||
use App\Models\ClaimsDumpFhplModel;
|
||||
use App\Models\ClientPolicyModel;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
abstract class BaseTpaClaimImportService
|
||||
{
|
||||
protected BaseConnection $db;
|
||||
protected $claimDumpFileModel;
|
||||
protected $clientPolicyModel;
|
||||
protected $policyNumberMapping;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = db_connect();
|
||||
$this->claimDumpFileModel = new ClaimDumpFileModel();
|
||||
$this->clientPolicyModel = new ClientPolicyModel();
|
||||
$this->policyNumberMapping = [
|
||||
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'Insurer Policy Number',
|
||||
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'Policy Number',
|
||||
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'policy_no',
|
||||
(int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'Policy No',
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'Policy Number',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'POLICY_NO',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,6 +46,8 @@ abstract class BaseTpaClaimImportService
|
||||
try {
|
||||
$fileData = $this->claimDumpFileModel->where('id', $fileId)->first();
|
||||
|
||||
$client_policy_data = $this->clientPolicyModel->where('id', $fileData['client_policy_id'])->first();
|
||||
|
||||
// Determine sheet name logic...
|
||||
if (env('FHPL_PRIMARY_KEY_CONSTANT') == $fileData['tpa_id']) {
|
||||
$rows = $this->readExcelBySheetName($filePath, 'Claims&Preauth');
|
||||
@ -47,6 +62,18 @@ abstract class BaseTpaClaimImportService
|
||||
return ['status' => false, 'message' => 'Excel file contains no data or wrong file upload'];
|
||||
}
|
||||
|
||||
if(isset($this->policyNumberMapping[$fileData['tpa_id']]) && !empty($this->policyNumberMapping[$fileData['tpa_id']])){
|
||||
$policy_number_column = $this->policyNumberMapping[$fileData['tpa_id']];
|
||||
}else{
|
||||
$policy_number_column = 'policy_no';
|
||||
}
|
||||
|
||||
|
||||
if($client_policy_data['policy_no'] != ($rows[0][$policy_number_column] ?? '')){
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Policy number mismatch in the file and in the system'];
|
||||
}
|
||||
|
||||
$tpaInsertData = $this->mapTPAData($rows, $fileId);
|
||||
|
||||
if (empty($tpaInsertData)) {
|
||||
@ -81,7 +108,7 @@ abstract class BaseTpaClaimImportService
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
$file_id = $params['file_id'];
|
||||
$file_id = $params['file_id'];
|
||||
$ticketMasterData = $this->mapClaimMasterData($file_id);
|
||||
|
||||
// Check if mapping failed
|
||||
@ -101,6 +128,12 @@ abstract class BaseTpaClaimImportService
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Ticket Master Claim bulk insert failed'];
|
||||
}
|
||||
// Map newly created ticket IDs back to the TPA staging table
|
||||
if (!$this->updateTicketIdInTPATable($file_id)) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Updating ticket_id in TPA table failed'];
|
||||
}
|
||||
|
||||
$message .= 'Ticket Master Claim bulk insert success. ';
|
||||
$hasExecutedTask = true;
|
||||
}else{
|
||||
@ -328,6 +361,16 @@ abstract class BaseTpaClaimImportService
|
||||
return $employeeData ?? [];
|
||||
}
|
||||
|
||||
public function checkStatusMapping($statusArray, $statusString)
|
||||
{
|
||||
foreach ($statusArray as $key => $value) {
|
||||
if (strtolower($statusString) == strtolower($key) || strtolower($statusString) == strtolower(trim($key))) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Excel rows to TPA table structure
|
||||
*/
|
||||
@ -349,9 +392,9 @@ abstract class BaseTpaClaimImportService
|
||||
abstract protected function importClaimMaster(array $data): bool;
|
||||
|
||||
/**
|
||||
* Update TPA table with ticket_master primary key
|
||||
* Update TPA table with ticket_master primary key for a given file.
|
||||
*/
|
||||
abstract protected function updateTicketIdInTPATable(): bool;
|
||||
abstract protected function updateTicketIdInTPATable(int $fileId): bool;
|
||||
|
||||
/**
|
||||
* Update TPA table with ticket_master insert rejected reason
|
||||
|
||||
@ -181,6 +181,13 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'Under Process' => 5,
|
||||
'Query' => 4,
|
||||
'Required Information' => 4,
|
||||
'In-Progress' => 5,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -235,14 +242,31 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
// Join ticket_master with FHPL staging on file_id + claim_dump_ref_id -> id
|
||||
$rows = $this->db->table('claims_dump_fhpl cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_fhpl');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_fhpl')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -400,7 +424,7 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['current_claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['current_claim_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -106,7 +106,13 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
protected $statusMapping = [
|
||||
'PAID' => 11,
|
||||
'SETTLED' => 11,
|
||||
'REJECTED' => 8,
|
||||
'APPROVED' => 9,
|
||||
'CLOSED' => 12,
|
||||
'QUERY' => 4,
|
||||
'REQUIRED INFORMATION' => 4,
|
||||
'IN-PROGRESS' => 5,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -152,14 +158,30 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_icici cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_icici');
|
||||
return $builder->upsertBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_icici')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -287,7 +309,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
$item['tpa_id'] = $client_policy_data['tpa_id'] ?? null;
|
||||
$item['acm_id'] = $client_policy_data['acm_id'] ?? null;
|
||||
$item['policy_no'] = $client_policy_data['policy_no'] ?? null;
|
||||
$item['relationship'] = $this->convertRelation($row['relation_group'] ?? '');
|
||||
$item['relationship'] = $this->convertRelation($row['relation'] ?? '');
|
||||
|
||||
$employee_data = $this->getEmployeeDetails($file_data['client_id'], $file_data['client_policy_id'], $row['employee_member_id'], $item['relationship']);
|
||||
|
||||
@ -317,7 +339,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['updated_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['updated_status']) ?? 61;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['file_id'] = $file_id;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -158,12 +158,15 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -192,16 +195,30 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_medi_assist');
|
||||
$builder->insertBatch($data);
|
||||
$rows = $this->db->table('claims_dump_medi_assist cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return true;
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_medi_assist')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -356,8 +373,8 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
// Meta fields
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
$item['claim_type'] = 1;
|
||||
$item['priority'] = 1;
|
||||
|
||||
@ -96,14 +96,15 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'CL Paid with Settlement Letter' => 11,
|
||||
'Settled' => 11,
|
||||
'Paid' => 11,
|
||||
'Rejected' => 8,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'CL Rejected' => 8,
|
||||
'CL Approved' => 9,
|
||||
'AL Closed' => 12,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -143,14 +144,30 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_reliance cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_reliance');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_reliance')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -307,7 +324,7 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['final_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['final_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
|
||||
@ -349,10 +349,17 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'CL Paid with Settlement Letter' => 11,
|
||||
'CL Rejected' => 8,
|
||||
'CL Approved' => 9,
|
||||
'AL Closed' => 12,
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
];
|
||||
|
||||
|
||||
@ -382,14 +389,30 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_vidal cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_vidal');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_vidal')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -542,7 +565,7 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
73
app/Models/ExpenseModel.php
Normal file
73
app/Models/ExpenseModel.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class ExpenseModel extends Model
|
||||
{
|
||||
protected $table = 'expenses';
|
||||
protected $primaryKey = 'id';
|
||||
protected $DBGroup = 'default';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'client_id',
|
||||
'client_policy_id',
|
||||
'description',
|
||||
'approved_by',
|
||||
'amount',
|
||||
'expense_date',
|
||||
'created_at',
|
||||
'created_by',
|
||||
'updated_at',
|
||||
'updated_by',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ['setCreatedBy'];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ['setUpdatedBy'];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
protected function setCreatedBy(array $data): array
|
||||
{
|
||||
if (empty($data['data']['created_by'])) {
|
||||
$data['data']['created_by'] = get_session_userid();
|
||||
}
|
||||
|
||||
if (empty($data['data']['created_at'])) {
|
||||
$data['data']['created_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if (! isset($data['data']['is_active'])) {
|
||||
$data['data']['is_active'] = 1;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function setUpdatedBy(array $data): array
|
||||
{
|
||||
if (empty($data['data']['updated_by'])) {
|
||||
$data['data']['updated_by'] = get_session_userid();
|
||||
}
|
||||
|
||||
if (empty($data['data']['updated_at'])) {
|
||||
$data['data']['updated_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return $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' ";
|
||||
|
||||
@ -24,6 +24,66 @@
|
||||
.dataTables_length label {height: 21px !important;}
|
||||
|
||||
.readonly-select { background-color: #f3f3f3 !important; cursor: not-allowed; pointer-events: none; }
|
||||
|
||||
.custom-tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-family: Georgia, serif;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: fixed; /* fixed instead of absolute to escape modal overflow */
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
white-space: normal; /* allow text wrapping */
|
||||
width: 320px; /* fixed width for multiline */
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1.6;
|
||||
z-index: 99999;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
top: 20px !important;
|
||||
left: 155px !important;
|
||||
}
|
||||
|
||||
.tooltiptext::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 20px;
|
||||
border: 6px solid transparent;
|
||||
border-top-color: #333;
|
||||
}
|
||||
|
||||
.custom-tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<!-- <div class="row">
|
||||
@ -117,6 +177,16 @@
|
||||
class="mdi mdi-dots-horizontal"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a target="_blank" class="dropdown-item" href="<?= base_url("util/download_claim_dump_file/") . $file['file_id']; ?>"><i class="mdi mdi-download mr-2 text-muted font-18 vertical-middle"></i>Download</a>
|
||||
|
||||
<?php if ($file['status'] === 'failed') : ?>
|
||||
<a href="javascript:void(0);"
|
||||
class="dropdown-item"
|
||||
onclick="downloadTpaErrorFile(<?= (int) $file['file_id']; ?>)">
|
||||
<i class="mdi mdi-file-excel mr-2 text-danger font-18 vertical-middle"></i>
|
||||
Download Error File
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- <a data-id="<?php echo $file['file_id'] ?>" data-toggle="modal" data-target="#full-width-modal-emp-list" class="dropdown-item view_emp_list" href="#"><i class="mdi mdi-eye mr-2 text-muted font-18 vertical-middle"></i>View</a> -->
|
||||
</div>
|
||||
</div>
|
||||
@ -151,7 +221,16 @@
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="myCenterModalLabel">Claim Dump Upload</h4>
|
||||
<h4 class="modal-title" id="myCenterModalLabel">
|
||||
Claim Dump Upload
|
||||
<i class="info-icon"
|
||||
data-toggle="tooltip"
|
||||
data-placement="right"
|
||||
data-html="true"
|
||||
title="1. If you want to upload a TPA-wise claim dump, select the client and policy, then upload the TPA's Excel file. <br><br> 2. The second option without selecting the client and policy allows you to upload multiple claims. You can download the sample file provided below, fill it in, and upload it. This is for bulk dump upload.">
|
||||
i
|
||||
</i>
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -219,6 +298,20 @@
|
||||
let policyListByClient = null;
|
||||
let client_list = null;
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.custom-tooltip').forEach(function(tooltip) {
|
||||
tooltip.addEventListener('mouseenter', function() {
|
||||
var icon = this.querySelector('.info-icon');
|
||||
var tip = this.querySelector('.tooltiptext');
|
||||
var rect = icon.getBoundingClientRect();
|
||||
tip.style.left = rect.left + 'px';
|
||||
tip.style.top = (rect.top - tip.offsetHeight - 10) + 'px';
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
getClientAndBranchAndPolicy();
|
||||
@ -232,10 +325,17 @@
|
||||
|
||||
let client_id = $('#client_id').val() ?? null;
|
||||
let client_policy_id = $('#client_policy_id').val() ?? null;
|
||||
let tpa_id = $('#tpa_id').val() ?? null;
|
||||
|
||||
if(client_id){
|
||||
|
||||
if(!client_policy_id){
|
||||
toastr.warning('Please select the client policy', 'Warning');
|
||||
toastr.warning('Please select the client policy', 'WARNING');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tpa_id){
|
||||
toastr.warning('TPA is required', 'WARNING');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -251,9 +351,15 @@
|
||||
'application/vnd.oasis.opendocument.spreadsheet'
|
||||
];
|
||||
|
||||
if (!fileInput.files.length) {
|
||||
toastr.warning('Please select an Excel file', 'WARNING');
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedFile = fileInput.files[0];
|
||||
|
||||
if (!allowedTypes.includes(selectedFile.type)) {
|
||||
alert('Please upload a valid Excel file (.xlsx, .xls, .ods)');
|
||||
toastr.warning('Please upload a valid Excel file (.xlsx, .xls, .ods)', 'WARNING');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -500,6 +606,70 @@
|
||||
|
||||
}
|
||||
|
||||
function downloadTpaErrorFile(file_id) {
|
||||
if (!file_id) {
|
||||
toastr.error('Invalid file reference for error download', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
var url = '<?= base_url("util/getTpaClaimDumpErrorData"); ?>/' + file_id;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
xhrFields: { responseType: 'blob' },
|
||||
success: function (data, status, xhr) {
|
||||
var disposition = xhr.getResponseHeader('Content-Disposition') || '';
|
||||
var contentType = xhr.getResponseHeader('Content-Type') || '';
|
||||
|
||||
// If server sent JSON (error), parse it and show via Toastr
|
||||
if (contentType.indexOf('application/json') !== -1) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
try {
|
||||
var json = JSON.parse(reader.result);
|
||||
var msg = json.message || 'Unable to download error file.';
|
||||
toastr.error(msg, 'Error');
|
||||
} catch (e) {
|
||||
toastr.error('Unable to download error file.', 'Error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise assume it is Excel and trigger a download
|
||||
var filename = 'tpa_claim_dump_errors_' + file_id + '.xlsx';
|
||||
var match = /filename="?([^"]+)"?/.exec(disposition);
|
||||
if (match && match[1]) {
|
||||
filename = match[1];
|
||||
}
|
||||
|
||||
var blob = new Blob([data], {
|
||||
type: contentType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
var link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
toastr.success('Error file download started', 'Success');
|
||||
},
|
||||
error: function (xhr) {
|
||||
var msg = 'Unable to download error file.';
|
||||
try {
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
if (json.message) {
|
||||
msg = json.message;
|
||||
}
|
||||
} catch (e) {}
|
||||
toastr.error(msg, 'Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.close').click(function(){
|
||||
$('#modal_body').empty()
|
||||
let html = `<div class="spinner-border text-primary" role="status" style="position: relative; left: 200px;"></div>`
|
||||
|
||||
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>
|
||||
586
app/Views/expense_list.php
Normal file
586
app/Views/expense_list.php
Normal file
@ -0,0 +1,586 @@
|
||||
<div class="row" id="expense_module">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-6 d-flex align-items-center">
|
||||
<!-- <h4 class="mb-0">Expense</h4> -->
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<!-- <button type="button" class="btn app-btn-secondary" id="btnResetExpense">
|
||||
<i class="mdi mdi-refresh mr-1"></i>Reset
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-form mb-4">
|
||||
<form role="form" class="parsley-examples" method="get" id="expense_form">
|
||||
<input type="hidden" value="<?= csrf_hash() ?>" name="<?= csrf_token() ?>" />
|
||||
<input type="hidden" name="id" id="expense_id" />
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label for="client_id">Client Name <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="client_id" name="client_id" required>
|
||||
<option value="">Select Client</option>
|
||||
<?php if (! empty($clients)) : ?>
|
||||
<?php foreach ($clients as $client) : ?>
|
||||
<option value="<?= $client['id']; ?>" <?= ! empty($filters['client_id']) && (int) $filters['client_id'] === (int) $client['id'] ? 'selected' : ''; ?>>
|
||||
<?= esc($client['client_name']); ?>
|
||||
<?= ! empty($client['short_name']) ? ' (' . esc($client['short_name']) . ')' : ''; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="client_policy_id">Policy No <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="client_policy_id" name="client_policy_id" required>
|
||||
<option value="">Select Policy</option>
|
||||
<?php if (! empty($policies_for_filter)) : ?>
|
||||
<?php foreach ($policies_for_filter as $policy) : ?>
|
||||
<option value="<?= $policy['id']; ?>" <?= ! empty($filters['client_policy_id']) && (int) $filters['client_policy_id'] === (int) $policy['id'] ? 'selected' : ''; ?>>
|
||||
<?= ! empty($policy['policy_type']) ? esc($policy['policy_type']) . ' - ' : ''; ?> <?= esc($policy['policy_no']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="approved_by">Approved By <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="approved_by" name="approved_by" required>
|
||||
<option value="">Select User</option>
|
||||
<?php if (! empty($approved_users)) : ?>
|
||||
<?php foreach ($approved_users as $user) : ?>
|
||||
<option value="<?= $user['id']; ?>" <?= ! empty($filters['approved_by']) && (int) $filters['approved_by'] === (int) $user['id'] ? 'selected' : ''; ?>>
|
||||
<?= esc($user['first_name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="expense_date">Expense Date <span class="text-danger">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="expense_date"
|
||||
name="expense_date"
|
||||
placeholder="DD-MM-YYYY"
|
||||
value="<?= isset($filters['expense_date']) ? esc($filters['expense_date']) : ''; ?>"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="amount">Amount <span class="text-danger">*</span></label>
|
||||
<input type="number" step="0.01" min="0" class="form-control" id="amount" name="amount" placeholder="Enter amount" value="<?= isset($filters['amount']) ? esc($filters['amount']) : ''; ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="description">Description <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3" placeholder="Enter description" required><?= isset($filters['description']) ? esc($filters['description']) : ''; ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right m-b-0">
|
||||
<!-- <button type="button" class="btn app-btn-secondary waves-effect waves-light mr-1" id="btnSearchExpense">
|
||||
Search
|
||||
</button> -->
|
||||
<button type="submit" class="btn app-btn-secondary waves-effect waves-light mr-1" id="btnSubmitExpense">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary waves-effect" id="btnCancelExpense">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table data-custom-table-css="table" class="table mb-0 nowrap w-100 table-centered" cellspacing="0" id="expense-table">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="font-weight-medium">S.No</th>
|
||||
<th class="font-weight-medium">Client</th>
|
||||
<th class="font-weight-medium">Policy No</th>
|
||||
<th class="font-weight-medium">Description</th>
|
||||
<th class="font-weight-medium">Approved By</th>
|
||||
<th class="font-weight-medium">Amount</th>
|
||||
<th class="font-weight-medium">Expense Date</th>
|
||||
<th class="font-weight-medium">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (! empty($expenses)) : ?>
|
||||
<?php foreach ($expenses as $index => $row) : ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= $index + 1; ?></td>
|
||||
<td>
|
||||
<?= esc($row['client_name'] ?? ''); ?>
|
||||
<?= ! empty($row['short_name']) ? ' (' . esc($row['short_name']) . ')' : ''; ?>
|
||||
</td>
|
||||
<td><?= esc($row['policy_no'] ?? ''); ?></td>
|
||||
<td><?= esc($row['description'] ?? ''); ?></td>
|
||||
<td><?= esc($row['approved_by_name'] ?? ''); ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0), 2); ?></td>
|
||||
<td>
|
||||
<?php if (! empty($row['expense_date'])): ?>
|
||||
<?= date('d-m-Y', strtotime($row['expense_date'])); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group dropdown">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle arrow-none btn btn-light btn-sm" data-toggle="dropdown" aria-expanded="false">
|
||||
<i class="mdi mdi-dots-horizontal"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="editExpense(<?= (int) $row['id']; ?>)">
|
||||
<i class="mdi mdi-pencil mr-2 text-muted font-18 vertical-middle"></i>Edit
|
||||
</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="deleteExpense(<?= (int) $row['id']; ?>)">
|
||||
<i class="mdi mdi-delete mr-2 text-muted font-18 vertical-middle"></i>Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const expenseValidationErrors = <?= ! empty($validation_errors) ? json_encode(array_values($validation_errors)) : '[]'; ?>;
|
||||
let expensePoliciesCache = {};
|
||||
|
||||
function resetExpenseForm() {
|
||||
$('#client_id').val('').trigger('change');
|
||||
$('#client_policy_id').empty().append('<option value="">Select Policy</option>').trigger('change');
|
||||
$('#approved_by').val('').trigger('change');
|
||||
$('#description').val('');
|
||||
$('#amount').val('');
|
||||
$('#expense_date').val('');
|
||||
$('#btnSubmitExpense').text('Save');
|
||||
window.location.href = '<?= base_url("expense"); ?>';
|
||||
}
|
||||
|
||||
function validateExpenseSave() {
|
||||
const errors = [];
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = ($('#description').val() || '').trim();
|
||||
const amount = ($('#amount').val() || '').trim();
|
||||
const expenseDate= ($('#expense_date').val() || '').trim();
|
||||
|
||||
const idPattern = /^[0-9]+$/;
|
||||
const descPattern = /^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/;
|
||||
const datePattern = /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/;
|
||||
|
||||
if (!clientId) {
|
||||
errors.push('Client is required.');
|
||||
} else if (!idPattern.test(clientId)) {
|
||||
errors.push('Invalid client selected.');
|
||||
}
|
||||
|
||||
if (!policyId) {
|
||||
errors.push('Policy is required.');
|
||||
} else if (!idPattern.test(policyId)) {
|
||||
errors.push('Invalid policy selected.');
|
||||
}
|
||||
|
||||
if (!approvedBy) {
|
||||
errors.push('Approved By is required.');
|
||||
} else if (!idPattern.test(approvedBy)) {
|
||||
errors.push('Invalid approver selected.');
|
||||
}
|
||||
|
||||
if (!desc) {
|
||||
errors.push('Description is required.');
|
||||
} else if (!descPattern.test(desc)) {
|
||||
errors.push('Description contains invalid characters.');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
errors.push('Amount is required.');
|
||||
} else if (isNaN(amount) || Number(amount) < 0) {
|
||||
errors.push('Amount must be a non-negative number.');
|
||||
}
|
||||
|
||||
if (!expenseDate) {
|
||||
errors.push('Expense Date is required.');
|
||||
} else if (!datePattern.test(expenseDate)) {
|
||||
errors.push('Expense Date must be in DD-MM-YYYY format.');
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
toastr.warning(errors.join('<br>'), 'Validation');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateExpenseSearchFilters() {
|
||||
const errors = [];
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = ($('#description').val() || '').trim();
|
||||
const amount = ($('#amount').val() || '').trim();
|
||||
const expenseDate= ($('#expense_date').val() || '').trim();
|
||||
|
||||
const idPattern = /^[0-9]+$/;
|
||||
const descPattern = /^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/;
|
||||
const datePattern = /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/;
|
||||
|
||||
if (clientId && !idPattern.test(clientId)) {
|
||||
errors.push('Invalid client selected for search.');
|
||||
}
|
||||
|
||||
if (policyId && !idPattern.test(policyId)) {
|
||||
errors.push('Invalid policy selected for search.');
|
||||
}
|
||||
|
||||
if (approvedBy && !idPattern.test(approvedBy)) {
|
||||
errors.push('Invalid approver selected for search.');
|
||||
}
|
||||
|
||||
if (desc && !descPattern.test(desc)) {
|
||||
errors.push('Description filter contains invalid characters.');
|
||||
}
|
||||
|
||||
if (amount) {
|
||||
if (isNaN(amount) || Number(amount) < 0) {
|
||||
errors.push('Amount filter must be a non-negative number.');
|
||||
}
|
||||
}
|
||||
|
||||
if (expenseDate && !datePattern.test(expenseDate)) {
|
||||
errors.push('Expense Date filter must be in DD-MM-YYYY format.');
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
toastr.warning(errors.join('<br>'), 'Validation');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function populatePolicies(clientId, selectedPolicyId = null) {
|
||||
if (!clientId) {
|
||||
$('#client_policy_id').empty().append('<option value="">Select Policy</option>').trigger('change');
|
||||
return;
|
||||
}
|
||||
|
||||
if (expensePoliciesCache[clientId]) {
|
||||
const policies = expensePoliciesCache[clientId];
|
||||
let optionsHtml = '<option value=\"\">Select Policy</option>';
|
||||
|
||||
policies.forEach(function (p) {
|
||||
optionsHtml += `<option value=\"${p.id}\">${p.policy_no}</option>`;
|
||||
});
|
||||
|
||||
$('#client_policy_id').html(optionsHtml);
|
||||
|
||||
if (selectedPolicyId) {
|
||||
$('#client_policy_id').val(selectedPolicyId).trigger('change');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/client-policies"); ?>',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: {client_id: clientId},
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
expensePoliciesCache[clientId] = res.data || [];
|
||||
|
||||
let optionsHtml = '<option value=\"\">Select Policy</option>';
|
||||
(res.data || []).forEach(function (p) {
|
||||
optionsHtml += `<option value=\"${p.id}\">${p.policy_no}</option>`;
|
||||
});
|
||||
|
||||
$('#client_policy_id').html(optionsHtml);
|
||||
|
||||
if (selectedPolicyId) {
|
||||
$('#client_policy_id').val(selectedPolicyId).trigger('change');
|
||||
}
|
||||
} else {
|
||||
toastr.warning(res.message || 'Unable to load policies', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unexpected error while loading policies', 'ERROR');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editExpense(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/get"); ?>/' + id,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.status && res.data) {
|
||||
const data = res.data;
|
||||
|
||||
$('#expense_id').val(data.id);
|
||||
$('#description').val(data.description || '');
|
||||
$('#amount').val(data.amount || '');
|
||||
if (data.expense_date) {
|
||||
const parts = (data.expense_date || '').split('-'); // Y-m-d
|
||||
if (parts.length === 3) {
|
||||
const formatted = parts[2] + '-' + parts[1] + '-' + parts[0];
|
||||
$('#expense_date').val(formatted);
|
||||
if (window.expenseDatePicker) {
|
||||
window.expenseDatePicker.setDate(formatted, true, 'd-m-Y');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.approved_by) {
|
||||
$('#approved_by').val(data.approved_by).trigger('change');
|
||||
}
|
||||
|
||||
if (data.client_id) {
|
||||
$('#client_id').val(data.client_id).trigger('change');
|
||||
populatePolicies(data.client_id, data.client_policy_id || null);
|
||||
}
|
||||
|
||||
$('#btnSubmitExpense').text('Update');
|
||||
$('html, body').animate({
|
||||
scrollTop: $('#expense_form').offset().top - 100
|
||||
}, 400);
|
||||
} else {
|
||||
toastr.warning(res.message || 'Expense not found', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unable to fetch expense details', 'ERROR');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteExpense(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "You want to delete this expense",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Yes",
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postData = {};
|
||||
postData['<?= csrf_token() ?>'] = '<?= csrf_hash() ?>';
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/delete"); ?>/' + id,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: postData,
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
toastr.success(res.message || 'Expense deleted successfully', 'SUCCESS');
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 800);
|
||||
} else {
|
||||
toastr.warning(res.message || 'Unable to delete expense', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unable to delete expense', 'ERROR');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#client_id').select2();
|
||||
$('#client_policy_id').select2();
|
||||
$('#approved_by').select2();
|
||||
|
||||
window.expenseDatePicker = flatpickr('#expense_date', {
|
||||
dateFormat: 'd-m-Y',
|
||||
allowInput: true
|
||||
});
|
||||
|
||||
$('#expense-table').DataTable({
|
||||
dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
||||
"<'row'<'col-sm-12'tr>>" +
|
||||
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
|
||||
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
|
||||
buttons: [
|
||||
{
|
||||
extend: 'collection',
|
||||
text: '<span class=" btn-custom"> Export </span><i class="mdi mdi-menu-down"></i>',
|
||||
className: 'btn app-btn-secondary ',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'csv',
|
||||
text: '<i class="mdi mdi-file-delimited"></i><span class="btn-custom"> CSV </span>',
|
||||
title: 'Expense List',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
title: 'Expense-List',
|
||||
sheetName: 'Expense-List',
|
||||
text: '<i class="mdi mdi-file-excel"></i><span class="btn-custom"> EXCEL </span>',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
orthogonal: 'sort',
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'pdf',
|
||||
text: '<i class="mdi mdi-file-pdf"></i><span class="btn-custom"> PDF </span>',
|
||||
title: 'Expense List',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
language: {
|
||||
search: `
|
||||
<div class="datatable-search-wrapper" style="position:relative; display:inline-block;">
|
||||
_INPUT_
|
||||
<i class="mdi mdi-magnify datatable-search-icon"
|
||||
style="position:absolute; right:11px; top:50%; transform:translateY(-53%); color:#666;"></i>
|
||||
<i class="mdi mdi-close-circle datatable-clear-icon"
|
||||
style="position:absolute; right:11px; top:50%; transform:translateY(-53%); color:#666; display:none;"></i>
|
||||
</div>`,
|
||||
searchPlaceholder: "Search",
|
||||
emptyTable: '<div class="text-center text-muted">No Data found</div>'
|
||||
}
|
||||
});
|
||||
|
||||
$('.dataTables_length label').css('height', '21px');
|
||||
|
||||
// Show any backend validation errors from search
|
||||
if (Array.isArray(expenseValidationErrors) && expenseValidationErrors.length) {
|
||||
expenseValidationErrors.forEach(function (msg) {
|
||||
toastr.warning(msg, 'Validation');
|
||||
});
|
||||
}
|
||||
|
||||
$('#client_id').on('change', function () {
|
||||
const clientId = $(this).val();
|
||||
populatePolicies(clientId);
|
||||
});
|
||||
|
||||
$('#btnSearchExpense').on('click', function () {
|
||||
if (!validateExpenseSearchFilters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {};
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = $('#description').val();
|
||||
const amount = $('#amount').val();
|
||||
const expenseDate = $('#expense_date').val();
|
||||
|
||||
if (clientId) {
|
||||
params.client_id = clientId;
|
||||
}
|
||||
if (policyId) {
|
||||
params.client_policy_id = policyId;
|
||||
}
|
||||
if (approvedBy) {
|
||||
params.approved_by = approvedBy;
|
||||
}
|
||||
if (desc) {
|
||||
params.description = desc;
|
||||
}
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
if (expenseDate) {
|
||||
params.expense_date = expenseDate;
|
||||
}
|
||||
|
||||
const query = $.param(params);
|
||||
const baseUrl = '<?= base_url("expense"); ?>';
|
||||
window.location.href = query ? baseUrl + '?' + query : baseUrl;
|
||||
});
|
||||
|
||||
$('#btnSubmitExpense').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateExpenseSave()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = $('#expense_form').serialize();
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/save"); ?>',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: formData,
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
toastr.success(res.message || 'Expense saved successfully', 'SUCCESS');
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 800);
|
||||
} else {
|
||||
const errors = res.errors || {};
|
||||
let errorMsg = res.message || 'Unable to save expense';
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
errorMsg = Object.values(errors).join('<br>');
|
||||
}
|
||||
|
||||
toastr.warning(errorMsg, 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unexpected error while saving expense', 'ERROR');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btnResetExpense, #btnCancelExpense').on('click', function () {
|
||||
resetExpenseForm();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1876,6 +1876,17 @@
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
<!-- Expence -->
|
||||
<?php if (in_array(get_role_id(), [1, 5])) { ?>
|
||||
<li class="li-seperate" id="clients-li">
|
||||
<a href="<?= base_url('expense') ?>" class="img-inactive">
|
||||
<img style="border-radius: 5px;"
|
||||
src="<?= base_url() . "public"; ?>/assets/images/expense_icon.png" alt="Logo" height="24">
|
||||
<span> Expense </span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
<!-- BDS -->
|
||||
<?php if ((get_role_id() == 1 || get_role_id() == 5 || get_role_id() == 4) || (in_array(MANAGEMENT_TEAM_ID, user_team()) || in_array(FINANCE_TEAM_ID, user_team()) || in_array(BUSINESS_TEAM_ID, user_team()) || in_array(POS_TEAM_ID, user_team()))) { ?>
|
||||
<li class="li-seperate" id="policy-transactions-li">
|
||||
|
||||
BIN
public/assets/images/expense_icon.png
Normal file
BIN
public/assets/images/expense_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
399
tests/unit/ExpenseControllerTest.php
Normal file
399
tests/unit/ExpenseControllerTest.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOption {
|
||||
/**
|
||||
* Lightweight stub of PhpOption\Option used only for testing env() handling.
|
||||
* This is intentionally minimal and should not be relied on in application code.
|
||||
*/
|
||||
class Option
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public static function fromValue($value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function map($callback): self
|
||||
{
|
||||
if ($this->value !== null) {
|
||||
$this->value = $callback($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrCall($callback)
|
||||
{
|
||||
return $this->value !== null ? $this->value : $callback();
|
||||
}
|
||||
|
||||
public function getOrThrow($exception)
|
||||
{
|
||||
if ($this->value === null) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace App\Filters {
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
// Test stubs for application filters to bypass logging, ACL, and security checks during tests.
|
||||
|
||||
class AclFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class HttpRequestLog implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class SecurityInputFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class GlobalPostFileUploadGuard implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class Cors implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Dotenv\Repository {
|
||||
interface RepositoryInterface
|
||||
{
|
||||
public function get($name);
|
||||
|
||||
public function set($name, $value);
|
||||
}
|
||||
|
||||
class RepositoryBuilder
|
||||
{
|
||||
public static function createWithDefaultAdapters(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function addAdapter($adapter): self
|
||||
{
|
||||
// No-op for testing.
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function immutable(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function make(): RepositoryInterface
|
||||
{
|
||||
return new class() implements RepositoryInterface {
|
||||
public function get($name)
|
||||
{
|
||||
$value = getenv($name);
|
||||
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
putenv("$name=$value");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Dotenv\Repository\Adapter {
|
||||
class PutenvAdapter
|
||||
{
|
||||
// Stub class used only to satisfy Illuminate\Support\Env references.
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tests\unit {
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
|
||||
class ExpenseControllerTest extends CIUnitTestCase
|
||||
{
|
||||
use FeatureTestTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Override routes for testing to avoid filters/middleware.
|
||||
$this->withRoutes([
|
||||
['get', 'expense', 'ExpenseController::index'],
|
||||
['post', 'expense/save', 'ExpenseController::save'],
|
||||
['get', 'expense/get/(:num)', 'ExpenseController::getExpense/$1'],
|
||||
['post', 'expense/delete/(:num)','ExpenseController::delete/$1'],
|
||||
['get', 'expense/client-policies', 'ExpenseController::clientPolicies'],
|
||||
]);
|
||||
|
||||
// Clean up any test data from previous runs
|
||||
$db = db_connect('default');
|
||||
$db->table('expenses')->like('description', 'CI4_TEST_', 'after')->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to insert an expense row directly into the database.
|
||||
*/
|
||||
private function createExpenseRecord(array $overrides = []): int
|
||||
{
|
||||
$db = db_connect('default');
|
||||
|
||||
$data = array_merge([
|
||||
'client_id' => 1,
|
||||
'client_policy_id' => 1,
|
||||
'description' => 'CI4_TEST_' . uniqid('', true),
|
||||
'approved_by' => 1,
|
||||
'amount' => 100.00,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'is_active' => 1,
|
||||
], $overrides);
|
||||
|
||||
$db->table('expenses')->insert($data);
|
||||
|
||||
return (int) $db->insertID();
|
||||
}
|
||||
|
||||
public function testCreateExpenseSuccess(): void
|
||||
{
|
||||
$payload = [
|
||||
'client_id' => '1',
|
||||
'client_policy_id' => '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'CI4_TEST_Valid Description 123-ABC',
|
||||
'expense_date' => '12-01-2026',
|
||||
'amount' => '250.75',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertIsArray($json);
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
$this->assertNotEmpty($json['id'] ?? null);
|
||||
|
||||
$insertedId = (int) $json['id'];
|
||||
|
||||
$db = db_connect('default');
|
||||
$row = $db->table('expenses')
|
||||
->where('description', $payload['description'])
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame(1, (int) $row['is_active']);
|
||||
$this->assertSame($payload['description'], $row['description']);
|
||||
$this->assertEquals(250.75, (float) $row['amount']);
|
||||
}
|
||||
|
||||
public function testCreateExpenseValidationFailureMissingFields(): void
|
||||
{
|
||||
$payload = [
|
||||
// all required fields missing / empty
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertSame('Input validation failed', $json['message'] ?? '');
|
||||
$this->assertArrayHasKey('client_id', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('client_policy_id', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('approved_by', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('description', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('amount', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('expense_date', $json['errors'] ?? []);
|
||||
}
|
||||
|
||||
public function testCreateExpenseValidationFailureInvalidDescription(): void
|
||||
{
|
||||
$payload = [
|
||||
'client_id' => '1',
|
||||
'client_policy_id' => '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Invalid @ Description <>', // invalid characters
|
||||
'expense_date' => '12-01-2026',
|
||||
'amount' => '100.00',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertArrayHasKey('description', $json['errors'] ?? []);
|
||||
$this->assertStringContainsString(
|
||||
'invalid characters',
|
||||
strtolower($json['errors']['description'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpdateExpenseSuccess(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord([
|
||||
'description' => 'CI4_TEST_Original Description',
|
||||
'amount' => 50.00,
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'id' => (string) $id,
|
||||
'client_id' => '1',
|
||||
'client_policy_id'=> '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Updated Description 456',
|
||||
'expense_date' => '13-01-2026',
|
||||
'amount' => '75.50',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
$this->assertSame($id, (int) ($json['id'] ?? 0));
|
||||
|
||||
$db = db_connect('default');
|
||||
$row = $db->table('expenses')->where('id', $id)->get()->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame('Updated Description 456', $row['description']);
|
||||
$this->assertEquals(75.50, (float) $row['amount']);
|
||||
}
|
||||
|
||||
public function testUpdateExpenseValidationFailureInvalidAmount(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord();
|
||||
|
||||
$payload = [
|
||||
'id' => (string) $id,
|
||||
'client_id' => '1',
|
||||
'client_policy_id'=> '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Another Valid Description',
|
||||
'expense_date' => '14-01-2026',
|
||||
'amount' => '-10', // invalid negative
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertArrayHasKey('amount', $json['errors'] ?? []);
|
||||
$this->assertStringContainsString(
|
||||
'cannot be negative',
|
||||
strtolower($json['errors']['amount'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
public function testDeleteExpenseSoftDeleteSuccess(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord();
|
||||
|
||||
$result = $this->post('expense/delete/' . $id);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
|
||||
$db = db_connect();
|
||||
$row = $db->table('expenses')->where('id', $id)->get()->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame(0, (int) $row['is_active']);
|
||||
}
|
||||
|
||||
public function testDeleteExpenseInvalidId(): void
|
||||
{
|
||||
$result = $this->post('expense/delete/0');
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertSame('Invalid expense id', $json['message'] ?? '');
|
||||
}
|
||||
|
||||
public function testSearchFilterByClientAndDescription(): void
|
||||
{
|
||||
// Create two distinct expenses
|
||||
$this->createExpenseRecord([
|
||||
'client_id' => 10,
|
||||
'description' => 'FilterMatch Description',
|
||||
]);
|
||||
|
||||
$this->createExpenseRecord([
|
||||
'client_id' => 20,
|
||||
'description' => 'Other Description',
|
||||
]);
|
||||
|
||||
$result = $this->get('expense?client_id=10&description=FilterMatch');
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$body = $result->getBody();
|
||||
|
||||
$this->assertStringContainsString('FilterMatch Description', $body);
|
||||
$this->assertStringNotContainsString('Other Description', $body);
|
||||
}
|
||||
|
||||
public function testSearchFilterValidationInvalidCharacters(): void
|
||||
{
|
||||
$result = $this->get('expense?description=<script>alert(1)</script>');
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$body = $result->getBody();
|
||||
|
||||
$this->assertStringContainsString('Description filter contains invalid characters.', $body);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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