487 lines
18 KiB
Python
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)
|