407 lines
15 KiB
Python
407 lines
15 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
|
|
|
|
|
|
# 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(
|
|
'--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']
|
|
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" 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,
|
|
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,
|
|
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,
|
|
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, 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 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'],
|
|
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)
|
|
|
|
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 create_user_for_staff(self, staff):
|
|
"""Create a user account for staff"""
|
|
username = self.generate_username(staff)
|
|
|
|
# Check if user already exists
|
|
if User.objects.filter(username=username).exists():
|
|
return
|
|
|
|
# Generate email
|
|
email = f"{staff.first_name.lower()}.{staff.last_name.lower()}@{staff.hospital.code.lower()}.sa"
|
|
|
|
# Check if email exists
|
|
if User.objects.filter(email=email).exists():
|
|
email = f"{username}@{staff.hospital.code.lower()}.sa"
|
|
|
|
# Create user
|
|
user = User.objects.create_user(
|
|
username=username,
|
|
email=email,
|
|
first_name=staff.first_name,
|
|
last_name=staff.last_name,
|
|
password='password123', # Default password
|
|
employee_id=staff.employee_id,
|
|
hospital=staff.hospital,
|
|
department=staff.department,
|
|
language='ar' if random.random() < 0.5 else 'en', # Random language preference
|
|
is_staff=True,
|
|
)
|
|
|
|
# Link staff to user
|
|
staff.user = user
|
|
staff.save(update_fields=['user'])
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f" ✓ Created user: {username}")
|
|
)
|
|
|
|
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
|