Implement ABDM identity storage and align OPD consult/medicine dose masters

This commit is contained in:
DevSoftTech Server 2026-04-04 17:26:40 +05:30
parent bd5601c131
commit 9d88aaa2a6
22 changed files with 1034 additions and 72 deletions

View File

@ -512,6 +512,97 @@ Date: 2026-02-26
- `app/Controllers/Opd_prescription.php`
- `app/Config/Routes.php`
---
## ABDM Identity Storage — Phase 1 (Paused)
Date: 2026-04-04
Status: **PAUSED** — Endpoint server (Gateway) development in progress separately.
Resume after: Gateway API is ready and endpoint URLs are confirmed.
### Reference Repository
**ABDM Gateway / Endpoint Server:**
> https://github.com/infodevsofttech-admin/HMS-ABDM-Gateway.git
All ABDM API calls (ABHA creation, HPR lookup, HFR registration, token exchange) will be proxied through this gateway. HMS CI4 will call the gateway's REST endpoints rather than ABDM sandbox/production directly.
---
### What Was Completed (Phase 1 — Local Identity Storage)
All work below is merged/committed in the HMS CI4 working directory. No gateway calls yet — only local DB storage of ABDM identifiers.
#### 1. Migration — `app/Database/Migrations/2026-04-04-000036_AddAbdmIdentityFields.php`
- Adds `abha_id VARCHAR(30) NULL` to `patient_master` (safe check; backfills from legacy cols: `abha_no` → `abha` → `abha_address`)
- Adds `hpr_id VARCHAR(40) NULL` to `doctor_master` (safe check)
- Private helpers `tableExists()` / `getColumns()` use raw `SHOW TABLES LIKE` / `SHOW COLUMNS FROM` (CI4 Migration `$db` does not expose `tableExists()` directly)
- **ACTION REQUIRED: Run `php spark migrate` when resuming**
#### 2. Patient ABHA ID — `app/Controllers/Patient.php`
- `create()` + `update()` capture `input_abha_id` (POST), validate 14 digits, write to detected column
- New endpoint `update_abha()` — AJAX quick-update for patient profile page
- Helper methods: `isValidAbhaId()`, `applyPatientAbhaFieldValues()`, `resolvePatientAbhaIdField()`
- Column resolution priority: `abha_id` → `abha_no` → `abha` → `abha_address`
#### 3. Patient Views
- `app/Views/billing/Patient_V.php` — ABHA ID input in New Patient tab (14-digit masked)
- `app/Views/billing/Person_profile_V.php` — ABHA ID display + quick-update button with JS handler → POST `billing/patient/update_abha`
- `app/Views/billing/Person_Edit_V.php` — ABHA ID editable field in full edit form
#### 4. Doctor HPR ID — `app/Controllers/Setting/Doctor.php`
- `store()` + `updateRecord()` capture `input_hpr_id`, validate format, write to detected column
- Helper methods: `resolveDoctorHprField()`, `isValidHprId()`
- Column resolution priority: `hpr_id` → `hpr_no` → `hpr_number`
- Validation regex: `/^[A-Z0-9\/-]{6,40}$/`
#### 5. Doctor Views
- `app/Views/Setting/Doctor/Doctor_V.php` — HPR ID input in create form
- `app/Views/Setting/Doctor/Doctor_profile_V.php` — HPR ID prefilled input in profile/edit form
#### 6. Hospital HFR ID — `app/Controllers/Setting/HospitalProfile.php`
- `index()` reads `H_HFR_ID` from `hospital_setting` key-value store
- `save()` validates HFR format, upserts `H_HFR_ID`
- `reset()` deletes `H_HFR_ID` entry
- View: `app/Views/Setting/Admin/hospital_profile.php` — HFR ID input field; wired to save FormData and reset JS
#### 7. Routes — `app/Config/Routes.php`
- Added: `$routes->post('patient/update_abha', 'Patient::update_abha');`
---
### What Remains — Phase 2 (After Gateway Is Ready)
These tasks depend on the Gateway API (https://github.com/infodevsofttech-admin/HMS-ABDM-Gateway.git) being operational.
| # | Task | Notes |
|---|------|-------|
| 1 | **ABHA Creation/Linking flow** | Call gateway to create/link ABHA from Patient form; store returned 14-digit ABHA in `abha_id` |
| 2 | **ABHA OTP Verification flow** | Gateway handles ABDM OTP; HMS shows verify modal; on success update patient record |
| 3 | **HPR Lookup/Verify for Doctors** | Call gateway HPR search by doctor name/reg-no; store verified HPR ID |
| 4 | **HFR Registration status** | Call gateway to check/register hospital in HFR; store `H_HFR_ID` |
| 5 | **Token management** | Gateway handles ABDM access tokens; HMS only sends UID + secret; no token stored in HMS DB |
| 6 | **Surface ABHA/HPR/HFR in Pathlab/Radiology print templates** | Add to diagnosis data arrays from joined master tables; expose as `{{abha_id}}`, `{{hpr_id}}`, `{{hfr_id}}` placeholders |
| 7 | **Patient search by ABHA ID** | Extend `Patient::search()` to include `abha_id` alongside `udai` and `mphone1` |
| 8 | **Snapshot HPR at lab report sign time** | Add `signed_hpr_id` column to lab invoice header; store at signing for immutable audit trail |
| 9 | **ABHA QR Code on OPD/IPD/Billing printouts** | Generate QR from ABHA ID; embed in print views |
---
### Pathlab / Radiology — Confirmed No Separate Storage Needed
- `Diagnosis.php` already JOINs `patient_master` (18+ JOIN references) → inherits `abha_id`
- `Diagnosis.php` reads `hospital_setting` via `readSetting()` → inherits `H_HFR_ID`
- Doctor name on reports traces back to `doctor_master` → inherits `hpr_id`
- **Exception:** Only if immutable audit snapshot is required at sign time (Phase 2, Task 8 above)
---
### Gateway Integration Notes (To Be Confirmed)
- Gateway base URL: to be confirmed from https://github.com/infodevsofttech-admin/HMS-ABDM-Gateway.git
- Expected auth: shared secret / API key between HMS and Gateway (not ABDM tokens directly)
- HMS will POST patient/doctor data to Gateway; Gateway returns ABDM response
- All ABDM sandbox vs. production switching should be configured at Gateway level, not in HMS
- HMS Config key to add when ready: `ABDM_GATEWAY_BASE_URL`, `ABDM_GATEWAY_KEY` in `.env`
### Validation status
- VS Code diagnostics reported no errors in modified files after final patches.

View File

@ -402,6 +402,7 @@ $routes->group('billing', function($routes) {
$routes->get('patient/get_name', 'Patient::get_name');
$routes->post('patient/update', 'Patient::update');
$routes->post('patient/update_aadhar', 'Patient::update_aadhar');
$routes->post('patient/update_abha', 'Patient::update_abha');
$routes->post('patient/update_card', 'Patient::update_card');
$routes->get('case/newcase/(:num)/(:num)', 'Billing\\CaseMaster::newcase/$1/$2');
$routes->get('case/newcase/(:num)/(:num)/(:num)', 'Billing\\CaseMaster::newcase/$1/$2/$3');

View File

@ -2990,6 +2990,11 @@ class Opd extends BaseController
$mapDose = [];
$mapWhen = [];
$mapFreq = [];
$mapWhere = [];
$mapDoseLocal = [];
$mapWhenLocal = [];
$mapFreqLocal = [];
$mapWhereLocal = [];
$medGenericMap = [];
$missingGenericMedIds = [];
@ -2997,14 +3002,23 @@ class Opd extends BaseController
$f = $this->db->getFieldNames('opd_dose_shed');
$idF = $this->resolveFirstField($f, ['dose_shed_id', 'id']);
$labelF = $this->resolveFirstField($f, ['dose_show_sign', 'dose_sign', 'dose_sign_desc', 'name']);
$localLabelF = $this->resolveFirstField($f, ['dose_show_desc', 'dose_sign_hindi', 'dose_sign_desc']);
if ($idF !== null && $labelF !== null) {
$rows = $this->db->table('opd_dose_shed')->select($idF . ' as id,' . $labelF . ' as label')->where($labelF . ' !=', '')->get()->getResultArray();
$select = [$idF . ' as id', $labelF . ' as label'];
if ($localLabelF !== null && $localLabelF !== $labelF) {
$select[] = $localLabelF . ' as local_label';
}
$rows = $this->db->table('opd_dose_shed')->select(implode(',', $select))->where($labelF . ' !=', '')->get()->getResultArray();
foreach ($rows as $r) {
$id = trim((string) ($r['id'] ?? ''));
$label = trim((string) ($r['label'] ?? ''));
if ($id !== '' && $label !== '') {
$mapDose[$id] = $label;
}
$localLabel = trim((string) ($r['local_label'] ?? ''));
if ($id !== '' && $localLabel !== '') {
$mapDoseLocal[$id] = $localLabel;
}
}
}
}
@ -3013,14 +3027,23 @@ class Opd extends BaseController
$f = $this->db->getFieldNames('opd_dose_when');
$idF = $this->resolveFirstField($f, ['dose_when_id', 'id']);
$labelF = $this->resolveFirstField($f, ['dose_sign', 'dose_sign_desc', 'name']);
$localLabelF = $this->resolveFirstField($f, ['dose_sign_hindi', 'dose_sign_desc']);
if ($idF !== null && $labelF !== null) {
$rows = $this->db->table('opd_dose_when')->select($idF . ' as id,' . $labelF . ' as label')->where($labelF . ' !=', '')->get()->getResultArray();
$select = [$idF . ' as id', $labelF . ' as label'];
if ($localLabelF !== null && $localLabelF !== $labelF) {
$select[] = $localLabelF . ' as local_label';
}
$rows = $this->db->table('opd_dose_when')->select(implode(',', $select))->where($labelF . ' !=', '')->get()->getResultArray();
foreach ($rows as $r) {
$id = trim((string) ($r['id'] ?? ''));
$label = trim((string) ($r['label'] ?? ''));
if ($id !== '' && $label !== '') {
$mapWhen[$id] = $label;
}
$localLabel = trim((string) ($r['local_label'] ?? ''));
if ($id !== '' && $localLabel !== '') {
$mapWhenLocal[$id] = $localLabel;
}
}
}
}
@ -3029,14 +3052,48 @@ class Opd extends BaseController
$f = $this->db->getFieldNames('opd_dose_frequency');
$idF = $this->resolveFirstField($f, ['dose_freq_id', 'id']);
$labelF = $this->resolveFirstField($f, ['dose_sign', 'dose_sign_desc', 'name']);
$localLabelF = $this->resolveFirstField($f, ['dose_sign_hindi', 'dose_sign_desc']);
if ($idF !== null && $labelF !== null) {
$rows = $this->db->table('opd_dose_frequency')->select($idF . ' as id,' . $labelF . ' as label')->where($labelF . ' !=', '')->get()->getResultArray();
$select = [$idF . ' as id', $labelF . ' as label'];
if ($localLabelF !== null && $localLabelF !== $labelF) {
$select[] = $localLabelF . ' as local_label';
}
$rows = $this->db->table('opd_dose_frequency')->select(implode(',', $select))->where($labelF . ' !=', '')->get()->getResultArray();
foreach ($rows as $r) {
$id = trim((string) ($r['id'] ?? ''));
$label = trim((string) ($r['label'] ?? ''));
if ($id !== '' && $label !== '') {
$mapFreq[$id] = $label;
}
$localLabel = trim((string) ($r['local_label'] ?? ''));
if ($id !== '' && $localLabel !== '') {
$mapFreqLocal[$id] = $localLabel;
}
}
}
}
if ($this->db->tableExists('opd_dose_where')) {
$f = $this->db->getFieldNames('opd_dose_where');
$idF = $this->resolveFirstField($f, ['dose_where_id', 'id']);
$labelF = $this->resolveFirstField($f, ['dose_sign', 'dose_sign_desc', 'name']);
$localLabelF = $this->resolveFirstField($f, ['dose_sign_hindi', 'dose_sign_desc']);
if ($idF !== null && $labelF !== null) {
$select = [$idF . ' as id', $labelF . ' as label'];
if ($localLabelF !== null && $localLabelF !== $labelF) {
$select[] = $localLabelF . ' as local_label';
}
$rows = $this->db->table('opd_dose_where')->select(implode(',', $select))->where($labelF . ' !=', '')->get()->getResultArray();
foreach ($rows as $r) {
$id = trim((string) ($r['id'] ?? ''));
$label = trim((string) ($r['label'] ?? ''));
if ($id !== '' && $label !== '') {
$mapWhere[$id] = $label;
}
$localLabel = trim((string) ($r['local_label'] ?? ''));
if ($id !== '' && $localLabel !== '') {
$mapWhereLocal[$id] = $localLabel;
}
}
}
}
@ -3045,9 +3102,15 @@ class Opd extends BaseController
$doseRaw = trim((string) ($medRow['dosage'] ?? ''));
$whenRaw = trim((string) ($medRow['dosage_when'] ?? ''));
$freqRaw = trim((string) ($medRow['dosage_freq'] ?? ''));
$medRow['dosage_label'] = ($doseRaw !== '' && isset($mapDose[$doseRaw])) ? $mapDose[$doseRaw] : $doseRaw;
$medRow['dosage_when_label'] = ($whenRaw !== '' && isset($mapWhen[$whenRaw])) ? $mapWhen[$whenRaw] : $whenRaw;
$medRow['dosage_freq_label'] = ($freqRaw !== '' && isset($mapFreq[$freqRaw])) ? $mapFreq[$freqRaw] : $freqRaw;
$whereRaw = trim((string) ($medRow['dosage_where'] ?? ''));
$medRow['dosage_label'] = ($doseRaw !== '' && $doseRaw !== '0' && isset($mapDose[$doseRaw])) ? $mapDose[$doseRaw] : ($doseRaw === '0' ? '' : $doseRaw);
$medRow['dosage_when_label'] = ($whenRaw !== '' && $whenRaw !== '0' && isset($mapWhen[$whenRaw])) ? $mapWhen[$whenRaw] : ($whenRaw === '0' ? '' : $whenRaw);
$medRow['dosage_freq_label'] = ($freqRaw !== '' && $freqRaw !== '0' && isset($mapFreq[$freqRaw])) ? $mapFreq[$freqRaw] : ($freqRaw === '0' ? '' : $freqRaw);
$medRow['dosage_where_label'] = ($whereRaw !== '' && $whereRaw !== '0' && isset($mapWhere[$whereRaw])) ? $mapWhere[$whereRaw] : ($whereRaw === '0' ? '' : $whereRaw);
$medRow['dosage_local_label'] = ($doseRaw !== '' && $doseRaw !== '0' && isset($mapDoseLocal[$doseRaw])) ? $mapDoseLocal[$doseRaw] : '';
$medRow['dosage_when_local_label'] = ($whenRaw !== '' && $whenRaw !== '0' && isset($mapWhenLocal[$whenRaw])) ? $mapWhenLocal[$whenRaw] : '';
$medRow['dosage_freq_local_label'] = ($freqRaw !== '' && $freqRaw !== '0' && isset($mapFreqLocal[$freqRaw])) ? $mapFreqLocal[$freqRaw] : '';
$medRow['dosage_where_local_label'] = ($whereRaw !== '' && $whereRaw !== '0' && isset($mapWhereLocal[$whereRaw])) ? $mapWhereLocal[$whereRaw] : '';
$genericInline = trim((string) ($medRow['genericname'] ?? ($medRow['salt_name'] ?? '')));
if ($genericInline === '') {

View File

@ -4733,6 +4733,8 @@ class Opd_prescription extends BaseController
$insert['remark'] = strtoupper(trim((string) $this->request->getPost('remark')));
}
$this->applyDoseMasterStringFields($insert, $fields);
$user = auth()->user();
if (in_array('update_by', $fields, true)) {
$userLabel = $user ? ($user->username ?? $user->email ?? 'User') : 'User';
@ -4991,6 +4993,8 @@ class Opd_prescription extends BaseController
}
}
$this->applyDoseMasterStringFields($update, $fields);
if (empty($update)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'No editable fields found']);
}
@ -5372,15 +5376,43 @@ class Opd_prescription extends BaseController
$fields = $this->db->getFieldNames($table);
$idField = $this->resolveFirstField($fields, $idCandidates);
$labelField = $this->resolveFirstField($fields, $labelCandidates);
if ($idField === null || $labelField === null) {
if ($idField === null) {
return [];
}
$labelField = null;
$secondaryLabelField = null;
$localLabelField = null;
if ($table === 'opd_dose_shed') {
$labelField = $this->resolveFirstField($fields, ['dose_show_sign', 'dose_sign', 'dose_sign_desc', 'name']);
$localLabelField = $this->resolveFirstField($fields, ['dose_show_desc', 'dose_sign_hindi', 'dose_sign_desc']);
} else {
$labelField = $this->resolveFirstField($fields, ['dose_sign', 'dose_sign_desc', 'name']);
$secondaryLabelField = $this->resolveFirstField($fields, ['dose_sign_desc', 'name']);
$localLabelField = $this->resolveFirstField($fields, ['dose_sign_hindi', 'dose_sign_desc']);
}
if ($labelField === null) {
$labelField = $this->resolveFirstField($fields, $labelCandidates);
}
if ($labelField === null) {
return [];
}
$select = [$idField . ' as id', $labelField . ' as label'];
if ($secondaryLabelField !== null && $secondaryLabelField !== $labelField) {
$select[] = $secondaryLabelField . ' as secondary_label';
}
if ($localLabelField !== null && $localLabelField !== $labelField) {
$select[] = $localLabelField . ' as local_label';
}
return $this->db->table($table)
->select($idField . ' as id,' . $labelField . ' as label')
->select(implode(',', $select))
->where($labelField . ' !=', '')
->orderBy($labelField, 'ASC')
->orderBy($idField, 'ASC')
->get()
->getResultArray();
}
@ -5396,7 +5428,27 @@ class Opd_prescription extends BaseController
foreach ($rows as $row) {
$id = trim((string) ($row['id'] ?? ''));
$label = trim((string) ($row['label'] ?? ''));
if ($id === '' || $label === '') {
if ($id === '' || $id === '0' || $label === '') {
continue;
}
$map[$id] = $label;
}
return $map;
}
private function getDoseMasterLocalLabelMap(string $table, array $idCandidates, array $labelCandidates): array
{
$rows = $this->getDoseMasterRows($table, $idCandidates, $labelCandidates);
if (empty($rows)) {
return [];
}
$map = [];
foreach ($rows as $row) {
$id = trim((string) ($row['id'] ?? ''));
$label = trim((string) ($row['local_label'] ?? ($row['secondary_label'] ?? '')));
if ($id === '' || $id === '0' || $label === '') {
continue;
}
$map[$id] = $label;
@ -5408,13 +5460,35 @@ class Opd_prescription extends BaseController
private function resolveDoseMasterLabel($value, array $map): string
{
$raw = trim((string) $value);
if ($raw === '') {
if ($raw === '' || $raw === '0') {
return '';
}
return $map[$raw] ?? $raw;
}
private function applyDoseMasterStringFields(array &$payload, array $fields): void
{
$doseMap = $this->getDoseMasterLabelMap('opd_dose_shed', ['dose_shed_id', 'id'], ['dose_show_sign', 'dose_sign', 'dose_sign_desc', 'name']);
$whenMap = $this->getDoseMasterLabelMap('opd_dose_when', ['dose_when_id', 'id'], ['dose_sign', 'dose_sign_desc', 'name']);
$freqMap = $this->getDoseMasterLabelMap('opd_dose_frequency', ['dose_freq_id', 'id'], ['dose_sign', 'dose_sign_desc', 'name']);
$whereMap = $this->getDoseMasterLabelMap('opd_dose_where', ['dose_where_id', 'id'], ['dose_sign', 'dose_sign_desc', 'name']);
$stringFields = [
'dosage_str' => $this->resolveDoseMasterLabel($payload['dosage'] ?? '', $doseMap),
'dosage_when_str' => $this->resolveDoseMasterLabel($payload['dosage_when'] ?? '', $whenMap),
'dosage_freq_str' => $this->resolveDoseMasterLabel($payload['dosage_freq'] ?? '', $freqMap),
'dosage_where_str' => $this->resolveDoseMasterLabel($payload['dosage_where'] ?? '', $whereMap),
];
foreach ($stringFields as $field => $value) {
if ($value === '' || ! in_array($field, $fields, true)) {
continue;
}
$payload[$field] = $value;
}
}
private function hydrateNabhPrescriptionFields(array $row, string $remarks): array
{
$pick = function (array $candidates) use ($row): string {

View File

@ -38,6 +38,7 @@ class Patient extends BaseController
public function create()
{
$isAjax = $this->request->isAJAX();
$abhaId = trim((string) $this->request->getPost('input_abha_id'));
$chk_age = $this->request->getPost('chk_age');
$age_month = (string) $this->request->getPost('input_age_month');
@ -75,6 +76,19 @@ class Patient extends BaseController
->with('error', $errorText);
}
if ($abhaId !== '' && ! $this->isValidAbhaId($abhaId)) {
if ($isAjax) {
return $this->response->setJSON([
'insertid' => 0,
'error_text' => 'ABHA ID must be a 14-digit number.',
]);
}
return redirect()->to(base_url('billing/patient'))
->withInput()
->with('error', 'ABHA ID must be a 14-digit number.');
}
$bloodGroup = trim((string) $this->request->getPost('input_blood_group'));
if ($bloodGroup === '') {
$bloodGroup = 'Not Define';
@ -96,6 +110,7 @@ class Patient extends BaseController
'udai' => strtoupper((string) $this->request->getPost('input_udai')),
'estimate_dob' => $estimate_dob,
];
$this->applyPatientAbhaFieldValues($data, $abhaId);
if ($chk_age === 'on') {
$data['age'] = $age_year;
@ -383,6 +398,7 @@ class Patient extends BaseController
}
$chk_age = $this->request->getPost('chk_age');
$abhaId = trim((string) $this->request->getPost('input_abha_id'));
$age_month = (string) $this->request->getPost('input_age_month');
$age_year = (string) $this->request->getPost('input_age_year');
@ -410,6 +426,13 @@ class Patient extends BaseController
]);
}
if ($abhaId !== '' && ! $this->isValidAbhaId($abhaId)) {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'ABHA ID must be a 14-digit number.',
]);
}
$data = [
'mphone1' => $this->request->getPost('input_mphone1'),
'p_fname' => strtoupper((string) $this->request->getPost('input_name')),
@ -427,6 +450,7 @@ class Patient extends BaseController
'estimate_dob' => $estimate_dob,
'blood_group' => $this->request->getPost('input_blood_group'),
];
$this->applyPatientAbhaFieldValues($data, $abhaId);
if ($chk_age === 'on') {
$data['age'] = $age_year;
@ -469,6 +493,42 @@ class Patient extends BaseController
]);
}
public function update_abha()
{
if (!$this->request->isAJAX()) {
return $this->response
->setStatusCode(400)
->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
$pid = (int) $this->request->getPost('p_id');
$abhaId = trim((string) $this->request->getPost('abha_id'));
if ($abhaId !== '' && ! $this->isValidAbhaId($abhaId)) {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'ABHA ID must be a 14-digit number.',
]);
}
$data = [];
$this->applyPatientAbhaFieldValues($data, $abhaId);
if ($data === []) {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'ABHA field not found in patient master.',
]);
}
$patientModel = new PatientModel();
$patientModel->updatePatient($data, $pid);
return $this->response->setJSON([
'update' => 1,
'showcontent' => 'Data Saved successfully',
]);
}
public function update_card()
{
if (!$this->request->isAJAX()) {
@ -970,5 +1030,38 @@ class Patient extends BaseController
}
}
private function applyPatientAbhaFieldValues(array &$data, string $abhaId): void
{
$targetField = $this->resolvePatientAbhaIdField();
if ($targetField !== null) {
$data[$targetField] = $abhaId;
}
}
private function resolvePatientAbhaIdField(): ?string
{
if (! $this->db->tableExists('patient_master')) {
return null;
}
$fields = $this->db->getFieldNames('patient_master') ?? [];
foreach (['abha_id', 'abha_no', 'abha'] as $field) {
if (in_array($field, $fields, true)) {
return $field;
}
}
if (in_array('abha_address', $fields, true)) {
return 'abha_address';
}
return null;
}
private function isValidAbhaId(string $value): bool
{
return preg_match('/^\d{14}$/', $value) === 1;
}
}

View File

@ -55,12 +55,30 @@ class Doctor extends BaseController
$doctorModel = new DoctorModel();
$regField = $this->resolveDoctorRegField();
$hprField = $this->resolveDoctorHprField();
$shortDescription = trim((string) $this->request->getPost('txt_doc_sign'));
$regInput = trim((string) $this->request->getPost('input_doc_reg_no'));
$hprInput = strtoupper(trim((string) $this->request->getPost('input_hpr_id')));
if ($regInput === '' && $shortDescription !== '') {
$regInput = $this->extractRegNoFromText($shortDescription);
}
if ($hprInput !== '' && ! $this->isValidHprId($hprInput)) {
$errors = ['HPR ID format is invalid. Use alphanumeric characters with / or -'];
if ($this->request->isAJAX()) {
$templateContext = $this->getDoctorTemplateContext();
return view('Setting/Doctor/Doctor_V', [
'errors' => $errors,
'formData' => $this->request->getPost(),
'template_options' => $templateContext['options'],
'opd_print_template_options' => $templateContext['opd_print_options'],
'template_fields' => $templateContext['fields'],
]);
}
return redirect()->back()->withInput()->with('errors', $errors);
}
$data = [
'p_title' => $this->request->getPost('select_title'),
'mphone1' => $this->request->getPost('input_mphone1'),
@ -91,6 +109,9 @@ class Doctor extends BaseController
if ($regField !== null) {
$data[$regField] = $regInput;
}
if ($hprField !== null) {
$data[$hprField] = $hprInput;
}
$insertId = $doctorModel->insert($data);
@ -141,12 +162,22 @@ class Doctor extends BaseController
$doctorModel = new DoctorModel();
$docId = (int) $this->request->getPost('doc_id');
$regField = $this->resolveDoctorRegField();
$hprField = $this->resolveDoctorHprField();
$shortDescription = trim((string) $this->request->getPost('txt_doc_sign'));
$regInput = trim((string) $this->request->getPost('input_doc_reg_no'));
$hprInput = strtoupper(trim((string) $this->request->getPost('input_hpr_id')));
if ($regInput === '' && $shortDescription !== '') {
$regInput = $this->extractRegNoFromText($shortDescription);
}
if ($hprInput !== '' && ! $this->isValidHprId($hprInput)) {
return $this->response->setJSON([
'update' => 0,
'showcontent' => '',
'error_text' => 'HPR ID format is invalid. Use alphanumeric characters with / or -',
]);
}
$data = [
'p_title' => $this->request->getPost('select_title'),
'mphone1' => $this->request->getPost('input_mphone1'),
@ -177,6 +208,9 @@ class Doctor extends BaseController
if ($regField !== null) {
$data[$regField] = $regInput;
}
if ($hprField !== null) {
$data[$hprField] = $hprInput;
}
$updated = $doctorModel->updateDoctor($data, $docId);
@ -542,6 +576,27 @@ class Doctor extends BaseController
return null;
}
private function resolveDoctorHprField(): ?string
{
if (! $this->db->tableExists('doctor_master')) {
return null;
}
$fields = $this->db->getFieldNames('doctor_master');
foreach (['hpr_id', 'hpr_no', 'hpr_number'] as $field) {
if (in_array($field, $fields, true)) {
return $field;
}
}
return null;
}
private function isValidHprId(string $value): bool
{
return preg_match('/^[A-Z0-9\/-]{6,40}$/', $value) === 1;
}
private function extractRegNoFromText(string $text): string
{
$normalized = preg_replace('/\s+/', ' ', trim($text));

View File

@ -23,6 +23,7 @@ class HospitalProfile extends BaseController
'hospital_address_2' => $this->readSettingValue('H_address_2'),
'hospital_phone' => $this->readSettingValue('H_phone_No'),
'hospital_email' => $this->readSettingValue('H_Email'),
'hospital_hfr_id' => $this->readSettingValue('H_HFR_ID'),
'footer_version' => $this->readSettingValue('HMS_UPDATE_ID') !== ''
? $this->readSettingValue('HMS_UPDATE_ID')
: hms_default_version_id(),
@ -56,6 +57,7 @@ class HospitalProfile extends BaseController
$address2 = trim((string) $this->request->getPost('hospital_address_2'));
$phone = trim((string) $this->request->getPost('hospital_phone'));
$email = trim((string) $this->request->getPost('hospital_email'));
$hfrId = strtoupper(trim((string) $this->request->getPost('hospital_hfr_id')));
$footerVersion = trim((string) $this->request->getPost('footer_version'));
$pharmacyName = trim((string) $this->request->getPost('pharmacy_name'));
@ -72,6 +74,15 @@ class HospitalProfile extends BaseController
]);
}
if ($hfrId !== '' && preg_match('/^[A-Z0-9\/-]{6,40}$/', $hfrId) !== 1) {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'HFR ID format is invalid. Use alphanumeric characters with / or -',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
$savedCount = 0;
if ($this->upsertSettingValue('H_Name', $name)) {
$savedCount++;
@ -88,6 +99,9 @@ class HospitalProfile extends BaseController
if ($this->upsertSettingValue('H_Email', $email)) {
$savedCount++;
}
if ($this->upsertSettingValue('H_HFR_ID', $hfrId)) {
$savedCount++;
}
if ($this->upsertSettingValue('HMS_UPDATE_ID', $footerVersion)) {
$savedCount++;
}
@ -204,6 +218,7 @@ class HospitalProfile extends BaseController
'H_address_2',
'H_phone_No',
'H_Email',
'H_HFR_ID',
'HMS_UPDATE_ID',
'H_logo',
'H_Med_Name',

View File

@ -0,0 +1,97 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddAbdmIdentityFields extends Migration
{
public function up()
{
$this->addPatientAbhaId();
$this->addDoctorHprId();
}
public function down()
{
if ($this->tableExists('patient_master') && in_array('abha_id', $this->getColumns('patient_master'), true)) {
$this->forge->dropColumn('patient_master', 'abha_id');
}
if ($this->tableExists('doctor_master') && in_array('hpr_id', $this->getColumns('doctor_master'), true)) {
$this->forge->dropColumn('doctor_master', 'hpr_id');
}
}
private function addPatientAbhaId(): void
{
if (! $this->tableExists('patient_master')) {
return;
}
$columns = $this->getColumns('patient_master');
if (! in_array('abha_id', $columns, true)) {
$this->forge->addColumn('patient_master', [
'abha_id' => [
'type' => 'VARCHAR',
'constraint' => 30,
'null' => true,
],
]);
$columns[] = 'abha_id';
}
if (in_array('abha_id', $columns, true) && in_array('abha_no', $columns, true)) {
$this->db->query("UPDATE patient_master SET abha_id = IFNULL(NULLIF(abha_id,''), NULLIF(abha_no,''))");
}
if (in_array('abha_id', $columns, true) && in_array('abha', $columns, true)) {
$this->db->query("UPDATE patient_master SET abha_id = IFNULL(NULLIF(abha_id,''), NULLIF(abha,''))");
}
if (in_array('abha_id', $columns, true) && in_array('abha_address', $columns, true)) {
$this->db->query("UPDATE patient_master SET abha_id = IFNULL(NULLIF(abha_id,''), NULLIF(abha_address,''))");
}
}
private function addDoctorHprId(): void
{
if (! $this->tableExists('doctor_master')) {
return;
}
if (! in_array('hpr_id', $this->getColumns('doctor_master'), true)) {
$this->forge->addColumn('doctor_master', [
'hpr_id' => [
'type' => 'VARCHAR',
'constraint' => 40,
'null' => true,
],
]);
}
}
/**
* @return string[]
*/
private function getColumns(string $table): array
{
if (! preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
return [];
}
$result = $this->db->query('SHOW COLUMNS FROM `' . $table . '`')->getResultArray();
return array_map(static fn (array $row): string => (string) ($row['Field'] ?? ''), $result);
}
private function tableExists(string $table): bool
{
if (! preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
return false;
}
$row = $this->db->query("SHOW TABLES LIKE '" . $table . "'")->getRowArray();
return ! empty($row);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class SyncOpdDoseShedMaster extends Migration
{
public function up()
{
if (! $this->tableExists('opd_dose_shed')) {
return;
}
$this->db->table('opd_dose_shed')->truncate();
$rows = [
[
'dose_shed_id' => 1,
'dose_show_sign' => '1 - 0 - 0',
'dose_show_desc' => 'सुबह',
],
[
'dose_shed_id' => 2,
'dose_show_sign' => '0 - 0 - 1',
'dose_show_desc' => 'रात को',
],
[
'dose_shed_id' => 3,
'dose_show_sign' => '1 - 0 - 1',
'dose_show_desc' => 'सुबह - शाम',
],
[
'dose_shed_id' => 4,
'dose_show_sign' => '1 - 1 - 1',
'dose_show_desc' => 'सुबह - दोपहर - शाम',
],
[
'dose_shed_id' => 5,
'dose_show_sign' => '1 - 1 - 0',
'dose_show_desc' => 'सुबह - दोपहर',
],
[
'dose_shed_id' => 6,
'dose_show_sign' => '0 - 1 - 0',
'dose_show_desc' => 'दोपहर',
],
[
'dose_shed_id' => 7,
'dose_show_sign' => '0 - 1 - 1',
'dose_show_desc' => 'दोपहर - शाम',
],
[
'dose_shed_id' => 8,
'dose_show_sign' => '0 - 0 - 0',
'dose_show_desc' => '',
],
];
$this->db->table('opd_dose_shed')->insertBatch($rows);
$this->resetAutoIncrement('opd_dose_shed', 9);
}
public function down()
{
// Intentionally left as no-op because previous master seed values are environment-specific.
}
private function tableExists(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);
}
private function resetAutoIncrement(string $table, int $nextId): void
{
if (! preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
return;
}
$nextId = max(1, $nextId);
$this->db->query('ALTER TABLE `' . $table . '` AUTO_INCREMENT = ' . $nextId);
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class SyncOpdDoseWhenMaster extends Migration
{
public function up()
{
if (! $this->tableExists('opd_dose_when')) {
return;
}
$this->db->table('opd_dose_when')->truncate();
$rows = [
[
'dose_when_id' => 1,
'dose_sign' => 'BF',
'dose_sign_desc' => 'Before Food',
'dose_sign_hindi' => 'भोजन से पहले',
],
[
'dose_when_id' => 2,
'dose_sign' => 'AF',
'dose_sign_desc' => 'After Food',
'dose_sign_hindi' => 'भोजन के बाद',
],
[
'dose_when_id' => 3,
'dose_sign' => 'BB',
'dose_sign_desc' => 'Before Breakfast',
'dose_sign_hindi' => 'नाश्ते से पहले',
],
[
'dose_when_id' => 4,
'dose_sign' => 'AB',
'dose_sign_desc' => 'After Breakfast',
'dose_sign_hindi' => 'नाश्ते के बाद',
],
[
'dose_when_id' => 5,
'dose_sign' => 'BL',
'dose_sign_desc' => 'Before Lunch',
'dose_sign_hindi' => 'दोपहर के भोजन से पहले',
],
[
'dose_when_id' => 6,
'dose_sign' => 'AL',
'dose_sign_desc' => 'After Lunch',
'dose_sign_hindi' => 'दोपहर के भोजन के बाद',
],
[
'dose_when_id' => 7,
'dose_sign' => 'BD',
'dose_sign_desc' => 'Before Dinner',
'dose_sign_hindi' => 'रात के खाने से पहले',
],
[
'dose_when_id' => 8,
'dose_sign' => 'AD',
'dose_sign_desc' => 'After Dinner',
'dose_sign_hindi' => 'रात के खाने के बाद',
],
[
'dose_when_id' => 9,
'dose_sign' => 'WF',
'dose_sign_desc' => 'With Food',
'dose_sign_hindi' => 'खाने के साथ',
],
[
'dose_when_id' => 10,
'dose_sign' => 'WT',
'dose_sign_desc' => 'With Tea',
'dose_sign_hindi' => 'चाय के साथ',
],
[
'dose_when_id' => 11,
'dose_sign' => 'ES',
'dose_sign_desc' => 'Empty Stomach',
'dose_sign_hindi' => 'खाली पेट',
],
[
'dose_when_id' => 12,
'dose_sign' => 'BT',
'dose_sign_desc' => 'Bed Time',
'dose_sign_hindi' => 'सोने का समय',
],
[
'dose_when_id' => 13,
'dose_sign' => 'BBATH',
'dose_sign_desc' => 'Before Bath',
'dose_sign_hindi' => 'स्नान से पहले',
],
[
'dose_when_id' => 14,
'dose_sign' => 'ABATH',
'dose_sign_desc' => 'After Bath',
'dose_sign_hindi' => 'नहाने के बाद',
],
[
'dose_when_id' => 15,
'dose_sign' => 'BS',
'dose_sign_desc' => 'Before Sun',
'dose_sign_hindi' => 'सूर्य से पहले',
],
[
'dose_when_id' => 16,
'dose_sign' => 'AS',
'dose_sign_desc' => 'After Sun',
'dose_sign_hindi' => 'दोपहर के बाद',
],
[
'dose_when_id' => 17,
'dose_sign' => 'SoS',
'dose_sign_desc' => 'SoS',
'dose_sign_hindi' => 'आपात स्थिति में',
],
];
$this->db->table('opd_dose_when')->insertBatch($rows);
$this->resetAutoIncrement('opd_dose_when', 18);
}
public function down()
{
// No-op: prior dose_when master rows vary by installation.
}
private function tableExists(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);
}
private function resetAutoIncrement(string $table, int $nextId): void
{
if (! preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
return;
}
$nextId = max(1, $nextId);
$this->db->query('ALTER TABLE `' . $table . '` AUTO_INCREMENT = ' . $nextId);
}
}

View File

@ -27,6 +27,10 @@
<label class="form-label">Hospital Email</label>
<input type="text" class="form-control" id="hospital_email" value="<?= esc($hospital_email ?? '') ?>" placeholder="hospital@example.com">
</div>
<div class="col-md-6">
<label class="form-label">Hospital HFR ID</label>
<input type="text" class="form-control" id="hospital_hfr_id" value="<?= esc($hospital_hfr_id ?? '') ?>" placeholder="ABDM HFR ID">
</div>
<div class="col-md-6">
<label class="form-label">Version / Update ID</label>
<input type="text" class="form-control" id="footer_version" value="<?= esc($footer_version ?? '') ?>" placeholder="e.g. 2026.03.27.01">
@ -147,6 +151,7 @@
fd.append('hospital_address_2', ($('#hospital_address_2').val() || '').trim());
fd.append('hospital_phone', ($('#hospital_phone').val() || '').trim());
fd.append('hospital_email', ($('#hospital_email').val() || '').trim());
fd.append('hospital_hfr_id', ($('#hospital_hfr_id').val() || '').trim());
fd.append('footer_version', ($('#footer_version').val() || '').trim());
fd.append('pharmacy_name', ($('#pharmacy_name').val() || '').trim());
fd.append('pharmacy_address', ($('#pharmacy_address').val() || '').trim());
@ -261,6 +266,7 @@
$('#hospital_address_2').val('');
$('#hospital_phone').val('');
$('#hospital_email').val('');
$('#hospital_hfr_id').val('');
$('#footer_version').val('');
$('#pharmacy_name').val('');
$('#pharmacy_address').val('');

View File

@ -66,6 +66,10 @@
<label class="form-label">Doctor Registration No.</label>
<input class="form-control" name="input_doc_reg_no" placeholder="NMC/MCI Registration No." type="text" value="<?= esc($formData['input_doc_reg_no'] ?? '') ?>">
</div>
<div class="col-md-4">
<label class="form-label">HPR ID</label>
<input class="form-control" name="input_hpr_id" placeholder="ABDM HPR ID" type="text" value="<?= esc($formData['input_hpr_id'] ?? '') ?>">
</div>
<div class="col-md-4">
<label class="form-label">Date of Birth</label>
<input class="form-control" name="datepicker_dob" type="date" value="<?= esc($doctorDobValue) ?>">

View File

@ -1,5 +1,6 @@
<?php
$doctorRegNo = '';
$doctorHprId = '';
if (!empty($data) && !empty($data[0])) {
foreach (['nmc_reg_no', 'mci_reg_no', 'registration_no', 'reg_no', 'doctor_reg_no', 'doc_reg_no', 'council_reg_no'] as $regField) {
if (isset($data[0]->{$regField}) && trim((string) $data[0]->{$regField}) !== '') {
@ -19,6 +20,13 @@ if (!empty($data) && !empty($data[0])) {
}
}
}
foreach (['hpr_id', 'hpr_no', 'hpr_number'] as $hprField) {
if (isset($data[0]->{$hprField}) && trim((string) $data[0]->{$hprField}) !== '') {
$doctorHprId = trim((string) $data[0]->{$hprField});
break;
}
}
}
?>
<?php
@ -81,6 +89,10 @@ if (!empty($data) && !empty($data[0]) && !empty($data[0]->dob)) {
<label class="form-label">Doctor Registration No.</label>
<input class="form-control" name="input_doc_reg_no" placeholder="NMC/MCI Registration No." type="text" value="<?= esc($doctorRegNo) ?>">
</div>
<div class="col-md-3">
<label class="form-label">HPR ID</label>
<input class="form-control" name="input_hpr_id" placeholder="ABDM HPR ID" type="text" value="<?= esc($doctorHprId) ?>">
</div>
<div class="col-md-3">
<label class="form-label">Date of Birth</label>
<input class="form-control" name="datepicker_dob" value="<?= esc($doctorDobValue) ?>" type="date" />

View File

@ -39,6 +39,14 @@
onchange="onchange_aadhar()">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">ABHA ID</label>
<input class="form-control" name="input_abha_id" id="input_abha_id"
placeholder="14-digit ABHA ID" type="text" autocomplete="off"
maxlength="14" data-inputmask='"mask": "99999999999999"' data-mask>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Phone Number</label>

View File

@ -10,6 +10,7 @@
</div>
<?php
$user = auth()->user();
$patientAbhaId = (string) ($data[0]->abha_id ?? $data[0]->abha_no ?? $data[0]->abha ?? $data[0]->abha_address ?? '');
$isAdmin = is_object($user) && method_exists($user, 'inGroup') ? $user->inGroup('admin') : false;
if ($data[0]->p_edit == 1 || $isAdmin) {
$readonly = '';
@ -82,6 +83,14 @@
autocomplete="off">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>ABHA ID</label>
<input class="form-control input-sm" name="input_abha_id" id="input_abha_id"
value="<?= esc($patientAbhaId) ?>" placeholder="14-digit ABHA ID" type="text"
autocomplete="off" maxlength="14">
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Phone Number</label>

View File

@ -13,6 +13,9 @@
<div class="jsError"></div>
<form role="form" class="form1">
<?= csrf_field() ?>
<?php
$patientAbhaId = (string) ($data[0]->abha_id ?? $data[0]->abha_no ?? $data[0]->abha ?? $data[0]->abha_address ?? '');
?>
<?php
$user = auth()->user();
$canEditOpd = is_object($user) && method_exists($user, 'can') ? $user->can('billing.opd.edit') : false;
@ -104,6 +107,10 @@
<div class="col-lg-3 col-md-4 label">Aadhar No.</div>
<div class="col-lg-9 col-md-8"><?=$data[0]->udai?></div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">ABHA ID</div>
<div class="col-lg-9 col-md-8"><?= esc($patientAbhaId) ?></div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Phone</div>
<div class="col-lg-9 col-md-8"><?=$data[0]->mphone1?></div>
@ -130,6 +137,13 @@
<button type="button" id="btn_update_aadhar" class="btn btn-info">Update</button>
</div>
</div>
<div class="col-lg-6">
<label class="form-label">ABHA ID</label>
<div class="input-group input-group-sm">
<input class="form-control" type="text" name="input_abha_id" id="input_abha_id" value="<?= esc($patientAbhaId) ?>" maxlength="14">
<button type="button" id="btn_update_abha" class="btn btn-info">Update</button>
</div>
</div>
<div class="col-lg-6">
<button type="button" class="btn btn-warning btn-sm mt-4" onclick="load_form('<?= base_url('billing/patient/show_cards') ?>/<?=$data[0]->id?>/1');">Insurance Update</button>
</div>
@ -283,6 +297,28 @@ $(document).ready(function() {
}
});
$('#btn_update_abha').click(function() {
var p_id = $('#p_id').val();
var abha_id = ($('#input_abha_id').val() || '').trim();
var csrf_name = '<?= csrf_token() ?>';
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
if (abha_id !== '' && !/^\d{14}$/.test(abha_id)) {
alert('ABHA ID must be a 14-digit number.');
return;
}
if (confirm("Are you sure Update ABHA ID.")) {
$.post('<?= base_url('billing/patient/update_abha') ?>', {
"p_id": p_id,
"abha_id": abha_id,
[csrf_name]: csrf_value
}, function(data) {
load_form('<?= base_url('billing/patient/person_record') ?>/' + p_id);
});
}
});
$('#btn_inc_opd').click(function() {
var p_id = $('#p_id').val();
load_form('<?= base_url('Opdcase/addopd') ?>/' + p_id);

View File

@ -330,13 +330,52 @@ $toLocalText = static function (string $input): string {
<?php foreach (($rx_medicines ?? []) as $idx => $med) : ?>
<?php
$dosage = trim((string) ($med['dosage'] ?? ''));
if ($dosage === '0') {
$dosage = '';
}
$when = trim((string) ($med['dosage_when'] ?? ''));
if ($when === '0') {
$when = '';
}
$freq = trim((string) ($med['dosage_freq'] ?? ''));
if ($freq === '0') {
$freq = '';
}
$where = trim((string) ($med['dosage_where_label'] ?? ($med['dosage_where_str'] ?? ($med['dosage_where'] ?? ''))));
if ($where === '0') {
$where = '';
}
$doseText = trim($dosage . ($freq !== '' ? ' / ' . $freq : ''));
$genericText = trim((string) ($med['genericname'] ?? ($med['generic_name'] ?? ($med['salt_name'] ?? ''))));
$instruction = trim(($when !== '' ? $when : '') . ((trim((string) ($med['remark'] ?? '')) !== '') ? (' | ' . trim((string) ($med['remark'] ?? ''))) : ''));
$doseLocal = $toLocalText(trim($doseText . ($when !== '' ? ' ' . $when : '')));
$instructionLocal = $toLocalText($instruction);
$instructionParts = [];
if ($when !== '') {
$instructionParts[] = $when;
}
if ($where !== '') {
$instructionParts[] = 'Where: ' . $where;
}
if (trim((string) ($med['remark'] ?? '')) !== '') {
$instructionParts[] = trim((string) ($med['remark'] ?? ''));
}
$instruction = trim(implode(' | ', $instructionParts));
$doseLocalParts = array_filter([
trim((string) ($med['dosage_local_label'] ?? '')),
trim((string) ($med['dosage_when_local_label'] ?? '')),
trim((string) ($med['dosage_freq_local_label'] ?? '')),
]);
$instructionLocalParts = [];
$whereLocal = trim((string) ($med['dosage_where_local_label'] ?? ''));
if ($whereLocal !== '') {
$instructionLocalParts[] = $whereLocal;
}
if ($instruction !== '') {
$translatedInstruction = $toLocalText($instruction);
if ($translatedInstruction !== '' && strtolower($translatedInstruction) !== strtolower($instruction)) {
$instructionLocalParts[] = $translatedInstruction;
}
}
$doseLocal = !empty($doseLocalParts) ? implode(' ', $doseLocalParts) : $toLocalText(trim($doseText . ($when !== '' ? ' ' . $when : '')));
$instructionLocal = !empty($instructionLocalParts) ? implode(' | ', array_values(array_unique($instructionLocalParts))) : $toLocalText($instruction);
?>
<tr>
<td><?= esc((string) ($idx + 1)) ?></td>

View File

@ -105,19 +105,27 @@
<div class="row g-1 mb-2">
<div class="col-6">
<label class="form-label form-label-sm mb-0">Dose</label>
<input type="text" id="med_default_dosage" class="form-control form-control-sm" placeholder="e.g. 1-0-1">
<select id="med_default_dosage" class="form-select form-select-sm">
<option value="">Select dose</option>
</select>
</div>
<div class="col-6">
<label class="form-label form-label-sm mb-0">When</label>
<input type="text" id="med_default_when" class="form-control form-control-sm" placeholder="e.g. AC">
<select id="med_default_when" class="form-select form-select-sm">
<option value="">Select when</option>
</select>
</div>
<div class="col-6">
<label class="form-label form-label-sm mb-0">Frequency</label>
<input type="text" id="med_default_freq" class="form-control form-control-sm" placeholder="e.g. TDS">
<select id="med_default_freq" class="form-select form-select-sm">
<option value="">Select frequency</option>
</select>
</div>
<div class="col-6">
<label class="form-label form-label-sm mb-0">Route</label>
<input type="text" id="med_default_where" class="form-control form-control-sm" placeholder="e.g. ORAL">
<select id="med_default_where" class="form-select form-select-sm">
<option value="">Select route</option>
</select>
</div>
<div class="col-4">
<label class="form-label form-label-sm mb-0">Days</label>
@ -151,6 +159,7 @@
var activeListFilter = 'all';
var companySuggestTimer = null;
var medMasterDataTable = null;
var defaultDoseMasterCache = { dose: [], when: [], freq: [], where: [] };
function showToast(message, type) {
var toastId = 'med_toast_' + Date.now();
@ -257,7 +266,93 @@
function clearForm() {
$('#med_id').val('0');
$('#med_item_name,#med_formulation,#med_genericname,#med_salt_name,#med_dosage_restriction,#med_company_name').val('');
$('#med_default_dosage,#med_default_when,#med_default_freq,#med_default_where,#med_default_days,#med_default_qty,#med_default_remark').val('');
$('#med_default_days,#med_default_qty,#med_default_remark').val('');
$('#med_default_dosage,#med_default_when,#med_default_freq,#med_default_where').val('');
}
function renderDefaultMasterSelect($select, rows, placeholder) {
var html = '<option value="">' + $('<div>').text(placeholder || 'Select').html() + '</option>';
(rows || []).forEach(function(row) {
var id = (row && row.id !== undefined) ? String(row.id) : '';
var label = (row && row.label !== undefined) ? String(row.label) : '';
var desc = (row && row.secondary_label !== undefined) ? String(row.secondary_label) : '';
if (!id || !label || id === '0') {
return;
}
var text = label;
if (desc && desc.toLowerCase() !== label.toLowerCase()) {
text += ' - ' + desc;
}
html += '<option value="' + $('<div>').text(id).html() + '">' + $('<div>').text(text).html() + '</option>';
});
$select.html(html);
}
function ensureDefaultMasterOption($select, value) {
value = (value || '').toString().trim();
if (!value || value === '0') {
return;
}
var safeValue = value.replace(/"/g, '&quot;');
if ($select.find('option[value="' + safeValue + '"]').length) {
return;
}
$select.append('<option value="' + $('<div>').text(value).html() + '">' + $('<div>').text(value + ' (Current)').html() + '</option>');
}
function setDefaultMasterValue($select, value) {
var raw = (value || '').toString().trim();
if (!raw || raw === '0') {
$select.val('');
return;
}
ensureDefaultMasterOption($select, raw);
if ($select.find('option[value="' + raw.replace(/"/g, '&quot;') + '"]').length) {
$select.val(raw);
return;
}
var probe = raw.toLowerCase();
var matchedVal = '';
$select.find('option').each(function() {
var txt = ($(this).text() || '').toString().trim().toLowerCase();
if (!txt) {
return;
}
if (txt === probe || txt.indexOf(probe) !== -1) {
matchedVal = ($(this).val() || '').toString();
return false;
}
});
if (matchedVal) {
$select.val(matchedVal);
return;
}
ensureDefaultMasterOption($select, raw);
$select.val(raw);
}
function loadDefaultDoseMasters(done) {
apiGet('<?= base_url('Opd_prescription/rx_group_dose_masters') ?>', function(data) {
defaultDoseMasterCache = {
dose: (data && data.dose) ? data.dose : [],
when: (data && data.when) ? data.when : [],
freq: (data && data.freq) ? data.freq : [],
where: (data && data.where) ? data.where : []
};
renderDefaultMasterSelect($('#med_default_dosage'), defaultDoseMasterCache.dose, 'Select dose');
renderDefaultMasterSelect($('#med_default_when'), defaultDoseMasterCache.when, 'Select when');
renderDefaultMasterSelect($('#med_default_freq'), defaultDoseMasterCache.freq, 'Select frequency');
renderDefaultMasterSelect($('#med_default_where'), defaultDoseMasterCache.where, 'Select route');
if (typeof done === 'function') {
done();
}
});
}
function buildMedicinePayload() {
@ -309,10 +404,10 @@
$('#med_salt_name').val(row.salt_name || row.sal_name || row.salt || row.saltname || '');
$('#med_dosage_restriction').val(row.dosage_restriction || row.dose_restriction || row.restriction_note || row.restriction || '');
$('#med_company_name').val(row.company_name || '');
$('#med_default_dosage').val(row.dosage || '');
$('#med_default_when').val(row.dosage_when || '');
$('#med_default_freq').val(row.dosage_freq || '');
$('#med_default_where').val(row.dosage_where || '');
setDefaultMasterValue($('#med_default_dosage'), row.dosage || '');
setDefaultMasterValue($('#med_default_when'), row.dosage_when || '');
setDefaultMasterValue($('#med_default_freq'), row.dosage_freq || '');
setDefaultMasterValue($('#med_default_where'), row.dosage_where || '');
$('#med_default_days').val(row.no_of_days || '');
$('#med_default_qty').val(row.qty || '');
$('#med_default_remark').val(row.remark || '');
@ -713,9 +808,11 @@
loadList();
loadDuplicateReport();
loadCompanySuggestions('');
var initialMedId = parseInt($('#med_id').val() || '0', 10);
if (initialMedId > 0) {
loadOne(initialMedId);
}
loadDefaultDoseMasters(function() {
var initialMedId = parseInt($('#med_id').val() || '0', 10);
if (initialMedId > 0) {
loadOne(initialMedId);
}
});
})();
</script>

View File

@ -761,49 +761,62 @@
Select group and preview medicines before add.
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-4">
<input type="text" class="form-control" id="med_name" list="medicine_suggest" placeholder="Medicine name">
<datalist id="medicine_suggest"></datalist>
<div class="border rounded p-3 mb-3 bg-light-subtle">
<div class="row g-3 align-items-end mb-2">
<div class="col-md-8">
<label class="form-label fw-semibold mb-1">Prescribed:</label>
<input type="text" class="form-control" id="med_name" list="medicine_suggest" placeholder="Search / enter medicine name">
<datalist id="medicine_suggest"></datalist>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold mb-1">Type</label>
<input type="text" class="form-control" id="med_type" placeholder="TAB,CAP,SYR,INJ">
</div>
</div>
<div class="col-md-2">
<input type="text" class="form-control" id="med_type" placeholder="Type">
<div class="row g-3 align-items-end mb-2">
<div class="col-md-3">
<label class="form-label fw-semibold mb-1">Dose:</label>
<select class="form-select" id="med_dosage">
<option value="">Select dose</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-semibold mb-1">When:</label>
<select class="form-select" id="med_when">
<option value="">Select when</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-semibold mb-1">Frequency:</label>
<select class="form-select" id="med_freq">
<option value="">Select frequency</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold mb-1">Duration:</label>
<input type="text" class="form-control" id="med_days" placeholder="e.g. 5 days / 1 month / 3 doses">
</div>
</div>
<div class="col-md-2">
<select class="form-select" id="med_dosage">
<option value="">Dose</option>
</select>
</div>
<div class="col-md-2">
<select class="form-select" id="med_when">
<option value="">When</option>
</select>
</div>
<div class="col-md-2">
<select class="form-select" id="med_freq">
<option value="">Freq</option>
</select>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-md-2">
<input type="text" class="form-control" id="med_days" placeholder="Days">
</div>
<div class="col-md-2">
<input type="text" class="form-control" id="med_qty" placeholder="Qty">
</div>
<div class="col-md-2">
<select class="form-select" id="med_where">
<option value="">Where</option>
</select>
</div>
<div class="col-md-4">
<input type="text" class="form-control" id="med_remark" placeholder="Remark">
</div>
<div class="col-md-2 d-grid gap-2">
<button type="button" class="btn btn-primary" id="btn_add_medicine">Add</button>
<button type="button" class="btn btn-outline-danger" id="btn_clear_medicine">Remove All</button>
<button type="button" class="btn btn-outline-secondary" id="btn_cancel_medicine_edit" style="display:none;">Cancel</button>
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label fw-semibold mb-1">Where:</label>
<select class="form-select" id="med_where">
<option value="">Body place</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-semibold mb-1">Qty:</label>
<input type="text" class="form-control" id="med_qty" placeholder="Qty">
</div>
<div class="col-md-5">
<label class="form-label fw-semibold mb-1">Medicine Advice:</label>
<input type="text" class="form-control" id="med_remark" placeholder="Instruction / medicine advice">
</div>
<div class="col-md-2 d-grid gap-2">
<button type="button" class="btn btn-primary" id="btn_add_medicine">+ADD / Update</button>
<button type="button" class="btn btn-outline-danger" id="btn_clear_medicine">Remove All</button>
<button type="button" class="btn btn-outline-secondary" id="btn_cancel_medicine_edit" style="display:none;">Cancel</button>
</div>
</div>
</div>
@ -831,7 +844,7 @@
<table class="table table-bordered table-sm rx-list-table" id="tbl_medicine">
<thead>
<tr>
<th>Medicine</th><th>Type</th><th>Dose</th><th>When</th><th>Freq</th><th>Where</th><th>Days</th><th>Qty</th><th>Remark</th><th width="170">Action</th>
<th>Medicine</th><th>Type</th><th>Dose</th><th>When</th><th>Frequency</th><th>Where</th><th>Duration</th><th>Qty</th><th>Medicine Advice</th><th width="170">Action</th>
</tr>
</thead>
<tbody><tr><td colspan="10" class="text-muted">No medicine added</td></tr></tbody>
@ -1129,17 +1142,18 @@
(rows || []).forEach(function(row) {
var id = (row && row.id !== undefined) ? String(row.id) : '';
var label = (row && row.label !== undefined) ? String(row.label) : '';
var localLabel = (row && row.local_label !== undefined) ? String(row.local_label) : '';
if (!id || !label) {
return;
}
html += '<option value="' + $('<div>').text(id).html() + '">' + $('<div>').text(label).html() + '</option>';
html += '<option value="' + $('<div>').text(id).html() + '"' + (localLabel ? ' data-local-label="' + $('<div>').text(localLabel).html() + '"' : '') + '>' + $('<div>').text(label).html() + '</option>';
});
$select.html(html);
}
function ensureMedicineMasterOption($select, value) {
value = (value || '').toString().trim();
if (!value) {
if (!value || value === '0') {
return;
}
@ -4825,18 +4839,26 @@
if (dosage) {
ensureMedicineMasterOption($('#med_dosage'), dosage);
$('#med_dosage').val(dosage).trigger('change');
} else {
$('#med_dosage').val('').trigger('change');
}
if (whenValue) {
ensureMedicineMasterOption($('#med_when'), whenValue);
$('#med_when').val(whenValue).trigger('change');
} else {
$('#med_when').val('').trigger('change');
}
if (freqValue) {
ensureMedicineMasterOption($('#med_freq'), freqValue);
$('#med_freq').val(freqValue).trigger('change');
} else {
$('#med_freq').val('').trigger('change');
}
if (whereValue) {
ensureMedicineMasterOption($('#med_where'), whereValue);
$('#med_where').val(whereValue).trigger('change');
} else {
$('#med_where').val('').trigger('change');
}
if (daysValue) {
$('#med_days').val(daysValue);

BIN
public/assets/img/logo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB