diff --git a/apps/appreciation/ui_views.py b/apps/appreciation/ui_views.py index 816e8fd..42ab966 100644 --- a/apps/appreciation/ui_views.py +++ b/apps/appreciation/ui_views.py @@ -13,7 +13,7 @@ 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, Physician +from apps.organizations.models import Department, Hospital, Staff from .models import ( Appreciation, @@ -34,7 +34,7 @@ from .models import ( def appreciation_list(request): """ Appreciations list view with advanced filters and pagination. - + Features: - Server-side pagination - Advanced filters (status, visibility, category, hospital, department) @@ -45,7 +45,7 @@ def appreciation_list(request): queryset = Appreciation.objects.select_related( 'sender', 'hospital', 'department', 'category' ) - + # Apply RBAC filters user = request.user if user.is_px_admin(): @@ -60,28 +60,28 @@ def appreciation_list(request): 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( @@ -92,27 +92,27 @@ def appreciation_list(request): # 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: @@ -120,42 +120,42 @@ def appreciation_list(request): 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, @@ -167,7 +167,7 @@ def appreciation_list(request): recipient_object_id=user.id ).count(), } - + context = { 'page_obj': page_obj, 'appreciations': page_obj.object_list, @@ -180,7 +180,7 @@ def appreciation_list(request): 'current_tab': tab, 'filters': request.GET, } - + return render(request, 'appreciation/appreciation_list.html', context) @@ -188,7 +188,7 @@ def appreciation_list(request): def appreciation_detail(request, pk): """ Appreciation detail view. - + Features: - Full appreciation details - Acknowledge action for recipients @@ -200,12 +200,12 @@ def appreciation_detail(request, pk): ), 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 @@ -213,23 +213,23 @@ def appreciation_detail(request, pk): 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, @@ -237,7 +237,7 @@ def appreciation_detail(request, pk): 'status_choices': AppreciationStatus.choices, 'visibility_choices': AppreciationVisibility.choices, } - + return render(request, 'appreciation/appreciation_detail.html', context) @@ -257,31 +257,31 @@ def appreciation_send(request): 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: # physician - recipient = Physician.objects.get(id=recipient_id) - recipient_content_type = ContentType.objects.get_for_model(Physician) - + 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, @@ -295,10 +295,10 @@ def appreciation_send(request): visibility=visibility, is_anonymous=is_anonymous, ) - + # Send appreciation appreciation.send() - + # Log audit AuditService.log_event( event_type='appreciation_sent', @@ -311,34 +311,34 @@ def appreciation_send(request): '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 Physician.DoesNotExist: - messages.error(request, "Physician 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) @@ -347,7 +347,7 @@ def appreciation_send(request): 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 ( @@ -356,10 +356,10 @@ def appreciation_acknowledge(request, pk): ): 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) @@ -372,57 +372,57 @@ def appreciation_acknowledge(request, pk): 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, @@ -434,7 +434,7 @@ def leaderboard_view(request): 'selected_month': month, 'filters': request.GET, } - + return render(request, 'appreciation/leaderboard.html', context) @@ -442,7 +442,7 @@ def leaderboard_view(request): def my_badges_view(request): """ User's badges view. - + Features: - All earned badges - Badge details and criteria @@ -450,50 +450,50 @@ def my_badges_view(request): """ 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) @@ -505,19 +505,19 @@ def my_badges_view(request): 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: @@ -526,21 +526,21 @@ def category_list(request): 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) @@ -549,12 +549,12 @@ def category_list(request): 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') @@ -566,17 +566,17 @@ def category_create(request): 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, @@ -590,14 +590,14 @@ def category_create(request): 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) @@ -607,19 +607,19 @@ def category_create(request): 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') @@ -631,16 +631,16 @@ def category_edit(request, pk): 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, } @@ -652,19 +652,19 @@ def category_edit(request, pk): 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', @@ -672,9 +672,9 @@ def category_delete(request, pk): user=request.user, metadata={'category_code': category.code} ) - + category.delete() - + messages.success(request, "Category deleted successfully.") return redirect('appreciation:category_list') @@ -687,19 +687,19 @@ def category_delete(request, pk): 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: @@ -708,21 +708,21 @@ def badge_list(request): 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) @@ -731,12 +731,12 @@ def badge_list(request): 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') @@ -750,17 +750,17 @@ def badge_create(request): 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, @@ -776,14 +776,14 @@ def badge_create(request): 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) @@ -793,19 +793,19 @@ def badge_create(request): 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') @@ -819,16 +819,16 @@ def badge_edit(request, pk): 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, } @@ -840,19 +840,19 @@ def badge_edit(request, pk): 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', @@ -860,9 +860,9 @@ def badge_delete(request, pk): user=request.user, metadata={'badge_code': badge.code} ) - + badge.delete() - + messages.success(request, "Badge deleted successfully.") return redirect('appreciation:badge_list') @@ -877,12 +877,12 @@ def get_users_by_hospital(request): 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']), @@ -890,31 +890,31 @@ def get_users_by_hospital(request): } for u in users ] - + return JsonResponse({'users': results}) @login_required -def get_physicians_by_hospital(request): - """Get physicians for a hospital (AJAX)""" +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({'physicians': []}) - - physicians = Physician.objects.filter( + return JsonResponse({'staff': []}) + + staff = Staff.objects.filter( hospital_id=hospital_id, status='active' - ).values('id', 'first_name', 'last_name', 'first_name_ar', 'last_name_ar') - + ).values('id', 'user__first_name', 'user__last_name') + results = [ { - 'id': str(p['id']), - 'name': f"{p['first_name']} {p['last_name']}", + 'id': str(s['id']), + 'name': f"{s['user__first_name']} {s['user__last_name']}", } - for p in physicians + for s in staff ] - - return JsonResponse({'physicians': results}) + + return JsonResponse({'staff': results}) @login_required @@ -923,12 +923,12 @@ def get_departments_by_hospital(request): 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']), @@ -936,7 +936,7 @@ def get_departments_by_hospital(request): } for d in departments ] - + return JsonResponse({'departments': results}) @@ -945,11 +945,11 @@ 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, @@ -972,7 +972,7 @@ def appreciation_summary_ajax(request): recipient_object_id=user.id ).count(), } - + # Get hospital rank if user.hospital: stats = AppreciationStats.objects.filter( @@ -982,5 +982,5 @@ def appreciation_summary_ajax(request): month=current_month ).first() summary['hospital_rank'] = stats.hospital_rank if stats else None - + return JsonResponse(summary) diff --git a/apps/appreciation/urls.py b/apps/appreciation/urls.py index eb126e0..081d42d 100644 --- a/apps/appreciation/urls.py +++ b/apps/appreciation/urls.py @@ -28,7 +28,7 @@ urlpatterns = [ # API Routes path('api/', include(router.urls)), path('api/leaderboard/', LeaderboardView.as_view(), name='api-leaderboard'), - + # UI Routes path('', ui_views.appreciation_list, name='appreciation_list'), path('send/', ui_views.appreciation_send, name='appreciation_send'), @@ -36,22 +36,22 @@ urlpatterns = [ path('acknowledge//', ui_views.appreciation_acknowledge, name='appreciation_acknowledge'), path('leaderboard/', ui_views.leaderboard_view, name='leaderboard_view'), path('badges/', ui_views.my_badges_view, name='my_badges_view'), - + # Admin: Category Management path('admin/categories/', ui_views.category_list, name='category_list'), path('admin/categories/create/', ui_views.category_create, name='category_create'), path('admin/categories//edit/', ui_views.category_edit, name='category_edit'), path('admin/categories//delete/', ui_views.category_delete, name='category_delete'), - + # Admin: Badge Management path('admin/badges/', ui_views.badge_list, name='badge_list'), path('admin/badges/create/', ui_views.badge_create, name='badge_create'), path('admin/badges//edit/', ui_views.badge_edit, name='badge_edit'), path('admin/badges//delete/', ui_views.badge_delete, name='badge_delete'), - + # AJAX Helpers path('ajax/users/', ui_views.get_users_by_hospital, name='get_users_by_hospital'), - path('ajax/physicians/', ui_views.get_physicians_by_hospital, name='get_physicians_by_hospital'), + path('ajax/staff/', ui_views.get_staff_by_hospital, name='get_staff_by_hospital'), path('ajax/departments/', ui_views.get_departments_by_hospital, name='get_departments_by_hospital'), path('ajax/summary/', ui_views.appreciation_summary_ajax, name='appreciation_summary_ajax'), ] diff --git a/apps/callcenter/ui_views.py b/apps/callcenter/ui_views.py index 4dd4da0..1523ed3 100644 --- a/apps/callcenter/ui_views.py +++ b/apps/callcenter/ui_views.py @@ -512,13 +512,8 @@ def get_departments_by_hospital(request): departments = Department.objects.filter( hospital_id=hospital_id, status='active' -<<<<<<< HEAD ).values('id', 'name', 'name_ar') - -======= - ).values('id', 'name_en', 'name_ar') ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) return JsonResponse({'departments': list(departments)}) @@ -532,16 +527,6 @@ def get_staff_by_hospital(request): staff_members = Staff.objects.filter( hospital_id=hospital_id, status='active' -<<<<<<< HEAD - ).values('id', 'first_name', 'last_name', 'specialization') - - # Format physician names - physicians_list = [ - { - 'id': str(p['id']), - 'name': f"Dr. {p['first_name']} {p['last_name']}", - 'specialty': p['specialization'] -======= ).values('id', 'first_name', 'last_name', 'staff_type', 'specialization') # Format staff names @@ -551,7 +536,6 @@ def get_staff_by_hospital(request): 'name': f"Dr. {s['first_name']} {s['last_name']}" if s['staff_type'] == 'physician' else f"{s['first_name']} {s['last_name']}", 'staff_type': s['staff_type'], 'specialization': s['specialization'] ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) } for s in staff_members ] diff --git a/apps/complaints/migrations/0003_inquiryattachment_inquiryupdate.py b/apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py similarity index 97% rename from apps/complaints/migrations/0003_inquiryattachment_inquiryupdate.py rename to apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py index 88bb53c..2b49018 100644 --- a/apps/complaints/migrations/0003_inquiryattachment_inquiryupdate.py +++ b/apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py @@ -9,7 +9,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('complaints', '0002_complaintcategory_complaintslaconfig_and_more'), + ('complaints', '0003_alter_complaintcategory_options_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] diff --git a/apps/complaints/ui_views.py b/apps/complaints/ui_views.py index 3e7076c..c7e22fa 100644 --- a/apps/complaints/ui_views.py +++ b/apps/complaints/ui_views.py @@ -816,21 +816,14 @@ def inquiry_list(request): @login_required def inquiry_detail(request, pk): """ -<<<<<<< HEAD Inquiry detail view with timeline and attachments. - + Features: - Full inquiry details - Timeline of all updates - Attachments management - Workflow actions (assign, status change, add note, respond) """ -======= - Inquiry detail view. - """ - from .models import Inquiry - ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) inquiry = get_object_or_404( Inquiry.objects.select_related( 'patient', 'hospital', 'department', 'assigned_to', 'responded_by' @@ -850,23 +843,18 @@ def inquiry_detail(request, pk): elif user.hospital and inquiry.hospital != user.hospital: messages.error(request, "You don't have permission to view this inquiry.") return redirect('complaints:inquiry_list') -<<<<<<< HEAD - + # Get timeline (updates) timeline = inquiry.updates.all().order_by('-created_at') - + # Get attachments attachments = inquiry.attachments.all().order_by('-created_at') - -======= ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) # Get assignable users assignable_users = User.objects.filter(is_active=True) if inquiry.hospital: assignable_users = assignable_users.filter(hospital=inquiry.hospital) -<<<<<<< HEAD - + # Status choices for the form status_choices = [ ('open', 'Open'), @@ -874,10 +862,7 @@ def inquiry_detail(request, pk): ('resolved', 'Resolved'), ('closed', 'Closed'), ] - -======= ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) context = { 'inquiry': inquiry, 'timeline': timeline, @@ -964,25 +949,25 @@ def inquiry_create(request): def inquiry_assign(request, pk): """Assign inquiry to user""" from .models import Inquiry - + inquiry = get_object_or_404(Inquiry, pk=pk) - + # 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 inquiries.") return redirect('complaints:inquiry_detail', pk=pk) - + user_id = request.POST.get('user_id') if not user_id: messages.error(request, "Please select a user to assign.") return redirect('complaints:inquiry_detail', pk=pk) - + try: assignee = User.objects.get(id=user_id) inquiry.assigned_to = assignee inquiry.save(update_fields=['assigned_to']) - + # Create update InquiryUpdate.objects.create( inquiry=inquiry, @@ -990,7 +975,7 @@ def inquiry_assign(request, pk): message=f"Assigned to {assignee.get_full_name()}", created_by=request.user ) - + # Log audit AuditService.log_event( event_type='assignment', @@ -998,12 +983,12 @@ def inquiry_assign(request, pk): user=request.user, content_object=inquiry ) - + messages.success(request, f"Inquiry assigned to {assignee.get_full_name()}.") - + except User.DoesNotExist: messages.error(request, "User not found.") - + return redirect('complaints:inquiry_detail', pk=pk) @@ -1012,32 +997,32 @@ def inquiry_assign(request, pk): def inquiry_change_status(request, pk): """Change inquiry status""" from .models import Inquiry - + inquiry = get_object_or_404(Inquiry, pk=pk) - + # 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 inquiry status.") return redirect('complaints:inquiry_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('complaints:inquiry_detail', pk=pk) - + old_status = inquiry.status inquiry.status = new_status - + # Handle status-specific logic if new_status == 'resolved' and not inquiry.response: messages.error(request, "Please add a response before resolving.") return redirect('complaints:inquiry_detail', pk=pk) - + inquiry.save() - + # Create update InquiryUpdate.objects.create( inquiry=inquiry, @@ -1047,7 +1032,7 @@ def inquiry_change_status(request, pk): old_status=old_status, new_status=new_status ) - + # Log audit AuditService.log_event( event_type='status_change', @@ -1056,7 +1041,7 @@ def inquiry_change_status(request, pk): content_object=inquiry, metadata={'old_status': old_status, 'new_status': new_status} ) - + messages.success(request, f"Inquiry status changed to {new_status}.") return redirect('complaints:inquiry_detail', pk=pk) @@ -1066,14 +1051,14 @@ def inquiry_change_status(request, pk): def inquiry_add_note(request, pk): """Add note to inquiry""" from .models import Inquiry - + inquiry = get_object_or_404(Inquiry, pk=pk) - + note = request.POST.get('note') if not note: messages.error(request, "Please enter a note.") return redirect('complaints:inquiry_detail', pk=pk) - + # Create update InquiryUpdate.objects.create( inquiry=inquiry, @@ -1081,7 +1066,7 @@ def inquiry_add_note(request, pk): message=note, created_by=request.user ) - + messages.success(request, "Note added successfully.") return redirect('complaints:inquiry_detail', pk=pk) @@ -1110,8 +1095,7 @@ def inquiry_respond(request, pk): inquiry.responded_by = request.user inquiry.status = 'resolved' inquiry.save() -<<<<<<< HEAD - + # Create update InquiryUpdate.objects.create( inquiry=inquiry, @@ -1119,10 +1103,7 @@ def inquiry_respond(request, pk): message="Response sent", created_by=request.user ) - -======= ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) # Log audit AuditService.log_event( event_type='inquiry_responded', diff --git a/apps/organizations/models.py b/apps/organizations/models.py index 763fc3f..08e4923 100644 --- a/apps/organizations/models.py +++ b/apps/organizations/models.py @@ -81,11 +81,6 @@ class Hospital(UUIDModel, TimeStampedModel): def __str__(self): return self.name -<<<<<<< HEAD - -# TODO: Add branch -======= ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) class Department(UUIDModel, TimeStampedModel): """Department within a hospital""" diff --git a/pyproject.toml b/pyproject.toml index 7bc20e8..e7a79cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,12 +24,9 @@ dependencies = [ "django-extensions>=4.1", "djangorestframework-stubs>=3.16.6", "rich>=14.2.0", -<<<<<<< HEAD "reportlab>=4.4.7", "openpyxl>=3.1.5", -======= "litellm>=1.0.0", ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) ] [project.optional-dependencies] diff --git a/templates/complaints/complaint_detail.html b/templates/complaints/complaint_detail.html index 2f3af0a..4504fb2 100644 --- a/templates/complaints/complaint_detail.html +++ b/templates/complaints/complaint_detail.html @@ -347,13 +347,8 @@

{{ complaint.resolution|linebreaks }}

-<<<<<<< HEAD {{ _("Resolved by")}} {{ complaint.resolved_by.get_full_name }} {{ _("on") }} {{ complaint.resolved_at|date:"M d, Y H:i" }} -======= - Resolved by {{ complaint.resolved_by.get_full_name }} - on {{ complaint.resolved_at|date:"M d, Y H:i" }} ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis)
@@ -640,11 +635,7 @@

-<<<<<<< HEAD {{ _("Status") }}: -======= - Status: ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis) {{ complaint.resolution_survey.get_status_display }} diff --git a/templates/complaints/complaint_form.html b/templates/complaints/complaint_form.html index 1af3c5c..68a9b9e 100644 --- a/templates/complaints/complaint_form.html +++ b/templates/complaints/complaint_form.html @@ -47,13 +47,12 @@

-<<<<<<< HEAD
{{ _("Patient Information")}}
- +
@@ -62,17 +61,15 @@ {{ _("Search by MRN or name")}}
- +
-
-======= ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis)
@@ -93,26 +90,16 @@
-<<<<<<< HEAD - ->>>>>>> 12310a5 (update complain and add ai and sentiment analysis)
@@ -175,9 +162,8 @@ -<<<<<<< HEAD
- +
@@ -192,10 +178,10 @@
- +
-
@@ -209,7 +195,7 @@
{{ _("Classification") }}
- +
@@ -234,14 +220,7 @@
- -======= - - AI will automatically generate title, classify severity/priority, and analyze emotion - -
->>>>>>> 12310a5 (update complain and add ai and sentiment analysis)