chore: untrack local dev debug scripts and internal notes from repository, update .gitignore

This commit is contained in:
DevSoftTech Server 2026-10-21 01:57:44 +05:30
parent 9d568463f1
commit 050035e1b0
88 changed files with 42 additions and 12034 deletions

42
.gitignore vendored
View File

@ -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

View File

@ -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 ✅

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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)**

View File

@ -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`

View File

@ -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.

View File

@ -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_`**

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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 &rarr; 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 &rarr; 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"
```

View File

@ -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 ✅

View File

@ -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`

View File

@ -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.

View File

@ -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

View File

@ -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`

View File

@ -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

View File

@ -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`

View File

@ -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)

View File

@ -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>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</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`

View File

@ -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

View File

@ -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! 🎉

View File

@ -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

View File

@ -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

View File

@ -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`

View File

@ -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!**

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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`

View File

@ -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`

View File

@ -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

View File

@ -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!**

View File

@ -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

View File

@ -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 |

View File

@ -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.

View File

@ -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>

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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;
}

View File

@ -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";

View File

@ -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();

View File

@ -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";

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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";
}
}

View File

@ -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();

View File

@ -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";

View File

@ -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);
}

View File

@ -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";

View File

@ -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();

View File

@ -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();

View File

@ -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";

View File

@ -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();

View File

@ -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();

View File

@ -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";

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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>&nbsp;</td>
<td>&nbsp;</td>
<td>UHID / Patient ID</td>
<td>P26061331171</td>
</tr>
<tr>
<td>Guardian</td>
<td>Wife of of &nbsp;&nbsp;JOGENDRA KUMAR</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>IPD No.</td>
<td>A26061007144</td>
</tr>
<tr>
<td>Age / Gender</td>
<td>40 Year / Female</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>Admission Date</td>
<td>04-06-2026&nbsp;03:14 PM</td>
</tr>
<tr>
<td>Address</td>
<td>MALDHAN*****</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>Discharge Date</td>
<td>07-06-2026&nbsp;10:00 AM</td>
</tr>
<tr>
<td>Phone No.</td>
<td>9520497324</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>TPA</td>
<td>Direct</td>
</tr>
<tr>
<td>Department&nbsp;</td>
<td>General Surgery</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
<p><br />
<b>Final Diagnosis</b> :<br /> RIGHT CERVICAL LYMPHADENOPATHY WITH ABSCESS</p>
<p> &nbsp; <b>Procedure : </b><br/>I &amp; D WITH L. N. EXCISION DONE UNDER GA&nbsp;&nbsp;&nbsp; / <b>Date of Procedure :</b> 2026-06-05</p>&nbsp; <b>Presenting Complaints and Reason for Admission</b> :<br /> PAIN WITH SWELLING RIGHT SIDE NECK 1 MONTH </p>
<p>&nbsp;&nbsp;&nbsp; <h4 class="discharge-section-heading">Drug Allergy / ADR</h4><div><b>Drug Allergy Status:</b> No Known Drug Allergy</div>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<b>General Examination on Admission : </b><br/>Pulse /min :<i>72/min</i>&nbsp;&nbsp;&nbsp;Respiration /min :<i>16/min</i>&nbsp;&nbsp;&nbsp;BP mmHg :<i>172/92mmHg</i></p>
<p> &nbsp; <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>&nbsp;&nbsp;&nbsp;Respiration /min :<i>18/min</i>&nbsp;&nbsp;&nbsp;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>&nbsp; <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>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</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>

View File

@ -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";
}

View File

@ -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";

View File

@ -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 ""

View File

@ -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();

View File

@ -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();

View File

@ -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";