271 lines
9.0 KiB
Python
271 lines
9.0 KiB
Python
"""
|
|
Surveys views and viewsets
|
|
"""
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils import timezone
|
|
from rest_framework import status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from apps.accounts.permissions import IsPXAdminOrHospitalAdmin
|
|
from apps.core.services import AuditService
|
|
|
|
from .models import SurveyInstance, SurveyQuestion, SurveyResponse, SurveyTemplate
|
|
from .serializers import (
|
|
PublicSurveySerializer,
|
|
SurveyInstanceSerializer,
|
|
SurveyQuestionSerializer,
|
|
SurveyResponseSerializer,
|
|
SurveySubmissionSerializer,
|
|
SurveyTemplateSerializer,
|
|
)
|
|
|
|
|
|
class SurveyTemplateViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for Survey Templates.
|
|
|
|
Permissions:
|
|
- PX Admins and Hospital Admins can manage templates
|
|
- Others can view templates
|
|
"""
|
|
queryset = SurveyTemplate.objects.all()
|
|
serializer_class = SurveyTemplateSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = ['survey_type', 'hospital', 'is_active', 'hospital__organization']
|
|
search_fields = ['name', 'name_ar', 'description']
|
|
ordering_fields = ['name', 'created_at']
|
|
ordering = ['hospital', 'name']
|
|
|
|
def get_queryset(self):
|
|
"""Filter templates based on user role"""
|
|
queryset = super().get_queryset().select_related('hospital').prefetch_related('questions')
|
|
user = self.request.user
|
|
|
|
# PX Admins see all templates
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins see templates for their hospital
|
|
if user.is_hospital_admin() and user.hospital:
|
|
return queryset.filter(hospital=user.hospital)
|
|
|
|
# Others see templates for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
|
|
class SurveyQuestionViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for Survey Questions.
|
|
|
|
Permissions:
|
|
- PX Admins and Hospital Admins can manage questions
|
|
"""
|
|
queryset = SurveyQuestion.objects.all()
|
|
serializer_class = SurveyQuestionSerializer
|
|
permission_classes = [IsAuthenticated, IsPXAdminOrHospitalAdmin]
|
|
filterset_fields = ['survey_template', 'question_type', 'is_required']
|
|
search_fields = ['text', 'text_ar']
|
|
ordering_fields = ['order', 'created_at']
|
|
ordering = ['survey_template', 'order']
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset().select_related('survey_template')
|
|
user = self.request.user
|
|
|
|
# PX Admins see all questions
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins see questions for their hospital
|
|
if user.is_hospital_admin() and user.hospital:
|
|
return queryset.filter(survey_template__hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
|
|
class SurveyInstanceViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for Survey Instances.
|
|
|
|
Permissions:
|
|
- All authenticated users can view survey instances
|
|
- PX Admins and Hospital Admins can create/manage instances
|
|
"""
|
|
queryset = SurveyInstance.objects.all()
|
|
serializer_class = SurveyInstanceSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = [
|
|
'survey_template', 'patient', 'status',
|
|
'delivery_channel', 'is_negative', 'journey_instance',
|
|
'survey_template__hospital__organization'
|
|
]
|
|
search_fields = ['patient__mrn', 'patient__first_name', 'patient__last_name', 'encounter_id']
|
|
ordering_fields = ['sent_at', 'completed_at', 'created_at']
|
|
ordering = ['-created_at']
|
|
|
|
def get_queryset(self):
|
|
"""Filter survey instances based on user role"""
|
|
queryset = super().get_queryset().select_related(
|
|
'survey_template', 'patient', 'journey_instance', 'journey_stage_instance'
|
|
).prefetch_related('responses')
|
|
|
|
user = self.request.user
|
|
|
|
# PX Admins see all survey instances
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins see instances for their hospital
|
|
if user.is_hospital_admin() and user.hospital:
|
|
return queryset.filter(survey_template__hospital=user.hospital)
|
|
|
|
# Others see instances for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(survey_template__hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def resend(self, request, pk=None):
|
|
"""Resend survey invitation"""
|
|
survey_instance = self.get_object()
|
|
|
|
if survey_instance.status == 'completed':
|
|
return Response(
|
|
{'error': 'Cannot resend completed survey'},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# Queue survey send task
|
|
from apps.surveys.tasks import send_survey_reminder
|
|
send_survey_reminder.delay(str(survey_instance.id))
|
|
|
|
return Response({'message': 'Survey invitation queued for resend'})
|
|
|
|
|
|
class SurveyResponseViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""
|
|
ViewSet for Survey Responses (read-only).
|
|
|
|
Responses are created via the public survey submission endpoint.
|
|
"""
|
|
queryset = SurveyResponse.objects.all()
|
|
serializer_class = SurveyResponseSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = ['survey_instance', 'question']
|
|
ordering = ['survey_instance', 'question__order']
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset().select_related('survey_instance', 'question')
|
|
user = self.request.user
|
|
|
|
# PX Admins see all responses
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins see responses for their hospital
|
|
if user.is_hospital_admin() and user.hospital:
|
|
return queryset.filter(survey_instance__survey_template__hospital=user.hospital)
|
|
|
|
# Others see responses for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(survey_instance__survey_template__hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
|
|
class PublicSurveyViewSet(viewsets.GenericViewSet):
|
|
"""
|
|
Public survey viewset for patient-facing survey access.
|
|
|
|
No authentication required - uses secure token.
|
|
"""
|
|
permission_classes = [AllowAny]
|
|
|
|
def retrieve(self, request, token=None):
|
|
"""
|
|
Get survey by access token.
|
|
|
|
GET /api/surveys/public/{token}/
|
|
"""
|
|
survey_instance = get_object_or_404(
|
|
SurveyInstance.objects.select_related('survey_template').prefetch_related(
|
|
'survey_template__questions'
|
|
),
|
|
access_token=token
|
|
)
|
|
|
|
# Check if token expired
|
|
if survey_instance.token_expires_at and survey_instance.token_expires_at < timezone.now():
|
|
return Response(
|
|
{'error': 'Survey link has expired'},
|
|
status=status.HTTP_410_GONE
|
|
)
|
|
|
|
# Check if already completed
|
|
if survey_instance.status == 'completed':
|
|
return Response(
|
|
{'error': 'Survey already completed', 'completed_at': survey_instance.completed_at},
|
|
status=status.HTTP_410_GONE
|
|
)
|
|
|
|
# Mark as opened if first time
|
|
if not survey_instance.opened_at:
|
|
survey_instance.opened_at = timezone.now()
|
|
survey_instance.save(update_fields=['opened_at'])
|
|
|
|
serializer = PublicSurveySerializer(survey_instance)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['post'], url_path='(?P<token>[^/.]+)/submit')
|
|
def submit(self, request, token=None):
|
|
"""
|
|
Submit survey responses.
|
|
|
|
POST /api/surveys/public/{token}/submit/
|
|
|
|
Body:
|
|
{
|
|
"responses": [
|
|
{"question_id": "uuid", "numeric_value": 5},
|
|
{"question_id": "uuid", "text_value": "Great service!"}
|
|
]
|
|
}
|
|
"""
|
|
survey_instance = get_object_or_404(
|
|
SurveyInstance,
|
|
access_token=token
|
|
)
|
|
|
|
# Check if token expired
|
|
if survey_instance.token_expires_at and survey_instance.token_expires_at < timezone.now():
|
|
return Response(
|
|
{'error': 'Survey link has expired'},
|
|
status=status.HTTP_410_GONE
|
|
)
|
|
|
|
# Check if already completed
|
|
if survey_instance.status == 'completed':
|
|
return Response(
|
|
{'error': 'Survey already completed'},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# Validate and create responses
|
|
serializer = SurveySubmissionSerializer(
|
|
data=request.data,
|
|
context={'survey_instance': survey_instance}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
return Response({
|
|
'message': 'Survey submitted successfully',
|
|
'score': float(survey_instance.total_score) if survey_instance.total_score else None
|
|
}, status=status.HTTP_201_CREATED)
|