926 lines
42 KiB
Python
926 lines
42 KiB
Python
import os
|
|
import django
|
|
|
|
# Set up Django environment
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
|
|
django.setup()
|
|
|
|
import random
|
|
from datetime import datetime, timedelta, date, time
|
|
from django.utils import timezone as django_timezone
|
|
from hr.models import Employee, Department, Schedule, ScheduleAssignment, TimeEntry, PerformanceReview, TrainingRecord
|
|
from accounts.models import User
|
|
from core.models import Tenant
|
|
from decimal import Decimal
|
|
import uuid
|
|
|
|
# Saudi-specific data constants
|
|
SAUDI_FIRST_NAMES_MALE = [
|
|
'Mohammed', 'Ahmed', 'Abdullah', 'Khalid', 'Omar', 'Ali', 'Hassan', 'Ibrahim',
|
|
'Yousef', 'Fahd', 'Faisal', 'Saud', 'Nasser', 'Abdulaziz', 'Abdulrahman',
|
|
'Majid', 'Saeed', 'Waleed', 'Tariq', 'Mansour', 'Sultan', 'Bandar',
|
|
'Turki', 'Nawaf', 'Rayan', 'Ziad', 'Adel', 'Salman', 'Fares', 'Amjad'
|
|
]
|
|
|
|
SAUDI_FIRST_NAMES_FEMALE = [
|
|
'Fatima', 'Aisha', 'Khadija', 'Maryam', 'Zahra', 'Noor', 'Layla', 'Sara',
|
|
'Hala', 'Noura', 'Reem', 'Lina', 'Dina', 'Rana', 'Jana', 'Maya',
|
|
'Amal', 'Hanan', 'Widad', 'Nada', 'Rawan', 'Ghada', 'Samar', 'Hind',
|
|
'Munira', 'Rahma', 'Najla', 'Dalal', 'Abeer', 'Manal'
|
|
]
|
|
|
|
SAUDI_LAST_NAMES = [
|
|
'Al-Rashid', 'Al-Otaibi', 'Al-Dosari', 'Al-Harbi', 'Al-Zahrani', 'Al-Ghamdi',
|
|
'Al-Qahtani', 'Al-Maliki', 'Al-Shammari', 'Al-Mutairi', 'Al-Subai', 'Al-Shamsi',
|
|
'Al-Faisal', 'Al-Saud', 'Al-Thani', 'Al-Fahd', 'Al-Sultan', 'Al-Nasser',
|
|
'Al-Mansour', 'Al-Khalil', 'Al-Ibrahim', 'Al-Hassan', 'Al-Ali', 'Al-Omar',
|
|
'Al-Ahmed', 'Al-Mohammed', 'Bin Laden', 'Bin Rashid', 'Bin Abdullah', 'Bin Sultan'
|
|
]
|
|
|
|
SAUDI_CITIES = [
|
|
'Riyadh', 'Jeddah', 'Mecca', 'Medina', 'Dammam', 'Khobar', 'Dhahran',
|
|
'Taif', 'Tabuk', 'Buraidah', 'Khamis Mushait', 'Hail', 'Hofuf', 'Najran',
|
|
'Jazan', 'Yanbu', 'Abha', 'Arar', 'Sakaka', 'Qatif'
|
|
]
|
|
|
|
SAUDI_DEPARTMENTS = [
|
|
('EMERGENCY', 'Emergency Department', 'Emergency medical services'),
|
|
('ICU', 'Intensive Care Unit', 'Critical care services'),
|
|
('CARDIOLOGY', 'Cardiology Department', 'Heart and cardiovascular care'),
|
|
('SURGERY', 'General Surgery', 'Surgical services'),
|
|
('ORTHOPEDICS', 'Orthopedics Department', 'Bone and joint care'),
|
|
('PEDIATRICS', 'Pediatrics Department', 'Children healthcare'),
|
|
('OBSTETRICS', 'Obstetrics & Gynecology', 'Women and maternity care'),
|
|
('RADIOLOGY', 'Radiology Department', 'Medical imaging services'),
|
|
('LABORATORY', 'Laboratory Services', 'Diagnostic testing'),
|
|
('PHARMACY', 'Pharmacy Department', 'Medication services'),
|
|
('NURSING', 'Nursing Services', 'Patient care services'),
|
|
('ADMINISTRATION', 'Administration', 'Hospital administration'),
|
|
('FINANCE', 'Finance Department', 'Financial management'),
|
|
('HR', 'Human Resources', 'Staff management'),
|
|
('IT', 'Information Technology', 'Technology services'),
|
|
('MAINTENANCE', 'Maintenance Services', 'Facility maintenance'),
|
|
('SECURITY', 'Security Department', 'Hospital security'),
|
|
('HOUSEKEEPING', 'Housekeeping Services', 'Cleaning services'),
|
|
('FOOD_SERVICE', 'Food Services', 'Dietary services'),
|
|
('SOCIAL_WORK', 'Social Work', 'Patient social services')
|
|
]
|
|
|
|
SAUDI_JOB_TITLES = {
|
|
'CLINICAL': [
|
|
'Chief Medical Officer', 'Medical Director', 'Department Head', 'Consultant Physician',
|
|
'Senior Physician', 'Physician', 'Resident Physician', 'Intern',
|
|
'Chief Nurse', 'Nurse Manager', 'Senior Nurse', 'Staff Nurse',
|
|
'Nurse Practitioner', 'Clinical Nurse Specialist', 'Charge Nurse',
|
|
'Pharmacist', 'Clinical Pharmacist', 'Pharmacy Technician',
|
|
'Radiologist', 'Radiology Technician', 'Medical Technologist',
|
|
'Laboratory Technician', 'Physical Therapist', 'Respiratory Therapist'
|
|
],
|
|
'ADMINISTRATIVE': [
|
|
'Chief Executive Officer', 'Chief Operating Officer', 'Administrator',
|
|
'Assistant Administrator', 'Department Manager', 'Supervisor',
|
|
'Administrative Assistant', 'Secretary', 'Clerk', 'Coordinator'
|
|
],
|
|
'SUPPORT': [
|
|
'Security Officer', 'Security Guard', 'Maintenance Technician',
|
|
'Housekeeping Supervisor', 'Housekeeper', 'Food Service Manager',
|
|
'Cook', 'Kitchen Assistant', 'Transport Aide', 'Receptionist'
|
|
]
|
|
}
|
|
|
|
SAUDI_TRAINING_PROGRAMS = [
|
|
'Basic Life Support (BLS)', 'Advanced Cardiac Life Support (ACLS)',
|
|
'Pediatric Advanced Life Support (PALS)', 'Infection Control',
|
|
'Patient Safety', 'Fire Safety', 'Emergency Procedures',
|
|
'HIPAA Compliance', 'Cultural Sensitivity', 'Arabic Language',
|
|
'Islamic Healthcare Ethics', 'Medication Administration',
|
|
'Wound Care Management', 'Electronic Health Records',
|
|
'Quality Improvement', 'Customer Service Excellence'
|
|
]
|
|
|
|
|
|
def create_saudi_departments(tenants):
|
|
"""Create Saudi hospital departments"""
|
|
departments = []
|
|
|
|
for tenant in tenants:
|
|
# Check for existing departments to avoid duplicates
|
|
existing_dept_codes = set(
|
|
Department.objects.filter(tenant=tenant).values_list('department_code', flat=True)
|
|
)
|
|
|
|
for dept_code, dept_name, dept_desc in SAUDI_DEPARTMENTS:
|
|
# Skip if department already exists for this tenant
|
|
if dept_code in existing_dept_codes:
|
|
print(f"Department {dept_code} already exists for {tenant.name}, skipping...")
|
|
continue
|
|
|
|
# Determine department type
|
|
if dept_code in ['EMERGENCY', 'ICU', 'CARDIOLOGY', 'SURGERY', 'ORTHOPEDICS',
|
|
'PEDIATRICS', 'OBSTETRICS', 'RADIOLOGY', 'LABORATORY', 'PHARMACY', 'NURSING']:
|
|
dept_type = 'CLINICAL'
|
|
elif dept_code in ['ADMINISTRATION', 'FINANCE', 'HR', 'IT']:
|
|
dept_type = 'ADMINISTRATIVE'
|
|
elif dept_code in ['MAINTENANCE', 'SECURITY', 'HOUSEKEEPING', 'FOOD_SERVICE']:
|
|
dept_type = 'SUPPORT'
|
|
else:
|
|
dept_type = 'ANCILLARY'
|
|
|
|
try:
|
|
department = Department.objects.create(
|
|
tenant=tenant,
|
|
department_code=dept_code,
|
|
name=dept_name,
|
|
description=dept_desc,
|
|
department_type=dept_type,
|
|
annual_budget=Decimal(str(random.randint(500000, 5000000))),
|
|
cost_center=f"CC-{dept_code}",
|
|
location=f"{random.choice(['Building A', 'Building B', 'Main Building'])}, Floor {random.randint(1, 5)}",
|
|
is_active=True,
|
|
notes=f"Primary {dept_name.lower()} for {tenant.name}",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(30, 365)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
|
|
)
|
|
departments.append(department)
|
|
|
|
except Exception as e:
|
|
print(f"Error creating department {dept_code} for {tenant.name}: {e}")
|
|
continue
|
|
|
|
created_count = len([d for d in departments if d.tenant == tenant])
|
|
print(f"Created {created_count} new departments for {tenant.name}")
|
|
|
|
return departments
|
|
|
|
|
|
def create_saudi_employees(tenants, departments, employees_per_tenant=150):
|
|
"""Create Saudi hospital employees"""
|
|
employees = []
|
|
|
|
for tenant in tenants:
|
|
tenant_departments = [dept for dept in departments if dept.tenant == tenant]
|
|
if not tenant_departments:
|
|
print(f"No departments found for {tenant.name}, skipping employee creation...")
|
|
continue
|
|
|
|
# Check existing employee numbers to avoid duplicates
|
|
existing_emp_numbers = set(
|
|
Employee.objects.filter(tenant=tenant).values_list('employee_number', flat=True)
|
|
)
|
|
|
|
created_count = 0
|
|
for i in range(employees_per_tenant):
|
|
# Generate unique employee number
|
|
emp_number = f"EMP-{i + 1:04d}"
|
|
counter = 1
|
|
original_emp_number = emp_number
|
|
|
|
while emp_number in existing_emp_numbers:
|
|
emp_number = f"{original_emp_number}-{counter}"
|
|
counter += 1
|
|
|
|
# Generate employee details
|
|
gender = random.choice(['MALE', 'FEMALE'])
|
|
first_name = random.choice(SAUDI_FIRST_NAMES_MALE if gender == 'MALE' else SAUDI_FIRST_NAMES_FEMALE)
|
|
last_name = random.choice(SAUDI_LAST_NAMES)
|
|
middle_name = random.choice(SAUDI_FIRST_NAMES_MALE + SAUDI_FIRST_NAMES_FEMALE) if random.choice(
|
|
[True, False]) else None
|
|
|
|
# Select department and job title
|
|
department = random.choice(tenant_departments)
|
|
if department.department_type == 'CLINICAL':
|
|
job_titles = SAUDI_JOB_TITLES['CLINICAL']
|
|
elif department.department_type == 'ADMINISTRATIVE':
|
|
job_titles = SAUDI_JOB_TITLES['ADMINISTRATIVE']
|
|
else:
|
|
job_titles = SAUDI_JOB_TITLES['SUPPORT']
|
|
|
|
job_title = random.choice(job_titles)
|
|
|
|
# Determine employment type and status
|
|
employment_type = random.choices(
|
|
['FULL_TIME', 'PART_TIME', 'CONTRACT', 'CONTRACT'],
|
|
weights=[70, 15, 10, 5]
|
|
)[0]
|
|
|
|
employment_status = random.choices(
|
|
['ACTIVE', 'INACTIVE', 'LEAVE'],
|
|
weights=[85, 10, 5]
|
|
)[0]
|
|
|
|
# Generate dates
|
|
hire_date = django_timezone.now().date() - timedelta(days=random.randint(30, 2000))
|
|
date_of_birth = hire_date - timedelta(days=random.randint(8000, 15000)) # 22-41 years old at hire
|
|
|
|
# Generate salary information
|
|
if 'Chief' in job_title or 'Director' in job_title:
|
|
annual_salary = Decimal(str(random.randint(300000, 600000)))
|
|
hourly_rate = None
|
|
elif 'Manager' in job_title or 'Head' in job_title:
|
|
annual_salary = Decimal(str(random.randint(180000, 350000)))
|
|
hourly_rate = None
|
|
elif 'Senior' in job_title or 'Consultant' in job_title:
|
|
annual_salary = Decimal(str(random.randint(120000, 250000)))
|
|
hourly_rate = None
|
|
else:
|
|
annual_salary = None
|
|
hourly_rate = Decimal(str(random.randint(50, 150)))
|
|
|
|
# FTE and hours
|
|
if employment_type == 'FULL_TIME':
|
|
fte_percentage = Decimal('100.00')
|
|
standard_hours = Decimal('40.00')
|
|
elif employment_type == 'PART_TIME':
|
|
fte_percentage = Decimal(str(random.choice([50, 60, 75])))
|
|
standard_hours = Decimal(str(fte_percentage / 100 * 40))
|
|
else:
|
|
fte_percentage = Decimal('100.00')
|
|
standard_hours = Decimal('40.00')
|
|
|
|
# Contact information
|
|
city = random.choice(SAUDI_CITIES)
|
|
phone = f"+966-{random.randint(11, 13)}-{random.randint(100, 999)}-{random.randint(1000, 9999)}"
|
|
mobile = f"+966-5{random.randint(0, 9)}-{random.randint(100, 999)}-{random.randint(1000, 9999)}"
|
|
|
|
try:
|
|
# Create employee
|
|
employee = Employee.objects.create(
|
|
tenant=tenant,
|
|
employee_number=emp_number,
|
|
first_name=first_name,
|
|
last_name=last_name,
|
|
middle_name=middle_name,
|
|
preferred_name=first_name if random.choice([True, False]) else None,
|
|
email=f"{first_name.lower()}.{last_name.lower().replace('-', '').replace(' ', '')}@{tenant.name.lower().replace(' ', '')}.sa",
|
|
phone=phone,
|
|
mobile_phone=mobile,
|
|
address_line_1=f"{random.randint(1, 999)} {random.choice(['King Fahd Road', 'Prince Sultan Street', 'Al-Malik Road', 'Olaya Street'])}",
|
|
address_line_2=f"Apt {random.randint(1, 50)}" if random.choice([True, False]) else None,
|
|
city=city,
|
|
state=random.choice(['Riyadh Province', 'Makkah Province', 'Eastern Province', 'Asir Province']),
|
|
postal_code=f"{random.randint(10000, 99999)}",
|
|
country='Saudi Arabia',
|
|
date_of_birth=date_of_birth,
|
|
gender=gender,
|
|
marital_status=random.choice(['SINGLE', 'MARRIED', 'DIVORCED', 'WIDOWED']),
|
|
department=department,
|
|
job_title=job_title,
|
|
employment_type=employment_type,
|
|
employment_status=employment_status,
|
|
hire_date=hire_date,
|
|
termination_date=None if employment_status != 'TERMINATED' else hire_date + timedelta(
|
|
days=random.randint(30, 1000)),
|
|
standard_hours_per_week=standard_hours,
|
|
fte_percentage=fte_percentage,
|
|
hourly_rate=hourly_rate,
|
|
annual_salary=annual_salary,
|
|
license_number=f"LIC-{random.randint(100000, 999999)}" if 'Physician' in job_title or 'Nurse' in job_title else None,
|
|
license_expiry_date=django_timezone.now().date() + timedelta(
|
|
days=random.randint(30, 730)) if 'Physician' in job_title or 'Nurse' in job_title else None,
|
|
certifications=[
|
|
{'name': 'BLS Certification', 'number': f"BLS-{random.randint(10000, 99999)}",
|
|
'expiry': (django_timezone.now().date() + timedelta(days=365)).isoformat()},
|
|
{'name': 'ACLS Certification', 'number': f"ACLS-{random.randint(10000, 99999)}",
|
|
'expiry': (django_timezone.now().date() + timedelta(days=730)).isoformat()}
|
|
] if 'Physician' in job_title or 'Nurse' in job_title else [],
|
|
emergency_contact_name=f"{random.choice(SAUDI_FIRST_NAMES_MALE + SAUDI_FIRST_NAMES_FEMALE)} {random.choice(SAUDI_LAST_NAMES)}",
|
|
emergency_contact_relationship=random.choice(['Spouse', 'Parent', 'Sibling', 'Child']),
|
|
emergency_contact_phone=f"+966-5{random.randint(0, 9)}-{random.randint(100, 999)}-{random.randint(1000, 9999)}",
|
|
notes=f"Employee hired for {department.name} department",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(1, 100)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
|
|
)
|
|
employees.append(employee)
|
|
existing_emp_numbers.add(emp_number)
|
|
created_count += 1
|
|
|
|
except Exception as e:
|
|
print(f"Error creating employee {emp_number} for {tenant.name}: {e}")
|
|
continue
|
|
|
|
print(f"Created {created_count} employees for {tenant.name}")
|
|
|
|
return employees
|
|
|
|
|
|
def assign_department_heads(departments, employees):
|
|
"""Assign department heads from senior employees"""
|
|
for department in departments:
|
|
# Skip if department already has a head
|
|
if department.department_head:
|
|
continue
|
|
|
|
# Find senior employees in this department
|
|
dept_employees = [emp for emp in employees if
|
|
emp.department == department and emp.employment_status == 'ACTIVE']
|
|
senior_employees = [emp for emp in dept_employees if
|
|
any(title in emp.job_title for title in ['Chief', 'Director', 'Head', 'Manager'])]
|
|
|
|
if senior_employees:
|
|
department.department_head = random.choice(senior_employees)
|
|
department.save()
|
|
|
|
print("Assigned department heads")
|
|
|
|
|
|
def assign_supervisors(employees):
|
|
"""Assign supervisors to employees based on hierarchy"""
|
|
for employee in employees:
|
|
if employee.employment_status != 'ACTIVE' or employee.supervisor:
|
|
continue
|
|
|
|
# Don't assign supervisors to top-level positions
|
|
if any(title in employee.job_title for title in ['Chief', 'Director']):
|
|
continue
|
|
|
|
# Find potential supervisors in the same department
|
|
potential_supervisors = [
|
|
emp for emp in employees
|
|
if emp.department == employee.department
|
|
and emp.employment_status == 'ACTIVE'
|
|
and emp != employee
|
|
and any(title in emp.job_title for title in ['Chief', 'Director', 'Head', 'Manager', 'Senior'])
|
|
]
|
|
|
|
if potential_supervisors:
|
|
employee.supervisor = random.choice(potential_supervisors)
|
|
employee.save()
|
|
|
|
print("Assigned employee supervisors")
|
|
|
|
|
|
def create_saudi_schedules(employees, schedules_per_employee=2):
|
|
"""Create work schedules for Saudi employees"""
|
|
schedules = []
|
|
|
|
schedule_patterns = {
|
|
'DAY_SHIFT': {
|
|
'sunday': {'start': '07:00', 'end': '19:00'},
|
|
'monday': {'start': '07:00', 'end': '19:00'},
|
|
'tuesday': {'start': '07:00', 'end': '19:00'},
|
|
'wednesday': {'start': '07:00', 'end': '19:00'},
|
|
'thursday': {'start': '07:00', 'end': '19:00'},
|
|
'friday': {'start': '07:00', 'end': '19:00'},
|
|
'saturday': {'start': '07:00', 'end': '19:00'},
|
|
},
|
|
# 'EVENING_SHIFT': {
|
|
# 'monday': {'start': '15:00', 'end': '23:00'},
|
|
# 'tuesday': {'start': '15:00', 'end': '23:00'},
|
|
# 'wednesday': {'start': '15:00', 'end': '23:00'},
|
|
# 'thursday': {'start': '15:00', 'end': '23:00'},
|
|
# 'friday': {'start': '13:00', 'end': '21:00'},
|
|
# 'saturday': {'start': '15:00', 'end': '23:00'},
|
|
# 'sunday': 'off'
|
|
# },
|
|
'NIGHT_SHIFT': {
|
|
'sunday': {'start': '19:00', 'end': '07:00'},
|
|
'monday': {'start': '19:00', 'end': '07:00'},
|
|
'tuesday': {'start': '19:00', 'end': '07:00'},
|
|
'wednesday': {'start': '19:00', 'end': '07:00'},
|
|
'thursday': {'start': '19:00', 'end': '07:00'},
|
|
'friday': {'start': '19:00', 'end': '07:00'},
|
|
'saturday': {'start': '19:00', 'end': '07:00'},
|
|
|
|
},
|
|
'ADMIN_HOURS': {
|
|
'sunday': {'start': '08:00', 'end': '17:00'},
|
|
'monday': {'start': '08:00', 'end': '17:00'},
|
|
'tuesday': {'start': '08:00', 'end': '17:00'},
|
|
'wednesday': {'start': '08:00', 'end': '17:00'},
|
|
'thursday': {'start': '08:00', 'end': '16:00'},
|
|
'friday': 'off',
|
|
'saturday': 'off',
|
|
|
|
}
|
|
}
|
|
|
|
clinical_employees = [emp for emp in employees if
|
|
emp.department and emp.department.department_type == 'CLINICAL' and emp.employment_status == 'ACTIVE']
|
|
admin_employees = [emp for emp in employees if
|
|
emp.department and emp.department.department_type in ['ADMINISTRATIVE',
|
|
'SUPPORT'] and emp.employment_status == 'ACTIVE']
|
|
|
|
# Create schedules for clinical staff (rotating shifts)
|
|
for employee in clinical_employees:
|
|
for i in range(schedules_per_employee):
|
|
schedule_type = random.choice(['REGULAR', 'ROTATING'])
|
|
pattern_name = random.choice(['DAY_SHIFT', 'EVENING_SHIFT', 'NIGHT_SHIFT'])
|
|
|
|
effective_date = django_timezone.now().date() - timedelta(days=random.randint(0, 180))
|
|
end_date = effective_date + timedelta(days=random.randint(90, 365)) if random.choice(
|
|
[True, False]) else None
|
|
|
|
try:
|
|
schedule = Schedule.objects.create(
|
|
employee=employee,
|
|
name=f"{employee.get_full_name()} - {pattern_name.replace('_', ' ').title()}",
|
|
description=f"{pattern_name.replace('_', ' ').title()} schedule for {employee.job_title}",
|
|
schedule_type=schedule_type,
|
|
effective_date=effective_date,
|
|
end_date=end_date,
|
|
schedule_pattern=schedule_patterns[pattern_name],
|
|
is_active=i == 0, # Only first schedule is active
|
|
notes=f"Standard {pattern_name.replace('_', ' ').lower()} for clinical staff",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(1, 90)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
|
|
)
|
|
schedules.append(schedule)
|
|
except Exception as e:
|
|
print(f"Error creating schedule for {employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
# Create schedules for administrative staff (regular hours)
|
|
for employee in admin_employees:
|
|
schedule_type = 'REGULAR'
|
|
pattern_name = 'ADMIN_HOURS'
|
|
|
|
effective_date = django_timezone.now().date() - timedelta(days=random.randint(0, 180))
|
|
|
|
try:
|
|
schedule = Schedule.objects.create(
|
|
employee=employee,
|
|
name=f"{employee.get_full_name()} - Administrative Hours",
|
|
description="Standard administrative working hours",
|
|
schedule_type=schedule_type,
|
|
effective_date=effective_date,
|
|
end_date=None,
|
|
schedule_pattern=schedule_patterns[pattern_name],
|
|
is_active=True,
|
|
notes="Standard administrative schedule",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(1, 90)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
|
|
)
|
|
schedules.append(schedule)
|
|
except Exception as e:
|
|
print(f"Error creating schedule for {employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(schedules)} employee schedules")
|
|
return schedules
|
|
|
|
|
|
def create_schedule_assignments(schedules, days_back=30):
|
|
"""Create specific schedule assignments"""
|
|
assignments = []
|
|
|
|
shift_types = {
|
|
'DAY_SHIFT': 'DAY',
|
|
# 'EVENING_SHIFT': 'EVENING',
|
|
'NIGHT_SHIFT': 'NIGHT',
|
|
'ADMIN_HOURS': 'DAY'
|
|
}
|
|
|
|
for schedule in schedules:
|
|
if not schedule.is_active:
|
|
continue
|
|
|
|
# Create assignments for the last 30 days
|
|
start_date = django_timezone.now().date() - timedelta(days=days_back)
|
|
|
|
for day_offset in range(days_back):
|
|
assignment_date = start_date + timedelta(days=day_offset)
|
|
weekday = assignment_date.strftime('%A').lower()
|
|
|
|
# Skip if no work scheduled for this day
|
|
if weekday not in schedule.schedule_pattern or schedule.schedule_pattern[weekday] == 'off':
|
|
continue
|
|
|
|
day_schedule = schedule.schedule_pattern[weekday]
|
|
start_time = datetime.strptime(day_schedule['start'], '%H:%M').time()
|
|
end_time = datetime.strptime(day_schedule['end'], '%H:%M').time()
|
|
|
|
# Determine shift type based on schedule pattern
|
|
pattern_key = None
|
|
for pattern_name, pattern_data in [
|
|
('DAY_SHIFT', schedule.schedule_pattern),
|
|
# ('EVENING_SHIFT', schedule.schedule_pattern),
|
|
('NIGHT_SHIFT', schedule.schedule_pattern),
|
|
('ADMIN_HOURS', schedule.schedule_pattern)
|
|
]:
|
|
if pattern_data == schedule.schedule_pattern:
|
|
pattern_key = pattern_name
|
|
break
|
|
|
|
shift_type = shift_types.get(pattern_key, 'DAY')
|
|
|
|
# Random status (mostly completed for past dates)
|
|
if assignment_date < django_timezone.now().date():
|
|
status = random.choices(
|
|
['COMPLETED', 'NO_SHOW', 'CANCELLED'],
|
|
weights=[90, 5, 5]
|
|
)[0]
|
|
else:
|
|
status = random.choices(
|
|
['SCHEDULED', 'CONFIRMED'],
|
|
weights=[70, 30]
|
|
)[0]
|
|
|
|
try:
|
|
assignment = ScheduleAssignment.objects.create(
|
|
schedule=schedule,
|
|
assignment_date=assignment_date,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
shift_type=shift_type,
|
|
department=schedule.employee.department,
|
|
location=schedule.employee.department.location if schedule.employee.department else None,
|
|
status=status,
|
|
break_minutes=15 if shift_type in ['DAY', 'EVENING'] else 30,
|
|
lunch_minutes=30 if shift_type in ['DAY', 'EVENING'] else 0,
|
|
notes=f"{shift_type.title()} shift assignment",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(0, 7)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 3))
|
|
)
|
|
assignments.append(assignment)
|
|
except Exception as e:
|
|
print(f"Error creating assignment for {schedule.employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(assignments)} schedule assignments")
|
|
return assignments
|
|
|
|
|
|
def create_time_entries(employees, days_back=30):
|
|
"""Create time entries for employees"""
|
|
entries = []
|
|
|
|
active_employees = [emp for emp in employees if emp.employment_status == 'ACTIVE']
|
|
|
|
for employee in active_employees:
|
|
# Create time entries for working days
|
|
start_date = django_timezone.now().date() - timedelta(days=days_back)
|
|
|
|
for day_offset in range(days_back):
|
|
work_date = start_date + timedelta(days=day_offset)
|
|
|
|
# Skip weekends for administrative staff
|
|
if (employee.department and employee.department.department_type == 'ADMINISTRATIVE'
|
|
and work_date.weekday() in [4, 5]): # Friday, Saturday
|
|
continue
|
|
|
|
# Random chance of working (85% for clinical, 90% for admin)
|
|
work_probability = 0.85 if employee.department and employee.department.department_type == 'CLINICAL' else 0.90
|
|
if random.random() > work_probability:
|
|
continue
|
|
|
|
# Generate work times based on typical schedules
|
|
if employee.department and employee.department.department_type == 'CLINICAL':
|
|
# Clinical staff - various shifts
|
|
shift_options = [
|
|
(time(7, 0), time(15, 0)), # Day shift
|
|
(time(15, 0), time(23, 0)), # Evening shift
|
|
(time(23, 0), time(7, 0)), # Night shift (next day)
|
|
]
|
|
start_time, end_time = random.choice(shift_options)
|
|
else:
|
|
# Administrative staff - regular hours
|
|
start_time = time(8, 0)
|
|
end_time = time(17, 0) if work_date.weekday() != 4 else time(12, 0) # Half day Friday
|
|
|
|
# Add some variance to clock in/out times
|
|
clock_in_variance = random.randint(-15, 15) # ±15 minutes
|
|
clock_out_variance = random.randint(-15, 15)
|
|
|
|
clock_in = datetime.combine(work_date, start_time) + timedelta(minutes=clock_in_variance)
|
|
clock_out = datetime.combine(work_date, end_time) + timedelta(minutes=clock_out_variance)
|
|
|
|
# Handle overnight shifts
|
|
if end_time < start_time:
|
|
clock_out += timedelta(days=1)
|
|
|
|
# Break and lunch times
|
|
break_start = clock_in + timedelta(hours=2, minutes=random.randint(0, 60))
|
|
break_end = break_start + timedelta(minutes=15)
|
|
|
|
lunch_start = clock_in + timedelta(hours=4, minutes=random.randint(0, 60))
|
|
lunch_end = lunch_start + timedelta(minutes=30)
|
|
|
|
entry_type = random.choices(
|
|
['REGULAR', 'OVERTIME', 'HOLIDAY'],
|
|
weights=[85, 10, 5]
|
|
)[0]
|
|
|
|
status = random.choices(
|
|
['APPROVED', 'SUBMITTED', 'DRAFT'],
|
|
weights=[70, 20, 10]
|
|
)[0]
|
|
|
|
try:
|
|
entry = TimeEntry.objects.create(
|
|
employee=employee,
|
|
work_date=work_date,
|
|
clock_in_time=clock_in,
|
|
clock_out_time=clock_out,
|
|
break_start_time=break_start,
|
|
break_end_time=break_end,
|
|
lunch_start_time=lunch_start,
|
|
lunch_end_time=lunch_end,
|
|
entry_type=entry_type,
|
|
department=employee.department,
|
|
location=employee.department.location if employee.department else None,
|
|
status=status,
|
|
notes=f"{entry_type.title()} work shift",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(0, 7)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 3))
|
|
)
|
|
entries.append(entry)
|
|
except Exception as e:
|
|
print(f"Error creating time entry for {employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(entries)} time entries")
|
|
return entries
|
|
|
|
|
|
def create_performance_reviews(employees):
|
|
"""Create performance reviews for employees"""
|
|
reviews = []
|
|
|
|
# Only create reviews for employees with 6+ months of service
|
|
eligible_employees = [
|
|
emp for emp in employees
|
|
if emp.employment_status == 'ACTIVE'
|
|
and (django_timezone.now().date() - emp.hire_date).days >= 180
|
|
]
|
|
|
|
competency_areas = [
|
|
'Clinical Skills', 'Communication', 'Teamwork', 'Professionalism',
|
|
'Quality of Work', 'Productivity', 'Problem Solving', 'Initiative',
|
|
'Reliability', 'Cultural Competency', 'Patient Care', 'Safety Compliance'
|
|
]
|
|
|
|
for employee in eligible_employees:
|
|
# Create 1-2 reviews per eligible employee
|
|
num_reviews = random.randint(1, 2)
|
|
|
|
for i in range(num_reviews):
|
|
# Review period (last 6-12 months)
|
|
review_period_months = random.randint(6, 12)
|
|
review_date = django_timezone.now().date() - timedelta(days=random.randint(0, 90))
|
|
period_start = review_date - timedelta(days=review_period_months * 30)
|
|
period_end = review_date - timedelta(days=30)
|
|
|
|
review_type = random.choices(
|
|
['ANNUAL', 'PROBATIONARY', 'MID_YEAR'],
|
|
weights=[60, 20, 20]
|
|
)[0]
|
|
|
|
# Generate overall rating (1-5, skewed toward higher ratings)
|
|
overall_rating = random.choices(
|
|
[2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0],
|
|
weights=[5, 10, 20, 25, 25, 10, 5]
|
|
)[0]
|
|
|
|
# Generate competency ratings
|
|
competency_ratings = {}
|
|
for competency in random.sample(competency_areas, random.randint(6, 10)):
|
|
rating = random.choices(
|
|
[2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0],
|
|
weights=[5, 10, 20, 25, 25, 10, 5]
|
|
)[0]
|
|
competency_ratings[competency] = rating
|
|
|
|
status = random.choices(
|
|
['COMPLETED', 'ACKNOWLEDGED', 'IN_PROGRESS'],
|
|
weights=[60, 30, 10]
|
|
)[0]
|
|
|
|
try:
|
|
review = PerformanceReview.objects.create(
|
|
employee=employee,
|
|
review_period_start=period_start,
|
|
review_period_end=period_end,
|
|
review_date=review_date,
|
|
review_type=review_type,
|
|
overall_rating=Decimal(str(overall_rating)),
|
|
competency_ratings=competency_ratings,
|
|
goals_achieved=f"{employee.first_name} successfully completed assigned patient care duties and contributed to department objectives.",
|
|
goals_not_achieved="Minor areas for improvement in documentation timeliness." if overall_rating < 4.0 else None,
|
|
future_goals="Continue professional development and maintain high standards of patient care.",
|
|
strengths=f"Strong {random.choice(['clinical skills', 'communication abilities', 'teamwork', 'dedication to patient care'])}.",
|
|
areas_for_improvement="Opportunities for growth in leadership and mentoring junior staff." if overall_rating < 4.5 else None,
|
|
development_plan="Recommended for advanced training programs and leadership development opportunities.",
|
|
training_recommendations=f"Suggested training: {random.choice(SAUDI_TRAINING_PROGRAMS)}",
|
|
employee_comments="Appreciate the feedback and committed to continuous improvement." if status == 'ACKNOWLEDGED' else None,
|
|
employee_signature_date=review_date + timedelta(
|
|
days=random.randint(1, 14)) if status == 'ACKNOWLEDGED' else None,
|
|
status=status,
|
|
notes=f"{review_type.title()} performance review completed",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(1, 60)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
|
|
)
|
|
reviews.append(review)
|
|
except Exception as e:
|
|
print(f"Error creating performance review for {employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(reviews)} performance reviews")
|
|
return reviews
|
|
|
|
|
|
def create_training_records(employees):
|
|
"""Create training records for employees"""
|
|
records = []
|
|
|
|
active_employees = [emp for emp in employees if emp.employment_status == 'ACTIVE']
|
|
|
|
training_by_role = {
|
|
'ALL': ['Orientation', 'Fire Safety', 'Emergency Procedures', 'HIPAA Compliance', 'Patient Safety'],
|
|
'CLINICAL': ['Basic Life Support (BLS)', 'Infection Control', 'Medication Administration',
|
|
'Wound Care Management'],
|
|
'PHYSICIAN': ['Advanced Cardiac Life Support (ACLS)', 'Pediatric Advanced Life Support (PALS)'],
|
|
'NURSE': ['IV Therapy', 'Pain Management', 'Electronic Health Records'],
|
|
'ADMIN': ['Customer Service Excellence', 'Quality Improvement', 'Arabic Language'],
|
|
'SUPPORT': ['Safety Training', 'Equipment Operation', 'Cultural Sensitivity']
|
|
}
|
|
|
|
for employee in active_employees:
|
|
# Mandatory training for all employees
|
|
mandatory_training = training_by_role['ALL'].copy()
|
|
|
|
# Role-specific training
|
|
if employee.department and employee.department.department_type == 'CLINICAL':
|
|
mandatory_training.extend(training_by_role['CLINICAL'])
|
|
|
|
if 'Physician' in employee.job_title:
|
|
mandatory_training.extend(training_by_role['PHYSICIAN'])
|
|
elif 'Nurse' in employee.job_title:
|
|
mandatory_training.extend(training_by_role['NURSE'])
|
|
|
|
elif employee.department and employee.department.department_type == 'ADMINISTRATIVE':
|
|
mandatory_training.extend(training_by_role['ADMIN'])
|
|
else:
|
|
mandatory_training.extend(training_by_role['SUPPORT'])
|
|
|
|
# Add some random additional training
|
|
all_training = list(set(mandatory_training + random.sample(SAUDI_TRAINING_PROGRAMS, random.randint(2, 5))))
|
|
|
|
for training_name in all_training:
|
|
# Training date (within employment period)
|
|
days_since_hire = (django_timezone.now().date() - employee.hire_date).days
|
|
training_date = employee.hire_date + timedelta(days=random.randint(0, days_since_hire))
|
|
|
|
# Completion date (usually same day or within a week)
|
|
completion_date = training_date + timedelta(days=random.randint(0, 7))
|
|
|
|
# Training type
|
|
if training_name in ['Orientation']:
|
|
training_type = 'ORIENTATION'
|
|
elif training_name in ['Fire Safety', 'Emergency Procedures', 'HIPAA Compliance', 'Patient Safety']:
|
|
training_type = 'MANDATORY'
|
|
elif 'Certification' in training_name or any(cert in training_name for cert in ['BLS', 'ACLS', 'PALS']):
|
|
training_type = 'CERTIFICATION'
|
|
elif training_name in ['Safety Training', 'Infection Control']:
|
|
training_type = 'SAFETY'
|
|
else:
|
|
training_type = 'CONTINUING_ED'
|
|
|
|
# Duration based on training type
|
|
if training_type == 'CERTIFICATION':
|
|
duration = Decimal(str(random.randint(8, 16))) # 8-16 hours
|
|
elif training_type == 'MANDATORY':
|
|
duration = Decimal(str(random.randint(2, 8))) # 2-8 hours
|
|
else:
|
|
duration = Decimal(str(random.randint(1, 4))) # 1-4 hours
|
|
|
|
# Expiry date for certifications
|
|
expiry_date = None
|
|
if training_type == 'CERTIFICATION':
|
|
expiry_date = completion_date + timedelta(days=random.randint(365, 730)) # 1-2 years
|
|
|
|
status = random.choices(
|
|
['COMPLETED', 'IN_PROGRESS', 'SCHEDULED'],
|
|
weights=[80, 15, 5]
|
|
)[0]
|
|
|
|
# Score and pass status
|
|
if status == 'COMPLETED':
|
|
score = Decimal(str(random.randint(75, 100)))
|
|
passed = score >= 70
|
|
else:
|
|
score = None
|
|
passed = False
|
|
|
|
try:
|
|
record = TrainingRecord.objects.create(
|
|
employee=employee,
|
|
training_name=training_name,
|
|
training_description=f"Comprehensive {training_name.lower()} training program",
|
|
training_type=training_type,
|
|
training_provider=random.choice([
|
|
'Saudi Healthcare Training Institute',
|
|
'Ministry of Health Training Center',
|
|
'King Fahd Medical Training Academy',
|
|
'Internal Training Department'
|
|
]),
|
|
instructor=f"Dr. {random.choice(SAUDI_FIRST_NAMES_MALE + SAUDI_FIRST_NAMES_FEMALE)} {random.choice(SAUDI_LAST_NAMES)}",
|
|
training_date=training_date,
|
|
completion_date=completion_date if status == 'COMPLETED' else None,
|
|
expiry_date=expiry_date,
|
|
duration_hours=duration,
|
|
credits_earned=duration if status == 'COMPLETED' else Decimal('0.00'),
|
|
status=status,
|
|
score=score,
|
|
passed=passed,
|
|
certificate_number=f"CERT-{random.randint(100000, 999999)}" if status == 'COMPLETED' and training_type == 'CERTIFICATION' else None,
|
|
certification_body='Saudi Healthcare Certification Board' if training_type == 'CERTIFICATION' else None,
|
|
training_cost=Decimal(str(random.randint(500, 5000))),
|
|
notes=f"{training_name} training completed successfully" if status == 'COMPLETED' else f"{training_name} training in progress",
|
|
created_at=django_timezone.now() - timedelta(days=random.randint(1, 30)),
|
|
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 15))
|
|
)
|
|
records.append(record)
|
|
except Exception as e:
|
|
print(f"Error creating training record for {employee.get_full_name()}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(records)} training records")
|
|
return records
|
|
|
|
|
|
def main():
|
|
"""Main function to generate all Saudi HR data"""
|
|
print("Starting Saudi Healthcare HR Data Generation...")
|
|
|
|
# Get existing tenants
|
|
tenants = list(Tenant.objects.all())
|
|
if not tenants:
|
|
print("❌ No tenants found. Please run the core data generator first.")
|
|
return
|
|
|
|
# Create departments
|
|
print("\n1. Creating Saudi Hospital Departments...")
|
|
departments = create_saudi_departments(tenants)
|
|
|
|
# Create employees
|
|
print("\n2. Creating Saudi Hospital Employees...")
|
|
employees = create_saudi_employees(tenants, departments, 120)
|
|
|
|
# Assign department heads and supervisors
|
|
print("\n3. Assigning Department Heads and Supervisors...")
|
|
assign_department_heads(departments, employees)
|
|
assign_supervisors(employees)
|
|
|
|
# Create schedules
|
|
print("\n4. Creating Employee Work Schedules...")
|
|
schedules = create_saudi_schedules(employees, 2)
|
|
|
|
# Create schedule assignments
|
|
print("\n5. Creating Schedule Assignments...")
|
|
assignments = create_schedule_assignments(schedules, 30)
|
|
|
|
# Create time entries
|
|
print("\n6. Creating Employee Time Entries...")
|
|
time_entries = create_time_entries(employees, 30)
|
|
|
|
# Create performance reviews
|
|
print("\n7. Creating Performance Reviews...")
|
|
reviews = create_performance_reviews(employees)
|
|
|
|
# Create training records
|
|
print("\n8. Creating Training Records...")
|
|
training_records = create_training_records(employees)
|
|
|
|
print(f"\n✅ Saudi Healthcare HR Data Generation Complete!")
|
|
print(f"📊 Summary:")
|
|
print(f" - Departments: {len(departments)}")
|
|
print(f" - Employees: {len(employees)}")
|
|
print(f" - Schedules: {len(schedules)}")
|
|
print(f" - Schedule Assignments: {len(assignments)}")
|
|
print(f" - Time Entries: {len(time_entries)}")
|
|
print(f" - Performance Reviews: {len(reviews)}")
|
|
print(f" - Training Records: {len(training_records)}")
|
|
|
|
# Department distribution
|
|
dept_counts = {}
|
|
for employee in employees:
|
|
if employee.department:
|
|
dept_type = employee.department.department_type
|
|
dept_counts[dept_type] = dept_counts.get(dept_type, 0) + 1
|
|
|
|
print(f"\n🏥 Employee Distribution by Department Type:")
|
|
for dept_type, count in dept_counts.items():
|
|
print(f" - {dept_type.title()}: {count}")
|
|
|
|
# Employment status distribution
|
|
status_counts = {}
|
|
for employee in employees:
|
|
status_counts[employee.employment_status] = status_counts.get(employee.employment_status, 0) + 1
|
|
|
|
print(f"\n👥 Employee Status Distribution:")
|
|
for status, count in status_counts.items():
|
|
print(f" - {status.title()}: {count}")
|
|
|
|
return {
|
|
'departments': departments,
|
|
'employees': employees,
|
|
'schedules': schedules,
|
|
'assignments': assignments,
|
|
'time_entries': time_entries,
|
|
'reviews': reviews,
|
|
'training_records': training_records
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |