fix(abdm): correct mobile ABHA discovery flow
Route Find ABHA via Mobile through standalone bridge OTP endpoints and remove exact placeholder values from generated FHIR bundles.
This commit is contained in:
parent
2ac6babd5d
commit
a7727c9d9e
|
|
@ -774,6 +774,8 @@ $routes->post('abha/create/initiate', 'Abha::initiate', ['filter' => 'permission
|
||||||
$routes->post('abha/create/verify_otp', 'Abha::verifyOtp', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/verify_otp', 'Abha::verifyOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||||
$routes->post('abha/create/communication', 'Abha::communication', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/communication', 'Abha::communication', ['filter' => 'permission:abdm.abha.create']);
|
||||||
$routes->post('abha/create/verify_comm_otp', 'Abha::verifyCommOtp', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/verify_comm_otp', 'Abha::verifyCommOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||||
|
$routes->post('abha/find/mobile/request-otp', 'Abha::communication', ['filter' => 'permission:abdm.abha.create']);
|
||||||
|
$routes->post('abha/find/mobile/verify-otp', 'Abha::verifyCommOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||||
$routes->post('abha/create/confirm_patient', 'Abha::confirmPatient', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/confirm_patient', 'Abha::confirmPatient', ['filter' => 'permission:abdm.abha.create']);
|
||||||
$routes->post('abha/create/address', 'Abha::address', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/address', 'Abha::address', ['filter' => 'permission:abdm.abha.create']);
|
||||||
$routes->post('abha/create/address_suggestions', 'Abha::addressSuggestions', ['filter' => 'permission:abdm.abha.create']);
|
$routes->post('abha/create/address_suggestions', 'Abha::addressSuggestions', ['filter' => 'permission:abdm.abha.create']);
|
||||||
|
|
|
||||||
|
|
@ -233,17 +233,20 @@ class Abha extends BaseController
|
||||||
|
|
||||||
$mobile = preg_replace('/\D/', '', trim((string) ($this->request->getPost('mobile') ?? '')));
|
$mobile = preg_replace('/\D/', '', trim((string) ($this->request->getPost('mobile') ?? '')));
|
||||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||||
|
$isMobileDiscovery = (string) $this->request->getPost('find_abha') === '1';
|
||||||
|
|
||||||
if (strlen($mobile) !== 10) {
|
if (strlen($mobile) !== 10) {
|
||||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'Valid 10-digit mobile number is required']);
|
return $this->response->setJSON(['ok' => 0, 'error_text' => 'Valid 10-digit mobile number is required']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($txnId === '') {
|
if (! $isMobileDiscovery && $txnId === '') {
|
||||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'Aadhaar enrolment txn_id is required']);
|
return $this->response->setJSON(['ok' => 0, 'error_text' => 'Aadhaar enrolment txn_id is required']);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = AbdmConnectorFactory::make()->abhaEnrolMobileRequestOtp(['mobile' => $mobile, 'txnId' => $txnId]);
|
$result = $isMobileDiscovery
|
||||||
|
? AbdmConnectorFactory::make()->abhaMobileGenerateOtp(['mobile' => $mobile])
|
||||||
|
: AbdmConnectorFactory::make()->abhaEnrolMobileRequestOtp(['mobile' => $mobile, 'txnId' => $txnId]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
@ -281,6 +284,7 @@ class Abha extends BaseController
|
||||||
|
|
||||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||||
$otp = trim((string) ($this->request->getPost('otp') ?? ''));
|
$otp = trim((string) ($this->request->getPost('otp') ?? ''));
|
||||||
|
$isMobileDiscovery = (string) $this->request->getPost('find_abha') === '1';
|
||||||
// The mobile itself isn't part of the OTP-verify payload (it's tied to txn_id
|
// The mobile itself isn't part of the OTP-verify payload (it's tied to txn_id
|
||||||
// on the bridge side), but the caller may resend it so we can persist it even
|
// on the bridge side), but the caller may resend it so we can persist it even
|
||||||
// if the gateway response doesn't echo it back.
|
// if the gateway response doesn't echo it back.
|
||||||
|
|
@ -291,7 +295,9 @@ class Abha extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = AbdmConnectorFactory::make()->abhaEnrolMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]);
|
$result = $isMobileDiscovery
|
||||||
|
? AbdmConnectorFactory::make()->abhaMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp])
|
||||||
|
: AbdmConnectorFactory::make()->abhaEnrolMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +325,7 @@ class Abha extends BaseController
|
||||||
$abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? '');
|
$abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? '');
|
||||||
$expectedAbha = preg_replace('/\D/', '', (string) (session()->get('abha_enrol_identity_' . $txnId) ?? ''));
|
$expectedAbha = preg_replace('/\D/', '', (string) (session()->get('abha_enrol_identity_' . $txnId) ?? ''));
|
||||||
$returnedAbha = preg_replace('/\D/', '', $abhaNum);
|
$returnedAbha = preg_replace('/\D/', '', $abhaNum);
|
||||||
if (strlen($expectedAbha) !== 14 || $returnedAbha !== $expectedAbha) {
|
if (! $isMobileDiscovery && (strlen($expectedAbha) !== 14 || $returnedAbha !== $expectedAbha)) {
|
||||||
log_message('error', '[ABHA] Enrolment identity mismatch blocked for txn ' . $txnId);
|
log_message('error', '[ABHA] Enrolment identity mismatch blocked for txn ' . $txnId);
|
||||||
return $this->response->setStatusCode(409)->setJSON([
|
return $this->response->setStatusCode(409)->setJSON([
|
||||||
'ok' => 0,
|
'ok' => 0,
|
||||||
|
|
@ -327,7 +333,9 @@ class Abha extends BaseController
|
||||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
session()->remove('abha_enrol_identity_' . $txnId);
|
if (! $isMobileDiscovery) {
|
||||||
|
session()->remove('abha_enrol_identity_' . $txnId);
|
||||||
|
}
|
||||||
$abhaAddress = (string) (
|
$abhaAddress = (string) (
|
||||||
($profile['preferredAddress'] ?? '')
|
($profile['preferredAddress'] ?? '')
|
||||||
?: ($profile['preferredAbhaAddress'] ?? '')
|
?: ($profile['preferredAbhaAddress'] ?? '')
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ class FhirDocumentBuilder
|
||||||
{
|
{
|
||||||
$clean = [];
|
$clean = [];
|
||||||
foreach ($value as $k => $v) {
|
foreach ($value as $k => $v) {
|
||||||
if ($v === null || $v === '') {
|
if ($v === null || $v === '' || $this->isPlaceholderValue($v)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (is_array($v)) {
|
if (is_array($v)) {
|
||||||
|
|
@ -310,6 +310,16 @@ class FhirDocumentBuilder
|
||||||
return $clean === [] ? null : $clean;
|
return $clean === [] ? null : $clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param mixed $value */
|
||||||
|
private function isPlaceholderValue($value): bool
|
||||||
|
{
|
||||||
|
if (! is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'NULL', 'NOT AVAILABLE'], true);
|
||||||
|
}
|
||||||
|
|
||||||
private function newId(string $prefix): string
|
private function newId(string $prefix): string
|
||||||
{
|
{
|
||||||
$prefix = strtolower(preg_replace('/[^a-z0-9]/i', '-', $prefix) ?: 'resource');
|
$prefix = strtolower(preg_replace('/[^a-z0-9]/i', '-', $prefix) ?: 'resource');
|
||||||
|
|
|
||||||
|
|
@ -665,7 +665,7 @@ class FhirR4Builder
|
||||||
? 'OPD-' . ($encounter['id'] ?? $bundleUuid) . '-' . date('Y-m-d')
|
? 'OPD-' . ($encounter['id'] ?? $bundleUuid) . '-' . date('Y-m-d')
|
||||||
: $bundleUuid;
|
: $bundleUuid;
|
||||||
|
|
||||||
return [
|
return $this->sanitizeBundle([
|
||||||
'resourceType' => 'Bundle',
|
'resourceType' => 'Bundle',
|
||||||
'id' => $bundleUuid,
|
'id' => $bundleUuid,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
|
|
@ -680,7 +680,7 @@ class FhirR4Builder
|
||||||
'type' => 'document',
|
'type' => 'document',
|
||||||
'timestamp' => $issuedAt,
|
'timestamp' => $issuedAt,
|
||||||
'entry' => $allEntries,
|
'entry' => $allEntries,
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1110,7 +1110,7 @@ class FhirR4Builder
|
||||||
? 'LAB-' . (string) ($diagnosticReport['id'] ?? $bundleUuid)
|
? 'LAB-' . (string) ($diagnosticReport['id'] ?? $bundleUuid)
|
||||||
: $bundleUuid;
|
: $bundleUuid;
|
||||||
|
|
||||||
return [
|
return $this->sanitizeBundle([
|
||||||
'resourceType' => 'Bundle',
|
'resourceType' => 'Bundle',
|
||||||
'id' => $bundleUuid,
|
'id' => $bundleUuid,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
|
|
@ -1125,7 +1125,7 @@ class FhirR4Builder
|
||||||
$resourceEntries,
|
$resourceEntries,
|
||||||
$observationEntries
|
$observationEntries
|
||||||
),
|
),
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1392,7 +1392,7 @@ class FhirR4Builder
|
||||||
? 'IPD-' . ($ipdCode !== '' ? $ipdCode : $bundleUuid)
|
? 'IPD-' . ($ipdCode !== '' ? $ipdCode : $bundleUuid)
|
||||||
: $bundleUuid;
|
: $bundleUuid;
|
||||||
|
|
||||||
return [
|
return $this->sanitizeBundle([
|
||||||
'resourceType' => 'Bundle',
|
'resourceType' => 'Bundle',
|
||||||
'id' => $bundleUuid,
|
'id' => $bundleUuid,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
|
|
@ -1406,7 +1406,7 @@ class FhirR4Builder
|
||||||
[['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]],
|
[['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]],
|
||||||
$resourceEntries
|
$resourceEntries
|
||||||
),
|
),
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1547,7 +1547,7 @@ class FhirR4Builder
|
||||||
$composition['author'] = [['display' => $provider]];
|
$composition['author'] = [['display' => $provider]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return $this->sanitizeBundle([
|
||||||
'resourceType' => 'Bundle',
|
'resourceType' => 'Bundle',
|
||||||
'type' => 'document',
|
'type' => 'document',
|
||||||
'timestamp' => $issuedAt,
|
'timestamp' => $issuedAt,
|
||||||
|
|
@ -1567,7 +1567,7 @@ class FhirR4Builder
|
||||||
], [
|
], [
|
||||||
'resource' => $claimResource,
|
'resource' => $claimResource,
|
||||||
]],
|
]],
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1866,7 +1866,7 @@ class FhirR4Builder
|
||||||
}
|
}
|
||||||
$hfrId = trim((string) ($organization['hfr_id'] ?? $organization['id'] ?? ''));
|
$hfrId = trim((string) ($organization['hfr_id'] ?? $organization['id'] ?? ''));
|
||||||
|
|
||||||
return [
|
return $this->sanitizeBundle([
|
||||||
'resourceType' => 'Bundle',
|
'resourceType' => 'Bundle',
|
||||||
'id' => $bundleUuid,
|
'id' => $bundleUuid,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
|
|
@ -1884,7 +1884,7 @@ class FhirR4Builder
|
||||||
'type' => 'document',
|
'type' => 'document',
|
||||||
'timestamp' => $issuedAt,
|
'timestamp' => $issuedAt,
|
||||||
'entry' => array_merge([['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]], $resourceEntries),
|
'entry' => array_merge([['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]], $resourceEntries),
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeImmunizationStatus(string $status): string
|
private function normalizeImmunizationStatus(string $status): string
|
||||||
|
|
@ -1977,6 +1977,38 @@ class FhirR4Builder
|
||||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array<string,mixed> $bundle
|
||||||
|
* @return array<string,mixed>
|
||||||
|
*/
|
||||||
|
private function sanitizeBundle(array $bundle): array
|
||||||
|
{
|
||||||
|
return $this->sanitizeFhirValue($bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param mixed $value
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function sanitizeFhirValue($value)
|
||||||
|
{
|
||||||
|
if (is_string($value)) {
|
||||||
|
return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'NULL', 'NOT AVAILABLE'], true) ? null : $value;
|
||||||
|
}
|
||||||
|
if (! is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$clean = [];
|
||||||
|
foreach ($value as $key => $nested) {
|
||||||
|
$sanitized = $this->sanitizeFhirValue($nested);
|
||||||
|
if ($sanitized === null || $sanitized === '' || $sanitized === []) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$clean[$key] = $sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean;
|
||||||
|
}
|
||||||
|
|
||||||
/** ISO 8601 timestamp with India Standard Time offset (+05:30). */
|
/** ISO 8601 timestamp with India Standard Time offset (+05:30). */
|
||||||
private function isoTimestamp(): string
|
private function isoTimestamp(): string
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -441,7 +441,7 @@
|
||||||
<div class="d-grid gap-2 mt-auto">
|
<div class="d-grid gap-2 mt-auto">
|
||||||
<button type="button" class="btn btn-success" onclick="abhaRegSelectMethod('number')"><i class="bi bi-person-vcard me-1"></i>By ABHA Number or ID</button>
|
<button type="button" class="btn btn-success" onclick="abhaRegSelectMethod('number')"><i class="bi bi-person-vcard me-1"></i>By ABHA Number or ID</button>
|
||||||
<button type="button" class="btn btn-outline-primary" onclick="abhaRegSelectMethod('address')"><i class="bi bi-at me-1"></i>By ABHA Address</button>
|
<button type="button" class="btn btn-outline-primary" onclick="abhaRegSelectMethod('address')"><i class="bi bi-at me-1"></i>By ABHA Address</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" onclick="abhaRegSelectMethod('mobile')"><i class="bi bi-phone me-1"></i>By Mobile OTP</button>
|
<button type="button" class="btn btn-outline-secondary" onclick="abhaRegSelectMethod('mobile')"><i class="bi bi-phone me-1"></i>Find ABHA via Mobile</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -759,12 +759,17 @@
|
||||||
<div class="small mb-1"><strong>Facility:</strong> <span id="abhareg_facility_name">—</span></div>
|
<div class="small mb-1"><strong>Facility:</strong> <span id="abhareg_facility_name">—</span></div>
|
||||||
<div class="small"><strong>HFR ID:</strong> <span id="abhareg_facility_hfr">—</span></div>
|
<div class="small"><strong>HFR ID:</strong> <span id="abhareg_facility_hfr">—</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-muted">Source: ABDM Bridge <code>GET /api/v3/facility/qr-code</code>.</div>
|
<div class="d-flex align-items-center gap-2 flex-wrap mt-3">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" onclick="load_form('<?= base_url('AbdmOpdQueue') ?>', 'ABDM OPD Queue');">
|
||||||
|
<i class="bi bi-ticket-perforated me-1"></i>Open ABDM OPD Queue
|
||||||
|
</button>
|
||||||
|
<span class="small text-muted">After the patient scans, confirm the incoming token here.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panel: Method 4 - Mobile OTP (handled by AbhaMobileModal) -->
|
<!-- Panel: Method 4 - Find ABHA by mobile (handled by AbhaMobileModal) -->
|
||||||
<div id="abhareg_panel_mobile" class="abhareg-panel d-none"></div>
|
<div id="abhareg_panel_mobile" class="abhareg-panel d-none"></div>
|
||||||
|
|
||||||
<!-- Common result card (all methods) -->
|
<!-- Common result card (all methods) -->
|
||||||
|
|
@ -1341,7 +1346,7 @@
|
||||||
address: { icon: '@', title: 'By ABHA Address', sub: 'Secure modal verification' },
|
address: { icon: '@', title: 'By ABHA Address', sub: 'Secure modal verification' },
|
||||||
qr: { icon: '📷', title: 'Scan Patient ABHA QR', sub: 'Scan QR → patient details' },
|
qr: { icon: '📷', title: 'Scan Patient ABHA QR', sub: 'Scan QR → patient details' },
|
||||||
facility: { icon: '🏥', title: 'Scan Facility QR', sub: 'Patient scans via ABHA app' },
|
facility: { icon: '🏥', title: 'Scan Facility QR', sub: 'Patient scans via ABHA app' },
|
||||||
mobile: { icon: '📱', title: 'By Mobile OTP', sub: 'OTP to ABHA-linked mobile' }
|
mobile: { icon: '📱', title: 'Find ABHA via Mobile', sub: 'Find an ABHA linked to this mobile number' }
|
||||||
};
|
};
|
||||||
|
|
||||||
window.abhaRegSelectMethod = function(method) {
|
window.abhaRegSelectMethod = function(method) {
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ $_aadh = '';
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile OTP Verify -->
|
<!-- Find ABHA via Mobile -->
|
||||||
<div class="tab-pane fade" id="cvpv-mobile" role="tabpanel">
|
<div class="tab-pane fade" id="cvpv-mobile" role="tabpanel">
|
||||||
<div id="cvpv-m-step1">
|
<div id="cvpv-m-step1">
|
||||||
<div class="col-sm-6 mb-3">
|
<div class="col-sm-6 mb-3">
|
||||||
|
|
@ -283,7 +283,7 @@ $_aadh = '';
|
||||||
</div>
|
</div>
|
||||||
<div id="cvpv_m_step1_msg"></div>
|
<div id="cvpv_m_step1_msg"></div>
|
||||||
<button type="button" class="btn btn-primary" id="cvpv_btn_m_send_otp">
|
<button type="button" class="btn btn-primary" id="cvpv_btn_m_send_otp">
|
||||||
<i class="bi bi-send me-1"></i>Send OTP
|
<i class="bi bi-search me-1"></i>Find ABHA
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="cvpv-m-step2" style="display:none">
|
<div id="cvpv-m-step2" style="display:none">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Partial: "Verify ABHA by Mobile OTP" modal.
|
* Partial: "Find ABHA via Mobile" modal.
|
||||||
*
|
*
|
||||||
* Mirrors partials/abha_verify_modal.php so both verification journeys look the
|
* Mirrors partials/abha_verify_modal.php so both verification journeys look the
|
||||||
* same; only the identifier differs (ABHA-linked mobile instead of ABHA number).
|
* same; only the identifier differs (ABHA-linked mobile instead of ABHA number).
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><i class="bi bi-phone text-primary me-2"></i>Verify ABHA by Mobile OTP</h5>
|
<h5 class="modal-title"><i class="bi bi-phone text-primary me-2"></i>Find ABHA via Mobile</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
<input type="text" class="form-control" id="abhaMobileNumber" maxlength="10" inputmode="numeric" autocomplete="tel" placeholder="10-digit mobile">
|
<input type="text" class="form-control" id="abhaMobileNumber" maxlength="10" inputmode="numeric" autocomplete="tel" placeholder="10-digit mobile">
|
||||||
<button type="button" class="btn btn-primary" id="abhaMobileSendOtpBtn"><i class="bi bi-send me-1"></i>Send OTP</button>
|
<button type="button" class="btn btn-primary" id="abhaMobileSendOtpBtn"><i class="bi bi-send me-1"></i>Send OTP</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">Enter the mobile number registered with the patient's ABHA account.</div>
|
<div class="form-text">Enter the mobile number registered with the patient's ABHA account to find and link it.</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="abhaMobileStep2" class="d-none">
|
<section id="abhaMobileStep2" class="d-none">
|
||||||
|
|
@ -181,7 +181,7 @@ window.AbhaMobileModal = (function () {
|
||||||
|
|
||||||
verifiedMobile = mobile;
|
verifiedMobile = mobile;
|
||||||
var button = $('#abhaMobileSendOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending');
|
var button = $('#abhaMobileSendOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending');
|
||||||
$.post('<?= base_url('abha/create/communication') ?>', { mobile: mobile, '<?= csrf_token() ?>': csrf() }, function (response) {
|
$.post('<?= base_url('abha/find/mobile/request-otp') ?>', { mobile: mobile, find_abha: 1, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||||
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
|
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
|
||||||
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'Unable to send OTP to this mobile number.')); return; }
|
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'Unable to send OTP to this mobile number.')); return; }
|
||||||
txnId = response.txn_id || '';
|
txnId = response.txn_id || '';
|
||||||
|
|
@ -201,7 +201,7 @@ window.AbhaMobileModal = (function () {
|
||||||
if (otp.length !== 6) { alertBox('warning', 'Enter the 6-digit OTP.'); return; }
|
if (otp.length !== 6) { alertBox('warning', 'Enter the 6-digit OTP.'); return; }
|
||||||
|
|
||||||
var button = $('#abhaMobileVerifyBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying');
|
var button = $('#abhaMobileVerifyBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying');
|
||||||
$.post('<?= base_url('abha/create/verify_comm_otp') ?>', { otp: otp, txn_id: txnId, mobile: verifiedMobile, '<?= csrf_token() ?>': csrf() }, function (response) {
|
$.post('<?= base_url('abha/find/mobile/verify-otp') ?>', { otp: otp, txn_id: txnId, mobile: verifiedMobile, find_abha: 1, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||||
button.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP');
|
button.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP');
|
||||||
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'OTP verification failed.')); return; }
|
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'OTP verification failed.')); return; }
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Libraries\Abdm\Fhir\FhirGeneratorFactory;
|
use App\Libraries\Abdm\Fhir\FhirGeneratorFactory;
|
||||||
|
use App\Libraries\Abdm\Fhir\FhirDocumentBuilder;
|
||||||
use App\Controllers\AbdmGateway;
|
use App\Controllers\AbdmGateway;
|
||||||
use App\Controllers\Ipd_discharge;
|
use App\Controllers\Ipd_discharge;
|
||||||
use App\Libraries\FhirR4Builder;
|
use App\Libraries\FhirR4Builder;
|
||||||
|
|
@ -16,6 +17,38 @@ final class FhirGeneratorsTest extends CIUnitTestCase
|
||||||
$this->factory = new FhirGeneratorFactory();
|
$this->factory = new FhirGeneratorFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDocumentBuilderRemovesExactPlaceholderValues(): void
|
||||||
|
{
|
||||||
|
$bundle = (new FhirDocumentBuilder())
|
||||||
|
->buildBundleMeta('placeholder-test', '2026-08-24T10:00:00+05:30')
|
||||||
|
->buildComposition([
|
||||||
|
'resourceType' => 'Composition',
|
||||||
|
'id' => 'placeholder-composition',
|
||||||
|
'status' => 'final',
|
||||||
|
'title' => 'NA',
|
||||||
|
'subject' => ['display' => 'N/A'],
|
||||||
|
'note' => 'Nasal congestion',
|
||||||
|
])
|
||||||
|
->toBundle();
|
||||||
|
|
||||||
|
$composition = $bundle['entry'][0]['resource'];
|
||||||
|
$this->assertArrayNotHasKey('title', $composition);
|
||||||
|
$this->assertArrayNotHasKey('subject', $composition);
|
||||||
|
$this->assertSame('Nasal congestion', $composition['note']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLegacyBuilderRemovesExactPlaceholderValues(): void
|
||||||
|
{
|
||||||
|
$bundle = (new FhirR4Builder())->buildClaimBundle(
|
||||||
|
['id' => 'patient-1', 'name' => 'NA', 'gender' => 'female', 'birthDate' => '1990-01-01'],
|
||||||
|
['id' => 'encounter-1'],
|
||||||
|
['id' => 'claim-1', 'provider' => 'N/A']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringNotContainsString('"NA"', (string) json_encode($bundle));
|
||||||
|
$this->assertStringNotContainsString('"N/A"', (string) json_encode($bundle));
|
||||||
|
}
|
||||||
|
|
||||||
public function testImmunizationCareContextReferenceIsStableAcrossClinicalDateChanges(): void
|
public function testImmunizationCareContextReferenceIsStableAcrossClinicalDateChanges(): void
|
||||||
{
|
{
|
||||||
$method = new ReflectionMethod(AbdmGateway::class, 'resolveImmunizationCareContextReference');
|
$method = new ReflectionMethod(AbdmGateway::class, 'resolveImmunizationCareContextReference');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user