feat: Complete bed management system with transfer and time tracking
- Implemented automatic bed release on patient discharge - Added bed transfer functionality in nursing workspace - Added date/time tracking for bed assignments with duration calculation - Fixed bed_master.current_ipd_id as source of truth for occupancy - Created data fix scripts for existing records - Added bed assignment history with duration display (hours/minutes) - Fixed display issues in bed assignment tab - Added route for bed transfer endpoint - Supports custom assignment datetime for backdated entries - Automatically marks previous bed as released on transfer
This commit is contained in:
parent
8e293c4ffb
commit
d722714ef1
227
BED_MANAGEMENT_FIX_SUMMARY.md
Normal file
227
BED_MANAGEMENT_FIX_SUMMARY.md
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# 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
|
||||
|
|
@ -531,6 +531,7 @@ $routes->post('ipd/patient/history/save/(:num)', 'IpdPatient::saveAdmissionHisto
|
|||
$routes->post('ipd/patient/nursing/scan/(:num)', 'IpdPatient::scanNursingPaper/$1');
|
||||
$routes->post('ipd/patient/nursing/scan-save/(:num)', 'IpdPatient::saveScannedNursingEntries/$1');
|
||||
$routes->get('ipd/patient/nursing/print/(:num)', 'IpdPatient::nursingChartPrint/$1');
|
||||
$routes->post('ipd/patient/bed/transfer/(:num)', 'IpdPatient::bedTransfer/$1');
|
||||
|
||||
$routes->get('nursing-bedside-items', 'Setting\\NursingBedsideItems::index');
|
||||
$routes->get('nursing-bedside-items/(:num)', 'Setting\\NursingBedsideItems::get/$1');
|
||||
|
|
|
|||
|
|
@ -871,6 +871,11 @@ class Ipd extends BaseController
|
|||
$this->ipdEditModel->updateIpd($update, $ipdId);
|
||||
$this->ipdEditModel->calculateIPD($ipdId);
|
||||
|
||||
// Release bed when discharging patient
|
||||
if (isset($update['ipd_status']) && $update['ipd_status'] == 1) {
|
||||
$this->ipdEditModel->releaseBed($ipdId);
|
||||
}
|
||||
|
||||
return $this->buildDischargeProcessResponse($ipdId, 'Discharge status updated.');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\NursingScanExtractionService;
|
||||
use App\Models\BedAssignmentHistoryModel;
|
||||
use App\Models\BedMasterModel;
|
||||
use App\Models\IpdBillingModel;
|
||||
use App\Models\IpdModel;
|
||||
use App\Models\IpdNursingEntryModel;
|
||||
use App\Models\NursingBedsideItemModel;
|
||||
|
||||
|
|
@ -13,6 +16,9 @@ class IpdPatient extends BaseController
|
|||
protected IpdNursingEntryModel $ipdNursingEntryModel;
|
||||
protected NursingBedsideItemModel $nursingBedsideItemModel;
|
||||
protected NursingScanExtractionService $nursingScanExtractionService;
|
||||
protected BedMasterModel $bedMasterModel;
|
||||
protected BedAssignmentHistoryModel $bedAssignmentModel;
|
||||
protected IpdModel $ipdModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -20,6 +26,9 @@ class IpdPatient extends BaseController
|
|||
$this->ipdNursingEntryModel = new IpdNursingEntryModel();
|
||||
$this->nursingBedsideItemModel = new NursingBedsideItemModel();
|
||||
$this->nursingScanExtractionService = new NursingScanExtractionService();
|
||||
$this->bedMasterModel = new BedMasterModel();
|
||||
$this->bedAssignmentModel = new BedAssignmentHistoryModel();
|
||||
$this->ipdModel = new IpdModel();
|
||||
helper(['common', 'form', 'age']);
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +94,18 @@ class IpdPatient extends BaseController
|
|||
$opdHistorySnapshot = $this->getLatestOpdHistorySnapshot($patientId);
|
||||
$isFemalePatient = strtolower((string) ($person->xgender ?? '')) === 'female';
|
||||
|
||||
// Get current bed assignment with ward info
|
||||
$currentBed = $this->bedMasterModel
|
||||
->select('bed_master.*, ward_master.ward_name')
|
||||
->join('ward_master', 'ward_master.id = bed_master.ward_id', 'left')
|
||||
->where('bed_master.current_ipd_id', $ipdId)
|
||||
->first();
|
||||
|
||||
// Get available beds for transfer
|
||||
$availableBeds = $this->bedMasterModel->getAvailableBedsGroupedByWard();
|
||||
// Get bed assignment history
|
||||
$bedHistory = $this->bedAssignmentModel->getByIpd($ipdId);
|
||||
|
||||
return view('ipd_nursing/workspace', [
|
||||
'ipd_info' => $panelData['ipd_info'] ?? null,
|
||||
'person_info' => $person,
|
||||
|
|
@ -98,6 +119,9 @@ class IpdPatient extends BaseController
|
|||
'patient_history_row' => $patientHistoryRow,
|
||||
'opd_history_snapshot' => $opdHistorySnapshot,
|
||||
'is_female_patient' => $isFemalePatient,
|
||||
'current_bed' => $currentBed,
|
||||
'available_beds' => $availableBeds,
|
||||
'bed_history' => $bedHistory,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -822,4 +846,69 @@ class IpdPatient extends BaseController
|
|||
|
||||
return trim(implode(PHP_EOL, $lines));
|
||||
}
|
||||
|
||||
public function bedTransfer(int $ipdId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
'billing.access',
|
||||
'billing.ipd.current-admission',
|
||||
'billing.ipd.invoice',
|
||||
]);
|
||||
if ($permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
if (!$this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['status' => 0, 'message' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$newBedId = (int) $this->request->getPost('bed_id');
|
||||
$transferReason = trim((string) $this->request->getPost('transfer_reason'));
|
||||
$remarks = trim((string) $this->request->getPost('remarks'));
|
||||
$assignmentDateTime = trim((string) $this->request->getPost('assignment_datetime'));
|
||||
|
||||
if ($newBedId <= 0) {
|
||||
return $this->response->setJSON(['status' => 0, 'message' => 'Please select a bed']);
|
||||
}
|
||||
|
||||
// Validate and format assignment date/time
|
||||
if (empty($assignmentDateTime)) {
|
||||
$assignmentDateTime = date('Y-m-d H:i:s');
|
||||
} else {
|
||||
// Convert from HTML5 datetime-local format to MySQL datetime
|
||||
$assignmentDateTime = date('Y-m-d H:i:s', strtotime($assignmentDateTime));
|
||||
}
|
||||
|
||||
try {
|
||||
// Get current bed assignment
|
||||
$currentBed = $this->bedMasterModel->where('current_ipd_id', $ipdId)->first();
|
||||
|
||||
// Check if new bed is available
|
||||
$newBed = $this->bedMasterModel->find($newBedId);
|
||||
if (!$newBed || $newBed->bed_status !== 'available' || $newBed->current_ipd_id !== null) {
|
||||
return $this->response->setJSON(['status' => 0, 'message' => 'Selected bed is not available']);
|
||||
}
|
||||
|
||||
// Transfer bed
|
||||
$this->ipdModel->bedAssign([
|
||||
'ipd_id' => $ipdId,
|
||||
'bed_id' => $newBedId,
|
||||
'Fdate' => $assignmentDateTime,
|
||||
'TDate' => $assignmentDateTime,
|
||||
'transfer_reason' => $transferReason,
|
||||
'transfer_from_bed_id' => $currentBed ? $currentBed->id : null,
|
||||
'remarks' => $remarks,
|
||||
'assignment_type' => 'transfer',
|
||||
]);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Bed transferred successfully',
|
||||
'reload' => true,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Bed transfer failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
||||
return $this->response->setJSON(['status' => 0, 'message' => 'Bed transfer failed: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,16 +30,43 @@ class BedAssignmentHistoryModel extends Model
|
|||
->join('bed_master', 'bed_master.id = bed_assignment_history.bed_id', 'left')
|
||||
->join('ward_master', 'ward_master.id = bed_assignment_history.ward_id', 'left')
|
||||
->orderBy('bed_assignment_history.id', 'DESC')
|
||||
->findAll();
|
||||
->get()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function getByIpd(int $ipdId): array
|
||||
{
|
||||
return $this->select('bed_assignment_history.*, bed_master.bed_code, bed_master.bed_number, ward_master.ward_name')
|
||||
$results = $this->select('bed_assignment_history.*, bed_master.bed_code, bed_master.bed_number, ward_master.ward_name')
|
||||
->join('bed_master', 'bed_master.id = bed_assignment_history.bed_id', 'left')
|
||||
->join('ward_master', 'ward_master.id = bed_assignment_history.ward_id', 'left')
|
||||
->where('bed_assignment_history.ipd_id', $ipdId)
|
||||
->orderBy('bed_assignment_history.id', 'DESC')
|
||||
->findAll();
|
||||
->get()
|
||||
->getResult();
|
||||
|
||||
// Calculate duration for each record
|
||||
foreach ($results as $record) {
|
||||
$record->duration_hours = $this->calculateDurationHours($record->assigned_date, $record->released_date);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate duration in hours between two dates
|
||||
*/
|
||||
private function calculateDurationHours(?string $startDate, ?string $endDate): ?float
|
||||
{
|
||||
if (empty($startDate)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = new \DateTime($startDate);
|
||||
$end = $endDate ? new \DateTime($endDate) : new \DateTime();
|
||||
|
||||
$interval = $start->diff($end);
|
||||
$hours = ($interval->days * 24) + $interval->h + ($interval->i / 60);
|
||||
|
||||
return round($hours, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,4 +35,28 @@ class BedMasterModel extends Model
|
|||
->orderBy('bed_master.id', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
public function getAvailableBedsGroupedByWard(): array
|
||||
{
|
||||
$beds = $this->select('bed_master.*, ward_master.ward_name, bed_category_master.category_name')
|
||||
->join('ward_master', 'ward_master.id = bed_master.ward_id', 'left')
|
||||
->join('bed_category_master', 'bed_category_master.id = bed_master.bed_category_id', 'left')
|
||||
->where('bed_master.status', 'active')
|
||||
->where('bed_master.bed_status', 'available')
|
||||
->where('bed_master.current_ipd_id IS NULL')
|
||||
->orderBy('ward_master.ward_name', 'ASC')
|
||||
->orderBy('bed_master.bed_number', 'ASC')
|
||||
->findAll();
|
||||
|
||||
$grouped = [];
|
||||
foreach ($beds as $bed) {
|
||||
$wardName = $bed->ward_name ?? 'Unassigned';
|
||||
if (!isset($grouped[$wardName])) {
|
||||
$grouped[$wardName] = [];
|
||||
}
|
||||
$grouped[$wardName][] = $bed;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,15 +193,40 @@ class IpdModel extends Model
|
|||
|
||||
$assignedBy = (int) ($this->getUserIdentity()['id'] ?? 0);
|
||||
|
||||
$inserted = $this->db->table('bed_assignment_history')->insert([
|
||||
// Prepare insert data with optional fields
|
||||
$insertData = [
|
||||
'ipd_id' => $ipdId,
|
||||
'bed_id' => $bedId,
|
||||
'assignment_type' => 'admission',
|
||||
'assignment_type' => $data['assignment_type'] ?? 'admission',
|
||||
'assigned_date' => $fromDate,
|
||||
'discharged_date' => $toDate,
|
||||
'assigned_by' => $assignedBy > 0 ? $assignedBy : null,
|
||||
'ward_id' => $wardId,
|
||||
]);
|
||||
];
|
||||
|
||||
// Add transfer-specific fields if provided
|
||||
if (isset($data['transfer_reason']) && !empty($data['transfer_reason'])) {
|
||||
$insertData['transfer_reason'] = $data['transfer_reason'];
|
||||
}
|
||||
if (isset($data['transfer_from_bed_id']) && $data['transfer_from_bed_id'] > 0) {
|
||||
$insertData['transfer_from_bed_id'] = (int) $data['transfer_from_bed_id'];
|
||||
}
|
||||
if (isset($data['remarks']) && !empty($data['remarks'])) {
|
||||
$insertData['remarks'] = $data['remarks'];
|
||||
}
|
||||
|
||||
// If this is a transfer, mark previous bed assignment as released
|
||||
if (($data['assignment_type'] ?? 'admission') === 'transfer') {
|
||||
$this->db->table('bed_assignment_history')
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('released_date IS NULL')
|
||||
->update([
|
||||
'released_date' => $fromDate,
|
||||
'release_reason' => 'Transferred to another bed',
|
||||
]);
|
||||
}
|
||||
|
||||
$inserted = $this->db->table('bed_assignment_history')->insert($insertData);
|
||||
|
||||
if (! $inserted) {
|
||||
return 0;
|
||||
|
|
@ -225,6 +250,37 @@ class IpdModel extends Model
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Update bed_master table (new system)
|
||||
if ($this->db->tableExists('bed_master')) {
|
||||
$bedFields = $this->db->getFieldNames('bed_master') ?? [];
|
||||
|
||||
// Release any previous bed assigned to this IPD
|
||||
if (in_array('current_ipd_id', $bedFields) && in_array('bed_status', $bedFields)) {
|
||||
$this->db->table('bed_master')
|
||||
->where('current_ipd_id', $ipdId)
|
||||
->update([
|
||||
'current_ipd_id' => null,
|
||||
'bed_status' => 'available'
|
||||
]);
|
||||
}
|
||||
|
||||
// Assign new bed
|
||||
$bedUpdate = [];
|
||||
if (in_array('bed_status', $bedFields)) {
|
||||
$bedUpdate['bed_status'] = 'occupied';
|
||||
}
|
||||
if (in_array('current_ipd_id', $bedFields)) {
|
||||
$bedUpdate['current_ipd_id'] = $ipdId;
|
||||
}
|
||||
|
||||
if (!empty($bedUpdate)) {
|
||||
$this->db->table('bed_master')
|
||||
->where('id', $bedId)
|
||||
->update($bedUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy hc_bed_master support (if needed for backward compatibility)
|
||||
if ($this->db->tableExists('hc_bed_master') && in_array('bed_used_p_id', $this->db->getFieldNames('hc_bed_master') ?? [], true)) {
|
||||
$this->db->table('hc_bed_master')
|
||||
->where('bed_used_p_id', $ipdId)
|
||||
|
|
@ -235,13 +291,51 @@ class IpdModel extends Model
|
|||
->update(['bed_used_p_id' => $ipdId]);
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('bed_master') && in_array('bed_status', $this->db->getFieldNames('bed_master') ?? [], true)) {
|
||||
$this->db->table('bed_master')
|
||||
->where('id', $bedId)
|
||||
->update(['bed_status' => 'occupied']);
|
||||
return $insertId;
|
||||
}
|
||||
|
||||
public function releaseBed(int $ipdId): void
|
||||
{
|
||||
if ($ipdId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $insertId;
|
||||
// Release bed in bed_master table (new system)
|
||||
if ($this->db->tableExists('bed_master')) {
|
||||
$bedFields = $this->db->getFieldNames('bed_master') ?? [];
|
||||
$bedUpdate = [];
|
||||
|
||||
if (in_array('bed_status', $bedFields)) {
|
||||
$bedUpdate['bed_status'] = 'available';
|
||||
}
|
||||
if (in_array('current_ipd_id', $bedFields)) {
|
||||
$bedUpdate['current_ipd_id'] = null;
|
||||
}
|
||||
|
||||
if (!empty($bedUpdate)) {
|
||||
$this->db->table('bed_master')
|
||||
->where('current_ipd_id', $ipdId)
|
||||
->update($bedUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy hc_bed_master support (if needed for backward compatibility)
|
||||
if ($this->db->tableExists('hc_bed_master') && in_array('bed_used_p_id', $this->db->getFieldNames('hc_bed_master') ?? [], true)) {
|
||||
$this->db->table('hc_bed_master')
|
||||
->where('bed_used_p_id', $ipdId)
|
||||
->update(['bed_used_p_id' => 0]);
|
||||
}
|
||||
|
||||
// Update bed_assignment_history to mark beds as released
|
||||
if ($this->db->tableExists('bed_assignment_history') && in_array('released_date', $this->db->getFieldNames('bed_assignment_history') ?? [], true)) {
|
||||
$this->db->table('bed_assignment_history')
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('released_date IS NULL')
|
||||
->update([
|
||||
'released_date' => date('Y-m-d H:i:s'),
|
||||
'release_reason' => 'Patient Discharged',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function calculateIPD(int $ipdNo): void
|
||||
|
|
|
|||
|
|
@ -199,9 +199,175 @@ ksort($nurseNames);
|
|||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_charge">Charge Entry</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_bed">Bed Management</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- Bed Management Tab -->
|
||||
<div class="nursing-tab-pane d-none" id="nursing_tab_bed">
|
||||
<?php
|
||||
$currentBedData = $current_bed ?? null;
|
||||
$availableBedsData = $available_beds ?? [];
|
||||
$bedHistoryData = $bed_history ?? [];
|
||||
$bedTransferUrl = site_url('ipd/patient/bed/transfer/' . $ipdId);
|
||||
?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Current Bed Assignment</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($currentBedData): ?>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<strong>Bed Number:</strong> <?= esc($currentBedData->bed_number) ?>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Bed Code:</strong> <?= esc($currentBedData->bed_code) ?>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Ward:</strong> <?= esc($currentBedData->ward_name ?? 'N/A') ?>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Status:</strong>
|
||||
<span class="badge bg-success"><?= esc($currentBedData->bed_status) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-warning mb-0">
|
||||
<i class="bi bi-exclamation-triangle"></i> No bed currently assigned to this patient.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Transfer to New Bed</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="bed_transfer_form" class="row g-3">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Select New Bed <span class="text-danger">*</span></label>
|
||||
<select name="bed_id" class="form-select" required>
|
||||
<option value="">-- Select Bed --</option>
|
||||
<?php foreach ($availableBedsData as $wardName => $beds): ?>
|
||||
<optgroup label="<?= esc($wardName) ?>">
|
||||
<?php foreach ($beds as $bed): ?>
|
||||
<option value="<?= esc($bed->id) ?>">
|
||||
<?= esc($bed->bed_number) ?> - <?= esc($bed->bed_code) ?>
|
||||
<?php if ($bed->category_name): ?>
|
||||
(<?= esc($bed->category_name) ?>)
|
||||
<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Transfer Reason <span class="text-danger">*</span></label>
|
||||
<select name="transfer_reason" class="form-select" required>
|
||||
<option value="">-- Select Reason --</option>
|
||||
<option value="Medical Requirement">Medical Requirement</option>
|
||||
<option value="ICU Transfer">ICU Transfer</option>
|
||||
<option value="Ward Transfer">Ward Transfer</option>
|
||||
<option value="Room Upgrade">Room Upgrade</option>
|
||||
<option value="Patient Request">Patient Request</option>
|
||||
<option value="Isolation Required">Isolation Required</option>
|
||||
<option value="Bed Maintenance">Bed Maintenance</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Assignment Date/Time <span class="text-danger">*</span></label>
|
||||
<input type="datetime-local" name="assignment_datetime" class="form-control" value="<?= date('Y-m-d\\TH:i') ?>" required>
|
||||
<small class="form-text text-muted">Time when bed assignment takes effect</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Additional Remarks</label>
|
||||
<textarea name="remarks" class="form-control" rows="2" placeholder="Any additional notes about the transfer..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-left-right"></i> Transfer Bed
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Bed Assignment History</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bed</th>
|
||||
<th>Ward</th>
|
||||
<th>Assigned Date</th>
|
||||
<th>Released Date</th>
|
||||
<th>Duration</th>
|
||||
<th>Type</th>
|
||||
<th>Remarks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($bedHistoryData)): ?>
|
||||
<?php foreach ($bedHistoryData as $history): ?>
|
||||
<tr>
|
||||
<td><?= esc($history->bed_number ?? '') ?> (<?= esc($history->bed_code ?? '') ?>)</td>
|
||||
<td><?= esc($history->ward_name ?? '') ?></td>
|
||||
<td><?= esc($history->assigned_date ?? '') ?></td>
|
||||
<td>
|
||||
<?php if ($history->released_date): ?>
|
||||
<?= esc($history->released_date) ?>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-success">Current</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($history->duration_hours) && $history->duration_hours !== null): ?>
|
||||
<?php
|
||||
$hours = floor($history->duration_hours);
|
||||
$minutes = round(($history->duration_hours - $hours) * 60);
|
||||
?>
|
||||
<strong><?= $hours ?>h <?= $minutes ?>m</strong>
|
||||
<?php if (!$history->released_date): ?>
|
||||
<small class="text-muted">(ongoing)</small>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc($history->assignment_type ?? 'admission') ?></td>
|
||||
<td><?= esc($history->remarks ?? '') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted">No bed assignment history found</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nursing-tab-pane d-none" id="nursing_tab_admission">
|
||||
<form class="row g-2 nursing-form" data-save-url="<?= esc($saveUrl) ?>">
|
||||
<?= csrf_field() ?>
|
||||
|
|
@ -1438,5 +1604,52 @@ ksort($nurseNames);
|
|||
nursingRefreshBedsideItems();
|
||||
nursingRefreshDoctorVisitTypes();
|
||||
nursingRefreshDoctorVisitRate();
|
||||
|
||||
// Bed Transfer Form Handler
|
||||
var bedTransferForm = document.getElementById('bed_transfer_form');
|
||||
if (bedTransferForm) {
|
||||
bedTransferForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var bedId = bedTransferForm.querySelector('[name="bed_id"]').value;
|
||||
var transferReason = bedTransferForm.querySelector('[name="transfer_reason"]').value;
|
||||
|
||||
if (!bedId) {
|
||||
notify('error', 'Bed Transfer', 'Please select a bed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transferReason) {
|
||||
notify('error', 'Bed Transfer', 'Please select a transfer reason');
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new window.FormData(bedTransferForm);
|
||||
|
||||
$.ajax({
|
||||
url: '<?= esc(site_url('ipd/patient/bed/transfer/' . $ipdId)) ?>',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'json'
|
||||
}).done(function (resp) {
|
||||
if (!resp || String(resp.status) !== '1') {
|
||||
notify('error', 'Bed Transfer', (resp && resp.message) ? resp.message : 'Bed transfer failed');
|
||||
return;
|
||||
}
|
||||
|
||||
notify('success', 'Bed Transfer', resp.message || 'Bed transferred successfully');
|
||||
|
||||
if (resp.reload) {
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
}).fail(function () {
|
||||
notify('error', 'Bed Transfer', 'Failed to transfer bed. Please try again.');
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
179
fix_bed_management.sql
Normal file
179
fix_bed_management.sql
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
-- =====================================================
|
||||
-- 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
|
||||
189
run_bed_fix.php
Normal file
189
run_bed_fix.php
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
<?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";
|
||||
Loading…
Reference in New Issue
Block a user