ABHA card: remove official/bridge actions and improve local card data
This commit is contained in:
parent
e89e186281
commit
3de17d955d
|
|
@ -725,7 +725,6 @@ $routes->post('abha/create/communication', 'Abha::communication');
|
|||
$routes->post('abha/create/verify_comm_otp', 'Abha::verifyCommOtp');
|
||||
$routes->post('abha/create/address', 'Abha::address');
|
||||
$routes->get('abha/card/(:segment)', 'Abha::card/$1');
|
||||
$routes->get('abha/card/official/(:segment)', 'Abha::officialCard/$1');
|
||||
$routes->post('abha/register/validate', 'Abha::validateAbha');
|
||||
$routes->get('abha/register/facility_qr', 'Abha::facilityQr');
|
||||
$routes->get('AbdmTaskBoard', 'AbdmTaskBoard::index', ['filter' => $abdmPermFilter]);
|
||||
|
|
|
|||
|
|
@ -377,17 +377,10 @@ class Abha extends BaseController
|
|||
if ($hmsId === '') {
|
||||
$hmsId = 'PID-' . (string) ($patient['id'] ?? '');
|
||||
}
|
||||
|
||||
$officialCardUrl = trim((string) ($hospitalBrand['official_card_url'] ?? ''));
|
||||
if ($officialCardUrl === '') {
|
||||
$officialCardUrl = 'https://abha.abdm.gov.in/abha/v3/';
|
||||
}
|
||||
|
||||
$bridgePortalUrl = trim((string) ($hospitalBrand['bridge_portal_url'] ?? ''));
|
||||
if ($bridgePortalUrl === '') {
|
||||
$bridgePortalUrl = 'https://abdm-bridge.e-atria.in/';
|
||||
}
|
||||
|
||||
$profilePhotoUrl = $this->resolvePatientPhotoUrl($patient);
|
||||
$mobileNo = trim((string) ($patient['mphone1'] ?? ''));
|
||||
$mobileVerified = (int) ($patient['abha_mobile_verified'] ?? 0) === 1;
|
||||
$abhaQrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=' . urlencode($abhaNumClean);
|
||||
$barcodeSvg = $this->buildCode39Svg($hmsId);
|
||||
|
||||
return view('abha/card', [
|
||||
|
|
@ -400,111 +393,13 @@ class Abha extends BaseController
|
|||
'dob' => $dobLabel,
|
||||
'hospital_name' => (string) ($hospitalBrand['hospital_name'] ?? 'E-Atria Hospital'),
|
||||
'hospital_logo_url' => (string) ($hospitalBrand['hospital_logo_url'] ?? base_url('assets/img/logo.png')),
|
||||
'brand_name' => 'E-Atria',
|
||||
'brand_name' => (string) ($hospitalBrand['brand_name'] ?? 'HMS'),
|
||||
'hms_id' => $hmsId,
|
||||
'hms_barcode_svg' => $barcodeSvg,
|
||||
'official_card_url' => $officialCardUrl,
|
||||
'bridge_portal_url' => $bridgePortalUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ABHA Official Card fetch — GET abha/card/official/{abha_number}
|
||||
// Proxies bridge GET /api/v3/abha/card and returns card_data for browser print.
|
||||
// -------------------------------------------------------------------------
|
||||
public function officialCard(string $abhaNumber = '')
|
||||
{
|
||||
$abhaNumClean = preg_replace('/\D/', '', $abhaNumber);
|
||||
if (strlen($abhaNumClean) !== 14) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Invalid ABHA number.',
|
||||
]);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$fields = $db->getFieldNames('patient_master') ?? [];
|
||||
|
||||
$abhaField = null;
|
||||
foreach (['abha_id', 'abha_no', 'abha'] as $f) {
|
||||
if (in_array($f, $fields, true)) {
|
||||
$abhaField = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($abhaField === null && in_array('abha_address', $fields, true)) {
|
||||
$abhaField = 'abha_address';
|
||||
}
|
||||
|
||||
$patient = null;
|
||||
if ($abhaField) {
|
||||
$patient = $db->table('patient_master')->where($abhaField, $abhaNumClean)->get()->getRowArray();
|
||||
if (! $patient) {
|
||||
$patient = $db->table('patient_master')->where($abhaField, $abhaNumber)->get()->getRowArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (! $patient) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Patient not found for this ABHA number.',
|
||||
]);
|
||||
}
|
||||
|
||||
$runtime = $this->resolveBridgeRuntimeSettings();
|
||||
if (($runtime['ok'] ?? 0) !== 1) {
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => (string) ($runtime['error_text'] ?? 'Bridge settings are incomplete.'),
|
||||
]);
|
||||
}
|
||||
|
||||
$query = [
|
||||
'abha_number' => $abhaNumClean,
|
||||
'patient_id' => (int) ($patient['id'] ?? 0),
|
||||
'abha_address' => trim((string) ($patient['abha_address'] ?? '')),
|
||||
'hfr_id' => (string) ($runtime['hfr_id'] ?? ''),
|
||||
];
|
||||
|
||||
$result = $this->bridgeGet(
|
||||
(string) ($runtime['url'] ?? ''),
|
||||
(string) ($runtime['token'] ?? ''),
|
||||
'/api/v3/abha/card',
|
||||
$query
|
||||
);
|
||||
|
||||
if (($result['ok'] ?? 0) !== 1) {
|
||||
$httpCode = (int) ($result['http_code'] ?? 502);
|
||||
if ($httpCode < 100 || $httpCode > 599) {
|
||||
$httpCode = 502;
|
||||
}
|
||||
return $this->response->setStatusCode($httpCode)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => (string) ($result['error_text'] ?? 'Failed to fetch official ABHA card from bridge.'),
|
||||
'bridge_http_code' => (int) ($result['http_code'] ?? 0),
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = $result['data'] ?? $result;
|
||||
$cardData = trim((string) ($payload['card_data'] ?? $payload['cardData'] ?? ''));
|
||||
if ($cardData === '') {
|
||||
return $this->response->setStatusCode(502)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Bridge response does not contain card_data.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (stripos($cardData, 'data:image') === 0) {
|
||||
$parts = explode(',', $cardData, 2);
|
||||
$cardData = $parts[1] ?? '';
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'card_data' => $cardData,
|
||||
'abha_number' => $abhaNumClean,
|
||||
'patient_id' => (int) ($patient['id'] ?? 0),
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
'profile_photo_url' => $profilePhotoUrl,
|
||||
'abha_qr_url' => $abhaQrUrl,
|
||||
'patient_mobile' => $mobileNo,
|
||||
'mobile_verified' => $mobileVerified,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -516,8 +411,7 @@ class Abha extends BaseController
|
|||
$branding = [
|
||||
'hospital_name' => '',
|
||||
'hospital_logo_url' => base_url('assets/img/logo.png'),
|
||||
'official_card_url' => '',
|
||||
'bridge_portal_url' => '',
|
||||
'brand_name' => 'HMS',
|
||||
];
|
||||
|
||||
try {
|
||||
|
|
@ -529,12 +423,11 @@ class Abha extends BaseController
|
|||
$rows = $db->table('hospital_setting')
|
||||
->select('s_name, s_value')
|
||||
->whereIn('s_name', [
|
||||
'H_Name',
|
||||
'H_logo',
|
||||
'HOSPITAL_NAME',
|
||||
'HOSPITAL_DISPLAY_NAME',
|
||||
'HOSPITAL_TITLE',
|
||||
'ABDM_OFFICIAL_CARD_URL',
|
||||
'ABDM_BRIDGE_PORTAL_URL',
|
||||
'EATRIA_BRIDGE_URL',
|
||||
])
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
|
|
@ -549,26 +442,20 @@ class Abha extends BaseController
|
|||
$settings[$key] = trim((string) ($row['s_value'] ?? ''));
|
||||
}
|
||||
|
||||
foreach (['HOSPITAL_NAME', 'HOSPITAL_DISPLAY_NAME', 'HOSPITAL_TITLE'] as $nameKey) {
|
||||
foreach (['H_Name', 'HOSPITAL_NAME', 'HOSPITAL_DISPLAY_NAME', 'HOSPITAL_TITLE'] as $nameKey) {
|
||||
if (! empty($settings[$nameKey])) {
|
||||
$branding['hospital_name'] = (string) $settings[$nameKey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($settings['ABDM_OFFICIAL_CARD_URL'])) {
|
||||
$branding['official_card_url'] = (string) $settings['ABDM_OFFICIAL_CARD_URL'];
|
||||
$logoName = trim((string) ($settings['H_logo'] ?? ''));
|
||||
if ($logoName !== '') {
|
||||
$branding['hospital_logo_url'] = base_url('assets/images/' . rawurlencode($logoName));
|
||||
}
|
||||
|
||||
if (! empty($settings['ABDM_BRIDGE_PORTAL_URL'])) {
|
||||
$branding['bridge_portal_url'] = (string) $settings['ABDM_BRIDGE_PORTAL_URL'];
|
||||
} elseif (! empty($settings['EATRIA_BRIDGE_URL'])) {
|
||||
$parsed = parse_url((string) $settings['EATRIA_BRIDGE_URL']);
|
||||
$scheme = (string) ($parsed['scheme'] ?? 'https');
|
||||
$host = (string) ($parsed['host'] ?? '');
|
||||
if ($host !== '') {
|
||||
$branding['bridge_portal_url'] = $scheme . '://' . $host . '/';
|
||||
}
|
||||
if ($branding['hospital_name'] !== '') {
|
||||
$branding['brand_name'] = $branding['hospital_name'];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Keep default branding if settings lookup fails.
|
||||
|
|
@ -582,131 +469,53 @@ class Abha extends BaseController
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
* @param array<string,mixed> $patient
|
||||
*/
|
||||
private function resolveBridgeRuntimeSettings(): array
|
||||
private function resolvePatientPhotoUrl(array $patient): string
|
||||
{
|
||||
$settings = [
|
||||
'url' => rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/'),
|
||||
'token' => trim((string) (getenv('EATRIA_BRIDGE_TOKEN') ?: '')),
|
||||
'hfr_id' => trim((string) (getenv('ABDM_HFR_ID') ?: '')),
|
||||
];
|
||||
$photoPath = '/assets/images/no_image.svg';
|
||||
|
||||
try {
|
||||
$db = \Config\Database::connect();
|
||||
if ($db->tableExists('hospital_setting')) {
|
||||
$rows = $db->table('hospital_setting')
|
||||
->select('s_name,s_value')
|
||||
->whereIn('s_name', ['EATRIA_BRIDGE_URL', 'EATRIA_BRIDGE_TOKEN', 'ABDM_HFR_ID', 'H_HFR_ID'])
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$key = trim((string) ($row['s_name'] ?? ''));
|
||||
$val = trim((string) ($row['s_value'] ?? ''));
|
||||
if ($key === 'EATRIA_BRIDGE_URL' && $val !== '') {
|
||||
$settings['url'] = rtrim($val, '/');
|
||||
}
|
||||
if ($key === 'EATRIA_BRIDGE_TOKEN' && $val !== '') {
|
||||
$settings['token'] = $val;
|
||||
}
|
||||
if ($key === 'ABDM_HFR_ID' && $val !== '') {
|
||||
$settings['hfr_id'] = $val;
|
||||
}
|
||||
if ($key === 'H_HFR_ID' && $val !== '' && (string) $settings['hfr_id'] === '') {
|
||||
$settings['hfr_id'] = $val;
|
||||
$profileFileId = (int) ($patient['profile_file_id'] ?? 0);
|
||||
if ($profileFileId > 0) {
|
||||
try {
|
||||
$db = \Config\Database::connect();
|
||||
if ($db->tableExists('file_upload_data')) {
|
||||
$fileRow = $db->table('file_upload_data')
|
||||
->select('full_path')
|
||||
->where('id', $profileFileId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
$fullPath = trim((string) ($fileRow['full_path'] ?? ''));
|
||||
if ($fullPath !== '') {
|
||||
$pos = strpos($fullPath, '/uploads/', 1);
|
||||
if ($pos !== false) {
|
||||
return substr($fullPath, $pos);
|
||||
}
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fall through to next source.
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Keep env/default values when DB lookup fails.
|
||||
}
|
||||
|
||||
if (stripos((string) $settings['token'], 'Bearer ') === 0) {
|
||||
$settings['token'] = trim(substr((string) $settings['token'], 7));
|
||||
}
|
||||
|
||||
if ((string) $settings['url'] === '') {
|
||||
return ['ok' => 0, 'error_text' => 'EATRIA_BRIDGE_URL is missing.'];
|
||||
}
|
||||
if ((string) $settings['token'] === '') {
|
||||
return ['ok' => 0, 'error_text' => 'EATRIA_BRIDGE_TOKEN is missing.'];
|
||||
}
|
||||
if ((string) $settings['hfr_id'] === '') {
|
||||
return ['ok' => 0, 'error_text' => 'ABDM_HFR_ID is missing.'];
|
||||
}
|
||||
|
||||
return ['ok' => 1] + $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $query
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function bridgeGet(string $baseUrl, string $token, string $path, array $query = []): array
|
||||
{
|
||||
$base = rtrim($baseUrl, '/');
|
||||
$normalizedPath = '/' . ltrim($path, '/');
|
||||
|
||||
$baseHasApiSuffix = (bool) preg_match('#/api$#i', $base);
|
||||
$pathHasApiPrefix = str_starts_with($normalizedPath, '/api/');
|
||||
if ($baseHasApiSuffix && $pathHasApiPrefix) {
|
||||
$normalizedPath = substr($normalizedPath, 4);
|
||||
}
|
||||
|
||||
$url = $base . $normalizedPath;
|
||||
if ($query !== []) {
|
||||
$url .= '?' . http_build_query($query);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = service('curlrequest', [
|
||||
'timeout' => 30,
|
||||
'connect_timeout' => 10,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
|
||||
$response = $client->get($url, [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . trim($token),
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
$httpCode = (int) $response->getStatusCode();
|
||||
$decoded = json_decode((string) $response->getBody(), true);
|
||||
if (! is_array($decoded)) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'http_code' => $httpCode,
|
||||
'error_text' => 'Non-JSON response from bridge.',
|
||||
];
|
||||
$profilePicture = trim((string) ($patient['profile_picture'] ?? ''));
|
||||
if ($profilePicture !== '') {
|
||||
$pos = strpos($profilePicture, '/uploads/', 1);
|
||||
if ($pos !== false) {
|
||||
return substr($profilePicture, $pos);
|
||||
}
|
||||
|
||||
$ok = (int) ($decoded['ok'] ?? (($httpCode >= 200 && $httpCode < 300) ? 1 : 0));
|
||||
if ($ok !== 1) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'http_code' => $httpCode,
|
||||
'error_text' => (string) ($decoded['error_text'] ?? $decoded['message'] ?? $decoded['error'] ?? 'Bridge request failed.'),
|
||||
'data' => $decoded,
|
||||
'request_id' => (string) ($decoded['request_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'http_code' => $httpCode,
|
||||
'data' => $decoded,
|
||||
'request_id' => (string) ($decoded['request_id'] ?? ''),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'http_code' => 0,
|
||||
'error_text' => $e->getMessage(),
|
||||
];
|
||||
return $profilePicture;
|
||||
}
|
||||
|
||||
$abhaPhotoBase64 = trim((string) ($patient['abha_profile_photo_base64'] ?? ''));
|
||||
if ($abhaPhotoBase64 !== '') {
|
||||
return str_starts_with($abhaPhotoBase64, 'data:image')
|
||||
? $abhaPhotoBase64
|
||||
: 'data:image/jpeg;base64,' . $abhaPhotoBase64;
|
||||
}
|
||||
|
||||
return $photoPath;
|
||||
}
|
||||
|
||||
private function buildCode39Svg(string $value): string
|
||||
|
|
|
|||
|
|
@ -3,489 +3,353 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ABHA Card — <?= esc($patient['p_fname'] ?? '') ?></title>
|
||||
<title>ABHA Card - <?= esc($patient['p_fname'] ?? '') ?></title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background: #f2f6fb;
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
background: linear-gradient(145deg, #eef4ff 0%, #f6f9ff 45%, #edf7ff 100%);
|
||||
min-height: 100vh;
|
||||
padding: 24px 12px;
|
||||
color: #1f2e44;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 30px 14px;
|
||||
color: #1f2937;
|
||||
}
|
||||
.abha-card {
|
||||
width: 430px;
|
||||
.abha-shell {
|
||||
width: 450px;
|
||||
max-width: 100%;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d7e3f3;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
box-shadow: 0 8px 24px rgba(15, 40, 77, 0.12);
|
||||
border: 1px solid #d6e3f7;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 14px 34px rgba(16, 48, 86, 0.16);
|
||||
padding: 16px;
|
||||
}
|
||||
.top-strip {
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e5edf9;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 12px;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e4edf8;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.gov-brand {
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.gov-emblem {
|
||||
.nha-tag {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: #0b4fa8;
|
||||
border-radius: 9px;
|
||||
background: #104998;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.gov-text {
|
||||
font-size: 13px;
|
||||
.header-text {
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
color: #123a73;
|
||||
font-weight: 700;
|
||||
color: #0c3d83;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.gov-text span {
|
||||
.header-text span {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #5b6b82;
|
||||
font-weight: 500;
|
||||
}
|
||||
.eatria-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid #d6e3f6;
|
||||
border-radius: 10px;
|
||||
padding: 6px 9px;
|
||||
background: #f8fbff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.eatria-brand img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.eatria-brand .title {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
color: #114a92;
|
||||
line-height: 1.15;
|
||||
}
|
||||
.eatria-brand .title span {
|
||||
display: block;
|
||||
color: #5f6f87;
|
||||
font-size: 10px;
|
||||
color: #5b6f8b;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hospital-name {
|
||||
margin-bottom: 12px;
|
||||
background: #f3f8ff;
|
||||
border: 1px solid #dce9f8;
|
||||
.logo-wrap {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
color: #214268;
|
||||
}
|
||||
.hospital-name strong {
|
||||
color: #0a3f84;
|
||||
}
|
||||
.card-body {
|
||||
border: 1px solid #d5e4f7;
|
||||
background: #f7fbff;
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.logo-wrap img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.hospital-name {
|
||||
border: 1px solid #d5e4f8;
|
||||
background: #f4f9ff;
|
||||
border-radius: 11px;
|
||||
font-size: 13px;
|
||||
color: #1e4a82;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.patient-photo {
|
||||
.hospital-name strong { color: #0f3f7f; }
|
||||
.patient-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 132px;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
.patient-main {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.photo {
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #d5e4f7;
|
||||
border: 3px solid #d9e8fb;
|
||||
object-fit: cover;
|
||||
background: #ecf4ff;
|
||||
flex-shrink: 0;
|
||||
background: #eef5ff;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.patient-photo-placeholder {
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #d5e4f7;
|
||||
background: #ecf4ff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 34px;
|
||||
flex-shrink: 0;
|
||||
color: #3465a7;
|
||||
}
|
||||
.patient-info { flex: 1; min-width: 0; }
|
||||
.patient-details { min-width: 0; }
|
||||
.patient-name {
|
||||
font-size: 21px;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.4px;
|
||||
color: #123f7d;
|
||||
text-transform: uppercase;
|
||||
color: #0f2f5f;
|
||||
line-height: 1.05;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-word;
|
||||
}
|
||||
.patient-detail {
|
||||
.meta {
|
||||
font-size: 13px;
|
||||
color: #425771;
|
||||
margin-bottom: 3px;
|
||||
color: #3d5778;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.abha-number-block {
|
||||
margin-top: 6px;
|
||||
.badge-phone {
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.verified {
|
||||
background: #ddfce8;
|
||||
color: #145c2f;
|
||||
border: 1px solid #a9efc4;
|
||||
}
|
||||
.registered {
|
||||
background: #eef4ff;
|
||||
color: #1f4e95;
|
||||
border: 1px solid #c6daf8;
|
||||
}
|
||||
.qr-card {
|
||||
border: 1px solid #d8e6fb;
|
||||
background: #fbfdff;
|
||||
border-radius: 11px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.qr-title {
|
||||
font-size: 10px;
|
||||
color: #5f7291;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.qr-image {
|
||||
width: 114px;
|
||||
height: 114px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #e0e8f5;
|
||||
background: #fff;
|
||||
}
|
||||
.qr-value {
|
||||
margin-top: 5px;
|
||||
font-size: 10px;
|
||||
color: #3b5474;
|
||||
word-break: break-word;
|
||||
}
|
||||
.abha-number-wrap {
|
||||
margin-top: 12px;
|
||||
border: 1px solid #d3e3f9;
|
||||
background: #edf5ff;
|
||||
border: 1px solid #d5e6fb;
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 11px;
|
||||
padding: 9px 11px;
|
||||
}
|
||||
.abha-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: #5d7292;
|
||||
letter-spacing: 1px;
|
||||
color: #5f7391;
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.abha-number {
|
||||
font-size: 34px;
|
||||
font-weight: 800;
|
||||
font-size: 33px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 3px;
|
||||
color: #0a3d86;
|
||||
line-height: 1.12;
|
||||
color: #0f4186;
|
||||
line-height: 1.1;
|
||||
word-break: break-word;
|
||||
}
|
||||
.barcode-box {
|
||||
.barcode-wrap {
|
||||
margin-top: 12px;
|
||||
border: 1px solid #dbe7f6;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #d7e5f8;
|
||||
border-radius: 11px;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
background: #ffffff;
|
||||
}
|
||||
.barcode-caption {
|
||||
.barcode-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: #5f7291;
|
||||
letter-spacing: 1px;
|
||||
color: #5f7391;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.barcode-visual {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.barcode-id {
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #1e3f69;
|
||||
text-align: center;
|
||||
color: #1d3f69;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.card-footer {
|
||||
.footer {
|
||||
margin-top: 10px;
|
||||
font-size: 10px;
|
||||
color: #637590;
|
||||
text-align: right;
|
||||
font-size: 10px;
|
||||
color: #5f7291;
|
||||
}
|
||||
.actions {
|
||||
margin-top: 22px;
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-print {
|
||||
background: #0b4ea8;
|
||||
background: #104998;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 10px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-print:hover { background: #083d84; }
|
||||
.btn-abdm {
|
||||
background: #fff;
|
||||
color: #0b4ea8;
|
||||
border: 2px solid #0b4ea8;
|
||||
border-radius: 8px;
|
||||
padding: 9px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
.btn-print:hover { background: #0b3f82; }
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.patient-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.qr-card {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
.patient-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
.abha-number {
|
||||
font-size: 27px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
.btn-abdm:hover { background: #e8f0fe; }
|
||||
.bridge-help-btn {
|
||||
background: #f6fbff;
|
||||
color: #0c4b95;
|
||||
border: 1px solid #bdd4f2;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.bridge-help-btn:hover { background: #eaf4ff; }
|
||||
.note {
|
||||
margin-top: 14px;
|
||||
font-size: 12px;
|
||||
color: #4f5f75;
|
||||
max-width: 430px;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.bridge-guide {
|
||||
max-width: 430px;
|
||||
margin-top: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d7e6f8;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: #304760;
|
||||
}
|
||||
.bridge-guide h3 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #0a3f84;
|
||||
}
|
||||
.bridge-guide ol {
|
||||
margin-left: 18px;
|
||||
}
|
||||
.bridge-guide li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.bridge-links {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.bridge-links a {
|
||||
color: #0a4a96;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
.bridge-links a:hover { text-decoration: underline; }
|
||||
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
.abha-card { box-shadow: none; }
|
||||
.actions, .note, .bridge-guide { display: none; }
|
||||
.abha-shell { box-shadow: none; border-color: #d7e5f8; }
|
||||
.actions { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php
|
||||
$name = esc($patient['p_fname'] ?? '');
|
||||
$name = esc($patient['p_fname'] ?? '');
|
||||
$abhaNumDisp = esc($abha_num ?? '');
|
||||
$photoData = !empty($patient['abha_photo']) ? $patient['abha_photo'] : '';
|
||||
$genderDisp = esc($gender ?? '');
|
||||
$dobDisp = esc($dob ?? '');
|
||||
$hospitalName = esc($hospital_name ?? 'E-Atria Hospital');
|
||||
$dobDisp = esc($dob ?? '');
|
||||
$hospitalName = esc($hospital_name ?? 'Hospital');
|
||||
$hospitalLogo = esc($hospital_logo_url ?? base_url('assets/img/logo.png'));
|
||||
$brandName = esc($brand_name ?? 'E-Atria');
|
||||
$profilePhotoUrl = (string) ($profile_photo_url ?? '/assets/images/no_image.svg');
|
||||
$profilePhotoUrlEsc = esc($profilePhotoUrl);
|
||||
$hmsId = esc($hms_id ?? '');
|
||||
$abhaQrUrl = esc($abha_qr_url ?? '');
|
||||
$barcodeSvg = (string) ($hms_barcode_svg ?? '');
|
||||
$abhaRaw = esc($abha_raw ?? '');
|
||||
$officialCardUrl = esc($official_card_url ?? 'https://abha.abdm.gov.in/abha/v3/');
|
||||
$bridgePortalUrl = esc($bridge_portal_url ?? 'https://abdm-bridge.e-atria.in/');
|
||||
$officialCardApiUrl = base_url('abha/card/official/' . ($abhaRaw !== '' ? $abhaRaw : preg_replace('/\D/', '', (string) ($abha_num ?? ''))));
|
||||
$patientMobile = esc($patient_mobile ?? '');
|
||||
$mobileVerified = (bool) ($mobile_verified ?? false);
|
||||
?>
|
||||
|
||||
<div class="abha-card">
|
||||
<div class="top-strip">
|
||||
<div class="gov-brand">
|
||||
<div class="gov-emblem">NHA</div>
|
||||
<div class="gov-text">Ayushman Bharat Digital Mission
|
||||
<div class="abha-shell">
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="nha-tag">NHA</div>
|
||||
<div class="header-text">
|
||||
Ayushman Bharat Digital Mission
|
||||
<span>ABHA Health Account Card</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eatria-brand">
|
||||
<img src="<?= $hospitalLogo ?>" alt="<?= $brandName ?> Logo">
|
||||
<div class="title"><?= $brandName ?><span>Digital Health Network</span></div>
|
||||
<div class="logo-wrap">
|
||||
<img src="<?= $hospitalLogo ?>" alt="Hospital Logo">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hospital-name">
|
||||
Hospital: <strong><?= $hospitalName ?></strong>
|
||||
</div>
|
||||
<div class="hospital-name">Hospital: <strong><?= $hospitalName ?></strong></div>
|
||||
|
||||
<div class="card-body">
|
||||
<?php if ($photoData && str_starts_with($photoData, 'data:image')): ?>
|
||||
<img class="patient-photo" src="<?= $photoData ?>" alt="Patient Photo">
|
||||
<?php elseif ($photoData): ?>
|
||||
<img class="patient-photo" src="data:image/jpeg;base64,<?= esc($photoData) ?>" alt="Patient Photo">
|
||||
<?php else: ?>
|
||||
<div class="patient-photo-placeholder">👤</div>
|
||||
<?php endif; ?>
|
||||
<div class="patient-area">
|
||||
<div>
|
||||
<div class="patient-main">
|
||||
<img class="photo" src="<?= $profilePhotoUrlEsc ?>" alt="Patient Photo">
|
||||
<div class="patient-details">
|
||||
<div class="patient-name"><?= $name !== '' ? $name : 'PATIENT' ?></div>
|
||||
<?php if ($genderDisp !== ''): ?>
|
||||
<div class="meta">Gender: <?= $genderDisp ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($dobDisp !== ''): ?>
|
||||
<div class="meta">DOB: <?= $dobDisp ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($hmsId !== ''): ?>
|
||||
<div class="meta">HMS ID: <?= $hmsId ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($patientMobile !== ''): ?>
|
||||
<div class="badge-phone <?= $mobileVerified ? 'verified' : 'registered' ?>">
|
||||
<?= $mobileVerified ? 'ABHA Verified Mobile' : 'ABHA Registered Mobile' ?>: <?= $patientMobile ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="patient-info">
|
||||
<div class="patient-name"><?= $name ?: '—' ?></div>
|
||||
<?php if ($genderDisp): ?>
|
||||
<div class="patient-detail">Gender: <?= $genderDisp ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($dobDisp): ?>
|
||||
<div class="patient-detail">DOB: <?= $dobDisp ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($hmsId): ?>
|
||||
<div class="patient-detail" style="margin-top:4px;">HMS ID: <?= $hmsId ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="abha-number-wrap">
|
||||
<div class="abha-label">ABHA Number</div>
|
||||
<div class="abha-number"><?= $abhaNumDisp !== '' ? $abhaNumDisp : 'NA' ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="abha-number-block">
|
||||
<div class="abha-label">ABHA Number</div>
|
||||
<div class="abha-number"><?= $abhaNumDisp ?: '—' ?></div>
|
||||
<div class="qr-card">
|
||||
<div class="qr-title">ABHA ID QR</div>
|
||||
<?php if ($abhaQrUrl !== ''): ?>
|
||||
<img class="qr-image" src="<?= $abhaQrUrl ?>" alt="ABHA QR Code">
|
||||
<?php else: ?>
|
||||
<div class="meta">QR unavailable</div>
|
||||
<?php endif; ?>
|
||||
<div class="qr-value"><?= $abhaNumDisp ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($barcodeSvg !== '' && $hmsId !== ''): ?>
|
||||
<div class="barcode-box">
|
||||
<div class="barcode-caption">HMS ID Barcode</div>
|
||||
<div class="barcode-visual"><?= $barcodeSvg ?></div>
|
||||
<div class="barcode-id"><?= $hmsId ?></div>
|
||||
</div>
|
||||
<div class="barcode-wrap">
|
||||
<div class="barcode-label">HMS ID Barcode</div>
|
||||
<div><?= $barcodeSvg ?></div>
|
||||
<div class="barcode-id"><?= $hmsId ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card-footer">Government of India | Ministry of Health & Family Welfare</div>
|
||||
<div class="footer">Government of India | Ministry of Health & Family Welfare</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn-print" onclick="window.print()">Print / Save PDF</button>
|
||||
<button type="button" class="btn-abdm" id="downloadOfficialCardBtn" data-fetch-url="<?= esc($officialCardApiUrl) ?>">
|
||||
Download Official Card
|
||||
</button>
|
||||
<a class="bridge-help-btn" href="<?= $bridgePortalUrl ?>" target="_blank" rel="noopener noreferrer">
|
||||
Open ABDM Bridge Portal
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="note" id="officialCardStatus" style="display:none;"></p>
|
||||
|
||||
<p class="note">
|
||||
This is hospital-branded ABHA print card. For QR-enabled official card, use the ABDM official flow below.
|
||||
</p>
|
||||
|
||||
<div class="bridge-guide">
|
||||
<h3>Official Card Download and Print (via ABDM Bridge assisted flow)</h3>
|
||||
<ol>
|
||||
<li>Click <strong>Download Official Card</strong> to fetch the official PNG card via ABDM Bridge API.</li>
|
||||
<li>A print window opens automatically with the fetched official card image.</li>
|
||||
<li>If bridge session/auth is invalid, open <strong>ABDM Bridge Portal</strong> and complete login/configuration.</li>
|
||||
<li>Retry <strong>Download Official Card</strong> after bridge auth is active.</li>
|
||||
</ol>
|
||||
<div class="bridge-links">
|
||||
<a href="<?= $bridgePortalUrl ?>" target="_blank" rel="noopener noreferrer">ABDM Bridge Portal</a>
|
||||
<a href="<?= $officialCardUrl ?>" target="_blank" rel="noopener noreferrer">Official ABDM Card Website</a>
|
||||
</div>
|
||||
<p style="margin-top:8px;">
|
||||
Official card will be downloaded for ABHA
|
||||
<strong><?= $abhaNumDisp ?></strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var btn = document.getElementById('downloadOfficialCardBtn');
|
||||
var statusEl = document.getElementById('officialCardStatus');
|
||||
if (!btn || !statusEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
function setStatus(msg, isError) {
|
||||
statusEl.style.display = 'block';
|
||||
statusEl.style.color = isError ? '#9f1239' : '#14532d';
|
||||
statusEl.style.background = isError ? '#ffe4e6' : '#dcfce7';
|
||||
statusEl.style.border = '1px solid ' + (isError ? '#fecdd3' : '#bbf7d0');
|
||||
statusEl.style.borderRadius = '8px';
|
||||
statusEl.style.padding = '8px 10px';
|
||||
statusEl.textContent = msg;
|
||||
}
|
||||
|
||||
function openPrintImage(base64Png) {
|
||||
var imageSrc = 'data:image/png;base64,' + base64Png;
|
||||
var w = window.open('', '_blank');
|
||||
if (!w) {
|
||||
setStatus('Popup blocked by browser. Please allow popups and try again.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var html = ''
|
||||
+ '<!DOCTYPE html><html><head><title>Official ABHA Card</title>'
|
||||
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />'
|
||||
+ '<style>body{margin:0;padding:18px;font-family:Arial,sans-serif;background:#fff;text-align:center;}'
|
||||
+ 'img{max-width:100%;height:auto;border:1px solid #d4d4d8;box-shadow:0 4px 14px rgba(0,0,0,.08);}'
|
||||
+ '.hint{margin-top:12px;color:#475569;font-size:12px;}</style></head><body>'
|
||||
+ '<img src="' + imageSrc + '" alt="Official ABHA Card" />'
|
||||
+ '<div class="hint">Use browser print to save or print the official card.</div>'
|
||||
+ '<script>window.onload=function(){window.focus();window.print();};<\/script>'
|
||||
+ '</body></html>';
|
||||
|
||||
w.document.open();
|
||||
w.document.write(html);
|
||||
w.document.close();
|
||||
}
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var url = btn.getAttribute('data-fetch-url') || '';
|
||||
if (!url) {
|
||||
setStatus('Official card endpoint missing. Please contact admin.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fetching...';
|
||||
setStatus('Fetching official card from ABDM Bridge...', false);
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json().then(function(body) {
|
||||
return { ok: res.ok, body: body };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
var body = result.body || {};
|
||||
if (!result.ok || Number(body.ok || 0) !== 1 || !body.card_data) {
|
||||
var err = body.error_text || 'Unable to fetch official card from bridge.';
|
||||
setStatus(err, true);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('Official card fetched. Opening print window...', false);
|
||||
openPrintImage(String(body.card_data));
|
||||
})
|
||||
.catch(function() {
|
||||
setStatus('Network/server error while fetching official card.', true);
|
||||
})
|
||||
.finally(function() {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Download Official Card';
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user