chore: untrack local dev debug scripts and internal notes from repository, update .gitignore
This commit is contained in:
parent
9d568463f1
commit
050035e1b0
42
.gitignore
vendored
42
.gitignore
vendored
|
|
@ -160,5 +160,47 @@ writable/debug/*
|
|||
writable/github_update_cache.json
|
||||
E-AtriaHMS.docx
|
||||
E-AtriaHMS.zip
|
||||
*.xlsx
|
||||
scratch/
|
||||
|
||||
# Local development, debug, and diagnostic scripts (Root only)
|
||||
/check_*.php
|
||||
/clear_*.php
|
||||
/debug_*.php
|
||||
/compare_*.php
|
||||
/find_*.php
|
||||
/dedupe_*.php
|
||||
/delete_*.php
|
||||
/run_*.php
|
||||
/test_*.php
|
||||
/verify_*.php
|
||||
/wipe_*.php
|
||||
/list_*.php
|
||||
/inspect_*.php
|
||||
/raw_*.php
|
||||
|
||||
# Temporary root test, SQL, and config files
|
||||
/*.conf
|
||||
/*.sql
|
||||
/*.xlsx
|
||||
/pdf_test_html.html
|
||||
/TEST_DEPLOYMENT.txt
|
||||
/info_script.sh
|
||||
/setup_git_config.sh
|
||||
|
||||
# Developer session logs and internal handover notes (Root only)
|
||||
/ABDM_*.md
|
||||
/ACTION_PLAN_*.md
|
||||
/BED_MANAGEMENT_*.md
|
||||
/BRIDGE_SYNC_*.md
|
||||
/DICOM_AI_*.md
|
||||
/DISCHARGE_*.md
|
||||
/DUAL_BRIDGE_*.md
|
||||
/FILE_INDEX_*.md
|
||||
/HMS_MEDICAL_STORE_*.md
|
||||
/MPDF_*.md
|
||||
/SESSION_SUMMARY_*.md
|
||||
/SURGERY_PROCEDURE_*.md
|
||||
/SYSTEM_OPS_TEST.md
|
||||
/WASA_SECURITY_*.md
|
||||
/WORKLOG_*.md
|
||||
|
|
|
|||
|
|
@ -1,450 +0,0 @@
|
|||
# HMS ABHA Card Download Implementation - Per ABDM Bridge v3 Spec
|
||||
|
||||
## Overview
|
||||
HMS implements the ABDM Bridge v3 Gateway endpoint for downloading official ABHA cards. This is fully documented and operational.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Implementation Checklist
|
||||
|
||||
### ✅ Gateway Endpoint Call
|
||||
**File**: `app/Libraries/Abdm/EAtriaBridgeConnector.php` (Lines 740-764)
|
||||
|
||||
**Endpoint**: `GET /api/v3/abha/card`
|
||||
|
||||
**Implementation**:
|
||||
```php
|
||||
$cardResult = $this->get(
|
||||
'/v3/abha/card',
|
||||
array_filter([
|
||||
'abha_number' => $abhaNumber,
|
||||
'abha_address' => $abhaAddress,
|
||||
], static fn($value): bool => trim((string) $value) !== ''),
|
||||
$patientToken !== '' ? ['X-Token' => 'Bearer ' . $patientToken] : []
|
||||
);
|
||||
```
|
||||
|
||||
**Parameters Sent**:
|
||||
- `abha_number`: ABHA ID (e.g., "91-5101-6530-5101")
|
||||
- `abha_address`: ABHA Address (e.g., "singhdevender0328@sbx")
|
||||
- `X-Token` Header: Patient authentication token (when available)
|
||||
|
||||
**Response Handling** (Lines 751-762):
|
||||
```php
|
||||
if (! empty($cardResult['ok']) && (int) $cardResult['ok'] === 1) {
|
||||
$cardData = is_array($cardResult['data'] ?? null)
|
||||
? $cardResult['data']
|
||||
: $cardResult;
|
||||
|
||||
// Extract card from any field name
|
||||
$card = $this->extractOfficialCard($cardResult)
|
||||
?: $this->extractOfficialCard($cardData);
|
||||
|
||||
if ($card !== '') {
|
||||
$result['card_base64'] = $card;
|
||||
$result['card_content_type'] = $this->resolveCardContentType($cardData);
|
||||
$result['card_source'] = $this->extractCardSource($cardResult);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Card Download Data Flow
|
||||
|
||||
```
|
||||
1. ABHA Verification Complete
|
||||
↓
|
||||
2. HMS Controller: Abha::verifyOtp() or verifyCommOtp()
|
||||
↓
|
||||
3. Card already in response?
|
||||
├─ YES → Use it (from verify-otp/verify-comm-otp response)
|
||||
└─ NO → Call Gateway...
|
||||
↓
|
||||
4. Bridge Connector: Call /v3/abha/card
|
||||
├─ Pass: abha_number, abha_address
|
||||
├─ Auth: X-Token header (patient-specific)
|
||||
└─ To: https://abdm-bridge.e-atria.in/api/v3/abha/card
|
||||
↓
|
||||
5. Gateway Response
|
||||
{
|
||||
"ok": 1,
|
||||
"card_format": "png",
|
||||
"official_card": "data:image/png;base64,...",
|
||||
"card_source": "abdm",
|
||||
"source": "abdm_official"
|
||||
}
|
||||
↓
|
||||
6. Extract Card Data
|
||||
├─ official_card field
|
||||
├─ card_data_uri field (if present)
|
||||
└─ multiple fallback options
|
||||
↓
|
||||
7. Return to Client
|
||||
{
|
||||
"ok": 1,
|
||||
"card_base64": "data:image/png;base64,...",
|
||||
"card_content_type": "image/png",
|
||||
"card_source": "abdm"
|
||||
}
|
||||
↓
|
||||
8. Browser Display
|
||||
└─ <img src="data:image/png;base64,...">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Card Field Extraction
|
||||
|
||||
**File**: `app/Libraries/Abdm/EAtriaBridgeConnector.php` (Lines 810-823)
|
||||
|
||||
**Supported Card Field Names** (in priority order):
|
||||
1. `official_card` ✅
|
||||
2. `card_data_uri` ✅
|
||||
3. `card_base64` ✅
|
||||
4. `card_data` ✅
|
||||
5. `abhaCard` ✅
|
||||
6. `abha_card` ✅
|
||||
7. `cardData` ✅
|
||||
8. `card` ✅
|
||||
|
||||
**Nested Structure Support**:
|
||||
```php
|
||||
foreach (['official_card', 'card_data_uri', 'card_base64', ...] as $key) {
|
||||
$value = $source[$key] ?? '';
|
||||
if (is_string($value) && trim($value) !== '') {
|
||||
return trim($value); // Found at top level
|
||||
}
|
||||
if (is_array($value)) {
|
||||
foreach (['base64', 'data', 'card_base64', 'card_data'] as $nestedKey) {
|
||||
if (is_string($value[$nestedKey] ?? null)) {
|
||||
return trim($value[$nestedKey]); // Found nested
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Card Display Views
|
||||
|
||||
### 1. HTML Card View
|
||||
**Route**: `GET abha/card/{abha_number}`
|
||||
**File**: `app/Controllers/Abha.php` (Lines 509-600)
|
||||
**Returns**: HTML page for display/printing
|
||||
|
||||
```php
|
||||
public function card(string $abhaNumber = '')
|
||||
{
|
||||
// Validate ABHA format
|
||||
$abhaNumClean = preg_replace('/\D/', '', $abhaNumber);
|
||||
|
||||
if (strlen($abhaNumClean) !== 14) {
|
||||
return $this->response->setStatusCode(400)
|
||||
->setBody('<h3>Invalid ABHA number.</h3>');
|
||||
}
|
||||
|
||||
// Look up patient in HMS database
|
||||
$patient = $db->table('patient_master')
|
||||
->where($abhaField, $abhaNumClean)->get()->getRowArray();
|
||||
|
||||
if (! $patient) {
|
||||
return $this->response->setStatusCode(404)
|
||||
->setBody('<h3>Patient not found.</h3>');
|
||||
}
|
||||
|
||||
// Render card view with patient data
|
||||
return view('abha/card', [
|
||||
'patient' => $patient,
|
||||
'abha_num' => $abhaDisp,
|
||||
'stored_abha_card' => trim($patient['abha_card_base64'] ?? ''),
|
||||
// ... more data
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Modal Download Links
|
||||
**Files**:
|
||||
- `app/Views/partials/abha_verify_modal.php` (Line 90)
|
||||
- `app/Views/partials/abha_create_modal.php` (Line 190)
|
||||
- `app/Views/partials/abha_mobile_modal.php` (Line 101)
|
||||
|
||||
**Implementation**:
|
||||
```html
|
||||
<a class="btn btn-outline-primary w-100 mt-2 d-none"
|
||||
id="abhaVerifyDownloadCard"
|
||||
download="ABHA-card">
|
||||
<i class="bi bi-download me-1"></i>Download ABHA Card
|
||||
</a>
|
||||
```
|
||||
|
||||
**JavaScript Handler** (abha_verify_modal.php, Lines 224-237):
|
||||
```javascript
|
||||
var card = profile.card_base64 || '';
|
||||
if (card) {
|
||||
// Convert to data URL if not already
|
||||
var cardSrc = String(card).indexOf('data:') === 0
|
||||
? card
|
||||
: 'data:' + (profile.card_content_type || 'image/png') + ';base64,' + card;
|
||||
|
||||
// Show warning if provisional
|
||||
var isOfficial = profile.card_source === 'abdm';
|
||||
var note = isOfficial
|
||||
? ''
|
||||
: '<div class="small text-warning mt-1">Provisional card generated by the Bridge.</div>';
|
||||
|
||||
// Display image
|
||||
$('#abhaVerifyCardWrap').html('<img src="' + cardSrc + '" alt="ABHA card">' + note);
|
||||
|
||||
// Enable download link
|
||||
$('#abhaVerifyDownloadCard').attr('href', cardSrc).removeClass('d-none');
|
||||
} else {
|
||||
$('#abhaVerifyCardWrap').html('<div class="text-center text-muted">Card not returned by Bridge.</div>');
|
||||
$('#abhaVerifyDownloadCard').addClass('d-none');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Request/Response Example
|
||||
|
||||
### Request to Gateway
|
||||
```bash
|
||||
GET https://abdm-bridge.e-atria.in/api/v3/abha/card?abha_number=91-5101-6530-5101&abha_address=singhdevender0328%40sbx
|
||||
Authorization: Bearer YOUR_HOSPITAL_API_KEY
|
||||
X-Token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
### Gateway Response
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"request_id": "REQ-20260814012256-...",
|
||||
"card_format": "png",
|
||||
"card_data_uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
|
||||
"official_card": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
|
||||
"card_source": "abdm",
|
||||
"source": "abdm_official"
|
||||
}
|
||||
```
|
||||
|
||||
### HMS Extracts & Returns to Client
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"card_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
|
||||
"card_content_type": "image/png",
|
||||
"card_source": "abdm",
|
||||
"abha_number": "91-5101-6530-5101",
|
||||
"name": "Devender Singh",
|
||||
"abha_address": "singhdevender0328@sbx",
|
||||
"verified_status": "VERIFIED",
|
||||
"kyc_verified": true
|
||||
}
|
||||
```
|
||||
|
||||
### Client Renders
|
||||
```javascript
|
||||
// In browser
|
||||
const dataUrl = response.card_base64; // Already includes "data:image/png;base64,"
|
||||
document.getElementById('abhaCard').src = dataUrl;
|
||||
|
||||
// Or create download link
|
||||
const link = document.createElement('a');
|
||||
link.href = dataUrl;
|
||||
link.download = 'ABHA-card.png';
|
||||
link.click();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Authentication Flow
|
||||
|
||||
### When Patient X-Token is Available
|
||||
```php
|
||||
// Step 1: Verify OTP → Get X-Token
|
||||
$verifyResult = $this->post('/v3/abha/login/verify-otp', $body);
|
||||
// Returns: X-Token header, official ABHA card
|
||||
|
||||
// Step 2: Call card endpoint with X-Token
|
||||
$cardResult = $this->get(
|
||||
'/v3/abha/card',
|
||||
['abha_number' => $abhaNum, 'abha_address' => $abhaAddress],
|
||||
['X-Token' => 'Bearer ' . $patientToken] // ← Authenticated request
|
||||
);
|
||||
// Returns: Official ABHA card (guaranteed)
|
||||
```
|
||||
|
||||
### When Only Hospital API Key Available
|
||||
```php
|
||||
// Card endpoint called with hospital API key only
|
||||
$cardResult = $this->get(
|
||||
'/v3/abha/card',
|
||||
['abha_number' => $abhaNum, 'abha_address' => $abhaAddress]
|
||||
// No X-Token header
|
||||
);
|
||||
// Returns: Bridge-generated card (provisional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 When Card is Downloaded
|
||||
|
||||
### Scenario 1: Card Already in Verify Response
|
||||
- User verifies ABHA via OTP
|
||||
- Bridge returns card in verify-otp response
|
||||
- Card immediately displayed in modal
|
||||
- No additional call to /v3/abha/card needed
|
||||
|
||||
### Scenario 2: Card Needs Explicit Fetch
|
||||
```php
|
||||
if ($card === '' && ($abhaNumber !== '' || $abhaAddress !== '')) {
|
||||
// Card was not in verify response
|
||||
// Call /v3/abha/card to fetch it
|
||||
$cardResult = $this->get('/v3/abha/card', $params, $headers);
|
||||
$card = $this->extractOfficialCard($cardResult);
|
||||
}
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- ✅ Card is always available (from verify or explicit fetch)
|
||||
- ✅ Automatic fallback if verify doesn't include card
|
||||
- ✅ No duplicate requests
|
||||
|
||||
---
|
||||
|
||||
## 💾 Card Storage
|
||||
|
||||
### In Patient Master Table
|
||||
**Column**: `abha_card_base64`
|
||||
**Added by**: Migration `2026-08-13-000001_AddAbhaCardToPatientMaster.php`
|
||||
**Purpose**: Store official ABHA card for quick display
|
||||
|
||||
**Usage**:
|
||||
```php
|
||||
// When displaying patient's existing card
|
||||
$cardBase64 = $patient['abha_card_base64'] ?? '';
|
||||
if ($cardBase64) {
|
||||
// Use stored card
|
||||
return view('abha/card', ['stored_abha_card' => $cardBase64]);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 User Flows
|
||||
|
||||
### Flow 1: Download Card During ABHA Creation
|
||||
```
|
||||
1. User clicks "Create ABHA"
|
||||
2. Selects Aadhaar OTP verification
|
||||
3. Enters Aadhaar + OTP
|
||||
4. Bridge returns ABHA profile + card
|
||||
5. Modal shows:
|
||||
├─ Profile photo
|
||||
├─ Official ABHA card (from response)
|
||||
└─ "Download ABHA Card" button (enabled)
|
||||
6. User clicks download → Browser saves PNG
|
||||
```
|
||||
|
||||
### Flow 2: View Patient's ABHA Card
|
||||
```
|
||||
1. User navigates to Patient Profile
|
||||
2. Clicks "View/Print ABHA Card" link
|
||||
3. Opens: GET abha/card/91-5101-6530-5101
|
||||
4. Card view renders with:
|
||||
├─ Official ABHA card image
|
||||
├─ Patient HMS ID
|
||||
├─ QR code
|
||||
└─ Print-ready layout
|
||||
5. User prints or saves as PDF
|
||||
```
|
||||
|
||||
### Flow 3: Explicit Card Download (Without Verification)
|
||||
```
|
||||
1. Use API: GET /abha/card/{abha_number}
|
||||
2. Bridge connector calls: GET /v3/abha/card
|
||||
3. Extracts official card from response
|
||||
4. Returns card_base64 in JSON
|
||||
5. Frontend displays/downloads card
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Status
|
||||
|
||||
| Component | Status | Evidence |
|
||||
|-----------|--------|----------|
|
||||
| Gateway endpoint called | ✅ | Line 744: `/v3/abha/card` |
|
||||
| Query parameters sent | ✅ | Lines 748-750: `abha_number`, `abha_address` |
|
||||
| X-Token authentication | ✅ | Line 752: `'X-Token' => 'Bearer ' . $patientToken` |
|
||||
| Response parsing | ✅ | Lines 751-762: Extract `ok`, `card_*` fields |
|
||||
| Card field extraction | ✅ | Lines 810-823: 8 field names supported |
|
||||
| Client-side rendering | ✅ | abha_verify_modal.php line 224+ |
|
||||
| Download functionality | ✅ | Modal download links with data URL |
|
||||
| Card storage | ✅ | `patient_master.abha_card_base64` column |
|
||||
| Display view | ✅ | `app/Views/abha/card` view |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
**Backend**:
|
||||
- `app/Controllers/Abha.php` - Card display and verification
|
||||
- `app/Libraries/Abdm/EAtriaBridgeConnector.php` - Gateway endpoint calls
|
||||
- `app/Config/Routes.php` - Route definition
|
||||
|
||||
**Frontend**:
|
||||
- `app/Views/partials/abha_verify_modal.php` - Verification modal with download
|
||||
- `app/Views/partials/abha_create_modal.php` - Create flow with download
|
||||
- `app/Views/partials/abha_mobile_modal.php` - Mobile OTP flow with download
|
||||
- `app/Views/abha/card.php` - Card display/print view
|
||||
|
||||
**Database**:
|
||||
- `app/Database/Migrations/2026-08-13-000001_AddAbhaCardToPatientMaster.php` - Card column migration
|
||||
- `patient_master.abha_card_base64` - Card storage column
|
||||
|
||||
---
|
||||
|
||||
## 📖 ABDM Bridge Documentation Reference
|
||||
|
||||
**Section 7 of Integration Guide**:
|
||||
```
|
||||
GET /api/v3/abha/card
|
||||
Authorization: Bearer YOUR_HOSPITAL_API_KEY
|
||||
|
||||
Query Parameters:
|
||||
- abha_number: ABHA ID
|
||||
- abha_address: ABHA address
|
||||
|
||||
Response:
|
||||
{
|
||||
"ok": 1,
|
||||
"card_format": "png",
|
||||
"card_data_uri": "data:image/png;base64,...",
|
||||
"official_card": "data:image/png;base64,...",
|
||||
"card_source": "abdm"
|
||||
}
|
||||
```
|
||||
|
||||
**HMS Implementation**: ✅ FULLY COMPLIANT
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Takeaways
|
||||
|
||||
1. ✅ HMS **calls** the Gateway `/v3/abha/card` endpoint as documented
|
||||
2. ✅ Passes **correct parameters** (abha_number, abha_address)
|
||||
3. ✅ Sends **X-Token** header for patient authentication when available
|
||||
4. ✅ **Extracts** card from multiple field names for compatibility
|
||||
5. ✅ **Returns** card as Base64 data URL to client
|
||||
6. ✅ **Displays** card in verification modals
|
||||
7. ✅ **Provides** download functionality
|
||||
8. ✅ **Stores** card in database for quick retrieval
|
||||
9. ✅ **Renders** dedicated card view for printing
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status**: COMPLETE ✅
|
||||
**ABDM Compliance**: VERIFIED ✅
|
||||
|
|
@ -1,398 +0,0 @@
|
|||
# ✅ HMS ABHA Card Download - Complete Verification
|
||||
|
||||
## Summary
|
||||
HMS has **fully implemented** the ABDM Bridge v3 card download feature exactly as documented in Section 7 of the Integration Guide.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Bridge Specification vs HMS Implementation
|
||||
|
||||
### Bridge Says:
|
||||
```
|
||||
GET /api/v3/abha/card?abha_number=91-5101-6530-5101
|
||||
Authorization: Bearer YOUR_HOSPITAL_API_KEY
|
||||
|
||||
Response:
|
||||
{
|
||||
"ok": 1,
|
||||
"card_format": "png",
|
||||
"card_data_uri": "data:image/png;base64,iVBORw0K...",
|
||||
"official_card": "data:image/png;base64,iVBORw0K...",
|
||||
"card_source": "abdm"
|
||||
}
|
||||
```
|
||||
|
||||
### HMS Implements:
|
||||
✅ Calls `/v3/abha/card` with correct parameters
|
||||
✅ Passes `Authorization: Bearer` header
|
||||
✅ Extracts `official_card` field
|
||||
✅ Returns card as Base64 data URL
|
||||
✅ Stores in database for quick access
|
||||
✅ Displays in modals with download option
|
||||
✅ Renders dedicated card view for printing
|
||||
|
||||
---
|
||||
|
||||
## 📍 Implementation Locations
|
||||
|
||||
### 1. **Gateway Endpoint Call**
|
||||
**File**: `app/Libraries/Abdm/EAtriaBridgeConnector.php`
|
||||
**Lines**: 744-764
|
||||
|
||||
```php
|
||||
$cardResult = $this->get(
|
||||
'/v3/abha/card',
|
||||
array_filter([
|
||||
'abha_number' => $abhaNumber,
|
||||
'abha_address' => $abhaAddress,
|
||||
]),
|
||||
$patientToken !== '' ? ['X-Token' => 'Bearer ' . $patientToken] : []
|
||||
);
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 2. **Card Response Handling**
|
||||
**File**: `app/Libraries/Abdm/EAtriaBridgeConnector.php`
|
||||
**Lines**: 751-762
|
||||
|
||||
```php
|
||||
if (! empty($cardResult['ok']) && (int) $cardResult['ok'] === 1) {
|
||||
$cardData = is_array($cardResult['data'] ?? null)
|
||||
? $cardResult['data']
|
||||
: $cardResult;
|
||||
|
||||
$card = $this->extractOfficialCard($cardResult)
|
||||
?: $this->extractOfficialCard($cardData);
|
||||
|
||||
if ($card !== '') {
|
||||
$result['card_base64'] = $card;
|
||||
$result['card_content_type'] = $this->resolveCardContentType($cardData);
|
||||
$result['card_source'] = $this->extractCardSource($cardResult);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 3. **Card Field Extraction**
|
||||
**File**: `app/Libraries/Abdm/EAtriaBridgeConnector.php`
|
||||
**Lines**: 810-823
|
||||
|
||||
Handles **8 different field names**:
|
||||
- `official_card` ✅
|
||||
- `card_data_uri` ✅
|
||||
- `card_base64` ✅
|
||||
- `card_data` ✅
|
||||
- `abhaCard` ✅
|
||||
- `abha_card` ✅
|
||||
- `cardData` ✅
|
||||
- `card` ✅
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 4. **Card Display in Modals**
|
||||
**Files**:
|
||||
- `app/Views/partials/abha_verify_modal.php` (Line 90, 224-237)
|
||||
- `app/Views/partials/abha_create_modal.php` (Line 190, 420-431)
|
||||
- `app/Views/partials/abha_mobile_modal.php` (Line 101, 230-240)
|
||||
|
||||
```javascript
|
||||
var card = profile.card_base64 || '';
|
||||
if (card) {
|
||||
var cardSrc = String(card).indexOf('data:') === 0
|
||||
? card
|
||||
: 'data:' + (profile.card_content_type || 'image/png') + ';base64,' + card;
|
||||
|
||||
$('#abhaVerifyCardWrap').html('<img src="' + cardSrc + '" alt="ABHA card">');
|
||||
$('#abhaVerifyDownloadCard').attr('href', cardSrc).removeClass('d-none');
|
||||
} else {
|
||||
$('#abhaVerifyCardWrap').html('<div class="text-muted">Card not returned.</div>');
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 5. **Card View for Printing**
|
||||
**File**: `app/Views/abha/card.php`
|
||||
**Lines**: 246-280
|
||||
|
||||
```php
|
||||
$storedAbhaCard = trim((string) ($stored_abha_card ?? ''));
|
||||
$storedAbhaCardSrc = $storedAbhaCard !== ''
|
||||
? (str_starts_with($storedAbhaCard, 'data:')
|
||||
? $storedAbhaCard
|
||||
: 'data:image/png;base64,' . $storedAbhaCard)
|
||||
: '';
|
||||
|
||||
if ($storedAbhaCardSrc !== ''):
|
||||
?>
|
||||
<div class="official-card-wrap">
|
||||
<div class="meta">Stored official ABHA card</div>
|
||||
<img class="official-card" src="<?= esc($storedAbhaCardSrc) ?>" alt="ABHA card">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 6. **Card Storage in Database**
|
||||
**File**: `app/Database/Migrations/2026-08-13-000001_AddAbhaCardToPatientMaster.php`
|
||||
**Column**: `patient_master.abha_card_base64`
|
||||
|
||||
```php
|
||||
if (! in_array('abha_card_base64', $fields, true)) {
|
||||
$this->forge->addColumn('patient_master', [
|
||||
'abha_card_base64' => [
|
||||
'type' => 'LONGTEXT',
|
||||
'null' => true,
|
||||
'after' => 'abha_address'
|
||||
]
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
### 7. **API Route**
|
||||
**File**: `app/Config/Routes.php`
|
||||
**Line**: 777
|
||||
|
||||
```php
|
||||
$routes->get('abha/card/(:segment)', 'Abha::card/$1', ['filter' => 'permission:abdm.abha.create']);
|
||||
```
|
||||
|
||||
✅ **Status**: IMPLEMENTED
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Complete User Journey
|
||||
|
||||
### Step 1: Verify ABHA
|
||||
```
|
||||
POST /abha/verify
|
||||
Body: { txn_id, otp, mobile }
|
||||
↓
|
||||
Bridge Response includes:
|
||||
- official_card (Base64 PNG)
|
||||
- card_base64
|
||||
- card_content_type
|
||||
```
|
||||
|
||||
### Step 2: HMS Receives Response
|
||||
```
|
||||
Controller: Abha::verifyOtp()
|
||||
- Extracts card from response
|
||||
- Passes to client
|
||||
```
|
||||
|
||||
### Step 3: Display in Modal
|
||||
```
|
||||
Modal (abha_verify_modal.php):
|
||||
- Shows card image
|
||||
- Enables download button
|
||||
- Shows "Official ABHA" indicator
|
||||
```
|
||||
|
||||
### Step 4: User Downloads
|
||||
```
|
||||
Click "Download ABHA Card"
|
||||
↓
|
||||
Browser downloads PNG with Base64 data URL
|
||||
↓
|
||||
Saves as: ABHA-card.png
|
||||
```
|
||||
|
||||
### Step 5: Later Access
|
||||
```
|
||||
Patient Profile → "View ABHA Card"
|
||||
↓
|
||||
GET /abha/card/91-5101-6530-5101
|
||||
↓
|
||||
Card view renders with:
|
||||
- Official ABHA card image (from database)
|
||||
- Patient HMS ID with barcode
|
||||
- QR code
|
||||
- Print-ready layout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Verification Matrix
|
||||
|
||||
| Feature | Bridge Requirement | HMS Implementation | Status |
|
||||
|---------|-------------------|-------------------|--------|
|
||||
| Gateway endpoint | `/v3/abha/card` | ✅ EAtriaBridgeConnector line 744 | ✅ DONE |
|
||||
| Query params | `abha_number`, `abha_address` | ✅ Lines 748-750 | ✅ DONE |
|
||||
| Authentication | Bearer token | ✅ Hospital API key + X-Token | ✅ DONE |
|
||||
| Card field name | `official_card` | ✅ Lines 810-823 (8 variants) | ✅ DONE |
|
||||
| Card format | `png` | ✅ PNG image Base64 | ✅ DONE |
|
||||
| Data URL format | `data:image/png;base64,...` | ✅ Lines 249, 428, 237 | ✅ DONE |
|
||||
| Card source | `abdm` or `provisional` | ✅ Line 759 | ✅ DONE |
|
||||
| Client display | Data URL in img tag | ✅ Modal JavaScript | ✅ DONE |
|
||||
| Download button | Enabled when card available | ✅ abha_verify_modal.php line 90 | ✅ DONE |
|
||||
| Card storage | Database persistence | ✅ `abha_card_base64` column | ✅ DONE |
|
||||
| Print view | Dedicated card page | ✅ `abha/card.php` | ✅ DONE |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Test Cases Passing
|
||||
|
||||
### Test 1: Card Fetch via Gateway
|
||||
```
|
||||
Given: ABHA verification complete
|
||||
When: card_base64 is empty from verify response
|
||||
Then: Bridge connector calls /v3/abha/card
|
||||
And: Extracts official_card from response
|
||||
Result: ✅ PASS
|
||||
```
|
||||
|
||||
### Test 2: Card Data URL Conversion
|
||||
```
|
||||
Given: Base64 card data from bridge
|
||||
When: Bridge returns plain Base64 (no data: prefix)
|
||||
Then: HMS adds "data:image/png;base64," prefix
|
||||
Result: ✅ PASS
|
||||
```
|
||||
|
||||
### Test 3: Card Display in Modal
|
||||
```
|
||||
Given: card_base64 in response
|
||||
When: Modal loads with card data
|
||||
Then: Image renders in #abhaVerifyCardWrap
|
||||
And: Download button becomes enabled
|
||||
Result: ✅ PASS
|
||||
```
|
||||
|
||||
### Test 4: Card Storage and Retrieval
|
||||
```
|
||||
Given: Patient with ABHA card created
|
||||
When: User visits patient profile
|
||||
Then: GET /abha/card/{abha} retrieves stored card
|
||||
And: Card displays in print-ready view
|
||||
Result: ✅ PASS
|
||||
```
|
||||
|
||||
### Test 5: Multi-field Extraction
|
||||
```
|
||||
Given: Bridge returns card in different field name
|
||||
When: Response has "card_data_uri" instead of "official_card"
|
||||
Then: extractOfficialCard handles all 8 variants
|
||||
Result: ✅ PASS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Evidence from Log 940
|
||||
|
||||
**Actual Response from Bridge** (Log 940):
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"request_id": "REQ-20260814005850-...",
|
||||
"official_card": "[BASE64_PNG]",
|
||||
"card_data": "[BASE64_PNG]",
|
||||
"abhaCard": "[BASE64_PNG]",
|
||||
"card_format": "png",
|
||||
"card_source": "abdm"
|
||||
}
|
||||
```
|
||||
|
||||
**HMS Processing**:
|
||||
1. ✅ Receives response with `ok: 1`
|
||||
2. ✅ Extracts `official_card` field
|
||||
3. ✅ Converts to data URL
|
||||
4. ✅ Returns as `card_base64` to client
|
||||
5. ✅ Client displays in modal
|
||||
6. ✅ User can download
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Integration Points
|
||||
|
||||
### External Calls
|
||||
```
|
||||
HMS → ABDM Bridge Gateway
|
||||
└─ POST /v3/abha/login/verify-otp (verify flow)
|
||||
└─ GET /v3/abha/card (explicit card download)
|
||||
```
|
||||
|
||||
### Internal Flow
|
||||
```
|
||||
Abha Controller
|
||||
↓
|
||||
EAtriaBridgeConnector::get('/v3/abha/card')
|
||||
↓
|
||||
Extract official_card
|
||||
↓
|
||||
Return card_base64 to client
|
||||
↓
|
||||
Modal displays + Download enabled
|
||||
↓
|
||||
User downloads or views
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Compliance Summary
|
||||
|
||||
### ABDM Bridge v3 Spec Compliance
|
||||
- ✅ Correct endpoint called
|
||||
- ✅ Correct parameters passed
|
||||
- ✅ Correct authentication headers
|
||||
- ✅ Correct response parsing
|
||||
- ✅ Correct field extraction
|
||||
- ✅ Correct data format handling
|
||||
- ✅ Correct user presentation
|
||||
|
||||
### Best Practices Implemented
|
||||
- ✅ Multiple field name fallbacks
|
||||
- ✅ Data URL format handling
|
||||
- ✅ Source indicator (official/provisional)
|
||||
- ✅ Database persistence
|
||||
- ✅ Print-ready view
|
||||
- ✅ Error handling
|
||||
- ✅ User-friendly UI
|
||||
|
||||
---
|
||||
|
||||
## ✅ Final Verdict
|
||||
|
||||
**HMS Implementation Status**: ✅ **COMPLETE AND VERIFIED**
|
||||
|
||||
The HMS system is correctly implementing the ABDM Bridge v3 Gateway card download endpoint exactly as specified in Section 7 of the Integration Guide.
|
||||
|
||||
All components are:
|
||||
- ✅ Implemented
|
||||
- ✅ Tested
|
||||
- ✅ Working
|
||||
- ✅ Compliant with Bridge specification
|
||||
|
||||
**No gaps found. Implementation is production-ready.**
|
||||
|
||||
---
|
||||
|
||||
## 📌 Key Files Reference
|
||||
|
||||
```
|
||||
Backend:
|
||||
├── app/Controllers/Abha.php (card display, verify endpoints)
|
||||
├── app/Libraries/Abdm/EAtriaBridgeConnector.php (gateway call)
|
||||
├── app/Config/Routes.php (route definition)
|
||||
└── app/Database/Migrations/2026-08-13-000001_AddAbhaCardToPatientMaster.php
|
||||
|
||||
Frontend:
|
||||
├── app/Views/partials/abha_verify_modal.php (verification + download)
|
||||
├── app/Views/partials/abha_create_modal.php (creation flow)
|
||||
├── app/Views/partials/abha_mobile_modal.php (mobile OTP flow)
|
||||
└── app/Views/abha/card.php (card display/print view)
|
||||
|
||||
Database:
|
||||
└── patient_master.abha_card_base64 (card storage)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Implementation Verified**: ✅ 2026-08-14
|
||||
**Bridge Version**: e-Atria Bridge v3
|
||||
**HMS Version**: CI4
|
||||
|
|
@ -1,391 +0,0 @@
|
|||
# PHP ABDM Gateway Deployment Guide
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
HMS CI4 (main app)
|
||||
↓ (queue event: event_type + payload)
|
||||
BridgeSyncService.processPending()
|
||||
↓ POST with Bearer token
|
||||
https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
↓ (PHP gateway routes event_type → internal handler)
|
||||
/api/v3/abha/validate
|
||||
/api/v3/consent/request
|
||||
/api/v3/bundle/push
|
||||
/api/v3/snomed/search
|
||||
↓ (gateway proxies to)
|
||||
ABDM M3 (https://dev.abdm.gov.in/api/v3)
|
||||
CSNOtk (https://csnotk.e-atria.in/csnoserv)
|
||||
```
|
||||
|
||||
## HMS CI4 Configuration (Already Updated)
|
||||
|
||||
**File:** `.env`
|
||||
|
||||
```env
|
||||
# Now pointing to PHP gateway instead of CSNOtk
|
||||
BRIDGE_SYNC_URL = https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
BRIDGE_SYNC_TOKEN = <MUST_GENERATE_STRONG_TOKEN> # Currently empty - see section below
|
||||
BRIDGE_SOURCE_CODE = SBXID_033661
|
||||
```
|
||||
|
||||
### Bearer Token Requirements
|
||||
|
||||
1. **Generate a strong random token** (minimum 32 characters, alphanumeric + special chars)
|
||||
- Example: `openssl rand -hex 32`
|
||||
- Result: `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2`
|
||||
|
||||
2. **Set this token in HMS CI4 .env:**
|
||||
```env
|
||||
BRIDGE_SYNC_TOKEN = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
```
|
||||
|
||||
3. **Use the SAME token in PHP gateway .env:**
|
||||
```env
|
||||
GATEWAY_BEARER_TOKEN = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PHP Gateway Setup on Ubuntu Server
|
||||
|
||||
### Prerequisites (to be verified on server)
|
||||
- PHP 8.3+ with CLI and extensions (json, curl, pdo_mysql)
|
||||
- MySQL 8.0+ (accessible locally)
|
||||
- Apache 2.4 with mod_rewrite and `a2enmod ssl`
|
||||
- Composer
|
||||
- SSL certificate (Let's Encrypt recommended)
|
||||
|
||||
### Deployment Steps
|
||||
|
||||
#### 1. Prepare Server Directory
|
||||
```bash
|
||||
# SSH to https://abdm-bridge.e-atria.in server
|
||||
ssh user@server-ip
|
||||
|
||||
# Create application directory
|
||||
sudo mkdir -p /opt/abdm-gateway
|
||||
sudo chown -R www-data:www-data /opt/abdm-gateway
|
||||
cd /opt/abdm-gateway
|
||||
```
|
||||
|
||||
#### 2. Clone/Copy PHP Gateway Code
|
||||
```bash
|
||||
# Option A: Copy from Windows (via SCP)
|
||||
scp -r d:\Workplace\HMS_CI4_OLD\gateway-php-ci4/* user@server-ip:/opt/abdm-gateway/
|
||||
|
||||
# Option B: If on Linux, clone from repository
|
||||
git clone <repo-url> /opt/abdm-gateway
|
||||
```
|
||||
|
||||
#### 3. Install Dependencies
|
||||
```bash
|
||||
cd /opt/abdm-gateway
|
||||
composer install --no-dev --optimize-autoloader
|
||||
```
|
||||
|
||||
#### 4. Configure Environment
|
||||
```bash
|
||||
# Copy environment template
|
||||
sudo cp .env.example .env
|
||||
sudo chown www-data:www-data .env
|
||||
|
||||
# Edit .env with server credentials
|
||||
sudo nano .env
|
||||
```
|
||||
|
||||
**Critical .env settings:**
|
||||
```env
|
||||
# Database (must match HMS MySQL or create separate)
|
||||
database.default.hostname = localhost
|
||||
database.default.database = abdm_gateway_db
|
||||
database.default.username = abdm_gw_user
|
||||
database.default.password = <strong-password>
|
||||
|
||||
# ABDM Configuration
|
||||
GATEWAY_BEARER_TOKEN = <SAME_TOKEN_AS_HMS>
|
||||
ABDM_URL = https://dev.abdm.gov.in/api/v3
|
||||
ABDM_TOKEN = 656f79f1-ef99-495f-9f37-713219ecbbcf
|
||||
SNOMED_SERVICE_URL = https://csnotk.e-atria.in/csnoserv
|
||||
GATEWAY_PUBLIC_URL = https://abdm-bridge.e-atria.in
|
||||
|
||||
# Facility IDs (from ABDM registration)
|
||||
ABDM_HFR_ID = <your-hfr-id>
|
||||
ABDM_NPI_ID = <your-npi-id>
|
||||
|
||||
# Security
|
||||
GATEWAY_TEST_MODE = false
|
||||
SESSION_COOKIE_SECURE = true
|
||||
CI_ENVIRONMENT = production
|
||||
```
|
||||
|
||||
#### 5. Generate Encryption Key
|
||||
```bash
|
||||
php spark key:generate
|
||||
```
|
||||
|
||||
#### 6. Create Database & User
|
||||
```bash
|
||||
sudo mysql -u root -p -e "
|
||||
CREATE DATABASE IF NOT EXISTS abdm_gateway_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS 'abdm_gw_user'@'localhost' IDENTIFIED BY '<strong-password>';
|
||||
GRANT ALL PRIVILEGES ON abdm_gateway_db.* TO 'abdm_gw_user'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
"
|
||||
```
|
||||
|
||||
#### 7. Run Migrations
|
||||
```bash
|
||||
cd /opt/abdm-gateway
|
||||
php spark migrate
|
||||
```
|
||||
|
||||
#### 8. Set Permissions
|
||||
```bash
|
||||
chmod -R 755 writable/
|
||||
chmod -R 755 public/
|
||||
sudo chown -R www-data:www-data writable/ public/ app/Database/Migrations/
|
||||
```
|
||||
|
||||
#### 9. Configure Apache VirtualHost
|
||||
```bash
|
||||
# Use provided Apache config as template
|
||||
sudo cp apache-vhost-abdm-bridge.conf /etc/apache2/sites-available/abdm-bridge.conf
|
||||
|
||||
# Enable site
|
||||
sudo a2ensite abdm-bridge
|
||||
|
||||
# Test configuration
|
||||
sudo apache2ctl configtest
|
||||
|
||||
# Reload Apache
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
**Sample VirtualHost** (if not using provided config):
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName abdm-bridge.e-atria.in
|
||||
DocumentRoot /opt/abdm-gateway/public
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/abdm-bridge.e-atria.in/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/abdm-bridge.e-atria.in/privkey.pem
|
||||
|
||||
<Directory /opt/abdm-gateway/public>
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php/$1 [L]
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
<Directory /opt/abdm-gateway>
|
||||
<Files "app/*">
|
||||
Deny from all
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
ErrorLog /var/log/apache2/abdm-bridge-error.log
|
||||
CustomLog /var/log/apache2/abdm-bridge-access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
#### 10. Obtain SSL Certificate
|
||||
```bash
|
||||
# Using Certbot
|
||||
sudo certbot certonly --apache -d abdm-bridge.e-atria.in
|
||||
|
||||
# Auto-renew
|
||||
sudo systemctl enable certbot.timer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Check & Smoke Tests
|
||||
|
||||
### 1. Health Endpoint (Public, no auth)
|
||||
```bash
|
||||
curl -i https://abdm-bridge.e-atria.in/api/v3/health
|
||||
```
|
||||
|
||||
**Expected Response (200):**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2024-05-13T10:30:00+05:30",
|
||||
"service": "abdm-bridge-gateway",
|
||||
"version": "1.0.0",
|
||||
"mode": "live",
|
||||
"uptime": 3600
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Gateway Status (Requires Bearer Token)
|
||||
```bash
|
||||
TOKEN="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
|
||||
curl -i -H "Authorization: Bearer $TOKEN" https://abdm-bridge.e-atria.in/api/v3/gateway/status
|
||||
```
|
||||
|
||||
**Expected Response (200):**
|
||||
```json
|
||||
{
|
||||
"gateway": "operational",
|
||||
"database": "connected",
|
||||
"abdm_m3": "reachable",
|
||||
"snomed_service": "reachable"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Queue Processing from HMS
|
||||
```bash
|
||||
# SSH to HMS server
|
||||
ssh user@hms-server
|
||||
|
||||
cd /opt/hms-ci4
|
||||
# Install/update cron jobs for HMS background workers (idempotent)
|
||||
bash scripts/install_hms_cron.sh /opt/hms-ci4
|
||||
|
||||
# Manual one-time run checks
|
||||
php spark abdm:push-sync --limit=20
|
||||
php spark bridge:sync --limit 5
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
ABDM Push Sync
|
||||
Processed: N
|
||||
Success: N
|
||||
Failed: 0
|
||||
|
||||
Bridge sync completed:
|
||||
- Processed: N
|
||||
- Sent: N
|
||||
- Failed: 0
|
||||
```
|
||||
|
||||
### 4. Recommended Cron Block (Installed by Script)
|
||||
```cron
|
||||
* * * * * cd /opt/hms-ci4 && php spark abdm:push-sync --limit=20 --worker=cron-abdm-m2 >> /opt/hms-ci4/writable/logs/cron/abdm_push_sync.log 2>&1
|
||||
* * * * * cd /opt/hms-ci4 && php spark bridge:sync --limit 10 >> /opt/hms-ci4/writable/logs/cron/bridge_sync.log 2>&1
|
||||
* * * * * cd /opt/hms-ci4 && php spark snomed:process-coding >> /opt/hms-ci4/writable/logs/cron/snomed_process.log 2>&1
|
||||
*/2 * * * * cd /opt/hms-ci4 && php spark abdm:hiu-poll --limit=30 >> /opt/hms-ci4/writable/logs/cron/abdm_hiu_poll.log 2>&1
|
||||
*/5 * * * * cd /opt/hms-ci4 && php spark abdm:hiu-retry --limit=20 >> /opt/hms-ci4/writable/logs/cron/abdm_hiu_retry.log 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Troubleshooting
|
||||
|
||||
### View Request Logs
|
||||
```bash
|
||||
# SSH to gateway server
|
||||
mysql -u abdm_gw_user -p abdm_gateway_db
|
||||
|
||||
# Check recent API calls
|
||||
SELECT request_id, endpoint, status_code, response_time_ms, created_at
|
||||
FROM abdm_request_logs
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
|
||||
# Check failed requests
|
||||
SELECT * FROM abdm_request_logs
|
||||
WHERE status_code >= 400
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;
|
||||
```
|
||||
|
||||
### View Audit Trail
|
||||
```bash
|
||||
# Track consent operations
|
||||
SELECT request_id, action, patient_abha, action_status, details, created_at
|
||||
FROM abdm_audit_trail
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
|
||||
# Track bundle operations
|
||||
SELECT * FROM abdm_bundles
|
||||
WHERE push_status IN ('pending', 'failed')
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### Debug PHP Gateway Logs
|
||||
```bash
|
||||
tail -f /var/log/apache2/abdm-bridge-error.log
|
||||
tail -f /opt/abdm-gateway/writable/logs/log-*.log
|
||||
```
|
||||
|
||||
### Common Issues & Fixes
|
||||
|
||||
**Issue: 403 Unauthorized on /api/v1/bridge**
|
||||
```
|
||||
Cause: Bearer token mismatch
|
||||
Fix: Verify HMS BRIDGE_SYNC_TOKEN matches gateway GATEWAY_BEARER_TOKEN
|
||||
Check no trailing spaces in token value
|
||||
```
|
||||
|
||||
**Issue: 500 Database Connection Error**
|
||||
```
|
||||
Cause: MySQL credentials incorrect or database not created
|
||||
Fix: Verify .env database settings match actual MySQL setup
|
||||
Run: php spark migrate --group=default
|
||||
```
|
||||
|
||||
**Issue: 404 on /api/v3/health**
|
||||
```
|
||||
Cause: Apache mod_rewrite not enabled or CI4 routing issue
|
||||
Fix: sudo a2enmod rewrite && sudo systemctl reload apache2
|
||||
Check .htaccess exists in /opt/abdm-gateway/public/
|
||||
```
|
||||
|
||||
**Issue: ABDM M3 Returns 503 (CloudFront)**
|
||||
```
|
||||
Cause: ABDM sandbox infrastructure issue
|
||||
Status: Not under our control; usually temporary
|
||||
Workaround: Enable test mode in .env (GATEWAY_TEST_MODE = true) for development
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Checklist
|
||||
|
||||
- [ ] HMS CI4 .env updated with PHP gateway URL and bearer token
|
||||
- [ ] PHP gateway code copied to /opt/abdm-gateway
|
||||
- [ ] Composer dependencies installed (`composer install`)
|
||||
- [ ] .env configured with database and ABDM credentials
|
||||
- [ ] Encryption key generated (`php spark key:generate`)
|
||||
- [ ] MySQL database created (`abdm_gateway_db`)
|
||||
- [ ] Migrations run (`php spark migrate`)
|
||||
- [ ] Permissions set on writable/ and public/ directories
|
||||
- [ ] Apache VirtualHost configured with SSL
|
||||
- [ ] DNS resolves abdm-bridge.e-atria.in to server IP
|
||||
- [ ] Health check returns 200 OK
|
||||
- [ ] Gateway status check returns operational
|
||||
- [ ] Test: HMS `php spark bridge:sync --limit 1` processes queue
|
||||
- [ ] Request logs populate in MySQL
|
||||
- [ ] Bearer token documented in secure location
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If deployment fails:
|
||||
1. Keep previous PHP gateway code backed up
|
||||
2. If Apache fails: `sudo systemctl restart apache2`
|
||||
3. If database corrupts: Restore from backup, re-run migrations
|
||||
4. If token lost: Generate new token, update both HMS and gateway .env files
|
||||
5. Revert HMS .env to old BRIDGE_SYNC_URL if needed (but keep updated for future use)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **PHP Gateway Code:** `d:\Workplace\HMS_CI4_OLD\gateway-php-ci4\`
|
||||
- **HMS Integration:** `app/Libraries/BridgeSyncService.php` (buildBridgeDispatchContext method)
|
||||
- **Queue Table:** `bridge_sync_queue` (HMS database)
|
||||
- **Logging Tables:** `abdm_api_logs`, `abdm_audit_trail`, `abdm_bundles`, `abdm_request_logs` (gateway database)
|
||||
- **Event Types Supported:** See Routes.php `/api/v1/bridge` handler documentation
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
# ABDM IPD Discharge Summary FHIR R4 Integration & Fixes
|
||||
|
||||
**Last Updated:** 2026-09-06
|
||||
**Module:** ABDM Gateway / IPD Discharge FHIR Generator
|
||||
**Files Modified:**
|
||||
- `app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php`
|
||||
- `app/Controllers/Ipd_discharge.php`
|
||||
- `app/Controllers/AbdmGateway.php`
|
||||
- `tests/unit/Abdm/FhirGeneratorsTest.php`
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Summary
|
||||
When generating and pushing an IPD Discharge Summary FHIR Bundle with attached PDF documents (Discharge Summary & IPD Bill) to an ABDM PHR app (e.g. ABHA / Aarogya Setu / Driefcase / Eka Care), the PHR application failed with:
|
||||
> **"Error in parsing the data. your data is incorrect"**
|
||||
|
||||
---
|
||||
|
||||
## 2. Root Cause Analysis (ABDM NRCES FHIR R4 Profile)
|
||||
|
||||
According to the official NRCES India ABDM FHIR R4 profile `https://nrces.in/ndhm/fhir/r4/StructureDefinition/DischargeSummaryRecord`:
|
||||
|
||||
1. **Composition Section Slicing Rules**:
|
||||
- The discriminator for `Composition.section` is `code.coding.code`.
|
||||
- Every section slice has a strict cardinality of **`0..1` (Max: 1)**.
|
||||
- Using non-standard codes or repeating slice codes (e.g., multiple sections with code `425044008`) violates the profile.
|
||||
|
||||
2. **Code Discrepancies**:
|
||||
- **Medications Section**: Previously assigned `721981007` (SNOMED for *Diagnostic studies report / Investigations*). Because PHR parsers expect `DiagnosticReport` entries under `721981007`, receiving `MedicationRequest` entries triggered a schema validation error.
|
||||
- **Discharge Diagnosis**: Previously used code `397659008`. In ABDM `DischargeSummaryRecord`, diagnoses must be grouped under the standard `MedicalHistory` slice (`1003642006`).
|
||||
- **Procedures**: Previously used code `371525003` instead of profile slice code `1003640003`.
|
||||
- **Care Plan / Advice**: Previously used code `736271009` instead of profile slice code `734163000`.
|
||||
|
||||
3. **Standard ABDM Discharge Summary Section Slices**:
|
||||
| Slice Name | Standard SNOMED Code | Standard Display | Target Profile |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **ChiefComplaints** | `422843007` | Chief complaint section | `Condition` |
|
||||
| **MedicalHistory** | `1003642006` | Past medical history section | `Condition`, `Procedure` |
|
||||
| **PhysicalExamination** | `425044008` | Physical exam section | `Observation` |
|
||||
| **Procedures** | `1003640003` | History of past procedure section | `Procedure` |
|
||||
| **Medications** | `1003606003` | Medication history section | `MedicationRequest` |
|
||||
| **Investigations** | `721981007` | Diagnostic studies report | `DiagnosticReportLab`, `DiagnosticReportImaging` |
|
||||
| **CarePlan** | `734163000` | Care plan | `CarePlan` |
|
||||
| **DocumentReference** | `373942005` | Discharge summary | `DocumentReference` |
|
||||
| **Allergies** | `722446000` | Allergy record | `AllergyIntolerance` |
|
||||
| **FamilyHistory** | `422432008` | Family history | `FamilyMemberHistory` |
|
||||
|
||||
4. **DocumentReference Requirements**:
|
||||
- `DocumentReference` requires valid `status`, `type`, `subject`, `content[0].attachment` (`contentType`, `data`, `title`, `creation`), `description`, and `text` (XHTML narrative).
|
||||
|
||||
---
|
||||
|
||||
## 3. Implemented Fixes
|
||||
|
||||
1. **`DischargeFhirGenerator.php`**:
|
||||
- Aligned all `Composition.section` slices to standard ABDM SNOMED codes and displays.
|
||||
- Merged diagnoses and clinical history under `Medical History` (`1003642006`).
|
||||
- Consolidated physical exam observations (admission/discharge) under single `Physical Examination` (`425044008`) slice.
|
||||
- Assigned correct `Medications` code (`1003606003`) to `MedicationRequest` entries.
|
||||
- Assigned `Care Plan` code (`734163000`) for follow-up and discharge advice.
|
||||
- Ensured `DocumentReference` includes `description` and valid `text` narrative.
|
||||
|
||||
2. **`Ipd_discharge.php`**:
|
||||
- Added `generateDischargeSummaryPdfBinary(int $ipdId, bool $withHeader = true)` to render mPDF binaries and cache to `writable/uploads/abdm/ipd/{ipdId}/discharge-summary.pdf`.
|
||||
- Linked `buildIpdPdfDocumentsList()` to auto-generate and include the PDF binary in ABDM FHIR requests.
|
||||
|
||||
3. **`AbdmGateway.php`**:
|
||||
- Integrated automatic generation of discharge summary PDFs when bundling or serving IPD care context records.
|
||||
|
||||
4. **Unit Tests**:
|
||||
- Updated `tests/unit/Abdm/FhirGeneratorsTest.php` assertions to match standard section slice titles.
|
||||
- All 42 unit tests pass.
|
||||
|
||||
---
|
||||
|
||||
## 4. Verification Command
|
||||
To dump and verify the IPD discharge bundle from CLI:
|
||||
```bash
|
||||
php spark fhir:dump
|
||||
```
|
||||
Output is saved to `writable/ipd9_bundle.json`.
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
# Session Handover: ABDM IPD Discharge Summary Lifecycle & Clinical Observations Integration
|
||||
|
||||
**Date:** October 17, 2026
|
||||
**Status:** ✅ Complete, Committed, Pushed & Verified on Live Environment
|
||||
**Target Repository:** `HMS_CI4_OLD` (`origin/main`)
|
||||
**Live Server:** `hms-live` (`root@159.89.175.190`, `/var/www/html/hms_etria`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement & User Feedback
|
||||
|
||||
1. **Premature FHIR Generation on Intermediate Save Buttons**:
|
||||
- The IPD Discharge Summary editor (`Ipd_discharge::ipd_select`) has numerous independent save buttons (`add_complaint`, `save_main`, `dietary_autosave`, `add_drug`, etc.).
|
||||
- Previously, intermediate clicks triggered `enqueueIpdDischargeSync` immediately while the patient was still admitted (`discarge_patient_status == 0`, no discharge date).
|
||||
- This generated an incomplete, draft FHIR DocumentBundle and queued it in `health_records` and `abdm_sync_outbox`.
|
||||
|
||||
2. **Missing FHIR Trigger in Billing Department Discharge**:
|
||||
- While ABDM care context discovery correctly requires an official discharge date, the Billing discharge action (`Billing\Ipd::updateDischargeStatus`) did **not** trigger FHIR bundle regeneration.
|
||||
- When the patient was officially discharged in billing, the old premature draft bundle remained in `health_records` without the final discharge date, final discharge status, or subsequent clinical sections.
|
||||
|
||||
3. **Missing Physical Examination & Vitals in FHIR Records (Screenshot 1 vs Screenshot 2)**:
|
||||
- In Screenshot 1 (Discharge Summary PDF), comprehensive General Examination on Admission and Examination on Discharge were present:
|
||||
- `Pulse : 40/min`, `Respiration : 25/min`, `Pallor : Negative`, `Jaundice : Negative`, `Cyanosis : Negative`, `Clubbing : Negative`, `Edema : Negative`, `JVP : Normal`
|
||||
- In Screenshot 2 (PHR App `abdm_etria`), only **Chief complaints** and **DocumentReference** were visible.
|
||||
- Examination was missing because `DischargeFhirGenerator.php` lacked the code to transform `$source['ui_physical_exam']` / `$source['observations']` into FHIR `Observation` resources and `Composition.section` entries.
|
||||
|
||||
4. **Missing Comorbidities (`cerebral vascular accident (CVA)`)**:
|
||||
- Comorbidities entered in the UI were saved in `opd_prescription.Prescriber_Remarks` via NABH history, but `enqueueIpdDischargeFhirSync` and `AbdmGateway` only checked `ipd_discharge_instructions.comp_report`, failing to load CVA as a FHIR `Condition`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Implementation Details
|
||||
|
||||
### A. Physical Examination & Vital Signs FHIR Observation Generation
|
||||
- **File**: `app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php`
|
||||
- Iterates over `$source['ui_physical_exam']` and `$source['observations']`.
|
||||
- Maps parameters (Pulse, Respiration, Pallor, Jaundice, Cyanosis, Clubbing, Edema, JVP, BP, Temperature, $\text{SpO}_2$) to SNOMED CT and LOINC concept codes.
|
||||
- Generates FHIR `Observation` resources with profile `https://nrces.in/ndhm/fhir/r4/StructureDefinition/Observation`.
|
||||
- Assigns categories (`vital-signs` or `exam`) and stores numeric values (`valueQuantity`) or clinical descriptions (`valueString`).
|
||||
- Added **Physical Examination Section** (SNOMED `425044008`, `Physical examination section`) to `Composition.section`, referencing all examination observations.
|
||||
- Updated narrative generator to render examination observations in the human-readable summary.
|
||||
|
||||
### B. Comorbidities (`CVA`) Extraction & Mapping
|
||||
- **Files**:
|
||||
- `app/Controllers/Ipd_discharge.php` (`enqueueIpdDischargeFhirSync`)
|
||||
- `app/Controllers/AbdmGateway.php` (`buildIpdDischargeGatewayPayload`)
|
||||
- Added fallback lookup to `opd_prescription` / NABH history (`Prescriber_Remarks` using `hydrateNabhFieldsFromOpdRow`) when `ipd_discharge_instructions` does not contain comorbidities.
|
||||
- Splits comma/newline delimited comorbidities and maps each into a FHIR `Condition` resource under the **Medical History / Problems** section.
|
||||
|
||||
### C. Gated Discharge Lifecycle & Billing Trigger
|
||||
- **File**: `app/Controllers/Ipd_discharge.php` (`enqueueIpdDischargeSync`)
|
||||
- Gated ABDM sync: verifies that `discarge_patient_status > 0` and `discharge_date` is non-empty before enqueuing or updating ABDM FHIR bundles.
|
||||
- Draft section saves while patient is admitted save locally to HMS MySQL tables only.
|
||||
- **File**: `app/Controllers/Billing/Ipd.php` (`updateDischargeStatus`)
|
||||
- Hooked official patient discharge event: triggers `$dischargeCtrl->enqueueIpdDischargeSync($ipdId, $patientId, 'final_discharge')`.
|
||||
- Compiles the final, complete FHIR DocumentBundle with discharge date, disposition, complaints, comorbidities, examinations, medications, advice, and attached PDF.
|
||||
|
||||
### D. Test Coverage
|
||||
- **File**: `tests/unit/Abdm/FhirGeneratorsTest.php`
|
||||
- Updated `testDischargeSummaryGeneratorCreatesFullSectionsAndComponents` to assert `Observation` resource inclusion and `Physical Examination` composition section.
|
||||
- All discharge unit tests passing (4/4 tests, 187 assertions).
|
||||
|
||||
---
|
||||
|
||||
## 3. Deployment & Live Verification
|
||||
|
||||
1. **Git Commit & Push**:
|
||||
- Branch: `origin/main`
|
||||
- Commits: `fdd188d` & `ab4b68b`
|
||||
2. **Live Server Deployment (`hms-live`)**:
|
||||
- Directory: `/var/www/html/hms_etria`
|
||||
- Pulled `origin/main` and cleared cache via `php spark cache:clear`.
|
||||
3. **Live Server Record Verification (Janvi Bisht - IPD 3224)**:
|
||||
- Command: `php spark abdm:resync-discharge 3224`
|
||||
- Output Verified in Gateway DB (`abdm_health_records` ID 616, Ref: `DISCHARGE-3224-20261017`):
|
||||
- **Composition**: 1
|
||||
- **Patient**: 1, **Practitioner**: 1, **Organization**: 1, **Encounter**: 1
|
||||
- **Conditions (3)**: `Abdominal Pain`, `Headache`, `cerebral vascular accident (CVA)`
|
||||
- **Observations (17)**: 16 Admission & Discharge Vitals/Exam + 1 Lab
|
||||
- **MedicationRequest (1)**: `DOZPANTA DSR`
|
||||
- **CarePlan (2)**: `Dietary Advice`, `Follow Up Review after 5 days`
|
||||
- **DocumentReference (1)**: Full Discharge Summary PDF (185 KB)
|
||||
- **AllergyIntolerance (1)**
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
# ABDM Log ID 940 - ABHA Login Verify Analysis Report
|
||||
|
||||
## Executive Summary
|
||||
✅ **ABHA Login Verify endpoint successfully returns the official ABHA card image**
|
||||
|
||||
Log ID 940 validates that the e-Atria ABDM Bridge v3 endpoint `POST /api/v3/abha/login/verify-otp` is working correctly and returning the official ABHA card data as expected.
|
||||
|
||||
---
|
||||
|
||||
## Log Details
|
||||
|
||||
### Request Information
|
||||
- **Log ID**: 940
|
||||
- **Endpoint**: `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp`
|
||||
- **HTTP Method**: POST
|
||||
- **Channel**: eatria_bridge
|
||||
- **Timestamp**: 2026-08-14 00:58:53 UTC
|
||||
|
||||
### Response Information
|
||||
- **Status Code**: 200 OK ✅
|
||||
- **Event Type**: `abha.login.verify-otp`
|
||||
- **Response Code**: success
|
||||
- **HTTP Status**: Success (no error_message)
|
||||
|
||||
---
|
||||
|
||||
## ABHA Account Data Retrieved
|
||||
|
||||
```
|
||||
ABHA Number: 91-5101-6530-5101
|
||||
ABHA Address: singhdevender0328@sbx
|
||||
Full Name: Devender Singh
|
||||
Date of Birth: 1979-03-28 (28/03/1979)
|
||||
Gender: Male (M)
|
||||
Mobile: 9720958717 (redacted in logs)
|
||||
Status: ACTIVE
|
||||
KYC Verified: YES ✅
|
||||
Mobile Verified: YES ✅
|
||||
Verification Type: AADHAAR
|
||||
Verification Status: VERIFIED ✅
|
||||
|
||||
Address:
|
||||
205 A, Ward No -3
|
||||
Bisht Niwas, New Avas Vikas
|
||||
Kashipur
|
||||
Udham Singh Nagar
|
||||
Uttarakhand, INDIA
|
||||
PIN: 244713
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card Data in Response ✅
|
||||
|
||||
### Card Data Found - YES
|
||||
The response includes the official ABHA card in multiple locations:
|
||||
|
||||
#### 1. **Top-Level Response Fields**
|
||||
```json
|
||||
{
|
||||
"official_card": "[BASE64_ENCODED_PNG_IMAGE]",
|
||||
"card_data": "[BASE64_ENCODED_PNG_IMAGE]",
|
||||
"abhaCard": "[BASE64_ENCODED_PNG_IMAGE]",
|
||||
"card_format": "png"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **Account Object**
|
||||
```json
|
||||
{
|
||||
"account": {
|
||||
"official_card": "[BASE64_ENCODED_PNG_IMAGE]",
|
||||
"... other account fields ..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Card Format Details
|
||||
- **Format**: PNG image
|
||||
- **Encoding**: Base64 string
|
||||
- **Size**: Approx. 10-15 KB when encoded
|
||||
- **Decoded Size**: Approx. 7-11 KB binary PNG data
|
||||
- **Content-Type**: `image/png`
|
||||
|
||||
---
|
||||
|
||||
## Bridge Message Validation
|
||||
|
||||
### Statement from ABDM Bridge
|
||||
> "The HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes."
|
||||
|
||||
### Validation Result: ✅ CONFIRMED
|
||||
|
||||
**What the Bridge Provides:**
|
||||
- ✅ `official_card` - Base64-encoded PNG image (FOUND)
|
||||
- ✅ `card_data` - Alternative reference to the same card (FOUND)
|
||||
- ✅ `abhaCard` - Additional card reference (FOUND)
|
||||
- ⚠️ `card_data_uri` - Not found in this response (may be alternative naming)
|
||||
|
||||
**Key Finding:** The bridge uses `official_card` (Base64 PNG) rather than `card_data_uri` (which would be a URI reference). This is acceptable since:
|
||||
1. The card image data is embedded directly
|
||||
2. HMS app can decode and display it without additional network calls
|
||||
3. Can be converted to a blob URL for browser rendering
|
||||
|
||||
---
|
||||
|
||||
## HMS Implementation Status
|
||||
|
||||
### Card Extraction - VERIFIED ✅
|
||||
File: [app/Controllers/Abha.php](app/Controllers/Abha.php)
|
||||
|
||||
**Method**: `extractAbhaCardData()`
|
||||
```php
|
||||
private function extractAbhaCardData(array $payload): string
|
||||
{
|
||||
$sources = [
|
||||
$payload, // Top-level
|
||||
$payload['data'] ?? [], // Nested data object
|
||||
$payload['account'] ?? [], // Account object
|
||||
$payload['profile'] ?? [], // Profile object
|
||||
$payload['gateway_patient'] ?? [], // Gateway patient
|
||||
// ... more sources
|
||||
];
|
||||
|
||||
// Checks these card field names:
|
||||
foreach (['official_card', 'card_data_uri', 'card_base64',
|
||||
'card_data', 'abhaCard', 'abha_card', 'cardData', 'card'] as $key) {
|
||||
// Extracts from any matching source
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bridge Connector - VERIFIED ✅
|
||||
File: [app/Libraries/Abdm/EAtriaBridgeConnector.php](app/Libraries/Abdm/EAtriaBridgeConnector.php)
|
||||
|
||||
**Method**: `extractOfficialCard()`
|
||||
- Handles multiple card field names
|
||||
- Supports nested card data structures
|
||||
- Extracts Base64 data correctly
|
||||
|
||||
### Response Building - VERIFIED ✅
|
||||
File: [app/Controllers/Abha.php](app/Controllers/Abha.php)
|
||||
Lines: ~140-180, ~320-360
|
||||
|
||||
**In `verifyOtp()` method:**
|
||||
```php
|
||||
$responseBase = [
|
||||
// ...
|
||||
'card_base64' => $this->extractAbhaCardData($result),
|
||||
'card_content_type' => $this->resolveAbhaCardContentType($result),
|
||||
'card_source' => $this->resolveAbhaCardSource($result),
|
||||
// ... other ABHA profile fields
|
||||
];
|
||||
```
|
||||
|
||||
**In `verifyCommOtp()` method:**
|
||||
- Same pattern applies
|
||||
- Card data extracted from verify-comm-otp response
|
||||
- Returned to client
|
||||
|
||||
---
|
||||
|
||||
## Client-Side Usage
|
||||
|
||||
### How to Render the Card in Browser
|
||||
```javascript
|
||||
// From ABHA verify response
|
||||
const response = {
|
||||
card_base64: "[BASE64_PNG_IMAGE]",
|
||||
card_content_type: "image/png"
|
||||
};
|
||||
|
||||
// Convert Base64 to Blob
|
||||
const binaryString = atob(response.card_base64);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
const blob = new Blob([bytes], { type: response.card_content_type });
|
||||
|
||||
// Create object URL for display
|
||||
const cardImageUrl = URL.createObjectURL(blob);
|
||||
|
||||
// Use in img tag
|
||||
document.getElementById('abhaCard').src = cardImageUrl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Across Routes
|
||||
|
||||
### Routes Tested/Verified
|
||||
1. ✅ **POST /abha/login/verify-otp** (Log 940 - CONFIRMED)
|
||||
2. ✅ **POST /abha/create/verify_comm_otp** (HMS code has same extraction)
|
||||
3. 🔄 Other routes should follow same pattern
|
||||
|
||||
### Routes Supporting Card Data
|
||||
Based on HMS implementation:
|
||||
- ABHA Aadhaar OTP verification: ✅ Extracts card data
|
||||
- ABHA Mobile OTP verification: ✅ Extracts card data
|
||||
- Any other ABDM response: ✅ Uses same `extractAbhaCardData()` method
|
||||
|
||||
---
|
||||
|
||||
## Key Observations
|
||||
|
||||
### 1. **Multiple Card Field Names**
|
||||
Bridge returns the same card image under multiple field names:
|
||||
- `official_card` (primary)
|
||||
- `card_data`
|
||||
- `abhaCard`
|
||||
- `card_data_uri` (may appear in other response types)
|
||||
|
||||
HMS handles all variations automatically.
|
||||
|
||||
### 2. **Flexible Field Mapping**
|
||||
Card data can appear at multiple levels:
|
||||
- Top-level response: `response.official_card`
|
||||
- Account nested: `response.account.official_card`
|
||||
- Data nested: `response.data.official_card`
|
||||
|
||||
HMS searches all these locations.
|
||||
|
||||
### 3. **Format Specification**
|
||||
- Card format returned: PNG
|
||||
- Encoding: Base64
|
||||
- Can be easily rendered in browser
|
||||
- No server-side PDF generation needed for card display
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### ✅ Current Implementation is Sound
|
||||
1. **Card extraction is robust** - handles multiple field names and nesting levels
|
||||
2. **Content-type resolution works** - correctly identifies PNG format
|
||||
3. **Response building includes card data** - returned to client for display
|
||||
|
||||
### 🔄 Potential Enhancements
|
||||
1. **Monitor for `card_data_uri`** - if bridge switches to URI references, add fetch logic
|
||||
2. **Test on other verification routes** - ensure consistency across all ABHA flows
|
||||
3. **Add card display** - show official card image in ABHA verification modal
|
||||
4. **Cache card image** - store Base64 in session to avoid re-fetching
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **ABDM Bridge v3 is successfully providing official ABHA card data**
|
||||
|
||||
Log ID 940 confirms:
|
||||
1. Card data is returned in standard response format
|
||||
2. HMS can extract this data from the response
|
||||
3. Card is in PNG format, Base64 encoded
|
||||
4. Implementation is ready for client-side rendering
|
||||
|
||||
The bridge message "HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes" is **VALIDATED** - the endpoint delivers the expected data successfully.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
- ABDM Bridge Endpoint: `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp`
|
||||
- E-Atria Documentation: `docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md`
|
||||
- HMS ABHA Controller: `app/Controllers/Abha.php`
|
||||
- Bridge Connector: `app/Libraries/Abdm/EAtriaBridgeConnector.php`
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
# ABDM Log 940 - Complete Analysis Summary
|
||||
|
||||
## 🎯 Main Finding
|
||||
**✅ ABHA Card Display is FULLY IMPLEMENTED and WORKING**
|
||||
|
||||
The e-Atria ABDM Bridge v3 endpoint successfully returns the official ABHA card image, and HMS is consuming and displaying it correctly.
|
||||
|
||||
---
|
||||
|
||||
## Log 940 Details
|
||||
- **Endpoint**: `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp`
|
||||
- **Status**: 200 OK ✅
|
||||
- **Timestamp**: 2026-08-14 00:58:53
|
||||
- **ABHA Number**: 91-5101-6530-5101 (Devender Singh)
|
||||
- **Verification Status**: VERIFIED ✅
|
||||
|
||||
---
|
||||
|
||||
## Card Data in Response
|
||||
|
||||
### What Bridge Returns
|
||||
```json
|
||||
{
|
||||
"official_card": "[BASE64_ENCODED_PNG]",
|
||||
"card_data": "[BASE64_ENCODED_PNG]",
|
||||
"abhaCard": "[BASE64_ENCODED_PNG]",
|
||||
"card_format": "png"
|
||||
}
|
||||
```
|
||||
|
||||
### Card Details
|
||||
- **Format**: PNG image
|
||||
- **Encoding**: Base64 string
|
||||
- **Size**: ~10-15 KB encoded
|
||||
- **Available in**:
|
||||
- Response root level
|
||||
- Account object: `response.account.official_card`
|
||||
- Multiple field names for compatibility
|
||||
|
||||
---
|
||||
|
||||
## HMS Implementation - VERIFIED ✅
|
||||
|
||||
### 1. Server-Side Extraction
|
||||
**File**: `app/Controllers/Abha.php`
|
||||
|
||||
**Methods extracting card data:**
|
||||
```php
|
||||
// Line 139 - verifyOtp() method
|
||||
'card_base64' => $this->extractAbhaCardData($result),
|
||||
'card_content_type' => $this->resolveAbhaCardContentType($result),
|
||||
'card_source' => $this->resolveAbhaCardSource($result),
|
||||
|
||||
// Line 325 - verifyCommOtp() method
|
||||
'card_base64' => $this->extractAbhaCardData($result),
|
||||
'card_content_type' => $this->resolveAbhaCardContentType($result),
|
||||
'card_source' => $this->resolveAbhaCardSource($result),
|
||||
```
|
||||
|
||||
**Extraction Logic:**
|
||||
- Searches 6+ nested locations for card data
|
||||
- Handles 8 different field names (official_card, card_data_uri, card_base64, etc.)
|
||||
- Supports nested array structures
|
||||
- Automatically detects PNG format
|
||||
|
||||
### 2. Client-Side Display
|
||||
**File**: `app/Views/partials/abha_verify_modal.php` (Lines 224-237)
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
var card = profile.card_base64 || '';
|
||||
if (card) {
|
||||
// Convert to data URL if not already
|
||||
var cardSrc = String(card).indexOf('data:') === 0
|
||||
? card
|
||||
: 'data:' + (profile.card_content_type || 'image/png') + ';base64,' + card;
|
||||
|
||||
// Check if it's official ABDM card
|
||||
var isOfficial = profile.card_source === 'abdm';
|
||||
|
||||
// Display appropriate note
|
||||
var note = isOfficial
|
||||
? ''
|
||||
: '<div class="small text-warning mt-1">Provisional card generated by the Bridge, not the official ABDM card.</div>';
|
||||
|
||||
// Render card image
|
||||
$('#abhaVerifyCardWrap').html('<img src="' + cardSrc + '" alt="ABHA card">' + note);
|
||||
|
||||
// Enable download link
|
||||
$('#abhaVerifyDownloadCard').attr('href', cardSrc).removeClass('d-none');
|
||||
} else {
|
||||
// Fallback if no card data
|
||||
$('#abhaVerifyCardWrap').html('<div class="text-center text-muted">Official ABHA card was not returned by the Bridge.</div>');
|
||||
$('#abhaVerifyDownloadCard').addClass('d-none');
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Additional Modal Support
|
||||
The card display is implemented in multiple modals:
|
||||
- ✅ `abha_verify_modal.php` - Main verification modal (LINE 224)
|
||||
- ✅ `abha_create_modal.php` - Create ABHA flow (LINE 420)
|
||||
- ✅ `abha_mobile_modal.php` - Mobile OTP verification (LINE 230)
|
||||
- ✅ `abha_patient_match_modal.php` - Patient matching (LINE 233)
|
||||
|
||||
---
|
||||
|
||||
## Bridge Message Validation
|
||||
|
||||
### Statement
|
||||
> "The HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes."
|
||||
|
||||
### Verification Result: ✅ CONFIRMED
|
||||
|
||||
**What the Bridge Sends:**
|
||||
- ✅ `official_card` - Base64 PNG (FOUND in log 940)
|
||||
- ✅ `card_data` - Base64 PNG (FOUND in log 940)
|
||||
- ✅ `abhaCard` - Base64 PNG (FOUND in log 940)
|
||||
- ⚠️ `card_data_uri` - Not in this response (may be alternative naming)
|
||||
|
||||
**HMS Consumption:**
|
||||
- ✅ Extracts from all locations automatically
|
||||
- ✅ Handles multiple field names
|
||||
- ✅ Displays with card source indicator (official vs. provisional)
|
||||
- ✅ Provides card download functionality
|
||||
|
||||
**Verification Routes Implemented:**
|
||||
1. ✅ Aadhaar OTP verification: `POST abha/verify` → `verifyOtp()`
|
||||
2. ✅ Mobile OTP verification: `POST abha/create/verify_comm_otp` → `verifyCommOtp()`
|
||||
3. ✅ Used across all ABHA verification flows
|
||||
|
||||
---
|
||||
|
||||
## Client-Side Rendering Features
|
||||
|
||||
### Card Image Display
|
||||
- Automatically converts Base64 to data URL
|
||||
- Renders PNG image inline in modal
|
||||
- Includes card source indicator (official/provisional)
|
||||
- Shows fallback message if no card data
|
||||
|
||||
### Card Download
|
||||
- Provides downloadable card link
|
||||
- Dynamically enabled/disabled based on card availability
|
||||
- Downloads as PNG image with Base64 encoding
|
||||
|
||||
### User Notifications
|
||||
```javascript
|
||||
// Shows warning if card is provisional (Bridge-generated, not official ABDM)
|
||||
if (!isOfficial) {
|
||||
showWarning("Provisional card generated by the Bridge, not the official ABDM card.");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
ABDM Bridge (v3)
|
||||
↓
|
||||
POST /api/v3/abha/login/verify-otp
|
||||
↓
|
||||
Response: {official_card: "BASE64_PNG", ...}
|
||||
↓
|
||||
HMS Controller: Abha::verifyOtp()
|
||||
↓
|
||||
extractAbhaCardData($result) → "BASE64_PNG"
|
||||
↓
|
||||
Response to Client:
|
||||
{
|
||||
ok: 1,
|
||||
card_base64: "BASE64_PNG",
|
||||
card_content_type: "image/png",
|
||||
card_source: "abdm",
|
||||
...other ABHA data...
|
||||
}
|
||||
↓
|
||||
Client JavaScript
|
||||
↓
|
||||
Convert to data URL: "data:image/png;base64,BASE64_PNG"
|
||||
↓
|
||||
Display in Modal: <img src="data:..." alt="ABHA card">
|
||||
↓
|
||||
User sees official ABHA card in verification modal ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the Feature
|
||||
|
||||
### In Browser (User Flow)
|
||||
1. User navigates to ABHA verification
|
||||
2. Selects "Verify via OTP" (Aadhaar or Mobile)
|
||||
3. Enters and verifies OTP
|
||||
4. Modal shows:
|
||||
- ABHA profile photo
|
||||
- **Official ABHA card image** ✅
|
||||
- Option to download card
|
||||
- Verification details
|
||||
5. User confirms account creation/linking
|
||||
|
||||
### API Response Test
|
||||
```bash
|
||||
POST https://localhost:8080/abha/verify
|
||||
Body: {
|
||||
txn_id: "3cb896f2-87d6-4dcb-a26e-09697cf1816a",
|
||||
otp: "123456"
|
||||
}
|
||||
|
||||
Response (success):
|
||||
{
|
||||
ok: 1,
|
||||
card_base64: "[BASE64_PNG]",
|
||||
card_content_type: "image/png",
|
||||
card_source: "abdm",
|
||||
abha_number: "91-5101-6530-5101",
|
||||
name: "Devender Singh",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bridge Message Translation
|
||||
|
||||
The bridge statement mentioned:
|
||||
- `official_card` ← refers to the Base64-encoded PNG image (not a URI)
|
||||
- `card_data_uri` ← alternative name for the same data
|
||||
- "across all verification routes" ← works in:
|
||||
- Aadhaar OTP flow ✅
|
||||
- Mobile OTP flow ✅
|
||||
- Any ABHA verification endpoint ✅
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **The ABDM Bridge v3 integration is working as expected**
|
||||
|
||||
1. Bridge sends official ABHA card in Base64 PNG format
|
||||
2. HMS extracts card data automatically from multiple field names
|
||||
3. HMS displays card image in verification modals
|
||||
4. Card download functionality is available
|
||||
5. Card source is identified (official vs. provisional)
|
||||
6. Works across all ABHA verification routes
|
||||
|
||||
The message "HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes" is **VALIDATED** with proof from:
|
||||
- Live log entry #940
|
||||
- Complete server-side implementation
|
||||
- Working client-side rendering
|
||||
|
||||
---
|
||||
|
||||
## Files Involved
|
||||
- `app/Controllers/Abha.php` - Card extraction & response building
|
||||
- `app/Libraries/Abdm/EAtriaBridgeConnector.php` - Bridge connector with card extraction
|
||||
- `app/Views/partials/abha_verify_modal.php` - Card display UI
|
||||
- `app/Views/partials/abha_create_modal.php` - Card display in create flow
|
||||
- `app/Views/partials/abha_mobile_modal.php` - Card display in mobile flow
|
||||
- `app/Views/partials/abha_patient_match_modal.php` - Card in patient matching
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
- Bridge Documentation: `docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md`
|
||||
- Database Log: `abdm_api_logs.id = 940`
|
||||
- Test Timestamp: 2026-08-14 00:58:53
|
||||
- Test Patient: Devender Singh (ABHA: 91-5101-6530-5101)
|
||||
Binary file not shown.
|
|
@ -1,310 +0,0 @@
|
|||
# ABDM Log 940 Analysis - Complete Documentation
|
||||
|
||||
## Overview
|
||||
This folder contains comprehensive analysis of **ABDM API Log ID 940**, which validates that the e-Atria ABDM Bridge v3 is successfully returning official ABHA card data.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Document Index
|
||||
|
||||
### 1. **ABDM_LOG_940_QUICK_REFERENCE.md**
|
||||
- **Purpose**: Quick lookup for developers
|
||||
- **Contains**:
|
||||
- Request/response summary
|
||||
- Card data field list
|
||||
- Key findings checklist
|
||||
- Card rendering JavaScript code
|
||||
- HMS response format
|
||||
- **Best For**: Quick reference during implementation
|
||||
|
||||
### 2. **ABDM_LOG_940_COMPLETE_REPORT.md**
|
||||
- **Purpose**: Comprehensive technical documentation
|
||||
- **Contains**:
|
||||
- Full implementation details
|
||||
- Bridge message validation
|
||||
- Client-side rendering code
|
||||
- Data flow diagram
|
||||
- Testing instructions
|
||||
- File references with line numbers
|
||||
- **Best For**: Understanding the complete flow
|
||||
|
||||
### 3. **ABDM_LOG_940_ANALYSIS.md**
|
||||
- **Purpose**: Formal analysis report
|
||||
- **Contains**:
|
||||
- Executive summary
|
||||
- Log details and response info
|
||||
- Card data validation
|
||||
- Bridge message validation
|
||||
- HMS implementation status
|
||||
- Recommendations
|
||||
- **Best For**: Documentation and compliance
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Findings
|
||||
|
||||
### ✅ Bridge Is Working
|
||||
- Endpoint: `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp`
|
||||
- Status Code: **200 OK**
|
||||
- Response Time: Normal
|
||||
- Error Rate: None
|
||||
|
||||
### ✅ Card Data Present
|
||||
- **official_card**: Base64-encoded PNG ✅
|
||||
- **card_data**: Base64-encoded PNG ✅
|
||||
- **abhaCard**: Base64-encoded PNG ✅
|
||||
- **card_format**: "png" ✅
|
||||
|
||||
### ✅ HMS Implementation Complete
|
||||
- Server: Extracts card from response ✅
|
||||
- Client: Displays card in modals ✅
|
||||
- Download: Card download functionality ✅
|
||||
- Verification: Works across all routes ✅
|
||||
|
||||
### ✅ Bridge Message Validated
|
||||
> "The HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes."
|
||||
|
||||
**Status: CONFIRMED** ✅
|
||||
|
||||
---
|
||||
|
||||
## 📊 Log Details
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Log ID | 940 |
|
||||
| Endpoint | `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp` |
|
||||
| HTTP Method | POST |
|
||||
| Status Code | 200 |
|
||||
| Response Status | success |
|
||||
| Timestamp | 2026-08-14 00:58:53 UTC |
|
||||
| ABHA Number | 91-5101-6530-5101 |
|
||||
| ABHA Address | singhdevender0328@sbx |
|
||||
| Name | Devender Singh |
|
||||
| Status | ACTIVE |
|
||||
| KYC Verified | YES |
|
||||
| Mobile Verified | YES |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 What's in the Response
|
||||
|
||||
### Root Level
|
||||
```json
|
||||
{
|
||||
"official_card": "[BASE64_PNG]",
|
||||
"card_data": "[BASE64_PNG]",
|
||||
"abhaCard": "[BASE64_PNG]",
|
||||
"card_format": "png",
|
||||
"account": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Account Object
|
||||
```json
|
||||
{
|
||||
"ABHANumber": "91-5101-6530-5101",
|
||||
"preferredAbhaAddress": "singhdevender0328@sbx",
|
||||
"name": "Devender Singh",
|
||||
"status": "ACTIVE",
|
||||
"gender": "M",
|
||||
"dateOfBirth": "1979-03-28",
|
||||
"address": "205 A, Ward No -3, Bisht Niwas, New Avas Vikas, Kashipur, Udham Singh Nagar, Uttarakhand",
|
||||
"stateName": "UTTARAKHAND",
|
||||
"districtName": "UDHAM SINGH NAGAR",
|
||||
"verificationStatus": "VERIFIED",
|
||||
"verificationType": "AADHAAR",
|
||||
"kycVerified": true,
|
||||
"mobileVerified": true,
|
||||
"official_card": "[BASE64_PNG]"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 Implementation Checklist
|
||||
|
||||
### Server-Side ✅
|
||||
- [x] Extract card from bridge response
|
||||
- [x] Handle multiple field names (official_card, card_data_uri, etc.)
|
||||
- [x] Support nested card structures
|
||||
- [x] Detect card format (PNG)
|
||||
- [x] Return card_base64 to client
|
||||
- [x] Include card_content_type
|
||||
- [x] Track card source (abdm/provisional)
|
||||
|
||||
**Files**:
|
||||
- `app/Controllers/Abha.php` (lines 139, 325)
|
||||
- `app/Libraries/Abdm/EAtriaBridgeConnector.php` (line 810)
|
||||
|
||||
### Client-Side ✅
|
||||
- [x] Receive card_base64 from server
|
||||
- [x] Convert Base64 to data URL
|
||||
- [x] Display card image in modal
|
||||
- [x] Show source indicator (official/provisional)
|
||||
- [x] Provide card download link
|
||||
- [x] Fallback message if no card
|
||||
|
||||
**Files**:
|
||||
- `app/Views/partials/abha_verify_modal.php` (line 224)
|
||||
- `app/Views/partials/abha_create_modal.php` (line 420)
|
||||
- `app/Views/partials/abha_mobile_modal.php` (line 230)
|
||||
- `app/Views/partials/abha_patient_match_modal.php` (line 233)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Implementation Guide
|
||||
|
||||
### For Frontend Developers
|
||||
```javascript
|
||||
// Step 1: Receive card from ABHA verify response
|
||||
const response = await fetch('/abha/verify', { /* ... */ });
|
||||
const data = await response.json();
|
||||
|
||||
// Step 2: Get card data
|
||||
const cardBase64 = data.card_base64;
|
||||
const cardContentType = data.card_content_type || 'image/png';
|
||||
|
||||
// Step 3: Create displayable URL
|
||||
const dataUrl = `data:${cardContentType};base64,${cardBase64}`;
|
||||
|
||||
// Step 4: Display in HTML
|
||||
document.getElementById('abhaCardImage').src = dataUrl;
|
||||
```
|
||||
|
||||
### For Backend Developers
|
||||
```php
|
||||
// Card data is automatically extracted by:
|
||||
// 1. extractAbhaCardData($result) → returns Base64 string
|
||||
// 2. resolveAbhaCardContentType($result) → returns "image/png"
|
||||
// 3. resolveAbhaCardSource($result) → returns "abdm" or "provisional"
|
||||
|
||||
// Already implemented in:
|
||||
// - Abha::verifyOtp()
|
||||
// - Abha::verifyCommOtp()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Status
|
||||
|
||||
### Bridge Functionality
|
||||
- [x] Endpoint responding with 200 OK
|
||||
- [x] Card data included in response
|
||||
- [x] Multiple field name support
|
||||
- [x] Proper PNG format
|
||||
- [x] Base64 encoding correct
|
||||
|
||||
### HMS Integration
|
||||
- [x] Card extraction working
|
||||
- [x] Multiple route support (Aadhaar OTP, Mobile OTP)
|
||||
- [x] Client-side rendering implemented
|
||||
- [x] Download functionality available
|
||||
- [x] Source indication (official vs provisional)
|
||||
|
||||
### User Experience
|
||||
- [x] Card displays in verification modal
|
||||
- [x] Card available for download
|
||||
- [x] Warning shown for provisional cards
|
||||
- [x] Fallback message if unavailable
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
### ABDM Bridge Contracts
|
||||
- `docs/abdm/ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md`
|
||||
- Endpoint: `/api/v3/abha/login/verify-otp`
|
||||
- Card field: `card_base64`
|
||||
- Format: Base64-PNG without data prefix
|
||||
|
||||
### HMS ABHA Implementation
|
||||
- `app/Controllers/Abha.php`
|
||||
- Main ABHA controller with verification logic
|
||||
- `app/Libraries/Abdm/EAtriaBridgeConnector.php`
|
||||
- Bridge connector with card extraction
|
||||
|
||||
### ABHA Modal Views
|
||||
- `app/Views/partials/abha_verify_modal.php`
|
||||
- Main verification modal with card display
|
||||
- `app/Views/partials/abha_create_modal.php`
|
||||
- Create ABHA flow with card display
|
||||
- `app/Views/partials/abha_mobile_modal.php`
|
||||
- Mobile OTP verification with card display
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Card Not Displaying?
|
||||
1. Check if `card_base64` is in response
|
||||
2. Verify `card_content_type` is set
|
||||
3. Inspect browser console for JS errors
|
||||
4. Check CSS for display/visibility issues
|
||||
|
||||
### Card Download Not Working?
|
||||
1. Ensure data URL is properly formatted
|
||||
2. Check browser console for errors
|
||||
3. Verify `href` attribute on download link
|
||||
4. Test in different browser
|
||||
|
||||
### "Provisional Card" Warning?
|
||||
1. This is correct - bridge-generated cards show this
|
||||
2. Only official ABDM cards show no warning
|
||||
3. Both types are functional
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Response Time | Normal |
|
||||
| Card Data Size | ~10-15 KB (Base64) |
|
||||
| PNG File Size | ~7-11 KB (decoded) |
|
||||
| Processing Time | <1 second |
|
||||
| Cache Duration | Session |
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Notes
|
||||
|
||||
1. **Card Base64**: Safe to store in session
|
||||
2. **Data URL**: Safe for img src attribute
|
||||
3. **Download**: Initiates native browser download
|
||||
4. **Redaction**: Sensitive fields redacted in logs
|
||||
5. **PII**: All personally identifiable data protected
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For questions about:
|
||||
- **Bridge Integration**: Check `ABHA_IDENTIFIER_LOGIN_BRIDGE_CONTRACT.md`
|
||||
- **HMS Implementation**: Review code in `app/Controllers/Abha.php`
|
||||
- **Frontend Display**: Check modal views in `app/Views/partials/`
|
||||
- **Card Extraction**: Review `EAtriaBridgeConnector.php`
|
||||
|
||||
---
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### 2026-08-14
|
||||
- Log 940 captured showing successful verify-otp response
|
||||
- Bridge confirmed returning official_card in Base64 PNG format
|
||||
- HMS implementation verified working across all routes
|
||||
- Documentation created and verified
|
||||
|
||||
---
|
||||
|
||||
## Document Maintenance
|
||||
|
||||
- **Last Updated**: 2026-08-14
|
||||
- **Status**: COMPLETE
|
||||
- **Verification**: LOG 940 ✅
|
||||
- **Bridge Version**: e-Atria Bridge v3
|
||||
- **HMS Version**: CI4
|
||||
|
||||
---
|
||||
|
||||
**All documentation files are located in the workspace root directory starting with `ABDM_LOG_940_`**
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
# ABDM M1, M2 & M3 Integration Session Handover
|
||||
**Session Name:** `ABDM M1 Search, M2 User-Initiated Linking & M3 HIU Consent Fixes`
|
||||
**Antigravity Conversation ID:** `227da3ab-2456-4a5d-a2ab-c74059c3178b`
|
||||
**Last Updated:** September 10, 2026 (Server Local Time: 00:56)
|
||||
**Git Branch:** `main` | **Latest Commit:** `b89bace`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Resume Instructions for Tomorrow
|
||||
|
||||
### 1. In Antigravity IDE
|
||||
1. Open Antigravity IDE with workspace `HMS_CI4_OLD`.
|
||||
2. In the Chat panel, click the **Chat History / Clock icon** (left drawer).
|
||||
3. Select this session: **`ABDM M1 Search, M2 User-Initiated Linking & M3 HIU Consent Fixes`** (ID: `227da3ab-2456-4a5d-a2ab-c74059c3178b`).
|
||||
4. Type your next request or question to continue seamlessly!
|
||||
|
||||
*Tip: If opening a new chat, mention `@ABDM_M1_M2_M3_SESSION_HANDOVER.md` to instantly restore all session context.*
|
||||
|
||||
---
|
||||
|
||||
## 📌 Summary of Completed Work
|
||||
|
||||
### 1. ABDM M1: "Find ABHA by Mobile" Complete Profile Flow
|
||||
- **Challenge:** Previously, after searching linked ABHA numbers via mobile OTP in Step 1, selecting an ABHA ID required a redundant "Verify ABHA ID" step to retrieve the patient's full demographic profile (name, gender, DOB, address, photo, etc.).
|
||||
- **Solution:**
|
||||
- Integrated direct verification API flow upon ABHA profile selection.
|
||||
- Automatically loads and populates the complete KYC details (full name, gender, DOB, mobile, address, ABHA address, profile photo base64) directly into the patient registration form.
|
||||
- Fixed multi-step wizard state indicators in the registration modal.
|
||||
|
||||
---
|
||||
|
||||
### 2. ABDM M2: User-Initiated Linking Architecture & Implementation
|
||||
- **Context & Observation from ABDM Team:**
|
||||
> *"M2: User initiated linking: There is understanding gap in user initiated linking flow.*
|
||||
> *FLOW: Patient must be registered without ABHA details in your system -> Records will be created in your system -> User will link them on their own end using PHR app."*
|
||||
- **Network Topology Architecture:**
|
||||
- `HMS (behind NAT on premise/VPN)` $\longleftrightarrow$ `ABDM Bridge (Live in Cloud)` $\longleftrightarrow$ `ABDM Gateway` $\longleftrightarrow$ `PHR App (e.g. ABHA App, Aarogya Setu)`
|
||||
- Each HMS has a unique domain/subdomain reachable by the Bridge via VPN or cloud tunnel.
|
||||
- **HMS Implementation:**
|
||||
- **Database Migration:** `2026-09-09-000003_CreateAbdmLinkTransactions.php` creates table `abdm_link_transactions` (tracks `transaction_id`, `patient_reference`, `care_context_reference`, `auth_mode`, `otp_hash`, `expiry_at`, `status`).
|
||||
- **Discovery Endpoint:** Handles `/v3/hip/patient/care-context/discover` from the Bridge:
|
||||
- Matches patient by demographic data (Mobile number, Name, Gender, Year of Birth).
|
||||
- Returns patient UHID (`p_code`) and all unlinked care contexts (OPD visits, IPD admissions, Lab investigations, Pharmacy dispense records).
|
||||
- **Linking Initiation (`/v3/hip/link/init`):**
|
||||
- Generates authentication OTP or confirms direct link.
|
||||
- **Linking Confirmation (`/v3/hip/link/confirm`):**
|
||||
- Verifies OTP and permanently registers link in `abdm_link_transactions` and updates `patient_master.abdm_linked_at`.
|
||||
- **Bridge Walkthrough Verified:** Analyzed and approved the ABDM Bridge Walkthrough specification (`ABDM_BridgeSide_M2_workthrouh.txt`).
|
||||
|
||||
---
|
||||
|
||||
### 3. M3 HIU Consent Requests List: Live Server 500 Error Resolution
|
||||
- **Issue:** On the live server (`https://demo.e-atria.net`), opening **"ABHA Patient Request List"** triggered:
|
||||
`Failed to load: Unable to load consent requests.`
|
||||
HAR log showed: `GET https://demo.e-atria.net/AbdmHiu/patient_request_list_data?q=&status=` returning `500 Internal Server Error` with empty response body `""`.
|
||||
- **Root Cause Analysis:**
|
||||
1. **Memory Exhaustion on Decrypted FHIR Bundles:** `abdm_hiu_workflows` stores multi-megabyte decrypted base64 bundles in `response_json` for `data_fetch` and `hi_data_push_callback`. Querying 2000 rows exhausted PHP memory (`128M`), triggering a fatal memory limit error.
|
||||
2. **Hardcoded Column Selection:** The query hardcoded 17 columns in `SELECT` without checking table fields. Any schema difference on the remote database caused a fatal `DatabaseException` (`Unknown column in field list`).
|
||||
3. **Array-to-String Cast Warnings:** In PHP 8.2+, accessing nested payload objects (`requester`, `purpose`, `expiry`, `hi_types`) that arrived as associative arrays triggered `Array to string conversion` warnings, which CodeIgniter converted to fatal `ErrorException`s.
|
||||
4. **Missing Exception Handling:** `AbdmHiu::patientRequestListData()` lacked a `try ... catch` block, causing CI in production to respond with HTTP 500 and empty content.
|
||||
5. **Column Mismatch on `patient_master`:** Looked for `p_mobile` instead of actual columns `mphone1` and `mphone2`.
|
||||
- **Changes Applied & Tested:**
|
||||
- `app/Controllers/AbdmHiu.php`: Wrapped in `try ... catch (\Throwable $e)`, added error logging, and guaranteed clean JSON response.
|
||||
- `app/Libraries/Abdm/ConsentSessionListService.php`:
|
||||
- Dynamically queries only existing columns in `abdm_hiu_workflows`.
|
||||
- Masks large JSON fields at the SQL level (`CASE WHEN operation IN ('data_fetch', 'hi_data_push_callback') THEN NULL ELSE response_json END`).
|
||||
- Added `extractScalarString()` and safe array parsing.
|
||||
- Updated phone resolution (`mphone1`, `mphone2`) and cleaned full names.
|
||||
- Adopted stable session correlation algorithm matching `Patient.php`.
|
||||
- `app/Views/abdm/patient_request_list.php`: Enhanced fetch error handling to display HTTP status codes cleanly.
|
||||
- **Git Commit:** `b89bace` pushed to `origin/main`.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Live Server Deployment Step
|
||||
|
||||
When deploying to `demo.e-atria.net`:
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
php spark cache:clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 Key Files & References
|
||||
|
||||
| File | Purpose |
|
||||
| :--- | :--- |
|
||||
| `app/Controllers/AbdmHiu.php` | Controller for HIU Consent Request List & Document fetch |
|
||||
| `app/Libraries/Abdm/ConsentSessionListService.php` | Core service computing global consent sessions and status |
|
||||
| `app/Views/abdm/patient_request_list.php` | UI view for ABHA Patient Request List table and modal |
|
||||
| `app/Database/Migrations/2026-09-09-000003_CreateAbdmLinkTransactions.php` | M2 user-initiated linking database table migration |
|
||||
| `ABDM_BridgeSide_M2_workthrouh.txt` | Detailed specification for ABDM Bridge user-initiated linking |
|
||||
| `app/Controllers/Patient.php` | Per-patient OPD profile ABDM consent and linking management |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Open Items / Next Steps for Tomorrow
|
||||
|
||||
1. **Live Verification on `demo.e-atria.net`:**
|
||||
- Verify that **ABHA Patient Request List** loads all historical requests smoothly without error.
|
||||
2. **M2 User-Initiated Linking End-to-End Test:**
|
||||
- Create an unlinked patient in HMS.
|
||||
- Add an OPD visit and a prescription/document.
|
||||
- Use ABHA/PHR App on Sandbox to discover records using mobile number and complete linking with OTP.
|
||||
- Verify care context shows as linked on both Bridge and HMS.
|
||||
3. **M2 Pull & Push Sync Verification:**
|
||||
- Verify automatic push of newly created OPD prescriptions and diagnostic reports for linked care contexts.
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
# ABDM M2/M3 Architecture Transition & Live Fetch Handover Session
|
||||
|
||||
**Date:** 2026-09-28
|
||||
**Scope:** HMS (`devsofttech_hms_ci4`) & ABDM Bridge Gateway (`HMS-ABDM-Gateway`)
|
||||
**Status:** Completed & Verified Live
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary & Problem Resolution
|
||||
|
||||
### Initial Issue
|
||||
When patients requested their medical records in the ABHA PHR app (e.g., ABHA SBX / Eka Care), OPD consultations were either failing to display under `OPConsultation` or showing incomplete information (such as missing medicines).
|
||||
|
||||
### Root Causes Identified
|
||||
1. **Misconception of ABDM M2 vs M3 Architecture:**
|
||||
- Previously, the workflow relied on "pre-pushing" medical bundles to the cloud bridge gateway in advance using manual "Submit to ABDM" buttons.
|
||||
- If an OPD prescription was created, auto-saved, and later had medicines added/updated, pre-pushed bundles became stale or incomplete.
|
||||
2. **ABDM NRCeS Profile Naming:**
|
||||
- ABDM defines the Health Information Type as `OPConsultation` (or `OPConsultRecord` for the FHIR profile `https://nrces.in/ndhm/fhir/r4/StructureDefinition/OPConsultRecord`). The gateway and HMS were previously defaulting to `Prescription`, causing the records to appear in wrong categories or get filtered out.
|
||||
3. **M3 On-Demand Pull Mechanism:**
|
||||
- In true ABDM architecture:
|
||||
- **Milestone 2 (M2):** Care Context Discovery & Linking. HMS only notifies the ABDM Gateway of the patient's care context reference (e.g., `OPD-15357-S33716-20260928`). **No medical data is pushed to ABDM or the cloud at this stage.**
|
||||
- **Milestone 3 (M3):** Data Transfer / Health Information Exchange. When the patient approves consent and pulls data in their PHR app, the ABDM Gateway sends `health-information/hip/request`. At that exact moment, the Bridge Gateway queries HMS over the secure VPN tunnel (`/records/fetch`), dynamically generating the latest, 100% fresh FHIR bundle containing all current medicines, encrypting it, and transferring it to the PHR app.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Code Changes
|
||||
|
||||
### A. HMS Codebase (`devsofttech_hms_ci4`)
|
||||
|
||||
1. **`app/Libraries/FhirR4Builder.php`**:
|
||||
- Added `buildOpConsultBundle()` conforming to the NRCeS `OPConsultRecord` profile:
|
||||
- Includes `Bundle.type = "document"`.
|
||||
- Generates `Composition` with type `OP Consultation Record` (`371530004` / `Consultation report`).
|
||||
- Generates `Patient`, `Practitioner`, and `Encounter` resources.
|
||||
- Maps chief complaints and diagnoses to `Condition` resources.
|
||||
- Maps prescribed medicines from `opd_prescrption_prescribed` to `MedicationRequest` resources.
|
||||
|
||||
2. **`app/Controllers/Opd_prescription.php`**:
|
||||
- Made `regenerateFhirBundleInternal($opd_p_id)` public so it can be invoked on-demand by the gateway handler.
|
||||
- Hooked automatic FHIR bundle regeneration directly into:
|
||||
- `medicine_add()`
|
||||
- `medicine_update()`
|
||||
- `medicine_remove()`
|
||||
- Guaranteed that whenever a doctor adds or removes medications in HMS, the cached FHIR bundle is instantly regenerated.
|
||||
|
||||
3. **`app/Controllers/AbdmGateway.php`**:
|
||||
- Added **Strategy 0** in `recordsFetch()`:
|
||||
- Parses the requested care context (e.g. `OPD-15357-S33716-20260928`).
|
||||
- Locates the prescription and calls `regenerateFhirBundleInternal()`.
|
||||
- Returns the fresh `OPConsultRecord` FHIR bundle in real time to the bridge gateway over the VPN.
|
||||
|
||||
4. **`app/Views/abdm/task_board.php`**:
|
||||
- **Removed all legacy "Submit to ABDM" / "Push to Gateway" buttons**:
|
||||
- Removed `btn-opd-consult-submit-gateway` ("Submit to ABDM" / "Submit Again").
|
||||
- Removed `btn-invoice-push` from Invoices table.
|
||||
- Removed `btnSubmitFhirToAbdm` ("Force gateway push") from FHIR preview modal header.
|
||||
- Removed "Submit" push action buttons from the Pending Tasks table.
|
||||
- **Added "Link Context" (M2)**:
|
||||
- If care context is unlinked, shows a clean "Link Context" button invoking `window.openAbdmHipLinkModal()`.
|
||||
- If care context is already linked, displays a green `Linked` badge.
|
||||
- **Updated Metric Cards**:
|
||||
- Renamed "Records Pushed" to "Care Contexts Linked".
|
||||
- **Preserved "Preview FHIR"**:
|
||||
- Doctors and administrators can still preview the FHIR JSON bundle directly in the UI.
|
||||
|
||||
---
|
||||
|
||||
### B. ABDM Bridge Gateway (`HMS-ABDM-Gateway`)
|
||||
|
||||
1. **`app/Controllers/AbdmGateway.php`**:
|
||||
- **`hipHealthInfoRequest()`**:
|
||||
- Updated logic so that whenever a hospital has a reachable VPN endpoint/IP, it **always** requests live fresh FHIR data from the hospital HMS (`forwardToHms($hipId, 'records/fetch', ...)`).
|
||||
- Keeps the gateway database cache strictly as an offline fallback.
|
||||
- **ABDM-1017 Handling**:
|
||||
- Addressed the National Health Authority (NHA) gateway propagation race condition where requesting encrypted health data immediately caused `HTTP 400 ABDM-1017: Invalid Transaction Id`. Added a 1.5-second propagation pause followed by automatic 2-second retry.
|
||||
- **Timezone Correction**:
|
||||
- Fixed UTC vs IST discrepancies in date range queries for requested care contexts.
|
||||
- **HI Type Normalization**:
|
||||
- Normalized `hiTypes` in care context notifications (`OPConsultRecord` -> `OPConsultation`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Live End-to-End Verification
|
||||
|
||||
A live verification test was executed between the Bridge Gateway on `10.8.0.1` and the DevSoft Tech HMS instance (`IN0510000828`):
|
||||
|
||||
- **Care Context:** `OPD-15357-S33716-20260928`
|
||||
- **Result:**
|
||||
- `FORWARD_RESULT_OK = 1`
|
||||
- `HI_TYPE = OPConsultRecord`
|
||||
- `BUNDLE_TYPE = document`
|
||||
- `ENTRIES_COUNT = 21`
|
||||
- Prescribed medicines dynamically confirmed present in the generated bundle:
|
||||
- `ACILOC (RANITIDINE 50 MG)`
|
||||
- `CAP PANTOP DSR (Pantoprazole 40mg + Domperidone 30mg)`
|
||||
- `TAB PARACETAMOL 500 MG`
|
||||
|
||||
---
|
||||
|
||||
## 4. Git Repositories & Commit Log
|
||||
|
||||
### 1. HMS Application (`devsofttech_hms_ci4`)
|
||||
- **Remote:** `https://github.com/infodevsofttech-admin/devsofttech_hms_ci4.git`
|
||||
- **Branch:** `main`
|
||||
- **Key Commits:**
|
||||
- `90e63d2`: `fix(abdm): generate and publish OPD consultations as OPConsultRecord and support dynamic hi_type`
|
||||
- `d0f7aa4`: `feat(abdm): enable live fresh FHIR fetch on pull, remove old push options, and keep care context linking`
|
||||
- **Live Deployment Path:** `/var/www/html/hms_etria` (synced to `origin/main`)
|
||||
|
||||
### 2. ABDM Bridge Gateway (`HMS-ABDM-Gateway`)
|
||||
- **Remote:** `https://github.com/infodevsofttech-admin/HMS-ABDM-Gateway.git`
|
||||
- **Branch:** `AG_version`
|
||||
- **Server Path:** `/mnt/volume_blr1_1790148792836_eatria/www/abdm-bridge-gateway`
|
||||
- **Key Commits:**
|
||||
- `212938d`: `fix(transfer): add retry on ABDM-1017, fix IST timezone parsing, and normalize notify hiTypes`
|
||||
- `25eb7f0`: `feat(bridge): always fetch fresh live FHIR bundle from HMS on PHR pull request`
|
||||
- **Status:** Pushed to GitHub and active in production.
|
||||
|
||||
---
|
||||
|
||||
## 5. Next Operational Steps & Best Practices
|
||||
|
||||
1. **Care Context Linking (M2):**
|
||||
- Use the ABDM Task Board or automated cron job to link patient care contexts (`Link Context` button).
|
||||
- This registers the token with ABDM so patient apps can discover their visits.
|
||||
2. **Health Records Fetching (M3):**
|
||||
- No manual intervention or pushing required!
|
||||
- When the patient opens their ABHA app and views records, the Bridge Gateway automatically pulls live data from HMS via VPN, encrypts it with ECDH keys, and delivers it securely.
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# ABDM M2 Push Sync Implementation
|
||||
|
||||
## Architecture Summary
|
||||
- Added dedicated sync tables: `abdm_sync_patient`, `abdm_sync_record`, `abdm_sync_outbox`.
|
||||
- Added queue/outbox service and worker command for reliable push delivery.
|
||||
- Added Gateway client for `/api/v1/abdm/gateway/care-context/link` with validation and response mapping.
|
||||
- Added FHIR generator framework with module-specific generators.
|
||||
- Added advanced coding resolver with LOINC/SNOMED/UCUM fallback logic and validation scoring.
|
||||
- Added observability endpoints:
|
||||
- `GET /AbdmSync/summary`
|
||||
- `POST /AbdmSync/replay_dead/{id}`
|
||||
|
||||
## HMS Field Mapping Contract (Core)
|
||||
- `patient.id` -> `Patient.identifier[system=https://hms.local/patient-id].value` (mandatory)
|
||||
- `patient.abha_id` -> `Patient.identifier[system=https://healthid.ndhm.gov.in].value` (optional)
|
||||
- `patient.abha_address` -> `Patient.identifier[system=https://abdm.gov.in/health-address].value` (optional)
|
||||
- `encounter.id` -> `Encounter.identifier.value` (optional/mandatory if linked visit)
|
||||
- `record_id` -> Composition/diagnostic/claim resource IDs (mandatory)
|
||||
- `completed_at` -> `Composition.date`, `Bundle.timestamp`, report `issued` (mandatory)
|
||||
- `visit_date` -> gateway payload `visit_date` (mandatory for sync payload)
|
||||
|
||||
## HI Type Mapping
|
||||
- OPD -> `OPConsultRecord`
|
||||
- Lab -> `DiagnosticReportRecord`
|
||||
- Discharge -> `DischargeSummaryRecord`
|
||||
- Invoice -> `InvoiceRecord`
|
||||
- Radiology -> `DiagnosticReportRecord`
|
||||
|
||||
## Quality Policy
|
||||
- Score >= 85: allow push
|
||||
- Score 70-84: allow push with warnings
|
||||
- Score < 70: mark review-needed (policy hook available)
|
||||
|
||||
## Outbox Retry Policy
|
||||
- Care-context outbound default retries: 10s, 30s, 60s
|
||||
- Legacy schedule fallback still supported for older outbox rows
|
||||
|
||||
## Security Notes
|
||||
- Bearer token never logged.
|
||||
- ABHA masked in logs (last 4 visible).
|
||||
- Payload logs exclude full FHIR bundle at info level.
|
||||
|
||||
## Sample Generator Outputs
|
||||
### OPD
|
||||
- Source fixture: `tests/unit/Abdm/fixtures/opd_source.json`
|
||||
- Generator: `App\\Libraries\\Abdm\\Fhir\\Generators\\OpdFhirGenerator`
|
||||
- Output: `hi_type=OPConsultRecord`, deterministic care-context.
|
||||
|
||||
### Lab
|
||||
- Source fixture: `tests/unit/Abdm/fixtures/lab_source.json`
|
||||
- Generator: `App\\Libraries\\Abdm\\Fhir\\Generators\\LabFhirGenerator`
|
||||
- Includes `DiagnosticReport` + linked `Observation` resources.
|
||||
|
||||
### Discharge
|
||||
- Source fixture: `tests/unit/Abdm/fixtures/discharge_source.json`
|
||||
- Generator: `App\\Libraries\\Abdm\\Fhir\\Generators\\DischargeFhirGenerator`
|
||||
|
||||
### Invoice
|
||||
- Source fixture: `tests/unit/Abdm/fixtures/invoice_source.json`
|
||||
- Generator: `App\\Libraries\\Abdm\\Fhir\\Generators\\InvoiceFhirGenerator`
|
||||
|
||||
## Commands
|
||||
- Worker: `php spark abdm:push-sync --limit=20`
|
||||
- Existing bridge worker remains separate: `php spark bridge:sync`
|
||||
|
||||
## Cron Setup (HMS Server)
|
||||
- Use `bash scripts/install_hms_cron.sh /var/www/html/hms_etria`
|
||||
- Deploy script now runs this installer automatically after migrate.
|
||||
- Managed cron block includes:
|
||||
- `abdm:push-sync` every minute
|
||||
- `bridge:sync` every minute
|
||||
- `snomed:process-coding` every minute
|
||||
- `abdm:hiu-poll` every 2 minutes
|
||||
- `abdm:hiu-retry` every 5 minutes
|
||||
|
||||
## Manual Replay
|
||||
- Endpoint: `POST /AbdmSync/replay_dead/{id}`
|
||||
- Resets dead row to `pending` for reprocessing.
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# ABDM M2 Test Session Handover & Verification Guide
|
||||
|
||||
**Date:** 2026-09-30
|
||||
**Branch:** `main`
|
||||
**Latest Commits:**
|
||||
- `6bd2430` — `fix(abdm-m1): sanitize otp error text, hide bridge request id, add beneficiary name to consent`
|
||||
- `a33b376` — `feat(abdm-m2): add HIP link modal and care context discovery on patient registration`
|
||||
- `ccd5909` — `docs: remove technical URLs, database fields and developer codes from user help guide`
|
||||
- `f7c3052` — `feat(abdm-m2): enable care context discovery and direct linking for invoice records`
|
||||
**Live Environment:** [demo.e-atria.net](https://demo.e-atria.net/) (`10.8.0.1` / `/var/www/html/hms_etria`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
This session verified and configured end-to-end readiness for **ABDM Milestone 2 (M2)** testing across all supported Health Information (HI) types:
|
||||
1. **OPConsultRecord** (OPD Consultations & Prescriptions)
|
||||
2. **DiagnosticReportRecord** (Laboratory & Radiology Reports)
|
||||
3. **DischargeSummaryRecord** (IPD Discharge Summaries)
|
||||
4. **ImmunizationRecord** (Vaccination Records)
|
||||
5. **InvoiceRecord** (OPD, IPD, and General Charges Invoices)
|
||||
|
||||
All features have been unit tested (57/57 tests passing, 495 assertions), committed, pushed to `origin/main`, and deployed live to `https://demo.e-atria.net/`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Changes Implemented
|
||||
|
||||
### A. Patient Registration & ABHA Management Integration
|
||||
- **File:** [Patient_V.php](file:///d:/Workplace/HMS_CI4_OLD/app/Views/billing/Patient_V.php)
|
||||
- Included `partials/abdm_hip_link_modal.php` on the Patient Registration view so the HIP link modal can be opened from registration screens.
|
||||
- Added hidden `input_abha_address` to `form.form1` so ABHA addresses are seamlessly submitted and saved to `patient_master`.
|
||||
- Added a dedicated 4th card under **Patient Management → ABHA Management** tab:
|
||||
- **Link Records to ABHA (HIP)** button (`openAbdmHipLinkModal()`)
|
||||
- **Send Deep Link SMS** button (`openAbdmHipSmsModal()`)
|
||||
|
||||
### B. Instant Care Context Discovery for New Patients
|
||||
- **File:** [AbdmGateway.php](file:///d:/Workplace/HMS_CI4_OLD/app/Controllers/AbdmGateway.php)
|
||||
- Updated `findCareContextsForPatient()` to query `opd_master` in addition to `opd_prescription`.
|
||||
- Previously, a care context was only discoverable after a doctor wrote a prescription or finalized a lab report.
|
||||
- Now, as soon as an OPD consultation appointment / slip is created at the reception desk, the visit is instantly discoverable and linkable as `OPD-{patientId}-S{opdId}-{YYYYMMDD}` with title `OPConsultRecord - Dr. {DoctorName} - {Date}`.
|
||||
- Added unit test `testDiscoversOpdMasterVisitWhenPrescriptionNotCreated` in [CareContextDiscoveryTest.php](file:///d:/Workplace/HMS_CI4_OLD/tests/unit/Abdm/CareContextDiscoveryTest.php).
|
||||
|
||||
### C. 14-Digit ABHA Number & `@sbx` Address Dual Resolution
|
||||
- **File:** [AbdmGateway.php](file:///d:/Workplace/HMS_CI4_OLD/app/Controllers/AbdmGateway.php)
|
||||
- Enhanced `hipPatientCareContexts()` so that if an ABHA address field contains a 14-digit numeric ABHA ID (or formatted `XX-XXXX-XXXX-XXXX`), it queries both `abha_address` and `abha_id` in `patient_master`.
|
||||
|
||||
### D. Invoice Care Context Discovery & Direct Linking
|
||||
- **Files:** [AbdmTaskBoard.php](file:///d:/Workplace/HMS_CI4_OLD/app/Controllers/AbdmTaskBoard.php), [task_board.php](file:///d:/Workplace/HMS_CI4_OLD/app/Views/abdm/task_board.php), [AbdmGateway.php](file:///d:/Workplace/HMS_CI4_OLD/app/Controllers/AbdmGateway.php), [abdm_hip_link_modal.php](file:///d:/Workplace/HMS_CI4_OLD/app/Views/partials/abdm_hip_link_modal.php)
|
||||
- Integrated `HI Type: InvoiceRecord` care contexts on the ABDM Work Task Board Invoice panel.
|
||||
- Linked `patient_master` to retrieve `abha_id` / `abha_address` across OPD, IPD, and Charges invoices.
|
||||
- Auto-generated standard care contexts: `INVOICE-OPD-{billId}-{date}`, `INVOICE-CHG-{billId}-{date}`, `INVOICE-IPD-{billId}-{date}`.
|
||||
- Enabled the **Link Context** button on invoice rows with complete task context (`task_type: 'invoice_publish'`, `source: invoiceSource`, `entity_id: billId`).
|
||||
- Added NRCES-compliant Invoice FHIR Bundle bridge push (`share_invoice_source_bundle`) directly upon linking from the modal.
|
||||
- Added dynamic DOM updates on the Task Board table row to reflect `LINKED` status, update badges, and show care context references immediately upon link confirmation.
|
||||
- Added unit test `testDiscoversInvoiceCareContextWhenInvoiceTaskRequested` in [CareContextDiscoveryTest.php](file:///d:/Workplace/HMS_CI4_OLD/tests/unit/Abdm/CareContextDiscoveryTest.php).
|
||||
|
||||
### E. M1 Feedback Updates (Retained & Verified)
|
||||
- Normalized invalid OTP error alerts strictly to `"Incorrect OTP"` (or attempts exceeded message).
|
||||
- Removed internal `Bridge Request ID: REQ-...` from user-facing screens.
|
||||
- Added healthcare worker declaration and beneficiary name consent input field before Aadhaar OTP dispatch.
|
||||
|
||||
---
|
||||
|
||||
## 3. M2 Testing Step-by-Step Walkthrough
|
||||
|
||||
### Step 1: Register Patient & Create Visit Slip
|
||||
1. Open **Patient Management → New Person** on `https://demo.e-atria.net/`.
|
||||
2. Register patient with Mobile Number, Name, Gender, DOB.
|
||||
3. On patient record screen, click **Appointment For OPD** and assign a doctor to generate the OPD slip.
|
||||
4. Care context `OPD-{patientId}-S{opdId}-{YYYYMMDD}` is now live and linkable.
|
||||
|
||||
### Step 2: Test HIP-Initiated Linking (Method 4 Demographic Auth)
|
||||
1. In patient profile or **ABHA Management**, click **Link Records to ABHA (HIP)** (or click **Link Context** on any Task Board card).
|
||||
2. Enter the patient's ABHA address (e.g., `user@sbx`).
|
||||
3. Patient demographics (Full Name, Gender, YOB) are pre-filled/verified.
|
||||
4. Select the care contexts (OPD consultation, invoice, lab/radiology, discharge summary).
|
||||
5. Click **Link Selected Records (Demographic Auth)**.
|
||||
6. The gateway returns a JWT Link Token and links the care context to the ABHA account.
|
||||
|
||||
### Step 3: Test Deep Link SMS (`sms/notify2`)
|
||||
1. On the same modal, switch to **Deep Link SMS (sms/notify2)** tab.
|
||||
2. Confirm the 10-digit mobile number and click **Send Deep Link SMS**.
|
||||
3. ABDM dispatches an SMS with an app deep link to the patient's mobile device.
|
||||
|
||||
### Step 4: Test User-Initiated Discovery & Linking (from ABHA PHR App)
|
||||
1. Open the ABDM Sandbox ABHA / PHR app on a smartphone.
|
||||
2. Search for the facility: **Dev Softtech** (Facility ID: `IN0510000871`).
|
||||
3. Search by patient mobile number or ABHA address.
|
||||
4. Select the discovered care contexts and request OTP.
|
||||
5. Verify OTP to confirm the linking.
|
||||
|
||||
### Step 5: Test Health Record View (FHIR Bundles in PHR App)
|
||||
1. Doctor submits clinical notes/prescription in **OPD Prescription** editor (or saves findings in **Radiology Report Editor**, or prints/generates an **Invoice**).
|
||||
2. In the PHR app, tap on the linked hospital and tap **Fetch Records** / **Pull Records**.
|
||||
3. The HMS generates and transfers the encrypted FHIR bundle (`OPConsultRecord`, `DiagnosticReportRecord`, or `InvoiceRecord`).
|
||||
4. Patient views findings, medications, and invoices directly in the PHR app.
|
||||
|
||||
---
|
||||
|
||||
## 4. Test Suite & Verification Results
|
||||
|
||||
```bash
|
||||
# Full PHPUnit Suite Execution
|
||||
php vendor/phpunit/phpunit/phpunit --no-coverage
|
||||
|
||||
# Result
|
||||
OK (57 tests, 495 assertions)
|
||||
Tests: 57, Assertions: 495, Failures: 0. 100% PASS.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployment Commands Reference
|
||||
|
||||
```powershell
|
||||
# Windows deployment to remote server (10.8.0.1)
|
||||
& "C:\Program Files\PuTTY\plink.exe" -ssh -batch -pw "bishT@9720958717H" root@10.8.0.1 "cd /var/www/html/hms_etria && git pull origin main && php spark cache:clear"
|
||||
```
|
||||
|
|
@ -1,396 +0,0 @@
|
|||
# 🎯 ABDM Bridge Integration - Master Documentation Index
|
||||
|
||||
## 📊 Analysis Complete: LOG ID 940 + Card Download Implementation
|
||||
|
||||
### Executive Summary
|
||||
✅ **ABHA Card Download is FULLY IMPLEMENTED and WORKING**
|
||||
|
||||
HMS has successfully implemented the ABDM Bridge v3 specification for downloading and displaying official ABHA cards. This documentation validates the implementation against the bridge specification.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Files
|
||||
|
||||
### Log ID 940 Analysis
|
||||
1. **[ABDM_LOG_940_README.md](ABDM_LOG_940_README.md)** - **START HERE**
|
||||
- Overview and document index
|
||||
- Key findings checklist
|
||||
- Implementation status
|
||||
- Quick reference guide
|
||||
- Related documentation links
|
||||
|
||||
2. **[ABDM_LOG_940_QUICK_REFERENCE.md](ABDM_LOG_940_QUICK_REFERENCE.md)** - Developer Quick Lookup
|
||||
- Request/response summary
|
||||
- Card data fields table
|
||||
- Key findings
|
||||
- Card rendering instructions
|
||||
- Mapped HMS response format
|
||||
|
||||
3. **[ABDM_LOG_940_COMPLETE_REPORT.md](ABDM_LOG_940_COMPLETE_REPORT.md)** - Full Technical Report
|
||||
- Complete implementation details
|
||||
- Server-side extraction code
|
||||
- Client-side display code
|
||||
- Data flow diagrams
|
||||
- Testing instructions
|
||||
- Bridge message validation
|
||||
|
||||
4. **[ABDM_LOG_940_ANALYSIS.md](ABDM_LOG_940_ANALYSIS.md)** - Formal Analysis Document
|
||||
- Executive summary
|
||||
- Log details and response info
|
||||
- ABHA account data
|
||||
- Card data validation
|
||||
- Bridge message validation
|
||||
- Recommendations
|
||||
|
||||
### Card Download Implementation
|
||||
5. **[ABDM_CARD_DOWNLOAD_IMPLEMENTATION.md](ABDM_CARD_DOWNLOAD_IMPLEMENTATION.md)** - Implementation Guide
|
||||
- Gateway endpoint call details
|
||||
- Card download data flow
|
||||
- Card field extraction strategy
|
||||
- Card display views
|
||||
- Request/response examples
|
||||
- Authentication flow
|
||||
- User flows
|
||||
|
||||
6. **[ABDM_CARD_DOWNLOAD_VERIFICATION.md](ABDM_CARD_DOWNLOAD_VERIFICATION.md)** - Verification Report
|
||||
- Bridge spec vs HMS implementation comparison
|
||||
- Implementation locations with file references
|
||||
- Complete user journey
|
||||
- Verification matrix
|
||||
- Test cases and evidence
|
||||
- Compliance summary
|
||||
- Final verdict
|
||||
|
||||
---
|
||||
|
||||
## ✅ What Was Verified
|
||||
|
||||
### Log 940 - ABHA Login Verify Response
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Endpoint | `https://abdm-bridge.e-atria.in/api/v3/abha/login/verify-otp` |
|
||||
| Status | 200 OK ✅ |
|
||||
| Response | Includes `official_card`, `card_data`, `abhaCard` |
|
||||
| Format | Base64-encoded PNG |
|
||||
| ABHA Subject | Devender Singh (91-5101-6530-5101) |
|
||||
| Timestamp | 2026-08-14 00:58:53 |
|
||||
|
||||
### Gateway Card Download Endpoint
|
||||
| Aspect | Status |
|
||||
|--------|--------|
|
||||
| Endpoint call | ✅ `/v3/abha/card` implemented |
|
||||
| Parameters | ✅ `abha_number`, `abha_address` sent |
|
||||
| Authentication | ✅ Bearer token + X-Token header |
|
||||
| Card extraction | ✅ 8 field name variants handled |
|
||||
| Response format | ✅ Data URL conversion implemented |
|
||||
| Client display | ✅ Modal + print view ready |
|
||||
| Download | ✅ Browser download enabled |
|
||||
| Storage | ✅ Database persistence working |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Findings
|
||||
|
||||
### Finding 1: Card Data in Bridge Response ✅
|
||||
Bridge returns official ABHA card in multiple fields:
|
||||
- `official_card` (Base64 PNG)
|
||||
- `card_data` (Base64 PNG)
|
||||
- `abhaCard` (Base64 PNG)
|
||||
- `card_format` ("png")
|
||||
|
||||
### Finding 2: HMS Extracts Automatically ✅
|
||||
EAtriaBridgeConnector handles:
|
||||
- Multiple field names
|
||||
- Nested structures
|
||||
- Data URL conversion
|
||||
- Content-type detection
|
||||
- Source tracking (official vs provisional)
|
||||
|
||||
### Finding 3: Card Displays Everywhere ✅
|
||||
Implemented in modals:
|
||||
- `abha_verify_modal.php` - Verification flow
|
||||
- `abha_create_modal.php` - Creation flow
|
||||
- `abha_mobile_modal.php` - Mobile OTP flow
|
||||
- `abha_patient_match_modal.php` - Patient matching
|
||||
|
||||
### Finding 4: Card View Ready for Printing ✅
|
||||
Dedicated view:
|
||||
- `app/Views/abha/card.php`
|
||||
- Shows official card image
|
||||
- Includes patient HMS ID
|
||||
- Shows QR code
|
||||
- Print-ready layout
|
||||
|
||||
### Finding 5: Bridge Message Validated ✅
|
||||
Bridge states: "HMS integration can now seamlessly consume `official_card` / `card_data_uri` across all verification routes."
|
||||
|
||||
**Result**: CONFIRMED - Both work as documented
|
||||
|
||||
---
|
||||
|
||||
## 📍 File Locations Reference
|
||||
|
||||
### Controllers
|
||||
```
|
||||
app/Controllers/Abha.php
|
||||
├── Line 509-600: card() → Card display view
|
||||
├── Line 139: verifyOtp() → Extract and return card
|
||||
└── Line 325: verifyCommOtp() → Extract and return card
|
||||
```
|
||||
|
||||
### Bridge Connector
|
||||
```
|
||||
app/Libraries/Abdm/EAtriaBridgeConnector.php
|
||||
├── Line 744: GET /v3/abha/card call
|
||||
├── Line 748-750: Parameters (abha_number, abha_address)
|
||||
├── Line 751-762: Response parsing and extraction
|
||||
└── Line 810-823: extractOfficialCard() → Handle 8 field names
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```
|
||||
app/Views/partials/
|
||||
├── abha_verify_modal.php (Line 90, 224-237) → Card + download
|
||||
├── abha_create_modal.php (Line 190, 420-431) → Card + download
|
||||
├── abha_mobile_modal.php (Line 101, 230-240) → Card + download
|
||||
└── abha_patient_match_modal.php (Line 233) → Card reference
|
||||
|
||||
app/Views/abha/
|
||||
└── card.php (Line 246-280) → Card display/print view
|
||||
```
|
||||
|
||||
### Database
|
||||
```
|
||||
app/Database/Migrations/
|
||||
└── 2026-08-13-000001_AddAbhaCardToPatientMaster.php
|
||||
└── Column: patient_master.abha_card_base64
|
||||
|
||||
app/Config/
|
||||
└── Routes.php (Line 777) → GET abha/card/{abha_number}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 User Flows Implemented
|
||||
|
||||
### Flow 1: ABHA Creation with Card
|
||||
```
|
||||
1. User starts ABHA creation
|
||||
2. Selects OTP verification method
|
||||
3. Verifies OTP
|
||||
4. Bridge returns card + profile
|
||||
5. Modal displays:
|
||||
✅ Profile photo
|
||||
✅ Official ABHA card image
|
||||
✅ Verification details
|
||||
6. User clicks "Download ABHA Card"
|
||||
7. Browser saves PNG file
|
||||
```
|
||||
|
||||
### Flow 2: View Patient Card
|
||||
```
|
||||
1. Open patient profile
|
||||
2. Click "View/Print ABHA Card"
|
||||
3. Opens: /abha/card/{abha_number}
|
||||
4. View displays:
|
||||
✅ Official ABHA card image
|
||||
✅ Patient HMS ID with barcode
|
||||
✅ QR code
|
||||
✅ Hospital branding
|
||||
5. User prints or saves as PDF
|
||||
```
|
||||
|
||||
### Flow 3: Explicit Card Download
|
||||
```
|
||||
1. Call API: GET /abha/card/{abha_number}
|
||||
2. HMS retrieves stored card
|
||||
3. Renders card view
|
||||
4. User downloads/prints
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Implementation Insights
|
||||
|
||||
### Smart Card Fetching
|
||||
- Card returned in verify response? → Use immediately
|
||||
- Card missing? → Call `/v3/abha/card` endpoint
|
||||
- Multiple field names? → Try all 8 variants
|
||||
- No data prefix? → Add `data:image/png;base64,` automatically
|
||||
|
||||
### Flexible Field Handling
|
||||
Bridge may return card under:
|
||||
- `official_card` (most common)
|
||||
- `card_data_uri` (alternative naming)
|
||||
- `card_base64` (explicit Base64)
|
||||
- `card_data` (generic field)
|
||||
- `abhaCard` (camelCase variant)
|
||||
- `abha_card` (snake_case variant)
|
||||
- `cardData` (alternative camelCase)
|
||||
- `card` (generic short name)
|
||||
|
||||
HMS handles all variants automatically.
|
||||
|
||||
### User Experience
|
||||
- Card displays immediately if in response
|
||||
- Falls back to gateway call if needed
|
||||
- Download link always available when card present
|
||||
- Helpful fallback message when card unavailable
|
||||
- Visual indicator for provisional vs official cards
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security & Compliance
|
||||
|
||||
### Authentication
|
||||
- ✅ Hospital API key in Authorization header
|
||||
- ✅ Patient X-Token in X-Token header when available
|
||||
- ✅ Secure card transmission via HTTPS
|
||||
- ✅ Card stored securely in database
|
||||
|
||||
### Data Protection
|
||||
- ✅ Card data is Base64 (not encrypted, but safe for image)
|
||||
- ✅ PII redacted in logs
|
||||
- ✅ Card stored locally to avoid repeated API calls
|
||||
- ✅ Access controlled via permission system
|
||||
|
||||
### Compliance
|
||||
- ✅ ABDM Bridge v3 specification compliant
|
||||
- ✅ Industry-standard image encoding (Base64 PNG)
|
||||
- ✅ Standard data URL format
|
||||
- ✅ Proper error handling
|
||||
|
||||
---
|
||||
|
||||
## ✨ Quality Indicators
|
||||
|
||||
| Indicator | Status |
|
||||
|-----------|--------|
|
||||
| Code coverage | ✅ Multiple modals implemented |
|
||||
| Error handling | ✅ Fallback messages present |
|
||||
| Performance | ✅ Caching in database |
|
||||
| UX | ✅ Download buttons, display indicators |
|
||||
| Documentation | ✅ Code comments, view labels |
|
||||
| Testing | ✅ Multiple data variants tested |
|
||||
| Logging | ✅ API calls logged in abdm_api_logs |
|
||||
|
||||
---
|
||||
|
||||
## 📖 How to Use These Docs
|
||||
|
||||
### For Quick Overview
|
||||
👉 Start with **ABDM_LOG_940_README.md**
|
||||
- Get summary of findings
|
||||
- Understand key components
|
||||
- See file references
|
||||
|
||||
### For Implementation Details
|
||||
👉 Read **ABDM_CARD_DOWNLOAD_IMPLEMENTATION.md**
|
||||
- Understand code flow
|
||||
- See request/response examples
|
||||
- Learn authentication
|
||||
|
||||
### For Quick Lookup
|
||||
👉 Use **ABDM_LOG_940_QUICK_REFERENCE.md**
|
||||
- Copy-paste code samples
|
||||
- Quick field reference
|
||||
- Rendering instructions
|
||||
|
||||
### For Complete Analysis
|
||||
👉 Read **ABDM_LOG_940_COMPLETE_REPORT.md**
|
||||
- Full technical details
|
||||
- Data flow diagrams
|
||||
- Bridge message validation
|
||||
|
||||
### For Verification
|
||||
👉 Check **ABDM_CARD_DOWNLOAD_VERIFICATION.md**
|
||||
- Compliance matrix
|
||||
- Test cases
|
||||
- Evidence from Log 940
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Key Takeaways
|
||||
|
||||
1. ✅ **Bridge works** - Log 940 proves card is in response
|
||||
2. ✅ **HMS integrates** - Card extraction code is present
|
||||
3. ✅ **Display ready** - Modals show card with download
|
||||
4. ✅ **Print ready** - Dedicated view for printing
|
||||
5. ✅ **Stored safely** - Database persistence implemented
|
||||
6. ✅ **User friendly** - Multiple modals, helpful UI
|
||||
7. ✅ **Fully compliant** - Matches bridge specification
|
||||
|
||||
---
|
||||
|
||||
## 📊 Statistics
|
||||
|
||||
### Code Implementation
|
||||
- **5 Controllers/Connectors** using card data
|
||||
- **4 Modal views** displaying cards
|
||||
- **1 Print view** for card rendering
|
||||
- **8 Card field name variants** supported
|
||||
- **1 Database column** for persistence
|
||||
- **1 Migration** for database setup
|
||||
|
||||
### Documentation
|
||||
- **6 Analysis documents** created
|
||||
- **2 Implementation reports** generated
|
||||
- **50+ code references** with line numbers
|
||||
- **3 Data flow diagrams** provided
|
||||
- **4+ request/response examples** documented
|
||||
|
||||
### Verification Coverage
|
||||
- ✅ Bridge endpoint working
|
||||
- ✅ Database storage verified
|
||||
- ✅ Client display tested
|
||||
- ✅ Download functionality confirmed
|
||||
- ✅ Multiple field names validated
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Next Steps
|
||||
|
||||
### For Users
|
||||
1. Open patient ABHA profile
|
||||
2. Click "View/Print ABHA Card"
|
||||
3. Download card when needed
|
||||
4. Print for records
|
||||
|
||||
### For Developers
|
||||
1. Review code in `app/Controllers/Abha.php`
|
||||
2. Check `EAtriaBridgeConnector` for bridge calls
|
||||
3. Test modals in browser
|
||||
4. Verify database storage
|
||||
|
||||
### For Maintenance
|
||||
1. Monitor `abdm_api_logs` for gateway calls
|
||||
2. Verify card quality in patient records
|
||||
3. Check for missing cards
|
||||
4. Validate download links
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Conclusion
|
||||
|
||||
**Status**: ✅ **COMPLETE AND VERIFIED**
|
||||
|
||||
HMS successfully implements the ABDM Bridge v3 card download specification. All components are working, tested, and documented.
|
||||
|
||||
**No gaps found. Ready for production.**
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For questions about:
|
||||
- **Bridge integration**: See ABDM_CARD_DOWNLOAD_IMPLEMENTATION.md
|
||||
- **Card display**: Check specific modal in app/Views/partials/
|
||||
- **Data flow**: Review ABDM_LOG_940_COMPLETE_REPORT.md
|
||||
- **Code locations**: Use file references in ABDM_CARD_DOWNLOAD_VERIFICATION.md
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-08-14
|
||||
**Bridge Version**: e-Atria Bridge v3
|
||||
**HMS Version**: CI4
|
||||
**Verification Status**: COMPLETE ✅
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
# Session Handover: Unified OPD Consultation Bundle & Single Care Context Integration
|
||||
|
||||
**Date:** October 6, 2026
|
||||
**Status:** ✅ Complete, Deployed & Verified on Live Environment
|
||||
**Target Repository:** `HMS_CI4_OLD` (`origin/main`) & `HMS-ABDM-Gateway` (`origin/AG_version`)
|
||||
**Live Server:** `hms-live` (`root@hms-live`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement & User Requests
|
||||
|
||||
1. **Default OTP Customization**:
|
||||
- The user requested updating the default sandbox OTP for ABDM care context linking to `654321` while preserving backward compatibility.
|
||||
2. **Duplicate Records in PHR App (Image 5)**:
|
||||
- Previously, a single OPD consultation visit generated 3 distinct records/care contexts:
|
||||
- `OPD-15353-S33727-20261006`
|
||||
- `PRESC-15353-S33727-20261006`
|
||||
- `WELLNESS-33727-20261006`
|
||||
- In the PHR application (e.g. ABHA App), this caused duplicate entries where `OPConsultation` and `WellnessRecord` appeared separately for the exact same visit.
|
||||
3. **Mislabeled Invoice Records (Images 2 & 3)**:
|
||||
- Older records for OPD invoices showed both `Prescription` and `Invoice` because historical rows had `hi_type = 'OPConsultRecord'` or lacked strict HI-type separation.
|
||||
4. **Unified NRCES Bundle**:
|
||||
- The user requested consolidating into a **single unified bundle & care context** for the OPD visit.
|
||||
- The care context display must dynamically reflect the clinical contents:
|
||||
`OPD Consult, Wellness, Prescription, HealthDocument` (including vitals, prescribed medicines, and scanned/uploaded files).
|
||||
5. **Auto-Generate / Update on Print**:
|
||||
- Automatically compile or update the FHIR record whenever the consultation form is saved or printed in HMS.
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Implementation Details
|
||||
|
||||
### A. NRCES FHIR R4 Specification Conformance
|
||||
- **Profile**: `https://nrces.in/ndhm/fhir/r4/StructureDefinition/OPConsultRecord`
|
||||
- Following the ABDM standard, an `OPConsultRecord` Document Bundle natively contains all clinical sections:
|
||||
1. **Chief Complaints**: `Condition` / complaint entries.
|
||||
2. **Physical Examination / Vitals**: `Observation` resources for Blood Pressure, Pulse, Temperature, $\text{SpO}_2$, Weight, and Height.
|
||||
3. **Problems & Diagnoses**: `Condition` resources with SNOMED CT and ICD-10 codings.
|
||||
4. **Medications / Prescriptions**: `MedicationRequest` resources with dose, frequency, and instructions.
|
||||
5. **Investigation Advice**: `ServiceRequest` for recommended tests/imaging.
|
||||
6. **Follow-Up Appointment**: `Appointment` resource for next visit date.
|
||||
7. **Document References**: `Binary` / `DocumentReference` for uploaded scans and generated prescription PDFs.
|
||||
- **Bundle Meta Hardening**:
|
||||
- Added required `'versionId' => '1'` and `'lastUpdated' => $issuedAt` across all builder methods in `app/Libraries/FhirR4Builder.php`.
|
||||
- Validated using NRCES FHIR validator (`validate_fhir` tool): 0 structural errors.
|
||||
|
||||
### B. Single Care Context & Cleanup of Redundant Records
|
||||
- **File**: `app/Controllers/Opd_prescription.php`
|
||||
- In `storePrescriptionFhirBundle()`, unified generation to compile only the single `OPConsultRecord`.
|
||||
- Removed generation of separate `PRESC-...` and `WELLNESS-...` records.
|
||||
- Automatically cleans up redundant standalone entries (`PRESC-...`, `WELLNESS-...`, and old alias patterns) from `health_records` and `opd_fhir_documents`.
|
||||
- In `resolveRecentFhirPushStatus()`, relaxed session reference search to `'-S' . $sessionId . '-'`.
|
||||
|
||||
### C. Clinical Component Labeling & Alias Deduplication
|
||||
- **File**: `app/Controllers/AbdmGateway.php`
|
||||
- In `findCareContextsForPatient()`:
|
||||
- Automatically builds dynamic component labels: `OPD Consult, Wellness, Prescription - Dr. Name - Date` (dynamically appends `, HealthDocument` if external reports or scans exist).
|
||||
- Length guarded to 85 characters to ensure clean display across all PHR apps.
|
||||
- Registers all aliases in `$seenRefs` (`OPD-{patientId}-S{sessionId}-{cleanDate}`, `OPD-{opdId}-S{sessionId}-{visitDate}`, etc.).
|
||||
- Tracks `$seenOpdSessions`: step 6c fallback suppresses any orphaned or legacy alias references matching an already processed OPD session.
|
||||
|
||||
### D. ABDM Bridge Gateway HI-Type Resolution Fix
|
||||
- **File**: `/var/www/html/abdm-bridge-gateway/app/Controllers/AbdmGateway.php` on `hms-live`
|
||||
- Modified `resolveAbdmHiTypeFromContext()` to apply **strict prefix checking** (`INVOICE-` $\rightarrow$ `Invoice`, `OPD-` $\rightarrow$ `OPConsultation`, `WELLNESS-` $\rightarrow$ `WellnessRecord`, etc.) before loose keyword searching on the display string.
|
||||
- This prevents composite titles like `"OPD Consult, Wellness"` from being misclassified into `WellnessRecord`.
|
||||
|
||||
### E. Task Board Synchronization Hardening
|
||||
- **File**: `app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php`
|
||||
- In `queryEligibleOpdVisits()`: sorted `opd_fhir_documents` to prioritize `OPConsultRecord` over legacy `PrescriptionRecord`.
|
||||
- Standardized care context reference derivation to canonical `OPD-{patientId}-S{sessionId}-{cleanDate}`.
|
||||
- In `linkAndPushOpdRecord()`: checks existing record by `care_context_reference` before inserting to prevent duplicate queued rows.
|
||||
- In `syncSingleWorkTask()`: applies canonical OPD reference format and `OPConsultRecord` prioritization.
|
||||
|
||||
### F. Auto-Generation on Consultation Print & PDF
|
||||
- **File**: `app/Controllers/Opd.php`
|
||||
- Added auto-generation / updating hook inside `opd_lettre_print()` and `opd_lettre_pdf()`.
|
||||
- When the consultation letter is printed or downloaded, `storePrescriptionFhirBundle()` is invoked in the background, updating `opd_fhir_documents` and `health_records`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Live Verification & Database Status
|
||||
|
||||
### Target Test Patient:
|
||||
- **Patient ID**: `15353` (`P26081015353`)
|
||||
- **ABHA Address**: `91310013085603@sbx`
|
||||
- **OPD ID**: `33793`
|
||||
- **Session ID**: `33727`
|
||||
|
||||
### Care Context Output:
|
||||
```text
|
||||
V3 Care Contexts Count: 2
|
||||
[0] ref=OPD-15353-S33727-20261006
|
||||
display=OPD Consult, Wellness, Prescription - Dr. Jayashri Joshi - 06 Oct 2026
|
||||
hiType=OPConsultation
|
||||
[1] ref=INVOICE-OPD-33793-2026-10-06
|
||||
display=Invoice D26100033793 (OPD - Rs 1,000.00)
|
||||
hiType=Invoice
|
||||
```
|
||||
|
||||
### Database `health_records` State on `hms-live`:
|
||||
| id | patient_id | hi_type | care_context_reference | push_status |
|
||||
|---|---|---|---|---|
|
||||
| 243 | 15353 | `InvoiceRecord` | `INVOICE-OPD-33793-2026-10-06` | `linked` |
|
||||
| 244 | 15353 | `OPConsultRecord` | `OPD-15353-S33727-20261006` | `local_discovery_ready` |
|
||||
|
||||
*(Legacy duplicate queued rows 247, 248, 249 and stale PrescriptionRecord document 123 were deleted).*
|
||||
|
||||
### Print Hook Verification:
|
||||
- Executing `opd_lettre_print(33727)` completes cleanly and updates `health_records` row `244` with latest timestamp.
|
||||
|
||||
---
|
||||
|
||||
## 4. Git Commits & Push Summary
|
||||
|
||||
### `HMS_CI4_OLD` (`main` branch):
|
||||
- `04eff5e`: Standardize OPD care context formatting and prevent duplicate session aliases
|
||||
- `b0a0811`: fix(fhir): add versionId and lastUpdated to DocumentBundle meta for full NRCES conformance
|
||||
- `e2bb0b1`: fix(abdm): enrich OPD and INVOICE care contexts with clinical components and bill summary
|
||||
- `f367603`: fix(abdm): unify consult visit into single OPConsultRecord bundle and auto-sync on print
|
||||
- `d46f222`: fix(abdm): set default sandbox linking OTP to 654321 and accept both 654321 and 123456
|
||||
|
||||
### `abdm-bridge-gateway` (`AG_version` branch on `hms-live`):
|
||||
- `eff0fe6`: fix(abdm): strict prefix precedence for hiType resolution to prevent multi-component display hijacking
|
||||
- Pushed to `https://github.com/infodevsofttech-admin/HMS-ABDM-Gateway.git`
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
# ABDM Integration Summary & Action Plan (May 13, 2026)
|
||||
|
||||
## Clarification Received
|
||||
|
||||
The user corrected a critical misunderstanding:
|
||||
|
||||
### ✅ What Was Clarified
|
||||
|
||||
There are **TWO separate, equally important bridges** in the HMS ABDM architecture:
|
||||
|
||||
**1. CSNOtk Bridge** (`https://csnotk.e-atria.in/api/bridge`)
|
||||
- **Primary Purpose:** Terminology validation & FHIR formatting
|
||||
- **Function:** Receives medical terms (fever, cough, etc.) → validates against SNOMED CT → returns FHIR-coded data
|
||||
- **Use Case:** When HMS needs to ensure medical terminology is standardized per SNOMED CT
|
||||
- **Event Types:** `snomed.diagnosis.validate`, `snomed.medication.validate`
|
||||
- **Data Source:** SnomedCT_InternationalRF2_PRODUCTION_20260501T120000Z (SNOMED CT International Release Files)
|
||||
- **Documentation:** `CSNOtk_API_v9.0/` folder + `docs/CSNOTK_API_REFERENCE.md`
|
||||
|
||||
**2. ABDM Bridge** (`https://abdm-bridge.e-atria.in/api/v1/bridge`)
|
||||
- **Primary Purpose:** ABDM M1 communication (patient health record sharing)
|
||||
- **Function:** Centralized gateway for multiple HMS installations to submit/retrieve data from ABDM Live/Sandbox
|
||||
- **Use Case:** When HMS needs to share patient records with ABDM (e.g., OPD prescriptions, lab reports, consent management)
|
||||
- **Event Types:** `abdm.abha.validate`, `abdm.fhir.share.requested`, `abdm.consent.requested`, etc.
|
||||
- **Architecture:** Multiple HMS instances (deployed at client sites) → Single ABDM Bridge → ABDM M1 servers
|
||||
- **Benefit:** Centralized token management, compliance, audit trail
|
||||
|
||||
---
|
||||
|
||||
## What Was WRONG (and Corrected)
|
||||
|
||||
### ❌ Previous Understanding
|
||||
- Assumed we should replace CSNOtk with ABDM Bridge
|
||||
- Thought both bridges did the same thing (ABDM communication)
|
||||
|
||||
### ✅ Corrected Understanding
|
||||
- **Both bridges must coexist**
|
||||
- They serve fundamentally different purposes
|
||||
- Event routing logic needed to distinguish them
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Documentation Created
|
||||
- ✅ `DUAL_BRIDGE_ARCHITECTURE.md` - Full architecture explanation
|
||||
- ✅ `ABDM_GATEWAY_PHP_DEPLOYMENT.md` - PHP gateway deployment steps
|
||||
- ✅ `BRIDGE_SYNC_SERVICE_IMPLEMENTATION.md` - Code implementation guide
|
||||
- ✅ `.copilot-instructions` - System prompt (auto-loaded for workspace)
|
||||
|
||||
### Configuration Status
|
||||
```env
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge # ✅ CORRECT (keep as is)
|
||||
BRIDGE_SYNC_TOKEN = # ⏳ Needs value
|
||||
ABDM_BRIDGE_URL = https://abdm-bridge.e-atria.in/api/v1/bridge # ⏳ Needs to be added
|
||||
ABDM_BRIDGE_TOKEN = # ⏳ Needs value
|
||||
BRIDGE_SOURCE_CODE = SBXID_033661 # ✅ CORRECT
|
||||
```
|
||||
|
||||
### Code Status
|
||||
- ✅ CsnotkTerminologyService exists (direct REST API integration for autocomplete)
|
||||
- ✅ BridgeSyncService exists (core queue processing)
|
||||
- ❌ BridgeSyncService routing logic NOT YET updated for dual-bridge support
|
||||
- **Need:** Conditional routing by event_type prefix (snomed.* vs abdm.*)
|
||||
|
||||
---
|
||||
|
||||
## Immediate Action Items (In Order)
|
||||
|
||||
### Phase 1: Configuration (No Code Changes)
|
||||
|
||||
**Task 1.1:** Generate TWO bearer tokens
|
||||
```bash
|
||||
# Token for CSNOtk Bridge
|
||||
openssl rand -hex 32
|
||||
# Result: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
|
||||
# Token for ABDM Bridge
|
||||
openssl rand -hex 32
|
||||
# Result: f2e1d0c9b8a7z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1
|
||||
```
|
||||
|
||||
**Task 1.2:** Update HMS `.env`
|
||||
```env
|
||||
# Keep existing (no change)
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
|
||||
BRIDGE_SYNC_TOKEN = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
|
||||
# Add new config keys
|
||||
ABDM_BRIDGE_URL = https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
ABDM_BRIDGE_TOKEN = f2e1d0c9b8a7z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Code Implementation
|
||||
|
||||
**Task 2.1:** Update `app/Libraries/BridgeSyncService.php`
|
||||
- Modify `buildDispatchContext()` to route by event_type prefix
|
||||
- Rename `buildBridgeDispatchContext()` → `buildCsnotkDispatchContext()`
|
||||
- Add new `buildAbdmDispatchContext()` method
|
||||
- Reference: `BRIDGE_SYNC_SERVICE_IMPLEMENTATION.md`
|
||||
|
||||
**Task 2.2:** Update `app/Config/AbdmConnector.php`
|
||||
- Add two new config keys for ABDM bridge:
|
||||
```php
|
||||
public string $abdmBridgeUrl = 'https://abdm-bridge.e-atria.in/api/v1/bridge';
|
||||
public string $abdmBridgeToken = '';
|
||||
```
|
||||
|
||||
**Task 2.3:** Testing & Validation
|
||||
- Queue test `snomed.diagnosis.validate` event
|
||||
- Queue test `abdm.abha.validate` event
|
||||
- Verify they route to correct endpoints
|
||||
- Check `abdm_api_logs.channel` is 'csnotk' or 'abdm' respectively
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Deployment
|
||||
|
||||
**Task 3.1:** Deploy PHP ABDM Gateway
|
||||
- Copy `gateway-php-ci4/` to server `/opt/abdm-gateway`
|
||||
- Run composer install
|
||||
- Configure `.env` with:
|
||||
- MySQL credentials
|
||||
- `GATEWAY_BEARER_TOKEN` = HMS `ABDM_BRIDGE_TOKEN`
|
||||
- ABDM M1 credentials
|
||||
- Run migrations
|
||||
- Configure Apache/SSL
|
||||
- Reference: `ABDM_GATEWAY_PHP_DEPLOYMENT.md`
|
||||
|
||||
**Task 3.2:** Test End-to-End
|
||||
```bash
|
||||
cd /opt/hms-ci4
|
||||
php spark bridge:sync --limit 10
|
||||
|
||||
# Should show both CSNOTK and ABDM events processed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deliverables Created (For User Reference)
|
||||
|
||||
### Documentation Files
|
||||
1. **`DUAL_BRIDGE_ARCHITECTURE.md`**
|
||||
- Complete architecture overview
|
||||
- Event routing table
|
||||
- Configuration requirements
|
||||
- CSNOtk vs ABDM contract details
|
||||
- Database schema implications
|
||||
|
||||
2. **`BRIDGE_SYNC_SERVICE_IMPLEMENTATION.md`**
|
||||
- Exact code changes needed (with line numbers)
|
||||
- Step-by-step implementation guide
|
||||
- Testing procedures
|
||||
- Code review checklist
|
||||
|
||||
3. **`ABDM_GATEWAY_PHP_DEPLOYMENT.md`**
|
||||
- 10-step server deployment
|
||||
- Configuration template
|
||||
- Health check & smoke tests
|
||||
- Troubleshooting guide
|
||||
- Rollback procedures
|
||||
|
||||
4. **`.copilot-instructions`**
|
||||
- System prompt loaded automatically for this workspace
|
||||
- Captures critical architecture requirements
|
||||
- Prevents future confusion
|
||||
|
||||
### Memory Files
|
||||
- **`/memories/session/php-gateway-architecture.md`** - Session working notes (auto-populated)
|
||||
|
||||
---
|
||||
|
||||
## FAQ (From User Clarifications)
|
||||
|
||||
**Q: Why are there TWO bridges?**
|
||||
A: CSNOtk handles terminology (SNOMED CT coding), ABDM handles patient data sharing. They're separate concerns.
|
||||
|
||||
**Q: What's the advantage of centralized ABDM Bridge?**
|
||||
A: Multiple HMS instances at different client sites all connect through one ABDM Bridge for:
|
||||
- Centralized token management
|
||||
- Audit trail consolidation
|
||||
- Simplified client deployment
|
||||
|
||||
**Q: Can CSNOtk events bypass the bridge?**
|
||||
A: Yes, CsnotkTerminologyService calls CSNOtk REST API directly (`/rest/search/suggest`) for autocomplete. The bridge is used for formal event processing.
|
||||
|
||||
**Q: Focus on M1 first?**
|
||||
A: Yes, ABDM M1 is the priority. M2 (privacy) and M3 (interoperability) can be added later.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Generate bearer tokens** (Task 1.1)
|
||||
2. **Update HMS .env** (Task 1.2)
|
||||
3. **Implement BridgeSyncService routing** (Task 2.1-2.3)
|
||||
4. **Test both event types** (Task 2.3)
|
||||
5. **Deploy PHP gateway** (Task 3.1)
|
||||
6. **Run end-to-end test** (Task 3.2)
|
||||
|
||||
---
|
||||
|
||||
## Questions for User (Before Implementation)
|
||||
|
||||
1. Should we prioritize CSNOtk bridge testing first, or ABDM bridge?
|
||||
2. Are there existing test events in bridge_sync_queue to use for routing validation?
|
||||
3. Should legacy event types (no prefix) still route to CSNOtk as fallback?
|
||||
4. Is the PHP gateway already accessible at https://abdm-bridge.e-atria.in, or does it need to be deployed?
|
||||
|
||||
---
|
||||
|
||||
## Architecture Diagram (ASCII)
|
||||
|
||||
```
|
||||
HMS CI4 (Main Application)
|
||||
|
|
||||
BridgeSyncService.processPending()
|
||||
(Routes by event_type)
|
||||
|
|
||||
___________|___________
|
||||
| |
|
||||
snomed.* events abdm.* events
|
||||
| |
|
||||
[CSNOtk Bridge] [ABDM Bridge]
|
||||
v v
|
||||
/api/bridge /api/v1/bridge
|
||||
(CSNOtk Terminology) (PHP Gateway at e-atria.in)
|
||||
v v
|
||||
SNOMED CT Database (/api/v3/* handlers)
|
||||
(Validate & Code) v
|
||||
v ABDM M1 Server
|
||||
Return SNOMED-coded (Live/Sandbox)
|
||||
FHIR Format
|
||||
| |
|
||||
└─────────────────────────┘
|
||||
|
|
||||
[Patient Health Data]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Status:** Architecture clarified, documentation created, awaiting implementation.
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
# Bed Management Fix Summary - bed_master System
|
||||
|
||||
## System Overview
|
||||
|
||||
Your hospital management system uses the **NEW bed_master table** for bed management (not the legacy hc_bed_master).
|
||||
|
||||
### Database Schema
|
||||
|
||||
**bed_master table columns:**
|
||||
- `id` - Primary key
|
||||
- `bed_number` - Bed number/name
|
||||
- `bed_code` - Unique bed code
|
||||
- `ward_id` - Reference to ward_master
|
||||
- `bed_status` - Status: 'available', 'occupied', 'maintenance', 'blocked', 'reserved', 'cleaning'
|
||||
- `current_ipd_id` - IPD ID of patient currently occupying this bed (NULL when available)
|
||||
- `status` - Record status: 'active', 'inactive'
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Bed Assignment**: When a patient is admitted, `bed_master.current_ipd_id` is set to the IPD ID, and `bed_status` is set to 'occupied'
|
||||
2. **Bed Release**: When a patient is discharged, `current_ipd_id` is cleared (set to NULL) and `bed_status` is set to 'available'
|
||||
3. **Bed Status Dashboard**: Queries `bed_master` joining with `ipd_master` on `current_ipd_id` to show current occupancy
|
||||
|
||||
## Issues Identified
|
||||
|
||||
### 1. Beds Not Released on Discharge
|
||||
**Problem**: When patients are discharged (ipd_status = 1), beds remain occupied:
|
||||
- `bed_master.bed_status` still shows 'occupied'
|
||||
- `bed_master.current_ipd_id` still has the IPD ID instead of NULL
|
||||
|
||||
**Impact**: Bed status dashboard shows 5 occupied beds but only 1 current admission, causing confusion and preventing bed reuse for new patients.
|
||||
|
||||
### 2. Blank "Bed Assign" Column
|
||||
**Problem**: The Current Admission view may show blank in the "Bed No [Type]" column.
|
||||
|
||||
**Root Cause**: The query looks for beds where `current_ipd_id` matches the IPD, but if discharge didn't clear this field, the join fails.
|
||||
|
||||
## Solutions Implemented
|
||||
|
||||
### Fix 1: Add Bed Release Logic
|
||||
|
||||
**File**: `app/Models/IpdModel.php`
|
||||
|
||||
Added new method `releaseBed(int $ipdId)` that:
|
||||
1. Clears `current_ipd_id` (sets to NULL) in `bed_master` table
|
||||
2. Sets `bed_status = 'available'` in `bed_master` table
|
||||
3. Updates `bed_assignment_history` with release date and reason (if that table is used)
|
||||
4. Also handles legacy `hc_bed_master` for backward compatibility (if it exists)
|
||||
|
||||
### Fix 2: Call Bed Release on Discharge
|
||||
|
||||
**File**: `app/Controllers/Billing/Ipd.php`
|
||||
|
||||
Modified the `updateDischargeStatus()` method to call `releaseBed()` when ipd_status is set to 1.
|
||||
|
||||
### Fix 3: Update Bed Assignment Logic
|
||||
|
||||
**File**: `app/Models/IpdModel.php`
|
||||
|
||||
Updated `bedAssign()` method to:
|
||||
1. Set `bed_master.current_ipd_id` to the IPD ID when assigning
|
||||
2. Set `bed_master.bed_status` to 'occupied'
|
||||
3. Clear any previous bed assigned to the same IPD (in case of bed transfer)
|
||||
|
||||
## Additional Recommendations
|
||||
|
||||
### 1. Run the SQL Fix to Clean Up Existing Data
|
||||
|
||||
For existing IPDs where beds weren't released properly, run the SQL script `fix_bed_management.sql`:
|
||||
|
||||
**Main fix (Step 2):**
|
||||
```sql
|
||||
UPDATE bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
SET
|
||||
bm.bed_status = 'available',
|
||||
bm.current_ipd_id = NULL
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL;
|
||||
```
|
||||
|
||||
This releases all beds for patients who have been discharged.
|
||||
|
||||
### 2. Verify the Fix
|
||||
|
||||
After running the SQL, check the verification queries (Step 4) to confirm:
|
||||
- All discharged patients' beds show as available
|
||||
- Occupied bed count matches current admission count
|
||||
- No beds have status mismatch
|
||||
|
||||
### 3. Monitor Bed Management
|
||||
|
||||
Use these pages to monitor:
|
||||
- `/setting/admin/bed-status` - View current bed occupancy by ward/floor
|
||||
- `/setting/admin/bed-management` - Manage bed master data
|
||||
- `/billing/ipd/current-admission` - View patients with their assigned beds
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
1. **Test Bed Assignment**:
|
||||
- Admit a new patient
|
||||
- Assign a bed
|
||||
- Verify `bed_master.bed_status = 'occupied'`
|
||||
- Verify `bed_master.current_ipd_id` = IPD ID
|
||||
|
||||
2. **Test Discharge**:
|
||||
- Discharge a patient
|
||||
- Verify `bed_master.bed_status = 'available'`
|
||||
- Verify `bed_master.current_ipd_id IS NULL`
|
||||
|
||||
3. **Test Current Admission View**:
|
||||
- Open `/billing/ipd/current-admission`
|
||||
- Verify "Bed No [Type]" column shows bed information
|
||||
- Verify all admitted patients show their bed assignments
|
||||
|
||||
4. **Test Bed Status Panel**:
|
||||
- Open `/setting/admin/bed-status`
|
||||
- Verify Available count is correct
|
||||
- Verify Occupied count matches current admissions
|
||||
- Click on occupied beds to see patient details
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **app/Models/IpdModel.php**
|
||||
- Updated `releaseBed()` method - releases beds on discharge
|
||||
- Updated `bedAssign()` method - sets current_ipd_id when assigning
|
||||
|
||||
2. **app/Controllers/Billing/Ipd.php**
|
||||
- Modified `updateDischargeStatus()` to call bed release
|
||||
|
||||
3. **fix_bed_management.sql**
|
||||
- SQL script to fix existing data (discharged patients' beds)
|
||||
|
||||
4. **BED_MANAGEMENT_FIX_SUMMARY.md**
|
||||
- This documentation file
|
||||
|
||||
## Database Schema Reference
|
||||
|
||||
### Tables Used
|
||||
|
||||
1. **bed_master** (Primary table - NEW system)
|
||||
- `bed_status`: 'available' | 'occupied' | 'maintenance' | 'blocked' | 'reserved' | 'cleaning'
|
||||
- `current_ipd_id`: IPD ID using this bed (NULL = available)
|
||||
- `bed_number`: Bed identifier
|
||||
- `bed_code`: Unique code
|
||||
- `ward_id`: Reference to ward_master
|
||||
- `status`: 'active' | 'inactive'
|
||||
|
||||
2. **bed_assignment_history** (Optional tracking table)
|
||||
- Records all bed assignments with dates
|
||||
- `released_date`: When bed was freed
|
||||
- `release_reason`: Why bed was freed
|
||||
- Provides audit trail for bed usage
|
||||
|
||||
3. **ipd_master** (Patient admission records)
|
||||
- `ipd_status`: 0=admitted, 1=discharged
|
||||
- `register_date`: Admission date
|
||||
- `discharge_date`: Discharge date
|
||||
|
||||
4. **ward_master** (Ward/room information)
|
||||
- `ward_name`: Ward/room name
|
||||
- `floor_number`: Floor level
|
||||
- `ward_type`: Type of ward
|
||||
|
||||
## How Bed Management Works Now
|
||||
|
||||
### Bed Assignment Flow:
|
||||
1. Patient admitted to IPD
|
||||
2. Bed selected from available beds
|
||||
3. `IpdModel::bedAssign()` called:
|
||||
- Creates entry in `bed_assignment_history`
|
||||
- Sets `bed_master.current_ipd_id` = IPD ID
|
||||
- Sets `bed_master.bed_status` = 'occupied'
|
||||
|
||||
### Bed Release Flow:
|
||||
1. Patient discharged (ipd_status = 1)
|
||||
2. `IpdModel::releaseBed()` called automatically:
|
||||
- Sets `bed_master.current_ipd_id` = NULL
|
||||
- Sets `bed_master.bed_status` = 'available'
|
||||
- Updates `bed_assignment_history.released_date`
|
||||
|
||||
### Bed Status Dashboard Query:
|
||||
```sql
|
||||
SELECT b.*, i.ipd_code, i.register_date, p.p_fname
|
||||
FROM bed_master b
|
||||
LEFT JOIN ipd_master i ON i.id = b.current_ipd_id AND i.ipd_status = 0
|
||||
LEFT JOIN patient_master p ON p.id = i.p_id
|
||||
WHERE b.status = 'active'
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Legacy `hc_bed_master` support kept for backward compatibility (optional)
|
||||
- Main system now uses `bed_master` exclusively
|
||||
- `current_ipd_id` is the key field linking beds to patients
|
||||
- Bed status transitions: available → occupied → available
|
||||
|
||||
## Support
|
||||
|
||||
If issues persist after running the fix:
|
||||
1. Check `ipd_master.ipd_status` (0=admitted, 1=discharged)
|
||||
2. Check `bed_master.current_ipd_id` values (should be NULL for available beds)
|
||||
3. Check `bed_master.bed_status` values (should match occupancy)
|
||||
4. Review application logs for errors in IpdModel methods
|
||||
5. Run the verification queries from Step 4 in the SQL script
|
||||
6. Check `/setting/admin/bed-status` dashboard for accurate counts
|
||||
|
||||
## Quick Troubleshooting
|
||||
|
||||
**Problem**: Beds still show occupied after discharge
|
||||
- **Solution**: Run Step 2 of fix_bed_management.sql
|
||||
|
||||
**Problem**: "Bed Assign" column is blank
|
||||
- **Solution**: Ensure bed_master.current_ipd_id is set during admission
|
||||
|
||||
**Problem**: Bed count mismatch
|
||||
- **Solution**: Run verification queries (Step 4) to identify discrepancies
|
||||
|
||||
**Problem**: Cannot assign bed (shows as occupied)
|
||||
- **Solution**: Check if current_ipd_id is stuck with old IPD - run fix SQL
|
||||
|
||||
## Contact
|
||||
|
||||
For additional support, check:
|
||||
- Application logs in `writable/logs/`
|
||||
- Database for constraint violations
|
||||
- CI4 debug toolbar for query errors
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
# BridgeSyncService Routing Implementation
|
||||
|
||||
## Current Issue
|
||||
|
||||
The `BridgeSyncService::buildBridgeDispatchContext()` method treats all events the same, sending them to a single `BRIDGE_SYNC_URL`.
|
||||
|
||||
**Current behavior (WRONG):**
|
||||
```
|
||||
All events → BRIDGE_SYNC_URL (only one URL)
|
||||
```
|
||||
|
||||
**Required behavior (CORRECT):**
|
||||
```
|
||||
snomed.* events → BRIDGE_SYNC_URL (CSNOtk)
|
||||
abdm.* events → ABDM_BRIDGE_URL (ABDM Bridge)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Update `readSetting()` to Support New Keys
|
||||
|
||||
**File:** `app/Libraries/BridgeSyncService.php`
|
||||
|
||||
**Current:** `readSetting()` already handles priority order (env → constant → config → DB), so no changes needed. Just ensure both config keys are recognized.
|
||||
|
||||
**Verify in `app/Config/AbdmConnector.php`:**
|
||||
```php
|
||||
// Add if not present:
|
||||
public string $dreamsoftBridgeUrl = 'https://csnotk.e-atria.in/api/bridge';
|
||||
public string $abdmBridgeUrl = 'https://abdm-bridge.e-atria.in/api/v1/bridge';
|
||||
public string $dreamsoftBridgeToken = '';
|
||||
public string $abdmBridgeToken = '';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Create New Method `buildDispatchContextByType()`
|
||||
|
||||
**Replace or update the current** `buildDispatchContext()` **method in** `app/Libraries/BridgeSyncService.php`
|
||||
|
||||
#### Current Code (Lines ~335-365)
|
||||
|
||||
```php
|
||||
private function buildDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$eventType = (string) ($row['event_type'] ?? '');
|
||||
if ($eventType === '') {
|
||||
return ['ok' => false, 'error' => 'Missing event_type'];
|
||||
}
|
||||
|
||||
$isAbdmOrNhcx = str_starts_with($eventType, 'abdm.') || str_starts_with($eventType, 'nhcx.');
|
||||
$provider = strtolower($this->readSetting('ABDM_SYNC_PROVIDER'));
|
||||
if ($provider === '' || ! $isAbdmOrNhcx) {
|
||||
$provider = strtolower($this->readSetting('BRIDGE_SYNC_PROVIDER'));
|
||||
}
|
||||
if ($provider === '') {
|
||||
$provider = 'bridge';
|
||||
}
|
||||
|
||||
if ($provider === 'eka' && $isAbdmOrNhcx) {
|
||||
return $this->buildEkaDispatchContext($row, $payload);
|
||||
}
|
||||
|
||||
return $this->buildBridgeDispatchContext($row, $payload);
|
||||
}
|
||||
```
|
||||
|
||||
#### Updated Code (ADD THIS)
|
||||
|
||||
```php
|
||||
private function buildDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$eventType = (string) ($row['event_type'] ?? '');
|
||||
if ($eventType === '') {
|
||||
return ['ok' => false, 'error' => 'Missing event_type'];
|
||||
}
|
||||
|
||||
// NEW ROUTING LOGIC: Route by event type prefix
|
||||
if (str_starts_with($eventType, 'snomed.')) {
|
||||
return $this->buildCsnotkDispatchContext($row, $payload);
|
||||
}
|
||||
|
||||
if (str_starts_with($eventType, 'abdm.') || str_starts_with($eventType, 'nhcx.')) {
|
||||
$provider = strtolower($this->readSetting('ABDM_SYNC_PROVIDER'));
|
||||
if ($provider === 'eka') {
|
||||
return $this->buildEkaDispatchContext($row, $payload);
|
||||
}
|
||||
return $this->buildAbdmDispatchContext($row, $payload);
|
||||
}
|
||||
|
||||
// Fallback for other event types
|
||||
$provider = strtolower($this->readSetting('BRIDGE_SYNC_PROVIDER'));
|
||||
if ($provider === '') {
|
||||
$provider = 'bridge';
|
||||
}
|
||||
|
||||
return $this->buildBridgeDispatchContext($row, $payload);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Rename & Update `buildBridgeDispatchContext()` → `buildCsnotkDispatchContext()`
|
||||
|
||||
**File:** `app/Libraries/BridgeSyncService.php` (Lines ~365-400)
|
||||
|
||||
#### Current Method
|
||||
```php
|
||||
private function buildBridgeDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$endpoint = $this->readSetting('BRIDGE_SYNC_URL');
|
||||
if ($endpoint === '') {
|
||||
return ['ok' => false, 'error' => 'BRIDGE_SYNC_URL is not configured'];
|
||||
}
|
||||
|
||||
$token = $this->readSetting('BRIDGE_SYNC_TOKEN');
|
||||
$source = $this->readSetting('BRIDGE_SOURCE_CODE');
|
||||
if ($source === '') {
|
||||
$source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
|
||||
}
|
||||
|
||||
$headers = ['Content-Type' => 'application/json'];
|
||||
if ($token !== '') {
|
||||
$headers['Authorization'] = 'Bearer ' . $token;
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'channel' => 'bridge',
|
||||
'endpoint' => $endpoint,
|
||||
'method' => 'POST',
|
||||
'headers' => $headers,
|
||||
'body' => [
|
||||
'queue_id' => (int) ($row['id'] ?? 0),
|
||||
'source' => $source,
|
||||
'event_type' => (string) ($row['event_type'] ?? ''),
|
||||
'entity_type' => (string) ($row['entity_type'] ?? ''),
|
||||
'entity_id' => (string) ($row['entity_id'] ?? ''),
|
||||
'payload' => $payload,
|
||||
'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
|
||||
],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### Rename to `buildCsnotkDispatchContext()` (Keep as is)
|
||||
|
||||
```php
|
||||
private function buildCsnotkDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$endpoint = $this->readSetting('BRIDGE_SYNC_URL');
|
||||
if ($endpoint === '') {
|
||||
return ['ok' => false, 'error' => 'BRIDGE_SYNC_URL (CSNOtk) is not configured'];
|
||||
}
|
||||
|
||||
$token = $this->readSetting('BRIDGE_SYNC_TOKEN');
|
||||
$source = $this->readSetting('BRIDGE_SOURCE_CODE');
|
||||
if ($source === '') {
|
||||
$source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
|
||||
}
|
||||
|
||||
$headers = ['Content-Type' => 'application/json'];
|
||||
if ($token !== '') {
|
||||
$headers['Authorization'] = 'Bearer ' . $token;
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'channel' => 'csnotk', // Changed from 'bridge' to 'csnotk'
|
||||
'endpoint' => $endpoint,
|
||||
'method' => 'POST',
|
||||
'headers' => $headers,
|
||||
'body' => [
|
||||
'queue_id' => (int) ($row['id'] ?? 0),
|
||||
'source' => $source,
|
||||
'event_type' => (string) ($row['event_type'] ?? ''),
|
||||
'entity_type' => (string) ($row['entity_type'] ?? ''),
|
||||
'entity_id' => (string) ($row['entity_id'] ?? ''),
|
||||
'payload' => $payload,
|
||||
'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
|
||||
],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Create New Method `buildAbdmDispatchContext()`
|
||||
|
||||
**File:** `app/Libraries/BridgeSyncService.php` (Add after `buildCsnotkDispatchContext()`)
|
||||
|
||||
```php
|
||||
/**
|
||||
* Build dispatch context for ABDM Bridge (routes to https://abdm-bridge.e-atria.in/api/v1/bridge)
|
||||
*
|
||||
* @param array<string, mixed> $row
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildAbdmDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$endpoint = $this->readSetting('ABDM_BRIDGE_URL');
|
||||
if ($endpoint === '') {
|
||||
return ['ok' => false, 'error' => 'ABDM_BRIDGE_URL is not configured'];
|
||||
}
|
||||
|
||||
$token = $this->readSetting('ABDM_BRIDGE_TOKEN');
|
||||
$source = $this->readSetting('BRIDGE_SOURCE_CODE');
|
||||
if ($source === '') {
|
||||
$source = (string) ($this->readSetting('HOSPITAL_CODE') ?: 'hms-local');
|
||||
}
|
||||
|
||||
$headers = ['Content-Type' => 'application/json'];
|
||||
if ($token !== '') {
|
||||
$headers['Authorization'] = 'Bearer ' . $token;
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'channel' => 'abdm', // Distinct from 'csnotk'
|
||||
'endpoint' => $endpoint,
|
||||
'method' => 'POST',
|
||||
'headers' => $headers,
|
||||
'body' => [
|
||||
'queue_id' => (int) ($row['id'] ?? 0),
|
||||
'source' => $source,
|
||||
'event_type' => (string) ($row['event_type'] ?? ''),
|
||||
'entity_type' => (string) ($row['entity_type'] ?? ''),
|
||||
'entity_id' => (string) ($row['entity_id'] ?? ''),
|
||||
'payload' => $payload,
|
||||
'occurred_at' => (string) ($row['created_at'] ?? Time::now('Asia/Kolkata')->toDateTimeString()),
|
||||
],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Rename OLD `buildBridgeDispatchContext()` to `buildLegacyDispatchContext()`
|
||||
|
||||
**For backward compatibility,** keep the old method but rename it:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Legacy bridge context (fallback for unknown event types)
|
||||
* @deprecated Use buildCsnotkDispatchContext() or buildAbdmDispatchContext()
|
||||
*
|
||||
* @param array<string, mixed> $row
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildLegacyDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
// ... original buildBridgeDispatchContext() code ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Test 1: Queue SNOMED Event
|
||||
```bash
|
||||
# In HMS database, insert test record:
|
||||
INSERT INTO bridge_sync_queue (
|
||||
channel, event_type, entity_type, entity_id,
|
||||
payload_json, payload_hash, status, attempts, max_attempts
|
||||
) VALUES (
|
||||
'bridge',
|
||||
'snomed.diagnosis.validate',
|
||||
'diagnosis',
|
||||
'0',
|
||||
'{"diagnosis_text":"fever","return_limit":10}',
|
||||
SHA2('snomed.diagnosis.validate|diagnosis|0|...', 256),
|
||||
'pending',
|
||||
0,
|
||||
10
|
||||
);
|
||||
|
||||
# Run queue processor
|
||||
php spark bridge:sync --limit 5
|
||||
|
||||
# Check logs
|
||||
SELECT * FROM abdm_api_logs WHERE channel='csnotk' AND event_type='snomed.diagnosis.validate' ORDER BY created_at DESC LIMIT 1;
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Queue record status changed to 'sent' (or 'retry' if CSNOtk unavailable)
|
||||
- `abdm_api_logs.endpoint` = `https://csnotk.e-atria.in/api/bridge`
|
||||
- `abdm_api_logs.channel` = 'csnotk'
|
||||
|
||||
### Test 2: Queue ABDM Event
|
||||
```bash
|
||||
# In HMS database, insert test record:
|
||||
INSERT INTO bridge_sync_queue (
|
||||
channel, event_type, entity_type, entity_id,
|
||||
payload_json, payload_hash, status, attempts, max_attempts
|
||||
) VALUES (
|
||||
'bridge',
|
||||
'abdm.abha.validate',
|
||||
'patient',
|
||||
'12345',
|
||||
'{"abha_id":"00-0000-0000-0000"}',
|
||||
SHA2('abdm.abha.validate|patient|12345|...', 256),
|
||||
'pending',
|
||||
0,
|
||||
10
|
||||
);
|
||||
|
||||
# Run queue processor
|
||||
php spark bridge:sync --limit 5
|
||||
|
||||
# Check logs
|
||||
SELECT * FROM abdm_api_logs WHERE channel='abdm' AND event_type='abdm.abha.validate' ORDER BY created_at DESC LIMIT 1;
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Queue record status changed to 'sent' (or 'retry' if gateway unavailable)
|
||||
- `abdm_api_logs.endpoint` = `https://abdm-bridge.e-atria.in/api/v1/bridge`
|
||||
- `abdm_api_logs.channel` = 'abdm'
|
||||
|
||||
---
|
||||
|
||||
## Configuration Before Testing
|
||||
|
||||
**Update HMS `.env`:**
|
||||
```env
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
|
||||
BRIDGE_SYNC_TOKEN = <32-char-hex-token-1>
|
||||
ABDM_BRIDGE_URL = https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
ABDM_BRIDGE_TOKEN = <32-char-hex-token-2>
|
||||
BRIDGE_SOURCE_CODE = SBXID_033661
|
||||
```
|
||||
|
||||
**Generate tokens:**
|
||||
```bash
|
||||
openssl rand -hex 32 # Token 1
|
||||
openssl rand -hex 32 # Token 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [ ] `buildDispatchContext()` correctly identifies event prefix (snomed.* vs abdm.*)
|
||||
- [ ] `buildCsnotkDispatchContext()` uses correct URL and token keys
|
||||
- [ ] `buildAbdmDispatchContext()` uses correct URL and token keys
|
||||
- [ ] Channel is set correctly ('csnotk' or 'abdm') for logging
|
||||
- [ ] Both methods return same structure for `processPending()` to consume
|
||||
- [ ] Error handling for missing config keys
|
||||
- [ ] Backward compatibility maintained for legacy event types
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **Dual-Bridge Architecture:** `DUAL_BRIDGE_ARCHITECTURE.md`
|
||||
- **BridgeSyncService:** `app/Libraries/BridgeSyncService.php`
|
||||
- **AbdmConnector Config:** `app/Config/AbdmConnector.php`
|
||||
- **API Logs Table:** `bridge_sync_queue`, `abdm_api_logs`
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
# DICOM File AI Analysis Implementation
|
||||
|
||||
## Overview
|
||||
DICOM (.dcm) files can now be uploaded and automatically converted to standard image format for AI analysis in the imaging diagnosis workflow. The system supports CT, MRI, and other DICOM-based imaging modalities.
|
||||
|
||||
## Architecture
|
||||
|
||||
### 1. **Python FastAPI Service** (`d:\Workplace\AI_Module_HMS\main.py`)
|
||||
|
||||
#### New Imports Added (Lines 19-26)
|
||||
```python
|
||||
# --- DICOM support imports ---
|
||||
try:
|
||||
import pydicom
|
||||
from pydicom.pixel_data_handlers.pillow_handler import apply_modality_lut
|
||||
except ImportError:
|
||||
pydicom = None
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import io
|
||||
```
|
||||
|
||||
#### New Helper Function: `_convert_dicom_to_image_bytes()` (Line ~1250)
|
||||
Converts DICOM pixel data to standard JPEG/PNG image format with automatic windowing:
|
||||
|
||||
**Key Features:**
|
||||
- **Window Level/Width Handling**: Uses DICOM's WindowCenter and WindowWidth attributes for proper visualization
|
||||
- **Auto-Scaling**: If window attributes unavailable, auto-scales pixel values to 0-255 range
|
||||
- **Grayscale → RGB Conversion**: Converts single-channel DICOM images to RGB to ensure compatibility
|
||||
- **Quality Preservation**: Uses JPEG quality=95 to minimize loss during conversion
|
||||
|
||||
**Input:** Raw DICOM file bytes
|
||||
**Output:** Image bytes ready for Google Vision API
|
||||
**Error Handling:** Returns None if conversion fails; service returns error to client
|
||||
|
||||
#### Modified `/diagnosis` Endpoint (Line ~1475)
|
||||
|
||||
**What Changed:**
|
||||
1. **File Type Detection**: Now accepts both image and DICOM files
|
||||
```python
|
||||
is_dicom = filename_lower.endswith(('.dcm', '.dicom')) or 'dicom' in content_type
|
||||
is_image = file.content_type and file.content_type.startswith("image/")
|
||||
```
|
||||
|
||||
2. **DICOM-to-JPEG Conversion Pipeline**:
|
||||
```python
|
||||
if is_dicom:
|
||||
converted_content = _convert_dicom_to_image_bytes(content, output_format="JPEG")
|
||||
if not converted_content:
|
||||
raise HTTPException(status_code=400, detail="Failed to convert DICOM file...")
|
||||
content = converted_content
|
||||
file.content_type = "image/jpeg"
|
||||
```
|
||||
|
||||
3. **Seamless AI Analysis**: After conversion, DICOM is processed identically to other image formats
|
||||
- Google Vision label detection
|
||||
- OCR extraction
|
||||
- Modality/technique detection
|
||||
- Gemini analysis via AI prompt system
|
||||
|
||||
**Error Conditions:**
|
||||
- Unsupported file type: `"Please upload a valid image file (JPEG/PNG) or DICOM (.dcm)"`
|
||||
- DICOM conversion failed: `"Failed to convert DICOM file. File may be corrupted."`
|
||||
|
||||
### 2. **PHP HMS Controller** (`d:\Workplace\HMS_CI4_OLD\app\Controllers\Diagnosis.php`)
|
||||
|
||||
**No Changes Required**
|
||||
- Backend file validation already includes `.dcm` and `.dicom` extensions
|
||||
- File upload and storage remain unchanged
|
||||
- Controller passes file path to `/diagnosis` endpoint which handles conversion
|
||||
|
||||
### 3. **UI File Picker** (`d:\Workplace\HMS_CI4_OLD\app\Views\diagnosis\pathology_detail.php`)
|
||||
|
||||
**No Changes Required**
|
||||
- File picker `accept` attribute already includes DICOM MIME types:
|
||||
```javascript
|
||||
input.accept = 'image/*,application/pdf,.dcm,application/dicom,application/dicom+json';
|
||||
```
|
||||
|
||||
### 4. **Upload Gallery** (`d:\Workplace\HMS_CI4_OLD\app\Views\diagnosis\imaging_upload_gallery.php`)
|
||||
|
||||
**No Changes Required**
|
||||
- Gallery already shows medical file icon for non-image types
|
||||
- Displays "Open File" button for DICOM files
|
||||
|
||||
## Data Flow
|
||||
|
||||
### CT/MRI DICOM Upload → AI Analysis Flow
|
||||
|
||||
```
|
||||
User uploads .dcm file
|
||||
↓
|
||||
PHP validates extension ([jpg, png, webp, pdf, dcm, dicom]) ✓
|
||||
↓
|
||||
File stored: public/uploads/diagnosis/{YYYY}/{MM}/{filename}.dcm
|
||||
↓
|
||||
User clicks "Analyze with AI"
|
||||
↓
|
||||
PHP POST to /diagnosis endpoint with file path
|
||||
↓
|
||||
Python FastAPI receives DICOM bytes
|
||||
↓
|
||||
_convert_dicom_to_image_bytes()
|
||||
- Reads DICOM with pydicom
|
||||
- Extracts pixel array
|
||||
- Applies window level/width normalization
|
||||
- Converts to RGB JPEG
|
||||
↓
|
||||
Google Vision API analyzes converted JPEG
|
||||
- Label detection (anatomical structures, findings)
|
||||
- OCR extraction (any text overlays)
|
||||
↓
|
||||
Modality-specific Gemini prompt applied
|
||||
- CT: Organ, vessel, bone evaluation
|
||||
- MRI: Brain, spine, vessel evaluation
|
||||
- etc.
|
||||
↓
|
||||
AI-generated radiology report returned
|
||||
↓
|
||||
PHP extracts narrative findings and renders in modal
|
||||
↓
|
||||
Doctor reviews and signs off
|
||||
```
|
||||
|
||||
## Supported Modalities
|
||||
|
||||
DICOM files can now be analyzed for:
|
||||
- **CT Scans**: Brain, Spine, Abdomen, Thorax, Angiography
|
||||
- **MRI Scans**: Brain, Spine, Abdomen, Pelvis, Cardiac
|
||||
- **Ultrasound**: (if DICOM format available)
|
||||
- **X-rays**: Converted and analyzed as images
|
||||
- **Other**: DR, CR, PT, NM formats (if pixel data available)
|
||||
|
||||
## Technical Details
|
||||
|
||||
### DICOM Windowing Algorithm
|
||||
DICOM images often use 12-bit or 16-bit pixel data with implicit window settings. The conversion function:
|
||||
|
||||
1. **Check for explicit window attributes**:
|
||||
- `WindowCenter` (DICOM tag 0028,1050)
|
||||
- `WindowWidth` (DICOM tag 0028,1051)
|
||||
- Ideal for chest X-rays, CTs with clinical preset windows
|
||||
|
||||
2. **If window attributes exist**:
|
||||
```
|
||||
min_val = WindowCenter - (WindowWidth / 2)
|
||||
max_val = WindowCenter + (WindowWidth / 2)
|
||||
Clip pixel data to [min_val, max_val]
|
||||
Scale to 0-255 range
|
||||
```
|
||||
|
||||
3. **Fallback: Auto-scale**:
|
||||
```
|
||||
scale = (pixel_array - min) / (max - min) × 255
|
||||
```
|
||||
|
||||
### RGB Conversion
|
||||
- Grayscale DICOM → PIL 'L' mode → RGB mode (for Google Vision compatibility)
|
||||
- RGB DICOM → Direct passthrough
|
||||
- Multi-frame DICOM → First frame used (future: frame selection UI)
|
||||
|
||||
### Output Format
|
||||
- **JPEG Quality**: 95 (high quality, minimal loss)
|
||||
- **File Type**: JPEG for all conversions (universal browser compatibility)
|
||||
- **Buffer Management**: BytesIO for in-memory conversion (no temporary files)
|
||||
|
||||
## Error Handling & Fallbacks
|
||||
|
||||
| Error | Root Cause | User Message | Recovery |
|
||||
|-------|-----------|--------------|----------|
|
||||
| Conversion fails | Corrupted DICOM, invalid pixel data | "Failed to convert DICOM file. File may be corrupted." | User uploads different file or original image |
|
||||
| Unsupported file type | Wrong extension or MIME type | "Please upload a valid image file (JPEG/PNG) or DICOM (.dcm)" | User checks file type |
|
||||
| AI analysis fails on DICOM | Image quality too poor after conversion | Generic fallback report | Local chest X-ray model used (if applicable) |
|
||||
| pydicom not installed | Missing dependency | Warning in logs, fallback validation | System admin installs pydicom |
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Upload CT abdomen DICOM → AI generates findings (liver, spleen, kidneys)
|
||||
- [ ] Upload MRI brain DICOM → AI generates findings (ventricles, lesions, midline)
|
||||
- [ ] Upload multi-frame DICOM → First frame analyzed correctly
|
||||
- [ ] Corrupted DICOM file → Error message shown, no crash
|
||||
- [ ] DICOM with non-standard window settings → Proper windowing applied
|
||||
- [ ] DICOM with text overlay → OCR extracts text metadata
|
||||
- [ ] Provider/model badges show correctly in results
|
||||
- [ ] Fallback warning appears if AI service uses local model
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **File Type Validation**: Extension + MIME type check before processing
|
||||
2. **Sandboxed Conversion**: DICOM parsing in isolated try/except block
|
||||
3. **Size Limits**: PHP file upload max (typically 100MB) prevents DoS
|
||||
4. **Failure Isolation**: DICOM conversion errors don't crash main endpoint
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
- **DICOM to JPEG conversion**: ~100-500ms (depends on image size/complexity)
|
||||
- **Google Vision analysis**: ~1-3s (network dependent)
|
||||
- **Full pipeline (upload → AI → modal display)**: ~5-10s
|
||||
- **Memory usage**: ~50-200MB per DICOM (loaded into RAM during conversion)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Multi-frame Support**: UI to select which frame to analyze
|
||||
2. **DICOM-to-Video**: Convert series to video for temporal review
|
||||
3. **Anonymization**: Strip PHI tags before AI analysis
|
||||
4. **Caching**: Store converted JPEG to avoid re-conversion
|
||||
5. **Batch Processing**: Upload multiple DICOM files at once
|
||||
6. **Regional Analysis**: AI focus on specific anatomical regions per user request
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Python**: pydicom (DICOM reading), PIL/Pillow (image conversion), NumPy (array operations)
|
||||
- **PHP**: No new dependencies (uses existing file upload framework)
|
||||
- **Database**: No schema changes required
|
||||
- **Frontend**: No changes (existing file picker supports DICOM MIME types)
|
||||
|
||||
## Deployment Notes
|
||||
|
||||
1. **Ensure pydicom installed** on AI server:
|
||||
```bash
|
||||
pip install pydicom pillow numpy
|
||||
```
|
||||
|
||||
2. **Restart FastAPI service** after code update:
|
||||
```bash
|
||||
# May require process restart or docker rebuild
|
||||
```
|
||||
|
||||
3. **Test with sample DICOM file** before production deployment
|
||||
|
||||
4. **Monitor logs** for conversion errors during initial deployment
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Implementation Complete
|
||||
**Date**: March 13, 2026
|
||||
**Last Updated**: AI service now accepts DICOM files for CT/MRI/imaging workflows
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
# Discharge Summary CSS Classes Reference
|
||||
|
||||
This document lists all the semantic CSS classes used in the discharge summary HTML, allowing you to style them separately without inline styles.
|
||||
|
||||
---
|
||||
|
||||
## CSS Class List
|
||||
|
||||
### Section Headings
|
||||
- **`.discharge-title`** - Main discharge title (H2)
|
||||
- **`.discharge-section-heading`** - Section headers (H4)
|
||||
|
||||
### Lists
|
||||
- **`.discharge-list`** - Main lists (ul/ol) for items
|
||||
- **`.discharge-sublist`** - Nested sublists (ul)
|
||||
- **`.no-bullet`** - List item with no bullet point
|
||||
|
||||
### Text Elements
|
||||
- **`.discharge-remark`** - Remark/secondary text (span)
|
||||
|
||||
### Tables
|
||||
- **`.discharge-table`** - Standard data tables with borders
|
||||
- **`.discharge-info-table`** - Patient info table
|
||||
- **`.discharge-medicine-table`** - Medicine list table
|
||||
- **`.discharge-signature-table`** - Signature block table
|
||||
|
||||
### Containers
|
||||
- **`.discharge-summary-info`** - Discharge summary info block (div)
|
||||
- **`.discharge-section`** - Generic section container (div)
|
||||
- **`.discharge-item`** - Individual item within a section (div)
|
||||
- **`.discharge-field`** - Field with label (div) - margin-bottom
|
||||
- **`.discharge-footer`** - Footer text (div) - margin-top
|
||||
- **`.nabh-guidance`** - NABH guidance note (div)
|
||||
|
||||
### Separators
|
||||
- **`.discharge-separator`** - Horizontal rule (hr)
|
||||
|
||||
---
|
||||
|
||||
## Suggested CSS Stylesheet
|
||||
|
||||
Copy this to your discharge template `template_css` field or add to your global styles:
|
||||
|
||||
```css
|
||||
/* Section Headings */
|
||||
.discharge-title {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.discharge-section-heading {
|
||||
margin: 16px 0 8px 0;
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
.discharge-list {
|
||||
margin: 0 0 10px 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.discharge-sublist {
|
||||
margin: 4px 0 8px 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.no-bullet {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* Text */
|
||||
.discharge-remark {
|
||||
color: #475569;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.discharge-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 4px 0 8px 0;
|
||||
}
|
||||
|
||||
.discharge-info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discharge-medicine-table {
|
||||
width: 100%;
|
||||
border-style: inset;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discharge-medicine-table th:first-child {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.discharge-medicine-table th:nth-child(3) {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.discharge-medicine-table th:nth-child(4) {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.discharge-signature-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.discharge-signature-table td {
|
||||
width: 33.33%;
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Containers */
|
||||
.discharge-summary-info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discharge-section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discharge-item {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.discharge-field {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.discharge-footer {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.nabh-guidance {
|
||||
font-size: 11px;
|
||||
color: #334155;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
background-color: #f8fafc;
|
||||
border-left: 3px solid #3b82f6;
|
||||
}
|
||||
|
||||
/* Separators */
|
||||
.discharge-separator {
|
||||
margin: 1px 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Responsive Adjustments (Optional)
|
||||
|
||||
Add these for print-optimized styles:
|
||||
|
||||
```css
|
||||
@media print {
|
||||
.discharge-title {
|
||||
font-size: 16pt;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.discharge-section-heading {
|
||||
page-break-after: avoid;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.discharge-table,
|
||||
.discharge-info-table,
|
||||
.discharge-medicine-table,
|
||||
.discharge-signature-table {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Apply Styles
|
||||
|
||||
### Method 1: Template CSS Field
|
||||
1. Go to discharge template settings
|
||||
2. Paste the CSS into the `template_css` field
|
||||
3. Save template
|
||||
|
||||
### Method 2: Global Styles
|
||||
1. Add to your print CSS file
|
||||
2. Include in the PDF generation header
|
||||
3. Apply site-wide
|
||||
|
||||
---
|
||||
|
||||
## Example Usage in Template
|
||||
|
||||
```html
|
||||
<h2 class="discharge-title">Discharge Summary</h2>
|
||||
<hr class="discharge-separator" />
|
||||
|
||||
<table class="discharge-info-table" border="1" cellpadding="6">
|
||||
<tr>
|
||||
<td><b>Name</b>: {{PATIENT_NAME}}</td>
|
||||
<td><b>UHID</b>: {{UHID}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="discharge-section">{{DISCHARGE_SUMMARY}}</div>
|
||||
<div class="discharge-section">{{PRESENTING_COMPLAINTS}}</div>
|
||||
<div class="discharge-section">{{FINAL_DIAGNOSIS}}</div>
|
||||
|
||||
<h4 class="discharge-section-heading">Discharge Medications</h4>
|
||||
<ol class="discharge-list">
|
||||
<li>Medicine 1 <span class="discharge-remark">(Take after food)</span></li>
|
||||
<li>Medicine 2 <span class="discharge-remark">[7 days]</span></li>
|
||||
</ol>
|
||||
|
||||
<table class="discharge-signature-table" border="0" cellpadding="10">
|
||||
<tr>
|
||||
<td>____________________<br>Consultant Signature</td>
|
||||
<td>____________________<br>Medical Officer</td>
|
||||
<td>____________________<br>Patient/Receiver</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Customization Tips
|
||||
|
||||
1. **Change Colors**: Modify `.discharge-remark` color from `#475569` to your brand color
|
||||
2. **Adjust Spacing**: Increase/decrease margins in heading classes
|
||||
3. **Font Sizes**: Change section heading sizes for emphasis
|
||||
4. **Table Borders**: Adjust border styles on table classes
|
||||
5. **Print Optimization**: Use `@media print` for print-specific styles
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **No inline styles** - All styling controlled via CSS
|
||||
✅ **Easy customization** - Change once, applies everywhere
|
||||
✅ **Consistent look** - Uniform styling across all sections
|
||||
✅ **Print-friendly** - Optimized for PDF generation
|
||||
✅ **Maintainable** - Update styles without touching PHP code
|
||||
|
||||
---
|
||||
|
||||
**Generated:** 2026-06-07
|
||||
**File Location:** `d:\Workplace\HMS_CI4_OLD\DISCHARGE_CSS_CLASSES.md`
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
# Discharge Summary Template Placeholders Reference
|
||||
|
||||
## Overview
|
||||
This document explains all available placeholders for discharge summary templates. Use these placeholders in your template HTML to insert dynamic content.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 MAIN DISCHARGE SUMMARY
|
||||
|
||||
### `{{CLINICAL_SUMMARY}}` ⭐ RECOMMENDED
|
||||
**Content:** The main discharge summary text from the HTML editor (instruction_remark field)
|
||||
**Source:** "Discharge Summary" section in the discharge form
|
||||
**Use this for:** The primary clinical narrative written by doctors
|
||||
|
||||
**Example:**
|
||||
```html
|
||||
<h2>Discharge Summary</h2>
|
||||
{{CLINICAL_SUMMARY}}
|
||||
```
|
||||
|
||||
### `{{DISCHARGE_SUMMARY}}` (Legacy)
|
||||
**Content:** Same as `{{CLINICAL_SUMMARY}}`
|
||||
**Note:** Kept for backward compatibility. Use `{{CLINICAL_SUMMARY}}` for clarity.
|
||||
|
||||
---
|
||||
|
||||
## 🔵 CLINICAL SECTIONS
|
||||
|
||||
### `{{FINAL_DIAGNOSIS}}`
|
||||
**Content:** Final diagnosis section
|
||||
**Source:** "Final Diagnosis" section
|
||||
|
||||
### `{{SURGERY}}`
|
||||
**Content:** Surgery details section
|
||||
**Source:** "Surgery" section
|
||||
|
||||
### `{{PROCEDURE}}`
|
||||
**Content:** Procedure details section
|
||||
**Source:** "Procedure" section
|
||||
|
||||
### `{{PRESENTING_COMPLAINTS}}`
|
||||
**Content:** Presenting complaints and reason for admission
|
||||
**Source:** "Presenting Complaints and Reason for Admission" section
|
||||
|
||||
### `{{PERSONAL_HISTORY}}`
|
||||
**Content:** Patient personal history
|
||||
**Source:** "Personal History" section
|
||||
|
||||
### `{{GENERAL_EXAM_ADMISSION}}`
|
||||
**Content:** General examination findings at admission
|
||||
**Source:** "General Examination on Admission" section
|
||||
|
||||
### `{{CLINICAL_INVESTIGATION_REPORTS}}`
|
||||
**Content:** Clinical investigation and lab test reports
|
||||
**Source:** "Clinical Investigation Reports" section
|
||||
|
||||
### `{{COURSE_IN_HOSPITAL}}`
|
||||
**Content:** Course/progress during hospitalization
|
||||
**Source:** "Course in the hospital" section
|
||||
|
||||
### `{{EXAMINATION_ON_DISCHARGE}}`
|
||||
**Content:** Examination findings at discharge
|
||||
**Source:** "Examination on Discharge" section
|
||||
|
||||
### `{{PAIN_MEASUREMENT_SCALE}}`
|
||||
**Content:** Pain assessment scale details
|
||||
**Source:** "Pain Measurement Scale" section
|
||||
|
||||
### `{{DRUG_ALLERGY_ADR}}`
|
||||
**Content:** Drug allergies and adverse drug reactions
|
||||
**Source:** "Drug Allergy / ADR" section
|
||||
|
||||
### `{{CO_MORBIDITIES}}`
|
||||
**Content:** Co-existing medical conditions
|
||||
**Source:** "Co-Morbidities" section
|
||||
|
||||
---
|
||||
|
||||
## 🟢 DISCHARGE INSTRUCTIONS & ADVICE
|
||||
|
||||
### `{{DISCHARGE_MEDICATIONS}}`
|
||||
**Content:** Medications prescribed at discharge
|
||||
**Source:** "Discharge Medications" section
|
||||
|
||||
### `{{DIETARY_ADVICE}}`
|
||||
**Content:** Dietary recommendations
|
||||
**Source:** "Dietary Advice" section
|
||||
|
||||
### `{{DISCHARGE_INSTRUCTIONS}}`
|
||||
**Content:** General discharge advice and instructions
|
||||
**Source:** "Discharge Advice/Instructions/Summary" section
|
||||
|
||||
### `{{OTHER_ADVICE}}`
|
||||
**Content:** Other miscellaneous advice
|
||||
**Source:** "Other Advice:" section
|
||||
|
||||
### `{{FOLLOW_UP_INSTRUCTIONS}}`
|
||||
**Content:** Follow-up appointment instructions
|
||||
**Source:** "Discharge Summary:" subsection (note the colon)
|
||||
|
||||
---
|
||||
|
||||
## 🟡 OTHER SECTIONS
|
||||
|
||||
### `{{SIGNATURE_BLOCK}}`
|
||||
**Content:** Signature table with consultant details
|
||||
**Source:** "Signature of Consultant" section
|
||||
|
||||
---
|
||||
|
||||
## 📋 PATIENT INFO PLACEHOLDERS
|
||||
|
||||
Use `{{PATIENT_INFO_TABLE}}` for the complete patient information header table.
|
||||
|
||||
---
|
||||
|
||||
## 💡 RECOMMENDED TEMPLATE STRUCTURE
|
||||
|
||||
```html
|
||||
<!-- Patient Information -->
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
|
||||
<!-- Main Clinical Summary -->
|
||||
<h2>Clinical Summary</h2>
|
||||
{{CLINICAL_SUMMARY}}
|
||||
|
||||
<!-- Clinical Sections -->
|
||||
{{PRESENTING_COMPLAINTS}}
|
||||
{{PAIN_MEASUREMENT_SCALE}}
|
||||
{{DRUG_ALLERGY_ADR}}
|
||||
{{CO_MORBIDITIES}}
|
||||
{{GENERAL_EXAM_ADMISSION}}
|
||||
{{PERSONAL_HISTORY}}
|
||||
{{CLINICAL_INVESTIGATION_REPORTS}}
|
||||
|
||||
<!-- Diagnosis & Treatment -->
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{SURGERY}}
|
||||
{{PROCEDURE}}
|
||||
{{COURSE_IN_HOSPITAL}}
|
||||
{{EXAMINATION_ON_DISCHARGE}}
|
||||
|
||||
<!-- Discharge Instructions -->
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
{{DIETARY_ADVICE}}
|
||||
{{OTHER_ADVICE}}
|
||||
{{DISCHARGE_INSTRUCTIONS}}
|
||||
{{FOLLOW_UP_INSTRUCTIONS}}
|
||||
|
||||
<!-- Signature -->
|
||||
{{SIGNATURE_BLOCK}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ IMPORTANT NOTES
|
||||
|
||||
1. **Empty Sections:** If a section has no data, the placeholder will be replaced with empty string (won't show in PDF)
|
||||
|
||||
2. **HTML Formatting:** Content from HTML editors (like `{{CLINICAL_SUMMARY}}`) preserves formatting (bold, lists, etc.)
|
||||
|
||||
3. **Placeholder Case:** Placeholders are case-sensitive. Use UPPERCASE with underscores.
|
||||
|
||||
4. **Missing Placeholders:** If you forget a placeholder, that content simply won't appear in the PDF
|
||||
|
||||
5. **Duplicate Placeholders:** You can use the same placeholder multiple times in a template if needed
|
||||
|
||||
---
|
||||
|
||||
## 🔄 MIGRATION GUIDE
|
||||
|
||||
### Old Placeholder → New Placeholder
|
||||
|
||||
| Old (Confusing) | New (Clear) | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `{{DISCHARGE_SUMMARY}}` | `{{CLINICAL_SUMMARY}}` | Both work, but use `{{CLINICAL_SUMMARY}}` for clarity |
|
||||
| `{{INSTRUCTION_REMARK}}` | `{{FOLLOW_UP_INSTRUCTIONS}}` | Old placeholder removed |
|
||||
| `{{DISCHARGE_ADVICE}}` | `{{FOLLOW_UP_INSTRUCTIONS}}` | More descriptive name |
|
||||
|
||||
### What Changed?
|
||||
|
||||
**Before (Confusing):**
|
||||
- `{{DISCHARGE_SUMMARY}}` - Main clinical summary
|
||||
- `{{DISCHARGE_ADVICE}}` - Follow-up instructions
|
||||
- `{{INSTRUCTION_REMARK}}` - Same as DISCHARGE_ADVICE
|
||||
|
||||
**After (Clear):**
|
||||
- `{{CLINICAL_SUMMARY}}` - Main clinical summary ⭐ Use this!
|
||||
- `{{FOLLOW_UP_INSTRUCTIONS}}` - Follow-up instructions
|
||||
- `{{DISCHARGE_INSTRUCTIONS}}` - General discharge advice
|
||||
|
||||
---
|
||||
|
||||
## 📝 EXAMPLE TEMPLATES
|
||||
|
||||
### Minimal Template
|
||||
```html
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
{{CLINICAL_SUMMARY}}
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
{{SIGNATURE_BLOCK}}
|
||||
```
|
||||
|
||||
### Comprehensive Template
|
||||
```html
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
|
||||
<h2>Clinical Summary</h2>
|
||||
{{CLINICAL_SUMMARY}}
|
||||
|
||||
{{PRESENTING_COMPLAINTS}}
|
||||
{{GENERAL_EXAM_ADMISSION}}
|
||||
{{CLINICAL_INVESTIGATION_REPORTS}}
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{SURGERY}}
|
||||
{{PROCEDURE}}
|
||||
{{COURSE_IN_HOSPITAL}}
|
||||
{{EXAMINATION_ON_DISCHARGE}}
|
||||
|
||||
<h2>Discharge Instructions</h2>
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
{{DIETARY_ADVICE}}
|
||||
{{OTHER_ADVICE}}
|
||||
{{FOLLOW_UP_INSTRUCTIONS}}
|
||||
|
||||
{{SIGNATURE_BLOCK}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 TROUBLESHOOTING
|
||||
|
||||
**Problem:** Discharge summary text not appearing in PDF
|
||||
**Solution:** Make sure you have `{{CLINICAL_SUMMARY}}` or `{{DISCHARGE_SUMMARY}}` in your template
|
||||
|
||||
**Problem:** Section appears empty in PDF
|
||||
**Solution:** Check if that section was filled in the discharge form. Empty sections = empty output.
|
||||
|
||||
**Problem:** Formatting looks wrong
|
||||
**Solution:** Check your template HTML structure. Each placeholder inserts its content with existing HTML tags.
|
||||
|
||||
---
|
||||
|
||||
## 📞 SUPPORT
|
||||
|
||||
For template editing issues, check:
|
||||
1. Template management page (ensure placeholders are spelled correctly)
|
||||
2. HTML debug mode: `/Ipd_discharge/show_discharge/{ipdId}/1?html=1&tpl=3`
|
||||
3. Content debug: `/Ipd_discharge/debug_discharge_html/{ipdId}`
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-06-11
|
||||
**Version:** 2.0 (Simplified naming)
|
||||
|
|
@ -1,479 +0,0 @@
|
|||
# Discharge Summary Placeholder HTML Reference
|
||||
|
||||
This document shows the exact HTML structure used for each discharge summary placeholder.
|
||||
|
||||
---
|
||||
|
||||
## {{DISCHARGE_SUMMARY}}
|
||||
|
||||
**Source:** `getDischargeStatusText()` - Status from `ipd_discharge_status` table
|
||||
**Join:** `ipd_master.ipd_status = ipd_discharge_status.id`
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 class="discharge-section-heading">Discharge Summary</h4>
|
||||
<!-- OR dynamic header based on ipd_status -->
|
||||
<h4 class="discharge-section-heading">Leave Against Medical Advice</h4>
|
||||
```
|
||||
|
||||
**Note:** This placeholder contains **ONLY** the status header text. No additional information like department, doctor, or dates.
|
||||
|
||||
**Status-based headers:**
|
||||
- ipd_status = 0 → "Discharge Summary"
|
||||
- ipd_status = 1 → "Discharge Summary" (from DB)
|
||||
- ipd_status = 2 → "Leave Against Medical Advice"
|
||||
- ipd_status = 3 → "Refer To Higher Centre"
|
||||
- ipd_status = 5 → "Dead Summary"
|
||||
- ipd_status = 7 → "Discharge on Request"
|
||||
|
||||
**For additional info, use separate tokens:**
|
||||
- `{{DEPARTMENT}}` - Department name
|
||||
- `{{DOCTOR_NAMES}}` - Treating doctor(s)
|
||||
- `{{ADMIT_DATE_ONLY}}` - Admission date
|
||||
- `{{DISCHARGE_DATE_ONLY}}` - Discharge date
|
||||
- `{{ADMISSION_TIME}}` - Admission time
|
||||
- `{{DISCHARGE_TIME}}` - Discharge time
|
||||
|
||||
---
|
||||
|
||||
## {{PRESENTING_COMPLAINTS}}
|
||||
|
||||
**Source:** `ipd_discharge_complaint` + `ipd_discharge_complaint_remark`
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Presenting Complaints and Reason for Admission</h4>
|
||||
<ul style="margin:0 0 10px 20px;">
|
||||
<li>Complaint 1 <span style="color:#475569;">(Remark)</span></li>
|
||||
<li>Complaint 2 <span style="color:#475569;">(Remark)</span></li>
|
||||
</ul>
|
||||
<div>Additional remark text from comp_remark field (rich text)</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{PAIN_MEASUREMENT_SCALE}}
|
||||
|
||||
**Source:** Pain measurement data from complaint metadata
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<div><b>Pain Measurement Scale:</b> Moderate (5)</div>
|
||||
```
|
||||
|
||||
**If no data:** Empty string (not shown)
|
||||
|
||||
---
|
||||
|
||||
## {{GENERAL_EXAM_ADMISSION}}
|
||||
|
||||
**Source:** Physical examination data
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<p><b>General Examination on Admission : </b><br/>
|
||||
BP: 120/80 mmHg, Pulse: 78 bpm, Temp: 98.6°F, Weight: 65 kg
|
||||
</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{PERSONAL_HISTORY}}
|
||||
|
||||
**Source:** `patient_master` table personal history flags
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Personal History</h4>
|
||||
<div>Smoking, Hypertension, Type 2 diabetes mellitus (DM)</div>
|
||||
```
|
||||
|
||||
**Fields checked:**
|
||||
- is_smoking → "Smoking"
|
||||
- is_alcohol → "Alcohol"
|
||||
- is_drug_abuse → "Drug abuse"
|
||||
- is_tobacoo → "Tobacco"
|
||||
- is_hypertesion → "Hypertension"
|
||||
- is_niddm → "Type 2 diabetes mellitus (DM)"
|
||||
- is_hbsag → "HBsAg"
|
||||
- is_hcv → "HCV"
|
||||
- is_hiv_I_II → "HIV I & II"
|
||||
|
||||
---
|
||||
|
||||
## {{DRUG_ALLERGY_ADR}}
|
||||
|
||||
**Source:** OPD history data
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Drug Allergy / ADR</h4>
|
||||
<div><b>Drug Allergy Status:</b> Known allergies</div>
|
||||
<div><b>Drug Allergy Details:</b> Penicillin, Aspirin</div>
|
||||
<div><b>ADR History:</b> Rash after penicillin</div>
|
||||
<div><b>Current Medications:</b> Metformin 500mg BD</div>
|
||||
```
|
||||
|
||||
**If no data:** Empty string (not shown)
|
||||
|
||||
---
|
||||
|
||||
## {{CO_MORBIDITIES}}
|
||||
|
||||
**Source:** OPD history `co_morbidities` field
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Co-Morbidities</h4>
|
||||
<div>Diabetes Mellitus, Hypertension, Chronic Kidney Disease</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{CLINICAL_INVESTIGATION_REPORTS}}
|
||||
|
||||
**Source:** Lab pathology + non-pathology reports
|
||||
**HTML Structure:**
|
||||
|
||||
### With Pathology Matrix:
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Clinical Investigation Reports</h4>
|
||||
<div><b>In-Hospital Lab:</b></div>
|
||||
<table style="width:100%;border-collapse:collapse;margin:4px 0 8px 0;" border="1" cellpadding="6">
|
||||
<tr>
|
||||
<th style="text-align:left;">Test</th>
|
||||
<th style="text-align:left;">Fixed Normals</th>
|
||||
<th style="text-align:left;">23-05-2026</th>
|
||||
<th style="text-align:left;">25-05-2026</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hemoglobin</td>
|
||||
<td>12-16 g/dL</td>
|
||||
<td>11.5</td>
|
||||
<td>13.2</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
### Non-Pathology Reports:
|
||||
```html
|
||||
<div><b>X-Ray / ECG / Sonography / CT / MRI:</b></div>
|
||||
<ul style="margin:4px 0 8px 20px;">
|
||||
<li>[24-05-2026] X-Ray - Chest PA View
|
||||
<br><span style="color:#475569;">Impression: Normal study</span>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Other Examinations:
|
||||
```html
|
||||
<div><b>Other Examinations / Provisional Diagnosis:</b><br>
|
||||
Additional examination notes
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{FINAL_DIAGNOSIS}}
|
||||
|
||||
**Source:** `ipd_discharge_diagnosis` + `ipd_discharge_diagnosis_remark`
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Final Diagnosis</h4>
|
||||
<ul style="margin:0 0 10px 20px;">
|
||||
<li>Diagnosis 1 <span style="color:#475569;">(Remark)</span></li>
|
||||
<li>Diagnosis 2</li>
|
||||
</ul>
|
||||
<div>Additional diagnosis remarks (rich text)</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{COURSE_IN_HOSPITAL}}
|
||||
|
||||
**Source:** `ipd_discharge_course` + `ipd_discharge_course_remark`
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Course in the hospital</h4>
|
||||
<ul style="margin:0 0 10px 20px;">
|
||||
<li>Patient admitted with complaints of...</li>
|
||||
<li>Treatment started with...</li>
|
||||
</ul>
|
||||
<div>Additional course remarks (rich text)</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{EXAMINATION_ON_DISCHARGE}}
|
||||
|
||||
**Source:** Discharge examination data
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<p><b>Examination on Discharge : </b>
|
||||
BP: 130/85 mmHg, Pulse: 72 bpm, General condition: Stable
|
||||
</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{SURGERY}}
|
||||
|
||||
**Source:** `ipd_discharge_surgery` table
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Surgery</h4>
|
||||
<table style="width:100%;border-collapse:collapse;margin:4px 0 8px 0;" border="1" cellpadding="6">
|
||||
<tr>
|
||||
<th style="text-align:left;">Name</th>
|
||||
<th style="text-align:left;">Date</th>
|
||||
<th style="text-align:left;">Remark</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Laparoscopy</td>
|
||||
<td>05-06-2026</td>
|
||||
<td>I & D WITH L. N. EXCISION DONE UNDER GA</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
**Alternative format (if no table structure):**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Surgery</h4>
|
||||
<ul style="margin:0 0 10px 20px;">
|
||||
<li>Laparoscopy <span style="color:#475569;">/ Date of Surgery : 2026-06-05</span></li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{PROCEDURE}}
|
||||
|
||||
**Source:** `ipd_discharge_procedure` table
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Procedure</h4>
|
||||
<table style="width:100%;border-collapse:collapse;margin:4px 0 8px 0;" border="1" cellpadding="6">
|
||||
<tr>
|
||||
<th style="text-align:left;">Name</th>
|
||||
<th style="text-align:left;">Date</th>
|
||||
<th style="text-align:left;">Remark</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Procedure TEST</td>
|
||||
<td>03-06-2026</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{DISCHARGE_MEDICATIONS}}
|
||||
|
||||
**Source:** `ipd_discharge_prescrption_prescribed` or `ipd_discharge_drug`
|
||||
|
||||
### Format 1 (New prescriptions with full details):
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Discharge Medications</h4>
|
||||
<table style="width:100%;border-style: inset;;margin-bottom:10px;" border="0" cellpadding="6">
|
||||
<tr>
|
||||
<th style="width:40px;">#</th>
|
||||
<th>Medicine</th>
|
||||
<th style="width:90px;">Qty</th>
|
||||
<th style="width:100px;">Days</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Tab Paracetamol 500mg</td>
|
||||
<td>30</td>
|
||||
<td>5 days</td>
|
||||
<td>1-0-1 After Food</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
### Format 2 (Simple drug list):
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Discharge Medications</h4>
|
||||
<ol style="margin:0 0 10px 20px;">
|
||||
<li>Paracetamol <span style="color:#475569;">500mg</span> <span style="color:#475569;">[5 days]</span></li>
|
||||
<li>Amoxicillin <span style="color:#475569;">250mg</span> <span style="color:#475569;">[7 days]</span></li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{DIETARY_ADVICE}}
|
||||
|
||||
**Source:** Dietary master data selected for patient
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<div style="margin-bottom:8px;"><strong>Dietary Advice:</strong></div>
|
||||
<ol style="margin:0 0 10px 20px;">
|
||||
<li><strong>High Protein Diet:</strong> Include eggs, chicken, fish, pulses</li>
|
||||
<li><strong>Avoid Spicy Food:</strong> No chilli, pepper, masala</li>
|
||||
<li><strong>Low Salt:</strong> Reduce salt intake to less than 5g per day</li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
**If no data:** Empty string (not shown)
|
||||
|
||||
---
|
||||
|
||||
## {{DISCHARGE_INSTRUCTIONS}}
|
||||
|
||||
**Source:** `ipd_discharge_instructions` table
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h4 style="margin:16px 0 8px 0;">Discharge Advice/Instructions/Summary</h4>
|
||||
|
||||
<!-- Dietary Advice section (if food IDs selected) -->
|
||||
<div style="margin-bottom:8px;"><strong>Dietary Advice:</strong></div>
|
||||
<ol style="margin:0 0 10px 20px;">
|
||||
<li><strong>Low Fat:</strong> Avoid fried foods</li>
|
||||
</ol>
|
||||
|
||||
<!-- Other Advice (if present) -->
|
||||
<div style="margin-bottom:8px;"><strong>Other Advice:</strong> Take adequate rest</div>
|
||||
|
||||
<!-- Main instructions (rich text from comp_remark) -->
|
||||
<div>
|
||||
Additional discharge instructions and advice text (can contain HTML formatting)
|
||||
</div>
|
||||
|
||||
<!-- Review After (if present) -->
|
||||
<div style="margin-top:6px;">Review after 7 Days (14-06-2026) days / as and when required</div>
|
||||
|
||||
<!-- Footer text (if present) -->
|
||||
<div style="margin-top:6px;">Emergency contact information</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## {{SIGNATURE_BLOCK}}
|
||||
|
||||
**Source:** Auto-generated signature table
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<table border="0" cellpadding="1" cellspacing="1" style="width:100%">
|
||||
<tbody>
|
||||
<tr><td> </td><td> </td><td> </td></tr>
|
||||
<tr>
|
||||
<td style="text-align:left; vertical-align:middle">_________________________</td>
|
||||
<td>_________________________</td>
|
||||
<td style="text-align:right; vertical-align:middle">_________________________</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:center; vertical-align:middle">Signature of Consultant</td>
|
||||
<td style="text-align:center; vertical-align:middle">Signature of Medical Officer</td>
|
||||
<td style="text-align:center; vertical-align:middle">Signature of Receiver / Date</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Placeholders (Not in Section Content)
|
||||
|
||||
### {{PATIENT_INFO_TABLE}}
|
||||
|
||||
**Source:** `buildAutoDischargeSummaryTable()`
|
||||
**HTML Structure:**
|
||||
```html
|
||||
<h2 style="text-align:center;margin:1px;padding:0px;">Discharge Summary</h2>
|
||||
<!-- OR dynamic header based on ipd_status -->
|
||||
<h2 style="text-align:center;margin:1px;padding:0px;">Leave Against Medical Advice</h2>
|
||||
|
||||
<hr style="margin:1px;padding:0px;" />
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td width="150px"><b>Name</b></td>
|
||||
<td width="250px">SUNITA DEVI</td>
|
||||
<td width="150px"><b>UHID</b></td>
|
||||
<td width="250px">P26061331171</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Age & Gender</b></td>
|
||||
<td width="250px">40 Year / Female</td>
|
||||
<td width="150px"><b>IPD No.</b></td>
|
||||
<td width="250px">A26061007144</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Guardian</b></td>
|
||||
<td width="250px">Wife of JOGENDRA KUMAR</td>
|
||||
<td width="150px"><b>Admission</b></td>
|
||||
<td width="250px">04-06-2026</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Phone No.</b></td>
|
||||
<td width="250px">9520497324</td>
|
||||
<td width="150px"><b>Discharge</b></td>
|
||||
<td width="250px">07-06-2026</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Address</b></td>
|
||||
<td width="250px">MALDHAN*****</td>
|
||||
<td width="150px"><b>Org. Name</b></td>
|
||||
<td width="250px">Direct</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Department</b></td>
|
||||
<td width="250px">General Surgery</td>
|
||||
<td width="150px"></td>
|
||||
<td width="250px"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr style="margin:1px;padding:0px;" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
### Common CSS Classes Used:
|
||||
- `margin:16px 0 8px 0` - Standard heading margins
|
||||
- `margin:0 0 10px 20px` - List indentation
|
||||
- `color:#475569` - Secondary/remark text color
|
||||
- `border-collapse:collapse` - Table borders
|
||||
- `text-align:left` - Default text alignment
|
||||
- `vertical-align:middle` - Table cell alignment
|
||||
|
||||
### HTML Tags Used:
|
||||
- `<h4>` - Section headers (16px top margin, 8px bottom)
|
||||
- `<h2>` - Main title headers
|
||||
- `<div>` - Content blocks
|
||||
- `<ul>` / `<ol>` - Lists
|
||||
- `<table>` - Structured data
|
||||
- `<b>` / `<strong>` - Bold text
|
||||
- `<span>` - Inline styling (remarks)
|
||||
- `<br>` - Line breaks
|
||||
|
||||
---
|
||||
|
||||
## Debug Endpoints
|
||||
|
||||
To view the actual rendered HTML:
|
||||
|
||||
1. **View full HTML source:**
|
||||
```
|
||||
/Ipd_discharge/debug_discharge_html/{ipdId}
|
||||
```
|
||||
Example: `http://localhost:8080/Ipd_discharge/debug_discharge_html/1`
|
||||
|
||||
2. **View database field values:**
|
||||
```
|
||||
/Ipd_discharge/debug_ipd_fields/{ipdId}
|
||||
```
|
||||
Example: `http://localhost:8080/Ipd_discharge/debug_ipd_fields/1`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All placeholders support both `{{TOKEN}}` and `{TOKEN}` formats
|
||||
- All placeholders also support lowercase and Ucfirst variants
|
||||
- Empty sections are NOT rendered (no HTML output)
|
||||
- Rich text fields preserve HTML formatting from CKEditor
|
||||
- All user input is escaped using `esc()` for XSS protection
|
||||
- Date formats: `dd-mm-yyyy` (e.g., 07-06-2026)
|
||||
- Time formats: `hh:mm AM/PM` (e.g., 02:44 PM)
|
||||
|
||||
---
|
||||
|
||||
**Generated:** 2026-06-07
|
||||
**File Location:** `d:\Workplace\HMS_CI4_OLD\DISCHARGE_PLACEHOLDER_HTML_REFERENCE.md`
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Discharge Summary HTML Editor Fix
|
||||
|
||||
## Problem
|
||||
After converting textarea controls to HTML editors (TinyMCE/similar) in the discharge form, saved discharge summaries were not appearing in the generated PDF.
|
||||
|
||||
## Root Cause
|
||||
The `normalizeRichText()` function at line 1591 in `Ipd_discharge.php` was stripping ALL HTML tags using `strip_tags()`. This was designed for plain textarea input, but when HTML editors save formatted content with `<p>`, `<b>`, `<div>`, `<ul>`, etc., all the markup was being removed, leaving empty or malformed content.
|
||||
|
||||
## Affected Fields
|
||||
- Presenting Complaints (complaint_remark)
|
||||
- Final Diagnosis (diagnosis_remark)
|
||||
- Course in Hospital (course_remark)
|
||||
- Summary of Key Investigations (inhos_remark)
|
||||
- Other/Systemic Examinations (systemic exam text)
|
||||
- Other Examinations/Provisional Diagnosis (other_exam_text)
|
||||
|
||||
## Solution
|
||||
Modified the following methods in `app/Controllers/Ipd_discharge.php`:
|
||||
|
||||
### 1. Enhanced `normalizeRichText()` (line 1591)
|
||||
- Detects if content contains HTML tags from HTML editor
|
||||
- **If HTML detected**: Preserves HTML structure, only cleans whitespace
|
||||
- **If plain text**: Uses original logic (strip tags, convert to plain text)
|
||||
- Backward compatible with existing plain textarea fields
|
||||
|
||||
### 2. Added `renderRichText()` (new method after normalizeRichText)
|
||||
- Detects HTML vs plain text content
|
||||
- **If HTML**: Returns content as-is for HTML rendering
|
||||
- **If plain text**: Applies `esc()` and `nl2br()` for safe output
|
||||
|
||||
### 3. Updated rendering methods
|
||||
- `buildNarrativeSection()`: Changed `nl2br(esc($remark))` to `$this->renderRichText($remark)`
|
||||
- Systemic exam section: Changed `nl2br(esc($value))` to `$this->renderRichText($value)`
|
||||
- Other exam section: Changed `nl2br(esc($otherExamText))` to `$this->renderRichText($otherExamText)`
|
||||
- Key investigations section: Changed `nl2br(esc($inhosRemark))` to `$this->renderRichText($inhosRemark)`
|
||||
|
||||
## Changes Made
|
||||
```
|
||||
File: app/Controllers/Ipd_discharge.php
|
||||
- Line ~1591: Enhanced normalizeRichText() to preserve HTML
|
||||
- Line ~1625: Added new renderRichText() helper method
|
||||
- Line ~1768: Updated buildNarrativeSection() to use renderRichText()
|
||||
- Line ~1932: Updated systemic exam rendering to use renderRichText()
|
||||
- Line ~2094: Updated other exam rendering to use renderRichText()
|
||||
- Line ~2113: Updated key investigations rendering to use renderRichText()
|
||||
```
|
||||
|
||||
## Testing
|
||||
1. Open discharge form: `/Ipd_discharge/ipd_select/1007170`
|
||||
2. Verify HTML editor content is saved correctly
|
||||
3. Preview discharge PDF
|
||||
4. Check that formatted content (bold, lists, paragraphs) appears correctly
|
||||
5. Test with both HTML editor and plain textarea fields (backward compatibility)
|
||||
|
||||
## Backward Compatibility
|
||||
✅ Existing plain textarea fields continue to work
|
||||
✅ HTML editor fields now render correctly
|
||||
✅ No database changes required
|
||||
✅ Automatic detection of content type (HTML vs plain text)
|
||||
|
||||
## Security
|
||||
- HTML editor content is NOT escaped (assumes HTML editor provides safe content)
|
||||
- Plain text content continues to be escaped with `esc()` function
|
||||
- No XSS risk if HTML editor properly sanitizes input
|
||||
|
|
@ -1,492 +0,0 @@
|
|||
# IPD Discharge Template System - CI3 to CI4 Migration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The CI4 discharge template system now **correctly matches** CI3 behavior where:
|
||||
- **Patient demographic table is NOT auto-generated in content**
|
||||
- **Content contains ONLY clinical sections** (complaints, diagnosis, medications, etc.)
|
||||
- **Templates control the entire layout** using tokens
|
||||
|
||||
---
|
||||
|
||||
## How It Works (CI3 vs CI4)
|
||||
|
||||
### CI3 System (Old - chamunda_discharge_template_html.php)
|
||||
|
||||
```php
|
||||
<!-- Patient table was HARDCODED in template -->
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>Name</b></td>
|
||||
<td><?= ucwords($ipd_master[0]->p_fname) ?></td>
|
||||
<td><b>UHID</b></td>
|
||||
<td><?= $ipd_master[0]->p_code ?></td>
|
||||
</tr>
|
||||
<!-- More rows... -->
|
||||
</table>
|
||||
|
||||
<!-- Clinical sections were injected as variables -->
|
||||
<?php if (isset($FinalDiagnosis)) { ?>
|
||||
<?= $FinalDiagnosis ?>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (isset($Surgery)) { ?>
|
||||
<?= $Surgery ?>
|
||||
<?php } ?>
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Template was a **PHP file** with hardcoded HTML for patient info
|
||||
- Content variables (`$FinalDiagnosis`, `$Surgery`, etc.) were **HTML fragments**
|
||||
- Template decided what to show and in what order
|
||||
|
||||
### CI4 System (New - Database Templates with Tokens)
|
||||
|
||||
```html
|
||||
<!-- Option 1: Build your own patient table using tokens -->
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>Name</b></td><td>{{PATIENT_NAME}}</td>
|
||||
<td><b>UHID</b></td><td>{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Age/Gender</b></td><td>{{AGE_GENDER}}</td>
|
||||
<td><b>IPD No</b></td><td>{{IPD_CODE}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Option 2: Use pre-built patient table token -->
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
|
||||
<!-- All clinical sections -->
|
||||
{{CONTENT}}
|
||||
|
||||
<!-- Or pick specific sections -->
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{SURGERY}}
|
||||
{{PROCEDURE}}
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Templates are stored in **database** (`discharge_template` table)
|
||||
- Use **{{TOKEN}}** syntax instead of PHP variables
|
||||
- Content ({{CONTENT}}) contains **ONLY clinical sections** (NO patient table)
|
||||
- Patient table available as `{{PATIENT_INFO_TABLE}}` token
|
||||
|
||||
---
|
||||
|
||||
## Available Tokens
|
||||
|
||||
### Patient Demographic Tokens
|
||||
|
||||
| Token | Description | Example Output |
|
||||
|-------|-------------|----------------|
|
||||
| `{{PATIENT_NAME}}` | Full patient name | KAMLA SINGH |
|
||||
| `{{UHID}}` | Patient UHID/code | P26051000001 |
|
||||
| `{{IPD_CODE}}` | IPD admission number | A26050000001 |
|
||||
| `{{AGE_GENDER}}` | Age and gender | 26 Year / Female |
|
||||
| `{{GUARDIAN}}` | Guardian relation and name | W/O of RAM SINGH |
|
||||
| `{{GUARDIAN_RELATION}}` | Guardian relation only | W/O of |
|
||||
| `{{GUARDIAN_NAME}}` | Guardian name only | RAM SINGH |
|
||||
| `{{PATIENT_ADDRESS}}` | Full address | KASHIPUR, District, State |
|
||||
| `{{PATIENT_PHONE}}` | Patient phone number | 9876543210 |
|
||||
|
||||
### IPD Information Tokens
|
||||
|
||||
| Token | Description | Example Output |
|
||||
|-------|-------------|----------------|
|
||||
| `{{DEPARTMENT}}` | Department name | General Medicine |
|
||||
| `{{ADMIT_DATE}}` | Admission date | 23-05-2026 |
|
||||
| `{{DISCHARGE_DATE}}` | Discharge date | 05-06-2026 |
|
||||
| `{{ADMIT_DATE_ONLY}}` | Admission date (same as ADMIT_DATE) | 23-05-2026 |
|
||||
| `{{DISCHARGE_DATE_ONLY}}` | Discharge date (same as DISCHARGE_DATE) | 05-06-2026 |
|
||||
| `{{ADMISSION_TIME}}` | Admission time | 10:30 AM |
|
||||
| `{{DISCHARGE_TIME}}` | Discharge time | 03:45 PM |
|
||||
| `{{ADMIT_TIME}}` | Admission time (same as ADMISSION_TIME) | 10:30 AM |
|
||||
| `{{INSURANCE_COMPANY}}` | Insurance company name | Star Health Insurance |
|
||||
| `{{DOCTOR_NAMES}}` | Treating doctor(s) with specialties | Dr. Rajesh Kumar [General Medicine], Dr. Priya Sharma [Cardiology] |
|
||||
| `{{DOCTOR_NAME}}` | Same as DOCTOR_NAMES | Dr. Rajesh Kumar [General Medicine] |
|
||||
|
||||
### Hospital Information Tokens
|
||||
|
||||
| Token | Description |
|
||||
|-------|-------------|
|
||||
| `{{H_Name}}` | Hospital name |
|
||||
| `{{hospital_address}}` | Combined hospital address |
|
||||
| `{{hospital_phone}}` | Hospital phone number |
|
||||
| `{{hospital_email}}` | Hospital email |
|
||||
| `{{H_logo}}` | Logo filename |
|
||||
| `{{H_logo_abs}}` | Absolute path to logo |
|
||||
|
||||
### Pre-Built Table Token
|
||||
|
||||
| Token | Description |
|
||||
|-------|-------------|
|
||||
| `{{PATIENT_INFO_TABLE}}` | Complete patient demographic table (matches CI3 format) |
|
||||
|
||||
### Content Tokens
|
||||
|
||||
| Token | Description |
|
||||
|-------|-------------|
|
||||
| `{{CONTENT}}` | **All clinical sections** (complaints, diagnosis, course, medications, etc.) |
|
||||
|
||||
### Individual Section Tokens
|
||||
|
||||
| Token | Description |
|
||||
|-------|-------------|
|
||||
| `{{FINAL_DIAGNOSIS}}` | Final diagnosis at discharge |
|
||||
| `{{SURGERY}}` | Surgical procedures performed |
|
||||
| `{{PROCEDURE}}` | Medical procedures |
|
||||
| `{{PRESENTING_COMPLAINTS}}` | Complaints with duration |
|
||||
| `{{GENERAL_EXAM_ADMISSION}}` | General examination on admission |
|
||||
| `{{CLINICAL_INVESTIGATION_REPORTS}}` | Lab and imaging reports |
|
||||
| `{{COURSE_IN_HOSPITAL}}` | Course during hospitalization |
|
||||
| `{{EXAMINATION_ON_DISCHARGE}}` | Examination findings at discharge |
|
||||
| `{{DISCHARGE_MEDICATIONS}}` | Medications prescribed |
|
||||
| `{{DIETARY_ADVICE}}` | Diet instructions |
|
||||
| `{{DISCHARGE_INSTRUCTIONS}}` | Discharge instructions and advice |
|
||||
| `{{PAIN_MEASUREMENT_SCALE}}` | Pain assessment |
|
||||
| `{{DRUG_ALLERGY_ADR}}` | Drug allergies and ADR history |
|
||||
| `{{CO_MORBIDITIES}}` | Co-morbid conditions |
|
||||
| `{{PERSONAL_HISTORY}}` | Personal history (smoking, alcohol, etc.) |
|
||||
|
||||
### Meta Tokens
|
||||
|
||||
| Token | Description |
|
||||
|-------|-------------|
|
||||
| `{{CURRENT_DATE}}` | Current date (dd-mm-yyyy) |
|
||||
| `{{PRINT_TIME}}` | Current timestamp |
|
||||
|
||||
---
|
||||
|
||||
## Template Examples
|
||||
|
||||
### Example 1: Blank Template (CI3-like, Just Content)
|
||||
|
||||
```html
|
||||
<!-- Empty template -->
|
||||
```
|
||||
|
||||
**Output:** Only clinical sections (complaints, diagnosis, medications, etc.)
|
||||
**No patient table** is shown automatically!
|
||||
|
||||
---
|
||||
|
||||
### Example 2: Minimal Template with Patient Table Token
|
||||
|
||||
```html
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
{{CONTENT}}
|
||||
```
|
||||
|
||||
**Output:** Patient demographic table + all clinical sections
|
||||
**Closest to CI3 default behavior**
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Custom Layout with Individual Tokens
|
||||
|
||||
```html
|
||||
<div style="border: 2px solid #333; padding: 20px;">
|
||||
<h1 style="text-align: center;">{{H_Name}}</h1>
|
||||
<h2 style="text-align: center;">Discharge Summary</h2>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td width="25%"><b>Name:</b></td>
|
||||
<td width="25%">{{PATIENT_NAME}}</td>
|
||||
<td width="25%"><b>UHID:</b></td>
|
||||
<td width="25%">{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Age/Gender:</b></td>
|
||||
<td>{{AGE_GENDER}}</td>
|
||||
<td><b>IPD No:</b></td>
|
||||
<td>{{IPD_CODE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Admission:</b></td>
|
||||
<td>{{ADMIT_DATE}}</td>
|
||||
<td><b>Discharge:</b></td>
|
||||
<td>{{DISCHARGE_DATE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Phone:</b></td>
|
||||
<td>{{PATIENT_PHONE}}</td>
|
||||
<td><b>Insurance:</b></td>
|
||||
<td>{{INSURANCE_COMPANY}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Department:</b></td>
|
||||
<td>{{DEPARTMENT}}</td>
|
||||
<td><b>Doctor(s):</b></td>
|
||||
<td>{{DOCTOR_NAMES}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
{{CONTENT}}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Output:** Custom-styled discharge with full control over layout, including phone, insurance, and doctor information
|
||||
|
||||
---
|
||||
|
||||
### Example 4: Selective Sections (Pick What to Show)
|
||||
|
||||
```html
|
||||
<h2>Discharge Summary</h2>
|
||||
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
|
||||
<div class="clinical-sections">
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{SURGERY}}
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
{{DISCHARGE_INSTRUCTIONS}}
|
||||
</div>
|
||||
|
||||
<!-- Deliberately omitting other sections -->
|
||||
```
|
||||
|
||||
**Output:** Shows ONLY diagnosis, surgery, medications, and instructions
|
||||
**Skips** complaints, course, examination, etc.
|
||||
|
||||
---
|
||||
|
||||
### Example 5: CI3 Chamunda Template Converted to CI4
|
||||
|
||||
```html
|
||||
<style>
|
||||
body { font-family: Verdana, Geneva, Tahoma, sans-serif; }
|
||||
table { border-collapse: collapse; }
|
||||
td { padding: 0.35em; font-size: 10pt; }
|
||||
</style>
|
||||
|
||||
<h2 style="text-align:center; margin:1px; padding:0px;">Discharge Summary</h2>
|
||||
<hr style="margin:1px; padding:0px;" />
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td width="150px"><b>Name</b></td>
|
||||
<td width="250px">{{PATIENT_NAME}}</td>
|
||||
<td width="150px"><b>UHID</b></td>
|
||||
<td width="250px">{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Age & Gender</b></td>
|
||||
<td width="250px">{{AGE_GENDER}}</td>
|
||||
<td width="150px"><b>IPD No.</b></td>
|
||||
<td width="250px">{{IPD_CODE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Guardian</b></td>
|
||||
<td width="250px">{{GUARDIAN}}</td>
|
||||
<td width="150px"><b>Admission</b></td>
|
||||
<td width="250px">{{ADMIT_DATE}} {{ADMISSION_TIME}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Phone No.</b></td>
|
||||
<td width="250px">{{hospital_phone}}</td>
|
||||
<td width="150px"><b>Discharge</b></td>
|
||||
<td width="250px">{{DISCHARGE_DATE}} {{DISCHARGE_TIME}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Address</b></td>
|
||||
<td width="250px">{{PATIENT_ADDRESS}}</td>
|
||||
<td width="150px"><b>Org. Name</b></td>
|
||||
<td width="250px">Direct</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="150px"><b>Department</b></td>
|
||||
<td width="250px">{{DEPARTMENT}}</td>
|
||||
<td width="150px"></td>
|
||||
<td width="250px"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr style="margin:1px; padding:0px;" />
|
||||
|
||||
{{CONTENT}}
|
||||
```
|
||||
|
||||
**Output:** Exact replica of CI3 Chamunda template using CI4 tokens!
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps from CI3 to CI4
|
||||
|
||||
### Step 1: Convert PHP Template to Token Template
|
||||
|
||||
**Old CI3 Template:**
|
||||
```php
|
||||
<h2><?= $h1_head ?></h2>
|
||||
<p>Patient: <?= ucwords($ipd_master[0]->p_fname) ?></p>
|
||||
<?php if (isset($FinalDiagnosis)) { ?>
|
||||
<?= $FinalDiagnosis ?>
|
||||
<?php } ?>
|
||||
```
|
||||
|
||||
**New CI4 Template:**
|
||||
```html
|
||||
<h2>Discharge Summary</h2>
|
||||
<p>Patient: {{PATIENT_NAME}}</p>
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
```
|
||||
|
||||
### Step 2: Replace PHP Variables with Tokens
|
||||
|
||||
| CI3 Variable | CI4 Token |
|
||||
|--------------|-----------|
|
||||
| `$ipd_master[0]->p_fname` | `{{PATIENT_NAME}}` |
|
||||
| `$ipd_master[0]->p_code` | `{{UHID}}` |
|
||||
| `$ipd_master[0]->ipd_code` | `{{IPD_CODE}}` |
|
||||
| `$ipd_master[0]->str_age . ' / ' . $xgender` | `{{AGE_GENDER}}` |
|
||||
| `$person->mphone1` | `{{PATIENT_PHONE}}` |
|
||||
| `$ipd_master[0]->ins_company_name` | `{{INSURANCE_COMPANY}}` |
|
||||
| `$doc_list_main_sign` (doctor names) | `{{DOCTOR_NAMES}}` |
|
||||
| `$FinalDiagnosis` | `{{FINAL_DIAGNOSIS}}` |
|
||||
| `$Surgery` | `{{SURGERY}}` |
|
||||
| `$Procedure` | `{{PROCEDURE}}` |
|
||||
| `$discharge_complaint` | `{{PRESENTING_COMPLAINTS}}` |
|
||||
| `$Course_in_the_hospital` | `{{COURSE_IN_HOSPITAL}}` |
|
||||
| `$Discharge_Medications` | `{{DISCHARGE_MEDICATIONS}}` |
|
||||
| `$Discharge_Instructions` | `{{DISCHARGE_INSTRUCTIONS}}` |
|
||||
|
||||
### Step 3: Remove PHP Conditionals
|
||||
|
||||
**Before:**
|
||||
```php
|
||||
<?php if (isset($Surgery)) { ?>
|
||||
<?= $Surgery ?>
|
||||
<?php } ?>
|
||||
```
|
||||
|
||||
**After:**
|
||||
```html
|
||||
{{SURGERY}}
|
||||
```
|
||||
|
||||
Tokens automatically handle empty values (nothing shown if no data).
|
||||
|
||||
### Step 4: Save to Database
|
||||
|
||||
1. Go to Template Management UI (if available)
|
||||
2. **OR** Insert directly into `discharge_template` table:
|
||||
|
||||
```sql
|
||||
INSERT INTO discharge_template (template_name, template_html, is_default, page_size)
|
||||
VALUES ('Chamunda Template', '<your converted template HTML>', 1, 'A4');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Template
|
||||
|
||||
### Test Case 1: Blank Template
|
||||
1. Create template with NO content (empty string)
|
||||
2. View discharge: `http://localhost:8080/Ipd_discharge/show_discharge/1/1?tpl=1`
|
||||
3. **Expected:** ONLY clinical sections (no patient table)
|
||||
|
||||
### Test Case 2: Just {{CONTENT}}
|
||||
1. Create template with: `{{CONTENT}}`
|
||||
2. View discharge
|
||||
3. **Expected:** ONLY clinical sections (no patient table)
|
||||
|
||||
### Test Case 3: Patient Table + Content
|
||||
1. Create template with: `{{PATIENT_INFO_TABLE}}{{CONTENT}}`
|
||||
2. View discharge
|
||||
3. **Expected:** Patient table + clinical sections
|
||||
|
||||
### Test Case 4: Custom Layout
|
||||
1. Create template with individual tokens
|
||||
2. View discharge
|
||||
3. **Expected:** Your custom layout with data filled in
|
||||
|
||||
---
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue 1: Patient table still showing when template is blank
|
||||
|
||||
**Cause:** Old cached discharge content in database
|
||||
**Solution:** Clear cached content:
|
||||
```bash
|
||||
php clear_discharge_content.php [ipd_id]
|
||||
```
|
||||
|
||||
### Issue 2: Token not replaced (shows {{TOKEN_NAME}})
|
||||
|
||||
**Cause:** Token name is misspelled or doesn't exist
|
||||
**Solution:** Check available tokens list above (case-insensitive)
|
||||
|
||||
### Issue 3: Want old CI3 behavior exactly
|
||||
|
||||
**Solution:** Use this template:
|
||||
```html
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
{{CONTENT}}
|
||||
```
|
||||
|
||||
### Issue 4: Need to show patient table in some templates but not others
|
||||
|
||||
**Solution:**
|
||||
- Template A: Include `{{PATIENT_INFO_TABLE}}`
|
||||
- Template B: Omit it (blank or just `{{CONTENT}}`)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO:
|
||||
- Use `{{PATIENT_INFO_TABLE}}` for quick CI3-style patient demographics
|
||||
- Use individual tokens for custom layouts
|
||||
- Use `{{CONTENT}}` to show all clinical sections at once
|
||||
- Clear cached content after template changes
|
||||
- Test with different IPD cases (with/without surgery, delivery, etc.)
|
||||
|
||||
### ❌ DON'T:
|
||||
- Don't expect patient table to auto-appear (it's a token now!)
|
||||
- Don't use PHP code in templates (use tokens instead)
|
||||
- Don't manually edit `ipd_discharge.content` column (use regenerate button)
|
||||
- Don't forget to set `is_default=1` for your primary template
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Viewing Options
|
||||
|
||||
| URL | Description |
|
||||
|-----|-------------|
|
||||
| `/Ipd_discharge/show_discharge/{id}/1` | PDF with default template |
|
||||
| `/Ipd_discharge/show_discharge/{id}/1?tpl=5` | PDF with template ID 5 |
|
||||
| `/Ipd_discharge/preview_discharge_report/{id}` | Preview in browser |
|
||||
| `/Ipd_discharge/preview_discharge_report/{id}?tpl=5` | Preview with template ID 5 |
|
||||
| `/Ipd_discharge/preview_discharge_report/{id}?regen=1` | Force regenerate content |
|
||||
|
||||
### Database Tables
|
||||
|
||||
- `discharge_template` - Template definitions
|
||||
- `ipd_discharge` - Generated content (cached)
|
||||
- `ipd_discharge_complaint` - Complaints data
|
||||
- `ipd_discharge_diagnosis` - Diagnosis data
|
||||
- `ipd_discharge_drug` - Medications data
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**CI3 Way:**
|
||||
- Template = PHP file with hardcoded patient table
|
||||
- Content = Variables injected into template
|
||||
|
||||
**CI4 Way (Corrected):**
|
||||
- Template = Database record with {{TOKENS}}
|
||||
- Content = ONLY clinical sections (no patient table by default)
|
||||
- Patient table = Available as `{{PATIENT_INFO_TABLE}}` token
|
||||
|
||||
**Result:** Full control over layout, matches CI3 flexibility! 🎉
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
# IPD Discharge Template Placeholders
|
||||
|
||||
This document lists all available placeholders that can be used in IPD discharge templates.
|
||||
|
||||
## How to Use Placeholders
|
||||
|
||||
Placeholders can be used in templates using double curly braces: `{{PLACEHOLDER_NAME}}`
|
||||
|
||||
Example:
|
||||
```html
|
||||
<h2>{{HOSPITAL_NAME}}</h2>
|
||||
<p>Patient: {{PATIENT_NAME}} ({{UHID}})</p>
|
||||
{{CONTENT}}
|
||||
{{SIGNATURE_BLOCK}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hospital Information
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|------------|-------------|---------|
|
||||
| `{{H_Name}}` or `{{HOSPITAL_NAME}}` | Hospital name | "City Hospital" |
|
||||
| `{{H_address_1}}` | Hospital address line 1 | "123 Main Street" |
|
||||
| `{{H_address_2}}` | Hospital address line 2 | "Medical District" |
|
||||
| `{{H_phone_No}}` or `{{HOSPITAL_PHONE}}` | Hospital phone number | "+91-123-4567890" |
|
||||
| `{{H_Email}}` or `{{HOSPITAL_EMAIL}}` | Hospital email | "info@hospital.com" |
|
||||
| `{{H_logo}}` | Hospital logo filename | "logo.png" |
|
||||
| `{{H_logo_abs}}` | Absolute path to logo | "/path/to/public/assets/images/logo.png" |
|
||||
| `{{HOSPITAL_ADDRESS}}` | Combined address (line 1 + line 2) | "123 Main Street, Medical District" |
|
||||
|
||||
---
|
||||
|
||||
## Patient Information
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|------------|-------------|---------|
|
||||
| `{{PATIENT_TITLE}}` | Patient title/prefix | "Mr." / "Mrs." / "Miss" |
|
||||
| `{{PATIENT_NAME}}` | Patient full name | "John Doe" |
|
||||
| `{{UHID}}` | Patient UHID/Registration number | "UHID123456" |
|
||||
| `{{IPD_CODE}}` | IPD admission code | "IPD001234" |
|
||||
| `{{AGE_GENDER}}` | Age and gender | "45 Years / Male" |
|
||||
| `{{GUARDIAN}}` or `{{GUARDIAN_NAME}}` | Guardian/relative name | "S/o Ramesh Kumar" |
|
||||
| `{{GUARDIAN_RELATION}}` | Guardian relation | "S/o" or "W/o" |
|
||||
| `{{PATIENT_ADDRESS}}` | Patient full address | "456 Park Lane, City" |
|
||||
| `{{PATIENT_PHONE}}` | Patient phone number | "+91-9876543210" |
|
||||
| `{{INSURANCE_COMPANY}}` | Insurance company name | "HDFC Ergo" or "Direct" |
|
||||
| `{{PATIENT_INFO_TABLE}}` | Auto-generated patient info table | (HTML table) |
|
||||
|
||||
---
|
||||
|
||||
## Admission & Discharge Dates
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|------------|-------------|---------|
|
||||
| `{{ADMIT_DATE}}` | Admission date | "05-06-2026" |
|
||||
| `{{DISCHARGE_DATE}}` | Discharge date | "07-06-2026" |
|
||||
| `{{ADMIT_DATE_ONLY}}` | Admission date (alternative) | "05-06-2026" |
|
||||
| `{{DISCHARGE_DATE_ONLY}}` | Discharge date (alternative) | "07-06-2026" |
|
||||
| `{{ADMISSION_TIME}}` | Admission time | "10:30 AM" |
|
||||
| `{{DISCHARGE_TIME}}` | Discharge time | "04:15 PM" |
|
||||
| `{{ADMIT_TIME}}` | Admission time (alternative) | "10:30 AM" |
|
||||
| `{{CURRENT_DATE}}` | Current date (when PDF generated) | "07-06-2026" |
|
||||
| `{{PRINT_TIME}}` | PDF generation timestamp | "07-06-2026 16:45:30" |
|
||||
|
||||
---
|
||||
|
||||
## Medical Information
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|------------|-------------|---------|
|
||||
| `{{DEPARTMENT}}` | Admission department | "General Medicine" |
|
||||
| `{{DOCTOR_NAMES}}` or `{{DOCTOR_NAME}}` | Treating doctor(s) | "Dr. Sharma, Dr. Patel" |
|
||||
|
||||
---
|
||||
|
||||
## Clinical Content Sections
|
||||
|
||||
### Full Content
|
||||
| Placeholder | Description |
|
||||
|------------|-------------|
|
||||
| `{{CONTENT}}` | **Complete discharge content** - includes all sections below |
|
||||
|
||||
### Individual Sections
|
||||
| Placeholder | Description |
|
||||
|------------|-------------|
|
||||
| `{{DISCHARGE_SUMMARY}}` | Discharge status header (e.g., "Discharged", "LAMA") |
|
||||
| `{{PRESENTING_COMPLAINTS}}` | Presenting complaints and reason for admission |
|
||||
| `{{PAIN_MEASUREMENT_SCALE}}` | Pain scale measurement (if recorded) |
|
||||
| `{{GENERAL_EXAM_ADMISSION}}` | General examination findings on admission |
|
||||
| `{{PERSONAL_HISTORY}}` | Personal history (smoking, alcohol, etc.) |
|
||||
| `{{DRUG_ALLERGY_ADR}}` | Drug allergy status and ADR history |
|
||||
| `{{CO_MORBIDITIES}}` | Co-morbidities |
|
||||
| `{{CLINICAL_INVESTIGATION_REPORTS}}` | Lab reports, X-rays, CT/MRI results |
|
||||
| `{{FINAL_DIAGNOSIS}}` | Final diagnosis |
|
||||
| `{{COURSE_IN_HOSPITAL}}` | Course in the hospital / treatment details |
|
||||
| `{{EXAMINATION_ON_DISCHARGE}}` | Physical examination on discharge |
|
||||
| `{{SURGERY}}` | Surgical procedures performed |
|
||||
| `{{PROCEDURE}}` | Other procedures performed |
|
||||
| `{{DISCHARGE_MEDICATIONS}}` | Discharge medications table |
|
||||
|
||||
### Discharge Instructions
|
||||
| Placeholder | Description |
|
||||
|------------|-------------|
|
||||
| `{{DISCHARGE_INSTRUCTIONS}}` | **Full instruction section** (Dietary + Other + Discharge Summary) |
|
||||
| `{{DIETARY_ADVICE}}` | Dietary advice (selected food items) |
|
||||
| `{{OTHER_ADVICE}}` | Other Advice field only |
|
||||
| `{{INSTRUCTION_REMARK}}` or `{{DISCHARGE_ADVICE}}` | Discharge Summary field only |
|
||||
|
||||
**NEW FEATURE:** You can now use `{{OTHER_ADVICE}}` and `{{DISCHARGE_ADVICE}}` separately to control layout!
|
||||
|
||||
Example:
|
||||
```html
|
||||
<h4>Other Advice</h4>
|
||||
{{OTHER_ADVICE}}
|
||||
|
||||
<h4>Discharge Summary</h4>
|
||||
{{DISCHARGE_ADVICE}}
|
||||
```
|
||||
|
||||
Or use the combined section:
|
||||
```html
|
||||
{{DISCHARGE_INSTRUCTIONS}}
|
||||
```
|
||||
|
||||
### Signature Block
|
||||
| Placeholder | Description |
|
||||
|------------|-------------|
|
||||
| `{{SIGNATURE_BLOCK}}` | Signature table for Consultant, Medical Officer, and Receiver |
|
||||
|
||||
---
|
||||
|
||||
## Legacy Aliases
|
||||
|
||||
These are provided for CI3 compatibility:
|
||||
|
||||
| Legacy Placeholder | Modern Equivalent |
|
||||
|-------------------|------------------|
|
||||
| `{FinalDiagnosis}` | `{{FINAL_DIAGNOSIS}}` |
|
||||
| `{Surgery}` | `{{SURGERY}}` |
|
||||
| `{Procedure}` | `{{PROCEDURE}}` |
|
||||
| `{personal_history}` | `{{PERSONAL_HISTORY}}` |
|
||||
| `{discharge_complaint}` | `{{PRESENTING_COMPLAINTS}}` |
|
||||
| `{discharge_general_exam}` | `{{GENERAL_EXAM_ADMISSION}}` |
|
||||
| `{Course_in_the_hospital}` | `{{COURSE_IN_HOSPITAL}}` |
|
||||
| `{Discharge_Medications}` | `{{DISCHARGE_MEDICATIONS}}` |
|
||||
| `{diet_advice}` | `{{DIETARY_ADVICE}}` |
|
||||
| `{Discharge_Instructions}` | `{{DISCHARGE_INSTRUCTIONS}}` |
|
||||
|
||||
---
|
||||
|
||||
## Example Template
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Discharge Summary</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.header { text-align: center; margin-bottom: 20px; }
|
||||
.patient-info { border: 1px solid #ccc; padding: 10px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>{{HOSPITAL_NAME}}</h2>
|
||||
<p>{{HOSPITAL_ADDRESS}}</p>
|
||||
<p>Phone: {{HOSPITAL_PHONE}} | Email: {{HOSPITAL_EMAIL}}</p>
|
||||
</div>
|
||||
|
||||
<div class="patient-info">
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Patient Name:</strong></td>
|
||||
<td>{{PATIENT_NAME}}</td>
|
||||
<td><strong>UHID:</strong></td>
|
||||
<td>{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Age/Gender:</strong></td>
|
||||
<td>{{AGE_GENDER}}</td>
|
||||
<td><strong>IPD Code:</strong></td>
|
||||
<td>{{IPD_CODE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Admission Date:</strong></td>
|
||||
<td>{{ADMIT_DATE}} {{ADMISSION_TIME}}</td>
|
||||
<td><strong>Discharge Date:</strong></td>
|
||||
<td>{{DISCHARGE_DATE}} {{DISCHARGE_TIME}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Department:</strong></td>
|
||||
<td>{{DEPARTMENT}}</td>
|
||||
<td><strong>Doctor:</strong></td>
|
||||
<td>{{DOCTOR_NAMES}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{CONTENT}}
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
{{SIGNATURE_BLOCK}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All placeholders are **case-insensitive**: `{{PATIENT_NAME}}`, `{{patient_name}}`, and `{{Patient_Name}}` all work
|
||||
- Use `{{CONTENT}}` for the complete discharge content, or use individual section placeholders for custom layouts
|
||||
- If a placeholder has no data, it will be replaced with an empty string
|
||||
- HTML content is preserved - sections already include proper formatting
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
# IPD Discharge Template Placeholders - Quick Reference
|
||||
|
||||
## 1. Hospital Information
|
||||
*From Hospital Setting table*
|
||||
|
||||
```
|
||||
{{H_Name}} - Hospital name
|
||||
{{H_address_1}} - Hospital address line 1
|
||||
{{H_address_2}} - Hospital address line 2
|
||||
{{H_phone_No}} - Hospital phone number
|
||||
{{H_Email}} - Hospital email
|
||||
{{H_logo}} - Logo filename
|
||||
{{H_logo_abs}} - Absolute path to logo file
|
||||
{{hospital_name}} - Hospital name (same as H_Name)
|
||||
{{hospital_address}} - Combined address (address_1 + address_2)
|
||||
{{hospital_phone}} - Hospital phone (same as H_phone_No)
|
||||
{{hospital_email}} - Hospital email (same as H_Email)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Patient Information
|
||||
*From Patient_Master table*
|
||||
|
||||
```
|
||||
{{PATIENT_NAME}} - Full patient name
|
||||
{{PATIENT_TITLE}} - Patient title/prefix (e.g., "Mr.", "Mrs.", "Miss")
|
||||
{{UHID}} - Patient UHID/registration code
|
||||
{{AGE_GENDER}} - Age and gender combined (e.g., "26 Year / Female")
|
||||
{{GUARDIAN}} - Guardian relation and name (e.g., "W/O RAM SINGH")
|
||||
{{GUARDIAN_RELATION}} - Guardian relation only (e.g., "W/O")
|
||||
{{GUARDIAN_NAME}} - Guardian name only (e.g., "RAM SINGH")
|
||||
{{PATIENT_ADDRESS}} - Full patient address
|
||||
{{PATIENT_PHONE}} - Patient phone number ✨ NEW
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. IPD Information
|
||||
*From IPD_Master table*
|
||||
|
||||
```
|
||||
{{IPD_CODE}} - IPD admission number (e.g., "A26050000001")
|
||||
{{DEPARTMENT}} - Department name (e.g., "General Medicine")
|
||||
{{ADMIT_DATE}} - Admission date (dd-mm-yyyy format)
|
||||
{{DISCHARGE_DATE}} - Discharge date (dd-mm-yyyy format)
|
||||
{{ADMIT_DATE_ONLY}} - Admission date (same as ADMIT_DATE)
|
||||
{{DISCHARGE_DATE_ONLY}} - Discharge date (same as DISCHARGE_DATE)
|
||||
{{ADMISSION_TIME}} - Admission time (HH:MM AM/PM)
|
||||
{{DISCHARGE_TIME}} - Discharge time (HH:MM AM/PM)
|
||||
{{ADMIT_TIME}} - Admission time (same as ADMISSION_TIME)
|
||||
{{ISDELIVERY}} - Delivery case flag (1 for yes, 0 for no)
|
||||
{{INSURANCE_COMPANY}} - Insurance company name (defaults to "Direct") ✨ NEW
|
||||
{{DOCTOR_NAMES}} - Treating doctors with specialties ✨ NEW
|
||||
{{DOCTOR_NAME}} - Same as DOCTOR_NAMES ✨ NEW
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. IPD Discharge Content
|
||||
|
||||
### 4.1 Complete Content Token
|
||||
|
||||
```
|
||||
{{CONTENT}} - All clinical sections combined (NO patient table)
|
||||
```
|
||||
|
||||
### 4.2 Section Placeholders for Custom Layout/Order
|
||||
|
||||
```
|
||||
{{DISCHARGE_SUMMARY}} - High-level summary (department, treating doctors, dates)
|
||||
{{FINAL_DIAGNOSIS}} - Final diagnosis at discharge
|
||||
{{SURGERY}} - Surgical procedures performed
|
||||
{{PROCEDURE}} - Medical procedures
|
||||
{{PERSONAL_HISTORY}} - Personal history (smoking, alcohol, etc.)
|
||||
{{PRESENTING_COMPLAINTS}} - Complaints with duration and reason for admission
|
||||
{{PAIN_MEASUREMENT_SCALE}} - Pain assessment scale
|
||||
{{GENERAL_EXAM_ADMISSION}} - General examination on admission
|
||||
{{CLINICAL_INVESTIGATION_REPORTS}} - Lab and imaging investigation reports
|
||||
{{COURSE_IN_HOSPITAL}} - Course during hospitalization
|
||||
{{EXAMINATION_ON_DISCHARGE}} - Examination findings at discharge
|
||||
{{DRUG_ALLERGY_ADR}} - Drug allergies and adverse drug reactions
|
||||
{{CO_MORBIDITIES}} - Co-morbid conditions
|
||||
{{DISCHARGE_MEDICATIONS}} - Medications prescribed at discharge
|
||||
{{DIETARY_ADVICE}} - Dietary advice and instructions
|
||||
{{DISCHARGE_INSTRUCTIONS}} - Discharge instructions and follow-up advice
|
||||
{{SIGNATURE_BLOCK}} - Signature block for consultant
|
||||
```
|
||||
|
||||
**Note:** `{{DISCHARGE_SUMMARY}}` section now includes:
|
||||
- Department name (from `ipd_master.dept_id` or `department_id`)
|
||||
- Treating doctor(s) with specializations (from `ipd_master.doc_list`)
|
||||
- Date of admission
|
||||
- Date of discharge
|
||||
|
||||
### 4.3 Pre-Built Patient Table
|
||||
|
||||
```
|
||||
{{PATIENT_INFO_TABLE}} - Complete patient demographic table (HTML)
|
||||
Includes: Name, UHID, Age/Gender, IPD No, Guardian,
|
||||
Phone, Admission, Discharge, Address, Insurance, Department
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Common / Meta Tokens
|
||||
|
||||
```
|
||||
{{CURRENT_DATE}} - Current date (dd-mm-yyyy format)
|
||||
{{PRINT_TIME}} - Current date and time (dd-mm-yyyy HH:MM:SS)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Usage Examples
|
||||
|
||||
### Example 1: Minimal Template (Just Content)
|
||||
```html
|
||||
{{CONTENT}}
|
||||
```
|
||||
**Result:** Shows only clinical sections (no patient table)
|
||||
|
||||
---
|
||||
|
||||
### Example 2: With Patient Table
|
||||
```html
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
{{CONTENT}}
|
||||
```
|
||||
**Result:** Patient demographics + all clinical sections
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Custom Layout with New Tokens
|
||||
```html
|
||||
<h2 style="text-align:center;">{{H_Name}}</h2>
|
||||
<h3 style="text-align:center;">Discharge Summary</h3>
|
||||
|
||||
<table border="1" width="100%">
|
||||
<tr>
|
||||
<td><b>Patient Name:</b></td>
|
||||
<td>{{PATIENT_NAME}}</td>
|
||||
<td><b>UHID:</b></td>
|
||||
<td>{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Phone:</b></td>
|
||||
<td>{{PATIENT_PHONE}}</td>
|
||||
<td><b>Insurance:</b></td>
|
||||
<td>{{INSURANCE_COMPANY}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4"><b>Treating Doctor(s):</b> {{DOCTOR_NAMES}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
{{CONTENT}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 4: Selective Sections Only
|
||||
```html
|
||||
<h2>Discharge Summary</h2>
|
||||
|
||||
{{PATIENT_INFO_TABLE}}
|
||||
|
||||
<h3>Clinical Information</h3>
|
||||
{{FINAL_DIAGNOSIS}}
|
||||
{{SURGERY}}
|
||||
{{PROCEDURE}}
|
||||
|
||||
<h3>Treatment Plan</h3>
|
||||
{{DISCHARGE_MEDICATIONS}}
|
||||
{{DIETARY_ADVICE}}
|
||||
{{DISCHARGE_INSTRUCTIONS}}
|
||||
```
|
||||
**Result:** Shows only selected sections, omits complaints, examination, etc.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **All tokens are case-insensitive** ({{PATIENT_NAME}}, {{patient_name}}, {{Patient_Name}} all work)
|
||||
- **Empty values show nothing** - No need for conditionals, tokens auto-hide if data is missing
|
||||
- **{{CONTENT}} never includes patient table** - Add {{PATIENT_INFO_TABLE}} explicitly if needed
|
||||
- **Doctor names from ipd_master.doc_list** - Automatically formatted as "Dr. Name [Specialty]"
|
||||
- **Insurance defaults to "Direct"** - Shows "Direct" when no insurance company assigned
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Template
|
||||
|
||||
1. Create/edit template in `discharge_template` table
|
||||
2. Clear cached content: `php clear_discharge_content.php [ipd_id]`
|
||||
3. View discharge: `http://localhost:8080/Ipd_discharge/show_discharge/[ipd_id]/1?tpl=[template_id]`
|
||||
4. Check output for correct token replacement
|
||||
|
||||
---
|
||||
|
||||
## Complete Template Example (Full Featured)
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
table { border-collapse: collapse; width: 100%; margin-bottom: 15px; }
|
||||
table td { padding: 8px; border: 1px solid #333; }
|
||||
h2 { text-align: center; color: #2c3e50; margin: 10px 0; }
|
||||
h3 { color: #34495e; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
||||
.header { text-align: center; margin-bottom: 20px; }
|
||||
.footer { margin-top: 30px; text-align: center; font-size: 10pt; color: #7f8c8d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Hospital Header -->
|
||||
<div class="header">
|
||||
<h2>{{H_Name}}</h2>
|
||||
<p>{{hospital_address}}<br>
|
||||
Phone: {{hospital_phone}} | Email: {{hospital_email}}</p>
|
||||
<h3>DISCHARGE SUMMARY</h3>
|
||||
</div>
|
||||
|
||||
<!-- Patient Demographics -->
|
||||
<table>
|
||||
<tr>
|
||||
<td width="20%"><b>Patient Name</b></td>
|
||||
<td width="30%">{{PATIENT_NAME}}</td>
|
||||
<td width="20%"><b>UHID</b></td>
|
||||
<td width="30%">{{UHID}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Age / Gender</b></td>
|
||||
<td>{{AGE_GENDER}}</td>
|
||||
<td><b>IPD No.</b></td>
|
||||
<td>{{IPD_CODE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Guardian</b></td>
|
||||
<td>{{GUARDIAN}}</td>
|
||||
<td><b>Phone</b></td>
|
||||
<td>{{PATIENT_PHONE}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Address</b></td>
|
||||
<td colspan="3">{{PATIENT_ADDRESS}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Department</b></td>
|
||||
<td>{{DEPARTMENT}}</td>
|
||||
<td><b>Insurance</b></td>
|
||||
<td>{{INSURANCE_COMPANY}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Admission Date/Time</b></td>
|
||||
<td>{{ADMIT_DATE}} {{ADMISSION_TIME}}</td>
|
||||
<td><b>Discharge Date/Time</b></td>
|
||||
<td>{{DISCHARGE_DATE}} {{DISCHARGE_TIME}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Treating Doctor(s)</b></td>
|
||||
<td colspan="3">{{DOCTOR_NAMES}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Clinical Content -->
|
||||
{{CONTENT}}
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<p>Generated on {{PRINT_TIME}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
This template includes:
|
||||
- ✅ All new tokens (PATIENT_PHONE, INSURANCE_COMPANY, DOCTOR_NAMES)
|
||||
- ✅ Complete patient demographics in custom table
|
||||
- ✅ All clinical sections via {{CONTENT}}
|
||||
- ✅ Professional styling
|
||||
- ✅ Hospital header and footer
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
# Dual-Bridge Architecture Configuration Guide
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
HMS CI4 Queues Events
|
||||
↓ Routes by event_type prefix
|
||||
├─→ snomed.* events
|
||||
│ ↓ POST to BRIDGE_SYNC_URL
|
||||
│ https://csnotk.e-atria.in/api/bridge
|
||||
│ (CSNOtk Bridge: Codes diagnosis → FHIR)
|
||||
│
|
||||
└─→ abdm.* events
|
||||
↓ POST to ABDM_BRIDGE_URL
|
||||
https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
(ABDM Bridge: Routes to ABDM M1 server)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Event Type Routing Table
|
||||
|
||||
| Event Type | Destination | Purpose |
|
||||
|-----------|-------------|---------|
|
||||
| `snomed.diagnosis.validate` | CSNOtk `/api/bridge` | Validate & code diagnosis with SNOMED CT |
|
||||
| `snomed.medication.validate` | CSNOtk `/api/bridge` | Validate & code medication with SNOMED CT |
|
||||
| `abdm.abha.validate` | ABDM Bridge `/api/v1/bridge` | Validate ABHA number with ABDM M1 |
|
||||
| `abdm.scan_share.lookup` | ABDM Bridge `/api/v1/bridge` | QR code lookup for health records |
|
||||
| `abdm.fhir.share.requested` | ABDM Bridge `/api/v1/bridge` | Push FHIR bundle to ABDM M1 |
|
||||
| `abdm.opd.prescription.share.requested` | ABDM Bridge `/api/v1/bridge` | Push OPD prescription bundle |
|
||||
| `abdm.ipd.discharge.share.requested` | ABDM Bridge `/api/v1/bridge` | Push IPD discharge bundle |
|
||||
| `abdm.diagnosis.report.share.requested` | ABDM Bridge `/api/v1/bridge` | Push diagnosis report bundle |
|
||||
| `abdm.consent.requested` | ABDM Bridge `/api/v1/bridge` | Request patient consent for data access |
|
||||
|
||||
---
|
||||
|
||||
## HMS .env Configuration Required
|
||||
|
||||
### Step 1: Dual Bridge URLs
|
||||
|
||||
Replace the current BRIDGE configuration with this:
|
||||
|
||||
```env
|
||||
# ==================== CSNOtk Bridge (SNOMED CT Terminology) ====================
|
||||
# Purpose: Receive diagnosis/medication events, validate with SNOMED CT, return FHIR-coded data
|
||||
# Endpoint: POST /api/bridge
|
||||
# Used by: BridgeSyncService when routing snomed.* events
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
|
||||
BRIDGE_SYNC_TOKEN =
|
||||
BRIDGE_SYNC_TIMEOUT = 20
|
||||
|
||||
# ==================== ABDM Bridge (ABDM M1 Communication) ====================
|
||||
# Purpose: Receive ABDM events, route to ABDM Live/Sandbox M1 server
|
||||
# Endpoint: POST /api/v1/bridge
|
||||
# Used by: BridgeSyncService when routing abdm.* events
|
||||
# Architecture: Multiple HMS instances → Single ABDM Bridge → ABDM M1 Server
|
||||
ABDM_BRIDGE_URL = https://abdm-bridge.e-atria.in/api/v1/bridge
|
||||
ABDM_BRIDGE_TOKEN =
|
||||
|
||||
# ==================== Bridge Configuration ====================
|
||||
BRIDGE_SOURCE_CODE = SBXID_033661
|
||||
BRIDGE_SYNC_PROVIDER = bridge
|
||||
|
||||
# ==================== SNOMED CT Terminology Service (Direct REST API) ====================
|
||||
# Purpose: Direct access to CSNOtk REST API for autocomplete (OPD diagnosis search)
|
||||
# Endpoints: /rest/search/suggest, /rest/search/lookup, /rest/search/validate/id
|
||||
# Used by: CsnotkTerminologyService in OPD_Prescription controller
|
||||
snomed.csnotk.baseUrl = https://csnotk.e-atria.in/csnoserv
|
||||
snomed.csnotk.enabled = true
|
||||
snomed.csnotk.timeoutSec = 10
|
||||
```
|
||||
|
||||
### Step 2: Generate Bearer Tokens
|
||||
|
||||
Generate TWO random tokens (one for each bridge):
|
||||
|
||||
```bash
|
||||
# Token 1 for CSNOtk Bridge
|
||||
openssl rand -hex 32
|
||||
# Example: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
|
||||
# Token 2 for ABDM Bridge
|
||||
openssl rand -hex 32
|
||||
# Example: f2e1d0c9b8a7z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1
|
||||
```
|
||||
|
||||
### Step 3: Update .env Tokens
|
||||
|
||||
```env
|
||||
BRIDGE_SYNC_TOKEN = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
ABDM_BRIDGE_TOKEN = f2e1d0c9b8a7z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## BridgeSyncService Routing Logic (To Be Implemented)
|
||||
|
||||
Update `app/Libraries/BridgeSyncService.php` buildBridgeDispatchContext() to route based on event prefix:
|
||||
|
||||
### Pseudocode Logic
|
||||
|
||||
```php
|
||||
private function buildBridgeDispatchContext(array $row, array $payload): array
|
||||
{
|
||||
$eventType = (string) ($row['event_type'] ?? '');
|
||||
|
||||
// Route snomed.* events to CSNOtk
|
||||
if (str_starts_with($eventType, 'snomed.')) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'channel' => 'csnotk',
|
||||
'endpoint' => $this->readSetting('BRIDGE_SYNC_URL'), // CSNOtk bridge
|
||||
'method' => 'POST',
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $this->readSetting('BRIDGE_SYNC_TOKEN'),
|
||||
],
|
||||
'body' => [
|
||||
'queue_id' => (int) ($row['id'] ?? 0),
|
||||
'source' => $source,
|
||||
'event_type' => $eventType,
|
||||
'payload' => $payload,
|
||||
'occurred_at' => (string) ($row['created_at'] ?? ''),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Route abdm.* events to ABDM Bridge
|
||||
if (str_starts_with($eventType, 'abdm.')) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'channel' => 'abdm',
|
||||
'endpoint' => $this->readSetting('ABDM_BRIDGE_URL'), // ABDM bridge
|
||||
'method' => 'POST',
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $this->readSetting('ABDM_BRIDGE_TOKEN'),
|
||||
],
|
||||
'body' => [
|
||||
'queue_id' => (int) ($row['id'] ?? 0),
|
||||
'source' => $source,
|
||||
'event_type' => $eventType,
|
||||
'payload' => $payload,
|
||||
'occurred_at' => (string) ($row['created_at'] ?? ''),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Unknown event type
|
||||
return ['ok' => false, 'error' => 'Unsupported event_type prefix'];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSNOtk Bridge `/api/bridge` Contract
|
||||
|
||||
### Request Format
|
||||
|
||||
```json
|
||||
{
|
||||
"queue_id": 12345,
|
||||
"source": "SBXID_033661",
|
||||
"event_type": "snomed.diagnosis.validate",
|
||||
"payload": {
|
||||
"diagnosis_text": "fever",
|
||||
"return_limit": 10
|
||||
},
|
||||
"occurred_at": "2024-05-13T10:30:00+05:30"
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format (Expected)
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"event_type": "snomed.diagnosis.validate",
|
||||
"data": [
|
||||
{
|
||||
"concept_id": "25064002",
|
||||
"term": "Headache",
|
||||
"hierarchy": "disorder",
|
||||
"is_preferred": 1,
|
||||
"concept_fsn": "Headache (disorder)"
|
||||
}
|
||||
],
|
||||
"request_id": "REQ-abc123"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ABDM Bridge `/api/v1/bridge` Contract
|
||||
|
||||
**(See `gateway-php-ci4/README.md` for full API contract)**
|
||||
|
||||
### Request Format
|
||||
|
||||
```json
|
||||
{
|
||||
"queue_id": 12345,
|
||||
"source": "SBXID_033661",
|
||||
"event_type": "abdm.abha.validate",
|
||||
"payload": {
|
||||
"abha_id": "00-0000-0000-0000"
|
||||
},
|
||||
"occurred_at": "2024-05-13T10:30:00+05:30"
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format (Expected)
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"event_type": "abdm.abha.validate",
|
||||
"request_id": "REQ-abc123",
|
||||
"data": {
|
||||
"abha": "00-0000-0000-0000",
|
||||
"status": "VALID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Configuration
|
||||
- [ ] Update HMS `.env` with both `BRIDGE_SYNC_URL` (CSNOtk) and `ABDM_BRIDGE_URL` (ABDM)
|
||||
- [ ] Generate two separate bearer tokens
|
||||
- [ ] Set `BRIDGE_SYNC_TOKEN` and `ABDM_BRIDGE_TOKEN` in .env
|
||||
- [ ] Document tokens in secure location (password manager)
|
||||
|
||||
### Phase 2: Code Changes
|
||||
- [ ] Update `BridgeSyncService::buildBridgeDispatchContext()` with routing logic
|
||||
- [ ] Update `BridgeSyncService::buildDispatchContext()` to handle snomed.* routing
|
||||
- [ ] Update `BridgeSyncService::readSetting()` to support `ABDM_BRIDGE_URL` and `ABDM_BRIDGE_TOKEN`
|
||||
- [ ] Add logging to distinguish between CSNOtk and ABDM bridge calls in `abdm_api_logs` table
|
||||
|
||||
### Phase 3: Testing
|
||||
- [ ] Queue a `snomed.diagnosis.validate` event and verify it goes to CSNOtk
|
||||
- [ ] Queue an `abdm.abha.validate` event and verify it goes to ABDM Bridge
|
||||
- [ ] Check `abdm_api_logs` for successful routing
|
||||
- [ ] Verify CSNOtk returns SNOMED-coded data
|
||||
- [ ] Verify ABDM Bridge routes to ABDM M1 correctly
|
||||
|
||||
### Phase 4: Deployment
|
||||
- [ ] Deploy PHP gateway to https://abdm-bridge.e-atria.in (if not already live)
|
||||
- [ ] Configure gateway `.env` with `GATEWAY_BEARER_TOKEN` matching HMS `ABDM_BRIDGE_TOKEN`
|
||||
- [ ] Run `php spark bridge:sync --limit 10` to test both bridge types
|
||||
- [ ] Monitor logs for any routing or authentication errors
|
||||
|
||||
---
|
||||
|
||||
## Database Logging
|
||||
|
||||
The `abdm_api_logs` table will now record:
|
||||
- `channel` = 'csnotk' or 'abdm' (for routing differentiation)
|
||||
- `event_type` = snomed.* or abdm.*
|
||||
- `endpoint` = CSNOtk or ABDM bridge URL used
|
||||
- `status` = 'success' or 'error'
|
||||
- `error_message` = 403 Unauthorized, 404 Not Found, etc.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: 403 Unauthorized on CSNOtk
|
||||
- **Check:** BRIDGE_SYNC_TOKEN matches CSNOtk Bridge expectation
|
||||
- **Verify:** Request to `https://csnotk.e-atria.in/api/bridge` includes bearer token
|
||||
|
||||
### Problem: 403 Unauthorized on ABDM Bridge
|
||||
- **Check:** ABDM_BRIDGE_TOKEN matches PHP gateway `GATEWAY_BEARER_TOKEN`
|
||||
- **Verify:** PHP gateway `.env` has the same token as HMS `ABDM_BRIDGE_TOKEN`
|
||||
|
||||
### Problem: Events stuck in pending status
|
||||
- **Check:** Routing logic correctly identifies event prefix (snomed.* vs abdm.*)
|
||||
- **Query:** `SELECT * FROM bridge_sync_queue WHERE status='pending' ORDER BY created_at DESC`
|
||||
- **Debug:** Add logging to see which endpoint was called
|
||||
|
||||
### Problem: CSNOtk returns 404 on /api/bridge
|
||||
- **Cause:** `/api/bridge` endpoint may not be exposed on the CSNOtk public server
|
||||
- **Fallback:** Check if CSNOtk only exposes `/rest/search/*` endpoints (direct API calls only)
|
||||
- **Action:** If needed, create intermediate queue processor for CSNOtk terminology service
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **CSNOtk REST API Reference:** `docs/CSNOTK_API_REFERENCE.md`
|
||||
- **CSNOtk Database:** `SnomedCT_InternationalRF2_PRODUCTION_20260501T120000Z/`
|
||||
- **ABDM PHP Gateway:** `gateway-php-ci4/` (deployment guide: `ABDM_GATEWAY_PHP_DEPLOYMENT.md`)
|
||||
- **Current CSNOtk Direct API Integration:** `app/Libraries/CsnotkTerminologyService.php`
|
||||
- **Bridge Service:** `app/Libraries/BridgeSyncService.php`
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
# ABDM Integration - Complete File Index
|
||||
|
||||
**Date:** May 12, 2026
|
||||
**Status:** Configuration + Middleware Gateway Complete ✅
|
||||
|
||||
---
|
||||
|
||||
## 📁 ABDM Configuration Files
|
||||
|
||||
### Location: `docs/`
|
||||
|
||||
| File | Purpose | Size | Read Time |
|
||||
|------|---------|------|-----------|
|
||||
| `ABDM_QUICK_START.md` | Fast-track 7-step testing guide | 8KB | 5 min |
|
||||
| `ABDM_SANDBOX_SETUP.md` | Complete integration reference | 15KB | 15 min |
|
||||
| `ABDM_SANDBOX_CHECKLIST.md` | Implementation status tracking | 10KB | 5 min |
|
||||
| `ABDM_GATEWAY_ARCHITECTURE.md` | CSNOtk vs Custom gateway comparison | 20KB | 10 min |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Middleware Gateway Files
|
||||
|
||||
### Location: `middleware-gateway/`
|
||||
|
||||
| File | Purpose | Type | Priority |
|
||||
|------|---------|------|----------|
|
||||
| `server.js` | Main Node.js application (400+ lines) | Code | ⭐⭐⭐ |
|
||||
| `package.json` | NPM dependencies (15 packages) | Config | ⭐⭐⭐ |
|
||||
| `Dockerfile` | Docker image definition | Config | ⭐⭐⭐ |
|
||||
| `docker-compose.yml` | Gateway + Nginx containers | Config | ⭐⭐⭐ |
|
||||
| `nginx.conf` | Reverse proxy + SSL config | Config | ⭐⭐⭐ |
|
||||
| `deploy.sh` | Automated Ubuntu deployment | Script | ⭐⭐⭐ |
|
||||
| `.env.example` | Configuration template | Config | ⭐⭐ |
|
||||
| `setup-ubuntu.sh` | System dependency installer | Script | ⭐ |
|
||||
| `README.md` | Quick reference guide | Docs | ⭐⭐ |
|
||||
| `DEPLOYMENT_GUIDE.md` | Detailed deployment steps | Docs | ⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Session Summaries
|
||||
|
||||
| File | Purpose | Date |
|
||||
|------|---------|------|
|
||||
| `SESSION_SUMMARY_ABDM_SANDBOX.md` | ABDM Sandbox configuration summary | May 12, 2026 |
|
||||
| `SESSION_SUMMARY_MIDDLEWARE_GATEWAY.md` | Middleware gateway creation summary | May 12, 2026 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Updated Configuration Files
|
||||
|
||||
| File | Change | Status |
|
||||
|------|--------|--------|
|
||||
| `.env` | Added ABDM gateway options documentation | ✅ Complete |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Navigation
|
||||
|
||||
### For Testing ABDM (This Week)
|
||||
1. Start: `docs/ABDM_QUICK_START.md` (7 tests)
|
||||
2. Reference: `docs/ABDM_SANDBOX_SETUP.md`
|
||||
3. Track: `docs/ABDM_SANDBOX_CHECKLIST.md`
|
||||
|
||||
### For Deploying Custom Gateway (Next Week)
|
||||
1. Overview: `middleware-gateway/README.md`
|
||||
2. Deploy: `middleware-gateway/DEPLOYMENT_GUIDE.md`
|
||||
3. Run: `sudo ./middleware-gateway/deploy.sh`
|
||||
|
||||
### For Architecture Understanding
|
||||
1. Read: `docs/ABDM_GATEWAY_ARCHITECTURE.md`
|
||||
2. Compare: CSNOtk vs Custom gateway options
|
||||
3. Decide: Which approach for your project
|
||||
|
||||
---
|
||||
|
||||
## 📊 What's Ready Now
|
||||
|
||||
### ✅ Configuration (5 files)
|
||||
- ABDM sandbox credentials in .env
|
||||
- SNOMED service endpoints
|
||||
- Gateway URLs (both options)
|
||||
- Middleware authentication setup
|
||||
|
||||
### ✅ Middleware Gateway (10 files)
|
||||
- Complete Node.js application
|
||||
- Docker containerization
|
||||
- Nginx reverse proxy
|
||||
- Automated deployment script
|
||||
- Full documentation
|
||||
|
||||
### ✅ Documentation (6 files)
|
||||
- Quick start guide
|
||||
- Full deployment guide
|
||||
- Architecture comparison
|
||||
- Implementation checklist
|
||||
- Session summaries
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Paths
|
||||
|
||||
### Path 1: Quick Testing (Today)
|
||||
```
|
||||
Current State:
|
||||
├─ ABDM credentials: ✅ Configured
|
||||
├─ CSNOtk gateway: ✅ Ready to use
|
||||
└─ OPD ABDM form: ✅ Ready to test
|
||||
|
||||
Action:
|
||||
→ docs/ABDM_QUICK_START.md (7 steps)
|
||||
→ Complete testing this week
|
||||
```
|
||||
|
||||
### Path 2: Custom Gateway (Next Week)
|
||||
```
|
||||
Preparation:
|
||||
├─ Provision Ubuntu server (DigitalOcean, AWS, etc)
|
||||
├─ DNS: Create A record for abdm-bridge.e-atria.in
|
||||
└─ Upload middleware-gateway/ folder
|
||||
|
||||
Deployment:
|
||||
→ ssh root@server
|
||||
→ cd /opt/middleware-gateway
|
||||
→ sudo ./deploy.sh
|
||||
|
||||
Time: 15 minutes (fully automated)
|
||||
```
|
||||
|
||||
### Path 3: Hybrid (Recommended)
|
||||
```
|
||||
Week 1: Use CSNOtk for testing
|
||||
Week 2: Deploy custom gateway in parallel
|
||||
Week 3: Switch HMS to new gateway
|
||||
Week 4+: Production operations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Key Commands
|
||||
|
||||
### Deploy Custom Gateway
|
||||
```bash
|
||||
cd /opt/middleware-gateway
|
||||
sudo ./deploy.sh
|
||||
```
|
||||
|
||||
### Configure HMS
|
||||
```bash
|
||||
# Edit .env
|
||||
BRIDGE_SYNC_URL = https://abdm-bridge.e-atria.in/api/v3
|
||||
|
||||
# Restart
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
### Test Gateway
|
||||
```bash
|
||||
curl https://abdm-bridge.e-atria.in/api/v3/health
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
docker logs -f abdm-bridge-gateway
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Feature Completeness
|
||||
|
||||
### ABDM Integration
|
||||
- ✅ Sandbox credentials configured
|
||||
- ✅ FHIR bundle generation
|
||||
- ✅ OPD ABDM compliance
|
||||
- ✅ SNOMED coding
|
||||
- ✅ ABHA validation ready
|
||||
- ✅ Consent workflow ready
|
||||
- ⏳ Bundle push infrastructure ready
|
||||
|
||||
### Gateway Infrastructure
|
||||
- ✅ Node.js application
|
||||
- ✅ Docker containerization
|
||||
- ✅ Nginx reverse proxy
|
||||
- ✅ SSL/TLS (Let's Encrypt)
|
||||
- ✅ Rate limiting
|
||||
- ✅ Request logging
|
||||
- ✅ Health checks
|
||||
- ✅ Auto-restart
|
||||
- ✅ Automated deployment
|
||||
- ✅ Complete documentation
|
||||
|
||||
---
|
||||
|
||||
## 🎓 File Purpose Summary
|
||||
|
||||
### Core Gateway Code
|
||||
- **server.js** → Main application logic (6 ABDM endpoints)
|
||||
- **package.json** → Dependencies (Express, Axios, Winston, etc)
|
||||
|
||||
### Containerization
|
||||
- **Dockerfile** → Build gateway image
|
||||
- **docker-compose.yml** → Orchestrate gateway + nginx
|
||||
|
||||
### Web Server
|
||||
- **nginx.conf** → Reverse proxy, SSL, rate limiting, headers
|
||||
|
||||
### Deployment
|
||||
- **deploy.sh** → Automated setup (10 steps in 15 min)
|
||||
- **.env.example** → Configuration template
|
||||
|
||||
### Documentation
|
||||
- **README.md** → Quick reference (5 min read)
|
||||
- **DEPLOYMENT_GUIDE.md** → Detailed steps (30 min read)
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security Features
|
||||
|
||||
- ✅ SSL/TLS 1.2+ encryption
|
||||
- ✅ Bearer token authentication
|
||||
- ✅ Request ID tracking
|
||||
- ✅ Rate limiting (100 req/15min)
|
||||
- ✅ Security headers (HSTS, CSP, etc)
|
||||
- ✅ CORS policy
|
||||
- ✅ No credentials in logs
|
||||
- ✅ Auto certificate renewal
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support Documentation
|
||||
|
||||
### Getting Started
|
||||
→ `middleware-gateway/README.md` (Quick reference)
|
||||
|
||||
### Detailed Setup
|
||||
→ `middleware-gateway/DEPLOYMENT_GUIDE.md` (Step-by-step)
|
||||
|
||||
### Architecture Decision
|
||||
→ `docs/ABDM_GATEWAY_ARCHITECTURE.md` (CSNOtk vs Custom)
|
||||
|
||||
### ABDM Integration
|
||||
→ `docs/ABDM_SANDBOX_SETUP.md` (Full reference)
|
||||
|
||||
### Quick Testing
|
||||
→ `docs/ABDM_QUICK_START.md` (7-step validation)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
Before going live:
|
||||
|
||||
- [ ] Middleware gateway deployed
|
||||
- [ ] Health check responding
|
||||
- [ ] ABHA endpoint working
|
||||
- [ ] Consent endpoint working
|
||||
- [ ] Bundle push working
|
||||
- [ ] SNOMED search working
|
||||
- [ ] HMS configured to use gateway
|
||||
- [ ] OPD ABHA flow tested end-to-end
|
||||
- [ ] Logs being collected
|
||||
- [ ] SSL certificate valid
|
||||
- [ ] Rate limiting active
|
||||
- [ ] Auto-renewal configured
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps (Immediate)
|
||||
|
||||
### If Testing This Week:
|
||||
1. Read: `docs/ABDM_QUICK_START.md`
|
||||
2. Follow: 7-step test sequence
|
||||
3. Report: Results from tests
|
||||
|
||||
### If Deploying Gateway Next Week:
|
||||
1. Provision: Ubuntu server
|
||||
2. Upload: `middleware-gateway/` folder
|
||||
3. Execute: `sudo ./deploy.sh`
|
||||
4. Verify: Health check passing
|
||||
5. Configure: HMS .env for new gateway
|
||||
|
||||
---
|
||||
|
||||
## 📁 Complete File Tree
|
||||
|
||||
```
|
||||
HMS_CI4_OLD/
|
||||
├── middleware-gateway/
|
||||
│ ├── server.js # Main application
|
||||
│ ├── package.json # Dependencies
|
||||
│ ├── Dockerfile # Container image
|
||||
│ ├── docker-compose.yml # Orchestration
|
||||
│ ├── nginx.conf # Reverse proxy
|
||||
│ ├── deploy.sh # Auto deployment
|
||||
│ ├── setup-ubuntu.sh # Dependencies
|
||||
│ ├── .env.example # Config template
|
||||
│ ├── README.md # Quick guide
|
||||
│ ├── DEPLOYMENT_GUIDE.md # Full guide
|
||||
│ └── logs/ # Application logs
|
||||
│
|
||||
├── docs/
|
||||
│ ├── ABDM_QUICK_START.md # 7-step testing
|
||||
│ ├── ABDM_SANDBOX_SETUP.md # Full reference
|
||||
│ ├── ABDM_SANDBOX_CHECKLIST.md # Tracking
|
||||
│ ├── ABDM_GATEWAY_ARCHITECTURE.md # Comparison
|
||||
│ └── ...
|
||||
│
|
||||
├── SESSION_SUMMARY_ABDM_SANDBOX.md
|
||||
├── SESSION_SUMMARY_MIDDLEWARE_GATEWAY.md
|
||||
├── .env # Updated config
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Ready to Deploy?
|
||||
|
||||
1. **This Week:** Test with CSNOtk (docs/ABDM_QUICK_START.md)
|
||||
2. **Next Week:** Deploy gateway (middleware-gateway/deploy.sh)
|
||||
3. **Week 3:** Switch HMS to new gateway
|
||||
4. **Week 4+:** Production operations
|
||||
|
||||
---
|
||||
|
||||
**Status:** 🟢 COMPLETE & PRODUCTION-READY
|
||||
**Total Files Created:** 10 (gateway) + 6 (docs) = 16 files
|
||||
**Deployment Time:** 15 minutes (automated)
|
||||
**Complexity:** Simple (one script)
|
||||
|
||||
---
|
||||
|
||||
**Questions?** → Check the relevant guide above.
|
||||
**Ready to deploy?** → Run `sudo ./middleware-gateway/deploy.sh` on Ubuntu server.
|
||||
|
||||
🎉 **Everything is ready!**
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# HMS Multi-Building Medical Store & ABDM Pharmacy System
|
||||
**Session Name:** `HMS Multi-Store Pharmacy & ABDM Integration`
|
||||
**Antigravity Conversation ID:** `3731d0c5-f4df-435e-b54f-f690562e18e3`
|
||||
**Last Updated:** September 8, 2026
|
||||
|
||||
---
|
||||
|
||||
## Quick Resume Instructions After Computer Restart
|
||||
|
||||
### 1. In Antigravity IDE
|
||||
1. Open Antigravity IDE and reopen the workspace `HMS_CI4_OLD`.
|
||||
2. In the Chat panel, click the **Chat History / Clock icon** (or open the left conversation drawer).
|
||||
3. Select this session: **`HMS Multi-Store Pharmacy & ABDM Integration`** (ID: `3731d0c5-f4df-435e-b54f-f690562e18e3`).
|
||||
4. Type your message and continue seamlessly!
|
||||
|
||||
*Tip: If you ever open a new chat, you can type `@HMS_MEDICAL_STORE_SESSION.md` or `@conversation` to instantly restore all context.*
|
||||
|
||||
---
|
||||
|
||||
## System Access & Credentials Summary
|
||||
|
||||
### Development Server
|
||||
- Command: `php spark serve` (in `d:\Workplace\HMS_CI4_OLD`)
|
||||
- Server URL: `http://localhost:8080`
|
||||
|
||||
### HMS Admin Management
|
||||
- **URL:** [http://localhost:8080/setting/admin/medical-store](http://localhost:8080/setting/admin/medical-store)
|
||||
- **Features:** Store configuration, DL 20B/21B licenses, GSTIN, Pharmacist details, ABDM HFR/HIP/HPR registries, 60-min OTP generator, terminal device authorization, and Excel / CSV stock import.
|
||||
|
||||
### Direct Counter Desks (React PWA)
|
||||
- **Counter A (Main Hospital Block A):**
|
||||
- Direct Link: [http://localhost:8080/MedicalStore/storeA](http://localhost:8080/MedicalStore/storeA)
|
||||
- Store Code: `ST-MAIN`
|
||||
- Permanent Security Key: `STORE-SEC-A91B4C72`
|
||||
- ABDM HFR ID: `IN0710001234`
|
||||
- Pharmacist HPR ID: `91-8822-4411-9901`
|
||||
- **Counter B (OPD Block B):**
|
||||
- Direct Link: [http://localhost:8080/MedicalStore/storeB](http://localhost:8080/MedicalStore/storeB)
|
||||
- Store Code: `ST-OPD-B2`
|
||||
- Permanent Security Key: `STORE-SEC-B84F29D1`
|
||||
- ABDM HFR ID: `IN0710001234-OPD`
|
||||
- Pharmacist HPR ID: `91-8822-4411-9902`
|
||||
|
||||
---
|
||||
|
||||
## Core Technical Reference
|
||||
|
||||
1. **Zero Legacy Disturbance:**
|
||||
- Legacy pharmacy tables (`invoice_med_master`, `inv_med_item`, `med_*`) and controllers remain 100% untouched.
|
||||
- All new tables use the **`mst_*`** prefix (15 tables).
|
||||
2. **ABDM Compatibility (M1 & M2):**
|
||||
- Patient search by 14-digit ABHA Number or ABHA Address.
|
||||
- Care Context generation: `PHARM-{store_code}-{invoice_no}`.
|
||||
- NRCES FHIR R4 Bundle profile: `https://nrces.in/ndhm/fhir/r4/StructureDefinition/MedicationDispenseDocument`.
|
||||
- Coded with SNOMED-CT clinical terminology.
|
||||
- Gateway Bridge queue integration: `abdm_sync_record`.
|
||||
- Bundle preview endpoint: `GET /api/v1/medical-store/abdm/bundle/{sale_id}`.
|
||||
3. **Multi-Format Invoices:**
|
||||
- 80mm Thermal Slip (Fast 1–5 item OTC/OPD).
|
||||
- A5 Invoice (Normal OPD/Walk-in with GST summary).
|
||||
- A4 Hospital Tax Invoice (IPD admissions & large bills).
|
||||
4. **Excel / CSV Stock Migration:**
|
||||
- Supported CSV import with auto-detected columns.
|
||||
5. **Double-Entry Accounting & Statutory Drug Register:**
|
||||
- Automated ledger postings, Daily Cash Book, GSTR-1 outward reports, and mandatory Schedule H1 drug register.
|
||||
6. **Retail Loose Tablet & Unit Dispensing:**
|
||||
- Exact per-tablet pricing (`Strip MRP / units_per_pack`), exact stock deduction in individual tablets, and support for Loose Tab, Strip, and Combo sales.
|
||||
7. **Split / Mixed Payment Mode & Audit:**
|
||||
- Supports partial payments (e.g. ₹35 Cash + ₹100 UPI) with 12-digit UTR and bank name capturing for bank reconciliation and audit.
|
||||
8. **Keyboard-First Zero-Mouse POS Workflow (Desktop Pharmacy Standard):**
|
||||
- High-contrast single-line Quick Keys ribbon.
|
||||
- Hotkeys: `F2` (Medicine Search), `F4` (Patient Search), `F7` (Whole Discount), `F8` (Payment Mode), `F9 / Ctrl+Enter` (Complete Sale & Print), `Alt+T` (Loose Tab), `Alt+S` (Strip), `Shift+Delete` (Remove Item).
|
||||
- Auto-focus on Medicine Search on page load; `Enter` on quantity immediately commits and jumps focus back to Medicine Lookup for the next scan.
|
||||
9. **Compact "Qty & Unit" Cell:**
|
||||
- Single-line input group with type selector dropdown (`Tab`, `Strip`, `Str+Tab`) positioned before the quantity box.
|
||||
- Automatically defaults to `Tab` (loose units).
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,282 +0,0 @@
|
|||
# mPDF Hindi (Devanagari) Font Configuration
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
### Problem
|
||||
- **Old font:** `dejavusans` - Very thin/light weight, limited Hindi glyph coverage
|
||||
- **Issue:** Poor rendering of Hindi/Devanagari text in PDFs
|
||||
|
||||
### Solution Implemented
|
||||
- **New font:** `freeserif` - Medium weight serif font with excellent Hindi/Devanagari support
|
||||
- **Result:** Better readability, proper glyph rendering, professional appearance
|
||||
|
||||
## Files Updated (2026-06-06)
|
||||
|
||||
### 1. Opd.php (3 instances)
|
||||
- Line ~2036: Invoice PDF generation
|
||||
- Line ~2192: Prescription PDF generation
|
||||
- Line ~2308: Document print with custom templates
|
||||
|
||||
### 2. Ipd_discharge.php
|
||||
- Already using `freeserif` ✅ (Lines 5485, 5532, 5581)
|
||||
|
||||
### 3. Billing\Ipd.php
|
||||
- Already using `freeserif` ✅ (Line 982)
|
||||
|
||||
### 4. Diagnosis.php (3 instances)
|
||||
- Line ~1517: Lab report with print settings
|
||||
- Line ~2201: Invoice PDF generation
|
||||
- Line ~2456: PDF create with timing logs
|
||||
|
||||
### 5. Billing\Charges.php (2 instances)
|
||||
- Line ~1140: Landscape invoice (A4-L format)
|
||||
- Line ~1151: Portrait invoice (A4 format)
|
||||
|
||||
### 6. Storestock.php (1 instance)
|
||||
- Line ~782: Indent/stock PDF generation
|
||||
|
||||
### 7. AbdmGateway.php (1 instance)
|
||||
- Line ~2043: ABDM health record PDF generation
|
||||
|
||||
### 8. DoctorDocument.php
|
||||
- Already using `freesans` ✅ (Line 1298) - Another good Unicode font
|
||||
|
||||
---
|
||||
|
||||
## Available mPDF Fonts for Hindi
|
||||
|
||||
### Built-in Fonts (No Additional Setup)
|
||||
|
||||
| Font Name | Type | Weight | Hindi Support | Best Use Case |
|
||||
|-----------|------|--------|---------------|---------------|
|
||||
| **freeserif** ✅ | Serif | Medium | Excellent | **Current choice** - Reports, prescriptions |
|
||||
| **freesans** | Sans-serif | Medium | Excellent | Modern documents, UI-heavy PDFs |
|
||||
| **dejavusans** ❌ | Sans-serif | Thin | Limited | Avoid - too thin |
|
||||
| **dejavusanscondensed** | Sans-serif | Medium | Good | Space-constrained layouts |
|
||||
|
||||
### Custom Font Installation (Advanced)
|
||||
|
||||
For production-grade Hindi typography, you can add custom fonts:
|
||||
|
||||
#### Option 1: Noto Sans Devanagari (Google Fonts)
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'fontDir' => [
|
||||
__DIR__ . '/../../public/fonts',
|
||||
realpath(__DIR__ . '/../../vendor/mpdf/mpdf/ttfonts')
|
||||
],
|
||||
'fontdata' => [
|
||||
'notosansdevanagari' => [
|
||||
'R' => 'NotoSansDevanagari-Regular.ttf',
|
||||
'B' => 'NotoSansDevanagari-Bold.ttf',
|
||||
]
|
||||
],
|
||||
'default_font' => 'notosansdevanagari',
|
||||
]);
|
||||
```
|
||||
|
||||
**Download:** https://fonts.google.com/noto/specimen/Noto+Sans+Devanagari
|
||||
|
||||
#### Option 2: Lohit Devanagari (Open Source)
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'fontDir' => [__DIR__ . '/../../public/fonts'],
|
||||
'fontdata' => [
|
||||
'lohitdevanagari' => [
|
||||
'R' => 'lohit-devanagari.ttf',
|
||||
]
|
||||
],
|
||||
'default_font' => 'lohitdevanagari',
|
||||
]);
|
||||
```
|
||||
|
||||
**Download:** https://github.com/pravins/lohit
|
||||
|
||||
#### Option 3: Windows Mangal Font
|
||||
If running on Windows server with Mangal installed:
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'fontDir' => ['C:/Windows/Fonts'],
|
||||
'fontdata' => [
|
||||
'mangal' => [
|
||||
'R' => 'mangal.ttf',
|
||||
'B' => 'mangalb.ttf',
|
||||
]
|
||||
],
|
||||
'default_font' => 'mangal',
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the Changes
|
||||
|
||||
### Quick Test
|
||||
1. Generate any PDF (OPD prescription, discharge summary, lab report)
|
||||
2. Check Hindi text rendering
|
||||
3. Compare with old PDFs - new font should be:
|
||||
- **Thicker/bolder** than before
|
||||
- **More readable** at all zoom levels
|
||||
- **Proper glyph rendering** for all Hindi characters
|
||||
|
||||
### Sample Hindi Text for Testing
|
||||
```
|
||||
नमस्ते। यह एक परीक्षण है।
|
||||
मरीज का नाम: राज कुमार शर्मा
|
||||
निदान: बुखार और सिरदर्द
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Font Weight Comparison
|
||||
|
||||
### Before (dejavusans)
|
||||
- Font weight: 300-400 (Light to Normal)
|
||||
- Line thickness: Very thin
|
||||
- Best for: English text only
|
||||
|
||||
### After (freeserif)
|
||||
- Font weight: 500-600 (Medium to Semi-bold)
|
||||
- Line thickness: Medium
|
||||
- Best for: Multilingual documents including Hindi
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Current Standard Configuration
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'orientation' => 'P',
|
||||
'margin_left' => 10,
|
||||
'margin_right' => 10,
|
||||
'margin_top' => 10,
|
||||
'margin_bottom' => 10,
|
||||
'default_font' => 'freeserif', // ✅ Changed
|
||||
'autoScriptToLang' => true, // Auto-detect language
|
||||
'autoLangToFont' => true, // Auto-switch font for different scripts
|
||||
'tempDir' => WRITEPATH . 'cache',
|
||||
]);
|
||||
```
|
||||
|
||||
### Additional Options for Better Hindi Support
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'default_font' => 'freeserif',
|
||||
'autoScriptToLang' => true,
|
||||
'autoLangToFont' => true,
|
||||
'useSubstitutions' => true, // Enable font substitution
|
||||
'CSSselectMedia' => 'print', // Better print styling
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inline Font Override
|
||||
|
||||
If you need different fonts for specific sections within the same PDF:
|
||||
|
||||
```html
|
||||
<style>
|
||||
.hindi-bold {
|
||||
font-family: freeserif;
|
||||
font-weight: bold;
|
||||
font-size: 14pt;
|
||||
}
|
||||
.english-section {
|
||||
font-family: freesans;
|
||||
font-size: 11pt;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="hindi-bold">
|
||||
मरीज का नाम: राज कुमार शर्मा
|
||||
</div>
|
||||
<div class="english-section">
|
||||
Patient Name: Raj Kumar Sharma
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Hindi text shows squares/boxes
|
||||
**Solution:** Ensure UTF-8 encoding in database and PHP files
|
||||
```php
|
||||
// In controller before PDF generation
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
```
|
||||
|
||||
### Issue: Font looks different in different PDFs
|
||||
**Check:** Verify all controllers use same font configuration
|
||||
|
||||
### Issue: Need even bolder font
|
||||
**Options:**
|
||||
1. Use CSS: `<span style="font-weight: bold;">हिंदी पाठ</span>`
|
||||
2. Switch to `freesans` (slightly bolder than freeserif)
|
||||
3. Install custom bold font (see Custom Font Installation above)
|
||||
|
||||
---
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- `freeserif` is built-in to mPDF - **no performance impact**
|
||||
- Custom fonts add ~2-3 MB to memory usage per font file
|
||||
- Font substitution (`autoLangToFont`) adds negligible overhead (~0.1s per PDF)
|
||||
|
||||
---
|
||||
|
||||
## Rollback Instructions
|
||||
|
||||
If you need to revert to old font:
|
||||
|
||||
```bash
|
||||
# Search and replace in all PHP files
|
||||
git diff # Review changes
|
||||
git checkout -- app/Controllers/ # Revert all controller changes
|
||||
```
|
||||
|
||||
Or manually change:
|
||||
```php
|
||||
'default_font' => 'dejavusans', // Old font
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Optional)
|
||||
|
||||
### 1. For Professional Hindi Typography
|
||||
- Download Noto Sans Devanagari from Google Fonts
|
||||
- Place TTF files in `public/fonts/` directory
|
||||
- Update mPDF configuration in each controller
|
||||
|
||||
### 2. For System-wide Font Configuration
|
||||
- Create a helper function `getPdfConfig()` in `app/Helpers/pdf_helper.php`
|
||||
- Centralize all mPDF configuration
|
||||
- Call from all controllers: `$mpdf = new Mpdf(getPdfConfig());`
|
||||
|
||||
### 3. For Font Testing
|
||||
- Create a test endpoint: `/test-pdf-fonts`
|
||||
- Generate sample PDF with all available fonts
|
||||
- Compare rendering quality
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
**mPDF Documentation:**
|
||||
- Fonts: https://mpdf.github.io/fonts-languages/fonts-in-mpdf-7-x.html
|
||||
- Indic Scripts: https://mpdf.github.io/fonts-languages/indic-fonts-v6.html
|
||||
|
||||
**Font Resources:**
|
||||
- Google Fonts: https://fonts.google.com/?subset=devanagari
|
||||
- Font Squirrel: https://www.fontsquirrel.com/fonts/list/language/hindi
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
- **2026-06-06:** Changed default font from `dejavusans` to `freeserif` across all controllers for better Hindi rendering and font weight
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
# mPDF Reference — HMS E-Atria Project
|
||||
|
||||
Source: https://mpdf.github.io/
|
||||
Version used: mpdf/mpdf (composer, see vendor/)
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Pattern — Single WriteHTML Call (used in this project)
|
||||
|
||||
mPDF does NOT require a full HTML document (`<!doctype>`, `<html>`, `<head>`, `<body>`).
|
||||
Pass a plain HTML fragment to `WriteHTML()`.
|
||||
|
||||
### Correct structure for a templated PDF with header/footer:
|
||||
|
||||
```
|
||||
<style>
|
||||
@page {
|
||||
margin-top: 3cm;
|
||||
margin-bottom: 2cm;
|
||||
margin-left: 0.8cm;
|
||||
margin-right: 0.8cm;
|
||||
margin-header: 1cm;
|
||||
margin-footer: 0.5cm;
|
||||
header: html_myHeader; /* activates the named header on every page */
|
||||
footer: html_myFooter;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: DejaVu Sans, sans-serif;
|
||||
font-size: 11pt;
|
||||
}
|
||||
table { width:100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #000; padding: 6px; }
|
||||
</style>
|
||||
|
||||
<htmlpageheader name="myHeader">
|
||||
<div style="text-align:center; font-weight:bold;">Hospital Name</div>
|
||||
<hr>
|
||||
</htmlpageheader>
|
||||
|
||||
<htmlpagefooter name="myFooter">
|
||||
<div style="text-align:right; font-size:9pt;">Page {PAGENO} of {nbpg}</div>
|
||||
</htmlpagefooter>
|
||||
|
||||
<h1>Discharge Summary</h1>
|
||||
<p>Patient content here…</p>
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- `@page` block MUST be in a `<style>` tag placed BEFORE the named header/footer blocks.
|
||||
- `header: html_myHeader;` in `@page` binds the named block to every page.
|
||||
- Named headers use `html_` prefix when referenced from `@page` / `AddPage()`.
|
||||
- Do NOT name any header/footer starting with `html_` when defining it — the prefix is reserved for referencing only.
|
||||
- `margin-header` = space between the top of the physical page and the top of the header content.
|
||||
- `margin-top` = space between the bottom of the header and the start of the body content.
|
||||
|
||||
---
|
||||
|
||||
## 2. PHP Constructor (new Mpdf\Mpdf([...]))
|
||||
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4', // A4, A4-L, A5, A6, LETTER, LEGAL, or [width_mm, height_mm]
|
||||
'margin_top' => 30, // mm — used as fallback if no @page CSS
|
||||
'margin_bottom' => 20,
|
||||
'margin_left' => 8,
|
||||
'margin_right' => 8,
|
||||
'margin_header' => 10, // mm — space reserved for header
|
||||
'margin_footer' => 5,
|
||||
'default_font' => 'freeserif', // project default for Hindi/Unicode support
|
||||
'tempDir' => WRITEPATH . 'cache',
|
||||
'autoScriptToLang' => true, // required for Hindi/Devanagari
|
||||
'autoLangToFont' => true,
|
||||
]);
|
||||
```
|
||||
|
||||
If `@page` CSS is present, CSS margins override the constructor margins.
|
||||
The constructor `format` always controls the page size (not settable via `@page`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Named HTML Header/Footer — Method 4 (recommended)
|
||||
|
||||
**Define** using custom HTML tags in the WriteHTML content:
|
||||
```html
|
||||
<htmlpageheader name="myHeader">
|
||||
<div>Header content with {PAGENO} {DATE j-m-Y}</div>
|
||||
</htmlpageheader>
|
||||
```
|
||||
|
||||
**Activate** using `@page` in `<style>`:
|
||||
```css
|
||||
@page {
|
||||
header: html_myHeader; /* Note: "html_" prefix ONLY in reference, not in name="" */
|
||||
footer: html_myFooter;
|
||||
}
|
||||
```
|
||||
|
||||
**Alternative activate** via PHP (for runtime change):
|
||||
```php
|
||||
$mpdf->SetHTMLHeaderByName('myHeader');
|
||||
$mpdf->SetHTMLFooterByName('myFooter');
|
||||
```
|
||||
|
||||
**Dynamic variables inside headers/footers:**
|
||||
- `{PAGENO}` — current page number
|
||||
- `{nbpg}` — total number of pages
|
||||
- `{DATE j-m-Y}` — date formatted using PHP date() format
|
||||
|
||||
---
|
||||
|
||||
## 4. Output Methods
|
||||
|
||||
```php
|
||||
// Inline (stream to browser)
|
||||
$mpdf->Output('filename.pdf', 'I');
|
||||
|
||||
// Download
|
||||
$mpdf->Output('filename.pdf', 'D');
|
||||
|
||||
// Save to file
|
||||
$mpdf->Output('/path/to/file.pdf', 'F');
|
||||
|
||||
// Return as string (used in this project)
|
||||
$binary = $mpdf->Output('filename.pdf', \Mpdf\Output\Destination::STRING_RETURN);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Page Numbers / Date in Footers
|
||||
|
||||
```html
|
||||
Page {PAGENO} of {nbpg} — recommended
|
||||
Page {PAGENO}/{nb} — also works
|
||||
Date: {DATE d-m-Y}
|
||||
```
|
||||
|
||||
Set alias for {nb}:
|
||||
```php
|
||||
$mpdf->AliasNbPages('{nbpg}'); // optional, default is {nbpg}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Supported CSS — Key Properties
|
||||
|
||||
### @page selector
|
||||
```css
|
||||
@page {
|
||||
size: A4; /* sets page size (alternative to constructor format) */
|
||||
margin-top: 3cm;
|
||||
margin-bottom: 2cm;
|
||||
margin-left: 0.8cm;
|
||||
margin-right: 0.8cm;
|
||||
margin-header: 1cm;
|
||||
margin-footer: 0.5cm;
|
||||
header: html_myHeader; /* HTML named header (use html_ prefix) */
|
||||
footer: html_myFooter;
|
||||
odd-header-name: html_myHeader; /* alternative: for odd pages only */
|
||||
odd-footer-name: html_myFooter;
|
||||
}
|
||||
@page :first { ... } /* first page only */
|
||||
@page :right { ... } /* right/odd pages */
|
||||
@page :left { ... } /* left/even pages */
|
||||
@page named { ... } /* named page selector, activated by page: named; on a block */
|
||||
```
|
||||
|
||||
### Units supported
|
||||
`px`, `pt`, `cm`, `mm`, `in`, `em`, `rem`, `ex`, `%`
|
||||
|
||||
### Not supported
|
||||
- CSS Grid, Flexbox
|
||||
- CSS Variables (--custom-property)
|
||||
- Most CSS3 transitions/animations
|
||||
- `position: sticky`, `position: relative` (only `fixed` and `absolute` supported)
|
||||
|
||||
---
|
||||
|
||||
## 7. Fonts — Hindi / Devanagari (used in discharge medicines)
|
||||
|
||||
For Hindi text (dose descriptions like "हर दो घंटे पर"):
|
||||
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'default_font' => 'freeserif',
|
||||
'autoScriptToLang' => true,
|
||||
'autoLangToFont' => true,
|
||||
]);
|
||||
```
|
||||
|
||||
`freeserif` is bundled with mPDF and contains Devanagari glyphs.
|
||||
For better rendering use `dejavusans` or configure a custom font.
|
||||
|
||||
Custom font example:
|
||||
```php
|
||||
$mpdf = new Mpdf([
|
||||
'fontDir' => [__DIR__ . '/fonts'],
|
||||
'fontdata' => $defaultFontData + [
|
||||
'myfont' => ['R' => 'MyFont-Regular.ttf'],
|
||||
],
|
||||
'default_font' => 'myfont',
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Images in Headers
|
||||
|
||||
For images in `<htmlpageheader>`, use absolute server path:
|
||||
```html
|
||||
<htmlpageheader name="myHeader">
|
||||
<img src="/var/www/html/hms_etria/public/assets/images/logo.png" style="width:80px;">
|
||||
</htmlpageheader>
|
||||
```
|
||||
|
||||
Do NOT use relative paths or `url()`. The `{{H_logo_abs}}` token in this project
|
||||
resolves to `FCPATH . 'assets/images/' . $hLogo`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Tables in PDF
|
||||
|
||||
```html
|
||||
<table width="100%" border="0" cellpadding="4" cellspacing="0">
|
||||
<thead>
|
||||
<tr style="background:#f2f2f2;">
|
||||
<th>Medicine</th><th>Dose</th><th>Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Paracetamol</td><td>1 tab BD</td><td>5</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
- Use `border-collapse: collapse` in CSS for clean borders.
|
||||
- `border="0"` attribute overrides CSS borders — use CSS instead.
|
||||
- Tables are not CSS `display:table` — they use mPDF's own table engine.
|
||||
|
||||
---
|
||||
|
||||
## 10. How Discharge PDF is Built in This Project
|
||||
|
||||
File: `app/Controllers/Ipd_discharge.php` — method `show_discharge()`
|
||||
|
||||
```
|
||||
1. Load template settings from ipd_discharge_templates (id = requested ?tpl=N or is_default=1)
|
||||
2. Read / auto-generate discharge content (token-replaced template_html)
|
||||
3. Build mPDF constructor with: format, margins (all from template settings)
|
||||
4. Build WriteHTML fragment:
|
||||
<style>@page { margins; header: html_myHeader; }</style>
|
||||
<style>template_css</style>
|
||||
<htmlpageheader name="myHeader">header_html (tokens replaced)</htmlpageheader>
|
||||
<htmlpagefooter name="myFooter">footer_html (tokens replaced)</htmlpagefooter>
|
||||
rendered template body (template_html with tokens replaced)
|
||||
5. $mpdf->WriteHTML($pdfHtml)
|
||||
6. Output as binary PDF string → CI4 response
|
||||
```
|
||||
|
||||
**Token replacement**: `{{TOKEN_NAME}}` or `{{token_name}}` — case-insensitive.
|
||||
Available tokens: H_Name, H_logo_abs, PATIENT_NAME, UHID, IPD_CODE, ADMIT_DATE,
|
||||
DISCHARGE_DATE, FINAL_DIAGNOSIS, SURGERY, DISCHARGE_MEDICATIONS, DISCHARGE_SUMMARY,
|
||||
CONTENT (all sections), and many more — see placeholder list in template editor.
|
||||
|
||||
**Debug**: `/Ipd_discharge/show_discharge/{ipdId}/1?tpl={tplId}&html=1`
|
||||
Shows the exact HTML string passed to `WriteHTML()`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Common Issues and Fixes
|
||||
|
||||
| Problem | Cause | Fix |
|
||||
|---|---|---|
|
||||
| Header not repeating on every page | Missing `header: html_myHeader` in `@page` | Add `header: html_myHeader;` to `@page` block in CSS textarea |
|
||||
| CSS appears as visible text | CSS injected inside body `<div>`, not in `<style>` before content | Put all CSS in template CSS field; system puts it in `<style>` |
|
||||
| `{PAGENO}` shows literally | Token not supported in body (only in headers/footers) | Move to `<htmlpagefooter>` |
|
||||
| Hindi/Unicode text missing | Wrong font set | Use `freeserif` or `dejavusans`; enable `autoScriptToLang` |
|
||||
| Image not loading | Relative path | Use absolute server path or `{{H_logo_abs}}` token |
|
||||
| Page size wrong | Using `size:` in @page | Use `format` in Mpdf constructor instead |
|
||||
| Margins not applied | `@page` CSS absent or conflicting constructor values | When using `@page` CSS, constructor margins act as fallback only |
|
||||
|
||||
---
|
||||
|
||||
## 12. References
|
||||
|
||||
- Full docs: https://mpdf.github.io/
|
||||
- Named HTML headers (Method 4): https://mpdf.github.io/headers-footers/method-4.html
|
||||
- Supported CSS: https://mpdf.github.io/css-stylesheets/supported-css.html
|
||||
- @page CSS: https://mpdf.github.io/paging/using-page.html
|
||||
- Fonts in mPDF 7+: https://mpdf.github.io/fonts-languages/fonts-in-mpdf-7-x.html
|
||||
- WriteHTML: https://mpdf.github.io/reference/mpdf-functions/writehtml.html
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
# Session Summary: ABDM Sandbox Integration Setup Complete
|
||||
|
||||
**Date:** May 12, 2026
|
||||
**Status:** ✅ Configuration Phase Complete
|
||||
**Next Phase:** Testing & Validation
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objectives Achieved This Session
|
||||
|
||||
### ✅ 1. ABDM Sandbox Credentials Configuration
|
||||
- **Client ID:** `SBXID_033661`
|
||||
- **Client Secret:** Securely configured in `.env`
|
||||
- **Approval Status:** Confirmed sandbox approved
|
||||
- **Integration Mode:** DreamSoft Middleware Gateway
|
||||
|
||||
### ✅ 2. Application Configuration
|
||||
- Updated `.env` with full ABDM sandbox block
|
||||
- Verified `app/Config/AbdmConnector.php` ready (no changes needed)
|
||||
- Confirmed ABDM controller structure exists (`AbdmGateway.php`, `AbdmTaskBoard.php`)
|
||||
- Verified FHIR builder available (`FhirR4Builder.php`)
|
||||
|
||||
### ✅ 3. Documentation Created
|
||||
1. **`docs/ABDM_SANDBOX_SETUP.md`** — Complete integration guide
|
||||
- 13 sections covering credentials, API endpoints, workflow, troubleshooting
|
||||
- Database schema requirements
|
||||
- Middleware gateway details
|
||||
- Testing instructions
|
||||
|
||||
2. **`docs/ABDM_SANDBOX_CHECKLIST.md`** — Implementation checklist
|
||||
- Configuration status tracking
|
||||
- Phased implementation plan
|
||||
- Security notes
|
||||
- Success metrics
|
||||
|
||||
3. **`docs/ABDM_QUICK_START.md`** — Fast-track guide
|
||||
- 7-step immediate sequence
|
||||
- 7 progressive tests (2 min → 10 min each)
|
||||
- Common issues & fixes
|
||||
- Quick reference
|
||||
|
||||
4. **Repository Memory:** `abdm-sandbox-setup.md`
|
||||
- Persistent configuration notes
|
||||
- Credentials reference
|
||||
- Next steps checklist
|
||||
|
||||
### ✅ 4. Prerequisite Features Verified
|
||||
All OPD enhancements already implemented:
|
||||
- ✅ ABHA input field with validation
|
||||
- ✅ SNOMED CT complaint/diagnosis auto-resolution
|
||||
- ✅ Vitals numeric normalization
|
||||
- ✅ Medicine master SNOMED/ATC coding
|
||||
- ✅ Recent patient entries chips (OPD productivity feature)
|
||||
- ✅ FHIR R4 bundle generation (OPD + Prescription records)
|
||||
|
||||
---
|
||||
|
||||
## 📦 What's Configured & Ready
|
||||
|
||||
### .env File
|
||||
```env
|
||||
abdm.connector = dreamsoft
|
||||
abdm.directClientId = SBXID_033661
|
||||
abdm.directClientSecret = 656f79f1-ef99-495f-9f37-713219ecbbcf
|
||||
abdm.directBaseUrl = https://dev.abdm.gov.in/api/v3
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
|
||||
BRIDGE_SOURCE_CODE = SBXID_033661
|
||||
snomed.csnotk.baseUrl = https://csnotk.e-atria.in/csnoserv
|
||||
snomed.csnotk.enabled = true
|
||||
```
|
||||
|
||||
### ABDM API Endpoints Available
|
||||
| Operation | Endpoint | Status |
|
||||
|-----------|----------|--------|
|
||||
| ABHA Validation | `/AbdmGateway/abha_validate` | ✅ Ready |
|
||||
| Consent Request | `/AbdmGateway/consent_request` | ✅ Ready |
|
||||
| Consent Callback | `/AbdmGateway/consent_callback` | ✅ Ready |
|
||||
| Push OPD Bundle | `/AbdmGateway/share_prescription_bundle` | ✅ Ready |
|
||||
| Push Lab Bundle | `/AbdmGateway/share_diagnosis_report_bundle` | ✅ Ready |
|
||||
| Push Discharge | `/AbdmGateway/share_ipd_discharge_bundle` | ✅ Ready |
|
||||
|
||||
---
|
||||
|
||||
## ⏳ Immediate Action Items (USER RESPONSIBLE)
|
||||
|
||||
### Step 1️⃣ Get Bearer Token (BLOCKING)
|
||||
Contact middleware provider (CSNOtk/DreamSoft):
|
||||
- Request bearer token for source code: `SBXID_033661`
|
||||
- Once received, add to `.env`:
|
||||
```env
|
||||
BRIDGE_SYNC_TOKEN = <token-from-provider>
|
||||
```
|
||||
|
||||
### Step 2️⃣ Register in ABDM Sandbox
|
||||
- Visit: https://sandbox.abdm.gov.in
|
||||
- Register organization with Client ID: `SBXID_033661`
|
||||
- Obtain:
|
||||
- HFR ID (Health Facility Registry ID)
|
||||
- NPI (National Practitioner Identifier)
|
||||
- Add to `.env`:
|
||||
```env
|
||||
ABDM_HFR_ID = your-hfr-id
|
||||
ABDM_NPI_ID = your-npi-id
|
||||
```
|
||||
|
||||
### Step 3️⃣ Get Test Data
|
||||
- Obtain 2-3 test ABHA numbers from sandbox portal
|
||||
- Example: `14-0061-0000-0001`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Sequence (After Steps 1-3)
|
||||
|
||||
Once you have the bearer token and test ABHAs, follow this sequence:
|
||||
|
||||
1. **Test 1:** Middleware Connectivity (2 min)
|
||||
- Verify `https://csnotk.e-atria.in/api/bridge` is reachable
|
||||
|
||||
2. **Test 2:** ABHA Validation (5 min)
|
||||
- Link test ABHA to HMS patient
|
||||
|
||||
3. **Test 3:** SNOMED Search (3 min)
|
||||
- Test diagnosis code lookup via CSNOtk
|
||||
|
||||
4. **Test 4:** OPD Creation (10 min)
|
||||
- Create full OPD consult with FHIR bundle generation
|
||||
|
||||
5. **Test 5:** Consent Request (5 min)
|
||||
- Initiate consent in HMS
|
||||
|
||||
6. **Test 6:** Accept Consent (2 min)
|
||||
- Manually accept in ABDM sandbox portal
|
||||
|
||||
7. **Test 7:** Bundle Push (3 min)
|
||||
- Push OPD record to ABDM sandbox
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Project Timeline
|
||||
|
||||
### Phase 1: Testing & Validation (This Week)
|
||||
- Get bearer token & test credentials
|
||||
- Run 7-step test sequence
|
||||
- Validate all 7 tests pass
|
||||
|
||||
### Phase 2: Database Schema (Week 2)
|
||||
- Add ABDM fields to `opd_prescription` table
|
||||
- Add ABHA fields to `patient_master` table
|
||||
- Create `abdm_consent` tracking table
|
||||
- Run migrations
|
||||
|
||||
### Phase 3: UI/UX Enhancements (Week 3)
|
||||
- Add ABHA link UI in patient registration
|
||||
- Add ABDM actions panel to OPD form
|
||||
- Create consent request dialog
|
||||
- Add bundle push progress indicator
|
||||
|
||||
### Phase 4: Additional HI Types (Week 4+)
|
||||
- Implement Lab Report (DiagnosticReportRecord) bundle
|
||||
- Implement Discharge Summary (DischargeSummaryRecord)
|
||||
- Add Wellness record support
|
||||
- Add Immunization support
|
||||
|
||||
### Phase 5: Production Hardening (Month 2)
|
||||
- Error logging & alerting
|
||||
- Retry/reconciliation service
|
||||
- Audit trail & compliance logging
|
||||
- Rate limiting & performance optimization
|
||||
|
||||
---
|
||||
|
||||
## 📁 Key Files Created/Modified
|
||||
|
||||
### Created
|
||||
- ✅ `docs/ABDM_SANDBOX_SETUP.md` — Full integration guide
|
||||
- ✅ `docs/ABDM_SANDBOX_CHECKLIST.md` — Implementation checklist
|
||||
- ✅ `docs/ABDM_QUICK_START.md` — Fast-track testing guide
|
||||
- ✅ `/memories/repo/abdm-sandbox-setup.md` — Persistent notes
|
||||
|
||||
### Modified
|
||||
- ✅ `.env` — Added ABDM sandbox configuration block
|
||||
|
||||
### Already Available
|
||||
- ✅ `app/Controllers/AbdmGateway.php` — ABDM operations
|
||||
- ✅ `app/Controllers/AbdmTaskBoard.php` — ABDM dashboard
|
||||
- ✅ `app/Config/AbdmConnector.php` — Configuration loader
|
||||
- ✅ `app/Libraries/FhirR4Builder.php` — FHIR bundle generation
|
||||
|
||||
---
|
||||
|
||||
## 🎓 How to Continue
|
||||
|
||||
### To Run Tests:
|
||||
1. Read: `docs/ABDM_QUICK_START.md`
|
||||
2. Complete immediate steps 1-3
|
||||
3. Follow 7-step test sequence
|
||||
4. Report results
|
||||
|
||||
### To Access Configuration:
|
||||
- `.env` — All credentials & URLs configured
|
||||
- Quick reference: `docs/ABDM_QUICK_START.md` (end of document)
|
||||
|
||||
### To Debug Issues:
|
||||
- Troubleshooting: `docs/ABDM_SANDBOX_SETUP.md` (Section 11)
|
||||
- Logs: `writable/logs/log-*.log`
|
||||
- Test connectivity: `docs/ABDM_QUICK_START.md` (Test 1)
|
||||
|
||||
### For Implementation Details:
|
||||
- Complete guide: `docs/ABDM_SANDBOX_SETUP.md`
|
||||
- Database schema: `docs/ABDM_SANDBOX_SETUP.md` (Section 6)
|
||||
- API endpoints: `docs/ABDM_SANDBOX_SETUP.md` (Section 4)
|
||||
|
||||
---
|
||||
|
||||
## 💡 Key Configuration Points
|
||||
|
||||
### Connector Mode
|
||||
```php
|
||||
abdm.connector = dreamsoft // Routes via middleware gateway
|
||||
// Alternative: direct_abdm // For future direct ABDM API calls
|
||||
```
|
||||
|
||||
### Middleware Gateway
|
||||
- **Provider:** CSNOtk (e-atria.in)
|
||||
- **Purpose:** ABHA validation proxy, consent relay, bundle routing
|
||||
- **Required:** Bearer token (to be obtained)
|
||||
|
||||
### SNOMED Terminology
|
||||
- **Service:** CSNOtk v9.0 at https://csnotk.e-atria.in/csnoserv
|
||||
- **Endpoint:** GET /search/suggest?term=<query>
|
||||
- **Usage:** Diagnosis/complaint auto-complete
|
||||
|
||||
### Direct ABDM API (Future)
|
||||
- **Fallback mode** for direct NHA connectivity
|
||||
- **Credentials ready** (client ID & secret in .env)
|
||||
- **URL:** https://dev.abdm.gov.in/api/v3
|
||||
- **Status:** Configured but not active (using dreamsoft mode)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Reminders
|
||||
|
||||
- ❌ **Never commit `.env` to git** — sensitive data exposed
|
||||
- ❌ **Never log credentials** — redact in production logs
|
||||
- ✅ **Use environment variables** for CI/CD deployment
|
||||
- ✅ **Rotate secrets periodically** (production)
|
||||
- ✅ **Audit ABDM operations** for compliance
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features Integrated (For Context)
|
||||
|
||||
These features were implemented in previous sessions as ABDM compliance foundation:
|
||||
|
||||
1. **OPD ABDM Validation**
|
||||
- Strict ABDM readiness checks before consult save
|
||||
- Mandatory ABHA for certain patient types
|
||||
- SNOMED-coded diagnosis/complaints
|
||||
- Structured vital signs
|
||||
|
||||
2. **Medicine Coding**
|
||||
- Optional SNOMED codes for medicines
|
||||
- Optional ATC codes for medicines
|
||||
- FHIR medication request enrichment
|
||||
- Coding gap filters
|
||||
|
||||
3. **FHIR R4 Bundle Generation**
|
||||
- OPConsultRecord (main complaint + vitals)
|
||||
- PrescriptionRecord (medicines prescribed)
|
||||
- Expanded resources: Encounter, Practitioner, Observation, Condition, AllergyIntolerance, ServiceRequest
|
||||
- Composition with multiple sections
|
||||
|
||||
4. **Clinical Data Quality**
|
||||
- Recent patient entries chips (fast reuse)
|
||||
- SNOMED auto-resolution during data entry
|
||||
- Vitals numeric normalization
|
||||
- Route terminology consistency ("Where" → "Route")
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
After Phase 1 (Testing), success looks like:
|
||||
|
||||
- ✅ Test 1: Middleware responds with 200 status
|
||||
- ✅ Test 2: ABHA validates and links (≤2 sec)
|
||||
- ✅ Test 3: SNOMED search returns results (≤3 sec)
|
||||
- ✅ Test 4: OPD saves with fhir_bundle_id populated
|
||||
- ✅ Test 5: Consent request created in database
|
||||
- ✅ Test 6: Consent status changes to "GRANTED"
|
||||
- ✅ Test 7: Bundle pushed with status = "pushed"
|
||||
|
||||
---
|
||||
|
||||
## 📞 Next Check-In
|
||||
|
||||
**Recommended:** After you complete immediate steps 1-3
|
||||
|
||||
At that point, we can:
|
||||
1. Run full 7-step test sequence
|
||||
2. Debug any connectivity issues
|
||||
3. Proceed to Phase 2 (database schema)
|
||||
4. Begin production data migration
|
||||
|
||||
---
|
||||
|
||||
**Status:** 🟢 Ready for Sandbox Testing
|
||||
**Configuration:** Complete
|
||||
**Documentation:** Complete
|
||||
**Waiting For:** Bearer token & facility registration details
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Reference the detailed documentation:
|
||||
- Quick answers: `docs/ABDM_QUICK_START.md`
|
||||
- Deep dive: `docs/ABDM_SANDBOX_SETUP.md`
|
||||
- Checklist: `docs/ABDM_SANDBOX_CHECKLIST.md`
|
||||
|
|
@ -1,478 +0,0 @@
|
|||
# ABDM Middleware Gateway Setup - Complete Summary
|
||||
|
||||
**Date:** May 12, 2026
|
||||
**Task:** Create custom middleware gateway for `abdm-bridge.e-atria.in`
|
||||
**Status:** ✅ COMPLETE & READY FOR DEPLOYMENT
|
||||
|
||||
---
|
||||
|
||||
## 📦 What's Been Created
|
||||
|
||||
### 1. Gateway Application (Node.js + Express)
|
||||
|
||||
**Files:**
|
||||
- `middleware-gateway/server.js` — Main application with 6 API endpoints
|
||||
- `middleware-gateway/package.json` — Dependencies (Express, Axios, Winston, JWT, etc.)
|
||||
- `middleware-gateway/.env.example` — Configuration template
|
||||
|
||||
**Endpoints Implemented:**
|
||||
1. `GET /api/v3/health` — Health check (no auth)
|
||||
2. `POST /api/v3/abha/validate` — ABHA validation proxy
|
||||
3. `POST /api/v3/consent/request` — Consent request relay
|
||||
4. `POST /api/v3/bundle/push` — FHIR bundle push to ABDM
|
||||
5. `GET /api/v3/snomed/search` — SNOMED search proxy
|
||||
6. `GET /api/v3/gateway/status` — Dependency health check
|
||||
7. `GET /api/v3/bundle/:bundleId/status` — Check push status
|
||||
|
||||
### 2. Docker Setup
|
||||
|
||||
**Files:**
|
||||
- `middleware-gateway/Dockerfile` — Container image definition
|
||||
- `middleware-gateway/docker-compose.yml` — Multi-container orchestration (Gateway + Nginx)
|
||||
- `middleware-gateway/nginx.conf` — Reverse proxy + SSL + rate limiting
|
||||
|
||||
**Features:**
|
||||
- ✅ Production-grade Nginx reverse proxy
|
||||
- ✅ SSL/TLS 1.2+ enforcement
|
||||
- ✅ Rate limiting (100 req/15min per IP)
|
||||
- ✅ Security headers (HSTS, CSP, X-Frame-Options)
|
||||
- ✅ Gzip compression
|
||||
- ✅ Health checks (30s interval)
|
||||
- ✅ Auto-restart on failure
|
||||
|
||||
### 3. Deployment Automation
|
||||
|
||||
**Files:**
|
||||
- `middleware-gateway/deploy.sh` — Fully automated Ubuntu deployment
|
||||
- `middleware-gateway/setup-ubuntu.sh` — System dependency installer
|
||||
|
||||
**What Deploy Script Does:**
|
||||
1. ✅ Updates system packages
|
||||
2. ✅ Installs Docker & Docker Compose
|
||||
3. ✅ Installs Nginx & Certbot
|
||||
4. ✅ Creates app user
|
||||
5. ✅ Sets up application directory
|
||||
6. ✅ Gets SSL certificate from Let's Encrypt
|
||||
7. ✅ Builds Docker images
|
||||
8. ✅ Starts containers
|
||||
9. ✅ Configures auto-renewal
|
||||
10. ✅ Sets up systemd service
|
||||
|
||||
**Deployment Time:** ~15 minutes (automated)
|
||||
|
||||
### 4. Documentation
|
||||
|
||||
**Files:**
|
||||
- `middleware-gateway/README.md` — Quick reference guide
|
||||
- `middleware-gateway/DEPLOYMENT_GUIDE.md` — Detailed deployment steps
|
||||
- `docs/ABDM_GATEWAY_ARCHITECTURE.md` — Architecture & comparison (CSNOtk vs Custom)
|
||||
- `docs/ABDM_QUICK_START.md` — Fast-track testing guide
|
||||
- `docs/ABDM_SANDBOX_SETUP.md` — Full integration guide
|
||||
- `docs/ABDM_SANDBOX_CHECKLIST.md` — Implementation checklist
|
||||
|
||||
### 5. Configuration Updates
|
||||
|
||||
**Files Modified:**
|
||||
- `.env` — Added gateway option documentation
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Summary
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ HMS CI4 │
|
||||
│ (PHP/Laravel) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
│ (Optional NEW PATH)
|
||||
│ https://abdm-bridge.e-atria.in/api/v3
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Your Middleware Gateway │
|
||||
│ (Node.js + Express) │
|
||||
│ - Logging │
|
||||
│ - Rate limiting │
|
||||
│ - Request tracking │
|
||||
│ - SSL/TLS │
|
||||
└────────┬──────────┬────────────┘
|
||||
│ │
|
||||
┌────────▼──┐ ┌───▼──────────┐
|
||||
│ ABDM │ │ SNOMED │
|
||||
│ M3 API │ │ (CSNOtk) │
|
||||
│ │ │ │
|
||||
│ M3 Auth │ │ Terminology │
|
||||
│ Consent │ │ Service │
|
||||
│ Bundle │ │ │
|
||||
│ Push │ │ Lookup terms │
|
||||
└───────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 Directory Structure
|
||||
|
||||
```
|
||||
middleware-gateway/
|
||||
├── server.js # Main application (400+ lines)
|
||||
├── package.json # Dependencies
|
||||
├── Dockerfile # Container image
|
||||
├── docker-compose.yml # Multi-container setup
|
||||
├── nginx.conf # Reverse proxy config
|
||||
├── .env.example # Configuration template
|
||||
├── deploy.sh # Automated deployment (automated setup)
|
||||
├── setup-ubuntu.sh # Dependency installer
|
||||
├── README.md # Quick reference
|
||||
├── DEPLOYMENT_GUIDE.md # Full deployment steps
|
||||
└── logs/ # Application logs (created at runtime)
|
||||
├── combined.log # All logs
|
||||
└── error.log # Error logs only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Options
|
||||
|
||||
### Option A: Full Automation (Recommended)
|
||||
```bash
|
||||
# 1. Upload files to Ubuntu server
|
||||
scp -r middleware-gateway/ root@server:/opt/
|
||||
|
||||
# 2. Run one script
|
||||
ssh root@server
|
||||
cd /opt/middleware-gateway
|
||||
sudo ./deploy.sh
|
||||
|
||||
# Time: ~15 minutes (mostly automated)
|
||||
```
|
||||
|
||||
### Option B: Manual Steps
|
||||
```bash
|
||||
# Follow detailed guide:
|
||||
middleware-gateway/DEPLOYMENT_GUIDE.md
|
||||
|
||||
# Time: ~30 minutes (more control)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration Required
|
||||
|
||||
### Before Deployment
|
||||
|
||||
1. **Create Ubuntu Server**
|
||||
- Provider: DigitalOcean, AWS, Linode, Vultr
|
||||
- OS: Ubuntu 20.04 LTS or later
|
||||
- Size: 1GB RAM minimum
|
||||
- Network: Public IP + domain
|
||||
|
||||
2. **DNS Configuration**
|
||||
```
|
||||
abdm-bridge.e-atria.in A xxx.xxx.xxx.xxx
|
||||
```
|
||||
|
||||
3. **Update .env File**
|
||||
```env
|
||||
BRIDGE_SYNC_TOKEN=<your-bearer-token>
|
||||
ABDM_M3_TOKEN=<your-abdm-token>
|
||||
ABDM_HFR_ID=<your-hfr-id>
|
||||
ABDM_NPI_ID=<your-npi-id>
|
||||
```
|
||||
|
||||
### After Deployment
|
||||
|
||||
1. **Verify Health**
|
||||
```bash
|
||||
curl https://abdm-bridge.e-atria.in/api/v3/health
|
||||
```
|
||||
|
||||
2. **Update HMS .env**
|
||||
```bash
|
||||
BRIDGE_SYNC_URL = https://abdm-bridge.e-atria.in/api/v3
|
||||
```
|
||||
|
||||
3. **Restart HMS**
|
||||
```bash
|
||||
docker-compose restart # or systemctl restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Technical Stack
|
||||
|
||||
| Component | Technology | Version |
|
||||
|-----------|-----------|---------|
|
||||
| Runtime | Node.js | 20+ |
|
||||
| Framework | Express | 4.18 |
|
||||
| HTTP Client | Axios | 1.6 |
|
||||
| Logging | Winston | 3.11 |
|
||||
| Rate Limit | express-rate-limit | 7.1 |
|
||||
| Authentication | JWT/Bearer | Custom |
|
||||
| Reverse Proxy | Nginx | Alpine |
|
||||
| Container | Docker | Latest |
|
||||
| Orchestration | Docker Compose | 3.8 |
|
||||
| SSL | Let's Encrypt | Auto-renew |
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
### Security
|
||||
- ✅ SSL/TLS 1.2+ enforcement
|
||||
- ✅ Bearer token authentication
|
||||
- ✅ Request ID tracking
|
||||
- ✅ CORS policy
|
||||
- ✅ Security headers (HSTS, CSP, etc.)
|
||||
- ✅ Rate limiting per IP
|
||||
- ✅ Helmet.js security
|
||||
|
||||
### Reliability
|
||||
- ✅ Health checks every 30s
|
||||
- ✅ Auto-restart on failure
|
||||
- ✅ Graceful shutdown handling
|
||||
- ✅ Error logging (combined + error logs)
|
||||
- ✅ Request/response tracking
|
||||
- ✅ Dependency health checks
|
||||
|
||||
### Logging & Monitoring
|
||||
- ✅ Winston logging framework
|
||||
- ✅ Request timestamps
|
||||
- ✅ Error stack traces
|
||||
- ✅ Performance metrics
|
||||
- ✅ Authorization tracking
|
||||
- ✅ Log rotation capability
|
||||
|
||||
### Performance
|
||||
- ✅ Gzip compression
|
||||
- ✅ Connection pooling
|
||||
- ✅ Timeout controls
|
||||
- ✅ Resource limits (1 CPU, 512MB RAM)
|
||||
- ✅ Load balancing ready
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Integration Steps
|
||||
|
||||
### Step 1: Deploy Gateway (First Time)
|
||||
```bash
|
||||
cd /opt/abdm-gateway
|
||||
sudo ./deploy.sh # 15 minutes
|
||||
|
||||
# Once deployed, it runs forever (auto-restart)
|
||||
```
|
||||
|
||||
### Step 2: Point HMS to New Gateway
|
||||
```bash
|
||||
# Edit HMS .env
|
||||
BRIDGE_SYNC_URL = https://abdm-bridge.e-atria.in/api/v3
|
||||
|
||||
# Or keep CSNOtk if preferred:
|
||||
BRIDGE_SYNC_URL = https://csnotk.e-atria.in/api/bridge
|
||||
```
|
||||
|
||||
### Step 3: No Code Changes Required
|
||||
```
|
||||
Your gateway is 100% compatible with CSNOtk
|
||||
It returns identical response format
|
||||
HMS works without any modification
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Expectations
|
||||
|
||||
| Metric | CSNOtk (Public) | Your Gateway |
|
||||
|--------|-----------------|--------------|
|
||||
| Health Check | 200-500ms | 50-100ms |
|
||||
| ABHA Validation | 500-1000ms | 400-800ms |
|
||||
| Consent Request | 1-2s | 800ms-1.5s |
|
||||
| Bundle Push | 2-5s | 1.5-4s |
|
||||
| SNOMED Search | 300-800ms | 200-600ms |
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security Checklist
|
||||
|
||||
After deployment, verify:
|
||||
|
||||
- [ ] HTTPS working (curl https://...)
|
||||
- [ ] Bearer token required for APIs
|
||||
- [ ] Rate limiting active (test with 101 requests)
|
||||
- [ ] Security headers present (curl -v)
|
||||
- [ ] Logs encrypted/secure
|
||||
- [ ] No credentials in logs
|
||||
- [ ] SSL certificate auto-renewal active
|
||||
- [ ] Firewall rules configured (80, 443 only)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Debugging
|
||||
|
||||
### Monitor Logs
|
||||
```bash
|
||||
ssh root@server
|
||||
cd /opt/abdm-gateway
|
||||
|
||||
# Real-time logs
|
||||
docker logs -f abdm-bridge-gateway
|
||||
|
||||
# Error logs only
|
||||
docker logs abdm-bridge-gateway | grep -i error
|
||||
|
||||
# Specific request
|
||||
docker logs abdm-bridge-gateway | grep REQUEST-ID
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
```bash
|
||||
# Gateway health
|
||||
curl https://abdm-bridge.e-atria.in/api/v3/health
|
||||
|
||||
# All dependencies
|
||||
curl -H "Authorization: Bearer TOKEN" \
|
||||
https://abdm-bridge.e-atria.in/api/v3/gateway/status
|
||||
|
||||
# Nginx status
|
||||
curl https://127.0.0.1/nginx-status
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Certificate error | Run `sudo certbot renew` |
|
||||
| Port in use | `sudo lsof -i :80` and kill process |
|
||||
| Container won't start | `docker-compose logs abdm-gateway` |
|
||||
| High memory | Increase Docker limit in docker-compose.yml |
|
||||
| Slow responses | Check network connectivity to ABDM M3 |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Deployment Checklist
|
||||
|
||||
- [ ] Ubuntu server provisioned
|
||||
- [ ] DNS record created for subdomain
|
||||
- [ ] Files uploaded to /opt/abdm-gateway
|
||||
- [ ] .env configured with credentials
|
||||
- [ ] deploy.sh executed (sudo ./deploy.sh)
|
||||
- [ ] SSL certificate obtained
|
||||
- [ ] Health check passing (curl https://...)
|
||||
- [ ] All 3 dependencies responding
|
||||
- [ ] HMS configured to use new gateway
|
||||
- [ ] Test OPD ABHA flow working
|
||||
- [ ] Logs being collected properly
|
||||
- [ ] Auto-renewal set (cron verified)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Recommended Timeline
|
||||
|
||||
### Week 1: Testing with CSNOtk
|
||||
- Use existing csnotk.e-atria.in
|
||||
- Complete 7-step test sequence
|
||||
- Validate ABDM integration works
|
||||
|
||||
### Week 2: Prepare Your Gateway
|
||||
- Provision Ubuntu server
|
||||
- Run deploy.sh (15 min)
|
||||
- Test in parallel with CSNOtk
|
||||
|
||||
### Week 3: Switch to Your Gateway
|
||||
- Update HMS .env to point to new gateway
|
||||
- Run full test sequence again
|
||||
- Monitor for any issues
|
||||
|
||||
### Week 4+: Production Operations
|
||||
- Your gateway fully operational
|
||||
- Monitor logs and performance
|
||||
- Plan scaling if needed
|
||||
|
||||
---
|
||||
|
||||
## 💡 Why Custom Gateway?
|
||||
|
||||
### Benefits Over Public CSNOtk:
|
||||
1. **Audit Trail** — Full logging of every API call
|
||||
2. **Performance** — Dedicated server, no shared load
|
||||
3. **Customization** — Extend with custom logic
|
||||
4. **Reliability** — Your SLA, not vendor's
|
||||
5. **Security** — Token management per environment
|
||||
6. **Monitoring** — Full visibility into errors
|
||||
7. **Scaling** — Easy to add caching, load balancing
|
||||
8. **Compliance** — Meets regulatory requirements
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Immediate Actions
|
||||
|
||||
### This Week (Before Next Sync):
|
||||
1. **Review Architecture:** Read `docs/ABDM_GATEWAY_ARCHITECTURE.md`
|
||||
2. **Provision Server:** Create Ubuntu VPS (DigitalOcean/AWS/etc)
|
||||
3. **Upload Files:** Copy middleware-gateway to /opt/
|
||||
4. **Test Deploy:** Run deploy.sh (fully automated)
|
||||
5. **Verify:** curl https://abdm-bridge.e-atria.in/api/v3/health
|
||||
|
||||
### After Deployment:
|
||||
1. Update HMS .env: `BRIDGE_SYNC_URL = https://abdm-bridge.e-atria.in/api/v3`
|
||||
2. Run OPD test → automatically uses new gateway
|
||||
3. All other ABDM operations work without changes
|
||||
|
||||
---
|
||||
|
||||
## 📚 Complete Documentation
|
||||
|
||||
| Document | Purpose | File |
|
||||
|----------|---------|------|
|
||||
| Quick Start | 30-min overview | `middleware-gateway/README.md` |
|
||||
| Deployment | Step-by-step setup | `middleware-gateway/DEPLOYMENT_GUIDE.md` |
|
||||
| Architecture | Gateway comparison | `docs/ABDM_GATEWAY_ARCHITECTURE.md` |
|
||||
| ABDM Guide | Full integration | `docs/ABDM_SANDBOX_SETUP.md` |
|
||||
| Checklist | Implementation tracking | `docs/ABDM_SANDBOX_CHECKLIST.md` |
|
||||
|
||||
---
|
||||
|
||||
## ✅ What's Ready Right Now
|
||||
|
||||
- ✅ Complete Node.js application (server.js)
|
||||
- ✅ Docker containerization (Dockerfile + docker-compose.yml)
|
||||
- ✅ Nginx reverse proxy with SSL
|
||||
- ✅ Fully automated deployment script
|
||||
- ✅ Complete documentation
|
||||
- ✅ Configuration templates
|
||||
- ✅ All dependencies defined
|
||||
|
||||
**No coding required.** Just:
|
||||
1. Provision Ubuntu server
|
||||
2. Run deploy.sh
|
||||
3. Update HMS .env
|
||||
4. Done! 🎉
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
- **Express.js:** https://expressjs.com
|
||||
- **Docker:** https://docs.docker.com
|
||||
- **Nginx:** https://nginx.org/en/docs/
|
||||
- **Let's Encrypt:** https://letsencrypt.org/getting-started/
|
||||
- **Node.js:** https://nodejs.org/en/docs/
|
||||
|
||||
---
|
||||
|
||||
**Status:** 🟢 COMPLETE & PRODUCTION-READY
|
||||
**Deployment Time:** 15 minutes (automated)
|
||||
**Complexity:** Simple (one deploy.sh script)
|
||||
**Maintenance:** Minimal (auto SSL renewal, auto-restart)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Questions?
|
||||
|
||||
All answered in:
|
||||
1. Quick ref: `middleware-gateway/README.md`
|
||||
2. Deep dive: `middleware-gateway/DEPLOYMENT_GUIDE.md`
|
||||
3. Architecture: `docs/ABDM_GATEWAY_ARCHITECTURE.md`
|
||||
|
||||
**Ready to deploy?** 🚀
|
||||
Next step: Provision Ubuntu server and run `sudo ./deploy.sh`
|
||||
|
|
@ -1,503 +0,0 @@
|
|||
# Surgery/Procedure Autocomplete Testing Checklist
|
||||
|
||||
## ✅ Implementation Complete
|
||||
|
||||
### **Enhanced Features:**
|
||||
1. ✅ Custom dropdown with SNOMED CT and ICD code display
|
||||
2. ✅ Arrow key navigation (↑↓)
|
||||
3. ✅ Enter/Escape key support
|
||||
4. ✅ Visual hover feedback
|
||||
5. ✅ Keyboard highlight (blue background)
|
||||
6. ✅ Code badges (blue for SNOMED, gray for ICD)
|
||||
7. ✅ Master CRUD integration
|
||||
8. ✅ **NEW: Quick "Save in Master" buttons** - Add new terms without opening full CRUD modal
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Procedure
|
||||
|
||||
### **Quick-Add Workflow Overview** ⭐ **NEW FEATURE**
|
||||
|
||||
The **"Save in Master"** button provides a fast way to add new surgery/procedure terms without leaving the discharge form:
|
||||
|
||||
**Traditional Flow (Master CRUD):**
|
||||
1. Click "Master CRUD" button → Modal opens
|
||||
2. Select type (Surgery/Procedure)
|
||||
3. Type name
|
||||
4. Enter codes
|
||||
5. Save
|
||||
6. Close modal
|
||||
7. Return to discharge form
|
||||
8. Type the name again in autocomplete
|
||||
9. Select from dropdown
|
||||
|
||||
**New Quick-Add Flow:**
|
||||
1. Type new surgery/procedure name in input
|
||||
2. Click "Save in Master" button → Quick modal opens
|
||||
3. Name already filled (from input)
|
||||
4. Enter SNOMED/ICD codes (optional)
|
||||
5. Click "Save in Master"
|
||||
6. Modal auto-closes
|
||||
7. Input keeps the name, master_id set
|
||||
8. **Ready to click "+ADD Row"** immediately!
|
||||
|
||||
**Benefits:**
|
||||
- ⚡ **Faster:** 3 fewer steps
|
||||
- 🎯 **Context-aware:** Name pre-filled
|
||||
- ✅ **No re-typing:** Input value preserved
|
||||
- 🔗 **Auto-linked:** Master ID set automatically
|
||||
- 📊 **ABDM-ready:** Can add codes during workflow
|
||||
|
||||
---
|
||||
|
||||
### **Test 1: Surgery Autocomplete Basic Flow**
|
||||
**Steps:**
|
||||
1. Navigate to any IPD discharge form: `http://localhost/Ipd_discharge/ipd_select/{ipd_id}`
|
||||
2. Scroll to "Surgery / Procedure / delivery if any" section
|
||||
3. Click in the **Surgery name** input field
|
||||
4. Type at least 2 characters (e.g., "append")
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Dropdown appears below input
|
||||
- ✅ Shows matching surgery names from master table
|
||||
- ✅ Each item displays:
|
||||
- Surgery name (bold, prominent)
|
||||
- Blue badge with SNOMED/term code (if exists)
|
||||
- Gray badge with ICD code (if exists)
|
||||
- ✅ Items are clickable with mouse hover highlighting
|
||||
- ✅ **NEW:** Green "Save in Master" button visible next to input
|
||||
|
||||
**Screenshot:**
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ Surgery name input: "append" [+Save in Master] │
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ Appendectomy │ │
|
||||
│ │ [SNOMED: 80146002] [ICD: K35.80] │ │
|
||||
│ ├───────────────────────────────────────┤ │
|
||||
│ │ Appendectomy, laparoscopic │ │
|
||||
│ │ [SNOMED: 174041007] [ICD: K35.89] │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Test 2: Keyboard Navigation**
|
||||
**Steps:**
|
||||
1. Type to show dropdown
|
||||
2. Press **Arrow Down** key multiple times
|
||||
3. Press **Arrow Up** key
|
||||
4. Press **Enter** to select highlighted item
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Arrow Down: Moves blue highlight down one item
|
||||
- ✅ Arrow Up: Moves blue highlight up one item
|
||||
- ✅ Highlighted item has blue background with white text
|
||||
- ✅ Highlighted item auto-scrolls into view if needed
|
||||
- ✅ Enter: Selects highlighted item and fills input
|
||||
- ✅ Dropdown closes after selection
|
||||
|
||||
---
|
||||
|
||||
### **Test 3: Mouse Selection**
|
||||
**Steps:**
|
||||
1. Type to show dropdown
|
||||
2. Hover mouse over different items
|
||||
3. Click on an item
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Hover: Item gets light gray background
|
||||
- ✅ Click: Input fills with selected surgery name
|
||||
- ✅ Dropdown closes
|
||||
- ✅ Hidden field `new_surgery_master_id` is populated with master record ID
|
||||
|
||||
---
|
||||
|
||||
### **Test 4: Escape Key**
|
||||
**Steps:**
|
||||
1. Type to show dropdown
|
||||
2. Press **Escape** key
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Dropdown closes immediately
|
||||
- ✅ Input value remains unchanged
|
||||
|
||||
---
|
||||
|
||||
### **Test 5: Procedure Autocomplete** *(Same as Surgery)*
|
||||
**Steps:**
|
||||
1. Scroll to **Procedure** section (below Surgery)
|
||||
2. Type in the **Procedure name** input
|
||||
3. Verify dropdown behavior is identical to Surgery
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Separate dropdown container (no interference)
|
||||
- ✅ Shows procedure terms (filtered by `type='procedure'`)
|
||||
- ✅ All keyboard/mouse behaviors work independently
|
||||
- ✅ Hidden field `new_procedure_master_id` populated correctly
|
||||
|
||||
---
|
||||
|
||||
### **Test 6: Master CRUD Integration**
|
||||
**Steps:**
|
||||
1. Click the **"Master CRUD"** button
|
||||
2. Modal should open with Surgery/Procedure master list
|
||||
3. Add a new surgery:
|
||||
- Type: Surgery
|
||||
- Name: "Test Surgery ABC"
|
||||
- Code: "TEST001"
|
||||
- ICD Code: "Z99.99"
|
||||
- Click **Save**
|
||||
4. Close modal
|
||||
5. Type "test" in Surgery name input
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Modal opens showing master list
|
||||
- ✅ New record saves successfully
|
||||
- ✅ Dropdown now shows "Test Surgery ABC" with codes
|
||||
- ✅ Can select the new item immediately
|
||||
|
||||
---
|
||||
|
||||
### **Test 7: Quick "Save in Master" Feature** ⭐ **NEW**
|
||||
**Steps:**
|
||||
1. Type a NEW surgery name (not in master): "Minimally Invasive Heart Surgery"
|
||||
2. Click the green **"Save in Master"** button next to the input
|
||||
3. Modal should open with the name pre-filled
|
||||
4. Enter optional codes:
|
||||
- SNOMED CT Code: "232717009"
|
||||
- ICD Code: "Z95.1"
|
||||
5. Click **"Save in Master"** button in modal
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Modal opens with title "Save New Surgery in Master"
|
||||
- ✅ Name field shows typed name (read-only)
|
||||
- ✅ SNOMED CT Code field is editable (optional)
|
||||
- ✅ ICD Code field is editable (optional)
|
||||
- ✅ Status shows "Saving..." while processing
|
||||
- ✅ Success message "✓ Saved successfully!" appears
|
||||
- ✅ Input field still shows the surgery name
|
||||
- ✅ Hidden field `new_surgery_master_id` is populated with new ID
|
||||
- ✅ Modal closes automatically after 1 second
|
||||
- ✅ Can now click "+ADD Row" to add the surgery to discharge
|
||||
|
||||
**Verification:**
|
||||
```sql
|
||||
-- Check if record was saved
|
||||
SELECT * FROM ipd_discharge_surgery_master
|
||||
WHERE term_name = 'Minimally Invasive Heart Surgery'
|
||||
ORDER BY id DESC LIMIT 1;
|
||||
```
|
||||
|
||||
**UI Screenshot:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Save New Surgery in Master [X] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Name * │
|
||||
│ [Minimally Invasive Heart Surgery] │
|
||||
│ This will be saved to the master table │
|
||||
│ │
|
||||
│ SNOMED CT Code │
|
||||
│ [232717009 ] │
|
||||
│ Optional - SNOMED CT terminology code │
|
||||
│ │
|
||||
│ ICD Code │
|
||||
│ [Z95.1 ] │
|
||||
│ Optional - ICD-10 code │
|
||||
│ │
|
||||
│ ✓ Saved successfully! │
|
||||
├─────────────────────────────────────────┤
|
||||
│ [Cancel] [✓ Save in Master] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Test 8: Quick-Add for Procedure** *(Same as Surgery)*
|
||||
**Steps:**
|
||||
1. Type a NEW procedure name: "Endoscopic Biopsy"
|
||||
2. Click **"Save in Master"** button next to Procedure input
|
||||
3. Modal opens with "Save New Procedure in Master" title
|
||||
4. Enter codes if needed
|
||||
5. Save and verify
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ All behavior same as Surgery quick-add
|
||||
- ✅ Modal title shows "Procedure" instead of "Surgery"
|
||||
- ✅ Saves to master with `term_type='procedure'`
|
||||
- ✅ Procedure input and `new_procedure_master_id` populated correctly
|
||||
|
||||
---
|
||||
|
||||
### **Test 9: Quick-Add Validation**
|
||||
**Steps:**
|
||||
1. Leave Surgery input EMPTY
|
||||
2. Click **"Save in Master"** button
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Alert: "Please enter a surgery name first"
|
||||
- ✅ Modal does NOT open
|
||||
- ✅ Focus remains on input field
|
||||
|
||||
---
|
||||
|
||||
### **Test 10: Quick-Add Integration with Autocomplete**
|
||||
**Steps:**
|
||||
1. Use quick-add to save "Test Surgery XYZ"
|
||||
2. Clear the input
|
||||
3. Type "test" to trigger autocomplete
|
||||
4. Verify new term appears in dropdown
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Newly saved term appears in autocomplete results
|
||||
- ✅ Shows any SNOMED/ICD codes that were entered
|
||||
- ✅ Can be selected from dropdown
|
||||
- ✅ Master linkage works correctly
|
||||
|
||||
---
|
||||
|
||||
### **Test 7 (OLD): Add Row Flow**
|
||||
**Steps:**
|
||||
1. Use autocomplete to select a surgery
|
||||
2. Fill in Date field (optional)
|
||||
3. Fill in Remark field (optional)
|
||||
4. Click **+ADD Row** button
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ New row appears in Surgery table above input
|
||||
- ✅ Row shows: Name | Date | Remark | Remove button
|
||||
- ✅ Form can be submitted with selected surgery
|
||||
- ✅ Surgery record links to master table via `new_surgery_master_id`
|
||||
|
||||
---
|
||||
|
||||
### **Test 11: Code Display Variations**
|
||||
**Test different master records:**
|
||||
- Record with both SNOMED and ICD → Shows both badges
|
||||
- Record with only SNOMED → Shows blue badge only
|
||||
- Record with only ICD → Shows gray badge only
|
||||
- Record with no codes → Shows name only (no badges)
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Badge display adapts correctly to available data
|
||||
- ✅ Layout remains clean and readable
|
||||
|
||||
---
|
||||
|
||||
### **Test 11: Code Display Variations**
|
||||
**Test different master records:**
|
||||
- Record with both SNOMED and ICD → Shows both badges
|
||||
- Record with only SNOMED → Shows blue badge only
|
||||
- Record with only ICD → Shows gray badge only
|
||||
- Record with no codes → Shows name only (no badges)
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Badge display adapts correctly to available data
|
||||
- ✅ Layout remains clean and readable
|
||||
|
||||
---
|
||||
|
||||
### **Test 12: Performance & Responsiveness**
|
||||
**Steps:**
|
||||
1. Type characters rapidly
|
||||
2. Delete and retype
|
||||
3. Test with slow network (throttle in DevTools)
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Search has 300ms debounce (doesn't spam requests)
|
||||
- ✅ Dropdown updates smoothly
|
||||
- ✅ No console errors
|
||||
- ✅ No JavaScript exceptions
|
||||
|
||||
---
|
||||
|
||||
### **Test 13: Edge Cases**
|
||||
**Steps:**
|
||||
1. Type only 1 character → Dropdown should NOT appear
|
||||
2. Type 2+ characters → Dropdown appears
|
||||
3. Clear input completely → Dropdown hides
|
||||
4. Click outside dropdown while open → Closes after 200ms
|
||||
5. Type search with no results → Dropdown hides
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ All edge cases handled gracefully
|
||||
- ✅ No errors in console
|
||||
- ✅ UI remains responsive
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Known Issues / Limitations
|
||||
|
||||
### ✅ **FIXED Issues:**
|
||||
1. ~~Old datalist code conflicting with new dropdown~~ → Removed `bindSurgeryTermLookup` calls
|
||||
2. ~~Hidden master_id fields not being populated~~ → Now correctly stores on selection
|
||||
3. ~~Dropdown positioning issues~~ → Using `position: relative` parent wrapper
|
||||
|
||||
### ⚠️ **Potential Future Enhancements:**
|
||||
1. **Fuzzy Search:** Currently exact substring match. Could add fuzzy/phonetic matching.
|
||||
2. **Recent Items:** Could cache recently used surgeries for quick access.
|
||||
3. **Favorites:** Allow marking frequently used items as favorites.
|
||||
4. **Multi-select:** For cases where multiple procedures done simultaneously.
|
||||
5. **Search by Code:** Currently searches by name. Could also search by SNOMED/ICD code directly.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Database Verification
|
||||
|
||||
### **Check Master Table:**
|
||||
```sql
|
||||
-- View all surgeries with codes
|
||||
SELECT * FROM ipd_discharge_surgery_master
|
||||
WHERE term_type = 'surgery' AND is_active = 1
|
||||
ORDER BY term_name;
|
||||
|
||||
-- View all procedures with codes
|
||||
SELECT * FROM ipd_discharge_surgery_master
|
||||
WHERE term_type = 'procedure' AND is_active = 1
|
||||
ORDER BY term_name;
|
||||
|
||||
-- Check if codes are populated
|
||||
SELECT
|
||||
term_type,
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN term_code IS NOT NULL AND term_code != '' THEN 1 ELSE 0 END) as with_snomed,
|
||||
SUM(CASE WHEN icd_code IS NOT NULL AND icd_code != '' THEN 1 ELSE 0 END) as with_icd
|
||||
FROM ipd_discharge_surgery_master
|
||||
GROUP BY term_type;
|
||||
```
|
||||
|
||||
### **Check Linked Records:**
|
||||
```sql
|
||||
-- After adding surgery via form
|
||||
SELECT
|
||||
s.surgery_name,
|
||||
s.surgery_date,
|
||||
s.surgery_remark,
|
||||
m.term_name as master_term_name,
|
||||
m.term_code as snomed_code,
|
||||
m.icd_code
|
||||
FROM ipd_discharge_surgery s
|
||||
LEFT JOIN ipd_discharge_surgery_master m ON m.id = s.surgery_master_id
|
||||
WHERE s.ipd_id = {test_ipd_id};
|
||||
|
||||
-- Same for procedures
|
||||
SELECT
|
||||
p.procedure_name,
|
||||
p.procedure_date,
|
||||
p.procedure_remark,
|
||||
m.term_name as master_term_name,
|
||||
m.term_code as snomed_code,
|
||||
m.icd_code
|
||||
FROM ipd_discharge_procedure p
|
||||
LEFT JOIN ipd_discharge_surgery_master m ON m.id = p.procedure_master_id
|
||||
WHERE p.ipd_id = {test_ipd_id};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### **Dropdown not appearing:**
|
||||
1. Check browser console for JavaScript errors
|
||||
2. Verify jQuery is loaded: `console.log(window.jQuery)`
|
||||
3. Check network tab for AJAX call to `surgery_master_lookup`
|
||||
4. Verify typing at least 2 characters
|
||||
|
||||
### **Codes not showing:**
|
||||
1. Run database query to check if master records have `term_code` and `icd_code` populated
|
||||
2. Check browser DevTools → Network → XHR response to see if codes are in JSON
|
||||
3. Verify badge CSS classes are rendering (inspect element)
|
||||
|
||||
### **Arrow keys not working:**
|
||||
1. Check if input has focus
|
||||
2. Verify `keydown` event listener is attached
|
||||
3. Check console for JavaScript errors
|
||||
4. Test in different browsers
|
||||
|
||||
### **Master CRUD not refreshing dropdown:**
|
||||
1. After saving in Master CRUD modal, new items should appear immediately
|
||||
2. The `fetchMasterRows()` is called after save
|
||||
3. Next autocomplete search will fetch updated list from database
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completion Checklist
|
||||
|
||||
### **Code Changes:**
|
||||
- [x] HTML: Replaced datalist with custom dropdown divs
|
||||
- [x] HTML: Added position-relative wrapper for dropdown positioning
|
||||
- [x] HTML: Added input-group layout for Surgery/Procedure inputs
|
||||
- [x] HTML: Added "Save in Master" buttons with Bootstrap styling
|
||||
- [x] HTML: Created quickAddTermModal with SNOMED/ICD fields
|
||||
- [x] JavaScript: Created `initSurgeryProcedureAutocomplete()` function
|
||||
- [x] JavaScript: Created `initTermAutocomplete()` with full keyboard support
|
||||
- [x] JavaScript: Created `initQuickAddTermHandlers()` for quick-add functionality
|
||||
- [x] JavaScript: Arrow key navigation with visual highlighting
|
||||
- [x] JavaScript: Click and hover event handlers
|
||||
- [x] JavaScript: 300ms debounce on input for performance
|
||||
- [x] JavaScript: Badge rendering for SNOMED/ICD codes
|
||||
- [x] JavaScript: Quick-add modal handlers with validation
|
||||
- [x] JavaScript: Auto-populate input and master_id after quick-add save
|
||||
- [x] JavaScript: Removed old `bindSurgeryTermLookup` calls from `initSurgeryTools()`
|
||||
- [x] CSS: Dropdown styling with Bootstrap classes
|
||||
- [x] Integration: Called from `initSurgeryProcedureAutocomplete()` on page load
|
||||
|
||||
### **Testing:**
|
||||
- [ ] Test 1: Surgery autocomplete basic flow
|
||||
- [ ] Test 2: Keyboard navigation (arrows, Enter, Escape)
|
||||
- [ ] Test 3: Mouse selection and hover
|
||||
- [ ] Test 4: Escape key closes dropdown
|
||||
- [ ] Test 5: Procedure autocomplete (independent operation)
|
||||
- [ ] Test 6: Master CRUD integration
|
||||
- [ ] Test 7: Quick "Save in Master" for Surgery ⭐ NEW
|
||||
- [ ] Test 8: Quick "Save in Master" for Procedure ⭐ NEW
|
||||
- [ ] Test 9: Quick-add validation (empty input) ⭐ NEW
|
||||
- [ ] Test 10: Quick-add integration with autocomplete ⭐ NEW
|
||||
- [ ] Test 11: Add row with selected item
|
||||
- [ ] Test 12: Code badge display variations
|
||||
- [ ] Test 13: Performance and debouncing
|
||||
- [ ] Test 14: Edge cases (min length, no results, blur)
|
||||
|
||||
### **Verification:**
|
||||
- [ ] No JavaScript console errors
|
||||
- [ ] No PHP errors in logs
|
||||
- [ ] Database records properly linked to master table
|
||||
- [ ] SNOMED and ICD codes visible in UI
|
||||
- [ ] Works on Chrome/Firefox/Edge
|
||||
- [ ] Works on mobile/tablet (touch selection)
|
||||
- [ ] Page load time not impacted
|
||||
|
||||
---
|
||||
|
||||
## 📝 User Acceptance
|
||||
|
||||
**Tested By:** _____________
|
||||
**Date:** _____________
|
||||
**Browser:** _____________
|
||||
**Result:** ✅ PASS / ❌ FAIL
|
||||
|
||||
**Notes:**
|
||||
```
|
||||
[Add any observations, issues, or suggestions here]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps (Optional Enhancements)
|
||||
|
||||
1. **Search Optimization:** Add full-text search index on `term_name`, `term_code`, `icd_code`
|
||||
2. **Code Validation:** Add SNOMED CT validator to verify codes are valid
|
||||
3. **ICD Lookup:** Add button to search ICD-10 codes from external API
|
||||
4. **Bulk Import:** Allow importing surgery/procedure master from CSV/Excel
|
||||
5. **Analytics:** Track most commonly used surgeries for quick access
|
||||
6. **Templates:** Create surgery groups/templates (e.g., "C-Section Package")
|
||||
7. **ABDM Sync:** Auto-sync selected surgeries to ABDM FHIR bundles
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Created:** 2026-06-06
|
||||
**Last Updated:** 2026-06-06
|
||||
**Status:** Ready for Testing
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# System Operations Panel - Test File
|
||||
|
||||
**Date:** August 4, 2026
|
||||
**Version:** 1.0.0
|
||||
|
||||
## Latest Updates
|
||||
|
||||
### Network IP Detection
|
||||
- Method 1: `$_SERVER['SERVER_ADDR']` (Apache/Nginx)
|
||||
- Method 2: `/sbin/ip` (absolute path)
|
||||
- Method 3: `/proc/net/route` parsing
|
||||
- Method 4: `hostname -I`
|
||||
- Method 5: `ifconfig`
|
||||
- Method 6: Socket connection to 8.8.8.8
|
||||
|
||||
### Internet Connectivity Check
|
||||
- 8 fallback methods for robust detection
|
||||
- Tests multiple DNS providers (Google, Cloudflare, Quad9)
|
||||
- Returns "Online" or "Offline" (never "Unavailable")
|
||||
|
||||
### Maintenance Actions
|
||||
- Update HMS (git pull)
|
||||
- Restart Web Server
|
||||
- Restart PHP-FPM
|
||||
- Shutdown Server
|
||||
- Reboot Server
|
||||
|
||||
---
|
||||
**If you can see this file on live server after git pull, the update system is working!**
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
HMS GitHub Deployment Verification Test
|
||||
========================================
|
||||
|
||||
This file is used to verify that the GitHub deployment feature works correctly.
|
||||
|
||||
Test Timestamp: 2026-08-04
|
||||
Deployed Method: Direct GitHub Download (via Deploy from GitHub button)
|
||||
|
||||
How to verify deployment worked:
|
||||
1. Check if this file exists on live server at: /var/www/html/hms_etria/TEST_DEPLOYMENT.txt
|
||||
2. Check file modification time matches deployment time
|
||||
3. Commit history should show this test in the latest deployment
|
||||
|
||||
Verification Commands (run on live server):
|
||||
ls -lh /var/www/html/hms_etria/TEST_DEPLOYMENT.txt
|
||||
date
|
||||
tail -50 /var/www/html/hms_etria/writable/system_update.log
|
||||
|
||||
Expected Output:
|
||||
- File should exist
|
||||
- File timestamp should match when you clicked "Deploy from GitHub"
|
||||
- system_update.log should show successful deployment with file list
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
# WASA Security Audit, Mobile Device Authorization & System Hardening Session Handover
|
||||
|
||||
**Date:** 2026-10-15
|
||||
**Branch:** `main`
|
||||
**Latest Commit:** `de4f658` — `feat(security): implement WASA audit mitigations, mobile anti-enumeration & device authorization, and user management fixes`
|
||||
**Live Environment:** [demo.e-atria.net](https://demo.e-atria.net/) (`10.8.0.1` / `/var/www/html/hms_etria`)
|
||||
**Status:** **All Fixes Implemented, Verified, Committed, Pushed, and Deployed Live**
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
This session addressed critical security recommendations from the **WASA (Web Application Security Audit)** assessment (`scratch/E-atria_WASA_Observations.xlsx`), overhauled the mobile PWA authentication architecture for nursing and medical staff to eliminate identity enumeration, resolved user creation validation issues, and deployed all updates to the production demonstration server (`https://demo.e-atria.net/`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Implementations & Resolved Areas
|
||||
|
||||
### A. WASA Audit Remediations (4 Key Observations)
|
||||
|
||||
| # | Observation | Severity / CWE | Key Technical Remediation |
|
||||
|---|-------------|----------------|---------------------------|
|
||||
| **1** | **Sensitive Information Exposure via API** | Medium / High<br>`CWE-200` | Permanently stripped `app_pin` from all API response payloads (`NursingApi.php` & `DoctorApi.php`). Replaced plain PINs with Bcrypt hashing (`PASSWORD_BCRYPT`). Enforced field minimization (only `id`, `name`, `department`). |
|
||||
| **2** | **Use of Outdated / Unsupported JS Libraries** | Medium<br>`CWE-1104` | Isolated rich text editors (CKEditor 4.22.1 / TinyMCE) strictly to authenticated administrative template management. Added server-side HTML Purifier filtering and output encoding (`esc()`). Hardened DataTables callback rendering against DOM-based XSS. |
|
||||
| **3** | **Hidden API Exposure & Sensitive Data Leakage** | Medium<br>`CWE-200` | Removed unauthenticated enumeration endpoints (`/api/v1/nursing/nurses`, `/api/v1/doctor/list`). Enforced HMAC `device_token` authorization, intranet subnet verification (`isHospitalIntranet()`), and brute-force rate-limiting (`checkRateLimit()`). |
|
||||
| **4** | **Missing HTTP Security Headers** | Low / Medium<br>`CWE-693` | Created and globally registered `app/Filters/SecureHeadersFilter.php`. Injects: `X-Frame-Options: SAMEORIGIN`, `X-Content-Type-Options: nosniff`, `X-XSS-Protection: 1; mode=block`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy: geolocation=(), camera=(self), microphone=()`, and `Strict-Transport-Security: max-age=31536000; includeSubDomains`. Enforced `HttpOnly`, `SameSite=Lax`, and `Secure` cookie flags. |
|
||||
|
||||
---
|
||||
|
||||
### B. Mobile App Anti-Enumeration & Device Security Redesign
|
||||
|
||||
- **Problem Addressed:** Anyone scanning a hospital mobile app QR code was presented with a staff profile dropdown listing doctor/nurse names and designations before authenticating.
|
||||
- **Frontend Changes (`public/App/Nursing/index.html` & `public/App/Doctor/index.html`):**
|
||||
- Completely eliminated public staff dropdowns and unauthenticated profile-fetching API calls.
|
||||
- **First-Time Device Pairing:** Staff must enter their **Staff ID / Doctor ID** + a 6-digit **Device Security Code** (issued by the HMS Admin) + **PIN/Password**.
|
||||
- **Quick Unlock for Paired Devices:** Device stores an HMAC-signed `device_token`. The user simply inputs their 4-digit PIN to unlock. Includes a clean *"Switch User / Re-pair Device"* option.
|
||||
- **Administrative Master Management:**
|
||||
- **Views:** `Nurse_Search_V.php`, `Doctor_Search_V.php`, and `Doctor_profile_V.php` now display 6-digit **Device Security Code** badges, copy buttons, clean pairing instruction cards in QR modals, and instant "Regenerate" actions.
|
||||
- **Models:** `NurseModel.php` and `DoctorModel.php` handle automatic generation and verification of unique 6-digit device security codes.
|
||||
- **Header QR Standees (`app/Views/partials/header.php`, `welcome_message.php`, `login.php`):**
|
||||
- Removed staff pre-selection logic and cleaned QR standee printable posters.
|
||||
|
||||
---
|
||||
|
||||
### C. CSRF Protection & "Action Not Allowed" Fixes
|
||||
|
||||
- **CSRF Engine:** In `app/Config/Security.php`, enabled session-based CSRF protection (`$csrfProtection = 'session'`, `$regenerate = true`).
|
||||
- **Global Interceptor (`public/assets/js/csrf-guard.js`):**
|
||||
- Intercepts all jQuery AJAX (`ajaxSend`, `ajaxComplete`), Axios, and native `window.fetch()` requests.
|
||||
- Injects `X-CSRF-TOKEN` and keeps form hidden inputs synchronized across SPA and AJAX actions.
|
||||
- **Fixed Specific Endpoints:**
|
||||
- `app/Views/diagnosis/pathology_detail.php`: X-Ray removal AJAX actions now include CSRF tokens and handle dynamic token refreshes.
|
||||
- `app/Views/billing/ipd/panel_discharge.php`: IPD discharge form validation and submission secured with synchronized CSRF tokens.
|
||||
|
||||
---
|
||||
|
||||
### D. User Management & Creation Validation Fixes
|
||||
|
||||
- **Validation Rules (`app/Config/Auth.php`):**
|
||||
- Updated `$usernameValidationRules` to permit hyphens and underscores: `regex_match[/\A[a-zA-Z0-9\._-]+\z/]`.
|
||||
- **Controller Logic (`app/Controllers/Setting/UserManagement.php`):**
|
||||
- Aligned username regex with Auth config.
|
||||
- Added automatic fallback email `<username>@hospital.local` when registering staff accounts without personal emails.
|
||||
- **UI Enhancements (`app/Views/Setting/Admin/UserManagement/create.php`):**
|
||||
- Added password eye show/hide toggle.
|
||||
- Added a quick *"Set Staff@123"* button for rapid onboarding.
|
||||
- Added client-side pre-submit validation preventing cryptic backend submission failures.
|
||||
|
||||
---
|
||||
|
||||
### E. IDE Environment & MCP Configuration
|
||||
|
||||
- **Issue:** IDE showed `MCP Error: android-management-api: calling "initialize": rejected by transport: oauth2: "invalid_grant" "Bad Request"`.
|
||||
- **Root Cause:** Global MCP config contained an entry for `android-management-api` configured with `google_credentials`. Since the Google OAuth refresh token for that specific enterprise API was expired/revoked, the IDE failed on initialization.
|
||||
- **Resolution:** Removed the unused `android-management-api` entry from `C:/Users/d_s_b/.gemini/config/mcp_config.json`. Verified valid JSON format. Error banner resolved.
|
||||
|
||||
---
|
||||
|
||||
## 3. Deployment & Git Status
|
||||
|
||||
1. **Commit:** `de4f658` pushed to `origin/main`.
|
||||
2. **Server:** Live deployment completed on `demo.e-atria.net` (`root@10.8.0.1` / `/var/www/html/hms_etria`):
|
||||
- `git pull origin main` executed (Fast-forward 26 files changed, 1640 insertions, 299 deletions).
|
||||
- CodeIgniter 4 CLI database migrations executed cleanly (`spark migrate --namespace App`).
|
||||
- Cache cleared (`spark cache:clear`).
|
||||
- Verified HTTP security headers (`X-Frame-Options: SAMEORIGIN`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`) live on `https://demo.e-atria.net/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Files Created / Modified
|
||||
|
||||
| File | Type | Purpose |
|
||||
|------|------|---------|
|
||||
| `app/Filters/SecureHeadersFilter.php` | New | Global response filter enforcing OWASP security headers |
|
||||
| `public/assets/js/csrf-guard.js` | New | Global CSRF interceptor for jQuery, Axios, and Fetch |
|
||||
| `app/Config/Filters.php` | Modified | Registered `secureheaders` filter globally |
|
||||
| `app/Config/Security.php` | Modified | Session CSRF and secure cookie configuration |
|
||||
| `app/Config/Auth.php` | Modified | Expanded username regex allowing `._-` characters |
|
||||
| `app/Controllers/Api/v1/NursingApi.php` | Modified | Device code pairing, bcrypt PINs, token auth & rate limits |
|
||||
| `app/Controllers/Api/v1/DoctorApi.php` | Modified | Device code pairing, bcrypt PINs, token auth & rate limits |
|
||||
| `app/Controllers/Setting/UserManagement.php` | Modified | Fallback email generation and username validation |
|
||||
| `app/Views/Setting/Admin/UserManagement/create.php` | Modified | Eye toggle, quick password helper, pre-validation |
|
||||
| `app/Views/Setting/Nurse/Nurse_Search_V.php` | Modified | Device security code badges, copy & pairing modal |
|
||||
| `app/Views/Setting/Doctor/Doctor_Search_V.php` | Modified | Device security code badges, copy & pairing modal |
|
||||
| `app/Views/Setting/Doctor/Doctor_profile_V.php` | Modified | Device security code display & regeneration |
|
||||
| `public/App/Nursing/index.html` | Modified | 3-factor device registration & quick PIN unlock PWA |
|
||||
| `public/App/Doctor/index.html` | Modified | 3-factor device registration & quick PIN unlock PWA |
|
||||
| `app/Views/diagnosis/pathology_detail.php` | Modified | CSRF token injection on X-ray delete actions |
|
||||
| `app/Views/billing/ipd/panel_discharge.php` | Modified | CSRF token injection on IPD discharge form |
|
||||
| `deploy.sh` | Modified | Added `hms-live` SSH host configuration support |
|
||||
| `C:/Users/d_s_b/.gemini/config/mcp_config.json` | Modified | Removed faulty `android-management-api` MCP server |
|
||||
|
|
@ -1,743 +0,0 @@
|
|||
# Worklog - HMS_CI4_OLD
|
||||
|
||||
Date: 2026-02-08
|
||||
|
||||
---
|
||||
|
||||
## ABDM Immunization Duplicate Control — 2026-08-15
|
||||
|
||||
- Made ImmunizationRecord care-context references stable per source record (`IMM-{record_id}`), while preserving previously stored references and deterministic aggregate fallbacks.
|
||||
- Removed the timestamp-based Immunization fallback from `FhirR4Builder` so retries cannot create a new gateway identity.
|
||||
- Normalized direct and outbox push responses: HTTP 201 succeeds; HTTP 409 succeeds only for `DUPLICATE_RECORD`; HTTP 422 remains a non-retryable validation failure with field details.
|
||||
- Preserved returned gateway `record_id`, `queue_id`, and `first_pushed_at`, persisting each when the deployed schema provides a matching column.
|
||||
- Added focused regression tests for stable Immunization references, accepted duplicate responses, generic conflicts, HTTP 201, and detailed HTTP 422 errors.
|
||||
|
||||
## ABDM M2 Link Status + Immunization Task Board — 2026-08-15
|
||||
|
||||
- Fixed M2 discovery/fetch to use canonical `health_records` care-context references and HI types first, with `patient_records` retained as a backward-compatible fallback.
|
||||
- Fixed link callbacks to accept snake_case and camelCase request/care-context identifiers, persist request IDs as transaction metadata, update both `record_links` and `health_records`, and complete source-keyed work tasks.
|
||||
- Made link synchronization idempotent: linked records cannot be downgraded, stale pending callbacks cannot erase failures, and duplicate callbacks preserve the confirmed link timestamp.
|
||||
- Added the `Immunization Record` task-board tab for completed vaccinations and local M2 registration using the exact `ImmunizationRecord` HI type.
|
||||
- Task-board Immunization registration forces `m2_only=1`; it stores the FHIR record for M2 discovery/linking and does not call a Push API.
|
||||
- Added focused tests for callback identifier parsing and status transition behavior.
|
||||
|
||||
## Storestock Module — 2026-04-19
|
||||
|
||||
Ported Hospital Stock Management (Storestock) module from CI3 to CI4.
|
||||
|
||||
### Files created
|
||||
- `app/Controllers/Storestock.php` — full port: index, indent_list, getIndentTable, new_indent, indent_create, indent_show, indent_items_list, add_item (type 1/2/3), get_drug, final_invoice, print_single_indent, store_stock, store_stock_result, get_product_stock, report_2, main_store
|
||||
- `app/Views/storestock/dashboard.php`
|
||||
- `app/Views/storestock/indent_list.php` — jQuery DataTable server-side, CI4 CSRF
|
||||
- `app/Views/storestock/new_indent.php` — location + employee indent creation
|
||||
- `app/Views/storestock/indent_edit.php` — add/update/delete items via AJAX, jQuery autocomplete for drug search
|
||||
- `app/Views/storestock/indent_item_list.php` — AJAX partial; qty edit, remove buttons
|
||||
- `app/Views/storestock/final_invoice.php` — read-only summary + print link
|
||||
- `app/Views/storestock/store_stock_search.php` — search form with schedule filter
|
||||
- `app/Views/storestock/stock_search_result.php` — result table + modal for batch detail
|
||||
- `app/Views/storestock/stock_item_history.php` — batch-wise purchase detail table
|
||||
- `app/Views/storestock/day_report.php` — date range stub
|
||||
- `app/Views/storestock/main_store_dashboard.php` — main store nav panel
|
||||
- `app/Views/storestock/store_print.php` — mPDF print template
|
||||
|
||||
### Routes modified
|
||||
- `app/Config/Routes.php` — 17 Storestock routes added after `service('auth')->routes()`
|
||||
|
||||
### Key patterns
|
||||
- Auth via `ensureStoreAccess()` — checks `hospital_stock.access` permission + group membership
|
||||
- `getIndentTable()` uses parameterised queries (no raw user-input interpolation)
|
||||
- `add_item()` calls stored procs `p_stock_update_purchase_id_store` + `p_update_med_GST_store`
|
||||
- indent_code generated as `S{ym}{padded_id}` after insert
|
||||
- Delete archives row to `inv_stock_item_delete` before deleting from `inv_stock_item`
|
||||
- `print_single_indent` uses mPDF (Mpdf\Mpdf), no debug toolbar
|
||||
|
||||
## Summary
|
||||
Ported OPD Charges, IPD Charges, and Package modules from CI3 to CI4 with consistent NiceAdmin UI, AJAX modal flows, Simple-DataTables, insurance rate handling, and print/Excel exports.
|
||||
|
||||
## OPD Charges
|
||||
- Controller: app/Controllers/Item.php
|
||||
- Model: app/Models/ItemModel.php
|
||||
- Routes: app/Config/Routes.php (group: item)
|
||||
- Views: app/Views/Setting/Charges/OPD_Charges/
|
||||
- item_search_V.php (header buttons: Charge Groups, Add New Charge; filters; print/export)
|
||||
- item_search_adv.php (list partial with DataTable)
|
||||
- item_profile_V.php (modal edit; insurance rates; refresh list on update)
|
||||
- item_create_V.php
|
||||
- item_type_search_v.php / item_type_profile.php / item_type_create.php
|
||||
- item_item_list.php (print layout with heading, borders, base rate fallback)
|
||||
- item_item_excel.php (Excel export)
|
||||
- item_insurance_list.php (insurance rate table partial)
|
||||
- Model joins (no DB views):
|
||||
- items: hc_items + hc_item_type
|
||||
- insurance items: hc_items_insurance + hc_insurance, filter isdelete=0
|
||||
- Print: insurance selection adds Amount + Code; missing insurance uses Base Rate
|
||||
- Excel: output via response headers (no debug toolbar injection)
|
||||
|
||||
## IPD Charges
|
||||
- Controller: app/Controllers/ItemIpd.php
|
||||
- Model: app/Models/ItemIpdModel.php
|
||||
- Routes: app/Config/Routes.php (groups: item-ipd + legacy Item_IPD)
|
||||
- Views: app/Views/Setting/Charges/IPD_Charges/
|
||||
- item_search_V.php, item_search_adv.php
|
||||
- item_profile_V.php, item_create_V.php
|
||||
- item_type_search_v.php / item_type_profile.php / item_type_create.php
|
||||
- item_item_list.php (print)
|
||||
- item_item_excel.php (Excel)
|
||||
- item_insurance_list.php
|
||||
- Model joins (no DB views):
|
||||
- items: ipd_items + ipd_item_type
|
||||
- insurance items: ipd_items_insurance + hc_insurance, filter isdelete=0
|
||||
|
||||
## Package (IPD Package)
|
||||
- Controller: app/Controllers/Package.php
|
||||
- Model: app/Models/PackageModel.php
|
||||
- Routes: app/Config/Routes.php (groups: package + legacy Package + legacy IPD_Package)
|
||||
- Views: app/Views/Setting/Charges/Package/
|
||||
- item_search_V.php, item_search_adv.php
|
||||
- item_profile_V.php, item_create_V.php
|
||||
- item_type_search_v.php / item_type_profile.php / item_type_create.php
|
||||
- item_item_list.php (print)
|
||||
- item_item_excel.php (Excel)
|
||||
- item_insurance_list.php
|
||||
- Model joins (no DB views):
|
||||
- packages: package, package_group
|
||||
- insurance items: package_insurance + hc_insurance, filter isdelete=0
|
||||
- Audit tables: package_update and package_insurance_update
|
||||
- If not present, audit insert skipped (prevents errors)
|
||||
|
||||
## UI + Behavior
|
||||
- Charges tiles updated:
|
||||
- setting/charges -> IPD tile uses item-ipd/search
|
||||
- setting/charges -> Package tile uses package/search
|
||||
- Modal edits refresh list:
|
||||
- OPD: window.refreshChargeList()
|
||||
- IPD: window.refreshChargeList()
|
||||
- Package: window.refreshPackageList()
|
||||
- Print buttons in header next to filter; export Excel added
|
||||
- Print layout: heading with group + insurance; no OPD/IPD or group columns; table borders
|
||||
- Base rate fallback: if insurance-specific rate missing, show base amount and Code column shows "Base Rate"
|
||||
|
||||
## Known Tables Used
|
||||
- OPD: hc_items, hc_item_type, hc_items_insurance, hc_items_update, hc_items_insurance_update
|
||||
- IPD: ipd_items, ipd_item_type, ipd_items_insurance, ipd_items_update, ipd_items_insurance_update
|
||||
- Package: package, package_group, package_insurance, package_update, package_insurance_update
|
||||
- Insurance: hc_insurance
|
||||
|
||||
## Notes for Next Session
|
||||
- If audit tables for Package are required, create migrations for package_update and package_insurance_update.
|
||||
- If isdelete column missing on insurance tables, remove filter.
|
||||
- Debug toolbar can break Excel; keep response headers to avoid injection.
|
||||
- Legacy routes are supported for old JS paths.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-16
|
||||
|
||||
## Diagnosis CI3 → CI4 Endpoint Mapping (Lab_Report parity)
|
||||
|
||||
### Fully mapped in CI4
|
||||
- `lab_master` → `Diagnosis::labMaster` via `Lab_Report/lab_master`
|
||||
- `lab_path/{lab_type}` → `Diagnosis::labPathLegacy` via `Lab_Report/lab_path/(:num)`
|
||||
- `search_lab_4/{lab_type}` → `Diagnosis::searchLab` via `Lab_Report/search_lab_4/(:num)`
|
||||
- `search_lab_4_srno/{lab_type}` → `Diagnosis::searchLabBySrno` via `Lab_Report/search_lab_4_srno/(:num)`
|
||||
- `search_lab_4_labno/{lab_type}` → `Diagnosis::searchLabByLabno` via `Lab_Report/search_lab_4_labno/(:num)`
|
||||
- `select_lab_invoice/{inv_id}/{lab_type}` → `Diagnosis::selectLabInvoicePath`
|
||||
- `select_lab_invoice_path/{inv_id}/{lab_type}` → `Diagnosis::selectLabInvoicePath`
|
||||
- `lab_date_show/{inv_id}/{lab_type}` → `Diagnosis::labDateShow`
|
||||
- `test_list/{inv_id}/{lab_type}` → `Diagnosis::testList`
|
||||
- `xray_test_list/{inv_id}/{lab_type}` → `Diagnosis::testList`
|
||||
- `lab_tab_1_process/{test_id}/{lab_type}` → `Diagnosis::labTab1Process`
|
||||
- `create_report_xray/{req_id}` → `Diagnosis::createReportXray`
|
||||
- `show_report_final/{req_id}` → `Diagnosis::showReportFinalXray`
|
||||
- `get_template_xray/{id}` → `Diagnosis::getTemplateXray`
|
||||
- `Final_Update_xray/{req_id}` → `Diagnosis::finalUpdateXray`
|
||||
- `confirm_report_xray/{req_id}` → `Diagnosis::confirmReportXray`
|
||||
|
||||
### Missing legacy endpoints (not yet mapped)
|
||||
- `report_file_list/{inv_id}/{lab_type}/{delete?}`
|
||||
- `select_lab_radiology/{inv_id}/{lab_type}`
|
||||
|
||||
### Notes
|
||||
- Current CI4 radiology flow uses `Diagnosis::selectLabInvoicePath` for detail screen and does not require a separate `select_lab_radiology` endpoint in active views.
|
||||
- File upload/list actions in CI4 diagnosis view are currently placeholders (`uploadFiles`, `showFiles`) and do not yet have controller endpoints.
|
||||
|
||||
## Future Backlog: AI-assisted Image Diagnosis (Ultrasound / X-Ray / CT)
|
||||
|
||||
### Goal
|
||||
- Add a professional image workspace where users can upload/scan images, review them, run AI-assisted analysis, and move AI draft findings into radiology report editor with doctor review.
|
||||
|
||||
### Phase 1 (MVP - internal)
|
||||
- Build one modal workspace in diagnosis detail screen:
|
||||
- Left panel: image preview, next/previous, zoom.
|
||||
- Right panel: AI findings, AI impression, confidence, doctor editable text.
|
||||
- Wire existing action buttons:
|
||||
- Upload Files -> open workspace + upload image.
|
||||
- Scan -> open workspace + scan image flow.
|
||||
- Show Files -> list previously uploaded/scanned images.
|
||||
- Add backend APIs in Diagnosis controller:
|
||||
- uploadImage(reqId)
|
||||
- listImages(reqId)
|
||||
- scanImage(reqId)
|
||||
- analyzeImageAi(reqId, fileId)
|
||||
- saveAiDraft(reqId, fileId)
|
||||
- acceptAiToReport(reqId)
|
||||
- Add DB table for AI result tracking (suggested: diagnosis_image_ai):
|
||||
- id, req_id, file_id, ai_findings, ai_impression, confidence,
|
||||
- model_name, model_version, status, reviewed_by, reviewed_at,
|
||||
- created_at, updated_at.
|
||||
- Add visible safety label: AI Suggestion - Doctor Review Required.
|
||||
|
||||
### Phase 2 (real AI integration)
|
||||
- Integrate external AI service endpoint (Python/FastAPI or equivalent).
|
||||
- Send selected image path to AI service and store structured response.
|
||||
- Add timeout/error handling and retry button in UI.
|
||||
|
||||
### Phase 3 (clinical workflow hardening)
|
||||
- Add audit trail for accepted/edited AI content before final report verify.
|
||||
- Add role check so only authorized users can accept AI output.
|
||||
- Keep final confirm manual; AI never auto-verifies reports.
|
||||
|
||||
### UI/Workflow Notes
|
||||
- AI output should prefill radiology editor, not directly replace final report.
|
||||
- Show per-image history for repeat review and comparison.
|
||||
- Keep terminology consistent with current imaging workflow labels.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-16
|
||||
|
||||
## Pharmacy Module Kickoff (Legacy Medical)
|
||||
|
||||
### What was verified
|
||||
- Legacy pharmacy dashboard source is in old code:
|
||||
- `old_code_ci3/views/Medical/Dashboard.php`
|
||||
- Main legacy pharmacy controller is very large and feature-rich:
|
||||
- `old_code_ci3/controllers/Medical.php` (~5k lines)
|
||||
- Related legacy controllers:
|
||||
- `old_code_ci3/controllers/Medical_backpanel.php`
|
||||
- `old_code_ci3/controllers/Medical_Report.php`
|
||||
- `old_code_ci3/controllers/Medical_Print.php`
|
||||
- `old_code_ci3/controllers/Payment_Medical.php`
|
||||
|
||||
### Important routing finding
|
||||
- Current CI4 improved auto-routing is disabled (`app/Config/Routing.php` has `autoRoute = false`).
|
||||
- No explicit CI4 `/Medical` routes found in `app/Config/Routes.php`.
|
||||
- No current CI4 Medical controller files found under `app/Controllers`.
|
||||
|
||||
### Legacy dashboard button map (from old Dashboard.php)
|
||||
- OPD Sale -> `/Medical/search_customer`
|
||||
- Invoice -> `/Medical/Invoice_Med_Draft`
|
||||
- IPD/Credit -> `/Medical/list_org_ipd`
|
||||
- Org. Credit -> `/Medical/list_org`
|
||||
- Return Counter Sale -> `/Medical/Invoice_Med_Return`
|
||||
- Day Report -> `/Medical_Report/Report_2`
|
||||
- Sale Day Report -> `/Medical_Report/Report_1`
|
||||
- Payment Report -> `/Medical_Report/Report_Payment_Recieved`
|
||||
- Store Stock -> `/Medical_backpanel/store_stock`
|
||||
- Store Main -> `/Medical/main_store`
|
||||
- Master -> `/Medical_backpanel`
|
||||
|
||||
### Suggested migration order (phase wise)
|
||||
1. Dashboard + navigation tiles.
|
||||
2. OPD Sale + Invoice Draft list.
|
||||
3. Return invoice flow.
|
||||
4. Store stock and stock statement.
|
||||
5. Reports (day/sale/payment).
|
||||
6. Backpanel master screens.
|
||||
|
||||
### SQL views received from user (confirmed)
|
||||
- `v_med_item_return`
|
||||
- `v_med_item_with_return`
|
||||
- `v_ipd_list`
|
||||
- `v_med_short_list`
|
||||
- `v_product_stock`
|
||||
- `v_product_stock_sub`
|
||||
- `v_sale_item`
|
||||
- `v_sale_item_sub`
|
||||
|
||||
### Dependency note
|
||||
- `v_ipd_list` depends on `v_ipd_doc_list` (seen in view body join).
|
||||
- Before CI4 pharmacy migration, ensure `v_ipd_doc_list` exists in target DB with compatible schema.
|
||||
|
||||
### Next implementation start point
|
||||
- Begin CI4 Pharmacy phase-1 with dashboard route/controller/view parity for `/Medical` tiles, then wire OPD Sale + Draft Invoice list endpoints.
|
||||
|
||||
### Phase-1 implementation done (CI4)
|
||||
- Added controller: app/Controllers/Medical.php
|
||||
- Added routes:
|
||||
- Medical
|
||||
- Medical/search_customer
|
||||
- Medical/Invoice_Med_Draft
|
||||
- Medical/Invoice_Med_Return
|
||||
- Medical/list_org_ipd
|
||||
- Medical/list_org
|
||||
- Medical/main_store
|
||||
- Medical/store_stock
|
||||
- Medical/master
|
||||
- Added views:
|
||||
- app/Views/medical/dashboard.php
|
||||
- app/Views/medical/search_customer.php
|
||||
- app/Views/medical/invoice_med_draft.php
|
||||
- app/Views/medical/placeholder.php
|
||||
- Status:
|
||||
- Dashboard works at /Medical.
|
||||
- OPD Sale and Invoice Draft panels are functional.
|
||||
- Remaining tiles currently show migration placeholder pending deeper CI3 parity port.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-19
|
||||
|
||||
## Pharmacy -> Master continuation
|
||||
|
||||
### Fix completed
|
||||
- Module: `Drug Sale Customer Wise Report`
|
||||
- Endpoint: `Medical::drug_patient_distribute`
|
||||
- Root-cause fixed in `getDrugPatientDistributeRows()`:
|
||||
- Legacy behavior: when Drug Name is blank and Schedule filter is blank, show all matching date-range records.
|
||||
- CI4 regression: returned empty result unless at least one schedule was selected.
|
||||
- Fix: schedule condition is now applied only when schedules are selected; blank schedule no longer forces empty result.
|
||||
|
||||
### Validation
|
||||
- `php -l app/Controllers/Medical.php` passed (no syntax errors).
|
||||
|
||||
### Step done: Med. Payment Edit Logs (legacy links)
|
||||
- Implemented legacy page endpoint: `Payment_Medical/payment_log`
|
||||
- Implemented legacy result endpoint: `Payment_Medical/payment_log_data`
|
||||
- Added CI4 controller methods in `app/Controllers/Payment_Medical.php`:
|
||||
- `payment_log()` for filter page
|
||||
- `payment_log_data()` for date-range log result table
|
||||
- Added CI4 views:
|
||||
- `app/Views/medical/payment_log.php`
|
||||
- `app/Views/medical/payment_log_data.php`
|
||||
- Updated routes in `app/Config/Routes.php`:
|
||||
- `GET Payment_Medical/payment_log -> Payment_Medical::payment_log`
|
||||
- `GET|POST Payment_Medical/payment_log_data -> Payment_Medical::payment_log_data`
|
||||
- Updated Master menu link in `app/Views/medical/master.php`:
|
||||
- `Med. Payment Edit Logs` now opens `Payment_Medical/payment_log`
|
||||
|
||||
### Step fix: Med. Payment Edit action buttons not working
|
||||
- Root cause: `payment_edit` HTML was injected using `innerHTML` in `payment_search`, so inline `<script>` inside the injected response did not execute.
|
||||
- Fix applied in `app/Views/medical/payment_search.php`:
|
||||
- Added delegated click handlers for:
|
||||
- `#btn_update_bank` -> `Payment_Medical/change_to_bank`
|
||||
- `#btn_update_cash` -> `Payment_Medical/change_to_cash`
|
||||
- `#btn_update_user` -> `Payment_Medical/change_user`
|
||||
- `#btn_update_amount` -> `Payment_Medical/update_amount`
|
||||
- Replaced direct result assignment with shared render helper to consistently update `#searchresult`.
|
||||
- Validation: `php -l app/Views/medical/payment_search.php` passed.
|
||||
|
||||
### Step fix: Med. Payment Edit duplicate submit (2x POST)
|
||||
- Evidence: HAR showed two POST requests for single button action (first with payload, second empty/no payload).
|
||||
- Root cause: both handlers were active:
|
||||
- Inline click handlers inside `app/Views/medical/payment_edit.php`
|
||||
- Delegated click handlers in `app/Views/medical/payment_search.php`
|
||||
- Fix:
|
||||
- Removed inline `<script>` handlers from `payment_edit.php`.
|
||||
- Kept `payment_search.php` delegated handlers as single source of action binding.
|
||||
- Added `actionInFlight` guard in `payment_search.php` to prevent rapid duplicate action posts.
|
||||
- Validation:
|
||||
- `php -l app/Views/medical/payment_edit.php` passed.
|
||||
- `php -l app/Views/medical/payment_search.php` passed.
|
||||
|
||||
### Step fix: Med. Payment Edit Logs showing blank
|
||||
- Symptom: `Payment_Medical/payment_log` displayed 0 rows for date range where legacy HMS showed logs.
|
||||
- Fix in `app/Controllers/Payment_Medical.php` (`payment_log_data`):
|
||||
- Replaced Query Builder date condition with raw SQL function filter:
|
||||
- `DATE(l.insert_datetime) BETWEEN 'from' AND 'to'`
|
||||
- Added fallback fetch (latest 500 logs, unfiltered) when filtered rows are empty, to handle schema/date-field drift gracefully.
|
||||
- Validation: `php -l app/Controllers/Payment_Medical.php` passed.
|
||||
|
||||
### Step update: log table name compatibility
|
||||
- User confirmed legacy expectation to store Med Payment Edit logs in `payment_history_log`.
|
||||
- Updated `app/Controllers/Payment_Medical.php`:
|
||||
- Added resolver to choose log table in this order:
|
||||
1) `payment_history_log`
|
||||
2) `paymentmedical_history_log`
|
||||
- Applied resolver to both:
|
||||
- write path (`insertPaymentLog`)
|
||||
- read path (`payment_log_data` joins/field detection)
|
||||
- Result: CI4 now reads/writes the same legacy table name where available, while still supporting older schema names.
|
||||
|
||||
### Step update: Update Log text population
|
||||
- User requested visible log text like: `Amount value Change 120 to 100`.
|
||||
- Updated `app/Controllers/Payment_Medical.php`:
|
||||
- `update_amount()` now writes explicit log text:
|
||||
- `Amount value Change <old> to <new>`
|
||||
- `insertPaymentLog()` now writes to `update_remark` when `update_log` column is missing.
|
||||
- `payment_log_data()` now reads `update_log` with fallback/coalesce from `update_remark`.
|
||||
- Validation: `php -l app/Controllers/Payment_Medical.php` passed.
|
||||
|
||||
### Step update: explicit logs for Bank/CASH/User actions
|
||||
- Added readable `update_log` text for:
|
||||
- `change_to_bank`: `Payment mode Change <old> to BANK [source] TranID:<id>`
|
||||
- `change_to_cash`: `Payment mode Change <old> to CASH`
|
||||
- `change_user`: `Update User Change <old_user> to <new_user>`
|
||||
- Also validates payment row existence before update in bank/cash actions.
|
||||
- Validation: `php -l app/Controllers/Payment_Medical.php` passed.
|
||||
|
||||
### Step done: Med. Invoice Item Update Logs (legacy parity)
|
||||
- Target legacy endpoints:
|
||||
- `Medical_backpanel/invoice_item_log`
|
||||
- `Medical_backpanel/invoice_item_log_data`
|
||||
- Updates applied:
|
||||
- Added route alias in `app/Config/Routes.php`:
|
||||
- `GET|POST Medical_backpanel/invoice_item_log_data -> Medical::invoice_item_log_data`
|
||||
- Updated `Medical::invoice_item_log_data` in `app/Controllers/Medical.php`:
|
||||
- Accepts both modern fields (`date_from`, `date_to`) and legacy `opd_date_range`.
|
||||
- Uses payment log table fallback:
|
||||
- `payment_history_log` first
|
||||
- `paymentmedical_history_log` fallback
|
||||
- Reads payment update text from `update_log` with fallback to `update_remark`.
|
||||
- Updated `app/Views/medical/invoice_item_log.php`:
|
||||
- Sends legacy `opd_date_range` token and posts to legacy URL `Medical_backpanel/invoice_item_log_data`.
|
||||
- Updated `app/Views/medical/invoice_item_log_data.php`:
|
||||
- Added DataTable initialization for search/pagination parity.
|
||||
- Validation:
|
||||
- `php -l app/Controllers/Medical.php` passed.
|
||||
- `php -l app/Config/Routes.php` passed.
|
||||
- `php -l app/Views/medical/invoice_item_log.php` passed.
|
||||
- `php -l app/Views/medical/invoice_item_log_data.php` passed.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-22
|
||||
|
||||
## OPD Upgrade - AI Requirements (User Confirmed)
|
||||
|
||||
### Mandatory AI behavior to keep in upgraded OPD
|
||||
- Use AI-assisted **Autotype** in OPD prescription text areas (fast drafting while doctor types).
|
||||
- Support **Hinglish -> English** conversion in complaint/diagnosis style inputs.
|
||||
- Show **Patient History Alerts** during OPD prescribing (old complaints/diagnosis/risk context visible before final save).
|
||||
|
||||
### Implementation direction (parity + enhancement)
|
||||
- Keep old familiar OPD workflow/tabs while adding AI features inline (no disruptive screen redesign).
|
||||
- Reuse current CI4 OPD AI foundation already present in `Opd_prescription`:
|
||||
- `complaints_search`
|
||||
- `complaints_parse`
|
||||
- `complaints_ai_draft`
|
||||
- Add history alert feed from patient prior OPD records and remarks in the prescription screen header/side context.
|
||||
|
||||
### Acceptance criteria
|
||||
- Doctor can type Hinglish symptoms and get normalized English complaint text quickly.
|
||||
- Doctor can trigger AI draft/autotype suggestion and then edit before save.
|
||||
- High-value history (past Rx trend / prior diagnosis / patient remarks / key vitals trend) appears as alert cards in OPD screen.
|
||||
- AI output is assistive only; final clinical decision remains manual.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-23
|
||||
|
||||
## OPD Prescription Session Checkpoint (Saved)
|
||||
|
||||
### Completed in this session
|
||||
- Rx-Group medicine parity improvements:
|
||||
- Dose master dropdowns integrated (Dose/When/Frequency/Where).
|
||||
- Human-readable dose labels shown in list (ID -> label mapping).
|
||||
- Rx-Group badge now shows group name (not only row ID).
|
||||
- Salt/Generic fallback improved in Rx-Group medicine list.
|
||||
- OPD medicine workspace data quality display:
|
||||
- Generic column fallback added (`genericname` -> `salt_name`) for blank generic values.
|
||||
- Clinical Templates bootstrap for fresh systems:
|
||||
- Added seeder `ClinicalTemplateWorkspaceSeeder` with practical master templates across all OPD sections.
|
||||
- Integrated into `OpdDemoMasterSeeder` so one command seeds templates.
|
||||
- OPD Prescription template UX enhancement:
|
||||
- Load Template modal now has suggested top templates.
|
||||
- Apply mode added: `Replace` or `Append`.
|
||||
- Quick suggestion chips added inside modal.
|
||||
- Doctor-wise learning behavior:
|
||||
- Added usage tracking endpoint for template apply action.
|
||||
- Added usage table auto-create: `opd_clinical_template_usage`.
|
||||
- `section_template_list` now prioritizes by doctor usage count, then scope/recency.
|
||||
|
||||
### Files changed (key)
|
||||
- `app/Controllers/Opd_prescription.php`
|
||||
- `app/Views/billing/opd_prescription_basic.php`
|
||||
- `app/Views/billing/rx_group_medicine_workspace.php`
|
||||
- `app/Config/Routes.php`
|
||||
- `app/Database/Seeds/ClinicalTemplateWorkspaceSeeder.php`
|
||||
- `app/Database/Seeds/OpdDemoMasterSeeder.php`
|
||||
|
||||
### Validation status
|
||||
- PHP lint passed for all modified files during session.
|
||||
- VS Code diagnostics reported no errors in changed files.
|
||||
|
||||
### Resume point for next OPD session
|
||||
- Continue with OPD Prescription optimization:
|
||||
- add doctor-level analytics panel for template usage trends,
|
||||
- optional specialty-wise template suggestions,
|
||||
- optional safety-check prompts before final save.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-25
|
||||
|
||||
## OPD Consult + Scan Workflow Checkpoint (Saved)
|
||||
|
||||
### Completed in this session
|
||||
- OPD queue behavior corrections:
|
||||
- Upload/scan no longer auto-moves OPD to Visited status.
|
||||
- Queue/tabs kept consistent for Waiting/Visited/Cancelled flow.
|
||||
- Visit completion action retained via explicit button workflow.
|
||||
- Language/readability cleanup:
|
||||
- Label text and spelling normalized in OPD UI areas.
|
||||
- Code comments/doc intent improved in touched controller/view sections.
|
||||
- Visited list mismatch fix:
|
||||
- Corrected query join behavior so Visited count and list data match.
|
||||
- Scan AI output enhancement:
|
||||
- Detailed multi-line diagnosis/report text generation added.
|
||||
- Report rendering updated to preserve line breaks in scan cards.
|
||||
- Consult scan visibility:
|
||||
- Current OPD uploaded scans and reports now visible in consult page.
|
||||
- `Use Report` actions wired for consult text usage.
|
||||
- Patient historical scan support:
|
||||
- Async panel for all patient OPD scan history added.
|
||||
- Manual `Run AI` action enabled for historical files.
|
||||
- Expand/Minimize image behavior added in history cards.
|
||||
- Missing history endpoint route added and fixed.
|
||||
- Consult layout UX:
|
||||
- Left and right consult panes now scroll independently on desktop.
|
||||
- Investigation + Advice flow alignment:
|
||||
- Investigation section compacted (profile/manual + Select2 search + Remove All).
|
||||
- Old-style Advice flow restored in consult:
|
||||
- Advice section moved to last block,
|
||||
- Next Visit and Refer To shown after Advice,
|
||||
- predefined advice list with `+Add` restored,
|
||||
- quick Next Visit chips restored.
|
||||
|
||||
### Files changed (key)
|
||||
- `app/Controllers/Opd.php`
|
||||
- `app/Controllers/Opd_prescription.php`
|
||||
- `app/Config/Routes.php`
|
||||
- `app/Views/billing/opd_appointment_dashboard.php`
|
||||
- `app/Views/billing/opd_appointment_list.php`
|
||||
- `app/Views/billing/opd_appointment_list_table.php`
|
||||
- `app/Views/billing/opd_prescription_basic.php`
|
||||
- `app/Views/billing/opd_scan_history_panel.php` (new)
|
||||
- `app/Views/billing/opd_scan_last_list.php`
|
||||
|
||||
### Validation status
|
||||
- VS Code diagnostics showed no errors for edited files after final patches.
|
||||
|
||||
### Next session focus
|
||||
- Start with print workflow updates (layout and output parity).
|
||||
|
||||
## Pinned Reminder for Next Session
|
||||
- First task: OPD print workflow.
|
||||
- Start with print layout parity and field order verification.
|
||||
- Then validate print output content for Advice, Next Visit, Investigation, and scan report sections.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-02-26
|
||||
|
||||
## OPD Consult UI + Rx-Group Workflow Checkpoint (Saved)
|
||||
|
||||
### Completed in this session
|
||||
- Print actions cleanup:
|
||||
- Replaced multiple print buttons with a single grouped `Print` dropdown in consult toolbar.
|
||||
- Kept all existing print routes/behavior unchanged under grouped menu.
|
||||
- Predefined Advice space optimization:
|
||||
- `Pre define Advise` section changed to compact mode.
|
||||
- Added show/hide toggle and fixed-height scroll list to prevent long-page expansion.
|
||||
- Legacy Rx-Group compatibility restored:
|
||||
- Added support for legacy URL access pattern:
|
||||
- `Opd_prescription/save_rx_group_list/{doc_id}`
|
||||
- Added backend apply endpoint to copy Rx-Group template medicines into current OPD session.
|
||||
- Rx-Group modal workflow implemented (old-system style):
|
||||
- Added `Rx Group` button in medicine section to open modal.
|
||||
- Modal lists all Rx groups.
|
||||
- Each group has dropdown preview of medicine list before add.
|
||||
- `+ Add` from modal injects medicines directly into current list.
|
||||
- Selected group label shown in medicine panel.
|
||||
- Rx-Group search and filtering (medicine-like UX):
|
||||
- Added modal search input for Rx-Group name.
|
||||
- Added filter scopes: `Active`, `Favorites`, `All`.
|
||||
- Added star toggle (`☆/★`) for favorites.
|
||||
- Favorites persisted in browser local storage.
|
||||
|
||||
### Files changed (key)
|
||||
- `app/Views/billing/opd_prescription_basic.php`
|
||||
- `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.
|
||||
|
||||
### Resume point for tomorrow
|
||||
- Continue with print parity fine tuning (spacing/font/line layout) against old HMS PDFs.
|
||||
- Optional polish: persist Rx-Group scope (`Active/Favorites/All`) per user session/profile.
|
||||
|
||||
---
|
||||
|
||||
Date: 2026-04-03
|
||||
|
||||
## Incident Note: CBC Template Missing on Remote (`Lab_Admin/report_list`)
|
||||
|
||||
### Symptom
|
||||
- On local, query returns CBC template:
|
||||
- `SELECT mstRepoKey, Title FROM lab_repo WHERE Title LIKE '%CBC%';` -> 1 row
|
||||
- On remote, same query initially returned 0 rows.
|
||||
- Pathology template list also showed mixed/non-pathology items in some states.
|
||||
|
||||
### Root Cause
|
||||
1. Seed data in `app/Database/Seeds/LabOemData/03_lab_repo.sql` contains:
|
||||
- row with `mstRepoKey = 0` (`Manual`)
|
||||
- row with `mstRepoKey = 1` (`COMPLETE BLOOD COUNT (CBC)`)
|
||||
2. During remote seeding, session SQL mode did not enforce `NO_AUTO_VALUE_ON_ZERO`.
|
||||
3. MySQL treated insert key `0` as auto-increment, effectively consuming key `1`.
|
||||
4. CBC row (`mstRepoKey=1`) then got skipped due to `INSERT IGNORE` key collision.
|
||||
5. Result: remote `lab_repo` missing CBC despite seed file being correct.
|
||||
|
||||
### Permanent Code Fixes Applied
|
||||
|
||||
1. Seeder SQL mode fix (critical)
|
||||
- File: `app/Database/Seeds/LabTemplateSeeder.php`
|
||||
- Change:
|
||||
- Save old SQL mode: `SET @OLD_SQL_MODE = @@SESSION.sql_mode`
|
||||
- Set session mode for seed: `SET SESSION SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"`
|
||||
- Restore old mode after seed.
|
||||
- Purpose: preserve explicit key `0`, prevent `0 -> 1` collision and CBC skip.
|
||||
|
||||
2. One-shot remote repair command
|
||||
- File: `app/Commands/RefreshLabTemplates.php`
|
||||
- Command: `php spark oem:refresh-lab-templates`
|
||||
- Behavior:
|
||||
- Truncates template tables only: `lab_repotests`, `lab_tests_option`, `lab_tests`, `lab_repo`, `lab_rgroups`, `radiology_ultrasound_template`
|
||||
- Does **not** truncate `hc_items`
|
||||
- Runs `LabTemplateSeeder`
|
||||
- Verifies CBC exists post-refresh.
|
||||
|
||||
3. Report list robustness (pathology list behavior)
|
||||
- File: `app/Controllers/Setting/Template.php`
|
||||
- `report_list()` adjusted to avoid hard dependency on `hc_items` existence while still applying safer filtering logic.
|
||||
|
||||
### Recovery Runbook (Remote)
|
||||
|
||||
1. Pull latest code:
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
2. Rebuild templates safely:
|
||||
```bash
|
||||
php spark oem:refresh-lab-templates
|
||||
```
|
||||
|
||||
3. Verify CBC exists:
|
||||
```sql
|
||||
SELECT mstRepoKey, Title FROM lab_repo WHERE Title LIKE '%CBC%';
|
||||
```
|
||||
|
||||
4. Verify charge mapping (optional):
|
||||
```sql
|
||||
SELECT r.mstRepoKey, r.Title, r.charge_id, i.itype
|
||||
FROM lab_repo r
|
||||
LEFT JOIN hc_items i ON i.id = r.charge_id
|
||||
WHERE r.Title LIKE '%CBC%';
|
||||
```
|
||||
|
||||
5. Open pathology template list:
|
||||
- URL: `/Lab_Admin/report_list`
|
||||
|
||||
### Expected Post-Fix State
|
||||
- CBC row present in `lab_repo` (`mstRepoKey=1`).
|
||||
- Pathology list loads expected templates.
|
||||
- No manual MySQL global SQL mode change required.
|
||||
|
||||
### Commits Related to This Incident
|
||||
- `259ea13` Diagnosis query `ONLY_FULL_GROUP_BY` fix (`edit-test-data`).
|
||||
- `721f374` / `d9e4d50` / `ab4b9a3` / `9d2f91e` pathology report-list hardening iterations.
|
||||
- `46a22f6` definitive seed-mode + refresh command fix.
|
||||
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<VirtualHost *:443>
|
||||
ServerName abdm-bridge.e-atria.in
|
||||
ServerAlias www.abdm-bridge.e-atria.in
|
||||
ServerAdmin admin@e-atria.in
|
||||
|
||||
DocumentRoot /var/www/html/abdm-bridge-gateway/public
|
||||
|
||||
# Enable rewrite engine globally
|
||||
RewriteEngine On
|
||||
|
||||
# Directory configuration - CRITICAL for .htaccess and rewrite rules
|
||||
<Directory /var/www/html/abdm-bridge-gateway/public>
|
||||
Options +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
|
||||
# DirectoryIndex for CodeIgniter
|
||||
DirectoryIndex index.php index.html index.htm
|
||||
|
||||
# CodeIgniter routing: send all requests to index.php
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# Don't rewrite existing files
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# Route all requests to index.php
|
||||
RewriteRule ^(.*)$ index.php/$1 [L]
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
# Logging
|
||||
ErrorLog ${APACHE_LOG_DIR}/abdm-bridge-https-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/abdm-bridge-https-access.log combined
|
||||
|
||||
# SSL Configuration
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/abdm-bridge.e-atria.in/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/abdm-bridge.e-atria.in/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
</VirtualHost>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
echo "=== Max id now ===\n";
|
||||
print_r($db->query("SELECT MAX(id) maxid FROM abdm_hiu_workflows")->fetch_assoc());
|
||||
|
||||
echo "\n=== rows id > 89 for abha 91510165305101@sbx ===\n";
|
||||
$res = $db->query("SELECT id, operation, workflow_state, status, http_code, request_id, gateway_request_id, created_at FROM abdm_hiu_workflows WHERE abha_address = '91510165305101@sbx' AND id > 89 ORDER BY id ASC");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
|
||||
echo "\n=== search for 6dbbe21b anywhere ===\n";
|
||||
$res2 = $db->query("SELECT id, operation, request_id, gateway_request_id, created_at FROM abdm_hiu_workflows WHERE request_id LIKE '%6dbbe21b%' OR gateway_request_id LIKE '%6dbbe21b%'");
|
||||
while ($row = $res2->fetch_assoc()) { print_r($row); }
|
||||
|
||||
echo "\n=== doc count for abha now ===\n";
|
||||
print_r($db->query("SELECT COUNT(*) c FROM abdm_hiu_documents WHERE abha_address = '91510165305101@sbx'")->fetch_assoc());
|
||||
$db->close();
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "Checking bed_assignment_history for IPD ID: 1\n";
|
||||
echo "=============================================\n\n";
|
||||
|
||||
$result = $db->query("
|
||||
SELECT
|
||||
bah.id,
|
||||
bah.ipd_id,
|
||||
bah.bed_id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
bm.current_ipd_id,
|
||||
DATE_FORMAT(bah.assigned_date, '%d-%m-%Y %H:%i') as assigned_date,
|
||||
bah.released_date
|
||||
FROM bed_assignment_history bah
|
||||
LEFT JOIN bed_master bm ON bm.id = bah.bed_id
|
||||
WHERE bah.ipd_id = 1
|
||||
ORDER BY bah.assigned_date DESC
|
||||
");
|
||||
|
||||
if ($result->num_rows > 0) {
|
||||
while($row = $result->fetch_assoc()) {
|
||||
echo "Assignment ID: {$row['id']}\n";
|
||||
echo "Bed: {$row['bed_number']} ({$row['bed_code']})\n";
|
||||
echo "Assigned Date: {$row['assigned_date']}\n";
|
||||
echo "Released Date: " . ($row['released_date'] ?? 'NOT RELEASED') . "\n";
|
||||
echo "Bed Status in master: {$row['bed_status']}\n";
|
||||
echo "Current IPD ID in bed: " . ($row['current_ipd_id'] ?? 'NULL') . "\n";
|
||||
echo "---\n";
|
||||
}
|
||||
} else {
|
||||
echo "No bed assignment history found for this patient.\n";
|
||||
echo "This patient may need a bed assigned manually.\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "Current Admissions (ipd_status = 0):\n";
|
||||
echo "====================================\n\n";
|
||||
|
||||
$result = $db->query("
|
||||
SELECT
|
||||
i.id,
|
||||
i.ipd_code,
|
||||
DATE_FORMAT(i.register_date, '%d-%m-%Y') as register_date,
|
||||
p.p_fname,
|
||||
p.p_lname,
|
||||
bm.bed_number,
|
||||
bm.current_ipd_id
|
||||
FROM ipd_master i
|
||||
LEFT JOIN patient_master p ON p.id = i.p_id
|
||||
LEFT JOIN bed_master bm ON bm.current_ipd_id = i.id
|
||||
WHERE i.ipd_status = 0
|
||||
ORDER BY i.register_date DESC
|
||||
");
|
||||
|
||||
if ($result->num_rows > 0) {
|
||||
while($row = $result->fetch_assoc()) {
|
||||
echo "IPD ID: {$row['id']}\n";
|
||||
echo "IPD Code: {$row['ipd_code']}\n";
|
||||
echo "Patient: {$row['p_fname']} {$row['p_lname']}\n";
|
||||
echo "Register Date: {$row['register_date']}\n";
|
||||
echo "Bed Assigned: " . ($row['bed_number'] ?? 'NOT ASSIGNED') . "\n";
|
||||
echo "---\n";
|
||||
}
|
||||
} else {
|
||||
echo "No current admissions found.\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* One-off diagnostic: lists abdm_hiu_documents rows (columns + rows for a
|
||||
* given ABHA address + total count). Reads DB credentials from the app's
|
||||
* own .env so it works unmodified on both local and production.
|
||||
*
|
||||
* Usage:
|
||||
* php83 check_hiu_documents.php -> rows for 91510165305101@sbx
|
||||
* php83 check_hiu_documents.php <abha_address> -> rows for a specific ABHA
|
||||
*/
|
||||
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (! is_file($envPath)) {
|
||||
fwrite(STDERR, "Could not find .env at {$envPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$env[trim($k)] = trim($v, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
$host = $env['database.default.hostname'] ?? 'localhost';
|
||||
$dbName = $env['database.default.database'] ?? '';
|
||||
$user = $env['database.default.username'] ?? '';
|
||||
$pass = $env['database.default.password'] ?? '';
|
||||
$port = (int) ($env['database.default.port'] ?? 3306);
|
||||
|
||||
if ($dbName === '' || $user === '') {
|
||||
fwrite(STDERR, "Could not resolve DB credentials from .env (looked for database.default.* keys)\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$db = @new mysqli($host, $user, $pass, $dbName, $port ?: 3306);
|
||||
if ($db->connect_errno) {
|
||||
fwrite(STDERR, "DB connect failed: " . $db->connect_error . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$abha = trim((string) ($argv[1] ?? '91510165305101@sbx'));
|
||||
|
||||
echo "Connected to DB '{$dbName}' on {$host}.\n\n";
|
||||
|
||||
echo "=== abdm_hiu_documents columns ===\n";
|
||||
$res = $db->query("SHOW COLUMNS FROM abdm_hiu_documents");
|
||||
while ($row = $res->fetch_assoc()) { echo $row['Field'] . "\n"; }
|
||||
|
||||
echo "\n=== rows for abha {$abha} ===\n";
|
||||
$stmt = $db->prepare("SELECT id, workflow_id, request_id, transaction_id, consent_ref, abha_address, patient_id, patient_name, care_context_reference, document_title, document_date, created_at, updated_at FROM abdm_hiu_documents WHERE abha_address = ?");
|
||||
$stmt->bind_param('s', $abha);
|
||||
$stmt->execute();
|
||||
$res2 = $stmt->get_result();
|
||||
while ($row = $res2->fetch_assoc()) { print_r($row); }
|
||||
|
||||
echo "\n=== total row count in table ===\n";
|
||||
$res3 = $db->query("SELECT COUNT(*) c FROM abdm_hiu_documents");
|
||||
print_r($res3->fetch_assoc());
|
||||
$db->close();
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$cols = $db->query('SHOW COLUMNS FROM hospital_setting');
|
||||
$colNames = [];
|
||||
while ($c = $cols->fetch_assoc()) {
|
||||
$colNames[] = $c['Field'];
|
||||
}
|
||||
echo "Columns: " . implode(', ', $colNames) . "\n";
|
||||
$rows = $db->query('SELECT * FROM hospital_setting LIMIT 5');
|
||||
while ($r = $rows->fetch_assoc()) {
|
||||
print_r($r);
|
||||
}
|
||||
$db->close();
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Bootstrap minimal CI4
|
||||
$paths = new \Config\Paths();
|
||||
$app = \CodeIgniter\Config\Services::codeigniter();
|
||||
$app->initialize();
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$row = $db->table('ipd_discharge_instructions')->where('ipd_id', 1007141)->get()->getRowArray();
|
||||
|
||||
if ($row) {
|
||||
echo "=== IPD 1007141 - ipd_discharge_instructions ===" . PHP_EOL . PHP_EOL;
|
||||
echo "comp_report (contains other_text JSON): " . PHP_EOL;
|
||||
echo substr($row['comp_report'] ?? '', 0, 300) . PHP_EOL . PHP_EOL;
|
||||
echo "comp_remark (Discharge Summary field): " . PHP_EOL;
|
||||
echo substr($row['comp_remark'] ?? '', 0, 300) . PHP_EOL;
|
||||
} else {
|
||||
echo "No record found for IPD 1007141" . PHP_EOL;
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
|
||||
<?php
|
||||
// Direct database check - no CI4 bootstrap needed
|
||||
$envFile = __DIR__ . '/.env';
|
||||
if (!file_exists($envFile)) {
|
||||
die("ERROR: .env file not found\n");
|
||||
}
|
||||
|
||||
// Manually parse .env because parse_ini_file doesn't handle CI4 format well
|
||||
$dbHost = 'localhost';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
$dbName = 'hms_data_ci4';
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'database.default.hostname') !== false) {
|
||||
$dbHost = trim(explode('=', $line, 2)[1] ?? 'localhost');
|
||||
} elseif (strpos($line, 'database.default.username') !== false) {
|
||||
$dbUser = trim(explode('=', $line, 2)[1] ?? 'root');
|
||||
} elseif (strpos($line, 'database.default.password') !== false) {
|
||||
$dbPass = trim(explode('=', $line, 2)[1] ?? '');
|
||||
} elseif (strpos($line, 'database.default.database') !== false) {
|
||||
$dbName = trim(explode('=', $line, 2)[1] ?? 'hms_data_ci4');
|
||||
}
|
||||
}
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
// Get IPD ID from command line or use default
|
||||
$ipdId = isset($argv[1]) ? intval($argv[1]) : 1;
|
||||
|
||||
echo "=== Checking instruction data for IPD ID: {$ipdId} ===\n\n";
|
||||
|
||||
$stmt = $conn->prepare("SELECT id, ipd_id, comp_report, comp_remark, review_after FROM ipd_discharge_instructions WHERE ipd_id = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $ipdId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
echo "ID: " . $row['id'] . "\n";
|
||||
echo "IPD ID: " . $row['ipd_id'] . "\n\n";
|
||||
|
||||
echo "=== comp_remark (Discharge Summary field) ===\n";
|
||||
echo $row['comp_remark'] . "\n\n";
|
||||
|
||||
echo "=== comp_report JSON (contains other_text = Other Advice field) ===\n";
|
||||
echo $row['comp_report'] . "\n\n";
|
||||
|
||||
if (!empty($row['comp_report'])) {
|
||||
$decoded = json_decode($row['comp_report'], true);
|
||||
if ($decoded && isset($decoded['other_text'])) {
|
||||
echo "=== Extracted other_text (Other Advice) ===\n";
|
||||
echo $decoded['other_text'] . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Review After ===\n";
|
||||
echo ($row['review_after'] ?? '(empty)') . "\n\n";
|
||||
} else {
|
||||
echo "No instruction record found for IPD ID {$ipdId}\n";
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
echo "=== Done ===\n";
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "Checking IPD ID: 2\n";
|
||||
echo "===================\n\n";
|
||||
|
||||
// Check IPD master data
|
||||
echo "IPD Master Data:\n";
|
||||
$result = $db->query("
|
||||
SELECT
|
||||
i.id,
|
||||
i.ipd_code,
|
||||
i.ipd_status,
|
||||
DATE_FORMAT(i.register_date, '%d-%m-%Y') as register_date,
|
||||
p.p_fname,
|
||||
p.p_lname
|
||||
FROM ipd_master i
|
||||
LEFT JOIN patient_master p ON p.id = i.p_id
|
||||
WHERE i.id = 2
|
||||
");
|
||||
if ($result && $result->num_rows > 0) {
|
||||
$row = $result->fetch_assoc();
|
||||
echo "IPD Code: {$row['ipd_code']}\n";
|
||||
echo "Patient: {$row['p_fname']} {$row['p_lname']}\n";
|
||||
echo "Status: " . ($row['ipd_status'] == 0 ? 'Admitted' : 'Discharged') . "\n";
|
||||
echo "Register Date: {$row['register_date']}\n\n";
|
||||
} else {
|
||||
echo "IPD ID 2 not found!\n\n";
|
||||
}
|
||||
|
||||
// Check bed_assignment_history
|
||||
echo "Bed Assignment History:\n";
|
||||
$result = $db->query("
|
||||
SELECT
|
||||
bah.id,
|
||||
bah.ipd_id,
|
||||
bah.bed_id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
w.ward_name,
|
||||
DATE_FORMAT(bah.assigned_date, '%d-%m-%Y %H:%i') as assigned_date,
|
||||
DATE_FORMAT(bah.released_date, '%d-%m-%Y %H:%i') as released_date,
|
||||
bah.assignment_type,
|
||||
bah.remarks
|
||||
FROM bed_assignment_history bah
|
||||
LEFT JOIN bed_master bm ON bm.id = bah.bed_id
|
||||
LEFT JOIN ward_master w ON w.id = bah.ward_id
|
||||
WHERE bah.ipd_id = 2
|
||||
ORDER BY bah.id DESC
|
||||
");
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
echo "Assignment ID: {$row['id']}\n";
|
||||
echo "Bed: {$row['bed_number']} ({$row['bed_code']})\n";
|
||||
echo "Ward: {$row['ward_name']}\n";
|
||||
echo "Assigned: {$row['assigned_date']}\n";
|
||||
echo "Released: " . ($row['released_date'] ?? 'NOT RELEASED') . "\n";
|
||||
echo "Type: {$row['assignment_type']}\n";
|
||||
echo "Remarks: {$row['remarks']}\n";
|
||||
echo "---\n";
|
||||
}
|
||||
} else {
|
||||
echo "No bed assignment history found for IPD ID 2\n\n";
|
||||
}
|
||||
|
||||
// Check current bed assignment in bed_master
|
||||
echo "Current Bed in bed_master:\n";
|
||||
$result = $db->query("
|
||||
SELECT
|
||||
bm.id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
bm.current_ipd_id,
|
||||
w.ward_name
|
||||
FROM bed_master bm
|
||||
LEFT JOIN ward_master w ON w.id = bm.ward_id
|
||||
WHERE bm.current_ipd_id = 2
|
||||
");
|
||||
if ($result && $result->num_rows > 0) {
|
||||
$row = $result->fetch_assoc();
|
||||
echo "Bed ID: {$row['id']}\n";
|
||||
echo "Bed: {$row['bed_number']} ({$row['bed_code']})\n";
|
||||
echo "Ward: {$row['ward_name']}\n";
|
||||
echo "Status: {$row['bed_status']}\n";
|
||||
} else {
|
||||
echo "No bed currently assigned in bed_master.current_ipd_id for IPD ID 2\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
// Check if legacy food_text is interfering
|
||||
$envFile = __DIR__ . '/.env';
|
||||
$dbHost = 'localhost';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
$dbName = 'hms_data_ci4';
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'database.default.hostname') !== false) {
|
||||
$dbHost = trim(explode('=', $line, 2)[1] ?? 'localhost');
|
||||
} elseif (strpos($line, 'database.default.username') !== false) {
|
||||
$dbUser = trim(explode('=', $line, 2)[1] ?? 'root');
|
||||
} elseif (strpos($line, 'database.default.password') !== false) {
|
||||
$dbPass = trim(explode('=', $line, 2)[1] ?? '');
|
||||
} elseif (strpos($line, 'database.default.database') !== false) {
|
||||
$dbName = trim(explode('=', $line, 2)[1] ?? 'hms_data_ci4');
|
||||
}
|
||||
}
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
$ipdId = isset($argv[1]) ? intval($argv[1]) : 1;
|
||||
|
||||
echo "=== Checking LEGACY ipd_discharge_drug_food_interaction for IPD ID: {$ipdId} ===\n\n";
|
||||
|
||||
// Check if table exists
|
||||
$tableCheck = $conn->query("SHOW TABLES LIKE 'ipd_discharge_drug_food_interaction'");
|
||||
if ($tableCheck->num_rows === 0) {
|
||||
echo "Table ipd_discharge_drug_food_interaction does NOT exist\n";
|
||||
$conn->close();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "Table exists. Checking for food_text column...\n";
|
||||
$colCheck = $conn->query("SHOW COLUMNS FROM ipd_discharge_drug_food_interaction LIKE 'food_text'");
|
||||
if ($colCheck->num_rows === 0) {
|
||||
echo "Column food_text does NOT exist\n";
|
||||
} else {
|
||||
echo "Column food_text EXISTS\n\n";
|
||||
|
||||
$stmt = $conn->prepare("SELECT id, ipd_id, food_id_list, food_text FROM ipd_discharge_drug_food_interaction WHERE ipd_id = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $ipdId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
echo "=== LEGACY RECORD FOUND ===\n";
|
||||
echo "ID: " . $row['id'] . "\n";
|
||||
echo "IPD ID: " . $row['ipd_id'] . "\n";
|
||||
echo "food_id_list: " . ($row['food_id_list'] ?? '(null)') . "\n";
|
||||
echo "food_text: " . ($row['food_text'] ?? '(null)') . "\n\n";
|
||||
|
||||
if (!empty($row['food_text'])) {
|
||||
echo "⚠️ WARNING: Legacy food_text has content! This might override instruction_other\n";
|
||||
}
|
||||
} else {
|
||||
echo "No legacy record found for IPD ID {$ipdId}\n";
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
echo "\n=== Done ===\n";
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$abha = '91510165305101@sbx';
|
||||
$res = $db->query("SELECT id, document_title, document_date, care_context_reference, created_at FROM abdm_hiu_documents WHERE abha_address = '{$abha}' ORDER BY id DESC LIMIT 5");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$res = $db->query("SHOW COLUMNS FROM opd_master");
|
||||
$cols = [];
|
||||
while ($row = $res->fetch_assoc()) { $cols[] = $row['Field']; }
|
||||
echo implode(', ', $cols) . "\n---\n";
|
||||
$res2 = $db->query("SELECT opd_id, opd_code, p_id, P_name, opd_book_date, running_opd, running_opd_id, no_visit FROM opd_master WHERE p_id = 11 ORDER BY opd_id DESC LIMIT 5");
|
||||
while ($row = $res2->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$res = $db->query("SHOW COLUMNS FROM opd_visit_list");
|
||||
while ($row = $res->fetch_assoc()) { echo $row['Field'] . "\n"; }
|
||||
echo "---\n";
|
||||
$res2 = $db->query("SELECT * FROM opd_visit_list WHERE p_id = 11 ORDER BY id DESC LIMIT 5");
|
||||
while ($row = $res2->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== Patients with non-empty abha_address (excluding the known test patient id=15) ===\n";
|
||||
$sql = "SELECT id, p_fname, p_lname, abha_address
|
||||
FROM patient_master
|
||||
WHERE abha_address IS NOT NULL AND abha_address != '' AND id != 15
|
||||
ORDER BY id DESC
|
||||
LIMIT 20";
|
||||
$res = $db->query($sql);
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
print_r($row);
|
||||
}
|
||||
|
||||
echo "\n=== hospital_setting HFR-related keys ===\n";
|
||||
$res2 = $db->query("SELECT s_name, s_value FROM hospital_setting WHERE s_name LIKE '%HFR%' OR s_name LIKE '%ABDM%'");
|
||||
while ($row = $res2->fetch_assoc()) {
|
||||
print_r($row);
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$abha = '91510165305101@sbx';
|
||||
$res = $db->query("SELECT id, operation, workflow_state, status, http_code, request_id, gateway_request_id, created_at, updated_at, completed_at FROM abdm_hiu_workflows WHERE abha_address = '{$abha}' ORDER BY id ASC");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
echo "\n=== doc count ===\n";
|
||||
$res2 = $db->query("SELECT COUNT(*) as c FROM abdm_hiu_documents WHERE abha_address = '{$abha}'");
|
||||
print_r($res2->fetch_assoc());
|
||||
$db->close();
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$abha = '91510165305101@sbx';
|
||||
|
||||
echo "=== Columns ===\n";
|
||||
$res = $db->query("SHOW COLUMNS FROM abdm_hiu_workflows");
|
||||
while ($row = $res->fetch_assoc()) { echo $row['Field'] . " (" . $row['Type'] . ")\n"; }
|
||||
|
||||
echo "\n=== Latest 20 rows for {$abha} ===\n";
|
||||
$res2 = $db->query("SELECT * FROM abdm_hiu_workflows WHERE abha_address = '{$abha}' ORDER BY id DESC LIMIT 20");
|
||||
while ($row = $res2->fetch_assoc()) {
|
||||
echo "--- id={$row['id']} ---\n";
|
||||
foreach ($row as $k => $v) {
|
||||
if (in_array($k, ['request_payload', 'response_payload', 'raw_response'])) {
|
||||
echo "$k: " . substr((string)$v, 0, 500) . "\n";
|
||||
} else {
|
||||
echo "$k: $v\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
$db->close();
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== patient_master rows matching Devender Singh or abha 91510165305101@sbx ===\n";
|
||||
$res = $db->query("SELECT id, p_fname, p_lname FROM patient_master WHERE p_fname LIKE '%Devender%' OR p_fname LIKE '%devender%'");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
|
||||
echo "\n=== Search abha columns (guess col names) ===\n";
|
||||
$res2 = $db->query("SHOW COLUMNS FROM patient_master LIKE '%abha%'");
|
||||
while ($row = $res2->fetch_assoc()) { echo $row['Field'] . "\n"; }
|
||||
$db->close();
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== Rows 80-89 for abha 91510165305101@sbx ===\n";
|
||||
$res = $db->query("SELECT * FROM abdm_hiu_workflows WHERE abha_address = '91510165305101@sbx' AND id >= 80 ORDER BY id ASC");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
echo "--- id={$row['id']} ---\n";
|
||||
foreach ($row as $k => $v) {
|
||||
if (in_array($k, ['request_json', 'response_json'])) {
|
||||
echo "$k: " . substr((string)$v, 0, 900) . "\n";
|
||||
} else {
|
||||
echo "$k: $v\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
$db->close();
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$res = $db->query("SELECT s_name, s_value FROM hospital_setting WHERE s_name = 'ABDM_BRIDGE_SSL_VERIFY'");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
// Quick script to check table structure
|
||||
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
|
||||
|
||||
// Bootstrap CI4
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
$pathsConfig = new Config\Paths();
|
||||
$bootstrap = \CodeIgniter\Boot::bootWeb($pathsConfig);
|
||||
$app = $bootstrap->getCodeIgniter();
|
||||
|
||||
// Get database connection
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Check table creation SQL
|
||||
$query = $db->query("SHOW CREATE TABLE ipd_discharge_templates");
|
||||
$row = $query->getRowArray();
|
||||
|
||||
echo "Table Structure:\n";
|
||||
echo "================\n\n";
|
||||
echo $row['Create Table'] . "\n\n";
|
||||
|
||||
// Check for foreign key references
|
||||
$query = $db->query("
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
COLUMN_NAME,
|
||||
CONSTRAINT_NAME,
|
||||
REFERENCED_TABLE_NAME,
|
||||
REFERENCED_COLUMN_NAME
|
||||
FROM
|
||||
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
||||
WHERE
|
||||
REFERENCED_TABLE_NAME = 'ipd_discharge_templates'
|
||||
AND TABLE_SCHEMA = DATABASE()
|
||||
");
|
||||
|
||||
echo "\nForeign Keys Referencing This Table:\n";
|
||||
echo "====================================\n\n";
|
||||
$results = $query->getResultArray();
|
||||
if (count($results) > 0) {
|
||||
foreach ($results as $row) {
|
||||
echo "Table: {$row['TABLE_NAME']}\n";
|
||||
echo "Column: {$row['COLUMN_NAME']}\n";
|
||||
echo "Constraint: {$row['CONSTRAINT_NAME']}\n";
|
||||
echo "References: {$row['REFERENCED_TABLE_NAME']}.{$row['REFERENCED_COLUMN_NAME']}\n\n";
|
||||
}
|
||||
} else {
|
||||
echo "No foreign keys reference this table.\n";
|
||||
}
|
||||
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
// Load CodeIgniter bootstrap
|
||||
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Bootstrap the application
|
||||
$app = require_once FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require FCPATH . '../system/bootstrap.php';
|
||||
|
||||
$app = Config\Services::codeigniter();
|
||||
$app->initialize();
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
echo "Checking discharge_template table...\n\n";
|
||||
|
||||
$query = $db->query('SELECT id, template_name, template_html, is_default FROM discharge_template WHERE id = 1 OR is_default = 1 LIMIT 5');
|
||||
|
||||
foreach($query->getResult() as $row) {
|
||||
echo "ID: " . $row->id . "\n";
|
||||
echo "Name: " . $row->template_name . "\n";
|
||||
echo "Is Default: " . $row->is_default . "\n";
|
||||
echo "HTML Length: " . strlen($row->template_html) . " chars\n";
|
||||
echo "HTML Preview:\n" . substr($row->template_html, 0, 500) . "\n";
|
||||
echo str_repeat("-", 80) . "\n\n";
|
||||
}
|
||||
|
||||
echo "\nChecking discharge content for IPD ID 1...\n\n";
|
||||
|
||||
$query2 = $db->query('SELECT ipd_id, LEFT(content, 500) as content_preview FROM ipd_discharge WHERE ipd_id = 1 LIMIT 1');
|
||||
$result = $query2->getResult();
|
||||
|
||||
if (!empty($result)) {
|
||||
$row = $result[0];
|
||||
echo "IPD ID: " . $row->ipd_id . "\n";
|
||||
echo "Content Preview:\n" . $row->content_preview . "\n";
|
||||
} else {
|
||||
echo "No discharge content found for IPD ID 1\n";
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* One-off diagnostic: search abdm_hiu_workflows and abdm_hiu_documents for a
|
||||
* given transaction_id / consent_id / care_context_reference substring.
|
||||
* Run from the CI4 project root on the server. Delete this file after use.
|
||||
*
|
||||
* Usage:
|
||||
* php83 check_transaction_lookup.php <search_string> [operation_filter] [limit]
|
||||
* php83 check_transaction_lookup.php 10b01dd7-062b-4fc1-91f1-82b3b1491eb6 consent_reconcile
|
||||
*/
|
||||
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (! is_file($envPath)) {
|
||||
fwrite(STDERR, "Could not find .env at {$envPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$env[trim($k)] = trim($v, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
$host = $env['database.default.hostname'] ?? 'localhost';
|
||||
$db = $env['database.default.database'] ?? '';
|
||||
$user = $env['database.default.username'] ?? '';
|
||||
$pass = $env['database.default.password'] ?? '';
|
||||
$port = (int) ($env['database.default.port'] ?? 3306);
|
||||
|
||||
$mysqli = @new mysqli($host, $user, $pass, $db, $port ?: 3306);
|
||||
if ($mysqli->connect_errno) {
|
||||
fwrite(STDERR, "DB connect failed: " . $mysqli->connect_error . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Connected to DB '{$db}' on {$host}.\n";
|
||||
|
||||
$needle = $argv[1] ?? '';
|
||||
if ($needle === '') {
|
||||
fwrite(STDERR, "Usage: php83 check_transaction_lookup.php <search_string>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$like = '%' . $mysqli->real_escape_string($needle) . '%';
|
||||
$opFilter = trim((string) ($argv[2] ?? ''));
|
||||
$limit = (int) ($argv[3] ?? 20);
|
||||
if ($limit <= 0) {
|
||||
$limit = 20;
|
||||
}
|
||||
|
||||
echo "\n=== abdm_hiu_workflows rows matching '{$needle}'" . ($opFilter !== '' ? " AND operation='{$opFilter}'" : '') . " (in request_json/response_json) ===\n";
|
||||
$opClause = $opFilter !== '' ? " AND operation = '" . $mysqli->real_escape_string($opFilter) . "'" : '';
|
||||
$sql = "SELECT id, operation, workflow_state, status, http_code, is_retryable, consent_id,
|
||||
abha_address, transaction_id, last_error, created_at, updated_at
|
||||
FROM abdm_hiu_workflows
|
||||
WHERE (request_json LIKE '{$like}' OR response_json LIKE '{$like}'
|
||||
OR transaction_id LIKE '{$like}' OR consent_id LIKE '{$like}'){$opClause}
|
||||
ORDER BY id DESC LIMIT {$limit}";
|
||||
$res = $mysqli->query($sql);
|
||||
if (! $res) {
|
||||
fwrite(STDERR, "Query failed: " . $mysqli->error . "\n");
|
||||
} else {
|
||||
$found = 0;
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$found++;
|
||||
print_r($row);
|
||||
}
|
||||
if ($found === 0) {
|
||||
echo "(no matching rows)\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n=== abdm_hiu_documents rows matching '{$needle}' ===\n";
|
||||
$sql2 = "SELECT id, workflow_id, request_id, transaction_id, consent_ref, consent_artifact_id,
|
||||
abha_address, patient_id, patient_name, care_context_reference, document_title,
|
||||
document_date, created_at, updated_at
|
||||
FROM abdm_hiu_documents
|
||||
WHERE transaction_id LIKE '{$like}' OR consent_ref LIKE '{$like}'
|
||||
OR care_context_reference LIKE '{$like}' OR abha_address LIKE '{$like}'
|
||||
ORDER BY id DESC LIMIT 20";
|
||||
$res2 = $mysqli->query($sql2);
|
||||
if (! $res2) {
|
||||
fwrite(STDERR, "Query failed: " . $mysqli->error . "\n");
|
||||
} else {
|
||||
$found2 = 0;
|
||||
while ($row = $res2->fetch_assoc()) {
|
||||
$found2++;
|
||||
print_r($row);
|
||||
}
|
||||
if ($found2 === 0) {
|
||||
echo "(no matching rows)\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* One-off diagnostic: dump the full raw response_json for a given
|
||||
* abdm_hiu_workflows.id, pretty-printed. Run from the CI4 project root on
|
||||
* the server. Delete this file after use.
|
||||
*
|
||||
* Usage:
|
||||
* php83 check_workflow_response.php <id>
|
||||
*/
|
||||
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (! is_file($envPath)) {
|
||||
fwrite(STDERR, "Could not find .env at {$envPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$env[trim($k)] = trim($v, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
$host = $env['database.default.hostname'] ?? 'localhost';
|
||||
$db = $env['database.default.database'] ?? '';
|
||||
$user = $env['database.default.username'] ?? '';
|
||||
$pass = $env['database.default.password'] ?? '';
|
||||
$port = (int) ($env['database.default.port'] ?? 3306);
|
||||
|
||||
$mysqli = @new mysqli($host, $user, $pass, $db, $port ?: 3306);
|
||||
if ($mysqli->connect_errno) {
|
||||
fwrite(STDERR, "DB connect failed: " . $mysqli->connect_error . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$id = (int) ($argv[1] ?? 0);
|
||||
if ($id <= 0) {
|
||||
fwrite(STDERR, "Usage: php83 check_workflow_response.php <workflow_id>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT id, operation, request_json, response_json FROM abdm_hiu_workflows WHERE id = ?");
|
||||
$stmt->bind_param('i', $id);
|
||||
$stmt->execute();
|
||||
$row = $stmt->get_result()->fetch_assoc();
|
||||
|
||||
if (! $row) {
|
||||
echo "No workflow row found for id={$id}\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "=== workflow id={$row['id']} op={$row['operation']} ===\n\n";
|
||||
echo "--- request_json ---\n";
|
||||
echo json_encode(json_decode((string) $row['request_json'], true), JSON_PRETTY_PRINT) . "\n\n";
|
||||
echo "--- response_json ---\n";
|
||||
echo json_encode(json_decode((string) $row['response_json'], true), JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
$mysqli->close();
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
// Clear discharge cache to force regeneration
|
||||
$envFile = __DIR__ . '/.env';
|
||||
$dbHost = 'localhost';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
$dbName = 'hms_data_ci4';
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'database.default.hostname') !== false) {
|
||||
$dbHost = trim(explode('=', $line, 2)[1] ?? 'localhost');
|
||||
} elseif (strpos($line, 'database.default.username') !== false) {
|
||||
$dbUser = trim(explode('=', $line, 2)[1] ?? 'root');
|
||||
} elseif (strpos($line, 'database.default.password') !== false) {
|
||||
$dbPass = trim(explode('=', $line, 2)[1] ?? '');
|
||||
} elseif (strpos($line, 'database.default.database') !== false) {
|
||||
$dbName = trim(explode('=', $line, 2)[1] ?? 'hms_data_ci4');
|
||||
}
|
||||
}
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
$ipdId = isset($argv[1]) ? intval($argv[1]) : 1;
|
||||
|
||||
echo "=== Clearing discharge cache for IPD ID: {$ipdId} ===\n\n";
|
||||
|
||||
$stmt = $conn->prepare("DELETE FROM ipd_discharge WHERE ipd_id = ?");
|
||||
$stmt->bind_param("i", $ipdId);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo "✅ Cleared cached content for IPD ID {$ipdId}\n";
|
||||
echo "Affected rows: " . $stmt->affected_rows . "\n\n";
|
||||
echo "Now visit the discharge preview page to regenerate content with the new 'Other Advice:' label.\n";
|
||||
echo "URL: /Ipd_discharge/preview_discharge_report/{$ipdId}?regen=1\n";
|
||||
} else {
|
||||
echo "❌ Failed to clear: " . $stmt->error . "\n";
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
echo "\n=== Done ===\n";
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Clear discharge content for specific IPD to force regeneration with new template logic
|
||||
*
|
||||
* Usage:
|
||||
* php clear_discharge_content.php [ipd_id]
|
||||
*
|
||||
* Example:
|
||||
* php clear_discharge_content.php 1
|
||||
*/
|
||||
|
||||
$ipdId = isset($argv[1]) ? (int) $argv[1] : 0;
|
||||
|
||||
if ($ipdId <= 0) {
|
||||
echo "Usage: php clear_discharge_content.php [ipd_id]\n";
|
||||
echo "Example: php clear_discharge_content.php 1\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Clearing discharge content for IPD ID: $ipdId\n\n";
|
||||
|
||||
// Database connection settings - UPDATE THESE IF NEEDED
|
||||
$host = 'localhost';
|
||||
$database = 'hms_data_ci4';
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
$port = 3306;
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$host;port=$port;dbname=$database;charset=utf8mb4",
|
||||
$username,
|
||||
$password,
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
// Check if content exists
|
||||
$stmt = $pdo->prepare('SELECT id, ipd_id FROM ipd_discharge WHERE ipd_id = ?');
|
||||
$stmt->execute([$ipdId]);
|
||||
$existing = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($existing)) {
|
||||
echo "✓ No discharge content found for IPD ID $ipdId\n";
|
||||
echo " The content will be auto-generated on next view.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "Found " . count($existing) . " discharge record(s) for IPD ID $ipdId\n";
|
||||
|
||||
// Delete the content
|
||||
$stmt = $pdo->prepare('DELETE FROM ipd_discharge WHERE ipd_id = ?');
|
||||
$deleted = $stmt->execute([$ipdId]);
|
||||
|
||||
if ($deleted) {
|
||||
echo "\n✓ Successfully cleared discharge content for IPD ID $ipdId\n";
|
||||
echo " Next time you view the discharge summary, it will regenerate with the new logic.\n";
|
||||
echo " If your template has demographic tokens ({{PATIENT_NAME}}, {{UHID}}, etc.),\n";
|
||||
echo " the auto-generated content will NOT include the patient information table.\n";
|
||||
} else {
|
||||
echo "\n✗ Failed to clear discharge content\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo "\n✗ Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
// Clear corrupt legacy food_text for IPD ID
|
||||
$envFile = __DIR__ . '/.env';
|
||||
$dbHost = 'localhost';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
$dbName = 'hms_data_ci4';
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'database.default.hostname') !== false) {
|
||||
$dbHost = trim(explode('=', $line, 2)[1] ?? 'localhost');
|
||||
} elseif (strpos($line, 'database.default.username') !== false) {
|
||||
$dbUser = trim(explode('=', $line, 2)[1] ?? 'root');
|
||||
} elseif (strpos($line, 'database.default.password') !== false) {
|
||||
$dbPass = trim(explode('=', $line, 2)[1] ?? '');
|
||||
} elseif (strpos($line, 'database.default.database') !== false) {
|
||||
$dbName = trim(explode('=', $line, 2)[1] ?? 'hms_data_ci4');
|
||||
}
|
||||
}
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
$ipdId = isset($argv[1]) ? intval($argv[1]) : 1;
|
||||
|
||||
echo "=== Clearing corrupt legacy food_text for IPD ID: {$ipdId} ===\n\n";
|
||||
|
||||
// Check if table and column exist
|
||||
$tableCheck = $conn->query("SHOW TABLES LIKE 'ipd_discharge_drug_food_interaction'");
|
||||
if ($tableCheck->num_rows === 0) {
|
||||
echo "Table does not exist, nothing to clean\n";
|
||||
$conn->close();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$colCheck = $conn->query("SHOW COLUMNS FROM ipd_discharge_drug_food_interaction LIKE 'food_text'");
|
||||
if ($colCheck->num_rows === 0) {
|
||||
echo "Column food_text does not exist, nothing to clean\n";
|
||||
$conn->close();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Clear the food_text
|
||||
$stmt = $conn->prepare("UPDATE ipd_discharge_drug_food_interaction SET food_text = '' WHERE ipd_id = ?");
|
||||
$stmt->bind_param("i", $ipdId);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo "✅ Cleared food_text for IPD ID {$ipdId}\n";
|
||||
echo "Affected rows: " . $stmt->affected_rows . "\n";
|
||||
} else {
|
||||
echo "❌ Failed to clear: " . $stmt->error . "\n";
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
echo "\n=== Done ===\n";
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$abha = 'meerabisht1981@sbx';
|
||||
echo "=== Distinct operations ever seen for {$abha} ===\n";
|
||||
$res = $db->query("SELECT operation, COUNT(*) c FROM abdm_hiu_workflows WHERE abha_address = '{$abha}' GROUP BY operation");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
|
||||
echo "\n=== Compare: patient 11 ABHA operations (for reference) ===\n";
|
||||
$res2 = $db->query("SELECT operation, COUNT(*) c FROM abdm_hiu_workflows WHERE abha_address = '91510165305101@sbx' GROUP BY operation");
|
||||
while ($row = $res2->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Debug bed assignment query - Direct DB approach
|
||||
*/
|
||||
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "Testing bed_assignment_history query for IPD ID 2\n";
|
||||
echo "==================================================\n\n";
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
bed_assignment_history.*,
|
||||
bed_master.bed_code,
|
||||
bed_master.bed_number,
|
||||
ward_master.ward_name
|
||||
FROM bed_assignment_history
|
||||
LEFT JOIN bed_master ON bed_master.id = bed_assignment_history.bed_id
|
||||
LEFT JOIN ward_master ON ward_master.id = bed_assignment_history.ward_id
|
||||
WHERE bed_assignment_history.ipd_id = 2
|
||||
ORDER BY bed_assignment_history.id DESC
|
||||
";
|
||||
|
||||
$result = $db->query($query);
|
||||
|
||||
echo "Query executed\n";
|
||||
echo "Rows found: " . $result->num_rows . "\n\n";
|
||||
|
||||
if ($result->num_rows > 0) {
|
||||
while ($row = $result->fetch_object()) {
|
||||
echo "Record ID: {$row->id}\n";
|
||||
echo "bed_code: " . ($row->bed_code ?? 'NULL') . "\n";
|
||||
echo "bed_number: " . ($row->bed_number ?? 'NULL') . "\n";
|
||||
echo "ward_name: " . ($row->ward_name ?? 'NULL') . "\n";
|
||||
echo "assigned_date: " . ($row->assigned_date ?? 'NULL') . "\n";
|
||||
echo "released_date: " . ($row->released_date ?? 'NULL') . "\n";
|
||||
echo "assignment_type: " . ($row->assignment_type ?? 'NULL') . "\n";
|
||||
echo "remarks: " . ($row->remarks ?? 'NULL') . "\n";
|
||||
echo "---\n";
|
||||
}
|
||||
} else {
|
||||
echo "No records found\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
|
||||
<?php
|
||||
// Debug script to check what's in the generated discharge content
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$envFile = __DIR__ . '/.env';
|
||||
$dbHost = 'localhost';
|
||||
$dbUser = 'root';
|
||||
$dbPass = '';
|
||||
$dbName = 'hms_data_ci4';
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'database.default.hostname') !== false) {
|
||||
$dbHost = trim(explode('=', $line, 2)[1] ?? 'localhost');
|
||||
} elseif (strpos($line, 'database.default.username') !== false) {
|
||||
$dbUser = trim(explode('=', $line, 2)[1] ?? 'root');
|
||||
} elseif (strpos($line, 'database.default.password') !== false) {
|
||||
$dbPass = trim(explode('=', $line, 2)[1] ?? '');
|
||||
} elseif (strpos($line, 'database.default.database') !== false) {
|
||||
$dbName = trim(explode('=', $line, 2)[1] ?? 'hms_data_ci4');
|
||||
}
|
||||
}
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
$ipdId = isset($argv[1]) ? intval($argv[1]) : 1;
|
||||
|
||||
echo "=== Checking discharge content for IPD ID: {$ipdId} ===\n\n";
|
||||
|
||||
// Check if cached content exists
|
||||
$stmt = $conn->prepare("SELECT content FROM ipd_discharge WHERE ipd_id = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $ipdId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$content = $row['content'];
|
||||
echo "=== CACHED CONTENT FOUND ===\n\n";
|
||||
|
||||
// Check for signature block
|
||||
if (stripos($content, 'Signature of Consultant') !== false) {
|
||||
echo "✅ 'Signature of Consultant' text FOUND in content\n";
|
||||
} else {
|
||||
echo "❌ 'Signature of Consultant' text NOT FOUND in content\n";
|
||||
}
|
||||
|
||||
// Check for Other Advice
|
||||
if (stripos($content, 'Other Advice') !== false) {
|
||||
echo "✅ 'Other Advice' text FOUND in content\n";
|
||||
} else {
|
||||
echo "❌ 'Other Advice' text NOT FOUND in content\n";
|
||||
}
|
||||
|
||||
// Check for Discharge Summary
|
||||
if (stripos($content, 'Discharge Summary:') !== false) {
|
||||
echo "✅ 'Discharge Summary:' label FOUND in content\n";
|
||||
} else {
|
||||
echo "❌ 'Discharge Summary:' label NOT FOUND in content\n";
|
||||
}
|
||||
|
||||
echo "\n=== CONTENT PREVIEW (first 2000 chars) ===\n";
|
||||
echo substr($content, 0, 2000) . "\n...\n\n";
|
||||
|
||||
echo "=== CONTENT PREVIEW (last 1000 chars) ===\n";
|
||||
echo "..." . substr($content, -1000) . "\n\n";
|
||||
} else {
|
||||
echo "No cached content found for IPD ID {$ipdId}\n";
|
||||
echo "Content is generated on-demand. Try accessing the preview page first.\n";
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
echo "=== Done ===\n";
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== Before cleanup: total rows ===\n";
|
||||
print_r($db->query("SELECT COUNT(*) c FROM abdm_hiu_documents")->fetch_assoc());
|
||||
|
||||
// Group by the new dedup key (transaction_id, consent_ref, care_context_reference, bundle_id)
|
||||
// and keep only the newest row (highest id) per group; delete the rest.
|
||||
$res = $db->query("
|
||||
SELECT transaction_id, consent_ref, care_context_reference, bundle_id, GROUP_CONCAT(id ORDER BY id DESC) ids
|
||||
FROM abdm_hiu_documents
|
||||
GROUP BY transaction_id, consent_ref, care_context_reference, bundle_id
|
||||
HAVING COUNT(*) > 1
|
||||
");
|
||||
|
||||
$totalDeleted = 0;
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$ids = explode(',', $row['ids']);
|
||||
$keepId = array_shift($ids); // newest (first in DESC order)
|
||||
if (empty($ids)) {
|
||||
continue;
|
||||
}
|
||||
$idsList = implode(',', array_map('intval', $ids));
|
||||
$db->query("DELETE FROM abdm_hiu_documents WHERE id IN ($idsList)");
|
||||
$totalDeleted += $db->affected_rows;
|
||||
echo "Kept id={$keepId}, deleted ids=[{$idsList}]\n";
|
||||
}
|
||||
|
||||
echo "\nTotal deleted: {$totalDeleted}\n";
|
||||
|
||||
// Recompute doc_hash for all remaining rows using the new (request_id-free) formula,
|
||||
// so future fetches correctly match and UPDATE instead of INSERT duplicates.
|
||||
echo "\n=== Recomputing doc_hash for remaining rows ===\n";
|
||||
$res2 = $db->query("SELECT id, transaction_id, consent_ref, care_context_reference, bundle_id FROM abdm_hiu_documents");
|
||||
$updated = 0;
|
||||
while ($row = $res2->fetch_assoc()) {
|
||||
$hash = sha1(implode('|', [
|
||||
(string) $row['transaction_id'],
|
||||
(string) $row['consent_ref'],
|
||||
(string) $row['care_context_reference'],
|
||||
(string) $row['bundle_id'],
|
||||
]));
|
||||
$escapedHash = $db->real_escape_string($hash);
|
||||
$db->query("UPDATE abdm_hiu_documents SET doc_hash = '{$escapedHash}' WHERE id = " . (int) $row['id']);
|
||||
$updated++;
|
||||
}
|
||||
echo "Recomputed hash for {$updated} rows.\n";
|
||||
|
||||
echo "\n=== After cleanup: total rows ===\n";
|
||||
print_r($db->query("SELECT COUNT(*) c FROM abdm_hiu_documents")->fetch_assoc());
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
$abhaAddress = $argv[1] ?? 'meerabisht1981@sbx';
|
||||
$escaped = $db->real_escape_string($abhaAddress);
|
||||
|
||||
$countResult = $db->query("SELECT COUNT(*) AS c FROM abdm_hiu_workflows WHERE abha_address = '{$escaped}'");
|
||||
$count = $countResult->fetch_assoc()['c'] ?? 0;
|
||||
echo "Rows to delete for {$abhaAddress}: {$count}\n";
|
||||
|
||||
$db->query("DELETE FROM abdm_hiu_workflows WHERE abha_address = '{$escaped}'");
|
||||
echo "Deleted. Affected rows: " . $db->affected_rows . "\n";
|
||||
|
||||
$docCountResult = $db->query("SHOW TABLES LIKE 'abdm_hiu_documents'");
|
||||
if ($docCountResult && $docCountResult->num_rows > 0) {
|
||||
$docRows = $db->query("SELECT COUNT(*) AS c FROM abdm_hiu_documents WHERE abha_address = '{$escaped}'");
|
||||
echo "Existing abdm_hiu_documents rows for {$abhaAddress}: " . ($docRows->fetch_assoc()['c'] ?? 0) . "\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* One-off diagnostic: delete abdm_hiu_documents rows for a given ABHA address
|
||||
* so a fresh end-to-end fetch test can be re-run without old persisted rows
|
||||
* masking whether new document_persisted counts are real. Does NOT touch
|
||||
* abdm_hiu_workflows (audit trail is preserved).
|
||||
*
|
||||
* Run from the CI4 project root on the server. Delete this file after use.
|
||||
*
|
||||
* Usage:
|
||||
* php83 delete_hiu_documents_for_abha.php <abha_address> --confirm
|
||||
*/
|
||||
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (! is_file($envPath)) {
|
||||
fwrite(STDERR, "Could not find .env at {$envPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$env[trim($k)] = trim($v, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
$host = $env['database.default.hostname'] ?? 'localhost';
|
||||
$db = $env['database.default.database'] ?? '';
|
||||
$user = $env['database.default.username'] ?? '';
|
||||
$pass = $env['database.default.password'] ?? '';
|
||||
$port = (int) ($env['database.default.port'] ?? 3306);
|
||||
|
||||
$mysqli = @new mysqli($host, $user, $pass, $db, $port ?: 3306);
|
||||
if ($mysqli->connect_errno) {
|
||||
fwrite(STDERR, "DB connect failed: " . $mysqli->connect_error . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Connected to DB '{$db}' on {$host}.\n";
|
||||
|
||||
$abha = $argv[1] ?? '';
|
||||
$confirm = in_array('--confirm', $argv, true);
|
||||
|
||||
if ($abha === '') {
|
||||
fwrite(STDERR, "Usage: php83 delete_hiu_documents_for_abha.php <abha_address> --confirm\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare('SELECT id, care_context_reference, document_title, document_date FROM abdm_hiu_documents WHERE abha_address = ?');
|
||||
$stmt->bind_param('s', $abha);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
|
||||
$rows = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
echo "\n=== Matching abdm_hiu_documents rows for abha_address='{$abha}' ===\n";
|
||||
if (empty($rows)) {
|
||||
echo "(no rows found — nothing to delete)\n";
|
||||
exit(0);
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
print_r($row);
|
||||
}
|
||||
echo "\nTotal: " . count($rows) . " row(s)\n";
|
||||
|
||||
if (! $confirm) {
|
||||
echo "\nDry run only. Re-run with --confirm to actually delete these rows.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$del = $mysqli->prepare('DELETE FROM abdm_hiu_documents WHERE abha_address = ?');
|
||||
$del->bind_param('s', $abha);
|
||||
$del->execute();
|
||||
|
||||
echo "\nDeleted " . $mysqli->affected_rows . " row(s) for abha_address='{$abha}'.\n";
|
||||
Binary file not shown.
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== Max id now ===\n";
|
||||
$res = $db->query("SELECT MAX(id) maxid FROM abdm_hiu_workflows");
|
||||
print_r($res->fetch_assoc());
|
||||
|
||||
echo "\n=== Show tables like %health% or %record% or %m3% or %hiu% ===\n";
|
||||
$res2 = $db->query("SHOW TABLES");
|
||||
while ($row = $res2->fetch_array()) {
|
||||
$t = $row[0];
|
||||
if (stripos($t, 'health') !== false || stripos($t, 'record') !== false || stripos($t, 'm3') !== false || stripos($t, 'hiu') !== false || stripos($t, 'abdm') !== false) {
|
||||
echo "$t\n";
|
||||
}
|
||||
}
|
||||
$db->close();
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== OPD visits for patient_id = 11 ===\n";
|
||||
$res = $db->query("SHOW TABLES LIKE '%opd%'");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
print_r($row);
|
||||
}
|
||||
$db->close();
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
echo "=== Search request_id/gateway_request_id containing 6dbbe21b ===\n";
|
||||
$res = $db->query("SELECT * FROM abdm_hiu_workflows WHERE request_id LIKE '%6dbbe21b%' OR gateway_request_id LIKE '%6dbbe21b%' OR hms_request_id LIKE '%6dbbe21b%'");
|
||||
$found = 0;
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$found++;
|
||||
echo "--- id={$row['id']} ---\n";
|
||||
foreach ($row as $k => $v) {
|
||||
if (in_array($k, ['request_json', 'response_json'])) {
|
||||
echo "$k: " . substr((string)$v, 0, 800) . "\n";
|
||||
} else {
|
||||
echo "$k: $v\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
echo "Found: $found\n";
|
||||
|
||||
echo "\n=== Count of rows for abha 91510165305101@sbx ===\n";
|
||||
$res2 = $db->query("SELECT COUNT(*) c, MAX(id) maxid FROM abdm_hiu_workflows WHERE abha_address = '91510165305101@sbx'");
|
||||
print_r($res2->fetch_assoc());
|
||||
$db->close();
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
-- =====================================================
|
||||
-- Bed Management Fix SQL Scripts
|
||||
-- For systems using bed_master table (NEW CI4 system)
|
||||
-- Run these to fix existing data issues
|
||||
-- =====================================================
|
||||
--
|
||||
-- IMPORTANT NOTES:
|
||||
-- 1. This script is for bed_master table (NEW system)
|
||||
-- 2. bed_master columns: bed_number, bed_code, ward_id, bed_status, current_ipd_id
|
||||
-- 3. bed_status values: 'available', 'occupied', 'maintenance', 'blocked', 'reserved', 'cleaning'
|
||||
-- 4. current_ipd_id: stores the IPD ID when bed is occupied (NULL when available)
|
||||
-- 5. Always backup your database before running!
|
||||
-- =====================================================
|
||||
|
||||
-- =====================================================
|
||||
-- Step 1: NOT NEEDED for bed_master system
|
||||
-- =====================================================
|
||||
-- The bed_master table tracks current occupancy via current_ipd_id column
|
||||
-- No historical backfill needed - just release discharged patients' beds in Step 2
|
||||
|
||||
-- =====================================================
|
||||
-- Step 2: Release beds for discharged patients
|
||||
-- =====================================================
|
||||
-- This is the MAIN FIX - releases beds when patients are discharged
|
||||
|
||||
UPDATE bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
SET
|
||||
bm.bed_status = 'available',
|
||||
bm.current_ipd_id = NULL
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- Step 2b: Fix phantom occupied beds
|
||||
-- =====================================================
|
||||
-- This fixes beds marked as 'occupied' but with no patient assigned
|
||||
-- These are "phantom" occupied beds that block bed availability
|
||||
|
||||
UPDATE bed_master
|
||||
SET bed_status = 'available'
|
||||
WHERE bed_status = 'occupied'
|
||||
AND current_ipd_id IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- Step 2c: Sync bed_assignment_history to bed_master.current_ipd_id
|
||||
-- =====================================================
|
||||
-- This syncs active bed assignments from history table to bed_master
|
||||
-- Fixes cases where bed was assigned in history but current_ipd_id wasn't set
|
||||
|
||||
UPDATE bed_master bm
|
||||
INNER JOIN bed_assignment_history bah ON bah.bed_id = bm.id
|
||||
INNER JOIN ipd_master i ON i.id = bah.ipd_id
|
||||
SET
|
||||
bm.current_ipd_id = bah.ipd_id,
|
||||
bm.bed_status = 'occupied'
|
||||
WHERE i.ipd_status = 0
|
||||
AND bah.released_date IS NULL
|
||||
AND bm.current_ipd_id IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- Step 3: Update bed_assignment_history (if you use it)
|
||||
-- =====================================================
|
||||
-- This marks bed assignments as released in the history table
|
||||
|
||||
UPDATE bed_assignment_history bah
|
||||
INNER JOIN ipd_master i ON i.id = bah.ipd_id
|
||||
SET
|
||||
bah.released_date = COALESCE(i.discharge_date, NOW()),
|
||||
bah.release_reason = 'Patient Discharged (Data Fix)'
|
||||
WHERE i.ipd_status = 1
|
||||
AND bah.released_date IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- Step 4: Verification Queries
|
||||
-- =====================================================
|
||||
|
||||
-- 4a. Check beds still marked as occupied for discharged patients
|
||||
SELECT
|
||||
bm.id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
bm.current_ipd_id,
|
||||
i.ipd_code,
|
||||
i.ipd_status,
|
||||
w.ward_name,
|
||||
DATE_FORMAT(i.discharge_date, '%d-%m-%Y') as discharge_date
|
||||
FROM bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
LEFT JOIN ward_master w ON w.id = bm.ward_id
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL;
|
||||
|
||||
-- Expected: Should return 0 rows after fix
|
||||
|
||||
-- 4b. Check currently admitted patients with their beds
|
||||
SELECT
|
||||
i.id as ipd_id,
|
||||
i.ipd_code,
|
||||
i.ipd_status,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
w.ward_name
|
||||
FROM ipd_master i
|
||||
LEFT JOIN bed_master bm ON bm.current_ipd_id = i.id
|
||||
LEFT JOIN ward_master w ON w.id = bm.ward_id
|
||||
WHERE i.ipd_status = 0
|
||||
ORDER BY i.ipd_code;
|
||||
|
||||
-- Expected: All active IPDs should show their assigned beds
|
||||
|
||||
-- 4c. Check current bed status summary
|
||||
SELECT
|
||||
COUNT(CASE WHEN bed_status = 'available' THEN 1 END) as available_beds,
|
||||
COUNT(CASE WHEN bed_status = 'occupied' THEN 1 END) as occupied_beds,
|
||||
COUNT(CASE WHEN bed_status = 'maintenance' THEN 1 END) as maintenance_beds,
|
||||
COUNT(CASE WHEN bed_status = 'blocked' THEN 1 END) as blocked_beds,
|
||||
COUNT(CASE WHEN bed_status = 'reserved' THEN 1 END) as reserved_beds,
|
||||
COUNT(CASE WHEN bed_status = 'cleaning' THEN 1 END) as cleaning_beds,
|
||||
COUNT(*) as total_beds
|
||||
FROM bed_master
|
||||
WHERE status = 'active';
|
||||
|
||||
-- 4d. Compare with current admissions count
|
||||
SELECT COUNT(*) as current_admissions
|
||||
FROM ipd_master
|
||||
WHERE ipd_status = 0;
|
||||
|
||||
-- The occupied_beds count should match current_admissions count
|
||||
|
||||
-- 4e. Find beds with status mismatch (occupied but no IPD assigned)
|
||||
SELECT
|
||||
bm.id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
bm.current_ipd_id,
|
||||
w.ward_name
|
||||
FROM bed_master bm
|
||||
LEFT JOIN ward_master w ON w.id = bm.ward_id
|
||||
WHERE bm.bed_status = 'occupied'
|
||||
AND bm.current_ipd_id IS NULL;
|
||||
|
||||
-- Expected: Should return 0 rows (all occupied beds should have current_ipd_id)
|
||||
|
||||
-- =====================================================
|
||||
-- Step 5: Optional - Clean up orphaned bed assignments
|
||||
-- =====================================================
|
||||
-- This removes bed_assignment_history records for IPDs that don't exist
|
||||
-- Only run if you use bed_assignment_history table
|
||||
|
||||
DELETE bah FROM bed_assignment_history bah
|
||||
LEFT JOIN ipd_master i ON i.id = bah.ipd_id
|
||||
WHERE i.id IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- NOTES:
|
||||
-- =====================================================
|
||||
-- 1. Always backup your database before running these scripts
|
||||
-- 2. Run Step 4 verification queries before and after to see the improvement
|
||||
-- 3. Step 2 is the main fix - it releases beds for discharged patients
|
||||
-- 4. These scripts are safe to run multiple times (idempotent)
|
||||
-- 5. Monitor the application after running to ensure no issues
|
||||
-- 6. For bed_master system: focuses on bed_status and current_ipd_id columns
|
||||
-- 7. If you don't use bed_assignment_history, skip Steps 3 and 5
|
||||
--
|
||||
-- WHAT THIS FIXES:
|
||||
-- - Beds marked as 'occupied' for patients who have been discharged
|
||||
-- - current_ipd_id not cleared when patients leave
|
||||
-- - Bed count mismatches in bed status dashboard
|
||||
-- - "Bed Assign" column showing blank because current_ipd_id not set properly
|
||||
--
|
||||
-- AFTER RUNNING:
|
||||
-- - All discharged patients' beds will be marked as 'available'
|
||||
-- - current_ipd_id will be NULL for available beds
|
||||
-- - Bed counts in dashboard will match actual occupancy
|
||||
-- - New bed assignments will work correctly with CI4 code
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/bash
|
||||
echo "--- Apache VirtualHost Configuration ---"
|
||||
cat /etc/apache2/sites-enabled/abdm-bridge-le-ssl.conf | grep -A 50 "VirtualHost"
|
||||
echo ""
|
||||
echo "--- Directory Listing: /var/www/html/abdm-bridge-gateway/public/ ---"
|
||||
ls -la /var/www/html/abdm-bridge-gateway/public/
|
||||
echo ""
|
||||
echo "--- .htaccess Details ---"
|
||||
ls -la /var/www/html/abdm-bridge-gateway/public/.htaccess
|
||||
echo ""
|
||||
echo "--- Apache Rewrite Module Check ---"
|
||||
apache2ctl -M | grep rewrite
|
||||
echo ""
|
||||
echo "--- PHP Version ---"
|
||||
php -v
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
<?php
|
||||
// List all IPD records with instruction data
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
$dbHost = $_ENV['database.default.hostname'] ?? 'localhost';
|
||||
$dbUser = $_ENV['database.default.username'] ?? 'root';
|
||||
$dbPass = $_ENV['database.default.password'] ?? '';
|
||||
$dbName = $_ENV['database.default.database'] ?? 'hms_data_ci4';
|
||||
|
||||
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
|
||||
if ($conn->connect_error) {
|
||||
die("ERROR: Connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
echo "=== Recent IPD instruction records ===\n\n";
|
||||
|
||||
$sql = "SELECT id, ipd_id, LEFT(comp_remark, 100) as comp_remark_preview,
|
||||
LEFT(comp_report, 100) as comp_report_preview,
|
||||
review_after, update_date
|
||||
FROM ipd_discharge_instructions
|
||||
ORDER BY id DESC
|
||||
LIMIT 20";
|
||||
|
||||
$result = $conn->query($sql);
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
echo "ID: {$row['id']} | IPD_ID: {$row['ipd_id']} | Updated: {$row['update_date']}\n";
|
||||
echo " comp_remark (Discharge Summary): " . substr(strip_tags($row['comp_remark_preview']), 0, 50) . "...\n";
|
||||
echo " comp_report (JSON with other_text): " . substr($row['comp_report_preview'], 0, 50) . "...\n\n";
|
||||
}
|
||||
} else {
|
||||
echo "No instruction records found\n";
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
Binary file not shown.
|
|
@ -1,171 +0,0 @@
|
|||
<style>body {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead {
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-left: 0.35em;
|
||||
padding-right: 0.35em;
|
||||
padding-top: 0.35em;
|
||||
padding-bottom: 0.35em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-left: 0.35em;
|
||||
padding-right: 0.35em;
|
||||
padding-top: 0.35em;
|
||||
padding-bottom: 0.35em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
td {
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
font-size: 10pt;
|
||||
padding: 1px;
|
||||
margin:1px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-indent: 0px;
|
||||
font-size: 10pt;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
font-size: 10pt;
|
||||
padding: 0px;
|
||||
margin:0px;
|
||||
|
||||
}
|
||||
|
||||
/* Section Headings */
|
||||
.discharge-section-heading {
|
||||
margin: 5px 0 8px 0;
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.discharge-title {
|
||||
text-align: center;
|
||||
margin: 2px 0;
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
.discharge-list {
|
||||
margin: 0 0 10px 20px;
|
||||
}
|
||||
|
||||
.discharge-sublist {
|
||||
margin: 2px 0 8px 20px;
|
||||
}
|
||||
|
||||
/* Text */
|
||||
.discharge-remark {
|
||||
color: #475569;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.discharge-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 4px 0 8px 0;
|
||||
}
|
||||
|
||||
.discharge-medicine-table {
|
||||
width: 100%;
|
||||
border-style: inset;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.discharge-signature-table {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Separators */
|
||||
.discharge-separator {
|
||||
margin: 1px 0;
|
||||
padding: 0;
|
||||
border-top: 1px solid #000;
|
||||
}</style><h3><h2>Discharge Summary</h2>
|
||||
<p></h3>
|
||||
|
||||
<table align="left" border="0" cellpadding="1" cellspacing="1" style="width:100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Patient Name</td>
|
||||
<td>SUNITA DEVI</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td>UHID / Patient ID</td>
|
||||
<td>P26061331171</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Guardian</td>
|
||||
<td>Wife of of JOGENDRA KUMAR</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td>IPD No.</td>
|
||||
<td>A26061007144</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Age / Gender</td>
|
||||
<td>40 Year / Female</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td>Admission Date</td>
|
||||
<td>04-06-2026 03:14 PM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td>MALDHAN*****</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td>Discharge Date</td>
|
||||
<td>07-06-2026 10:00 AM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Phone No.</td>
|
||||
<td>9520497324</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td>TPA</td>
|
||||
<td>Direct</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Department </td>
|
||||
<td>General Surgery</td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><br />
|
||||
<b>Final Diagnosis</b> :<br /> RIGHT CERVICAL LYMPHADENOPATHY WITH ABSCESS</p>
|
||||
<p> <b>Procedure : </b><br/>I & D WITH L. N. EXCISION DONE UNDER GA / <b>Date of Procedure :</b> 2026-06-05</p> <b>Presenting Complaints and Reason for Admission</b> :<br /> PAIN WITH SWELLING RIGHT SIDE NECK 1 MONTH </p>
|
||||
<p> <h4 class="discharge-section-heading">Drug Allergy / ADR</h4><div><b>Drug Allergy Status:</b> No Known Drug Allergy</div>
|
||||
<p> <b>General Examination on Admission : </b><br/>Pulse /min :<i>72/min</i> Respiration /min :<i>16/min</i> BP mmHg :<i>172/92mmHg</i></p>
|
||||
<p> <b>Course in the hospital</b> :<br /> IVF IV ANTIBIOTICS , ANALGESIC ATT STARTED FROM 6/6/26</p> <b>Examination on Discharge : </b>Pulse /min :<i>76/min</i> Respiration /min :<i>18/min</i> BP mmHg :<i>132/84mmHg</i></p>
|
||||
<p> <h4 class="discharge-section-heading">Discharge Medications</h4><table class="discharge-medicine-table" border="0" cellpadding="6"><thead><tr><th>Medicine Name</th><th>Dosage</th><th>Qty</th><th>Day</th></tr></thead><tbody><tr><td>TAB POP DSR</td><td>BID (TWO TIME A DAY) / दिन में दो बार</td><td>1</td><td>5 DAYS</td></tr><tr><td>TAB CEFUBON CV</td><td>BID (TWO TIME A DAY) / दिन में दो बार</td><td>1</td><td>5 DAYS</td></tr><tr><td>TAB DICLOSIS SP</td><td>TID (THREE TIME A DAY) / दिन में तीन बार</td><td>1</td><td>5 DAYS</td></tr><tr><td>TAB FORECOX</td><td>BID (TWO TIME A DAY) / दिन में दो बार</td><td>1</td><td>15 DAYS</td></tr><tr><td>TAB BONCORED</td><td>BID (TWO TIME A DAY) / दिन में दो बार</td><td>1</td><td>15 DAYS</td></tr></tbody></table> <strong>Dietary Advice:</strong></div><ol class="discharge-list"><li><strong>DIET AS ADVICE:</strong> DIET AS ADVICE</li><li><strong>Balanced Meals:</strong> प्रत्येक भोजन में कार्बोहाइड्रेट, प्रोटीन और स्वस्थ वसा का मिश्रण शामिल करें।</li><li><strong>Small, Frequent Meals:</strong> पाचन तंत्र पर अधिक बोझ से बचने के लिए तीन बार अधिक भोजन करने के बजाय दिन में 5-6 बार छोटी मात्रा में भोजन करें।</li><li><strong>Avoid Processed Foods:</strong> मीठे, नमकीन और तले हुए खाद्य पदार्थों का सेवन सीमित करें। डिब्बाबंद स्नैक्स और ज़्यादा प्रिज़र्वेटिव वाले पेय पदार्थों से बचें।</li></ol><div><p>PT WAS K/C/O RT CERVICAL LYMPHADENOPATHY WITH ABSCESS NOW ADMITTED FOR OPERATION . ALL NECESSARY INVESTIGATION WERE CARRIED OUT. PT WAS OPERATED I & D WITH L. N . EXCISION DONE UNDER GA AND MANAGED WITH IVF IV ANTIBIOTICS ANALGESIC AND OTHER SUPPORTIVE MEASURES. </p>
|
||||
|
||||
<p> NOW PT IS BEING DISCHARGED IN A STABLE CONDITION WITH MEDICATION . </p></div><div class="discharge-footer">Review after 5 Days (12-06-2026) Days days / as and when required</div>
|
||||
<table class="discharge-signature-table" border="0" cellpadding="1" cellspacing="1"><tbody><tr><td> </td><td> </td><td> </td></tr><tr><td>_________________________</td><td>_________________________</td><td>_________________________</td></tr><tr> <h4 class="discharge-section-heading">Discharge Advice/Instructions/Summary</h4><div class="discharge-field"> <h2>Discharge Summary</h2>
|
||||
<p> <td>Signature of Consultant</td></p>
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
$rows = $db->query("SELECT s_name, s_value FROM hospital_setting WHERE s_name IN ('EATRIA_BRIDGE_TOKEN','ABDM_HFR_ID')");
|
||||
$settings = [];
|
||||
while ($r = $rows->fetch_assoc()) {
|
||||
$settings[$r['s_name']] = $r['s_value'];
|
||||
}
|
||||
$db->close();
|
||||
|
||||
$token = trim((string) ($settings['EATRIA_BRIDGE_TOKEN'] ?? ''));
|
||||
$hfrId = trim((string) ($settings['ABDM_HFR_ID'] ?? ''));
|
||||
|
||||
$requestId = $argv[1] ?? 'REQ-20260728030125-b03b873b';
|
||||
|
||||
$url = 'https://abdm-bridge.e-atria.in/api/v1/hiu/consent/status?' . http_build_query([
|
||||
'request_id' => $requestId,
|
||||
'hfr_id' => $hfrId,
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $token,
|
||||
],
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
echo "URL: {$url}\n";
|
||||
echo "HTTP Code: {$httpCode}\n";
|
||||
echo "Curl error: {$err}\n";
|
||||
if ($raw !== false) {
|
||||
echo "--- Headers ---\n" . substr($raw, 0, $headerSize) . "\n";
|
||||
echo "--- Body (raw bytes length=" . strlen(substr($raw, $headerSize)) . ") ---\n";
|
||||
echo substr($raw, $headerSize) . "\n";
|
||||
}
|
||||
189
run_bed_fix.php
189
run_bed_fix.php
|
|
@ -1,189 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Run Bed Management Fix
|
||||
* This script executes the SQL fixes for bed management issues
|
||||
*/
|
||||
|
||||
// Database credentials from .env
|
||||
$hostname = 'localhost';
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
$database = 'hms_data_ci4';
|
||||
|
||||
// Connect to database
|
||||
$db = new mysqli($hostname, $username, $password, $database);
|
||||
|
||||
if ($db->connect_error) {
|
||||
die("Connection failed: " . $db->connect_error);
|
||||
}
|
||||
|
||||
echo "==============================================\n";
|
||||
echo "Bed Management Fix Script\n";
|
||||
echo "==============================================\n\n";
|
||||
|
||||
// Step 0: Check current state BEFORE fix
|
||||
echo "BEFORE FIX - Current State:\n";
|
||||
echo "----------------------------\n";
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
COUNT(CASE WHEN bed_status = 'available' THEN 1 END) as available_beds,
|
||||
COUNT(CASE WHEN bed_status = 'occupied' THEN 1 END) as occupied_beds,
|
||||
COUNT(*) as total_beds
|
||||
FROM bed_master
|
||||
WHERE status = 'active'
|
||||
";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Total Beds: {$row->total_beds}\n";
|
||||
echo "Available: {$row->available_beds}\n";
|
||||
echo "Occupied: {$row->occupied_beds}\n";
|
||||
|
||||
$query = "SELECT COUNT(*) as current_admissions FROM ipd_master WHERE ipd_status = 0";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Current Admissions: {$row->current_admissions}\n";
|
||||
|
||||
$query = "
|
||||
SELECT COUNT(*) as problem_beds
|
||||
FROM bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL
|
||||
";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Beds stuck with discharged patients: {$row->problem_beds}\n\n";
|
||||
|
||||
// Step 1: Release beds for discharged patients (MAIN FIX)
|
||||
echo "Step 1: Releasing beds for discharged patients...\n";
|
||||
$sql = "
|
||||
UPDATE bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
SET
|
||||
bm.bed_status = 'available',
|
||||
bm.current_ipd_id = NULL
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL
|
||||
";
|
||||
$db->query($sql);
|
||||
$affected = $db->affected_rows;
|
||||
echo "✓ Released {$affected} beds\n\n";
|
||||
|
||||
// Step 1b: Fix phantom occupied beds (occupied status but no patient assigned)
|
||||
echo "Step 1b: Fixing phantom occupied beds (occupied but no current_ipd_id)...\n";
|
||||
$sql = "
|
||||
UPDATE bed_master
|
||||
SET bed_status = 'available'
|
||||
WHERE bed_status = 'occupied'
|
||||
AND current_ipd_id IS NULL
|
||||
";
|
||||
$db->query($sql);
|
||||
$affected = $db->affected_rows;
|
||||
echo "✓ Fixed {$affected} phantom occupied beds\n\n";
|
||||
|
||||
// Step 1c: Sync bed_assignment_history to bed_master.current_ipd_id
|
||||
echo "Step 1c: Syncing bed assignments from history to bed_master...\n";
|
||||
$sql = "
|
||||
UPDATE bed_master bm
|
||||
INNER JOIN bed_assignment_history bah ON bah.bed_id = bm.id
|
||||
INNER JOIN ipd_master i ON i.id = bah.ipd_id
|
||||
SET
|
||||
bm.current_ipd_id = bah.ipd_id,
|
||||
bm.bed_status = 'occupied'
|
||||
WHERE i.ipd_status = 0
|
||||
AND bah.released_date IS NULL
|
||||
AND bm.current_ipd_id IS NULL
|
||||
";
|
||||
$db->query($sql);
|
||||
$affected = $db->affected_rows;
|
||||
echo "✓ Synced {$affected} bed assignments\n\n";
|
||||
|
||||
// Step 2: Update bed_assignment_history (if table exists)
|
||||
$tableCheck = $db->query("SHOW TABLES LIKE 'bed_assignment_history'");
|
||||
if ($tableCheck->num_rows > 0) {
|
||||
echo "Step 2: Updating bed_assignment_history...\n";
|
||||
$sql = "
|
||||
UPDATE bed_assignment_history bah
|
||||
INNER JOIN ipd_master i ON i.id = bah.ipd_id
|
||||
SET
|
||||
bah.released_date = COALESCE(i.discharge_date, NOW()),
|
||||
bah.release_reason = 'Patient Discharged (Data Fix)'
|
||||
WHERE i.ipd_status = 1
|
||||
AND bah.released_date IS NULL
|
||||
";
|
||||
$db->query($sql);
|
||||
$affected = $db->affected_rows;
|
||||
echo "✓ Updated {$affected} history records\n\n";
|
||||
} else {
|
||||
echo "Step 2: bed_assignment_history table not found, skipping...\n\n";
|
||||
}
|
||||
|
||||
// Step 3: Verify the fix
|
||||
echo "AFTER FIX - Verification:\n";
|
||||
echo "-------------------------\n";
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
COUNT(CASE WHEN bed_status = 'available' THEN 1 END) as available_beds,
|
||||
COUNT(CASE WHEN bed_status = 'occupied' THEN 1 END) as occupied_beds,
|
||||
COUNT(*) as total_beds
|
||||
FROM bed_master
|
||||
WHERE status = 'active'
|
||||
";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Total Beds: {$row->total_beds}\n";
|
||||
echo "Available: {$row->available_beds}\n";
|
||||
echo "Occupied: {$row->occupied_beds}\n";
|
||||
|
||||
$query = "SELECT COUNT(*) as current_admissions FROM ipd_master WHERE ipd_status = 0";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Current Admissions: {$row->current_admissions}\n";
|
||||
|
||||
$query = "
|
||||
SELECT COUNT(*) as problem_beds
|
||||
FROM bed_master bm
|
||||
INNER JOIN ipd_master i ON i.id = bm.current_ipd_id
|
||||
WHERE i.ipd_status = 1
|
||||
AND bm.current_ipd_id IS NOT NULL
|
||||
";
|
||||
$result = $db->query($query);
|
||||
$row = $result->fetch_object();
|
||||
echo "Beds stuck with discharged patients: {$row->problem_beds}\n\n";
|
||||
|
||||
// Check for any mismatches
|
||||
$query = "
|
||||
SELECT
|
||||
bm.id,
|
||||
bm.bed_number,
|
||||
bm.bed_code,
|
||||
bm.bed_status,
|
||||
bm.current_ipd_id
|
||||
FROM bed_master bm
|
||||
LEFT JOIN ward_master w ON w.id = bm.ward_id
|
||||
WHERE bm.bed_status = 'occupied'
|
||||
AND bm.current_ipd_id IS NULL
|
||||
LIMIT 5
|
||||
";
|
||||
$result = $db->query($query);
|
||||
if ($result->num_rows > 0) {
|
||||
echo "⚠ Warning: Found beds marked 'occupied' but no current_ipd_id:\n";
|
||||
while ($bed = $result->fetch_assoc()) {
|
||||
echo " - Bed #{$bed['bed_number']} (ID: {$bed['id']})\n";
|
||||
}
|
||||
echo "\n";
|
||||
} else {
|
||||
echo "✓ No status mismatches found\n\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
||||
echo "==============================================\n";
|
||||
echo "Fix completed successfully!\n";
|
||||
echo "==============================================\n";
|
||||
echo "\nNext steps:\n";
|
||||
echo "1. Check bed status at: /setting/admin/bed-status\n";
|
||||
echo "2. Test discharge a patient and verify bed is released\n";
|
||||
echo "3. Test admitting a patient and verify bed is assigned\n";
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Setup script to configure git for www-data user
|
||||
# For PUBLIC repositories - no authentication required
|
||||
# Run this on your live server as root to fix git pull issues
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================="
|
||||
echo "HMS Git Configuration (Public Repo)"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "ERROR: This script must be run as root"
|
||||
echo "Usage: sudo bash setup_git_config.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine web server user
|
||||
WEB_USER="www-data"
|
||||
if ! id "$WEB_USER" &>/dev/null; then
|
||||
echo "ERROR: User '$WEB_USER' not found. Please adjust WEB_USER variable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# HMS installation directory
|
||||
HMS_DIR="/var/www/html/hms_etria"
|
||||
if [ ! -d "$HMS_DIR" ]; then
|
||||
echo "ERROR: HMS directory not found at $HMS_DIR"
|
||||
echo "Please update HMS_DIR in this script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Configuration Details:"
|
||||
echo " Web user: $WEB_USER"
|
||||
echo " HMS dir: $HMS_DIR"
|
||||
echo " Repo type: PUBLIC (no authentication needed)"
|
||||
echo ""
|
||||
|
||||
# 1. Fix directory permissions
|
||||
echo "[1/4] Fixing directory permissions..."
|
||||
chown -R "$WEB_USER:$WEB_USER" "$HMS_DIR"
|
||||
chmod -R 755 "$HMS_DIR"
|
||||
chmod -R 755 "$HMS_DIR/.git"
|
||||
echo " Directory ownership set to $WEB_USER"
|
||||
|
||||
# 2. Set local git config (repository-specific, avoids /var/www/.gitconfig permission issues)
|
||||
echo "[2/4] Setting git user name (local repository config)..."
|
||||
cd "$HMS_DIR"
|
||||
sudo -u "$WEB_USER" git config user.name "www-data"
|
||||
|
||||
# 3. Set git email (local repository config)
|
||||
echo "[3/4] Setting git email (local repository config)..."
|
||||
cd "$HMS_DIR"
|
||||
sudo -u "$WEB_USER" git config user.email "deploy@localhost"
|
||||
|
||||
# 4. Test git status
|
||||
echo "[4/4] Testing git status..."
|
||||
cd "$HMS_DIR"
|
||||
if sudo -u "$WEB_USER" git status >/dev/null 2>&1; then
|
||||
echo "✓ Git status check PASSED"
|
||||
else
|
||||
echo "✗ Git status check FAILED"
|
||||
echo " Output:"
|
||||
sudo -u "$WEB_USER" git status
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "✓ Git setup completed successfully!"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Configuration stored in: $HMS_DIR/.git/config"
|
||||
echo "Remote: $(cd $HMS_DIR && sudo -u $WEB_USER git config --get remote.origin.url)"
|
||||
echo "Branch: main"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Go to System Operations panel"
|
||||
echo " 2. Click 'Update HMS' button"
|
||||
echo " 3. Confirm the git pull action"
|
||||
echo ""
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the panelTab endpoint
|
||||
*/
|
||||
|
||||
// Simulate what the controller does
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
|
||||
$ipdId = 2;
|
||||
|
||||
echo "Simulating panelTab(2, 'bed-assign')\n";
|
||||
echo "=====================================\n\n";
|
||||
|
||||
// Step 1: Get panel data
|
||||
echo "Step 1: Get IpdPanelInfo...\n";
|
||||
$query = "SELECT * FROM ipd_master WHERE id = {$ipdId}";
|
||||
$result = $db->query($query);
|
||||
if ($result && $result->num_rows > 0) {
|
||||
echo "✓ IPD data found\n\n";
|
||||
} else {
|
||||
echo "✗ IPD not found\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Step 2: Get bed assignments
|
||||
echo "Step 2: Get bed assignments...\n";
|
||||
$query = "
|
||||
SELECT
|
||||
bed_assignment_history.*,
|
||||
bed_master.bed_code,
|
||||
bed_master.bed_number,
|
||||
ward_master.ward_name
|
||||
FROM bed_assignment_history
|
||||
LEFT JOIN bed_master ON bed_master.id = bed_assignment_history.bed_id
|
||||
LEFT JOIN ward_master ON ward_master.id = bed_assignment_history.ward_id
|
||||
WHERE bed_assignment_history.ipd_id = {$ipdId}
|
||||
ORDER BY bed_assignment_history.id DESC
|
||||
";
|
||||
$result = $db->query($query);
|
||||
echo "✓ Query executed\n";
|
||||
echo "✓ Rows found: {$result->num_rows}\n\n";
|
||||
|
||||
if ($result->num_rows > 0) {
|
||||
echo "Step 3: Render view with data...\n";
|
||||
$bed_assignments = [];
|
||||
while ($row = $result->fetch_object()) {
|
||||
$bed_assignments[] = $row;
|
||||
}
|
||||
|
||||
echo "✓ Data prepared\n";
|
||||
echo "✓ Array count: " . count($bed_assignments) . "\n\n";
|
||||
|
||||
echo "Step 4: Check if view can access data...\n";
|
||||
echo "Sample data from first record:\n";
|
||||
if (!empty($bed_assignments)) {
|
||||
$first = $bed_assignments[0];
|
||||
echo " bed_code: " . ($first->bed_code ?? 'NULL') . "\n";
|
||||
echo " bed_number: " . ($first->bed_number ?? 'NULL') . "\n";
|
||||
echo " ward_name: " . ($first->ward_name ?? 'NULL') . "\n";
|
||||
echo " assigned_date: " . ($first->assigned_date ?? 'NULL') . "\n";
|
||||
echo " assignment_type: " . ($first->assignment_type ?? 'NULL') . "\n";
|
||||
}
|
||||
|
||||
echo "\n✓ View should display this data\n";
|
||||
} else {
|
||||
echo "✗ No bed assignments found - view will show empty message\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
|
||||
print_r($db->query("SELECT COUNT(*) c FROM abdm_hiu_documents WHERE abha_address = '91510165305101@sbx'")->fetch_assoc());
|
||||
$res = $db->query("SELECT id, request_id, care_context_reference, updated_at FROM abdm_hiu_documents WHERE abha_address = '91510165305101@sbx'");
|
||||
while ($row = $res->fetch_assoc()) { print_r($row); }
|
||||
$db->close();
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* One-off diagnostic: wipe ALL M3 HIU data for a given ABHA address
|
||||
* (abdm_hiu_documents + abdm_hiu_workflows + abdm_hiu_consent_artifacts)
|
||||
* so a completely fresh consent request can be tested end-to-end without
|
||||
* any leftover test data/state from prior runs.
|
||||
*
|
||||
* Run from the CI4 project root on the server. Delete this file after use.
|
||||
*
|
||||
* Usage:
|
||||
* php83 wipe_hiu_data_for_abha.php <abha_address> (dry run)
|
||||
* php83 wipe_hiu_data_for_abha.php <abha_address> --confirm (actually delete)
|
||||
*/
|
||||
|
||||
$envPath = __DIR__ . '/.env';
|
||||
if (! is_file($envPath)) {
|
||||
fwrite(STDERR, "Could not find .env at {$envPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$env[trim($k)] = trim($v, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
$host = $env['database.default.hostname'] ?? 'localhost';
|
||||
$db = $env['database.default.database'] ?? '';
|
||||
$user = $env['database.default.username'] ?? '';
|
||||
$pass = $env['database.default.password'] ?? '';
|
||||
$port = (int) ($env['database.default.port'] ?? 3306);
|
||||
|
||||
$mysqli = @new mysqli($host, $user, $pass, $db, $port ?: 3306);
|
||||
if ($mysqli->connect_errno) {
|
||||
fwrite(STDERR, "DB connect failed: " . $mysqli->connect_error . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Connected to DB '{$db}' on {$host}.\n";
|
||||
|
||||
$abha = $argv[1] ?? '';
|
||||
$confirm = in_array('--confirm', $argv, true);
|
||||
|
||||
if ($abha === '') {
|
||||
fwrite(STDERR, "Usage: php83 wipe_hiu_data_for_abha.php <abha_address> [--confirm]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$tables = [
|
||||
'abdm_hiu_documents' => 'abha_address',
|
||||
'abdm_hiu_workflows' => 'abha_address',
|
||||
'abdm_hiu_consent_artifacts' => 'abha_address',
|
||||
];
|
||||
|
||||
$counts = [];
|
||||
foreach ($tables as $table => $column) {
|
||||
$checkTable = $mysqli->query("SHOW TABLES LIKE '{$table}'");
|
||||
if (! $checkTable || $checkTable->num_rows === 0) {
|
||||
echo "\n(table '{$table}' does not exist, skipping)\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT COUNT(*) AS c FROM {$table} WHERE {$column} = ?");
|
||||
$stmt->bind_param('s', $abha);
|
||||
$stmt->execute();
|
||||
$row = $stmt->get_result()->fetch_assoc();
|
||||
$counts[$table] = (int) ($row['c'] ?? 0);
|
||||
|
||||
echo "\n=== {$table} rows matching {$column}='{$abha}' ===\n";
|
||||
echo "Count: {$counts[$table]}\n";
|
||||
}
|
||||
|
||||
$total = array_sum($counts);
|
||||
if ($total === 0) {
|
||||
echo "\n(no rows found in any table — nothing to delete)\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (! $confirm) {
|
||||
echo "\nDry run only. Total rows across all tables: {$total}.\n";
|
||||
echo "Re-run with --confirm to actually delete these rows.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
foreach ($tables as $table => $column) {
|
||||
if (! isset($counts[$table]) || $counts[$table] === 0) {
|
||||
continue;
|
||||
}
|
||||
$del = $mysqli->prepare("DELETE FROM {$table} WHERE {$column} = ?");
|
||||
$del->bind_param('s', $abha);
|
||||
$del->execute();
|
||||
echo "\nDeleted {$mysqli->affected_rows} row(s) from {$table} for {$column}='{$abha}'.\n";
|
||||
}
|
||||
|
||||
echo "\nDone. All M3 HIU data wiped for abha_address='{$abha}'. Ready for a fresh consent request test.\n";
|
||||
Loading…
Reference in New Issue
Block a user