diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index 0af6898a..beadfa73 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -471,7 +471,7 @@ class ClientController extends AdminController $headerData['page_name'] = 'Clients'; // Both Browser Tab name And Page name are same. // $data['clientList'] = $this->clientModel->getCreatedByUserName(1); // passing client_type $data['client_rm'] = $this->clientRMModel->getAllClientRM(); - $data['lead_data'] = $this->leadsModel->getLeadForInsertClientList(); + $data['lead_data'] = $this->leadsModel->getLeadForInsertClientList([1, 3]); $rawList = $this->clientModel->getCreatedByUserName(1); $clientRM = $data['client_rm']; @@ -1037,7 +1037,7 @@ class ClientController extends AdminController $editData['client_branch'] = $this->clientBranchModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_relation'] = $this->clientRMModel->where(['client_id' => $id, 'is_active' => 1])->findAll(); $editData['client_branch']['role'] = get_role_id(); - $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList(null, $id); + $editData['lead_data'] = $this->leadsModel->getLeadForInsertClientList([2], $id); $clientPoliceData = $this->clientPolicyModel->getClientPolicyByClientId($id); foreach ($clientPoliceData as $key => $value) { diff --git a/app/Controllers/EmployeeServiceController.php b/app/Controllers/EmployeeServiceController.php index ecf2d49a..6db0be9d 100755 --- a/app/Controllers/EmployeeServiceController.php +++ b/app/Controllers/EmployeeServiceController.php @@ -19,6 +19,7 @@ use App\Models\MessageModel; use App\Models\ClientBranchModel; use App\Models\NotificationModel; use App\Models\InsurerModel; +use App\Models\TicketMasterModel; use App\Helpers\sendMailNotification; @@ -46,6 +47,7 @@ class EmployeeServiceController extends AdminController protected $clientBranchModel; protected $notificationModel; protected $insurerModel; + protected $ticketMasterModel; protected $general_relationships = [ 'self' => [ @@ -355,10 +357,13 @@ class EmployeeServiceController extends AdminController 'col_idx' => 6, 'col_cell_name' => 'G', 'col_name' => 'Claim status', - 'is_mandatory' => ['D'], + 'is_mandatory' => false, + // 'is_mandatory' => ['D'], + 'is_column_optional' => true, 'data_type' => 'str', 'format' => null, - 'allowed_values' => [0,1] + 'allowed_values' => null + // 'allowed_values' => [0,1] ] ]; @@ -779,6 +784,7 @@ class EmployeeServiceController extends AdminController $this->clientBranchModel = new ClientBranchModel(); $this->notificationModel = new NotificationModel(); $this->insurerModel = new InsurerModel(); + $this->ticketMasterModel = new TicketMasterModel(); } public function excelFileFormatValidation($params) @@ -842,8 +848,22 @@ class EmployeeServiceController extends AdminController $excel_columns = ($excel_data[0]); // var_dump($total_defined_columns);die(); $excel_columns_count = count($excel_columns); - if($total_defined_columns != $excel_columns_count) - { + $optional_column_count = 0; + foreach ($columns_to_check as $column_config) { + if (!empty($column_config['is_column_optional'])) { + $optional_column_count++; + } + } + $min_required_columns = $total_defined_columns - $optional_column_count; + + if ($optional_column_count > 0) { + if ($excel_columns_count < $min_required_columns || $excel_columns_count > $total_defined_columns) { + $message = "File columns count mismatch. Expected between $min_required_columns and $total_defined_columns and received - $excel_columns_count"; + $this->myLogger->logme('error',($message . ' for file id ' . $file_id)); + $this->fileModel->where('id', $file_id)->set(['status' => 'failed','reason' => json_encode(['error_summary' => array_count_values([5]),'error_data' => $message])])->update(); + return array('error_summary' => [5], 'error_data' => $message); + } + } elseif ($total_defined_columns != $excel_columns_count) { //columns count mismatch $message = "File columns count mismatch. Expected - $total_defined_columns and received - $excel_columns_count"; // echo $message; @@ -1532,38 +1552,52 @@ class EmployeeServiceController extends AdminController return (count($employee_data_group_by_family)); } + /** + * Returns claim_status for deletion: 1 if an active claim exists in ticket_master for the employee and policy, else 0. + */ + protected function getEmployeeClaimStatusForDeletion(int $emp_id, int $client_policy_id): int + { + $claim = $this->ticketMasterModel + ->where('emp_id', $emp_id) + ->where('client_policy_id', $client_policy_id) + ->where('is_active', 1) + ->first(); + + return $claim ? 1 : 0; + } + //deletion of emp public function employeeDisembark($params) { - helper('excel_util_helper'); + helper('excel_util_helper'); //get file name $file_id = $params['file_id']; $file = $this->fileModel->find((int)$file_id); // dd($file); - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $sheet = $spreadsheet->getActiveSheet(); - + $highestRowAndColumn = $sheet->getHighestRowAndColumn(); $allowedHighestColumn = end($this->deletion_excel_columns); $excel_data = $sheet->rangeToArray('A1:' . $allowedHighestColumn['col_cell_name'] . $highestRowAndColumn['row']); // $excel_data = $sheet->rangeToArray('A1:' . $highestRowAndColumn['column'] . $highestRowAndColumn['row']); - + // dd($excel_data); unset($excel_data[0]); // kint::dump($excel_data); $endorsement_data = []; - $policy_terms = $this->clientPolicyModel->getPolicyDetails($file['client_id'],$file['policy_id']); + $policy_terms = $this->clientPolicyModel->getPolicyDetails($file['client_id'], $file['policy_id']); $insurer = new InsurerModel(); $insurer = ($insurer->find((int)$policy_terms[0]->insurer_id)); // kint::dump($insurer); //make closure funciton which is going to use only by this method - $endorsement = function($data,$file,$row) use ($insurer){ + $endorsement = function ($data, $file, $row) use ($insurer) { $group_key = rand(100000, 999999); - $row[4] = change_date_format($row[4],'d-M-Y','Y-m-d');// date of exit from excel + $row[4] = change_date_format($row[4], 'd-M-Y', 'Y-m-d'); // date of exit from excel // Kint::dump($row[4]); // check if insurer configured with add one day for deletion // if($insurer['deletion_add_day'] == true) @@ -1572,115 +1606,103 @@ class EmployeeServiceController extends AdminController // } // dd($row[4]); //for emp table - // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'emp_status','old_value' => $data['emp_status'],'new_value' => 'deactivate','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); + // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'emp_status','old_value' => $data['emp_status'],'new_value' => 'deactivate','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); // dd( $this->empEndorsementModel->getLastQuery()); // $this->empEndorsementModel->save(['pk' => (int)$data['emp_id'],'emp_code' => $data['emp_code'],'table_name' => 'employees','actions' => 'd','name' => $data['name'],'field_name' => 'change_event','old_value' => $data['change_event'],'new_value' => 'deletion','created_by' => $file['created_by'],'remarks' => 'general deletion']); // for employee policy table - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'date_of_exit','old_value' => $data['date_of_exit'],'new_value' => $row[4],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'reason_for_exit','old_value' => $data['reason_for_exit'],'new_value' => $row[5],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'status','old_value' => $data['status'],'new_value' => 'inactive','created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); - $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'],'emp_code' => $data['emp_code'],'table_name' => 'employee_polices','actions' => 'd','name' => $data['name'],'field_name' => 'claim_status','old_value' => $data['claim_status'],'new_value' => $row[6],'created_by' => $file['created_by'],'remarks' => 'general deletion','group_key' => $group_key,'file_id' => $file['id'],'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'date_of_exit', 'old_value' => $data['date_of_exit'], 'new_value' => $row[4], 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'reason_for_exit', 'old_value' => $data['reason_for_exit'], 'new_value' => $row[5], 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'status', 'old_value' => $data['status'], 'new_value' => 'inactive', 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); + $claim_status = $this->getEmployeeClaimStatusForDeletion((int) $data['emp_id'], (int) $file['policy_id']); + $this->empEndorsementModel->save(['pk' => (int)$data['emp_policy_id'], 'emp_code' => $data['emp_code'], 'table_name' => 'employee_polices', 'actions' => 'd', 'name' => $data['name'], 'field_name' => 'claim_status', 'old_value' => $data['claim_status'], 'new_value' => $claim_status, 'created_by' => $file['created_by'], 'remarks' => 'general deletion', 'group_key' => $group_key, 'file_id' => $file['id'], 'status' => 'pending']); }; //iterate each row - foreach ($excel_data as $col_key => $row) - { - if(check_row_is_empty_or_null($row)) - { - break; + foreach ($excel_data as $col_key => $row) { + if (check_row_is_empty_or_null($row)) { + break; } // echo '
START- ' . $row[2]; $employee = $this->employeeModel - ->select('employees.*, employee_polices.id as emp_policy_pk') - ->join('employee_polices', 'employees.id = employee_polices.employee_id') - ->where('employees.emp_code', $row[1]) - ->where('employees.name',$row[2]) - ->where('employees.client_id',$file['client_id']) - ->where('employees.client_branch_id',$file['client_branch_id']) - ->where('employee_polices.client_policy_id',$file['policy_id']) - ->where('employees.emp_status','active') - ->where('employees.is_active', 1) - ->where('employee_polices.status','active') - ->where('employee_polices.is_active', 1) - ->first(); + ->select('employees.*, employee_polices.id as emp_policy_pk') + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->where('employees.emp_code', $row[1]) + ->where('employees.name', $row[2]) + ->where('employees.client_id', $file['client_id']) + ->where('employees.client_branch_id', $file['client_branch_id']) + ->where('employee_polices.client_policy_id', $file['policy_id']) + ->where('employees.emp_status', 'active') + ->where('employees.is_active', 1) + ->where('employee_polices.status', 'active') + ->where('employee_polices.is_active', 1) + ->first(); // $employee = $this->employeeModel->where('emp_code', $row[1])->where('name',$row[2])->where('client_id',$file['client_id'])->first(); // dd($employee); // kint::dump($employee); - if(is_array($employee) && count($employee)) - { + if (is_array($employee) && count($employee)) { // dd($employee); - $existing_endorsements = $this->empEndorsementModel->where('actions','d') - ->where('table_name','employee_polices') - ->where('endorsement_id is null') - ->where('emp_code',$employee['emp_code']) - ->where('name',$employee['name']) - ->where('pk',$employee['emp_policy_pk']) - ->where('field_name','status') - ->where('status !=','truncated') - ->where('is_active', 1) - ->findAll(); + $existing_endorsements = $this->empEndorsementModel->where('actions', 'd') + ->where('table_name', 'employee_polices') + ->where('endorsement_id is null') + ->where('emp_code', $employee['emp_code']) + ->where('name', $employee['name']) + ->where('pk', $employee['emp_policy_pk']) + ->where('field_name', 'status') + ->where('status !=', 'truncated') + ->where('is_active', 1) + ->findAll(); - // dd($existing_endorsements); - if(!count($existing_endorsements)) - { - // dd($employee); - if(strtolower($employee['relationship']) != 'self') - { - - if(!in_array($employee['id'],$endorsement_data))//make entry in endorsement firsttime only with checking that PK of emp exisintg in variable $endorsement_data - { - $employee_policy = $this->employeePolicyModel - ->where('employee_id',$employee['id']) - ->where('client_policy_id',$file['policy_id']) - ->where('status','active') - ->where('is_active', 1) - ->first(); + // dd($existing_endorsements); + if (!count($existing_endorsements)) { + // dd($employee); + if (strtolower($employee['relationship']) != 'self') { - $data = ['emp_id' => $employee['id'],'name' => $employee['name'],'emp_status' => $employee['emp_status'],'emp_policy_id' => $employee_policy['id'],'emp_code' => $employee['emp_code'],'change_event' => $employee['change_event'],'date_of_exit' => $employee_policy['date_of_exit'],'reason_for_exit' => $employee_policy['reason_for_exit'],'status' => $employee_policy['status'],'claim_status' => $employee_policy['claim_status']]; - $endorsement($data,$file,$row); - $endorsement_data[] = $employee['id']; - } + if (!in_array($employee['id'], $endorsement_data)) //make entry in endorsement firsttime only with checking that PK of emp exisintg in variable $endorsement_data + { + $employee_policy = $this->employeePolicyModel + ->where('employee_id', $employee['id']) + ->where('client_policy_id', $file['policy_id']) + ->where('status', 'active') + ->where('is_active', 1) + ->first(); + + $data = ['emp_id' => $employee['id'], 'name' => $employee['name'], 'emp_status' => $employee['emp_status'], 'emp_policy_id' => $employee_policy['id'], 'emp_code' => $employee['emp_code'], 'change_event' => $employee['change_event'], 'date_of_exit' => $employee_policy['date_of_exit'], 'reason_for_exit' => $employee_policy['reason_for_exit'], 'status' => $employee_policy['status'], 'claim_status' => $employee_policy['claim_status']]; + $endorsement($data, $file, $row); + $endorsement_data[] = $employee['id']; + } + } else { + + $family = $this->employeeModel->getEmpFamilybyEmpCode(emp_code: $row[1], client_id: $file['client_id'], client_policy_id: $file['policy_id'], emp_status: ['active'], policy_status: ['active']); + // Kint::dump($family); + foreach ($family as $key => $emp) { + + if (!in_array($emp['emp_id'], $endorsement_data)) { + $endorsement($emp, $file, $row); + + $endorsement_data[] = $emp['emp_id']; } - else - { - - $family = $this->employeeModel->getEmpFamilybyEmpCode(emp_code: $row[1],client_id: $file['client_id'],client_policy_id: $file ['policy_id'],emp_status: ['active'],policy_status: ['active']); - // Kint::dump($family); - foreach ($family as $key => $emp) { - - if(!in_array($emp['emp_id'],$endorsement_data)) - { - $endorsement($emp,$file,$row); - - $endorsement_data[] = $emp['emp_id']; - - } - } - - } - }else{ - $this->myLogger->logme("error",'Existing endorsement pending for this employee : emp_code : {emp_code} - emp_name : {name}',['emp_code' => $row[1],'name' => $row[2]]); + } } - } - else - { - $this->myLogger->logme("error",'{emp_code} - {name} not found',['emp_code' => $row[1],'name' => $row[2]]); + } else { + $this->myLogger->logme("error", 'Existing endorsement pending for this employee : emp_code : {emp_code} - emp_name : {name}', ['emp_code' => $row[1], 'name' => $row[2]]); + } + } else { + $this->myLogger->logme("error", '{emp_code} - {name} not found', ['emp_code' => $row[1], 'name' => $row[2]]); } // print_r($endorsement_data); } - - $this->fileModel->where('id', $file_id)->set(['status' => 'success','reason' => ''])->update(); - $this->myLogger->logme("error",'{file_id} uploaded success',['file_id' => $file_id]); + + $this->fileModel->where('id', $file_id)->set(['status' => 'success', 'reason' => ''])->update(); + $this->myLogger->logme("error", '{file_id} uploaded success', ['file_id' => $file_id]); //set success msg to pull notifications - $this->setPullNotification($this->getFileMetaDataByFileId($file_id,'success')); - return $endorsement_data; - + $this->setPullNotification($this->getFileMetaDataByFileId($file_id, 'success')); + return $endorsement_data; } //correction of emp diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php index d6cd0df5..41f4252e 100644 --- a/app/Controllers/LeadsController.php +++ b/app/Controllers/LeadsController.php @@ -2895,6 +2895,11 @@ class LeadsController extends BaseController $filename = "{$rfq_data['client_name']}_{$rfq_data['policy_type']}_{$string}_" . $policy_year . '_' . '.xlsx'; } + $filenameBase = pathinfo($filename, PATHINFO_FILENAME); + $filenameBase = preg_replace('/[^a-zA-Z0-9\-_ ]/u', '', $filenameBase); + $filenameBase = preg_replace('/\s+/', ' ', trim($filenameBase)); + $filename = ($filenameBase !== '' ? $filenameBase : 'export') . '.xlsx'; + //claim history new sheet; if ($claim_history == 1 && !empty($rfq_data['fin_years_claims'])) { diff --git a/app/Controllers/PolicyTransactionController.php b/app/Controllers/PolicyTransactionController.php index a381b23f..8c2436d7 100644 --- a/app/Controllers/PolicyTransactionController.php +++ b/app/Controllers/PolicyTransactionController.php @@ -1319,6 +1319,14 @@ class PolicyTransactionController extends BaseController $old_pt_co_share_data = $this->PTCOShareDetailsModel->where('is_active', 1)->where("id", $id)->first(); if ($this->policyTransactionModel->update($id, $data)) { + if (!empty($old_pt_data) && isset($data['policy_no'])) { + $this->updateRelatedPolicyTransactionPolicyNo( + (int) $id, + (string) ($old_pt_data['policy_no'] ?? ''), + (string) $data['policy_no'] + ); + } + $this->insertTransactionStatus($id, $data, 1); $emp_data = $this->processInsertIndividualMemberInEmpTable($data); @@ -1376,6 +1384,59 @@ class PolicyTransactionController extends BaseController return $this->respondError("Failed to update policy transaction"); } + /** + * Sync policy_no on related policy_transaction rows when inception policy number changes. + */ + private function updateRelatedPolicyTransactionPolicyNo( + int $inceptionPtId, + string $oldPolicyNo, + string $newPolicyNo + ): array { + $oldPolicyNo = trim($oldPolicyNo); + $newPolicyNo = trim($newPolicyNo); + + if ($oldPolicyNo === '' || $newPolicyNo === '' || $oldPolicyNo === $newPolicyNo) { + return ['status' => false, 'message' => 'No policy number change detected', 'updated_ids' => []]; + } + + $relatedRecords = $this->policyTransactionModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->findAll(); + + if (empty($relatedRecords)) { + return ['status' => true, 'message' => 'No related policy transactions found', 'updated_ids' => []]; + } + + $updatedIds = []; + $updatedBy = get_session_userid(); + + foreach ($relatedRecords as $record) { + $this->policyTransactionModel + ->where('id', $record['id']) + ->set(['policy_no' => $newPolicyNo, 'updated_by' => $updatedBy]) + ->update(); + + if ($this->policyTransactionModel->affectedRows() > 0) { + $updatedIds[] = $record['id']; + } + } + + $this->myLogger->logme('error', 'Updated policy_no on related policy_transaction records: ' . json_encode([ + 'inception_pt_id' => $inceptionPtId, + 'old_policy_no' => $oldPolicyNo, + 'new_policy_no' => $newPolicyNo, + 'updated_ids' => $updatedIds, + ])); + + return [ + 'status' => true, + 'message' => 'Related policy transaction policy numbers updated', + 'updated_ids' => $updatedIds, + ]; + } + private function insertOrUpdateCoShareDetails($data, $pt_id) { // Prepare data for insertion and updating diff --git a/app/Helpers/excel_util_helper.php b/app/Helpers/excel_util_helper.php index 5aa68990..5b474732 100755 --- a/app/Helpers/excel_util_helper.php +++ b/app/Helpers/excel_util_helper.php @@ -41,6 +41,10 @@ if (!function_exists('check_columns_name')) { $definedColIdx = $definedCol['col_idx']; $definedColName = $definedCol['col_name']; + if (!empty($definedCol['is_column_optional']) && !array_key_exists($definedColIdx, $excelColumns)) { + continue; + } + if (strtolower(strip_tags(trim($excelColumns[$definedColIdx]))) !== strtolower($definedColName)) { $mismatchedColumns[] = "Column order conflict. Column order no " . ($definedColIdx + 1) . " expected : " . $definedColName . " and received : " . $excelColumns[$definedColIdx] . "
"; } diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php index ef88ba21..ba841687 100644 --- a/app/Models/LeadsModel.php +++ b/app/Models/LeadsModel.php @@ -205,7 +205,7 @@ class LeadsModel extends Model ->groupEnd(); if ($type) { - $query->where('leads.lead_type', $type); + $query->whereIn('leads.lead_type', $type); } if ($client_id) { $query->where('leads.client_id', $client_id); diff --git a/app/Views/view_rfq.php b/app/Views/view_rfq.php index f768f740..c1d4c104 100644 --- a/app/Views/view_rfq.php +++ b/app/Views/view_rfq.php @@ -907,7 +907,7 @@
- "> + ">
@@ -1345,6 +1345,12 @@ var policy_start_date_datePicker = flatpickr("#policy_start_date", { dateFormat: "d/m/Y", allowInput: false, + onChange: function(selectedDates, dateStr, instance) { + var endDate = new Date(selectedDates[0]); + endDate.setFullYear(endDate.getFullYear() + 1); + endDate.setDate(endDate.getDate() - 1); + policy_end_date_datePicker.setDate(endDate); + } }); var policy_end_date_datePicker = flatpickr("#policy_end_date", { @@ -7838,8 +7844,32 @@ function appendMultiFileData(data) { } $('#agreed_percentage').on('input', function () { + let val = this.value.replace(/[^\d.]/g, ''); + const parts = val.split('.'); + if (parts.length > 2) { + val = parts[0] + '.' + parts.slice(1).join(''); + } + if (val !== '' && !val.endsWith('.')) { + let v = parseFloat(val); + if (!isNaN(v)) { + if (v < 0) val = '0'; + else if (v > 100) val = '100'; + } + } + this.value = val; + }); + + $('#agreed_percentage').on('blur', function () { + if (this.value === '' || this.value === '.') { + this.value = ''; + return; + } let v = parseFloat(this.value); - this.value = (v < 0) ? 0 : (v > 100 ? 100 : v); + if (isNaN(v)) { + this.value = ''; + } else { + this.value = (v < 0) ? 0 : (v > 100 ? 100 : v); + } }); diff --git a/tests/smoke_deletion_excel_optional_claim_status.php b/tests/smoke_deletion_excel_optional_claim_status.php new file mode 100644 index 00000000..fb0ea890 --- /dev/null +++ b/tests/smoke_deletion_excel_optional_claim_status.php @@ -0,0 +1,203 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +helper('excel_util_helper'); + +use App\Controllers\EmployeeServiceController; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xls; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function getDeletionExcelColumns(EmployeeServiceController $controller): array +{ + $property = new ReflectionProperty($controller, 'deletion_excel_columns'); + $property->setAccessible(true); + + return $property->getValue($controller); +} + +function deletionColumnCountIsValid(array $columnsToCheck, array $excelHeaderRow): bool +{ + $totalDefinedColumns = count($columnsToCheck); + $excelColumnsCount = count($excelHeaderRow); + $optionalColumnCount = 0; + + foreach ($columnsToCheck as $columnConfig) { + if (!empty($columnConfig['is_column_optional'])) { + $optionalColumnCount++; + } + } + + $minRequiredColumns = $totalDefinedColumns - $optionalColumnCount; + + if ($optionalColumnCount > 0) { + return $excelColumnsCount >= $minRequiredColumns && $excelColumnsCount <= $totalDefinedColumns; + } + + return $excelColumnsCount === $totalDefinedColumns; +} + +function validateDeletionRowClaimStatus(array $columnsToCheck, array $row, string $currentColumnAction = 'D'): array +{ + $result = ['error_summary' => [], 'error_data' => []]; + $keys = array_keys($columnsToCheck); + $rowKey = 2; + + foreach ($row as $colKey => $col) { + if (!isset($keys[$colKey])) { + continue; + } + + $isMandatory = $columnsToCheck[$keys[$colKey]]['is_mandatory']; + $allowedValues = $columnsToCheck[$keys[$colKey]]['allowed_values']; + $columnName = $columnsToCheck[$keys[$colKey]]['col_name']; + $columnIndex = $columnsToCheck[$keys[$colKey]]['col_idx']; + + if (is_bool($isMandatory) && $isMandatory === true && ($col === '' || $col === null)) { + $result['error_summary'][] = 1; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value is mandatory'; + } + + if ( + $currentColumnAction !== null + && is_array($isMandatory) + && in_array(strtoupper(trim($currentColumnAction)), $isMandatory, true) + && ($col === '' || $col === null) + ) { + $result['error_summary'][] = 1; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value is mandatory for this action/event'; + } + + if ( + ($isMandatory === true && isset($allowedValues) && is_array($allowedValues)) + || (is_array($isMandatory) && isset($allowedValues) && is_array($allowedValues)) + ) { + if (!in_array(trim((string) $col), $allowedValues, true)) { + $result['error_summary'][] = 3; + $result['error_data'][$rowKey][$keys[$colKey]]['col_name'] = $columnName; + $result['error_data'][$rowKey][$keys[$colKey]]['col_idx'] = $columnIndex; + $result['error_data'][$rowKey][$keys[$colKey]]['error'][] = 'Value not allowed'; + } + } + } + + return $result; +} + +$controller = new EmployeeServiceController(); +$deletionColumns = getDeletionExcelColumns($controller); +$claimStatusColumn = $deletionColumns['claim_status'] ?? []; +$headerWithClaim = ['S.No', 'EMP ID', 'NAME OF EMP/DEP', 'Change event', 'Date of exit', 'Reason for exit', 'Claim status']; +$headerWithoutClaim = ['S.No', 'EMP ID', 'NAME OF EMP/DEP', 'Change event', 'Date of exit', 'Reason for exit']; +$sampleRowWithEmptyClaim = ['1', 'EMP001', 'Test Employee', 'deletion', '19-May-2026', 'Resigned', '']; +$sampleRowWithoutClaimCol = ['1', 'EMP001', 'Test Employee', 'deletion', '19-May-2026', 'Resigned']; + +ok('claim_status is not mandatory', ($claimStatusColumn['is_mandatory'] ?? null) === false); +ok('claim_status column is optional', !empty($claimStatusColumn['is_column_optional'])); +ok( + 'claim_status has no allowed_values constraint', + !array_key_exists('allowed_values', $claimStatusColumn) || $claimStatusColumn['allowed_values'] === null +); + +ok('7-column header passes column count check', deletionColumnCountIsValid($deletionColumns, $headerWithClaim)); +ok('6-column header passes column count check', deletionColumnCountIsValid($deletionColumns, $headerWithoutClaim)); +ok('5-column header fails column count check', !deletionColumnCountIsValid($deletionColumns, array_slice($headerWithoutClaim, 0, 5))); +ok('8-column header fails column count check', !deletionColumnCountIsValid($deletionColumns, array_merge($headerWithClaim, ['Extra']))); + +$mismatchWithoutClaim = check_columns_name($deletionColumns, $headerWithoutClaim); +ok('header without Claim status has no column-name mismatch', count($mismatchWithoutClaim) === 0); + +$mismatchWithClaim = check_columns_name($deletionColumns, $headerWithClaim); +ok('header with Claim status has no column-name mismatch', count($mismatchWithClaim) === 0); + +$wrongHeader = $headerWithoutClaim; +$wrongHeader[5] = 'Wrong reason column'; +$mismatchWrong = check_columns_name($deletionColumns, $wrongHeader); +ok('wrong required header is still detected', count($mismatchWrong) > 0); + +$rowErrorsEmptyClaim = validateDeletionRowClaimStatus($deletionColumns, $sampleRowWithEmptyClaim); +ok( + 'empty Claim status value does not fail row validation', + count($rowErrorsEmptyClaim['error_summary']) === 0, + 'errors=' . count($rowErrorsEmptyClaim['error_summary']) +); + +$rowErrorsMissingClaimCol = validateDeletionRowClaimStatus($deletionColumns, $sampleRowWithoutClaimCol); +ok( + 'missing Claim status column does not fail row validation', + count($rowErrorsMissingClaimCol['error_summary']) === 0, + 'errors=' . count($rowErrorsMissingClaimCol['error_summary']) +); + +$tempDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR; +$tempFileSixCols = $tempDir . 'smoke_deletion_6cols_' . date('Ymd_His') . '.xls'; +$spreadsheet = new Spreadsheet(); +$sheet = $spreadsheet->getActiveSheet(); + +foreach ($headerWithoutClaim as $idx => $header) { + $sheet->setCellValue(chr(65 + $idx) . '1', $header); +} +foreach ($sampleRowWithoutClaimCol as $idx => $value) { + $sheet->setCellValue(chr(65 + $idx) . '2', $value); +} + +$writer = new Xls($spreadsheet); +$writer->save($tempFileSixCols); + +$loadedSpreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($tempFileSixCols); +$loadedSheet = $loadedSpreadsheet->getActiveSheet(); +$loadedHeader = $loadedSheet->rangeToArray('A1:F1')[0]; +$loadedRow = $loadedSheet->rangeToArray('A2:F2')[0]; + +ok('generated 6-column xls header count is 6', count($loadedHeader) === 6); +ok('generated 6-column xls passes column count rule', deletionColumnCountIsValid($deletionColumns, $loadedHeader)); +ok('generated 6-column xls passes header-name check', count(check_columns_name($deletionColumns, $loadedHeader)) === 0); +ok( + 'generated 6-column xls row has no claim_status validation errors', + count(validateDeletionRowClaimStatus($deletionColumns, $loadedRow)['error_summary']) === 0 +); + +@unlink($tempFileSixCols); + +echo PHP_EOL . implode(PHP_EOL, $results) . PHP_EOL; +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/smoke_employee_claim_status_for_deletion.php b/tests/smoke_employee_claim_status_for_deletion.php new file mode 100644 index 00000000..f634c92b --- /dev/null +++ b/tests/smoke_employee_claim_status_for_deletion.php @@ -0,0 +1,146 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +use App\Controllers\EmployeeServiceController; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function invokeClaimStatus(EmployeeServiceController $controller, int $empId, int $clientPolicyId): int +{ + $method = new ReflectionMethod($controller, 'getEmployeeClaimStatusForDeletion'); + $method->setAccessible(true); + + return (int) $method->invoke($controller, $empId, $clientPolicyId); +} + +$db = db_connect('default'); +$controller = new EmployeeServiceController(); + +$withClaim = $db->query( + 'SELECT emp_id, client_policy_id + FROM ticket_master + WHERE is_active = 1 AND emp_id > 0 AND client_policy_id > 0 + LIMIT 1' +)->getRowArray(); + +$withoutClaim = $db->query( + 'SELECT e.id AS emp_id, ep.client_policy_id + FROM employees e + JOIN employee_polices ep ON ep.employee_id = e.id + WHERE e.is_active = 1 + AND ep.is_active = 1 + AND e.id > 0 + AND ep.client_policy_id > 0 + AND NOT EXISTS ( + SELECT 1 + FROM ticket_master tm + WHERE tm.emp_id = e.id + AND tm.client_policy_id = ep.client_policy_id + AND tm.is_active = 1 + ) + LIMIT 1' +)->getRowArray(); + +$wrongPolicy = $db->query( + 'SELECT id FROM client_policy WHERE id > 0 ORDER BY id DESC LIMIT 1' +)->getRowArray(); + +ok('fixture with claim found', is_array($withClaim) && ! empty($withClaim)); +ok('fixture without claim found', is_array($withoutClaim) && ! empty($withoutClaim)); + +if ($withClaim) { + $empId = (int) $withClaim['emp_id']; + $policyId = (int) $withClaim['client_policy_id']; + $result = invokeClaimStatus($controller, $empId, $policyId); + + ok( + 'returns 1 when active claim exists for emp_id + client_policy_id', + $result === 1, + "emp_id={$empId}, client_policy_id={$policyId}, got={$result}" + ); + + if (isset($argv[1], $argv[2])) { + $manualEmp = (int) $argv[1]; + $manualPolicy = (int) $argv[2]; + $manualResult = invokeClaimStatus($controller, $manualEmp, $manualPolicy); + $expected = $db->table('ticket_master') + ->where('emp_id', $manualEmp) + ->where('client_policy_id', $manualPolicy) + ->where('is_active', 1) + ->countAllResults() > 0 ? 1 : 0; + + ok( + 'manual args match ticket_master lookup', + $manualResult === $expected, + "emp_id={$manualEmp}, client_policy_id={$manualPolicy}, got={$manualResult}, expected={$expected}" + ); + } +} + +if ($withClaim && $wrongPolicy) { + $empId = (int) $withClaim['emp_id']; + $wrongPolicyId = (int) $wrongPolicy['id']; + + if ($wrongPolicyId !== (int) $withClaim['client_policy_id']) { + $result = invokeClaimStatus($controller, $empId, $wrongPolicyId); + ok( + 'returns 0 when emp has claim on a different client_policy_id', + $result === 0, + "emp_id={$empId}, client_policy_id={$wrongPolicyId}, got={$result}" + ); + } +} + +if ($withoutClaim) { + $empId = (int) $withoutClaim['emp_id']; + $policyId = (int) $withoutClaim['client_policy_id']; + $result = invokeClaimStatus($controller, $empId, $policyId); + + ok( + 'returns 0 when no active claim exists for emp_id + client_policy_id', + $result === 0, + "emp_id={$empId}, client_policy_id={$policyId}, got={$result}" + ); +} + +echo PHP_EOL . implode(PHP_EOL, $results) . PHP_EOL; +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/smoke_inception_policy_no_sync.php b/tests/smoke_inception_policy_no_sync.php new file mode 100644 index 00000000..f08b8ed8 --- /dev/null +++ b/tests/smoke_inception_policy_no_sync.php @@ -0,0 +1,152 @@ +systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; +require_once SYSTEMPATH . 'Config/DotEnv.php'; +(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); + +defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development')); + +$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php'; +if (is_file($boot)) { + require_once $boot; +} + +use App\Controllers\PolicyTransactionController; +use App\Models\PolicyTransactionModel; + +$pass = 0; +$fail = 0; +$results = []; + +function ok(string $label, bool $cond, string $detail = ''): void +{ + global $pass, $fail, $results; + if ($cond) { + $pass++; + $results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : ''); + } else { + $fail++; + $results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : ''); + } +} + +function invokePolicyNoSync(PolicyTransactionController $controller, int $inceptionPtId, string $oldPolicyNo, string $newPolicyNo): array +{ + $method = new ReflectionMethod($controller, 'updateRelatedPolicyTransactionPolicyNo'); + $method->setAccessible(true); + + return $method->invoke($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); +} + +$db = db_connect('default'); +$ptModel = new PolicyTransactionModel(); +$controller = new PolicyTransactionController(); + +$inceptionPtId = isset($argv[1]) ? (int) $argv[1] : 0; + +if ($inceptionPtId <= 0) { + $seed = $db->query( + "SELECT pt.id + FROM policy_transaction pt + WHERE pt.is_active = 1 + AND pt.action_type = 'inception' + AND pt.policy_no IS NOT NULL + AND pt.policy_no <> '' + AND EXISTS ( + SELECT 1 + FROM policy_transaction other + WHERE other.is_active = 1 + AND other.policy_no = pt.policy_no + AND other.id <> pt.id + ) + ORDER BY pt.id ASC + LIMIT 1" + )->getRowArray(); + + $inceptionPtId = (int) ($seed['id'] ?? 0); +} + +ok('seed inception row found', $inceptionPtId > 0, "pt_id={$inceptionPtId}"); + +$inception = $ptModel->where('is_active', 1)->where('id', $inceptionPtId)->first(); +ok('inception row loaded', !empty($inception), 'id=' . ($inception['id'] ?? 'n/a')); + +$oldPolicyNo = trim((string) ($inception['policy_no'] ?? '')); +ok('inception has policy_no', $oldPolicyNo !== '', $oldPolicyNo); + +$relatedBefore = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->findAll(); + +ok('related rows exist before sync', count($relatedBefore) > 0, 'count=' . count($relatedBefore)); + +$newPolicyNo = $oldPolicyNo . '_SMOKE_' . time(); +$relatedIds = array_map(static fn(array $row): int => (int) $row['id'], $relatedBefore); + +// No-op cases +$noChange = invokePolicyNoSync($controller, $inceptionPtId, $oldPolicyNo, $oldPolicyNo); +ok('no-op when policy_no unchanged', ($noChange['status'] ?? null) === false); + +$emptyOld = invokePolicyNoSync($controller, $inceptionPtId, '', $newPolicyNo); +ok('no-op when old policy_no empty', ($emptyOld['status'] ?? null) === false); + +// Apply sync on related rows only (inception row still has old policy_no for this call) +$result = invokePolicyNoSync($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); +ok('sync returns success', ($result['status'] ?? null) === true, json_encode($result)); +ok('sync updated expected row count', count($result['updated_ids'] ?? []) === count($relatedIds), 'updated=' . count($result['updated_ids'] ?? [])); + +foreach ($relatedIds as $relatedId) { + $row = $ptModel->where('id', $relatedId)->first(); + ok("related row {$relatedId} has new policy_no", ($row['policy_no'] ?? '') === $newPolicyNo, $row['policy_no'] ?? 'missing'); +} + +$inceptionAfter = $ptModel->where('id', $inceptionPtId)->first(); +ok('inception row unchanged by sync helper', ($inceptionAfter['policy_no'] ?? '') === $oldPolicyNo, $inceptionAfter['policy_no'] ?? 'missing'); + +$stillOld = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->countAllResults(); +ok('no related rows left with old policy_no', $stillOld === 0, "remaining={$stillOld}"); + +// Restore test data +foreach ($relatedIds as $relatedId) { + $ptModel->where('id', $relatedId)->set(['policy_no' => $oldPolicyNo])->update(); +} + +$restored = $ptModel + ->where('is_active', 1) + ->where('id !=', $inceptionPtId) + ->where('policy_no', $oldPolicyNo) + ->countAllResults(); +ok('related rows restored', $restored === count($relatedIds), "restored={$restored}"); + +echo PHP_EOL . '=== smoke_inception_policy_no_sync ===' . PHP_EOL; +echo 'inception_pt_id: ' . $inceptionPtId . PHP_EOL; +echo 'policy_no: ' . $oldPolicyNo . PHP_EOL; +echo 'related_rows: ' . count($relatedIds) . PHP_EOL; +echo PHP_EOL; + +foreach ($results as $line) { + echo $line . PHP_EOL; +} + +echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL; + +exit($fail > 0 ? 1 : 0); diff --git a/tests/unit/PolicyTransactionControllerTest.php b/tests/unit/PolicyTransactionControllerTest.php index 50abd143..448fc3e4 100644 --- a/tests/unit/PolicyTransactionControllerTest.php +++ b/tests/unit/PolicyTransactionControllerTest.php @@ -112,6 +112,82 @@ class PolicyTransactionControllerTest extends CIUnitTestCase * * This reuses the same transformation logic as cronDailyBDSReport. */ + protected function invokePolicyNoSync(PolicyTransactionController $controller, int $inceptionPtId, string $oldPolicyNo, string $newPolicyNo): array + { + $method = new \ReflectionMethod($controller, 'updateRelatedPolicyTransactionPolicyNo'); + $method->setAccessible(true); + + return $method->invoke($controller, $inceptionPtId, $oldPolicyNo, $newPolicyNo); + } + + protected function injectPolicyNoSyncStubModel(PolicyTransactionController $controller, array $relatedRecords): void + { + $stubModel = new class($relatedRecords) { + private array $relatedRecords; + public array $updated = []; + + public function __construct(array $relatedRecords) + { + $this->relatedRecords = $relatedRecords; + } + + public function where($field, $value = null) + { + return $this; + } + + public function findAll(): array + { + return $this->relatedRecords; + } + + public function set(array $data) + { + $this->pending = $data; + return $this; + } + + public function update() + { + $this->updated[] = $this->pending ?? []; + return true; + } + + public function affectedRows(): int + { + return 1; + } + }; + + $refClass = new \ReflectionClass($controller); + $prop = $refClass->getProperty('policyTransactionModel'); + $prop->setAccessible(true); + $prop->setValue($controller, $stubModel); + } + + public function testUpdateRelatedPolicyTransactionPolicyNoSkipsWhenUnchanged(): void + { + $controller = $this->makeController(); + $result = $this->invokePolicyNoSync($controller, 10, 'POL-001', 'POL-001'); + + $this->assertFalse($result['status']); + $this->assertSame([], $result['updated_ids']); + } + + public function testUpdateRelatedPolicyTransactionPolicyNoUpdatesMatchingRows(): void + { + $controller = $this->makeController(); + $this->injectPolicyNoSyncStubModel($controller, [ + ['id' => 101], + ['id' => 102], + ]); + + $result = $this->invokePolicyNoSync($controller, 10, 'POL-OLD', 'POL-NEW'); + + $this->assertTrue($result['status']); + $this->assertSame([101, 102], $result['updated_ids']); + } + public function testGenerateDailyBdsReportExcelToDownloads(): void { helper(['excel_import_export_helper', 'utility_helper']);