agdar/referrals/views.py
Marwan Alwali 2f1681b18c update
2025-11-11 13:44:48 +03:00

638 lines
24 KiB
Python

"""
Referrals views for the Tenhal Multidisciplinary Healthcare Platform.
This module contains views for referral management including:
- Inter-discipline referrals
- External provider referrals
- Referral tracking and status
- Auto-referral rules (e.g., ASD→Pediatrician)
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone
from django.views.generic import ListView, DetailView, CreateView, UpdateView, TemplateView, FormView
from django.urls import reverse_lazy
from django.contrib import messages
from django.http import JsonResponse
from django.views import View
from core.mixins import (
TenantFilterMixin,
RolePermissionMixin,
AuditLogMixin,
HTMXResponseMixin,
SuccessMessageMixin,
PaginationMixin,
)
from core.models import User, Patient, Tenant
from .models import Referral
from .forms import ReferralForm, ExternalReferralForm
class ReferralListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin,
HTMXResponseMixin, ListView):
"""
Referral list view.
Features:
- Filter by patient, from/to discipline, status, urgency
- Search by patient name/MRN
- Role-based filtering (providers see their referrals)
"""
model = Referral
template_name = 'referrals/referral_list.html'
htmx_template_name = 'referrals/partials/referral_list_partial.html'
context_object_name = 'referrals'
paginate_by = 25
def get_queryset(self):
"""Get filtered queryset."""
queryset = super().get_queryset()
user = self.request.user
# Role-based filtering
if user.role in [User.Role.DOCTOR, User.Role.NURSE, User.Role.OT,
User.Role.SLP, User.Role.ABA]:
# Clinical staff see referrals from or to them, plus external referrals to their clinic
queryset = queryset.filter(
Q(referred_by=user) |
Q(referred_to=user) |
Q(from_discipline=Referral.Discipline.EXTERNAL, to_clinic__isnull=False)
)
# Admins and other roles see all referrals (no additional filtering needed)
# Apply search
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(patient__first_name_en__icontains=search_query) |
Q(patient__last_name_en__icontains=search_query) |
Q(patient__mrn__icontains=search_query)
)
# Apply filters
patient_id = self.request.GET.get('patient')
if patient_id:
queryset = queryset.filter(patient_id=patient_id)
from_discipline = self.request.GET.get('from_discipline')
if from_discipline:
queryset = queryset.filter(from_discipline=from_discipline)
to_discipline = self.request.GET.get('to_discipline')
if to_discipline:
queryset = queryset.filter(to_discipline=to_discipline)
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
urgency = self.request.GET.get('urgency')
if urgency:
queryset = queryset.filter(urgency=urgency)
date_from = self.request.GET.get('date_from')
if date_from:
queryset = queryset.filter(created_at__gte=date_from)
date_to = self.request.GET.get('date_to')
if date_to:
queryset = queryset.filter(created_at__lte=date_to)
return queryset.select_related(
'patient', 'referred_by', 'referred_to'
).order_by('-created_at')
def get_context_data(self, **kwargs):
"""Add filter options."""
context = super().get_context_data(**kwargs)
# Add filter options
context['status_choices'] = Referral.Status.choices
context['priority_choices'] = Referral.Priority.choices
context['referral_type_choices'] = Referral.ReferralType.choices
# Add current filters
context['current_filters'] = {
'search': self.request.GET.get('search', ''),
'patient': self.request.GET.get('patient', ''),
'from_discipline': self.request.GET.get('from_discipline', ''),
'to_discipline': self.request.GET.get('to_discipline', ''),
'status': self.request.GET.get('status', ''),
'urgency': self.request.GET.get('urgency', ''),
'date_from': self.request.GET.get('date_from', ''),
'date_to': self.request.GET.get('date_to', ''),
}
# Get all referrals
queryset = self.get_queryset()
# Split into sent and received
user = self.request.user
if user.role == User.Role.ADMIN:
# Admins see all referrals in both tabs
context['sent_referrals'] = queryset.filter(referred_by__isnull=False)
context['received_referrals'] = queryset
else:
# Clinical staff see their sent and received referrals
context['sent_referrals'] = queryset.filter(referred_by=user)
context['received_referrals'] = queryset.filter(
Q(referred_to=user) |
Q(from_discipline=Referral.Discipline.EXTERNAL, to_clinic__isnull=False)
)
# Add statistics
context['stats'] = {
'total_referrals': queryset.count(),
'pending': queryset.filter(status=Referral.Status.PENDING).count(),
'accepted': queryset.filter(status=Referral.Status.ACKNOWLEDGED).count(),
'completed': queryset.filter(status=Referral.Status.COMPLETED).count(),
'urgent': queryset.filter(priority=Referral.Priority.URGENT).count(),
}
return context
class ReferralDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView):
"""
Referral detail view.
Features:
- Full referral details
- From/to discipline and provider
- Reason, urgency, status
- Response and notes
- Status history
"""
model = Referral
template_name = 'referrals/referral_detail.html'
context_object_name = 'referral'
def get_context_data(self, **kwargs):
"""Add status history and available actions."""
context = super().get_context_data(**kwargs)
referral = self.object
# Get status history if available
if hasattr(referral, 'history'):
context['status_history'] = referral.history.all()[:10]
# Check if user can accept/reject
user = self.request.user
context['can_respond'] = (
referral.status == Referral.Status.PENDING and
(user == referral.referred_to or user.role == User.Role.ADMIN)
)
# Check if user can cancel
context['can_cancel'] = (
referral.status in [Referral.Status.PENDING, Referral.Status.ACKNOWLEDGED] and
(user == referral.referred_by or user.role == User.Role.ADMIN)
)
return context
class ReferralCreateView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin,
SuccessMessageMixin, CreateView):
"""
Referral creation view.
Features:
- Create inter-discipline or external referral
- From/to discipline
- Reason, urgency
- Auto-referral rules (e.g., ASD→Pediatrician)
"""
model = Referral
form_class = ReferralForm
template_name = 'referrals/referral_form.html'
success_message = "Referral created successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE,
User.Role.OT, User.Role.SLP, User.Role.ABA]
def get_success_url(self):
"""Redirect to referral detail."""
return reverse_lazy('referrals:referral_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
"""Set tenant and referred_by."""
# Set tenant
form.instance.tenant = self.request.user.tenant
# Set referred_by
form.instance.referred_by = self.request.user
# Set from_discipline based on user role
form.instance.from_discipline = self._get_discipline_from_role(self.request.user.role)
# Set referral date if not provided
if not form.instance.referral_date:
form.instance.referral_date = timezone.now().date()
# Set initial status
form.instance.status = Referral.Status.PENDING
# Save referral
response = super().form_valid(form)
# Send notification to recipient (TODO)
self._send_referral_notification()
return response
def _get_discipline_from_role(self, role):
"""Map user role to discipline."""
role_to_discipline = {
User.Role.DOCTOR: Referral.Discipline.MEDICAL,
User.Role.NURSE: Referral.Discipline.NURSING,
User.Role.OT: Referral.Discipline.OT,
User.Role.SLP: Referral.Discipline.SLP,
User.Role.ABA: Referral.Discipline.ABA,
}
return role_to_discipline.get(role, Referral.Discipline.MEDICAL)
def _send_referral_notification(self):
"""Send notification to recipient."""
# TODO: Implement notification
pass
def get_initial(self):
"""Set initial form values."""
initial = super().get_initial()
# Pre-populate patient if provided in URL
patient_id = self.request.GET.get('patient')
if patient_id:
try:
patient = Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
initial['patient'] = patient
except Patient.DoesNotExist:
pass
return initial
def get_context_data(self, **kwargs):
"""Add form title and patient info."""
context = super().get_context_data(**kwargs)
context['form_title'] = 'Create Referral'
context['submit_text'] = 'Create Referral'
# Get patient if provided
patient_id = self.request.GET.get('patient')
if patient_id:
try:
context['patient'] = Patient.objects.get(
pk=patient_id,
tenant=self.request.user.tenant
)
except Patient.DoesNotExist:
pass
return context
class ReferralUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin,
AuditLogMixin, SuccessMessageMixin, UpdateView):
"""
Referral update view.
Features:
- Update referral details
- Only creator can update pending referrals
"""
model = Referral
form_class = ReferralForm
template_name = 'referrals/referral_form.html'
success_message = "Referral updated successfully!"
allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE,
User.Role.OT, User.Role.SLP, User.Role.ABA]
def get_success_url(self):
"""Redirect to referral detail."""
return reverse_lazy('referrals:referral_detail', kwargs={'pk': self.object.pk})
def get_form(self, form_class=None):
"""Disable fields if referral is not pending."""
form = super().get_form(form_class)
if self.object.status != Referral.Status.PENDING:
for field in form.fields:
form.fields[field].disabled = True
return form
def get_context_data(self, **kwargs):
"""Add form title."""
context = super().get_context_data(**kwargs)
context['form_title'] = f'Update Referral - {self.object.patient.mrn}'
context['submit_text'] = 'Update Referral'
return context
class ReferralAcceptView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""
Accept referral view.
Features:
- Accept pending referral
- Redirect to detail page with success message
"""
allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE,
User.Role.OT, User.Role.SLP, User.Role.ABA]
def get(self, request, *args, **kwargs):
"""Mark referral as accepted."""
referral = get_object_or_404(Referral, pk=kwargs['pk'], tenant=request.user.tenant)
# Check if user can accept
if referral.status != Referral.Status.PENDING:
messages.error(request, 'Only pending referrals can be accepted.')
return redirect('referrals:referral_detail', pk=referral.pk)
if request.user != referral.referred_to and request.user.role != User.Role.ADMIN:
messages.error(request, 'You do not have permission to accept this referral.')
return redirect('referrals:referral_detail', pk=referral.pk)
# Update status
referral.status = Referral.Status.ACKNOWLEDGED
referral.responded_at = timezone.now()
referral.save()
# Send notification to referrer (TODO)
self._send_acceptance_notification(referral)
messages.success(request, 'Referral accepted successfully!')
return redirect('referrals:referral_detail', pk=referral.pk)
def _send_acceptance_notification(self, referral):
"""Send notification to referrer."""
# TODO: Implement notification
pass
class ReferralRejectView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""
Reject referral view.
Features:
- Reject pending referral
- Redirect to detail page with message
"""
allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE,
User.Role.OT, User.Role.SLP, User.Role.ABA]
def get(self, request, *args, **kwargs):
"""Mark referral as rejected."""
referral = get_object_or_404(Referral, pk=kwargs['pk'], tenant=request.user.tenant)
# Check if user can reject
if referral.status != Referral.Status.PENDING:
messages.error(request, 'Only pending referrals can be rejected.')
return redirect('referrals:referral_detail', pk=referral.pk)
if request.user != referral.referred_to and request.user.role != User.Role.ADMIN:
messages.error(request, 'You do not have permission to reject this referral.')
return redirect('referrals:referral_detail', pk=referral.pk)
# Update status
referral.status = Referral.Status.DECLINED
referral.responded_at = timezone.now()
referral.save()
# Send notification to referrer (TODO)
self._send_rejection_notification(referral)
messages.warning(request, 'Referral rejected.')
return redirect('referrals:referral_detail', pk=referral.pk)
def _send_rejection_notification(self, referral):
"""Send notification to referrer."""
# TODO: Implement notification
pass
class ReferralCompleteView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View):
"""
Complete referral view.
Features:
- Mark accepted referral as completed
- Redirect to detail page with success message
"""
allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE,
User.Role.OT, User.Role.SLP, User.Role.ABA]
def get(self, request, *args, **kwargs):
"""Mark referral as completed."""
referral = get_object_or_404(Referral, pk=kwargs['pk'], tenant=request.user.tenant)
# Check if user can complete
if referral.status != Referral.Status.ACKNOWLEDGED:
messages.error(request, 'Only accepted referrals can be completed.')
return redirect('referrals:referral_detail', pk=referral.pk)
if request.user != referral.referred_to and request.user.role != User.Role.ADMIN:
messages.error(request, 'You do not have permission to complete this referral.')
return redirect('referrals:referral_detail', pk=referral.pk)
# Update status
referral.status = Referral.Status.COMPLETED
referral.completed_at = timezone.now()
referral.save()
# Send notification to referrer (TODO)
self._send_completion_notification(referral)
messages.success(request, 'Referral marked as completed!')
return redirect('referrals:referral_detail', pk=referral.pk)
def _send_completion_notification(self, referral):
"""Send notification to referrer."""
# TODO: Implement notification
pass
class ExternalReferralCreateView(FormView):
"""
Public view for external centers to submit referrals.
Does not require authentication.
"""
form_class = ExternalReferralForm
template_name = 'referrals/external_referral_form.html'
success_url = reverse_lazy('referrals:external_referral_success')
def get_context_data(self, **kwargs):
"""Add tenant information to context."""
context = super().get_context_data(**kwargs)
# Get tenant from subdomain or settings (you may need to adjust this)
# For now, we'll get the first active tenant
try:
tenant = Tenant.objects.filter(is_active=True).first()
context['tenant'] = tenant
except Tenant.DoesNotExist:
context['tenant'] = None
return context
def form_valid(self, form):
"""Process the external referral submission."""
# Get tenant (adjust based on your multi-tenancy setup)
tenant = Tenant.objects.filter(is_active=True).first()
if not tenant:
messages.error(self.request, 'Unable to process referral at this time.')
return self.form_invalid(form)
# Create or get patient
patient_data = {
'first_name_en': form.cleaned_data['patient_first_name'],
'last_name_en': form.cleaned_data['patient_last_name'],
'date_of_birth': form.cleaned_data['patient_date_of_birth'],
'sex': form.cleaned_data['patient_sex'],
'national_id': form.cleaned_data.get('patient_national_id', ''),
'caregiver_name': form.cleaned_data['caregiver_name'],
'caregiver_phone': form.cleaned_data['caregiver_phone'],
'caregiver_relationship': form.cleaned_data['caregiver_relationship'],
'tenant': tenant,
}
# Try to find existing patient by national_id or create new
patient = None
if patient_data['national_id']:
patient = Patient.objects.filter(
tenant=tenant,
national_id=patient_data['national_id']
).first()
if not patient:
# Create new patient (MRN will be auto-generated by signal)
patient = Patient.objects.create(**patient_data)
# Get or create an "External" clinic for external referrals
from core.models import Clinic
from_clinic, _ = Clinic.objects.get_or_create(
tenant=tenant,
code='EXT',
defaults={
'name_en': 'External Referrals',
'name_ar': 'الإحالات الخارجية',
'specialty': 'EXTERNAL',
'is_active': True,
}
)
# Map requested specialty to target clinic
to_clinic = None
to_discipline = None
requested_specialty = form.cleaned_data.get('requested_specialty')
if requested_specialty:
to_clinic = Clinic.objects.filter(
tenant=tenant,
specialty=requested_specialty,
is_active=True
).first()
if to_clinic:
to_discipline = requested_specialty
# Create referral object
referral = Referral.objects.create(
tenant=tenant,
patient=patient,
from_clinic=from_clinic,
external_provider_name=form.cleaned_data['referring_center_name'],
external_provider_contact=(
f"Doctor: {form.cleaned_data['referring_doctor_name']}\n"
f"Phone: {form.cleaned_data['referring_contact_phone']}\n"
f"Email: {form.cleaned_data['referring_contact_email']}"
),
reason=form.cleaned_data['reason'],
urgency=form.cleaned_data['urgency'],
clinical_summary=form.cleaned_data.get('clinical_summary', ''),
from_discipline=Referral.Discipline.EXTERNAL,
status=Referral.Status.PENDING,
to_clinic=to_clinic,
to_discipline=to_discipline,
notes=(
f"External Referral from: {form.cleaned_data['referring_center_name']}\n"
f"Referring Doctor: {form.cleaned_data['referring_doctor_name']}\n"
f"Contact: {form.cleaned_data['referring_contact_phone']} / {form.cleaned_data['referring_contact_email']}"
)
)
# Send notification to staff (TODO: implement)
self._send_staff_notification(referral, form.cleaned_data)
# Send confirmation email to referring center
self._send_confirmation_email(referral, form.cleaned_data)
messages.success(
self.request,
'Referral submitted successfully! We will contact you soon with updates.'
)
return super().form_valid(form)
def _send_staff_notification(self, referral, form_data):
"""Send notification to internal staff about new external referral."""
# TODO: Implement notification to staff
pass
def _send_confirmation_email(self, referral, form_data):
"""Send confirmation email to referring center."""
# TODO: Implement confirmation email
pass
class ExternalReferralSuccessView(TemplateView):
"""Success page after external referral submission."""
template_name = 'referrals/external_referral_success.html'
class GetProvidersByClinicView(LoginRequiredMixin, View):
"""
AJAX endpoint to get providers filtered by clinic.
Returns JSON list of providers for the selected clinic.
"""
def get(self, request, *args, **kwargs):
"""Get providers for the specified clinic."""
clinic_id = request.GET.get('clinic_id')
if not clinic_id:
return JsonResponse({'providers': []})
try:
# Get providers who have this clinic in their specialties
from appointments.models import Provider
providers = Provider.objects.filter(
tenant=request.user.tenant,
specialties__id=clinic_id,
is_available=True,
user__is_active=True
).select_related('user').values(
'user__id',
'user__first_name',
'user__last_name'
).distinct()
# Format provider data
provider_list = [
{
'id': str(provider['user__id']),
'name': f"{provider['user__first_name']} {provider['user__last_name']}"
}
for provider in providers
]
return JsonResponse({'providers': provider_list})
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)