1011 lines
33 KiB
Python
1011 lines
33 KiB
Python
"""
|
|
Appreciation UI views - Server-rendered templates for appreciation management
|
|
"""
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.paginator import Paginator
|
|
from django.db.models import Q, Count
|
|
from django.http import JsonResponse
|
|
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, Staff
|
|
|
|
from .models import (
|
|
Appreciation,
|
|
AppreciationBadge,
|
|
AppreciationCategory,
|
|
AppreciationStatus,
|
|
AppreciationVisibility,
|
|
AppreciationStats,
|
|
UserBadge,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# APPRECIATION LIST & DETAIL VIEWS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def appreciation_list(request):
|
|
"""
|
|
Appreciations list view with advanced filters and pagination.
|
|
|
|
Features:
|
|
- Server-side pagination
|
|
- Advanced filters (status, visibility, category, hospital, department)
|
|
- Search by message
|
|
- Tab-based navigation (Received, Sent, Leaderboard, Badges)
|
|
"""
|
|
# Base queryset with optimizations
|
|
queryset = Appreciation.objects.select_related(
|
|
'sender', 'hospital', 'department', 'category'
|
|
)
|
|
|
|
# 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(
|
|
Q(department=user.department) | Q(department__isnull=True)
|
|
)
|
|
elif user.hospital:
|
|
queryset = queryset.filter(hospital=user.hospital)
|
|
else:
|
|
queryset = queryset.none()
|
|
|
|
# Apply visibility filter based on user
|
|
from django.contrib.contenttypes.models import ContentType
|
|
user_content_type = ContentType.objects.get_for_model(user)
|
|
|
|
visibility_filter = (
|
|
Q(sender=user) |
|
|
Q(recipient_content_type=user_content_type, recipient_object_id=user.id)
|
|
)
|
|
|
|
if user.department:
|
|
visibility_filter |= Q(visibility=AppreciationVisibility.DEPARTMENT, department=user.department)
|
|
|
|
if user.hospital:
|
|
visibility_filter |= Q(visibility=AppreciationVisibility.HOSPITAL, hospital=user.hospital)
|
|
|
|
visibility_filter |= Q(visibility=AppreciationVisibility.PUBLIC)
|
|
queryset = queryset.filter(visibility_filter)
|
|
|
|
# Apply filters from request
|
|
tab = request.GET.get('tab', 'received')
|
|
|
|
if tab == 'received':
|
|
# Show appreciations received by user
|
|
queryset = queryset.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
)
|
|
elif tab == 'sent':
|
|
# Show appreciations sent by user
|
|
queryset = queryset.filter(sender=user)
|
|
# 'leaderboard' and 'badges' tabs are handled separately
|
|
|
|
status_filter = request.GET.get('status')
|
|
if status_filter:
|
|
queryset = queryset.filter(status=status_filter)
|
|
|
|
visibility_filter_req = request.GET.get('visibility')
|
|
if visibility_filter_req:
|
|
queryset = queryset.filter(visibility=visibility_filter_req)
|
|
|
|
category_filter = request.GET.get('category')
|
|
if category_filter:
|
|
queryset = queryset.filter(category_id=category_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)
|
|
|
|
# Search
|
|
search_query = request.GET.get('search')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(message_en__icontains=search_query) |
|
|
Q(message_ar__icontains=search_query)
|
|
)
|
|
|
|
# Date range filters
|
|
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', '-sent_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)
|
|
|
|
categories = AppreciationCategory.objects.filter(is_active=True)
|
|
if not user.is_px_admin() and user.hospital:
|
|
categories = categories.filter(Q(hospital_id=user.hospital.id) | Q(hospital__isnull=True))
|
|
|
|
# Statistics
|
|
user_content_type = ContentType.objects.get_for_model(user)
|
|
|
|
stats = {
|
|
'received': Appreciation.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).count(),
|
|
'sent': Appreciation.objects.filter(sender=user).count(),
|
|
'badges_earned': UserBadge.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).count(),
|
|
}
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'appreciations': page_obj.object_list,
|
|
'stats': stats,
|
|
'hospitals': hospitals,
|
|
'departments': departments,
|
|
'categories': categories,
|
|
'status_choices': AppreciationStatus.choices,
|
|
'visibility_choices': AppreciationVisibility.choices,
|
|
'current_tab': tab,
|
|
'filters': request.GET,
|
|
}
|
|
|
|
return render(request, 'appreciation/appreciation_list.html', context)
|
|
|
|
|
|
@login_required
|
|
def appreciation_detail(request, pk):
|
|
"""
|
|
Appreciation detail view.
|
|
|
|
Features:
|
|
- Full appreciation details
|
|
- Acknowledge action for recipients
|
|
- Related appreciations
|
|
"""
|
|
appreciation = get_object_or_404(
|
|
Appreciation.objects.select_related(
|
|
'sender', 'hospital', 'department', 'category'
|
|
),
|
|
pk=pk
|
|
)
|
|
|
|
# Check access
|
|
user = request.user
|
|
from django.contrib.contenttypes.models import ContentType
|
|
user_content_type = ContentType.objects.get_for_model(user)
|
|
|
|
can_view = (
|
|
appreciation.sender == user or
|
|
(appreciation.recipient_content_type == user_content_type and
|
|
appreciation.recipient_object_id == user.id) or
|
|
user.is_px_admin() or
|
|
(user.is_hospital_admin() and appreciation.hospital == user.hospital)
|
|
)
|
|
|
|
if not can_view:
|
|
messages.error(request, "You don't have permission to view this appreciation.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
# Check if user is recipient
|
|
is_recipient = (
|
|
appreciation.recipient_content_type == user_content_type and
|
|
appreciation.recipient_object_id == user.id
|
|
)
|
|
|
|
# Get related appreciations
|
|
related = Appreciation.objects.filter(
|
|
recipient_content_type=appreciation.recipient_content_type,
|
|
recipient_object_id=appreciation.recipient_object_id
|
|
).exclude(pk=appreciation.pk)[:5]
|
|
|
|
context = {
|
|
'appreciation': appreciation,
|
|
'is_recipient': is_recipient,
|
|
'related': related,
|
|
'status_choices': AppreciationStatus.choices,
|
|
'visibility_choices': AppreciationVisibility.choices,
|
|
}
|
|
|
|
return render(request, 'appreciation/appreciation_detail.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def appreciation_send(request):
|
|
"""Send appreciation form"""
|
|
if request.method == 'POST':
|
|
try:
|
|
# Get form data
|
|
recipient_type = request.POST.get('recipient_type')
|
|
recipient_id = request.POST.get('recipient_id')
|
|
category_id = request.POST.get('category_id', None)
|
|
message_en = request.POST.get('message_en')
|
|
message_ar = request.POST.get('message_ar', '')
|
|
visibility = request.POST.get('visibility', AppreciationVisibility.PRIVATE)
|
|
is_anonymous = request.POST.get('is_anonymous') == 'on'
|
|
hospital_id = request.POST.get('hospital_id')
|
|
department_id = request.POST.get('department_id', None)
|
|
|
|
# Validate required fields
|
|
if not all([recipient_type, recipient_id, message_en, hospital_id]):
|
|
messages.error(request, "Please fill in all required fields.")
|
|
return redirect('appreciation:appreciation_send')
|
|
|
|
# Get recipient
|
|
if recipient_type == 'user':
|
|
recipient = User.objects.get(id=recipient_id)
|
|
recipient_content_type = ContentType.objects.get_for_model(User)
|
|
else: # staff
|
|
recipient = Staff.objects.get(id=recipient_id)
|
|
recipient_content_type = ContentType.objects.get_for_model(Staff)
|
|
|
|
# Get hospital and department
|
|
hospital = Hospital.objects.get(id=hospital_id)
|
|
department = None
|
|
if department_id:
|
|
department = Department.objects.get(id=department_id)
|
|
|
|
# Get category
|
|
category = None
|
|
if category_id:
|
|
category = AppreciationCategory.objects.get(id=category_id)
|
|
|
|
# Create appreciation
|
|
appreciation = Appreciation.objects.create(
|
|
sender=request.user,
|
|
recipient_content_type=recipient_content_type,
|
|
recipient_object_id=recipient_id,
|
|
hospital=hospital,
|
|
department=department,
|
|
category=category,
|
|
message_en=message_en,
|
|
message_ar=message_ar,
|
|
visibility=visibility,
|
|
is_anonymous=is_anonymous,
|
|
)
|
|
|
|
# Send appreciation
|
|
appreciation.send()
|
|
|
|
# Log audit
|
|
AuditService.log_event(
|
|
event_type='appreciation_sent',
|
|
description=f"Appreciation sent to {str(recipient)}",
|
|
user=request.user,
|
|
content_object=appreciation,
|
|
metadata={
|
|
'visibility': visibility,
|
|
'category': category.name_en if category else None,
|
|
'anonymous': is_anonymous
|
|
}
|
|
)
|
|
|
|
messages.success(request, "Appreciation sent successfully!")
|
|
return redirect('appreciation:appreciation_detail', pk=appreciation.id)
|
|
|
|
except User.DoesNotExist:
|
|
messages.error(request, "User not found.")
|
|
except Staff.DoesNotExist:
|
|
messages.error(request, "Staff not found.")
|
|
except Exception as e:
|
|
messages.error(request, f"Error sending appreciation: {str(e)}")
|
|
|
|
return redirect('appreciation:appreciation_send')
|
|
|
|
# GET request - show form
|
|
hospitals = Hospital.objects.filter(status='active')
|
|
if not request.user.is_px_admin() and request.user.hospital:
|
|
hospitals = hospitals.filter(id=request.user.hospital.id)
|
|
|
|
categories = AppreciationCategory.objects.filter(is_active=True)
|
|
if not request.user.is_px_admin() and request.user.hospital:
|
|
categories = categories.filter(Q(hospital_id=request.user.hospital.id) | Q(hospital__isnull=True))
|
|
|
|
context = {
|
|
'hospitals': hospitals,
|
|
'categories': categories,
|
|
'visibility_choices': AppreciationVisibility.choices,
|
|
}
|
|
|
|
return render(request, 'appreciation/appreciation_send_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["POST"])
|
|
def appreciation_acknowledge(request, pk):
|
|
"""Acknowledge appreciation"""
|
|
appreciation = get_object_or_404(Appreciation, pk=pk)
|
|
|
|
# Check if user is recipient
|
|
user_content_type = ContentType.objects.get_for_model(request.user)
|
|
if not (
|
|
appreciation.recipient_content_type == user_content_type and
|
|
appreciation.recipient_object_id == request.user.id
|
|
):
|
|
messages.error(request, "You can only acknowledge appreciations sent to you.")
|
|
return redirect('appreciation:appreciation_detail', pk=pk)
|
|
|
|
# Acknowledge
|
|
appreciation.acknowledge()
|
|
|
|
messages.success(request, "Appreciation acknowledged successfully.")
|
|
return redirect('appreciation:appreciation_detail', pk=pk)
|
|
|
|
|
|
# ============================================================================
|
|
# LEADERBOARD VIEWS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def leaderboard_view(request):
|
|
"""
|
|
Appreciation leaderboard view.
|
|
|
|
Features:
|
|
- Monthly rankings
|
|
- Hospital and department filters
|
|
- Top recipients with badges
|
|
"""
|
|
user = request.user
|
|
|
|
# Get date range
|
|
now = timezone.now()
|
|
year = int(request.GET.get('year', now.year))
|
|
month = int(request.GET.get('month', now.month))
|
|
|
|
# Build base query
|
|
queryset = AppreciationStats.objects.filter(year=year, month=month)
|
|
|
|
# Apply RBAC
|
|
if not user.is_px_admin() and user.hospital:
|
|
queryset = queryset.filter(hospital=user.hospital)
|
|
|
|
# Apply filters
|
|
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)
|
|
|
|
# Order by received count
|
|
queryset = queryset.order_by('-received_count')
|
|
|
|
# Pagination
|
|
page_size = int(request.GET.get('page_size', 50))
|
|
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 months for filter
|
|
months = [(i, timezone.datetime(year=year, month=i, day=1).strftime('%B')) for i in range(1, 13)]
|
|
years = range(now.year - 1, now.year + 2)
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'leaderboard': page_obj.object_list,
|
|
'hospitals': hospitals,
|
|
'departments': departments,
|
|
'months': months,
|
|
'years': years,
|
|
'selected_year': year,
|
|
'selected_month': month,
|
|
'filters': request.GET,
|
|
}
|
|
|
|
return render(request, 'appreciation/leaderboard.html', context)
|
|
|
|
|
|
@login_required
|
|
def my_badges_view(request):
|
|
"""
|
|
User's badges view.
|
|
|
|
Features:
|
|
- All earned badges
|
|
- Badge details and criteria
|
|
- Progress toward next badges
|
|
"""
|
|
user = request.user
|
|
user_content_type = ContentType.objects.get_for_model(user)
|
|
|
|
# Get user's badges
|
|
queryset = UserBadge.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).select_related('badge').order_by('-earned_at')
|
|
|
|
# Pagination
|
|
page_size = int(request.GET.get('page_size', 20))
|
|
paginator = Paginator(queryset, page_size)
|
|
page_number = request.GET.get('page', 1)
|
|
page_obj = paginator.get_page(page_number)
|
|
|
|
# Get available badges for progress tracking
|
|
available_badges = AppreciationBadge.objects.filter(is_active=True)
|
|
if not request.user.is_px_admin() and request.user.hospital:
|
|
available_badges = available_badges.filter(Q(hospital_id=request.user.hospital.id) | Q(hospital__isnull=True))
|
|
|
|
# Calculate progress for each badge
|
|
badge_progress = []
|
|
total_received = Appreciation.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).count()
|
|
|
|
for badge in available_badges:
|
|
earned = queryset.filter(badge=badge).exists()
|
|
progress = 0
|
|
if badge.criteria_type == 'count':
|
|
progress = min(100, int((total_received / badge.criteria_value) * 100))
|
|
|
|
badge_progress.append({
|
|
'badge': badge,
|
|
'earned': earned,
|
|
'progress': progress,
|
|
})
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'badges': page_obj.object_list,
|
|
'total_received': total_received,
|
|
'badge_progress': badge_progress,
|
|
}
|
|
|
|
return render(request, 'appreciation/my_badges.html', context)
|
|
|
|
|
|
# ============================================================================
|
|
# ADMIN: CATEGORY MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def category_list(request):
|
|
"""List and manage appreciation categories"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to manage categories.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
# Base queryset
|
|
queryset = AppreciationCategory.objects.all()
|
|
|
|
# Apply RBAC
|
|
if not user.is_px_admin() and user.hospital:
|
|
queryset = queryset.filter(Q(hospital_id=user.hospital.id) | Q(hospital__isnull=True))
|
|
|
|
# Search
|
|
search_query = request.GET.get('search')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(name_en__icontains=search_query) |
|
|
Q(name_ar__icontains=search_query) |
|
|
Q(code__icontains=search_query)
|
|
)
|
|
|
|
# Ordering
|
|
queryset = queryset.order_by('order', 'code')
|
|
|
|
# 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)
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'categories': page_obj.object_list,
|
|
}
|
|
|
|
return render(request, 'appreciation/admin/category_list.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def category_create(request):
|
|
"""Create appreciation category"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to create categories.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
code = request.POST.get('code')
|
|
name_en = request.POST.get('name_en')
|
|
name_ar = request.POST.get('name_ar', '')
|
|
description_en = request.POST.get('description_en', '')
|
|
description_ar = request.POST.get('description_ar', '')
|
|
icon = request.POST.get('icon', 'fa-heart')
|
|
color = request.POST.get('color', '#FF5733')
|
|
order = request.POST.get('order', 0)
|
|
is_active = request.POST.get('is_active') == 'on'
|
|
|
|
# Get hospital
|
|
hospital = None
|
|
if user.is_hospital_admin() and user.hospital:
|
|
hospital = user.hospital
|
|
|
|
# Validate
|
|
if not all([code, name_en]):
|
|
messages.error(request, "Please fill in all required fields.")
|
|
return redirect('appreciation:category_create')
|
|
|
|
# Create category
|
|
AppreciationCategory.objects.create(
|
|
code=code,
|
|
name_en=name_en,
|
|
name_ar=name_ar,
|
|
description_en=description_en,
|
|
description_ar=description_ar,
|
|
icon=icon,
|
|
color=color,
|
|
order=order,
|
|
is_active=is_active,
|
|
hospital=hospital,
|
|
)
|
|
|
|
messages.success(request, "Category created successfully.")
|
|
return redirect('appreciation:category_list')
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Error creating category: {str(e)}")
|
|
return redirect('appreciation:category_create')
|
|
|
|
context = {}
|
|
return render(request, 'appreciation/admin/category_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def category_edit(request, pk):
|
|
"""Edit appreciation category"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to edit categories.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
category = get_object_or_404(AppreciationCategory, pk=pk)
|
|
|
|
# Check access
|
|
if not user.is_px_admin() and category.hospital != user.hospital:
|
|
messages.error(request, "You don't have permission to edit this category.")
|
|
return redirect('appreciation:category_list')
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
category.code = request.POST.get('code')
|
|
category.name_en = request.POST.get('name_en')
|
|
category.name_ar = request.POST.get('name_ar', '')
|
|
category.description_en = request.POST.get('description_en', '')
|
|
category.description_ar = request.POST.get('description_ar', '')
|
|
category.icon = request.POST.get('icon', 'fa-heart')
|
|
category.color = request.POST.get('color', '#FF5733')
|
|
category.order = request.POST.get('order', 0)
|
|
category.is_active = request.POST.get('is_active') == 'on'
|
|
|
|
category.save()
|
|
|
|
messages.success(request, "Category updated successfully.")
|
|
return redirect('appreciation:category_list')
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Error updating category: {str(e)}")
|
|
return redirect('appreciation:category_edit', pk=pk)
|
|
|
|
context = {
|
|
'category': category,
|
|
}
|
|
return render(request, 'appreciation/admin/category_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["POST"])
|
|
def category_delete(request, pk):
|
|
"""Delete appreciation category"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to delete categories.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
category = get_object_or_404(AppreciationCategory, pk=pk)
|
|
|
|
# Check if category is in use
|
|
if Appreciation.objects.filter(category=category).exists():
|
|
messages.error(request, "Cannot delete category that is in use.")
|
|
return redirect('appreciation:category_list')
|
|
|
|
# Log audit
|
|
AuditService.log_event(
|
|
event_type='category_deleted',
|
|
description=f"Appreciation category deleted: {category.name_en}",
|
|
user=request.user,
|
|
metadata={'category_code': category.code}
|
|
)
|
|
|
|
category.delete()
|
|
|
|
messages.success(request, "Category deleted successfully.")
|
|
return redirect('appreciation:category_list')
|
|
|
|
|
|
# ============================================================================
|
|
# ADMIN: BADGE MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def badge_list(request):
|
|
"""List and manage appreciation badges"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to manage badges.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
# Base queryset
|
|
queryset = AppreciationBadge.objects.all()
|
|
|
|
# Apply RBAC
|
|
if not user.is_px_admin() and user.hospital:
|
|
queryset = queryset.filter(Q(hospital_id=user.hospital.id) | Q(hospital__isnull=True))
|
|
|
|
# Search
|
|
search_query = request.GET.get('search')
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(name_en__icontains=search_query) |
|
|
Q(name_ar__icontains=search_query) |
|
|
Q(code__icontains=search_query)
|
|
)
|
|
|
|
# Ordering
|
|
queryset = queryset.order_by('order', 'code')
|
|
|
|
# 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)
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'badges': page_obj.object_list,
|
|
}
|
|
|
|
return render(request, 'appreciation/admin/badge_list.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def badge_create(request):
|
|
"""Create appreciation badge"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to create badges.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
code = request.POST.get('code')
|
|
name_en = request.POST.get('name_en')
|
|
name_ar = request.POST.get('name_ar', '')
|
|
description_en = request.POST.get('description_en', '')
|
|
description_ar = request.POST.get('description_ar', '')
|
|
icon = request.POST.get('icon', 'fa-award')
|
|
color = request.POST.get('color', '#FFD700')
|
|
criteria_type = request.POST.get('criteria_type', 'count')
|
|
criteria_value = request.POST.get('criteria_value', 5)
|
|
order = request.POST.get('order', 0)
|
|
is_active = request.POST.get('is_active') == 'on'
|
|
|
|
# Get hospital
|
|
hospital = None
|
|
if user.is_hospital_admin() and user.hospital:
|
|
hospital = user.hospital
|
|
|
|
# Validate
|
|
if not all([code, name_en, criteria_value]):
|
|
messages.error(request, "Please fill in all required fields.")
|
|
return redirect('appreciation:badge_create')
|
|
|
|
# Create badge
|
|
AppreciationBadge.objects.create(
|
|
code=code,
|
|
name_en=name_en,
|
|
name_ar=name_ar,
|
|
description_en=description_en,
|
|
description_ar=description_ar,
|
|
icon=icon,
|
|
color=color,
|
|
criteria_type=criteria_type,
|
|
criteria_value=int(criteria_value),
|
|
order=order,
|
|
is_active=is_active,
|
|
hospital=hospital,
|
|
)
|
|
|
|
messages.success(request, "Badge created successfully.")
|
|
return redirect('appreciation:badge_list')
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Error creating badge: {str(e)}")
|
|
return redirect('appreciation:badge_create')
|
|
|
|
context = {}
|
|
return render(request, 'appreciation/admin/badge_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def badge_edit(request, pk):
|
|
"""Edit appreciation badge"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to edit badges.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
badge = get_object_or_404(AppreciationBadge, pk=pk)
|
|
|
|
# Check access
|
|
if not user.is_px_admin() and badge.hospital != user.hospital:
|
|
messages.error(request, "You don't have permission to edit this badge.")
|
|
return redirect('appreciation:badge_list')
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
badge.code = request.POST.get('code')
|
|
badge.name_en = request.POST.get('name_en')
|
|
badge.name_ar = request.POST.get('name_ar', '')
|
|
badge.description_en = request.POST.get('description_en', '')
|
|
badge.description_ar = request.POST.get('description_ar', '')
|
|
badge.icon = request.POST.get('icon', 'fa-award')
|
|
badge.color = request.POST.get('color', '#FFD700')
|
|
badge.criteria_type = request.POST.get('criteria_type', 'count')
|
|
badge.criteria_value = request.POST.get('criteria_value', 5)
|
|
badge.order = request.POST.get('order', 0)
|
|
badge.is_active = request.POST.get('is_active') == 'on'
|
|
|
|
badge.save()
|
|
|
|
messages.success(request, "Badge updated successfully.")
|
|
return redirect('appreciation:badge_list')
|
|
|
|
except Exception as e:
|
|
messages.error(request, f"Error updating badge: {str(e)}")
|
|
return redirect('appreciation:badge_edit', pk=pk)
|
|
|
|
context = {
|
|
'badge': badge,
|
|
}
|
|
return render(request, 'appreciation/admin/badge_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["POST"])
|
|
def badge_delete(request, pk):
|
|
"""Delete appreciation badge"""
|
|
user = request.user
|
|
|
|
# Check permission
|
|
if not (user.is_px_admin() or user.is_hospital_admin()):
|
|
messages.error(request, "You don't have permission to delete badges.")
|
|
return redirect('appreciation:appreciation_list')
|
|
|
|
badge = get_object_or_404(AppreciationBadge, pk=pk)
|
|
|
|
# Check if badge is in use
|
|
if UserBadge.objects.filter(badge=badge).exists():
|
|
messages.error(request, "Cannot delete badge that has been earned.")
|
|
return redirect('appreciation:badge_list')
|
|
|
|
# Log audit
|
|
AuditService.log_event(
|
|
event_type='badge_deleted',
|
|
description=f"Appreciation badge deleted: {badge.name_en}",
|
|
user=request.user,
|
|
metadata={'badge_code': badge.code}
|
|
)
|
|
|
|
badge.delete()
|
|
|
|
messages.success(request, "Badge deleted successfully.")
|
|
return redirect('appreciation:badge_list')
|
|
|
|
|
|
# ============================================================================
|
|
# AJAX/API HELPERS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def get_users_by_hospital(request):
|
|
"""Get users for a hospital (AJAX)"""
|
|
hospital_id = request.GET.get('hospital_id')
|
|
if not hospital_id:
|
|
return JsonResponse({'users': []})
|
|
|
|
users = User.objects.filter(
|
|
hospital_id=hospital_id,
|
|
is_active=True
|
|
).values('id', 'first_name', 'last_name')
|
|
|
|
results = [
|
|
{
|
|
'id': str(u['id']),
|
|
'name': f"{u['first_name']} {u['last_name']}",
|
|
}
|
|
for u in users
|
|
]
|
|
|
|
return JsonResponse({'users': results})
|
|
|
|
|
|
@login_required
|
|
def get_staff_by_hospital(request):
|
|
"""Get staff for a hospital (AJAX)"""
|
|
hospital_id = request.GET.get('hospital_id')
|
|
if not hospital_id:
|
|
return JsonResponse({'staff': []})
|
|
|
|
staff = Staff.objects.filter(
|
|
hospital_id=hospital_id,
|
|
status='active'
|
|
).values('id', 'user__first_name', 'user__last_name')
|
|
|
|
results = [
|
|
{
|
|
'id': str(s['id']),
|
|
'name': f"{s['user__first_name']} {s['user__last_name']}",
|
|
}
|
|
for s in staff
|
|
]
|
|
|
|
return JsonResponse({'staff': results})
|
|
|
|
|
|
@login_required
|
|
def get_physicians_by_hospital(request):
|
|
"""Get physicians for a hospital (AJAX)"""
|
|
hospital_id = request.GET.get('hospital_id')
|
|
if not hospital_id:
|
|
return JsonResponse({'physicians': []})
|
|
|
|
physicians = Staff.objects.filter(
|
|
hospital_id=hospital_id,
|
|
status='active',
|
|
staff_type='physician'
|
|
).values('id', 'user__first_name', 'user__last_name')
|
|
|
|
results = [
|
|
{
|
|
'id': str(p['id']),
|
|
'name': f"{p['user__first_name']} {p['user__last_name']}",
|
|
}
|
|
for p in physicians
|
|
]
|
|
|
|
return JsonResponse({'physicians': results})
|
|
|
|
|
|
@login_required
|
|
def get_departments_by_hospital(request):
|
|
"""Get departments for a hospital (AJAX)"""
|
|
hospital_id = request.GET.get('hospital_id')
|
|
if not hospital_id:
|
|
return JsonResponse({'departments': []})
|
|
|
|
departments = Department.objects.filter(
|
|
hospital_id=hospital_id,
|
|
status='active'
|
|
).values('id', 'name', 'name_ar')
|
|
|
|
results = [
|
|
{
|
|
'id': str(d['id']),
|
|
'name': d['name'],
|
|
}
|
|
for d in departments
|
|
]
|
|
|
|
return JsonResponse({'departments': results})
|
|
|
|
|
|
@login_required
|
|
def appreciation_summary_ajax(request):
|
|
"""Get appreciation summary for current user (AJAX)"""
|
|
user = request.user
|
|
user_content_type = ContentType.objects.get_for_model(user)
|
|
|
|
now = timezone.now()
|
|
current_year = now.year
|
|
current_month = now.month
|
|
|
|
summary = {
|
|
'total_received': Appreciation.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).count(),
|
|
'total_sent': Appreciation.objects.filter(sender=user).count(),
|
|
'this_month_received': Appreciation.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id,
|
|
sent_at__year=current_year,
|
|
sent_at__month=current_month
|
|
).count(),
|
|
'this_month_sent': Appreciation.objects.filter(
|
|
sender=user,
|
|
sent_at__year=current_year,
|
|
sent_at__month=current_month
|
|
).count(),
|
|
'badges_earned': UserBadge.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id
|
|
).count(),
|
|
}
|
|
|
|
# Get hospital rank
|
|
if user.hospital:
|
|
stats = AppreciationStats.objects.filter(
|
|
recipient_content_type=user_content_type,
|
|
recipient_object_id=user.id,
|
|
year=current_year,
|
|
month=current_month
|
|
).first()
|
|
summary['hospital_rank'] = stats.hospital_rank if stats else None
|
|
|
|
return JsonResponse(summary)
|