531 lines
18 KiB
Python
531 lines
18 KiB
Python
"""
|
|
Signal handlers for appointments app.
|
|
|
|
This module contains signal handlers for automating various tasks related to
|
|
appointments, queue management, and notifications.
|
|
"""
|
|
|
|
from django.db.models.signals import post_save, pre_save, post_delete
|
|
from django.dispatch import receiver, Signal
|
|
from django.utils import timezone
|
|
from django.core.mail import send_mail
|
|
from django.conf import settings
|
|
|
|
from .models import (
|
|
AppointmentRequest, QueueEntry, WaitingQueue,
|
|
TelemedicineSession, WaitingList
|
|
)
|
|
from core.utils import AuditLogger
|
|
|
|
|
|
# Custom signals
|
|
appointment_status_changed = Signal()
|
|
queue_position_updated = Signal()
|
|
appointment_reminder_due = Signal()
|
|
telemedicine_session_started = Signal()
|
|
|
|
|
|
@receiver(pre_save, sender=AppointmentRequest)
|
|
def track_appointment_status_change(sender, instance, **kwargs):
|
|
"""
|
|
Track appointment status changes and trigger appropriate actions.
|
|
|
|
This signal handler:
|
|
- Detects status changes
|
|
- Sends custom signal for status changes
|
|
- Updates related records
|
|
"""
|
|
if instance.pk:
|
|
try:
|
|
old_instance = AppointmentRequest.objects.get(pk=instance.pk)
|
|
|
|
# Check if status changed
|
|
if old_instance.status != instance.status:
|
|
# Send custom signal
|
|
appointment_status_changed.send(
|
|
sender=sender,
|
|
instance=instance,
|
|
old_status=old_instance.status,
|
|
new_status=instance.status
|
|
)
|
|
|
|
# Update timestamps based on status
|
|
if instance.status == 'CHECKED_IN' and not instance.checked_in_at:
|
|
instance.checked_in_at = timezone.now()
|
|
elif instance.status == 'COMPLETED' and not instance.completed_at:
|
|
instance.completed_at = timezone.now()
|
|
elif instance.status == 'CANCELLED' and not instance.cancelled_at:
|
|
instance.cancelled_at = timezone.now()
|
|
|
|
except AppointmentRequest.DoesNotExist:
|
|
pass
|
|
|
|
|
|
@receiver(post_save, sender=AppointmentRequest)
|
|
def handle_appointment_creation(sender, instance, created, **kwargs):
|
|
"""
|
|
Handle actions after appointment is created.
|
|
|
|
This signal handler:
|
|
- Sends confirmation notifications
|
|
- Creates audit log entry
|
|
- Updates related queue entries
|
|
"""
|
|
if created:
|
|
# Log appointment creation
|
|
AuditLogger.log_event(
|
|
tenant=instance.tenant,
|
|
event_type='CREATE',
|
|
event_category='APPOINTMENT_MANAGEMENT',
|
|
action='Appointment Created',
|
|
description=f'New appointment created for {instance.patient} with {instance.provider}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Send confirmation notification (if configured)
|
|
if hasattr(settings, 'SEND_APPOINTMENT_CONFIRMATIONS') and settings.SEND_APPOINTMENT_CONFIRMATIONS:
|
|
send_appointment_confirmation(instance)
|
|
|
|
|
|
@receiver(appointment_status_changed)
|
|
def handle_appointment_status_change(sender, instance, old_status, new_status, **kwargs):
|
|
"""
|
|
Handle appointment status changes.
|
|
|
|
This signal handler:
|
|
- Sends status change notifications
|
|
- Updates queue entries
|
|
- Logs status changes
|
|
"""
|
|
# Log status change
|
|
AuditLogger.log_event(
|
|
tenant=instance.tenant,
|
|
event_type='UPDATE',
|
|
event_category='APPOINTMENT_MANAGEMENT',
|
|
action='Appointment Status Changed',
|
|
description=f'Appointment status changed from {old_status} to {new_status}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Update related queue entry if exists
|
|
try:
|
|
queue_entry = QueueEntry.objects.get(appointment=instance)
|
|
|
|
# Map appointment status to queue entry status
|
|
status_mapping = {
|
|
'CHECKED_IN': 'WAITING',
|
|
'IN_PROGRESS': 'IN_SERVICE',
|
|
'COMPLETED': 'COMPLETED',
|
|
'CANCELLED': 'CANCELLED',
|
|
'NO_SHOW': 'NO_SHOW'
|
|
}
|
|
|
|
if new_status in status_mapping:
|
|
queue_entry.status = status_mapping[new_status]
|
|
queue_entry.save()
|
|
|
|
except QueueEntry.DoesNotExist:
|
|
pass
|
|
|
|
# Send notification based on status
|
|
if new_status in ['CONFIRMED', 'RESCHEDULED', 'CANCELLED']:
|
|
send_status_notification(instance, new_status)
|
|
|
|
|
|
@receiver(post_save, sender=QueueEntry)
|
|
def handle_queue_entry_update(sender, instance, created, **kwargs):
|
|
"""
|
|
Handle queue entry creation and updates.
|
|
|
|
This signal handler:
|
|
- Updates queue positions
|
|
- Sends queue notifications
|
|
- Logs queue changes
|
|
"""
|
|
if created:
|
|
# Log queue entry creation
|
|
AuditLogger.log_event(
|
|
tenant=instance.queue.tenant,
|
|
event_type='CREATE',
|
|
event_category='QUEUE_MANAGEMENT',
|
|
action='Patient Added to Queue',
|
|
description=f'{instance.patient} added to {instance.queue.name}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Update positions for all entries in the queue
|
|
update_queue_positions(instance.queue)
|
|
|
|
# Send queue position notification
|
|
if hasattr(settings, 'SEND_QUEUE_NOTIFICATIONS') and settings.SEND_QUEUE_NOTIFICATIONS:
|
|
send_queue_notification(instance)
|
|
|
|
|
|
@receiver(pre_save, sender=QueueEntry)
|
|
def track_queue_status_change(sender, instance, **kwargs):
|
|
"""
|
|
Track queue entry status changes.
|
|
|
|
This signal handler:
|
|
- Detects status changes
|
|
- Updates timestamps
|
|
- Sends custom signal
|
|
"""
|
|
if instance.pk:
|
|
try:
|
|
old_instance = QueueEntry.objects.get(pk=instance.pk)
|
|
|
|
# Check if status changed
|
|
if old_instance.status != instance.status:
|
|
# Update timestamps based on status
|
|
if instance.status == 'CALLED' and not instance.called_at:
|
|
instance.called_at = timezone.now()
|
|
elif instance.status == 'IN_SERVICE' and not instance.served_at:
|
|
instance.served_at = timezone.now()
|
|
elif instance.status == 'COMPLETED' and not instance.completed_at:
|
|
instance.completed_at = timezone.now()
|
|
|
|
# Send custom signal
|
|
queue_position_updated.send(
|
|
sender=sender,
|
|
instance=instance,
|
|
old_status=old_instance.status,
|
|
new_status=instance.status
|
|
)
|
|
|
|
except QueueEntry.DoesNotExist:
|
|
pass
|
|
|
|
|
|
@receiver(queue_position_updated)
|
|
def handle_queue_position_change(sender, instance, old_status, new_status, **kwargs):
|
|
"""
|
|
Handle queue position/status changes.
|
|
|
|
This signal handler:
|
|
- Logs position changes
|
|
- Sends notifications
|
|
- Updates related appointments
|
|
"""
|
|
# Log status change
|
|
AuditLogger.log_event(
|
|
tenant=instance.queue.tenant,
|
|
event_type='UPDATE',
|
|
event_category='QUEUE_MANAGEMENT',
|
|
action='Queue Status Changed',
|
|
description=f'Queue entry status changed from {old_status} to {new_status}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Update related appointment if exists
|
|
if instance.appointment:
|
|
status_mapping = {
|
|
'WAITING': 'CHECKED_IN',
|
|
'CALLED': 'CHECKED_IN',
|
|
'IN_SERVICE': 'IN_PROGRESS',
|
|
'COMPLETED': 'COMPLETED',
|
|
'NO_SHOW': 'NO_SHOW',
|
|
'CANCELLED': 'CANCELLED'
|
|
}
|
|
|
|
if new_status in status_mapping:
|
|
instance.appointment.status = status_mapping[new_status]
|
|
instance.appointment.save()
|
|
|
|
|
|
@receiver(post_save, sender=TelemedicineSession)
|
|
def handle_telemedicine_session(sender, instance, created, **kwargs):
|
|
"""
|
|
Handle telemedicine session creation and updates.
|
|
|
|
This signal handler:
|
|
- Sends session invitations
|
|
- Logs session events
|
|
- Updates appointment status
|
|
"""
|
|
if created:
|
|
# Log session creation
|
|
AuditLogger.log_event(
|
|
tenant=instance.appointment.tenant,
|
|
event_type='CREATE',
|
|
event_category='TELEMEDICINE',
|
|
action='Telemedicine Session Created',
|
|
description=f'Telemedicine session created for appointment {instance.appointment.pk}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Send session invitation
|
|
if hasattr(settings, 'SEND_TELEMEDICINE_INVITATIONS') and settings.SEND_TELEMEDICINE_INVITATIONS:
|
|
send_telemedicine_invitation(instance)
|
|
|
|
# Check if session started
|
|
if instance.status == 'IN_PROGRESS' and instance.actual_start:
|
|
telemedicine_session_started.send(
|
|
sender=sender,
|
|
instance=instance
|
|
)
|
|
|
|
|
|
@receiver(telemedicine_session_started)
|
|
def handle_telemedicine_session_start(sender, instance, **kwargs):
|
|
"""
|
|
Handle telemedicine session start.
|
|
|
|
This signal handler:
|
|
- Updates appointment status
|
|
- Logs session start
|
|
- Sends notifications
|
|
"""
|
|
# Update appointment status
|
|
if instance.appointment:
|
|
instance.appointment.status = 'IN_PROGRESS'
|
|
instance.appointment.actual_start_time = instance.actual_start
|
|
instance.appointment.save()
|
|
|
|
# Log session start
|
|
AuditLogger.log_event(
|
|
tenant=instance.appointment.tenant,
|
|
event_type='UPDATE',
|
|
event_category='TELEMEDICINE',
|
|
action='Telemedicine Session Started',
|
|
description=f'Telemedicine session started at {instance.actual_start}',
|
|
content_object=instance
|
|
)
|
|
|
|
|
|
@receiver(post_save, sender=WaitingList)
|
|
def handle_waiting_list_update(sender, instance, created, **kwargs):
|
|
"""
|
|
Handle waiting list entry creation and updates.
|
|
|
|
This signal handler:
|
|
- Updates positions
|
|
- Sends notifications
|
|
- Logs changes
|
|
"""
|
|
if created:
|
|
# Log waiting list entry creation
|
|
AuditLogger.log_event(
|
|
tenant=instance.tenant,
|
|
event_type='CREATE',
|
|
event_category='WAITING_LIST',
|
|
action='Patient Added to Waiting List',
|
|
description=f'{instance.patient} added to waiting list for {instance.specialty}',
|
|
content_object=instance
|
|
)
|
|
|
|
# Update position
|
|
instance.update_position()
|
|
|
|
# Send confirmation notification
|
|
if hasattr(settings, 'SEND_WAITLIST_CONFIRMATIONS') and settings.SEND_WAITLIST_CONFIRMATIONS:
|
|
send_waitlist_confirmation(instance)
|
|
|
|
|
|
@receiver(post_delete, sender=AppointmentRequest)
|
|
def handle_appointment_deletion(sender, instance, **kwargs):
|
|
"""
|
|
Handle appointment deletion.
|
|
|
|
This signal handler:
|
|
- Logs deletion
|
|
- Cleans up related records
|
|
"""
|
|
# Log deletion
|
|
AuditLogger.log_event(
|
|
tenant=instance.tenant,
|
|
event_type='DELETE',
|
|
event_category='APPOINTMENT_MANAGEMENT',
|
|
action='Appointment Deleted',
|
|
description=f'Appointment for {instance.patient} with {instance.provider} was deleted',
|
|
content_object=None
|
|
)
|
|
|
|
|
|
# Helper functions for notifications
|
|
|
|
def send_appointment_confirmation(appointment):
|
|
"""Send appointment confirmation notification."""
|
|
if appointment.patient.email:
|
|
subject = f'Appointment Confirmation - {appointment.scheduled_datetime.strftime("%B %d, %Y")}'
|
|
message = f"""
|
|
Dear {appointment.patient.get_full_name()},
|
|
|
|
Your appointment has been confirmed:
|
|
|
|
Date: {appointment.scheduled_datetime.strftime("%B %d, %Y")}
|
|
Time: {appointment.scheduled_datetime.strftime("%I:%M %p")}
|
|
Provider: {appointment.provider.get_full_name()}
|
|
Type: {appointment.get_appointment_type_display()}
|
|
Location: {appointment.location or 'To be determined'}
|
|
|
|
Please arrive 15 minutes early for check-in.
|
|
|
|
If you need to reschedule or cancel, please contact us at least 24 hours in advance.
|
|
|
|
Thank you,
|
|
{appointment.tenant.name if appointment.tenant else 'Hospital Management'}
|
|
"""
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[appointment.patient.email],
|
|
fail_silently=True
|
|
)
|
|
except Exception as e:
|
|
# Log error but don't raise
|
|
print(f"Error sending appointment confirmation: {e}")
|
|
|
|
|
|
def send_status_notification(appointment, status):
|
|
"""Send appointment status change notification."""
|
|
if appointment.patient.email:
|
|
status_messages = {
|
|
'CONFIRMED': 'Your appointment has been confirmed.',
|
|
'RESCHEDULED': 'Your appointment has been rescheduled.',
|
|
'CANCELLED': 'Your appointment has been cancelled.'
|
|
}
|
|
|
|
subject = f'Appointment {status.title()} - {appointment.scheduled_datetime.strftime("%B %d, %Y")}'
|
|
message = f"""
|
|
Dear {appointment.patient.get_full_name()},
|
|
|
|
{status_messages.get(status, 'Your appointment status has been updated.')}
|
|
|
|
Appointment Details:
|
|
Date: {appointment.scheduled_datetime.strftime("%B %d, %Y")}
|
|
Time: {appointment.scheduled_datetime.strftime("%I:%M %p")}
|
|
Provider: {appointment.provider.get_full_name()}
|
|
|
|
If you have any questions, please contact us.
|
|
|
|
Thank you,
|
|
{appointment.tenant.name if appointment.tenant else 'Hospital Management'}
|
|
"""
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[appointment.patient.email],
|
|
fail_silently=True
|
|
)
|
|
except Exception as e:
|
|
print(f"Error sending status notification: {e}")
|
|
|
|
|
|
def send_queue_notification(queue_entry):
|
|
"""Send queue position notification."""
|
|
if queue_entry.patient.email:
|
|
subject = f'Queue Position - {queue_entry.queue.name}'
|
|
message = f"""
|
|
Dear {queue_entry.patient.get_full_name()},
|
|
|
|
You have been added to the queue: {queue_entry.queue.name}
|
|
|
|
Your position: {queue_entry.queue_position}
|
|
Estimated wait time: {queue_entry.estimated_service_time or 'To be determined'}
|
|
|
|
We will notify you when it's your turn.
|
|
|
|
Thank you for your patience,
|
|
{queue_entry.queue.tenant.name if queue_entry.queue.tenant else 'Hospital Management'}
|
|
"""
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[queue_entry.patient.email],
|
|
fail_silently=True
|
|
)
|
|
except Exception as e:
|
|
print(f"Error sending queue notification: {e}")
|
|
|
|
|
|
def send_telemedicine_invitation(session):
|
|
"""Send telemedicine session invitation."""
|
|
if session.appointment.patient.email:
|
|
subject = f'Telemedicine Appointment - {session.scheduled_start.strftime("%B %d, %Y")}'
|
|
message = f"""
|
|
Dear {session.appointment.patient.get_full_name()},
|
|
|
|
Your telemedicine appointment is scheduled:
|
|
|
|
Date: {session.scheduled_start.strftime("%B %d, %Y")}
|
|
Time: {session.scheduled_start.strftime("%I:%M %p")}
|
|
Provider: {session.appointment.provider.get_full_name()}
|
|
Platform: {session.get_platform_display()}
|
|
|
|
Meeting Link: {session.meeting_url}
|
|
Meeting ID: {session.meeting_id}
|
|
Password: {session.meeting_password if session.meeting_password else 'Not required'}
|
|
|
|
Please test your connection 10 minutes before the appointment.
|
|
|
|
Thank you,
|
|
{session.appointment.tenant.name if session.appointment.tenant else 'Hospital Management'}
|
|
"""
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[session.appointment.patient.email],
|
|
fail_silently=True
|
|
)
|
|
except Exception as e:
|
|
print(f"Error sending telemedicine invitation: {e}")
|
|
|
|
|
|
def send_waitlist_confirmation(waiting_list_entry):
|
|
"""Send waiting list confirmation notification."""
|
|
if waiting_list_entry.contact_email:
|
|
subject = f'Waiting List Confirmation - {waiting_list_entry.specialty}'
|
|
message = f"""
|
|
Dear {waiting_list_entry.patient.get_full_name()},
|
|
|
|
You have been added to the waiting list for {waiting_list_entry.specialty}.
|
|
|
|
Priority: {waiting_list_entry.get_priority_display()}
|
|
Position: {waiting_list_entry.position}
|
|
Estimated wait time: {waiting_list_entry.estimated_wait_time or 'To be determined'}
|
|
|
|
We will contact you when an appointment becomes available.
|
|
|
|
Contact method: {waiting_list_entry.get_contact_method_display()}
|
|
|
|
Thank you for your patience,
|
|
{waiting_list_entry.tenant.name if waiting_list_entry.tenant else 'Hospital Management'}
|
|
"""
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[waiting_list_entry.contact_email],
|
|
fail_silently=True
|
|
)
|
|
except Exception as e:
|
|
print(f"Error sending waitlist confirmation: {e}")
|
|
|
|
|
|
def update_queue_positions(queue):
|
|
"""Update positions for all entries in a queue."""
|
|
entries = QueueEntry.objects.filter(
|
|
queue=queue,
|
|
status='WAITING'
|
|
).order_by('priority_score', 'joined_at')
|
|
|
|
for index, entry in enumerate(entries, start=1):
|
|
if entry.queue_position != index:
|
|
entry.queue_position = index
|
|
entry.save(update_fields=['queue_position'])
|