diff --git a/.DS_Store b/.DS_Store index a9edf1ea..ff1afbfd 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/appointments/__pycache__/admin.cpython-312.pyc b/appointments/__pycache__/admin.cpython-312.pyc index 647d5ed5..e062354c 100644 Binary files a/appointments/__pycache__/admin.cpython-312.pyc and b/appointments/__pycache__/admin.cpython-312.pyc differ diff --git a/appointments/__pycache__/forms.cpython-312.pyc b/appointments/__pycache__/forms.cpython-312.pyc index f955dbd7..0b8962c8 100644 Binary files a/appointments/__pycache__/forms.cpython-312.pyc and b/appointments/__pycache__/forms.cpython-312.pyc differ diff --git a/appointments/__pycache__/models.cpython-312.pyc b/appointments/__pycache__/models.cpython-312.pyc index 2870cc34..d253311d 100644 Binary files a/appointments/__pycache__/models.cpython-312.pyc and b/appointments/__pycache__/models.cpython-312.pyc differ diff --git a/appointments/__pycache__/urls.cpython-312.pyc b/appointments/__pycache__/urls.cpython-312.pyc index 8f5fee78..a08b1834 100644 Binary files a/appointments/__pycache__/urls.cpython-312.pyc and b/appointments/__pycache__/urls.cpython-312.pyc differ diff --git a/appointments/__pycache__/views.cpython-312.pyc b/appointments/__pycache__/views.cpython-312.pyc index d66e29ff..0c64ffa1 100644 Binary files a/appointments/__pycache__/views.cpython-312.pyc and b/appointments/__pycache__/views.cpython-312.pyc differ diff --git a/appointments/admin.py b/appointments/admin.py index 386132b7..332aa571 100644 --- a/appointments/admin.py +++ b/appointments/admin.py @@ -5,10 +5,7 @@ Admin configuration for appointments app. from django.contrib import admin from django.utils.html import format_html from django.utils import timezone -from .models import ( - AppointmentRequest, SlotAvailability, WaitingQueue, QueueEntry, - TelemedicineSession, AppointmentTemplate -) +from .models import * class QueueEntryInline(admin.TabularInline): @@ -459,3 +456,151 @@ class AppointmentTemplateAdmin(admin.ModelAdmin): 'tenant', 'created_by' ) + +@admin.register(WaitingList) +class WaitingListAdmin(admin.ModelAdmin): + """ + Admin configuration for WaitingList model. + """ + list_display = [ + 'patient', 'specialty', 'priority', 'urgency_score', + 'status', 'position', 'days_waiting', 'last_contacted' + ] + list_filter = [ + 'tenant', 'department', 'specialty', 'priority', 'status', + 'appointment_type', 'authorization_required', 'requires_interpreter' + ] + search_fields = [ + 'patient__first_name', 'patient__last_name', 'patient__mrn', + 'clinical_indication', 'referring_provider' + ] + ordering = ['priority', 'urgency_score', 'created_at'] + + fieldsets = ( + ('Patient Information', { + 'fields': ('tenant', 'patient', 'department', 'provider') + }), + ('Service Request', { + 'fields': ('appointment_type', 'specialty', 'clinical_indication') + }), + ('Priority and Urgency', { + 'fields': ('priority', 'urgency_score', 'diagnosis_codes') + }), + ('Patient Preferences', { + 'fields': ( + 'preferred_date', 'preferred_time', 'flexible_scheduling', + 'earliest_acceptable_date', 'latest_acceptable_date' + ) + }), + ('Contact Information', { + 'fields': ('contact_method', 'contact_phone', 'contact_email') + }), + ('Status and Position', { + 'fields': ('status', 'position', 'estimated_wait_time') + }), + ('Contact History', { + 'fields': ( + 'last_contacted', 'contact_attempts', 'max_contact_attempts', + 'appointments_offered', 'appointments_declined' + ) + }), + ('Requirements', { + 'fields': ( + 'requires_interpreter', 'interpreter_language', + 'accessibility_requirements', 'transportation_needed' + ), + 'classes': ('collapse',) + }), + ('Insurance and Authorization', { + 'fields': ( + 'insurance_verified', 'authorization_required', + 'authorization_status', 'authorization_number' + ), + 'classes': ('collapse',) + }), + ('Referral Information', { + 'fields': ( + 'referring_provider', 'referral_date', 'referral_urgency' + ), + 'classes': ('collapse',) + }), + ('Outcome', { + 'fields': ( + 'scheduled_appointment', 'removal_reason', 'removal_notes', + 'removed_at', 'removed_by' + ), + 'classes': ('collapse',) + }), + ('Notes', { + 'fields': ('notes',) + }), + ('Metadata', { + 'fields': ('waiting_list_id', 'created_at', 'updated_at', 'created_by'), + 'classes': ('collapse',) + }), + ) + + readonly_fields = ['waiting_list_id', 'created_at', 'updated_at', 'days_waiting'] + + def get_queryset(self, request): + return super().get_queryset(request).select_related( + 'tenant', 'patient', 'department', 'provider', 'created_by', + 'removed_by', 'scheduled_appointment' + ) + + def days_waiting(self, obj): + return obj.days_waiting + + days_waiting.short_description = 'Days Waiting' + + +@admin.register(WaitingListContactLog) +class WaitingListContactLogAdmin(admin.ModelAdmin): + """ + Admin configuration for WaitingListContactLog model. + """ + list_display = [ + 'waiting_list_entry', 'contact_date', 'contact_method', + 'contact_outcome', 'appointment_offered', 'patient_response' + ] + list_filter = [ + 'contact_method', 'contact_outcome', 'appointment_offered', + 'patient_response', 'contact_date' + ] + search_fields = [ + 'waiting_list_entry__patient__first_name', + 'waiting_list_entry__patient__last_name', + 'notes' + ] + ordering = ['-contact_date'] + + fieldsets = ( + ('Contact Information', { + 'fields': ('waiting_list_entry', 'contact_method', 'contact_outcome') + }), + ('Appointment Offer', { + 'fields': ( + 'appointment_offered', 'offered_date', 'offered_time', + 'patient_response' + ) + }), + ('Follow-up', { + 'fields': ('next_contact_date',) + }), + ('Notes', { + 'fields': ('notes',) + }), + ('Metadata', { + 'fields': ('contact_date', 'contacted_by'), + 'classes': ('collapse',) + }), + ) + + readonly_fields = ['contact_date'] + + def get_queryset(self, request): + return super().get_queryset(request).select_related( + 'waiting_list_entry__patient', 'contacted_by' + ) + + diff --git a/appointments/forms.py b/appointments/forms.py index d6172ba9..dc0514ee 100644 --- a/appointments/forms.py +++ b/appointments/forms.py @@ -5,15 +5,11 @@ Forms for Appointments app CRUD operations. from django import forms from django.core.exceptions import ValidationError from django.utils import timezone -from datetime import datetime, time, timedelta -from .models import ( - AppointmentRequest, SlotAvailability, WaitingQueue, QueueEntry, - TelemedicineSession, AppointmentTemplate -) +from datetime import datetime, time, timedelta, date +from .models import * from patients.models import PatientProfile from accounts.models import User -from hr.models import Employee - +from hr.models import Employee, Department class AppointmentRequestForm(forms.ModelForm): """ @@ -361,8 +357,6 @@ class AppointmentSearchForm(forms.Form): ).order_by('last_name', 'first_name') - - class QueueSearchForm(forms.Form): """ Form for searching queues. @@ -441,6 +435,437 @@ class SlotSearchForm(forms.Form): ).order_by('last_name', 'first_name') +class WaitingListForm(forms.ModelForm): + """ + Form for creating and updating waiting list entries. + """ + + class Meta: + model = WaitingList + fields = [ + 'patient', 'department', 'provider', 'appointment_type', 'specialty', + 'priority', 'urgency_score', 'clinical_indication', 'diagnosis_codes', + 'preferred_date', 'preferred_time', 'flexible_scheduling', + 'earliest_acceptable_date', 'latest_acceptable_date', + 'acceptable_days', 'acceptable_times', + 'contact_method', 'contact_phone', 'contact_email', + 'requires_interpreter', 'interpreter_language', + 'accessibility_requirements', 'transportation_needed', + 'insurance_verified', 'authorization_required', + 'referring_provider', 'referral_date', 'referral_urgency', + 'notes' + ] + + widgets = { + 'patient': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'department': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'provider': forms.Select(attrs={ + 'class': 'form-select' + }), + 'appointment_type': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'specialty': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'priority': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'urgency_score': forms.NumberInput(attrs={ + 'class': 'form-control', + 'min': 1, + 'max': 10, + 'required': True + }), + 'clinical_indication': forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 4, + 'required': True, + 'placeholder': 'Describe the clinical reason for this appointment request...' + }), + 'diagnosis_codes': forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 2, + 'placeholder': 'Enter ICD-10 codes separated by commas' + }), + 'preferred_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date', + 'min': date.today().isoformat() + }), + 'preferred_time': forms.TimeInput(attrs={ + 'class': 'form-control', + 'type': 'time' + }), + 'flexible_scheduling': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'earliest_acceptable_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date', + 'min': date.today().isoformat() + }), + 'latest_acceptable_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), + 'acceptable_days': forms.CheckboxSelectMultiple(attrs={ + 'class': 'form-check-input' + }), + 'contact_method': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'contact_phone': forms.TextInput(attrs={ + 'class': 'form-control', + 'placeholder': '(555) 123-4567' + }), + 'contact_email': forms.EmailInput(attrs={ + 'class': 'form-control', + 'placeholder': 'patient@example.com' + }), + 'requires_interpreter': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'interpreter_language': forms.TextInput(attrs={ + 'class': 'form-control', + 'placeholder': 'e.g., Spanish, Mandarin, ASL' + }), + 'accessibility_requirements': forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 3, + 'placeholder': 'Describe any accessibility needs...' + }), + 'transportation_needed': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'insurance_verified': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'authorization_required': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'referring_provider': forms.TextInput(attrs={ + 'class': 'form-control', + 'placeholder': 'Dr. Smith, Internal Medicine' + }), + 'referral_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), + 'referral_urgency': forms.Select(attrs={ + 'class': 'form-select' + }), + 'notes': forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 4, + 'placeholder': 'Additional notes and comments...' + }), + } + + def __init__(self, *args, **kwargs): + self.tenant = kwargs.pop('tenant', None) + super().__init__(*args, **kwargs) + + # Filter choices based on tenant + if self.tenant: + self.fields['patient'].queryset = self.fields['patient'].queryset.filter( + tenant=self.tenant + ) + self.fields['department'].queryset = self.fields['department'].queryset.filter( + tenant=self.tenant + ) + if 'provider' in self.fields: + self.fields['provider'].queryset = self.fields['provider'].queryset.filter( + tenant=self.tenant + ) + + def clean(self): + cleaned_data = super().clean() + + # Validate date ranges + preferred_date = cleaned_data.get('preferred_date') + earliest_date = cleaned_data.get('earliest_acceptable_date') + latest_date = cleaned_data.get('latest_acceptable_date') + + if preferred_date and preferred_date < date.today(): + raise ValidationError("Preferred date cannot be in the past.") + + if earliest_date and latest_date and earliest_date > latest_date: + raise ValidationError("Earliest acceptable date must be before latest acceptable date.") + + if preferred_date and earliest_date and preferred_date < earliest_date: + raise ValidationError("Preferred date must be within acceptable date range.") + + if preferred_date and latest_date and preferred_date > latest_date: + raise ValidationError("Preferred date must be within acceptable date range.") + + # Validate contact information + contact_method = cleaned_data.get('contact_method') + contact_phone = cleaned_data.get('contact_phone') + contact_email = cleaned_data.get('contact_email') + + if contact_method == 'PHONE' and not contact_phone: + raise ValidationError("Phone number is required when phone is selected as contact method.") + + if contact_method == 'EMAIL' and not contact_email: + raise ValidationError("Email address is required when email is selected as contact method.") + + # Validate interpreter requirements + requires_interpreter = cleaned_data.get('requires_interpreter') + interpreter_language = cleaned_data.get('interpreter_language') + + if requires_interpreter and not interpreter_language: + raise ValidationError("Interpreter language is required when interpreter services are needed.") + + return cleaned_data + + +class WaitingListContactLogForm(forms.ModelForm): + """ + Form for logging contact attempts with waiting list patients. + """ + + class Meta: + model = WaitingListContactLog + fields = [ + 'contact_method', 'contact_outcome', 'appointment_offered', + 'offered_date', 'offered_time', 'patient_response', + 'notes', 'next_contact_date' + ] + + widgets = { + 'contact_method': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'contact_outcome': forms.Select(attrs={ + 'class': 'form-select', + 'required': True + }), + 'appointment_offered': forms.CheckboxInput(attrs={ + 'class': 'form-check-input' + }), + 'offered_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }), + 'offered_time': forms.TimeInput(attrs={ + 'class': 'form-control', + 'type': 'time' + }), + 'patient_response': forms.Select(attrs={ + 'class': 'form-select' + }), + 'notes': forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 4, + 'placeholder': 'Notes from contact attempt...' + }), + 'next_contact_date': forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date', + 'min': date.today().isoformat() + }), + } + + def clean(self): + cleaned_data = super().clean() + + appointment_offered = cleaned_data.get('appointment_offered') + offered_date = cleaned_data.get('offered_date') + offered_time = cleaned_data.get('offered_time') + patient_response = cleaned_data.get('patient_response') + + if appointment_offered: + if not offered_date: + raise ValidationError("Offered date is required when appointment is offered.") + if not offered_time: + raise ValidationError("Offered time is required when appointment is offered.") + if not patient_response: + raise ValidationError("Patient response is required when appointment is offered.") + + next_contact_date = cleaned_data.get('next_contact_date') + if next_contact_date and next_contact_date < date.today(): + raise ValidationError("Next contact date cannot be in the past.") + + return cleaned_data + + +class WaitingListFilterForm(forms.Form): + """ + Form for filtering waiting list entries. + """ + + PRIORITY_CHOICES = [ + ('', 'All Priorities'), + ('EMERGENCY', 'Emergency'), + ('STAT', 'STAT'), + ('URGENT', 'Urgent'), + ('ROUTINE', 'Routine'), + ] + + STATUS_CHOICES = [ + ('', 'All Status'), + ('ACTIVE', 'Active'), + ('CONTACTED', 'Contacted'), + ('OFFERED', 'Appointment Offered'), + ('SCHEDULED', 'Scheduled'), + ('CANCELLED', 'Cancelled'), + ('EXPIRED', 'Expired'), + ] + + department = forms.ModelChoiceField( + queryset=None, + required=False, + empty_label="All Departments", + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + specialty = forms.ChoiceField( + choices=[('', 'All Specialties')] + WaitingList._meta.get_field('specialty').choices, + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + priority = forms.ChoiceField( + choices=PRIORITY_CHOICES, + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + status = forms.ChoiceField( + choices=STATUS_CHOICES, + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + provider = forms.ModelChoiceField( + queryset=None, + required=False, + empty_label="All Providers", + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + date_from = forms.DateField( + required=False, + widget=forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }) + ) + + date_to = forms.DateField( + required=False, + widget=forms.DateInput(attrs={ + 'class': 'form-control', + 'type': 'date' + }) + ) + + urgency_min = forms.IntegerField( + required=False, + min_value=1, + max_value=10, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'placeholder': 'Min urgency (1-10)' + }) + ) + + urgency_max = forms.IntegerField( + required=False, + min_value=1, + max_value=10, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'placeholder': 'Max urgency (1-10)' + }) + ) + + def __init__(self, *args, **kwargs): + tenant = kwargs.pop('tenant', None) + super().__init__(*args, **kwargs) + + if tenant: + self.fields['department'].queryset = Department.objects.filter(tenant=tenant) + self.fields['provider'].queryset = User.objects.filter( + tenant=tenant + ) + + +class WaitingListBulkActionForm(forms.Form): + """ + Form for bulk actions on waiting list entries. + """ + + ACTION_CHOICES = [ + ('', 'Select Action'), + ('contact', 'Mark as Contacted'), + ('cancel', 'Cancel Entries'), + ('update_priority', 'Update Priority'), + ('transfer_provider', 'Transfer to Provider'), + ('export', 'Export Selected'), + ] + + action = forms.ChoiceField( + choices=ACTION_CHOICES, + required=True, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + # Fields for specific actions + new_priority = forms.ChoiceField( + choices=WaitingList._meta.get_field('priority').choices, + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + transfer_provider = forms.ModelChoiceField( + queryset=None, + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + contact_notes = forms.CharField( + required=False, + widget=forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 3, + 'placeholder': 'Notes for contact action...' + }) + ) + + cancellation_reason = forms.CharField( + required=False, + widget=forms.Textarea(attrs={ + 'class': 'form-control', + 'rows': 3, + 'placeholder': 'Reason for cancellation...' + }) + ) + + def __init__(self, *args, **kwargs): + tenant = kwargs.pop('tenant', None) + super().__init__(*args, **kwargs) + + if tenant: + from django.contrib.auth import get_user_model + User = get_user_model() + + self.fields['transfer_provider'].queryset = User.objects.filter( + tenant=tenant + ) + # from django import forms # from django.core.exceptions import ValidationError # from django.utils import timezone diff --git a/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py b/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py new file mode 100644 index 00000000..7b3fa2ed --- /dev/null +++ b/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py @@ -0,0 +1,688 @@ +# Generated by Django 5.2.6 on 2025-09-11 17:03 + +import django.core.validators +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("appointments", "0002_initial"), + ("core", "0001_initial"), + ("hr", "0001_initial"), + ("patients", "0003_remove_insuranceinfo_subscriber_ssn_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="WaitingList", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "waiting_list_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + help_text="Unique waiting list entry identifier", + unique=True, + ), + ), + ( + "appointment_type", + models.CharField( + choices=[ + ("CONSULTATION", "Consultation"), + ("FOLLOW_UP", "Follow-up"), + ("PROCEDURE", "Procedure"), + ("SURGERY", "Surgery"), + ("DIAGNOSTIC", "Diagnostic"), + ("THERAPY", "Therapy"), + ("VACCINATION", "Vaccination"), + ("SCREENING", "Screening"), + ("EMERGENCY", "Emergency"), + ("TELEMEDICINE", "Telemedicine"), + ("OTHER", "Other"), + ], + help_text="Type of appointment requested", + max_length=50, + ), + ), + ( + "specialty", + models.CharField( + choices=[ + ("FAMILY_MEDICINE", "Family Medicine"), + ("INTERNAL_MEDICINE", "Internal Medicine"), + ("PEDIATRICS", "Pediatrics"), + ("CARDIOLOGY", "Cardiology"), + ("DERMATOLOGY", "Dermatology"), + ("ENDOCRINOLOGY", "Endocrinology"), + ("GASTROENTEROLOGY", "Gastroenterology"), + ("NEUROLOGY", "Neurology"), + ("ONCOLOGY", "Oncology"), + ("ORTHOPEDICS", "Orthopedics"), + ("PSYCHIATRY", "Psychiatry"), + ("RADIOLOGY", "Radiology"), + ("SURGERY", "Surgery"), + ("UROLOGY", "Urology"), + ("GYNECOLOGY", "Gynecology"), + ("OPHTHALMOLOGY", "Ophthalmology"), + ("ENT", "Ear, Nose & Throat"), + ("EMERGENCY", "Emergency Medicine"), + ("OTHER", "Other"), + ], + help_text="Medical specialty required", + max_length=100, + ), + ), + ( + "priority", + models.CharField( + choices=[ + ("ROUTINE", "Routine"), + ("URGENT", "Urgent"), + ("STAT", "STAT"), + ("EMERGENCY", "Emergency"), + ], + default="ROUTINE", + help_text="Clinical priority level", + max_length=20, + ), + ), + ( + "urgency_score", + models.PositiveIntegerField( + default=1, + help_text="Clinical urgency score (1-10, 10 being most urgent)", + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(10), + ], + ), + ), + ( + "clinical_indication", + models.TextField( + help_text="Clinical reason for appointment request" + ), + ), + ( + "diagnosis_codes", + models.JSONField( + blank=True, default=list, help_text="ICD-10 diagnosis codes" + ), + ), + ( + "preferred_date", + models.DateField( + blank=True, + help_text="Patient preferred appointment date", + null=True, + ), + ), + ( + "preferred_time", + models.TimeField( + blank=True, + help_text="Patient preferred appointment time", + null=True, + ), + ), + ( + "flexible_scheduling", + models.BooleanField( + default=True, + help_text="Patient accepts alternative dates/times", + ), + ), + ( + "earliest_acceptable_date", + models.DateField( + blank=True, + help_text="Earliest acceptable appointment date", + null=True, + ), + ), + ( + "latest_acceptable_date", + models.DateField( + blank=True, + help_text="Latest acceptable appointment date", + null=True, + ), + ), + ( + "acceptable_days", + models.JSONField( + blank=True, + default=list, + help_text="Acceptable days of week (0=Monday, 6=Sunday)", + ), + ), + ( + "acceptable_times", + models.JSONField( + blank=True, default=list, help_text="Acceptable time ranges" + ), + ), + ( + "contact_method", + models.CharField( + choices=[ + ("PHONE", "Phone"), + ("EMAIL", "Email"), + ("SMS", "SMS"), + ("PORTAL", "Patient Portal"), + ("MAIL", "Mail"), + ], + default="PHONE", + help_text="Preferred contact method", + max_length=20, + ), + ), + ( + "contact_phone", + models.CharField( + blank=True, + help_text="Contact phone number", + max_length=20, + null=True, + ), + ), + ( + "contact_email", + models.EmailField( + blank=True, + help_text="Contact email address", + max_length=254, + null=True, + ), + ), + ( + "status", + models.CharField( + choices=[ + ("ACTIVE", "Active"), + ("CONTACTED", "Contacted"), + ("OFFERED", "Appointment Offered"), + ("SCHEDULED", "Scheduled"), + ("CANCELLED", "Cancelled"), + ("EXPIRED", "Expired"), + ("TRANSFERRED", "Transferred"), + ], + default="ACTIVE", + help_text="Waiting list status", + max_length=20, + ), + ), + ( + "position", + models.PositiveIntegerField( + blank=True, + help_text="Position in waiting list queue", + null=True, + ), + ), + ( + "estimated_wait_time", + models.PositiveIntegerField( + blank=True, help_text="Estimated wait time in days", null=True + ), + ), + ( + "last_contacted", + models.DateTimeField( + blank=True, + help_text="Last contact attempt date/time", + null=True, + ), + ), + ( + "contact_attempts", + models.PositiveIntegerField( + default=0, help_text="Number of contact attempts made" + ), + ), + ( + "max_contact_attempts", + models.PositiveIntegerField( + default=3, help_text="Maximum contact attempts before expiring" + ), + ), + ( + "appointments_offered", + models.PositiveIntegerField( + default=0, help_text="Number of appointments offered" + ), + ), + ( + "appointments_declined", + models.PositiveIntegerField( + default=0, help_text="Number of appointments declined" + ), + ), + ( + "last_offer_date", + models.DateTimeField( + blank=True, + help_text="Date of last appointment offer", + null=True, + ), + ), + ( + "requires_interpreter", + models.BooleanField( + default=False, help_text="Patient requires interpreter services" + ), + ), + ( + "interpreter_language", + models.CharField( + blank=True, + help_text="Required interpreter language", + max_length=50, + null=True, + ), + ), + ( + "accessibility_requirements", + models.TextField( + blank=True, + help_text="Special accessibility requirements", + null=True, + ), + ), + ( + "transportation_needed", + models.BooleanField( + default=False, + help_text="Patient needs transportation assistance", + ), + ), + ( + "insurance_verified", + models.BooleanField( + default=False, help_text="Insurance coverage verified" + ), + ), + ( + "authorization_required", + models.BooleanField( + default=False, help_text="Prior authorization required" + ), + ), + ( + "authorization_status", + models.CharField( + choices=[ + ("NOT_REQUIRED", "Not Required"), + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("DENIED", "Denied"), + ("EXPIRED", "Expired"), + ], + default="NOT_REQUIRED", + help_text="Authorization status", + max_length=20, + ), + ), + ( + "authorization_number", + models.CharField( + blank=True, + help_text="Authorization number", + max_length=100, + null=True, + ), + ), + ( + "referring_provider", + models.CharField( + blank=True, + help_text="Referring provider name", + max_length=200, + null=True, + ), + ), + ( + "referral_date", + models.DateField( + blank=True, help_text="Date of referral", null=True + ), + ), + ( + "referral_urgency", + models.CharField( + choices=[ + ("ROUTINE", "Routine"), + ("URGENT", "Urgent"), + ("STAT", "STAT"), + ], + default="ROUTINE", + help_text="Referral urgency level", + max_length=20, + ), + ), + ( + "removal_reason", + models.CharField( + blank=True, + choices=[ + ("SCHEDULED", "Appointment Scheduled"), + ("PATIENT_CANCELLED", "Patient Cancelled"), + ("PROVIDER_CANCELLED", "Provider Cancelled"), + ("NO_RESPONSE", "No Response to Contact"), + ("INSURANCE_ISSUE", "Insurance Issue"), + ("TRANSFERRED", "Transferred to Another Provider"), + ("EXPIRED", "Entry Expired"), + ("DUPLICATE", "Duplicate Entry"), + ("OTHER", "Other"), + ], + help_text="Reason for removal from waiting list", + max_length=50, + null=True, + ), + ), + ( + "removal_notes", + models.TextField( + blank=True, + help_text="Additional notes about removal", + null=True, + ), + ), + ( + "removed_at", + models.DateTimeField( + blank=True, + help_text="Date/time removed from waiting list", + null=True, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "notes", + models.TextField( + blank=True, help_text="Additional notes and comments", null=True + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + help_text="User who created the waiting list entry", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_waiting_list_entries", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "department", + models.ForeignKey( + help_text="Department for appointment", + on_delete=django.db.models.deletion.CASCADE, + related_name="waiting_list_entries", + to="hr.department", + ), + ), + ( + "patient", + models.ForeignKey( + help_text="Patient on waiting list", + on_delete=django.db.models.deletion.CASCADE, + related_name="waiting_list_entries", + to="patients.patientprofile", + ), + ), + ( + "provider", + models.ForeignKey( + blank=True, + help_text="Preferred healthcare provider", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="provider_waiting_list", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "removed_by", + models.ForeignKey( + blank=True, + help_text="User who removed entry from waiting list", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="removed_waiting_list_entries", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "scheduled_appointment", + models.ForeignKey( + blank=True, + help_text="Scheduled appointment from waiting list", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="waiting_list_entry", + to="appointments.appointmentrequest", + ), + ), + ( + "tenant", + models.ForeignKey( + help_text="Organization tenant", + on_delete=django.db.models.deletion.CASCADE, + related_name="waiting_list_entries", + to="core.tenant", + ), + ), + ], + options={ + "verbose_name": "Waiting List Entry", + "verbose_name_plural": "Waiting List Entries", + "db_table": "appointments_waiting_list", + "ordering": ["priority", "urgency_score", "created_at"], + }, + ), + migrations.CreateModel( + name="WaitingListContactLog", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "contact_date", + models.DateTimeField( + auto_now_add=True, help_text="Date and time of contact attempt" + ), + ), + ( + "contact_method", + models.CharField( + choices=[ + ("PHONE", "Phone Call"), + ("EMAIL", "Email"), + ("SMS", "SMS"), + ("PORTAL", "Patient Portal Message"), + ("MAIL", "Mail"), + ("IN_PERSON", "In Person"), + ], + help_text="Method of contact used", + max_length=20, + ), + ), + ( + "contact_outcome", + models.CharField( + choices=[ + ("SUCCESSFUL", "Successful Contact"), + ("NO_ANSWER", "No Answer"), + ("BUSY", "Line Busy"), + ("VOICEMAIL", "Left Voicemail"), + ("EMAIL_SENT", "Email Sent"), + ("EMAIL_BOUNCED", "Email Bounced"), + ("SMS_SENT", "SMS Sent"), + ("SMS_FAILED", "SMS Failed"), + ("WRONG_NUMBER", "Wrong Number"), + ("DECLINED", "Patient Declined"), + ], + help_text="Outcome of contact attempt", + max_length=20, + ), + ), + ( + "appointment_offered", + models.BooleanField( + default=False, + help_text="Appointment was offered during contact", + ), + ), + ( + "offered_date", + models.DateField( + blank=True, help_text="Date of offered appointment", null=True + ), + ), + ( + "offered_time", + models.TimeField( + blank=True, help_text="Time of offered appointment", null=True + ), + ), + ( + "patient_response", + models.CharField( + blank=True, + choices=[ + ("ACCEPTED", "Accepted Appointment"), + ("DECLINED", "Declined Appointment"), + ("REQUESTED_DIFFERENT", "Requested Different Time"), + ("WILL_CALL_BACK", "Will Call Back"), + ("NO_LONGER_NEEDED", "No Longer Needed"), + ("INSURANCE_ISSUE", "Insurance Issue"), + ("NO_RESPONSE", "No Response"), + ], + help_text="Patient response to contact", + max_length=20, + null=True, + ), + ), + ( + "notes", + models.TextField( + blank=True, help_text="Notes from contact attempt", null=True + ), + ), + ( + "next_contact_date", + models.DateField( + blank=True, + help_text="Scheduled date for next contact attempt", + null=True, + ), + ), + ( + "contacted_by", + models.ForeignKey( + blank=True, + help_text="Staff member who made contact", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "waiting_list_entry", + models.ForeignKey( + help_text="Associated waiting list entry", + on_delete=django.db.models.deletion.CASCADE, + related_name="contact_logs", + to="appointments.waitinglist", + ), + ), + ], + options={ + "verbose_name": "Waiting List Contact Log", + "verbose_name_plural": "Waiting List Contact Logs", + "db_table": "appointments_waiting_list_contact_log", + "ordering": ["-contact_date"], + }, + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["tenant", "status"], name="appointment_tenant__a558da_idx" + ), + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["patient", "status"], name="appointment_patient_73f03d_idx" + ), + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["department", "specialty", "status"], + name="appointment_departm_78fd70_idx", + ), + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["priority", "urgency_score"], + name="appointment_priorit_30fb90_idx", + ), + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["status", "created_at"], name="appointment_status_cfe551_idx" + ), + ), + migrations.AddIndex( + model_name="waitinglist", + index=models.Index( + fields=["provider", "status"], name="appointment_provide_dd6c2b_idx" + ), + ), + migrations.AddIndex( + model_name="waitinglistcontactlog", + index=models.Index( + fields=["waiting_list_entry", "contact_date"], + name="appointment_waiting_50d8ac_idx", + ), + ), + migrations.AddIndex( + model_name="waitinglistcontactlog", + index=models.Index( + fields=["contact_outcome"], name="appointment_contact_ad9c45_idx" + ), + ), + migrations.AddIndex( + model_name="waitinglistcontactlog", + index=models.Index( + fields=["next_contact_date"], name="appointment_next_co_b29984_idx" + ), + ), + ] diff --git a/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc b/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc new file mode 100644 index 00000000..12a8e50c Binary files /dev/null and b/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc differ diff --git a/appointments/models.py b/appointments/models.py index 6b1c8f5d..edb37303 100644 --- a/appointments/models.py +++ b/appointments/models.py @@ -1176,3 +1176,640 @@ class AppointmentTemplate(models.Model): def __str__(self): return f"{self.name} ({self.specialty})" + +class WaitingList(models.Model): + """ + Patient waiting list for appointment scheduling. + Follows healthcare industry standards for patient queue management. + """ + APPOINTMENT_TYPE_CHOICES = [ + ('CONSULTATION', 'Consultation'), + ('FOLLOW_UP', 'Follow-up'), + ('PROCEDURE', 'Procedure'), + ('SURGERY', 'Surgery'), + ('DIAGNOSTIC', 'Diagnostic'), + ('THERAPY', 'Therapy'), + ('VACCINATION', 'Vaccination'), + ('SCREENING', 'Screening'), + ('EMERGENCY', 'Emergency'), + ('TELEMEDICINE', 'Telemedicine'), + ('OTHER', 'Other'), + ] + SPECIALTY_CHOICES = [ + ('FAMILY_MEDICINE', 'Family Medicine'), + ('INTERNAL_MEDICINE', 'Internal Medicine'), + ('PEDIATRICS', 'Pediatrics'), + ('CARDIOLOGY', 'Cardiology'), + ('DERMATOLOGY', 'Dermatology'), + ('ENDOCRINOLOGY', 'Endocrinology'), + ('GASTROENTEROLOGY', 'Gastroenterology'), + ('NEUROLOGY', 'Neurology'), + ('ONCOLOGY', 'Oncology'), + ('ORTHOPEDICS', 'Orthopedics'), + ('PSYCHIATRY', 'Psychiatry'), + ('RADIOLOGY', 'Radiology'), + ('SURGERY', 'Surgery'), + ('UROLOGY', 'Urology'), + ('GYNECOLOGY', 'Gynecology'), + ('OPHTHALMOLOGY', 'Ophthalmology'), + ('ENT', 'Ear, Nose & Throat'), + ('EMERGENCY', 'Emergency Medicine'), + ('OTHER', 'Other'), + ] + PRIORITY_CHOICES = [ + ('ROUTINE', 'Routine'), + ('URGENT', 'Urgent'), + ('STAT', 'STAT'), + ('EMERGENCY', 'Emergency'), + ] + CONTACT_METHOD_CHOICES = [ + ('PHONE', 'Phone'), + ('EMAIL', 'Email'), + ('SMS', 'SMS'), + ('PORTAL', 'Patient Portal'), + ('MAIL', 'Mail'), + ] + STATUS_CHOICES = [ + ('ACTIVE', 'Active'), + ('CONTACTED', 'Contacted'), + ('OFFERED', 'Appointment Offered'), + ('SCHEDULED', 'Scheduled'), + ('CANCELLED', 'Cancelled'), + ('EXPIRED', 'Expired'), + ('TRANSFERRED', 'Transferred'), + ] + AUTHORIZATION_STATUS_CHOICES = [ + ('NOT_REQUIRED', 'Not Required'), + ('PENDING', 'Pending'), + ('APPROVED', 'Approved'), + ('DENIED', 'Denied'), + ('EXPIRED', 'Expired'), + ] + REFERRAL_URGENCY_CHOICES = [ + ('ROUTINE', 'Routine'), + ('URGENT', 'Urgent'), + ('STAT', 'STAT'), + ] + REMOVAL_REASON_CHOICES = [ + ('SCHEDULED', 'Appointment Scheduled'), + ('PATIENT_CANCELLED', 'Patient Cancelled'), + ('PROVIDER_CANCELLED', 'Provider Cancelled'), + ('NO_RESPONSE', 'No Response to Contact'), + ('INSURANCE_ISSUE', 'Insurance Issue'), + ('TRANSFERRED', 'Transferred to Another Provider'), + ('EXPIRED', 'Entry Expired'), + ('DUPLICATE', 'Duplicate Entry'), + ('OTHER', 'Other'), + ] + + # Basic Identifiers + waiting_list_id = models.UUIDField( + default=uuid.uuid4, + unique=True, + editable=False, + help_text='Unique waiting list entry identifier' + ) + + # Tenant relationship + tenant = models.ForeignKey( + 'core.Tenant', + on_delete=models.CASCADE, + related_name='waiting_list_entries', + help_text='Organization tenant' + ) + + # Patient Information + patient = models.ForeignKey( + 'patients.PatientProfile', + on_delete=models.CASCADE, + related_name='waiting_list_entries', + help_text='Patient on waiting list' + ) + + # Provider and Service Information + provider = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name='provider_waiting_list', + blank=True, + null=True, + help_text='Preferred healthcare provider' + ) + + department = models.ForeignKey( + 'hr.Department', + on_delete=models.CASCADE, + related_name='waiting_list_entries', + help_text='Department for appointment' + ) + + appointment_type = models.CharField( + max_length=50, + choices=APPOINTMENT_TYPE_CHOICES, + help_text='Type of appointment requested' + ) + + specialty = models.CharField( + max_length=100, + choices=SPECIALTY_CHOICES, + help_text='Medical specialty required' + ) + + # Priority and Clinical Information + priority = models.CharField( + max_length=20, + choices=PRIORITY_CHOICES, + default='ROUTINE', + help_text='Clinical priority level' + ) + + urgency_score = models.PositiveIntegerField( + default=1, + validators=[MinValueValidator(1), MaxValueValidator(10)], + help_text='Clinical urgency score (1-10, 10 being most urgent)' + ) + + clinical_indication = models.TextField( + help_text='Clinical reason for appointment request' + ) + + diagnosis_codes = models.JSONField( + default=list, + blank=True, + help_text='ICD-10 diagnosis codes' + ) + + # Patient Preferences + preferred_date = models.DateField( + blank=True, + null=True, + help_text='Patient preferred appointment date' + ) + + preferred_time = models.TimeField( + blank=True, + null=True, + help_text='Patient preferred appointment time' + ) + + flexible_scheduling = models.BooleanField( + default=True, + help_text='Patient accepts alternative dates/times' + ) + + earliest_acceptable_date = models.DateField( + blank=True, + null=True, + help_text='Earliest acceptable appointment date' + ) + + latest_acceptable_date = models.DateField( + blank=True, + null=True, + help_text='Latest acceptable appointment date' + ) + + acceptable_days = models.JSONField( + default=list, + blank=True, + help_text='Acceptable days of week (0=Monday, 6=Sunday)' + ) + + acceptable_times = models.JSONField( + default=list, + blank=True, + help_text='Acceptable time ranges' + ) + + # Communication Preferences + contact_method = models.CharField( + max_length=20, + choices=CONTACT_METHOD_CHOICES, + default='PHONE', + help_text='Preferred contact method' + ) + + contact_phone = models.CharField( + max_length=20, + blank=True, + null=True, + help_text='Contact phone number' + ) + + contact_email = models.EmailField( + blank=True, + null=True, + help_text='Contact email address' + ) + + # Status and Workflow + status = models.CharField( + max_length=20, + choices=STATUS_CHOICES, + default='ACTIVE', + help_text='Waiting list status' + ) + + # Position and Timing + position = models.PositiveIntegerField( + blank=True, + null=True, + help_text='Position in waiting list queue' + ) + + estimated_wait_time = models.PositiveIntegerField( + blank=True, + null=True, + help_text='Estimated wait time in days' + ) + + # Contact History + last_contacted = models.DateTimeField( + blank=True, + null=True, + help_text='Last contact attempt date/time' + ) + + contact_attempts = models.PositiveIntegerField( + default=0, + help_text='Number of contact attempts made' + ) + + max_contact_attempts = models.PositiveIntegerField( + default=3, + help_text='Maximum contact attempts before expiring' + ) + + # Appointment Offers + appointments_offered = models.PositiveIntegerField( + default=0, + help_text='Number of appointments offered' + ) + + appointments_declined = models.PositiveIntegerField( + default=0, + help_text='Number of appointments declined' + ) + + last_offer_date = models.DateTimeField( + blank=True, + null=True, + help_text='Date of last appointment offer' + ) + + # Scheduling Constraints + requires_interpreter = models.BooleanField( + default=False, + help_text='Patient requires interpreter services' + ) + + interpreter_language = models.CharField( + max_length=50, + blank=True, + null=True, + help_text='Required interpreter language' + ) + + accessibility_requirements = models.TextField( + blank=True, + null=True, + help_text='Special accessibility requirements' + ) + + transportation_needed = models.BooleanField( + default=False, + help_text='Patient needs transportation assistance' + ) + + # Insurance and Authorization + insurance_verified = models.BooleanField( + default=False, + help_text='Insurance coverage verified' + ) + + authorization_required = models.BooleanField( + default=False, + help_text='Prior authorization required' + ) + + authorization_status = models.CharField( + max_length=20, + choices=AUTHORIZATION_STATUS_CHOICES, + default='NOT_REQUIRED', + help_text='Authorization status' + ) + + authorization_number = models.CharField( + max_length=100, + blank=True, + null=True, + help_text='Authorization number' + ) + + # Referral Information + referring_provider = models.CharField( + max_length=200, + blank=True, + null=True, + help_text='Referring provider name' + ) + + referral_date = models.DateField( + blank=True, + null=True, + help_text='Date of referral' + ) + + referral_urgency = models.CharField( + max_length=20, + choices=REFERRAL_URGENCY_CHOICES, + default='ROUTINE', + help_text='Referral urgency level' + ) + + # Outcome Tracking + scheduled_appointment = models.ForeignKey( + 'AppointmentRequest', + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name='waiting_list_entry', + help_text='Scheduled appointment from waiting list' + ) + + removal_reason = models.CharField( + max_length=50, + choices=REMOVAL_REASON_CHOICES, + blank=True, + null=True, + help_text='Reason for removal from waiting list' + ) + + removal_notes = models.TextField( + blank=True, + null=True, + help_text='Additional notes about removal' + ) + + removed_at = models.DateTimeField( + blank=True, + null=True, + help_text='Date/time removed from waiting list' + ) + + removed_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name='removed_waiting_list_entries', + help_text='User who removed entry from waiting list' + ) + + # Audit Trail + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + created_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='created_waiting_list_entries', + help_text='User who created the waiting list entry' + ) + + # Notes and Comments + notes = models.TextField( + blank=True, + null=True, + help_text='Additional notes and comments' + ) + + class Meta: + db_table = 'appointments_waiting_list' + verbose_name = 'Waiting List Entry' + verbose_name_plural = 'Waiting List Entries' + ordering = ['priority', 'urgency_score', 'created_at'] + indexes = [ + models.Index(fields=['tenant', 'status']), + models.Index(fields=['patient', 'status']), + models.Index(fields=['department', 'specialty', 'status']), + models.Index(fields=['priority', 'urgency_score']), + models.Index(fields=['status', 'created_at']), + models.Index(fields=['provider', 'status']), + ] + + def __str__(self): + return f"{self.patient.get_full_name()} - {self.specialty} ({self.status})" + + @property + def days_waiting(self): + """Calculate number of days patient has been waiting.""" + return (timezone.now().date() - self.created_at.date()).days + + @property + def is_overdue_contact(self): + """Check if contact is overdue based on priority.""" + if not self.last_contacted: + return self.days_waiting > 1 + + days_since_contact = (timezone.now().date() - self.last_contacted.date()).days + + if self.priority == 'EMERGENCY': + return days_since_contact > 0 # Same day contact required + elif self.priority == 'STAT': + return days_since_contact > 1 # Next day contact required + elif self.priority == 'URGENT': + return days_since_contact > 3 # 3 day contact window + else: + return days_since_contact > 7 # Weekly contact for routine + + @property + def should_expire(self): + """Check if waiting list entry should expire.""" + if self.contact_attempts >= self.max_contact_attempts: + return True + + # Expire after 90 days for routine, 30 days for urgent + max_days = 30 if self.priority in ['URGENT', 'STAT', 'EMERGENCY'] else 90 + return self.days_waiting > max_days + + def calculate_position(self): + """Calculate position in waiting list queue.""" + # Priority-based position calculation + priority_weights = { + 'EMERGENCY': 1000, + 'STAT': 800, + 'URGENT': 600, + 'ROUTINE': 400, + } + + base_score = priority_weights.get(self.priority, 400) + urgency_bonus = self.urgency_score * 10 + wait_time_bonus = min(self.days_waiting, 30) # Cap at 30 days + + total_score = base_score + urgency_bonus + wait_time_bonus + + # Count entries with higher scores + higher_priority = WaitingList.objects.filter( + department=self.department, + specialty=self.specialty, + status='ACTIVE', + tenant=self.tenant + ).exclude(id=self.id) + + position = 1 + for entry in higher_priority: + entry_score = ( + priority_weights.get(entry.priority, 400) + + entry.urgency_score * 10 + + min(entry.days_waiting, 30) + ) + if entry_score > total_score: + position += 1 + + return position + + def update_position(self): + """Update position in waiting list.""" + self.position = self.calculate_position() + self.save(update_fields=['position']) + + def estimate_wait_time(self): + """Estimate wait time based on historical data and current queue.""" + # This would typically use historical scheduling data + # For now, provide basic estimation + base_wait = { + 'EMERGENCY': 1, + 'STAT': 3, + 'URGENT': 7, + 'ROUTINE': 14, + } + + estimated_days = base_wait.get(self.priority, 14) + + # Adjust based on queue position + if self.position: + estimated_days += max(0, (self.position - 1) * 2) + + return estimated_days + + +class WaitingListContactLog(models.Model): + """ + Contact log for waiting list entries. + Tracks all communication attempts with patients on waiting list. + """ + CONTACT_METHOD_CHOICES = [ + ('PHONE', 'Phone Call'), + ('EMAIL', 'Email'), + ('SMS', 'SMS'), + ('PORTAL', 'Patient Portal Message'), + ('MAIL', 'Mail'), + ('IN_PERSON', 'In Person'), + ] + CONTACT_OUTCOME_CHOICES = [ + ('SUCCESSFUL', 'Successful Contact'), + ('NO_ANSWER', 'No Answer'), + ('BUSY', 'Line Busy'), + ('VOICEMAIL', 'Left Voicemail'), + ('EMAIL_SENT', 'Email Sent'), + ('EMAIL_BOUNCED', 'Email Bounced'), + ('SMS_SENT', 'SMS Sent'), + ('SMS_FAILED', 'SMS Failed'), + ('WRONG_NUMBER', 'Wrong Number'), + ('DECLINED', 'Patient Declined'), + ] + PATIENT_RESPONSE_CHOICES = [ + ('ACCEPTED', 'Accepted Appointment'), + ('DECLINED', 'Declined Appointment'), + ('REQUESTED_DIFFERENT', 'Requested Different Time'), + ('WILL_CALL_BACK', 'Will Call Back'), + ('NO_LONGER_NEEDED', 'No Longer Needed'), + ('INSURANCE_ISSUE', 'Insurance Issue'), + ('NO_RESPONSE', 'No Response'), + ] + + waiting_list_entry = models.ForeignKey( + WaitingList, + on_delete=models.CASCADE, + related_name='contact_logs', + help_text='Associated waiting list entry' + ) + + contact_date = models.DateTimeField( + auto_now_add=True, + help_text='Date and time of contact attempt' + ) + + contact_method = models.CharField( + max_length=20, + choices=CONTACT_METHOD_CHOICES, + help_text='Method of contact used' + ) + + contact_outcome = models.CharField( + max_length=20, + choices=CONTACT_OUTCOME_CHOICES, + help_text='Outcome of contact attempt' + ) + + appointment_offered = models.BooleanField( + default=False, + help_text='Appointment was offered during contact' + ) + + offered_date = models.DateField( + blank=True, + null=True, + help_text='Date of offered appointment' + ) + + offered_time = models.TimeField( + blank=True, + null=True, + help_text='Time of offered appointment' + ) + + patient_response = models.CharField( + max_length=20, + choices=PATIENT_RESPONSE_CHOICES, + blank=True, + null=True, + help_text='Patient response to contact' + ) + + notes = models.TextField( + blank=True, + null=True, + help_text='Notes from contact attempt' + ) + + next_contact_date = models.DateField( + blank=True, + null=True, + help_text='Scheduled date for next contact attempt' + ) + + contacted_by = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, + blank=True, + help_text='Staff member who made contact' + ) + + class Meta: + db_table = 'appointments_waiting_list_contact_log' + verbose_name = 'Waiting List Contact Log' + verbose_name_plural = 'Waiting List Contact Logs' + ordering = ['-contact_date'] + indexes = [ + models.Index(fields=['waiting_list_entry', 'contact_date']), + models.Index(fields=['contact_outcome']), + models.Index(fields=['next_contact_date']), + ] + + def __str__(self): + return f"{self.waiting_list_entry.patient.get_full_name()} - {self.contact_method} ({self.contact_outcome})" + diff --git a/appointments/templates/.DS_Store b/appointments/templates/.DS_Store index 92b5a68f..6f9a9259 100644 Binary files a/appointments/templates/.DS_Store and b/appointments/templates/.DS_Store differ diff --git a/appointments/templates/appointments/.DS_Store b/appointments/templates/appointments/.DS_Store index 74dbb686..4b8879d8 100644 Binary files a/appointments/templates/appointments/.DS_Store and b/appointments/templates/appointments/.DS_Store differ diff --git a/appointments/templates/appointments/partials/contact_log_list.html b/appointments/templates/appointments/partials/contact_log_list.html new file mode 100644 index 00000000..9d8a5588 --- /dev/null +++ b/appointments/templates/appointments/partials/contact_log_list.html @@ -0,0 +1,39 @@ +{% for log in contact_logs %} +
+
+
+ + {{ log.get_contact_method_display }} - + + {{ log.get_contact_outcome_display }} + +
+ {{ log.contact_date|date:"M d, Y H:i" }} +
+

{{ log.notes|default:"No notes." }}

+ {% if log.appointment_offered %} +

+ Appointment Offered: + {{ log.offered_date|date:"M d, Y" }} at {{ log.offered_time|time:"g:i A" }} +

+

+ Patient Response: + + {{ log.get_patient_response_display }} + +

+ {% endif %} + {% if log.next_contact_date %} +

+ Next Contact: {{ log.next_contact_date|date:"M d, Y" }} +

+ {% endif %} + Contacted by: {{ log.contacted_by.get_full_name|default:"N/A" }} +
+{% empty %} +
+ +

No contact logs available for this entry.

+
+{% endfor %} + diff --git a/appointments/templates/appointments/queue/waiting_queue_form.html b/appointments/templates/appointments/queue/waiting_queue_form.html index ff2b5d6f..0d1b68ed 100644 --- a/appointments/templates/appointments/queue/waiting_queue_form.html +++ b/appointments/templates/appointments/queue/waiting_queue_form.html @@ -151,22 +151,15 @@ {% csrf_token %} -
-
-

+
+
+

Basic Information -

-Configure the basic details of the waiting queue -
- - - - -
+

Configure the basic details of the waiting queue

-
-
+ +
+
@@ -221,8 +215,7 @@ {% endif %}
-
-

+
diff --git a/appointments/templates/appointments/waiting_list/waiting_list.html b/appointments/templates/appointments/waiting_list/waiting_list.html new file mode 100644 index 00000000..e0be4dcf --- /dev/null +++ b/appointments/templates/appointments/waiting_list/waiting_list.html @@ -0,0 +1,525 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Patient Waiting List Management{% endblock %} + +{% block css %} + + + + +{% endblock %} + +{% block content %} + + + + + +

+ + Patient Waiting List Management + Manage appointment waiting list and patient queue +

+ + + +
+
+
+
+
+
+
Total Waiting
+

{{ stats.total }}

+ Active entries +
+
+ +
+
+
+
+
+
+
+
+
+
+
Urgent Cases
+

{{ stats.urgent }}

+ High priority +
+
+ +
+
+
+
+
+
+
+
+
+
+
Contacted
+

{{ stats.contacted }}

+ Recently contacted +
+
+ +
+
+
+
+
+
+
+
+
+
+
Avg. Wait
+

{{ stats.avg_wait_days }}

+ Days waiting +
+
+ +
+
+
+
+
+
+ + + +
+
+

+ Filter Waiting List +

+
+ + + +
+
+
+{#
#} +{#
#} +{# {{ filter_form.department.label_tag }}#} +{# {{ filter_form.department }}#} +{#
#} +{#
#} +{# {{ filter_form.specialty.label_tag }}#} +{# {{ filter_form.specialty }}#} +{#
#} +{#
#} +{# {{ filter_form.priority.label_tag }}#} +{# {{ filter_form.priority }}#} +{#
#} +{#
#} +{# {{ filter_form.status.label_tag }}#} +{# {{ filter_form.status }}#} +{#
#} +{#
#} +{# {{ filter_form.provider.label_tag }}#} +{# {{ filter_form.provider }}#} +{#
#} +{#
#} +{# #} +{#
#} +{# #} +{# #} +{# Clear#} +{# #} +{#
#} +{#
#} +{#
#} +
+
+ + + +
+ +
+

+ Waiting List Entries +

+
+ + Add to Waiting List + + +
+
+ + + +
+ +
+
+
+ {% csrf_token %} +
+
+ + +
+
+ {{ bulk_action_form.action }} +
+ +
+
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + {% for entry in waiting_list %} + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
+ + Pos.PatientDepartmentSpecialtyPriorityUrgencyStatusWait TimeLast ContactActions
+ + + {{ entry.position|default:"-" }} + +
+
+ {{ entry.patient.first_name|first }}{{ entry.patient.last_name|first }} +
+
+
{{ entry.patient.get_full_name }}
+ + MRN: {{ entry.patient.mrn|default:"N/A" }} + {% if entry.requires_interpreter %} + + {% endif %} + +
+
+
+ {{ entry.department.name }} + + {{ entry.get_specialty_display }} + + {% if entry.priority == 'EMERGENCY' %} + {{ entry.get_priority_display }} + {% elif entry.priority == 'STAT' %} + {{ entry.get_priority_display }} + {% elif entry.priority == 'URGENT' %} + {{ entry.get_priority_display }} + {% else %} + {{ entry.get_priority_display }} + {% endif %} + +
+ {{ entry.urgency_score }} +
+
+
+
+
+ {% if entry.status == 'ACTIVE' %} + Active + {% elif entry.status == 'CONTACTED' %} + Contacted + {% elif entry.status == 'OFFERED' %} + Offered + {% elif entry.status == 'SCHEDULED' %} + Scheduled + {% elif entry.status == 'CANCELLED' %} + Cancelled + {% else %} + {{ entry.get_status_display }} + {% endif %} + +
+
{{ entry.days_waiting }}
+ days +
+
+ {% if entry.last_contacted %} +
+
{{ entry.last_contacted|date:"M d" }}
+ {{ entry.last_contacted|timesince }} ago +
+ {% else %} + Never + {% endif %} + {% if entry.is_overdue_contact %} + + {% endif %} +
+
+ + + + + + + +
+
+
+ +

No patients currently on waiting list

+ + Add First Patient + +
+
+
+ + + + {% if is_paginated %} + {% include 'partial/pagination.html' %} + {% endif %} + +
+ +
+ + + + +{% endblock %} + +{% block js %} + + + + + + + + + + +{% endblock %} + diff --git a/appointments/templates/appointments/waiting_list/waiting_list_confirm_delete.html b/appointments/templates/appointments/waiting_list/waiting_list_confirm_delete.html new file mode 100644 index 00000000..d846f81f --- /dev/null +++ b/appointments/templates/appointments/waiting_list/waiting_list_confirm_delete.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Confirm Cancellation{% endblock %} + +{% block content %} + + + + + +

+ + Confirm Waiting List Entry Cancellation + Permanently remove this patient from the waiting list +

+ + + +
+
+

+ Warning: This action cannot be undone! +

+
+
+
+

Are you absolutely sure you want to cancel this waiting list entry?

+

Cancelling this entry will remove {{ object.patient.get_full_name }} from the waiting list for {{ object.get_specialty_display }}.

+

This action is usually taken when the patient no longer requires the appointment, has been scheduled through other means, or has been transferred.

+
+ +
+
+
+ +

{{ object.patient.get_full_name }}

+
+
+ +

{{ object.get_specialty_display }}

+
+
+
+
+ +

{{ object.get_priority_display }}

+
+
+ +

{{ object.days_waiting }} days

+
+
+
+ +
+ {% csrf_token %} +
+ + Cancel + + +
+
+
+
+ +{% endblock %} + diff --git a/appointments/templates/appointments/waiting_list/waiting_list_detail.html b/appointments/templates/appointments/waiting_list/waiting_list_detail.html new file mode 100644 index 00000000..2e117b92 --- /dev/null +++ b/appointments/templates/appointments/waiting_list/waiting_list_detail.html @@ -0,0 +1,427 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Waiting List Entry Details{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} + + + + + +

+ + Waiting List Entry Details + Comprehensive view of patient waiting list entry +

+ + + +
+
+

+ Patient: {{ entry.patient.get_full_name }} +

+ +
+ +
+
+
+ +
+
Patient & Service Information
+
+
Patient Name:
+
{{ entry.patient.get_full_name }} (MRN: {{ entry.patient.mrn|default:'N/A' }})
+
+
+
Department:
+
{{ entry.department.name }}
+
+
+
Preferred Provider:
+
{{ entry.provider.get_full_name|default:'Any' }}
+
+
+
Appointment Type:
+
{{ entry.get_appointment_type_display }}
+
+
+
Medical Specialty:
+
{{ entry.get_specialty_display }}
+
+
+ + +
+
Clinical Priority & Urgency
+
+
Priority Level:
+
+ {% if entry.priority == 'EMERGENCY' %} + {{ entry.get_priority_display }} + {% elif entry.priority == 'STAT' %} + {{ entry.get_priority_display }} + {% elif entry.priority == 'URGENT' %} + {{ entry.get_priority_display }} + {% else %} + {{ entry.get_priority_display }} + {% endif %} +
+
+
+
Urgency Score:
+
{{ entry.urgency_score }} / 10
+
+
+
Clinical Indication:
+
{{ entry.clinical_indication|linebreaksbr }}
+
+
+
Diagnosis Codes:
+
{{ entry.diagnosis_codes|join:", "|default:'N/A' }}
+
+
+ + +
+
Patient Scheduling Preferences
+
+
Preferred Date:
+
{{ entry.preferred_date|date:"M d, Y"|default:'Any' }}
+
+
+
Preferred Time:
+
{{ entry.preferred_time|time:"g:i A"|default:'Any' }}
+
+
+
Flexible Scheduling:
+
{% if entry.flexible_scheduling %}Yes{% else %}No{% endif %}
+
+ {% if entry.flexible_scheduling %} +
+
Acceptable Date Range:
+
+ {% if entry.earliest_acceptable_date %}{{ entry.earliest_acceptable_date|date:"M d, Y" }}{% else %}Any{% endif %} + to + {% if entry.latest_acceptable_date %}{{ entry.latest_acceptable_date|date:"M d, Y" }}{% else %}Any{% endif %} +
+
+ {% endif %} +
+ + +
+
Contact Information
+
+
Preferred Method:
+
{{ entry.get_contact_method_display }}
+
+
+
Phone:
+
{{ entry.contact_phone|default:'N/A' }}
+
+
+
Email:
+
{{ entry.contact_email|default:'N/A' }}
+
+
+ + +
+
Special Requirements & Accommodations
+
+
Interpreter Needed:
+
{% if entry.requires_interpreter %}Yes ({{ entry.interpreter_language|default:'N/A' }}){% else %}No{% endif %}
+
+
+
Transportation Needed:
+
{% if entry.transportation_needed %}Yes{% else %}No{% endif %}
+
+
+
Accessibility:
+
{{ entry.accessibility_requirements|default:'None'|linebreaksbr }}
+
+
+ + +
+
Insurance & Authorization
+
+
Insurance Verified:
+
{% if entry.insurance_verified %}Yes{% else %}No{% endif %}
+
+
+
Authorization Required:
+
{% if entry.authorization_required %}Yes{% else %}No{% endif %}
+
+
+
Authorization Status:
+
{{ entry.get_authorization_status_display }}
+
+
+
Authorization Number:
+
{{ entry.authorization_number|default:'N/A' }}
+
+
+ + +
+
Referral Information
+
+
Referring Provider:
+
{{ entry.referring_provider|default:'N/A' }}
+
+
+
Referral Date:
+
{{ entry.referral_date|date:"M d, Y"|default:'N/A' }}
+
+
+
Referral Urgency:
+
{{ entry.get_referral_urgency_display }}
+
+
+ + +
+
Additional Notes
+
+
{{ entry.notes|default:'No additional notes.'|linebreaksbr }}
+
+
+ + +
+
Outcome Tracking
+
+
Status:
+
+ {% if entry.status == 'ACTIVE' %} + {{ entry.get_status_display }} + {% elif entry.status == 'CONTACTED' %} + {{ entry.get_status_display }} + {% elif entry.status == 'OFFERED' %} + {{ entry.get_status_display }} + {% elif entry.status == 'SCHEDULED' %} + {{ entry.get_status_display }} + {% elif entry.status == 'CANCELLED' %} + {{ entry.get_status_display }} + {% else %} + {{ entry.get_status_display }} + {% endif %} +
+
+
+
Scheduled Appointment:
+ +
+ {% if entry.status == 'CANCELLED' or entry.status == 'EXPIRED' or entry.status == 'TRANSFERRED' %} +
+
Removal Reason:
+
{{ entry.get_removal_reason_display|default:'N/A' }}
+
+
+
Removal Notes:
+
{{ entry.removal_notes|default:'None'|linebreaksbr }}
+
+
+
Removed At:
+
{{ entry.removed_at|date:"M d, Y H:i"|default:'N/A' }}
+
+
+
Removed By:
+
{{ entry.removed_by.get_full_name|default:'N/A' }}
+
+ {% endif %} +
+ + +
+
Metadata
+
+
Entry ID:
+
{{ entry.waiting_list_id }}
+
+
+
Created At:
+
{{ entry.created_at|date:"M d, Y H:i" }} by {{ entry.created_by.get_full_name|default:'N/A' }}
+
+
+
Last Updated:
+
{{ entry.updated_at|date:"M d, Y H:i" }}
+
+
+
+ +
+ +
+
+
Waiting List Metrics
+
+
+
+
Current Position:
+
{{ entry.position|default:'N/A' }}
+
+
+
Days Waiting:
+
{{ entry.days_waiting }} days
+
+
+
Estimated Wait Time:
+
{{ estimated_wait_time }} days
+
+
+
Contact Attempts:
+
{{ entry.contact_attempts }}
+
+
+
Overdue Contact:
+
{% if entry.is_overdue_contact %}Yes{% else %}No{% endif %}
+
+
+
+ + +
+
+
Contact Log
+
+
+ {% include 'appointments/partials/contact_log_list.html' %} +
+ +
+
+
+
+
+ + + + +{% endblock %} + +{% block extra_js %} + + +{% endblock %} + diff --git a/appointments/templates/appointments/waiting_list/waiting_list_form.html b/appointments/templates/appointments/waiting_list/waiting_list_form.html new file mode 100644 index 00000000..645035e4 --- /dev/null +++ b/appointments/templates/appointments/waiting_list/waiting_list_form.html @@ -0,0 +1,561 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}{% if object %}Edit{% else %}Add{% endif %} Waiting List Entry{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} + + + + + +

+ + {% if object %}Edit Waiting List Entry{% else %}Add Patient to Waiting List{% endif %} + {% if object %}Update patient information{% else %}Register new waiting list entry{% endif %} +

+ + + +
+
+

+ Patient Information & Request Details +

+ +
+ +
+
+ {% csrf_token %} + + +
+
+ Patient & Service Information +
+ +
+
+
+ + {{ form.patient }} + {% if form.patient.errors %} +
{{ form.patient.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.department }} + {% if form.department.errors %} +
{{ form.department.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.provider }} + Leave blank for any available provider + {% if form.provider.errors %} +
{{ form.provider.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.appointment_type }} + {% if form.appointment_type.errors %} +
{{ form.appointment_type.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.specialty }} + {% if form.specialty.errors %} +
{{ form.specialty.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+ Clinical Priority & Urgency +
+ +
+
+
+ + {{ form.priority }} +
+
+ Select priority level +
+
+ {% if form.priority.errors %} +
{{ form.priority.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.urgency_score }} +
+
+
+ 1 = Routine, 10 = Most Urgent + {% if form.urgency_score.errors %} +
{{ form.urgency_score.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.clinical_indication }} + {% if form.clinical_indication.errors %} +
{{ form.clinical_indication.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.diagnosis_codes }} + Enter diagnosis codes separated by commas (e.g., M25.511, Z51.11) + {% if form.diagnosis_codes.errors %} +
{{ form.diagnosis_codes.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+ Patient Scheduling Preferences +
+ +
+
+
+ + {{ form.preferred_date }} + {% if form.preferred_date.errors %} +
{{ form.preferred_date.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.preferred_time }} + {% if form.preferred_time.errors %} +
{{ form.preferred_time.errors.0 }}
+ {% endif %} +
+
+
+
+
+ {{ form.flexible_scheduling }} + +
+
+
+
+ + +
+ + +
+
+ Contact Information +
+ +
+
+
+ + {{ form.contact_method }} + {% if form.contact_method.errors %} +
{{ form.contact_method.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.contact_phone }} + {% if form.contact_phone.errors %} +
{{ form.contact_phone.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.contact_email }} + {% if form.contact_email.errors %} +
{{ form.contact_email.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+ Special Requirements & Accommodations +
+ +
+
+
+ {{ form.requires_interpreter }} + +
+ + +
+
+
+ {{ form.transportation_needed }} + +
+
+
+ +
+
+
+ + {{ form.accessibility_requirements }} + Describe any special accessibility needs (wheelchair access, hearing assistance, etc.) + {% if form.accessibility_requirements.errors %} +
{{ form.accessibility_requirements.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+ Insurance & Authorization +
+ +
+
+
+ {{ form.insurance_verified }} + +
+
+
+
+ {{ form.authorization_required }} + +
+
+
+
+ + +
+
+ Referral Information +
+ +
+
+
+ + {{ form.referring_provider }} + {% if form.referring_provider.errors %} +
{{ form.referring_provider.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.referral_date }} + {% if form.referral_date.errors %} +
{{ form.referral_date.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.referral_urgency }} + {% if form.referral_urgency.errors %} +
{{ form.referral_urgency.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+ Additional Notes +
+ +
+
+
+ + {{ form.notes }} + {% if form.notes.errors %} +
{{ form.notes.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+
+ + Cancel + +
+ +
+
+
+
+
+
+
+ +{% endblock %} + +{% block extra_js %} + + + + +{% endblock %} + diff --git a/appointments/templates/appointments/waiting_list/waitinglistentry_list.html b/appointments/templates/appointments/waiting_list/waitinglistentry_list.html new file mode 100644 index 00000000..af70bb31 --- /dev/null +++ b/appointments/templates/appointments/waiting_list/waitinglistentry_list.html @@ -0,0 +1,299 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Waiting List Management{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} + + + + + +

+ + Waiting List Management + Manage patient waiting list entries +

+ + + +
+ +
+

+ Waiting List Entries +

+ +
+ + + +
+ +
+
+
+
+
+
+
Total Waiting
+

{{ waitinglistentry_list|length }}

+
+
+ +
+
+
+
+
+
+
+
+
+
+
Pending
+

{{ waitinglistentry_list|length }}

+
+
+ +
+
+
+
+
+
+
+
+
+
+
Priority
+

0

+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avg. Wait Time
+

5 days

+
+
+ +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+ + + +
+ + + + + + + + + + + + + + + + {% for entry in waitinglistentry_list %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
#PatientAppointment TypeDesired DateDesired TimeStatusWait TimePriorityActions
{{ forloop.counter }} +
+
+
+ {{ entry.patient.first_name|first }}{{ entry.patient.last_name|first }} +
+
+
+
{{ entry.patient.get_full_name }}
+ ID: {{ entry.patient.patient_id }} +
+
+
+ {{ entry.appointment_type.name }} + + {% if entry.desired_date %} + {{ entry.desired_date|date:"M d, Y" }} + {% else %} + Any date + {% endif %} + + {% if entry.desired_time %} + {{ entry.desired_time|time:"g:i A" }} + {% else %} + Any time + {% endif %} + + {% if entry.status == 'pending' %} + Pending + {% elif entry.status == 'contacted' %} + Contacted + {% elif entry.status == 'scheduled' %} + Scheduled + {% elif entry.status == 'cancelled' %} + Cancelled + {% else %} + {{ entry.status|title }} + {% endif %} + + {{ entry.created_at|timesince }} + + Normal + + +
+
+ +

No waiting list entries found

+ + Add First Entry + +
+
+
+ +
+ +
+ +{% endblock %} + +{% block extra_js %} + + + + + + + + + + +{% endblock %} + diff --git a/appointments/templates/appointments/waitlist/waitlist_management.html b/appointments/templates/appointments/waiting_list/waitlist_management.html similarity index 95% rename from appointments/templates/appointments/waitlist/waitlist_management.html rename to appointments/templates/appointments/waiting_list/waitlist_management.html index 526575f0..ddec9a03 100644 --- a/appointments/templates/appointments/waitlist/waitlist_management.html +++ b/appointments/templates/appointments/waiting_list/waitlist_management.html @@ -4,8 +4,8 @@ {% block title %}Waitlist Management{% endblock %} {% block css %} - - + + {% endblock %} {% block content %} @@ -163,15 +163,6 @@

Current Waitlist

- {% if messages %} - {% for message in messages %} - - {% endfor %} - {% endif %} -
@@ -405,10 +396,10 @@ {% endblock %} {% block js %} - - - - + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_detail (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_detail (1).html new file mode 100644 index 00000000..26fc8239 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_detail (1).html @@ -0,0 +1,649 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Surgical Note - {{ note.patient.get_full_name }}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ +
+
+ +

+ Surgical Note Details +

+
+
+
+ + Back to List + + {% if note.status != 'signed' %} + + Edit + + {% endif %} + +
+ + +
+
+
+
+ + +
+
+
+

{{ note.procedure_name }}

+
+
+

Patient: {{ note.patient.get_full_name }}

+

Patient ID: {{ note.patient.patient_id }}

+

Date of Birth: {{ note.patient.date_of_birth|date:"M d, Y" }}

+
+
+

Surgery Date: {{ note.surgery_date|date:"M d, Y" }}

+

Surgeon: {{ note.surgeon.get_full_name }}

+

Operating Room: {{ note.operating_room.name }}

+
+
+
+
+
+ + {{ note.get_status_display }} + +
+
+ + {{ note.get_priority_display }} Priority + +
+

Note ID: {{ note.id }}

+
+
+
+ + +
+
+ Pre-operative Information +
+
+
+
+
Pre-operative Diagnosis
+
{{ note.preoperative_diagnosis|default:"Not specified" }}
+
+
+
Planned Procedure
+
{{ note.planned_procedure|default:"Not specified" }}
+
+
+
Anesthesia Type
+
{{ note.get_anesthesia_type_display|default:"Not specified" }}
+
+
+
ASA Classification
+
{{ note.asa_classification|default:"Not specified" }}
+
+
+ + {% if note.preoperative_notes %} +
+
Pre-operative Notes
+
{{ note.preoperative_notes|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Operative Procedure +
+
+
+
+
Actual Procedure
+
{{ note.actual_procedure|default:"Not specified" }}
+
+
+
Procedure Duration
+
{{ note.procedure_duration|default:"Not specified" }}
+
+
+
Incision Type
+
{{ note.incision_type|default:"Not specified" }}
+
+
+
Closure Method
+
{{ note.closure_method|default:"Not specified" }}
+
+
+ + {% if note.operative_findings %} +
+
Operative Findings
+
{{ note.operative_findings|linebreaks }}
+
+ {% endif %} + + {% if note.procedure_description %} +
+
Detailed Procedure Description
+
{{ note.procedure_description|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Post-operative Information +
+
+
+
+
Post-operative Diagnosis
+
{{ note.postoperative_diagnosis|default:"Not specified" }}
+
+
+
Estimated Blood Loss
+
{{ note.estimated_blood_loss|default:"Not specified" }}
+
+
+
Complications
+
{{ note.complications|default:"None reported" }}
+
+
+
Condition at End
+
{{ note.condition_at_end|default:"Not specified" }}
+
+
+ + {% if note.postoperative_instructions %} +
+
Post-operative Instructions
+
{{ note.postoperative_instructions|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Surgical Team +
+
+
+
+
Primary Surgeon
+
{{ note.surgeon.get_full_name }}
+
+ {% if note.assistant_surgeon %} +
+
Assistant Surgeon
+
{{ note.assistant_surgeon.get_full_name }}
+
+ {% endif %} + {% if note.anesthesiologist %} +
+
Anesthesiologist
+
{{ note.anesthesiologist.get_full_name }}
+
+ {% endif %} + {% if note.scrub_nurse %} +
+
Scrub Nurse
+
{{ note.scrub_nurse.get_full_name }}
+
+ {% endif %} +
+
+
+ + + {% if note.revisions.exists %} +
+
+ Revision History +
+
+
+ {% for revision in note.revisions.all %} +
+
+
+
+
{{ revision.get_action_display }}
+ {{ revision.created_at|date:"M d, Y g:i A" }} +
+

By: {{ revision.created_by.get_full_name }}

+ {% if revision.reason %} +

Reason: {{ revision.reason }}

+ {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endif %} + + + {% if note.status == 'signed' %} +
+
+ Electronic Signature +
+
+
+
+ +
Electronically Signed
+

Signed by: {{ note.signed_by.get_full_name }}

+

Date: {{ note.signed_at|date:"M d, Y g:i A" }}

+

IP Address: {{ note.signature_ip|default:"Not recorded" }}

+
+
+
+
+ {% elif note.status == 'completed' %} +
+
+ Electronic Signature Required +
+

This note is complete and ready for electronic signature.

+ +
+ {% endif %} +
+ + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_detail.html b/operating_theatre/templates/operating_theatre/others/surgical_note_detail.html new file mode 100644 index 00000000..26fc8239 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_detail.html @@ -0,0 +1,649 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Surgical Note - {{ note.patient.get_full_name }}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ +
+
+ +

+ Surgical Note Details +

+
+
+
+ + Back to List + + {% if note.status != 'signed' %} + + Edit + + {% endif %} + +
+ + +
+
+
+
+ + +
+
+
+

{{ note.procedure_name }}

+
+
+

Patient: {{ note.patient.get_full_name }}

+

Patient ID: {{ note.patient.patient_id }}

+

Date of Birth: {{ note.patient.date_of_birth|date:"M d, Y" }}

+
+
+

Surgery Date: {{ note.surgery_date|date:"M d, Y" }}

+

Surgeon: {{ note.surgeon.get_full_name }}

+

Operating Room: {{ note.operating_room.name }}

+
+
+
+
+
+ + {{ note.get_status_display }} + +
+
+ + {{ note.get_priority_display }} Priority + +
+

Note ID: {{ note.id }}

+
+
+
+ + +
+
+ Pre-operative Information +
+
+
+
+
Pre-operative Diagnosis
+
{{ note.preoperative_diagnosis|default:"Not specified" }}
+
+
+
Planned Procedure
+
{{ note.planned_procedure|default:"Not specified" }}
+
+
+
Anesthesia Type
+
{{ note.get_anesthesia_type_display|default:"Not specified" }}
+
+
+
ASA Classification
+
{{ note.asa_classification|default:"Not specified" }}
+
+
+ + {% if note.preoperative_notes %} +
+
Pre-operative Notes
+
{{ note.preoperative_notes|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Operative Procedure +
+
+
+
+
Actual Procedure
+
{{ note.actual_procedure|default:"Not specified" }}
+
+
+
Procedure Duration
+
{{ note.procedure_duration|default:"Not specified" }}
+
+
+
Incision Type
+
{{ note.incision_type|default:"Not specified" }}
+
+
+
Closure Method
+
{{ note.closure_method|default:"Not specified" }}
+
+
+ + {% if note.operative_findings %} +
+
Operative Findings
+
{{ note.operative_findings|linebreaks }}
+
+ {% endif %} + + {% if note.procedure_description %} +
+
Detailed Procedure Description
+
{{ note.procedure_description|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Post-operative Information +
+
+
+
+
Post-operative Diagnosis
+
{{ note.postoperative_diagnosis|default:"Not specified" }}
+
+
+
Estimated Blood Loss
+
{{ note.estimated_blood_loss|default:"Not specified" }}
+
+
+
Complications
+
{{ note.complications|default:"None reported" }}
+
+
+
Condition at End
+
{{ note.condition_at_end|default:"Not specified" }}
+
+
+ + {% if note.postoperative_instructions %} +
+
Post-operative Instructions
+
{{ note.postoperative_instructions|linebreaks }}
+
+ {% endif %} +
+
+ + +
+
+ Surgical Team +
+
+
+
+
Primary Surgeon
+
{{ note.surgeon.get_full_name }}
+
+ {% if note.assistant_surgeon %} +
+
Assistant Surgeon
+
{{ note.assistant_surgeon.get_full_name }}
+
+ {% endif %} + {% if note.anesthesiologist %} +
+
Anesthesiologist
+
{{ note.anesthesiologist.get_full_name }}
+
+ {% endif %} + {% if note.scrub_nurse %} +
+
Scrub Nurse
+
{{ note.scrub_nurse.get_full_name }}
+
+ {% endif %} +
+
+
+ + + {% if note.revisions.exists %} +
+
+ Revision History +
+
+
+ {% for revision in note.revisions.all %} +
+
+
+
+
{{ revision.get_action_display }}
+ {{ revision.created_at|date:"M d, Y g:i A" }} +
+

By: {{ revision.created_by.get_full_name }}

+ {% if revision.reason %} +

Reason: {{ revision.reason }}

+ {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endif %} + + + {% if note.status == 'signed' %} +
+
+ Electronic Signature +
+
+
+
+ +
Electronically Signed
+

Signed by: {{ note.signed_by.get_full_name }}

+

Date: {{ note.signed_at|date:"M d, Y g:i A" }}

+

IP Address: {{ note.signature_ip|default:"Not recorded" }}

+
+
+
+
+ {% elif note.status == 'completed' %} +
+
+ Electronic Signature Required +
+

This note is complete and ready for electronic signature.

+ +
+ {% endif %} +
+ + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_form (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_form (1).html new file mode 100644 index 00000000..c8a073a4 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_form (1).html @@ -0,0 +1,318 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}{% if object %}Edit Surgical Note{% else %}Create Surgical Note{% endif %}{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} + + + + + +

+ + {% if object %}Edit Surgical Note{% else %}Create New Surgical Note{% endif %} + {% if object %}Update existing surgical record{% else %}Document a new surgical procedure{% endif %} +

+ + + +
+
+

+ Surgical Note Details +

+ +
+ +
+
+ {% csrf_token %} + + +
+
Patient & Procedure Information
+ +
+
+
+ + {{ form.patient }} + {% if form.patient.errors %} +
{{ form.patient.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.surgeon }} + {% if form.surgeon.errors %} +
{{ form.surgeon.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.procedure_name }} + {% if form.procedure_name.errors %} +
{{ form.procedure_name.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.procedure_code }} + {% if form.procedure_code.errors %} +
{{ form.procedure_code.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.procedure_date }} + {% if form.procedure_date.errors %} +
{{ form.procedure_date.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.procedure_time }} + {% if form.procedure_time.errors %} +
{{ form.procedure_time.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
Pre-operative Details
+ +
+ + {{ form.pre_operative_diagnosis }} + {% if form.pre_operative_diagnosis.errors %} +
{{ form.pre_operative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.anesthesia_type }} + {% if form.anesthesia_type.errors %} +
{{ form.anesthesia_type.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.anesthesiologist }} + {% if form.anesthesiologist.errors %} +
{{ form.anesthesiologist.errors.0 }}
+ {% endif %} +
+
+ + +
+
Intra-operative Details
+ +
+ + {{ form.post_operative_diagnosis }} + {% if form.post_operative_diagnosis.errors %} +
{{ form.post_operative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.procedure_description }} + {% if form.procedure_description.errors %} +
{{ form.procedure_description.errors.0 }}
+ {% endif %} +
+ +
+
+
+ + {{ form.estimated_blood_loss }} + {% if form.estimated_blood_loss.errors %} +
{{ form.estimated_blood_loss.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.complications }} + {% if form.complications.errors %} +
{{ form.complications.errors.0 }}
+ {% endif %} +
+
+
+ +
+ + {{ form.specimens_sent }} + {% if form.specimens_sent.errors %} +
{{ form.specimens_sent.errors.0 }}
+ {% endif %} +
+
+ + +
+
Post-operative Details
+ +
+ + {{ form.post_operative_orders }} + {% if form.post_operative_orders.errors %} +
{{ form.post_operative_orders.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.condition_on_discharge }} + {% if form.condition_on_discharge.errors %} +
{{ form.condition_on_discharge.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.follow_up_plan }} + {% if form.follow_up_plan.errors %} +
{{ form.follow_up_plan.errors.0 }}
+ {% endif %} +
+
+ + +
+
Signature
+ +
+ + {{ form.signed_by }} + {% if form.signed_by.errors %} +
{{ form.signed_by.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.signature_date }} + {% if form.signature_date.errors %} +
{{ form.signature_date.errors.0 }}
+ {% endif %} +
+
+ + +
+
+
+ + Cancel + +
+ +
+
+
+
+ +
+
+ +{% endblock %} + +{% block extra_js %} + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_form.html b/operating_theatre/templates/operating_theatre/others/surgical_note_form.html new file mode 100644 index 00000000..c20f042f --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_form.html @@ -0,0 +1,745 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{% if object %}Edit Surgical Note{% else %}Create Surgical Note{% endif %}{% endblock %} + +{% block extra_css %} + + + + +{% endblock %} + +{% block content %} +
+
+ +
+
+ +
+
+ + +
+
+
+
1
+
Basic Info
+
+
+
2
+
Procedure
+
+
+
3
+
Documentation
+
+
+
4
+
Review
+
+
+
+
+
+
+ +
+ {% csrf_token %} + + + {% if not object %} +
+
Use Template
+
+
+ +
+
+ +
+
+
+ {% endif %} + + +
+
+

+ + Basic Information +

+
+ +
+
+
+ + {{ form.patient }} + {% if form.patient.errors %} +
{{ form.patient.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.surgical_case }} + {% if form.surgical_case.errors %} +
{{ form.surgical_case.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.surgery_date }} + {% if form.surgery_date.errors %} +
{{ form.surgery_date.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.start_time }} + {% if form.start_time.errors %} +
{{ form.start_time.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.end_time }} + {% if form.end_time.errors %} +
{{ form.end_time.errors.0 }}
+ {% endif %} +
+
+
+ +
+
+
+ + {{ form.primary_surgeon }} + {% if form.primary_surgeon.errors %} +
{{ form.primary_surgeon.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.assistant_surgeons }} + {% if form.assistant_surgeons.errors %} +
{{ form.assistant_surgeons.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+

+ + Procedure Details +

+
+ +
+
+
+ + {{ form.procedure_name }} + {% if form.procedure_name.errors %} +
{{ form.procedure_name.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.cpt_code }} + {% if form.cpt_code.errors %} +
{{ form.cpt_code.errors.0 }}
+ {% endif %} +
+
+
+ +
+ + {{ form.preoperative_diagnosis }} + {% if form.preoperative_diagnosis.errors %} +
{{ form.preoperative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.postoperative_diagnosis }} + {% if form.postoperative_diagnosis.errors %} +
{{ form.postoperative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+
+
+ + {{ form.anesthesia_type }} + {% if form.anesthesia_type.errors %} +
{{ form.anesthesia_type.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.anesthesiologist }} + {% if form.anesthesiologist.errors %} +
{{ form.anesthesiologist.errors.0 }}
+ {% endif %} +
+
+
+
+ + +
+
+

+ + Surgical Documentation +

+
+ +
+ + {{ form.operative_technique }} + {% if form.operative_technique.errors %} +
{{ form.operative_technique.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.findings }} + {% if form.findings.errors %} +
{{ form.findings.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.complications }} + {% if form.complications.errors %} +
{{ form.complications.errors.0 }}
+ {% endif %} +
+ +
+
+
+ + {{ form.estimated_blood_loss }} + {% if form.estimated_blood_loss.errors %} +
{{ form.estimated_blood_loss.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.specimens_sent }} + {% if form.specimens_sent.errors %} +
{{ form.specimens_sent.errors.0 }}
+ {% endif %} +
+
+
+ +
+ + {{ form.postoperative_instructions }} + {% if form.postoperative_instructions.errors %} +
{{ form.postoperative_instructions.errors.0 }}
+ {% endif %} +
+
+ + +
+
+

+ + Electronic Signature +

+
+ +
+
+
+ +
+
+
+ +
Click to sign or upload signature image
+
+
+
+
+ + + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+ + Cancel + + +
+
+
+ +
+
+ + + +{% endblock %} + +{% block extra_js %} + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_list (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_list (1).html new file mode 100644 index 00000000..69c4314e --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_list (1).html @@ -0,0 +1,516 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Surgical Notes{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} +
+ +
+
+ +

+ Surgical Notes Management +

+
+ +
+ + +
+
+
+ +
+
{{ stats.total_notes|default:0 }}
+
Total Notes
+
+ +
+
+ +
+
{{ stats.draft_notes|default:0 }}
+
Draft Notes
+
+ +
+
+ +
+
{{ stats.completed_notes|default:0 }}
+
Completed
+
+ +
+
+ +
+
{{ stats.signed_notes|default:0 }}
+
Signed
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ +
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ Surgical Notes +
+
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + {% for note in notes %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
+ + PatientProcedureSurgeonDateStatusPriorityLast ModifiedActions
+ + +
+
+ +
+
+
{{ note.patient.get_full_name }}
+ ID: {{ note.patient.patient_id }} +
+
+
+
{{ note.procedure_name }}
+ {{ note.procedure_code|default:"" }} +
+
{{ note.surgeon.get_full_name }}
+ {{ note.surgeon.specialization|default:"" }} +
+
{{ note.surgery_date|date:"M d, Y" }}
+ {{ note.surgery_date|time:"g:i A" }} +
+ + {{ note.get_status_display }} + + + + {{ note.get_priority_display }} + + +
{{ note.updated_at|date:"M d, Y" }}
+ {{ note.updated_at|time:"g:i A" }} +
+
+ + + + {% if note.status != 'signed' %} + + + + {% endif %} + + {% if note.status != 'signed' %} + + + + {% endif %} +
+
+ +

No surgical notes found

+ + Create First Note + +
+
+
+
+
+{% endblock %} + +{% block extra_js %} + + + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_list.html b/operating_theatre/templates/operating_theatre/others/surgical_note_list.html new file mode 100644 index 00000000..69c4314e --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_list.html @@ -0,0 +1,516 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Surgical Notes{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} +
+ +
+
+ +

+ Surgical Notes Management +

+
+ +
+ + +
+
+
+ +
+
{{ stats.total_notes|default:0 }}
+
Total Notes
+
+ +
+
+ +
+
{{ stats.draft_notes|default:0 }}
+
Draft Notes
+
+ +
+
+ +
+
{{ stats.completed_notes|default:0 }}
+
Completed
+
+ +
+
+ +
+
{{ stats.signed_notes|default:0 }}
+
Signed
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ +
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ Surgical Notes +
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + {% for note in notes %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
+ + PatientProcedureSurgeonDateStatusPriorityLast ModifiedActions
+ + +
+
+ +
+
+
{{ note.patient.get_full_name }}
+ ID: {{ note.patient.patient_id }} +
+
+
+
{{ note.procedure_name }}
+ {{ note.procedure_code|default:"" }} +
+
{{ note.surgeon.get_full_name }}
+ {{ note.surgeon.specialization|default:"" }} +
+
{{ note.surgery_date|date:"M d, Y" }}
+ {{ note.surgery_date|time:"g:i A" }} +
+ + {{ note.get_status_display }} + + + + {{ note.get_priority_display }} + + +
{{ note.updated_at|date:"M d, Y" }}
+ {{ note.updated_at|time:"g:i A" }} +
+
+ + + + {% if note.status != 'signed' %} + + + + {% endif %} + + {% if note.status != 'signed' %} + + + + {% endif %} +
+
+ +

No surgical notes found

+ + Create First Note + +
+
+
+
+
+{% endblock %} + +{% block extra_js %} + + + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete (1).html new file mode 100644 index 00000000..cbe45d74 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete (1).html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Confirm Delete Surgical Note Template{% endblock %} + +{% block content %} + + + + + +

+ + Confirm Delete Surgical Note Template + Permanently remove this reusable template +

+ + + +
+
+

+ Warning: This action cannot be undone! +

+
+
+
+

Are you absolutely sure you want to delete this surgical note template?

+

Deleting the template {{ object.name }} will permanently remove it from the system.

+

Any surgical notes created using this template will retain their content, but you will no longer be able to use this template for new notes.

+
+ +
+
+
+ +

{{ object.name }}

+
+
+ +

{{ object.specialty }}

+
+
+
+
+ +

{{ object.procedure_type }}

+
+
+ +

{{ object.updated_at|date:"M d, Y H:i" }}

+
+
+
+ +
+ {% csrf_token %} +
+ + Cancel + + +
+
+
+
+ +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete.html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete.html new file mode 100644 index 00000000..63d3ca08 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_confirm_delete.html @@ -0,0 +1,837 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Delete Template - {{ object.name }}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ +
+
+ +
+
+ + +
+
+ +
+ +

Delete "{{ object.name }}"?

+ +

+ You are about to permanently delete this surgical note template. This action cannot be undone and may have significant impact on your hospital's documentation workflow. +

+ + +
+
+ + Template Information +
+ +
+
+
Template Name
+
{{ object.name }}
+
+ +
+
Specialty
+
{{ object.get_specialty_display }}
+
+ +
+
Created By
+
{{ object.created_by.get_full_name }}
+
+ +
+
Created Date
+
{{ object.created_at|date:"F d, Y" }}
+
+ +
+
Last Modified
+
{{ object.updated_at|date:"F d, Y g:i A" }}
+
+ +
+
Status
+
+ {% if object.is_active %} + Active + {% elif object.is_draft %} + Draft + {% else %} + Archived + {% endif %} +
+
+
+
+ + +
+
+ + Usage Statistics +
+
+
+
{{ object.usage_count|default:0 }}
+
Times Used
+
+
+
{{ object.active_notes_count|default:0 }}
+
Active Notes
+
+
+
{{ object.users_count|default:0 }}
+
Users
+
+
+
{{ object.departments_count|default:0 }}
+
Departments
+
+
+
+ + +
+
+ + Deletion Impact +
+
    +
  • All template content and structure will be permanently lost
  • +
  • {{ object.usage_count|default:0 }} existing surgical notes using this template will lose their template reference
  • +
  • Users who have bookmarked this template will lose access
  • +
  • Template sharing permissions will be revoked
  • +
  • Version history and audit trail will be preserved for compliance
  • + {% if object.is_active %} +
  • Warning: This is an active template currently in use
  • + {% endif %} + {% if object.usage_count > 10 %} +
  • High Impact: This template is heavily used ({{ object.usage_count }} times)
  • + {% endif %} +
+
+ + + {% if object.dependent_templates.exists %} +
+
+ + Dependent Templates +
+

The following templates are based on this template and may be affected:

+ {% for dependent in object.dependent_templates.all %} +
+ {{ dependent.name }} + {{ dependent.usage_count|default:0 }} uses +
+ {% endfor %} +
+ {% endif %} + + +
+
+ + Template Preview (Last 10 lines) +
+
+{{ object.content|default:"No content available"|truncatewords:50 }} + +--- +Template Fields: {{ object.field_count|default:0 }} +Last Modified: {{ object.updated_at|date:"F d, Y g:i A" }} +Version: {{ object.version|default:"1.0" }} +
+
+ + +
+
+ + Before You Delete +
+
    +
  1. +
    + Backup Important Data
    + Export the template if you might need it later +
    +
  2. +
  3. +
    + Notify Affected Users
    + Inform users who regularly use this template +
    +
  4. +
  5. +
    + Check Dependencies
    + Ensure no critical workflows depend on this template +
    +
  6. +
  7. +
    + Provide Alternative
    + Suggest alternative templates for users +
    +
  8. +
+
+ + +
+ {% csrf_token %} + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
This information will be logged for audit purposes
+
+ + +
+ + +
Type the exact template name: {{ object.name }}
+
+ + +
+ + Cancel + + +
+
+ + +
+ + Security Notice: This action will be logged and may be subject to review by hospital administration. The deletion will be recorded in the audit trail with your user information and timestamp. +
+ + +
+ + Deletion will be performed by: {{ user.get_full_name }} ({{ user.username }}) + on +
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail (1).html new file mode 100644 index 00000000..3e28a80b --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail (1).html @@ -0,0 +1,136 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Surgical Note Template Details{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} + + + + + +

+ + Surgical Note Template Details + Comprehensive view of surgical note template +

+ + + +
+
+

+ Template: {{ object.name }} +

+ +
+ +
+
+
+ +
+
Template Information
+
+
Template Name:
+
{{ object.name }}
+
+
+
Specialty:
+
{{ object.specialty }}
+
+
+
Procedure Type:
+
{{ object.procedure_type }}
+
+
+
Description:
+
{{ object.description|default:\'N/A\'|linebreaksbr }}
+
+
+ + +
+
Template Content
+
+
Pre-operative Diagnosis:
+
{{ object.pre_operative_diagnosis|default:\'N/A\'|linebreaksbr }}
+
+
+
Anesthesia Type:
+
{{ object.anesthesia_type|default: +\'N/A\' }}
+
+
+
Post-operative Diagnosis:
+
{{ object.post_operative_diagnosis|default: +\'N/A\'|linebreaksbr }}
+
+
+
Procedure Description:
+
{{ object.procedure_description|default: +\'N/A\'|linebreaksbr }}
+
+
+
Post-operative Orders:
+
{{ object.post_operative_orders|default: +\'N/A\'|linebreaksbr }}
+
+
+
Follow-up Plan:
+
{{ object.follow_up_plan|default: +\'N/A\'|linebreaksbr }}
+
+
+ + +
+
Metadata
+
+
Created At:
+
{{ object.created_at|date:"M d, Y H:i" }} by {{ object.created_by.get_full_name|default: +\'N/A\' }}
+
+
+
Last Updated:
+
{{ object.updated_at|date:"M d, Y H:i" }}
+
+
+
+
+
+
+ +{% endblock %} + +{% block extra_js %} +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail.html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail.html new file mode 100644 index 00000000..022f63f0 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_detail.html @@ -0,0 +1,937 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ object.name }} - Template Details{% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block content %} +
+
+ +
+
+
+

{{ object.name }}

+

{{ object.description }}

+
+
+
+ +
+
+
{{ object.get_specialty_display }}
+ Specialty +
+
+
+
+ +
+
+
{{ object.created_by.get_full_name }}
+ Created by +
+
+
+
+ +
+
+
{{ object.created_at|date:"M d, Y" }}
+ Created +
+
+
+
+ +
+
+
{{ object.usage_count|default:0 }}
+ Times Used +
+
+
+
+
+ {% if object.is_active %} + + Active + + {% elif object.is_draft %} + + Draft + + {% else %} + + Archived + + {% endif %} +
+
+
+ + +
+ + Use This Template + + + Edit Template + + + + + + Back to Templates + +
+ + +
+
+
{{ object.usage_count|default:0 }}
+
Total Usage
+
+
+
{{ object.field_count|default:0 }}
+
Form Fields
+
+
+
{{ object.version|default:"1.0" }}
+
Version
+
+
+
{{ object.rating|default:"4.5" }}
+
Rating
+
+
+ + +
+ +
+
+

+ + Template Details +

+
+ + +
+
Tags
+
+ {% for tag in object.tags.all %} + {{ tag.name }} + {% empty %} + General + Surgery + Documentation + {% endfor %} +
+
+ + +
+
Description
+

{{ object.description|default:"No description provided." }}

+
+ + +
+
Template Fields
+
+
+
Patient Information
+ Required +
Basic patient demographics and identifiers
+
+
+
Procedure Details
+ Required +
Surgical procedure name and codes
+
+
+
Preoperative Diagnosis
+ Required +
Diagnosis before surgery
+
+
+
Postoperative Diagnosis
+ Required +
Diagnosis after surgery
+
+
+
Operative Technique
+ Required +
Detailed surgical procedure description
+
+
+
Complications
+ Optional +
Any complications during surgery
+
+
+
+ + +
+
Template Structure
+
+SURGICAL NOTE TEMPLATE - {{ object.name|upper }} + +PATIENT INFORMATION: +- Name: [Patient Name] +- DOB: [Date of Birth] +- MRN: [Medical Record Number] +- Date of Surgery: [Surgery Date] + +PROCEDURE INFORMATION: +- Primary Surgeon: [Surgeon Name] +- Assistant Surgeon(s): [Assistant Names] +- Anesthesiologist: [Anesthesiologist Name] +- Anesthesia Type: [Anesthesia Type] + +DIAGNOSES: +- Preoperative Diagnosis: [Pre-op Diagnosis] +- Postoperative Diagnosis: [Post-op Diagnosis] + +PROCEDURE PERFORMED: +[Procedure Name and Details] + +OPERATIVE TECHNIQUE: +[Detailed description of surgical technique and steps] + +FINDINGS: +[Surgical findings and observations] + +COMPLICATIONS: +[Any complications encountered] + +ESTIMATED BLOOD LOSS: [Amount] mL + +SPECIMENS SENT: +[Specimens sent to pathology] + +POSTOPERATIVE INSTRUCTIONS: +[Post-operative care instructions] + +SURGEON SIGNATURE: ________________________ +Date: _______________ +
+
+
+ + +
+ +
+
+
+ + Usage Statistics +
+
+
+ +
+
+ + +
+
+
+ + Version History +
+
+ +
+
+ v{{ object.version|default:"1.0" }} + {{ object.updated_at|date:"M d, Y" }} +
+
+ Current version - {{ object.description|truncatewords:10 }} +
+
+ +
+
+ v0.9 + {{ object.created_at|date:"M d, Y" }} +
+
+ Initial template creation with basic fields +
+
+
+ + +
+
+
+ + Quick Stats +
+
+ +
+
+ Completion Rate + 95% +
+
+
+
+
+ +
+
+ User Satisfaction + 4.8/5 +
+
+
+
+
+ +
+
+ Error Rate + 2% +
+
+
+
+
+ +
+ Based on last 30 days usage +
+
+
+
+ + +
+
+

+ + Template Preview +

+
+ +
+
+
+
Patient Information
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
Procedure Details
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
Surgical Documentation
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_form (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_form (1).html new file mode 100644 index 00000000..8ba4e857 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_form (1).html @@ -0,0 +1,207 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}{% if object %}Edit Surgical Note Template{% else %}Create Surgical Note Template{% endif %}{% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block content %} + + + + + +

+ + {% if object %}Edit Surgical Note Template{% else %}Create New Surgical Note Template{% endif %} + {% if object %}Update existing template{% else %}Define a new reusable template for surgical notes{% endif %} +

+ + + +
+
+

+ Template Details +

+ +
+ +
+
+ {% csrf_token %} + + +
+
Template Information
+ +
+ + {{ form.name }} + {% if form.name.errors %} +
{{ form.name.errors.0 }}
+ {% endif %} +
+ +
+
+
+ + {{ form.specialty }} + {% if form.specialty.errors %} +
{{ form.specialty.errors.0 }}
+ {% endif %} +
+
+
+
+ + {{ form.procedure_type }} + {% if form.procedure_type.errors %} +
{{ form.procedure_type.errors.0 }}
+ {% endif %} +
+
+
+ +
+ + {{ form.description }} + {% if form.description.errors %} +
{{ form.description.errors.0 }}
+ {% endif %} +
+
+ + +
+
Template Content (Pre-fill fields for new notes)
+ +
+ + {{ form.pre_operative_diagnosis }} + {% if form.pre_operative_diagnosis.errors %} +
{{ form.pre_operative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.anesthesia_type }} + {% if form.anesthesia_type.errors %} +
{{ form.anesthesia_type.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.post_operative_diagnosis }} + {% if form.post_operative_diagnosis.errors %} +
{{ form.post_operative_diagnosis.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.procedure_description }} + {% if form.procedure_description.errors %} +
{{ form.procedure_description.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.post_operative_orders }} + {% if form.post_operative_orders.errors %} +
{{ form.post_operative_orders.errors.0 }}
+ {% endif %} +
+ +
+ + {{ form.follow_up_plan }} + {% if form.follow_up_plan.errors %} +
{{ form.follow_up_plan.errors.0 }}
+ {% endif %} +
+
+ + +
+
+
+ + Cancel + +
+ +
+
+
+
+
+
+
+ +{% endblock %} + +{% block extra_js %} + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_form.html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_form.html new file mode 100644 index 00000000..818a0333 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_form.html @@ -0,0 +1,1110 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{% if object %}Edit Template{% else %}Create Template{% endif %}{% endblock %} + +{% block extra_css %} + + + + + +{% endblock %} + +{% block content %} +
+
+ +
+
+
+
+

+ + {% if object %}Edit Template{% else %}Template Builder{% endif %} +

+

+ {% if object %} + Update and modify your surgical note template + {% else %} + Create a comprehensive surgical note template with custom fields and structure + {% endif %} +

+
+
+ +
+
+
+ +
+ +
+
+
+
1
+
Basic Info
+
+
+
2
+
Fields
+
+
+
3
+
Layout
+
+
+
4
+
Review
+
+
+
+
+
+
+ +
+ {% csrf_token %} + + +
+
+

+ + Basic Information +

+ +
+ +
+
+
+
+ + {{ form.name }} + {% if form.name.errors %} +
{{ form.name.errors.0 }}
+ {% endif %} +
Choose a descriptive name for your template
+
+
+
+
+ + {{ form.specialty }} + {% if form.specialty.errors %} +
{{ form.specialty.errors.0 }}
+ {% endif %} +
+
+
+ +
+ + {{ form.description }} + {% if form.description.errors %} +
{{ form.description.errors.0 }}
+ {% endif %} +
Provide a brief description of when and how to use this template
+
+ +
+
+
+ + {{ form.tags }} + {% if form.tags.errors %} +
{{ form.tags.errors.0 }}
+ {% endif %} +
Add tags to help organize and find this template
+
+
+
+
+ + {{ form.status }} + {% if form.status.errors %} +
{{ form.status.errors.0 }}
+ {% endif %} +
+
+
+
+
+ + + {% if not object %} +
+
+

+ + Start from Template +

+
+ +
+

Choose a base template to start with, or create from scratch:

+
+
+
+
General Surgery
+ Standard surgical note template +
+
+
+
+
Cardiac Surgery
+ Specialized for cardiac procedures +
+
+
+
+
Orthopedic Surgery
+ Tailored for orthopedic procedures +
+
+
+
+
+ {% endif %} + + +
+
+

+ + Template Builder +

+
+ +
+ + + +
+ + +
+
+
+
Available Fields
+
+
+ Text +
+
+ Textarea +
+
+ Select +
+
+ Date +
+
+ Time +
+
+ Number +
+
+ Checkbox +
+
+ Signature +
+
+ + +
+ +
+
Template Fields
+
+
+ +

Click on field types to add them to your template

+
+
+
+
+
+ + +
+
+ + +
Use HTML and Django template syntax to create your template
+
+
+ + +
+
+
+ +
Template Preview
+

Build your template to see the preview here

+
+
+
+
+
+
+
+ + +
+
+
+ + +
+
+ + Cancel + + +
+
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + + + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_list (1).html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_list (1).html new file mode 100644 index 00000000..55e6550a --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_list (1).html @@ -0,0 +1,135 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Surgical Note Templates{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} + + + + + +

+ + Surgical Note Templates + Manage reusable templates for surgical notes +

+ + + +
+ +
+

+ Available Templates +

+ +
+ + + +
+
+ + + + + + + + + + + + + {% for template in object_list %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
#Template NameSpecialtyProcedure TypeLast UpdatedActions
{{ forloop.counter }}{{ template.name }}{{ template.specialty }}{{ template.procedure_type }}{{ template.updated_at|date:"M d, Y H:i" }} + +
+
+ +

No surgical note templates found.

+ + Create First Template + +
+
+
+
+ +
+ +{% endblock %} + +{% block extra_js %} + + + + + + + + + + +{% endblock %} + diff --git a/operating_theatre/templates/operating_theatre/others/surgical_note_template_list.html b/operating_theatre/templates/operating_theatre/others/surgical_note_template_list.html new file mode 100644 index 00000000..e4a0ffe0 --- /dev/null +++ b/operating_theatre/templates/operating_theatre/others/surgical_note_template_list.html @@ -0,0 +1,947 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Surgical Note Templates{% endblock %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} +
+
+ +
+
+
+

+ + Surgical Note Templates +

+

+ Manage and organize surgical note templates for consistent documentation across all procedures +

+
+ +
+
+ + +
+
+
+ +
+
{{ total_templates|default:0 }}
+
Total Templates
+
+ +
+
+ +
+
{{ active_templates|default:0 }}
+
Active Templates
+
+ +
+
+ +
+
{{ popular_templates|default:0 }}
+
Most Used
+
+ +
+
+ +
+
{{ recent_templates|default:0 }}
+
Recent Updates
+
+
+ + +
+
+ + Quick Actions +
+
+
+
+ +
+
Create New Template
+
Build a new surgical note template from scratch
+
+ +
+
+ +
+
Import Template
+
Import templates from external sources or files
+
+ +
+
+ +
+
Export Templates
+
Export selected templates for backup or sharing
+
+ +
+
+ +
+
Usage Analytics
+
View detailed usage statistics and insights
+
+
+
+ + +
+
+ + Filter Templates +
+
+
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + +
+
+

+ + Template Library +

+
+ + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + {% for template in templates %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Template NameSpecialtyStatusUsageCreatedLast UpdatedActions
+
+
+ +
+
+
{{ template.name }}
+ {{ template.description|truncatewords:10 }} +
+
+
+ + {{ template.get_specialty_display }} + + + {% if template.is_active %} + Active + {% elif template.is_draft %} + Draft + {% else %} + Archived + {% endif %} + +
+ {{ template.usage_count|default:0 }} +
+
+
+
+
+
{{ template.created_at|date:"M d, Y" }}
+ {{ template.created_by.get_full_name }} +
{{ template.updated_at|date:"M d, Y g:i A" }} + +
+ +
No Templates Found
+

Create your first surgical note template to get started.

+ + Create Template + +
+
+
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + + + + + +{% endblock %} + diff --git a/patients/.DS_Store b/patients/.DS_Store index b83f52ef..795fc496 100644 Binary files a/patients/.DS_Store and b/patients/.DS_Store differ diff --git a/patients/templates/patients/dashboard-last one.html b/patients/templates/patients/dashboard-last one.html new file mode 100644 index 00000000..d317773b --- /dev/null +++ b/patients/templates/patients/dashboard-last one.html @@ -0,0 +1,488 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Patient Management Dashboard{% endblock %} + +{% block extra_css %} + + + + +{% endblock %} + +{% block content %} + + + + + +

+ + Patient Management Dashboard + Comprehensive overview of patient care and management +

+ + + +
+
+
+
+
+
+
Total Patients
+

{{ stats.total_patients|default:0 }}

+ Active registrations +
+
+ +
+
+
+
+
+
+
+
+
+
+
Active Inpatients
+

{{ stats.active_inpatients|default:0 }}

+ Currently admitted +
+
+ +
+
+
+
+
+
+
+
+
+
+
Today's Visits
+

{{ stats.todays_visits|default:0 }}

+ Outpatient visits +
+
+ +
+
+
+
+
+
+
+
+
+
+
Critical Alerts
+

{{ stats.critical_alerts|default:0 }}

+ Require attention +
+
+ +
+
+
+
+
+
+ + + +
+
+ +
+
+ + + +
+ +
+ +
+
+

+ Recent Admissions +

+ +
+
+
+ + + + + + + + + + + + + {% for admission in recent_admissions %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
PatientRoomAdmission DateAttending PhysicianStatusActions
+
+
+ {{ admission.patient.first_name|first }}{{ admission.patient.last_name|first }} +
+
+
{{ admission.patient.get_full_name }}
+ MRN: {{ admission.patient.mrn }} +
+
+
+ {{ admission.room.number }} + {{ admission.room.ward.name }} + {{ admission.admission_date|date:"M d, Y H:i" }}{{ admission.attending_physician.get_full_name }} + {% if admission.status == 'ACTIVE' %} + Active + {% elif admission.status == 'PENDING' %} + Pending + {% else %} + {{ admission.get_status_display }} + {% endif %} + + +
+ +

No recent admissions

+
+
+
+
+ + +
+
+

+ Patient Activity Trends +

+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+
+

+ Critical Alerts +

+
+ +
+
+
+ {% for alert in critical_alerts %} +
+
+
+
{{ alert.title }}
+

{{ alert.message }}

+ + {{ alert.created_at|timesince }} ago + +
+ +
+
+ {% empty %} +
+ +

No critical alerts

+
+ {% endfor %} +
+
+ + +
+
+

+ Today's Schedule +

+ +
+
+ {% for appointment in todays_appointments %} +
+
+ {{ appointment.patient.first_name|first }}{{ appointment.patient.last_name|first }} +
+
+
{{ appointment.patient.get_full_name }}
+ {{ appointment.get_appointment_type_display }} +
+ {{ appointment.appointment_time|time:"g:i A" }} +
+
+
+ + {{ appointment.get_status_display }} + +
+
+ {% empty %} +
+ +

No appointments scheduled for today

+
+ {% endfor %} +
+
+ + +
+
+

+ Department Occupancy +

+
+
+ {% for dept in department_occupancy %} +
+
+ {{ dept.name }} + {{ dept.occupied }}/{{ dept.capacity }} +
+
+
+
+ {{ dept.occupancy_rate }}% occupancy +
+ {% empty %} +
+ +

No department data available

+
+ {% endfor %} +
+
+
+
+ +{% endblock %} + +{% block extra_js %} + + + + + +{% endblock %} +