""" Feedback views - Server-rendered templates for feedback 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, Count, Avg 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.accounts.models import User from apps.core.services import AuditService from apps.organizations.models import Department, Hospital, Patient, Staff from .models import ( Feedback, FeedbackAttachment, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackCategory, ) from .forms import ( FeedbackForm, FeedbackResponseForm, FeedbackStatusChangeForm, ) @login_required def feedback_list(request): """ Feedback list view with advanced filters and pagination. Features: - Server-side pagination - Advanced filters (status, type, sentiment, category, hospital, etc.) - Search by title, message, patient name - Statistics dashboard - Export capability """ # Base queryset with optimizations queryset = Feedback.objects.select_related( "patient", "hospital", "department", "staff", "assigned_to", "reviewed_by", "acknowledged_by", "closed_by" ).filter(is_deleted=False) # Apply RBAC filters user = request.user # Get selected hospital for PX Admins (from middleware) selected_hospital = getattr(request, "tenant_hospital", None) if user.is_px_admin(): # PX Admins see all, but filter by selected hospital if set if selected_hospital: queryset = queryset.filter(hospital=selected_hospital) elif user.is_hospital_admin() and user.hospital: queryset = queryset.filter(hospital=user.hospital) elif user.is_department_manager() and user.department: queryset = queryset.filter(department=user.department) elif user.hospital: queryset = queryset.filter(hospital=user.hospital) else: queryset = queryset.none() # Apply filters from request feedback_type_filter = request.GET.get("feedback_type") if feedback_type_filter: queryset = queryset.filter(feedback_type=feedback_type_filter) status_filter = request.GET.get("status") if status_filter: queryset = queryset.filter(status=status_filter) category_filter = request.GET.get("category") if category_filter: queryset = queryset.filter(category=category_filter) sentiment_filter = request.GET.get("sentiment") if sentiment_filter: queryset = queryset.filter(sentiment=sentiment_filter) priority_filter = request.GET.get("priority") if priority_filter: queryset = queryset.filter(priority=priority_filter) hospital_filter = request.GET.get("hospital") if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) department_filter = request.GET.get("department") if department_filter: queryset = queryset.filter(department_id=department_filter) staff_filter = request.GET.get("staff") if staff_filter: queryset = queryset.filter(staff_id=staff_filter) assigned_to_filter = request.GET.get("assigned_to") if assigned_to_filter: queryset = queryset.filter(assigned_to_id=assigned_to_filter) rating_min = request.GET.get("rating_min") if rating_min: queryset = queryset.filter(rating__gte=rating_min) rating_max = request.GET.get("rating_max") if rating_max: queryset = queryset.filter(rating__lte=rating_max) is_featured = request.GET.get("is_featured") if is_featured == "true": queryset = queryset.filter(is_featured=True) requires_follow_up = request.GET.get("requires_follow_up") if requires_follow_up == "true": queryset = queryset.filter(requires_follow_up=True) # Search search_query = request.GET.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(message__icontains=search_query) | Q(patient__mrn__icontains=search_query) | Q(patient__first_name__icontains=search_query) | Q(patient__last_name__icontains=search_query) | Q(contact_name__icontains=search_query) ) # Date range filters date_from = request.GET.get("date_from") if date_from: queryset = queryset.filter(created_at__gte=date_from) date_to = request.GET.get("date_to") if date_to: queryset = queryset.filter(created_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) departments = Department.objects.filter(status="active") if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Get assignable users assignable_users = User.objects.filter(is_active=True) if user.hospital: assignable_users = assignable_users.filter(hospital=user.hospital) # Statistics stats = { "total": queryset.count(), "submitted": queryset.filter(status=FeedbackStatus.SUBMITTED).count(), "reviewed": queryset.filter(status=FeedbackStatus.REVIEWED).count(), "acknowledged": queryset.filter(status=FeedbackStatus.ACKNOWLEDGED).count(), "compliments": queryset.filter(feedback_type=FeedbackType.COMPLIMENT).count(), "suggestions": queryset.filter(feedback_type=FeedbackType.SUGGESTION).count(), "avg_rating": queryset.aggregate(Avg("rating"))["rating__avg"] or 0, "positive": queryset.filter(sentiment="positive").count(), "negative": queryset.filter(sentiment="negative").count(), } context = { "page_obj": page_obj, "feedbacks": page_obj.object_list, "stats": stats, "departments": departments, "assignable_users": assignable_users, "status_choices": FeedbackStatus.choices, "type_choices": FeedbackType.choices, "category_choices": FeedbackCategory.choices, "filters": request.GET, } return render(request, "feedback/feedback_list.html", context) @login_required def feedback_detail(request, pk): """ Feedback detail view with timeline, attachments, and actions. Features: - Full feedback details - Timeline of all responses - Attachments management - Workflow actions (assign, status change, add response) """ feedback = get_object_or_404( Feedback.objects.select_related( "patient", "hospital", "department", "staff", "assigned_to", "reviewed_by", "acknowledged_by", "closed_by" ).prefetch_related("attachments", "responses__created_by"), pk=pk, is_deleted=False, ) # Check access user = request.user if not user.is_px_admin(): if user.is_hospital_admin() and feedback.hospital != user.hospital: messages.error(request, "You don't have permission to view this feedback.") return redirect("feedback:feedback_list") elif user.is_department_manager() and feedback.department != user.department: messages.error(request, "You don't have permission to view this feedback.") return redirect("feedback:feedback_list") elif user.hospital and feedback.hospital != user.hospital: messages.error(request, "You don't have permission to view this feedback.") return redirect("feedback:feedback_list") # Get timeline (responses) timeline = feedback.responses.all().order_by("-created_at") # Get attachments attachments = feedback.attachments.all().order_by("-created_at") # Get assignable users assignable_users = User.objects.filter(is_active=True) if feedback.hospital: assignable_users = assignable_users.filter(hospital=feedback.hospital) context = { "feedback": feedback, "timeline": timeline, "attachments": attachments, "assignable_users": assignable_users, "status_choices": FeedbackStatus.choices, "can_edit": user.is_px_admin() or user.is_hospital_admin(), } return render(request, "feedback/feedback_detail.html", context) @login_required @require_http_methods(["GET", "POST"]) def feedback_create(request): """Create new feedback""" if request.method == "POST": form = FeedbackForm(request.POST, request=request) if form.is_valid(): try: feedback = form.save(commit=False) # Set default sentiment if not set if not feedback.sentiment: feedback.sentiment = "neutral" feedback.save() # Create initial response FeedbackResponse.objects.create( feedback=feedback, response_type="note", message=f"Feedback submitted by {request.user.get_full_name()}", created_by=request.user, is_internal=True, ) # Log audit AuditService.log_event( event_type="feedback_created", description=f"Feedback created: {feedback.title}", user=request.user, content_object=feedback, metadata={ "feedback_type": feedback.feedback_type, "category": feedback.category, "rating": feedback.rating, }, ) messages.success(request, f"Feedback #{feedback.id} created successfully.") return redirect("feedback:feedback_detail", pk=feedback.id) except Exception as e: messages.error(request, f"Error creating feedback: {str(e)}") else: messages.error(request, "Please correct the errors below.") else: form = FeedbackForm(request=request) # Get patients for selection patients = Patient.objects.filter(status="active") if request.user.hospital: patients = patients.filter(primary_hospital=request.user.hospital) context = { "form": form, "patients": patients, "is_create": True, } return render(request, "feedback/feedback_form.html", context) @login_required @require_http_methods(["GET", "POST"]) def feedback_update(request, pk): """Update existing feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to edit feedback.") return redirect("feedback:feedback_detail", pk=pk) if request.method == "POST": form = FeedbackForm(request.POST, instance=feedback, request=request) if form.is_valid(): try: feedback = form.save() # Create update response FeedbackResponse.objects.create( feedback=feedback, response_type="note", message=f"Feedback updated by {request.user.get_full_name()}", created_by=request.user, is_internal=True, ) # Log audit AuditService.log_event( event_type="feedback_updated", description=f"Feedback updated: {feedback.title}", user=request.user, content_object=feedback, ) messages.success(request, "Feedback updated successfully.") return redirect("feedback:feedback_detail", pk=feedback.id) except Exception as e: messages.error(request, f"Error updating feedback: {str(e)}") else: messages.error(request, "Please correct the errors below.") else: form = FeedbackForm(instance=feedback, request=request) # Get patients for selection patients = Patient.objects.filter(status="active") if request.user.hospital: patients = patients.filter(primary_hospital=request.user.hospital) context = { "form": form, "feedback": feedback, "patients": patients, "is_create": False, } return render(request, "feedback/feedback_form.html", context) @login_required @require_http_methods(["GET", "POST"]) def feedback_delete(request, pk): """Soft delete feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to delete feedback.") return redirect("feedback:feedback_detail", pk=pk) if request.method == "POST": try: feedback.soft_delete(user=request.user) # Log audit AuditService.log_event( event_type="feedback_deleted", description=f"Feedback deleted: {feedback.title}", user=request.user, content_object=feedback, ) messages.success(request, "Feedback deleted successfully.") return redirect("feedback:feedback_list") except Exception as e: messages.error(request, f"Error deleting feedback: {str(e)}") return redirect("feedback:feedback_detail", pk=pk) context = { "feedback": feedback, } return render(request, "feedback/feedback_delete_confirm.html", context) @login_required @require_http_methods(["POST"]) def feedback_assign(request, pk): """Assign feedback to user""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to assign feedback.") return redirect("feedback:feedback_detail", pk=pk) user_id = request.POST.get("user_id") note = request.POST.get("note", "") if not user_id: messages.error(request, "Please select a user to assign.") return redirect("feedback:feedback_detail", pk=pk) try: assignee = User.objects.get(id=user_id) feedback.assigned_to = assignee feedback.assigned_at = timezone.now() feedback.save(update_fields=["assigned_to", "assigned_at"]) # Create response message = f"Assigned to {assignee.get_full_name()}" if note: message += f"\nNote: {note}" FeedbackResponse.objects.create( feedback=feedback, response_type="assignment", message=message, created_by=request.user, is_internal=True ) # Log audit AuditService.log_event( event_type="assignment", description=f"Feedback assigned to {assignee.get_full_name()}", user=request.user, content_object=feedback, ) messages.success(request, f"Feedback assigned to {assignee.get_full_name()}.") except User.DoesNotExist: messages.error(request, "User not found.") return redirect("feedback:feedback_detail", pk=pk) @login_required @require_http_methods(["POST"]) def feedback_change_status(request, pk): """Change feedback status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to change feedback status.") return redirect("feedback:feedback_detail", pk=pk) new_status = request.POST.get("status") note = request.POST.get("note", "") if not new_status: messages.error(request, "Please select a status.") return redirect("feedback:feedback_detail", pk=pk) old_status = feedback.status feedback.status = new_status # Handle status-specific logic if new_status == FeedbackStatus.REVIEWED: feedback.reviewed_at = timezone.now() feedback.reviewed_by = request.user elif new_status == FeedbackStatus.ACKNOWLEDGED: feedback.acknowledged_at = timezone.now() feedback.acknowledged_by = request.user elif new_status == FeedbackStatus.CLOSED: feedback.closed_at = timezone.now() feedback.closed_by = request.user feedback.save() # Create response message = note or f"Status changed from {old_status} to {new_status}" FeedbackResponse.objects.create( feedback=feedback, response_type="status_change", message=message, created_by=request.user, old_status=old_status, new_status=new_status, is_internal=True, ) # Log audit AuditService.log_event( event_type="status_change", description=f"Feedback status changed from {old_status} to {new_status}", user=request.user, content_object=feedback, metadata={"old_status": old_status, "new_status": new_status}, ) messages.success(request, f"Feedback status changed to {new_status}.") return redirect("feedback:feedback_detail", pk=pk) @login_required @require_http_methods(["POST"]) def feedback_add_response(request, pk): """Add response to feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) response_type = request.POST.get("response_type", "response") message = request.POST.get("message") is_internal = request.POST.get("is_internal") == "on" if not message: messages.error(request, "Please enter a response message.") return redirect("feedback:feedback_detail", pk=pk) # Create response FeedbackResponse.objects.create( feedback=feedback, response_type=response_type, message=message, created_by=request.user, is_internal=is_internal, ) messages.success(request, "Response added successfully.") return redirect("feedback:feedback_detail", pk=pk) @login_required @require_http_methods(["POST"]) def feedback_toggle_featured(request, pk): """Toggle featured status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to feature feedback.") return redirect("feedback:feedback_detail", pk=pk) feedback.is_featured = not feedback.is_featured feedback.save(update_fields=["is_featured"]) status = "featured" if feedback.is_featured else "unfeatured" messages.success(request, f"Feedback {status} successfully.") return redirect("feedback:feedback_detail", pk=pk) @login_required @require_http_methods(["POST"]) def feedback_toggle_follow_up(request, pk): """Toggle follow-up required status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to modify feedback.") return redirect("feedback:feedback_detail", pk=pk) feedback.requires_follow_up = not feedback.requires_follow_up feedback.save(update_fields=["requires_follow_up"]) status = "marked for follow-up" if feedback.requires_follow_up else "unmarked for follow-up" messages.success(request, f"Feedback {status} successfully.") return redirect("feedback:feedback_detail", pk=pk)