484 lines
17 KiB
Python
484 lines
17 KiB
Python
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils.safestring import mark_safe
|
|
from .models import (
|
|
QualityIndicator, QualityMeasurement, IncidentReport,
|
|
RiskAssessment, AuditPlan, AuditFinding, ImprovementProject
|
|
)
|
|
|
|
|
|
class QualityMeasurementInline(admin.TabularInline):
|
|
"""Inline for quality measurements"""
|
|
model = QualityMeasurement
|
|
extra = 0
|
|
fields = ['measurement_date', 'value', 'numerator', 'denominator', 'status', 'verified_by']
|
|
readonly_fields = ['created_by', 'created_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related('verified_by', 'created_by')
|
|
|
|
|
|
@admin.register(QualityIndicator)
|
|
class QualityIndicatorAdmin(admin.ModelAdmin):
|
|
"""Admin for quality indicators"""
|
|
list_display = [
|
|
'name', 'category', 'type', 'frequency', 'target_value',
|
|
'current_status_display', 'responsible_department', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'category', 'type', 'frequency', 'is_active',
|
|
'regulatory_requirement', 'responsible_department'
|
|
]
|
|
search_fields = ['name', 'description', 'data_source']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
fieldsets = [
|
|
('Basic Information', {
|
|
'fields': ['name', 'description', 'category', 'type']
|
|
}),
|
|
('Measurement Details', {
|
|
'fields': ['measurement_unit', 'target_value', 'threshold_warning', 'threshold_critical']
|
|
}),
|
|
('Calculation', {
|
|
'fields': ['calculation_method', 'data_source', 'frequency']
|
|
}),
|
|
('Responsibility', {
|
|
'fields': ['responsible_department', 'responsible_user']
|
|
}),
|
|
('Settings', {
|
|
'fields': ['is_active', 'regulatory_requirement']
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ['created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
inlines = [QualityMeasurementInline]
|
|
|
|
def current_status_display(self, obj):
|
|
"""Display current status with color coding"""
|
|
status = obj.current_status
|
|
colors = {
|
|
'within_target': 'green',
|
|
'warning': 'orange',
|
|
'critical': 'red',
|
|
'improving': 'blue',
|
|
'declining': 'purple',
|
|
'no_data': 'gray'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {};">{}</span>',
|
|
colors.get(status, 'black'),
|
|
status.replace('_', ' ').title()
|
|
)
|
|
current_status_display.short_description = 'Current Status'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'responsible_department', 'responsible_user'
|
|
).prefetch_related('measurements')
|
|
|
|
|
|
@admin.register(QualityMeasurement)
|
|
class QualityMeasurementAdmin(admin.ModelAdmin):
|
|
"""Admin for quality measurements"""
|
|
list_display = [
|
|
'indicator', 'measurement_date', 'value', 'status_display',
|
|
'verified_by', 'created_by'
|
|
]
|
|
list_filter = [
|
|
'status', 'measurement_date', 'verified_by',
|
|
'indicator__category', 'indicator__type'
|
|
]
|
|
search_fields = ['indicator__name', 'notes', 'data_source_reference']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
date_hierarchy = 'measurement_date'
|
|
|
|
fieldsets = [
|
|
('Measurement', {
|
|
'fields': ['indicator', 'measurement_date', 'value']
|
|
}),
|
|
('Rate Calculation', {
|
|
'fields': ['numerator', 'denominator'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Status and Notes', {
|
|
'fields': ['status', 'notes', 'data_source_reference']
|
|
}),
|
|
('Verification', {
|
|
'fields': ['verified_by', 'verified_at']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_by', 'created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
|
|
def status_display(self, obj):
|
|
"""Display status with color coding"""
|
|
colors = {
|
|
'within_target': 'green',
|
|
'warning': 'orange',
|
|
'critical': 'red',
|
|
'improving': 'blue',
|
|
'declining': 'purple'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {};">{}</span>',
|
|
colors.get(obj.status, 'black'),
|
|
obj.get_status_display()
|
|
)
|
|
status_display.short_description = 'Status'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'indicator', 'verified_by', 'created_by'
|
|
)
|
|
|
|
|
|
class RiskAssessmentInline(admin.TabularInline):
|
|
"""Inline for risk assessments"""
|
|
model = RiskAssessment
|
|
extra = 0
|
|
fields = ['title', 'risk_category', 'risk_level', 'status', 'responsible_person']
|
|
readonly_fields = ['risk_score', ]
|
|
|
|
|
|
@admin.register(IncidentReport)
|
|
class IncidentReportAdmin(admin.ModelAdmin):
|
|
"""Admin for incident reports"""
|
|
list_display = [
|
|
'incident_number', 'title', 'incident_type', 'severity_display',
|
|
'status', 'assigned_to', 'incident_date'
|
|
]
|
|
list_filter = [
|
|
'incident_type', 'severity', 'category', 'status', 'priority',
|
|
'incident_date', 'is_confidential', 'regulatory_notification'
|
|
]
|
|
search_fields = ['incident_number', 'title', 'description', 'location']
|
|
readonly_fields = ['incident_number', 'created_at', 'updated_at']
|
|
date_hierarchy = 'incident_date'
|
|
|
|
fieldsets = [
|
|
('Incident Details', {
|
|
'fields': ['incident_number', 'title', 'description']
|
|
}),
|
|
('Classification', {
|
|
'fields': ['incident_type', 'severity', 'category', 'location']
|
|
}),
|
|
('Timeline', {
|
|
'fields': ['incident_date', 'discovered_date']
|
|
}),
|
|
('People Involved', {
|
|
'fields': ['patient', 'reported_by']
|
|
}),
|
|
('Investigation', {
|
|
'fields': ['status', 'priority', 'assigned_to', 'due_date']
|
|
}),
|
|
('Analysis', {
|
|
'fields': ['root_cause', 'contributing_factors'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Actions', {
|
|
'fields': ['corrective_actions', 'preventive_actions'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Settings', {
|
|
'fields': ['is_confidential', 'regulatory_notification']
|
|
}),
|
|
('Closure', {
|
|
'fields': ['closed_date'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
inlines = [RiskAssessmentInline]
|
|
|
|
def severity_display(self, obj):
|
|
"""Display severity with color coding"""
|
|
colors = {
|
|
'no_harm': 'green',
|
|
'minor_harm': 'yellow',
|
|
'moderate_harm': 'orange',
|
|
'severe_harm': 'red',
|
|
'death': 'darkred'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
colors.get(obj.severity, 'black'),
|
|
obj.get_severity_display()
|
|
)
|
|
severity_display.short_description = 'Severity'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'patient', 'reported_by', 'assigned_to'
|
|
)
|
|
|
|
|
|
@admin.register(RiskAssessment)
|
|
class RiskAssessmentAdmin(admin.ModelAdmin):
|
|
"""Admin for risk assessments"""
|
|
list_display = [
|
|
'title', 'risk_category', 'risk_level_display',
|
|
'status', 'responsible_person', 'review_date'
|
|
]
|
|
list_filter = [
|
|
'risk_category', 'risk_type', 'risk_level',
|
|
'status', 'control_effectiveness', 'review_date'
|
|
]
|
|
search_fields = ['title', 'description', 'current_controls', 'mitigation_plan']
|
|
readonly_fields = ['risk_score', 'risk_level', 'created_at', 'updated_at']
|
|
date_hierarchy = 'review_date'
|
|
|
|
fieldsets = [
|
|
('Risk Information', {
|
|
'fields': ['title', 'description', 'risk_category', 'risk_type']
|
|
}),
|
|
('Initial Risk Assessment', {
|
|
'fields': ['likelihood', 'impact', 'risk_score', 'risk_level']
|
|
}),
|
|
('Current Controls', {
|
|
'fields': ['current_controls', 'control_effectiveness']
|
|
}),
|
|
('Residual Risk', {
|
|
'fields': ['residual_likelihood', 'residual_impact', 'residual_risk_score', 'residual_risk_level']
|
|
}),
|
|
('Mitigation', {
|
|
'fields': ['mitigation_plan', 'responsible_person', 'review_date']
|
|
}),
|
|
('Status', {
|
|
'fields': ['status', 'incident_report']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_by', 'created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
|
|
def risk_level_display(self, obj):
|
|
"""Display risk level with color coding"""
|
|
colors = {
|
|
'low': 'green',
|
|
'medium': 'yellow',
|
|
'high': 'orange',
|
|
'critical': 'red'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
colors.get(obj.risk_level, 'black'),
|
|
obj.get_risk_level_display()
|
|
)
|
|
risk_level_display.short_description = 'Risk Level'
|
|
|
|
def residual_risk_level_display(self, obj):
|
|
"""Display residual risk level with color coding"""
|
|
colors = {
|
|
'low': 'green',
|
|
'medium': 'yellow',
|
|
'high': 'orange',
|
|
'critical': 'red'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
colors.get(obj.residual_risk_level, 'black'),
|
|
obj.get_residual_risk_level_display()
|
|
)
|
|
residual_risk_level_display.short_description = 'Residual Risk Level'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'responsible_person', 'incident_report', 'created_by'
|
|
)
|
|
|
|
|
|
class AuditFindingInline(admin.TabularInline):
|
|
"""Inline for audit findings"""
|
|
model = AuditFinding
|
|
extra = 0
|
|
fields = ['finding_number', 'title', 'finding_type', 'severity', 'status', 'responsible_person']
|
|
readonly_fields = ['finding_number']
|
|
|
|
|
|
@admin.register(AuditPlan)
|
|
class AuditPlanAdmin(admin.ModelAdmin):
|
|
"""Admin for audit plans"""
|
|
list_display = [
|
|
'title', 'audit_type', 'department', 'auditor',
|
|
'planned_start_date', 'status', 'findings_count_display'
|
|
]
|
|
list_filter = [
|
|
'audit_type', 'status', 'priority', 'regulatory_requirement',
|
|
'planned_start_date', 'department'
|
|
]
|
|
search_fields = ['title', 'description', 'scope', 'criteria', 'accreditation_body']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
date_hierarchy = 'planned_start_date'
|
|
filter_horizontal = ['audit_team']
|
|
|
|
fieldsets = [
|
|
('Audit Information', {
|
|
'fields': ['title', 'description', 'audit_type']
|
|
}),
|
|
('Scope and Criteria', {
|
|
'fields': ['scope', 'criteria', 'department']
|
|
}),
|
|
('Team', {
|
|
'fields': ['auditor', 'audit_team']
|
|
}),
|
|
('Schedule', {
|
|
'fields': ['planned_start_date', 'planned_end_date', 'actual_start_date', 'actual_end_date']
|
|
}),
|
|
('Status', {
|
|
'fields': ['status', 'priority']
|
|
}),
|
|
('Regulatory', {
|
|
'fields': ['regulatory_requirement', 'accreditation_body'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_by', 'created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
inlines = [AuditFindingInline]
|
|
|
|
def findings_count_display(self, obj):
|
|
"""Display findings count with link"""
|
|
count = obj.findings_count
|
|
if count > 0:
|
|
url = reverse('admin:quality_auditfinding_changelist') + f'?audit_plan__id__exact={obj.id}'
|
|
return format_html('<a href="{}">{} findings</a>', url, count)
|
|
return '0 findings'
|
|
findings_count_display.short_description = 'Findings'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'department', 'auditor', 'created_by'
|
|
).prefetch_related('audit_team', 'findings')
|
|
|
|
|
|
@admin.register(AuditFinding)
|
|
class AuditFindingAdmin(admin.ModelAdmin):
|
|
"""Admin for audit findings"""
|
|
list_display = [
|
|
'finding_number', 'title', 'audit_plan', 'finding_type',
|
|
'severity_display', 'status', 'responsible_person', 'target_completion_date'
|
|
]
|
|
list_filter = [
|
|
'finding_type', 'severity', 'category', 'status',
|
|
'corrective_action_required', 'target_completion_date', 'audit_plan__audit_type'
|
|
]
|
|
search_fields = ['finding_number', 'title', 'description', 'evidence', 'criteria_reference']
|
|
readonly_fields = ['finding_number', 'created_at', 'updated_at']
|
|
date_hierarchy = 'target_completion_date'
|
|
|
|
fieldsets = [
|
|
('Finding Information', {
|
|
'fields': ['audit_plan', 'finding_number', 'title', 'description']
|
|
}),
|
|
('Classification', {
|
|
'fields': ['finding_type', 'severity', 'category', 'criteria_reference']
|
|
}),
|
|
('Evidence and Analysis', {
|
|
'fields': ['evidence', 'root_cause']
|
|
}),
|
|
('Corrective Action', {
|
|
'fields': ['corrective_action_required', 'corrective_action_plan', 'responsible_person']
|
|
}),
|
|
('Timeline', {
|
|
'fields': ['target_completion_date', 'actual_completion_date']
|
|
}),
|
|
('Status and Verification', {
|
|
'fields': ['status', 'verification_method', 'verified_by', 'verified_date']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_by', 'created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
|
|
def severity_display(self, obj):
|
|
"""Display severity with color coding"""
|
|
colors = {
|
|
'minor': 'green',
|
|
'major': 'orange',
|
|
'critical': 'red'
|
|
}
|
|
return format_html(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
colors.get(obj.severity, 'black'),
|
|
obj.get_severity_display()
|
|
)
|
|
severity_display.short_description = 'Severity'
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'audit_plan', 'responsible_person', 'verified_by', 'created_by'
|
|
)
|
|
|
|
|
|
@admin.register(ImprovementProject)
|
|
class ImprovementProjectAdmin(admin.ModelAdmin):
|
|
"""Admin for improvement projects"""
|
|
list_display = [
|
|
'project_number', 'title', 'project_type', 'status',
|
|
'project_manager', 'planned_start_date', 'phase'
|
|
]
|
|
list_filter = [
|
|
'project_type', 'methodology', 'status', 'phase',
|
|
'planned_start_date', 'department'
|
|
]
|
|
search_fields = ['project_number', 'title', 'description', 'problem_statement', 'goal_statement']
|
|
readonly_fields = ['project_number', 'duration_planned', 'duration_actual', 'created_at', 'updated_at']
|
|
date_hierarchy = 'planned_start_date'
|
|
filter_horizontal = ['project_team']
|
|
|
|
fieldsets = [
|
|
('Project Information', {
|
|
'fields': ['project_number', 'title', 'description', 'project_type', 'methodology']
|
|
}),
|
|
('Problem and Goals', {
|
|
'fields': ['problem_statement', 'goal_statement', 'scope']
|
|
}),
|
|
('Metrics', {
|
|
'fields': ['success_metrics', 'baseline_data', 'target_metrics']
|
|
}),
|
|
('Team', {
|
|
'fields': ['project_manager', 'project_team', 'sponsor', 'department']
|
|
}),
|
|
('Timeline', {
|
|
'fields': ['planned_start_date', 'planned_end_date', 'actual_start_date', 'actual_end_date', 'duration_planned', 'duration_actual']
|
|
}),
|
|
('Status', {
|
|
'fields': ['status', 'phase']
|
|
}),
|
|
('Budget and ROI', {
|
|
'fields': ['budget', 'actual_cost', 'roi_expected', 'roi_actual'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Outcomes', {
|
|
'fields': ['lessons_learned', 'sustainability_plan'],
|
|
'classes': ['collapse']
|
|
}),
|
|
('Audit', {
|
|
'fields': ['created_by', 'created_at', 'updated_at'],
|
|
'classes': ['collapse']
|
|
})
|
|
]
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'project_manager', 'sponsor', 'department', 'created_by'
|
|
).prefetch_related('project_team')
|
|
|
|
|
|
# Register models with custom admin classes
|
|
admin.site.site_header = "Hospital Management System - Quality Administration"
|
|
admin.site.site_title = "Quality Admin"
|
|
admin.site.index_title = "Quality Management Administration"
|
|
|