590 lines
20 KiB
Python
590 lines
20 KiB
Python
"""
|
|
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
|
|
if user.is_px_admin():
|
|
pass # See all
|
|
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)
|
|
|
|
# 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)
|
|
|
|
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,
|
|
'hospitals': hospitals,
|
|
'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, user=request.user)
|
|
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(user=request.user)
|
|
|
|
# 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, user=request.user)
|
|
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, user=request.user)
|
|
|
|
# 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)
|