ABDM: finalize ABHA verify persistence, M2 routes/workflows, and location masters seeding
This commit is contained in:
parent
75e71c2343
commit
985d476bc1
|
|
@ -499,6 +499,7 @@ $routes->group('billing', function($routes) {
|
|||
$routes->post('patient/save_profile_image/(:num)', 'Patient::save_profile_image/$1');
|
||||
$routes->match(['get', 'post'], 'patient/patient_file_upload/(:num)', 'Patient::patient_file_upload/$1');
|
||||
$routes->get('patient/city', 'Patient::city');
|
||||
$routes->get('patient/district_list', 'Patient::district_list');
|
||||
$routes->get('patient/get_name', 'Patient::get_name');
|
||||
$routes->post('patient/update', 'Patient::update');
|
||||
$routes->post('patient/update_aadhar', 'Patient::update_aadhar');
|
||||
|
|
|
|||
|
|
@ -2762,13 +2762,13 @@ class AbdmGateway extends BaseController
|
|||
$fields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$abhaField = $this->resolveFirstExistingColumn($fields, ['abha_id', 'abha_no', 'abha', 'abha_address']);
|
||||
|
||||
$select = 'id,p_code,p_fname,mphone1';
|
||||
$select = 'id,p_code,p_fname,mphone1,dob,gender';
|
||||
if ($abhaField) {
|
||||
$select .= ',' . $abhaField . ' AS patient_abha';
|
||||
}
|
||||
|
||||
$bucket = [];
|
||||
$append = static function (array $rows, string $reason) use (&$bucket): void {
|
||||
$append = static function (array $rows, string $reason, int $score) use (&$bucket): void {
|
||||
foreach ($rows as $row) {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
|
|
@ -2776,26 +2776,88 @@ class AbdmGateway extends BaseController
|
|||
}
|
||||
if (! isset($bucket[$id])) {
|
||||
$row['match_reasons'] = [];
|
||||
$row['match_score'] = 0;
|
||||
$bucket[$id] = $row;
|
||||
}
|
||||
if (! in_array($reason, $bucket[$id]['match_reasons'], true)) {
|
||||
$bucket[$id]['match_reasons'][] = $reason;
|
||||
}
|
||||
if ($score > ($bucket[$id]['match_score'] ?? 0)) {
|
||||
$bucket[$id]['match_score'] = $score;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Definitive identifier matches (score 4)
|
||||
if ($abhaField && ($identity['abha_number'] !== '' || $identity['abha_address'] !== '')) {
|
||||
$abha = $identity['abha_number'] !== '' ? $identity['abha_number'] : $identity['abha_address'];
|
||||
$rows = $this->db->table('patient_master')->select($select)->where($abhaField, $abha)->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'ABHA');
|
||||
$append($rows, 'ABHA', 4);
|
||||
}
|
||||
|
||||
if ($identity['phone'] !== '') {
|
||||
// Demographic matches — primary criteria
|
||||
$nameUpper = strtoupper(trim((string) ($identity['patient_name'] ?? '')));
|
||||
$rawGender = strtoupper(trim((string) ($identity['gender'] ?? '')));
|
||||
$genderDb = ($rawGender === 'F' || $rawGender === '2') ? 2 : ($rawGender !== '' ? 1 : null);
|
||||
$dob = trim((string) ($identity['dob'] ?? ''));
|
||||
$dobValid = preg_match('/^\d{4}-\d{2}-\d{2}$/', $dob) ? $dob : '';
|
||||
$yearOnly = $dobValid !== '' ? (int) substr($dobValid, 0, 4) : 0;
|
||||
|
||||
if ($nameUpper !== '' && $dobValid !== '') {
|
||||
// Name + exact DOB [+ gender] — high confidence (score 3)
|
||||
$q = $this->db->table('patient_master')->select($select)
|
||||
->where('UPPER(p_fname)', $nameUpper)
|
||||
->where('dob', $dobValid);
|
||||
if ($genderDb !== null) {
|
||||
$q->where('gender', $genderDb);
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + DOB + Gender', 3);
|
||||
} else {
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + DOB', 3);
|
||||
}
|
||||
}
|
||||
|
||||
if ($nameUpper !== '' && $yearOnly > 0) {
|
||||
// Name + year of birth [+ gender] — medium confidence (score 2)
|
||||
$q = $this->db->table('patient_master')->select($select)
|
||||
->where('UPPER(p_fname)', $nameUpper)
|
||||
->where('YEAR(dob)', $yearOnly);
|
||||
if ($genderDb !== null) {
|
||||
$q->where('gender', $genderDb);
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + Year of Birth + Gender', 2);
|
||||
} else {
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + Year of Birth', 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Phone match — supplementary only (score 1), ambiguous for families
|
||||
if (($identity['phone'] ?? '') !== '') {
|
||||
$rows = $this->db->table('patient_master')->select($select)->where('mphone1', $identity['phone'])->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Phone');
|
||||
$append($rows, 'Phone', 1);
|
||||
}
|
||||
|
||||
return array_values($bucket);
|
||||
// Assign confidence label and sort by score descending
|
||||
$confidenceLabel = static function (int $score): string {
|
||||
if ($score >= 4) { return 'definitive'; }
|
||||
if ($score === 3) { return 'high'; }
|
||||
if ($score === 2) { return 'medium'; }
|
||||
return 'low';
|
||||
};
|
||||
|
||||
$result = array_values($bucket);
|
||||
usort($result, static function ($a, $b) {
|
||||
return ($b['match_score'] ?? 0) <=> ($a['match_score'] ?? 0);
|
||||
});
|
||||
|
||||
foreach ($result as &$row) {
|
||||
$row['match_confidence'] = $confidenceLabel((int) ($row['match_score'] ?? 0));
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function resolveFirstExistingColumn(array $fields, array $candidates): ?string
|
||||
|
|
@ -3702,6 +3764,7 @@ class AbdmGateway extends BaseController
|
|||
// Accept txnId (API native) or txn_id (HMS form shorthand)
|
||||
$txnId = trim((string) ($body['txnId'] ?? $body['txn_id'] ?? $this->request->getPost('txnId') ?? $this->request->getPost('txn_id') ?? ''));
|
||||
$otp = trim((string) ($body['otp'] ?? $this->request->getPost('otp') ?? ''));
|
||||
$requestedPid = (int) ($body['p_id'] ?? $this->request->getPost('p_id') ?? 0);
|
||||
|
||||
if ($txnId === '' || $otp === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txnId and otp are required']);
|
||||
|
|
@ -3713,7 +3776,11 @@ class AbdmGateway extends BaseController
|
|||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
if (! empty($result['ok']) && (int) $result['ok'] === 1) {
|
||||
$result = $this->enrichGatewayOtpVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($this->sanitizeGatewayOtpVerifyResponse($result));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3759,6 +3826,7 @@ class AbdmGateway extends BaseController
|
|||
// Accept txnId (API native) or txn_id (HMS form shorthand)
|
||||
$txnId = trim((string) ($body['txnId'] ?? $body['txn_id'] ?? $this->request->getPost('txnId') ?? $this->request->getPost('txn_id') ?? ''));
|
||||
$otp = trim((string) ($body['otp'] ?? $this->request->getPost('otp') ?? ''));
|
||||
$requestedPid = (int) ($body['p_id'] ?? $this->request->getPost('p_id') ?? 0);
|
||||
|
||||
if ($txnId === '' || $otp === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txnId and otp are required']);
|
||||
|
|
@ -3770,7 +3838,449 @@ class AbdmGateway extends BaseController
|
|||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
if (! empty($result['ok']) && (int) $result['ok'] === 1) {
|
||||
$result = $this->enrichGatewayOtpVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($this->sanitizeGatewayOtpVerifyResponse($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function enrichGatewayOtpVerifyResult(array $result, int $requestedPid): array
|
||||
{
|
||||
$payload = [];
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
$payload = $result['data'];
|
||||
} elseif (is_array($result)) {
|
||||
$payload = $result;
|
||||
}
|
||||
|
||||
$profile = $this->pickGatewayOtpProfile($payload);
|
||||
$gatewayPatientPayload = is_array($payload['gateway_patient'] ?? null) ? $payload['gateway_patient'] : [];
|
||||
|
||||
$abhaRaw = trim((string) (
|
||||
$profile['ABHANumber']
|
||||
?? $profile['abha_number']
|
||||
?? $profile['abha_id']
|
||||
?? $payload['ABHANumber']
|
||||
?? $payload['abha_number']
|
||||
?? $payload['abha_id']
|
||||
?? ''
|
||||
));
|
||||
$abhaDigits = preg_replace('/\D/', '', $abhaRaw);
|
||||
$abhaAddress = trim((string) (
|
||||
$profile['preferredAddress']
|
||||
?? $profile['preferredAbhaAddress']
|
||||
?? $profile['abha_address']
|
||||
?? $payload['preferredAddress']
|
||||
?? $payload['preferredAbhaAddress']
|
||||
?? $payload['abha_address']
|
||||
?? ''
|
||||
));
|
||||
$gatewayMobile = preg_replace('/\D/', '', (string) (
|
||||
$profile['mobile']
|
||||
?? $payload['mobile']
|
||||
?? $payload['mobileNumber']
|
||||
?? ($gatewayPatientPayload['mobile'] ?? '')
|
||||
));
|
||||
|
||||
$gatewayAddress = trim((string) (
|
||||
($profile['address'] ?? '')
|
||||
?: ($profile['address_line'] ?? '')
|
||||
?: ($payload['address'] ?? '')
|
||||
?: ($payload['address_line'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['address'] ?? '')
|
||||
?: ($gatewayPatientPayload['address_line'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$gatewayCity = trim((string) (
|
||||
($profile['city'] ?? '')
|
||||
?: ($payload['city'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['city'] ?? '')
|
||||
?: ($gatewayPatientPayload['city'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$gatewayDistrict = trim((string) (
|
||||
($profile['districtName'] ?? '')
|
||||
?: ($profile['district_name'] ?? '')
|
||||
?: ($payload['districtName'] ?? '')
|
||||
?: ($payload['district_name'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['district_name'] ?? '')
|
||||
?: ($gatewayPatientPayload['district'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$gatewayState = trim((string) (
|
||||
($profile['stateName'] ?? '')
|
||||
?: ($profile['state_name'] ?? '')
|
||||
?: ($payload['stateName'] ?? '')
|
||||
?: ($payload['state_name'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['state_name'] ?? '')
|
||||
?: ($gatewayPatientPayload['state_name'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$gatewayZip = trim((string) (
|
||||
($profile['pinCode'] ?? '')
|
||||
?: ($profile['pin_code'] ?? '')
|
||||
?: ($payload['pinCode'] ?? '')
|
||||
?: ($payload['pin_code'] ?? '')
|
||||
?: ($payload['pincode'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['pin_code'] ?? '')
|
||||
?: ($gatewayPatientPayload['pincode'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$gatewayEmail = trim((string) (
|
||||
($profile['email'] ?? '')
|
||||
?: ($payload['email'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['email'] ?? '')
|
||||
?: ($gatewayPatientPayload['email'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
|
||||
$verifiedStatus = trim((string) (
|
||||
($payload['gateway_abha_profile']['status'] ?? '')
|
||||
?: ($profile['verifiedStatus'] ?? '')
|
||||
?: ($profile['status'] ?? '')
|
||||
?: ($payload['verifiedStatus'] ?? '')
|
||||
?: ($payload['status'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$verificationType = trim((string) (
|
||||
($payload['gateway_abha_profile']['abha_type'] ?? '')
|
||||
?: ($profile['verificationType'] ?? '')
|
||||
?: ($profile['abhaType'] ?? '')
|
||||
?: ($payload['verificationType'] ?? '')
|
||||
?: ($payload['abhaType'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$kycVerified = $profile['kycVerified'] ?? $payload['kycVerified'] ?? null;
|
||||
if ($kycVerified === null) {
|
||||
$kycVerified = strtolower(trim((string) ($payload['gateway_abha_profile']['status'] ?? ''))) === 'verified' ? 1 : 0;
|
||||
}
|
||||
$mobileVerified = $profile['mobileVerified']
|
||||
?? $payload['mobileVerified']
|
||||
?? ($payload['gateway_abha_profile']['mobile_verified'] ?? null);
|
||||
|
||||
$patientId = $this->resolveGatewayOtpPatientId($requestedPid, $payload, $abhaDigits, $gatewayMobile);
|
||||
if ($patientId <= 0 || ! $this->db->tableExists('patient_master')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$updates = [];
|
||||
|
||||
$abhaField = $this->resolveFirstExistingColumn($fields, ['abha_id', 'abha_no', 'abha']);
|
||||
if ($abhaField !== null && $abhaDigits !== '') {
|
||||
$updates[$abhaField] = $abhaDigits;
|
||||
}
|
||||
if ($abhaAddress !== '' && in_array('abha_address', $fields, true)) {
|
||||
$updates['abha_address'] = $abhaAddress;
|
||||
}
|
||||
|
||||
if ($gatewayMobile !== '' && in_array('mphone1', $fields, true)) {
|
||||
$updates['mphone1'] = $gatewayMobile;
|
||||
}
|
||||
if ($gatewayAddress !== '' && in_array('add1', $fields, true)) {
|
||||
$updates['add1'] = $gatewayAddress;
|
||||
}
|
||||
if ($gatewayCity !== '' && in_array('city', $fields, true)) {
|
||||
$updates['city'] = $gatewayCity;
|
||||
}
|
||||
if ($gatewayDistrict !== '' && in_array('district', $fields, true)) {
|
||||
$updates['district'] = $gatewayDistrict;
|
||||
}
|
||||
if ($gatewayState !== '' && in_array('state', $fields, true)) {
|
||||
$updates['state'] = $gatewayState;
|
||||
}
|
||||
if ($gatewayZip !== '' && in_array('zip', $fields, true)) {
|
||||
$updates['zip'] = $gatewayZip;
|
||||
}
|
||||
if ($gatewayEmail !== '' && in_array('email1', $fields, true)) {
|
||||
$updates['email1'] = $gatewayEmail;
|
||||
}
|
||||
|
||||
if ($verifiedStatus !== '' && in_array('abha_verified_status', $fields, true)) {
|
||||
$updates['abha_verified_status'] = strtoupper($verifiedStatus);
|
||||
}
|
||||
if ($verificationType !== '' && in_array('abha_verification_type', $fields, true)) {
|
||||
$updates['abha_verification_type'] = strtoupper($verificationType);
|
||||
}
|
||||
if ($kycVerified !== null && in_array('abha_kyc_verified', $fields, true)) {
|
||||
$updates['abha_kyc_verified'] = $this->toDbBool($kycVerified);
|
||||
}
|
||||
if ($mobileVerified !== null && in_array('abha_mobile_verified', $fields, true)) {
|
||||
$updates['abha_mobile_verified'] = $this->toDbBool($mobileVerified);
|
||||
}
|
||||
|
||||
if (in_array('abdm_linked_at', $fields, true) && ($abhaDigits !== '' || $abhaAddress !== '')) {
|
||||
$updates['abdm_linked_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if ($updates !== []) {
|
||||
$this->db->table('patient_master')->where('id', $patientId)->update($updates);
|
||||
}
|
||||
|
||||
$result['patient_id'] = $patientId;
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
$result['data']['patient_id'] = $patientId;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
private function resolveGatewayOtpPatientId(int $requestedPid, array $payload, string $abhaDigits, string $mobile): int
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($requestedPid > 0) {
|
||||
$row = $this->db->table('patient_master')->select('id')->where('id', $requestedPid)->get(1)->getRowArray();
|
||||
if ($row !== null) {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$abhaField = $this->resolveFirstExistingColumn($fields, ['abha_id', 'abha_no', 'abha']);
|
||||
if ($abhaField !== null && $abhaDigits !== '') {
|
||||
$row = $this->db->table('patient_master')->select('id')->where($abhaField, $abhaDigits)->get(1)->getRowArray();
|
||||
if ($row !== null) {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ($mobile !== '' && in_array('mphone1', $fields, true)) {
|
||||
$row = $this->db->table('patient_master')->select('id')->where('mphone1', $mobile)->get(1)->getRowArray();
|
||||
if ($row !== null) {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$fallbackIds = [
|
||||
(int) ($payload['patient_id'] ?? 0),
|
||||
(int) ($payload['gateway_patient']['id'] ?? 0),
|
||||
];
|
||||
foreach ($fallbackIds as $pid) {
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$row = $this->db->table('patient_master')->select('id')->where('id', $pid)->get(1)->getRowArray();
|
||||
if ($row !== null) {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function pickGatewayOtpProfile(array $payload): array
|
||||
{
|
||||
$candidates = [
|
||||
$payload['ABHAProfile'] ?? null,
|
||||
$payload['gateway_abha_profile'] ?? null,
|
||||
$payload['profile'] ?? null,
|
||||
$payload['accounts'][0] ?? null,
|
||||
$payload['gateway_patient'] ?? null,
|
||||
$payload,
|
||||
];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if (! is_array($candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (['ABHANumber', 'abha_number', 'abha_id', 'preferredAddress', 'preferredAbhaAddress', 'abha_address', 'fullName', 'full_name', 'firstName', 'dob', 'date_of_birth'] as $key) {
|
||||
if (array_key_exists($key, $candidate) && trim((string) $candidate[$key]) !== '') {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function toDbBool($value): int
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value ? 1 : 0;
|
||||
}
|
||||
|
||||
$v = strtolower(trim((string) $value));
|
||||
if ($v === '1' || $v === 'true' || $v === 'yes' || $v === 'y') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only safe/required fields for gateway ABHA OTP verification APIs.
|
||||
*
|
||||
* @param array<string, mixed> $result
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeGatewayOtpVerifyResponse(array $result): array
|
||||
{
|
||||
$ok = ! empty($result['ok']) && (int) $result['ok'] === 1;
|
||||
if (! $ok) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'error_text' => (string) ($result['error_text'] ?? $result['message'] ?? $result['data']['message'] ?? 'OTP verification failed'),
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
$payload = $result['data'];
|
||||
} elseif (is_array($result)) {
|
||||
$payload = $result;
|
||||
}
|
||||
|
||||
$profile = $this->pickGatewayOtpProfile($payload);
|
||||
$gatewayPatientPayload = is_array($payload['gateway_patient'] ?? null) ? $payload['gateway_patient'] : [];
|
||||
|
||||
$txnId = (string) ($payload['txnId'] ?? $payload['txn_id'] ?? $result['txnId'] ?? $result['txn_id'] ?? '');
|
||||
$abhaRaw = (string) ($payload['ABHANumber'] ?? $payload['abha_number'] ?? $payload['abha_id'] ?? $profile['ABHANumber'] ?? $profile['abha_number'] ?? $profile['abha_id'] ?? '');
|
||||
$abhaDigits = preg_replace('/\D/', '', $abhaRaw);
|
||||
$abhaAddress = (string) ($payload['preferredAbhaAddress'] ?? $payload['abha_address'] ?? $profile['preferredAbhaAddress'] ?? $profile['abha_address'] ?? $profile['preferredAddress'] ?? '');
|
||||
$name = trim((string) (
|
||||
$payload['name']
|
||||
?? $payload['full_name']
|
||||
?? $profile['name']
|
||||
?? $profile['fullName']
|
||||
?? $profile['full_name']
|
||||
?? trim(implode(' ', array_filter([
|
||||
(string) ($profile['firstName'] ?? ''),
|
||||
(string) ($profile['middleName'] ?? ''),
|
||||
(string) ($profile['lastName'] ?? ''),
|
||||
])))
|
||||
));
|
||||
$mobile = (string) ($payload['mobile'] ?? $payload['mobileNumber'] ?? $profile['mobile'] ?? ($gatewayPatientPayload['mobile'] ?? ''));
|
||||
$gender = (string) ($payload['gender'] ?? $profile['gender'] ?? ($gatewayPatientPayload['gender'] ?? ''));
|
||||
$dob = (string) ($payload['dob'] ?? $payload['date_of_birth'] ?? $profile['dob'] ?? $profile['date_of_birth'] ?? ($gatewayPatientPayload['date_of_birth'] ?? ''));
|
||||
$address = (string) (
|
||||
($payload['address'] ?? '')
|
||||
?: ($payload['address_line'] ?? '')
|
||||
?: ($profile['address'] ?? '')
|
||||
?: ($profile['address_line'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['address'] ?? '')
|
||||
?: ($gatewayPatientPayload['address_line'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$pinCode = (string) (
|
||||
($payload['pinCode'] ?? '')
|
||||
?: ($payload['pin_code'] ?? '')
|
||||
?: ($payload['pincode'] ?? '')
|
||||
?: ($profile['pinCode'] ?? '')
|
||||
?: ($profile['pin_code'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['pin_code'] ?? '')
|
||||
?: ($gatewayPatientPayload['pincode'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$stateCode = (string) (
|
||||
($payload['stateCode'] ?? '')
|
||||
?: ($payload['state_code'] ?? '')
|
||||
?: ($profile['stateCode'] ?? '')
|
||||
?: ($profile['state_code'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['state_code'] ?? '')
|
||||
?: ($gatewayPatientPayload['state_code'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$stateName = (string) (
|
||||
($payload['stateName'] ?? '')
|
||||
?: ($payload['state_name'] ?? '')
|
||||
?: ($profile['stateName'] ?? '')
|
||||
?: ($profile['state_name'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['state_name'] ?? '')
|
||||
?: ($gatewayPatientPayload['state_name'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$districtCode = (string) (
|
||||
($payload['districtCode'] ?? '')
|
||||
?: ($payload['district_code'] ?? '')
|
||||
?: ($profile['districtCode'] ?? '')
|
||||
?: ($profile['district_code'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['district_code'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$districtName = (string) (
|
||||
($payload['districtName'] ?? '')
|
||||
?: ($payload['district_name'] ?? '')
|
||||
?: ($profile['districtName'] ?? '')
|
||||
?: ($profile['district_name'] ?? '')
|
||||
?: ($payload['gateway_abha_profile']['district_name'] ?? '')
|
||||
?: ($gatewayPatientPayload['district'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
|
||||
$matchedPatientId = (int) ($result['patient_id'] ?? $payload['patient_id'] ?? 0);
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
'message' => (string) ($payload['message'] ?? $result['message'] ?? 'ABHA OTP verified successfully'),
|
||||
'txn_id' => $txnId,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_address' => $abhaAddress,
|
||||
'name' => $name,
|
||||
'mobile' => $mobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'address' => $address,
|
||||
'pin_code' => $pinCode,
|
||||
'state_code' => $stateCode,
|
||||
'state_name' => $stateName,
|
||||
'district_code' => $districtCode,
|
||||
'district_name' => $districtName,
|
||||
'patient_id' => $matchedPatientId > 0 ? $matchedPatientId : null,
|
||||
'data' => [
|
||||
'txnId' => $txnId,
|
||||
'abha_number' => $abhaDigits,
|
||||
'name' => $name,
|
||||
'mobile' => $mobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'address' => $address,
|
||||
'pin_code' => $pinCode,
|
||||
'state_code' => $stateCode,
|
||||
'state_name' => $stateName,
|
||||
'district_code' => $districtCode,
|
||||
'district_name' => $districtName,
|
||||
'patient_id' => $matchedPatientId > 0 ? $matchedPatientId : null,
|
||||
'profile' => [
|
||||
'ABHANumber' => $abhaDigits,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_id' => $abhaDigits,
|
||||
'preferredAbhaAddress' => $abhaAddress,
|
||||
'preferredAddress' => $abhaAddress,
|
||||
'name' => $name,
|
||||
'full_name' => $name,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'mobile' => $mobile,
|
||||
'address' => $address,
|
||||
'pinCode' => $pinCode,
|
||||
'stateCode' => $stateCode,
|
||||
'stateName' => $stateName,
|
||||
'districtCode' => $districtCode,
|
||||
'districtName' => $districtName,
|
||||
'firstName' => (string) ($profile['firstName'] ?? ''),
|
||||
'middleName' => (string) ($profile['middleName'] ?? ''),
|
||||
'lastName' => (string) ($profile['lastName'] ?? ''),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ class AbdmOpdQueue extends BaseController
|
|||
$abhaField = $this->resolveFirstExistingColumn($fields, ['abha_id', 'abha_no', 'abha', 'abha_address']);
|
||||
$aadhaarField = $this->resolveFirstExistingColumn($fields, ['udai', 'aadhar_no', 'aadhaar_no', 'aadhaar', 'adhar_no']);
|
||||
|
||||
$matches = $this->findPatientMatches($db, $abhaField, $aadhaarField, $abhaRaw, $abhaAddr, $aadhaarRaw, $phone);
|
||||
$matches = $this->findPatientMatches($db, $abhaField, $aadhaarField, $abhaRaw, $abhaAddr, $aadhaarRaw, $phone, $name, $gender, $dob);
|
||||
|
||||
if ($action === 'check') {
|
||||
return $this->response->setJSON([
|
||||
|
|
@ -494,9 +494,12 @@ class AbdmOpdQueue extends BaseController
|
|||
string $abhaRaw,
|
||||
string $abhaAddr,
|
||||
string $aadhaarRaw,
|
||||
string $phone
|
||||
string $phone,
|
||||
string $name = '',
|
||||
string $gender = '',
|
||||
string $dob = ''
|
||||
): array {
|
||||
$select = 'id,p_code,p_fname,mphone1';
|
||||
$select = 'id,p_code,p_fname,mphone1,dob,gender';
|
||||
if ($abhaField) {
|
||||
$select .= ',' . $abhaField . ' AS patient_abha';
|
||||
}
|
||||
|
|
@ -505,7 +508,7 @@ class AbdmOpdQueue extends BaseController
|
|||
}
|
||||
|
||||
$bucket = [];
|
||||
$append = static function (array $rows, string $reason) use (&$bucket): void {
|
||||
$append = static function (array $rows, string $reason, int $score) use (&$bucket): void {
|
||||
foreach ($rows as $row) {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
|
|
@ -513,35 +516,95 @@ class AbdmOpdQueue extends BaseController
|
|||
}
|
||||
if (! isset($bucket[$id])) {
|
||||
$row['match_reasons'] = [];
|
||||
$row['match_score'] = 0;
|
||||
$bucket[$id] = $row;
|
||||
}
|
||||
if (! in_array($reason, $bucket[$id]['match_reasons'], true)) {
|
||||
$bucket[$id]['match_reasons'][] = $reason;
|
||||
}
|
||||
if ($score > ($bucket[$id]['match_score'] ?? 0)) {
|
||||
$bucket[$id]['match_score'] = $score;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Definitive identifier matches (score 4)
|
||||
if ($abhaField && $abhaRaw !== '') {
|
||||
$rows = $db->table('patient_master')->select($select)->where($abhaField, $abhaRaw)->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'ABHA No');
|
||||
$append($rows, 'ABHA No', 4);
|
||||
}
|
||||
|
||||
if ($abhaField && $abhaAddr !== '' && $abhaAddr !== $abhaRaw) {
|
||||
$rows = $db->table('patient_master')->select($select)->where($abhaField, $abhaAddr)->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'ABHA Address');
|
||||
$append($rows, 'ABHA Address', 4);
|
||||
}
|
||||
|
||||
if ($aadhaarField && $aadhaarRaw !== '') {
|
||||
$rows = $db->table('patient_master')->select($select)->where($aadhaarField, $aadhaarRaw)->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Aadhaar');
|
||||
$append($rows, 'Aadhaar', 4);
|
||||
}
|
||||
|
||||
// Demographic matches — primary criteria
|
||||
$nameUpper = strtoupper(trim($name));
|
||||
$genderDb = ($gender === 'F' || $gender === '2') ? 2 : ($gender !== '' ? 1 : null);
|
||||
$dobValid = preg_match('/^\d{4}-\d{2}-\d{2}$/', $dob) ? $dob : '';
|
||||
$yearOnly = $dobValid !== '' ? (int) substr($dobValid, 0, 4) : 0;
|
||||
|
||||
if ($nameUpper !== '' && $dobValid !== '') {
|
||||
// Name + exact DOB [+ gender] — high confidence (score 3)
|
||||
$q = $db->table('patient_master')->select($select)
|
||||
->where('UPPER(p_fname)', $nameUpper)
|
||||
->where('dob', $dobValid);
|
||||
if ($genderDb !== null) {
|
||||
$q->where('gender', $genderDb);
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + DOB + Gender', 3);
|
||||
} else {
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + DOB', 3);
|
||||
}
|
||||
}
|
||||
|
||||
if ($nameUpper !== '' && $yearOnly > 0) {
|
||||
// Name + year of birth [+ gender] — medium confidence (score 2)
|
||||
$q = $db->table('patient_master')->select($select)
|
||||
->where('UPPER(p_fname)', $nameUpper)
|
||||
->where('YEAR(dob)', $yearOnly);
|
||||
if ($genderDb !== null) {
|
||||
$q->where('gender', $genderDb);
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + Year of Birth + Gender', 2);
|
||||
} else {
|
||||
$rows = $q->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Name + Year of Birth', 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Phone match — supplementary only (score 1), ambiguous for families
|
||||
if ($phone !== '') {
|
||||
$rows = $db->table('patient_master')->select($select)->where('mphone1', $phone)->limit(10)->get()->getResultArray();
|
||||
$append($rows, 'Phone');
|
||||
$append($rows, 'Phone', 1);
|
||||
}
|
||||
|
||||
return array_values($bucket);
|
||||
// Assign human-readable confidence label and sort by score descending
|
||||
$confidenceLabel = static function (int $score): string {
|
||||
if ($score >= 4) { return 'definitive'; }
|
||||
if ($score === 3) { return 'high'; }
|
||||
if ($score === 2) { return 'medium'; }
|
||||
return 'low';
|
||||
};
|
||||
|
||||
$result = array_values($bucket);
|
||||
usort($result, static function ($a, $b) {
|
||||
return ($b['match_score'] ?? 0) <=> ($a['match_score'] ?? 0);
|
||||
});
|
||||
|
||||
foreach ($result as &$row) {
|
||||
$row['match_confidence'] = $confidenceLabel((int) ($row['match_score'] ?? 0));
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function createPatientFromToken(
|
||||
|
|
|
|||
|
|
@ -86,16 +86,29 @@ class Abha extends BaseController
|
|||
$profile = $this->pickGatewayAbhaProfile($payload);
|
||||
|
||||
$abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? '');
|
||||
$abhaAddress = (string) ($profile['preferredAbhaAddress'] ?? $profile['abha_address'] ?? $payload['preferredAbhaAddress'] ?? $payload['abha_address'] ?? '');
|
||||
$abhaAddress = (string) (
|
||||
($profile['preferredAddress'] ?? '')
|
||||
?: ($profile['preferredAbhaAddress'] ?? '')
|
||||
?: ($profile['abha_address'] ?? '')
|
||||
?: ($payload['preferredAddress'] ?? '')
|
||||
?: ($payload['preferredAbhaAddress'] ?? '')
|
||||
?: ($payload['abha_address'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$name = (string) ($profile['name'] ?? $profile['fullName'] ?? $profile['full_name'] ?? $payload['name'] ?? $payload['full_name'] ?? '');
|
||||
$photo = (string) ($profile['profilePhoto'] ?? $profile['profile_photo'] ?? $payload['profilePhoto'] ?? $payload['profile_photo'] ?? '');
|
||||
$mobile = (string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? '');
|
||||
$profileGender = (string) ($profile['gender'] ?? $payload['gender'] ?? '');
|
||||
$profileDob = (string) ($profile['dob'] ?? $profile['date_of_birth'] ?? $payload['dob'] ?? $payload['date_of_birth'] ?? '');
|
||||
$verifiedStatus = (string) ($profile['verifiedStatus'] ?? $payload['verifiedStatus'] ?? '');
|
||||
$verificationType = (string) ($profile['verificationType'] ?? $payload['verificationType'] ?? '');
|
||||
$verifiedStatus = (string) (($payload['gateway_abha_profile']['status'] ?? '') ?: ($profile['verifiedStatus'] ?? '') ?: ($profile['status'] ?? '') ?: ($payload['verifiedStatus'] ?? '') ?: ($payload['status'] ?? ''));
|
||||
$verificationType = (string) (($payload['gateway_abha_profile']['abha_type'] ?? '') ?: ($profile['verificationType'] ?? '') ?: ($profile['abhaType'] ?? '') ?: ($payload['verificationType'] ?? '') ?: ($payload['abhaType'] ?? ''));
|
||||
$kycVerified = $profile['kycVerified'] ?? $payload['kycVerified'] ?? null;
|
||||
$mobileVerified = $profile['mobileVerified'] ?? $payload['mobileVerified'] ?? null;
|
||||
$address = (string) (($profile['address'] ?? '') ?: ($profile['address_line'] ?? '') ?: ($payload['address'] ?? '') ?: ($payload['address_line'] ?? '') ?: ($payload['gateway_abha_profile']['address'] ?? '') ?: ($payload['gateway_patient']['address_line'] ?? ''));
|
||||
$zip = (string) (($profile['pinCode'] ?? '') ?: ($profile['pin_code'] ?? '') ?: ($payload['pinCode'] ?? '') ?: ($payload['pin_code'] ?? '') ?: ($payload['gateway_abha_profile']['pin_code'] ?? '') ?: ($payload['gateway_patient']['pincode'] ?? ''));
|
||||
$stateName = (string) (($profile['stateName'] ?? '') ?: ($profile['state_name'] ?? '') ?: ($payload['stateName'] ?? '') ?: ($payload['state_name'] ?? '') ?: ($payload['gateway_abha_profile']['state_name'] ?? '') ?: ($payload['gateway_patient']['state_name'] ?? ''));
|
||||
$districtName = (string) (($profile['districtName'] ?? '') ?: ($profile['district_name'] ?? '') ?: ($payload['districtName'] ?? '') ?: ($payload['district_name'] ?? '') ?: ($payload['gateway_abha_profile']['district_name'] ?? '') ?: ($payload['gateway_patient']['district'] ?? ''));
|
||||
$email = (string) (($profile['email'] ?? '') ?: ($payload['email'] ?? '') ?: ($payload['gateway_abha_profile']['email'] ?? '') ?: ($payload['gateway_patient']['email'] ?? ''));
|
||||
|
||||
$patientInfo = $this->autoCreateOrFindPatient(
|
||||
$abhaNum,
|
||||
|
|
@ -110,6 +123,11 @@ class Abha extends BaseController
|
|||
'verification_type' => $verificationType,
|
||||
'kyc_verified' => $kycVerified,
|
||||
'mobile_verified' => $mobileVerified,
|
||||
'address' => $address,
|
||||
'district' => $districtName,
|
||||
'state' => $stateName,
|
||||
'zip' => $zip,
|
||||
'email' => $email,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -198,16 +216,29 @@ class Abha extends BaseController
|
|||
$profile = $this->pickGatewayAbhaProfile($payload);
|
||||
|
||||
$abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? '');
|
||||
$abhaAddress = (string) ($profile['preferredAbhaAddress'] ?? $profile['abha_address'] ?? $payload['preferredAbhaAddress'] ?? $payload['abha_address'] ?? '');
|
||||
$abhaAddress = (string) (
|
||||
($profile['preferredAddress'] ?? '')
|
||||
?: ($profile['preferredAbhaAddress'] ?? '')
|
||||
?: ($profile['abha_address'] ?? '')
|
||||
?: ($payload['preferredAddress'] ?? '')
|
||||
?: ($payload['preferredAbhaAddress'] ?? '')
|
||||
?: ($payload['abha_address'] ?? '')
|
||||
?: ''
|
||||
);
|
||||
$name = (string) ($profile['name'] ?? $profile['fullName'] ?? $profile['full_name'] ?? $payload['name'] ?? $payload['full_name'] ?? '');
|
||||
$photo = (string) ($profile['profilePhoto'] ?? $profile['profile_photo'] ?? $payload['profilePhoto'] ?? $payload['profile_photo'] ?? '');
|
||||
$gender = (string) ($profile['gender'] ?? $payload['gender'] ?? '');
|
||||
$dob = (string) ($profile['dob'] ?? $profile['date_of_birth'] ?? $payload['dob'] ?? $payload['date_of_birth'] ?? '');
|
||||
$mobile = (string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? '');
|
||||
$verifiedStatus = (string) ($profile['verifiedStatus'] ?? $payload['verifiedStatus'] ?? '');
|
||||
$verificationType = (string) ($profile['verificationType'] ?? $payload['verificationType'] ?? '');
|
||||
$verifiedStatus = (string) (($payload['gateway_abha_profile']['status'] ?? '') ?: ($profile['verifiedStatus'] ?? '') ?: ($profile['status'] ?? '') ?: ($payload['verifiedStatus'] ?? '') ?: ($payload['status'] ?? ''));
|
||||
$verificationType = (string) (($payload['gateway_abha_profile']['abha_type'] ?? '') ?: ($profile['verificationType'] ?? '') ?: ($profile['abhaType'] ?? '') ?: ($payload['verificationType'] ?? '') ?: ($payload['abhaType'] ?? ''));
|
||||
$kycVerified = $profile['kycVerified'] ?? $payload['kycVerified'] ?? null;
|
||||
$mobileVerified = $profile['mobileVerified'] ?? $payload['mobileVerified'] ?? null;
|
||||
$address = (string) (($profile['address'] ?? '') ?: ($profile['address_line'] ?? '') ?: ($payload['address'] ?? '') ?: ($payload['address_line'] ?? '') ?: ($payload['gateway_abha_profile']['address'] ?? '') ?: ($payload['gateway_patient']['address_line'] ?? ''));
|
||||
$zip = (string) (($profile['pinCode'] ?? '') ?: ($profile['pin_code'] ?? '') ?: ($payload['pinCode'] ?? '') ?: ($payload['pin_code'] ?? '') ?: ($payload['gateway_abha_profile']['pin_code'] ?? '') ?: ($payload['gateway_patient']['pincode'] ?? ''));
|
||||
$stateName = (string) (($profile['stateName'] ?? '') ?: ($profile['state_name'] ?? '') ?: ($payload['stateName'] ?? '') ?: ($payload['state_name'] ?? '') ?: ($payload['gateway_abha_profile']['state_name'] ?? '') ?: ($payload['gateway_patient']['state_name'] ?? ''));
|
||||
$districtName = (string) (($profile['districtName'] ?? '') ?: ($profile['district_name'] ?? '') ?: ($payload['districtName'] ?? '') ?: ($payload['district_name'] ?? '') ?: ($payload['gateway_abha_profile']['district_name'] ?? '') ?: ($payload['gateway_patient']['district'] ?? ''));
|
||||
$email = (string) (($profile['email'] ?? '') ?: ($payload['email'] ?? '') ?: ($payload['gateway_abha_profile']['email'] ?? '') ?: ($payload['gateway_patient']['email'] ?? ''));
|
||||
|
||||
$patientInfo = $this->autoCreateOrFindPatient(
|
||||
$abhaNum,
|
||||
|
|
@ -222,6 +253,11 @@ class Abha extends BaseController
|
|||
'verification_type' => $verificationType,
|
||||
'kyc_verified' => $kycVerified,
|
||||
'mobile_verified' => $mobileVerified,
|
||||
'address' => $address,
|
||||
'district' => $districtName,
|
||||
'state' => $stateName,
|
||||
'zip' => $zip,
|
||||
'email' => $email,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -498,6 +534,31 @@ class Abha extends BaseController
|
|||
$target['abha_mobile_verified'] = $this->toDbBool($abhaMeta['mobile_verified']);
|
||||
}
|
||||
|
||||
$address = trim((string) ($abhaMeta['address'] ?? ''));
|
||||
if ($address !== '' && in_array('add1', $fields, true)) {
|
||||
$target['add1'] = $address;
|
||||
}
|
||||
|
||||
$district = trim((string) ($abhaMeta['district'] ?? ''));
|
||||
if ($district !== '' && in_array('district', $fields, true)) {
|
||||
$target['district'] = strtoupper($district);
|
||||
}
|
||||
|
||||
$state = trim((string) ($abhaMeta['state'] ?? ''));
|
||||
if ($state !== '' && in_array('state', $fields, true)) {
|
||||
$target['state'] = strtoupper($state);
|
||||
}
|
||||
|
||||
$zip = trim((string) ($abhaMeta['zip'] ?? ''));
|
||||
if ($zip !== '' && in_array('zip', $fields, true)) {
|
||||
$target['zip'] = $zip;
|
||||
}
|
||||
|
||||
$email = trim((string) ($abhaMeta['email'] ?? ''));
|
||||
if ($email !== '' && in_array('email1', $fields, true)) {
|
||||
$target['email1'] = $email;
|
||||
}
|
||||
|
||||
$profilePhoto = trim((string) ($abhaMeta['profile_photo'] ?? ''));
|
||||
if ($profilePhoto !== '' && in_array('abha_profile_photo_base64', $fields, true)) {
|
||||
$target['abha_profile_photo_base64'] = $profilePhoto;
|
||||
|
|
@ -609,10 +670,11 @@ class Abha extends BaseController
|
|||
private function pickGatewayAbhaProfile(array $payload): array
|
||||
{
|
||||
$candidates = [
|
||||
$payload['gateway_patient'] ?? null,
|
||||
$payload['ABHAProfile'] ?? null,
|
||||
$payload['accounts'][0] ?? null,
|
||||
$payload['gateway_abha_profile'] ?? null,
|
||||
$payload['profile'] ?? null,
|
||||
$payload['accounts'][0] ?? null,
|
||||
$payload['gateway_patient'] ?? null,
|
||||
$payload,
|
||||
];
|
||||
|
||||
|
|
@ -630,7 +692,7 @@ class Abha extends BaseController
|
|||
*/
|
||||
private function looksLikeGatewayAbhaProfile(array $profile): bool
|
||||
{
|
||||
foreach (['ABHANumber', 'abha_id', 'preferredAbhaAddress', 'abha_address', 'name', 'fullName', 'full_name', 'gender', 'dob', 'date_of_birth', 'profilePhoto', 'profile_photo'] as $key) {
|
||||
foreach (['ABHANumber', 'abha_id', 'preferredAddress', 'preferredAbhaAddress', 'abha_address', 'name', 'fullName', 'full_name', 'gender', 'dob', 'date_of_birth', 'profilePhoto', 'profile_photo'] as $key) {
|
||||
if (array_key_exists($key, $profile) && trim((string) $profile[$key]) !== '') {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ class Patient extends BaseController
|
|||
->findAll();
|
||||
|
||||
$data['user'] = $user;
|
||||
|
||||
$data['india_states'] = [];
|
||||
if ($this->db->tableExists('india_state')) {
|
||||
$data['india_states'] = $this->db->table('india_state')
|
||||
->select('id, state_name')
|
||||
->orderBy('state_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
return view('billing/Patient_V', $data);
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +367,7 @@ class Patient extends BaseController
|
|||
$result = $this->enrichAbhaVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
return $this->response->setJSON($this->sanitizeAbhaVerifyApiResponse($result));
|
||||
}
|
||||
|
||||
public function abhaMobileGenerateOtp()
|
||||
|
|
@ -409,7 +419,7 @@ class Patient extends BaseController
|
|||
$result = $this->enrichAbhaVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
return $this->response->setJSON($this->sanitizeAbhaVerifyApiResponse($result));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1237,6 +1247,28 @@ class Patient extends BaseController
|
|||
return $this->response->setJSON($patientModel->getCitySuggestions($q));
|
||||
}
|
||||
|
||||
public function district_list()
|
||||
{
|
||||
$q = trim((string) $this->request->getGet('term'));
|
||||
if ($q === '' || strlen($q) < 2) {
|
||||
return $this->response->setJSON([]);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('abdm_district_master')) {
|
||||
return $this->response->setJSON([]);
|
||||
}
|
||||
|
||||
$rows = $this->db->table('abdm_district_master')
|
||||
->select('district_name AS value, district_name AS label, state_code')
|
||||
->like('district_name', $q)
|
||||
->orderBy('district_name', 'ASC')
|
||||
->limit(15)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return $this->response->setJSON($rows);
|
||||
}
|
||||
|
||||
public function get_name()
|
||||
{
|
||||
$q = strtolower((string) $this->request->getGet('term'));
|
||||
|
|
@ -1385,14 +1417,104 @@ class Patient extends BaseController
|
|||
}
|
||||
|
||||
$profile = $this->pickGatewayAbhaProfile($payload);
|
||||
$gatewayPatientPayload = is_array($payload['gateway_patient'] ?? null) ? $payload['gateway_patient'] : [];
|
||||
$abhaNumberRaw = trim((string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? ''));
|
||||
$abhaDigits = preg_replace('/\D/', '', $abhaNumberRaw);
|
||||
$abhaAddress = trim((string) ($profile['preferredAbhaAddress'] ?? $profile['abha_address'] ?? $payload['abha_address'] ?? $payload['preferredAbhaAddress'] ?? ''));
|
||||
$abhaAddress = trim((string) (
|
||||
$profile['preferredAddress']
|
||||
?? $profile['preferredAbhaAddress']
|
||||
?? $profile['abha_address']
|
||||
?? $payload['abha_address']
|
||||
?? $payload['preferredAddress']
|
||||
?? $payload['preferredAbhaAddress']
|
||||
?? ''
|
||||
));
|
||||
$gatewayName = trim((string) ($profile['name'] ?? $profile['fullName'] ?? $profile['full_name'] ?? $payload['name'] ?? $payload['full_name'] ?? ''));
|
||||
$gatewayMobile = trim((string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? ''));
|
||||
$gatewayDob = trim((string) ($profile['dob'] ?? $profile['date_of_birth'] ?? $payload['dob'] ?? $payload['date_of_birth'] ?? ''));
|
||||
$gatewayGender = trim((string) ($profile['gender'] ?? $payload['gender'] ?? ''));
|
||||
$gatewayPhoto = trim((string) ($profile['profilePhoto'] ?? $profile['profile_photo'] ?? $payload['profilePhoto'] ?? $payload['profile_photo'] ?? ''));
|
||||
$gatewayEmail = trim((string) ($profile['email'] ?? $payload['email'] ?? ''));
|
||||
$gatewayAddress = trim((string) (
|
||||
$profile['address']
|
||||
?? $profile['address_line']
|
||||
?? $payload['address']
|
||||
?? $payload['address_line']
|
||||
?? ($gatewayPatientPayload['address_line'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$gatewayZip = trim((string) (
|
||||
$profile['pinCode']
|
||||
?? $profile['pin_code']
|
||||
?? $payload['pinCode']
|
||||
?? $payload['pin_code']
|
||||
?? $payload['pincode']
|
||||
?? ($gatewayPatientPayload['pincode'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$gatewayStateCode = trim((string) (
|
||||
$profile['stateCode']
|
||||
?? $profile['state_code']
|
||||
?? $payload['stateCode']
|
||||
?? $payload['state_code']
|
||||
?? ($gatewayPatientPayload['state_code'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$gatewayStateName = trim((string) (
|
||||
$profile['stateName']
|
||||
?? $profile['state_name']
|
||||
?? $payload['stateName']
|
||||
?? $payload['state_name']
|
||||
?? ($gatewayPatientPayload['state_name'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$gatewayDistrictCode = trim((string) (
|
||||
$profile['districtCode']
|
||||
?? $profile['district_code']
|
||||
?? $payload['districtCode']
|
||||
?? $payload['district_code']
|
||||
?? ($payload['gateway_abha_profile']['district_code'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$gatewayDistrictName = trim((string) (
|
||||
$profile['districtName']
|
||||
?? $profile['district_name']
|
||||
?? $payload['districtName']
|
||||
?? $payload['district_name']
|
||||
?? ($payload['gateway_abha_profile']['district_name'] ?? '')
|
||||
?? ($gatewayPatientPayload['district'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$verifiedStatus = trim((string) (
|
||||
($payload['gateway_abha_profile']['status'] ?? '')
|
||||
?: ($profile['verifiedStatus'] ?? '')
|
||||
?: ($profile['status'] ?? '')
|
||||
?: ($profile['abhaStatus'] ?? '')
|
||||
?: ($payload['verifiedStatus'] ?? '')
|
||||
?: ($payload['status'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$verificationType = trim((string) (
|
||||
($payload['gateway_abha_profile']['abha_type'] ?? '')
|
||||
?: ($profile['verificationType'] ?? '')
|
||||
?: ($profile['abhaType'] ?? '')
|
||||
?: ($payload['verificationType'] ?? '')
|
||||
?: ($payload['abhaType'] ?? '')
|
||||
?: ''
|
||||
));
|
||||
$kycVerified = $profile['kycVerified'] ?? $payload['kycVerified'] ?? null;
|
||||
if ($kycVerified === null) {
|
||||
$kycVerified = strtolower(trim((string) ($payload['gateway_abha_profile']['status'] ?? ''))) === 'verified' ? 1 : 0;
|
||||
}
|
||||
$mobileVerified = $profile['mobileVerified']
|
||||
?? $payload['mobileVerified']
|
||||
?? ($payload['gateway_abha_profile']['mobile_verified'] ?? null);
|
||||
$gatewayCity = trim((string) ($profile['city'] ?? $payload['city'] ?? ($gatewayPatientPayload['city'] ?? '')));
|
||||
if ($gatewayCity === '') {
|
||||
$gatewayCity = $this->extractCityFromAddress($gatewayAddress, $gatewayDistrictName, $gatewayStateName);
|
||||
}
|
||||
|
||||
$this->upsertAbdmLocationMasters($gatewayStateCode, $gatewayStateName, $gatewayDistrictCode, $gatewayDistrictName);
|
||||
|
||||
$patient = $this->findPatientForAbhaProfile($requestedPid, $abhaDigits, $gatewayMobile);
|
||||
$patientId = (int) ($patient['id'] ?? 0);
|
||||
|
|
@ -1415,6 +1537,39 @@ class Patient extends BaseController
|
|||
}
|
||||
|
||||
$pmFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
if ($gatewayMobile !== '' && in_array('mphone1', $pmFields, true)) {
|
||||
$updates['mphone1'] = preg_replace('/\D/', '', $gatewayMobile);
|
||||
}
|
||||
if ($gatewayAddress !== '' && in_array('add1', $pmFields, true)) {
|
||||
$updates['add1'] = $gatewayAddress;
|
||||
}
|
||||
if ($gatewayCity !== '' && in_array('city', $pmFields, true)) {
|
||||
$updates['city'] = $gatewayCity;
|
||||
}
|
||||
if ($gatewayDistrictName !== '' && in_array('district', $pmFields, true)) {
|
||||
$updates['district'] = $gatewayDistrictName;
|
||||
}
|
||||
if ($gatewayStateName !== '' && in_array('state', $pmFields, true)) {
|
||||
$updates['state'] = $gatewayStateName;
|
||||
}
|
||||
if ($gatewayZip !== '' && in_array('zip', $pmFields, true)) {
|
||||
$updates['zip'] = $gatewayZip;
|
||||
}
|
||||
if ($gatewayEmail !== '' && in_array('email1', $pmFields, true)) {
|
||||
$updates['email1'] = $gatewayEmail;
|
||||
}
|
||||
if ($verifiedStatus !== '' && in_array('abha_verified_status', $pmFields, true)) {
|
||||
$updates['abha_verified_status'] = $verifiedStatus;
|
||||
}
|
||||
if ($verificationType !== '' && in_array('abha_verification_type', $pmFields, true)) {
|
||||
$updates['abha_verification_type'] = $verificationType;
|
||||
}
|
||||
if ($kycVerified !== null && in_array('abha_kyc_verified', $pmFields, true)) {
|
||||
$updates['abha_kyc_verified'] = (int) ((string) $kycVerified === '1' || (string) $kycVerified === 'true' || (string) $kycVerified === 'yes');
|
||||
}
|
||||
if ($mobileVerified !== null && in_array('abha_mobile_verified', $pmFields, true)) {
|
||||
$updates['abha_mobile_verified'] = (int) ((string) $mobileVerified === '1' || (string) $mobileVerified === 'true' || (string) $mobileVerified === 'yes');
|
||||
}
|
||||
if ($abhaAddress !== '' && in_array('abha_address', $pmFields, true)) {
|
||||
$updates['abha_address'] = $abhaAddress;
|
||||
}
|
||||
|
|
@ -1442,6 +1597,13 @@ class Patient extends BaseController
|
|||
'mobile' => $gatewayMobile,
|
||||
'dob' => $gatewayDob,
|
||||
'gender' => $gatewayGender,
|
||||
'address' => $gatewayAddress,
|
||||
'city' => $gatewayCity,
|
||||
'zip' => $gatewayZip,
|
||||
'state_code' => $gatewayStateCode,
|
||||
'state_name' => $gatewayStateName,
|
||||
'district_code' => $gatewayDistrictCode,
|
||||
'district_name' => $gatewayDistrictName,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_address' => $abhaAddress,
|
||||
'photo_base64' => $gatewayPhoto,
|
||||
|
|
@ -1490,6 +1652,188 @@ class Patient extends BaseController
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only safe/required fields for ABHA OTP verification APIs.
|
||||
* Prevents leaking connector tokens, full bridge profile dumps, and base64 photo blobs.
|
||||
*
|
||||
* @param array<string, mixed> $result
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeAbhaVerifyApiResponse(array $result): array
|
||||
{
|
||||
$ok = ! empty($result['ok']) && (int) $result['ok'] === 1;
|
||||
if (! $ok) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'error_text' => (string) ($result['error_text'] ?? $result['message'] ?? $result['data']['message'] ?? 'OTP verification failed'),
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
$payload = $result['data'];
|
||||
} elseif (is_array($result)) {
|
||||
$payload = $result;
|
||||
}
|
||||
|
||||
$profile = $this->pickGatewayAbhaProfile($payload);
|
||||
|
||||
$txnId = (string) ($payload['txnId'] ?? $payload['txn_id'] ?? $result['txnId'] ?? $result['txn_id'] ?? '');
|
||||
$abhaRaw = (string) ($payload['ABHANumber'] ?? $payload['abha_number'] ?? $payload['abha_id'] ?? $profile['ABHANumber'] ?? $profile['abha_number'] ?? $profile['abha_id'] ?? '');
|
||||
$abhaDigits = preg_replace('/\D/', '', $abhaRaw);
|
||||
$abhaAddress = (string) ($payload['preferredAbhaAddress'] ?? $payload['abha_address'] ?? $profile['preferredAbhaAddress'] ?? $profile['abha_address'] ?? $profile['preferredAddress'] ?? '');
|
||||
$name = trim((string) (
|
||||
$payload['name']
|
||||
?? $payload['full_name']
|
||||
?? $profile['name']
|
||||
?? $profile['fullName']
|
||||
?? $profile['full_name']
|
||||
?? trim(implode(' ', array_filter([
|
||||
(string) ($profile['firstName'] ?? ''),
|
||||
(string) ($profile['middleName'] ?? ''),
|
||||
(string) ($profile['lastName'] ?? ''),
|
||||
])))
|
||||
));
|
||||
$mobile = (string) ($payload['mobile'] ?? $payload['mobileNumber'] ?? $profile['mobile'] ?? '');
|
||||
$gender = (string) ($payload['gender'] ?? $profile['gender'] ?? '');
|
||||
$dob = (string) ($payload['dob'] ?? $payload['date_of_birth'] ?? $profile['dob'] ?? $profile['date_of_birth'] ?? '');
|
||||
$address = (string) ($payload['address'] ?? $payload['address_line'] ?? $profile['address'] ?? $profile['address_line'] ?? '');
|
||||
$pinCode = (string) ($payload['pinCode'] ?? $payload['pin_code'] ?? $payload['pincode'] ?? $profile['pinCode'] ?? $profile['pin_code'] ?? '');
|
||||
$stateCode = (string) ($payload['stateCode'] ?? $payload['state_code'] ?? $profile['stateCode'] ?? $profile['state_code'] ?? '');
|
||||
$stateName = (string) ($payload['stateName'] ?? $payload['state_name'] ?? $profile['stateName'] ?? $profile['state_name'] ?? '');
|
||||
$districtCode = (string) ($payload['districtCode'] ?? $payload['district_code'] ?? $profile['districtCode'] ?? $profile['district_code'] ?? '');
|
||||
$districtName = (string) ($payload['districtName'] ?? $payload['district_name'] ?? $profile['districtName'] ?? $profile['district_name'] ?? '');
|
||||
|
||||
$matchedPatientId = (int) ($result['hms_patient']['matched_patient_id'] ?? 0);
|
||||
$patientId = (int) ($result['patient_id'] ?? $payload['patient_id'] ?? $matchedPatientId);
|
||||
$pCode = (string) ($result['p_code'] ?? $payload['p_code'] ?? '');
|
||||
$isNew = $result['is_new_patient'] ?? $payload['is_new_patient'] ?? null;
|
||||
|
||||
$safeProfile = [
|
||||
'ABHANumber' => $abhaDigits,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_id' => $abhaDigits,
|
||||
'preferredAbhaAddress' => $abhaAddress,
|
||||
'preferredAddress' => $abhaAddress,
|
||||
'name' => $name,
|
||||
'full_name' => $name,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'mobile' => $mobile,
|
||||
'address' => $address,
|
||||
'pinCode' => $pinCode,
|
||||
'stateCode' => $stateCode,
|
||||
'stateName' => $stateName,
|
||||
'districtCode' => $districtCode,
|
||||
'districtName' => $districtName,
|
||||
'firstName' => (string) ($profile['firstName'] ?? ''),
|
||||
'middleName' => (string) ($profile['middleName'] ?? ''),
|
||||
'lastName' => (string) ($profile['lastName'] ?? ''),
|
||||
];
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
'message' => (string) ($payload['message'] ?? $result['message'] ?? 'ABHA OTP verified successfully'),
|
||||
'txn_id' => $txnId,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_address' => $abhaAddress,
|
||||
'name' => $name,
|
||||
'mobile' => $mobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'address' => $address,
|
||||
'pin_code' => $pinCode,
|
||||
'state_code' => $stateCode,
|
||||
'state_name' => $stateName,
|
||||
'district_code' => $districtCode,
|
||||
'district_name' => $districtName,
|
||||
'patient_id' => $patientId > 0 ? $patientId : null,
|
||||
'p_code' => $pCode !== '' ? $pCode : null,
|
||||
'is_new_patient' => $isNew,
|
||||
'data' => [
|
||||
'txnId' => $txnId,
|
||||
'abha_number' => $abhaDigits,
|
||||
'name' => $name,
|
||||
'mobile' => $mobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'address' => $address,
|
||||
'pin_code' => $pinCode,
|
||||
'state_code' => $stateCode,
|
||||
'state_name' => $stateName,
|
||||
'district_code' => $districtCode,
|
||||
'district_name' => $districtName,
|
||||
'profile' => $safeProfile,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function extractCityFromAddress(string $address, string $districtName = '', string $stateName = ''): string
|
||||
{
|
||||
$address = trim($address);
|
||||
if ($address === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$districtNorm = mb_strtoupper(trim($districtName));
|
||||
$stateNorm = mb_strtoupper(trim($stateName));
|
||||
$parts = array_values(array_filter(array_map('trim', explode(',', $address)), static fn (string $v): bool => $v !== ''));
|
||||
if ($parts === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for ($i = count($parts) - 1; $i >= 0; $i--) {
|
||||
$candidate = trim((string) ($parts[$i] ?? ''));
|
||||
if ($candidate === '' || preg_match('/\d/', $candidate) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$norm = mb_strtoupper($candidate);
|
||||
if (($districtNorm !== '' && $norm === $districtNorm) || ($stateNorm !== '' && $norm === $stateNorm)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function upsertAbdmLocationMasters(string $stateCode, string $stateName, string $districtCode, string $districtName): void
|
||||
{
|
||||
// State data lives in india_state (read-only reference — no writes needed from ABHA flow).
|
||||
|
||||
if ($districtCode !== '' && $districtName !== '' && $this->db->tableExists('abdm_district_master')) {
|
||||
$districtBuilder = $this->db->table('abdm_district_master')
|
||||
->select('id')
|
||||
->where('district_code', $districtCode);
|
||||
if ($stateCode !== '') {
|
||||
$districtBuilder->where('state_code', $stateCode);
|
||||
}
|
||||
$districtRow = $districtBuilder->limit(1)->get()->getRowArray();
|
||||
|
||||
if ($districtRow === null) {
|
||||
$this->db->table('abdm_district_master')->insert([
|
||||
'district_code' => $districtCode,
|
||||
'district_name' => $districtName,
|
||||
'state_code' => $stateCode,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
} else {
|
||||
$this->db->table('abdm_district_master')
|
||||
->where('id', (int) ($districtRow['id'] ?? 0))
|
||||
->update([
|
||||
'district_name' => $districtName,
|
||||
'state_code' => $stateCode,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
|
|
@ -1497,10 +1841,11 @@ class Patient extends BaseController
|
|||
private function pickGatewayAbhaProfile(array $payload): array
|
||||
{
|
||||
$candidates = [
|
||||
$payload['gateway_patient'] ?? null,
|
||||
$payload['ABHAProfile'] ?? null,
|
||||
$payload['accounts'][0] ?? null,
|
||||
$payload['gateway_abha_profile'] ?? null,
|
||||
$payload['profile'] ?? null,
|
||||
$payload['accounts'][0] ?? null,
|
||||
$payload['gateway_patient'] ?? null,
|
||||
$payload,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateAbdmLocationMasters extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->createStateMaster();
|
||||
$this->createDistrictMaster();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if ($this->hasTable('abdm_district_master')) {
|
||||
$this->forge->dropTable('abdm_district_master', true);
|
||||
}
|
||||
|
||||
if ($this->hasTable('abdm_state_master')) {
|
||||
$this->forge->dropTable('abdm_state_master', true);
|
||||
}
|
||||
}
|
||||
|
||||
private function createStateMaster(): void
|
||||
{
|
||||
if ($this->hasTable('abdm_state_master')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'state_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 10,
|
||||
'null' => false,
|
||||
],
|
||||
'state_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => false,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey('state_code');
|
||||
$this->forge->addKey('state_name');
|
||||
$this->forge->createTable('abdm_state_master', true);
|
||||
}
|
||||
|
||||
private function createDistrictMaster(): void
|
||||
{
|
||||
if ($this->hasTable('abdm_district_master')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'district_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 10,
|
||||
'null' => false,
|
||||
],
|
||||
'district_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => false,
|
||||
],
|
||||
'state_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 10,
|
||||
'null' => false,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey(['district_code', 'state_code']);
|
||||
$this->forge->addKey('district_name');
|
||||
$this->forge->addKey('state_code');
|
||||
$this->forge->createTable('abdm_district_master', true);
|
||||
}
|
||||
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
if (! preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = $this->db->query("SHOW TABLES LIKE '" . $table . "'")->getRowArray();
|
||||
return ! empty($row);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
/**
|
||||
* Drop abdm_state_master — superseded by the existing india_state table
|
||||
* which already has all 38 states/UTs and is used as the authoritative
|
||||
* reference for the patient registration State dropdown.
|
||||
*/
|
||||
class DropAbdmStateMaster extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$row = $this->db->query("SHOW TABLES LIKE 'abdm_state_master'")->getRowArray();
|
||||
if (! empty($row)) {
|
||||
$this->forge->dropTable('abdm_state_master', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate the table if rolled back (data is not restored)
|
||||
if (! empty($this->db->query("SHOW TABLES LIKE 'abdm_state_master'")->getRowArray())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
|
||||
'state_code' => ['type' => 'VARCHAR', 'constraint' => 10, 'null' => false],
|
||||
'state_name' => ['type' => 'VARCHAR', 'constraint' => 120, 'null' => false],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey('state_code');
|
||||
$this->forge->createTable('abdm_state_master', true);
|
||||
}
|
||||
}
|
||||
205
app/Database/Seeds/AbdmLocationMasterSeeder.php
Normal file
205
app/Database/Seeds/AbdmLocationMasterSeeder.php
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class AbdmLocationMasterSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Canonical India state/UT code map used by ABDM payloads.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function stateMap(): array
|
||||
{
|
||||
return [
|
||||
'1' => 'JAMMU AND KASHMIR',
|
||||
'2' => 'HIMACHAL PRADESH',
|
||||
'3' => 'PUNJAB',
|
||||
'4' => 'CHANDIGARH',
|
||||
'5' => 'UTTARAKHAND',
|
||||
'6' => 'HARYANA',
|
||||
'7' => 'DELHI',
|
||||
'8' => 'RAJASTHAN',
|
||||
'9' => 'UTTAR PRADESH',
|
||||
'10' => 'BIHAR',
|
||||
'11' => 'SIKKIM',
|
||||
'12' => 'ARUNACHAL PRADESH',
|
||||
'13' => 'NAGALAND',
|
||||
'14' => 'MANIPUR',
|
||||
'15' => 'MIZORAM',
|
||||
'16' => 'TRIPURA',
|
||||
'17' => 'MEGHALAYA',
|
||||
'18' => 'ASSAM',
|
||||
'19' => 'WEST BENGAL',
|
||||
'20' => 'JHARKHAND',
|
||||
'21' => 'ODISHA',
|
||||
'22' => 'CHHATTISGARH',
|
||||
'23' => 'MADHYA PRADESH',
|
||||
'24' => 'GUJARAT',
|
||||
'25' => 'DAMAN AND DIU',
|
||||
'26' => 'DADRA AND NAGAR HAVELI',
|
||||
'27' => 'MAHARASHTRA',
|
||||
'28' => 'ANDHRA PRADESH',
|
||||
'29' => 'KARNATAKA',
|
||||
'30' => 'GOA',
|
||||
'31' => 'LAKSHADWEEP',
|
||||
'32' => 'KERALA',
|
||||
'33' => 'TAMIL NADU',
|
||||
'34' => 'PUDUCHERRY',
|
||||
'35' => 'ANDAMAN AND NICOBAR ISLANDS',
|
||||
'36' => 'TELANGANA',
|
||||
'37' => 'ANDHRA PRADESH',
|
||||
'38' => 'LADAKH',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* District seed data.
|
||||
* state_code '5' = Uttarakhand (ABDM numeric code, matches india_state.id).
|
||||
* District codes follow NIC format (UK01–UK13).
|
||||
* The legacy ABDM-numeric entry (code 56) is kept for backward compatibility.
|
||||
*
|
||||
* @return array<int, array{district_code:string,district_name:string,state_code:string}>
|
||||
*/
|
||||
private function districtSeeds(): array
|
||||
{
|
||||
return [
|
||||
// Legacy ABDM-numeric code (from live ABHA verify responses)
|
||||
['district_code' => '56', 'district_name' => 'UDHAM SINGH NAGAR', 'state_code' => '5'],
|
||||
|
||||
// Uttarakhand — NIC district codes
|
||||
['district_code' => 'UK01', 'district_name' => 'ALMORA', 'state_code' => '5'],
|
||||
|
||||
// Uttar Pradesh — NIC district codes (state_code '9' = india_state.id)
|
||||
['district_code' => 'UP01', 'district_name' => 'AGRA', 'state_code' => '9'],
|
||||
['district_code' => 'UP02', 'district_name' => 'ALIGARH', 'state_code' => '9'],
|
||||
['district_code' => 'UP03', 'district_name' => 'AMBEDKAR NAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP04', 'district_name' => 'AMETHI', 'state_code' => '9'],
|
||||
['district_code' => 'UP05', 'district_name' => 'AMROHA', 'state_code' => '9'],
|
||||
['district_code' => 'UP06', 'district_name' => 'AURAIYA', 'state_code' => '9'],
|
||||
['district_code' => 'UP07', 'district_name' => 'AYODHYA', 'state_code' => '9'],
|
||||
['district_code' => 'UP08', 'district_name' => 'AZAMGARH', 'state_code' => '9'],
|
||||
['district_code' => 'UP09', 'district_name' => 'BAGHPAT', 'state_code' => '9'],
|
||||
['district_code' => 'UP10', 'district_name' => 'BAHRAICH', 'state_code' => '9'],
|
||||
['district_code' => 'UP11', 'district_name' => 'BALLIA', 'state_code' => '9'],
|
||||
['district_code' => 'UP12', 'district_name' => 'BALRAMPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP13', 'district_name' => 'BANDA', 'state_code' => '9'],
|
||||
['district_code' => 'UP14', 'district_name' => 'BARABANKI', 'state_code' => '9'],
|
||||
['district_code' => 'UP15', 'district_name' => 'BAREILLY', 'state_code' => '9'],
|
||||
['district_code' => 'UP16', 'district_name' => 'BASTI', 'state_code' => '9'],
|
||||
['district_code' => 'UP17', 'district_name' => 'BHADOHI', 'state_code' => '9'],
|
||||
['district_code' => 'UP18', 'district_name' => 'BIJNOR', 'state_code' => '9'],
|
||||
['district_code' => 'UP19', 'district_name' => 'BUDAUN', 'state_code' => '9'],
|
||||
['district_code' => 'UP20', 'district_name' => 'BULANDSHAHR', 'state_code' => '9'],
|
||||
['district_code' => 'UP21', 'district_name' => 'CHANDAULI', 'state_code' => '9'],
|
||||
['district_code' => 'UP22', 'district_name' => 'CHITRAKOOT', 'state_code' => '9'],
|
||||
['district_code' => 'UP23', 'district_name' => 'DEORIA', 'state_code' => '9'],
|
||||
['district_code' => 'UP24', 'district_name' => 'ETAH', 'state_code' => '9'],
|
||||
['district_code' => 'UP25', 'district_name' => 'ETAWAH', 'state_code' => '9'],
|
||||
['district_code' => 'UP26', 'district_name' => 'FARRUKHABAD', 'state_code' => '9'],
|
||||
['district_code' => 'UP27', 'district_name' => 'FATEHPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP28', 'district_name' => 'FIROZABAD', 'state_code' => '9'],
|
||||
['district_code' => 'UP29', 'district_name' => 'GAUTAM BUDDHA NAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP30', 'district_name' => 'GHAZIABAD', 'state_code' => '9'],
|
||||
['district_code' => 'UP31', 'district_name' => 'GHAZIPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP32', 'district_name' => 'GONDA', 'state_code' => '9'],
|
||||
['district_code' => 'UP33', 'district_name' => 'GORAKHPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP34', 'district_name' => 'HAMIRPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP35', 'district_name' => 'HAPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP36', 'district_name' => 'HARDOI', 'state_code' => '9'],
|
||||
['district_code' => 'UP37', 'district_name' => 'HATHRAS', 'state_code' => '9'],
|
||||
['district_code' => 'UP38', 'district_name' => 'JALAUN', 'state_code' => '9'],
|
||||
['district_code' => 'UP39', 'district_name' => 'JAUNPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP40', 'district_name' => 'JHANSI', 'state_code' => '9'],
|
||||
['district_code' => 'UP41', 'district_name' => 'KANNAUJ', 'state_code' => '9'],
|
||||
['district_code' => 'UP42', 'district_name' => 'KANPUR DEHAT', 'state_code' => '9'],
|
||||
['district_code' => 'UP43', 'district_name' => 'KANPUR NAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP44', 'district_name' => 'KANSHIRAM NAGAR (KASGANJ)', 'state_code' => '9'],
|
||||
['district_code' => 'UP45', 'district_name' => 'KAUSHAMBI', 'state_code' => '9'],
|
||||
['district_code' => 'UP46', 'district_name' => 'KUSHINAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP47', 'district_name' => 'LAKHIMPUR KHERI', 'state_code' => '9'],
|
||||
['district_code' => 'UP48', 'district_name' => 'LALITPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP49', 'district_name' => 'LUCKNOW', 'state_code' => '9'],
|
||||
['district_code' => 'UP50', 'district_name' => 'MAHARAJGANJ', 'state_code' => '9'],
|
||||
['district_code' => 'UP51', 'district_name' => 'MAHOBA', 'state_code' => '9'],
|
||||
['district_code' => 'UP52', 'district_name' => 'MAINPURI', 'state_code' => '9'],
|
||||
['district_code' => 'UP53', 'district_name' => 'MATHURA', 'state_code' => '9'],
|
||||
['district_code' => 'UP54', 'district_name' => 'MAU', 'state_code' => '9'],
|
||||
['district_code' => 'UP55', 'district_name' => 'MEERUT', 'state_code' => '9'],
|
||||
['district_code' => 'UP56', 'district_name' => 'MIRZAPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP57', 'district_name' => 'MORADABAD', 'state_code' => '9'],
|
||||
['district_code' => 'UP58', 'district_name' => 'MUZAFFARNAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP59', 'district_name' => 'PILIBHIT', 'state_code' => '9'],
|
||||
['district_code' => 'UP60', 'district_name' => 'PRATAPGARH', 'state_code' => '9'],
|
||||
['district_code' => 'UP61', 'district_name' => 'PRAYAGRAJ', 'state_code' => '9'],
|
||||
['district_code' => 'UP62', 'district_name' => 'RAE BARELI', 'state_code' => '9'],
|
||||
['district_code' => 'UP63', 'district_name' => 'RAMPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP64', 'district_name' => 'SAHARANPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP65', 'district_name' => 'SAMBHAL', 'state_code' => '9'],
|
||||
['district_code' => 'UP66', 'district_name' => 'SANT KABIR NAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP67', 'district_name' => 'SHAHJAHANPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP68', 'district_name' => 'SHAMLI', 'state_code' => '9'],
|
||||
['district_code' => 'UP69', 'district_name' => 'SHRAVASTI', 'state_code' => '9'],
|
||||
['district_code' => 'UP70', 'district_name' => 'SIDDHARTHNAGAR', 'state_code' => '9'],
|
||||
['district_code' => 'UP71', 'district_name' => 'SITAPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP72', 'district_name' => 'SONBHADRA', 'state_code' => '9'],
|
||||
['district_code' => 'UP73', 'district_name' => 'SULTANPUR', 'state_code' => '9'],
|
||||
['district_code' => 'UP74', 'district_name' => 'UNNAO', 'state_code' => '9'],
|
||||
['district_code' => 'UP75', 'district_name' => 'VARANASI', 'state_code' => '9'],
|
||||
['district_code' => 'UK02', 'district_name' => 'BAGESHWAR', 'state_code' => '5'],
|
||||
['district_code' => 'UK03', 'district_name' => 'CHAMOLI', 'state_code' => '5'],
|
||||
['district_code' => 'UK04', 'district_name' => 'CHAMPAWAT', 'state_code' => '5'],
|
||||
['district_code' => 'UK05', 'district_name' => 'DEHRADUN', 'state_code' => '5'],
|
||||
['district_code' => 'UK06', 'district_name' => 'HARIDWAR', 'state_code' => '5'],
|
||||
['district_code' => 'UK07', 'district_name' => 'NAINITAL', 'state_code' => '5'],
|
||||
['district_code' => 'UK08', 'district_name' => 'PAURI GARHWAL', 'state_code' => '5'],
|
||||
['district_code' => 'UK09', 'district_name' => 'PITHORAGARH', 'state_code' => '5'],
|
||||
['district_code' => 'UK10', 'district_name' => 'RUDRAPRAYAG', 'state_code' => '5'],
|
||||
['district_code' => 'UK11', 'district_name' => 'TEHRI GARHWAL', 'state_code' => '5'],
|
||||
['district_code' => 'UK12', 'district_name' => 'UDHAM SINGH NAGAR', 'state_code' => '5'],
|
||||
['district_code' => 'UK13', 'district_name' => 'UTTARKASHI', 'state_code' => '5'],
|
||||
];
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_district_master')) {
|
||||
// Table is created by migration 2026-06-24-000610.
|
||||
return;
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
// State reference data lives in india_state (pre-seeded). No writes needed here.
|
||||
|
||||
// Upsert districts by unique (district_code, state_code).
|
||||
$districtTable = $this->db->table('abdm_district_master');
|
||||
foreach ($this->districtSeeds() as $row) {
|
||||
$existing = $districtTable
|
||||
->select('id')
|
||||
->where('district_code', $row['district_code'])
|
||||
->where('state_code', $row['state_code'])
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($existing) {
|
||||
$districtTable->where('id', (int) ($existing['id'] ?? 0))->update([
|
||||
'district_name' => $row['district_name'],
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$districtTable->insert([
|
||||
'district_code' => $row['district_code'],
|
||||
'district_name' => $row['district_name'],
|
||||
'state_code' => $row['state_code'],
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +107,51 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function redactSensitiveForLog(array $payload): array
|
||||
{
|
||||
$walk = function ($value, string $key = '') use (&$walk) {
|
||||
if (is_array($value)) {
|
||||
$out = [];
|
||||
foreach ($value as $k => $v) {
|
||||
$kk = (string) $k;
|
||||
$out[$k] = $walk($v, $kk);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
$lowerKey = strtolower($key);
|
||||
if (in_array($lowerKey, ['token', 'refresh_token', 'refreshtoken', 'authorization', 'auth', 'otp'], true)) {
|
||||
return '[REDACTED]';
|
||||
}
|
||||
|
||||
if (in_array($lowerKey, ['photo', 'profilephoto', 'profile_photo'], true)) {
|
||||
return '[REDACTED_IMAGE_BASE64]';
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$trimmed = trim($value);
|
||||
|
||||
// Redact JWT-like values by shape.
|
||||
if (preg_match('/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/', $trimmed)) {
|
||||
return '[REDACTED_JWT]';
|
||||
}
|
||||
|
||||
// Redact probable base64 blobs (e.g., profile photo) to keep logs small.
|
||||
if (strlen($trimmed) > 400 && preg_match('/^[A-Za-z0-9+\/=\r\n]+$/', $trimmed)) {
|
||||
return '[REDACTED_LARGE_BASE64]';
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
return $walk($payload);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal HTTP helper
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -176,16 +221,22 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
|
||||
// Log outgoing request (mask token)
|
||||
$maskedToken = $this->token !== '' ? (substr($this->token, 0, 6) . '***' . substr($this->token, -4)) : '(none)';
|
||||
$safeRequestBody = $this->redactSensitiveForLog($body);
|
||||
log_message('debug', '[EAtriaBridge] --> ' . $method . ' ' . $url
|
||||
. ' | token=' . $maskedToken
|
||||
. ' | body=' . json_encode($body));
|
||||
. ' | body=' . json_encode($safeRequestBody));
|
||||
|
||||
$raw = curl_exec($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlErr = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
log_message('debug', '[EAtriaBridge] <-- HTTP ' . $httpCode . ' | raw=' . (string) $raw);
|
||||
$safeRawForLog = (string) $raw;
|
||||
$decodedRawForLog = json_decode((string) $raw, true);
|
||||
if (is_array($decodedRawForLog)) {
|
||||
$safeRawForLog = (string) json_encode($this->redactSensitiveForLog($decodedRawForLog), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
log_message('debug', '[EAtriaBridge] <-- HTTP ' . $httpCode . ' | raw=' . $safeRawForLog);
|
||||
|
||||
if ($curlErr !== '') {
|
||||
log_message('error', '[EAtriaBridge] cURL error on ' . $url . ': ' . $curlErr);
|
||||
|
|
@ -236,9 +287,11 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
|
||||
$decodedResp = json_decode($rawResponse, true);
|
||||
$responseJson = is_array($decodedResp)
|
||||
? (string) json_encode($decodedResp, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
? (string) json_encode($this->redactSensitiveForLog($decodedResp), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
: (trim($rawResponse) !== '' ? $rawResponse : null);
|
||||
|
||||
$safeRequestBody = $this->redactSensitiveForLog($requestBody);
|
||||
|
||||
$db->table('abdm_api_logs')->insert([
|
||||
'channel' => 'eatria_bridge',
|
||||
'event_type' => $eventType !== '' ? $eventType : $path,
|
||||
|
|
@ -246,7 +299,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
'http_method' => strtoupper($method),
|
||||
'entity_type' => null,
|
||||
'entity_id' => null,
|
||||
'request_json' => (string) json_encode($requestBody, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'request_json' => (string) json_encode($safeRequestBody, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'response_code' => $httpCode > 0 ? $httpCode : null,
|
||||
'response_json' => $responseJson,
|
||||
'status' => $status,
|
||||
|
|
|
|||
|
|
@ -497,26 +497,42 @@
|
|||
|
||||
if (r.requires_confirmation) {
|
||||
const matches = Array.isArray(r.matches) ? r.matches : [];
|
||||
const confidenceBadge = conf => {
|
||||
const map = { definitive: ['success','Definitive'], high: ['primary','High'], medium: ['warning','Medium'], low: ['secondary','Low'] };
|
||||
const [cls, lbl] = map[conf] || ['secondary', 'Low'];
|
||||
return `<span class="badge bg-${cls} ms-1">${lbl}</span>`;
|
||||
};
|
||||
const genderLabel = g => g == 2 ? 'F' : g == 1 ? 'M' : '—';
|
||||
const cards = matches.map(m => {
|
||||
const reasons = Array.isArray(m.match_reasons) ? m.match_reasons.join(', ') : '';
|
||||
return `<div class="card mb-2 border-warning">
|
||||
const conf = m.match_confidence || 'low';
|
||||
const borderCls = conf === 'definitive' ? 'border-success' : conf === 'high' ? 'border-primary' : conf === 'medium' ? 'border-warning' : 'border-secondary';
|
||||
return `<div class="card mb-2 ${borderCls}">
|
||||
<div class="card-body py-2 px-3 small">
|
||||
<div class="fw-bold">${esc(m.p_code || 'UHID N/A')} - ${esc(m.p_fname || 'Unnamed')}</div>
|
||||
<div class="text-muted">Phone: ${esc(m.mphone1 || '—')} | ABHA: ${esc(m.patient_abha || '—')} | Aadhaar: ${esc(m.patient_aadhaar || '—')}</div>
|
||||
<div class="text-warning">Matched by: ${esc(reasons || 'Data match')}</div>
|
||||
<button class="btn btn-sm btn-outline-primary mt-2" onclick="abdmQResolveExisting(${Number(t.id) || 0}, ${Number(m.id) || 0}, '${payloadEncoded}')">Use This Patient</button>
|
||||
<div class="fw-bold">${esc(m.p_code || 'UHID N/A')} - ${esc(m.p_fname || 'Unnamed')} ${confidenceBadge(conf)}</div>
|
||||
<div class="text-muted">DOB: ${esc(m.dob || '—')} | Gender: ${genderLabel(m.gender)} | Phone: ${esc(m.mphone1 || '—')}</div>
|
||||
<div class="text-muted">ABHA: ${esc(m.patient_abha || '—')} | Aadhaar: ${esc(m.patient_aadhaar || '—')}</div>
|
||||
<div class="text-muted fst-italic">Matched by: ${esc(reasons || 'Data match')}</div>
|
||||
<button class="btn btn-sm btn-outline-primary mt-2" onclick="abdmQResolveExisting(${Number(t.id) || 0}, ${Number(m.id) || 0}, '${payloadEncoded}')">Link to This Patient</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const createNewCard = `<div class="card mb-2 border-dashed border-2 border-secondary">
|
||||
<div class="card-body py-2 px-3 small">
|
||||
<div class="fw-bold text-secondary"><i class="bi bi-person-plus me-1"></i>None of the above — Register as New Patient</div>
|
||||
<div class="text-muted">This ABHA does not belong to any listed record. A new HMS patient will be created.</div>
|
||||
<button class="btn btn-sm btn-success mt-2" onclick="abdmQCreateNewPatient(${Number(t.id) || 0}, '${payloadEncoded}')">Create New Patient</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
body.innerHTML = `
|
||||
<div class="alert alert-warning py-2 small mb-2">
|
||||
Similar patient records found. Confirm existing patient or create a new record.
|
||||
Possible matching records found in HMS. Select the correct patient to link, or register as new.
|
||||
</div>
|
||||
${cards}
|
||||
<button class="btn btn-sm btn-success w-100 mt-2" onclick="abdmQCreateNewPatient(${Number(t.id) || 0}, '${payloadEncoded}')">Create New Patient</button>
|
||||
<a href="${BASE}billing/patient" class="btn btn-sm btn-outline-secondary w-100 mt-2" target="_blank">Open Manual Patient Registration (/billing/patient)</a>
|
||||
<button class="btn btn-sm btn-outline-secondary w-100 mt-2" data-bs-dismiss="modal">Close</button>`;
|
||||
${cards}${createNewCard}
|
||||
<a href="${BASE}billing/patient" class="btn btn-sm btn-outline-secondary w-100 mt-1" target="_blank">Open Manual Patient Registration</a>
|
||||
<button class="btn btn-sm btn-outline-secondary w-100 mt-1" data-bs-dismiss="modal">Close</button>`;
|
||||
} else {
|
||||
abdmQCreateNewPatient(t.id, payloadEncoded, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -611,16 +611,35 @@
|
|||
cardHtml += '<div class="small mb-2"><strong>Phone:</strong> ' + ((identity.phone || '-') + '') + '</div>';
|
||||
|
||||
if (matches.length > 0) {
|
||||
cardHtml += '<div class="small fw-semibold mb-1">Matching HMS Patients</div>';
|
||||
cardHtml += '<div class="small fw-semibold mb-1">Possible Matching HMS Patients</div>';
|
||||
matches.forEach(function (m) {
|
||||
var conf = m.match_confidence || 'low';
|
||||
var confLabels = { definitive: 'Definitive', high: 'High', medium: 'Medium', low: 'Low' };
|
||||
var confBadgeClass = { definitive: 'bg-success', high: 'bg-primary', medium: 'bg-warning text-dark', low: 'bg-secondary' };
|
||||
var confBadge = '<span class="badge ' + (confBadgeClass[conf] || 'bg-secondary') + ' ms-1 small">' + (confLabels[conf] || 'Low') + '</span>';
|
||||
var reasons = Array.isArray(m.match_reasons) ? m.match_reasons.join(', ') : '';
|
||||
var genderLabel = m.gender == 2 ? 'F' : m.gender == 1 ? 'M' : '—';
|
||||
var borderClass = conf === 'definitive' ? 'border-success' : conf === 'high' ? 'border-primary' : conf === 'medium' ? 'border-warning' : 'border-secondary';
|
||||
var mLabel = ((m.p_code || 'UHID') + ' - ' + (m.p_fname || 'Unnamed'));
|
||||
cardHtml += '<div class="d-flex justify-content-between align-items-center border rounded p-1 mb-1 bg-white">';
|
||||
cardHtml += '<div class="small">' + mLabel + '</div>';
|
||||
cardHtml += '<button type="button" class="btn btn-sm btn-outline-primary" data-scan-use-existing="1" data-patient-id="' + (m.id || 0) + '">Use</button>';
|
||||
cardHtml += '<div class="border rounded p-1 mb-1 bg-white ' + borderClass + '">';
|
||||
cardHtml += '<div class="d-flex justify-content-between align-items-start">';
|
||||
cardHtml += '<div class="small"><span class="fw-semibold">' + mLabel + '</span>' + confBadge + '</div>';
|
||||
cardHtml += '<button type="button" class="btn btn-sm btn-outline-primary ms-2" data-scan-use-existing="1" data-patient-id="' + (m.id || 0) + '">Link</button>';
|
||||
cardHtml += '</div>';
|
||||
cardHtml += '<div class="small text-muted">DOB: ' + (m.dob || '—') + ' | Gender: ' + genderLabel + ' | Phone: ' + (m.mphone1 || '—') + '</div>';
|
||||
cardHtml += '<div class="small text-muted fst-italic">Matched by: ' + reasons + '</div>';
|
||||
cardHtml += '</div>';
|
||||
});
|
||||
// Add "Create New Patient" as a selectable card option at the end of the list
|
||||
cardHtml += '<div class="border rounded border-2 border-secondary p-1 mb-1 bg-white">';
|
||||
cardHtml += '<div class="d-flex justify-content-between align-items-start">';
|
||||
cardHtml += '<div class="small"><span class="fw-semibold text-secondary">None of the above — Register as New Patient</span></div>';
|
||||
cardHtml += '<button type="button" class="btn btn-sm btn-success ms-2" data-scan-create-new="1">Create New</button>';
|
||||
cardHtml += '</div>';
|
||||
cardHtml += '<div class="small text-muted">This ABHA does not belong to any listed record.</div>';
|
||||
cardHtml += '</div>';
|
||||
} else {
|
||||
cardHtml += '<div class="small text-muted mb-2">No existing patient matched from ABHA/phone.</div>';
|
||||
cardHtml += '<div class="small text-muted mb-2">No existing patient matched from HMS demographics or ABHA.</div>';
|
||||
}
|
||||
|
||||
cardHtml += '<div class="d-flex gap-2 mt-2">';
|
||||
|
|
@ -659,6 +678,28 @@
|
|||
});
|
||||
});
|
||||
|
||||
scanResolvePane.querySelectorAll('[data-scan-create-new="1"]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
setSandboxStatus('Creating new patient from scan result...', 'muted');
|
||||
post('<?= base_url('AbdmGateway/scan_share_link_patient') ?>/' + queueId, {
|
||||
action: 'create_new',
|
||||
abha_number: (identity.abha_number || ''),
|
||||
abha_address: (identity.abha_address || ''),
|
||||
patient_name: (identity.patient_name || ''),
|
||||
phone: (identity.phone || ''),
|
||||
gender: (identity.gender || ''),
|
||||
dob: (identity.dob || '')
|
||||
}, function (resp) {
|
||||
if (!resp || parseInt(resp.ok || 0, 10) !== 1) {
|
||||
setSandboxStatus((resp && resp.error_text) ? resp.error_text : 'Unable to create patient.', 'danger');
|
||||
return;
|
||||
}
|
||||
clearScanResolvePane();
|
||||
setSandboxStatus('New patient created: ' + (resp.p_code || resp.patient_id || '-') + '.', 'success');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var createBtn = document.getElementById('btnScanCreateNewPatient');
|
||||
if (createBtn) {
|
||||
createBtn.addEventListener('click', function () {
|
||||
|
|
|
|||
|
|
@ -261,15 +261,20 @@
|
|||
<div class="form-group">
|
||||
<label class="form-label">District</label>
|
||||
<input class="form-control" name="input_district"
|
||||
id="input_district" placeholder="District" type="text"
|
||||
autocomplete="on">
|
||||
id="input_district" placeholder="Type to search or add new…" type="text"
|
||||
autocomplete="off">
|
||||
<div class="form-text text-muted small">Type to search; unlisted districts can be entered freely.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label">State</label>
|
||||
<input class="form-control" name="input_state"
|
||||
id="input_state" placeholder="State" type="text" autocomplete="on">
|
||||
<select class="form-select" name="input_state" id="input_state">
|
||||
<option value="">— Select State —</option>
|
||||
<?php foreach (($india_states ?? []) as $st): ?>
|
||||
<option value="<?= esc($st['state_name']) ?>"><?= esc($st['state_name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1325,7 +1330,16 @@
|
|||
$('#input_city').val(p.city);
|
||||
}
|
||||
if (p.state && !$('#input_state').val()) {
|
||||
$('#input_state').val(p.state);
|
||||
// Try to match existing option (case-insensitive); fall back to first partial match
|
||||
var stateTarget = (p.state || '').trim().toUpperCase();
|
||||
var $sel = $('#input_state');
|
||||
var bestVal = '';
|
||||
$sel.find('option').each(function() {
|
||||
if ($(this).val().toUpperCase() === stateTarget) { bestVal = $(this).val(); return false; }
|
||||
});
|
||||
if (bestVal) {
|
||||
$sel.val(bestVal);
|
||||
}
|
||||
}
|
||||
if (p.dob && !$('#datepicker_dob').val()) {
|
||||
$('#datepicker_dob').val(p.dob);
|
||||
|
|
@ -1351,7 +1365,28 @@
|
|||
autofocus: true,
|
||||
select: function(event, ui) {
|
||||
$("#input_district").val(ui.item.l_district);
|
||||
$("#input_state").val(ui.item.l_state);
|
||||
var stateVal = (ui.item.l_state || '').trim().toUpperCase();
|
||||
var $stateSelect = $("#input_state");
|
||||
var matched = false;
|
||||
$stateSelect.find('option').each(function() {
|
||||
if ($(this).val().toUpperCase() === stateVal) {
|
||||
$stateSelect.val($(this).val());
|
||||
matched = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!matched && stateVal !== '') {
|
||||
$stateSelect.append($('<option>').val(ui.item.l_state).text(ui.item.l_state).prop('selected', true));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#input_district").autocomplete({
|
||||
source: "<?= base_url('billing/patient/district_list') ?>",
|
||||
minLength: 2,
|
||||
select: function(event, ui) {
|
||||
$("#input_district").val(ui.item.value);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user