Marwan Alwali 263292f6be update
2025-11-04 00:50:06 +03:00

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'])