2025-08-12 13:33:25 +03:00

1179 lines
32 KiB
Python

"""
Appointments app models for hospital management system.
Provides appointment scheduling, queue management, and telemedicine functionality.
"""
import uuid
from django.db import models
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
from django.utils import timezone
from django.conf import settings
from datetime import timedelta, datetime, time
import json
class AppointmentRequest(models.Model):
"""
Appointment request model for scheduling patient appointments.
"""
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'),
]
STATUS_CHOICES=[
('PENDING', 'Pending'),
('SCHEDULED', 'Scheduled'),
('CONFIRMED', 'Confirmed'),
('CHECKED_IN', 'Checked In'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('NO_SHOW', 'No Show'),
('RESCHEDULED', 'Rescheduled'),
]
TELEMEDICINE_PLATFORM_CHOICES=[
('ZOOM', 'Zoom'),
('TEAMS', 'Microsoft Teams'),
('WEBEX', 'Cisco Webex'),
('DOXY', 'Doxy.me'),
('CUSTOM', 'Custom Platform'),
('OTHER', 'Other'),
]
# Basic Identifiers
request_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique appointment request identifier'
)
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='appointment_requests',
help_text='Organization tenant'
)
# Patient Information
patient = models.ForeignKey(
'patients.PatientProfile',
on_delete=models.CASCADE,
related_name='appointment_requests',
help_text='Patient requesting appointment'
)
# Provider Information
provider = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='provider_appointments',
help_text='Healthcare provider'
)
# Appointment Details
appointment_type = models.CharField(
max_length=50,
choices=APPOINTMENT_TYPE_CHOICES,
help_text='Type of appointment'
)
specialty = models.CharField(
max_length=100,
choices=SPECIALTY_CHOICES,
help_text='Medical specialty'
)
# Scheduling Information
preferred_date = models.DateField(
help_text='Patient preferred appointment date'
)
preferred_time = models.TimeField(
blank=True,
null=True,
help_text='Patient preferred appointment time'
)
duration_minutes = models.PositiveIntegerField(
default=30,
validators=[MinValueValidator(15), MaxValueValidator(480)],
help_text='Appointment duration in minutes'
)
# Scheduling Flexibility
flexible_scheduling = models.BooleanField(
default=True,
help_text='Patient accepts alternative 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_times = models.JSONField(
default=list,
blank=True,
help_text='Acceptable time slots (JSON array)'
)
# Priority and Urgency
priority = models.CharField(
max_length=20,
choices=PRIORITY_CHOICES,
default='ROUTINE',
help_text='Appointment priority'
)
urgency_score = models.PositiveIntegerField(
default=1,
validators=[MinValueValidator(1), MaxValueValidator(10)],
help_text='Urgency score (1-10, 10 being most urgent)'
)
# Clinical Information
chief_complaint = models.TextField(
help_text='Patient chief complaint or reason for visit'
)
clinical_notes = models.TextField(
blank=True,
null=True,
help_text='Additional clinical notes'
)
referring_provider = models.CharField(
max_length=200,
blank=True,
null=True,
help_text='Referring provider name'
)
# 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_number = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Authorization number'
)
# Status and Workflow
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='PENDING',
help_text='Appointment status'
)
# Scheduled Information
scheduled_datetime = models.DateTimeField(
blank=True,
null=True,
help_text='Scheduled appointment date and time'
)
scheduled_end_datetime = models.DateTimeField(
blank=True,
null=True,
help_text='Scheduled appointment end time'
)
# Location Information
location = models.CharField(
max_length=200,
blank=True,
null=True,
help_text='Appointment location'
)
room_number = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Room number'
)
# Telemedicine Information
is_telemedicine = models.BooleanField(
default=False,
help_text='Telemedicine appointment'
)
telemedicine_platform = models.CharField(
max_length=50,
choices=TELEMEDICINE_PLATFORM_CHOICES,
blank=True,
null=True,
help_text='Telemedicine platform'
)
meeting_url = models.URLField(
blank=True,
null=True,
help_text='Telemedicine meeting URL'
)
meeting_id = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Meeting ID or room number'
)
meeting_password = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Meeting password'
)
# Check-in Information
checked_in_at = models.DateTimeField(
blank=True,
null=True,
help_text='Patient check-in time'
)
checked_in_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='checked_in_appointments',
help_text='Staff member who checked in patient'
)
# Completion Information
completed_at = models.DateTimeField(
blank=True,
null=True,
help_text='Appointment completion time'
)
actual_duration_minutes = models.PositiveIntegerField(
blank=True,
null=True,
help_text='Actual appointment duration'
)
# Cancellation Information
cancelled_at = models.DateTimeField(
blank=True,
null=True,
help_text='Cancellation timestamp'
)
cancelled_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='cancelled_appointments',
help_text='User who cancelled appointment'
)
cancellation_reason = models.TextField(
blank=True,
null=True,
help_text='Reason for cancellation'
)
# Rescheduling Information
rescheduled_from = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='rescheduled_appointments',
help_text='Original appointment if rescheduled'
)
# Communication Preferences
reminder_preferences = models.JSONField(
default=dict,
blank=True,
help_text='Reminder preferences (email, SMS, phone)'
)
# Special Requirements
special_requirements = models.TextField(
blank=True,
null=True,
help_text='Special requirements or accommodations'
)
interpreter_needed = models.BooleanField(
default=False,
help_text='Interpreter services needed'
)
interpreter_language = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Required interpreter language'
)
# Metadata
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_appointments',
help_text='User who created the appointment request'
)
class Meta:
db_table = 'appointments_appointment_request'
verbose_name = 'Appointment Request'
verbose_name_plural = 'Appointment Requests'
ordering = ['-created_at']
indexes = [
models.Index(fields=['tenant', 'status']),
models.Index(fields=['patient', 'status']),
models.Index(fields=['provider', 'scheduled_datetime']),
models.Index(fields=['scheduled_datetime']),
models.Index(fields=['priority', 'urgency_score']),
models.Index(fields=['appointment_type', 'specialty']),
]
def __str__(self):
return f"{self.patient.get_full_name()} - {self.appointment_type} ({self.status})"
@property
def is_overdue(self):
"""
Check if appointment is overdue.
"""
if self.scheduled_datetime and self.status in ['SCHEDULED', 'CONFIRMED']:
return timezone.now() > self.scheduled_datetime
return False
@property
def wait_time_minutes(self):
"""
Calculate wait time if checked in.
"""
if self.checked_in_at and self.status == 'CHECKED_IN':
return int((timezone.now() - self.checked_in_at).total_seconds() / 60)
return None
class SlotAvailability(models.Model):
"""
Provider availability slots for appointment scheduling.
"""
AVAILABILITY_TYPE_CHOICES=[
('REGULAR', 'Regular Hours'),
('EXTENDED', 'Extended Hours'),
('EMERGENCY', 'Emergency'),
('ON_CALL', 'On Call'),
('TELEMEDICINE', 'Telemedicine Only'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='availability_slots',
help_text='Organization tenant'
)
# Provider Information
provider = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='availability_slots',
help_text='Healthcare provider'
)
# Slot Information
slot_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique slot identifier'
)
# Date and Time
date = models.DateField(
help_text='Availability date'
)
start_time = models.TimeField(
help_text='Slot start time'
)
end_time = models.TimeField(
help_text='Slot end time'
)
duration_minutes = models.PositiveIntegerField(
help_text='Slot duration in minutes'
)
# Availability Type
availability_type = models.CharField(
max_length=20,
choices=AVAILABILITY_TYPE_CHOICES,
default='REGULAR',
help_text='Type of availability'
)
# Capacity and Booking
max_appointments = models.PositiveIntegerField(
default=1,
help_text='Maximum appointments for this slot'
)
booked_appointments = models.PositiveIntegerField(
default=0,
help_text='Number of booked appointments'
)
# Location Information
location = models.CharField(
max_length=200,
help_text='Appointment location'
)
room_number = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Room number'
)
# Specialty and Services
specialty = models.CharField(
max_length=100,
help_text='Medical specialty for this slot'
)
appointment_types = models.JSONField(
default=list,
help_text='Allowed appointment types for this slot'
)
# Restrictions
patient_restrictions = models.JSONField(
default=dict,
blank=True,
help_text='Patient restrictions (age, gender, etc.)'
)
insurance_restrictions = models.JSONField(
default=list,
blank=True,
help_text='Accepted insurance types'
)
# Telemedicine Support
supports_telemedicine = models.BooleanField(
default=False,
help_text='Slot supports telemedicine appointments'
)
telemedicine_only = models.BooleanField(
default=False,
help_text='Telemedicine only slot'
)
# Status
is_active = models.BooleanField(
default=True,
help_text='Slot is active and bookable'
)
is_blocked = models.BooleanField(
default=False,
help_text='Slot is temporarily blocked'
)
block_reason = models.CharField(
max_length=200,
blank=True,
null=True,
help_text='Reason for blocking slot'
)
# Recurring Pattern
is_recurring = models.BooleanField(
default=False,
help_text='Slot is part of recurring pattern'
)
recurrence_pattern = models.JSONField(
default=dict,
blank=True,
help_text='Recurrence pattern configuration'
)
recurrence_end_date = models.DateField(
blank=True,
null=True,
help_text='End date for recurring pattern'
)
# Metadata
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_availability_slots',
help_text='User who created the slot'
)
class Meta:
db_table = 'appointments_slot_availability'
verbose_name = 'Slot Availability'
verbose_name_plural = 'Slot Availability'
ordering = ['date', 'start_time']
indexes = [
models.Index(fields=['tenant', 'provider', 'date']),
models.Index(fields=['date', 'start_time']),
models.Index(fields=['specialty']),
models.Index(fields=['is_active', 'is_blocked']),
]
unique_together = ['provider', 'date', 'start_time']
def __str__(self):
return f"{self.provider.get_full_name()} - {self.date} {self.start_time}-{self.end_time}"
@property
def is_available(self):
"""
Check if slot has availability.
"""
return (
self.is_active and
not self.is_blocked and
self.booked_appointments < self.max_appointments
)
@property
def available_capacity(self):
"""
Get available capacity for slot.
"""
return max(0, self.max_appointments - self.booked_appointments)
class WaitingQueue(models.Model):
"""
Waiting queue for managing patient flow.
"""
QUEUE_TYPE_CHOICES=[
('PROVIDER', 'Provider Queue'),
('SPECIALTY', 'Specialty Queue'),
('LOCATION', 'Location Queue'),
('PROCEDURE', 'Procedure Queue'),
('EMERGENCY', 'Emergency Queue'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='waiting_queues',
help_text='Organization tenant'
)
# Queue Information
queue_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique queue identifier'
)
name = models.CharField(
max_length=200,
help_text='Queue name'
)
description = models.TextField(
blank=True,
null=True,
help_text='Queue description'
)
# Queue Type and Configuration
queue_type = models.CharField(
max_length=20,
choices=QUEUE_TYPE_CHOICES,
help_text='Type of queue'
)
# Associated Resources
providers = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='waiting_queues',
blank=True,
help_text='Providers associated with this queue'
)
specialty = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Medical specialty'
)
location = models.CharField(
max_length=200,
blank=True,
null=True,
help_text='Queue location'
)
# Queue Management
max_queue_size = models.PositiveIntegerField(
default=50,
help_text='Maximum queue size'
)
average_service_time_minutes = models.PositiveIntegerField(
default=30,
help_text='Average service time in minutes'
)
# Priority Configuration
priority_weights = models.JSONField(
default=dict,
help_text='Priority weights for queue ordering'
)
# Status
is_active = models.BooleanField(
default=True,
help_text='Queue is active'
)
is_accepting_patients = models.BooleanField(
default=True,
help_text='Queue is accepting new patients'
)
# Operating Hours
operating_hours = models.JSONField(
default=dict,
help_text='Queue operating hours by day'
)
# Metadata
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_queues',
help_text='User who created the queue'
)
class Meta:
db_table = 'appointments_waiting_queue'
verbose_name = 'Waiting Queue'
verbose_name_plural = 'Waiting Queues'
ordering = ['name']
indexes = [
models.Index(fields=['tenant', 'queue_type']),
models.Index(fields=['specialty']),
models.Index(fields=['is_active']),
]
def __str__(self):
return f"{self.name} ({self.queue_type})"
@property
def current_queue_size(self):
"""
Get current queue size.
"""
return self.queue_entries.filter(status='WAITING').count()
@property
def estimated_wait_time_minutes(self):
"""
Calculate estimated wait time.
"""
queue_size = self.current_queue_size
return queue_size * self.average_service_time_minutes
class QueueEntry(models.Model):
"""
Individual entry in a waiting queue.
"""
STATUS_CHOICES=[
('WAITING', 'Waiting'),
('CALLED', 'Called'),
('IN_SERVICE', 'In Service'),
('COMPLETED', 'Completed'),
('LEFT', 'Left Queue'),
('NO_SHOW', 'No Show'),
]
# Queue and Patient
queue = models.ForeignKey(
WaitingQueue,
on_delete=models.CASCADE,
related_name='queue_entries',
help_text='Waiting queue'
)
patient = models.ForeignKey(
'patients.PatientProfile',
on_delete=models.CASCADE,
related_name='queue_entries',
help_text='Patient in queue'
)
appointment = models.ForeignKey(
AppointmentRequest,
on_delete=models.CASCADE,
related_name='queue_entries',
help_text='Associated appointment'
)
# Entry Information
entry_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique entry identifier'
)
# Queue Position and Priority
queue_position = models.PositiveIntegerField(
help_text='Position in queue'
)
priority_score = models.FloatField(
default=1.0,
help_text='Priority score for queue ordering'
)
# Timing Information
joined_at = models.DateTimeField(
auto_now_add=True,
help_text='Time patient joined queue'
)
estimated_service_time = models.DateTimeField(
blank=True,
null=True,
help_text='Estimated service time'
)
called_at = models.DateTimeField(
blank=True,
null=True,
help_text='Time patient was called'
)
served_at = models.DateTimeField(
blank=True,
null=True,
help_text='Time patient was served'
)
# Status
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='WAITING',
help_text='Queue entry status'
)
# Provider Assignment
assigned_provider = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='assigned_queue_entries',
help_text='Assigned provider'
)
# Communication
notification_sent = models.BooleanField(
default=False,
help_text='Notification sent to patient'
)
notification_method = models.CharField(
max_length=20,
choices=[
('SMS', 'SMS'),
('EMAIL', 'Email'),
('PHONE', 'Phone Call'),
('PAGER', 'Pager'),
('APP', 'Mobile App'),
],
blank=True,
null=True,
help_text='Notification method used'
)
# Notes
notes = models.TextField(
blank=True,
null=True,
help_text='Additional notes'
)
# Metadata
updated_at = models.DateTimeField(auto_now=True)
updated_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='updated_queue_entries',
help_text='User who last updated entry'
)
class Meta:
db_table = 'appointments_queue_entry'
verbose_name = 'Queue Entry'
verbose_name_plural = 'Queue Entries'
ordering = ['queue', 'priority_score', 'joined_at']
indexes = [
models.Index(fields=['queue', 'status']),
models.Index(fields=['patient']),
models.Index(fields=['priority_score']),
models.Index(fields=['joined_at']),
]
def __str__(self):
return f"{self.patient.get_full_name()} - {self.queue.name} (#{self.queue_position})"
@property
def wait_time_minutes(self):
"""
Calculate current wait time.
"""
if self.status == 'WAITING':
return int((timezone.now() - self.joined_at).total_seconds() / 60)
elif self.served_at:
return int((self.served_at - self.joined_at).total_seconds() / 60)
return None
class TelemedicineSession(models.Model):
"""
Telemedicine session management.
"""
PLATFORM_CHOICES=[
('ZOOM', 'Zoom'),
('TEAMS', 'Microsoft Teams'),
('WEBEX', 'Cisco Webex'),
('DOXY', 'Doxy.me'),
('CUSTOM', 'Custom Platform'),
('OTHER', 'Other'),
]
STATUS_CHOICES=[
('SCHEDULED', 'Scheduled'),
('READY', 'Ready to Start'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('FAILED', 'Failed'),
]
# Session Information
session_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique session identifier'
)
# Associated Appointment
appointment = models.OneToOneField(
AppointmentRequest,
on_delete=models.CASCADE,
related_name='telemedicine_session',
help_text='Associated appointment'
)
# Platform Information
platform = models.CharField(
max_length=50,
choices=PLATFORM_CHOICES,
help_text='Telemedicine platform'
)
# Meeting Details
meeting_url = models.URLField(
help_text='Meeting URL'
)
meeting_id = models.CharField(
max_length=100,
help_text='Meeting ID or room number'
)
meeting_password = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Meeting password'
)
# Session Configuration
waiting_room_enabled = models.BooleanField(
default=True,
help_text='Waiting room enabled'
)
recording_enabled = models.BooleanField(
default=False,
help_text='Session recording enabled'
)
recording_consent = models.BooleanField(
default=False,
help_text='Patient consent for recording'
)
# Security Settings
encryption_enabled = models.BooleanField(
default=True,
help_text='End-to-end encryption enabled'
)
password_required = models.BooleanField(
default=True,
help_text='Password required to join'
)
# Session Status
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='SCHEDULED',
help_text='Session status'
)
# Timing Information
scheduled_start = models.DateTimeField(
help_text='Scheduled start time'
)
scheduled_end = models.DateTimeField(
help_text='Scheduled end time'
)
actual_start = models.DateTimeField(
blank=True,
null=True,
help_text='Actual start time'
)
actual_end = models.DateTimeField(
blank=True,
null=True,
help_text='Actual end time'
)
# Participants
provider_joined_at = models.DateTimeField(
blank=True,
null=True,
help_text='Provider join time'
)
patient_joined_at = models.DateTimeField(
blank=True,
null=True,
help_text='Patient join time'
)
# Technical Information
connection_quality = models.CharField(
max_length=20,
choices=[
('EXCELLENT', 'Excellent'),
('GOOD', 'Good'),
('FAIR', 'Fair'),
('POOR', 'Poor'),
],
blank=True,
null=True,
help_text='Connection quality'
)
technical_issues = models.TextField(
blank=True,
null=True,
help_text='Technical issues encountered'
)
# Recording Information
recording_url = models.URLField(
blank=True,
null=True,
help_text='Recording URL'
)
recording_duration_minutes = models.PositiveIntegerField(
blank=True,
null=True,
help_text='Recording duration in minutes'
)
# Session Notes
session_notes = models.TextField(
blank=True,
null=True,
help_text='Session notes'
)
# Metadata
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_telemedicine_sessions',
help_text='User who created the session'
)
class Meta:
db_table = 'appointments_telemedicine_session'
verbose_name = 'Telemedicine Session'
verbose_name_plural = 'Telemedicine Sessions'
ordering = ['-scheduled_start']
indexes = [
models.Index(fields=['appointment']),
models.Index(fields=['status']),
models.Index(fields=['scheduled_start']),
]
def __str__(self):
return f"Telemedicine - {self.appointment.patient.get_full_name()} ({self.status})"
@property
def duration_minutes(self):
"""
Calculate session duration.
"""
if self.actual_start and self.actual_end:
return int((self.actual_end - self.actual_start).total_seconds() / 60)
return None
class AppointmentTemplate(models.Model):
"""
Templates for common appointment types.
"""
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='appointment_templates',
help_text='Organization tenant'
)
# Template Information
name = models.CharField(
max_length=200,
help_text='Template name'
)
description = models.TextField(
blank=True,
null=True,
help_text='Template description'
)
# Appointment Configuration
appointment_type = models.CharField(
max_length=50,
help_text='Default appointment type'
)
specialty = models.CharField(
max_length=100,
help_text='Medical specialty'
)
duration_minutes = models.PositiveIntegerField(
help_text='Default duration in minutes'
)
# Scheduling Rules
advance_booking_days = models.PositiveIntegerField(
default=30,
help_text='Maximum advance booking days'
)
minimum_notice_hours = models.PositiveIntegerField(
default=24,
help_text='Minimum notice required in hours'
)
# Requirements
insurance_verification_required = models.BooleanField(
default=False,
help_text='Insurance verification required'
)
authorization_required = models.BooleanField(
default=False,
help_text='Prior authorization required'
)
# Instructions
pre_appointment_instructions = models.TextField(
blank=True,
null=True,
help_text='Pre-appointment instructions for patient'
)
post_appointment_instructions = models.TextField(
blank=True,
null=True,
help_text='Post-appointment instructions template'
)
# Forms and Documents
required_forms = models.JSONField(
default=list,
blank=True,
help_text='Required forms for this appointment type'
)
# Status
is_active = models.BooleanField(
default=True,
help_text='Template is active'
)
# Metadata
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_appointment_templates',
help_text='User who created the template'
)
class Meta:
db_table = 'appointments_appointment_template'
verbose_name = 'Appointment Template'
verbose_name_plural = 'Appointment Templates'
ordering = ['specialty', 'name']
indexes = [
models.Index(fields=['tenant', 'specialty']),
models.Index(fields=['appointment_type']),
models.Index(fields=['is_active']),
]
def __str__(self):
return f"{self.name} ({self.specialty})"