""" HR app admin configuration. """ 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 django.db.models import Count, Avg, Sum from decimal import Decimal from datetime import date, timedelta from .models import * # ============================================================================ # INLINE ADMIN CLASSES # ============================================================================ class ScheduleInline(admin.TabularInline): """ Inline admin for employee schedules. """ model = Schedule extra = 0 fields = [ 'name', 'schedule_type', 'effective_date', 'end_date', 'is_active' ] readonly_fields = ['schedule_id'] class TimeEntryInline(admin.TabularInline): """ Inline admin for employee time entries. """ model = TimeEntry extra = 0 fields = [ 'work_date', 'clock_in_time', 'clock_out_time', 'total_hours', 'entry_type', 'status' ] readonly_fields = ['entry_id', 'total_hours'] class PerformanceReviewInline(admin.TabularInline): """ Inline admin for employee performance reviews. """ model = PerformanceReview extra = 0 fields = [ 'review_date', 'review_type', 'overall_rating', 'status' ] readonly_fields = ['review_id'] class TrainingRecordInline(admin.TabularInline): """ Inline admin for employee training records. """ model = TrainingRecord extra = 0 fields = [ 'program', 'session', 'completion_date', 'status', 'passed' ] readonly_fields = ['record_id'] class ProgramModuleInline(admin.TabularInline): """ Inline admin for training program modules. """ model = ProgramModule extra = 0 fields = ['order', 'title', 'hours'] ordering = ['order'] class ProgramPrerequisiteInline(admin.TabularInline): """ Inline admin for training program prerequisites. """ model = ProgramPrerequisite extra = 0 fk_name = 'program' fields = ['required_program'] class TrainingSessionInline(admin.TabularInline): """ Inline admin for training sessions. """ model = TrainingSession extra = 0 fields = [ 'title', 'start_at', 'end_at', 'instructor', 'delivery_method', 'capacity' ] readonly_fields = ['session_id'] class TrainingAttendanceInline(admin.TabularInline): """ Inline admin for training attendance. """ model = TrainingAttendance extra = 0 fields = [ 'checked_in_at', 'checked_out_at', 'status', 'notes' ] class TrainingAssessmentInline(admin.TabularInline): """ Inline admin for training assessments. """ model = TrainingAssessment extra = 0 fields = [ 'name', 'max_score', 'score', 'passed', 'taken_at' ] @admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): """ Admin interface for employees. """ list_display = [ 'employee_id', 'get_full_name', 'job_title', 'department', 'employment_type', 'employment_status', 'hire_date', 'years_of_service_display', 'license_status_display' ] list_filter = [ 'tenant', 'department', 'employment_type', 'employment_status', 'gender', 'hire_date' ] search_fields = [ 'employee_id', 'first_name', 'last_name', 'email', 'job_title' ] readonly_fields = [ 'age', 'years_of_service', 'is_license_expired', 'created_at', 'updated_at' ] fieldsets = [ ('Employee Information', { 'fields': [ 'tenant', 'user' ] }), ('Personal Information', { 'fields': [ 'first_name', 'father_name', 'grandfather_name', 'last_name', ] }), ('Contact Information', { 'fields': [ 'email', 'phone', 'mobile_phone', 'profile_picture' ] }), ('Address Information', { 'fields': [ 'address_line_1', 'address_line_2', 'city', 'postal_code', 'country' ], 'classes': ['collapse'] }), ('Personal Details', { 'fields': [ 'date_of_birth', 'gender', 'marital_status' ] }), ('Employment Information', { 'fields': [ 'department', 'job_title', 'employment_type', 'employment_status' ] }), ('Employment Dates', { 'fields': [ 'hire_date', 'termination_date' ] }), ('Supervisor Information', { 'fields': [ 'supervisor' ] }), ('Work Schedule Information', { 'fields': [ 'standard_hours_per_week', 'fte_percentage' ] }), ('Compensation Information', { 'fields': [ 'hourly_rate', 'annual_salary' ] }), ('Professional Information', { 'fields': [ 'license_number', 'license_expiry_date', ] }), ('Emergency Contact', { 'fields': [ 'emergency_contact_name', 'emergency_contact_relationship', 'emergency_contact_phone' ], 'classes': ['collapse'] }), ('Calculated Fields', { 'fields': [ 'age', 'years_of_service', 'is_license_expired', ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [ScheduleInline, TimeEntryInline, PerformanceReviewInline, TrainingRecordInline] date_hierarchy = 'hire_date' def years_of_service_display(self, obj): """Display years of service.""" years = obj.years_of_service if years: return f"{years:.1f} years" return "0 years" years_of_service_display.short_description = 'Years of Service' def license_status_display(self, obj): """Display license status with color coding.""" if not obj.license_number: return format_html('No License') if obj.is_license_expired: return format_html('⚠️ Expired') if obj.license_expiry_date: days_to_expiry = (obj.license_expiry_date - date.today()).days if days_to_expiry <= 30: return format_html('⚠️ Expires Soon') return format_html('✓ Valid') license_status_display.short_description = 'License Status' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('department', 'supervisor') @admin.register(Department) class DepartmentAdmin(admin.ModelAdmin): """ Admin interface for departments. """ list_display = [ 'code', 'name', 'department_type', 'department_head', 'employee_count_display', 'total_fte_display', 'is_active' ] list_filter = [ 'tenant', 'department_type', 'is_active' ] search_fields = [ 'code', 'name', 'description' ] readonly_fields = [ 'department_id', 'employee_count', 'total_fte', 'created_at', 'updated_at' ] fieldsets = [ ('Department Information', { 'fields': [ 'department_id', 'tenant', 'code', 'name', 'description' ] }), ('Department Type', { 'fields': [ 'department_type' ] }), ('Hierarchy', { 'fields': [ 'parent_department' ] }), ('Management', { 'fields': [ 'department_head' ] }), ('Budget Information', { 'fields': [ 'annual_budget', 'cost_center' ] }), ('Location Information', { 'fields': [ 'location' ] }), ('Department Status', { 'fields': [ 'is_active' ] }), ('Summary Information', { 'fields': [ 'employee_count', 'total_fte' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] def employee_count_display(self, obj): """Display employee count.""" return obj.employee_count employee_count_display.short_description = 'Employees' def total_fte_display(self, obj): """Display total FTE.""" return f"{obj.total_fte:.1f}" total_fte_display.short_description = 'Total FTE' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('department_head') class ScheduleAssignmentInline(admin.TabularInline): """ Inline admin for schedule assignments. """ model = ScheduleAssignment extra = 0 fields = [ 'assignment_date', 'start_time', 'end_time', 'shift_type', 'status' ] readonly_fields = ['assignment_id', 'total_hours'] @admin.register(Schedule) class ScheduleAdmin(admin.ModelAdmin): """ Admin interface for schedules. """ list_display = [ 'employee_name', 'name', 'schedule_type', 'effective_date', 'end_date', 'is_current_display', 'is_active' ] list_filter = [ 'schedule_type', 'effective_date', 'is_active' ] search_fields = [ 'employee__first_name', 'employee__last_name', 'employee__employee_id', 'name' ] readonly_fields = [ 'schedule_id', 'tenant', 'is_current', 'created_at', 'updated_at' ] fieldsets = [ ('Schedule Information', { 'fields': [ 'schedule_id', 'employee', 'name', 'description' ] }), ('Schedule Type', { 'fields': [ 'schedule_type' ] }), ('Schedule Dates', { 'fields': [ 'effective_date', 'end_date' ] }), ('Schedule Pattern', { 'fields': [ 'schedule_pattern' ] }), ('Schedule Status', { 'fields': [ 'is_active' ] }), ('Approval Information', { 'fields': [ 'approved_by', 'approval_date' ] }), ('Status Information', { 'fields': [ 'is_current' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [ScheduleAssignmentInline] date_hierarchy = 'effective_date' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def is_current_display(self, obj): """Display current status with icon.""" if obj.is_current: return format_html('✓ Current') return format_html('Not Current') is_current_display.short_description = 'Current' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(employee__tenant=request.user.tenant) return qs.select_related('employee', 'approved_by') @admin.register(ScheduleAssignment) class ScheduleAssignmentAdmin(admin.ModelAdmin): """ Admin interface for schedule assignments. """ list_display = [ 'employee_name', 'assignment_date', 'start_time', 'end_time', 'shift_type', 'total_hours_display', 'status' ] list_filter = [ 'shift_type', 'status', 'assignment_date' ] search_fields = [ 'schedule__employee__first_name', 'schedule__employee__last_name', 'schedule__employee__employee_id' ] readonly_fields = [ 'assignment_id', 'tenant', 'employee', 'total_hours', 'created_at', 'updated_at' ] fieldsets = [ ('Assignment Information', { 'fields': [ 'assignment_id', 'schedule' ] }), ('Date and Time', { 'fields': [ 'assignment_date', 'start_time', 'end_time' ] }), ('Shift Information', { 'fields': [ 'shift_type' ] }), ('Location Information', { 'fields': [ 'department', 'location' ] }), ('Assignment Status', { 'fields': [ 'status' ] }), ('Break Information', { 'fields': [ 'break_minutes', 'lunch_minutes' ] }), ('Calculated Information', { 'fields': [ 'total_hours' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant', 'employee' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] date_hierarchy = 'assignment_date' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def total_hours_display(self, obj): """Display total hours.""" return f"{obj.total_hours:.2f}" total_hours_display.short_description = 'Hours' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(schedule__employee__tenant=request.user.tenant) return qs.select_related('schedule__employee', 'department') @admin.register(TimeEntry) class TimeEntryAdmin(admin.ModelAdmin): """ Admin interface for time entries. """ list_display = [ 'employee_name', 'work_date', 'clock_in_time', 'clock_out_time', 'total_hours', 'entry_type', 'status', 'approval_status_display' ] list_filter = [ 'entry_type', 'status', 'work_date' ] search_fields = [ 'employee__first_name', 'employee__last_name', 'employee__employee_id' ] readonly_fields = [ 'entry_id', 'tenant', 'regular_hours', 'overtime_hours', 'total_hours', 'is_approved', 'created_at', 'updated_at' ] fieldsets = [ ('Time Entry Information', { 'fields': [ 'entry_id', 'employee' ] }), ('Date and Time', { 'fields': [ 'work_date', 'clock_in_time', 'clock_out_time' ] }), ('Break Times', { 'fields': [ 'break_start_time', 'break_end_time', 'lunch_start_time', 'lunch_end_time' ] }), ('Hours Information', { 'fields': [ 'regular_hours', 'overtime_hours', 'total_hours' ] }), ('Entry Type', { 'fields': [ 'entry_type' ] }), ('Department and Location', { 'fields': [ 'department', 'location' ] }), ('Approval Information', { 'fields': [ 'approved_by', 'approval_date' ] }), ('Entry Status', { 'fields': [ 'status' ] }), ('Status Information', { 'fields': [ 'is_approved' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] date_hierarchy = 'work_date' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def approval_status_display(self, obj): """Display approval status with icon.""" if obj.is_approved: return format_html('✓ Approved') elif obj.status == 'REJECTED': return format_html('✗ Rejected') elif obj.status == 'SUBMITTED': return format_html('⏳ Pending') return format_html('Draft') approval_status_display.short_description = 'Approval' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(employee__tenant=request.user.tenant) return qs.select_related('employee', 'department', 'approved_by') @admin.register(PerformanceReview) class PerformanceReviewAdmin(admin.ModelAdmin): """ Admin interface for performance reviews. """ list_display = [ 'employee_name', 'review_date', 'review_type', 'overall_rating', 'status', 'is_overdue_display' ] list_filter = [ 'review_type', 'status', 'overall_rating', 'review_date' ] search_fields = [ 'employee__first_name', 'employee__last_name', ] readonly_fields = [ 'review_id', 'is_overdue', 'created_at', 'updated_at' ] fieldsets = [ ('Review Information', { 'fields': [ 'review_id', 'employee' ] }), ('Review Period', { 'fields': [ 'review_period_start', 'review_period_end', 'review_date' ] }), ('Review Type', { 'fields': [ 'review_type' ] }), ('Reviewer Information', { 'fields': [ 'reviewer' ] }), ('Overall Rating', { 'fields': [ 'overall_rating' ] }), ('Competency Ratings', { 'fields': [ 'competency_ratings' ] }), ('Goals and Objectives', { 'fields': [ 'goals_achieved', 'goals_not_achieved', 'future_goals' ] }), ('Strengths and Areas for Improvement', { 'fields': [ 'strengths', 'areas_for_improvement' ] }), ('Development Plan', { 'fields': [ 'development_plan', 'training_recommendations' ] }), ('Employee Comments', { 'fields': [ 'employee_comments', 'employee_signature_date' ] }), ('Review Status', { 'fields': [ 'status' ] }), ('Status Information', { 'fields': [ 'is_overdue' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] date_hierarchy = 'review_date' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def is_overdue_display(self, obj): """Display overdue status with icon.""" if obj.is_overdue: return format_html('⚠️ Overdue') return format_html('✓ On Time') is_overdue_display.short_description = 'Status' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(employee__tenant=request.user.tenant) return qs.select_related('employee', 'reviewer') # ============================================================================ # TRAINING ADMIN CLASSES # ============================================================================ @admin.register(TrainingPrograms) class TrainingProgramsAdmin(admin.ModelAdmin): """ Admin interface for training programs. """ list_display = [ 'name', 'program_type', 'program_provider', 'instructor', 'duration_hours', 'cost', 'is_certified', 'validity_display' ] list_filter = [ 'tenant', 'program_type', 'is_certified', 'program_provider' ] search_fields = [ 'name', 'description', 'program_provider' ] readonly_fields = [ 'program_id', 'created_at', 'updated_at' ] fieldsets = [ ('Program Information', { 'fields': [ 'program_id', 'tenant', 'name', 'description' ] }), ('Program Details', { 'fields': [ 'program_type', 'program_provider', 'instructor' ] }), ('Schedule Information', { 'fields': [ 'start_date', 'end_date', 'duration_hours' ] }), ('Cost Information', { 'fields': [ 'cost' ] }), ('Certification Information', { 'fields': [ 'is_certified', 'validity_days', 'notify_before_days' ] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [ProgramModuleInline, ProgramPrerequisiteInline, TrainingSessionInline] date_hierarchy = 'start_date' def validity_display(self, obj): """Display validity information.""" if obj.is_certified and obj.validity_days: return f"{obj.validity_days} days" return "No expiry" validity_display.short_description = 'Validity' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('instructor', 'created_by') @admin.register(ProgramModule) class ProgramModuleAdmin(admin.ModelAdmin): """ Admin interface for program modules. """ list_display = [ 'program', 'order', 'title', 'hours' ] list_filter = [ 'program__tenant', 'program' ] search_fields = [ 'title', 'program__name' ] ordering = ['program', 'order'] def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(program__tenant=request.user.tenant) return qs.select_related('program') @admin.register(ProgramPrerequisite) class ProgramPrerequisiteAdmin(admin.ModelAdmin): """ Admin interface for program prerequisites. """ list_display = [ 'program', 'required_program' ] list_filter = [ 'program__tenant', 'program' ] search_fields = [ 'program__name', 'required_program__name' ] def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(program__tenant=request.user.tenant) return qs.select_related('program', 'required_program') @admin.register(TrainingSession) class TrainingSessionAdmin(admin.ModelAdmin): """ Admin interface for training sessions. """ list_display = [ 'program', 'title_display', 'instructor', 'start_at', 'end_at', 'delivery_method', 'capacity', 'enrollment_count' ] list_filter = [ 'program__tenant', 'delivery_method', 'start_at' ] search_fields = [ 'title', 'program__name', 'instructor__first_name', 'instructor__last_name' ] readonly_fields = [ 'session_id', 'enrollment_count', 'created_at' ] fieldsets = [ ('Session Information', { 'fields': [ 'session_id', 'program', 'title' ] }), ('Instructor Information', { 'fields': [ 'instructor' ] }), ('Schedule Information', { 'fields': [ 'start_at', 'end_at' ] }), ('Delivery Information', { 'fields': [ 'delivery_method', 'location' ] }), ('Capacity Information', { 'fields': [ 'capacity', 'enrollment_count' ] }), ('Cost Override', { 'fields': [ 'cost_override', 'hours_override' ] }), ('Metadata', { 'fields': [ 'created_at', 'created_by' ], 'classes': ['collapse'] }) ] date_hierarchy = 'start_at' def title_display(self, obj): """Display session title or program name.""" return obj.title or obj.program.name title_display.short_description = 'Title' def enrollment_count(self, obj): """Display enrollment count.""" return obj.enrollments.count() enrollment_count.short_description = 'Enrollments' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(program__tenant=request.user.tenant) return qs.select_related('program', 'instructor', 'created_by') @admin.register(TrainingRecord) class TrainingRecordAdmin(admin.ModelAdmin): """ Admin interface for training records (enrollments). """ list_display = [ 'employee_name', 'program', 'session', 'enrolled_at', 'completion_date', 'status', 'passed', 'expiry_status_display' ] list_filter = [ 'employee__tenant', 'status', 'passed', 'program__program_type', 'enrolled_at', 'completion_date' ] search_fields = [ 'employee__first_name', 'employee__last_name', 'employee__employee_id', 'program__name' ] readonly_fields = [ 'record_id', 'enrolled_at', 'hours', 'effective_cost', 'eligible_for_certificate', 'completion_percentage', 'created_at', 'updated_at' ] fieldsets = [ ('Enrollment Information', { 'fields': [ 'record_id', 'employee', 'program', 'session' ] }), ('Enrollment Dates', { 'fields': [ 'enrolled_at', 'started_at', 'completion_date', 'expiry_date' ] }), ('Status Information', { 'fields': [ 'status', 'passed' ] }), ('Performance Information', { 'fields': [ 'credits_earned', 'score' ] }), ('Cost Information', { 'fields': [ 'cost_paid', 'effective_cost' ] }), ('Calculated Information', { 'fields': [ 'hours', 'eligible_for_certificate', 'completion_percentage' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [TrainingAttendanceInline, TrainingAssessmentInline] date_hierarchy = 'enrolled_at' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def expiry_status_display(self, obj): """Display expiry status with color coding.""" if not obj.expiry_date: return format_html('No Expiry') days_to_expiry = (obj.expiry_date - date.today()).days if days_to_expiry < 0: return format_html('⚠️ Expired') elif days_to_expiry <= 30: return format_html('⚠️ Expires Soon') else: return format_html('✓ Valid') expiry_status_display.short_description = 'Expiry Status' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(employee__tenant=request.user.tenant) return qs.select_related('employee', 'program', 'session', 'created_by') @admin.register(TrainingAttendance) class TrainingAttendanceAdmin(admin.ModelAdmin): """ Admin interface for training attendance. """ list_display = [ 'employee_name', 'program_name', 'checked_in_at', 'checked_out_at', 'status', 'duration_display' ] list_filter = [ 'enrollment__employee__tenant', 'status', 'checked_in_at' ] search_fields = [ 'enrollment__employee__first_name', 'enrollment__employee__last_name', 'enrollment__program__name' ] readonly_fields = [ 'duration_display' ] fieldsets = [ ('Attendance Information', { 'fields': [ 'enrollment' ] }), ('Check-in/out Times', { 'fields': [ 'checked_in_at', 'checked_out_at' ] }), ('Status Information', { 'fields': [ 'status' ] }), ('Duration Information', { 'fields': [ 'duration_display' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }) ] date_hierarchy = 'checked_in_at' def employee_name(self, obj): """Display employee name.""" return obj.enrollment.employee.get_full_name() employee_name.short_description = 'Employee' def program_name(self, obj): """Display program name.""" return obj.enrollment.program.name program_name.short_description = 'Program' def duration_display(self, obj): """Display attendance duration.""" if obj.checked_in_at and obj.checked_out_at: duration = obj.checked_out_at - obj.checked_in_at hours = duration.total_seconds() / 3600 return f"{hours:.2f} hours" return "Incomplete" duration_display.short_description = 'Duration' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(enrollment__employee__tenant=request.user.tenant) return qs.select_related('enrollment__employee', 'enrollment__program') @admin.register(TrainingAssessment) class TrainingAssessmentAdmin(admin.ModelAdmin): """ Admin interface for training assessments. """ list_display = [ 'employee_name', 'program_name', 'name', 'score', 'max_score', 'percentage_display', 'passed', 'taken_at' ] list_filter = [ 'enrollment__employee__tenant', 'passed', 'taken_at' ] search_fields = [ 'enrollment__employee__first_name', 'enrollment__employee__last_name', 'enrollment__program__name', 'name' ] readonly_fields = [ 'percentage_display' ] fieldsets = [ ('Assessment Information', { 'fields': [ 'enrollment', 'name' ] }), ('Score Information', { 'fields': [ 'max_score', 'score', 'percentage_display', 'passed' ] }), ('Date Information', { 'fields': [ 'taken_at' ] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }) ] date_hierarchy = 'taken_at' def employee_name(self, obj): """Display employee name.""" return obj.enrollment.employee.get_full_name() employee_name.short_description = 'Employee' def program_name(self, obj): """Display program name.""" return obj.enrollment.program.name program_name.short_description = 'Program' def percentage_display(self, obj): """Display score percentage.""" if obj.score is not None and obj.max_score > 0: percentage = (obj.score / obj.max_score) * 100 color = 'green' if percentage >= 70 else 'orange' if percentage >= 50 else 'red' return format_html( '{:.1f}%', color, percentage ) return "N/A" percentage_display.short_description = 'Percentage' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(enrollment__employee__tenant=request.user.tenant) return qs.select_related('enrollment__employee', 'enrollment__program') @admin.register(TrainingCertificates) class TrainingCertificatesAdmin(admin.ModelAdmin): """ Admin interface for training certificates. """ list_display = [ 'employee_name', 'certificate_name', 'program', 'issued_date', 'expiry_date', 'expiry_status_display', 'certificate_number' ] list_filter = [ 'employee__tenant', 'program', 'issued_date', 'expiry_date' ] search_fields = [ 'employee__first_name', 'employee__last_name', 'certificate_name', 'certificate_number', 'program__name' ] readonly_fields = [ 'certificate_id', 'is_expired', 'days_to_expiry', 'created_at', 'updated_at' ] fieldsets = [ ('Certificate Information', { 'fields': [ 'certificate_id', 'employee', 'program', 'enrollment' ] }), ('Certificate Details', { 'fields': [ 'certificate_name', 'certificate_number', 'certification_body' ] }), ('Date Information', { 'fields': [ 'issued_date', 'expiry_date' ] }), ('File Information', { 'fields': [ 'file' ] }), ('Signature Information', { 'fields': [ 'created_by', 'signed_by' ] }), ('Status Information', { 'fields': [ 'is_expired', 'days_to_expiry' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] date_hierarchy = 'issued_date' def employee_name(self, obj): """Display employee name.""" return obj.employee.get_full_name() employee_name.short_description = 'Employee' def expiry_status_display(self, obj): """Display expiry status with color coding.""" if not obj.expiry_date: return format_html('No Expiry') if obj.is_expired: return format_html('⚠️ Expired') if obj.days_to_expiry is not None and obj.days_to_expiry <= 30: return format_html('⚠️ Expires Soon') return format_html('✓ Valid') expiry_status_display.short_description = 'Expiry Status' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(employee__tenant=request.user.tenant) return qs.select_related('employee', 'program', 'enrollment', 'created_by', 'signed_by') # ============================================================================ # CUSTOM ADMIN ACTIONS # ============================================================================ def mark_training_completed(modeladmin, request, queryset): """Mark selected training records as completed.""" updated = queryset.update(status='COMPLETED', completion_date=date.today()) modeladmin.message_user(request, f'{updated} training records marked as completed.') mark_training_completed.short_description = "Mark selected training as completed" def mark_training_passed(modeladmin, request, queryset): """Mark selected training records as passed.""" updated = queryset.update(passed=True) modeladmin.message_user(request, f'{updated} training records marked as passed.') mark_training_passed.short_description = "Mark selected training as passed" def generate_certificates(modeladmin, request, queryset): """Generate certificates for completed training.""" count = 0 for record in queryset.filter(status='COMPLETED', passed=True): if record.program.is_certified and not hasattr(record, 'certificate'): TrainingCertificates.objects.create( program=record.program, employee=record.employee, enrollment=record, certificate_name=f"{record.program.name} Certificate", expiry_date=TrainingCertificates.compute_expiry(record.program, date.today()), created_by=request.user ) count += 1 modeladmin.message_user(request, f'{count} certificates generated.') generate_certificates.short_description = "Generate certificates for completed training" # Add actions to TrainingRecord admin TrainingRecordAdmin.actions = [mark_training_completed, mark_training_passed, generate_certificates] # ============================================================================ # ADMIN SITE CUSTOMIZATION # ============================================================================ # Customize admin site admin.site.site_header = "Hospital Management System - HR" admin.site.site_title = "HR Admin" admin.site.index_title = "Human Resources Administration"