""" HR app models for hospital management system. Provides staff management, scheduling, performance management, and training tracking. """ import uuid from django.db import models from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator from django.utils import timezone from django.conf import settings from datetime import timedelta, datetime, date, time from decimal import Decimal import json class Employee(models.Model): """ Employee model for hospital staff management. """ # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='employees', help_text='Organization tenant' ) # User relationship (optional - for employees who have system access) user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='employee_profile', help_text='Associated user account' ) # Employee Information employee_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique employee identifier' ) employee_number = models.CharField( max_length=20, help_text='Employee number' ) # Personal Information first_name = models.CharField( max_length=50, help_text='First name' ) last_name = models.CharField( max_length=50, help_text='Last name' ) middle_name = models.CharField( max_length=50, blank=True, null=True, help_text='Middle name' ) preferred_name = models.CharField( max_length=50, blank=True, null=True, help_text='Preferred name' ) # Contact Information email = models.EmailField( blank=True, null=True, help_text='Email address' ) phone = models.CharField( max_length=20, blank=True, null=True, help_text='Phone number' ) mobile_phone = models.CharField( max_length=20, blank=True, null=True, help_text='Mobile phone number' ) # Address Information address_line_1 = models.CharField( max_length=100, blank=True, null=True, help_text='Address line 1' ) address_line_2 = models.CharField( max_length=100, blank=True, null=True, help_text='Address line 2' ) city = models.CharField( max_length=50, blank=True, null=True, help_text='City' ) state = models.CharField( max_length=50, blank=True, null=True, help_text='State/Province' ) postal_code = models.CharField( max_length=20, blank=True, null=True, help_text='Postal/ZIP code' ) country = models.CharField( max_length=50, blank=True, null=True, help_text='Country' ) # Personal Details date_of_birth = models.DateField( blank=True, null=True, help_text='Date of birth' ) gender = models.CharField( max_length=10, choices=[ ('MALE', 'Male'), ('FEMALE', 'Female'), ('OTHER', 'Other'), ('UNKNOWN', 'Unknown'), ], blank=True, null=True, help_text='Gender' ) marital_status = models.CharField( max_length=20, choices=[ ('SINGLE', 'Single'), ('MARRIED', 'Married'), ('DIVORCED', 'Divorced'), ('WIDOWED', 'Widowed'), ('SEPARATED', 'Separated'), ('OTHER', 'Other'), ], blank=True, null=True, help_text='Marital status' ) # Employment Information department = models.ForeignKey( 'Department', on_delete=models.SET_NULL, null=True, blank=True, related_name='employees', help_text='Department' ) job_title = models.CharField( max_length=100, help_text='Job title' ) employment_type = models.CharField( max_length=20, choices=[ ('FULL_TIME', 'Full Time'), ('PART_TIME', 'Part Time'), ('CONTRACT', 'Contract'), ('TEMPORARY', 'Temporary'), ('INTERN', 'Intern'), ('VOLUNTEER', 'Volunteer'), ('PER_DIEM', 'Per Diem'), ('CONSULTANT', 'Consultant'), ], help_text='Employment type' ) employment_status = models.CharField( max_length=20, choices=[ ('ACTIVE', 'Active'), ('INACTIVE', 'Inactive'), ('TERMINATED', 'Terminated'), ('SUSPENDED', 'Suspended'), ('LEAVE', 'On Leave'), ('RETIRED', 'Retired'), ], default='ACTIVE', help_text='Employment status' ) # Employment Dates hire_date = models.DateField( help_text='Hire date' ) termination_date = models.DateField( blank=True, null=True, help_text='Termination date' ) # Supervisor Information supervisor = models.ForeignKey( 'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='direct_reports', help_text='Direct supervisor' ) # Work Schedule Information standard_hours_per_week = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('40.00'), help_text='Standard hours per week' ) fte_percentage = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('100.00'), validators=[MinValueValidator(0), MaxValueValidator(100)], help_text='FTE percentage' ) # Compensation Information hourly_rate = models.DecimalField( max_digits=10, decimal_places=2, blank=True, null=True, help_text='Hourly rate' ) annual_salary = models.DecimalField( max_digits=12, decimal_places=2, blank=True, null=True, help_text='Annual salary' ) # Professional Information license_number = models.CharField( max_length=50, blank=True, null=True, help_text='Professional license number' ) license_expiry_date = models.DateField( blank=True, null=True, help_text='License expiry date' ) certifications = models.JSONField( default=list, help_text='Professional certifications' ) # Emergency Contact emergency_contact_name = models.CharField( max_length=100, blank=True, null=True, help_text='Emergency contact name' ) emergency_contact_relationship = models.CharField( max_length=50, blank=True, null=True, help_text='Emergency contact relationship' ) emergency_contact_phone = models.CharField( max_length=20, blank=True, null=True, help_text='Emergency contact phone' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Employee notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_employees', help_text='User who created the employee record' ) class Meta: db_table = 'hr_employee' verbose_name = 'Employee' verbose_name_plural = 'Employees' ordering = ['last_name', 'first_name'] indexes = [ models.Index(fields=['tenant', 'employment_status']), models.Index(fields=['employee_number']), models.Index(fields=['last_name', 'first_name']), models.Index(fields=['department']), models.Index(fields=['hire_date']), ] unique_together = ['tenant', 'employee_number'] def __str__(self): return f"{self.employee_number} - {self.get_full_name()}" def get_full_name(self): """ Get employee's full name. """ if self.middle_name: return f"{self.first_name} {self.middle_name} {self.last_name}" return f"{self.first_name} {self.last_name}" def get_display_name(self): """ Get employee's display name (preferred name if available). """ if self.preferred_name: return f"{self.preferred_name} {self.last_name}" return self.get_full_name() @property def age(self): """ Calculate employee's age. """ if self.date_of_birth: today = date.today() return today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)) return None @property def years_of_service(self): """ Calculate years of service. """ if self.hire_date: end_date = self.termination_date or date.today() return (end_date - self.hire_date).days / 365.25 return 0 @property def is_license_expired(self): """ Check if professional license is expired. """ if self.license_expiry_date: return self.license_expiry_date < date.today() return False @property def full_address(self): """ Get full address. """ parts = [ self.address_line_1, self.address_line_2, f"{self.city}, {self.state} {self.postal_code}", self.country ] return "\n".join([part for part in parts if part]) class Department(models.Model): """ Department model for organizational structure. """ # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='hr_departments', help_text='Organization tenant' ) # Department Information department_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique department identifier' ) department_code = models.CharField( max_length=20, help_text='Department code' ) name = models.CharField( max_length=100, help_text='Department name' ) description = models.TextField( blank=True, null=True, help_text='Department description' ) # Department Type department_type = models.CharField( max_length=20, choices=[ ('CLINICAL', 'Clinical'), ('ADMINISTRATIVE', 'Administrative'), ('SUPPORT', 'Support'), ('ANCILLARY', 'Ancillary'), ('EXECUTIVE', 'Executive'), ], help_text='Department type' ) # Hierarchy parent_department = models.ForeignKey( 'self', on_delete=models.CASCADE, null=True, blank=True, related_name='child_departments', help_text='Parent department' ) # Management department_head = models.ForeignKey( Employee, on_delete=models.SET_NULL, null=True, blank=True, related_name='headed_departments', help_text='Department head' ) # Budget Information annual_budget = models.DecimalField( max_digits=12, decimal_places=2, blank=True, null=True, help_text='Annual budget' ) cost_center = models.CharField( max_length=20, blank=True, null=True, help_text='Cost center code' ) # Location Information location = models.CharField( max_length=100, blank=True, null=True, help_text='Department location' ) # Department Status is_active = models.BooleanField( default=True, help_text='Department is active' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Department notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_hr_departments', help_text='User who created the department' ) class Meta: db_table = 'hr_department' verbose_name = 'Department' verbose_name_plural = 'Departments' ordering = ['name'] indexes = [ models.Index(fields=['tenant', 'department_type']), models.Index(fields=['department_code']), models.Index(fields=['name']), models.Index(fields=['is_active']), ] unique_together = ['tenant', 'department_code'] def __str__(self): return f"{self.department_code} - {self.name}" @property def employee_count(self): """ Get number of employees in department. """ return self.employees.filter(employment_status='ACTIVE').count() @property def total_fte(self): """ Calculate total FTE for department. """ return sum(emp.fte_percentage for emp in self.employees.filter(employment_status='ACTIVE')) / 100 class Schedule(models.Model): """ Schedule model for employee work schedules. """ # Employee relationship employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='schedules', help_text='Employee' ) # Schedule Information schedule_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique schedule identifier' ) name = models.CharField( max_length=100, help_text='Schedule name' ) description = models.TextField( blank=True, null=True, help_text='Schedule description' ) # Schedule Type schedule_type = models.CharField( max_length=20, choices=[ ('REGULAR', 'Regular Schedule'), ('ROTATING', 'Rotating Schedule'), ('FLEXIBLE', 'Flexible Schedule'), ('ON_CALL', 'On-Call Schedule'), ('TEMPORARY', 'Temporary Schedule'), ], help_text='Schedule type' ) # Schedule Dates effective_date = models.DateField( help_text='Effective date' ) end_date = models.DateField( blank=True, null=True, help_text='End date' ) # Schedule Pattern (JSON) schedule_pattern = models.JSONField( default=dict, help_text='Schedule pattern configuration' ) # Schedule Status is_active = models.BooleanField( default=True, help_text='Schedule is active' ) # Approval Information approved_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_schedules', help_text='User who approved the schedule' ) approval_date = models.DateTimeField( blank=True, null=True, help_text='Approval date and time' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Schedule notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_schedules', help_text='User who created the schedule' ) class Meta: db_table = 'hr_schedule' verbose_name = 'Schedule' verbose_name_plural = 'Schedules' ordering = ['-effective_date'] indexes = [ models.Index(fields=['employee', 'effective_date']), models.Index(fields=['schedule_type']), models.Index(fields=['is_active']), ] def __str__(self): return f"{self.employee.get_full_name()} - {self.name}" @property def tenant(self): """ Get tenant from employee. """ return self.employee.tenant @property def is_current(self): """ Check if schedule is currently active. """ today = date.today() if self.end_date: return self.effective_date <= today <= self.end_date return self.effective_date <= today class ScheduleAssignment(models.Model): """ Schedule assignment model for specific shift assignments. """ # Schedule relationship schedule = models.ForeignKey( Schedule, on_delete=models.CASCADE, related_name='assignments', help_text='Schedule' ) # Assignment Information assignment_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique assignment identifier' ) # Date and Time assignment_date = models.DateField( help_text='Assignment date' ) start_time = models.TimeField( help_text='Start time' ) end_time = models.TimeField( help_text='End time' ) # Shift Information shift_type = models.CharField( max_length=20, choices=[ ('DAY', 'Day Shift'), ('EVENING', 'Evening Shift'), ('NIGHT', 'Night Shift'), ('WEEKEND', 'Weekend Shift'), ('HOLIDAY', 'Holiday Shift'), ('ON_CALL', 'On-Call'), ('OVERTIME', 'Overtime'), ], help_text='Shift type' ) # Location Information department = models.ForeignKey( Department, on_delete=models.SET_NULL, null=True, blank=True, related_name='schedule_assignments', help_text='Department' ) location = models.CharField( max_length=100, blank=True, null=True, help_text='Specific location' ) # Assignment Status status = models.CharField( max_length=20, choices=[ ('SCHEDULED', 'Scheduled'), ('CONFIRMED', 'Confirmed'), ('COMPLETED', 'Completed'), ('CANCELLED', 'Cancelled'), ('NO_SHOW', 'No Show'), ], default='SCHEDULED', help_text='Assignment status' ) # Break Information break_minutes = models.PositiveIntegerField( default=0, help_text='Break time in minutes' ) lunch_minutes = models.PositiveIntegerField( default=0, help_text='Lunch time in minutes' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Assignment notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'hr_schedule_assignment' verbose_name = 'Schedule Assignment' verbose_name_plural = 'Schedule Assignments' ordering = ['assignment_date', 'start_time'] indexes = [ models.Index(fields=['schedule', 'assignment_date']), models.Index(fields=['assignment_date']), models.Index(fields=['status']), ] def __str__(self): return f"{self.schedule.employee.get_full_name()} - {self.assignment_date} {self.start_time}-{self.end_time}" @property def tenant(self): """ Get tenant from schedule. """ return self.schedule.tenant @property def employee(self): """ Get employee from schedule. """ return self.schedule.employee @property def total_hours(self): """ Calculate total hours for assignment. """ start_datetime = datetime.combine(self.assignment_date, self.start_time) end_datetime = datetime.combine(self.assignment_date, self.end_time) # Handle overnight shifts if self.end_time < self.start_time: end_datetime += timedelta(days=1) total_minutes = (end_datetime - start_datetime).total_seconds() / 60 total_minutes -= (self.break_minutes + self.lunch_minutes) return total_minutes / 60 class TimeEntry(models.Model): """ Time entry model for tracking actual work hours. """ # Employee relationship employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='time_entries', help_text='Employee' ) # Time Entry Information entry_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique time entry identifier' ) # Date and Time work_date = models.DateField( help_text='Work date' ) clock_in_time = models.DateTimeField( blank=True, null=True, help_text='Clock in time' ) clock_out_time = models.DateTimeField( blank=True, null=True, help_text='Clock out time' ) # Break Times break_start_time = models.DateTimeField( blank=True, null=True, help_text='Break start time' ) break_end_time = models.DateTimeField( blank=True, null=True, help_text='Break end time' ) lunch_start_time = models.DateTimeField( blank=True, null=True, help_text='Lunch start time' ) lunch_end_time = models.DateTimeField( blank=True, null=True, help_text='Lunch end time' ) # Hours Information regular_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), help_text='Regular hours worked' ) overtime_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), help_text='Overtime hours worked' ) total_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), help_text='Total hours worked' ) # Entry Type entry_type = models.CharField( max_length=20, choices=[ ('REGULAR', 'Regular Time'), ('OVERTIME', 'Overtime'), ('HOLIDAY', 'Holiday'), ('VACATION', 'Vacation'), ('SICK', 'Sick Leave'), ('PERSONAL', 'Personal Time'), ('BEREAVEMENT', 'Bereavement'), ('JURY_DUTY', 'Jury Duty'), ('TRAINING', 'Training'), ], default='REGULAR', help_text='Entry type' ) # Department and Location department = models.ForeignKey( Department, on_delete=models.SET_NULL, null=True, blank=True, related_name='time_entries', help_text='Department' ) location = models.CharField( max_length=100, blank=True, null=True, help_text='Work location' ) # Approval Information approved_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_time_entries', help_text='User who approved the time entry' ) approval_date = models.DateTimeField( blank=True, null=True, help_text='Approval date and time' ) # Entry Status status = models.CharField( max_length=20, choices=[ ('DRAFT', 'Draft'), ('SUBMITTED', 'Submitted'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected'), ('PAID', 'Paid'), ], default='DRAFT', help_text='Entry status' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Time entry notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'hr_time_entry' verbose_name = 'Time Entry' verbose_name_plural = 'Time Entries' ordering = ['-work_date'] indexes = [ models.Index(fields=['employee', 'work_date']), models.Index(fields=['work_date']), models.Index(fields=['status']), models.Index(fields=['entry_type']), ] def __str__(self): return f"{self.employee.get_full_name()} - {self.work_date}" def save(self, *args, **kwargs): """ Calculate hours automatically. """ if self.clock_in_time and self.clock_out_time: # Calculate total time worked total_minutes = (self.clock_out_time - self.clock_in_time).total_seconds() / 60 # Subtract break time if self.break_start_time and self.break_end_time: break_minutes = (self.break_end_time - self.break_start_time).total_seconds() / 60 total_minutes -= break_minutes # Subtract lunch time if self.lunch_start_time and self.lunch_end_time: lunch_minutes = (self.lunch_end_time - self.lunch_start_time).total_seconds() / 60 total_minutes -= lunch_minutes self.total_hours = Decimal(str(total_minutes / 60)) # Calculate regular vs overtime hours (assuming 8 hours is regular) if self.total_hours <= 8: self.regular_hours = self.total_hours self.overtime_hours = Decimal('0.00') else: self.regular_hours = Decimal('8.00') self.overtime_hours = self.total_hours - Decimal('8.00') super().save(*args, **kwargs) @property def tenant(self): """ Get tenant from employee. """ return self.employee.tenant @property def is_approved(self): """ Check if time entry is approved. """ return self.status == 'APPROVED' class PerformanceReview(models.Model): """ Performance review model for employee evaluations. """ # Employee relationship employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='performance_reviews', help_text='Employee being reviewed' ) # Review Information review_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique review identifier' ) # Review Period review_period_start = models.DateField( help_text='Review period start date' ) review_period_end = models.DateField( help_text='Review period end date' ) review_date = models.DateField( help_text='Review date' ) # Review Type review_type = models.CharField( max_length=20, choices=[ ('ANNUAL', 'Annual Review'), ('PROBATIONARY', 'Probationary Review'), ('MID_YEAR', 'Mid-Year Review'), ('PROJECT', 'Project Review'), ('DISCIPLINARY', 'Disciplinary Review'), ('PROMOTION', 'Promotion Review'), ], help_text='Review type' ) # Reviewer Information reviewer = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='conducted_reviews', help_text='Reviewer' ) # Overall Rating overall_rating = models.DecimalField( max_digits=3, decimal_places=1, validators=[MinValueValidator(1), MaxValueValidator(5)], help_text='Overall rating (1-5)' ) # Competency Ratings (JSON) competency_ratings = models.JSONField( default=dict, help_text='Individual competency ratings' ) # Goals and Objectives goals_achieved = models.TextField( blank=True, null=True, help_text='Goals achieved during review period' ) goals_not_achieved = models.TextField( blank=True, null=True, help_text='Goals not achieved during review period' ) future_goals = models.TextField( blank=True, null=True, help_text='Goals for next review period' ) # Strengths and Areas for Improvement strengths = models.TextField( blank=True, null=True, help_text='Employee strengths' ) areas_for_improvement = models.TextField( blank=True, null=True, help_text='Areas for improvement' ) # Development Plan development_plan = models.TextField( blank=True, null=True, help_text='Professional development plan' ) training_recommendations = models.TextField( blank=True, null=True, help_text='Training recommendations' ) # Employee Comments employee_comments = models.TextField( blank=True, null=True, help_text='Employee comments' ) employee_signature_date = models.DateField( blank=True, null=True, help_text='Employee signature date' ) # Review Status status = models.CharField( max_length=20, choices=[ ('DRAFT', 'Draft'), ('IN_PROGRESS', 'In Progress'), ('COMPLETED', 'Completed'), ('ACKNOWLEDGED', 'Acknowledged by Employee'), ('DISPUTED', 'Disputed'), ], default='DRAFT', help_text='Review status' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Additional notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'hr_performance_review' verbose_name = 'Performance Review' verbose_name_plural = 'Performance Reviews' ordering = ['-review_date'] indexes = [ models.Index(fields=['employee', 'review_date']), models.Index(fields=['review_type']), models.Index(fields=['status']), models.Index(fields=['overall_rating']), ] def __str__(self): return f"{self.employee.get_full_name()} - {self.review_type} ({self.review_date})" @property def tenant(self): """ Get tenant from employee. """ return self.employee.tenant @property def is_overdue(self): """ Check if review is overdue. """ if self.status in ['DRAFT', 'IN_PROGRESS']: return self.review_date < date.today() return False class TrainingRecord(models.Model): """ Training record model for employee training and certifications. """ # Employee relationship employee = models.ForeignKey( Employee, on_delete=models.CASCADE, related_name='training_records', help_text='Employee' ) # Training Information record_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique training record identifier' ) training_name = models.CharField( max_length=200, help_text='Training name' ) training_description = models.TextField( blank=True, null=True, help_text='Training description' ) # Training Type training_type = models.CharField( max_length=20, choices=[ ('ORIENTATION', 'Orientation'), ('MANDATORY', 'Mandatory Training'), ('CONTINUING_ED', 'Continuing Education'), ('CERTIFICATION', 'Certification'), ('SKILLS', 'Skills Training'), ('SAFETY', 'Safety Training'), ('COMPLIANCE', 'Compliance Training'), ('LEADERSHIP', 'Leadership Development'), ('TECHNICAL', 'Technical Training'), ('OTHER', 'Other'), ], help_text='Training type' ) # Training Provider training_provider = models.CharField( max_length=200, blank=True, null=True, help_text='Training provider' ) instructor = models.CharField( max_length=100, blank=True, null=True, help_text='Instructor name' ) # Training Dates training_date = models.DateField( help_text='Training date' ) completion_date = models.DateField( blank=True, null=True, help_text='Completion date' ) expiry_date = models.DateField( blank=True, null=True, help_text='Certification expiry date' ) # Training Details duration_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), help_text='Training duration in hours' ) credits_earned = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), help_text='Credits earned' ) # Training Status status = models.CharField( max_length=20, choices=[ ('SCHEDULED', 'Scheduled'), ('IN_PROGRESS', 'In Progress'), ('COMPLETED', 'Completed'), ('CANCELLED', 'Cancelled'), ('NO_SHOW', 'No Show'), ('FAILED', 'Failed'), ], default='SCHEDULED', help_text='Training status' ) # Results score = models.DecimalField( max_digits=5, decimal_places=2, blank=True, null=True, help_text='Training score/grade' ) passed = models.BooleanField( default=False, help_text='Training passed' ) # Certification Information certificate_number = models.CharField( max_length=50, blank=True, null=True, help_text='Certificate number' ) certification_body = models.CharField( max_length=200, blank=True, null=True, help_text='Certification body' ) # Cost Information training_cost = models.DecimalField( max_digits=10, decimal_places=2, default=Decimal('0.00'), help_text='Training cost' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Training notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_training_records', help_text='User who created the training record' ) class Meta: db_table = 'hr_training_record' verbose_name = 'Training Record' verbose_name_plural = 'Training Records' ordering = ['-training_date'] indexes = [ models.Index(fields=['employee', 'training_date']), models.Index(fields=['training_type']), models.Index(fields=['status']), models.Index(fields=['expiry_date']), ] def __str__(self): return f"{self.employee.get_full_name()} - {self.training_name}" @property def tenant(self): """ Get tenant from employee. """ return self.employee.tenant @property def is_expired(self): """ Check if certification is expired. """ if self.expiry_date: return self.expiry_date < date.today() return False @property def days_to_expiry(self): """ Calculate days to expiry. """ if self.expiry_date: return (self.expiry_date - date.today()).days return None @property def is_due_for_renewal(self): """ Check if certification is due for renewal (within 30 days). """ if self.expiry_date: return (self.expiry_date - date.today()).days <= 30 return False