320 lines
10 KiB
Python
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)
|