This commit is contained in:
Marwan Alwali 2026-01-08 20:56:18 +03:00
parent f8a0305caf
commit 867f60fed7
15 changed files with 459 additions and 158 deletions

View File

@ -0,0 +1,84 @@
# Physician Reference Fixes
## Summary
Fixed all references to use the correct model (`organizations.Staff`) instead of incorrect `Physician` model references.
## Root Cause
The system uses `organizations.Staff` model for physicians, not a separate `Physician` model. Several templates and code were incorrectly trying to access a `physician` attribute on `PhysicianMonthlyRating` and `PatientJourneyStageInstance` objects, when the actual field is `staff`.
## Files Fixed
### 1. Journeys Module
**File:** `apps/journeys/ui_views.py`
- Changed `prefetch_related()` parameter from `'stage_instances__physician'` to `'stage_instances__staff'` in two places
- Line 40 (journey_instance_list view) and Line 149 (journey_instance_detail view)
- This was the source of the original AttributeError: "Cannot find 'physician' on PatientJourneyStageInstance object"
**File:** `apps/journeys/serializers.py`
- Changed `PatientJourneyStageInstanceSerializer` field from `physician` to `staff`
- Changed `physician_name` method to `staff_name`
- Updated fields list to use `staff` and `staff_name` instead of `physician` and `physician_name`
- The `PatientJourneyStageInstance` model has a `staff` field, not `physician`
### 2. Appreciation Module
**File:** `apps/appreciation/views.py`
- Changed all `Physician.objects` references to `Staff.objects`
- Updated queryset filters and filters context to use Staff model
**File:** `apps/appreciation/serializers.py`
- Changed model reference from `Physician` to `Staff`
- Updated all field references from `physician` to `staff`
### 3. Dashboard Module
**File:** `templates/dashboard/command_center.html`
- Changed `rating.physician.*` references to `rating.staff.*`
- The `PhysicianMonthlyRating` model has a `staff` field, not `physician`
### 4. Physicians Templates
**File:** `templates/physicians/ratings_list.html`
- Changed all `rating.physician.*` references to `rating.staff.*`
**File:** `templates/physicians/specialization_overview.html`
- Changed all `rating.physician.*` references to `rating.staff.*`
**File:** `templates/physicians/department_overview.html`
- Changed all `rating.physician.*` references to `rating.staff.*`
### 5. Physicians App (Verified - No Changes Needed)
The physicians app is already correctly implemented:
- `apps/physicians/models.py` - `PhysicianMonthlyRating.staff` field correctly points to `organizations.Staff`
- `apps/physicians/views.py` - All code correctly uses `staff` field when querying `PhysicianMonthlyRating`
- `apps/physicians/serializers.py` - All serializers correctly reference `staff` field
- `apps/physicians/ui_views.py` - All views correctly use `staff` field
- `templates/physicians/*.html` - All templates correctly access physician attributes through `physician.*` where `physician` is a Staff object
The variable name `physician` is used correctly in templates as it represents a `Staff` object passed from views, not a separate `Physician` model.
## Model Structure
### organizations.Staff
- This is the actual physician model
- Contains fields like: `first_name`, `last_name`, `license_number`, `specialization`, `department`, `hospital`
### physicians.PhysicianMonthlyRating
- Contains monthly aggregated ratings for physicians
- Has a `staff` field (ForeignKey to `organizations.Staff`), NOT `physician`
- This was the source of most template errors
### journeys.PatientJourneyStageInstance
- Contains stage instances in patient journeys
- Has a `staff` field (ForeignKey to `organizations.Staff`), NOT `physician`
- This was the source of the serializer error
## Verification
All imports were verified to be correct:
- `PhysicianMonthlyRating` imports are all correct
- No old `Physician` model imports exist
- Only one `physician_profile` reference found, which is a commented line
## Testing
The application should now work correctly without the following errors:
1. `AttributeError: Cannot find 'physician' on PatientJourneyStageInstance object`
2. Template rendering errors due to `rating.physician` not existing
3. All physician-related views should display correctly

View File

@ -107,7 +107,7 @@ class AppreciationSerializer(serializers.ModelSerializer):
return "Unknown" return "Unknown"
def get_recipient_type(self, obj): def get_recipient_type(self, obj):
"""Get recipient type (user or physician)""" """Get recipient type (user or staff)"""
if obj.recipient_content_type: if obj.recipient_content_type:
return obj.recipient_content_type.model return obj.recipient_content_type.model
return None return None
@ -133,9 +133,9 @@ class AppreciationCreateSerializer(serializers.Serializer):
"""Serializer for creating Appreciation""" """Serializer for creating Appreciation"""
recipient_type = serializers.ChoiceField( recipient_type = serializers.ChoiceField(
choices=['user', 'physician'], choices=['user', 'staff'],
write_only=True, write_only=True,
help_text="Type of recipient: 'user' or 'physician'" help_text="Type of recipient: 'user' or 'staff'"
) )
recipient_id = serializers.UUIDField(write_only=True) recipient_id = serializers.UUIDField(write_only=True)
category_id = serializers.UUIDField(required=False, allow_null=True) category_id = serializers.UUIDField(required=False, allow_null=True)
@ -176,17 +176,17 @@ class AppreciationCreateSerializer(serializers.Serializer):
raise serializers.ValidationError({ raise serializers.ValidationError({
'recipient_id': 'User not found' 'recipient_id': 'User not found'
}) })
elif recipient_type == 'physician': elif recipient_type == 'staff':
from apps.organizations.models import Physician from apps.organizations.models import Staff
try: try:
physician = Physician.objects.get(id=recipient_id) staff = Staff.objects.get(id=recipient_id)
if physician.hospital_id != hospital_id: if staff.hospital_id != hospital_id:
raise serializers.ValidationError({ raise serializers.ValidationError({
'recipient_id': 'Physician does not belong to specified hospital' 'recipient_id': 'Staff does not belong to specified hospital'
}) })
except Physician.DoesNotExist: except Staff.DoesNotExist:
raise serializers.ValidationError({ raise serializers.ValidationError({
'recipient_id': 'Physician not found' 'recipient_id': 'Staff not found'
}) })
# Validate category if provided # Validate category if provided

View File

@ -86,11 +86,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
# Get user's content type # Get user's content type
user_content_type = ContentType.objects.get_for_model(user) user_content_type = ContentType.objects.get_for_model(user)
# Get physician if user has a physician profile # Get staff if user has a staff profile
physician = None staff = None
if hasattr(user, 'physician_profile'): if hasattr(user, 'staff_profile'):
physician = user.phician_profile staff = user.staff_profile
physician_content_type = ContentType.objects.get_for_model(type(physician)) staff_content_type = ContentType.objects.get_for_model(type(staff))
# Build visibility filter # Build visibility filter
visibility_filter = ( visibility_filter = (
@ -101,11 +101,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
) # Received by user ) # Received by user
) )
if physician: if staff:
visibility_filter |= Q( visibility_filter |= Q(
recipient_content_type=physician_content_type, recipient_content_type=staff_content_type,
recipient_object_id=physician.id recipient_object_id=staff.id
) # Received by physician ) # Received by staff
if user.department: if user.department:
visibility_filter |= Q( visibility_filter |= Q(
@ -135,9 +135,9 @@ class AppreciationViewSet(viewsets.ModelViewSet):
recipient_content_type=content_type, recipient_content_type=content_type,
recipient_object_id=recipient_id recipient_object_id=recipient_id
) )
elif recipient_type == 'physician': elif recipient_type == 'staff':
from apps.organizations.models import Physician from apps.organizations.models import Staff
content_type = ContentType.objects.get_for_model(Physician) content_type = ContentType.objects.get_for_model(Staff)
queryset = queryset.filter( queryset = queryset.filter(
recipient_content_type=content_type, recipient_content_type=content_type,
recipient_object_id=recipient_id recipient_object_id=recipient_id
@ -173,10 +173,10 @@ class AppreciationViewSet(viewsets.ModelViewSet):
from apps.accounts.models import User from apps.accounts.models import User
recipient = User.objects.get(id=recipient_id) recipient = User.objects.get(id=recipient_id)
content_type = ContentType.objects.get_for_model(User) content_type = ContentType.objects.get_for_model(User)
else: # physician else: # staff
from apps.organizations.models import Physician from apps.organizations.models import Staff
recipient = Physician.objects.get(id=recipient_id) recipient = Staff.objects.get(id=recipient_id)
content_type = ContentType.objects.get_for_model(Physician) content_type = ContentType.objects.get_for_model(Staff)
# Get hospital # Get hospital
from apps.organizations.models import Hospital from apps.organizations.models import Hospital
@ -243,10 +243,10 @@ class AppreciationViewSet(viewsets.ModelViewSet):
# Get user's appreciations # Get user's appreciations
user_content_type = ContentType.objects.get_for_model(request.user) user_content_type = ContentType.objects.get_for_model(request.user)
# Check if user has physician profile # Check if user has staff profile
physician = None staff = None
if hasattr(request.user, 'physician_profile'): if hasattr(request.user, 'staff_profile'):
physician = request.user.physician_profile staff = request.user.staff_profile
# Build query # Build query
queryset = self.get_queryset().filter( queryset = self.get_queryset().filter(
@ -256,11 +256,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
) )
) )
if physician: if staff:
physician_content_type = ContentType.objects.get_for_model(type(physician)) staff_content_type = ContentType.objects.get_for_model(type(staff))
queryset |= self.get_queryset().filter( queryset |= self.get_queryset().filter(
recipient_content_type=physician_content_type, recipient_content_type=staff_content_type,
recipient_object_id=physician.id recipient_object_id=staff.id
) )
# Paginate # Paginate
@ -440,11 +440,11 @@ class UserBadgeViewSet(viewsets.ReadOnlyModelViewSet):
# Get user's content type # Get user's content type
user_content_type = ContentType.objects.get_for_model(user) user_content_type = ContentType.objects.get_for_model(user)
# Filter by user or user's physician profile # Filter by user or user's staff profile
physician = None staff = None
if hasattr(user, 'physician_profile'): if hasattr(user, 'staff_profile'):
physician = user.physician_profile staff = user.staff_profile
physician_content_type = ContentType.objects.get_for_model(type(physician)) staff_content_type = ContentType.objects.get_for_model(type(staff))
queryset = queryset.filter( queryset = queryset.filter(
Q( Q(
@ -453,10 +453,10 @@ class UserBadgeViewSet(viewsets.ReadOnlyModelViewSet):
) )
) )
if physician: if staff:
queryset |= queryset.filter( queryset |= queryset.filter(
recipient_content_type=physician_content_type, recipient_content_type=staff_content_type,
recipient_object_id=physician.id recipient_object_id=staff.id
) )
return queryset.select_related('badge') return queryset.select_related('badge')

View File

@ -32,19 +32,27 @@ def handle_complaint_created(sender, instance, created, **kwargs):
send_complaint_notification, send_complaint_notification,
) )
# Trigger AI analysis (determines severity and priority) # Try to trigger async tasks, but don't fail if Redis/Celery is unavailable
analyze_complaint_with_ai.delay(str(instance.id)) try:
# Trigger AI analysis (determines severity and priority)
analyze_complaint_with_ai.delay(str(instance.id))
# Trigger PX Action creation (if configured) # Trigger PX Action creation (if configured)
create_action_from_complaint.delay(str(instance.id)) create_action_from_complaint.delay(str(instance.id))
# Send notification # Send notification
send_complaint_notification.delay( send_complaint_notification.delay(
complaint_id=str(instance.id), complaint_id=str(instance.id),
event_type='created' event_type='created'
) )
logger.info(f"Complaint created: {instance.id} - {instance.title}") logger.info(f"Complaint created: {instance.id} - {instance.title} - Async tasks queued")
except Exception as e:
# Log the error but don't prevent complaint creation
logger.warning(
f"Complaint created: {instance.id} - {instance.title} - "
f"Async tasks could not be queued (Celery/Redis unavailable): {e}"
)
@receiver(post_save, sender=SurveyInstance) @receiver(post_save, sender=SurveyInstance)
@ -60,12 +68,19 @@ def handle_survey_completed(sender, instance, created, **kwargs):
if instance.metadata.get('complaint_id'): if instance.metadata.get('complaint_id'):
from apps.complaints.tasks import check_resolution_survey_threshold from apps.complaints.tasks import check_resolution_survey_threshold
check_resolution_survey_threshold.delay( try:
survey_instance_id=str(instance.id), check_resolution_survey_threshold.delay(
complaint_id=instance.metadata['complaint_id'] survey_instance_id=str(instance.id),
) complaint_id=instance.metadata['complaint_id']
)
logger.info( logger.info(
f"Resolution survey completed for complaint {instance.metadata['complaint_id']}: " f"Resolution survey completed for complaint {instance.metadata['complaint_id']}: "
f"Score = {instance.total_score}" f"Score = {instance.total_score} - Async task queued"
) )
except Exception as e:
# Log the error but don't prevent survey completion
logger.warning(
f"Resolution survey completed for complaint {instance.metadata['complaint_id']}: "
f"Score = {instance.total_score} - Async task could not be queued (Celery/Redis unavailable): {e}"
)

View File

@ -53,7 +53,7 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
"""Journey stage instance serializer""" """Journey stage instance serializer"""
stage_name = serializers.CharField(source='stage_template.name', read_only=True) stage_name = serializers.CharField(source='stage_template.name', read_only=True)
stage_order = serializers.IntegerField(source='stage_template.order', read_only=True) stage_order = serializers.IntegerField(source='stage_template.order', read_only=True)
physician_name = serializers.SerializerMethodField() staff_name = serializers.SerializerMethodField()
department_name = serializers.CharField(source='department.name', read_only=True) department_name = serializers.CharField(source='department.name', read_only=True)
survey_status = serializers.CharField(source='survey_instance.status', read_only=True) survey_status = serializers.CharField(source='survey_instance.status', read_only=True)
@ -62,7 +62,7 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'journey_instance', 'stage_template', 'stage_name', 'stage_order', 'id', 'journey_instance', 'stage_template', 'stage_name', 'stage_order',
'status', 'completed_at', 'completed_by_event', 'status', 'completed_at', 'completed_by_event',
'physician', 'physician_name', 'department', 'department_name', 'staff', 'staff_name', 'department', 'department_name',
'survey_instance', 'survey_status', 'survey_sent_at', 'survey_instance', 'survey_status', 'survey_sent_at',
'metadata', 'created_at', 'updated_at' 'metadata', 'created_at', 'updated_at'
] ]
@ -72,10 +72,10 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
'created_at', 'updated_at' 'created_at', 'updated_at'
] ]
def get_physician_name(self, obj): def get_staff_name(self, obj):
"""Get physician full name""" """Get staff full name"""
if obj.physician: if obj.staff:
return obj.physician.get_full_name() return obj.staff.get_full_name()
return None return None

View File

@ -36,7 +36,7 @@ def journey_instance_list(request):
'department' 'department'
).prefetch_related( ).prefetch_related(
'stage_instances__stage_template', 'stage_instances__stage_template',
'stage_instances__physician', 'stage_instances__staff',
'stage_instances__survey_instance' 'stage_instances__survey_instance'
) )
@ -146,7 +146,7 @@ def journey_instance_detail(request, pk):
'department' 'department'
).prefetch_related( ).prefetch_related(
'stage_instances__stage_template', 'stage_instances__stage_template',
'stage_instances__physician', 'stage_instances__staff',
'stage_instances__department', 'stage_instances__department',
'stage_instances__survey_instance', 'stage_instances__survey_instance',
'stage_instances__completed_by_event' 'stage_instances__completed_by_event'

BIN
dump.rdb

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PX360 1.0\n" "Project-Id-Version: PX360 1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-08 10:47+0300\n" "POT-Creation-Date: 2026-01-08 15:08+0300\n"
"PO-Revision-Date: 2025-12-15 12:30+0300\n" "PO-Revision-Date: 2025-12-15 12:30+0300\n"
"Last-Translator: PX360 Team\n" "Last-Translator: PX360 Team\n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
@ -983,6 +983,8 @@ msgstr "اسم العائلة"
#: templates/callcenter/complaint_form.html:274 #: templates/callcenter/complaint_form.html:274
#: templates/callcenter/inquiry_form.html:233 #: templates/callcenter/inquiry_form.html:233
#: templates/complaints/complaint_detail.html:847 #: templates/complaints/complaint_detail.html:847
#: templates/complaints/complaint_detail.html:904
#: templates/complaints/complaint_detail.html:1060
#: templates/complaints/complaint_form.html:266 #: templates/complaints/complaint_form.html:266
#: templates/complaints/inquiry_form.html:237 #: templates/complaints/inquiry_form.html:237
#: templates/observations/category_form.html:91 #: templates/observations/category_form.html:91
@ -1626,6 +1628,7 @@ msgstr "النتيجة"
#: templates/ai_engine/sentiment_detail.html:68 #: templates/ai_engine/sentiment_detail.html:68
#: templates/ai_engine/sentiment_list.html:125 #: templates/ai_engine/sentiment_list.html:125
#: templates/ai_engine/tags/sentiment_card.html:22 #: templates/ai_engine/tags/sentiment_card.html:22
#: templates/complaints/complaint_detail.html:410
msgid "Confidence" msgid "Confidence"
msgstr "الثقة" msgstr "الثقة"
@ -1891,6 +1894,7 @@ msgid "PX Command Center"
msgstr "مركز قيادة تجربة المرضى" msgstr "مركز قيادة تجربة المرضى"
#: templates/analytics/command_center.html:99 #: templates/analytics/command_center.html:99
#: templates/complaints/complaint_detail.html:942
msgid "Loading..." msgid "Loading..."
msgstr "جارٍ التحميل..." msgstr "جارٍ التحميل..."
@ -1970,6 +1974,7 @@ msgid "All Hospitals"
msgstr "جميع المستشفيات" msgstr "جميع المستشفيات"
#: templates/analytics/command_center.html:188 #: templates/analytics/command_center.html:188
#: templates/complaints/complaint_detail.html:929
#: templates/complaints/complaint_list.html:258 #: templates/complaints/complaint_list.html:258
#: templates/observations/observation_list.html:234 #: templates/observations/observation_list.html:234
#: templates/physicians/leaderboard.html:94 #: templates/physicians/leaderboard.html:94
@ -2348,6 +2353,7 @@ msgstr "نوع المستلم"
#: templates/appreciation/appreciation_send_form.html:57 #: templates/appreciation/appreciation_send_form.html:57
#: templates/appreciation/leaderboard.html:95 #: templates/appreciation/leaderboard.html:95
#: templates/complaints/complaint_detail.html:986
msgid "Recipient" msgid "Recipient"
msgstr "المستلم" msgstr "المستلم"
@ -3695,10 +3701,88 @@ msgstr "المرفقات"
msgid "PX Actions" msgid "PX Actions"
msgstr "إجراءات تجربة المريض" msgstr "إجراءات تجربة المريض"
#: templates/complaints/complaint_detail.html:264
msgid "AI Mapped"
msgstr "تم التعيين بواسطة الذكاء الاصطناعي"
#: templates/complaints/complaint_detail.html:276 #: templates/complaints/complaint_detail.html:276
msgid "Staff Member" msgid "Staff Member"
msgstr "موظف" msgstr "موظف"
#: templates/complaints/complaint_detail.html:290
msgid "AI Matched"
msgstr "مطابق بالذكاء الاصطناعي"
#: templates/complaints/complaint_detail.html:296
msgid "Extracted from complaint"
msgstr "مستخرج من الشكوى"
#: templates/complaints/complaint_detail.html:298
#: templates/complaints/complaint_detail.html:350
msgid "confidence"
msgstr "مستوى الثقة"
#: templates/complaints/complaint_detail.html:313
msgid "Staff Suggestions"
msgstr "اقتراحات الموظفين"
#: templates/complaints/complaint_detail.html:315
msgid "Needs Review"
msgstr "بحاجة إلى مراجعة"
#: templates/complaints/complaint_detail.html:321
msgid "AI extracted name"
msgstr "اسم مستخرج بالذكاء الاصطناعي"
#: templates/complaints/complaint_detail.html:322
msgid "potential match"
msgstr "تطابق محتمل"
#: templates/complaints/complaint_detail.html:322
msgid "No matches found"
msgstr "لم يتم العثور على تطابقات"
#: templates/complaints/complaint_detail.html:354
msgid "Currently assigned"
msgstr "مُعيّن حاليًا"
#: templates/complaints/complaint_detail.html:358
msgid "Select"
msgstr "اختيار"
#: templates/complaints/complaint_detail.html:369
msgid "Search All Staff"
msgstr "البحث في جميع الموظفين"
#: templates/complaints/complaint_detail.html:392
msgid "AI Analysis"
msgstr "تحليل الذكاء الاصطناعي"
#: templates/complaints/complaint_detail.html:394
msgid "AI Generated"
msgstr "تم إنشاؤه بالذكاء الاصطناعي"
#: templates/complaints/complaint_detail.html:400
msgid "Emotion Analysis"
msgstr "تحليل المشاعر"
#: templates/complaints/complaint_detail.html:415
msgid "Intensity"
msgstr "الشدة"
#: templates/complaints/complaint_detail.html:439
msgid "Summary"
msgstr "ملخص"
#: templates/complaints/complaint_detail.html:449
msgid "Suggested Action"
msgstr "الإجراء المقترح"
#: templates/complaints/complaint_detail.html:455
#: templates/observations/convert_to_action.html:94
msgid "Create PX Action"
msgstr "إنشاء إجراء PX"
#: templates/complaints/complaint_detail.html:465 #: templates/complaints/complaint_detail.html:465
msgid "Resolution" msgid "Resolution"
msgstr "الحل" msgstr "الحل"
@ -3810,10 +3894,132 @@ msgstr "تصعيد الشكوى"
msgid "This will escalate" msgid "This will escalate"
msgstr "سيتم التصعيد" msgstr "سيتم التصعيد"
#: templates/complaints/complaint_detail.html:838
#, fuzzy
#| msgid "Checklist Items Management"
msgid "complaint to higher management"
msgstr "إدارة عناصر قائمة التحقق"
#: templates/complaints/complaint_detail.html:843 #: templates/complaints/complaint_detail.html:843
msgid "Explain why this complaint needs escalation..." msgid "Explain why this complaint needs escalation..."
msgstr "اشرح سبب حاجة هذه الشكوى إلى التصعيد..." msgstr "اشرح سبب حاجة هذه الشكوى إلى التصعيد..."
#: templates/complaints/complaint_detail.html:863
#, fuzzy
#| msgid "Create PX Action"
msgid "Create PX Action from AI Suggestion"
msgstr "إنشاء إجراء PX"
#: templates/complaints/complaint_detail.html:870
msgid "This will create a PX Action based on the AI-suggested action for this complaint"
msgstr "سيؤدي هذا إلى إنشاء إجراء PX بناءً على الإجراء المقترح بالذكاء الاصطناعي لهذه الشكوى"
msgid "Auto-mapped Category"
msgstr "فئة معينة تلقائيًا"
msgid "Category automatically mapped from complaint category"
msgstr "تم تعيين الفئة تلقائيًا من فئة الشكوى"
#: templates/complaints/complaint_detail.html:886
#, fuzzy
#| msgid "Rating (Optional)"
msgid "Assign To (Optional)"
msgstr "التقييم (اختياري)"
#: templates/complaints/complaint_detail.html:888
#, fuzzy
#| msgid "Unassigned"
msgid "Leave unassigned"
msgstr "غير معين"
#: templates/complaints/complaint_detail.html:893
msgid "If left unassigned, you can assign the action later"
msgstr "إذا تُرك بدون تعيين، يمكنك تعيين الإجراء لاحقًا"
#: templates/complaints/complaint_detail.html:897
#, fuzzy
#| msgid "Description"
msgid "Action Description"
msgstr "الوصف"
#: templates/complaints/complaint_detail.html:906
#, fuzzy
#| msgid "Create PX Action"
msgid "Create Action"
msgstr "إنشاء إجراء PX"
#: templates/complaints/complaint_detail.html:919
#, fuzzy
#| msgid "Staff Member"
msgid "Select Staff Member"
msgstr "موظف"
#: templates/complaints/complaint_detail.html:927
#, fuzzy
#| msgid "Compliance by Department"
msgid "Filter by Department"
msgstr "الامتثال حسب القسم"
#: templates/complaints/complaint_detail.html:933
#, fuzzy
#| msgid "Search All Staff"
msgid "Search Staff"
msgstr "البحث في جميع الموظفين"
#: templates/complaints/complaint_detail.html:963
#, fuzzy
#| msgid "Send Notification"
msgid "Send Complaint Notification"
msgstr "إرسال إشعار"
#: templates/complaints/complaint_detail.html:972
#, fuzzy
#| msgid "Summary"
msgid "AI Summary"
msgstr "ملخص"
#: templates/complaints/complaint_detail.html:973
msgid "you can edit this before sending"
msgstr "يمكنك تعديل هذا قبل الإرسال"
msgid "This is AI-generated summary. You can edit it before sending"
msgstr "هذا ملخص تم إنشاؤه بالذكاء الاصطناعي. يمكنك تعديله قبل الإرسال"
msgid "Staff Member Assigned"
msgstr "تم تعيين الموظف"
msgid "This staff member has no user account in the system"
msgstr "هذا الموظف لا يملك حساب مستخدم في النظام"
#: templates/complaints/complaint_detail.html:1019
#, fuzzy
#| msgid "Department View"
msgid "Department Head of"
msgstr "عرض الأقسام"
#: templates/complaints/complaint_detail.html:1025
#: templates/complaints/complaint_detail.html:1042
#, fuzzy
#| msgid "No rating history available"
msgid "No recipient available"
msgstr "لا يوجد سجل تقييمات"
#: templates/complaints/complaint_detail.html:1026
msgid "The assigned staff has no user account and no department manager is set"
msgstr "الموظف المعيّن لا يملك حساب مستخدم ولا يوجد مدير قسم محدد"
msgid "Manager of"
msgstr "مدير"
msgid "No staff or department manager assigned to this complaint"
msgstr "لم يتم تعيين موظف أو مدير قسم لهذه الشكوى"
msgid "Additional Message (Optional)"
msgstr "رسالة إضافية (اختياري)"
msgid "Send Email"
msgstr "إرسال البريد الإلكتروني"
#: templates/complaints/complaint_form.html:5 #: templates/complaints/complaint_form.html:5
#: templates/complaints/complaint_list.html:94 #: templates/complaints/complaint_list.html:94
msgid "New Complaint" msgid "New Complaint"
@ -5005,10 +5211,6 @@ msgstr "تعيين إلى القسم"
msgid "Assign to User" msgid "Assign to User"
msgstr "تعيين إلى المستخدم" msgstr "تعيين إلى المستخدم"
#: templates/observations/convert_to_action.html:94
msgid "Create PX Action"
msgstr "إنشاء إجراء PX"
#: templates/observations/observation_detail.html:5 #: templates/observations/observation_detail.html:5
msgid "Observation Detail" msgid "Observation Detail"
msgstr "تفاصيل الملاحظة" msgstr "تفاصيل الملاحظة"

View File

@ -261,7 +261,7 @@
</div> </div>
{% if complaint.metadata.ai_analysis.old_department %} {% if complaint.metadata.ai_analysis.old_department %}
<span class="badge bg-info ms-auto"> <span class="badge bg-info ms-auto">
<i class="bi bi-robot me-1"></i>AI Mapped <i class="bi bi-robot me-1"></i>{{ _("AI Mapped")}}
</span> </span>
{% endif %} {% endif %}
</div> </div>
@ -287,15 +287,15 @@
{% endif %} {% endif %}
</div> </div>
<span class="badge bg-success ms-auto"> <span class="badge bg-success ms-auto">
<i class="bi bi-robot me-1"></i>AI Matched <i class="bi bi-robot me-1"></i>{{ _("AI Matched")}}
</span> </span>
</div> </div>
{% if complaint.metadata.ai_analysis.extracted_staff_name %} {% if complaint.metadata.ai_analysis.extracted_staff_name %}
<small class="text-muted mt-1 d-block"> <small class="text-muted mt-1 d-block">
<i class="bi bi-lightning me-1"></i> <i class="bi bi-lightning me-1"></i>
Extracted from complaint: "{{ complaint.metadata.ai_analysis.extracted_staff_name }}" {{ _("Extracted from complaint")}}: "{{ complaint.metadata.ai_analysis.extracted_staff_name }}"
{% if complaint.metadata.ai_analysis.staff_confidence %} {% if complaint.metadata.ai_analysis.staff_confidence %}
(confidence: {{ complaint.metadata.ai_analysis.staff_confidence|floatformat:0 }}%) ({{ _("confidence") }}: {{ complaint.metadata.ai_analysis.staff_confidence|floatformat:0 }}%)
{% endif %} {% endif %}
</small> </small>
{% endif %} {% endif %}
@ -310,16 +310,16 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="info-label"> <div class="info-label">
<i class="bi bi-people me-1"></i> <i class="bi bi-people me-1"></i>
Staff Suggestions {{ _("Staff Suggestions")}}
{% if complaint.metadata.ai_analysis.needs_staff_review %} {% if complaint.metadata.ai_analysis.needs_staff_review %}
<span class="badge bg-warning ms-2">Needs Review</span> <span class="badge bg-warning ms-2">{{ _("Needs Review")}}</span>
{% endif %} {% endif %}
</div> </div>
<div class="info-value"> <div class="info-value">
{% if complaint.metadata.ai_analysis.extracted_staff_name %} {% if complaint.metadata.ai_analysis.extracted_staff_name %}
<p class="text-muted mb-2"> <p class="text-muted mb-2">
AI extracted name: <strong>"{{ complaint.metadata.ai_analysis.extracted_staff_name }}"</strong> {{ _("AI extracted name")}}: <strong>"{{ complaint.metadata.ai_analysis.extracted_staff_name }}"</strong>
({% if complaint.metadata.ai_analysis.staff_match_count %}{{ complaint.metadata.ai_analysis.staff_match_count }} potential match{{ complaint.metadata.ai_analysis.staff_match_count|pluralize }}{% else %}No matches found{% endif %}) ({% if complaint.metadata.ai_analysis.staff_match_count %}{{ complaint.metadata.ai_analysis.staff_match_count }}{{ _("potential match")}}{{ complaint.metadata.ai_analysis.staff_match_count|pluralize }}{% else %}{{ _("No matches found")}}{% endif %})
</p> </p>
{% endif %} {% endif %}
@ -347,15 +347,15 @@
</div> </div>
<div class="text-end"> <div class="text-end">
<span class="badge {% if staff_match.confidence >= 0.7 %}bg-success{% elif staff_match.confidence >= 0.5 %}bg-warning{% else %}bg-danger{% endif %}"> <span class="badge {% if staff_match.confidence >= 0.7 %}bg-success{% elif staff_match.confidence >= 0.5 %}bg-warning{% else %}bg-danger{% endif %}">
{{ staff_match.confidence|mul:100|floatformat:0 }}% confidence {{ staff_match.confidence|mul:100|floatformat:0 }}% {{ _("confidence") }}
</span> </span>
{% if complaint.staff and staff_match.id == complaint.staff.id|stringformat:"s" %} {% if complaint.staff and staff_match.id == complaint.staff.id|stringformat:"s" %}
<div class="small text-success mt-1"> <div class="small text-success mt-1">
<i class="bi bi-check-circle-fill"></i> Currently assigned <i class="bi bi-check-circle-fill"></i> {{ _("Currently assigned")}}
</div> </div>
{% elif not complaint.staff %} {% elif not complaint.staff %}
<button class="btn btn-sm btn-outline-primary mt-1" onclick="assignStaff('{{ staff_match.id }}', '{{ staff_match.name_en }}')"> <button class="btn btn-sm btn-outline-primary mt-1" onclick="assignStaff('{{ staff_match.id }}', '{{ staff_match.name_en }}')">
<i class="bi bi-person-plus"></i> Select <i class="bi bi-person-plus"></i> {{ _("Select") }}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -366,7 +366,7 @@
<div class="mt-3"> <div class="mt-3">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#staffSelectionModal"> <button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#staffSelectionModal">
<i class="bi bi-search me-1"></i> Search All Staff <i class="bi bi-search me-1"></i> {{ _("Search All Staff")}}
</button> </button>
</div> </div>
</div> </div>
@ -384,20 +384,20 @@
</div> </div>
<!-- AI Analysis Section --> <!-- AI Analysis Section -->
{% if complaint.short_description or complaint.suggested_action %} {% if complaint.short_description_ar or complaint.suggested_action_ar %}
<hr> <hr>
<div class="mb-3"> <div class="mb-3">
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center mb-3">
<div class="info-label mb-0 me-2"> <div class="info-label mb-0 me-2">
<i class="bi bi-robot me-1"></i>AI Analysis <i class="bi bi-robot me-1"></i>{{ _("AI Analysis")}}
</div> </div>
<span class="badge bg-info">AI Generated</span> <span class="badge bg-info">{{ _("AI Generated")}}</span>
</div> </div>
<!-- Emotion Analysis --> <!-- Emotion Analysis -->
{% if complaint.emotion %} {% if complaint.emotion %}
<div class="mb-3"> <div class="mb-3">
<div class="info-label" style="font-size: 0.8rem;">Emotion Analysis</div> <div class="info-label" style="font-size: 0.8rem;">{{ _("Emotion Analysis")}}</div>
<div class="card mb-0" style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-left: 4px solid #6c757d;"> <div class="card mb-0" style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-left: 4px solid #6c757d;">
<div class="card-body py-2"> <div class="card-body py-2">
<div class="row align-items-center mb-2"> <div class="row align-items-center mb-2">
@ -407,12 +407,12 @@
</span> </span>
</div> </div>
<div class="col-md-6 text-md-end"> <div class="col-md-6 text-md-end">
<small class="text-muted">Confidence: {{ complaint.emotion_confidence|mul:100|floatformat:0 }}%</small> <small class="text-muted">{{ _("Confidence") }}: {{ complaint.emotion_confidence|mul:100|floatformat:0 }}%</small>
</div> </div>
</div> </div>
<div class="mb-1"> <div class="mb-1">
<div class="d-flex justify-content-between mb-1"> <div class="d-flex justify-content-between mb-1">
<small class="text-muted">Intensity</small> <small class="text-muted">{{ _("Intensity") }}</small>
<small>{{ complaint.emotion_intensity|floatformat:2 }} / 1.0</small> <small>{{ complaint.emotion_intensity|floatformat:2 }} / 1.0</small>
</div> </div>
<div class="progress" style="height: 6px;"> <div class="progress" style="height: 6px;">
@ -434,25 +434,25 @@
</div> </div>
{% endif %} {% endif %}
{% if complaint.short_description %} {% if complaint.short_description_ar %}
<div class="mb-3"> <div class="mb-3">
<div class="info-label" style="font-size: 0.8rem;">Summary</div> <div class="info-label" style="font-size: 0.8rem;">{{ _("Summary") }}</div>
<div class="alert alert-light border-info mb-0" style="background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);"> <div class="alert alert-light border-info mb-0" style="background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);">
<i class="bi bi-info-circle me-1 text-info"></i> <i class="bi bi-info-circle me-1 text-info"></i>
<small>{{ complaint.short_description }}</small> <small>{{ complaint.short_description_ar }}</small>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if complaint.suggested_action %} {% if complaint.suggested_action_ar %}
<div class="mb-0"> <div class="mb-0">
<div class="info-label" style="font-size: 0.8rem;">Suggested Action</div> <div class="info-label" style="font-size: 0.8rem;">{{ _("Suggested Action")}}</div>
<div class="alert alert-success mb-0" style="background: linear-gradient(135deg, #e8f5e9 0%, #e1f5fe 100%); border-color: #4caf50;"> <div class="alert alert-success mb-0" style="background: linear-gradient(135deg, #e8f5e9 0%, #e1f5fe 100%); border-color: #4caf50;">
<i class="bi bi-lightning me-1 text-success"></i> <i class="bi bi-lightning me-1 text-success"></i>
<small>{{ complaint.suggested_action }}</small> <small>{{ complaint.suggested_action_ar }}</small>
</div> </div>
<button type="button" class="btn btn-sm btn-primary mt-2" data-bs-toggle="modal" data-bs-target="#createActionModal"> <button type="button" class="btn btn-sm btn-primary mt-2" data-bs-toggle="modal" data-bs-target="#createActionModal">
<i class="bi bi-plus-circle me-1"></i> Create PX Action <i class="bi bi-plus-circle me-1"></i> {{ _("Create PX Action")}}
</button> </button>
</div> </div>
{% endif %} {% endif %}
@ -835,7 +835,7 @@
<div class="modal-body"> <div class="modal-body">
<div class="alert alert-warning"> <div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i> <i class="bi bi-exclamation-triangle me-2"></i>
{{ _("This will escalate")}} complaint to higher management}}. {{ _("This will escalate")}} {{ _("complaint to higher management")}}}}.
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{% trans "Reason for Escalation" %}</label> <label class="form-label">{% trans "Reason for Escalation" %}</label>
@ -860,18 +860,18 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<i class="bi bi-lightning-fill me-2"></i>Create PX Action from AI Suggestion <i class="bi bi-lightning-fill me-2"></i>{{ _("Create PX Action from AI Suggestion")}}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="alert alert-info"> <div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i> <i class="bi bi-info-circle me-2"></i>
This will create a PX Action based on the AI-suggested action for this complaint. {{ _("This will create a PX Action based on the AI-suggested action for this complaint")}}.
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Auto-mapped Category</label> <label class="form-label">{{ _("Auto-mapped Category")}}</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-text"> <span class="input-group-text">
<i class="bi bi-tag"></i> <i class="bi bi-tag"></i>
@ -879,31 +879,31 @@
<input type="text" id="autoMappedCategory" class="form-control" readonly <input type="text" id="autoMappedCategory" class="form-control" readonly
value="{% if complaint.category %}{{ complaint.category.name_en }}{% else %}Other{% endif %}"> value="{% if complaint.category %}{{ complaint.category.name_en }}{% else %}Other{% endif %}">
</div> </div>
<small class="text-muted">Category automatically mapped from complaint category</small> <small class="text-muted">{{ _("Category automatically mapped from complaint category")}}</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Assign To (Optional)</label> <label class="form-label">{{ _("Assign To (Optional)")}}</label>
<select id="actionAssignTo" class="form-select"> <select id="actionAssignTo" class="form-select">
<option value="">Leave unassigned</option> <option value="">{{ _("Leave unassigned")}}</option>
{% for user_obj in assignable_users %} {% for user_obj in assignable_users %}
<option value="{{ user_obj.id }}">{{ user_obj.get_full_name }}</option> <option value="{{ user_obj.id }}">{{ user_obj.get_full_name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<small class="text-muted">If left unassigned, you can assign the action later.</small> <small class="text-muted">{{ _("If left unassigned, you can assign the action later")}}.</small>
</div> </div>
<div class="mb-0"> <div class="mb-0">
<label class="form-label">Action Description</label> <label class="form-label">{{ _("Action Description")}}</label>
<div class="alert alert-success mb-0"> <div class="alert alert-success mb-0">
<small>{{ complaint.suggested_action }}</small> <small>{{ complaint.suggested_action }}</small>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Cancel") }}</button>
<button type="button" id="createActionBtn" class="btn btn-primary" onclick="createAction()"> <button type="button" id="createActionBtn" class="btn btn-primary" onclick="createAction()">
<i class="bi bi-plus-circle me-1"></i>Create Action <i class="bi bi-plus-circle me-1"></i>{{ _("Create Action")}}
</button> </button>
</div> </div>
</div> </div>
@ -916,7 +916,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<i class="bi bi-person-badge me-2"></i>Select Staff Member <i class="bi bi-person-badge me-2"></i>{{ _("Select Staff Member")}}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
@ -924,13 +924,13 @@
<!-- Department Filter --> <!-- Department Filter -->
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Filter by Department</label> <label class="form-label">{{ _("Filter by Department")}}</label>
<select id="staffDepartmentFilter" class="form-select"> <select id="staffDepartmentFilter" class="form-select">
<option value="">All Departments</option> <option value="">{{ _("All Departments")}}</option>
</select> </select>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Search Staff</label> <label class="form-label">{{ _("Search Staff")}}</label>
<input type="text" id="staffSearchInput" class="form-control" placeholder="Search by name or job title..."> <input type="text" id="staffSearchInput" class="form-control" placeholder="Search by name or job title...">
</div> </div>
</div> </div>
@ -939,7 +939,7 @@
<div id="staffListContainer" class="border rounded" style="max-height: 400px; overflow-y: auto;"> <div id="staffListContainer" class="border rounded" style="max-height: 400px; overflow-y: auto;">
<div class="text-center py-4"> <div class="text-center py-4">
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">{{ _("Loading...")}}</span>
</div> </div>
</div> </div>
</div> </div>
@ -960,7 +960,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<i class="bi bi-envelope me-2"></i>Send Complaint Notification <i class="bi bi-envelope me-2"></i>{{ _("Send Complaint Notification")}}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
@ -969,13 +969,13 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label"> <label class="form-label">
<i class="bi bi-robot me-1"></i> <i class="bi bi-robot me-1"></i>
AI Summary {{ _("AI Summary")}}
<small class="text-muted">(you can edit this before sending)</small> <small class="text-muted">({{ _("you can edit this before sending")}})</small>
</label> </label>
<textarea id="emailMessage" class="form-control" rows="5" <textarea id="emailMessage" class="form-control" rows="5"
placeholder="Enter message to send...">{{ complaint.short_description }}</textarea> placeholder="Enter message to send...">{{ complaint.short_description }}</textarea>
<small class="text-muted"> <small class="text-muted">
This is AI-generated summary. You can edit it before sending. {{ _("This is AI-generated summary. You can edit it before sending")}}.
</small> </small>
</div> </div>
@ -983,7 +983,7 @@
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h6 class="card-title"> <h6 class="card-title">
<i class="bi bi-person-check me-1"></i>Recipient <i class="bi bi-person-check me-1"></i>{{ _("Recipient") }}
</h6> </h6>
{% if complaint.staff and complaint.staff.user %} {% if complaint.staff and complaint.staff.user %}
@ -1000,14 +1000,14 @@
<!-- Staff exists but has no user account --> <!-- Staff exists but has no user account -->
<div class="alert alert-warning mb-2"> <div class="alert alert-warning mb-2">
<i class="bi bi-exclamation-triangle-fill me-1"></i> <i class="bi bi-exclamation-triangle-fill me-1"></i>
<strong>Staff Member Assigned:</strong> {{ complaint.staff.get_full_name }} <strong>{{ _("Staff Member Assigned")}}:</strong> {{ complaint.staff.get_full_name }}
{% if complaint.staff.job_title %} {% if complaint.staff.job_title %}
<br><small class="text-muted">{{ complaint.staff.job_title }}</small> <br><small class="text-muted">{{ complaint.staff.job_title }}</small>
{% endif %} {% endif %}
<hr class="my-2"> <hr class="my-2">
<small class="text-muted"> <small class="text-muted">
<i class="bi bi-info-circle me-1"></i> <i class="bi bi-info-circle me-1"></i>
This staff member has no user account in the system. {{ _("This staff member has no user account in the system")}}.
</small> </small>
</div> </div>
@ -1016,14 +1016,14 @@
<div class="alert alert-info mb-0"> <div class="alert alert-info mb-0">
<i class="bi bi-person-badge me-1"></i> <i class="bi bi-person-badge me-1"></i>
<strong>Actual Recipient:</strong> {{ complaint.department.manager.get_full_name }} <strong>Actual Recipient:</strong> {{ complaint.department.manager.get_full_name }}
<br><small class="text-muted">Department Head of {{ complaint.department.name_en }}</small> <br><small class="text-muted">{{ _("Department Head of")}} {{ complaint.department.name_en }}</small>
</div> </div>
{% else %} {% else %}
<!-- No fallback recipient --> <!-- No fallback recipient -->
<div class="alert alert-danger mb-0"> <div class="alert alert-danger mb-0">
<i class="bi bi-x-circle-fill me-1"></i> <i class="bi bi-x-circle-fill me-1"></i>
<strong>No recipient available</strong> <strong>{{ _("No recipient available")}}</strong>
<br><small>The assigned staff has no user account and no department manager is set.</small> <br><small>{{ _("The assigned staff has no user account and no department manager is set")}}.</small>
</div> </div>
{% endif %} {% endif %}
@ -1032,15 +1032,15 @@
<div class="alert alert-info mb-0"> <div class="alert alert-info mb-0">
<i class="bi bi-person-badge me-1"></i> <i class="bi bi-person-badge me-1"></i>
<strong>Department Head:</strong> {{ complaint.department.manager.get_full_name }} <strong>Department Head:</strong> {{ complaint.department.manager.get_full_name }}
<br><small class="text-muted">Manager of {{ complaint.department.name_en }}</small> <br><small class="text-muted">{{ _("Manager of")}} {{ complaint.department.name_en }}</small>
</div> </div>
{% else %} {% else %}
<!-- No recipient at all --> <!-- No recipient at all -->
<div class="alert alert-danger mb-0"> <div class="alert alert-danger mb-0">
<i class="bi bi-exclamation-triangle-fill me-1"></i> <i class="bi bi-exclamation-triangle-fill me-1"></i>
<strong>No recipient available</strong> <strong>{{ _("No recipient available")}}</strong>
<br><small>No staff or department manager assigned to this complaint.</small> <br><small>{{ _("No staff or department manager assigned to this complaint")}}.</small>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -1049,7 +1049,7 @@
<!-- Optional Additional Message --> <!-- Optional Additional Message -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label"> <label class="form-label">
Additional Message (Optional) {{ _("Additional Message (Optional)")}}
</label> </label>
<textarea id="additionalMessage" class="form-control" rows="3" <textarea id="additionalMessage" class="form-control" rows="3"
placeholder="Add any additional notes to send..."></textarea> placeholder="Add any additional notes to send..."></textarea>
@ -1057,11 +1057,11 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel {{ _("Cancel") }}
</button> </button>
<button type="button" id="sendNotificationBtn" class="btn btn-primary" <button type="button" id="sendNotificationBtn" class="btn btn-primary"
onclick="sendNotification()"> onclick="sendNotification()">
<i class="bi bi-send me-1"></i>Send Email <i class="bi bi-send me-1"></i>{{ _("Send Email")}}
</button> </button>
</div> </div>
</div> </div>

View File

@ -150,7 +150,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for rating in top_physicians %} {% for rating in top_staff %}
<tr onclick="window.location=''" style="cursor: pointer;"> <tr onclick="window.location=''" style="cursor: pointer;">
<td> <td>
{% if forloop.counter == 1 %} {% if forloop.counter == 1 %}
@ -164,13 +164,13 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<strong>{{ rating.physician.get_full_name }}</strong><br> <strong>{{ rating.staff }}</strong><br>
<small class="text-muted">{{ rating.physician.license_number }}</small> <small class="text-muted">{{ rating.staff.license_number }}</small>
</td> </td>
<td>{{ rating.physician.specialization }}</td> <td>{{ rating.staff.specialization }}</td>
<td> <td>
{% if rating.physician.department %} {% if rating.staff.department %}
{{ rating.physician.department.name }} {{ rating.staff.department.name }}
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-muted">-</span>
{% endif %} {% endif %}

View File

@ -227,10 +227,10 @@
</div> </div>
{% endif %} {% endif %}
{% if stage.physician %} {% if stage.staff %}
<div class="stage-meta-item"> <div class="stage-meta-item">
<i class="bi bi-person-badge"></i> <i class="bi bi-person-badge"></i>
<small>Dr. {{ stage.physician.first_name }} {{ stage.physician.last_name }}</small> <small>{{ stage.staff.first_name }} {{ stage.staff.last_name }}</small>
</div> </div>
{% endif %} {% endif %}

View File

@ -125,7 +125,7 @@
</thead> </thead>
<tbody> <tbody>
{% for rating in dept_data.physicians %} {% for rating in dept_data.physicians %}
<tr onclick="window.location='{% url 'physicians:physician_detail' rating.physician.id %}'" style="cursor: pointer;"> <tr onclick="window.location='{% url 'physicians:physician_detail' rating.staff.id %}'" style="cursor: pointer;">
<td> <td>
{% if forloop.counter <= 3 %} {% if forloop.counter <= 3 %}
<strong class="text-primary">#{{ forloop.counter }}</strong> <strong class="text-primary">#{{ forloop.counter }}</strong>
@ -134,10 +134,10 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<strong>{{ rating.physician.get_full_name }}</strong><br> <strong>{{ rating.staff.get_full_name }}</strong><br>
<small class="text-muted">{{ rating.physician.license_number }}</small> <small class="text-muted">{{ rating.staff.license_number }}</small>
</td> </td>
<td>{{ rating.physician.specialization }}</td> <td>{{ rating.staff.specialization }}</td>
<td> <td>
<strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong> <strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong>
</td> </td>
@ -152,7 +152,7 @@
{% endif %} {% endif %}
</td> </td>
<td onclick="event.stopPropagation();"> <td onclick="event.stopPropagation();">
<a href="{% url 'physicians:physician_detail' rating.physician.id %}" <a href="{% url 'physicians:physician_detail' rating.staff.id %}"
class="btn btn-sm btn-outline-primary"> class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</a> </a>

View File

@ -109,23 +109,23 @@
</thead> </thead>
<tbody> <tbody>
{% for rating in ratings %} {% for rating in ratings %}
<tr onclick="window.location='{% url 'physicians:physician_detail' rating.physician.id %}'" style="cursor: pointer;"> <tr onclick="window.location='{% url 'physicians:physician_detail' rating.staff.id %}'" style="cursor: pointer;">
<td> <td>
<strong>{{ rating.year }}-{{ rating.month|stringformat:"02d" }}</strong> <strong>{{ rating.year }}-{{ rating.month|stringformat:"02d" }}</strong>
</td> </td>
<td> <td>
<strong>{{ rating.physician.get_full_name }}</strong><br> <strong>{{ rating.staff.get_full_name }}</strong><br>
<small class="text-muted">{{ rating.physician.license_number }}</small> <small class="text-muted">{{ rating.staff.license_number }}</small>
</td> </td>
<td>{{ rating.physician.specialization }}</td> <td>{{ rating.staff.specialization }}</td>
<td> <td>
{% if rating.physician.department %} {% if rating.staff.department %}
{{ rating.physician.department.name }} {{ rating.staff.department.name }}
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-muted">-</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ rating.physician.hospital.name }}</td> <td>{{ rating.staff.hospital.name }}</td>
<td> <td>
<h5 class="mb-0">{{ rating.average_rating|floatformat:2 }}</h5> <h5 class="mb-0">{{ rating.average_rating|floatformat:2 }}</h5>
</td> </td>
@ -159,7 +159,7 @@
</div> </div>
</td> </td>
<td onclick="event.stopPropagation();"> <td onclick="event.stopPropagation();">
<a href="{% url 'physicians:physician_detail' rating.physician.id %}" <a href="{% url 'physicians:physician_detail' rating.staff.id %}"
class="btn btn-sm btn-outline-primary"> class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</a> </a>

View File

@ -124,7 +124,7 @@
</thead> </thead>
<tbody> <tbody>
{% for rating in spec_data.physicians %} {% for rating in spec_data.physicians %}
<tr onclick="window.location='{% url 'physicians:physician_detail' rating.physician.id %}'" style="cursor: pointer;"> <tr onclick="window.location='{% url 'physicians:physician_detail' rating.staff.id %}'" style="cursor: pointer;">
<td> <td>
{% if forloop.counter <= 3 %} {% if forloop.counter <= 3 %}
<strong class="text-primary">#{{ forloop.counter }}</strong> <strong class="text-primary">#{{ forloop.counter }}</strong>
@ -133,17 +133,17 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<strong>{{ rating.physician.get_full_name }}</strong><br> <strong>{{ rating.staff.get_full_name }}</strong><br>
<small class="text-muted">{{ rating.physician.license_number }}</small> <small class="text-muted">{{ rating.staff.license_number }}</small>
</td> </td>
<td> <td>
{% if rating.physician.department %} {% if rating.staff.department %}
{{ rating.physician.department.name }} {{ rating.staff.department.name }}
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-muted">-</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ rating.physician.hospital.name }}</td> <td>{{ rating.staff.hospital.name }}</td>
<td> <td>
<strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong> <strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong>
</td> </td>
@ -151,7 +151,7 @@
<span class="badge bg-light text-dark">{{ rating.total_surveys }}</span> <span class="badge bg-light text-dark">{{ rating.total_surveys }}</span>
</td> </td>
<td onclick="event.stopPropagation();"> <td onclick="event.stopPropagation();">
<a href="{% url 'physicians:physician_detail' rating.physician.id %}" <a href="{% url 'physicians:physician_detail' rating.staff.id %}"
class="btn btn-sm btn-outline-primary"> class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</a> </a>