MERGE_UAT_SALES_CR_WITH_OPPOTUNITES

This commit is contained in:
Ubuntu 2026-05-12 18:29:26 +05:30
commit 65be0db2fe
16 changed files with 1367 additions and 146 deletions

View File

@ -508,6 +508,7 @@ $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
$routes->post("saveInvoicePaymentDetails", "PolicyTransactionController::saveInvoicePaymentDetails");
$routes->get("deletePaymentEntry/(:any)", "PolicyTransactionController::deletePaymentEntry/$1");
$routes->get("downloadSampleInsurerStatement", "PolicyTransactionController::downloadSampleInsurerStatement");
$routes->get("downloadInsurerStatement/(:num)", "PolicyTransactionController::downloadInsurerStatement/$1");
$routes->get("getFileErr/(:any)", "PolicyTransactionController::getFileErr/$1");
$routes->get("getInsurerStatementMonth", "PolicyTransactionController::getInsurerStatementMonth");
$routes->get("deleteStatement/(:any)", "PolicyTransactionController::deleteStatement/$1");
@ -1010,6 +1011,9 @@ $routes->group('sales', function($routes) {
// Get all leads with filters
$routes->get('leads', 'SalesController::getLeads');
// Export leads by created date range
$routes->get('leads/export', 'SalesController::exportLeads');
// Get lead statistics
$routes->get('leads/stats', 'SalesController::getLeadStats');
@ -1056,6 +1060,9 @@ $routes->group('sales', function($routes) {
// Get all activities with filters
$routes->get('activities', 'SalesController::getActivities');
// Export activities by created date range
$routes->get('activities/export', 'SalesController::exportActivities');
// Get upcoming activities
$routes->get('activities/upcoming', 'SalesController::getUpcomingActivities');

View File

@ -1068,7 +1068,7 @@ class FhplApiController extends BaseController
'is_active' => 1,
'created_by' => $file_info[0]['created_by'] ?? null,
'action_flag_status' => $row['IsActive'] == 1 ? 'A' : 'D'
'action_flag_status' => $row['IsActive'] == 1 ? 'A' : 'D',
'si' => $row['BASE_SUMINSURED'],
'doj' => change_date_format($row['DATE_OF_JOINING'],'Y-m-d\TH:i:s'),
'endorsement_no' => $row['ENDORSEMENT_NO']

View File

@ -571,6 +571,70 @@ class LeadsController extends BaseController
}
// $leadFormType = (int) ($postData['lead_form_type'] ?? 1);
// $postedPolicyTypeIds = array_map('intval', (array) ($postData['policy_type_id'] ?? []));
// $isEbRenewalGpaClaimHistory = $leadFormType === 1
// && (int) ($postData['lead_type'] ?? 0) === 2
// && in_array(1, $postedPolicyTypeIds, true)
// && (int) ($postData['claim_history'] ?? 0) === 1;
// if ($isEbRenewalGpaClaimHistory) {
// $rules['first_year.*'] = [
// 'rules' => 'required|regex_match[/^\d{4}-\d{4}$/]',
// 'errors' => [
// 'required' => 'Claim Year is required for all entries.',
// 'regex_match' => 'Year must be in format YYYY-YYYY.',
// ],
// ];
// $rules['emp_id.*'] = [
// 'rules' => 'required',
// 'errors' => ['required' => 'Employee ID is required in Claim History.'],
// ];
// $rules['emp_name.*'] = [
// 'rules' => 'required',
// 'errors' => ['required' => 'Employee Name is required in Claim History.'],
// ];
// $rules['gender.*'] = [
// 'rules' => 'required|in_list[Female,Male]',
// 'errors' => [
// 'required' => 'Gender is required in Claim History.',
// 'in_list' => 'Gender must be Female or Male.',
// ],
// ];
// $rules['designation.*'] = [
// 'rules' => 'required',
// 'errors' => ['required' => 'Designation is required in Claim History.'],
// ];
// $rules['sum_insured.*'] = [
// 'rules' => 'required|numeric',
// 'errors' => [
// 'required' => 'Sum Insured is required in Claim History.',
// 'numeric' => 'Sum Insured must be a number.',
// ],
// ];
// $rules['first_death_date.*'] = [
// 'rules' => 'required|regex_match[/^[0-9]{2}-([0-9]{2}|[A-Za-z]{3})-[0-9]{4}$/]',
// 'errors' => [
// 'required' => 'Date of Death is required.',
// 'regex_match' => 'Date of Death must be in DD-MM-YYYY or DD-MMM-YYYY format.',
// ],
// ];
// $rules['first_cause_of_death.*'] = [
// 'rules' => 'required|in_list[natural_death,suicide,accident,cardiac_arrest,septic_shock,heart_attack]',
// 'errors' => [
// 'required' => 'Nature/Cause Of Death is required.',
// 'in_list' => 'Nature/Cause Of Death is invalid.',
// ],
// ];
// $rules['first_claim_amount.*'] = [
// 'rules' => 'required|numeric',
// 'errors' => [
// 'required' => 'Claim/Settled Amount is required.',
// 'numeric' => 'Claim/Settled Amount must be a number.',
// ],
// ];
// }
// 1. MANUALLY VALIDATE FILES BEFORE PROCESSING
// $allFiles = $this->request->getFiles();
// foreach ($allFiles as $inputName => $files) {
@ -680,7 +744,7 @@ class LeadsController extends BaseController
$data['client_code'] = generate_client_code('IC');
}
if (isset($data['lead_form_type'])) {
if ((int) ($data['lead_form_type'] ?? 1) === 2) {
$data = $this->prepareSingleLeadData($data);
} else {
$data = $this->prepareMultipleLeadData($data);
@ -950,10 +1014,10 @@ class LeadsController extends BaseController
$insertCount[] = $insert;
$this->insertLeadStatus($insert, $value['status'], 3);
if (in_array((int) $value['lead_type'], [1, 3], true) && $value['status'] == 'won') {
$leadDataForClient = $this->leadsModel->find($insert);
$this->createClientWithLeadData($leadDataForClient);
}
// if (in_array((int) $value['lead_type'], [1, 3], true) && $value['status'] == 'won') {
// $leadDataForClient = $this->leadsModel->find($insert);
// $this->createClientWithLeadData($leadDataForClient);
// }
if ($value['lead_form_type'] == 1) {
//for this push the job to the calculateMembersDemography() function
@ -981,12 +1045,12 @@ class LeadsController extends BaseController
$this->insertMultiFilesData($data[0]['multi_file_data'], $id, $data[0]['lead_form_type']);
$this->insertLeadStatus($id, $data[0]['status'], 3);
if (in_array((int) $data[0]['lead_type'], [1, 3], true) && $data[0]['status'] == 'won') {
$leadDataForClient = $this->leadsModel->find($id);
if (empty($leadDataForClient['is_client_created'])) {
$this->createClientWithLeadData($leadDataForClient);
}
}
// if (in_array((int) $data[0]['lead_type'], [1, 3], true) && $data[0]['status'] == 'won') {
// $leadDataForClient = $this->leadsModel->find($id);
// if (empty($leadDataForClient['is_client_created'])) {
// $this->createClientWithLeadData($leadDataForClient);
// }
// }
return $this->respond(['status' => true, 'lead_id' => $id, 'message' => "Opportunity updated successfully", 'data' => $data], 200);
}
@ -2140,6 +2204,8 @@ class LeadsController extends BaseController
{
helper('excel_util_helper');
$rfq_data = $this->RFQModel->getRFQTableDataWithLeadIDAndType($lead_id, $type);
$claim_history = $rfq_data['claim_history'] ?? 0;
$is_placement = false;
$length = 0;
@ -2176,7 +2242,7 @@ class LeadsController extends BaseController
'No of Dependents' => $rfq_data['incept_dept_count'],
'Total Lives' => $rfq_data['incept_no_of_lives'],
'Period of Insurance ' => ! empty($rfq_data['policy_start_date']) && ! empty($rfq_data['policy_end_date']) ? date('d-m-Y', strtotime($rfq_data['policy_start_date'])) . ' to ' . date('d-m-Y', strtotime($rfq_data['policy_end_date'])) : "To be decided",
// 'Period of Insurance ' => ! empty($rfq_data['policy_start_date']) && ! empty($rfq_data['policy_end_date']) ? date('d-m-Y', strtotime($rfq_data['policy_start_date'])) . ' to ' . date('d-m-Y', strtotime($rfq_data['policy_end_date'])) : "To be decided",
// 'Insurer' => $rfq_data['insurer_name'] ?? " - ",
// 'TPA' => $rfq_data['tpa_name'] ?? " - ",
// 'Policy Run Days' => $rfq_data['policy_run_days'],
@ -2186,7 +2252,7 @@ class LeadsController extends BaseController
'Insured' => $rfq_data['client_name'],
'No of Employees at Inception' => $rfq_data['incept_emp_count'],
// 'Total Sum Insured at Inception ' => $rfq_data['total_si_at_incept'],
'Policy Period' => ! empty($rfq_data['policy_start_date']) && ! empty($rfq_data['policy_end_date']) ? date('d-m-Y', strtotime($rfq_data['policy_start_date'])) . ' to ' . date('d-m-Y', strtotime($rfq_data['policy_end_date'])) : "To be decided",
// 'Policy Period' => ! empty($rfq_data['policy_start_date']) && ! empty($rfq_data['policy_end_date']) ? date('d-m-Y', strtotime($rfq_data['policy_start_date'])) . ' to ' . date('d-m-Y', strtotime($rfq_data['policy_end_date'])) : "To be decided",
// 'Insurer' => $rfq_data['insurer_name'] ?? " - ",
// 'TPA' => $rfq_data['tpa_name'] ?? " - ",
'Policy Type' => $this->leadType[$rfq_data['lead_type']] ?? "",
@ -2279,7 +2345,7 @@ class LeadsController extends BaseController
'Existing Insurer' => $rfq_data['insurer_name'],
'No of Employees at Renewal' => $rfq_data['renewal_emp_count'],
'Total Sum Insured at Renewal' => $rfq_data['total_si_at_renewal'],
'Claims Experience for last 3 years' => "Mentioned in Claims sheet",
'Claims Experience for last 3 years' => $claim_history == "1" ? "Mentioned in Claims sheet" : "Nil",
// 'Insurer' => $rfq_data['insurer_name'] ?? " - ",
// 'TPA' => $rfq_data['tpa_name'] ?? " - ",
// 'Total Lives at Inception' => $rfq_data['total_lives_at_incept'],
@ -2796,7 +2862,7 @@ class LeadsController extends BaseController
}
//claim history new sheet;
if (! empty($rfq_data['fin_years_claims'])) {
if ($claim_history == 1 && !empty($rfq_data['fin_years_claims'])) {
$claim_details = json_decode($rfq_data['fin_years_claims'], true) ?? [];
@ -4774,7 +4840,18 @@ class LeadsController extends BaseController
// 🔹 Client Details (Single Row)
$data['actual_lead_client_details'] = $this->leadModel
->select('sales_actual_leads.company_name, clients.short_name, clients.client_type, sales_actual_leads.email, sales_actual_leads.phone, sales_actual_leads.address, sales_actual_leads.website, sales_actual_leads.gst_number, sales_actual_leads.status, sales_actual_leads.assigned_to')
->select("
sales_actual_leads.company_name,
clients.short_name,
clients.client_type,
sales_actual_leads.email,
sales_actual_leads.phone,
sales_actual_leads.address,
sales_actual_leads.website,
COALESCE(NULLIF(sales_actual_leads.gst_number, ''), clients.gst) AS gst_number,
sales_actual_leads.status,
sales_actual_leads.assigned_to
", false)
->join('clients', 'clients.id = sales_actual_leads.client_id', 'left')
->where('sales_actual_leads.lead_id', $actual_lead_id)
->first(); // first row only

View File

@ -4236,7 +4236,7 @@ class PolicyTransactionController extends BaseController
return array('status' => $ret_status, 'error_code' => $error_data['error_code'], 'error_data' => $error_data['error_data']);
}
public function validateInsurerStatement($params)
public function validateInsurerStatementOLD1($params)
{
helper('excel_util_helper');
@ -4392,8 +4392,7 @@ class PolicyTransactionController extends BaseController
}
}
public function updateInsurerStatement($params)
public function updateInsurerStatementOLD($params)
{
helper('excel_util_helper');
//get file info
@ -4559,6 +4558,375 @@ class PolicyTransactionController extends BaseController
return array('status' => $ret_status, 'error_code' => $error_data['error_code'], 'error_data' => $error_data['error_data']);
}
public function validateInsurerStatement($params)
{
/*
* Changes made: 12-06-2024
* - Added safe file_id handling and DB file record validation before using file details.
* - Fixed physical file missing response and error message.
* - Skips empty Excel rows and collects unique policy numbers from the uploaded statement.
* - Fetches NHance source records only for those uploaded policy numbers.
* - Sanitizes policy and endorsement numbers before comparison to avoid hidden-space mismatch.
* - Validates each row by policy number + endorsement number combination.
* - Detects duplicate policy + endorsement rows and returns row-wise validation errors.
*/
helper('excel_util_helper');
$file_id = $params['file_id'] ?? 0;
try {
$error_data = ['error_code' => '', 'error_data' => []];
$status = 'success';
$ret_status = true;
//get file info
$file = $this->insurerStatements->find((int)$file_id);
// dd($file);
if (empty($file)) {
//file not found in DB
$this->insurerStatements->where('id', $file_id)->set(['file_status' => 'failed', 'reason' => json_encode(['error_code' => 0, 'error_data' => 'statement file not found in DB'])])->update();
return array('status' => false, 'error_code' => 0, 'error_data' => 'statement file not found in DB');
}
$file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name'];
//check physical file
if (!file_exists($file_name_with_path)) {
//file not found update status and reason
$message = "Physical file not found";
// echo $message;
$this->myLogger->logme('error', ($message . ' for statement file id ' . $file_id));
$this->insurerStatements->where('id', $file_id)->set(['file_status' => 'failed', 'reason' => json_encode(['error_code' => 0, 'error_data' => $message])])->update();
return array('status' => false, 'error_code' => 0, 'error_data' => $message); //0 - Physcial file not found
}
//get excel data to php array
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path);
$sheet = $spreadsheet->getActiveSheet();
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
$excel_data = $sheet->rangeToArray('A1:' . $highestColumn . $highestRow);
unset($excel_data[0]);
$excel_data = ExcelSanitizeHelper::sanitizeArrayData($excel_data);
// dd($excel_data);
//get no of line items and update in DB
$line_items = 0;
$policyNos = [];
foreach ($excel_data as $row) {
if (check_row_is_empty_or_null($row)) {
continue;
}
// row[1] => second column (B column)
$policyNo = $this->sanitizeStatementLookupValue($row[1] ?? '');
if ($policyNo !== '') {
$policyNos[] = $policyNo;
}
}
$policyNos = array_values(array_unique($policyNos));
// get uploaded month transactions data
// $source_data = $this->PTCOShareDetailsModel->getNonReconcileredPolicyTransactions(insurer_id: $file['insurer_id'], insurer_branch_id: $file['branch_id'], month: $file['month']);
$source_data = !empty($policyNos) ? $this->PTCOShareDetailsModel->getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id: $file['insurer_id'], insurer_branch_id: $file['branch_id'], policy_no: $policyNos) : [];
// dd($source_data);
$source_lookup = [];
$source_policy_lookup = [];
$source_endorsement_lookup = [];
foreach ($source_data as $source_row) {
$source_policy_no = $this->sanitizeStatementLookupValue($source_row['policy_no'] ?? '');
$source_endorsement_no = $this->sanitizeStatementLookupValue($source_row['endorsement_no'] ?? '');
$source_entry_key = $source_policy_no . '|' . $source_endorsement_no;
$source_lookup[$source_entry_key] = true;
$source_policy_lookup[$source_policy_no] = true;
$source_endorsement_lookup[$source_policy_no][$source_endorsement_no] = true;
}
// check policy no,insurer and etc in DB for this month
// if all good return true, otherwise return false with messssage
$matched_entry = [];
$error_messages = []; // row-wise error storage
foreach ($excel_data as $excel_key => $excel_row) {
$row_number = $excel_key;
$is_row_empty = check_row_is_empty_or_null($excel_row);
$policy_source_found = 0;
$endorsement_source_found = 0;
$duplicate_found = 0;
if (!$is_row_empty) {
$policy_no = $this->sanitizeStatementLookupValue($excel_row[1] ?? ''); //policy_number from excel
$endorsement_no = $this->sanitizeStatementLookupValue($excel_row[2] ?? ''); //endorsement number from excel
$entry_key = $policy_no . '|' . $endorsement_no;
if (isset($matched_entry[$entry_key])) {
$duplicate_found = 1;
} elseif (isset($source_lookup[$entry_key])) {
$line_items = $line_items + 1;
$matched_entry[$entry_key] = true;
continue;
}
$policy_source_found = isset($source_policy_lookup[$policy_no]) ? 1 : 0;
$endorsement_source_found = isset($source_endorsement_lookup[$policy_no][$endorsement_no]) ? 1 : 0;
// Policy number mismatch
if ($policy_source_found == 0) {
$error_messages[$row_number]['policy_no_mismatch'] =
"Policy number <strong>({$policy_no}) </strong> not in NHance.";
}
// Endorsement number mismatch
if ($policy_source_found == 1 && $endorsement_source_found == 0) {
$error_messages[$row_number]['endorsement_no_mismatch'] =
"Endorsement number <strong>({$endorsement_no})</strong> not in NHance for policy <strong>({$policy_no})</strong>.";
}
// Duplicate check
if ($duplicate_found == 1) {
$error_messages[$row_number]['duplicate'] =
"Duplicate entry found. This policy and endorsement <strong>({$policy_no} | {$endorsement_no})</strong> combination has already been matched.";
}
}
}
// print_rr($error_messages);die;
if (!empty($error_messages)) {
$error_data['error_code'] = 2;
$error_data['error_data'] = $error_messages;
$status = 'failed';
$ret_status = false;
}
//update in DB
$this->insurerStatements->where('id', $file_id)->set(['line_items' => $line_items, 'file_status' => $status, 'reason' => json_encode($error_data)])->update();
return array('status' => $ret_status, 'error_code' => $error_data['error_code'], 'error_data' => $error_data['error_data']);
} catch (\Throwable $th) {
$errorData = [
'message' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine(),
'code' => $th->getCode(),
'trace' => $th->getTraceAsString(),
'trace_array' => $th->getTrace(), // full array version (optional)
'function' => $th->getTrace()[0]['function'] ?? null,
'class' => $th->getTrace()[0]['class'] ?? null,
];
$this->myLogger->logme("error", "POLICY-TRANSACTION-CONTROLLER - validateInsurerStatement: Exception: " . json_encode($errorData ?? []));
$this->insurerStatements->where('id', $file_id)->set(['line_items' => 0, 'file_status' => 'failed', 'reason' => json_encode($errorData)])->update();
return array('status' => false, 'error_code' => [], 'error_data' => $errorData);
}
}
private function sanitizeStatementLookupValue($value): string
{
$value = trim((string)($value ?? ''));
$value = preg_replace('/^\p{Z}+|\p{Z}+$/u', '', $value);
return preg_replace('/[\x{200B}\x{200C}\x{200D}\x{FEFF}\x{00A0}\x{200E}\x{200F}\x{202A}-\x{202E}]/u', '', $value);
}
public function updateInsurerStatement($params)
{
/*
* Changes made: 12-06-2024
* - Added safe file_id handling and DB file record validation before using file details.
* - Fixed physical file missing response and error message.
* - Skips empty Excel rows and collects unique policy numbers from the uploaded statement.
* - Fetches NHance source records only for those uploaded policy numbers.
* - Uses the same sanitized policy number + endorsement number matching as validation.
* - Inserts statement details only for matched rows and skips empty or unmatched Excel rows.
* - Added safe default handling for amount and reward columns before calculation/insert.
*/
helper('excel_util_helper');
$file_id = $params['file_id'] ?? 0;
$error_data = ['error_code' => '', 'error_data' => []];
$status = 'success';
$ret_status = true;
//get file info
$file = $this->insurerStatements->find((int)$file_id);
// dd($file);
if (empty($file)) {
//file not found in DB
return array('status' => false, 'error_code' => 0, 'error_data' => 'statement file not found in DB');
}
$file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name'];
//check physical file
if (!file_exists($file_name_with_path)) {
//file not found update status and reason
$message = "Physical file not found";
// echo $message;
$this->myLogger->logme('error', ($message . ' for statement file id ' . $file_id));
$this->insurerStatements->where('id', $file_id)->set(['file_status' => 'failed', 'reason' => json_encode(['error_code' => 0, 'error_data' => $message])])->update();
return array('status' => false, 'error_code' => 0, 'error_data' => $message); //0 - Physcial file not found
}
//get excel data to php array
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path);
$sheet = $spreadsheet->getActiveSheet();
$highestRowAndColumn = $sheet->getHighestRowAndColumn();
// dd($highestRowAndColumn);
$excel_data = $sheet->rangeToArray('A1:' . $highestRowAndColumn['column'] . $highestRowAndColumn['row']);
unset($excel_data[0]);
$excel_data = ExcelSanitizeHelper::sanitizeArrayData($excel_data);
// dd($excel_data);
$policyNos = [];
foreach ($excel_data as $row) {
if (check_row_is_empty_or_null($row)) {
continue;
}
$policyNo = $this->sanitizeStatementLookupValue($row[1] ?? '');
if ($policyNo !== '') {
$policyNos[] = $policyNo;
}
}
$policyNos = array_values(array_unique($policyNos));
// get uploaded month transactions data
// $source_data = $this->PTCOShareDetailsModel->getNonReconcileredPolicyTransactions(insurer_id: $file['insurer_id'], insurer_branch_id: $file['branch_id'], month: $file['month']);
$source_data = !empty($policyNos) ? $this->PTCOShareDetailsModel->getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id: $file['insurer_id'], insurer_branch_id: $file['branch_id'], policy_no: $policyNos) : [];
// Kint::dump($source_data);//die;
// Kint::dump($excel_data);
// die;
$source_lookup = [];
foreach ($source_data as $source_row) {
$source_policy_no = $this->sanitizeStatementLookupValue($source_row['policy_no'] ?? '');
$source_endorsement_no = $this->sanitizeStatementLookupValue($source_row['endorsement_no'] ?? '');
$source_lookup[$source_policy_no . '|' . $source_endorsement_no][] = $source_row;
}
// check policy no,insurer and etc in DB for this month
// if all good return true, otherwise return false with messssage
$data_to_update = [];
try {
foreach ($excel_data as $excel_row) {
if (check_row_is_empty_or_null($excel_row)) {
continue;
}
// $policy_start_date = preg_replace('/^\p{Z}+|\p{Z}+$/u', '', $excel_row[4]); //policy_start_date from excel
// $policy_end_date = preg_replace('/^\p{Z}+|\p{Z}+$/u', '', $excel_row[5]); //policy_end_date from excel
$policy_no = $this->sanitizeStatementLookupValue($excel_row[1] ?? ''); //policy_number from excel
// $client_name = preg_replace('/^\p{Z}+|\p{Z}+$/u', '', $excel_row[3]); //clientname from excel
$endorsement_no = $this->sanitizeStatementLookupValue($excel_row[2] ?? ''); //endorsement number from excel
$entry_key = $policy_no . '|' . $endorsement_no;
if (empty($source_lookup[$entry_key])) {
continue;
}
$source_row = array_shift($source_lookup[$entry_key]);
//calculate percentage first
$total_amt = 0;
// $actual_bp_per = trim($excel_row[9]); //commented becoz this filed removed tfrom excel file
$actual_bp_per = 0; //set default value 0 for maintaining existing code flow
$actual_bp_brokerage = (int) trim((string)($excel_row[5] ?? 0));
$actual_bp_amt = (int) trim((string)($excel_row[3] ?? 0));
if (($actual_bp_brokerage && $actual_bp_brokerage != 0 && $actual_bp_brokerage != "")) {
$total_amt += $actual_bp_brokerage;
//percentage reverse calculation
if (($actual_bp_per == 0 || $actual_bp_per == 0) && !empty($actual_bp_amt)) {
$actual_bp_per = (int) round(($actual_bp_brokerage / $actual_bp_amt) * 100, 2);
}
} else {
$actual_bp_brokerage = $actual_bp_amt * ($actual_bp_per / 100);
$total_amt += $actual_bp_brokerage;
}
// $actual_tp_per = trim($excel_row[10]);//commented becoz this filed removed tfrom excel file
$actual_tp_per = 0;//set default value 0 for maintaining existing code flow
$actual_tp_brokerage = (int) trim((string)($excel_row[6] ?? 0));
$actual_tp_amt = (int) trim((string)($excel_row[4] ?? 0));
if ($actual_tp_brokerage && $actual_tp_brokerage != 0 && $actual_tp_brokerage != "") {
$total_amt += $actual_tp_brokerage;
//percentage reverse calculation
if (($actual_tp_per == 0 || $actual_tp_per == "") && !empty($actual_tp_amt)) {
$actual_tp_per = (int) round(($actual_tp_brokerage / $actual_tp_amt) * 100, 2);
}
} else {
$actual_tp_brokerage = $actual_tp_amt * ($actual_tp_per / 100);
$total_amt += $actual_tp_brokerage;
}
// $actual_tep_per = trim($excel_row[11]);
// $actual_tep_brokerage = trim($excel_row[14]);
// $actual_tep_amt = trim($excel_row[8]);
$actual_tep_per = 0;
$actual_tep_brokerage = 0;
$actual_tep_amt = 0;
if ($actual_tep_brokerage && $actual_tep_brokerage != 0 && $actual_tep_brokerage != "") {
$total_amt += $actual_tep_brokerage;
//percentage reverse calculation
if (($actual_tep_per == 0 || $actual_tep_per == "") && !empty($actual_tep_amt)) {
$actual_tep_per = ($actual_tep_brokerage / $actual_tep_amt) * 100;
}
} else {
$actual_tep_brokerage = $actual_tep_amt * ($actual_tep_per / 100);
$total_amt += $actual_tep_brokerage;
}
//find variance
$variance_amt = $source_row['exp_amt'] - $total_amt;
$data_to_update[] = ['co_share_id' => $source_row['id'], 'actual_bp_amt' => $actual_bp_amt, 'actual_tp_amt' => $actual_tp_amt, 'actual_tep_amt' => $actual_tep_amt, 'actual_bp_per' => $actual_bp_per, 'actual_tp_per' => $actual_tp_per, 'actual_tep_per' => $actual_tep_per, 'variance' => $variance_amt, 'actual_tep_brokerage_amt' => $actual_tep_brokerage, 'actual_tp_brokerage_amt' => $actual_tp_brokerage, 'actual_bp_brokerage_amt' => $actual_bp_brokerage, 'reward' => trim((string)($excel_row[7] ?? '')), 'statement_id' => $file_id];
}
} catch (\Throwable $th) {
$this->myLogger->logme("error", "REST-AUTH-CONTROLLER - updateInsurerStatement: Exception: " . $th->getMessage() . " --- Line: " . $th->getLine() . " --- Trace: " . $th->getTraceAsString());
$errorData = [
'message' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine(),
'code' => $th->getCode(),
'trace' => $th->getTraceAsString(),
'trace_array' => $th->getTrace(), // full array version (optional)
'function' => $th->getTrace()[0]['function'] ?? null,
'class' => $th->getTrace()[0]['class'] ?? null,
];
return ['status' => 'failed', 'code' => 500, 'message' => $th->getMessage(), 'error_data' => $errorData];
}
// dd($data_to_update);
$this->coShareStmtDetailsModel->insertBatch($data_to_update, 'id');
// dd($data_to_update);
// if($error_data['error_code'])
// {
// $status = 'failed';
// $ret_status = false;
// }
//update in DB
$this->insurerStatements->where('id', $file_id)->set(['file_status' => $status, 'reason' => json_encode($error_data), 'invoice_status' => 'pending'])->update();
return array('status' => $ret_status, 'error_code' => $error_data['error_code'], 'error_data' => $error_data['error_data']);
}
public function getInvoicePaymentDetails()
{
$statement_id = $this->request->getUri()->getSegment(4);

View File

@ -371,6 +371,102 @@ class SalesController extends BaseController
}
}
/**
* Export leads filtered by created_at date range.
* GET /sales/leads/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD
*/
public function exportLeads()
{
try {
$range = $this->getExportDateRange();
} catch (\InvalidArgumentException $e) {
return $this->failValidationErrors($e->getMessage());
}
try {
$db = \Config\Database::connect();
$builder = $db->table('sales_actual_leads sal')
->select('
sal.company_name,
c.short_name,
sal.address,
sal.website,
sal.gst_number,
sal.email,
sal.phone,
sal.status,
sal.created_at,
up.first_name as assigned_to_name,
COUNT(DISTINCT sa.activity_id) as total_activity
')
->join('clients c', 'c.id = sal.client_id', 'left')
->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
->where("sal.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false);
$status = $this->request->getGet('status');
if (!empty($status)) {
$builder->where('sal.status', $status);
}
$assignedToIds = $this->getExportAssignedToIds();
if (!empty($assignedToIds)) {
$builder->whereIn('sal.assigned_to', $assignedToIds);
}
$search = $this->request->getGet('search');
if (!empty($search)) {
$builder->groupStart()
->like('sal.company_name', $search)
->orLike('c.short_name', $search)
->orLike('sal.address', $search)
->orLike('sal.website', $search)
->orLike('sal.gst_number', $search)
->orLike('sal.email', $search)
->orLike('sal.phone', $search)
->orLike('up.first_name', $search)
->groupEnd();
}
$leads = $builder
->groupBy('sal.lead_id, sal.company_name, c.short_name, sal.address, sal.website, sal.gst_number, sal.email, sal.phone, sal.status, sal.created_at, up.first_name')
->orderBy('sal.created_at', 'DESC')
->get()
->getResultArray();
if (empty($leads)) {
return $this->exportNotFoundResponse("Leads Not Found {$range['display']}");
}
$rows = [];
$serialNo = 1;
foreach ($leads as $lead) {
$rows[] = [
$serialNo++,
$lead['company_name'] ?? '',
$lead['short_name'] ?? '',
$lead['address'] ?? '',
$lead['website'] ?? '',
$lead['gst_number'] ?? '',
$lead['email'] ?? '',
$lead['phone'] ?? '',
$lead['status'] ?? '',
$lead['assigned_to_name'] ?? '',
$lead['total_activity'] ?? 0,
$this->formatExportDate($lead['created_at'] ?? ''),
];
}
return $this->streamCsvDownload(
"sales-leads-{$range['from_label']}-to-{$range['to_label']}.csv",
['S.No', 'Company', 'Company Short Name', 'Address', 'Website', 'GST', 'Email', 'Phone', 'Status', 'Assigned To', 'Total Activity', 'Created At'],
$rows
);
} catch (\Exception $e) {
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Get single lead with complete details
* GET /api/sales/leads/{id}
@ -757,6 +853,104 @@ class SalesController extends BaseController
}
}
/**
* Export activities filtered by created_at date range.
* GET /sales/activities/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD
*/
public function exportActivities()
{
try {
$range = $this->getExportDateRange();
} catch (\InvalidArgumentException $e) {
return $this->failValidationErrors($e->getMessage());
}
try {
$db = \Config\Database::connect();
$builder = $db->table('sales_activities sa')
->select("
sa.activity_type,
sa.status,
sa.scheduled_date,
sa.created_at,
sa.notes,
sa.completion_notes,
sal.company_name,
c.short_name,
up1.first_name as assigned_to_name,
GROUP_CONCAT(DISTINCT up2.first_name SEPARATOR ', ') as additional_assigned_names
")
->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left')
->join('clients c', 'c.id = sal.client_id', 'left')
->join('user_profiles up1', 'up1.id = sa.assigned_to', 'left')
->join('user_profiles up2', "
sa.additional_assigned_ids IS NOT NULL
AND sa.additional_assigned_ids != ''
AND sa.additional_assigned_ids != '[]'
AND JSON_VALID(sa.additional_assigned_ids)
AND JSON_CONTAINS(sa.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR)))
", 'left')
->where("sa.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false);
$status = $this->request->getGet('status');
if (!empty($status)) {
$builder->where('sa.status', $status);
}
$assignedToIds = $this->getExportAssignedToIds();
if (!empty($assignedToIds)) {
$builder->whereIn('sa.assigned_to', $assignedToIds);
}
$search = $this->request->getGet('search');
if (!empty($search)) {
$builder->groupStart()
->like('sal.company_name', $search)
->orLike('c.short_name', $search)
->orLike('sa.status', $search)
->orLike('sa.activity_type', $search)
->orLike('up1.first_name', $search)
->orLike('up2.first_name', $search)
->groupEnd();
}
$activities = $builder
->groupBy('sa.activity_id')
->orderBy('sa.created_at', 'DESC')
->get()
->getResultArray();
if (empty($activities)) {
return $this->exportNotFoundResponse("Activities not Found {$range['display']}");
}
$rows = [];
$serialNo = 1;
foreach ($activities as $activity) {
$rows[] = [
$serialNo++,
$activity['company_name'] ?? '',
$activity['short_name'] ?? '',
$activity['activity_type'] ?? '',
ucfirst((string) ($activity['status'] ?? '')),
$activity['assigned_to_name'] ?? '',
$activity['additional_assigned_names'] ?? '',
$this->formatExportDate($activity['scheduled_date'] ?? ''),
$this->formatExportDate($activity['created_at'] ?? ''),
$activity['notes'] ?: ($activity['completion_notes'] ?? ''),
];
}
return $this->streamCsvDownload(
"sales-activities-{$range['from_label']}-to-{$range['to_label']}.csv",
['S.No', 'Company', 'Company Short Name', 'Activity Type', 'Status', 'Assigned To', 'Additional Members', 'Scheduled Date', 'Created At', 'Notes'],
$rows
);
} catch (\Exception $e) {
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Get activities for a specific lead
* GET /api/sales/leads/{leadId}/activities
@ -1251,6 +1445,104 @@ class SalesController extends BaseController
return $filters;
}
private function getExportDateRange(): array
{
$fromDate = trim((string) $this->request->getGet('from_date'));
$toDate = trim((string) $this->request->getGet('to_date'));
if ($fromDate === '' || $toDate === '') {
throw new \InvalidArgumentException('Please select from date and to date.');
}
$from = $this->normalizeExportDate($fromDate, '00:00:00');
$to = $this->normalizeExportDate($toDate, '23:59:59');
if ($from === null || $to === null) {
throw new \InvalidArgumentException('Invalid date range. Use YYYY-MM-DD format.');
}
if (strtotime($from) > strtotime($to)) {
throw new \InvalidArgumentException('From date cannot be after to date.');
}
return [
'from' => $from,
'to' => $to,
'from_label' => date('Y-m-d', strtotime($from)),
'to_label' => date('Y-m-d', strtotime($to)),
'display' => date('d-m-Y', strtotime($from)) . ' to ' . date('d-m-Y', strtotime($to)),
];
}
private function normalizeExportDate(string $date, string $time): ?string
{
$dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $date . ' ' . $time);
if (!$dateTime || $dateTime->format('Y-m-d') !== $date) {
return null;
}
return $dateTime->format('Y-m-d H:i:s');
}
private function getExportAssignedToIds(): array
{
$assignedTo = (string) $this->request->getGet('assigned_to');
if ($assignedTo === '') {
return [];
}
return array_values(array_filter(array_map('trim', explode(',', $assignedTo)), static function ($id) {
return ctype_digit($id);
}));
}
private function streamCsvDownload(string $filename, array $headers, array $rows)
{
$handle = fopen('php://temp', 'r+');
fwrite($handle, "\xEF\xBB\xBF");
fputcsv($handle, $headers);
foreach ($rows as $row) {
fputcsv($handle, $row);
}
rewind($handle);
$csv = stream_get_contents($handle);
fclose($handle);
return $this->response
->setHeader('Content-Type', 'text/csv; charset=UTF-8')
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
->setBody($csv);
}
private function exportNotFoundResponse(string $message)
{
return $this->response
->setStatusCode(ResponseInterface::HTTP_NOT_FOUND)
->setJSON([
'status' => 'error',
'message' => $message,
]);
}
private function formatExportDate(?string $value): string
{
if (empty($value)) {
return '';
}
$timestamp = strtotime($value);
if ($timestamp === false) {
return $value;
}
return date('d/m/Y h:i A', $timestamp);
}
// ==================== Dashboard ====================
@ -1707,15 +1999,23 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
// ── Won Leads for this user in FY (Table 2 in modal) ──
// leads.actual_lead_id maps to sales_actual_leads.id
// leads.type: 1 = EB, else = Non-EB
// leads.lead_form_type: 1 = EB, else = Non-EB
// leads.lead_type: 1 = Fresh, 2 = Renewal, 3 = Roll Over
$wonLeads = $db->query("
SELECT
l.id AS opportunities_id,
l.actual_lead_id,
COALESCE(l.lead_form_type, 1) AS lead_form_type_id,
l.lead_type AS lead_type_id,
sal.company_name AS company,
l.client_name AS client_name,
CASE WHEN l.lead_type = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_type,
CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type,
CASE
WHEN l.lead_type = 1 THEN 'Fresh'
WHEN l.lead_type = 2 THEN 'Renewal'
WHEN l.lead_type = 3 THEN 'Roll Over'
ELSE ''
END AS lead_type,
l.created_at AS created_at,
l.status
FROM leads l

View File

@ -141,14 +141,8 @@ if (! function_exists('merge_ticket_pdfs')) {
$src = $sourceFile['path'];
try {
if ($sourceFile['mime'] === 'application/pdf') {
$pageCount = $mpdf->setSourceFile($src);
for ($p = 1; $p <= $pageCount; $p++) {
$tplId = $mpdf->importPage($p);
$mpdf->AddPage();
// adjustPageSize=true makes the output page match the imported page.
$mpdf->useTemplate($tplId, 0, 0, null, null, true);
$totalPages++;
}
$pageCount = merge_ticket_pdf_add_pdf_pages($mpdf, $src, $tempDir);
$totalPages += $pageCount;
log_message('error', "merge_ticket_pdfs | added PDF | file={$src} | pages={$pageCount}");
} elseif (in_array($sourceFile['mime'], ['image/jpeg', 'image/png'], true)) {
if (merge_ticket_pdf_add_image_page($mpdf, $src)) {
@ -226,6 +220,81 @@ if (! function_exists('merge_ticket_pdfs')) {
}
}
if (! function_exists('merge_ticket_pdf_add_pdf_pages')) {
/**
* Import PDF pages. If FPDI cannot import the source (commonly encrypted
* PDFs), normalize through Ghostscript and retry.
*/
function merge_ticket_pdf_add_pdf_pages(Mpdf $mpdf, string $pdfPath, string $tempDir): int
{
try {
return merge_ticket_pdf_import_pdf_pages($mpdf, $pdfPath);
} catch (\Throwable $e) {
log_message('error', "merge_ticket_pdfs | direct PDF import failed | file={$pdfPath} | " . $e->getMessage());
$normalizedPath = merge_ticket_pdf_normalize_with_ghostscript($pdfPath, $tempDir);
if ($normalizedPath === null) {
throw $e;
}
try {
$pageCount = merge_ticket_pdf_import_pdf_pages($mpdf, $normalizedPath);
log_message('error', "merge_ticket_pdfs | Ghostscript normalized PDF imported | original={$pdfPath} | normalized={$normalizedPath} | pages={$pageCount}");
@unlink($normalizedPath);
return $pageCount;
} catch (\Throwable $normalizedError) {
@unlink($normalizedPath);
throw $normalizedError;
}
}
}
}
if (! function_exists('merge_ticket_pdf_import_pdf_pages')) {
function merge_ticket_pdf_import_pdf_pages(Mpdf $mpdf, string $pdfPath): int
{
$pageCount = $mpdf->setSourceFile($pdfPath);
for ($p = 1; $p <= $pageCount; $p++) {
$tplId = $mpdf->importPage($p);
$mpdf->AddPage();
// adjustPageSize=true makes the output page match the imported page.
$mpdf->useTemplate($tplId, 0, 0, null, null, true);
}
return $pageCount;
}
}
if (! function_exists('merge_ticket_pdf_normalize_with_ghostscript')) {
function merge_ticket_pdf_normalize_with_ghostscript(string $pdfPath, string $tempDir): ?string
{
$ghostscript = is_executable('/usr/bin/gs') ? '/usr/bin/gs' : trim((string) @shell_exec('command -v gs 2>/dev/null'));
if ($ghostscript === '' || ! is_executable($ghostscript)) {
log_message('error', "merge_ticket_pdfs | Ghostscript not available for PDF normalization | file={$pdfPath}");
return null;
}
$normalizedPath = rtrim($tempDir, '/\\') . DIRECTORY_SEPARATOR . 'normalized_' . uniqid('', true) . '.pdf';
$cmd = escapeshellarg($ghostscript)
. ' -q -dNOPAUSE -dBATCH -dSAFER -sDEVICE=pdfwrite -dCompatibilityLevel=1.4'
. ' -sOutputFile=' . escapeshellarg($normalizedPath)
. ' ' . escapeshellarg($pdfPath)
. ' 2>&1';
$output = [];
$exitCode = 1;
@exec($cmd, $output, $exitCode);
if ($exitCode !== 0 || ! is_file($normalizedPath) || filesize($normalizedPath) <= 0) {
log_message('error', 'merge_ticket_pdfs | Ghostscript normalization failed | file=' . $pdfPath . ' | exit=' . $exitCode . ' | output=' . implode(' ', $output));
@unlink($normalizedPath);
return null;
}
return $normalizedPath;
}
}
if (! function_exists('merge_ticket_pdf_resolve_mime')) {
/**
* Resolve file type from stored MIME, filesystem MIME, and extension.

View File

@ -121,5 +121,41 @@ class PTCOShareDetailsModel extends Model
->get()
->getResultArray();
}
public function getNonReconcileredPolicyTransactionByPolicyAndEndorsement(string $insurer_id, string $insurer_branch_id, array $policy_no)
{
$builder = $this->db->table('pt_co_share_details pt_co')
->select("
pt_co.id,
pt_co.pt_id,
pt_co.exp_amt,
pt.id AS policy_transaction_id,
pt.endorsement_no,
c.client_name,
pt.created_at,
pt_co.insurer_id,
pt_co.insurer_branch_id,
pt.policy_no,
pt.policy_issue_date,
pt.policy_start_date,
pt.policy_end_date,
pt.status,
pt_co.bp_amt,
pt_co.tp_amt,
pt_co.tep_amt,
pt_co.exp_amt,
pt_co.statement_id
")
->join('policy_transaction pt', 'pt_co.pt_id = pt.id')
->join('clients c', 'pt.client_id = c.id')
->where('pt_co.is_active', 1)
->where('pt.is_active', 1)
->where('pt_co.insurer_id', $insurer_id)
->where('pt_co.insurer_branch_id', $insurer_branch_id)
->whereIn('pt.policy_no', $policy_no);
return $builder->get()->getResultArray();
}
}

View File

@ -302,7 +302,15 @@ $isl_col_width_px = nhance_dt_column_widths_px($isl_header_labels, $isl_col_max_
<td><?php echo $row['short_name'] . '-' . $row['branch_code']; ?></td>
<td><?php echo change_date_format($row['month'], 'Y-m-d', 'M-Y'); ?></td>
<td><?php echo $row['stmt_sno'] ?> </td>
<td><?php echo $row['file_name'] ?> </td>
<td>
<?php if (!empty($row['file_name'])) { ?>
<a href="<?php echo base_url('policy_tranction/statement/downloadInsurerStatement/' . $row['id']); ?>" class="text-primary" title="Download <?php echo esc($row['file_name']); ?>">
<?php echo esc($row['file_name']); ?>
</a>
<?php } else { ?>
-
<?php } ?>
</td>
<td><?php echo $row['line_items'] ?></td>
<td><?php echo $row['file_status'];
if ($row['file_status'] == 'failed') {

View File

@ -166,6 +166,7 @@
enctype="multipart/form-data">
<input type="hidden" name="id" id="leads_primarykey">
<input type="hidden" name="lead_form_type" id="lead_form_type_id" value="<?= isset($selected_lead_type) ? $selected_lead_type : 1 ?>">
<input type="hidden" name="actual_lead_id" id="actual_lead_id" value="<?= isset($actual_lead_id) ? $actual_lead_id : 0 ?>">
@ -219,7 +220,7 @@
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="card-title mb-0">Client Information</h4>
<div class="form-group freshFields mb-0 d-flex align-items-center">
<div class="form-group freshFields existing-client-toggle mb-0 d-flex align-items-center">
<label for="exixting_client" class="mb-0 mr-2">Existing Client</label>
<label class="switch mb-0">
<input id="exixting_client" type="checkbox" name="exixting_client">
@ -480,8 +481,30 @@
<script>
var actualLeadClientName = '';
var actualLeadShortName = '';
var actualLeadGstNumber = '';
var actualLeadContactDetails = null;
let claimIndex = 1;
function restoreActualLeadGstNumber() {
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0 && actualLeadGstNumber) {
$('#gst').val(actualLeadGstNumber);
}
}
function restoreActualLeadContactDetails(force = false) {
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0 && actualLeadContactDetails) {
if (force || !$('#contact_person_name').val()) {
$('#contact_person_name').val(actualLeadContactDetails.name || '');
}
if (force || !$('#contact_person_mobile').val()) {
$('#contact_person_mobile').val(actualLeadContactDetails.mobile || '');
}
if (force || !$('#contact_person_email').val()) {
$('#contact_person_email').val(actualLeadContactDetails.email || '');
}
}
}
setTimeout(() => {
var policyStart = $(".policy_start_date");
var policyEnd = $(".policy_end_date");
@ -744,7 +767,7 @@
}
leadTypeBsedHideAndShow(lead_type)
leadTypeBsedHideAndShow(lead_type, false, policy_type_id)
if (lead_type == 1) {
@ -822,11 +845,15 @@
$('#lost_reason').val(res.data.lost_reason || '');
$('#notes').val(res.data.notes);
let existingClientId = res.data.client_id || res.data.is_client_created || 0;
const isExistingClient = res.data.client_id !== undefined
&& res.data.client_id !== null
&& String(res.data.client_id) !== ''
&& String(res.data.client_id) !== '0';
let existingClientId = isExistingClient ? res.data.client_id : 0;
console.log('existingClientId', existingClientId);
if (lead_type == 3) {
if (existingClientId && existingClientId != 0) {
if (lead_type == 1 || lead_type == 3) {
if (isExistingClient) {
$('#exixting_client').prop('checked', true).trigger('change');
} else {
$('#exixting_client').prop('checked', false).trigger('change');
@ -851,6 +878,7 @@
.contact_person_email);
$('#policy_type_id_1').val(res.data.policy_type_id)
.select2();
togglePolicyDateFields(lead_type, res.data.policy_type_id);
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');
}, 1000)
@ -1093,6 +1121,7 @@
var lead_type = $('#lead_type').val();
console.log("lead_type", lead_type);
togglePolicyDateFields(lead_type);
let url = '<?= base_url('util/getPolicyTypeFields/') ?>';
@ -1281,7 +1310,7 @@
</select>
</div>
<div class="form-group col-md-4">
<div class="form-group col-md-4 policy-date-field">
<label for="policy_start_date">Date of Commencement <span class="text-danger">*</span></label>
<div class="input-icon">
<input type="text" class="form-control policy_start_date" id="policy_start_date_${increment}" name="policy_start_date[]" placeholder="Enter DOC" onchange="calculatePolicyMetrics(this)" autocomplete="off" >
@ -1290,7 +1319,7 @@
</div>
<div class="form-group col-md-4">
<div class="form-group col-md-4 policy-date-field">
<label for="policy_end_date">Date of Expiry <span class="text-danger">*</span></label>
<div class="input-icon">
<input type="text" class="form-control policy_end_date" id="policy_end_date_${increment}" name="policy_end_date[]" placeholder="Enter DOE" autocomplete="off">
@ -1776,6 +1805,8 @@
const finyearJsonString = gatherClaimExperienceData(policy_type_ids);
console.log('finyearJsonString', finyearJsonString);
formData.append('finyear', finyearJsonString);
let claimHistoryStatus = $("#claim_history").length > 0 && $("#claim_history").prop("checked") ? 1 : 0;
formData.set('claim_history', claimHistoryStatus);
$.ajax({
data: formData,
@ -1898,6 +1929,7 @@
$('#policy_start_date').val("");
$('#policy_end_date').val("");
$("#appendArea_1").empty();
togglePolicyDateFields(value);
// $('#source_policy_start_date').val("");
// $('#source_policy_end_date').val("");
@ -1938,6 +1970,8 @@
// --- Re-populate Actual Lead data if it was cleared ---
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0) {
$('#client_name').val(actualLeadClientName);
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
// Re-generate short name from the stored client name to ensure consistency
if (actualLeadClientName.trim() !== '') {
let baseName = actualLeadClientName
@ -2019,6 +2053,12 @@
let claimData = [];
if ($("#claim_history").length > 0 && !$("#claim_history").prop("checked")) {
return JSON.stringify({
"finyear": claimData
});
}
$(".claim-row").each(function() {
let year = $(this).find("[name='first_year[]']").val();
@ -2156,15 +2196,23 @@
let policy_type_id = $('#policy_type_id_' + count).val()
console.log('policy_type_id', policy_type_id);
let lead_type = $('#lead_type').val();
let showClaimHistorySwitch = lead_type == 2 && policy_type_id == 1;
console.log('claimIndex from parent', claimIndex);
let increment = claimIndex;
let claimsFields = `
let claimsFields = `${showClaimHistorySwitch && !document.querySelector('#claim_history') ? `<div class="custom-control custom-switch">
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" checked>
<label class="custom-control-label" for="claim_history">Claims History</label>
</div><br>` : ``}`;
claimsFields += `
<div class="row claim-row">
<div class="form-group col-md-2">
<label for="first_year_${increment}">Year<span class="text-danger">*</span></label>
<select class="form-control first_year_" id="first_year_${increment}" name="first_year[]">
<select class="form-control first_year_ claim-input" id="first_year_${increment}" name="first_year[]">
<option value="">Select Year</option>
<?php foreach ($lastFiveYears as $year) {
echo "<option value='$year'>$year</option>";
@ -2174,18 +2222,18 @@
<div class="form-group col-md-2">
<label for="emp_id_${increment}">Emp ID<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="emp_id_${increment}" name="emp_id[]">
<input type="text" class="form-control claim-input" id="emp_id_${increment}" name="emp_id[]">
</div>
<div class="form-group col-md-2">
<label for="emp_name_${increment}">Employee Name<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="emp_name_${increment}" name="emp_name[]">
<input type="text" class="form-control claim-input" id="emp_name_${increment}" name="emp_name[]">
</div>
<div class="form-group col-md-2">
<label for="gender_${increment}">Gender<span class="text-danger">*</span></label>
<select class="form-control" id="gender_${increment}" name="gender[]">
<select class="form-control claim-input" id="gender_${increment}" name="gender[]">
<option value="">Select Gender</option>
<option value="Female">Female</option>
<option value="Male">Male</option>
@ -2194,25 +2242,25 @@
<div class="form-group col-md-2">
<label for="designation_${increment}">Designation <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="designation_${increment}" name="designation[]">
<input type="text" class="form-control claim-input" id="designation_${increment}" name="designation[]">
</div>
<div class="form-group col-md-2">
<label for="sum_insured_${increment}">Sum Insured <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="sum_insured_${increment}" name="sum_insured[]">
<input type="text" class="form-control claim-input" id="sum_insured_${increment}" name="sum_insured[]">
</div>
<div class="form-group col-md-2">
<label for="first_death_date_${increment}">Date of Death<span class="text-danger">*</span></label>
<div class="input-icon">
<input type="text" class="form-control death_date flatpickr-date" id="first_death_date_${increment}" name="first_death_date[]" autocomplete="off">
<input type="text" class="form-control death_date flatpickr-date claim-input" id="first_death_date_${increment}" name="first_death_date[]" autocomplete="off">
<i class="mdi mdi-calendar-blank-outline additional-icon"></i>
</div>
</div>
<div class="form-group col-md-2">
<label for="first_cause_of_death_${increment}">Nature/Cause Of Death <span class="text-danger">*</span></label>
<select class="form-control" id="first_cause_of_death_${increment}" name="first_cause_of_death[]">
<select class="form-control claim-input" id="first_cause_of_death_${increment}" name="first_cause_of_death[]">
<option value="">Select Cause of Death</option>
<?php foreach ($causeOfDeath as $cause => $death_value) {
echo "<option value='$cause'>$death_value</option>";
@ -2222,7 +2270,7 @@
<div class="form-group col-md-2">
<label for="first_claim_amount_${increment}">Claim/Settled Amount<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="first_claim_amount_${increment}" name="first_claim_amount[]">
<input type="text" class="form-control claim-input" id="first_claim_amount_${increment}" name="first_claim_amount[]">
</div>
<!-- <div class="form-group col-md-2">
@ -2283,7 +2331,13 @@
maxDate: 'today', // Optional: disable future dates
});
if (showClaimHistorySwitch) {
claimHistoryToggle();
}
toggleRequiredFields();
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
}
@ -2297,7 +2351,48 @@
}
}
function leadTypeBsedHideAndShow(value, resetValues = false) {
function claimHistoryToggle() {
let claimHistoryStatus = $("#claim_history").prop("checked") ? 1 : 0;
if (claimHistoryStatus == 1) {
$(".claim-row").show();
$(".claim-input").attr("required", true);
} else {
$(".claim-row").hide();
$(".claim-input").removeAttr("required").val("");
$(".claim-input").each(function() {
if ($(this).hasClass('select2-hidden-accessible')) {
$(this).val(null).trigger('change');
}
if ($(this).parsley) {
$(this).parsley().reset();
}
});
}
toggleRequiredFields();
}
function togglePolicyDateFields(value, fallbackPolicyTypeId = null) {
const policyTypesToHide = ['1', '2', '3', '4', '5', '6', '7'];
$('.dynamic-form-row').each(function(index) {
const selectedPolicyTypeId = $(this).find('[id^="policy_type_id_"]').val();
const policyTypeId = String(selectedPolicyTypeId || (index === 0 ? fallbackPolicyTypeId || '' : ''));
const shouldHide = value == 1 && policyTypesToHide.includes(policyTypeId);
$(this).find('.policy-date-field').toggle(!shouldHide);
});
}
function leadTypeBsedHideAndShow(value, resetValues = false, fallbackPolicyTypeId = null) {
togglePolicyDateFields(value, fallbackPolicyTypeId);
if (value == 2) {
$('.existing-client-toggle').removeClass('d-flex').addClass('d-none');
} else {
$('.existing-client-toggle').removeClass('d-none').addClass('d-flex');
}
if (value == 1) {
$('.btnDiv').show();
@ -2363,6 +2458,10 @@
$('.rolloverhide').hide();
$('.rolloverhide').find('select, input, textarea').removeAttr('required').prop('required', false);
$('#source_policy_id, #source_policy_start_date, #source_policy_end_date')
.removeAttr('required')
.prop('required', false);
if (resetValues) {
@ -2448,6 +2547,8 @@
}
toggleRequiredFields();
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
}
function toggleRequiredFields() {
@ -2561,6 +2662,8 @@
$("#client_name").prop("required", true);
$('#client_name').closest('.form-group').show();
}
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
});
@ -2576,7 +2679,8 @@
actualLeadClientName = actual_lead_client_details.company_name || '';
$('#client_name').val(actualLeadClientName);
$('#gst').val(actual_lead_client_details.gst_number || '');
actualLeadGstNumber = actual_lead_client_details.gst_number || actual_lead_client_details.gst || '';
$('#gst').val(actualLeadGstNumber);
if (actual_lead_client_details.client_type !== undefined && actual_lead_client_details.client_type !== null && actual_lead_client_details.client_type !== '') {
$('#client_type').val(String(actual_lead_client_details.client_type));
}
@ -2627,12 +2731,8 @@
// CONTACT PERSON AUTO FILL
// -------------------------------
if (actual_lead_contact_person_details) {
$('#contact_person_name').val(actual_lead_contact_person_details.name || '');
$('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || '');
$('#contact_person_email').val(actual_lead_contact_person_details.email || '');
actualLeadContactDetails = actual_lead_contact_person_details;
restoreActualLeadContactDetails();
}

View File

@ -831,13 +831,20 @@ if (isset($selected_lead_type)) {
let policy_type_id = data.policy_type_id || null;
let lead_type = data.lead_type || null;
const isExistingClient = data.client_id !== undefined
&& data.client_id !== null
&& String(data.client_id) !== ''
&& String(data.client_id) !== '0';
if ([1, 6, 7].includes(policy_type_id)) {
let newId = 'appendAreaForClaim_' + dataIncrement;
$('#appendAreaForClaim').attr('id', newId);
}
leadTypeBsedHideAndShow(lead_type);
leadTypeBsedHideAndShow(lead_type, false, policy_type_id);
if ($('#claim_history').length && typeof claimHistoryToggle === 'function') {
claimHistoryToggle();
}
$('.loader').fadeIn();
$('.loader-mask').fadeIn();
@ -846,7 +853,7 @@ if (isset($selected_lead_type)) {
if (lead_type == 1 || lead_type == 3) {
$('.claim-row').hide();
if(data.is_client_created == null || data.is_client_created == 0 || data.is_client_created == ""){
if(!isExistingClient){
$('#exixting_client').prop('checked', false);
$('#exixting_client').prop('disabled', true);
@ -935,6 +942,10 @@ if (isset($selected_lead_type)) {
}
}
if ($('#claim_history').length && typeof claimHistoryToggle === 'function') {
claimHistoryToggle();
}
if(lead_type == 3){
let incurred_claim_date_id = 'incurred_claim_date';
@ -1110,7 +1121,12 @@ if (isset($selected_lead_type)) {
leadTypeBsedHideAndShow(lead_type);
if(data.client_id == 0 || data.client_id == null){
const isExistingClient = data.client_id !== undefined
&& data.client_id !== null
&& String(data.client_id) !== ''
&& String(data.client_id) !== '0';
if(!isExistingClient){
temp_client_id = 0;
}else{
temp_client_id = data.client_id
@ -1118,7 +1134,7 @@ if (isset($selected_lead_type)) {
if(lead_type == 1 || lead_type == 3){
if(data.is_client_created == null || data.is_client_created == 0 || data.is_client_created == ""){
if(!isExistingClient){
$('#exixting_client').prop('checked', false);
$('#exixting_client').prop('disabled', true);
@ -1324,6 +1340,7 @@ if (isset($selected_lead_type)) {
$('#contact_person_email').val(data.contact_person_email || '');
$('#policy_type_id').val(data.policy_type_id || '').select2();
$('#policy_type_id_1').val(data.policy_type_id || '').select2();
togglePolicyDateFields(lead_type, data.policy_type_id || '');
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');

View File

@ -493,6 +493,22 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
<script>
var actualLeadClientName = '';
var actualLeadShortName = '';
var actualLeadGstNumber = '';
var actualLeadContactDetails = null;
function restoreActualLeadGstNumber() {
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0 && actualLeadGstNumber) {
$('#gst').val(actualLeadGstNumber);
}
}
function restoreActualLeadContactDetails() {
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0 && actualLeadContactDetails) {
$('#contact_person_name').val(actualLeadContactDetails.name || '');
$('#contact_person_mobile').val(actualLeadContactDetails.mobile || '');
$('#contact_person_email').val(actualLeadContactDetails.email || '');
}
}
$(document).ready(function() {
$('#rfq_qcr_viewers').parsley({
@ -561,6 +577,22 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
addFileField(1);
})
function togglePolicyDateFields(value, fallbackPolicyTypeId = null) {
const policyTypesToHide = ['1', '2', '3', '4', '5', '6', '7'];
const selectedPolicyTypeId = $('#policy_type_id').val();
const policyTypeId = String(selectedPolicyTypeId || fallbackPolicyTypeId || '');
const shouldHide = value == 1 && policyTypesToHide.includes(policyTypeId);
const $policyDateFields = $('#policy_start_date, #policy_end_date').closest('.form-group');
$policyDateFields.toggle(!shouldHide);
if (shouldHide) {
$policyDateFields.find('input').prop('required', false).val('');
} else if (value == 1 || value == 3) {
$policyDateFields.find('input').prop('required', true);
}
}
function getPolicyTypeFields(input) {
console.log('getPolicyTypeFields', input);
@ -855,6 +887,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
// --- Re-populate Actual Lead data if it was cleared ---
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0) {
$('#client_name').val(actualLeadClientName);
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
if (actualLeadShortName.trim() !== '') {
$('#client_short_name').val(actualLeadShortName);
} else if (actualLeadClientName.trim() !== '') {
@ -955,6 +989,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
}
}
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
}
var allContacts = "";
function getBranchData(input,inputType) {
@ -1348,6 +1384,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
$("#client_id").prop("required", false);
$("#client_branch_id").prop("required", false);
}
restoreActualLeadGstNumber();
restoreActualLeadContactDetails();
});
$(document).ready(function() {
@ -1362,7 +1400,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
actualLeadClientName = actual_lead_client_details.company_name || '';
$('#client_name').val(actualLeadClientName);
$('#gst').val(actual_lead_client_details.gst_number || '');
actualLeadGstNumber = actual_lead_client_details.gst_number || actual_lead_client_details.gst || '';
$('#gst').val(actualLeadGstNumber);
if (actual_lead_client_details.client_type !== undefined && actual_lead_client_details.client_type !== null && actual_lead_client_details.client_type !== '') {
$('#client_type').val(String(actual_lead_client_details.client_type));
}
@ -1411,12 +1450,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
// CONTACT PERSON AUTO FILL
// -------------------------------
if (actual_lead_contact_person_details) {
$('#contact_person_name').val(actual_lead_contact_person_details.name || '');
$('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || '');
$('#contact_person_email').val(actual_lead_contact_person_details.email || '');
actualLeadContactDetails = actual_lead_contact_person_details;
restoreActualLeadContactDetails();
}

View File

@ -41,10 +41,23 @@
<hr>
<?php if(isset($lead_edit_data)) { ?>
<?php
$savedClaims = !empty($lead_edit_data['fin_years_claims_array']) ? $lead_edit_data['fin_years_claims_array'] : [];
$isClaimHistoryChecked = (isset($lead_edit_data['claim_history']) && (int) $lead_edit_data['claim_history'] === 1) || !empty($savedClaims);
$showClaimHistorySwitch = isset($lead_edit_data['lead_type'], $lead_edit_data['policy_type_id'])
&& (int) $lead_edit_data['lead_type'] === 2
&& (int) $lead_edit_data['policy_type_id'] === 1;
?>
<?php if ($showClaimHistorySwitch) { ?>
<div class="custom-control custom-switch">
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" <?= $isClaimHistoryChecked ? 'checked' : '' ?>>
<label class="custom-control-label" for="claim_history">Claims History</label>
</div><br>
<?php } ?>
<div class="form-row" id="appendAreaForClaim_1">
<?php
$claims = !empty($lead_edit_data['fin_years_claims_array']) ? $lead_edit_data['fin_years_claims_array'] : [ ['year' => '', 'claim_amount' => '', 'status' => '', 'claim_type' => '', 'cause_of_death' => '', 'death_date' => ''] ];
$claims = !empty($savedClaims) ? $savedClaims : [ ['year' => '', 'claim_amount' => '', 'status' => '', 'claim_type' => '', 'cause_of_death' => '', 'death_date' => ''] ];
foreach ($claims as $key => $value) { ?>
@ -52,7 +65,7 @@
<div class="form-group col-md-2">
<label for="first_year">Year<span class="text-danger">*</span></label>
<select class="form-control first_year_" id="first_year" name="first_year[]">
<select class="form-control first_year_ claim-input" id="first_year" name="first_year[]">
<option value="">Select Year</option>
<?php foreach ($lastFiveYears as $year) {
$selected = ($year == $value['year']) ? 'selected' : '';
@ -63,17 +76,17 @@
<div class="form-group col-md-2">
<label for="emp_id">Emp ID<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="emp_id" name="emp_id[]" value="<?= htmlspecialchars(isset($value['emp_id']) ? $value['emp_id'] : '-' ) ?>">
<input type="text" class="form-control claim-input" id="emp_id" name="emp_id[]" value="<?= htmlspecialchars(isset($value['emp_id']) ? $value['emp_id'] : '-' ) ?>">
</div>
<div class="form-group col-md-2">
<label for="emp_name">Employee Name<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="emp_name" name="emp_name[]" value="<?= htmlspecialchars(isset($value['emp_name']) ? $value['emp_name'] : '-' ) ?>">
<input type="text" class="form-control claim-input" id="emp_name" name="emp_name[]" value="<?= htmlspecialchars(isset($value['emp_name']) ? $value['emp_name'] : '-' ) ?>">
</div>
<div class="form-group col-md-2">
<label for="gender">Gender<span class="text-danger">*</span></label>
<select class="form-control" id="gender" name="gender[]">
<select class="form-control claim-input" id="gender" name="gender[]">
<option value="">Select Gender</option>
<option value="Female" <?= (isset($value['gender']) && $value['gender'] == 'Female') ? 'selected' : '' ?>>Female</option>
<option value="Male" <?= (isset($value['gender']) && $value['gender'] == 'Male') ? 'selected' : '' ?>>Male</option>
@ -83,22 +96,22 @@
<div class="form-group col-md-2">
<label for="designation">Designation <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="designation" name="designation[]" value="<?= htmlspecialchars(isset($value['designation']) ? $value['designation'] : '-' ) ?>">
<input type="text" class="form-control claim-input" id="designation" name="designation[]" value="<?= htmlspecialchars(isset($value['designation']) ? $value['designation'] : '-' ) ?>">
</div>
<div class="form-group col-md-2">
<label for="sum_insured">Sum Insured <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="sum_insured" name="sum_insured[]" value="<?= htmlspecialchars(isset($value['sum_insured']) ? $value['sum_insured'] : '-' ) ?>">
<input type="text" class="form-control claim-input" id="sum_insured" name="sum_insured[]" value="<?= htmlspecialchars(isset($value['sum_insured']) ? $value['sum_insured'] : '-' ) ?>">
</div>
<div class="form-group col-md-2">
<label for="first_death_date">Date of Death<span class="text-danger">*</span></label>
<input type="text" class="form-control flatpickr-date" id="first_death_date" name="first_death_date[]" value="<?= htmlspecialchars($value['death_date']) ?>" autocomplete="off">
<input type="text" class="form-control flatpickr-date claim-input" id="first_death_date" name="first_death_date[]" value="<?= htmlspecialchars($value['death_date']) ?>" autocomplete="off">
</div>
<div class="form-group col-md-2">
<label for="first_cause_of_death">Nature/Cause Of Death <span class="text-danger">*</span></label>
<select class="form-control" id="first_cause_of_death" name="first_cause_of_death[]">
<select class="form-control claim-input" id="first_cause_of_death" name="first_cause_of_death[]">
<option value="">Select Cause of Death</option>
<?php foreach ($causeOfDeath as $cause => $death_value) {
$selected = ($cause == $value['cause_of_death']) ? 'selected' : '';
@ -109,7 +122,7 @@
<div class="form-group col-md-2">
<label for="first_claim_amount">Claim/Settled Amount<span class="text-danger">*</span></label>
<input type="text" class="form-control" id="first_claim_amount" name="first_claim_amount[]" value="<?= htmlspecialchars(isset($value['claim_amount']) ? $value['claim_amount'] : ( isset($value['settled']) ? $value['settled'] : '-' )) ?>">
<input type="text" class="form-control claim-input" id="first_claim_amount" name="first_claim_amount[]" value="<?= htmlspecialchars(isset($value['claim_amount']) ? $value['claim_amount'] : ( isset($value['settled']) ? $value['settled'] : '-' )) ?>">
</div>
<!-- <div class="form-group col-md-2">

View File

@ -224,7 +224,7 @@
<option value="<?= esc($year) ?>" <?= (($current_fin_year ?? '') === $year) ? 'selected' : '' ?>><?= esc($year) ?></option>
<?php endforeach; ?>
</select>
<button class="toolbar-icon-btn export-btn" onclick="exportActivitiesCsv()" title="Export" aria-label="Export activities">
<button class="toolbar-icon-btn export-btn" id="activityExportExcelBtn" title="Export Excel" aria-label="Export activities">
<i class="mdi mdi-file-excel"></i>
</button>
<button class="toolbar-icon-btn add-btn" onclick="openMainActivityModal()" title="Add Activity" aria-label="Add activity">
@ -1426,37 +1426,95 @@ function downloadCsv(filename, rows) {
URL.revokeObjectURL(link.href);
}
async function exportActivitiesCsv() {
function getExportFilename(response, fallbackFilename) {
const disposition = response.headers.get('content-disposition') || '';
const match = disposition.match(/filename="?([^"]+)"?/i);
return match ? match[1] : fallbackFilename;
}
async function downloadExportFile(url, fallbackFilename) {
const response = await fetch(url);
const contentType = response.headers.get('content-type') || '';
if (!response.ok || contentType.includes('application/json')) {
let message = 'Export failed. Please try again.';
try {
const json = await response.json();
message = json.message || json.messages?.error || message;
} catch (err) {
// Keep the generic message when the response cannot be parsed.
}
return response.status === 404 ? toastr.warning(message) : toastr.error(message);
}
const blob = await response.blob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = getExportFilename(response, fallbackFilename);
link.click();
URL.revokeObjectURL(link.href);
}
function initActivityExportDateRangePicker() {
const $button = $('#activityExportExcelBtn');
if (!$button.length) return;
if (typeof moment === 'undefined' || typeof $.fn.daterangepicker === 'undefined') {
$button.on('click', () => toastr.error('Date range picker is not available'));
return;
}
$button.daterangepicker({
autoUpdateInput: false,
startDate: moment().startOf('month'),
endDate: moment(),
maxDate: moment(),
opens: 'left',
drops: 'down',
locale: {
format: 'DD-MM-YYYY',
applyLabel: 'Generate Excel',
cancelLabel: 'Cancel'
},
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment()],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
});
$button.on('apply.daterangepicker', function (ev, picker) {
exportActivitiesCsv(
picker.startDate.format('YYYY-MM-DD'),
picker.endDate.format('YYYY-MM-DD')
);
});
}
async function exportActivitiesCsv(fromDate, toDate) {
const selectedMemberIds = getSelectedMemberIds(salesManagerWithHeadIds);
if (selectedMemberIds.length === 0) return toastr.warning('No members available to export');
if (!fromDate || !toDate) return toastr.warning('Please select a date range');
const params = new URLSearchParams({
status: filter === 'all' ? '' : filter,
search: document.getElementById('mainSearch')?.value || '',
limit: 10000,
offset: 0,
assigned_to: selectedMemberIds.join(','),
financial_year: getSelectedFinancialYear()
from_date: fromDate,
to_date: toDate
});
const res = await fetch(`${API}/activities?${params.toString()}`);
const json = await res.json();
const rows = [
['Company', 'Activity Type', 'Status', 'Assigned To', 'Additional Members', 'Scheduled Date', 'Created At', 'Notes'],
...(json.data || []).map(a => [
a.company_name || '',
a.activity_type || '',
capitalize(a.status),
a.assigned_to_name || '',
a.additional_assigned_names || '',
formatIndianDate(a.scheduled_date),
formatIndianDate(a.created_at),
a.notes || a.completion_notes || ''
])
];
downloadCsv(`sales-activities-${getSelectedFinancialYear()}.csv`, rows);
await downloadExportFile(
`${API}/activities/export?${params.toString()}`,
`sales-activities-${fromDate}-to-${toDate}.csv`
);
}
window.addEventListener('load', initActivityExportDateRangePicker);
initSalesToolbarFilters();
fetchActivities();

View File

@ -942,20 +942,27 @@ function openModal(id) {
/* ── Won Leads Table (Table 2) ── */
const wonLeads = opp.won_leads || [];
const wonRows = wonLeads.map(function(l) {
const typeBadgeClass = l.lead_type === 'EB'
// const opportunityType = [
// l.lead_form_type || '',
// l.lead_type || ''
// ].filter(Boolean).join('/').toUpperCase();
const opportunityType = [
l.lead_form_type || '',
].filter(Boolean).join('').toUpperCase();
const typeBadgeClass = l.lead_form_type === 'EB'
? 'style="background:#eff6ff;color:#2563eb;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"'
: 'style="background:#f0fdf4;color:#16a34a;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"';
const canRedirect = l.lead_type_id && l.actual_lead_id && l.opportunities_id;
const canRedirect = l.lead_form_type_id && l.actual_lead_id && l.opportunities_id;
const rowAttrs = canRedirect
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
+ JSON.stringify(String(l.lead_type_id)) + ','
+ JSON.stringify(String(l.lead_form_type_id)) + ','
+ JSON.stringify(String(l.actual_lead_id)) + ','
+ JSON.stringify(String(l.opportunities_id)) + ')\''
: '';
return '<tr' + rowAttrs + '>'
+ '<td style="font-weight:700;color:#0f172a;">' + (l.company || '—') + '</td>'
+ '<td><span ' + typeBadgeClass + '>' + (l.lead_type || '—') + '</span></td>'
+ '<td><span ' + typeBadgeClass + '>' + (opportunityType || '—') + '</span></td>'
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
+ '</tr>';
}).join('');

View File

@ -200,7 +200,7 @@
<option value="<?= esc($year) ?>" <?= (($current_fin_year ?? '') === $year) ? 'selected' : '' ?>><?= esc($year) ?></option>
<?php endforeach; ?>
</select>
<button class="toolbar-icon-btn export-btn" onclick="exportLeadsCsv()" title="Export" aria-label="Export leads">
<button class="toolbar-icon-btn export-btn" id="leadExportExcelBtn" title="Export Excel" aria-label="Export leads">
<i class="mdi mdi-file-excel"></i>
</button>
<button class="toolbar-icon-btn add-btn" onclick="openModal('addLeadModal')" title="Add Lead" aria-label="Add lead">
@ -672,31 +672,64 @@
/* Reset Bootstrap's default position: absolute if present */
position: static;
}
.contact-person-entry-row .form-control-sm {
width: 100% !important;
height: 31px;
}
.contact-person-entry-row {
align-items: flex-start !important;
}
.contact-person-entry-row .contact-field-col {
flex: 0 0 25%;
max-width: 25%;
}
.contact-person-entry-row .form-label {
min-height: 18px;
margin-bottom: 4px;
}
@media (max-width: 767.98px) {
.contact-person-entry-row .contact-field-col {
flex: 0 0 100%;
max-width: 100%;
}
}
.contact-action-buttons {
display: flex;
align-items: center;
gap: 6px;
height: 31px;
}
.contact-action-buttons .btn {
width: 36px;
min-width: 36px;
padding-left: 0;
padding-right: 0;
}
</style>
<div class="col-12 ml-1">
<label class="form-label fw-bold small ml-1">Contact Persons</label>
<input type="hidden" id="editing_contact_id" value="">
<div class="row g-0-5 align-items-end ml-1">
<div class="col-md-3 col-12 mb-2 mb-md-0">
<div class="row g-0-5 align-items-end ml-1 contact-person-entry-row">
<div class="col-md-3 col-12 mb-2 mb-md-0 contact-field-col">
<label class="form-label">Person Name <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-sm" id="contact_name" placeholder="Person Name" oninput="this.value = this.value.replace(/[^A-Za-z\s]/g, ''); validateContactField('name')">
<small id="contact_name_error" class="text-danger d-block mt-1 field-error"></small>
</div>
<div class="col-md-3 col-6">
<div class="col-md-3 col-6 mb-2 mb-md-0 contact-field-col">
<label class="form-label">Mobile <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-sm" id="contact_mobile" placeholder="Mobile" maxlength="10" oninput="this.value = this.value.replace(/[^0-9]/g, '').substring(0, 10); validateContactField('mobile')">
<small id="contact_mobile_error" class="text-danger d-block mt-1 field-error"></small>
</div>
<div class="col-md-3 col-6">
<div class="col-md-3 col-6 mb-2 mb-md-0 contact-field-col">
<label class="form-label">Designation </label>
<input type="text" class="form-control form-control-sm" id="contact_designation" placeholder="Designation" oninput="this.value = this.value.replace(/[^A-Za-z\s]/g, '')">
</div>
<div class="col-md-1 col-3">
<div class="col-md-1 col-3 mb-2 mb-md-0">
<label class="form-label"> </label>
<div class="primary-container">
<label for="contact_is_primary" class="primary-label">Primary</label>
@ -710,13 +743,13 @@
Save
</button>
</div> -->
<div class="col-md-2 col-6">
<div class="col-md-2 col-6 mb-2 mb-md-0">
<label class="form-label"> </label>
<div style="display:flex; gap:2px;">
<button type="button" id="btnSaveContact" class="btn btn-sm btn-info text-white" style="width:42%" title="Save">
<div class="contact-action-buttons">
<button type="button" id="btnSaveContact" class="btn btn-sm btn-info text-white" title="Save">
</button>
<button type="button" id="btnClearContact" class="btn btn-sm btn-danger text-white" style="width:42%" title="Clear">
<button type="button" id="btnClearContact" class="btn btn-sm btn-danger text-white" title="Clear">
</button>
</div>
@ -1261,8 +1294,9 @@ async function viewDetail(id) {
function renderCard(opps) {
const opp_cont = document.getElementById('opportunitiesContainer');
opp_cont.innerHTML = opps.length ? opps.map(o => {
let lead_type = o.lead_type == 1 ? 'EB' : 'Non-EB';
opp_cont.innerHTML = opps.length ? opps.map(o => {
const opportunityFormType = Number(o.lead_form_type || 1);
let lead_form_type = opportunityFormType === 1 ? 'EB' : 'Non-EB';
let status = o.status?.toLowerCase();
let statusClass = {
won: 'status-text-won',
@ -1287,7 +1321,7 @@ function renderCard(opps) {
<div class="opportunities-list" id="leadOpportunitiesList">
<div class="opportunity-card" style="bottom:2px;">
<div class="opportunity-header">
<div class="opportunity-title">${lead_type}</div>
<div class="opportunity-title">${lead_form_type}</div>
</div>
<div class="opportunity-details">
<div class="opportunity-detail-item">
@ -2191,12 +2225,12 @@ function submitToRedirectwithactualLeadIDUrl(){
let actual_lead_id = document.getElementById('opp_lead_id').value;
let lead_type = $('input[name="lead_form_type"]:checked').val();
let lead_form_type = $('input[name="lead_form_type"]:checked').val();
// URL: /type / actual_lead_id
let url = '<?=base_url('/util/getLeadNonEB/')?>' + lead_type + '/' + actual_lead_id ;
let url = '<?=base_url('/util/getLeadNonEB/')?>' + lead_form_type + '/' + actual_lead_id ;
console.log('lead_type ', lead_type);
console.log('lead_form_type ', lead_form_type);
console.log('opp_lead_id ', actual_lead_id);
console.log('url ', url);
@ -2245,7 +2279,8 @@ document.getElementById('do_follow').addEventListener('change', function () {
});
async function openEditLeadModal(id) {
async function openEditLeadModal(id, preservedFields = {}) {
preservedFields = preservedFields || {};
// 1. Reset UI State
document.getElementById('hidden_lead_id').value = id;
@ -2290,9 +2325,12 @@ async function openEditLeadModal(id) {
form.querySelector('[name="company_name"]').value = lead.company_name || '';
form.querySelector('[name="email"]').value = lead.email || '';
form.querySelector('[name="phone"]').value = lead.phone || '';
form.querySelector('[name="address"]').value = lead.address || '';
form.querySelector('[name="website"]').value = lead.website || '';
form.querySelector('[name="gst_number"]').value = lead.gst_number || '';
form.querySelector('[name="address"]').value =
Object.prototype.hasOwnProperty.call(preservedFields, 'address') ? preservedFields.address : (lead.address || '');
form.querySelector('[name="website"]').value =
Object.prototype.hasOwnProperty.call(preservedFields, 'website') ? preservedFields.website : (lead.website || '');
form.querySelector('[name="gst_number"]').value =
Object.prototype.hasOwnProperty.call(preservedFields, 'gst_number') ? preservedFields.gst_number : (lead.gst_number || '');
// form.querySelector('[name="status"]').value = lead.status || 'New';
form.querySelector('[name="assigned_to"]').value = lead.assigned_to || '';
form.querySelector('[name="assigned_to"]').value = lead.assigned_to || '';
@ -2384,15 +2422,27 @@ document.getElementById('contactPersonsList').onclick = async (e) => {
if (!confirm('Are you sure you want to remove this contact?')) return;
try {
const preservedFields = getEditLeadPreservedFields();
const res = await fetch(`${API}/contacts/${contactId}`, { method: 'DELETE' });
if (res.ok) {
toastr.success('Removed successfully');
openEditLeadModal(document.getElementById('hidden_lead_id').value); // Refresh
openEditLeadModal(document.getElementById('hidden_lead_id').value, preservedFields); // Refresh
}
} catch (err) { console.error(err); }
}
};
function getEditLeadPreservedFields() {
const form = document.getElementById('editLeadForm');
if (!form) return {};
return {
address: form.querySelector('[name="address"]')?.value || '',
website: form.querySelector('[name="website"]')?.value || '',
gst_number: form.querySelector('[name="gst_number"]')?.value || ''
};
}
// --- 2. SAVE / UPDATE LOGIC ---
const btnSaveContact = document.getElementById('btnSaveContact');
if (btnSaveContact) {
@ -2417,6 +2467,7 @@ if (btnSaveContact) {
const method = editingId ? 'PUT' : 'POST';
try {
const preservedFields = getEditLeadPreservedFields();
const res = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
@ -2430,7 +2481,7 @@ if (btnSaveContact) {
resetContactForm();
// Refresh List
openEditLeadModal(leadId);
openEditLeadModal(leadId, preservedFields);
} else {
const err = await res.json();
if (err.messages?.name) setContactFieldError('name', err.messages.name);
@ -2537,35 +2588,95 @@ function downloadCsv(filename, rows) {
URL.revokeObjectURL(link.href);
}
async function exportLeadsCsv() {
function getExportFilename(response, fallbackFilename) {
const disposition = response.headers.get('content-disposition') || '';
const match = disposition.match(/filename="?([^"]+)"?/i);
return match ? match[1] : fallbackFilename;
}
async function downloadExportFile(url, fallbackFilename) {
const response = await fetch(url);
const contentType = response.headers.get('content-type') || '';
if (!response.ok || contentType.includes('application/json')) {
let message = 'Export failed. Please try again.';
try {
const json = await response.json();
message = json.message || json.messages?.error || message;
} catch (err) {
// Keep the generic message when the response cannot be parsed.
}
return response.status === 404 ? toastr.warning(message) : toastr.error(message);
}
const blob = await response.blob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = getExportFilename(response, fallbackFilename);
link.click();
URL.revokeObjectURL(link.href);
}
function initLeadExportDateRangePicker() {
const $button = $('#leadExportExcelBtn');
if (!$button.length) return;
if (typeof moment === 'undefined' || typeof $.fn.daterangepicker === 'undefined') {
$button.on('click', () => toastr.error('Date range picker is not available'));
return;
}
$button.daterangepicker({
autoUpdateInput: false,
startDate: moment().startOf('month'),
endDate: moment(),
maxDate: moment(),
opens: 'left',
drops: 'down',
locale: {
format: 'DD-MM-YYYY',
applyLabel: 'Generate Excel',
cancelLabel: 'Cancel'
},
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment()],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
});
$button.on('apply.daterangepicker', function (ev, picker) {
exportLeadsCsv(
picker.startDate.format('YYYY-MM-DD'),
picker.endDate.format('YYYY-MM-DD')
);
});
}
async function exportLeadsCsv(fromDate, toDate) {
const selectedMemberIds = getSelectedMemberIds(salesManagerHeadIds);
if (selectedMemberIds.length === 0) return toastr.warning('No members available to export');
if (!fromDate || !toDate) return toastr.warning('Please select a date range');
const params = new URLSearchParams({
status: filter === 'all' ? '' : filter,
search: document.getElementById('mainSearch')?.value || '',
limit: 10000,
offset: 0,
assigned_to: selectedMemberIds.join(','),
financial_year: getSelectedFinancialYear()
from_date: fromDate,
to_date: toDate
});
const res = await fetch(`${API}/leads?${params.toString()}`);
const json = await res.json();
const rows = [
['Company', 'Email', 'Phone', 'Status', 'Assigned To', 'Created At'],
...(json.data || []).map(l => [
l.company_name || '',
l.email || '',
l.phone || '',
capitalizeStatus(l.status),
l.assigned_to_name || '',
formatIndianDate(l.created_at)
])
];
downloadCsv(`sales-leads-${getSelectedFinancialYear()}.csv`, rows);
await downloadExportFile(
`${API}/leads/export?${params.toString()}`,
`sales-leads-${fromDate}-to-${toDate}.csv`
);
}
window.addEventListener('load', initLeadExportDateRangePicker);
initSalesToolbarFilters();
fetchLeads().then(() => {
const leadId = new URLSearchParams(window.location.search).get('lead_id');

View File

@ -884,13 +884,13 @@
</div>
<div class="form-group col-md-3">
<label for="policy_start_date">Policy Start Date</label>
<input type="text" class="form-control" id="policy_start_date" name="policy_start_date" placeholder="DD/MM/YYY" value="<?= isset($lead_data['policy_start_date']) ? date('d-m-Y', strtotime($lead_data['policy_start_date'])) : "" ?>">
<label for="policy_start_date">Policy Start Date <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="policy_start_date" name="policy_start_date" placeholder="DD/MM/YYY" value="<?= isset($lead_data['policy_start_date']) ? date('d-m-Y', strtotime($lead_data['policy_start_date'])) : "" ?>" required>
</div>
<div class="form-group col-md-3">
<label for="policy_end_date">Policy End Date</label>
<input type="text" class="form-control" id="policy_end_date" name="policy_end_date" placeholder="DD/MM/YYY" value="<?= isset($lead_data['policy_end_date']) ? date('d-m-Y', strtotime($lead_data['policy_end_date'])) : "" ?>">
<label for="policy_end_date">Policy End Date <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="policy_end_date" name="policy_end_date" placeholder="DD/MM/YYY" value="<?= isset($lead_data['policy_end_date']) ? date('d-m-Y', strtotime($lead_data['policy_end_date'])) : "" ?>" required>
</div>
<div class="form-group col-md-3">
@ -4429,6 +4429,18 @@ function constructURL_ForPlacementMailSend(return_type = false) {
let acm_pk = $('#acm_id option:selected').data('id');
let is_installment = $("#is_installment_switch").is(":checked") ? 1 : 0;
if (!policy_start_date) {
toastr.warning('Policy Start Date is required.', 'Warning');
$('#policy_start_date').focus();
return false;
}
if (!policy_end_date) {
toastr.warning('Policy End Date is required.', 'Warning');
$('#policy_end_date').focus();
return false;
}
// Process `to` field
to = to.split(',').map(email => email.trim());
// cc = cc.map(Number); // or split if it's a string: `cc.split(',').map(email => email.trim())`
@ -7552,6 +7564,9 @@ function appendMultiFileData(data) {
// Data to send in the AJAX request
let requestData = constructURL_ForPlacementMailSend(true);
if (requestData === false) {
return false;
}
console.log('requestData', requestData);
// Show loader