2025-10-06 15:25:37 +03:00

487 lines
18 KiB
Python

"""
Views for Insurance Approvals app.
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db.models import Q, Count, Case, When, IntegerField
from django.utils import timezone
from django.http import JsonResponse, HttpResponse
from django.contrib.contenttypes.models import ContentType
from datetime import timedelta
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import (
InsuranceApprovalRequest,
ApprovalDocument,
ApprovalCommunicationLog,
ApprovalTemplate,
ApprovalStatusHistory
)
from .forms import (
InsuranceApprovalRequestForm,
ApprovalStatusUpdateForm,
ApprovalDocumentForm,
ApprovalCommunicationForm,
ApprovalTemplateForm,
ApprovalSearchForm
)
# ============================================================================
# Dashboard & List Views
# ============================================================================
class ApprovalDashboardView(LoginRequiredMixin, ListView):
"""Dashboard view showing approval statistics and recent requests."""
model = InsuranceApprovalRequest
template_name = 'insurance_approvals/dashboard.html'
context_object_name = 'recent_requests'
paginate_by = 10
def get_queryset(self):
tenant = self.request.user.tenant
return InsuranceApprovalRequest.objects.filter(
tenant=tenant
).select_related(
'patient', 'insurance_info', 'requesting_provider', 'assigned_to'
).order_by('-created_at')[:10]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
# Get statistics
all_requests = InsuranceApprovalRequest.objects.filter(tenant=tenant)
context['stats'] = {
'total': all_requests.count(),
'pending': all_requests.filter(status='PENDING_SUBMISSION').count(),
'submitted': all_requests.filter(status='SUBMITTED').count(),
'under_review': all_requests.filter(status='UNDER_REVIEW').count(),
'approved': all_requests.filter(status='APPROVED').count(),
'denied': all_requests.filter(status='DENIED').count(),
'urgent': all_requests.filter(is_urgent=True, status__in=['PENDING_SUBMISSION', 'SUBMITTED', 'UNDER_REVIEW']).count(),
'expiring_soon': all_requests.filter(
expiration_date__lte=timezone.now().date() + timedelta(days=30),
expiration_date__gte=timezone.now().date(),
status__in=['APPROVED', 'PARTIALLY_APPROVED']
).count(),
}
# Status breakdown
context['status_breakdown'] = all_requests.values('status').annotate(
count=Count('id')
).order_by('-count')
# Priority breakdown
context['priority_breakdown'] = all_requests.values('priority').annotate(
count=Count('id')
).order_by('-count')
# Request type breakdown
context['type_breakdown'] = all_requests.values('request_type').annotate(
count=Count('id')
).order_by('-count')
# My assignments
if self.request.user.is_authenticated:
context['my_assignments'] = all_requests.filter(
assigned_to=self.request.user,
status__in=['SUBMITTED', 'UNDER_REVIEW', 'MORE_INFO_REQUIRED']
).count()
return context
class ApprovalListView(LoginRequiredMixin, ListView):
"""List view with filtering and search."""
model = InsuranceApprovalRequest
template_name = 'insurance_approvals/approval_list.html'
context_object_name = 'approvals'
paginate_by = 25
def get_queryset(self):
queryset = InsuranceApprovalRequest.objects.filter(
tenant=self.request.user.tenant
).select_related(
'patient', 'insurance_info', 'requesting_provider', 'assigned_to'
).prefetch_related('documents', 'status_history')
# Apply filters from search form
form = ApprovalSearchForm(self.request.GET)
if form.is_valid():
if form.cleaned_data.get('status'):
queryset = queryset.filter(status__in=form.cleaned_data['status'])
if form.cleaned_data.get('priority'):
queryset = queryset.filter(priority__in=form.cleaned_data['priority'])
if form.cleaned_data.get('request_type'):
queryset = queryset.filter(request_type__in=form.cleaned_data['request_type'])
if form.cleaned_data.get('patient_name'):
queryset = queryset.filter(
Q(patient__first_name__icontains=form.cleaned_data['patient_name']) |
Q(patient__last_name__icontains=form.cleaned_data['patient_name'])
)
if form.cleaned_data.get('approval_number'):
queryset = queryset.filter(approval_number__icontains=form.cleaned_data['approval_number'])
if form.cleaned_data.get('authorization_number'):
queryset = queryset.filter(authorization_number__icontains=form.cleaned_data['authorization_number'])
if form.cleaned_data.get('date_from'):
queryset = queryset.filter(created_at__date__gte=form.cleaned_data['date_from'])
if form.cleaned_data.get('date_to'):
queryset = queryset.filter(created_at__date__lte=form.cleaned_data['date_to'])
if form.cleaned_data.get('expiring_soon'):
queryset = queryset.filter(
expiration_date__lte=timezone.now().date() + timedelta(days=30),
expiration_date__gte=timezone.now().date()
)
if form.cleaned_data.get('assigned_to_me'):
queryset = queryset.filter(assigned_to=self.request.user)
# Default ordering
return queryset.order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_form'] = ApprovalSearchForm(self.request.GET)
return context
class ApprovalDetailView(LoginRequiredMixin, DetailView):
"""Detailed view of an approval request."""
model = InsuranceApprovalRequest
template_name = 'insurance_approvals/approval_detail.html'
context_object_name = 'approval'
def get_queryset(self):
return InsuranceApprovalRequest.objects.filter(
tenant = self.request.user.tenant
).select_related(
'patient', 'insurance_info', 'requesting_provider',
'assigned_to', 'submitted_by', 'created_by'
).prefetch_related(
'documents', 'status_history', 'communications'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
approval = self.object
# Forms for various actions
context['status_form'] = ApprovalStatusUpdateForm()
context['document_form'] = ApprovalDocumentForm()
context['communication_form'] = ApprovalCommunicationForm()
# Get related order
if approval.content_type and approval.object_id:
try:
context['related_order'] = approval.content_object
except:
context['related_order'] = None
# Timeline of events
context['timeline'] = self._build_timeline(approval)
return context
def _build_timeline(self, approval):
"""Build a timeline of all events for this approval."""
events = []
# Status changes
for history in approval.status_history.all():
events.append({
'type': 'status_change',
'timestamp': history.changed_at,
'user': history.changed_by,
'data': history
})
# Communications
for comm in approval.communications.all():
events.append({
'type': 'communication',
'timestamp': comm.communicated_at,
'user': comm.communicated_by,
'data': comm
})
# Documents
for doc in approval.documents.all():
events.append({
'type': 'document',
'timestamp': doc.uploaded_at,
'user': doc.uploaded_by,
'data': doc
})
# Sort by timestamp
events.sort(key=lambda x: x['timestamp'], reverse=True)
return events
# ============================================================================
# Create & Update Views
# ============================================================================
class ApprovalCreateView(LoginRequiredMixin, CreateView):
"""Create a new approval request."""
model = InsuranceApprovalRequest
form_class = InsuranceApprovalRequestForm
template_name = 'insurance_approvals/approval_form.html'
success_url = reverse_lazy('insurance_approvals:list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['tenant'] = self.request.user.tenant
kwargs['user'] = self.request.user
# If creating from an order, get the order object
if 'content_type_id' in self.request.GET and 'object_id' in self.request.GET:
try:
content_type = ContentType.objects.get(pk=self.request.GET['content_type_id'])
order = content_type.get_object_for_this_type(pk=self.request.GET['object_id'])
kwargs['order'] = order
except:
pass
return kwargs
def form_valid(self, form):
messages.success(self.request, 'Insurance approval request created successfully.')
return super().form_valid(form)
class ApprovalUpdateView(LoginRequiredMixin, UpdateView):
"""Update an existing approval request."""
model = InsuranceApprovalRequest
form_class = InsuranceApprovalRequestForm
template_name = 'insurance_approvals/approval_form.html'
def get_queryset(self):
tenant = self.request.user.tenant
return InsuranceApprovalRequest.objects.filter(tenant=tenant)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['tenant'] = self.request.user.tenant
kwargs['user'] = self.request.user
return kwargs
def get_success_url(self):
return reverse_lazy('insurance_approvals:detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
messages.success(self.request, 'Approval request updated successfully.')
return super().form_valid(form)
# ============================================================================
# HTMX Views
# ============================================================================
@login_required
def htmx_update_status(request, pk):
tenant = request.user.tenant
"""HTMX endpoint to update approval status."""
approval = get_object_or_404(
InsuranceApprovalRequest,
pk=pk,
tenant=tenant
)
if request.method == 'POST':
form = ApprovalStatusUpdateForm(request.POST)
if form.is_valid():
# Update status
old_status = approval.status
approval.status = form.cleaned_data['status']
# Update approval-specific fields
if form.cleaned_data.get('authorization_number'):
approval.authorization_number = form.cleaned_data['authorization_number']
if form.cleaned_data.get('approved_quantity'):
approval.approved_quantity = form.cleaned_data['approved_quantity']
if form.cleaned_data.get('effective_date'):
approval.effective_date = form.cleaned_data['effective_date']
if form.cleaned_data.get('expiration_date'):
approval.expiration_date = form.cleaned_data['expiration_date']
# Update denial-specific fields
if form.cleaned_data.get('denial_reason'):
approval.denial_reason = form.cleaned_data['denial_reason']
if form.cleaned_data.get('denial_code'):
approval.denial_code = form.cleaned_data['denial_code']
approval.save()
# Create status history (signal will handle this, but we can also do it manually)
ApprovalStatusHistory.objects.create(
approval_request=approval,
from_status=old_status,
to_status=approval.status,
reason=form.cleaned_data.get('reason'),
notes=form.cleaned_data.get('notes'),
changed_by=request.user
)
messages.success(request, f'Status updated to {approval.get_status_display()}')
# Return updated status badge
return render(request, 'insurance_approvals/partials/status_badge.html', {
'approval': approval
})
form = ApprovalStatusUpdateForm()
return render(request, 'insurance_approvals/partials/status_update_form.html', {
'form': form,
'approval': approval
})
@login_required
def htmx_upload_document(request, pk):
tenant = request.user.tenant
"""HTMX endpoint to upload a document."""
approval = get_object_or_404(
InsuranceApprovalRequest,
pk=pk,
tenant=tenant
)
if request.method == 'POST':
form = ApprovalDocumentForm(request.POST, request.FILES)
if form.is_valid():
document = form.save(commit=False)
document.approval_request = approval
document.uploaded_by = request.user
document.save()
messages.success(request, 'Document uploaded successfully.')
# Return updated document list
return render(request, 'insurance_approvals/partials/document_list.html', {
'approval': approval
})
form = ApprovalDocumentForm()
return render(request, 'insurance_approvals/partials/document_upload_form.html', {
'form': form,
'approval': approval
})
@login_required
def htmx_log_communication(request, pk):
"""HTMX endpoint to log a communication."""
tenant = request.user.tenant
approval = get_object_or_404(
InsuranceApprovalRequest,
pk=pk,
tenant = tenant
)
if request.method == 'POST':
form = ApprovalCommunicationForm(request.POST)
if form.is_valid():
communication = form.save(commit=False)
communication.approval_request = approval
communication.communicated_by = request.user
communication.save()
# Update last contact info on approval
approval.last_contact_date = communication.communicated_at
approval.last_contact_method = communication.communication_type
approval.last_contact_notes = communication.message[:500]
approval.save()
messages.success(request, 'Communication logged successfully.')
# Return updated communication list
return render(request, 'insurance_approvals/partials/communication_list.html', {
'approval': approval
})
form = ApprovalCommunicationForm()
return render(request, 'insurance_approvals/partials/communication_form.html', {
'form': form,
'approval': approval
})
@login_required
def htmx_dashboard_stats(request):
"""HTMX endpoint for real-time dashboard statistics."""
tenant = request.user.tenant
all_requests = InsuranceApprovalRequest.objects.filter(tenant=tenant)
stats = {
'total': all_requests.count(),
'pending': all_requests.filter(status='PENDING_SUBMISSION').count(),
'submitted': all_requests.filter(status='SUBMITTED').count(),
'under_review': all_requests.filter(status='UNDER_REVIEW').count(),
'approved': all_requests.filter(status='APPROVED').count(),
'denied': all_requests.filter(status='DENIED').count(),
'urgent': all_requests.filter(is_urgent=True, status__in=['PENDING_SUBMISSION', 'SUBMITTED', 'UNDER_REVIEW']).count(),
'expiring_soon': all_requests.filter(
expiration_date__lte=timezone.now().date() + timedelta(days=30),
expiration_date__gte=timezone.now().date(),
status__in=['APPROVED', 'PARTIALLY_APPROVED']
).count(),
}
return render(request, 'insurance_approvals/partials/dashboard_stats.html', {'stats': stats})
# ============================================================================
# Utility Views
# ============================================================================
@login_required
def create_from_order(request, content_type_id, object_id):
"""Create an approval request from an existing order."""
try:
content_type = ContentType.objects.get(pk=content_type_id)
order = content_type.get_object_for_this_type(pk=object_id)
# Redirect to create view with order info
return redirect(f"{reverse_lazy('insurance_approvals:create')}?content_type_id={content_type_id}&object_id={object_id}")
except:
messages.error(request, 'Could not find the specified order.')
return redirect('insurance_approvals:list')
@login_required
def submit_approval(request, pk):
tenant = request.user.tenant
"""Submit an approval request to insurance."""
approval = get_object_or_404(
InsuranceApprovalRequest,
pk=pk,
tenant = tenant
)
if approval.status == 'DRAFT':
approval.status = 'PENDING_SUBMISSION'
approval.submitted_by = request.user
approval.submitted_date = timezone.now()
approval.save()
messages.success(request, 'Approval request submitted successfully.')
else:
messages.warning(request, 'This approval request has already been submitted.')
return redirect('insurance_approvals:detail', pk=pk)