571 lines
27 KiB
Python
571 lines
27 KiB
Python
"""
|
|
Management command to seed complaint data with bilingual support (English and Arabic)
|
|
"""
|
|
import random
|
|
import uuid
|
|
from datetime import timedelta
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
from django.db import transaction
|
|
from django.utils import timezone
|
|
|
|
from apps.accounts.models import User
|
|
from apps.complaints.models import Complaint, ComplaintCategory, ComplaintUpdate
|
|
from apps.organizations.models import Hospital, Department, Staff
|
|
from apps.px_sources.models import PXSource
|
|
|
|
|
|
# English complaint templates
|
|
ENGLISH_COMPLAINTS = {
|
|
'staff_mentioned': [
|
|
{
|
|
'title': 'Rude behavior from nurse during shift',
|
|
'description': 'I was extremely disappointed by the rude behavior of the nurse {staff_name} during the night shift on {date}. She was dismissive and unprofessional when I asked for pain medication. Her attitude made my hospital experience very unpleasant.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'Physician misdiagnosed my condition',
|
|
'description': 'Dr. {staff_name} misdiagnosed my condition and prescribed wrong medication. I had to suffer for 3 more days before another doctor caught the error. This negligence is unacceptable and needs to be addressed immediately.',
|
|
'category': 'clinical_care',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'Nurse ignored call button for over 30 minutes',
|
|
'description': 'Despite pressing the call button multiple times, nurse {staff_name} did not respond for over 30 minutes. When she finally arrived, she was annoyed and unhelpful. This level of neglect is unacceptable in a healthcare setting.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'Physician did not explain treatment plan clearly',
|
|
'description': 'Dr. {staff_name} did not take the time to explain my diagnosis or treatment plan. He was rushing and seemed impatient with my questions. I felt dismissed and anxious about my treatment.',
|
|
'category': 'clinical_care',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'Nurse made medication error',
|
|
'description': 'Nurse {staff_name} attempted to give me medication meant for another patient. I only noticed because the name on the label was different. This is a serious safety concern that needs immediate investigation.',
|
|
'category': 'clinical_care',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'Admin staff was unhelpful with billing inquiry',
|
|
'description': 'The administrative staff member {staff_name} was extremely unhelpful when I asked questions about my bill. She was dismissive and refused to explain the charges properly. This poor customer service reflects badly on the hospital.',
|
|
'category': 'communication',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'Nurse was compassionate and helpful',
|
|
'description': 'I want to express my appreciation for nurse {staff_name} who went above and beyond to make me comfortable during my stay. Her kind and caring demeanor made a difficult situation much more bearable.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
},
|
|
{
|
|
'title': 'Physician provided excellent care',
|
|
'description': 'Dr. {staff_name} provided exceptional care and took the time to thoroughly explain my condition and treatment options. His expertise and bedside manner were outstanding.',
|
|
'category': 'clinical_care',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
}
|
|
],
|
|
'general': [
|
|
{
|
|
'title': 'Long wait time in emergency room',
|
|
'description': 'I had to wait over 4 hours in the emergency room despite being in severe pain. The lack of attention and delay in treatment was unacceptable for an emergency situation.',
|
|
'category': 'wait_time',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'Room was not clean upon admission',
|
|
'description': 'When I was admitted to my room, it was not properly cleaned. There was dust on the surfaces and the bathroom was not sanitary. This is concerning for patient safety.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'Air conditioning not working properly',
|
|
'description': 'The air conditioning in my room was not working for 2 days. Despite multiple complaints to staff, nothing was done. The room was uncomfortably hot which affected my recovery.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'Billing statement has incorrect charges',
|
|
'description': 'My billing statement contains charges for procedures and medications I never received. I have tried to resolve this issue multiple times but have not received any assistance.',
|
|
'category': 'billing',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'Difficulty getting prescription refills',
|
|
'description': 'Getting prescription refills has been extremely difficult. The process is unclear and there is poor communication between the pharmacy and doctors. This has caused delays in my treatment.',
|
|
'category': 'communication',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'Parking is inadequate for visitors',
|
|
'description': 'There is very limited parking available for visitors. I had to circle multiple times to find a spot and was late for my appointment. This needs to be addressed.',
|
|
'category': 'facility',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
},
|
|
{
|
|
'title': 'Food quality has declined',
|
|
'description': 'The quality of hospital food has significantly declined. Meals are often cold, not appetizing, and don\'t meet dietary requirements. This affects patient satisfaction.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
}
|
|
]
|
|
}
|
|
|
|
# Arabic complaint templates
|
|
ARABIC_COMPLAINTS = {
|
|
'staff_mentioned': [
|
|
{
|
|
'title': 'سلوك غير مهذب من الممرضة أثناء المناوبة',
|
|
'description': 'كنت محبطاً جداً من السلوك غير المهذب للممرضة {staff_name} خلال المناوبة الليلية في {date}. كانت متجاهلة وغير مهنية عندما طلبت دواء للم. موقفها جعل تجربتي في المستشفى غير سارة.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'الطبيب تشخص خطأ في حالتي',
|
|
'description': 'تشخص د. {staff_name} خطأ في حالتي ووصف دواء خاطئ. اضطررت للمعاناة لمدة 3 أيام إضافية قبل أن يكتشف طبيب آخر الخطأ. هذا الإهمال غير مقبول ويجب معالجته فوراً.',
|
|
'category': 'clinical_care',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'الممرضة تجاهلت زر الاستدعاء لأكثر من 30 دقيقة',
|
|
'description': 'على الرغم من الضغط على زر الاستدعاء عدة مرات، لم تستجب الممرضة {staff_name} لأكثر من 30 دقيقة. عندما وصلت أخيراً، كانت منزعجة وغير مفيدة. هذا مستوى من الإهمال غير مقبول في بيئة الرعاية الصحية.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'الطبيب لم يوضح خطة العلاج بوضوح',
|
|
'description': 'د. {staff_name} لم يأخذ الوقت لتوضيح تشخيصي أو خطة العلاج. كان يتسرع ويبدو متضايقاً من أسئلتي. شعرت بالإقصاء والقلق بشأن علاجي.',
|
|
'category': 'clinical_care',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'الممرضة ارتكبت خطأ في الدواء',
|
|
'description': 'حاولت الممرضة {staff_name} إعطائي دواء مخصص لمريض آخر. لاحظت ذلك فقط لأن الاسم على الملصق مختلف. هذا قلق خطير على السلامة يحتاج إلى تحقيق فوري.',
|
|
'category': 'clinical_care',
|
|
'severity': 'critical',
|
|
'priority': 'urgent'
|
|
},
|
|
{
|
|
'title': 'موظف الإدارة كان غير مفيد في استفسار الفوترة',
|
|
'description': 'كان موظف الإدارة {staff_name} غير مفيد جداً عندما سألت عن فاتورتي. كان متجاهلاً ورفض توضيح الرسوم بشكل صحيح. هذه الخدمة السيئة للعملاء تعكس سلباً على المستشفى.',
|
|
'category': 'communication',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'الممرضة كانت متعاطفة ومساعدة',
|
|
'description': 'أريد أن أعبر عن تقديري للممرضة {staff_name} التي بذلت ما هو أبعد من المتوقع لجعلي مرتاحاً خلال إقامتي. كلمتها اللطيفة والراعية جعلت الموقف الصعب أكثر قابلية للتحمل.',
|
|
'category': 'staff_behavior',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
},
|
|
{
|
|
'title': 'الطبيب قدم رعاية ممتازة',
|
|
'description': 'قدم د. {staff_name} رعاية استثنائية وأخذ الوقت لتوضيح حالتي وخيارات العلاج بدقة. كانت خبرته وأسلوبه مع المرضى ممتازين.',
|
|
'category': 'clinical_care',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
}
|
|
],
|
|
'general': [
|
|
{
|
|
'title': 'وقت انتظار طويل في الطوارئ',
|
|
'description': 'اضطررت للانتظار أكثر من 4 ساعات في غرفة الطوارئ رغم أنني كنت أعاني من ألم شديد. عدم الانتباه والتأخير في العلاج غير مقبول لحالة طارئة.',
|
|
'category': 'wait_time',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'الغرفة لم تكن نظيفة عند القبول',
|
|
'description': 'عندما تم قبولي في غرفتي، لم تكن نظيفة بشكل صحيح. كان هناك غبار على الأسطح وحمام غير صحي. هذا مصدر قلق لسلامة المرضى.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'التكييف لا يعمل بشكل صحيح',
|
|
'description': 'لم يكن التكييف في غرفتي يعمل لمدة يومين. على الرغم من شكاوى متعددة للموظفين، لم يتم فعل شيء. كانت الغرفة ساخنة بشكل غير مريح مما أثر على تعافيي.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'كشف الفاتورة يحتوي على رسوم غير صحيحة',
|
|
'description': 'كشف فاتورتي يحتوي على رسوم لإجراءات وأدوية لم أتلقها أبداً. حاولت حل هذه المشكلة عدة مرات لكن لم أتلق أي مساعدة.',
|
|
'category': 'billing',
|
|
'severity': 'high',
|
|
'priority': 'high'
|
|
},
|
|
{
|
|
'title': 'صعوبة الحصول على وصفات طبية',
|
|
'description': 'الحصول على وصفات طبية كان صعباً للغاية. العملية غير واضحة وهناك تواصل سيء بين الصيدلية والأطباء. هذا تسبب في تأخير في علاجي.',
|
|
'category': 'communication',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
},
|
|
{
|
|
'title': 'مواقف السيارات غير كافية للزوار',
|
|
'description': 'هناك مواقف سيارات محدودة جداً للزوار. اضطررت للدوران عدة مرات لإيجاد مكان وتأخرت عن موعدي. هذا يجب معالجته.',
|
|
'category': 'facility',
|
|
'severity': 'low',
|
|
'priority': 'low'
|
|
},
|
|
{
|
|
'title': 'جودة الطعام انخفضت',
|
|
'description': 'جودة طعام المستشفى انخفضت بشكل كبير. الوجبات غالباً باردة وغير شهية ولا تلبي المتطلبات الغذائية. هذا يؤثر على رضا المرضى.',
|
|
'category': 'facility',
|
|
'severity': 'medium',
|
|
'priority': 'medium'
|
|
}
|
|
]
|
|
}
|
|
|
|
# Patient names for complaints
|
|
PATIENT_NAMES_EN = [
|
|
'John Smith', 'Sarah Johnson', 'Ahmed Al-Rashid', 'Fatima Hassan',
|
|
'Michael Brown', 'Layla Al-Otaibi', 'David Wilson', 'Nora Al-Dosari',
|
|
'James Taylor', 'Aisha Al-Qahtani'
|
|
]
|
|
|
|
PATIENT_NAMES_AR = [
|
|
'محمد العتيبي', 'فاطمة الدوسري', 'أحمد القحطاني', 'سارة الشمري',
|
|
'خالد الحربي', 'نورة المطيري', 'عبدالله العنزي', 'مريم الزهراني',
|
|
'سعود الشهري', 'هند السالم'
|
|
]
|
|
|
|
# Source mapping for PXSource
|
|
SOURCE_MAPPING = {
|
|
'patient': ('Patient', 'مريض'),
|
|
'family': ('Family Member', 'عضو العائلة'),
|
|
'staff': ('Staff', 'موظف'),
|
|
'call_center': ('Call Center', 'مركز الاتصال'),
|
|
'online': ('Online Form', 'نموذج عبر الإنترنت'),
|
|
'in_person': ('In Person', 'شخصياً'),
|
|
'survey': ('Survey', 'استبيان'),
|
|
'social_media': ('Social Media', 'وسائل التواصل الاجتماعي'),
|
|
}
|
|
|
|
# Categories mapping
|
|
CATEGORY_MAP = {
|
|
'clinical_care': 'الرعاية السريرية',
|
|
'staff_behavior': 'سلوك الموظفين',
|
|
'facility': 'المرافق والبيئة',
|
|
'wait_time': 'وقت الانتظار',
|
|
'billing': 'الفواتير',
|
|
'communication': 'التواصل',
|
|
'other': 'أخرى'
|
|
}
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Seed complaint data with bilingual support (English and Arabic)'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--count',
|
|
type=int,
|
|
default=10,
|
|
help='Number of complaints to create (default: 10)'
|
|
)
|
|
parser.add_argument(
|
|
'--arabic-percent',
|
|
type=int,
|
|
default=70,
|
|
help='Percentage of Arabic complaints (default: 70)'
|
|
)
|
|
parser.add_argument(
|
|
'--hospital-code',
|
|
type=str,
|
|
help='Target hospital code (default: all hospitals)'
|
|
)
|
|
parser.add_argument(
|
|
'--staff-mention-percent',
|
|
type=int,
|
|
default=60,
|
|
help='Percentage of staff-mentioned complaints (default: 60)'
|
|
)
|
|
parser.add_argument(
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Preview without making changes'
|
|
)
|
|
parser.add_argument(
|
|
'--clear',
|
|
action='store_true',
|
|
help='Clear existing complaints first'
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
count = options['count']
|
|
arabic_percent = options['arabic_percent']
|
|
hospital_code = options['hospital_code']
|
|
staff_mention_percent = options['staff_mention_percent']
|
|
dry_run = options['dry_run']
|
|
clear_existing = options['clear']
|
|
|
|
self.stdout.write(f"\n{'='*60}")
|
|
self.stdout.write("Complaint 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 active hospitals found. Please create hospitals first.")
|
|
)
|
|
return
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"Found {hospitals.count()} hospital(s)")
|
|
)
|
|
|
|
# Get all categories
|
|
all_categories = ComplaintCategory.objects.filter(is_active=True)
|
|
if not all_categories.exists():
|
|
self.stdout.write(
|
|
self.style.ERROR("No complaint categories found. Please run seed_complaint_configs first.")
|
|
)
|
|
return
|
|
|
|
# Get all staff
|
|
all_staff = Staff.objects.filter(status='active')
|
|
if not all_staff.exists():
|
|
self.stdout.write(
|
|
self.style.WARNING("No staff found. Staff-mentioned complaints will not have linked staff.")
|
|
)
|
|
|
|
# Ensure PXSource instances exist
|
|
self.ensure_pxsources()
|
|
|
|
# Display configuration
|
|
self.stdout.write("\nConfiguration:")
|
|
self.stdout.write(f" Total complaints to create: {count}")
|
|
arabic_count = int(count * arabic_percent / 100)
|
|
english_count = count - arabic_count
|
|
self.stdout.write(f" Arabic complaints: {arabic_count} ({arabic_percent}%)")
|
|
self.stdout.write(f" English complaints: {english_count} ({100-arabic_percent}%)")
|
|
staff_mentioned_count = int(count * staff_mention_percent / 100)
|
|
general_count = count - staff_mentioned_count
|
|
self.stdout.write(f" Staff-mentioned: {staff_mentioned_count} ({staff_mention_percent}%)")
|
|
self.stdout.write(f" General: {general_count} ({100-staff_mention_percent}%)")
|
|
self.stdout.write(f" Status: All OPEN")
|
|
self.stdout.write(f" Dry run: {dry_run}")
|
|
|
|
# Clear existing complaints if requested
|
|
if clear_existing:
|
|
if dry_run:
|
|
self.stdout.write(
|
|
self.style.WARNING(f"\nWould delete {Complaint.objects.count()} existing complaints")
|
|
)
|
|
else:
|
|
deleted_count = Complaint.objects.count()
|
|
Complaint.objects.all().delete()
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"\n✓ Deleted {deleted_count} existing complaints")
|
|
)
|
|
|
|
# Track created complaints
|
|
created_complaints = []
|
|
by_language = {'en': 0, 'ar': 0}
|
|
by_type = {'staff_mentioned': 0, 'general': 0}
|
|
|
|
# Create complaints
|
|
for i in range(count):
|
|
# Determine language (alternate based on percentage)
|
|
is_arabic = i < arabic_count
|
|
lang = 'ar' if is_arabic else 'en'
|
|
|
|
# Determine type (staff-mentioned vs general)
|
|
is_staff_mentioned = random.random() < (staff_mention_percent / 100)
|
|
complaint_type = 'staff_mentioned' if is_staff_mentioned else 'general'
|
|
|
|
# Select hospital (round-robin through available hospitals)
|
|
hospital = hospitals[i % len(hospitals)]
|
|
|
|
# Select staff if needed
|
|
staff_member = None
|
|
if is_staff_mentioned and all_staff.exists():
|
|
# Try to find staff from same hospital
|
|
hospital_staff = all_staff.filter(hospital=hospital)
|
|
if hospital_staff.exists():
|
|
staff_member = random.choice(hospital_staff)
|
|
else:
|
|
staff_member = random.choice(all_staff)
|
|
|
|
# Get complaint templates for language and type
|
|
templates = ARABIC_COMPLAINTS[complaint_type] if is_arabic else ENGLISH_COMPLAINTS[complaint_type]
|
|
template = random.choice(templates)
|
|
|
|
# Get category
|
|
category_code = template['category']
|
|
category = all_categories.filter(code=category_code).first()
|
|
|
|
# Prepare complaint data
|
|
complaint_data = self.prepare_complaint_data(
|
|
template=template,
|
|
staff_member=staff_member,
|
|
category=category,
|
|
hospital=hospital,
|
|
is_arabic=is_arabic,
|
|
i=i
|
|
)
|
|
|
|
if dry_run:
|
|
self.stdout.write(
|
|
f" Would create: {complaint_data['title']} ({lang.upper()}) - {complaint_type}"
|
|
)
|
|
created_complaints.append({
|
|
'title': complaint_data['title'],
|
|
'language': lang,
|
|
'type': complaint_type
|
|
})
|
|
else:
|
|
# Create complaint
|
|
complaint = Complaint.objects.create(**complaint_data)
|
|
|
|
# Create timeline entry
|
|
self.create_timeline_entry(complaint)
|
|
|
|
created_complaints.append(complaint)
|
|
|
|
# Track statistics
|
|
by_language[lang] += 1
|
|
by_type[complaint_type] += 1
|
|
|
|
# Summary
|
|
self.stdout.write("\n" + "="*60)
|
|
self.stdout.write("Summary:")
|
|
self.stdout.write(f" Total complaints created: {len(created_complaints)}")
|
|
self.stdout.write(f" Arabic: {by_language['ar']}")
|
|
self.stdout.write(f" English: {by_language['en']}")
|
|
self.stdout.write(f" Staff-mentioned: {by_type['staff_mentioned']}")
|
|
self.stdout.write(f" General: {by_type['general']}")
|
|
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("Complaint seeding completed successfully!\n"))
|
|
|
|
def prepare_complaint_data(self, template, staff_member, category, hospital, is_arabic, i):
|
|
"""Prepare complaint data from template"""
|
|
# Generate description with staff name if applicable
|
|
description = template['description']
|
|
if staff_member:
|
|
staff_name = f"{staff_member.first_name_ar} {staff_member.last_name_ar}" if is_arabic else f"{staff_member.first_name} {staff_member.last_name}"
|
|
description = description.format(staff_name=staff_name, date=timezone.now().date())
|
|
|
|
# Generate reference number
|
|
reference = self.generate_reference_number(hospital.code)
|
|
|
|
# Generate patient name
|
|
patient_names = PATIENT_NAMES_AR if is_arabic else PATIENT_NAMES_EN
|
|
patient_name = patient_names[i % len(patient_names)]
|
|
|
|
# Generate contact info
|
|
contact_method = random.choice(['email', 'phone', 'both'])
|
|
if contact_method == 'email':
|
|
email = f"patient{i}@example.com"
|
|
phone = ""
|
|
elif contact_method == 'phone':
|
|
email = ""
|
|
phone = f"+9665{random.randint(10000000, 99999999)}"
|
|
else:
|
|
email = f"patient{i}@example.com"
|
|
phone = f"+9665{random.randint(10000000, 99999999)}"
|
|
|
|
# Select source key
|
|
source_key = random.choice(list(SOURCE_MAPPING.keys()))
|
|
source_instance = self.get_source_instance(source_key)
|
|
|
|
# Get department (if staff member exists, use their department)
|
|
department = staff_member.department if staff_member else None
|
|
|
|
# Prepare complaint data
|
|
data = {
|
|
'reference_number': reference,
|
|
'hospital': hospital,
|
|
'department': department,
|
|
'category': category,
|
|
'title': template['title'],
|
|
'description': description,
|
|
'severity': template['severity'],
|
|
'priority': template['priority'],
|
|
'source': source_instance,
|
|
'status': 'open',
|
|
'contact_name': patient_name,
|
|
'contact_phone': phone,
|
|
'contact_email': email,
|
|
'staff': staff_member,
|
|
}
|
|
|
|
return data
|
|
|
|
def generate_reference_number(self, hospital_code):
|
|
"""Generate unique complaint reference number"""
|
|
short_uuid = str(uuid.uuid4())[:8].upper()
|
|
year = timezone.now().year
|
|
return f"CMP-{hospital_code}-{year}-{short_uuid}"
|
|
|
|
def create_timeline_entry(self, complaint):
|
|
"""Create initial timeline entry for complaint"""
|
|
ComplaintUpdate.objects.create(
|
|
complaint=complaint,
|
|
update_type='status_change',
|
|
old_status='',
|
|
new_status='open',
|
|
message='Complaint created and registered',
|
|
created_by=None # System-created
|
|
)
|
|
|
|
def ensure_pxsources(self):
|
|
"""Ensure all required PXSource instances exist"""
|
|
for source_key, (name_en, name_ar) in SOURCE_MAPPING.items():
|
|
PXSource.objects.get_or_create(
|
|
name_en=name_en,
|
|
defaults={
|
|
'name_ar': name_ar,
|
|
'description': f'{name_en} source for complaints and inquiries',
|
|
'is_active': True
|
|
}
|
|
)
|
|
|
|
def get_source_instance(self, source_key):
|
|
"""Get PXSource instance by source key"""
|
|
name_en, _ = SOURCE_MAPPING.get(source_key, ('Other', 'أخرى'))
|
|
try:
|
|
return PXSource.objects.get(name_en=name_en, is_active=True)
|
|
except PXSource.DoesNotExist:
|
|
# Fallback to first active source
|
|
return PXSource.objects.filter(is_active=True).first()
|