Store ABHA card and improve patient comparison

This commit is contained in:
DevSoftTech Server 2026-08-13 11:30:02 +05:30
parent 6a670d4c48
commit 73ad7ef11f
4 changed files with 94 additions and 2 deletions

View File

@ -474,6 +474,7 @@ class Abha extends BaseController
'abha_qr_url' => $abhaQrUrl,
'patient_mobile' => $mobileNo,
'mobile_verified' => $mobileVerified,
'stored_abha_card' => trim((string) ($patient['abha_card_base64'] ?? '')),
]);
}
@ -803,6 +804,7 @@ class Abha extends BaseController
$abhaMeta = [
'abha_address' => (string) ($this->request->getPost('abha_address') ?? ''),
'profile_photo' => (string) ($this->request->getPost('photo') ?? ''),
'card_base64' => (string) ($this->request->getPost('card_base64') ?? ''),
'verified_status' => (string) ($this->request->getPost('verified_status') ?? ''),
'verification_type' => (string) ($this->request->getPost('verification_type') ?? ''),
'kyc_verified' => $this->request->getPost('kyc_verified'),
@ -1025,7 +1027,7 @@ class Abha extends BaseController
}
$selectFields = array_values(array_intersect(
['id', 'p_code', 'p_fname', 'p_lname', 'gender', 'dob', 'age', 'mphone1', 'udai', 'add1', 'district', 'state', 'zip'],
['id', 'p_code', 'p_fname', 'p_lname', 'gender', 'dob', 'age', 'mphone1', 'udai', 'add1', 'district', 'state', 'zip', 'profile_file_id', 'profile_picture', 'abha_profile_photo_base64'],
$fields
));
$select = implode(',', $selectFields);
@ -1109,6 +1111,7 @@ class Abha extends BaseController
'dob' => $rowDob,
'age' => $rowAgeYears,
'mobile' => (string) ($row['mphone1'] ?? ''),
'photo_url' => $this->resolvePatientPhotoUrl($row),
'address' => trim(implode(', ', array_filter([
(string) ($row['add1'] ?? ''),
(string) ($row['district'] ?? ''),
@ -1391,6 +1394,11 @@ class Abha extends BaseController
if ($profilePhoto !== '' && in_array('abha_profile_photo_base64', $fields, true)) {
$target['abha_profile_photo_base64'] = $profilePhoto;
}
$cardBase64 = trim((string) ($abhaMeta['card_base64'] ?? ''));
if ($cardBase64 !== '' && in_array('abha_card_base64', $fields, true)) {
$target['abha_card_base64'] = $cardBase64;
}
}
private function toDbBool($value): int

View File

@ -0,0 +1,43 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddAbhaCardToPatientMaster extends Migration
{
public function up(): void
{
if (! $this->tableExists('patient_master')) {
return;
}
$fields = $this->getColumns('patient_master');
if (! in_array('abha_card_base64', $fields, true)) {
$this->forge->addColumn('patient_master', [
'abha_card_base64' => [
'type' => 'LONGTEXT',
'null' => true,
],
]);
}
}
public function down(): void
{
if ($this->tableExists('patient_master') && in_array('abha_card_base64', $this->getColumns('patient_master'), true)) {
$this->forge->dropColumn('patient_master', 'abha_card_base64');
}
}
private function getColumns(string $table): array
{
$result = $this->db->query('SHOW COLUMNS FROM `' . $table . '`')->getResultArray();
return array_map(static fn (array $row): string => (string) ($row['Field'] ?? ''), $result);
}
private function tableExists(string $table): bool
{
return ! empty($this->db->query("SHOW TABLES LIKE '" . $table . "'")->getRowArray());
}
}

View File

@ -168,6 +168,8 @@
background: #fff;
}
.qr-value { margin-top: 5px; font-size: 10px; color: #3b5474; word-break: break-word; }
.official-card-wrap { margin-top: 14px; text-align: center; }
.official-card { display: block; width: 100%; max-height: 480px; object-fit: contain; border: 1px solid #d8e6fb; border-radius: 8px; background: #fff; }
.abha-number-wrap {
margin-top: 12px;
border: 1px solid #d3e3f9;
@ -241,6 +243,10 @@ $abhaQrUrl = esc($abha_qr_url ?? '');
$barcodeImage = esc($hms_barcode_image ?? '');
$patientMobile = esc($patient_mobile ?? '');
$mobileVerified = (bool) ($mobile_verified ?? false);
$storedAbhaCard = trim((string) ($stored_abha_card ?? ''));
$storedAbhaCardSrc = $storedAbhaCard !== ''
? (str_starts_with($storedAbhaCard, 'data:') ? $storedAbhaCard : 'data:image/png;base64,' . $storedAbhaCard)
: '';
?>
<div class="abha-shell">
@ -263,6 +269,13 @@ $mobileVerified = (bool) ($mobile_verified ?? false);
</div>
</div>
<?php if ($storedAbhaCardSrc !== ''): ?>
<div class="official-card-wrap">
<div class="meta">Stored official ABHA card</div>
<img class="official-card" src="<?= esc($storedAbhaCardSrc) ?>" alt="Stored official ABHA card">
</div>
<?php endif; ?>
<?php if ($barcodeImage !== '' && $hmsId !== ''): ?>
<div class="hospital-strip">
<div class="patient-id-strip">

View File

@ -60,6 +60,7 @@
<span class="badge bg-secondary" id="abhaMatch_count">0 found</span>
</div>
<div id="abhaMatch_candidates" style="max-height:360px;overflow-y:auto"></div>
<div id="abhaMatch_update_preview" class="alert alert-info py-2 small d-none"></div>
<div id="abhaMatch_empty" class="alert alert-warning py-2 small d-none">
No matching patient found in HMS by name, age or gender. You can create a new patient record.
</div>
@ -118,6 +119,27 @@ window.AbhaPatientMatchModal = (function () {
function alertHtml(type, msg) {
return '<div class="alert alert-' + type + ' py-2">' + msg + '</div>';
}
function profileValue(value) { return String(value == null ? '' : value).trim().toUpperCase(); }
function normalizedGender(value) {
var gender = profileValue(value);
if (gender === '1' || gender === 'M' || gender === 'MALE') return 'M';
if (gender === '2' || gender === 'F' || gender === 'FEMALE') return 'F';
if (gender === '3' || gender === 'O' || gender === 'OTHER') return 'O';
return gender;
}
function renderUpdatePreview(c) {
var fields = [];
var p = _profile || {};
if (profileValue(p.name) !== profileValue(c.name)) fields.push('Name');
if (normalizedGender(p.gender) !== normalizedGender(c.gender_label)) fields.push('Gender');
if (String(p.dob || '').slice(0, 4) !== String(c.dob || '').slice(0, 4)) fields.push('Birth year');
if (String(p.mobile || '').replace(/\D/g, '') !== String(c.mobile || '').replace(/\D/g, '')) fields.push('Mobile');
var abhaAddress = [p.address, p.district, p.state, p.zip].filter(Boolean).join(', ');
if (profileValue(abhaAddress) !== profileValue(c.address)) fields.push('Address');
$('#abhaMatch_update_preview').toggleClass('d-none', !fields.length).html(
fields.length ? '<strong>Update Details will change:</strong> ' + fields.map(esc).join(', ') : '<strong>No HMS demographic changes detected.</strong>'
);
}
function renderProfile(p) {
var abhaNum = p.abha_number || '';
@ -157,7 +179,8 @@ window.AbhaPatientMatchModal = (function () {
+ ' <div class="card-body py-2">'
+ ' <div class="d-flex justify-content-between align-items-start">'
+ ' <div>'
+ ' <div class="fw-semibold">' + esc(c.name || '—') + ' <small class="text-muted">(' + esc(c.p_code || '') + ')</small></div>'
+ (c.photo_url ? ' <img src="' + esc(c.photo_url) + '" alt="Patient photo" class="rounded-circle me-2" style="width:38px;height:38px;object-fit:cover;vertical-align:middle">' : '')
+ ' <div class="fw-semibold d-inline-block">' + esc(c.name || '—') + ' <small class="text-muted">(' + esc(c.p_code || '') + ')</small></div>'
+ ' <div class="mt-1">'
+ badge(m.name, 'Name')
+ badge(m.age, 'Age ' + (c.age != null ? c.age : '?'))
@ -185,6 +208,8 @@ window.AbhaPatientMatchModal = (function () {
$('.abhaMatch-candidate[data-id="' + id + '"]').addClass('border-success bg-success-subtle');
$('.abhaMatch-radio[value="' + id + '"]').prop('checked', true);
$('#abhaMatch_keep_existing_btn,#abhaMatch_update_existing_btn').prop('disabled', !id);
var selected = (_profile && window._abhaMatchCandidates || []).find(function (candidate) { return Number(candidate.id) === Number(id); });
if (selected) renderUpdatePreview(selected);
}
function submitConfirm(action, patientId, updateMode, $btn) {
@ -205,6 +230,7 @@ window.AbhaPatientMatchModal = (function () {
dob: _profile.dob || '',
abha_address: _profile.abha_address || '',
photo: _profile.photo || '',
card_base64: _profile.card_base64 || '',
verified_status: _profile.verified_status || '',
verification_type: _profile.verification_type || '',
kyc_verified: _profile.kyc_verified,
@ -264,12 +290,14 @@ window.AbhaPatientMatchModal = (function () {
return {
open: function (profile, candidates, onResolved) {
_profile = profile || {};
window._abhaMatchCandidates = candidates || [];
_selectedId = 0;
_onResolved = onResolved;
$('#abhaMatch_alert').empty();
$('#abhaMatch_keep_existing_btn,#abhaMatch_update_existing_btn').prop('disabled', true);
renderProfile(_profile);
renderCandidates(candidates || []);
$('#abhaMatch_update_preview').addClass('d-none').empty();
if (_bsModal) { _bsModal.show(); } else { $modalEl.modal('show'); }
}
};