""" Complaints admin """ from django.contrib import admin from django.utils.html import format_html from .models import ( Complaint, ComplaintAttachment, ComplaintCategory, ComplaintMeeting, ComplaintPRInteraction, ComplaintSLAConfig, ComplaintThreshold, ComplaintUpdate, EscalationRule, Inquiry, ExplanationSLAConfig, ComplaintInvolvedDepartment, ComplaintInvolvedStaff, OnCallAdminSchedule, OnCallAdmin, ComplaintAdverseAction, ComplaintAdverseActionAttachment, ) admin.site.register(ExplanationSLAConfig) class ComplaintAttachmentInline(admin.TabularInline): """Inline admin for complaint attachments""" model = ComplaintAttachment extra = 0 fields = ['file', 'filename', 'file_size', 'uploaded_by', 'description'] readonly_fields = ['file_size'] class ComplaintUpdateInline(admin.TabularInline): """Inline admin for complaint updates""" model = ComplaintUpdate extra = 1 fields = ['update_type', 'message', 'created_by', 'created_at'] readonly_fields = ['created_at'] ordering = ['-created_at'] class ComplaintInvolvedDepartmentInline(admin.TabularInline): """Inline admin for involved departments""" model = ComplaintInvolvedDepartment extra = 0 fields = ['department', 'role', 'is_primary', 'assigned_to', 'response_submitted'] autocomplete_fields = ['department', 'assigned_to'] class ComplaintInvolvedStaffInline(admin.TabularInline): """Inline admin for involved staff""" model = ComplaintInvolvedStaff extra = 0 fields = ['staff', 'role', 'explanation_requested', 'explanation_received'] autocomplete_fields = ['staff'] @admin.register(Complaint) class ComplaintAdmin(admin.ModelAdmin): """Complaint admin""" list_display = [ 'title_preview', 'complaint_type_badge', 'patient', 'hospital', 'location_hierarchy', 'category', 'severity_badge', 'status_badge', 'sla_indicator', 'created_by', 'assigned_to', 'created_at' ] list_filter = [ 'status', 'severity', 'priority', 'category', 'source', 'location', 'main_section', 'subsection', 'is_overdue', 'hospital', 'created_by', 'created_at' ] search_fields = [ 'title', 'description', 'patient__mrn', 'patient__first_name', 'patient__last_name', 'encounter_id' ] ordering = ['-created_at'] date_hierarchy = 'created_at' inlines = [ComplaintUpdateInline, ComplaintAttachmentInline, ComplaintInvolvedDepartmentInline, ComplaintInvolvedStaffInline] fieldsets = ( ('Patient & Encounter', { 'fields': ('patient', 'encounter_id') }), ('Organization', { 'fields': ('hospital', 'department', 'staff') }), ('Location Hierarchy', { 'fields': ('location', 'main_section', 'subsection') }), ('Complaint Details', { 'fields': ('complaint_type', 'title', 'description', 'category', 'subcategory') }), ('Classification', { 'fields': ('priority', 'severity', 'source') }), ('Creator Tracking', { 'fields': ('created_by',) }), ('Status & Assignment', { 'fields': ('status', 'assigned_to', 'assigned_at') }), ('SLA Tracking', { 'fields': ('due_at', 'is_overdue', 'reminder_sent_at', 'escalated_at') }), ('Resolution', { 'fields': ('resolution', 'resolved_at', 'resolved_by') }), ('Closure', { 'fields': ('closed_at', 'closed_by', 'resolution_survey', 'resolution_survey_sent_at') }), ('Metadata', { 'fields': ('metadata', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = [ 'assigned_at', 'reminder_sent_at', 'escalated_at', 'resolved_at', 'closed_at', 'resolution_survey_sent_at', 'created_at', 'updated_at' ] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related( 'patient', 'hospital', 'department', 'staff', 'location', 'main_section', 'subsection', 'assigned_to', 'resolved_by', 'closed_by', 'resolution_survey', 'created_by' ) def title_preview(self, obj): """Show preview of title""" return obj.title[:60] + '...' if len(obj.title) > 60 else obj.title title_preview.short_description = 'Title' def location_hierarchy(self, obj): """Display location hierarchy in admin""" parts = [] if obj.location: parts.append(obj.location.name) if obj.main_section: parts.append(obj.main_section.name) if obj.subsection: parts.append(obj.subsection.name) if not parts: return '—' hierarchy = ' → '.join(parts) return format_html('{}', hierarchy) location_hierarchy.short_description = 'Location' def severity_badge(self, obj): """Display severity with color badge""" colors = { 'low': 'info', 'medium': 'warning', 'high': 'danger', 'critical': 'danger', } color = colors.get(obj.severity, 'secondary') return format_html( '{}', color, obj.get_severity_display() ) severity_badge.short_description = 'Severity' def status_badge(self, obj): """Display status with color badge""" colors = { 'open': 'danger', 'in_progress': 'warning', 'resolved': 'info', 'closed': 'success', 'cancelled': 'secondary', } color = colors.get(obj.status, 'secondary') return format_html( '{}', color, obj.get_status_display() ) status_badge.short_description = 'Status' def complaint_type_badge(self, obj): """Display complaint type with color badge""" colors = { 'complaint': 'danger', 'appreciation': 'success', } color = colors.get(obj.complaint_type, 'secondary') return format_html( '{}', color, obj.get_complaint_type_display() ) complaint_type_badge.short_description = 'Type' def sla_indicator(self, obj): """Display SLA status""" if obj.is_overdue: return format_html('OVERDUE') from django.utils import timezone time_remaining = obj.due_at - timezone.now() hours_remaining = time_remaining.total_seconds() / 3600 if hours_remaining < 4: return format_html('DUE SOON') else: return format_html('ON TIME') sla_indicator.short_description = 'SLA' @admin.register(ComplaintAttachment) class ComplaintAttachmentAdmin(admin.ModelAdmin): """Complaint attachment admin""" list_display = ['complaint', 'filename', 'file_type', 'file_size', 'uploaded_by', 'created_at'] list_filter = ['file_type', 'created_at'] search_fields = ['filename', 'description', 'complaint__title'] ordering = ['-created_at'] fieldsets = ( (None, { 'fields': ('complaint', 'file', 'filename', 'file_type', 'file_size') }), ('Details', { 'fields': ('uploaded_by', 'description') }), ('Metadata', { 'fields': ('created_at', 'updated_at') }), ) readonly_fields = ['file_size', 'created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('complaint', 'uploaded_by') @admin.register(ComplaintUpdate) class ComplaintUpdateAdmin(admin.ModelAdmin): """Complaint update admin""" list_display = ['complaint', 'update_type', 'message_preview', 'created_by', 'created_at'] list_filter = ['update_type', 'created_at'] search_fields = ['message', 'complaint__title'] ordering = ['-created_at'] fieldsets = ( (None, { 'fields': ('complaint', 'update_type', 'message') }), ('Status Change', { 'fields': ('old_status', 'new_status'), 'classes': ('collapse',) }), ('Details', { 'fields': ('created_by', 'metadata') }), ('Metadata', { 'fields': ('created_at', 'updated_at') }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('complaint', 'created_by') def message_preview(self, obj): """Show preview of message""" return obj.message[:100] + '...' if len(obj.message) > 100 else obj.message message_preview.short_description = 'Message' @admin.register(Inquiry) class InquiryAdmin(admin.ModelAdmin): """Inquiry admin""" list_display = [ 'subject_preview', 'patient', 'contact_name', 'hospital', 'category', 'status', 'created_by', 'assigned_to', 'created_at' ] list_filter = ['status', 'category', 'source', 'hospital', 'created_by', 'created_at'] search_fields = [ 'subject', 'message', 'contact_name', 'contact_phone', 'patient__mrn', 'patient__first_name', 'patient__last_name' ] ordering = ['-created_at'] fieldsets = ( ('Patient Information', { 'fields': ('patient',) }), ('Contact Information (if no patient)', { 'fields': ('contact_name', 'contact_phone', 'contact_email'), 'classes': ('collapse',) }), ('Organization', { 'fields': ('hospital', 'department') }), ('Inquiry Details', { 'fields': ('subject', 'message', 'category', 'source') }), ('Creator Tracking', { 'fields': ('created_by',) }), ('Status & Assignment', { 'fields': ('status', 'assigned_to') }), ('Response', { 'fields': ('response', 'responded_at', 'responded_by') }), ('Metadata', { 'fields': ('created_at', 'updated_at') }), ) readonly_fields = ['responded_at', 'created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related( 'patient', 'hospital', 'department', 'assigned_to', 'responded_by', 'created_by' ) def subject_preview(self, obj): """Show preview of subject""" return obj.subject[:60] + '...' if len(obj.subject) > 60 else obj.subject subject_preview.short_description = 'Subject' @admin.register(ComplaintSLAConfig) class ComplaintSLAConfigAdmin(admin.ModelAdmin): """Complaint SLA Configuration admin""" list_display = [ 'hospital', 'source', 'severity', 'priority', 'sla_hours', 'reminder_timing_display', 'is_active' ] list_filter = ['hospital', 'source', 'severity', 'priority', 'is_active'] search_fields = ['hospital__name_en', 'hospital__name_ar', 'source__name_en'] ordering = ['hospital', 'source', 'severity', 'priority'] fieldsets = ( ('Hospital', { 'fields': ('hospital',) }), ('Source & Classification', { 'fields': ('source', 'severity', 'priority') }), ('SLA Configuration', { 'fields': ('sla_hours', 'reminder_hours_before') }), ('Source-Based Timing (Hours After Creation)', { 'fields': ( 'first_reminder_hours_after', 'second_reminder_hours_after', 'escalation_hours_after' ), 'description': 'When set, these override the "Hours Before Deadline" timing. Used for source-based SLAs (e.g., MOH, CCHI).' }), ('Status', { 'fields': ('is_active',) }), ('Metadata', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('hospital', 'source') def reminder_timing_display(self, obj): """Display reminder timing method""" if obj.source and obj.first_reminder_hours_after: return format_html( 'Source-based: {}h / {}h', obj.first_reminder_hours_after, obj.second_reminder_hours_after or 'N/A' ) elif obj.reminder_hours_before: return format_html( 'Deadline-based: {}h before', obj.reminder_hours_before ) else: return '—' reminder_timing_display.short_description = 'Reminder Timing' @admin.register(ComplaintCategory) class ComplaintCategoryAdmin(admin.ModelAdmin): """Complaint Category admin""" list_display = [ 'name_en', 'code', 'hospitals_display', 'parent', 'order', 'is_active' ] list_filter = ['is_active', 'parent'] search_fields = ['name_en', 'name_ar', 'code', 'description_en'] ordering = ['order', 'name_en'] fieldsets = ( ('Hospitals', { 'fields': ('hospitals',) }), ('Category Details', { 'fields': ('code', 'name_en', 'name_ar') }), ('Description', { 'fields': ('description_en', 'description_ar'), 'classes': ('collapse',) }), ('Hierarchy', { 'fields': ('parent', 'order') }), ('Status', { 'fields': ('is_active',) }), ('Metadata', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at'] filter_horizontal = ['hospitals'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('parent').prefetch_related('hospitals') def hospitals_display(self, obj): """Display hospitals for category""" hospital_count = obj.hospitals.count() if hospital_count == 0: return 'System-wide' elif hospital_count == 1: return obj.hospitals.first().name else: return f'{hospital_count} hospitals' hospitals_display.short_description = 'Hospitals' @admin.register(EscalationRule) class EscalationRuleAdmin(admin.ModelAdmin): """Escalation Rule admin""" list_display = [ 'name', 'hospital', 'escalate_to_role', 'trigger_on_overdue', 'order', 'is_active' ] list_filter = [ 'hospital', 'escalate_to_role', 'trigger_on_overdue', 'severity_filter', 'priority_filter', 'is_active' ] search_fields = ['name', 'description', 'hospital__name_en'] ordering = ['hospital', 'order'] fieldsets = ( ('Hospital', { 'fields': ('hospital',) }), ('Rule Details', { 'fields': ('name', 'description') }), ('Trigger Conditions', { 'fields': ('trigger_on_overdue', 'trigger_hours_overdue') }), ('Escalation Target', { 'fields': ('escalate_to_role', 'escalate_to_user') }), ('Filters', { 'fields': ('severity_filter', 'priority_filter'), 'classes': ('collapse',) }), ('Order & Status', { 'fields': ('order', 'is_active') }), ('Metadata', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('hospital', 'escalate_to_user') @admin.register(ComplaintThreshold) class ComplaintThresholdAdmin(admin.ModelAdmin): """Complaint Threshold admin""" list_display = [ 'hospital', 'threshold_type', 'comparison_display', 'threshold_value', 'action_type', 'is_active' ] list_filter = [ 'hospital', 'threshold_type', 'comparison_operator', 'action_type', 'is_active' ] search_fields = ['hospital__name_en', 'hospital__name_ar'] ordering = ['hospital', 'threshold_type'] fieldsets = ( ('Hospital', { 'fields': ('hospital',) }), ('Threshold Configuration', { 'fields': ('threshold_type', 'threshold_value', 'comparison_operator') }), ('Action', { 'fields': ('action_type',) }), ('Status', { 'fields': ('is_active',) }), ('Metadata', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('hospital') def comparison_display(self, obj): """Display comparison operator""" return f"{obj.get_comparison_operator_display()}" comparison_display.short_description = 'Comparison' @admin.register(ComplaintPRInteraction) class ComplaintPRInteractionAdmin(admin.ModelAdmin): """PR Interaction admin""" list_display = [ 'complaint', 'contact_date', 'contact_method_display', 'pr_staff', 'procedure_explained', 'created_at' ] list_filter = [ 'contact_method', 'procedure_explained', 'created_at' ] search_fields = [ 'complaint__title', 'statement_text', 'notes', 'pr_staff__first_name', 'pr_staff__last_name' ] ordering = ['-contact_date'] fieldsets = ( ('Complaint', { 'fields': ('complaint',) }), ('Contact Details', { 'fields': ('contact_date', 'contact_method', 'pr_staff') }), ('Interaction Details', { 'fields': ('statement_text', 'procedure_explained', 'notes') }), ('Metadata', { 'fields': ('created_by', 'created_at', 'updated_at') }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('complaint', 'pr_staff', 'created_by') def contact_method_display(self, obj): """Display contact method""" return obj.get_contact_method_display() contact_method_display.short_description = 'Method' @admin.register(ComplaintMeeting) class ComplaintMeetingAdmin(admin.ModelAdmin): """Complaint Meeting admin""" list_display = [ 'complaint', 'meeting_date', 'meeting_type_display', 'outcome_preview', 'created_by', 'created_at' ] list_filter = [ 'meeting_type', 'created_at' ] search_fields = [ 'complaint__title', 'outcome', 'notes' ] ordering = ['-meeting_date'] fieldsets = ( ('Complaint', { 'fields': ('complaint',) }), ('Meeting Details', { 'fields': ('meeting_date', 'meeting_type') }), ('Meeting Outcome', { 'fields': ('outcome', 'notes') }), ('Metadata', { 'fields': ('created_by', 'created_at', 'updated_at') }), ) readonly_fields = ['created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('complaint', 'created_by') def meeting_type_display(self, obj): """Display meeting type""" return obj.get_meeting_type_display() meeting_type_display.short_description = 'Type' def outcome_preview(self, obj): """Show preview of outcome""" return obj.outcome[:100] + '...' if len(obj.outcome) > 100 else obj.outcome outcome_preview.short_description = 'Outcome' @admin.register(ComplaintInvolvedDepartment) class ComplaintInvolvedDepartmentAdmin(admin.ModelAdmin): """Complaint Involved Department admin""" list_display = [ 'complaint', 'department', 'role', 'is_primary', 'assigned_to', 'response_submitted', 'created_at' ] list_filter = ['role', 'is_primary', 'response_submitted', 'created_at'] search_fields = [ 'complaint__title', 'complaint__reference_number', 'department__name', 'notes' ] ordering = ['-is_primary', '-created_at'] autocomplete_fields = ['complaint', 'department', 'assigned_to', 'added_by'] fieldsets = ( ('Complaint', { 'fields': ('complaint',) }), ('Department & Role', { 'fields': ('department', 'role', 'is_primary') }), ('Assignment', { 'fields': ('assigned_to', 'assigned_at') }), ('Response', { 'fields': ('response_submitted', 'response_submitted_at', 'response_notes') }), ('Notes', { 'fields': ('notes',) }), ('Metadata', { 'fields': ('added_by', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['assigned_at', 'response_submitted_at', 'created_at', 'updated_at'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related( 'complaint', 'department', 'assigned_to', 'added_by' ) @admin.register(ComplaintInvolvedStaff) class ComplaintInvolvedStaffAdmin(admin.ModelAdmin): """Complaint Involved Staff admin""" list_display = [ 'complaint', 'staff', 'role', 'explanation_requested', 'explanation_received', 'created_at' ] list_filter = ['role', 'explanation_requested', 'explanation_received', 'created_at'] search_fields = [ 'complaint__title', 'complaint__reference_number', 'staff__first_name', 'staff__last_name', 'notes' ] ordering = ['role', '-created_at'] autocomplete_fields = ['complaint', 'staff', 'added_by'] fieldsets = ( ('Complaint', { 'fields': ('complaint',) }), ('Staff & Role', { 'fields': ('staff', 'role') }), ('Explanation', { 'fields': ( 'explanation_requested', 'explanation_requested_at', 'explanation_received', 'explanation_received_at', 'explanation' ) }), ('Notes', { 'fields': ('notes',) }), ('Metadata', { 'fields': ('added_by', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = [ 'explanation_requested_at', 'explanation_received_at', 'created_at', 'updated_at' ] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related( 'complaint', 'staff', 'added_by' ) class OnCallAdminInline(admin.TabularInline): """Inline admin for on-call admins""" model = OnCallAdmin extra = 1 fields = [ 'admin_user', 'start_date', 'end_date', 'notification_priority', 'is_active', 'notify_email', 'notify_sms', 'sms_phone' ] autocomplete_fields = ['admin_user'] def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('admin_user') @admin.register(OnCallAdminSchedule) class OnCallAdminScheduleAdmin(admin.ModelAdmin): """On-Call Admin Schedule admin""" list_display = [ 'hospital_or_system', 'working_hours_display', 'working_days_display', 'timezone', 'is_active', 'created_at' ] list_filter = ['is_active', 'timezone', 'created_at'] search_fields = ['hospital__name'] inlines = [OnCallAdminInline] fieldsets = ( ('Scope', { 'fields': ('hospital', 'is_active') }), ('Working Hours Configuration', { 'fields': ('work_start_time', 'work_end_time', 'timezone', 'working_days'), 'description': 'Configure working hours. Outside these hours, only on-call admins will be notified.' }), ) readonly_fields = ['created_at', 'updated_at'] def hospital_or_system(self, obj): """Display hospital name or 'System-wide'""" if obj.hospital: return obj.hospital.name return format_html('System-wide') hospital_or_system.short_description = 'Scope' hospital_or_system.admin_order_field = 'hospital__name' def working_hours_display(self, obj): """Display working hours""" return f"{obj.work_start_time.strftime('%H:%M')} - {obj.work_end_time.strftime('%H:%M')}" working_hours_display.short_description = 'Working Hours' def working_days_display(self, obj): """Display working days as abbreviated day names""" days = obj.get_working_days_list() day_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] selected_days = [day_names[d] for d in days if 0 <= d <= 6] return ', '.join(selected_days) if selected_days else 'None' working_days_display.short_description = 'Working Days' def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('hospital') @admin.register(OnCallAdmin) class OnCallAdminAdmin(admin.ModelAdmin): """On-Call Admin admin""" list_display = [ 'admin_user', 'schedule', 'notification_priority', 'date_range', 'contact_preferences', 'is_active' ] list_filter = [ 'is_active', 'notify_email', 'notify_sms', 'schedule__hospital', 'created_at' ] search_fields = [ 'admin_user__email', 'admin_user__first_name', 'admin_user__last_name', 'sms_phone' ] autocomplete_fields = ['admin_user', 'schedule'] fieldsets = ( ('Assignment', { 'fields': ('schedule', 'admin_user', 'is_active') }), ('Active Period (Optional)', { 'fields': ('start_date', 'end_date'), 'description': 'Leave empty for permanent assignment' }), ('Notification Settings', { 'fields': ( 'notification_priority', 'notify_email', 'notify_sms', 'sms_phone' ), 'description': 'Configure how this admin should be notified for after-hours complaints' }), ) readonly_fields = ['created_at', 'updated_at'] def date_range(self, obj): """Display date range""" if obj.start_date and obj.end_date: return f"{obj.start_date} to {obj.end_date}" elif obj.start_date: return f"From {obj.start_date}" elif obj.end_date: return f"Until {obj.end_date}" return format_html('Permanent') date_range.short_description = 'Active Period' def contact_preferences(self, obj): """Display contact preferences""" prefs = [] if obj.notify_email: prefs.append('📧 Email') if obj.notify_sms: prefs.append(f'📱 SMS ({obj.sms_phone or "user phone"})') return ', '.join(prefs) if prefs else 'None' contact_preferences.short_description = 'Contact' def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('admin_user', 'schedule', 'schedule__hospital') class ComplaintAdverseActionAttachmentInline(admin.TabularInline): """Inline admin for adverse action attachments""" model = ComplaintAdverseActionAttachment extra = 0 fields = ['file', 'filename', 'description', 'uploaded_by'] readonly_fields = ['filename', 'file_size'] @admin.register(ComplaintAdverseAction) class ComplaintAdverseActionAdmin(admin.ModelAdmin): """Admin for complaint adverse actions""" list_display = [ 'complaint_reference', 'action_type_display', 'severity_badge', 'incident_date', 'status_badge', 'is_escalated', 'created_at' ] list_filter = [ 'action_type', 'severity', 'status', 'is_escalated', 'incident_date', 'created_at' ] search_fields = [ 'complaint__reference_number', 'complaint__title', 'description', 'patient_impact' ] date_hierarchy = 'incident_date' inlines = [ComplaintAdverseActionAttachmentInline] fieldsets = ( ('Complaint Information', { 'fields': ('complaint',) }), ('Adverse Action Details', { 'fields': ( 'action_type', 'severity', 'description', 'incident_date', 'location' ) }), ('Impact & Staff', { 'fields': ( 'patient_impact', 'involved_staff' ) }), ('Verification & Investigation', { 'fields': ( 'status', 'reported_by', 'investigation_notes', 'investigated_by', 'investigated_at' ), 'classes': ('collapse',) }), ('Resolution', { 'fields': ( 'resolution', 'resolved_by', 'resolved_at' ), 'classes': ('collapse',) }), ('Escalation', { 'fields': ( 'is_escalated', 'escalated_at' ) }), ) readonly_fields = ['created_at', 'updated_at'] def complaint_reference(self, obj): """Display complaint reference""" return format_html( '{}', obj.complaint.id, obj.complaint.reference_number ) complaint_reference.short_description = 'Complaint' def action_type_display(self, obj): """Display action type with formatting""" return obj.get_action_type_display() action_type_display.short_description = 'Action Type' def severity_badge(self, obj): """Display severity as colored badge""" colors = { 'low': '#22c55e', # green 'medium': '#f59e0b', # amber 'high': '#ef4444', # red 'critical': '#7f1d1d', # dark red } color = colors.get(obj.severity, '#64748b') return format_html( '{}', color, obj.get_severity_display() ) severity_badge.short_description = 'Severity' def status_badge(self, obj): """Display status as colored badge""" colors = { 'reported': '#f59e0b', 'under_investigation': '#3b82f6', 'verified': '#22c55e', 'unfounded': '#64748b', 'resolved': '#10b981', } color = colors.get(obj.status, '#64748b') return format_html( '{}', color, obj.get_status_display() ) status_badge.short_description = 'Status' def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('complaint', 'reported_by', 'investigated_by', 'resolved_by') @admin.register(ComplaintAdverseActionAttachment) class ComplaintAdverseActionAttachmentAdmin(admin.ModelAdmin): """Admin for adverse action attachments""" list_display = ['adverse_action', 'filename', 'file_type', 'uploaded_by', 'created_at'] list_filter = ['file_type', 'created_at'] search_fields = ['filename', 'description', 'adverse_action__complaint__reference_number'] ordering = ['-created_at']