From ca0eee96258d13a8abd89d142117bdfa3516316b Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 7 Aug 2026 17:03:45 +0530 Subject: [PATCH 1/2] FIX_EXECED_DEPENDENT_APPROVED_COUNT --- app/Controllers/EmployeeRestController.php | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index e8cc0f87..64ae7539 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -7080,6 +7080,20 @@ class EmployeeRestController extends AdminController } if ($status === 'approved') { + $floaterLimitCheck = $this->checkFamilyFloaterDependentLimit( + $employee, + (int) $client_policy_id, + (int) $employee_id + ); + + if ($floaterLimitCheck['allowed'] === false) { + return $this->respond([ + 'status' => 'failed', + 'code' => 400, + 'data' => $floaterLimitCheck['message'], + ], 200); + } + $employee_update_data = [ 'updated_by' => $updated_by, 'processed_by' => $processed_by_role, @@ -7153,6 +7167,95 @@ class EmployeeRestController extends AdminController } } + /** + * On approve: ensure spouse/child counts stay within policy_terms.family_floaters limits. + * + * @return array{allowed: bool, message: string|null} + */ + private function checkFamilyFloaterDependentLimit(array $employee, int $clientPolicyId, int $employeeId): array + { + $relationship = strtolower(trim((string) ($employee['relationship'] ?? ''))); + $floaterKey = ! empty($employee['family_floater_key']) + ? preg_replace('/\d/', '', strtolower(trim((string) $employee['family_floater_key']))) + : strtolower((string) ($this->RelationshipMap($employee['relationship'] ?? '') ?? '')); + + $isSpouse = ($relationship === 'spouse' || $floaterKey === 'spouse'); + $isChild = in_array($relationship, ['son', 'daughter'], true) || $floaterKey === 'child'; + + // Only spouse / child are validated against family_floaters for dependent-add approve + if (! $isSpouse && ! $isChild) { + return ['allowed' => true, 'message' => null]; + } + + $clientPolicy = $this->clientPolicyModel->select('policy_terms')->where('id', $clientPolicyId)->first(); + if (empty($clientPolicy['policy_terms'])) { + return ['allowed' => true, 'message' => null]; + } + + $policyTerms = json_decode($clientPolicy['policy_terms'], true); + if (! is_array($policyTerms) || empty($policyTerms['family_floaters'])) { + return ['allowed' => true, 'message' => null]; + } + + $familyFloaters = is_array($policyTerms['family_floaters']) + ? $policyTerms['family_floaters'] + : (array) $policyTerms['family_floaters']; + + $allowedSpouseCount = (int) ($familyFloaters['spouse'] ?? 0); + $allowedChildCount = (int) ($familyFloaters['childrens'] ?? 0); + + $existingFamily = $this->employeeModel + ->select('employees.id, employees.relationship, employees.family_floater_key') + ->join('employee_polices', 'employee_polices.employee_id = employees.id') + ->where('employees.emp_code', $employee['emp_code']) + ->where('employees.client_id', $employee['client_id']) + ->where('employee_polices.client_policy_id', $clientPolicyId) + ->where('employees.emp_status', 'active') + ->where('employee_polices.status', 'active') + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->where('employees.id !=', $employeeId) + ->findAll(); + + $activeSpouseCount = 0; + $activeChildCount = 0; + + foreach ($existingFamily as $member) { + $memberRelationship = strtolower(trim((string) ($member['relationship'] ?? ''))); + $memberFloaterKey = ! empty($member['family_floater_key']) + ? preg_replace('/\d/', '', strtolower(trim((string) $member['family_floater_key']))) + : ''; + + if ($memberRelationship === 'spouse' || $memberFloaterKey === 'spouse') { + $activeSpouseCount++; + } elseif (in_array($memberRelationship, ['son', 'daughter'], true) || $memberFloaterKey === 'child') { + $activeChildCount++; + } + } + + if ($isSpouse) { + if (($activeSpouseCount + 1) > $allowedSpouseCount) { + return [ + 'allowed' => false, + 'message' => 'Spouse count exceeds policy family floater limit. Allowed: ' + . $allowedSpouseCount . ', existing active: ' . $activeSpouseCount, + ]; + } + } + + if ($isChild) { + if (($activeChildCount + 1) > $allowedChildCount) { + return [ + 'allowed' => false, + 'message' => 'Child count exceeds policy family floater limit. Allowed: ' + . $allowedChildCount . ', existing active: ' . $activeChildCount, + ]; + } + } + + return ['allowed' => true, 'message' => null]; + } + /** * List dependent-add workflow members (API + mobile). * Returns only pending_approval, approved, and rejected dependents (not all members). From 779e20a648f99e83bc573bed98a3da4ca930851a Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 7 Aug 2026 17:59:33 +0530 Subject: [PATCH 2/2] FIX_DEPENDENT_ADDITION --- app/Controllers/EmployeeRestController.php | 293 ++++++++++++++++++++- 1 file changed, 285 insertions(+), 8 deletions(-) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 64ae7539..da75bf5a 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -7004,8 +7004,11 @@ class EmployeeRestController extends AdminController { try { $data = $this->request->getJSON(true); + log_message('error', '[processDependentAdd] Step 1: Request received | payload=' . json_encode($data)); + $this->myLogger->logme('error', '[processDependentAdd] Step 1: Request received | payload=' . json_encode($data)); if (empty($data)) { + log_message('error', '[processDependentAdd] Step 1 failed: Request body is required'); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Request body is required'], 200); } @@ -7018,75 +7021,118 @@ class EmployeeRestController extends AdminController // Processor role: HR (app) or ACM (internal portal session user) — used for approve and reject $processed_by_role = ! empty($hr_id) ? 'HR' : 'ACM'; + log_message('error', '[processDependentAdd] Step 2: Parsed params | employee_id=' . $employee_id + . ' | client_policy_id=' . $client_policy_id + . ' | status=' . $status + . ' | hr_id=' . ($hr_id ?? 'null') + . ' | updated_by=' . ($updated_by ?? 'null') + . ' | processed_by_role=' . $processed_by_role); + $this->myLogger->logme('error', '[processDependentAdd] Step 2: Parsed params | employee_id=' . $employee_id + . ' | client_policy_id=' . $client_policy_id + . ' | status=' . $status + . ' | processed_by_role=' . $processed_by_role); + if (empty($employee_id)) { + log_message('error', '[processDependentAdd] Step 2 failed: employee_id is required'); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'employee_id is required'], 200); } if (empty($client_policy_id)) { + log_message('error', '[processDependentAdd] Step 2 failed: client_policy_id is required'); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'client_policy_id is required'], 200); } if (empty($status)) { + log_message('error', '[processDependentAdd] Step 2 failed: status is required'); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'status is required'], 200); } if ($status !== 'approved' && $status !== 'rejected') { + log_message('error', '[processDependentAdd] Step 2 failed: status is invalid | status=' . $status); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'status is invalid'], 200); } if ($status === 'rejected' && $reject_reason === '') { + log_message('error', '[processDependentAdd] Step 2 failed: reject_reason is required for rejection'); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'reject_reason is required'], 200); } + log_message('error', '[processDependentAdd] Step 3: Fetching employee | employee_id=' . $employee_id); $employee = $this->employeeModel ->where('id', $employee_id) ->first(); if (!$employee) { + log_message('error', '[processDependentAdd] Step 3 failed: Employee not found | employee_id=' . $employee_id); return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Employee not found'], 200); } $emp_status = strtolower((string) ($employee['emp_status'] ?? '')); + log_message('error', '[processDependentAdd] Step 3 success: Employee found | emp_code=' . ($employee['emp_code'] ?? '') + . ' | relationship=' . ($employee['relationship'] ?? '') + . ' | emp_status=' . $emp_status + . ' | family_floater_key=' . ($employee['family_floater_key'] ?? '')); if ($emp_status === 'active') { + log_message('error', '[processDependentAdd] Step 4 failed: Dependent is already approved | employee_id=' . $employee_id); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Dependent is already approved'], 200); } if ($status === 'approved') { if (! in_array($emp_status, ['pending_approval', 'rejected'], true)) { + log_message('error', '[processDependentAdd] Step 4 failed: Dependent cannot be approved | emp_status=' . $emp_status); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Dependent cannot be approved'], 200); } } elseif ($emp_status !== 'pending_approval') { + log_message('error', '[processDependentAdd] Step 4 failed: Only pending dependents can be rejected | emp_status=' . $emp_status); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Only pending dependents can be rejected'], 200); } + log_message('error', '[processDependentAdd] Step 4: Employee status validation passed | emp_status=' . $emp_status); + + log_message('error', '[processDependentAdd] Step 5: Fetching employee policy | employee_id=' . $employee_id + . ' | client_policy_id=' . $client_policy_id); $employee_policy = $this->employeePolicyModel ->where('employee_id', $employee_id) ->where('client_policy_id', $client_policy_id) ->first(); if (!$employee_policy) { + log_message('error', '[processDependentAdd] Step 5 failed: Employee policy not found'); return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Employee policy not found'], 200); } $policy_status = strtolower((string) ($employee_policy['status'] ?? '')); + log_message('error', '[processDependentAdd] Step 5 success: Employee policy found | policy_status=' . $policy_status); if ($status === 'approved') { if (! in_array($policy_status, ['pending_approval', 'rejected'], true)) { + log_message('error', '[processDependentAdd] Step 6 failed: Employee policy cannot be approved | policy_status=' . $policy_status); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Employee policy cannot be approved'], 200); } } elseif ($policy_status !== 'pending_approval') { + log_message('error', '[processDependentAdd] Step 6 failed: Only pending employee policy can be rejected | policy_status=' . $policy_status); return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'Only pending employee policy can be rejected'], 200); } + log_message('error', '[processDependentAdd] Step 6: Policy status validation passed | policy_status=' . $policy_status); + if ($status === 'approved') { + log_message('error', '[processDependentAdd] Step 7: Checking family floater spouse/child limit'); $floaterLimitCheck = $this->checkFamilyFloaterDependentLimit( $employee, (int) $client_policy_id, (int) $employee_id ); + log_message('error', '[processDependentAdd] Step 7 result: family floater check | ' + . json_encode($floaterLimitCheck)); + $this->myLogger->logme('error', '[processDependentAdd] Step 7 result: family floater check | ' + . json_encode($floaterLimitCheck)); + if ($floaterLimitCheck['allowed'] === false) { + log_message('error', '[processDependentAdd] Step 7 failed: Limit exceeded | message=' + . ($floaterLimitCheck['message'] ?? '')); return $this->respond([ 'status' => 'failed', 'code' => 400, @@ -7108,6 +7154,7 @@ class EmployeeRestController extends AdminController 'is_active' => 1, 'reject_reason' => null, ]; + log_message('error', '[processDependentAdd] Step 8: Prepared approve update data'); } else { $employee_update_data = [ 'updated_by' => $updated_by, @@ -7123,14 +7170,22 @@ class EmployeeRestController extends AdminController 'status' => 'rejected', 'is_active' => 0, ]; + log_message('error', '[processDependentAdd] Step 8: Prepared reject update data | reject_reason=' . $reject_reason); } + log_message('error', '[processDependentAdd] Step 9: Updating employee | employee_id=' . $employee_id + . ' | data=' . json_encode($employee_update_data)); $employee_updated = $this->employeeModel->update($employee_id, $employee_update_data); if (!$employee_updated) { + log_message('error', '[processDependentAdd] Step 9 failed: Failed to update employee status'); return $this->respond(['status' => 'failed', 'code' => 500, 'data' => 'Failed to update employee status'], 200); } + log_message('error', '[processDependentAdd] Step 9 success: Employee updated'); + log_message('error', '[processDependentAdd] Step 10: Updating employee policy | employee_id=' . $employee_id + . ' | client_policy_id=' . $client_policy_id + . ' | data=' . json_encode($policy_update_data)); $policy_updated = $this->employeePolicyModel ->where('employee_id', $employee_id) ->where('client_policy_id', $client_policy_id) @@ -7138,31 +7193,60 @@ class EmployeeRestController extends AdminController ->update(); if (!$policy_updated) { + log_message('error', '[processDependentAdd] Step 10 failed: Failed to update employee policy status'); return $this->respond(['status' => 'failed', 'code' => 500, 'data' => 'Failed to update employee policy status'], 200); } + log_message('error', '[processDependentAdd] Step 10 success: Employee policy updated'); + + $truncatedDependentIds = []; + if ($status === 'approved') { + log_message('error', '[processDependentAdd] Step 11: Truncating excess pending dependents if limit exceeded'); + $truncatedDependentIds = $this->truncateExcessPendingDependents( + $employee, + (int) $client_policy_id, + (int) $employee_id, + $updated_by, + $processed_by_role + ); + log_message('error', '[processDependentAdd] Step 11 result: truncated_dependent_ids=' + . json_encode($truncatedDependentIds)); + $this->myLogger->logme('error', '[processDependentAdd] Step 11 result: truncated_dependent_ids=' + . json_encode($truncatedDependentIds)); + } if ($status === 'rejected') { + log_message('error', '[processDependentAdd] Step 12: Sending dependent rejected notification mail'); $this->sendDependentRejectedNotificationMail( employee: $employee, employeePolicy: $employee_policy, rejectReason: $reject_reason, processedByRole: $processed_by_role ); + log_message('error', '[processDependentAdd] Step 12 done: Reject notification mail triggered'); } + log_message('error', '[processDependentAdd] Step 13: Success | status=' . $status + . ' | employee_id=' . $employee_id + . ' | truncated_dependent_ids=' . json_encode($truncatedDependentIds)); + $this->myLogger->logme('error', '[processDependentAdd] Step 13: Success | status=' . $status + . ' | employee_id=' . $employee_id + . ' | truncated_dependent_ids=' . json_encode($truncatedDependentIds)); + return $this->respond([ 'status' => 'success', 'code' => 200, 'message' => $status === 'approved' ? 'Dependent approved successfully' : 'Dependent rejected successfully', 'data' => [ - 'employee_id' => $employee_id, - 'client_policy_id' => $client_policy_id, - 'status' => $status, - 'reject_reason' => $status === 'rejected' ? $reject_reason : null, + 'employee_id' => $employee_id, + 'client_policy_id' => $client_policy_id, + 'status' => $status, + 'reject_reason' => $status === 'rejected' ? $reject_reason : null, + 'truncated_dependent_ids' => $truncatedDependentIds, ], ], 200); } catch (\Exception $e) { log_message('error', 'Error in processDependentAdd: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine()); + $this->myLogger->logme('error', 'Error in processDependentAdd: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine()); return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500); } } @@ -7174,6 +7258,12 @@ class EmployeeRestController extends AdminController */ private function checkFamilyFloaterDependentLimit(array $employee, int $clientPolicyId, int $employeeId): array { + log_message('error', '[checkFamilyFloaterDependentLimit] Start | employee_id=' . $employeeId + . ' | client_policy_id=' . $clientPolicyId + . ' | emp_code=' . ($employee['emp_code'] ?? '') + . ' | relationship=' . ($employee['relationship'] ?? '') + . ' | family_floater_key=' . ($employee['family_floater_key'] ?? '')); + $relationship = strtolower(trim((string) ($employee['relationship'] ?? ''))); $floaterKey = ! empty($employee['family_floater_key']) ? preg_replace('/\d/', '', strtolower(trim((string) $employee['family_floater_key']))) @@ -7182,18 +7272,26 @@ class EmployeeRestController extends AdminController $isSpouse = ($relationship === 'spouse' || $floaterKey === 'spouse'); $isChild = in_array($relationship, ['son', 'daughter'], true) || $floaterKey === 'child'; + log_message('error', '[checkFamilyFloaterDependentLimit] Resolved type | relationship=' . $relationship + . ' | floater_key=' . $floaterKey + . ' | is_spouse=' . ($isSpouse ? '1' : '0') + . ' | is_child=' . ($isChild ? '1' : '0')); + // Only spouse / child are validated against family_floaters for dependent-add approve if (! $isSpouse && ! $isChild) { + log_message('error', '[checkFamilyFloaterDependentLimit] Skip: not spouse/child, allowing'); return ['allowed' => true, 'message' => null]; } $clientPolicy = $this->clientPolicyModel->select('policy_terms')->where('id', $clientPolicyId)->first(); if (empty($clientPolicy['policy_terms'])) { + log_message('error', '[checkFamilyFloaterDependentLimit] Skip: policy_terms empty, allowing'); return ['allowed' => true, 'message' => null]; } $policyTerms = json_decode($clientPolicy['policy_terms'], true); if (! is_array($policyTerms) || empty($policyTerms['family_floaters'])) { + log_message('error', '[checkFamilyFloaterDependentLimit] Skip: family_floaters missing, allowing'); return ['allowed' => true, 'message' => null]; } @@ -7204,6 +7302,10 @@ class EmployeeRestController extends AdminController $allowedSpouseCount = (int) ($familyFloaters['spouse'] ?? 0); $allowedChildCount = (int) ($familyFloaters['childrens'] ?? 0); + log_message('error', '[checkFamilyFloaterDependentLimit] Allowed counts | spouse=' . $allowedSpouseCount + . ' | childrens=' . $allowedChildCount + . ' | family_floaters=' . json_encode($familyFloaters)); + $existingFamily = $this->employeeModel ->select('employees.id, employees.relationship, employees.family_floater_key') ->join('employee_polices', 'employee_polices.employee_id = employees.id') @@ -7217,6 +7319,9 @@ class EmployeeRestController extends AdminController ->where('employees.id !=', $employeeId) ->findAll(); + log_message('error', '[checkFamilyFloaterDependentLimit] Active family members found | count=' + . count($existingFamily) . ' | members=' . json_encode($existingFamily)); + $activeSpouseCount = 0; $activeChildCount = 0; @@ -7233,29 +7338,201 @@ class EmployeeRestController extends AdminController } } + log_message('error', '[checkFamilyFloaterDependentLimit] Active counts | spouse=' . $activeSpouseCount + . ' | child=' . $activeChildCount); + if ($isSpouse) { if (($activeSpouseCount + 1) > $allowedSpouseCount) { + $message = 'Spouse count exceeds policy family floater limit. Allowed: ' + . $allowedSpouseCount . ', existing active: ' . $activeSpouseCount; + log_message('error', '[checkFamilyFloaterDependentLimit] Blocked: ' . $message); return [ 'allowed' => false, - 'message' => 'Spouse count exceeds policy family floater limit. Allowed: ' - . $allowedSpouseCount . ', existing active: ' . $activeSpouseCount, + 'message' => $message, ]; } } if ($isChild) { if (($activeChildCount + 1) > $allowedChildCount) { + $message = 'Child count exceeds policy family floater limit. Allowed: ' + . $allowedChildCount . ', existing active: ' . $activeChildCount; + log_message('error', '[checkFamilyFloaterDependentLimit] Blocked: ' . $message); return [ 'allowed' => false, - 'message' => 'Child count exceeds policy family floater limit. Allowed: ' - . $allowedChildCount . ', existing active: ' . $activeChildCount, + 'message' => $message, ]; } } + log_message('error', '[checkFamilyFloaterDependentLimit] Allowed: within family floater limit'); return ['allowed' => true, 'message' => null]; } + /** + * After approving a spouse/child: if family_floaters slots are full (or pending would exceed), + * mark excess remaining pending_approval dependents as truncate + is_active = 0. + * + * @return int[] Truncated employee ids + */ + private function truncateExcessPendingDependents( + array $employee, + int $clientPolicyId, + int $approvedEmployeeId, + $updatedBy, + string $processedByRole + ): array { + log_message('error', '[truncateExcessPendingDependents] Start | approved_employee_id=' . $approvedEmployeeId + . ' | client_policy_id=' . $clientPolicyId + . ' | emp_code=' . ($employee['emp_code'] ?? '') + . ' | relationship=' . ($employee['relationship'] ?? '') + . ' | updated_by=' . ($updatedBy ?? 'null') + . ' | processed_by_role=' . $processedByRole); + + $relationship = strtolower(trim((string) ($employee['relationship'] ?? ''))); + $floaterKey = ! empty($employee['family_floater_key']) + ? preg_replace('/\d/', '', strtolower(trim((string) $employee['family_floater_key']))) + : strtolower((string) ($this->RelationshipMap($employee['relationship'] ?? '') ?? '')); + + $isSpouse = ($relationship === 'spouse' || $floaterKey === 'spouse'); + $isChild = in_array($relationship, ['son', 'daughter'], true) || $floaterKey === 'child'; + + log_message('error', '[truncateExcessPendingDependents] Resolved type | is_spouse=' . ($isSpouse ? '1' : '0') + . ' | is_child=' . ($isChild ? '1' : '0')); + + if (! $isSpouse && ! $isChild) { + log_message('error', '[truncateExcessPendingDependents] Skip: not spouse/child'); + return []; + } + + $clientPolicy = $this->clientPolicyModel->select('policy_terms')->where('id', $clientPolicyId)->first(); + if (empty($clientPolicy['policy_terms'])) { + log_message('error', '[truncateExcessPendingDependents] Skip: policy_terms empty'); + return []; + } + + $policyTerms = json_decode($clientPolicy['policy_terms'], true); + if (! is_array($policyTerms) || empty($policyTerms['family_floaters'])) { + log_message('error', '[truncateExcessPendingDependents] Skip: family_floaters missing'); + return []; + } + + $familyFloaters = is_array($policyTerms['family_floaters']) + ? $policyTerms['family_floaters'] + : (array) $policyTerms['family_floaters']; + + $allowedCount = $isSpouse + ? (int) ($familyFloaters['spouse'] ?? 0) + : (int) ($familyFloaters['childrens'] ?? 0); + + log_message('error', '[truncateExcessPendingDependents] Allowed count for type | allowed=' . $allowedCount + . ' | family_floaters=' . json_encode($familyFloaters)); + + $familyMembers = $this->employeeModel + ->select('employees.id, employees.relationship, employees.family_floater_key, employees.emp_status, employee_polices.status as policy_status') + ->join('employee_polices', 'employee_polices.employee_id = employees.id') + ->where('employees.emp_code', $employee['emp_code']) + ->where('employees.client_id', $employee['client_id']) + ->where('employee_polices.client_policy_id', $clientPolicyId) + ->where('employees.id !=', $approvedEmployeeId) + ->groupStart() + ->groupStart() + ->where('employees.emp_status', 'active') + ->where('employee_polices.status', 'active') + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->groupEnd() + ->orGroupStart() + ->where('employees.emp_status', 'pending_approval') + ->where('employee_polices.status', 'pending_approval') + ->groupEnd() + ->groupEnd() + ->orderBy('employees.id', 'ASC') + ->findAll(); + + log_message('error', '[truncateExcessPendingDependents] Family members fetched | count=' + . count($familyMembers) . ' | members=' . json_encode($familyMembers)); + + $activeCount = 1; // just-approved dependent already counts toward the limit + $pendingIds = []; + + foreach ($familyMembers as $member) { + $memberRelationship = strtolower(trim((string) ($member['relationship'] ?? ''))); + $memberFloaterKey = ! empty($member['family_floater_key']) + ? preg_replace('/\d/', '', strtolower(trim((string) $member['family_floater_key']))) + : ''; + + $sameType = $isSpouse + ? ($memberRelationship === 'spouse' || $memberFloaterKey === 'spouse') + : (in_array($memberRelationship, ['son', 'daughter'], true) || $memberFloaterKey === 'child'); + + if (! $sameType) { + continue; + } + + $memberEmpStatus = strtolower((string) ($member['emp_status'] ?? '')); + $memberPolicyStatus = strtolower((string) ($member['policy_status'] ?? '')); + + if ($memberEmpStatus === 'active' && $memberPolicyStatus === 'active') { + $activeCount++; + } elseif ($memberEmpStatus === 'pending_approval' && $memberPolicyStatus === 'pending_approval') { + $pendingIds[] = (int) $member['id']; + } + } + + log_message('error', '[truncateExcessPendingDependents] Counts | active_including_approved=' . $activeCount + . ' | pending_ids=' . json_encode($pendingIds)); + + if (empty($pendingIds)) { + log_message('error', '[truncateExcessPendingDependents] No pending dependents to truncate'); + return []; + } + + $remainingSlots = max(0, $allowedCount - $activeCount); + // Keep only as many pending as remaining slots; truncate the rest (excess) + $idsToTruncate = array_slice($pendingIds, $remainingSlots); + + log_message('error', '[truncateExcessPendingDependents] Slot calc | allowed=' . $allowedCount + . ' | active=' . $activeCount + . ' | remaining_slots=' . $remainingSlots + . ' | ids_to_truncate=' . json_encode($idsToTruncate)); + + if (empty($idsToTruncate)) { + log_message('error', '[truncateExcessPendingDependents] No excess pending; nothing to truncate'); + return []; + } + + log_message('error', '[truncateExcessPendingDependents] Updating employees to truncate | ids=' + . json_encode($idsToTruncate)); + $this->employeeModel + ->whereIn('id', $idsToTruncate) + ->set([ + 'emp_status' => 'truncate', + 'is_active' => 0, + 'updated_by' => $updatedBy, + 'processed_by' => $processedByRole, + ]) + ->update(); + + log_message('error', '[truncateExcessPendingDependents] Updating employee_polices to truncate | ids=' + . json_encode($idsToTruncate) . ' | client_policy_id=' . $clientPolicyId); + $this->employeePolicyModel + ->whereIn('employee_id', $idsToTruncate) + ->where('client_policy_id', $clientPolicyId) + ->set([ + 'status' => 'truncate', + 'is_active' => 0, + 'updated_by' => $updatedBy, + 'processed_by' => $processedByRole, + ]) + ->update(); + + log_message('error', '[truncateExcessPendingDependents] Done | truncated_ids=' . json_encode($idsToTruncate)); + $this->myLogger->logme('error', '[truncateExcessPendingDependents] Done | truncated_ids=' . json_encode($idsToTruncate)); + + return $idsToTruncate; + } + /** * List dependent-add workflow members (API + mobile). * Returns only pending_approval, approved, and rejected dependents (not all members).