""" Complaints admin """ from django.contrib import admin from django.utils.html import format_html, mark_safe from .models import ( Complaint, ComplaintAttachment, ComplaintCategory, ComplaintMeeting, ComplaintPRInteraction, ComplaintSLAConfig, ComplaintThreshold, ComplaintUpdate, EscalationRule, Inquiry, ExplanationSLAConfig, ComplaintInvolvedDepartment, ComplaintInvolvedStaff, OnCallAdminSchedule, OnCallAdmin, ComplaintAdverseAction, ComplaintAdverseActionAttachment, ) class ExplanationSLAConfigAdmin(admin.ModelAdmin): list_display = ( "hospital", "response_hours", "reminder_hours_before", "second_reminder_enabled", "second_reminder_hours_before", "auto_escalate_enabled", "is_active", ) list_filter = ("is_active", "second_reminder_enabled", "auto_escalate_enabled") fieldsets = ( ( None, { "fields": ( "hospital", "response_hours", "reminder_hours_before", "second_reminder_enabled", "second_reminder_hours_before", "is_active", ) }, ), ("Escalation", {"fields": ("auto_escalate_enabled", "escalation_hours_overdue", "max_escalation_levels")}), ) admin.site.register(ExplanationSLAConfig, ExplanationSLAConfigAdmin) 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", "forwarded_at", "first_reminder_sent_at", "second_reminder_sent_at", "response_submitted", "response_submitted_at", "delay_reason", "delayed_person", ] autocomplete_fields = ["department", "assigned_to"] readonly_fields = ["response_submitted_at"] 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", "satisfaction_badge", "created_by", "assigned_to", "created_at", ] list_filter = [ "status", "severity", "priority", "category", "source", "location", "main_section", "subsection", "is_overdue", "hospital", "created_by", "created_at", "satisfaction", ] search_fields = [ "title", "description", "patient__mrn", "patient__first_name", "patient__last_name", "encounter_id", "file_number", "moh_reference", "chi_reference", "complaint_subject", ] ordering = ["-created_at"] date_hierarchy = "created_at" inlines = [ ComplaintUpdateInline, ComplaintAttachmentInline, ComplaintInvolvedDepartmentInline, ComplaintInvolvedStaffInline, ] fieldsets = ( ( "Patient & Encounter", {"fields": ("patient", "patient_name", "file_number", "encounter_id", "incident_date", "contact_phone")}, ), ("Organization", {"fields": ("hospital", "department", "staff")}), ("Location Hierarchy", {"fields": ("location", "main_section", "subsection")}), ( "Complaint Details", { "fields": ( "complaint_type", "title", "ai_brief_en", "ai_brief_ar", "description", "complaint_subject", "category", "subcategory", ) }, ), ("Classification", {"fields": ("priority", "severity", "source")}), ( "External References", {"fields": ("moh_reference", "moh_reference_date", "chi_reference", "chi_reference_date")}, ), ("Creator Tracking", {"fields": ("created_by",)}), ("Status & Assignment", {"fields": ("status", "assigned_to", "assigned_at", "activated_at")}), ("Workflow Timeline", {"fields": ("form_sent_at", "forwarded_to_dept_at", "response_date")}), ( "SLA Tracking", { "fields": ( "due_at", "is_overdue", "reminder_sent_at", "second_reminder_sent_at", "breached_at", "escalated_at", ) }, ), ( "Resolution", { "fields": ( "resolution", "resolution_category", "resolution_outcome", "resolution_outcome_other", "resolved_at", "resolved_by", ) }, ), ( "Satisfaction", { "fields": ( "satisfaction", "action_taken_by_dept", "action_result", "recommendation_action_plan", "delay_reason_closure", "explanation_delay_reason", ) }, ), ("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", "activated_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_en or obj.location.name_ar or str(obj.location)) if obj.main_section: parts.append(obj.main_section.name_en or obj.main_section.name_ar or str(obj.main_section)) if obj.subsection: parts.append(obj.subsection.name_en or obj.subsection.name_ar or str(obj.subsection)) if not parts: return "—" hierarchy = " → ".join(parts) return format_html('{0}', 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('{1}', color, obj.get_severity_display()) severity_badge.short_description = "Severity" def status_badge(self, obj): """Display status with color badge""" colors = { "open": "warning", "in_progress": "info", "partially_resolved": "info", "resolved": "success", "closed": "secondary", "cancelled": "secondary", "contacted": "info", "contacted_no_response": "danger", } color = colors.get(obj.status, "secondary") return format_html('{1}', color, obj.get_status_display()) status_badge.short_description = "Status" def satisfaction_badge(self, obj): """Display satisfaction with color badge""" if not obj.satisfaction: return "—" colors = { "satisfied": "success", "neutral": "warning", "dissatisfied": "danger", "no_response": "secondary", "escalated": "danger", } color = colors.get(obj.satisfaction, "secondary") return format_html('{1}', color, obj.get_satisfaction_display()) satisfaction_badge.short_description = "Satisfaction" 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('{1}', 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 mark_safe('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 mark_safe('DUE SOON') else: return mark_safe('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: {0}h / {1}h', obj.first_reminder_hours_after, obj.second_reminder_hours_after or "N/A", ) elif obj.reminder_hours_before: return format_html( 'Deadline-based: {0}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 mark_safe('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 mark_safe('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"]