2026-01-24 15:27:30 +03:00

451 lines
18 KiB
Python

"""
Management command to seed staff data with bilingual support (English and Arabic)
"""
import random
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from apps.accounts.models import User
from apps.organizations.models import Hospital, Department, Staff
from apps.organizations.services import StaffService
# Saudi names data - Paired to ensure English and Arabic correspond
NAMES_MALE = [
{'en': 'Mohammed', 'ar': 'محمد'},
{'en': 'Ahmed', 'ar': 'أحمد'},
{'en': 'Abdullah', 'ar': 'عبدالله'},
{'en': 'Khalid', 'ar': 'خالد'},
{'en': 'Saud', 'ar': 'سعود'},
{'en': 'Fahd', 'ar': 'فهد'},
{'en': 'Abdulaziz', 'ar': 'عبدالعزيز'},
{'en': 'Sultan', 'ar': 'سلطان'},
{'en': 'Faisal', 'ar': 'فيصل'},
{'en': 'Omar', 'ar': 'عمر'},
{'en': 'Turki', 'ar': 'تركي'},
{'en': 'Nasser', 'ar': 'ناصر'},
{'en': 'Mishari', 'ar': 'مشاري'},
{'en': 'Abdulrahman', 'ar': 'عبدالرحمن'},
{'en': 'Yousef', 'ar': 'يوسف'},
{'en': 'Ali', 'ar': 'علي'}
]
NAMES_FEMALE = [
{'en': 'Fatimah', 'ar': 'فاطمة'},
{'en': 'Aisha', 'ar': 'عائشة'},
{'en': 'Maryam', 'ar': 'مريم'},
{'en': 'Noura', 'ar': 'نورة'},
{'en': 'Sarah', 'ar': 'سارة'},
{'en': 'Hind', 'ar': 'هند'},
{'en': 'Latifa', 'ar': 'لطيفة'},
{'en': 'Mona', 'ar': 'منى'},
{'en': 'Reem', 'ar': 'ريم'},
{'en': 'Jawaher', 'ar': 'جواهر'},
{'en': 'Lina', 'ar': 'لينا'},
{'en': 'Dua', 'ar': 'دعاء'},
{'en': 'Maha', 'ar': 'مها'},
{'en': 'Jumanah', 'ar': 'جمانة'},
{'en': 'Rahaf', 'ar': 'رحاب'},
{'en': 'Samar', 'ar': 'سمر'}
]
LAST_NAMES = [
{'en': 'Al-Otaibi', 'ar': 'العتيبي'},
{'en': 'Al-Dosari', 'ar': 'الدوسري'},
{'en': 'Al-Qahtani', 'ar': 'القحطاني'},
{'en': 'Al-Shammari', 'ar': 'الشمري'},
{'en': 'Al-Harbi', 'ar': 'الحربي'},
{'en': 'Al-Mutairi', 'ar': 'المطيري'},
{'en': 'Al-Anazi', 'ar': 'العنزي'},
{'en': 'Al-Zahrani', 'ar': 'الزهراني'},
{'en': 'Al-Ghamdi', 'ar': 'الغامدي'},
{'en': 'Al-Shehri', 'ar': 'الشهري'},
{'en': 'Al-Salem', 'ar': 'السالم'},
{'en': 'Al-Fahad', 'ar': 'الفالح'}
]
# Specializations for physicians
PHYSICIAN_SPECIALIZATIONS = [
'Internal Medicine', 'General Surgery', 'Pediatrics', 'Obstetrics & Gynecology',
'Cardiology', 'Orthopedics', 'Neurology', 'Dermatology', 'Ophthalmology',
'ENT', 'Urology', 'Nephrology', 'Gastroenterology', 'Pulmonology',
'Endocrinology', 'Rheumatology', 'Hematology', 'Oncology', 'Psychiatry'
]
# Job titles for nurses
NURSE_JOB_TITLES = [
'Registered Nurse', 'Senior Nurse', 'Nurse Practitioner', 'ICU Nurse',
'Emergency Nurse', 'Pediatric Nurse', 'Operating Room Nurse', 'Head Nurse'
]
# Job titles for admin staff
ADMIN_JOB_TITLES = [
'Medical Receptionist', 'Administrative Assistant', 'Medical Records Clerk',
'Hospital Administrator', 'Department Secretary', 'Patient Services Representative',
'Front Desk Officer', 'Billing Specialist'
]
class Command(BaseCommand):
help = 'Seed staff data with bilingual support (English and Arabic)'
def add_arguments(self, parser):
parser.add_argument(
'--hospital-code',
type=str,
help='Target hospital code (default: all hospitals)'
)
parser.add_argument(
'--count',
type=int,
default=10,
help='Number of staff to create per type (default: 10)'
)
parser.add_argument(
'--physicians',
type=int,
default=10,
help='Number of physicians to create (default: 10)'
)
parser.add_argument(
'--nurses',
type=int,
default=15,
help='Number of nurses to create (default: 15)'
)
parser.add_argument(
'--admin-staff',
type=int,
default=5,
help='Number of admin staff to create (default: 5)'
)
parser.add_argument(
'--create-users',
action='store_true',
help='Create user accounts for staff'
)
parser.add_argument(
'--send-emails',
action='store_true',
help='Send credential emails to newly created users'
)
parser.add_argument(
'--clear',
action='store_true',
help='Clear existing staff first'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Preview without making changes'
)
def handle(self, *args, **options):
hospital_code = options['hospital_code']
count = options['count']
physicians_count = options['physicians']
nurses_count = options['nurses']
admin_staff_count = options['admin_staff']
create_users = options['create_users']
send_emails = options['send_emails']
clear_existing = options['clear']
dry_run = options['dry_run']
self.stdout.write(f"\n{'='*60}")
self.stdout.write("Staff Data Seeding Command")
self.stdout.write(f"{'='*60}\n")
with transaction.atomic():
# Get hospitals
if hospital_code:
hospitals = Hospital.objects.filter(code=hospital_code)
if not hospitals.exists():
self.stdout.write(
self.style.ERROR(f"Hospital with code '{hospital_code}' not found")
)
return
else:
hospitals = Hospital.objects.filter(status='active')
if not hospitals.exists():
self.stdout.write(
self.style.ERROR("No hospitals found. Please create hospitals first.")
)
return
self.stdout.write(
self.style.SUCCESS(f"Found {hospitals.count()} hospital(s) to seed staff")
)
# Display configuration
self.stdout.write("\nConfiguration:")
self.stdout.write(f" Physicians per hospital: {physicians_count}")
self.stdout.write(f" Nurses per hospital: {nurses_count}")
self.stdout.write(f" Admin staff per hospital: {admin_staff_count}")
self.stdout.write(f" Total staff per hospital: {physicians_count + nurses_count + admin_staff_count}")
self.stdout.write(f" Create user accounts: {create_users}")
self.stdout.write(f" Send credential emails: {send_emails}")
self.stdout.write(f" Clear existing: {clear_existing}")
self.stdout.write(f" Dry run: {dry_run}")
# Get all departments for assignment
all_departments = Department.objects.filter(status='active')
if not all_departments.exists():
self.stdout.write(
self.style.WARNING("\nNo departments found. Staff will be created without departments.")
)
# Clear existing staff if requested
if clear_existing:
if dry_run:
self.stdout.write(
self.style.WARNING(f"\nWould delete {Staff.objects.count()} existing staff")
)
else:
deleted_count = Staff.objects.count()
Staff.objects.all().delete()
self.stdout.write(
self.style.SUCCESS(f"\n✓ Deleted {deleted_count} existing staff")
)
# Track created staff
created_staff = []
# Seed physicians
physicians = self.create_staff_type(
hospitals=hospitals,
departments=all_departments,
staff_type=Staff.StaffType.PHYSICIAN,
count=physicians_count,
job_titles=PHYSICIAN_SPECIALIZATIONS,
create_users=create_users,
send_emails=send_emails,
dry_run=dry_run
)
created_staff.extend(physicians)
# Seed nurses
nurses = self.create_staff_type(
hospitals=hospitals,
departments=all_departments,
staff_type=Staff.StaffType.NURSE,
count=nurses_count,
job_titles=NURSE_JOB_TITLES,
create_users=create_users,
send_emails=send_emails,
dry_run=dry_run
)
created_staff.extend(nurses)
# Seed admin staff
admins = self.create_staff_type(
hospitals=hospitals,
departments=all_departments,
staff_type=Staff.StaffType.ADMIN,
count=admin_staff_count,
job_titles=ADMIN_JOB_TITLES,
create_users=create_users,
send_emails=send_emails,
dry_run=dry_run
)
created_staff.extend(admins)
# Summary
self.stdout.write("\n" + "="*60)
self.stdout.write("Summary:")
self.stdout.write(f" Physicians created: {len(physicians)}")
self.stdout.write(f" Nurses created: {len(nurses)}")
self.stdout.write(f" Admin staff created: {len(admins)}")
self.stdout.write(f" Total staff created: {len(created_staff)}")
self.stdout.write("="*60 + "\n")
if dry_run:
self.stdout.write(self.style.WARNING("DRY RUN: No changes were made\n"))
else:
self.stdout.write(self.style.SUCCESS("Staff seeding completed successfully!\n"))
def create_staff_type(self, hospitals, departments, staff_type, count, job_titles,
create_users, send_emails, dry_run):
"""Create staff of a specific type"""
created = []
staff_type_display = dict(Staff.StaffType.choices).get(staff_type, staff_type)
self.stdout.write(f"\nSeeding {staff_type_display}...")
for hospital in hospitals:
# Get departments for this hospital
hospital_depts = [d for d in departments if d.hospital == hospital]
for i in range(count):
# Generate names (paired to ensure English and Arabic correspond)
if staff_type == Staff.StaffType.NURSE:
# More female nurses (70%)
if random.random() < 0.7:
first_name = random.choice(NAMES_FEMALE)
else:
first_name = random.choice(NAMES_MALE)
else:
# Physicians and admin staff are mostly male (60%)
if random.random() < 0.6:
first_name = random.choice(NAMES_MALE)
else:
first_name = random.choice(NAMES_FEMALE)
last_name = random.choice(LAST_NAMES)
job_title = random.choice(job_titles)
# Generate employee ID
employee_id = self.generate_employee_id(hospital.code, staff_type)
# Generate random email
email = self.generate_staff_email(first_name['en'], last_name['en'], hospital.code)
# Generate license number for physicians
license_number = None
if staff_type == Staff.StaffType.PHYSICIAN:
license_number = self.generate_license_number()
# Select department (optional)
department = random.choice(hospital_depts) if hospital_depts and random.random() > 0.1 else None
# Specialization for all staff types (required field)
if staff_type == Staff.StaffType.PHYSICIAN:
specialization = job_title
elif staff_type == Staff.StaffType.NURSE:
specialization = random.choice(['General Nursing', 'Patient Care', 'Clinical Nursing', 'Nursing'])
elif staff_type == Staff.StaffType.ADMIN:
specialization = random.choice(['Administration', 'Hospital Operations', 'Medical Administration', 'Healthcare Admin'])
else:
specialization = 'General Staff'
if dry_run:
self.stdout.write(
f" Would create: {first_name['en']} {last_name['en']} ({first_name['ar']} {last_name['ar']}) - {job_title}"
)
created.append({'type': staff_type_display, 'name': f"{first_name['en']} {last_name['en']}"})
else:
# Create staff
staff = Staff.objects.create(
first_name=first_name['en'],
last_name=last_name['en'],
first_name_ar=first_name['ar'],
last_name_ar=last_name['ar'],
email=email,
staff_type=staff_type,
job_title=job_title,
license_number=license_number,
specialization=specialization,
employee_id=employee_id,
hospital=hospital,
department=department,
status='active'
)
# Create user account if requested
if create_users:
self.create_user_for_staff(staff, send_emails)
created.append(staff)
self.stdout.write(
self.style.SUCCESS(f" ✓ Created {len([s for s in created if isinstance(s, Staff)])} {staff_type_display}")
)
return created
def generate_employee_id(self, hospital_code, staff_type):
"""Generate unique employee ID"""
prefix = {
Staff.StaffType.PHYSICIAN: 'DR',
Staff.StaffType.NURSE: 'RN',
Staff.StaffType.ADMIN: 'ADM',
Staff.StaffType.OTHER: 'STF'
}.get(staff_type, 'STF')
random_num = random.randint(10000, 99999)
return f"{prefix}-{hospital_code}-{random_num}"
def generate_license_number(self):
"""Generate unique license number"""
random_num = random.randint(1000000, 9999999)
return f"MOH-LIC-{random_num}"
def generate_staff_email(self, first_name, last_name, hospital_code):
"""Generate unique random email for staff"""
# Clean up names for email (remove spaces and special characters)
clean_first = ''.join(c.lower() for c in first_name if c.isalnum() or c == ' ')
clean_last = ''.join(c.lower() for c in last_name if c.isalnum() or c == ' ')
base = f"{clean_first.replace(' ', '.')}.{clean_last.replace(' ', '.')}"
email = f"{base}@{hospital_code.lower()}.sa"
# Add random suffix if email already exists
counter = 1
while Staff.objects.filter(email=email).exists():
random_num = random.randint(1, 999)
email = f"{base}{random_num}@{hospital_code.lower()}.sa"
counter += 1
if counter > 100: # Safety limit
break
return email
def create_user_for_staff(self, staff, send_email=False):
"""Create a user account for staff using StaffService"""
try:
# Use email that was already set on staff during creation
email = staff.email
# Get role for this staff type
role = StaffService.get_staff_type_role(staff.staff_type)
# Create mock request object for StaffService
class MockRequest:
def build_absolute_uri(self, location=''):
from django.conf import settings
return f"{settings.SITE_URL if hasattr(settings, 'SITE_URL') else 'http://localhost:8000'}{location}"
request = MockRequest()
# Create user account using StaffService
user, was_created, password = StaffService.create_user_for_staff(staff, role, request)
if was_created:
self.stdout.write(
self.style.SUCCESS(f" ✓ Created user: {user.email} (role: {role})")
)
# Send credential email if requested
if send_email:
try:
StaffService.send_credentials_email(staff, password, request)
self.stdout.write(
self.style.SUCCESS(f" ✓ Sent credential email to: {email}")
)
except Exception as email_error:
self.stdout.write(
self.style.WARNING(f" ⚠ Failed to send email: {str(email_error)}")
)
else:
self.stdout.write(
self.style.SUCCESS(f" ✓ Linked existing user: {user.email} (role: {role})")
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f" ✗ Failed to create user for {staff.first_name} {staff.last_name}: {str(e)}")
)
def generate_username(self, staff):
"""Generate unique username"""
base = f"{staff.first_name.lower()}.{staff.last_name.lower()}"
username = base
# Add suffix if username exists
counter = 1
while User.objects.filter(username=username).exists():
username = f"{base}{counter}"
counter += 1
return username