agdar/referrals/views.py
2025-11-02 14:35:35 +03:00

626 lines
22 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
queryset = queryset.filter(
Q(from_provider=user) | Q(to_provider=user)
)
# 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', 'from_provider', 'to_provider'
).order_by('-created_at')
def get_context_data(self, **kwargs):
"""Add filter options."""
context = super().get_context_data(**kwargs)
# Add filter options
context['discipline_choices'] = Referral.Discipline.choices
context['status_choices'] = Referral.Status.choices
context['urgency_choices'] = Referral.Urgency.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', ''),
}
# Add statistics
queryset = self.get_queryset()
context['stats'] = {
'total_referrals': queryset.count(),
'pending': queryset.filter(status=Referral.Status.PENDING).count(),
'accepted': queryset.filter(status=Referral.Status.ACCEPTED).count(),
'urgent': queryset.filter(urgency=Referral.Urgency.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.to_provider or user.role == User.Role.ADMIN)
)
# Check if user can cancel
context['can_cancel'] = (
referral.status in [Referral.Status.PENDING, Referral.Status.ACCEPTED] and
(user == referral.from_provider 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 from_provider."""
# Set tenant
form.instance.tenant = self.request.user.tenant
# Set from_provider
form.instance.from_provider = 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_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, AuditLogMixin,
SuccessMessageMixin, UpdateView):
"""
Accept referral view.
Features:
- Accept pending referral
- Add response notes
- Update status to ACCEPTED
"""
model = Referral
fields = ['response_notes']
template_name = 'referrals/referral_accept.html'
success_message = "Referral accepted 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):
"""Update status to ACCEPTED."""
referral = form.instance
# Check if user can accept
if referral.status != Referral.Status.PENDING:
messages.error(self.request, 'Only pending referrals can be accepted.')
return redirect('referrals:referral_detail', pk=referral.pk)
if self.request.user != referral.to_provider and self.request.user.role != User.Role.ADMIN:
messages.error(self.request, 'You do not have permission to accept this referral.')
return redirect('referrals:referral_detail', pk=referral.pk)
# Update status
referral.status = Referral.Status.ACCEPTED
referral.response_date = timezone.now().date()
# Save
response = super().form_valid(form)
# Send notification to referrer
self._send_acceptance_notification(referral)
return response
def _send_acceptance_notification(self, referral):
"""Send notification to referrer."""
# TODO: Implement notification
pass
class ReferralRejectView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin,
SuccessMessageMixin, UpdateView):
"""
Reject referral view.
Features:
- Reject pending referral
- Add rejection reason
- Update status to REJECTED
"""
model = Referral
fields = ['response_notes']
template_name = 'referrals/referral_reject.html'
success_message = "Referral rejected."
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):
"""Update status to REJECTED."""
referral = form.instance
# Check if user can reject
if referral.status != Referral.Status.PENDING:
messages.error(self.request, 'Only pending referrals can be rejected.')
return redirect('referrals:referral_detail', pk=referral.pk)
if self.request.user != referral.to_provider and self.request.user.role != User.Role.ADMIN:
messages.error(self.request, 'You do not have permission to reject this referral.')
return redirect('referrals:referral_detail', pk=referral.pk)
# Update status
referral.status = Referral.Status.REJECTED
referral.response_date = timezone.now().date()
# Save
response = super().form_valid(form)
# Send notification to referrer
self._send_rejection_notification(referral)
return response
def _send_rejection_notification(self, referral):
"""Send notification to referrer."""
# TODO: Implement notification
pass
class ReferralCompleteView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin,
SuccessMessageMixin, UpdateView):
"""
Complete referral view.
Features:
- Mark accepted referral as completed
- Add completion notes
- Update status to COMPLETED
"""
model = Referral
fields = ['completion_notes']
template_name = 'referrals/referral_complete.html'
success_message = "Referral marked as completed!"
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):
"""Update status to COMPLETED."""
referral = form.instance
# Check if user can complete
if referral.status != Referral.Status.ACCEPTED:
messages.error(self.request, 'Only accepted referrals can be completed.')
return redirect('referrals:referral_detail', pk=referral.pk)
if self.request.user != referral.to_provider and self.request.user.role != User.Role.ADMIN:
messages.error(self.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.completion_date = timezone.now().date()
# Save
response = super().form_valid(form)
# Send notification to referrer
self._send_completion_notification(referral)
return response
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:
# Generate MRN for new patient
from core.services import generate_mrn
patient_data['mrn'] = generate_mrn(tenant)
patient = Patient.objects.create(**patient_data)
# Create referral
referral = form.instance
referral.tenant = tenant
referral.patient = patient
# Set external provider information
referral.external_provider_name = form.cleaned_data['referring_center_name']
referral.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']}"
)
# Set referral details
referral.reason = form.cleaned_data['reason']
referral.urgency = form.cleaned_data['urgency']
referral.clinical_summary = form.cleaned_data.get('clinical_summary', '')
referral.from_discipline = Referral.Discipline.EXTERNAL
referral.status = Referral.Status.PENDING
# Map requested specialty to clinic
requested_specialty = form.cleaned_data.get('requested_specialty')
if requested_specialty:
from core.models import Clinic
to_clinic = Clinic.objects.filter(
tenant=tenant,
specialty=requested_specialty,
is_active=True
).first()
if to_clinic:
referral.to_clinic = to_clinic
referral.to_discipline = requested_specialty
# Store referring center contact info in notes
referral.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']}"
)
referral.save()
# 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)