MERGE_UAT_BUG_FIXES

This commit is contained in:
Ubuntu 2026-07-20 17:16:42 +05:30
commit 1c5a14475f
10 changed files with 2046 additions and 21 deletions

View File

@ -73,6 +73,12 @@ class Acl
'teams' => []
],
// ===================== LOGIN DIAGNOSTIC =====================
'#^/security/login-diagnostic#' => [
'roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID],
'teams' => []
],
// ===================== INTERNAL TEST =====================
'#^/test#' => [
'roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID],

View File

@ -594,6 +594,11 @@ $routes->group('security/rate-limits', ['filter' => 'authMVC'], function ($route
$routes->post('unblock-user', 'RateLimitAdminController::unblockUser');
});
$routes->group('security/login-diagnostic', ['filter' => 'authMVC'], function ($routes) {
$routes->get('/', 'LoginDiagnosticController::index');
$routes->post('diagnose', 'LoginDiagnosticController::diagnose');
});
//saml - routes
// $routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFilter'] ], function ($routes) {

View File

@ -1253,7 +1253,8 @@ class ClientController extends AdminController
$data['is_member_modify_allowed'] = $this->request->getPost('is_member_modify_allowed') ? 1 : 0;
$data['enrolment_visibility'] = $this->request->getPost('enrolment_visibility') ? 1 : 0;
$data['is_lgbtq'] = $this->request->getPost('is_lgbtq') ? 1 : 0;
$data['is_premium_summery'] = $this->request->getPost('is_premium_summery') ? 1 : 0;
$premiumSummary = (int) $this->request->getPost('is_premium_summery');
$data['is_premium_summery'] = in_array($premiumSummary, [0, 1, 2], true) ? $premiumSummary : 0;
if ($policy_type_id == 1 || $policy_type_id == 2 || $policy_type_id == 6 || $policy_type_id == 7) {
@ -1386,7 +1387,8 @@ class ClientController extends AdminController
$data['policy_end_date'] = change_date_format($this->request->getPost('policy_end_date'), 'd-m-Y', 'Y-m-d');
// $data['is_member_modify_allowed'] = $this->request->getPost('is_member_modify_allowed') ? 1 : 0;
$data['is_lgbtq'] = $this->request->getPost('is_lgbtq') ? 1 : 0;
$data['is_premium_summery'] = $this->request->getPost('is_premium_summery') ? 1 : 0;
$premiumSummary = (int) $this->request->getPost('is_premium_summery');
$data['is_premium_summery'] = in_array($premiumSummary, [0, 1, 2], true) ? $premiumSummary : 0;
if ($data['inception_type'] == 2) {
$data['open_date'] = change_date_format($this->request->getPost('open_date'), 'd-m-Y', 'Y-m-d');

View File

@ -0,0 +1,68 @@
<?php
namespace App\Controllers;
use App\Helpers\LoginDiagnosticHelper;
/**
* Admin-only employee login diagnostic (PRE + POST).
* Does not modify existing auth controllers/models/views.
*/
class LoginDiagnosticController extends BaseController
{
public function index(): void
{
$email = trim((string) $this->request->getGet('email'));
$mobile = trim((string) $this->request->getGet('mobile'));
$data = [
'title' => 'Employee Login Diagnostic',
'email' => $email,
'mobile' => $mobile,
'result' => null,
'has_search' => false,
];
if ($email !== '' || $mobile !== '') {
$data['has_search'] = true;
$data['result'] = LoginDiagnosticHelper::diagnose(
$email !== '' ? $email : null,
$mobile !== '' ? $mobile : null
);
}
echo view('admin/login_diagnostic', $data);
}
public function diagnose()
{
$email = trim((string) $this->request->getPost('email'));
$mobile = trim((string) $this->request->getPost('mobile'));
if ($email === '' && $mobile === '') {
$json = $this->request->getJSON(true);
if (is_array($json)) {
$email = trim((string) ($json['email'] ?? ''));
$mobile = trim((string) ($json['mobile'] ?? ''));
}
}
if ($this->request->isAJAX() || str_contains((string) $this->request->getHeaderLine('Accept'), 'application/json')) {
$result = LoginDiagnosticHelper::diagnose(
$email !== '' ? $email : null,
$mobile !== '' ? $mobile : null
);
return $this->response->setJSON($result);
}
$query = [];
if ($email !== '') {
$query['email'] = $email;
}
if ($mobile !== '') {
$query['mobile'] = $mobile;
}
return redirect()->to(base_url('security/login-diagnostic?' . http_build_query($query)));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -317,8 +317,7 @@ if(!function_exists('check_si'))
$return_array['error'] = !empty($return_array['error']) ? ($return_array['error'].', '.'Sum insured not configured for this age slab') : 'Sum insured not configured for this age slab';
}
if(empty($received_si))
{
if (isset($received_si) && trim((string) $received_si) === '') {
$return_array['status'] = false;
$return_array['error'] = "Sum insured value mandantory";
}

View File

@ -0,0 +1,632 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= esc($title ?? 'Employee Login Diagnostic') ?></title>
<style>
:root{
--bg: #F6F8F9;
--surface: #FFFFFF;
--border: #E3E7EA;
--border-strong: #CBD2D8;
--text-primary: #16212B;
--text-secondary: #5B6770;
--text-muted: #8B959C;
--teal: #0E8F9C;
--teal-dark: #0A6B75;
--teal-tint: #E7F5F6;
--success-bg: #E4F5EC;
--success-border: #B9E3CC;
--success-text: #1B7A4C;
--warn-bg: #FFF6E0;
--warn-border: #F0D9A0;
--warn-text: #8A5B00;
--danger-bg: #FDECEE;
--danger-border: #F0C4CB;
--danger-text: #9B1C3A;
--danger-code: #C43D6E;
--low-badge-bg: #EDEFF1;
--low-badge-text: #4A545C;
--med-badge-bg: #FFF3CD;
--med-badge-text: #664D03;
--high-badge-bg: #F8D7DA;
--high-badge-text: #842029;
--mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
--sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--radius-sm: 6px;
--radius-md: 10px;
}
*{box-sizing:border-box;}
body{
margin:0;
background:var(--bg);
font-family:var(--sans);
color:var(--text-primary);
padding:32px 40px 64px;
}
.wrap{max-width:1320px;margin:0 auto;}
.page-head{margin-bottom:24px;}
.page-head h1{font-size:22px;font-weight:600;margin:0 0 4px;letter-spacing:-0.01em;}
.page-head p{margin:0;font-size:13.5px;color:var(--text-secondary);}
.page-head p b{color:var(--text-primary);font-weight:600;}
.card{
background:var(--surface);
border:1px solid var(--border);
border-radius:var(--radius-md);
}
.search-card{padding:20px 24px;margin-bottom:16px;}
.search-grid{display:grid;grid-template-columns:1fr 1fr auto auto;gap:16px;align-items:end;}
.field label{display:block;font-size:12.5px;font-weight:600;color:var(--text-primary);margin-bottom:6px;}
.field label span{font-weight:400;color:var(--text-muted);font-family:var(--mono);font-size:11.5px;margin-left:2px;}
.field input{
width:100%;height:38px;border:1px solid var(--border-strong);border-radius:var(--radius-sm);
padding:0 12px;font-size:13.5px;font-family:var(--sans);color:var(--text-primary);
background:#FCFDFD;
}
.field input::placeholder{color:var(--text-muted);}
.field input:focus{outline:none;border-color:var(--teal);box-shadow:0 0 0 3px rgba(14,143,156,0.12);}
.btn{
height:38px;padding:0 20px;border-radius:var(--radius-sm);font-size:13.5px;font-weight:600;
border:1px solid transparent;cursor:pointer;white-space:nowrap;text-decoration:none;
display:inline-flex;align-items:center;justify-content:center;
}
.btn-primary{background:var(--teal);color:#fff;}
.btn-primary:hover{background:var(--teal-dark);}
.btn-ghost{background:#fff;border-color:var(--border-strong);color:var(--text-primary);}
.btn-ghost:hover{background:#F5F6F7;}
.search-hint{grid-column:1/-1;font-size:12px;color:var(--text-muted);margin:2px 0 0;}
.search-hint b{color:var(--text-secondary);font-weight:600;}
.status-banner{
display:flex;align-items:center;justify-content:space-between;
border-radius:var(--radius-md);padding:16px 22px;margin-bottom:16px;
}
.status-ok{background:var(--success-bg);border:1px solid var(--success-border);}
.status-ok .txt strong{color:var(--success-text);}
.status-ok .txt span{color:#2C5F45;}
.status-ok .copy-btn{border-color:var(--success-border);color:var(--success-text);}
.status-ok .copy-btn:hover{background:#F1FBF6;}
.status-warn,.status-partial{background:var(--warn-bg);border:1px solid var(--warn-border);}
.status-warn .txt strong,.status-partial .txt strong{color:var(--warn-text);}
.status-warn .txt span,.status-partial .txt span{color:#6B4A10;}
.status-warn .copy-btn,.status-partial .copy-btn{border-color:var(--warn-border);color:var(--warn-text);}
.status-fail{background:var(--danger-bg);border:1px solid var(--danger-border);}
.status-fail .txt strong{color:var(--danger-text);}
.status-fail .txt span{color:#7A2A3A;}
.status-fail .copy-btn{border-color:var(--danger-border);color:var(--danger-text);}
.status-banner .txt strong{display:block;font-size:14.5px;font-weight:700;margin-bottom:2px;}
.status-banner .txt span{font-size:13px;}
.status-banner .copy-btn{
background:#fff;font-size:12.5px;font-weight:600;padding:8px 14px;border-radius:var(--radius-sm);
cursor:pointer;border:1px solid;
}
.section-title{
font-size:12.5px;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;
color:var(--text-secondary);padding:14px 20px;background:#F8F9FA;
border:1px solid var(--border);border-bottom:none;border-radius:var(--radius-md) var(--radius-md) 0 0;
}
.section-title span{text-transform:none;font-weight:400;letter-spacing:0;color:var(--text-muted);}
table{width:100%;border-collapse:collapse;}
.problems-table{border:1px solid var(--border);border-radius:0 0 var(--radius-md) var(--radius-md);overflow:hidden;margin-bottom:20px;background:#fff;}
.problems-table th{
text-align:left;font-size:12px;font-weight:700;color:var(--text-secondary);
padding:10px 20px;border-bottom:1px solid var(--border);background:#fff;
}
.problems-table td{
padding:12px 20px;font-size:13.5px;border-bottom:1px solid var(--border);
border-left:3px solid var(--teal);vertical-align:middle;
}
.problems-table tr:last-child td{border-bottom:none;}
.problems-table tr:nth-child(even) td{background:#FAFBFC;}
.problems-table .empty{border-left-color:transparent;color:var(--text-muted);text-align:center;}
.badge{
display:inline-block;font-size:11px;font-weight:700;padding:3px 9px;border-radius:4px;letter-spacing:0.03em;
}
.badge-low{background:var(--low-badge-bg);color:var(--low-badge-text);}
.badge-medium{background:var(--med-badge-bg);color:var(--med-badge-text);}
.badge-high{background:var(--high-badge-bg);color:var(--high-badge-text);}
.code{font-family:var(--mono);font-size:12.5px;color:var(--danger-code);font-weight:600;}
.fix-text{color:var(--text-secondary);}
.compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;}
.compare-card{border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;background:#fff;}
.compare-head{
padding:14px 20px;background:#F8F9FA;border-bottom:1px solid var(--border);
font-size:13px;font-weight:700;color:var(--text-primary);
}
.compare-head span{font-weight:400;color:var(--text-muted);}
.kv-table td{padding:10px 20px;font-size:13px;border-bottom:1px solid var(--border);}
.kv-table tr:last-child td{border-bottom:none;}
.kv-table tr:nth-child(even) td{background:#FAFBFC;}
.kv-key{color:var(--text-secondary);font-weight:600;width:44%;}
.kv-val{color:var(--text-primary);font-family:var(--mono);font-size:12.5px;}
.check{color:#1B9C56;font-weight:700;}
.cross{color:var(--danger-code);font-weight:700;}
.val-flag{color:var(--danger-code);font-weight:700;}
.alert-inline{margin:12px 20px;padding:10px 12px;border-radius:var(--radius-sm);background:var(--warn-bg);border:1px solid var(--warn-border);color:var(--warn-text);font-size:12.5px;}
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:0;padding:0 4px;}
.tab{
padding:11px 18px;font-size:13px;font-weight:600;color:var(--text-secondary);
cursor:pointer;border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none;
font-family:var(--sans);
}
.tab.active{color:var(--teal-dark);border-bottom-color:var(--teal);}
.tab:hover:not(.active){color:var(--text-primary);}
.tab-panel{border:1px solid var(--border);border-top:none;border-radius:0 0 var(--radius-md) var(--radius-md);overflow:hidden;background:#fff;display:none;}
.tab-panel.active{display:block;}
.cred-table th{
text-align:left;font-size:12px;font-weight:700;color:var(--text-secondary);
padding:12px 20px;background:#F8F9FA;border-bottom:1px solid var(--border);
}
.cred-table th.center, .cred-table td.center{text-align:left;width:22%;}
.cred-table td{padding:11px 20px;font-size:13.5px;border-bottom:1px solid var(--border);}
.cred-table tr:last-child td{border-bottom:none;}
.cred-table tr:nth-child(even) td{background:#FAFBFC;}
.cred-key{font-weight:600;color:var(--text-primary);}
.val-yes{color:#1B9C56;font-weight:600;}
.val-no{color:var(--text-muted);}
.val-raw{font-family:var(--mono);color:var(--danger-code);font-weight:600;}
.muted{color:var(--text-muted);font-size:13px;padding:16px 20px;}
.dup-wrap{padding:16px 20px;}
.dup-wrap h6{margin:0 0 10px;font-size:13px;font-weight:700;}
.dup-wrap h6 + .dup-table, .dup-wrap .dup-block{margin-bottom:18px;}
.dup-subhead{font-size:12px;font-weight:700;color:var(--text-secondary);margin:14px 0 8px;text-transform:uppercase;letter-spacing:0.03em;}
.dup-subhead .count{font-weight:600;color:var(--text-muted);text-transform:none;letter-spacing:0;}
.dup-table{border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.dup-table th,.dup-table td{padding:8px 12px;font-size:12px;border-bottom:1px solid var(--border);text-align:left;}
.dup-table th{background:#F8F9FA;font-weight:700;color:var(--text-secondary);}
.dup-table tr:last-child td{border-bottom:none;}
.winner{display:inline-block;background:var(--success-bg);color:var(--success-text);font-size:10px;font-weight:700;padding:2px 6px;border-radius:4px;margin-right:4px;}
.dup-badge{display:inline-block;font-size:10px;font-weight:700;padding:2px 6px;border-radius:4px;}
.dup-self{background:var(--danger-bg);color:var(--danger-text);}
.dup-family{background:var(--warn-bg);color:var(--warn-text);}
.row-self-dup td{background:#FDECEE !important;}
.row-family-dup td{background:#FFF8E8 !important;}
.problems-table tr.sev-high td{border-left-color:var(--danger-text);background:#FDF2F4;}
.problems-table tr.sev-medium td{border-left-color:#C9A227;}
.problems-table tr.sev-low td{border-left-color:var(--text-muted);}
.window-open{color:#1B9C56;font-weight:700;}
.window-closed{color:var(--danger-text);font-weight:700;}
.window-pending{color:var(--warn-text);font-weight:700;}
.window-missing{color:var(--text-muted);font-weight:600;}
.policy-wrap{padding:16px 20px;}
.policy-wrap h6{margin:0 0 10px;font-size:13px;font-weight:700;}
.policy-note{font-size:12px;color:var(--text-muted);margin:0 0 12px;}
@media (max-width:900px){
body{padding:20px 16px 40px;}
.search-grid{grid-template-columns:1fr;}
.compare-grid{grid-template-columns:1fr;}
.status-banner{flex-direction:column;align-items:flex-start;gap:12px;}
}
</style>
</head>
<body>
<div class="wrap">
<div class="page-head">
<h1>Employee login diagnostic</h1>
<p><b>PRE</b> (enrollment) + <b>POST</b> (live) &middot; read-only &middot; does not send OTP</p>
</div>
<form method="get" action="<?= base_url('security/login-diagnostic') ?>" class="card search-card">
<div class="search-grid">
<div class="field">
<label>Email <span>email_corporate</span></label>
<input type="text" name="email" value="<?= esc($email ?? '') ?>" placeholder="user@company.com">
</div>
<div class="field">
<label>Mobile</label>
<input type="text" name="mobile" value="<?= esc($mobile ?? '') ?>" placeholder="10-digit mobile">
</div>
<button type="submit" class="btn btn-primary">Diagnose</button>
<a class="btn btn-ghost" href="<?= base_url('security/login-diagnostic') ?>">Clear</a>
<p class="search-hint">If both are filled, login logic uses <b>mobile first</b> (same as auth APIs).</p>
</div>
</form>
<?php if (! empty($has_search) && is_array($result ?? null)): ?>
<?php
$v = $result['verdict'] ?? 'WILL_FAIL';
$bannerClass = match ($v) {
'OK' => 'status-ok',
'OK_WITH_WARNINGS' => 'status-warn',
'PARTIAL' => 'status-partial',
default => 'status-fail',
};
$pre = $result['pre'] ?? [];
$post = $result['post'] ?? [];
$merge = $result['merge'] ?? [];
$problems = $result['problems'] ?? [];
$connectivity = $result['connectivity'] ?? [];
$pc = $pre['credentials'] ?? [];
$oc = $post['credentials'] ?? [];
$pt = $pre['token'] ?? [];
$ot = $post['token'] ?? [];
$yn = static fn ($ok) => $ok ? '<span class="val-yes">Yes</span>' : '<span class="val-no">No</span>';
$rawFlag = static function ($val): string {
if ($val === null || $val === '') {
return 'null';
}
return (string) $val;
};
$mark = static fn (bool $ok) => $ok ? '<span class="check">&#10003;</span>' : '<span class="cross">&#10007;</span>';
?>
<div class="status-banner <?= esc($bannerClass) ?>">
<div class="txt">
<strong><?= esc($v) ?></strong>
<span><?= esc($result['verdict_message'] ?? '') ?></span>
</div>
<button type="button" class="copy-btn" id="copyDiagSummary">Copy summary</button>
</div>
<div class="section-title">Problems <span>(failed checks only)</span></div>
<table class="problems-table">
<tr>
<th style="width:80px;">Severity</th>
<th style="width:190px;">Code</th>
<th>Message</th>
<th style="width:340px;">Fix</th>
</tr>
<?php if (empty($problems)): ?>
<tr><td class="empty" colspan="4">No problems detected.</td></tr>
<?php else: ?>
<?php foreach ($problems as $p): ?>
<?php $sev = strtolower((string) ($p['severity'] ?? 'low')); ?>
<tr class="sev-<?= esc($sev) ?>">
<td><span class="badge badge-<?= esc($sev) ?>"><?= esc(strtoupper($sev)) ?></span></td>
<td><span class="code"><?= esc($p['code'] ?? '') ?></span></td>
<td><?= esc($p['message'] ?? '') ?></td>
<td class="fix-text"><?= esc($p['fix'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</table>
<div class="compare-grid">
<div class="compare-card">
<div class="compare-head">PRE <span>(enrollment)</span></div>
<?= view('admin/login_diagnostic_side', ['side' => $pre, 'mark' => $mark]) ?>
</div>
<div class="compare-card">
<div class="compare-head">POST <span>(live)</span></div>
<?= view('admin/login_diagnostic_side', ['side' => $post, 'mark' => $mark]) ?>
</div>
</div>
<div class="tabs" role="tablist">
<button type="button" class="tab active" data-tab="merge">Merge</button>
<button type="button" class="tab" data-tab="dup">Duplicates</button>
<button type="button" class="tab" data-tab="policies">Policies</button>
<button type="button" class="tab" data-tab="wellness">Wellness</button>
<button type="button" class="tab" data-tab="cred">Credentials</button>
<button type="button" class="tab" data-tab="token">Token</button>
<button type="button" class="tab" data-tab="conn">Connectivity</button>
</div>
<div class="tab-panel active" id="panel-merge">
<table class="cred-table">
<tr><td class="cred-key">PRE eligible</td><td><?= ! empty($merge['pre_eligible']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">POST eligible</td><td><?= ! empty($merge['post_eligible']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">PRE short_name</td><td class="val-raw"><?= esc((string) ($merge['pre_short_name'] ?? '')) ?></td></tr>
<tr><td class="cred-key">POST short_name</td><td class="val-raw"><?= esc((string) ($merge['post_short_name'] ?? '')) ?></td></tr>
<tr><td class="cred-key">short_name match</td><td><?= isset($merge['short_name_match']) ? ($merge['short_name_match'] ? 'Yes' : 'No') : '—' ?></td></tr>
<tr><td class="cred-key">PRE created_at</td><td class="val-raw"><?= esc((string) ($merge['pre_created_at'] ?? '—')) ?></td></tr>
<tr><td class="cred-key">POST created_at</td><td class="val-raw"><?= esc((string) ($merge['post_created_at'] ?? '—')) ?></td></tr>
<tr><td class="cred-key">Would return both</td><td><?= ! empty($merge['would_return_both']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">Winner</td><td><b><?= esc((string) ($merge['winner'] ?? '—')) ?></b></td></tr>
<tr><td class="cred-key">OTP sync possible</td><td><?= ! empty($merge['otp_sync_possible']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">Reason</td><td class="fix-text"><?= esc((string) ($merge['reason'] ?? '')) ?></td></tr>
</table>
</div>
<div class="tab-panel" id="panel-dup">
<div class="dup-wrap">
<?php
$preDup = $pre['duplicates'] ?? [];
$postDup = $post['duplicates'] ?? [];
$renderSideDups = static function (string $title, array $side, array $dup): void {
$selfRows = $dup['self_rows'] ?? [];
$familyRows = $dup['family_rows'] ?? [];
// Fallback if older payload
if ($selfRows === [] && $familyRows === [] && ! empty($side['raw_matches'])) {
foreach ($side['raw_matches'] as $r) {
if (strtolower(trim((string) ($r['relationship'] ?? ''))) === 'self') {
$selfRows[] = $r;
} else {
$familyRows[] = $r;
}
}
}
?>
<div class="dup-block">
<h6><?= esc($title) ?></h6>
<div class="dup-subhead">Self <span class="count">(<?= count($selfRows) ?>)</span></div>
<?= view('admin/login_diagnostic_matches', ['rows' => $selfRows]) ?>
<div class="dup-subhead">Family <span class="count">(<?= count($familyRows) ?>)</span></div>
<?= view('admin/login_diagnostic_matches', ['rows' => $familyRows]) ?>
</div>
<?php
};
?>
<?php $renderSideDups('PRE raw matches', $pre, $preDup); ?>
<?php if (! empty($pre['raw_by_personal'])): ?>
<div class="dup-block">
<h6>PRE matches on email_personal only</h6>
<?= view('admin/login_diagnostic_matches', ['rows' => $pre['raw_by_personal']]) ?>
</div>
<?php endif; ?>
<?php $renderSideDups('POST raw matches', $post, $postDup); ?>
</div>
</div>
<div class="tab-panel" id="panel-policies">
<div class="policy-wrap">
<p class="policy-note">PRE: enrollment open/close on <code>employee_polices</code>. POST: live policy mapping. Wellness details are on the <b>Wellness</b> tab (POST only).</p>
<?php if (! empty($pre['policies_error'])): ?>
<div class="alert-inline">PRE policies error: <?= esc($pre['policies_error']) ?></div>
<?php endif; ?>
<?php if (! empty($post['policies_error'])): ?>
<div class="alert-inline">POST policies error: <?= esc($post['policies_error']) ?></div>
<?php endif; ?>
<?php
$windowClass = static function (string $w): string {
return match ($w) {
'OPEN' => 'window-open',
'CLOSED' => 'window-closed',
'NOT_STARTED' => 'window-pending',
default => 'window-missing',
};
};
$renderPolicies = static function (string $title, array $policies, bool $showEnrollment) use ($windowClass): void {
?>
<div class="dup-block">
<h6><?= esc($title) ?> <span class="count">(<?= count($policies) ?>)</span></h6>
<?php if (empty($policies)): ?>
<p class="muted" style="padding:0;">None</p>
<?php else: ?>
<div class="dup-table">
<table>
<thead>
<tr>
<th>Emp policy</th>
<th>Client policy</th>
<th>Policy no</th>
<th>EP status</th>
<th>EP active</th>
<th>Policy period</th>
<?php if ($showEnrollment): ?>
<th>Enrollment open</th>
<th>Enrollment close</th>
<th>Window</th>
<?php else: ?>
<th>CP open_for_enrollment</th>
<th>CP open/close</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($policies as $pol): ?>
<?php
$w = (string) ($pol['enrollment_window'] ?? '');
$rowClass = ($showEnrollment && in_array($w, ['CLOSED', 'NOT_STARTED', 'MISSING'], true)) ? 'row-family-dup' : '';
if ($showEnrollment && $w === 'CLOSED') {
$rowClass = 'row-self-dup';
}
?>
<tr class="<?= esc($rowClass) ?>">
<td><?= esc((string) ($pol['emp_policy_id'] ?? '')) ?></td>
<td><?= esc((string) ($pol['client_policy_id'] ?? '')) ?></td>
<td><?= esc((string) ($pol['policy_no'] ?? '—')) ?></td>
<td><?= esc((string) ($pol['emp_policy_status'] ?? '')) ?></td>
<td><?= esc((string) ($pol['emp_policy_is_active'] ?? '')) ?></td>
<td><?= esc((string) (($pol['policy_start_date'] ?? '—') . ' → ' . ($pol['policy_end_date'] ?? '—'))) ?></td>
<?php if ($showEnrollment): ?>
<td class="val-raw"><?= esc((string) ($pol['enrollment_open_date'] ?? 'null')) ?></td>
<td class="val-raw"><?= esc((string) ($pol['enrollment_close_date'] ?? 'null')) ?></td>
<td class="<?= esc($windowClass($w)) ?>"><?= esc($w) ?></td>
<?php else: ?>
<td><?= esc((string) ($pol['open_for_enrollment'] ?? '—')) ?></td>
<td><?= esc((string) (($pol['client_open_date'] ?? '—') . ' → ' . ($pol['client_close_date'] ?? '—'))) ?></td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?php
};
$renderPolicies('PRE employee policies (enrollment dates)', $pre['policies'] ?? [], true);
$renderPolicies('POST employee policies (live mapping)', $post['policies'] ?? [], false);
?>
</div>
</div>
<div class="tab-panel" id="panel-wellness">
<div class="policy-wrap">
<p class="policy-note"><b>POST only.</b> Wellness SSO needs <code>wellness_onboard</code> not null and not <code>0</code> (same as <code>getWellnessUrl</code>). PRE has no wellness fields.</p>
<?php
$postPolicies = $post['policies'] ?? [];
$wellnessReadyCount = 0;
$wellnessPlanCount = 0;
foreach ($postPolicies as $wp) {
if (! empty($wp['wellness_ready'])) {
$wellnessReadyCount++;
}
$pid = $wp['wellness_plan_id'] ?? null;
if ($pid !== null && $pid !== '' && (string) $pid !== '0') {
$wellnessPlanCount++;
}
}
$overallReady = $wellnessReadyCount > 0;
?>
<table class="cred-table" style="margin-bottom:16px;">
<tr>
<td class="cred-key">Overall wellness ready</td>
<td class="<?= $overallReady ? 'window-open' : 'window-closed' ?>">
<?= $overallReady ? 'Yes' : 'No' ?>
<span class="fix-text">(<?= (int) $wellnessReadyCount ?> of <?= count($postPolicies) ?> policies onboarded)</span>
</td>
</tr>
<tr>
<td class="cred-key">Policies with wellness_plan_id</td>
<td><?= (int) $wellnessPlanCount ?></td>
</tr>
<tr>
<td class="cred-key">Rule</td>
<td class="fix-text">Ready = <code>wellness_onboard</code> is not null and not 0</td>
</tr>
</table>
<div class="dup-block">
<h6>POST wellness by policy <span class="count">(<?= count($postPolicies) ?>)</span></h6>
<?php if (empty($postPolicies)): ?>
<p class="muted" style="padding:0;">No POST employee policies to check.</p>
<?php else: ?>
<div class="dup-table">
<table>
<thead>
<tr>
<th>Emp policy</th>
<th>Client policy</th>
<th>Policy no</th>
<th>EP status</th>
<th>EP active</th>
<th>wellness_onboard</th>
<th>Wellness ready</th>
<th>wellness_plan_id</th>
<th>wellness_vendor_id</th>
</tr>
</thead>
<tbody>
<?php foreach ($postPolicies as $pol): ?>
<?php $ready = ! empty($pol['wellness_ready']); ?>
<tr class="<?= $ready ? '' : 'row-self-dup' ?>">
<td><?= esc((string) ($pol['emp_policy_id'] ?? '')) ?></td>
<td><?= esc((string) ($pol['client_policy_id'] ?? '')) ?></td>
<td><?= esc((string) ($pol['policy_no'] ?? '—')) ?></td>
<td><?= esc((string) ($pol['emp_policy_status'] ?? '')) ?></td>
<td><?= esc((string) ($pol['emp_policy_is_active'] ?? '')) ?></td>
<td class="val-raw"><?= esc((string) ($pol['wellness_onboard_raw'] ?? 'null')) ?></td>
<td class="<?= $ready ? 'window-open' : 'window-closed' ?>"><?= $ready ? 'Yes' : 'No' ?></td>
<td class="val-raw"><?= esc((string) (($pol['wellness_plan_id'] ?? '') !== '' && ($pol['wellness_plan_id'] ?? null) !== null ? $pol['wellness_plan_id'] : 'null')) ?></td>
<td class="val-raw"><?= esc((string) (($pol['wellness_vendor_id'] ?? '') !== '' && ($pol['wellness_vendor_id'] ?? null) !== null ? $pol['wellness_vendor_id'] : 'null')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tab-panel" id="panel-cred">
<table class="cred-table">
<tr>
<th></th>
<th class="center">PRE</th>
<th class="center">POST</th>
</tr>
<tr><td class="cred-key">OTP set</td><td class="center"><?= $yn(! empty($pc['otp_set'])) ?></td><td class="center"><?= $yn(! empty($oc['otp_set'])) ?></td></tr>
<tr><td class="cred-key">MPIN set</td><td class="center"><?= $yn(! empty($pc['mpin_set'])) ?></td><td class="center"><?= $yn(! empty($oc['mpin_set'])) ?></td></tr>
<tr><td class="cred-key">MPIN skipped</td><td class="center"><?= $yn(! empty($pc['mpin_skipped'])) ?></td><td class="center"><?= $yn(! empty($oc['mpin_skipped'])) ?></td></tr>
<tr><td class="cred-key">Biometric set</td><td class="center"><?= $yn(! empty($pc['biometric_set'])) ?></td><td class="center"><?= $yn(! empty($oc['biometric_set'])) ?></td></tr>
<tr>
<td class="cred-key">is_biometric_enabled <span style="font-weight:400;color:var(--text-muted);">(raw)</span></td>
<td class="center val-raw"><?= esc($rawFlag($pc['is_biometric_enabled'] ?? null)) ?></td>
<td class="center val-raw"><?= esc($rawFlag($oc['is_biometric_enabled'] ?? null)) ?></td>
</tr>
<tr><td class="cred-key">Password set</td><td class="center"><?= $yn(! empty($pc['password_set'])) ?></td><td class="center"><?= $yn(! empty($oc['password_set'])) ?></td></tr>
</table>
</div>
<div class="tab-panel" id="panel-token">
<table class="cred-table">
<tr>
<th></th>
<th class="center">PRE</th>
<th class="center">POST</th>
</tr>
<tr><td class="cred-key">Status</td><td class="center"><?= esc((string) ($pt['status'] ?? '—')) ?></td><td class="center"><?= esc((string) ($ot['status'] ?? '—')) ?></td></tr>
<tr><td class="cred-key">Details</td><td class="center fix-text"><?= esc((string) ($pt['human'] ?? '—')) ?></td><td class="center fix-text"><?= esc((string) ($ot['human'] ?? '—')) ?></td></tr>
<tr><td class="cred-key">TOKENTIMEOUT env</td><td colspan="2"><?= esc((string) ($pt['tokentimeout_env'] ?? $ot['tokentimeout_env'] ?? '—')) ?> seconds</td></tr>
</table>
</div>
<div class="tab-panel" id="panel-conn">
<table class="cred-table">
<tr><td class="cred-key">POST_ENROLLMENT_BASEURL</td><td class="val-raw"><?= esc((string) ($connectivity['base_url'] ?? '')) ?></td></tr>
<tr><td class="cred-key">App-Signature configured</td><td><?= ! empty($connectivity['app_signature']) ? 'Yes' : 'No' ?></td></tr>
<tr><td class="cred-key">POST API reachable</td><td><?= ! empty($connectivity['api_reachable']) ? 'Yes' : 'No' ?> (HTTP <?= esc((string) ($connectivity['api_http_code'] ?? '—')) ?>)</td></tr>
<tr><td class="cred-key">API message</td><td class="fix-text"><?= esc((string) ($connectivity['api_message'] ?? '')) ?></td></tr>
<tr><td class="cred-key">postDB connected</td><td><?= ! empty($connectivity['post_db']) ? 'Yes' : 'No' ?></td></tr>
<?php if (! empty($connectivity['post_db_message'])): ?>
<tr><td class="cred-key">postDB error</td><td class="fix-text"><?= esc((string) $connectivity['post_db_message']) ?></td></tr>
<?php endif; ?>
</table>
</div>
<script>
(function () {
var tabs = document.querySelectorAll('.tab');
var panels = {
merge: document.getElementById('panel-merge'),
dup: document.getElementById('panel-dup'),
policies: document.getElementById('panel-policies'),
wellness: document.getElementById('panel-wellness'),
cred: document.getElementById('panel-cred'),
token: document.getElementById('panel-token'),
conn: document.getElementById('panel-conn')
};
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
tabs.forEach(function (t) { t.classList.remove('active'); });
Object.keys(panels).forEach(function (k) { panels[k].classList.remove('active'); });
tab.classList.add('active');
var key = tab.getAttribute('data-tab');
if (panels[key]) panels[key].classList.add('active');
});
});
var btn = document.getElementById('copyDiagSummary');
if (btn) {
btn.addEventListener('click', function () {
var lines = [];
lines.push('Verdict: <?= esc($v, 'js') ?>');
lines.push('<?= esc($result['verdict_message'] ?? '', 'js') ?>');
lines.push('Input email=<?= esc($result['input']['email'] ?? '', 'js') ?> mobile=<?= esc($result['input']['mobile'] ?? '', 'js') ?>');
lines.push('Merge winner: <?= esc($merge['winner'] ?? '', 'js') ?>');
<?php foreach ($problems as $p): ?>
lines.push('[<?= esc(strtoupper((string) ($p['severity'] ?? '')), 'js') ?>] <?= esc($p['code'] ?? '', 'js') ?>: <?= esc($p['message'] ?? '', 'js') ?>');
<?php endforeach; ?>
navigator.clipboard.writeText(lines.join('\n')).then(function () {
btn.textContent = 'Copied';
setTimeout(function () { btn.textContent = 'Copy summary'; }, 1500);
});
});
}
})();
</script>
<?php elseif (! empty($has_search)): ?>
<div class="status-banner status-fail">
<div class="txt">
<strong>WILL_FAIL</strong>
<span>No diagnostic result returned.</span>
</div>
</div>
<?php endif; ?>
</div>
</body>
</html>

View File

@ -0,0 +1,62 @@
<?php /** @var array $rows */ ?>
<?php if (empty($rows)): ?>
<p class="muted" style="padding:0;">None</p>
<?php else: ?>
<div class="dup-table">
<table>
<thead>
<tr>
<th></th>
<th>ID</th>
<th>Client</th>
<th>Emp code</th>
<th>Name</th>
<th>Rel</th>
<th>Status</th>
<th>Active</th>
<th>Mobile</th>
<th>Email corp</th>
<th>Policies</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<?php
$rowClass = '';
if (! empty($row['is_self_duplicate']) || ! empty($row['is_cross_self_dup'])) {
$rowClass = 'row-self-dup';
} elseif (! empty($row['is_family_duplicate'])) {
$rowClass = 'row-family-dup';
}
?>
<tr class="<?= esc($rowClass) ?>">
<td>
<?php if (! empty($row['is_login_winner'])): ?>
<span class="winner">WINNER</span>
<?php endif; ?>
<?php if (! empty($row['dup_badge'])): ?>
<span class="dup-badge <?= ! empty($row['is_self_duplicate']) || ! empty($row['is_cross_self_dup']) ? 'dup-self' : 'dup-family' ?>">
<?= esc((string) $row['dup_badge']) ?>
</span>
<?php elseif (! empty($row['is_login_pick_if_filters_passed'])): ?>
<span class="winner" style="background:#EDEFF1;color:#4A545C;">highest id</span>
<?php endif; ?>
</td>
<td><?= esc((string) ($row['id'] ?? '')) ?></td>
<td><?= esc((string) ($row['client_id'] ?? '')) ?></td>
<td><?= esc((string) ($row['emp_code'] ?? '')) ?></td>
<td><?= esc((string) ($row['name'] ?? '')) ?></td>
<td><?= esc((string) ($row['relationship'] ?? '')) ?></td>
<td><?= esc((string) ($row['emp_status'] ?? '')) ?></td>
<td><?= esc((string) ($row['is_active'] ?? '')) ?></td>
<td><?= esc((string) ($row['mobile'] ?? '')) ?></td>
<td><?= esc((string) ($row['email_corporate'] ?? '')) ?></td>
<td><?= esc((string) ($row['policies'] ?? '—')) ?></td>
<td><?= esc((string) ($row['created_at'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>

View File

@ -0,0 +1,48 @@
<?php
/** @var array $side */
/** @var callable $mark */
$fs = $side['filters_summary'] ?? [];
$eligible = $side['eligible'] ?? null;
$error = $side['error'] ?? null;
?>
<?php if (! empty($error)): ?>
<div class="alert-inline"><?= esc($error) ?></div>
<?php endif; ?>
<table class="kv-table">
<tr>
<td class="kv-key">Raw found</td>
<td class="kv-val">
<?= ! empty($side['raw_matches'])
? $mark(true) . ' Yes (' . count($side['raw_matches']) . ')'
: $mark(false) . ' No' ?>
</td>
</tr>
<tr>
<td class="kv-key">Eligible for login</td>
<td class="kv-val"><?= ! empty($eligible) ? $mark(true) . ' Yes' : $mark(false) . ' No' ?></td>
</tr>
<tr><td class="kv-key">self</td><td class="kv-val"><?= isset($fs['self']) ? $mark((bool) $fs['self']) : '—' ?></td></tr>
<tr><td class="kv-key">is_active</td><td class="kv-val"><?= isset($fs['is_active']) ? $mark((bool) $fs['is_active']) : '—' ?></td></tr>
<tr><td class="kv-key">emp_status OK</td><td class="kv-val"><?= isset($fs['emp_status_ok']) ? $mark((bool) $fs['emp_status_ok']) : '—' ?></td></tr>
<tr><td class="kv-key">has policy</td><td class="kv-val"><?= isset($fs['has_policy']) ? $mark((bool) $fs['has_policy']) : '—' ?></td></tr>
<tr><td class="kv-key">policy status OK</td><td class="kv-val"><?= isset($fs['policy_status_ok']) ? $mark((bool) $fs['policy_status_ok']) : '—' ?></td></tr>
<tr><td class="kv-key">client_id</td><td class="kv-val"><?= esc((string) ($side['client_id'] ?? ($eligible['client_id'] ?? '—'))) ?></td></tr>
<tr><td class="kv-key">short_name</td><td class="kv-val val-flag"><?= esc((string) ($side['client_short_name'] ?? '')) ?></td></tr>
<?php if (! empty($eligible)): ?>
<tr><td class="kv-key">employee id</td><td class="kv-val"><?= esc((string) ($eligible['id'] ?? '')) ?></td></tr>
<tr><td class="kv-key">emp_code</td><td class="kv-val"><?= esc((string) ($eligible['emp_code'] ?? '')) ?></td></tr>
<tr><td class="kv-key">name</td><td class="kv-val"><?= esc((string) ($eligible['name'] ?? '')) ?></td></tr>
<tr><td class="kv-key">emp_status</td><td class="kv-val"><?= esc((string) ($eligible['emp_status'] ?? '')) ?></td></tr>
<tr><td class="kv-key">mobile</td><td class="kv-val"><?= esc((string) ($eligible['mobile'] ?? '')) ?></td></tr>
<tr><td class="kv-key">email_corporate</td><td class="kv-val"><?= esc((string) ($eligible['email_corporate'] ?? '')) ?></td></tr>
<?php endif; ?>
<?php if (! empty($fs['notes'])): ?>
<tr><td class="kv-key">Notes</td><td class="kv-val" style="font-family:var(--sans);color:var(--text-secondary);"><?= esc(implode(' ', $fs['notes'])) ?></td></tr>
<?php endif; ?>
<?php if (! empty($side['eligible_via_api']) && empty($eligible)): ?>
<tr>
<td class="kv-key">API eligible</td>
<td class="kv-val"><span class="check">&#10003;</span> via getPostEmployeeDataForAuth (id <?= esc((string) ($side['eligible_via_api']['id'] ?? '')) ?>)</td>
</tr>
<?php endif; ?>
</table>

View File

@ -393,13 +393,13 @@ input:checked + .slider:before {
Is LGBTQ Enable</label>
</div>
<div class="form-group col-md-4" style="padding-top:50px;padding-bottom: 0px;">
<label for="is_premium_summery" style=" position: relative;bottom: 5px;">
<label class="switch">
<input id="is_premium_summery" type="checkbox" name="is_premium_summery" checked>
<span class="slider round" style="height: 27px;"></span>
</label>
Is Premium Summary Display Enabled </label>
<div class="form-group col-md-4">
<label for="is_premium_summery">Premium Summary Display</label>
<select class="form-control" name="is_premium_summery" id="is_premium_summery">
<option value="0">Disable Premium Summary Display</option>
<option value="1" selected>Enable Premium Summary Display</option>
<option value="2">Enable Premium Total Display</option>
</select>
</div>
</div>
@ -1114,11 +1114,7 @@ $('body').on('click', '.btnPolicyEdit', function() {
$('#is_lgbtq').prop('checked', false);
}
if (res.data.is_premium_summery == 1) {
$('#is_premium_summery').prop('checked', true);
} else {
$('#is_premium_summery').prop('checked', false);
}
$('#is_premium_summery').val(res.data.is_premium_summery ?? 0).trigger('change');
// Member Modify Data Enable or Disable
// if (res.data.is_member_modify_allowed == 1) {
@ -2247,11 +2243,7 @@ function getClientPolicyDataForEdit(client_policy_id) {
$('#is_lgbtq').prop('checked', false);
}
if (res.data.is_premium_summery == 1) {
$('#is_premium_summery').prop('checked', true);
} else {
$('#is_premium_summery').prop('checked', false);
}
$('#is_premium_summery').val(res.data.is_premium_summery ?? 0).trigger('change');
if (res.data.policy_type_id == '4' || res.data.policy_type_id == '5' || res.data.policy_type_id == '72') {