250 lines
7.6 KiB
Python
250 lines
7.6 KiB
Python
"""
|
|
Referrals models for cross-clinic patient referrals.
|
|
|
|
This module handles referrals between departments/clinics,
|
|
referral tracking, and reception notifications.
|
|
"""
|
|
|
|
from django.db import models
|
|
from django.utils.translation import gettext_lazy as _
|
|
from simple_history.models import HistoricalRecords
|
|
|
|
from core.models import (
|
|
UUIDPrimaryKeyMixin,
|
|
TimeStampedMixin,
|
|
TenantOwnedMixin,
|
|
)
|
|
|
|
|
|
class Referral(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin):
|
|
"""
|
|
Patient referrals between clinics/departments.
|
|
Tracks referral workflow from initiation to appointment booking.
|
|
"""
|
|
|
|
class Status(models.TextChoices):
|
|
PENDING = 'PENDING', _('Pending')
|
|
ACKNOWLEDGED = 'ACKNOWLEDGED', _('Acknowledged')
|
|
APPOINTMENT_BOOKED = 'APPOINTMENT_BOOKED', _('Appointment Booked')
|
|
COMPLETED = 'COMPLETED', _('Completed')
|
|
DECLINED = 'DECLINED', _('Declined')
|
|
CANCELLED = 'CANCELLED', _('Cancelled')
|
|
|
|
class Priority(models.TextChoices):
|
|
ROUTINE = 'ROUTINE', _('Routine')
|
|
URGENT = 'URGENT', _('Urgent')
|
|
EMERGENCY = 'EMERGENCY', _('Emergency')
|
|
|
|
class ReferralType(models.TextChoices):
|
|
INITIAL_ASSESSMENT = 'INITIAL_ASSESSMENT', _('Initial Assessment')
|
|
CONSULTATION = 'CONSULTATION', _('Consultation')
|
|
FOLLOW_UP = 'FOLLOW_UP', _('Follow-up')
|
|
SECOND_OPINION = 'SECOND_OPINION', _('Second Opinion')
|
|
|
|
# Patient & Clinics
|
|
patient = models.ForeignKey(
|
|
'core.Patient',
|
|
on_delete=models.CASCADE,
|
|
related_name='referrals',
|
|
verbose_name=_("Patient")
|
|
)
|
|
from_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.CASCADE,
|
|
related_name='outgoing_referrals',
|
|
verbose_name=_("From Clinic")
|
|
)
|
|
to_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.CASCADE,
|
|
related_name='incoming_referrals',
|
|
verbose_name=_("To Clinic")
|
|
)
|
|
|
|
# Referral Details
|
|
referral_type = models.CharField(
|
|
max_length=30,
|
|
choices=ReferralType.choices,
|
|
default=ReferralType.CONSULTATION,
|
|
verbose_name=_("Referral Type")
|
|
)
|
|
priority = models.CharField(
|
|
max_length=15,
|
|
choices=Priority.choices,
|
|
default=Priority.ROUTINE,
|
|
verbose_name=_("Priority")
|
|
)
|
|
reason = models.TextField(
|
|
verbose_name=_("Reason for Referral"),
|
|
help_text=_("Clinical reason for referring to another department")
|
|
)
|
|
clinical_notes = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Clinical Notes"),
|
|
help_text=_("Additional clinical information for receiving clinic")
|
|
)
|
|
|
|
# Workflow
|
|
referred_by = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
related_name='made_referrals',
|
|
verbose_name=_("Referred By")
|
|
)
|
|
referred_to = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='received_referrals',
|
|
verbose_name=_("Referred To"),
|
|
help_text=_("Specific therapist if known")
|
|
)
|
|
|
|
# Status Tracking
|
|
status = models.CharField(
|
|
max_length=30,
|
|
choices=Status.choices,
|
|
default=Status.PENDING,
|
|
verbose_name=_("Status")
|
|
)
|
|
acknowledged_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Acknowledged At")
|
|
)
|
|
acknowledged_by = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='acknowledged_referrals',
|
|
verbose_name=_("Acknowledged By")
|
|
)
|
|
|
|
# Appointment Linkage
|
|
appointment = models.ForeignKey(
|
|
'appointments.Appointment',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='referrals',
|
|
verbose_name=_("Appointment"),
|
|
help_text=_("Appointment booked for this referral")
|
|
)
|
|
appointment_booked_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Appointment Booked At")
|
|
)
|
|
appointment_booked_by = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='booked_referral_appointments',
|
|
verbose_name=_("Appointment Booked By")
|
|
)
|
|
|
|
# Completion
|
|
completed_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Completed At")
|
|
)
|
|
outcome_notes = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Outcome Notes"),
|
|
help_text=_("Notes about the referral outcome")
|
|
)
|
|
|
|
# Decline/Cancel
|
|
declined_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Declined At")
|
|
)
|
|
declined_by = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='declined_referrals',
|
|
verbose_name=_("Declined By")
|
|
)
|
|
decline_reason = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Decline Reason")
|
|
)
|
|
|
|
history = HistoricalRecords()
|
|
|
|
class Meta:
|
|
verbose_name = _("Referral")
|
|
verbose_name_plural = _("Referrals")
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['patient', 'status']),
|
|
models.Index(fields=['from_clinic', 'status']),
|
|
models.Index(fields=['to_clinic', 'status']),
|
|
models.Index(fields=['referred_by', 'status']),
|
|
models.Index(fields=['status', 'created_at']),
|
|
models.Index(fields=['tenant', 'status']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Referral: {self.patient} from {self.from_clinic.name_en} to {self.to_clinic.name_en}"
|
|
|
|
def acknowledge(self, user):
|
|
"""Acknowledge receipt of referral."""
|
|
from django.utils import timezone
|
|
self.status = self.Status.ACKNOWLEDGED
|
|
self.acknowledged_at = timezone.now()
|
|
self.acknowledged_by = user
|
|
self.save()
|
|
|
|
def book_appointment(self, appointment, user):
|
|
"""Link appointment to referral."""
|
|
from django.utils import timezone
|
|
self.appointment = appointment
|
|
self.status = self.Status.APPOINTMENT_BOOKED
|
|
self.appointment_booked_at = timezone.now()
|
|
self.appointment_booked_by = user
|
|
self.save()
|
|
|
|
def complete(self, outcome_notes=""):
|
|
"""Mark referral as completed."""
|
|
from django.utils import timezone
|
|
self.status = self.Status.COMPLETED
|
|
self.completed_at = timezone.now()
|
|
self.outcome_notes = outcome_notes
|
|
self.save()
|
|
|
|
def decline(self, user, reason):
|
|
"""Decline the referral."""
|
|
from django.utils import timezone
|
|
self.status = self.Status.DECLINED
|
|
self.declined_at = timezone.now()
|
|
self.declined_by = user
|
|
self.decline_reason = reason
|
|
self.save()
|
|
|
|
@classmethod
|
|
def get_pending_for_clinic(cls, clinic, tenant):
|
|
"""Get pending referrals for a clinic."""
|
|
return cls.objects.filter(
|
|
to_clinic=clinic,
|
|
tenant=tenant,
|
|
status=cls.Status.PENDING
|
|
).select_related('patient', 'from_clinic', 'referred_by')
|
|
|
|
@classmethod
|
|
def get_pending_for_reception(cls, tenant):
|
|
"""Get referrals pending appointment booking."""
|
|
return cls.objects.filter(
|
|
tenant=tenant,
|
|
status__in=[cls.Status.PENDING, cls.Status.ACKNOWLEDGED],
|
|
appointment__isnull=True
|
|
).select_related('patient', 'from_clinic', 'to_clinic', 'referred_by')
|