287 lines
9.1 KiB
Python
287 lines
9.1 KiB
Python
"""
|
|
PX Action Center admin
|
|
"""
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
|
|
from .models import PXAction, PXActionAttachment, PXActionLog, PXActionSLAConfig, RoutingRule
|
|
|
|
|
|
class PXActionLogInline(admin.TabularInline):
|
|
"""Inline admin for action logs"""
|
|
model = PXActionLog
|
|
extra = 1
|
|
fields = ['log_type', 'message', 'created_by', 'created_at']
|
|
readonly_fields = ['created_at']
|
|
ordering = ['-created_at']
|
|
|
|
|
|
class PXActionAttachmentInline(admin.TabularInline):
|
|
"""Inline admin for action attachments"""
|
|
model = PXActionAttachment
|
|
extra = 0
|
|
fields = ['file', 'filename', 'is_evidence', 'uploaded_by', 'description']
|
|
readonly_fields = ['file_size']
|
|
|
|
|
|
@admin.register(PXAction)
|
|
class PXActionAdmin(admin.ModelAdmin):
|
|
"""PX Action admin"""
|
|
list_display = [
|
|
'title_preview', 'source_type', 'hospital', 'category',
|
|
'severity_badge', 'status_badge', 'sla_indicator',
|
|
'assigned_to', 'escalation_level', 'created_at'
|
|
]
|
|
list_filter = [
|
|
'status', 'source_type', 'severity', 'priority', 'category',
|
|
'is_overdue', 'requires_approval', 'hospital', 'created_at'
|
|
]
|
|
search_fields = ['title', 'description', 'metadata']
|
|
ordering = ['-created_at']
|
|
date_hierarchy = 'created_at'
|
|
inlines = [PXActionLogInline, PXActionAttachmentInline]
|
|
|
|
fieldsets = (
|
|
('Source', {
|
|
'fields': ('source_type', 'content_type', 'object_id')
|
|
}),
|
|
('Action Details', {
|
|
'fields': ('title', 'description', 'category')
|
|
}),
|
|
('Organization', {
|
|
'fields': ('hospital', 'department')
|
|
}),
|
|
('Classification', {
|
|
'fields': ('priority', 'severity')
|
|
}),
|
|
('Status & Assignment', {
|
|
'fields': ('status', 'assigned_to', 'assigned_at')
|
|
}),
|
|
('SLA Tracking', {
|
|
'fields': ('due_at', 'is_overdue', 'reminder_sent_at', 'escalated_at', 'escalation_level')
|
|
}),
|
|
('Approval', {
|
|
'fields': ('requires_approval', 'approved_by', 'approved_at')
|
|
}),
|
|
('Closure', {
|
|
'fields': ('closed_at', 'closed_by')
|
|
}),
|
|
('Action Plan & Outcome', {
|
|
'fields': ('action_plan', 'outcome'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('metadata', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = [
|
|
'assigned_at', 'reminder_sent_at', 'escalated_at',
|
|
'approved_at', 'closed_at',
|
|
'created_at', 'updated_at'
|
|
]
|
|
|
|
def get_queryset(self, request):
|
|
qs = super().get_queryset(request)
|
|
return qs.select_related(
|
|
'hospital', 'department', 'assigned_to',
|
|
'approved_by', 'closed_by', 'content_type'
|
|
)
|
|
|
|
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 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(
|
|
'<span class="badge bg-{}">{}</span>',
|
|
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',
|
|
'pending_approval': 'info',
|
|
'approved': 'success',
|
|
'closed': 'success',
|
|
'cancelled': 'secondary',
|
|
}
|
|
color = colors.get(obj.status, 'secondary')
|
|
return format_html(
|
|
'<span class="badge bg-{}">{}</span>',
|
|
color,
|
|
obj.get_status_display()
|
|
)
|
|
status_badge.short_description = 'Status'
|
|
|
|
def sla_indicator(self, obj):
|
|
"""Display SLA status"""
|
|
if obj.is_overdue:
|
|
return format_html(
|
|
'<span class="badge bg-danger">OVERDUE (Level {})</span>',
|
|
obj.escalation_level
|
|
)
|
|
|
|
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('<span class="badge bg-warning">DUE SOON</span>')
|
|
else:
|
|
return format_html('<span class="badge bg-success">ON TIME</span>')
|
|
sla_indicator.short_description = 'SLA'
|
|
|
|
|
|
@admin.register(PXActionLog)
|
|
class PXActionLogAdmin(admin.ModelAdmin):
|
|
"""PX Action log admin"""
|
|
list_display = ['action', 'log_type', 'message_preview', 'created_by', 'created_at']
|
|
list_filter = ['log_type', 'created_at']
|
|
search_fields = ['message', 'action__title']
|
|
ordering = ['-created_at']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('action', 'log_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('action', '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(PXActionAttachment)
|
|
class PXActionAttachmentAdmin(admin.ModelAdmin):
|
|
"""PX Action attachment admin"""
|
|
list_display = ['action', 'filename', 'is_evidence', 'file_size', 'uploaded_by', 'created_at']
|
|
list_filter = ['is_evidence', 'file_type', 'created_at']
|
|
search_fields = ['filename', 'description', 'action__title']
|
|
ordering = ['-created_at']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('action', 'file', 'filename', 'file_type', 'file_size')
|
|
}),
|
|
('Details', {
|
|
'fields': ('is_evidence', '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('action', 'uploaded_by')
|
|
|
|
|
|
@admin.register(PXActionSLAConfig)
|
|
class PXActionSLAConfigAdmin(admin.ModelAdmin):
|
|
"""PX Action SLA configuration admin"""
|
|
list_display = [
|
|
'name', 'hospital', 'department',
|
|
'critical_hours', 'high_hours', 'medium_hours', 'low_hours',
|
|
'auto_escalate', 'is_active'
|
|
]
|
|
list_filter = ['is_active', 'auto_escalate', 'hospital']
|
|
search_fields = ['name']
|
|
ordering = ['hospital', 'name']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('name', 'hospital', 'department')
|
|
}),
|
|
('SLA Durations (hours)', {
|
|
'fields': ('critical_hours', 'high_hours', 'medium_hours', 'low_hours')
|
|
}),
|
|
('Reminder Configuration', {
|
|
'fields': ('reminder_hours_before',)
|
|
}),
|
|
('Escalation Configuration', {
|
|
'fields': ('auto_escalate', 'escalation_delay_hours')
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active',)
|
|
}),
|
|
('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('hospital', 'department')
|
|
|
|
|
|
@admin.register(RoutingRule)
|
|
class RoutingRuleAdmin(admin.ModelAdmin):
|
|
"""Routing rule admin"""
|
|
list_display = [
|
|
'name', 'source_type', 'category', 'severity',
|
|
'hospital', 'assign_to_role', 'priority', 'is_active'
|
|
]
|
|
list_filter = ['source_type', 'severity', 'is_active', 'hospital']
|
|
search_fields = ['name', 'description']
|
|
ordering = ['-priority', 'name']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('name', 'description', 'priority')
|
|
}),
|
|
('Conditions', {
|
|
'fields': ('source_type', 'category', 'severity', 'hospital', 'department')
|
|
}),
|
|
('Routing Target', {
|
|
'fields': ('assign_to_role', 'assign_to_user', 'assign_to_department')
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active',)
|
|
}),
|
|
('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('hospital', 'department', 'assign_to_user', 'assign_to_department')
|