527 lines
21 KiB
PHP
527 lines
21 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use CodeIgniter\API\ResponseTrait;
|
|
|
|
/**
|
|
* Standalone HR login diagnostic tool.
|
|
* Does not modify existing auth / HR access controllers.
|
|
*/
|
|
class HrLoginDiagnoseController extends AdminController
|
|
{
|
|
use ResponseTrait;
|
|
|
|
public function index()
|
|
{
|
|
$email = trim((string) $this->request->getGet('email'));
|
|
$mobile = trim((string) $this->request->getGet('mobile'));
|
|
|
|
$data = [
|
|
'title' => 'HR Login Diagnostic',
|
|
'email' => $email,
|
|
'mobile' => $mobile,
|
|
'result' => null,
|
|
'has_search' => false,
|
|
];
|
|
|
|
if ($email !== '' || $mobile !== '') {
|
|
$data['has_search'] = true;
|
|
$data['result'] = $this->diagnose($email, $mobile);
|
|
}
|
|
|
|
return view('hr_login_diagnose', $data);
|
|
}
|
|
|
|
public function check()
|
|
{
|
|
try {
|
|
$payload = $this->request->getJSON(true);
|
|
if (!is_array($payload) || empty($payload)) {
|
|
$payload = [
|
|
'email' => $this->request->getPost('email'),
|
|
'mobile' => $this->request->getPost('mobile'),
|
|
];
|
|
}
|
|
|
|
$email = trim((string) ($payload['email'] ?? ''));
|
|
$mobile = trim((string) ($payload['mobile'] ?? ''));
|
|
|
|
if ($email === '' && $mobile === '') {
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'Enter email or mobile.',
|
|
], 400);
|
|
}
|
|
|
|
$result = $this->diagnose($email, $mobile);
|
|
|
|
return $this->respond([
|
|
'status' => true,
|
|
'data' => $result,
|
|
], 200);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'HrLoginDiagnose check failed: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
|
|
|
return $this->respond([
|
|
'status' => false,
|
|
'message' => 'Diagnosis failed: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
private function diagnose(string $email, string $mobile): array
|
|
{
|
|
$issues = [];
|
|
$postDb = \Config\Database::connect();
|
|
$contacts = $this->fetchPostContacts($postDb, $email, $mobile);
|
|
|
|
if (empty($contacts)) {
|
|
$issues[] = $this->issue(
|
|
'HR_NOT_FOUND',
|
|
'high',
|
|
'No active HR found in post level_contacts for the given email/mobile (contact_type=client).',
|
|
'Create/activate the HR contact under the correct client branch, or verify the email/mobile.'
|
|
);
|
|
|
|
return [
|
|
'input' => ['email' => $email, 'mobile' => $mobile],
|
|
'overall_status' => 'FAIL',
|
|
'summary' => 'HR not found in post DB.',
|
|
'issues' => $issues,
|
|
'post' => ['contacts' => []],
|
|
'hr_access_control'=> [],
|
|
'pre' => ['reachable' => null, 'mappings' => []],
|
|
'login_simulation' => [
|
|
'would_authenticate' => false,
|
|
'cards' => [],
|
|
],
|
|
];
|
|
}
|
|
|
|
$accessRows = [];
|
|
$preMaps = [];
|
|
$loginCards = [];
|
|
$preReachable = null;
|
|
|
|
try {
|
|
$preDb = \Config\Database::connect('preDB');
|
|
$preDb->connect();
|
|
$preReachable = true;
|
|
} catch (\Throwable $e) {
|
|
$preReachable = false;
|
|
$preDb = null;
|
|
$issues[] = $this->issue(
|
|
'PRE_DB_UNREACHABLE',
|
|
'high',
|
|
'Could not connect to preDB: ' . $e->getMessage(),
|
|
'Check preDB credentials in Database config / .env.'
|
|
);
|
|
}
|
|
|
|
foreach ($contacts as $contact) {
|
|
$flags = [
|
|
'HR_FOUND' => true,
|
|
'BRANCH_ACTIVE' => (int) ($contact['branch_active'] ?? 0) === 1,
|
|
'CLIENT_ACTIVE' => (int) ($contact['client_active'] ?? 0) === 1,
|
|
];
|
|
|
|
if (!$flags['BRANCH_ACTIVE']) {
|
|
$issues[] = $this->issue(
|
|
'BRANCH_INACTIVE',
|
|
'high',
|
|
"Branch inactive for post_hr_id={$contact['post_hr_id']} (branch #{$contact['post_branch_id']}).",
|
|
'Activate client_branch.is_active=1 for this branch.'
|
|
);
|
|
}
|
|
if (!$flags['CLIENT_ACTIVE']) {
|
|
$issues[] = $this->issue(
|
|
'CLIENT_INACTIVE',
|
|
'high',
|
|
"Client inactive for post_hr_id={$contact['post_hr_id']} (client #{$contact['post_client_id']}).",
|
|
'Activate clients.is_active=1 for this client.'
|
|
);
|
|
}
|
|
|
|
$access = $this->fetchAccessRow(
|
|
$postDb,
|
|
(int) $contact['post_hr_id'],
|
|
(int) $contact['post_branch_id'],
|
|
(int) $contact['post_client_id']
|
|
);
|
|
|
|
$modules = [];
|
|
$hasModules = false;
|
|
$tokenReady = false;
|
|
$failureReason = null;
|
|
|
|
if (empty($access)) {
|
|
$failureReason = 'NO_HR_ACCESS_CONTROL_ROW';
|
|
$issues[] = $this->issue(
|
|
'NO_HR_ACCESS_CONTROL_ROW',
|
|
'high',
|
|
"No hr_access_control row for post_hr_id={$contact['post_hr_id']}, post_branch_id={$contact['post_branch_id']}, post_client_id={$contact['post_client_id']}.",
|
|
'Open Client Onboarding → HR Access and save at least one module for this HR/branch.'
|
|
);
|
|
} else {
|
|
$modules = json_decode($access['allowed_modules'] ?? '[]', true);
|
|
if (!is_array($modules)) {
|
|
$modules = [];
|
|
}
|
|
$hasModules = $this->hasAnyModule($modules);
|
|
$tokenReady = $hasModules;
|
|
|
|
if (!$hasModules) {
|
|
$failureReason = 'EMPTY_ALLOWED_MODULES';
|
|
$issues[] = $this->issue(
|
|
'EMPTY_ALLOWED_MODULES',
|
|
'high',
|
|
"hr_access_control id={$access['id']} exists but allowed_modules is empty.",
|
|
'Assign pre/post modules in HR Access Control and save.'
|
|
);
|
|
}
|
|
}
|
|
|
|
$accessRows[] = [
|
|
'id' => $access['id'] ?? null,
|
|
'post_hr_id' => (int) $contact['post_hr_id'],
|
|
'pre_hr_id' => isset($access['pre_hr_id']) ? (int) $access['pre_hr_id'] : null,
|
|
'post_branch_id' => (int) $contact['post_branch_id'],
|
|
'pre_branch_id' => isset($access['pre_branch_id']) ? (int) $access['pre_branch_id'] : null,
|
|
'post_client_id' => (int) $contact['post_client_id'],
|
|
'pre_client_id' => isset($access['pre_client_id']) ? (int) $access['pre_client_id'] : null,
|
|
'allowed_modules' => $modules,
|
|
'allowed_pre_policies' => json_decode($access['allowed_pre_policies'] ?? '[]', true) ?: [],
|
|
'allowed_active_policies' => json_decode($access['allowed_active_policies'] ?? '[]', true) ?: [],
|
|
'allowed_cd' => json_decode($access['allowed_cd'] ?? '[]', true) ?: [],
|
|
'is_active' => isset($access['is_active']) ? (int) $access['is_active'] : null,
|
|
'has_token' => $tokenReady,
|
|
'token_ready' => $tokenReady,
|
|
'failure_reason' => $failureReason,
|
|
'note' => $failureReason ?: 'OK',
|
|
];
|
|
|
|
$preMap = $this->buildPreMapping($preDb, $contact, $access);
|
|
$preMaps[] = $preMap;
|
|
|
|
if ($preReachable === true) {
|
|
if (empty($preMap['PRE_BRANCH_LINKED'])) {
|
|
$issues[] = $this->issue(
|
|
'PRE_BRANCH_NOT_LINKED',
|
|
'medium',
|
|
"Post branch #{$contact['post_branch_id']} has no valid pre_branch_id link.",
|
|
'Set client_branch.pre_branch_id to the matching pre branch id.'
|
|
);
|
|
} elseif (empty($preMap['PRE_HR_MATCHED'])) {
|
|
$issues[] = $this->issue(
|
|
'PRE_HR_NOT_MATCHED',
|
|
'medium',
|
|
"No pre level_contacts match for email+mobile under pre client #{$preMap['pre_client_id']}.",
|
|
'Align email and mobile between pre and post HR contacts.'
|
|
);
|
|
} elseif (empty($preMap['PRE_IDS_CONSISTENT']) && !empty($access)) {
|
|
$issues[] = $this->issue(
|
|
'PRE_IDS_INCONSISTENT',
|
|
'medium',
|
|
"hr_access_control pre ids do not match live pre data for post_hr_id={$contact['post_hr_id']}.",
|
|
'Re-save HR Access Control so pre_hr_id / pre_client_id / pre_branch_id are refreshed.'
|
|
);
|
|
}
|
|
}
|
|
|
|
$canLoginCard = $flags['BRANCH_ACTIVE'] && $flags['CLIENT_ACTIVE'] && $tokenReady;
|
|
$loginCards[] = [
|
|
'client' => $contact['client_name'] ?? '',
|
|
'branch' => $contact['branch_name'] ?? '',
|
|
'post_hr_id' => (int) $contact['post_hr_id'],
|
|
'post_client_id' => (int) $contact['post_client_id'],
|
|
'post_branch_id' => (int) $contact['post_branch_id'],
|
|
'has_token' => $canLoginCard,
|
|
'failure_reason' => $canLoginCard
|
|
? null
|
|
: ($failureReason
|
|
?: (!$flags['BRANCH_ACTIVE'] ? 'BRANCH_INACTIVE'
|
|
: (!$flags['CLIENT_ACTIVE'] ? 'CLIENT_INACTIVE' : 'UNKNOWN'))),
|
|
];
|
|
|
|
$contact['hr_found'] = true;
|
|
$contact['flags'] = $flags;
|
|
}
|
|
|
|
$anyToken = false;
|
|
foreach ($loginCards as $card) {
|
|
if (!empty($card['has_token'])) {
|
|
$anyToken = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$wouldAuth = !empty($contacts);
|
|
$overall = ($wouldAuth && $anyToken && empty(array_filter($issues, static fn ($i) => ($i['severity'] ?? '') === 'high')))
|
|
? 'PASS'
|
|
: (($anyToken && $wouldAuth) ? 'WARN' : 'FAIL');
|
|
|
|
if ($overall === 'PASS') {
|
|
$summary = 'HR identity, access row, and login readiness look OK.';
|
|
$verdict = 'OK';
|
|
} elseif ($overall === 'WARN') {
|
|
$summary = 'Login may work for some cards, but mapping/access issues remain.';
|
|
$verdict = 'OK_WITH_WARNINGS';
|
|
} else {
|
|
$summary = 'HR login will fail or return empty token/modules.';
|
|
$verdict = 'WILL_FAIL';
|
|
}
|
|
|
|
// Deduplicate issues by code+message
|
|
$unique = [];
|
|
$deduped = [];
|
|
foreach ($issues as $issue) {
|
|
$key = ($issue['code'] ?? '') . '|' . ($issue['message'] ?? '');
|
|
if (isset($unique[$key])) {
|
|
continue;
|
|
}
|
|
$unique[$key] = true;
|
|
$deduped[] = $issue;
|
|
}
|
|
|
|
$postContacts = array_map(static function ($c) {
|
|
return [
|
|
'post_hr_id' => (int) $c['post_hr_id'],
|
|
'hr_name' => $c['hr_name'] ?? '',
|
|
'hr_mail' => $c['hr_mail'] ?? '',
|
|
'hr_mobile' => $c['hr_mobile'] ?? '',
|
|
'is_active' => (int) ($c['is_active'] ?? 0),
|
|
'post_branch_id' => (int) ($c['post_branch_id'] ?? 0),
|
|
'branch_name' => $c['branch_name'] ?? '',
|
|
'branch_active' => (int) ($c['branch_active'] ?? 0),
|
|
'pre_branch_id' => $c['pre_branch_id'] ?? null,
|
|
'post_client_id' => (int) ($c['post_client_id'] ?? 0),
|
|
'client_name' => $c['client_name'] ?? '',
|
|
'client_active' => (int) ($c['client_active'] ?? 0),
|
|
'hr_found' => true,
|
|
];
|
|
}, $contacts);
|
|
|
|
$firstPost = $postContacts[0] ?? [];
|
|
$firstPre = $preMaps[0] ?? [];
|
|
$tokenCards = array_filter($loginCards, static fn ($c) => ! empty($c['has_token']));
|
|
$preMatched = array_filter($preMaps, static fn ($m) => ! empty($m['PRE_HR_MATCHED']));
|
|
|
|
return [
|
|
'input' => ['email' => $email, 'mobile' => $mobile],
|
|
'overall_status' => $overall,
|
|
'verdict' => $verdict,
|
|
'verdict_message' => $summary,
|
|
'summary' => $summary,
|
|
'issues' => $deduped,
|
|
'problems' => $deduped,
|
|
'post' => [
|
|
'contacts' => $postContacts,
|
|
'raw_found' => count($postContacts) > 0,
|
|
'eligible' => $anyToken,
|
|
'filters_summary' => [
|
|
'hr_active' => ! empty($firstPost) && (int) ($firstPost['is_active'] ?? 0) === 1,
|
|
'branch_active' => ! empty($firstPost) && (int) ($firstPost['branch_active'] ?? 0) === 1,
|
|
'client_active' => ! empty($firstPost) && (int) ($firstPost['client_active'] ?? 0) === 1,
|
|
'has_access' => count($tokenCards) > 0,
|
|
'token_ready' => $anyToken,
|
|
],
|
|
'primary' => $firstPost,
|
|
],
|
|
'hr_access_control' => $accessRows,
|
|
'pre' => [
|
|
'reachable' => $preReachable,
|
|
'mappings' => $preMaps,
|
|
'raw_found' => count($preMatched) > 0,
|
|
'eligible' => count($preMatched) > 0,
|
|
'filters_summary' => [
|
|
'db_reachable' => $preReachable === true,
|
|
'PRE_BRANCH_LINKED' => ! empty($firstPre['PRE_BRANCH_LINKED']),
|
|
'PRE_HR_MATCHED' => ! empty($firstPre['PRE_HR_MATCHED']),
|
|
'PRE_IDS_CONSISTENT' => ! empty($firstPre['PRE_IDS_CONSISTENT']),
|
|
'PRE_CLIENT_MAPPED' => ! empty($firstPre['PRE_CLIENT_MAPPED']),
|
|
],
|
|
'primary' => $firstPre,
|
|
'error' => $preReachable === false ? 'preDB connection failed' : null,
|
|
],
|
|
'login_simulation' => [
|
|
'would_authenticate' => $wouldAuth,
|
|
'cards' => $loginCards,
|
|
],
|
|
'merge' => [
|
|
'post_eligible' => $anyToken,
|
|
'pre_eligible' => count($preMatched) > 0,
|
|
'post_contacts' => count($postContacts),
|
|
'pre_matched' => count($preMatched),
|
|
'token_cards' => count($tokenCards),
|
|
'access_rows' => count(array_filter($accessRows, static fn ($a) => ! empty($a['id']))),
|
|
'would_return_both' => $anyToken && count($preMatched) > 0,
|
|
'winner' => ($anyToken && count($preMatched) > 0) ? 'both' : ($anyToken ? 'post' : (count($preMatched) > 0 ? 'pre' : 'none')),
|
|
'reason' => $summary,
|
|
],
|
|
];
|
|
}
|
|
|
|
private function fetchPostContacts($db, string $email, string $mobile): array
|
|
{
|
|
$builder = $db->table('level_contacts lc')
|
|
->select("
|
|
lc.id AS post_hr_id,
|
|
lc.name AS hr_name,
|
|
lc.email AS hr_mail,
|
|
lc.mobile AS hr_mobile,
|
|
lc.is_active,
|
|
cb.id AS post_branch_id,
|
|
cb.branch_name,
|
|
cb.is_active AS branch_active,
|
|
cb.pre_branch_id,
|
|
c.id AS post_client_id,
|
|
c.client_name,
|
|
c.is_active AS client_active
|
|
")
|
|
->join('client_branch cb', 'cb.id = lc.ref_id', 'left')
|
|
->join('clients c', 'c.id = cb.client_id', 'left')
|
|
->where('lc.contact_type', 'client')
|
|
->where('lc.is_active', 1);
|
|
|
|
if ($email !== '' && $mobile !== '') {
|
|
$builder->groupStart()
|
|
->where('lc.email', $email)
|
|
->orWhere('lc.mobile', $mobile)
|
|
->groupEnd();
|
|
} elseif ($email !== '') {
|
|
$builder->where('lc.email', $email);
|
|
} else {
|
|
$builder->where('lc.mobile', $mobile);
|
|
}
|
|
|
|
return $builder->get()->getResultArray();
|
|
}
|
|
|
|
private function fetchAccessRow($db, int $postHrId, int $postBranchId, int $postClientId): ?array
|
|
{
|
|
$row = $db->table('hr_access_control')
|
|
->where('post_hr_id', $postHrId)
|
|
->where('post_branch_id', $postBranchId)
|
|
->where('post_client_id', $postClientId)
|
|
->where('is_active', 1)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
return $row ?: null;
|
|
}
|
|
|
|
private function hasAnyModule(array $modules): bool
|
|
{
|
|
if (isset($modules['pre']) || isset($modules['post'])) {
|
|
$pre = is_array($modules['pre'] ?? null) ? $modules['pre'] : [];
|
|
$post = is_array($modules['post'] ?? null) ? $modules['post'] : [];
|
|
|
|
return count($pre) > 0 || count($post) > 0;
|
|
}
|
|
|
|
return count($modules) > 0;
|
|
}
|
|
|
|
private function buildPreMapping($preDb, array $contact, ?array $access): array
|
|
{
|
|
$result = [
|
|
'post_hr_id' => (int) $contact['post_hr_id'],
|
|
'pre_hr_id' => null,
|
|
'pre_client_id' => null,
|
|
'pre_branch_id' => $contact['pre_branch_id'] ?? null,
|
|
'pre_client_name' => null,
|
|
'pre_branch_name' => null,
|
|
'hr_name' => null,
|
|
'hr_mail' => null,
|
|
'hr_mobile' => null,
|
|
'PRE_BRANCH_LINKED' => false,
|
|
'PRE_HR_MATCHED' => false,
|
|
'PRE_IDS_CONSISTENT' => false,
|
|
'PRE_CLIENT_MAPPED' => false,
|
|
'access_pre_hr_id' => isset($access['pre_hr_id']) ? (int) $access['pre_hr_id'] : null,
|
|
'access_pre_client_id' => isset($access['pre_client_id']) ? (int) $access['pre_client_id'] : null,
|
|
'access_pre_branch_id' => isset($access['pre_branch_id']) ? (int) $access['pre_branch_id'] : null,
|
|
];
|
|
|
|
if ($preDb === null) {
|
|
return $result;
|
|
}
|
|
|
|
$preBranchId = $contact['pre_branch_id'] ?? null;
|
|
if (empty($preBranchId)) {
|
|
return $result;
|
|
}
|
|
|
|
$preBranch = $preDb->table('client_branch cb')
|
|
->select('cb.id AS pre_branch_id, cb.client_id AS pre_client_id, cb.branch_name, c.client_name')
|
|
->join('clients c', 'c.id = cb.client_id', 'left')
|
|
->where('cb.id', $preBranchId)
|
|
->where('cb.is_active', 1)
|
|
->where('c.is_active', 1)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (empty($preBranch)) {
|
|
return $result;
|
|
}
|
|
|
|
$result['PRE_BRANCH_LINKED'] = true;
|
|
$result['pre_branch_id'] = (int) $preBranch['pre_branch_id'];
|
|
$result['pre_client_id'] = (int) $preBranch['pre_client_id'];
|
|
$result['pre_branch_name'] = $preBranch['branch_name'] ?? null;
|
|
$result['pre_client_name'] = $preBranch['client_name'] ?? null;
|
|
$result['PRE_CLIENT_MAPPED'] = true;
|
|
|
|
$email = trim((string) ($contact['hr_mail'] ?? ''));
|
|
$mobile = trim((string) ($contact['hr_mobile'] ?? ''));
|
|
|
|
$preHrBuilder = $preDb->table('level_contacts lc')
|
|
->select('lc.id AS pre_hr_id, lc.name, lc.email, lc.mobile')
|
|
->join('client_branch cb', 'cb.id = lc.ref_id')
|
|
->where('lc.contact_type', 'client')
|
|
->where('lc.is_active', 1)
|
|
->where('cb.is_active', 1)
|
|
->where('cb.client_id', $preBranch['pre_client_id']);
|
|
|
|
if ($email !== '' && $mobile !== '') {
|
|
$preHrBuilder->where('lc.email', $email)->where('lc.mobile', $mobile);
|
|
} elseif ($email !== '') {
|
|
$preHrBuilder->where('lc.email', $email);
|
|
} elseif ($mobile !== '') {
|
|
$preHrBuilder->where('lc.mobile', $mobile);
|
|
}
|
|
|
|
$preHr = $preHrBuilder->get()->getRowArray();
|
|
if (!empty($preHr)) {
|
|
$result['PRE_HR_MATCHED'] = true;
|
|
$result['pre_hr_id'] = (int) $preHr['pre_hr_id'];
|
|
$result['hr_name'] = $preHr['name'] ?? null;
|
|
$result['hr_mail'] = $preHr['email'] ?? null;
|
|
$result['hr_mobile'] = $preHr['mobile'] ?? null;
|
|
}
|
|
|
|
if (!empty($access) && $result['PRE_HR_MATCHED']) {
|
|
$idsMatch =
|
|
(int) ($access['pre_hr_id'] ?? 0) === (int) $result['pre_hr_id']
|
|
&& (int) ($access['pre_client_id'] ?? 0) === (int) $result['pre_client_id']
|
|
&& (int) ($access['pre_branch_id'] ?? 0) === (int) $result['pre_branch_id'];
|
|
$result['PRE_IDS_CONSISTENT'] = $idsMatch;
|
|
} elseif (empty($access)) {
|
|
$result['PRE_IDS_CONSISTENT'] = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function issue(string $code, string $severity, string $message, string $fix = ''): array
|
|
{
|
|
return [
|
|
'code' => $code,
|
|
'severity' => $severity,
|
|
'message' => $message,
|
|
'fix' => $fix,
|
|
];
|
|
}
|
|
}
|