This commit is contained in:
Marwan Alwali 2025-08-20 16:06:10 +03:00
parent f941a8e608
commit 99858b4075
29 changed files with 8212 additions and 738 deletions

View File

@ -33,6 +33,7 @@ APPOINTMENT_TYPES = [
'THERAPY', 'VACCINATION', 'SCREENING', 'EMERGENCY', 'TELEMEDICINE'
]
SAUDI_CHIEF_COMPLAINTS = [
'Chest pain and shortness of breath',
'Abdominal pain and nausea',

View File

@ -64,12 +64,12 @@ ARABIC_FAMILY_NAMES = [
]
def create_saudi_tenants(count=20):
def create_saudi_tenants(count=1):
"""Create Saudi healthcare tenants"""
tenants = []
for i in range(count):
hospital_name = random.choice(SAUDI_HOSPITALS)
hospital_name = 'King Faisal Specialist Hospital'
city = random.choice(SAUDI_CITIES)
province = random.choice(SAUDI_PROVINCES)
@ -112,6 +112,8 @@ def create_super_user():
tenant1 = Tenant.objects.get(pk=1)
User.objects.create_superuser(
username="admin",
first_name="Marwan",
last_name="Alwali",
email="marwan@tenhal.sa",
password="Kfsh&rc9788",
tenant=tenant1 # assumes your User model has a ForeignKey to Tenant named `tenant`
@ -266,32 +268,32 @@ def create_saudi_notifications(tenants):
{
'title': 'Ramadan Schedule Update',
'message': 'Hospital operating hours have been adjusted for the holy month of Ramadan. Please check the updated schedule.',
'type': 'schedule',
'priority': 'high'
'type': 'INFO',
'priority': 'HIGH'
},
{
'title': 'CBAHI Accreditation Renewal',
'message': 'Annual CBAHI accreditation renewal is due. Please ensure all documentation is up to date.',
'type': 'compliance',
'priority': 'critical'
'type': 'INFO',
'priority': 'URGENT'
},
{
'title': 'Ministry of Health Guidelines',
'message': 'New MOH guidelines for patient data privacy have been published. Review required.',
'type': 'policy',
'priority': 'high'
'type': 'UPDATE',
'priority': 'HIGH'
},
{
'title': 'Hajj Season Preparation',
'message': 'Special preparations for Hajj season medical services are now in effect.',
'type': 'seasonal',
'priority': 'medium'
'type': 'UPDATE',
'priority': 'MEDIUM'
},
{
'title': 'Arabic Language Interface',
'message': 'The system now supports full Arabic language interface. Switch in your profile settings.',
'type': 'feature',
'priority': 'low'
'type': 'FEATURE',
'priority': 'LOW'
}
]
@ -333,7 +335,7 @@ def create_saudi_audit_logs(tenants, count_per_tenant=100):
'vital_signs_recorded', 'medication_administered'
]
risk_levels = ['low', 'medium', 'high', 'critical']
risk_levels = AuditLogEntry.RISK_LEVEL_CHOICES
for tenant in tenants:
for _ in range(count_per_tenant):
@ -343,8 +345,8 @@ def create_saudi_audit_logs(tenants, count_per_tenant=100):
tenant=tenant,
log_id=uuid.uuid4(),
event_type=event_type,
event_category='clinical' if event_type in ['medical_record_access',
'prescription_issued'] else 'administrative',
event_category='CLINICAL_DATA' if event_type in ['medical_record_access',
'prescription_issued'] else 'SYSTEM_ADMINISTRATION',
user_email=f"{random.choice(ARABIC_FIRST_NAMES_MALE + ARABIC_FIRST_NAMES_FEMALE).lower()}@{tenant.name.lower().replace(' ', '')}.sa",
user_role=random.choice(['doctor', 'nurse', 'pharmacist', 'admin', 'receptionist']),
session_key=uuid.uuid4(),
@ -379,10 +381,7 @@ def create_saudi_integration_logs(tenants, count_per_tenant=50):
"""Create Saudi-specific integration logs"""
integration_logs = []
saudi_integrations = [
'MOH_HESN', 'NPHIES', 'Absher', 'SAUDI_POST', 'SAMA_PAYMENTS',
'CBAHI_PORTAL', 'SFDA_PORTAL', 'ZATCA_INTEGRATION'
]
saudi_integrations = IntegrationLog.INTEGRATION_TYPE_CHOICES
message_types = [
'patient_eligibility', 'claim_submission', 'payment_notification',
@ -400,7 +399,7 @@ def create_saudi_integration_logs(tenants, count_per_tenant=50):
integration_type=integration_type,
direction=random.choice(['inbound', 'outbound']),
external_system=integration_type,
endpoint=f"https://api.{integration_type.lower()}.gov.sa/v1/{message_type}",
endpoint=f"https://api.{integration_type}.gov.sa/v1/{message_type}",
message_type=message_type,
message_id=f"MSG-{uuid.uuid4().hex[:12].upper()}",
correlation_id=uuid.uuid4(),
@ -414,7 +413,7 @@ def create_saudi_integration_logs(tenants, count_per_tenant=50):
'response_code': '200',
'data': {'verified': True, 'eligibility': 'active'}
},
status=random.choice(['success', 'success', 'success', 'failed']), # Mostly successful
status=random.choice(['SUCCESS', 'SUCCESS', 'SUCCESS', 'FAILED']), # Mostly successful
error_code=None if random.choice([True, True, True, False]) else 'ERR_401',
error_message=None if random.choice([True, True, True, False]) else 'Authentication failed',
processing_time_ms=random.randint(100, 5000),
@ -433,7 +432,7 @@ def main():
# Create tenants
print("\n1. Creating Saudi Healthcare Tenants...")
tenants = create_saudi_tenants(15)
tenants = create_saudi_tenants(1)
# Create Super user
print("\n2. Creating Super User...")

Binary file not shown.

View File

@ -17,7 +17,38 @@ class Employee(models.Model):
"""
Employee model for hospital staff management.
"""
GENDER_CHOICES = [
('MALE', 'Male'),
('FEMALE', 'Female'),
('OTHER', 'Other'),
('UNKNOWN', 'Unknown'),
]
MARITAL_STATUS_CHOICES = [
('SINGLE', 'Single'),
('MARRIED', 'Married'),
('DIVORCED', 'Divorced'),
('WIDOWED', 'Widowed'),
('SEPARATED', 'Separated'),
('OTHER', 'Other'),
]
EMPLOYMENT_TYPE_CHOICES = [
('FULL_TIME', 'Full Time'),
('PART_TIME', 'Part Time'),
('CONTRACT', 'Contract'),
('TEMPORARY', 'Temporary'),
('INTERN', 'Intern'),
('VOLUNTEER', 'Volunteer'),
('PER_DIEM', 'Per Diem'),
('CONSULTANT', 'Consultant'),
]
EMPLOYMENT_STATUS_CHOICES = [
('ACTIVE', 'Active'),
('INACTIVE', 'Inactive'),
('TERMINATED', 'Terminated'),
('SUSPENDED', 'Suspended'),
('LEAVE', 'On Leave'),
('RETIRED', 'Retired'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
@ -135,26 +166,14 @@ class Employee(models.Model):
)
gender = models.CharField(
max_length=10,
choices=[
('MALE', 'Male'),
('FEMALE', 'Female'),
('OTHER', 'Other'),
('UNKNOWN', 'Unknown'),
],
choices=GENDER_CHOICES,
blank=True,
null=True,
help_text='Gender'
)
marital_status = models.CharField(
max_length=20,
choices=[
('SINGLE', 'Single'),
('MARRIED', 'Married'),
('DIVORCED', 'Divorced'),
('WIDOWED', 'Widowed'),
('SEPARATED', 'Separated'),
('OTHER', 'Other'),
],
choices=MARITAL_STATUS_CHOICES,
blank=True,
null=True,
help_text='Marital status'
@ -175,28 +194,12 @@ class Employee(models.Model):
)
employment_type = models.CharField(
max_length=20,
choices=[
('FULL_TIME', 'Full Time'),
('PART_TIME', 'Part Time'),
('CONTRACT', 'Contract'),
('TEMPORARY', 'Temporary'),
('INTERN', 'Intern'),
('VOLUNTEER', 'Volunteer'),
('PER_DIEM', 'Per Diem'),
('CONSULTANT', 'Consultant'),
],
choices=EMPLOYMENT_TYPE_CHOICES,
help_text='Employment type'
)
employment_status = models.CharField(
max_length=20,
choices=[
('ACTIVE', 'Active'),
('INACTIVE', 'Inactive'),
('TERMINATED', 'Terminated'),
('SUSPENDED', 'Suspended'),
('LEAVE', 'On Leave'),
('RETIRED', 'Retired'),
],
choices=EMPLOYMENT_STATUS_CHOICES,
default='ACTIVE',
help_text='Employment status'
)

View File

@ -199,7 +199,7 @@ def create_saudi_employees(tenants, departments, employees_per_tenant=150):
# Determine employment type and status
employment_type = random.choices(
['FULL_TIME', 'PART_TIME', 'CONTRACT', 'PER_DIEM'],
['FULL_TIME', 'PART_TIME', 'CONTRACT', 'CONTRACT'],
weights=[70, 15, 10, 5]
)[0]
@ -355,40 +355,42 @@ def create_saudi_schedules(employees, schedules_per_employee=2):
schedule_patterns = {
'DAY_SHIFT': {
'monday': {'start': '07:00', 'end': '15:00'},
'tuesday': {'start': '07:00', 'end': '15:00'},
'wednesday': {'start': '07:00', 'end': '15:00'},
'thursday': {'start': '07:00', 'end': '15:00'},
'friday': {'start': '07:00', 'end': '12:00'}, # Half day Friday
'saturday': {'start': '07:00', 'end': '15:00'},
'sunday': 'off'
},
'EVENING_SHIFT': {
'monday': {'start': '15:00', 'end': '23:00'},
'tuesday': {'start': '15:00', 'end': '23:00'},
'wednesday': {'start': '15:00', 'end': '23:00'},
'thursday': {'start': '15:00', 'end': '23:00'},
'friday': {'start': '13:00', 'end': '21:00'},
'saturday': {'start': '15:00', 'end': '23:00'},
'sunday': 'off'
'sunday': {'start': '07:00', 'end': '19:00'},
'monday': {'start': '07:00', 'end': '19:00'},
'tuesday': {'start': '07:00', 'end': '19:00'},
'wednesday': {'start': '07:00', 'end': '19:00'},
'thursday': {'start': '07:00', 'end': '19:00'},
'friday': {'start': '07:00', 'end': '19:00'},
'saturday': {'start': '07:00', 'end': '19:00'},
},
# 'EVENING_SHIFT': {
# 'monday': {'start': '15:00', 'end': '23:00'},
# 'tuesday': {'start': '15:00', 'end': '23:00'},
# 'wednesday': {'start': '15:00', 'end': '23:00'},
# 'thursday': {'start': '15:00', 'end': '23:00'},
# 'friday': {'start': '13:00', 'end': '21:00'},
# 'saturday': {'start': '15:00', 'end': '23:00'},
# 'sunday': 'off'
# },
'NIGHT_SHIFT': {
'monday': {'start': '23:00', 'end': '07:00'},
'tuesday': {'start': '23:00', 'end': '07:00'},
'wednesday': {'start': '23:00', 'end': '07:00'},
'thursday': {'start': '23:00', 'end': '07:00'},
'friday': 'off',
'saturday': {'start': '23:00', 'end': '07:00'},
'sunday': {'start': '23:00', 'end': '07:00'}
'sunday': {'start': '19:00', 'end': '07:00'},
'monday': {'start': '19:00', 'end': '07:00'},
'tuesday': {'start': '19:00', 'end': '07:00'},
'wednesday': {'start': '19:00', 'end': '07:00'},
'thursday': {'start': '19:00', 'end': '07:00'},
'friday': {'start': '19:00', 'end': '07:00'},
'saturday': {'start': '19:00', 'end': '07:00'},
},
'ADMIN_HOURS': {
'sunday': {'start': '08:00', 'end': '17:00'},
'monday': {'start': '08:00', 'end': '17:00'},
'tuesday': {'start': '08:00', 'end': '17:00'},
'wednesday': {'start': '08:00', 'end': '17:00'},
'thursday': {'start': '08:00', 'end': '17:00'},
'friday': {'start': '08:00', 'end': '12:00'},
'thursday': {'start': '08:00', 'end': '16:00'},
'friday': 'off',
'saturday': 'off',
'sunday': 'off'
}
}
@ -463,7 +465,7 @@ def create_schedule_assignments(schedules, days_back=30):
shift_types = {
'DAY_SHIFT': 'DAY',
'EVENING_SHIFT': 'EVENING',
# 'EVENING_SHIFT': 'EVENING',
'NIGHT_SHIFT': 'NIGHT',
'ADMIN_HOURS': 'DAY'
}
@ -491,7 +493,7 @@ def create_schedule_assignments(schedules, days_back=30):
pattern_key = None
for pattern_name, pattern_data in [
('DAY_SHIFT', schedule.schedule_pattern),
('EVENING_SHIFT', schedule.schedule_pattern),
# ('EVENING_SHIFT', schedule.schedule_pattern),
('NIGHT_SHIFT', schedule.schedule_pattern),
('ADMIN_HOURS', schedule.schedule_pattern)
]:

View File

@ -15,7 +15,7 @@ class WardAdmin(admin.ModelAdmin):
Admin interface for Ward model.
"""
list_display = [
'name', 'ward_id', 'ward_type', 'specialty', 'total_beds',
'tenant','name', 'ward_id', 'ward_type', 'specialty', 'total_beds',
'occupancy_display', 'building', 'floor', 'is_active'
]
list_filter = [
@ -70,9 +70,10 @@ class WardAdmin(admin.ModelAdmin):
color = 'orange'
else:
color = 'green'
filled_beds = obj.total_beds - obj.available_beds
return format_html(
'<span style="color: {};">{:.1f}% ({}/{})</span>',
color, rate, obj.total_beds - obj.available_beds, obj.total_beds
'<span style="color: {};">{}% ({}/{})</span>',
color, rate, filled_beds, obj.total_beds
)
occupancy_display.short_description = 'Occupancy'

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-08-19 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("inpatients", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="ward",
name="ward_id",
field=models.CharField(help_text="Unique ward identifier", max_length=50),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 5.2.4 on 2025-08-19 19:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("inpatients", "0002_alter_ward_ward_id"),
]
operations = [
migrations.AlterField(
model_name="bed",
name="bed_position",
field=models.CharField(
blank=True,
choices=[
("A", "A"),
("B", "B"),
("C", "C"),
("D", "D"),
("E", "E"),
("F", "F"),
],
help_text="Position within room",
max_length=20,
null=True,
),
),
]

View File

@ -11,39 +11,14 @@ from django.conf import settings
from datetime import timedelta, datetime, time
import json
from kombu.transport.redis import PRIORITY_STEPS
class Ward(models.Model):
"""
Hospital ward model for organizing patient care areas.
"""
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='wards',
help_text='Organization tenant'
)
# Ward Information
ward_id = models.CharField(
max_length=20,
help_text='Unique ward identifier'
)
name = models.CharField(
max_length=200,
help_text='Ward name'
)
description = models.TextField(
blank=True,
null=True,
help_text='Ward description'
)
# Ward Type and Specialty
ward_type = models.CharField(
max_length=50,
choices=[
WARD_TYPE_CHOICES = [
('GENERAL', 'General Medical'),
('SURGICAL', 'Surgical'),
('ICU', 'Intensive Care Unit'),
@ -62,13 +37,8 @@ class Ward(models.Model):
('ISOLATION', 'Isolation'),
('STEP_DOWN', 'Step Down'),
('OTHER', 'Other'),
],
help_text='Type of ward'
)
specialty = models.CharField(
max_length=100,
choices=[
]
SPECIALITY_CHOICES = [
('GENERAL_MEDICINE', 'General Medicine'),
('SURGERY', 'Surgery'),
('CARDIOLOGY', 'Cardiology'),
@ -83,7 +53,45 @@ class Ward(models.Model):
('CRITICAL_CARE', 'Critical Care'),
('REHABILITATION', 'Rehabilitation'),
('OTHER', 'Other'),
],
]
GENDER_RESTRICTIONS_CHOICES = [
('NONE', 'No Restrictions'),
('MALE_ONLY', 'Male Only'),
('FEMALE_ONLY', 'Female Only'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='wards',
help_text='Organization tenant'
)
# Ward Information
ward_id = models.CharField(
max_length=50,
help_text='Unique ward identifier'
)
name = models.CharField(
max_length=200,
help_text='Ward name'
)
description = models.TextField(
blank=True,
null=True,
help_text='Ward description'
)
# Ward Type and Specialty
ward_type = models.CharField(
max_length=50,
choices=WARD_TYPE_CHOICES,
help_text='Type of ward'
)
specialty = models.CharField(
max_length=100,
choices=SPECIALITY_CHOICES,
help_text='Medical specialty'
)
@ -175,11 +183,7 @@ class Ward(models.Model):
)
gender_restrictions = models.CharField(
max_length=20,
choices=[
('NONE', 'No Restrictions'),
('MALE_ONLY', 'Male Only'),
('FEMALE_ONLY', 'Female Only'),
],
choices=GENDER_RESTRICTIONS_CHOICES,
default='NONE',
help_text='Gender restrictions'
)
@ -238,7 +242,7 @@ class Ward(models.Model):
unique_together = ['tenant', 'ward_id']
def __str__(self):
return f"{self.name} ({self.ward_id})"
return self.name
@property
def occupancy_rate(self):
@ -246,9 +250,10 @@ class Ward(models.Model):
Calculate current occupancy rate.
"""
occupied_beds = self.beds.filter(status='OCCUPIED').count()
if self.total_beds > 0:
return (occupied_beds / self.total_beds) * 100
return 0
if self.total_beds == 0:
return 0.0
return 100 * (occupied_beds / self.total_beds)
@property
def available_beds(self):
@ -257,6 +262,10 @@ class Ward(models.Model):
"""
return self.beds.filter(status='AVAILABLE').count()
@property
def occupied_beds(self):
return self.beds.filter(status='OCCUPIED').count()
class Bed(models.Model):
"""
@ -299,10 +308,12 @@ class Bed(models.Model):
('TERMINAL', 'Terminal Cleaning'),
]
BED_POSITION_CHOICES = [
('WINDOW', 'Window Side'),
('DOOR', 'Door Side'),
('CENTER', 'Center'),
('CORNER', 'Corner'),
('A', 'A'),
('B', 'B'),
('C', 'C'),
('D', 'D'),
('E', 'E'),
('F', 'F'),
]
# Ward relationship
ward = models.ForeignKey(
@ -503,7 +514,69 @@ class Admission(models.Model):
"""
Patient admission model for tracking inpatient stays.
"""
ADMISSION_TYPE_CHOICES = [
('EMERGENCY', 'Emergency Admission'),
('ELECTIVE', 'Elective Admission'),
('URGENT', 'Urgent Admission'),
('OBSERVATION', 'Observation'),
('DAY_SURGERY', 'Day Surgery'),
('TRANSFER', 'Transfer from Another Facility'),
('READMISSION', 'Readmission'),
('DIRECT', 'Direct Admission'),
('OTHER', 'Other'),
]
ADMISSION_SOURCE_CHOICES = [
('EMERGENCY', 'Emergency Department'),
('OUTPATIENT', 'Outpatient Clinic'),
('PHYSICIAN_OFFICE', 'Physician Office'),
('TRANSFER', 'Transfer from Another Hospital'),
('NURSING_HOME', 'Nursing Home'),
('HOME', 'Home'),
('AMBULATORY_SURGERY', 'Ambulatory Surgery'),
('OTHER_FACILITY', 'Other Healthcare Facility'),
('OTHER', 'Other'),
]
STATUS_CHOICES = [
('PENDING', 'Pending Admission'),
('ADMITTED', 'Admitted'),
('TRANSFERRED', 'Transferred'),
('DISCHARGED', 'Discharged'),
('DECEASED', 'Deceased'),
('LEFT_AMA', 'Left Against Medical Advice'),
('CANCELLED', 'Cancelled'),
]
PRIORITY_CHOICES = [
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('CRITICAL', 'Critical'),
]
DISCHARGE_DISPOSITION_CHOICES = [
('HOME', 'Home'),
('HOME_HEALTH', 'Home with Health Services'),
('NURSING_HOME', 'Nursing Home'),
('REHAB_FACILITY', 'Rehabilitation Facility'),
('HOSPICE', 'Hospice'),
('TRANSFER', 'Transfer to Another Hospital'),
('DECEASED', 'Deceased'),
('LEFT_AMA', 'Left Against Medical Advice'),
('OTHER', 'Other'),
]
ISOLATION_TYPE_CHOICES = [
('CONTACT', 'Contact Precautions'),
('DROPLET', 'Droplet Precautions'),
('AIRBORNE', 'Airborne Precautions'),
('PROTECTIVE', 'Protective Isolation'),
('STRICT', 'Strict Isolation'),
]
CODE_STATUS_CHOICES = [
('FULL_CODE', 'Full Code'),
('DNR', 'Do Not Resuscitate'),
('DNI', 'Do Not Intubate'),
('DNR_DNI', 'DNR/DNI'),
('COMFORT_CARE', 'Comfort Care Only'),
('LIMITED', 'Limited Code'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
@ -534,33 +607,13 @@ class Admission(models.Model):
)
admission_type = models.CharField(
max_length=30,
choices=[
('EMERGENCY', 'Emergency Admission'),
('ELECTIVE', 'Elective Admission'),
('URGENT', 'Urgent Admission'),
('OBSERVATION', 'Observation'),
('DAY_SURGERY', 'Day Surgery'),
('TRANSFER', 'Transfer from Another Facility'),
('READMISSION', 'Readmission'),
('DIRECT', 'Direct Admission'),
('OTHER', 'Other'),
],
choices=ADMISSION_TYPE_CHOICES,
help_text='Type of admission'
)
admission_source = models.CharField(
max_length=30,
choices=[
('EMERGENCY', 'Emergency Department'),
('OUTPATIENT', 'Outpatient Clinic'),
('PHYSICIAN_OFFICE', 'Physician Office'),
('TRANSFER', 'Transfer from Another Hospital'),
('NURSING_HOME', 'Nursing Home'),
('HOME', 'Home'),
('AMBULATORY_SURGERY', 'Ambulatory Surgery'),
('OTHER_FACILITY', 'Other Healthcare Facility'),
('OTHER', 'Other'),
],
choices=ADMISSION_SOURCE_CHOICES,
help_text='Source of admission'
)
@ -616,15 +669,7 @@ class Admission(models.Model):
# Admission Status
status = models.CharField(
max_length=20,
choices=[
('PENDING', 'Pending Admission'),
('ADMITTED', 'Admitted'),
('TRANSFERRED', 'Transferred'),
('DISCHARGED', 'Discharged'),
('DECEASED', 'Deceased'),
('LEFT_AMA', 'Left Against Medical Advice'),
('CANCELLED', 'Cancelled'),
],
choices=STATUS_CHOICES,
default='PENDING',
help_text='Current admission status'
)
@ -632,12 +677,7 @@ class Admission(models.Model):
# Priority and Acuity
priority = models.CharField(
max_length=20,
choices=[
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('CRITICAL', 'Critical'),
],
choices=PRIORITY_CHOICES,
default='ROUTINE',
help_text='Admission priority'
)
@ -691,17 +731,7 @@ class Admission(models.Model):
)
discharge_disposition = models.CharField(
max_length=30,
choices=[
('HOME', 'Home'),
('HOME_HEALTH', 'Home with Health Services'),
('NURSING_HOME', 'Nursing Home'),
('REHAB_FACILITY', 'Rehabilitation Facility'),
('HOSPICE', 'Hospice'),
('TRANSFER', 'Transfer to Another Hospital'),
('DECEASED', 'Deceased'),
('LEFT_AMA', 'Left Against Medical Advice'),
('OTHER', 'Other'),
],
choices=DISCHARGE_DISPOSITION_CHOICES,
blank=True,
null=True,
help_text='Discharge disposition'
@ -714,13 +744,7 @@ class Admission(models.Model):
)
isolation_type = models.CharField(
max_length=30,
choices=[
('CONTACT', 'Contact Precautions'),
('DROPLET', 'Droplet Precautions'),
('AIRBORNE', 'Airborne Precautions'),
('PROTECTIVE', 'Protective Isolation'),
('STRICT', 'Strict Isolation'),
],
choices=ISOLATION_TYPE_CHOICES,
blank=True,
null=True,
help_text='Type of isolation required'
@ -747,14 +771,7 @@ class Admission(models.Model):
# Code Status
code_status = models.CharField(
max_length=20,
choices=[
('FULL_CODE', 'Full Code'),
('DNR', 'Do Not Resuscitate'),
('DNI', 'Do Not Intubate'),
('DNR_DNI', 'DNR/DNI'),
('COMFORT_CARE', 'Comfort Care Only'),
('LIMITED', 'Limited Code'),
],
choices=CODE_STATUS_CHOICES,
default='FULL_CODE',
help_text='Code status'
)
@ -822,10 +839,49 @@ class Admission(models.Model):
return self.status in ['PENDING', 'ADMITTED', 'TRANSFERRED']
class Transfer(models.Model):
"""
Patient transfer model for tracking ward/bed changes.
"""
TRANSFER_TYPE_CHOICES = [
('WARD', 'Ward Transfer'),
('BED', 'Bed Transfer'),
('ROOM', 'Room Transfer'),
('UNIT', 'Unit Transfer'),
('FACILITY', 'Facility Transfer'),
]
STATUS_CHOICES = [
('REQUESTED', 'Requested'),
('APPROVED', 'Approved'),
('SCHEDULED', 'Scheduled'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'),
]
PRIORITY_CHOICES = [
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('STAT', 'STAT'),
]
TRANSPORT_METHOD_CHOICES = [
('WHEELCHAIR', 'Wheelchair'),
('STRETCHER', 'Stretcher'),
('BED', 'Hospital Bed'),
('AMBULATORY', 'Walking'),
('AMBULANCE', 'Ambulance'),
('OTHER', 'Other'),
]
PATIENT_CONDITION_CHOICES = [
('STABLE', 'Stable'),
('UNSTABLE', 'Unstable'),
('CRITICAL', 'Critical'),
('IMPROVING', 'Improving'),
('DETERIORATING', 'Deteriorating'),
]
# Transfer Information
transfer_id = models.UUIDField(
@ -852,13 +908,7 @@ class Transfer(models.Model):
# Transfer Details
transfer_type = models.CharField(
max_length=20,
choices=[
('WARD', 'Ward Transfer'),
('BED', 'Bed Transfer'),
('ROOM', 'Room Transfer'),
('UNIT', 'Unit Transfer'),
('FACILITY', 'Facility Transfer'),
],
choices=TRANSFER_TYPE_CHOICES,
help_text='Type of transfer'
)
@ -913,15 +963,7 @@ class Transfer(models.Model):
# Transfer Status
status = models.CharField(
max_length=20,
choices=[
('REQUESTED', 'Requested'),
('APPROVED', 'Approved'),
('SCHEDULED', 'Scheduled'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'),
],
choices=STATUS_CHOICES,
default='REQUESTED',
help_text='Transfer status'
)
@ -932,12 +974,7 @@ class Transfer(models.Model):
)
priority = models.CharField(
max_length=20,
choices=[
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('STAT', 'STAT'),
],
choices=PRIORITY_CHOICES,
default='ROUTINE',
help_text='Transfer priority'
)
@ -969,14 +1006,7 @@ class Transfer(models.Model):
# Transport Information
transport_method = models.CharField(
max_length=20,
choices=[
('WHEELCHAIR', 'Wheelchair'),
('STRETCHER', 'Stretcher'),
('BED', 'Hospital Bed'),
('AMBULATORY', 'Walking'),
('AMBULANCE', 'Ambulance'),
('OTHER', 'Other'),
],
choices=TRANSPORT_METHOD_CHOICES,
blank=True,
null=True,
help_text='Method of transport'
@ -1003,13 +1033,7 @@ class Transfer(models.Model):
# Clinical Information
patient_condition = models.CharField(
max_length=20,
choices=[
('STABLE', 'Stable'),
('UNSTABLE', 'Unstable'),
('CRITICAL', 'Critical'),
('IMPROVING', 'Improving'),
('DETERIORATING', 'Deteriorating'),
],
choices=PATIENT_CONDITION_CHOICES,
help_text='Patient condition at time of transfer'
)
vital_signs = models.JSONField(
@ -1083,7 +1107,37 @@ class DischargeSummary(models.Model):
"""
Discharge summary model for documenting patient discharge.
"""
DISCHARGE_DISPOSITION_CHOICES = [
('HOME', 'Home'),
('HOME_HEALTH', 'Home with Health Services'),
('NURSING_HOME', 'Nursing Home'),
('REHAB_FACILITY', 'Rehabilitation Facility'),
('HOSPICE', 'Hospice'),
('TRANSFER', 'Transfer to Another Hospital'),
('OTHER', 'Other'),
]
TRANSPORTATION_METHOD_CHOICES = [
('PRIVATE', 'Private Vehicle'),
('TAXI', 'Taxi'),
('AMBULANCE', 'Ambulance'),
('MEDICAL_TRANSPORT', 'Medical Transport'),
('PUBLIC_TRANSPORT', 'Public Transportation'),
('WALKING', 'Walking'),
('OTHER', 'Other'),
]
PATIENT_UNDERSTANDING_CHOICES = [
('EXCELLENT', 'Excellent'),
('GOOD', 'Good'),
('FAIR', 'Fair'),
('POOR', 'Poor'),
]
READMISSION_RISK_CHOICES=[
('LOW', 'Low Risk'),
('MODERATE', 'Moderate Risk'),
('HIGH', 'High Risk'),
('VERY_HIGH', 'Very High Risk'),
]
# Admission relationship
admission = models.OneToOneField(
Admission,
@ -1200,15 +1254,7 @@ class DischargeSummary(models.Model):
# Discharge Disposition
discharge_disposition = models.CharField(
max_length=30,
choices=[
('HOME', 'Home'),
('HOME_HEALTH', 'Home with Health Services'),
('NURSING_HOME', 'Nursing Home'),
('REHAB_FACILITY', 'Rehabilitation Facility'),
('HOSPICE', 'Hospice'),
('TRANSFER', 'Transfer to Another Hospital'),
('OTHER', 'Other'),
],
choices=DISCHARGE_DISPOSITION_CHOICES,
help_text='Discharge disposition'
)
discharge_location = models.CharField(
@ -1225,15 +1271,7 @@ class DischargeSummary(models.Model):
)
transportation_method = models.CharField(
max_length=30,
choices=[
('PRIVATE', 'Private Vehicle'),
('TAXI', 'Taxi'),
('AMBULANCE', 'Ambulance'),
('MEDICAL_TRANSPORT', 'Medical Transport'),
('PUBLIC_TRANSPORT', 'Public Transportation'),
('WALKING', 'Walking'),
('OTHER', 'Other'),
],
choices=TRANSPORTATION_METHOD_CHOICES,
blank=True,
null=True,
help_text='Method of transportation'
@ -1264,12 +1302,7 @@ class DischargeSummary(models.Model):
)
patient_understanding = models.CharField(
max_length=20,
choices=[
('EXCELLENT', 'Excellent'),
('GOOD', 'Good'),
('FAIR', 'Fair'),
('POOR', 'Poor'),
],
choices=PATIENT_UNDERSTANDING_CHOICES,
blank=True,
null=True,
help_text='Patient understanding of instructions'
@ -1296,12 +1329,7 @@ class DischargeSummary(models.Model):
# Quality Measures
readmission_risk = models.CharField(
max_length=20,
choices=[
('LOW', 'Low Risk'),
('MODERATE', 'Moderate Risk'),
('HIGH', 'High Risk'),
('VERY_HIGH', 'Very High Risk'),
],
choices=READMISSION_RISK_CHOICES,
blank=True,
null=True,
help_text='Risk of readmission'
@ -1374,7 +1402,52 @@ class SurgerySchedule(models.Model):
"""
Surgery schedule model for tracking surgical procedures.
"""
SURGERY_TYPE_CHOICES = [
('ELECTIVE', 'Elective'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('TRAUMA', 'Trauma'),
('TRANSPLANT', 'Transplant'),
('CARDIAC', 'Cardiac'),
('NEUROSURGERY', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic'),
('GENERAL', 'General Surgery'),
('OTHER', 'Other'),
]
ANESTHESIA_TYPE_CHOICES = [
('GENERAL', 'General Anesthesia'),
('REGIONAL', 'Regional Anesthesia'),
('LOCAL', 'Local Anesthesia'),
('SPINAL', 'Spinal Anesthesia'),
('EPIDURAL', 'Epidural Anesthesia'),
('MAC', 'Monitored Anesthesia Care'),
('SEDATION', 'Conscious Sedation'),
('OTHER', 'Other'),
]
STATUS_CHOICES = [
('SCHEDULED', 'Scheduled'),
('CONFIRMED', 'Confirmed'),
('PREP', 'Pre-operative Prep'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('POSTPONED', 'Postponed'),
('DELAYED', 'Delayed'),
]
RECOVERY_LOCATION_CHOICES = [
('PACU', 'Post-Anesthesia Care Unit'),
('ICU', 'Intensive Care Unit'),
('WARD', 'Regular Ward'),
('SAME_DAY', 'Same Day Surgery'),
('HOME', 'Home'),
('OTHER', 'Other'),
]
PRIORITY_CHOICES = [
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('STAT', 'STAT'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
@ -1418,18 +1491,7 @@ class SurgerySchedule(models.Model):
)
surgery_type = models.CharField(
max_length=30,
choices=[
('ELECTIVE', 'Elective'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('TRAUMA', 'Trauma'),
('TRANSPLANT', 'Transplant'),
('CARDIAC', 'Cardiac'),
('NEUROSURGERY', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic'),
('GENERAL', 'General Surgery'),
('OTHER', 'Other'),
],
choices=SURGERY_TYPE_CHOICES,
help_text='Type of surgery'
)
@ -1497,16 +1559,7 @@ class SurgerySchedule(models.Model):
# Anesthesia Information
anesthesia_type = models.CharField(
max_length=30,
choices=[
('GENERAL', 'General Anesthesia'),
('REGIONAL', 'Regional Anesthesia'),
('LOCAL', 'Local Anesthesia'),
('SPINAL', 'Spinal Anesthesia'),
('EPIDURAL', 'Epidural Anesthesia'),
('MAC', 'Monitored Anesthesia Care'),
('SEDATION', 'Conscious Sedation'),
('OTHER', 'Other'),
],
choices=ANESTHESIA_TYPE_CHOICES,
blank=True,
null=True,
help_text='Type of anesthesia'
@ -1551,16 +1604,7 @@ class SurgerySchedule(models.Model):
# Surgery Status
status = models.CharField(
max_length=20,
choices=[
('SCHEDULED', 'Scheduled'),
('CONFIRMED', 'Confirmed'),
('PREP', 'Pre-operative Prep'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('POSTPONED', 'Postponed'),
('DELAYED', 'Delayed'),
],
choices=STATUS_CHOICES,
default='SCHEDULED',
help_text='Surgery status'
)
@ -1602,14 +1646,7 @@ class SurgerySchedule(models.Model):
# Recovery Information
recovery_location = models.CharField(
max_length=50,
choices=[
('PACU', 'Post-Anesthesia Care Unit'),
('ICU', 'Intensive Care Unit'),
('WARD', 'Regular Ward'),
('SAME_DAY', 'Same Day Surgery'),
('HOME', 'Home'),
('OTHER', 'Other'),
],
choices=RECOVERY_LOCATION_CHOICES,
blank=True,
null=True,
help_text='Post-operative recovery location'
@ -1618,12 +1655,7 @@ class SurgerySchedule(models.Model):
# Priority and Urgency
priority = models.CharField(
max_length=20,
choices=[
('ROUTINE', 'Routine'),
('URGENT', 'Urgent'),
('EMERGENT', 'Emergent'),
('STAT', 'STAT'),
],
choices=PRIORITY_CHOICES,
default='ROUTINE',
help_text='Surgery priority'
)

View File

@ -11,10 +11,12 @@ urlpatterns = [
# Main views
path('', views.InpatientDashboardView.as_view(), name='dashboard'),
path('wards/', views.WardListView.as_view(), name='ward_list'),
path('wards/<int:pk>/', views.WardDetailView.as_view(), name='ward_detail'),
path('beds/', views.BedManagementView.as_view(), name='bed_management'),
path('admissions/', views.AdmissionListView.as_view(), name='admission_list'),
path('admissions/<int:pk>/', views.AdmissionDetailView.as_view(), name='admission_detail'),
path('admissions/create/', views.AdmissionCreateView.as_view(), name='admission_create'),
path('admissions/<int:pk>/edit/', views.AdmissionUpdateView.as_view(), name='admission_update'),
path('transfers/', views.TransferManagementView.as_view(), name='transfer_management'),
path('surgery/', views.SurgeryScheduleView.as_view(), name='surgery_schedule'),
@ -37,6 +39,7 @@ urlpatterns = [
# Actions
path('transfer/<int:admission_id>/', views.transfer_patient, name='transfer_patient'),
path('discharge/<int:pk>/', views.discharge_patient, name='discharge_patient'),
path('transfer/<int:transfer_id>/approve/', views.approve_transfer, name='approve_transfer'),
path('transfer/<int:transfer_id>/complete/', views.complete_transfer, name='complete_transfer'),
path('bed/<int:bed_id>/status/', views.update_bed_status, name='update_bed_status'),

10
inpatients/utils.py Normal file
View File

@ -0,0 +1,10 @@
# from .models import Admission
#
#
#
# def discharge(request, admission_id):
# admission = Admission.objects.get(admission_id=admission_id)
#
# admission.status = 'DISCHARGED'
# admission.save()
# return None

View File

@ -1,7 +1,7 @@
"""
Views for inpatients app.
"""
from django.conf.locale import te
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
@ -423,10 +423,10 @@ class BedDetailView(LoginRequiredMixin, DetailView):
# Get bed history - admissions that used this bed
context['admission_history'] = Admission.objects.filter(
Q(initial_bed=bed) | Q(current_bed=bed)
Q(current_bed=bed) | Q(current_bed=bed)
).select_related(
'patient', 'admitting_physician'
).order_by('-admitted_at')[:10]
).order_by('-admission_datetime')[:10]
# Get maintenance history if available
# This would require a model to track maintenance events
@ -554,10 +554,29 @@ class BedManagementView(LoginRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
wards = Ward.objects.filter(tenant=tenant, is_active=True)
occupancy_rates = []
for ward in wards:
try:
occupancy_rates.append(ward.occupancy_rate)
except Exception:
pass # Optional: handle edge cases if any
average_occupancy = (
sum(occupancy_rates) / len(occupancy_rates)
if occupancy_rates else 0
)
context.update({
'wards': Ward.objects.filter(tenant=self.request.user.tenant, is_active=True),
'bed_statuses': Bed._meta.get_field('status').choices,
'bed_types': Bed._meta.get_field('bed_type').choices,
'wards': Ward.objects.filter(tenant=tenant, is_active=True),
'bed_statuses': Bed.STATUS_CHOICES,
'bed_types': Bed.BED_TYPE_CHOICES,
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(),
'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(),
'maintenance_beds': Bed.objects.filter(ward__tenant=tenant, status='MAINTENANCE').count(),
'occupancy_rate': average_occupancy,
})
return context
@ -607,8 +626,8 @@ class AdmissionListView(LoginRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'admission_statuses': Admission._meta.get_field('status').choices,
'admission_types': Admission._meta.get_field('admission_type').choices,
'admission_statuses': Admission.STATUS_CHOICES,
'admission_types': Admission.ADMISSION_TYPE_CHOICES,
'wards': Ward.objects.filter(tenant=self.request.user.tenant, is_active=True),
})
return context
@ -1540,62 +1559,62 @@ def maintenance_bed(request, pk):
# return context
#
#
# class WardDetailView(LoginRequiredMixin, DetailView):
# """
# Detail view for a ward.
# """
# model = Ward
# template_name = 'inpatients/ward_detail.html'
# context_object_name = 'ward'
#
# def get_queryset(self):
# """Filter wards by tenant."""
# return Ward.objects.filter(
# tenant=self.request.user.tenant
# ).select_related('nurse_manager')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# ward = self.get_object()
#
# # Get beds for this ward with patient information
# context['beds'] = Bed.objects.filter(
# ward=ward
# ).select_related(
# 'current_patient', 'current_admission'
# ).order_by('room_number', 'bed_number')
#
# # Group beds by room for display
# rooms = {}
# for bed in context['beds']:
# room_num = bed.room_number
# if room_num not in rooms:
# rooms[room_num] = []
# rooms[room_num].append(bed)
# context['rooms'] = rooms
#
# # Get ward statistics
# context['total_beds'] = context['beds'].count()
# context['available_beds'] = context['beds'].filter(status='AVAILABLE').count()
# context['occupied_beds'] = context['beds'].filter(status='OCCUPIED').count()
# context['maintenance_beds'] = context['beds'].filter(
# status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
# ).count()
#
# if context['total_beds'] > 0:
# context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100
# else:
# context['occupancy_rate'] = 0
#
# # Get recent admissions to this ward
# context['recent_admissions'] = Admission.objects.filter(
# Q(initial_ward=ward) | Q(current_bed__ward=ward),
# status__in=['ADMITTED', 'READY_FOR_DISCHARGE']
# ).select_related(
# 'patient', 'admitting_physician'
# ).order_by('-admitted_at')[:10]
#
# return context
class WardDetailView(LoginRequiredMixin, DetailView):
"""
Detail view for a ward.
"""
model = Ward
template_name = 'inpatients/ward_detail.html'
context_object_name = 'ward'
def get_queryset(self):
"""Filter wards by tenant."""
return Ward.objects.filter(
tenant=self.request.user.tenant
).select_related('nurse_manager')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
ward = self.get_object()
# Get beds for this ward with patient information
context['beds'] = Bed.objects.filter(
ward=ward
).select_related(
'current_patient', 'current_admission'
).order_by('room_number', 'bed_number')
# Group beds by room for display
rooms = {}
for bed in context['beds']:
room_num = bed.room_number
if room_num not in rooms:
rooms[room_num] = []
rooms[room_num].append(bed)
context['rooms'] = rooms
# Get ward statistics
context['total_beds'] = context['beds'].count()
context['available_beds'] = context['beds'].filter(status='AVAILABLE').count()
context['occupied_beds'] = context['beds'].filter(status='OCCUPIED').count()
context['maintenance_beds'] = context['beds'].filter(
status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
).count()
if context['total_beds'] > 0:
context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100
else:
context['occupancy_rate'] = 0
# Get recent admissions to this ward
context['recent_admissions'] = Admission.objects.filter(
Q(initial_ward=ward) | Q(current_bed__ward=ward),
status__in=['ADMITTED', 'READY_FOR_DISCHARGE']
).select_related(
'patient', 'admitting_physician'
).order_by('-admitted_at')[:10]
return context
#
#
# class WardCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
@ -2069,96 +2088,99 @@ def maintenance_bed(request, pk):
# return reverse('inpatients:admission_detail', kwargs={'pk': self.object.pk})
#
#
# class AdmissionUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update view for an admission.
# """
# model = Admission
# form_class = AdmissionForm
# template_name = 'inpatients/admission_form.html'
# permission_required = 'inpatients.change_admission'
#
# def get_queryset(self):
# """Filter admissions by tenant."""
# return Admission.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs['user'] = self.request.user
# return kwargs
#
# def form_valid(self, form):
# # Check if status is changing to ADMITTED
# old_status = self.get_object().status
# new_status = form.instance.status
#
# response = super().form_valid(form)
#
# # If changing to ADMITTED, assign the bed
# if old_status != 'ADMITTED' and new_status == 'ADMITTED' and form.instance.initial_bed:
# form.instance.initial_bed.assign_patient(form.instance.patient, form.instance)
# messages.success(self.request, _('Patient admitted and assigned to bed successfully'))
# else:
# messages.success(self.request, _('Admission updated successfully'))
#
# return response
#
# def get_success_url(self):
# return reverse('inpatients:admission_detail', kwargs={'pk': self.object.pk})
#
#
# @login_required
class AdmissionUpdateView(LoginRequiredMixin, UpdateView):
"""
Update view for an admission.
"""
model = Admission
form_class = AdmissionForm
template_name = 'inpatients/admissions/admission_form.html'
permission_required = 'inpatients.change_admission'
def get_queryset(self):
"""Filter admissions by tenant."""
return Admission.objects.filter(tenant=self.request.user.tenant)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
# Check if status is changing to ADMITTED
old_status = self.get_object().status
new_status = form.instance.status
response = super().form_valid(form)
# If changing to ADMITTED, assign the bed
if old_status != 'ADMITTED' and new_status == 'ADMITTED' and form.instance.initial_bed:
form.instance.initial_bed.assign_patient(form.instance.patient, form.instance)
messages.success(self.request, _('Patient admitted and assigned to bed successfully'))
else:
messages.success(self.request, _('Admission updated successfully'))
return response
def get_success_url(self):
return reverse('inpatients:admission_detail', kwargs={'pk': self.object.pk})
@login_required
# @permission_required('inpatients.change_admission')
# def discharge_patient(request, pk):
# """
# View to discharge a patient.
# """
# admission = get_object_or_404(
# Admission,
# pk=pk,
# tenant=request.user.tenant
# )
#
# # Only admitted patients can be discharged
# if admission.status not in ['ADMITTED', 'READY_FOR_DISCHARGE']:
# messages.error(request, _('Only admitted patients or patients ready for discharge can be discharged'))
# return redirect('inpatients:admission_detail', pk=admission.pk)
#
# if request.method == 'POST':
# summary_form = DischargeSummaryForm(
# request.POST,
# user=request.user,
# admission=admission
# )
#
# if summary_form.is_valid():
# summary = summary_form.save(commit=False)
# summary.patient = admission.patient
# summary.created_by = request.user
# summary.save()
#
# # Link summary to admission and discharge
# admission.discharge_summary = summary
# admission.discharge(request.user)
#
# messages.success(request, _('Patient discharged successfully'))
# return redirect('inpatients:admission_detail', pk=admission.pk)
# else:
# initial = {
# 'patient': admission.patient,
# 'discharge_diagnosis': admission.admitting_diagnosis,
# 'doctor_name': request.user.get_full_name() if request.user.role in ['DOCTOR', 'SPECIALIST'] else ''
# }
# summary_form = DischargeSummaryForm(
# initial=initial,
# user=request.user,
# admission=admission
# )
#
# return render(request, 'inpatients/discharge_form.html', {
# 'admission': admission,
# 'form': summary_form
# })
def discharge_patient(request, pk):
print("function is right")
print(pk)
"""
View to discharge a patient.
"""
admission = get_object_or_404(
Admission,
pk=pk,
tenant=request.user.tenant
)
print(admission)
# Only admitted patients can be discharged
if admission.status not in ['ADMITTED', 'READY_FOR_DISCHARGE']:
messages.error(request, _('Only admitted patients or patients ready for discharge can be discharged'))
return redirect('inpatients:admission_detail', pk=admission.pk)
if request.method == 'POST':
summary_form = DischargeSummaryForm(
request.POST,
user=request.user,
admission=admission
)
if summary_form.is_valid():
summary = summary_form.save(commit=False)
summary.patient = admission.patient
summary.created_by = request.user
summary.save()
# Link summary to admission and discharge
admission.discharge_summary = summary
# admission.status = 'DISCHARGED'
messages.success(request, _('Patient discharged successfully'))
return redirect('inpatients:admission_detail', pk=admission.pk)
else:
initial = {
'patient': admission.patient,
'discharge_diagnosis': admission.admitting_diagnosis,
'doctor_name': request.user.get_full_name() if request.user.role in ['DOCTOR', 'SPECIALIST'] else ''
}
summary_form = DischargeSummaryForm(
initial=initial,
user=request.user,
admission=admission
)
return render(request, 'inpatients/discharges/discharge_form.html', {
'admission': admission,
'form': summary_form
})
#
#
# @login_required
@ -2955,33 +2977,36 @@ def maintenance_bed(request, pk):
# return redirect('inpatients:bed_detail', pk=bed.pk)
#
#
# @login_required
@login_required
# @permission_required('inpatients.change_bed')
# def maintenance_bed(request, pk):
# """
# Mark a bed for maintenance.
# """
# bed = get_object_or_404(
# Bed,
# pk=pk,
# ward__tenant=request.user.tenant
# )
#
# # Only available beds can be marked for maintenance
# if bed.status != 'AVAILABLE':
# messages.error(request, _('Only available beds can be marked for maintenance'))
# return redirect('inpatients:bed_detail', pk=bed.pk)
#
# if request.method == 'POST':
# notes = request.POST.get('notes')
#
# bed.mark_maintenance(notes)
# messages.success(request, _('Bed marked for maintenance successfully'))
# return redirect('inpatients:bed_detail', pk=bed.pk)
#
# return render(request, 'inpatients/maintenance_bed.html', {
# 'bed': bed
# })
def maintenance_bed(request, pk):
"""
Mark a bed for maintenance.
"""
bed = get_object_or_404(
Bed,
pk=pk,
ward__tenant=request.user.tenant
)
# Only available beds can be marked for maintenance
if bed.status != 'AVAILABLE':
messages.error(request, _('Only available beds can be marked for maintenance'))
return redirect('inpatients:bed_detail', pk=bed.pk)
if request.method == 'POST':
notes = request.POST.get('notes')
# bed.mark_maintenance(notes)
bed.status = 'MAINTENANCE'
bed.notes = notes
bed.save()
messages.success(request, _('Bed marked for maintenance successfully'))
return redirect('inpatients:bed_detail', pk=bed.pk)
return render(request, 'inpatients/maintenance_bed.html', {
'bed': bed
})
#
#
# @login_required

View File

@ -24,13 +24,10 @@ SAUDI_WARD_NAMES = [
'Orthopedic Ward', 'Maternity Ward', 'Pediatric Ward', 'Oncology Ward'
]
SAUDI_SPECIALTIES = [
'Internal Medicine', 'Cardiology', 'Cardiac Surgery', 'Orthopedics',
'General Surgery', 'Neurology', 'Neurosurgery', 'Oncology',
'Pediatrics', 'Obstetrics and Gynecology', 'Emergency Medicine',
'Intensive Care', 'Anesthesiology', 'Psychiatry', 'Dermatology',
'Ophthalmology', 'ENT', 'Urology', 'Nephrology', 'Endocrinology'
]
SAUDI_SPECIALTIES = ['GENERAL_MEDICINE','SURGERY','CARDIOLOGY','NEUROLOGY','ONCOLOGY',
'PEDIATRICS','OBSTETRICS','GYNECOLOGY','ORTHOPEDICS','PSYCHIATRY',
'EMERGENCY','CRITICAL_CARE','REHABILITATION',
]
SAUDI_BUILDINGS = [
'King Fahd Medical Tower', 'Prince Sultan Building', 'Al-Faisal Complex',
@ -71,15 +68,9 @@ def create_saudi_wards(tenants, wards_per_tenant=12):
"""Create Saudi hospital wards"""
wards = []
ward_types = [
('medical', 'Medical Ward'),
('surgical', 'Surgical Ward'),
('icu', 'Intensive Care Unit'),
('pediatric', 'Pediatric Ward'),
('maternity', 'Maternity Ward'),
('emergency', 'Emergency Ward'),
('psychiatric', 'Psychiatric Ward'),
('rehabilitation', 'Rehabilitation Ward')
ward_types =['GENERAL','SURGICAL','ICU','CCU','NICU','PICU','EMERGENCY','MATERNITY','PEDIATRIC',
'ONCOLOGY','CARDIAC','ORTHOPEDIC','NEUROLOGY','PSYCHIATRY','REHABILITATION',
'ISOLATION','STEP_DOWN',
]
for tenant in tenants:
@ -123,7 +114,7 @@ def create_saudi_wards(tenants, wards_per_tenant=12):
ward_id=uuid.uuid4(),
name=ward_name,
description=f"Specialized {specialty.lower()} ward providing comprehensive inpatient care",
ward_type=ward_type[0],
ward_type=ward_type,
specialty=specialty,
total_beds=total_beds,
private_rooms=private_rooms,
@ -176,23 +167,15 @@ def create_saudi_beds(wards):
beds = []
bed_types = [
('standard', 'Standard Hospital Bed'),
('electric', 'Electric Adjustable Bed'),
('icu', 'ICU Bed'),
('bariatric', 'Bariatric Bed'),
('pediatric', 'Pediatric Bed')
'STANDARD','ICU','CARDIAC','ISOLATION','BARIATRIC','PEDIATRIC',
'NEONATAL','MATERNITY','PSYCHIATRIC','STRETCHER','RECLINER'
]
room_types = [
('private', 'Private Room'),
('semi_private', 'Semi-Private Room'),
('shared', 'Shared Room'),
('isolation', 'Isolation Room')
]
room_types = ['PRIVATE','SEMI_PRIVATE','SHARED','ICU','ISOLATION']
bed_statuses = ['available', 'occupied', 'maintenance', 'blocked']
bed_statuses = ['AVAILABLE','OCCUPIED','RESERVED','MAINTENANCE','CLEANING','OUT_OF_ORDER','BLOCKED']
cleaning_levels = ['standard', 'deep', 'isolation', 'terminal']
cleaning_levels = ['STANDARD','DEEP','ISOLATION','TERMINAL']
for ward in wards:
for bed_num in range(1, ward.total_beds + 1):
@ -213,13 +196,13 @@ def create_saudi_beds(wards):
ward=ward,
bed_number=bed_number,
room_number=room_number,
bed_type=bed_type[0],
room_type=room_type[0],
bed_type=bed_type,
room_type=room_type,
status=status,
occupied_since=django_timezone.now() - timedelta(
days=random.randint(1, 30)) if status == 'occupied' else None,
days=random.randint(1, 30)) if status == 'OCCUPIED' else None,
reserved_until=django_timezone.now() + timedelta(
hours=random.randint(1, 24)) if status == 'available' and random.choice([True, False]) else None,
hours=random.randint(1, 24)) if status == 'AVAILABLE' and random.choice([True, False]) else None,
equipment=[
'Cardiac Monitor', 'IV Pole', 'Oxygen Outlet', 'Suction Outlet',
'Nurse Call System', 'Bedside Table', 'Privacy Curtain'
@ -250,34 +233,21 @@ def create_saudi_admissions(tenants, beds, admissions_per_tenant=100):
"""Create Saudi hospital admissions"""
admissions = []
admission_types = [
('emergency', 'Emergency Admission'),
('elective', 'Elective Admission'),
('urgent', 'Urgent Admission'),
('transfer', 'Transfer from Another Facility')
admission_types = ['EMERGENCY','ELECTIVE','URGENT','OBSERVATION','DAY_SURGERY',
'TRANSFER','READMISSION','DIRECT',
]
admission_sources = [
('emergency_department', 'Emergency Department'),
('outpatient_clinic', 'Outpatient Clinic'),
('physician_office', 'Physician Office'),
('transfer_acute', 'Transfer from Acute Care'),
('transfer_ltc', 'Transfer from Long-term Care'),
('direct_admission', 'Direct Admission')
admission_sources = ['EMERGENCY','OUTPATIENT','PHYSICIAN_OFFICE','TRANSFER',
'NURSING_HOME','HOME','AMBULATORY_SURGERY'
]
statuses = ['active', 'discharged', 'transferred']
priorities = ['routine', 'urgent', 'emergent', 'critical']
statuses = ['PENDING','ADMITTED','TRANSFERRED','DISCHARGED','DECEASED','LEFT_AMA','CANCELLED',]
priorities = ['ROUTINE','URGENT','EMERGENT','CRITICAL']
acuity_levels = ['1', '2', '3', '4', '5'] # 1 = highest acuity
isolation_types = [
'contact', 'droplet', 'airborne', 'protective', 'contact_plus',
'strict_isolation', 'reverse_isolation'
]
isolation_types = ['CONTACT','DROPLET','AIRBORNE','PROTECTIVE','STRICT',]
code_statuses = [
'full_code', 'dnr', 'dni', 'comfort_care', 'limited_intervention'
]
code_statuses = ['FULL_CODE','DNR','DNI','DNR_DNI','COMFORT_CARE','LIMITED',]
for tenant in tenants:
# Get patients and staff for this tenant
@ -309,8 +279,8 @@ def create_saudi_admissions(tenants, beds, admissions_per_tenant=100):
admission_id=uuid.uuid4(),
patient=patient,
admission_datetime=admission_datetime,
admission_type=random.choice(admission_types)[0],
admission_source=random.choice(admission_sources)[0],
admission_type=random.choice(admission_types),
admission_source=random.choice(admission_sources),
chief_complaint=random.choice([
'Chest pain', 'Shortness of breath', 'Abdominal pain',
'Fever and chills', 'Weakness and fatigue', 'Nausea and vomiting',
@ -375,17 +345,11 @@ def create_saudi_transfers(admissions):
"""Create transfer records for Saudi patients"""
transfers = []
transfer_types = [
('ward_to_ward', 'Ward to Ward'),
('ward_to_icu', 'Ward to ICU'),
('icu_to_ward', 'ICU to Ward'),
('er_to_ward', 'ER to Ward'),
('ward_to_or', 'Ward to OR')
]
transfer_types = ['WARD','BED','ROOM','UNIT','FACILITY',]
transfer_statuses = ['requested', 'approved', 'in_progress', 'completed', 'cancelled']
transport_methods = ['bed', 'wheelchair', 'stretcher', 'walking', 'ambulance']
priorities = ['routine', 'urgent', 'emergent', 'stat']
transfer_statuses = ['REQUESTED','APPROVED','SCHEDULED','IN_PROGRESS','COMPLETED','CANCELLED','DELAYED',]
transport_methods = ['WHEELCHAIR','STRETCHER','BED','AMBULATORY','AMBULANCE','OTHER',]
priorities = ['ROUTINE','URGENT','EMERGENT','STAT',]
# Create transfers for 30% of admissions
for admission in random.sample(admissions, int(len(admissions) * 0.3)):
@ -394,7 +358,7 @@ def create_saudi_transfers(admissions):
# Get available beds in different wards
available_beds = Bed.objects.filter(
ward__tenant=admission.tenant,
status='available'
status='AVAILABLE'
).exclude(ward=admission.current_ward)
if not available_beds:
@ -425,10 +389,10 @@ def create_saudi_transfers(admissions):
scheduled_datetime = None
actual_datetime = None
if status in ['approved', 'in_progress', 'completed']:
if status in ['APPROVED', 'IN_PROGRESS', 'COMPLETED']:
scheduled_datetime = requested_datetime + timedelta(hours=random.randint(1, 12))
if status in ['in_progress', 'completed']:
if status in ['IN_PROGRESS', 'COMPLETED']:
actual_datetime = scheduled_datetime + timedelta(minutes=random.randint(-30, 60))
# Get transport team members
@ -444,7 +408,7 @@ def create_saudi_transfers(admissions):
transfer_id=uuid.uuid4(),
admission=admission,
patient=admission.patient,
transfer_type=transfer_type[0],
transfer_type=transfer_type,
from_ward=admission.current_ward,
from_bed=admission.current_bed,
to_ward=to_ward,
@ -473,9 +437,9 @@ def create_saudi_transfers(admissions):
},
handoff_report=f"Patient transferred for {random.choice(['specialized care', 'higher acuity monitoring', 'bed availability'])}",
medications_transferred=random.sample(SAUDI_MEDICATIONS, random.randint(2, 5)),
delay_reason="Bed not ready" if status == 'approved' and random.choice([True, False]) else None,
complications="None noted" if status == 'completed' else None,
notes=f"Transfer completed successfully" if status == 'completed' else None,
delay_reason="Bed not ready" if status == 'APPROVED' and random.choice([True, False]) else None,
complications="None noted" if status == 'COMPLETED' else None,
notes=f"Transfer completed successfully" if status == 'COMPLETED' else None,
created_at=requested_datetime,
updated_at=requested_datetime + timedelta(hours=random.randint(1, 24))
)
@ -485,19 +449,19 @@ def create_saudi_transfers(admissions):
transfer.transport_team.set(transport_team_members)
# Update admission current location if transfer completed
if status == 'completed':
if status == 'COMPLETED':
admission.current_ward = to_ward
admission.current_bed = to_bed
admission.save()
# Update bed statuses
transfer.from_bed.status = 'available'
transfer.from_bed.status = 'AVAILABLE'
transfer.from_bed.current_patient = None
transfer.from_bed.current_admission = None
transfer.from_bed.occupied_since = None
transfer.from_bed.save()
to_bed.status = 'occupied'
to_bed.status = 'OCCUPIED'
to_bed.current_patient = admission.patient
to_bed.current_admission = admission
to_bed.occupied_since = actual_datetime
@ -513,18 +477,9 @@ def create_saudi_discharge_summaries(admissions):
"""Create discharge summaries for discharged Saudi patients"""
summaries = []
discharged_admissions = [adm for adm in admissions if adm.status == 'discharged' and adm.discharge_datetime]
discharged_admissions = [adm for adm in admissions if adm.status == 'DISCHARGED' and adm.discharge_datetime]
discharge_dispositions = [
('home', 'Home'),
('home_health', 'Home with Health Services'),
('skilled_nursing', 'Skilled Nursing Facility'),
('rehabilitation', 'Rehabilitation Facility'),
('ltac', 'Long-term Acute Care'),
('hospice', 'Hospice Care'),
('transfer_acute', 'Transfer to Acute Care'),
('ama', 'Against Medical Advice')
]
discharge_dispositions = ['HOME','HOME_HEALTH','NURSING_HOME','REHAB_FACILITY','HOSPICE','TRANSFER','DECEASED','LEFT_AMA','OTHER',]
for admission in discharged_admissions:
length_of_stay = (admission.discharge_datetime.date() - admission.admission_datetime.date()).days
@ -582,7 +537,7 @@ def create_saudi_discharge_summaries(admissions):
"Chest pain", "Swelling in legs", "Unusual bleeding"
],
when_to_call="Call physician or return to emergency department if experiencing any warning signs",
discharge_disposition=random.choice(discharge_dispositions)[0],
discharge_disposition=random.choice(discharge_dispositions),
discharge_location="Home" if random.choice([True, True, False]) else "Rehabilitation facility",
transportation_arranged=True,
transportation_method=random.choice(['private_vehicle', 'taxi', 'ambulance', 'hospital_transport']),
@ -617,32 +572,14 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
"""Create surgery schedules for Saudi patients"""
surgeries = []
surgery_types = [
('elective', 'Elective Surgery'),
('emergency', 'Emergency Surgery'),
('urgent', 'Urgent Surgery'),
('add_on', 'Add-on Surgery')
]
surgery_types = ['ELECTIVE','URGENT','EMERGENT','TRAUMA','TRANSPLANT',
'CARDIAC','NEUROSURGERY','ORTHOPEDIC','GENERAL','OTHER',]
anesthesia_types = [
('general', 'General Anesthesia'),
('regional', 'Regional Anesthesia'),
('local', 'Local Anesthesia'),
('mac', 'Monitored Anesthesia Care'),
('spinal', 'Spinal Anesthesia'),
('epidural', 'Epidural Anesthesia')
]
anesthesia_types = ['GENERAL','REGIONAL','LOCAL','SPINAL','EPIDURAL','MAC','SEDATION','OTHER',]
surgery_statuses = [
('scheduled', 'Scheduled'),
('confirmed', 'Confirmed'),
('in_progress', 'In Progress'),
('completed', 'Completed'),
('cancelled', 'Cancelled'),
('delayed', 'Delayed')
]
surgery_statuses = ['SCHEDULED','CONFIRMED','PREP','IN_PROGRESS','COMPLETED','CANCELLED','POSTPONED','DELAYED',]
priorities = ['routine', 'urgent', 'emergent', 'elective']
priorities = ['ROUTINE','URGENT','EMERGENT','STAT',]
for tenant in tenants:
# Get required staff and patients
@ -674,9 +611,9 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
actual_end_time = None
actual_duration = None
if status in ['in_progress', 'completed']:
if status in ['IN_PROGRESS', 'COMPLETED']:
actual_start_time = datetime.combine(surgery_date, start_time)
if status == 'completed':
if status == 'COMPLETED':
actual_duration = estimated_duration + random.randint(-30, 60)
actual_end_time = actual_start_time + timedelta(minutes=actual_duration)
@ -687,7 +624,7 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
admission=admission, # Always provide admission - required field
procedure_name=procedure,
procedure_code=f"CPT-{random.randint(10000, 99999)}",
surgery_type=random.choice(surgery_types)[0],
surgery_type=random.choice(surgery_types),
scheduled_date=surgery_date,
scheduled_start_time=start_time,
estimated_duration_minutes=estimated_duration,
@ -697,7 +634,7 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
anesthesiologist=random.choice(anesthesiologists),
scrub_nurse=random.choice(nurses),
circulating_nurse=random.choice(nurses),
anesthesia_type=random.choice(anesthesia_types)[0],
anesthesia_type=random.choice(anesthesia_types),
preop_diagnosis=random.choice(SAUDI_COMMON_DIAGNOSES),
preop_orders=[
'NPO after midnight', 'Pre-op antibiotics', 'Type and crossmatch',
@ -713,13 +650,13 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
actual_start_time=actual_start_time,
actual_end_time=actual_end_time,
actual_duration_minutes=actual_duration,
postop_diagnosis=random.choice(SAUDI_COMMON_DIAGNOSES) if status == 'completed' else None,
procedure_performed=procedure if status == 'completed' else None,
complications="None" if status == 'completed' and random.choice([True, True, False]) else None,
postop_diagnosis=random.choice(SAUDI_COMMON_DIAGNOSES) if status == 'COMPLETED' else None,
procedure_performed=procedure if status == 'COMPLETED' else None,
complications="None" if status == 'COMPLETED' and random.choice([True, True, False]) else None,
recovery_location=random.choice(
['PACU', 'ICU', 'Ward', 'Same Day Surgery']) if status == 'completed' else None,
['PACU', 'ICU', 'WARD', 'SAME_DAY']) if status == 'COMPLETED' else None,
priority=random.choice(priorities),
surgery_notes=f"Successful {procedure.lower()} performed without complications" if status == 'completed' else None,
surgery_notes=f"Successful {procedure.lower()} performed without complications" if status == 'COMPLETED' else None,
created_at=django_timezone.now() - timedelta(days=random.randint(1, 30)),
updated_at=django_timezone.now() - timedelta(hours=random.randint(1, 24))
)

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@ SAUDI_STATES = [
'Jazan Province', 'Madinah Province', 'Qassim Province', 'Tabuk Province'
]
SAUDI_LANGUAGES = ['Arabic', 'English', 'Urdu', 'Hindi', 'Bengali', 'Tagalog']
SAUDI_LANGUAGES = ['Arabic', 'English']
SAUDI_ALLERGIES = [
'Penicillin', 'Aspirin', 'Ibuprofen', 'Sulfa drugs', 'Contrast dye',
@ -110,7 +110,7 @@ def create_saudi_patient_profiles():
date_of_birth = date(birth_year, birth_month, birth_day)
# Generate unique MRN
mrn = f"MRN{tenant.id}{random.randint(100000, 999999)}"
mrn = random.randint(100000, 999999)
# Check if MRN already exists
while PatientProfile.objects.filter(mrn=mrn).exists():

BIN
templates/.DS_Store vendored

Binary file not shown.

View File

@ -18,10 +18,18 @@
<!-- ================== BEGIN core-css ================== -->
<link href="{% static 'css/vendor.min.css' %}" rel="stylesheet" />
{# <link href="{% static 'css/default/app.min.css' %}" rel="stylesheet" />#}
<link href="{% static 'css/apple/app.min.css' %}" rel="stylesheet" />
<link href="{% static 'css/default/app.min.css' %}" rel="stylesheet" />
{# <link href="{% static 'css/apple/app.min.css' %}" rel="stylesheet" />#}
{# <link href="{% static 'css/transparent/app.min.css' %}" rel="stylesheet" />#}
<link href="{% static 'css/custom.css' %}" rel="stylesheet" />
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet"/>
</head>
<body>
<!-- Your page content -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
</body>
<script src="{% static 'plugins/apexcharts/dist/apexcharts.min.js' %}"></script>
<!-- HTMX -->
<script src="{% static 'js/htmx.min.js' %}"></script>

View File

@ -261,22 +261,31 @@
</div>
<div>
<div class="fw-semibold">{{ admission.patient.get_full_name }}</div>
<small class="text-muted">{{ admission.patient.patient_id }}</small>
<small class="text-muted">{{ admission.patient.mrn }}</small>
</div>
</div>
</td>
<td>
<div>{{ admission.ward.name }}</div>
<small class="text-muted">Bed {{ admission.bed.bed_number }}</small>
<div>{{ admission.current_ward.name }}</div>
<small class="text-muted">Bed {{ admission.current_bed.bed_number }}</small>
</td>
<td>
<div>{{ admission.admission_date|date:"M d, Y" }}</div>
<small class="text-muted">{{ admission.admission_time|time:"H:i" }}</small>
<div>{{ admission.admission_datetime|date:"M d, Y" }}</div>
<small class="text-muted ">{{ admission.admission_datetime|time:"H:i" }}</small>
</td>
<td>
<span class="badge bg-{% if admission.admission_type == 'EMERGENCY' %}danger{% elif admission.admission_type == 'ELECTIVE' %}primary{% elif admission.admission_type == 'TRANSFER' %}info{% else %}secondary{% endif %}">
{{ admission.get_admission_type_display }}
</span>
<div class="badge
bg-{% if admission.admission_type == 'EMERGENCY' %}danger
{% elif admission.admission_type == 'ELECTIVE' %}primary
{% elif admission.admission_type == 'TRANSFER' %}info
{% elif admission.admission_type == 'URGENT' %}yellow
{% elif admission.admission_type == 'OBSERVATION' %}indego
{% elif admission.admission_type == 'DAY_SURGERY' %}purple
{% elif admission.admission_type == 'DIRECT' %}red
{% else %}secondary
{% endif %}">
{{ admission.admission_type }}
</div>
</td>
<td>
{% if admission.attending_physician %}
@ -315,9 +324,10 @@
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'inpatients:admission_edit' admission.pk %}">
<li><a class="dropdown-item" href="{% url 'inpatients:admission_update' admission.pk %}">
<i class="fas fa-edit me-2"></i>Edit
</a></li>
{% if admission.status == 'ADMITTED' %}
@ -365,48 +375,7 @@
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="text-muted">
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} admissions
</span>
</div>
<nav aria-label="Admissions pagination">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% include 'partial/pagination.html'%}
{% endif %}
</div>
</div>

View File

@ -15,11 +15,12 @@
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'inpatients:bed_form' object.pk %}" class="btn btn-primary">
<a href="{% url 'inpatients:bed_update' object.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-2"></i>Edit Bed
</a>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('available')">
@ -35,9 +36,9 @@
<i class="fas fa-broom text-primary me-2"></i>Mark for Cleaning
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="{% url 'inpatients:bed_confirm_delete' object.pk %}">
<i class="fas fa-trash me-2"></i>Delete Bed
</a></li>
{# <li><a class="dropdown-item text-danger" href="{% url 'inpatients:bed_confirm_delete' object.pk %}">#}
{# <i class="fas fa-trash me-2"></i>Delete Bed#}
{# </a></li>#}
</ul>
</div>
</div>

View File

@ -45,6 +45,15 @@
border-color: #b6effb;
}
.bed-reserved {
background-color: #fff3cd;
color: #856404;
}
.bed-out_of_order {
background-color: #b6b4b4;
color: #434242;
}
.bed-status-available {
color: #198754;
}
@ -65,6 +74,40 @@
color: #0dcaf0;
}
.bed-status-reserved {
color: #ffc107;
}
.bed-status-out_of_order {
color: #6c757d;
}
.bg-available {
background-color: #198754;
}
.bg-occupied {
background-color: #dc3545;
}
.bg-maintenance {
background-color: #fd7e14;
}
.bg-blocked {
background-color: #6c757d;
}
.bg-cleaning {
background-color: #0dcaf0;
}
.bg-reserved {
background-color: #ffc107;
}
.bg-out_of_order {
background-color: #6c757d;
}
.bed-icon {
position: relative;
}
@ -257,13 +300,13 @@
<div class="progress-bar bg-info"
role="progressbar"
style="width: {{ occupancy_rate }}%"
title="{{ occupancy_rate }}% occupancy">
{{ occupancy_rate }}%
title="{{ occupancy_rate|floatformat:0 }}% occupancy">
{{ occupancy_rate|floatformat:0}}%
</div>
</div>
</div>
<div class="ms-3">
<h4 class="mb-0">{{ occupancy_rate }}%</h4>
<h4 class="mb-0">{{ occupancy_rate|floatformat:0 }}%</h4>
</div>
</div>
<div class="mt-2">
@ -278,17 +321,18 @@
<h5 class="card-title mb-0">Ward Distribution</h5>
</div>
<div class="card-body">
{% for ward in ward_stats %}
{% for ward in wards %}
<div class="d-flex justify-content-between align-items-center mb-2">
<span>{{ ward.name }}</span>
<div class="d-flex align-items-center">
<div class="progress me-2" style="width: 100px; height: 8px;">
<div class="progress-bar bg-{{ ward.occupancy_color }}"
<div class="progress-bar
bg-{%if ward.occupancy_rate >= 90%}red{% elif ward.occupancy_rate >= 75 %}orange{% elif ward.occupancy_rate >= 50 %}blue{% else %}green{% endif %}"
role="progressbar"
style="width: {{ ward.occupancy_rate }}%">
</div>
</div>
<span class="text-muted">{{ ward.occupied }}/{{ ward.total }}</span>
<span class="text-muted">{{ ward.occupancy_rate|floatformat:0 }}%</span>
</div>
</div>
{% endfor %}
@ -332,23 +376,23 @@
<div class="col-md-3">
<select class="form-select" id="statusFilter">
<option value="">All Status</option>
<option value="available">Available</option>
<option value="occupied">Occupied</option>
<option value="maintenance">Maintenance</option>
<option value="blocked">Blocked</option>
<option value="cleaning">Cleaning</option>
</select>
</div>
<div class="col-md-3">
<select class="form-select" id="bedTypeFilter">
<option value="">All Types</option>
<option value="standard">Standard</option>
<option value="icu">ICU</option>
<option value="isolation">Isolation</option>
<option value="pediatric">Pediatric</option>
<option value="maternity">Maternity</option>
<option value="AVAILABLE">Available</option>
<option value="OCCUPIED">Occupied</option>
<option value="MAINTENANCE">Maintenance</option>
<option value="BLOCKED">Blocked</option>
<option value="CLEANING">Cleaning</option>
</select>
</div>
{# <div class="col-md-3">#}
{# <select class="form-select" id="bedTypeFilter">#}
{# <option value="">All Types</option>#}
{# <option value="standard">Standard</option>#}
{# <option value="icu">ICU</option>#}
{# <option value="isolation">Isolation</option>#}
{# <option value="pediatric">Pediatric</option>#}
{# <option value="maternity">Maternity</option>#}
{# </select>#}
{# </div>#}
<div class="col-md-3">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search beds..." id="bedSearch">
@ -370,29 +414,79 @@
<div class="row">
{% for bed in ward.beds.all %}
<div class="col-md-2 col-sm-3 col-4 mb-3">
<div class="bed-card card h-100 bed-{{ bed.status }}"
<div class="bed-card card h-100 bed-{{ bed.status|lower }}"
data-bed-id="{{ bed.id }}"
data-status="{{ bed.status }}"
data-status="{{ bed.status|lower }}"
data-type="{{ bed.bed_type }}"
onclick="selectBed('{{ bed.id }}')">
<div class="card-body p-2 text-center">
<div class="bed-icon mb-2">
<i class="fa fa-bed fa-2x bed-status-{{ bed.status }}"></i>
<i class="fa fa-bed fa-2x bed-status-{{ bed.status|lower }}"></i>
</div>
<h6 class="card-title mb-1">{{ bed.bed_number }}</h6>
<small class="text-muted">{{ bed.room_number }}</small>
{% if bed.current_patient %}
{% if bed.current_admission %}
<div class="mt-2">
<small class="text-truncate d-block">{{ bed.current_patient.get_full_name }}</small>
<small class="text-muted">{{ bed.occupied_since|timesince }} ago</small>
</div>
{% endif %}
<div class="bed-status-badge">
<span class="badge bg-{{ bed.status_color }} badge-sm">
<span class="badge bg-{{ bed.status|lower }} badge-sm">
{{ bed.get_status_display }}
</span>
</div>
</div>
<div class="card-footer">
<div class="btn-group btn-group-sm">
<a class="btn btn-sm btn-primary" href="{% url 'inpatients:bed_detail' bed.id %}" title="View Details">
<i class="fa fa-eye"></i>
</a>
{# <button class="btn btn-outline-primary" onclick="viewBedDetails('{{ bed.id }}')" title="View Details">#}
{# <i class="fa fa-eye"></i>#}
{# </button>#}
<a class="btn btn-sm btn-secondary" href="{% url 'inpatients:bed_update' bed.id %}" title="Edit">
<i class="fa fa-edit"></i>
</a>
{% if bed.status == 'AVAILABLE' %}
<a class="btn btn-sm btn-success" href="{% url 'inpatients:admission_create' %}" title="Assign Patient">
<i class="fa fa-user-plus"></i>
</a>
{% elif bed.status == 'OCCUPIED' %}
{% if bed.current_admission %}
<a class="btn btn-sm btn-warning" href="{% url 'inpatients:discharge_patient' bed.current_admission.id %}" title="Discharge">
<i class="fa fa-sign-out-alt"></i>
</a>
{% endif %}
{# <button class="btn btn-outline-warning" onclick="dischargePatient('{{ bed.current_admission.id }}')" title="Discharge">#}
{# <i class="fa fa-sign-out-alt"></i>#}
{# </button>#}
{% endif %}
<div class="btn-group btn-group-sm">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" title="More Actions">
<i class="fa fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'inpatients:maintenance_bed' bed.id %}">
<i class="fa fa-tools me-2"></i>Schedule Maintenance
</a></li>
<li><a class="dropdown-item" href="#" onclick="scheduleCleaning('{{ bed.id }}')">
<i class="fa fa-broom me-2"></i>Schedule Cleaning
</a></li>
<li><a class="dropdown-item" href="#" onclick="blockBed('{{ bed.id }}')">
<i class="fa fa-ban me-2"></i>Block Bed
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="viewHistory('{{ bed.id }}')">
<i class="fa fa-history me-2"></i>View History
</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
@ -418,14 +512,14 @@
</tr>
</thead>
<tbody>
{% for bed in all_beds %}
{% for bed in beds %}
<tr data-bed-id="{{ bed.id }}"
data-status="{{ bed.status }}"
data-status="{{ bed.status|lower }}"
data-type="{{ bed.bed_type }}"
data-ward="{{ bed.ward.id }}">
<td>
<div class="d-flex align-items-center">
<i class="fa fa-bed me-2 bed-status-{{ bed.status }}"></i>
<i class="fa fa-bed me-2 bed-{{ bed.status|lower }}"></i>
<strong>{{ bed.bed_number }}</strong>
</div>
</td>
@ -435,15 +529,15 @@
<span class="badge bg-secondary">{{ bed.get_bed_type_display }}</span>
</td>
<td>
<span class="badge bg-{{ bed.status_color }}">
<span class="badge bg-{{ bed.status|lower }}">
{{ bed.get_status_display }}
</span>
</td>
<td>
{% if bed.current_patient %}
{% if bed.current_admission %}
<div>
<strong>{{ bed.current_patient.get_full_name }}</strong>
<br><small class="text-muted">{{ bed.current_patient.patient_id }}</small>
<br><small class="text-muted">{{ bed.current_patient.mrn }}</small>
</div>
{% else %}
<span class="text-muted">-</span>
@ -461,20 +555,28 @@
</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="viewBedDetails('{{ bed.id }}')" title="View Details">
<a class="btn btn-outline-primary" href="{% url 'inpatients:bed_detail' bed.id %}" title="View Details">
<i class="fa fa-eye"></i>
</button>
</a>
{# <button class="btn btn-outline-primary" onclick="viewBedDetails('{{ bed.id }}')" title="View Details">#}
{# <i class="fa fa-eye"></i>#}
{# </button>#}
<button class="btn btn-outline-secondary" onclick="editBed('{{ bed.id }}')" title="Edit">
<i class="fa fa-edit"></i>
</button>
{% if bed.status == 'available' %}
{% if bed.status == 'AVAILABLE' %}
<button class="btn btn-outline-success" onclick="assignPatient('{{ bed.id }}')" title="Assign Patient">
<i class="fa fa-user-plus"></i>
</button>
{% elif bed.status == 'occupied' %}
<button class="btn btn-outline-warning" onclick="dischargePatient('{{ bed.id }}')" title="Discharge">
{% elif bed.status == 'OCCUPIED' %}
{% if bed.current_admission %}
<a class="btn btn-outline-warning" href="{% url 'inpatients:discharge_patient' bed.current_admission.id %}" title="Discharge">
<i class="fa fa-sign-out-alt"></i>
</button>
</a>
{% endif %}
{# <button class="btn btn-outline-warning" onclick="dischargePatient('{{ bed.current_admission.id }}')" title="Discharge">#}
{# <i class="fa fa-sign-out-alt"></i>#}
{# </button>#}
{% endif %}
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info dropdown-toggle" data-bs-toggle="dropdown" title="More Actions">
@ -502,6 +604,11 @@
{% endfor %}
</tbody>
</table>
<div class="text-center">
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>
</div>
</div>
@ -582,6 +689,7 @@
{% endblock %}
{% block js %}
<script src="{% static 'plugins/dropzone/dist/min/dropzone.min.js' %}"></script>
<script>
$(document).ready(function() {
setupEventHandlers();
@ -696,21 +804,21 @@ function assignPatientToBed() {
});
}
function dischargePatient(bedId) {
if (confirm('Are you sure you want to discharge the patient from this bed?')) {
$.post('', {
bed_id: bedId,
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Patient discharged successfully');
updateBedStatuses();
} else {
toastr.error('Failed to discharge patient: ' + data.error);
}
});
}
}
{#function dischargePatient(admissionId) {#}
{# if (confirm('Are you sure you want to discharge the patient from this bed?')) {#}
{# $.post('{% url 'inpatients:discharge_patient' 0 %}'.replace('0', admissionId), {#}
{#bed_id: bedId,#}
{# csrfmiddlewaretoken: '{{ csrf_token }}'#}
{# }, function(data) {#}
{# if (data.success) {#}
{# toastr.success('Patient discharged successfully');#}
{# updateBedStatuses();#}
{# } else {#}
{# toastr.error('Failed to discharge patient: ' + data.error);#}
{# }#}
{# });#}
{# }#}
{# }#}
function scheduleMaintenance(bedId) {
$.post('', {
@ -792,8 +900,8 @@ function updateBedStatuses() {
.addClass('bed-' + bed.status);
bedCard.find('.bed-status-badge .badge')
.removeClass('bg-success bg-danger bg-warning bg-secondary bg-info')
.addClass('bg-' + bed.status_color)
.text(bed.status_display);
.addClass('bg-' + bed.status)
.text(bed.status);
// Update row
bedRow.find('.badge').first()

View File

@ -297,7 +297,7 @@
<small class="text-muted">{{ ward.get_ward_type_display }}</small>
</div>
<div class="text-end">
<span class="badge bg-primary">{{ ward.occupied_beds }}/{{ ward.total_beds }}</span>
<span class="badge bg-primary">{{ ward.o }}/{{ ward.total_beds }}</span>
<br><small class="text-muted">{{ ward.occupancy_rate|floatformat:0 }}%</small>
</div>
</div>

View File

@ -0,0 +1,670 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Bed Maintenance Management{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.maintenance-status-scheduled { color: #ffc107; }
.maintenance-status-in-progress { color: #17a2b8; }
.maintenance-status-completed { color: #28a745; }
.maintenance-status-cancelled { color: #6c757d; }
.maintenance-status-overdue { color: #dc3545; }
.priority-low { color: #28a745; }
.priority-medium { color: #ffc107; }
.priority-high { color: #fd7e14; }
.priority-critical { color: #dc3545; }
.bed-status-available { background-color: #d4edda; color: #155724; }
.bed-status-occupied { background-color: #f8d7da; color: #721c24; }
.bed-status-maintenance { background-color: #fff3cd; color: #856404; }
.bed-status-out-of-order { background-color: #f5c6cb; color: #721c24; }
.maintenance-card {
transition: all 0.3s ease;
border-left: 4px solid #dee2e6;
}
.maintenance-card.scheduled { border-left-color: #ffc107; }
.maintenance-card.in-progress { border-left-color: #17a2b8; }
.maintenance-card.completed { border-left-color: #28a745; }
.maintenance-card.overdue { border-left-color: #dc3545; }
.maintenance-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
}
.stats-card.warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stats-card.success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stats-card.info {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
@media (max-width: 768px) {
.maintenance-card {
margin-bottom: 15px;
}
.table-responsive {
font-size: 0.875rem;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3">
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li>
<li class="breadcrumb-item active">Bed Maintenance</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-tools me-2"></i>Bed Maintenance Management
</h1>
</div>
<div class="ms-auto">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#scheduleMaintenanceModal">
<i class="fas fa-plus me-1"></i>Schedule Maintenance
</button>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6">
<div class="card stats-card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-white-75 mb-1">Total Beds</div>
<div class="h3 mb-0 text-white">{{ stats.total_beds|default:0 }}</div>
</div>
<div class="ms-3">
<i class="fas fa-bed fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card stats-card warning">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-white-75 mb-1">Under Maintenance</div>
<div class="h3 mb-0 text-white">{{ stats.under_maintenance|default:0 }}</div>
</div>
<div class="ms-3">
<i class="fas fa-wrench fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card stats-card success">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-white-75 mb-1">Scheduled Today</div>
<div class="h3 mb-0 text-white">{{ stats.scheduled_today|default:0 }}</div>
</div>
<div class="ms-3">
<i class="fas fa-calendar-check fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card stats-card info">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-white-75 mb-1">Overdue</div>
<div class="h3 mb-0 text-white">{{ stats.overdue|default:0 }}</div>
</div>
<div class="ms-3">
<i class="fas fa-exclamation-triangle fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filters and Search -->
<div class="card mb-4">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Ward/Unit</label>
<select class="form-select" id="ward-filter">
<option value="">All Wards</option>
{% for ward in wards %}
<option value="{{ ward.id }}">{{ ward.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Maintenance Status</label>
<select class="form-select" id="status-filter">
<option value="">All Statuses</option>
<option value="scheduled">Scheduled</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
<option value="overdue">Overdue</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Priority</label>
<select class="form-select" id="priority-filter">
<option value="">All Priorities</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Date Range</label>
<select class="form-select" id="date-filter">
<option value="">All Dates</option>
<option value="today">Today</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="overdue">Overdue</option>
</select>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="search-input" placeholder="Search beds, rooms, or maintenance notes...">
</div>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-outline-secondary me-2" onclick="clearFilters()">
<i class="fas fa-times me-1"></i>Clear Filters
</button>
<button type="button" class="btn btn-success" onclick="exportMaintenanceReport()">
<i class="fas fa-file-excel me-1"></i>Export Report
</button>
</div>
</div>
</div>
</div>
<!-- Maintenance Schedule Table -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-list me-2"></i>Maintenance Schedule
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="maintenanceTable" class="table table-striped table-hover">
<thead>
<tr>
<th>Bed</th>
<th>Ward/Room</th>
<th>Maintenance Type</th>
<th>Priority</th>
<th>Scheduled Date</th>
<th>Status</th>
<th>Assigned To</th>
<th>Progress</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for maintenance in maintenance_schedules %}
<tr>
<td>
<div class="d-flex align-items-center">
<i class="fas fa-bed me-2 text-muted"></i>
<div>
<div class="fw-bold">{{ maintenance.bed.bed_number }}</div>
<small class="text-muted">{{ maintenance.bed.bed_type }}</small>
</div>
</div>
</td>
<td>
<div>
<div class="fw-bold">{{ maintenance.bed.ward.name }}</div>
<small class="text-muted">Room {{ maintenance.bed.room_number }}</small>
</div>
</td>
<td>
<span class="badge bg-info">{{ maintenance.get_maintenance_type_display }}</span>
</td>
<td>
<span class="badge priority-{{ maintenance.priority }}">
{{ maintenance.get_priority_display }}
</span>
</td>
<td>
<div>
<div>{{ maintenance.scheduled_date|date:"M d, Y" }}</div>
<small class="text-muted">{{ maintenance.scheduled_time|time:"H:i" }}</small>
</div>
</td>
<td>
<span class="badge maintenance-status-{{ maintenance.status }}">
{{ maintenance.get_status_display }}
</span>
</td>
<td>
{% if maintenance.assigned_to %}
<div>
<div class="fw-bold">{{ maintenance.assigned_to.get_full_name }}</div>
<small class="text-muted">{{ maintenance.assigned_to.department }}</small>
</div>
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</td>
<td>
<div class="progress" style="height: 6px;">
<div class="progress-bar" role="progressbar"
style="width: {{ maintenance.progress_percentage }}%"
aria-valuenow="{{ maintenance.progress_percentage }}"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">{{ maintenance.progress_percentage }}%</small>
</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="viewMaintenance('{{ maintenance.id }}')">
<i class="fas fa-eye"></i>
</button>
{% if maintenance.status == 'scheduled' %}
<button type="button" class="btn btn-sm btn-outline-success"
onclick="startMaintenance('{{ maintenance.id }}')">
<i class="fas fa-play"></i>
</button>
{% endif %}
{% if maintenance.status == 'in_progress' %}
<button type="button" class="btn btn-sm btn-outline-warning"
onclick="updateProgress('{{ maintenance.id }}')">
<i class="fas fa-edit"></i>
</button>
{% endif %}
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="editMaintenance('{{ maintenance.id }}')">
<i class="fas fa-edit"></i>
</button>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center py-4">
<div class="text-muted">
<i class="fas fa-tools fa-3x mb-3"></i>
<p>No maintenance schedules found.</p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#scheduleMaintenanceModal">
<i class="fas fa-plus me-1"></i>Schedule First Maintenance
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Schedule Maintenance Modal -->
<div class="modal fade" id="scheduleMaintenanceModal" tabindex="-1" aria-labelledby="scheduleMaintenanceModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="scheduleMaintenanceModalLabel">
<i class="fas fa-calendar-plus me-2"></i>Schedule Bed Maintenance
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="scheduleMaintenanceForm" method="post">
{% csrf_token %}
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Ward <span class="text-danger">*</span></label>
<select class="form-select" name="ward" id="ward-select" required>
<option value="">Select ward...</option>
{% for ward in wards %}
<option value="{{ ward.id }}">{{ ward.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Bed <span class="text-danger">*</span></label>
<select class="form-select" name="bed" id="bed-select" required>
<option value="">Select bed...</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Maintenance Type <span class="text-danger">*</span></label>
<select class="form-select" name="maintenance_type" required>
<option value="">Select type...</option>
<option value="routine">Routine Inspection</option>
<option value="cleaning">Deep Cleaning</option>
<option value="repair">Repair</option>
<option value="replacement">Part Replacement</option>
<option value="calibration">Equipment Calibration</option>
<option value="safety_check">Safety Check</option>
<option value="preventive">Preventive Maintenance</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Priority <span class="text-danger">*</span></label>
<select class="form-select" name="priority" required>
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Scheduled Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" name="scheduled_date" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Scheduled Time <span class="text-danger">*</span></label>
<input type="time" class="form-control" name="scheduled_time" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Assign To</label>
<select class="form-select" name="assigned_to">
<option value="">Select technician...</option>
{% for technician in maintenance_staff %}
<option value="{{ technician.id }}">{{ technician.get_full_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Estimated Duration (hours)</label>
<input type="number" class="form-control" name="estimated_duration"
min="0.5" step="0.5" placeholder="2.0">
</div>
</div>
</div>
<div class="form-group mb-3">
<label class="form-label">Description/Notes</label>
<textarea class="form-control" name="description" rows="3"
placeholder="Describe the maintenance work to be performed..."></textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_staff" id="notify-staff" checked>
<label class="form-check-label" for="notify-staff">
Notify assigned staff member
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>Schedule Maintenance
</button>
</div>
</form>
</div>
</div>
</div>
<!-- View Maintenance Modal -->
<div class="modal fade" id="viewMaintenanceModal" tabindex="-1" aria-labelledby="viewMaintenanceModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewMaintenanceModalLabel">
<i class="fas fa-eye me-2"></i>Maintenance Details
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="maintenance-details">
<!-- Content loaded via AJAX -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Close
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#maintenanceTable').DataTable({
responsive: true,
pageLength: 25,
order: [[4, 'asc']], // Sort by scheduled date
columnDefs: [
{ orderable: false, targets: [8] } // Actions column
]
});
// Filter functionality
$('#ward-filter, #status-filter, #priority-filter, #date-filter').on('change', function() {
applyFilters();
});
$('#search-input').on('keyup', function() {
table.search(this.value).draw();
});
// Ward selection for bed loading
$('#ward-select').on('change', function() {
const wardId = $(this).val();
const bedSelect = $('#bed-select');
bedSelect.empty().append('<option value="">Loading beds...</option>');
if (wardId) {
$.ajax({
url: '{% url "inpatients:get_ward_beds" %}',
data: { ward_id: wardId },
success: function(data) {
bedSelect.empty().append('<option value="">Select bed...</option>');
data.beds.forEach(function(bed) {
bedSelect.append(`<option value="${bed.id}">${bed.bed_number} - ${bed.bed_type}</option>`);
});
},
error: function() {
bedSelect.empty().append('<option value="">Error loading beds</option>');
}
});
} else {
bedSelect.empty().append('<option value="">Select bed...</option>');
}
});
// Form submission
$('#scheduleMaintenanceForm').on('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
$.ajax({
url: '{% url "inpatients:schedule_maintenance" %}',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
$('#scheduleMaintenanceModal').modal('hide');
location.reload(); // Refresh page to show new maintenance
} else {
alert('Error scheduling maintenance: ' + response.message);
}
},
error: function() {
alert('Error scheduling maintenance. Please try again.');
}
});
});
});
function applyFilters() {
const ward = $('#ward-filter').val();
const status = $('#status-filter').val();
const priority = $('#priority-filter').val();
const dateRange = $('#date-filter').val();
// Apply filters to DataTable
const table = $('#maintenanceTable').DataTable();
// Custom filtering logic would go here
// For now, we'll just trigger a page refresh with query parameters
const params = new URLSearchParams();
if (ward) params.append('ward', ward);
if (status) params.append('status', status);
if (priority) params.append('priority', priority);
if (dateRange) params.append('date_range', dateRange);
const url = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
window.location.href = url;
}
function clearFilters() {
$('#ward-filter, #status-filter, #priority-filter, #date-filter').val('');
$('#search-input').val('');
window.location.href = window.location.pathname;
}
function viewMaintenance(maintenanceId) {
$.ajax({
url: '{% url "inpatients:maintenance_detail" pk="0" %}'.replace('0', maintenanceId),
success: function(data) {
$('#maintenance-details').html(data);
$('#viewMaintenanceModal').modal('show');
},
error: function() {
alert('Error loading maintenance details.');
}
});
}
function startMaintenance(maintenanceId) {
if (confirm('Start this maintenance task?')) {
$.ajax({
url: '{% url "inpatients:start_maintenance" pk="0" %}'.replace('0', maintenanceId),
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error starting maintenance: ' + response.message);
}
},
error: function() {
alert('Error starting maintenance.');
}
});
}
}
function updateProgress(maintenanceId) {
const progress = prompt('Enter progress percentage (0-100):');
if (progress !== null && progress >= 0 && progress <= 100) {
$.ajax({
url: '{% url "inpatients:update_maintenance_progress" pk="0" %}'.replace('0', maintenanceId),
method: 'POST',
data: {
'progress': progress,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error updating progress: ' + response.message);
}
},
error: function() {
alert('Error updating progress.');
}
});
}
}
function editMaintenance(maintenanceId) {
window.location.href = '{% url "inpatients:edit_maintenance" pk="0" %}'.replace('0', maintenanceId);
}
function exportMaintenanceReport() {
const params = new URLSearchParams(window.location.search);
params.append('export', 'excel');
window.location.href = '{% url "inpatients:maintenance_report" %}?' + params.toString();
}
</script>
{% endblock %}