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,12 +11,54 @@ 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.
"""
WARD_TYPE_CHOICES = [
('GENERAL', 'General Medical'),
('SURGICAL', 'Surgical'),
('ICU', 'Intensive Care Unit'),
('CCU', 'Cardiac Care Unit'),
('NICU', 'Neonatal ICU'),
('PICU', 'Pediatric ICU'),
('EMERGENCY', 'Emergency'),
('MATERNITY', 'Maternity'),
('PEDIATRIC', 'Pediatric'),
('ONCOLOGY', 'Oncology'),
('CARDIAC', 'Cardiac'),
('ORTHOPEDIC', 'Orthopedic'),
('NEUROLOGY', 'Neurology'),
('PSYCHIATRY', 'Psychiatry'),
('REHABILITATION', 'Rehabilitation'),
('ISOLATION', 'Isolation'),
('STEP_DOWN', 'Step Down'),
('OTHER', 'Other'),
]
SPECIALITY_CHOICES = [
('GENERAL_MEDICINE', 'General Medicine'),
('SURGERY', 'Surgery'),
('CARDIOLOGY', 'Cardiology'),
('NEUROLOGY', 'Neurology'),
('ONCOLOGY', 'Oncology'),
('PEDIATRICS', 'Pediatrics'),
('OBSTETRICS', 'Obstetrics'),
('GYNECOLOGY', 'Gynecology'),
('ORTHOPEDICS', 'Orthopedics'),
('PSYCHIATRY', 'Psychiatry'),
('EMERGENCY', 'Emergency Medicine'),
('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',
@ -27,7 +69,7 @@ class Ward(models.Model):
# Ward Information
ward_id = models.CharField(
max_length=20,
max_length=50,
help_text='Unique ward identifier'
)
name = models.CharField(
@ -43,47 +85,13 @@ class Ward(models.Model):
# Ward Type and Specialty
ward_type = models.CharField(
max_length=50,
choices=[
('GENERAL', 'General Medical'),
('SURGICAL', 'Surgical'),
('ICU', 'Intensive Care Unit'),
('CCU', 'Cardiac Care Unit'),
('NICU', 'Neonatal ICU'),
('PICU', 'Pediatric ICU'),
('EMERGENCY', 'Emergency'),
('MATERNITY', 'Maternity'),
('PEDIATRIC', 'Pediatric'),
('ONCOLOGY', 'Oncology'),
('CARDIAC', 'Cardiac'),
('ORTHOPEDIC', 'Orthopedic'),
('NEUROLOGY', 'Neurology'),
('PSYCHIATRY', 'Psychiatry'),
('REHABILITATION', 'Rehabilitation'),
('ISOLATION', 'Isolation'),
('STEP_DOWN', 'Step Down'),
('OTHER', 'Other'),
],
choices=WARD_TYPE_CHOICES,
help_text='Type of ward'
)
specialty = models.CharField(
max_length=100,
choices=[
('GENERAL_MEDICINE', 'General Medicine'),
('SURGERY', 'Surgery'),
('CARDIOLOGY', 'Cardiology'),
('NEUROLOGY', 'Neurology'),
('ONCOLOGY', 'Oncology'),
('PEDIATRICS', 'Pediatrics'),
('OBSTETRICS', 'Obstetrics'),
('GYNECOLOGY', 'Gynecology'),
('ORTHOPEDICS', 'Orthopedics'),
('PSYCHIATRY', 'Psychiatry'),
('EMERGENCY', 'Emergency Medicine'),
('CRITICAL_CARE', 'Critical Care'),
('REHABILITATION', 'Rehabilitation'),
('OTHER', 'Other'),
],
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,11 +839,50 @@ 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(
default=uuid.uuid4,
@ -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,16 +68,10 @@ 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:
for i in range(wards_per_tenant):
@ -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,30 +414,80 @@
<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 }}"
data-bed-id="{{ bed.id }}"
data-status="{{ bed.status }}"
<div class="bed-card card h-100 bed-{{ bed.status|lower }}"
data-bed-id="{{ bed.id }}"
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>
<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 %}
</div>
@ -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 %}