HH/apps/surveys/ui_views.py
2025-12-28 20:01:22 +03:00

320 lines
10 KiB
Python

"""
Survey Console UI views - Server-rendered templates for survey management
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Q, Prefetch
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from apps.core.services import AuditService
from apps.organizations.models import Department, Hospital
from .models import SurveyInstance, SurveyTemplate
from .tasks import send_satisfaction_feedback
@login_required
def survey_instance_list(request):
"""
Survey instances list view with filters.
Features:
- Server-side pagination
- Filters (status, journey type, hospital, date range)
- Search by patient MRN
- Score display
"""
# Base queryset with optimizations
queryset = SurveyInstance.objects.select_related(
'survey_template',
'patient',
'journey_instance__journey_template',
'journey_stage_instance__stage_template'
).prefetch_related(
'responses__question'
)
# Apply RBAC filters
user = request.user
if user.is_px_admin():
pass # See all
elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(survey_template__hospital=user.hospital)
elif user.hospital:
queryset = queryset.filter(survey_template__hospital=user.hospital)
else:
queryset = queryset.none()
# Apply filters
status_filter = request.GET.get('status')
if status_filter:
queryset = queryset.filter(status=status_filter)
survey_type = request.GET.get('survey_type')
if survey_type:
queryset = queryset.filter(survey_template__survey_type=survey_type)
is_negative = request.GET.get('is_negative')
if is_negative == 'true':
queryset = queryset.filter(is_negative=True)
hospital_filter = request.GET.get('hospital')
if hospital_filter:
queryset = queryset.filter(survey_template__hospital_id=hospital_filter)
# Search
search_query = request.GET.get('search')
if search_query:
queryset = queryset.filter(
Q(patient__mrn__icontains=search_query) |
Q(patient__first_name__icontains=search_query) |
Q(patient__last_name__icontains=search_query) |
Q(encounter_id__icontains=search_query)
)
# Date range
date_from = request.GET.get('date_from')
if date_from:
queryset = queryset.filter(sent_at__gte=date_from)
date_to = request.GET.get('date_to')
if date_to:
queryset = queryset.filter(sent_at__lte=date_to)
# Ordering
order_by = request.GET.get('order_by', '-created_at')
queryset = queryset.order_by(order_by)
# Pagination
page_size = int(request.GET.get('page_size', 25))
paginator = Paginator(queryset, page_size)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
# Get filter options
hospitals = Hospital.objects.filter(status='active')
if not user.is_px_admin() and user.hospital:
hospitals = hospitals.filter(id=user.hospital.id)
# Statistics
stats = {
'total': queryset.count(),
'sent': queryset.filter(status='sent').count(),
'completed': queryset.filter(status='completed').count(),
'negative': queryset.filter(is_negative=True).count(),
}
context = {
'page_obj': page_obj,
'surveys': page_obj.object_list,
'stats': stats,
'hospitals': hospitals,
'filters': request.GET,
}
return render(request, 'surveys/instance_list.html', context)
@login_required
def survey_instance_detail(request, pk):
"""
Survey instance detail view with responses.
Features:
- Full survey details
- All responses
- Score breakdown
- Related journey/stage info
"""
survey = get_object_or_404(
SurveyInstance.objects.select_related(
'survey_template',
'patient',
'journey_instance__journey_template',
'journey_stage_instance__stage_template'
).prefetch_related(
'responses__question'
),
pk=pk
)
# Get responses
responses = survey.responses.all().order_by('question__order')
context = {
'survey': survey,
'responses': responses,
}
return render(request, 'surveys/instance_detail.html', context)
@login_required
def survey_template_list(request):
"""Survey templates list view"""
queryset = SurveyTemplate.objects.select_related('hospital').prefetch_related('questions')
# Apply RBAC filters
user = request.user
if user.is_px_admin():
pass
elif user.hospital:
queryset = queryset.filter(hospital=user.hospital)
else:
queryset = queryset.none()
# Apply filters
survey_type = request.GET.get('survey_type')
if survey_type:
queryset = queryset.filter(survey_type=survey_type)
hospital_filter = request.GET.get('hospital')
if hospital_filter:
queryset = queryset.filter(hospital_id=hospital_filter)
is_active = request.GET.get('is_active')
if is_active == 'true':
queryset = queryset.filter(is_active=True)
elif is_active == 'false':
queryset = queryset.filter(is_active=False)
# Ordering
queryset = queryset.order_by('hospital', 'survey_type', 'name')
# Pagination
page_size = int(request.GET.get('page_size', 25))
paginator = Paginator(queryset, page_size)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
# Get filter options
hospitals = Hospital.objects.filter(status='active')
if not user.is_px_admin() and user.hospital:
hospitals = hospitals.filter(id=user.hospital.id)
context = {
'page_obj': page_obj,
'templates': page_obj.object_list,
'hospitals': hospitals,
'filters': request.GET,
}
return render(request, 'surveys/template_list.html', context)
@login_required
@require_http_methods(["POST"])
def survey_log_patient_contact(request, pk):
"""
Log patient contact for negative survey.
This records that the user contacted the patient to discuss
the negative survey feedback.
"""
survey = get_object_or_404(SurveyInstance, pk=pk)
# Check permission
user = request.user
if not user.is_px_admin() and not user.is_hospital_admin():
if user.hospital and survey.survey_template.hospital != user.hospital:
messages.error(request, "You don't have permission to modify this survey.")
return redirect('surveys:instance_detail', pk=pk)
# Check if survey is negative
if not survey.is_negative:
messages.warning(request, "This survey is not marked as negative.")
return redirect('surveys:instance_detail', pk=pk)
# Get form data
contact_notes = request.POST.get('contact_notes', '')
issue_resolved = request.POST.get('issue_resolved') == 'on'
if not contact_notes:
messages.error(request, "Please provide contact notes.")
return redirect('surveys:instance_detail', pk=pk)
try:
# Update survey
survey.patient_contacted = True
survey.patient_contacted_at = timezone.now()
survey.patient_contacted_by = user
survey.contact_notes = contact_notes
survey.issue_resolved = issue_resolved
survey.save(update_fields=[
'patient_contacted', 'patient_contacted_at',
'patient_contacted_by', 'contact_notes', 'issue_resolved'
])
# Log audit
AuditService.log_event(
event_type='survey_patient_contacted',
description=f"Patient contacted for negative survey by {user.get_full_name()}",
user=user,
content_object=survey,
metadata={
'contact_notes': contact_notes,
'issue_resolved': issue_resolved,
'survey_score': float(survey.total_score) if survey.total_score else None
}
)
status = "resolved" if issue_resolved else "discussed"
messages.success(request, f"Patient contact logged successfully. Issue marked as {status}.")
except Exception as e:
messages.error(request, f"Error logging patient contact: {str(e)}")
return redirect('surveys:instance_detail', pk=pk)
@login_required
@require_http_methods(["POST"])
def survey_send_satisfaction_feedback(request, pk):
"""
Send satisfaction feedback form to patient.
This creates and sends a feedback form to assess patient satisfaction
with how their negative survey concerns were addressed.
"""
survey = get_object_or_404(SurveyInstance, pk=pk)
# Check permission
user = request.user
if not user.is_px_admin() and not user.is_hospital_admin():
if user.hospital and survey.survey_template.hospital != user.hospital:
messages.error(request, "You don't have permission to modify this survey.")
return redirect('surveys:instance_detail', pk=pk)
# Check if survey is negative
if not survey.is_negative:
messages.warning(request, "This survey is not marked as negative.")
return redirect('surveys:instance_detail', pk=pk)
# Check if patient was contacted
if not survey.patient_contacted:
messages.error(request, "Please log patient contact before sending satisfaction feedback.")
return redirect('surveys:instance_detail', pk=pk)
# Check if already sent
if survey.satisfaction_feedback_sent:
messages.warning(request, "Satisfaction feedback has already been sent for this survey.")
return redirect('surveys:instance_detail', pk=pk)
try:
# Trigger async task to send satisfaction feedback
send_satisfaction_feedback.delay(str(survey.id), str(user.id))
messages.success(
request,
"Satisfaction feedback form is being sent to the patient. "
"They will receive a link to provide their feedback."
)
except Exception as e:
messages.error(request, f"Error sending satisfaction feedback: {str(e)}")
return redirect('surveys:instance_detail', pk=pk)