update
This commit is contained in:
parent
f8a0305caf
commit
867f60fed7
84
PHYSICIAN_REFERENCE_FIXES.md
Normal file
84
PHYSICIAN_REFERENCE_FIXES.md
Normal 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
|
||||
@ -107,7 +107,7 @@ class AppreciationSerializer(serializers.ModelSerializer):
|
||||
return "Unknown"
|
||||
|
||||
def get_recipient_type(self, obj):
|
||||
"""Get recipient type (user or physician)"""
|
||||
"""Get recipient type (user or staff)"""
|
||||
if obj.recipient_content_type:
|
||||
return obj.recipient_content_type.model
|
||||
return None
|
||||
@ -133,9 +133,9 @@ class AppreciationCreateSerializer(serializers.Serializer):
|
||||
"""Serializer for creating Appreciation"""
|
||||
|
||||
recipient_type = serializers.ChoiceField(
|
||||
choices=['user', 'physician'],
|
||||
choices=['user', 'staff'],
|
||||
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)
|
||||
category_id = serializers.UUIDField(required=False, allow_null=True)
|
||||
@ -176,17 +176,17 @@ class AppreciationCreateSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError({
|
||||
'recipient_id': 'User not found'
|
||||
})
|
||||
elif recipient_type == 'physician':
|
||||
from apps.organizations.models import Physician
|
||||
elif recipient_type == 'staff':
|
||||
from apps.organizations.models import Staff
|
||||
try:
|
||||
physician = Physician.objects.get(id=recipient_id)
|
||||
if physician.hospital_id != hospital_id:
|
||||
staff = Staff.objects.get(id=recipient_id)
|
||||
if staff.hospital_id != hospital_id:
|
||||
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({
|
||||
'recipient_id': 'Physician not found'
|
||||
'recipient_id': 'Staff not found'
|
||||
})
|
||||
|
||||
# Validate category if provided
|
||||
|
||||
@ -86,11 +86,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
# Get user's content type
|
||||
user_content_type = ContentType.objects.get_for_model(user)
|
||||
|
||||
# Get physician if user has a physician profile
|
||||
physician = None
|
||||
if hasattr(user, 'physician_profile'):
|
||||
physician = user.phician_profile
|
||||
physician_content_type = ContentType.objects.get_for_model(type(physician))
|
||||
# Get staff if user has a staff profile
|
||||
staff = None
|
||||
if hasattr(user, 'staff_profile'):
|
||||
staff = user.staff_profile
|
||||
staff_content_type = ContentType.objects.get_for_model(type(staff))
|
||||
|
||||
# Build visibility filter
|
||||
visibility_filter = (
|
||||
@ -101,11 +101,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
) # Received by user
|
||||
)
|
||||
|
||||
if physician:
|
||||
if staff:
|
||||
visibility_filter |= Q(
|
||||
recipient_content_type=physician_content_type,
|
||||
recipient_object_id=physician.id
|
||||
) # Received by physician
|
||||
recipient_content_type=staff_content_type,
|
||||
recipient_object_id=staff.id
|
||||
) # Received by staff
|
||||
|
||||
if user.department:
|
||||
visibility_filter |= Q(
|
||||
@ -135,9 +135,9 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
recipient_content_type=content_type,
|
||||
recipient_object_id=recipient_id
|
||||
)
|
||||
elif recipient_type == 'physician':
|
||||
from apps.organizations.models import Physician
|
||||
content_type = ContentType.objects.get_for_model(Physician)
|
||||
elif recipient_type == 'staff':
|
||||
from apps.organizations.models import Staff
|
||||
content_type = ContentType.objects.get_for_model(Staff)
|
||||
queryset = queryset.filter(
|
||||
recipient_content_type=content_type,
|
||||
recipient_object_id=recipient_id
|
||||
@ -173,10 +173,10 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
from apps.accounts.models import User
|
||||
recipient = User.objects.get(id=recipient_id)
|
||||
content_type = ContentType.objects.get_for_model(User)
|
||||
else: # physician
|
||||
from apps.organizations.models import Physician
|
||||
recipient = Physician.objects.get(id=recipient_id)
|
||||
content_type = ContentType.objects.get_for_model(Physician)
|
||||
else: # staff
|
||||
from apps.organizations.models import Staff
|
||||
recipient = Staff.objects.get(id=recipient_id)
|
||||
content_type = ContentType.objects.get_for_model(Staff)
|
||||
|
||||
# Get hospital
|
||||
from apps.organizations.models import Hospital
|
||||
@ -243,10 +243,10 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
# Get user's appreciations
|
||||
user_content_type = ContentType.objects.get_for_model(request.user)
|
||||
|
||||
# Check if user has physician profile
|
||||
physician = None
|
||||
if hasattr(request.user, 'physician_profile'):
|
||||
physician = request.user.physician_profile
|
||||
# Check if user has staff profile
|
||||
staff = None
|
||||
if hasattr(request.user, 'staff_profile'):
|
||||
staff = request.user.staff_profile
|
||||
|
||||
# Build query
|
||||
queryset = self.get_queryset().filter(
|
||||
@ -256,11 +256,11 @@ class AppreciationViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
if physician:
|
||||
physician_content_type = ContentType.objects.get_for_model(type(physician))
|
||||
if staff:
|
||||
staff_content_type = ContentType.objects.get_for_model(type(staff))
|
||||
queryset |= self.get_queryset().filter(
|
||||
recipient_content_type=physician_content_type,
|
||||
recipient_object_id=physician.id
|
||||
recipient_content_type=staff_content_type,
|
||||
recipient_object_id=staff.id
|
||||
)
|
||||
|
||||
# Paginate
|
||||
@ -440,11 +440,11 @@ class UserBadgeViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
# Get user's content type
|
||||
user_content_type = ContentType.objects.get_for_model(user)
|
||||
|
||||
# Filter by user or user's physician profile
|
||||
physician = None
|
||||
if hasattr(user, 'physician_profile'):
|
||||
physician = user.physician_profile
|
||||
physician_content_type = ContentType.objects.get_for_model(type(physician))
|
||||
# Filter by user or user's staff profile
|
||||
staff = None
|
||||
if hasattr(user, 'staff_profile'):
|
||||
staff = user.staff_profile
|
||||
staff_content_type = ContentType.objects.get_for_model(type(staff))
|
||||
|
||||
queryset = queryset.filter(
|
||||
Q(
|
||||
@ -453,10 +453,10 @@ class UserBadgeViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
if physician:
|
||||
if staff:
|
||||
queryset |= queryset.filter(
|
||||
recipient_content_type=physician_content_type,
|
||||
recipient_object_id=physician.id
|
||||
recipient_content_type=staff_content_type,
|
||||
recipient_object_id=staff.id
|
||||
)
|
||||
|
||||
return queryset.select_related('badge')
|
||||
|
||||
@ -32,19 +32,27 @@ def handle_complaint_created(sender, instance, created, **kwargs):
|
||||
send_complaint_notification,
|
||||
)
|
||||
|
||||
# Trigger AI analysis (determines severity and priority)
|
||||
analyze_complaint_with_ai.delay(str(instance.id))
|
||||
# Try to trigger async tasks, but don't fail if Redis/Celery is unavailable
|
||||
try:
|
||||
# Trigger AI analysis (determines severity and priority)
|
||||
analyze_complaint_with_ai.delay(str(instance.id))
|
||||
|
||||
# Trigger PX Action creation (if configured)
|
||||
create_action_from_complaint.delay(str(instance.id))
|
||||
# Trigger PX Action creation (if configured)
|
||||
create_action_from_complaint.delay(str(instance.id))
|
||||
|
||||
# Send notification
|
||||
send_complaint_notification.delay(
|
||||
complaint_id=str(instance.id),
|
||||
event_type='created'
|
||||
)
|
||||
# Send notification
|
||||
send_complaint_notification.delay(
|
||||
complaint_id=str(instance.id),
|
||||
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)
|
||||
@ -60,12 +68,19 @@ def handle_survey_completed(sender, instance, created, **kwargs):
|
||||
if instance.metadata.get('complaint_id'):
|
||||
from apps.complaints.tasks import check_resolution_survey_threshold
|
||||
|
||||
check_resolution_survey_threshold.delay(
|
||||
survey_instance_id=str(instance.id),
|
||||
complaint_id=instance.metadata['complaint_id']
|
||||
)
|
||||
try:
|
||||
check_resolution_survey_threshold.delay(
|
||||
survey_instance_id=str(instance.id),
|
||||
complaint_id=instance.metadata['complaint_id']
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Resolution survey completed for complaint {instance.metadata['complaint_id']}: "
|
||||
f"Score = {instance.total_score}"
|
||||
)
|
||||
logger.info(
|
||||
f"Resolution survey completed for complaint {instance.metadata['complaint_id']}: "
|
||||
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}"
|
||||
)
|
||||
|
||||
@ -53,7 +53,7 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
|
||||
"""Journey stage instance serializer"""
|
||||
stage_name = serializers.CharField(source='stage_template.name', 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)
|
||||
survey_status = serializers.CharField(source='survey_instance.status', read_only=True)
|
||||
|
||||
@ -62,7 +62,7 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
'id', 'journey_instance', 'stage_template', 'stage_name', 'stage_order',
|
||||
'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',
|
||||
'metadata', 'created_at', 'updated_at'
|
||||
]
|
||||
@ -72,10 +72,10 @@ class PatientJourneyStageInstanceSerializer(serializers.ModelSerializer):
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
def get_physician_name(self, obj):
|
||||
"""Get physician full name"""
|
||||
if obj.physician:
|
||||
return obj.physician.get_full_name()
|
||||
def get_staff_name(self, obj):
|
||||
"""Get staff full name"""
|
||||
if obj.staff:
|
||||
return obj.staff.get_full_name()
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ def journey_instance_list(request):
|
||||
'department'
|
||||
).prefetch_related(
|
||||
'stage_instances__stage_template',
|
||||
'stage_instances__physician',
|
||||
'stage_instances__staff',
|
||||
'stage_instances__survey_instance'
|
||||
)
|
||||
|
||||
@ -146,7 +146,7 @@ def journey_instance_detail(request, pk):
|
||||
'department'
|
||||
).prefetch_related(
|
||||
'stage_instances__stage_template',
|
||||
'stage_instances__physician',
|
||||
'stage_instances__staff',
|
||||
'stage_instances__department',
|
||||
'stage_instances__survey_instance',
|
||||
'stage_instances__completed_by_event'
|
||||
|
||||
Binary file not shown.
@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PX360 1.0\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"
|
||||
"Last-Translator: PX360 Team\n"
|
||||
"Language-Team: Arabic\n"
|
||||
@ -983,6 +983,8 @@ msgstr "اسم العائلة"
|
||||
#: templates/callcenter/complaint_form.html:274
|
||||
#: templates/callcenter/inquiry_form.html:233
|
||||
#: 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/inquiry_form.html:237
|
||||
#: templates/observations/category_form.html:91
|
||||
@ -1626,6 +1628,7 @@ msgstr "النتيجة"
|
||||
#: templates/ai_engine/sentiment_detail.html:68
|
||||
#: templates/ai_engine/sentiment_list.html:125
|
||||
#: templates/ai_engine/tags/sentiment_card.html:22
|
||||
#: templates/complaints/complaint_detail.html:410
|
||||
msgid "Confidence"
|
||||
msgstr "الثقة"
|
||||
|
||||
@ -1891,6 +1894,7 @@ msgid "PX Command Center"
|
||||
msgstr "مركز قيادة تجربة المرضى"
|
||||
|
||||
#: templates/analytics/command_center.html:99
|
||||
#: templates/complaints/complaint_detail.html:942
|
||||
msgid "Loading..."
|
||||
msgstr "جارٍ التحميل..."
|
||||
|
||||
@ -1970,6 +1974,7 @@ msgid "All Hospitals"
|
||||
msgstr "جميع المستشفيات"
|
||||
|
||||
#: templates/analytics/command_center.html:188
|
||||
#: templates/complaints/complaint_detail.html:929
|
||||
#: templates/complaints/complaint_list.html:258
|
||||
#: templates/observations/observation_list.html:234
|
||||
#: templates/physicians/leaderboard.html:94
|
||||
@ -2348,6 +2353,7 @@ msgstr "نوع المستلم"
|
||||
|
||||
#: templates/appreciation/appreciation_send_form.html:57
|
||||
#: templates/appreciation/leaderboard.html:95
|
||||
#: templates/complaints/complaint_detail.html:986
|
||||
msgid "Recipient"
|
||||
msgstr "المستلم"
|
||||
|
||||
@ -3695,10 +3701,88 @@ msgstr "المرفقات"
|
||||
msgid "PX Actions"
|
||||
msgstr "إجراءات تجربة المريض"
|
||||
|
||||
#: templates/complaints/complaint_detail.html:264
|
||||
msgid "AI Mapped"
|
||||
msgstr "تم التعيين بواسطة الذكاء الاصطناعي"
|
||||
|
||||
#: templates/complaints/complaint_detail.html:276
|
||||
msgid "Staff Member"
|
||||
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
|
||||
msgid "Resolution"
|
||||
msgstr "الحل"
|
||||
@ -3810,10 +3894,132 @@ msgstr "تصعيد الشكوى"
|
||||
msgid "This will escalate"
|
||||
msgstr "سيتم التصعيد"
|
||||
|
||||
#: templates/complaints/complaint_detail.html:838
|
||||
#, fuzzy
|
||||
#| msgid "Checklist Items Management"
|
||||
msgid "complaint to higher management"
|
||||
msgstr "إدارة عناصر قائمة التحقق"
|
||||
|
||||
#: templates/complaints/complaint_detail.html:843
|
||||
msgid "Explain why this complaint needs escalation..."
|
||||
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_list.html:94
|
||||
msgid "New Complaint"
|
||||
@ -5005,10 +5211,6 @@ msgstr "تعيين إلى القسم"
|
||||
msgid "Assign to User"
|
||||
msgstr "تعيين إلى المستخدم"
|
||||
|
||||
#: templates/observations/convert_to_action.html:94
|
||||
msgid "Create PX Action"
|
||||
msgstr "إنشاء إجراء PX"
|
||||
|
||||
#: templates/observations/observation_detail.html:5
|
||||
msgid "Observation Detail"
|
||||
msgstr "تفاصيل الملاحظة"
|
||||
|
||||
@ -261,7 +261,7 @@
|
||||
</div>
|
||||
{% if complaint.metadata.ai_analysis.old_department %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -287,15 +287,15 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
{% if complaint.metadata.ai_analysis.extracted_staff_name %}
|
||||
<small class="text-muted mt-1 d-block">
|
||||
<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 %}
|
||||
(confidence: {{ complaint.metadata.ai_analysis.staff_confidence|floatformat:0 }}%)
|
||||
({{ _("confidence") }}: {{ complaint.metadata.ai_analysis.staff_confidence|floatformat:0 }}%)
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
@ -310,16 +310,16 @@
|
||||
<div class="col-md-12">
|
||||
<div class="info-label">
|
||||
<i class="bi bi-people me-1"></i>
|
||||
Staff Suggestions
|
||||
{{ _("Staff Suggestions")}}
|
||||
{% 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 %}
|
||||
</div>
|
||||
<div class="info-value">
|
||||
{% if complaint.metadata.ai_analysis.extracted_staff_name %}
|
||||
<p class="text-muted mb-2">
|
||||
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 %})
|
||||
{{ _("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 %})
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
@ -347,15 +347,15 @@
|
||||
</div>
|
||||
<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 %}">
|
||||
{{ staff_match.confidence|mul:100|floatformat:0 }}% confidence
|
||||
{{ staff_match.confidence|mul:100|floatformat:0 }}% {{ _("confidence") }}
|
||||
</span>
|
||||
{% if complaint.staff and staff_match.id == complaint.staff.id|stringformat:"s" %}
|
||||
<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>
|
||||
{% elif not complaint.staff %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -366,7 +366,7 @@
|
||||
|
||||
<div class="mt-3">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -384,20 +384,20 @@
|
||||
</div>
|
||||
|
||||
<!-- AI Analysis Section -->
|
||||
{% if complaint.short_description or complaint.suggested_action %}
|
||||
{% if complaint.short_description_ar or complaint.suggested_action_ar %}
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<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>
|
||||
<span class="badge bg-info">AI Generated</span>
|
||||
<span class="badge bg-info">{{ _("AI Generated")}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Emotion Analysis -->
|
||||
{% if complaint.emotion %}
|
||||
<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-body py-2">
|
||||
<div class="row align-items-center mb-2">
|
||||
@ -407,12 +407,12 @@
|
||||
</span>
|
||||
</div>
|
||||
<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 class="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>
|
||||
</div>
|
||||
<div class="progress" style="height: 6px;">
|
||||
@ -434,25 +434,25 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if complaint.short_description %}
|
||||
{% if complaint.short_description_ar %}
|
||||
<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%);">
|
||||
<i class="bi bi-info-circle me-1 text-info"></i>
|
||||
<small>{{ complaint.short_description }}</small>
|
||||
<small>{{ complaint.short_description_ar }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if complaint.suggested_action %}
|
||||
{% if complaint.suggested_action_ar %}
|
||||
<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;">
|
||||
<i class="bi bi-lightning me-1 text-success"></i>
|
||||
<small>{{ complaint.suggested_action }}</small>
|
||||
<small>{{ complaint.suggested_action_ar }}</small>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -835,7 +835,7 @@
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<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 class="mb-3">
|
||||
<label class="form-label">{% trans "Reason for Escalation" %}</label>
|
||||
@ -860,18 +860,18 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<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 class="mb-3">
|
||||
<label class="form-label">Auto-mapped Category</label>
|
||||
<label class="form-label">{{ _("Auto-mapped Category")}}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-tag"></i>
|
||||
@ -879,31 +879,31 @@
|
||||
<input type="text" id="autoMappedCategory" class="form-control" readonly
|
||||
value="{% if complaint.category %}{{ complaint.category.name_en }}{% else %}Other{% endif %}">
|
||||
</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 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">
|
||||
<option value="">Leave unassigned</option>
|
||||
<option value="">{{ _("Leave unassigned")}}</option>
|
||||
{% for user_obj in assignable_users %}
|
||||
<option value="{{ user_obj.id }}">{{ user_obj.get_full_name }}</option>
|
||||
{% endfor %}
|
||||
</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 class="mb-0">
|
||||
<label class="form-label">Action Description</label>
|
||||
<label class="form-label">{{ _("Action Description")}}</label>
|
||||
<div class="alert alert-success mb-0">
|
||||
<small>{{ complaint.suggested_action }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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()">
|
||||
<i class="bi bi-plus-circle me-1"></i>Create Action
|
||||
<i class="bi bi-plus-circle me-1"></i>{{ _("Create Action")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -916,7 +916,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
@ -924,13 +924,13 @@
|
||||
<!-- Department Filter -->
|
||||
<div class="row mb-3">
|
||||
<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">
|
||||
<option value="">All Departments</option>
|
||||
<option value="">{{ _("All Departments")}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<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...">
|
||||
</div>
|
||||
</div>
|
||||
@ -939,7 +939,7 @@
|
||||
<div id="staffListContainer" class="border rounded" style="max-height: 400px; overflow-y: auto;">
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<span class="visually-hidden">{{ _("Loading...")}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -960,7 +960,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
@ -969,13 +969,13 @@
|
||||
<div class="mb-3">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-robot me-1"></i>
|
||||
AI Summary
|
||||
<small class="text-muted">(you can edit this before sending)</small>
|
||||
{{ _("AI Summary")}}
|
||||
<small class="text-muted">({{ _("you can edit this before sending")}})</small>
|
||||
</label>
|
||||
<textarea id="emailMessage" class="form-control" rows="5"
|
||||
placeholder="Enter message to send...">{{ complaint.short_description }}</textarea>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -983,7 +983,7 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
|
||||
{% if complaint.staff and complaint.staff.user %}
|
||||
@ -1000,14 +1000,14 @@
|
||||
<!-- Staff exists but has no user account -->
|
||||
<div class="alert alert-warning mb-2">
|
||||
<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 %}
|
||||
<br><small class="text-muted">{{ complaint.staff.job_title }}</small>
|
||||
{% endif %}
|
||||
<hr class="my-2">
|
||||
<small class="text-muted">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -1016,14 +1016,14 @@
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-person-badge me-1"></i>
|
||||
<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>
|
||||
{% else %}
|
||||
<!-- No fallback recipient -->
|
||||
<div class="alert alert-danger mb-0">
|
||||
<i class="bi bi-x-circle-fill me-1"></i>
|
||||
<strong>No recipient available</strong>
|
||||
<br><small>The assigned staff has no user account and no department manager is set.</small>
|
||||
<strong>{{ _("No recipient available")}}</strong>
|
||||
<br><small>{{ _("The assigned staff has no user account and no department manager is set")}}.</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -1032,15 +1032,15 @@
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-person-badge me-1"></i>
|
||||
<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>
|
||||
|
||||
{% else %}
|
||||
<!-- No recipient at all -->
|
||||
<div class="alert alert-danger mb-0">
|
||||
<i class="bi bi-exclamation-triangle-fill me-1"></i>
|
||||
<strong>No recipient available</strong>
|
||||
<br><small>No staff or department manager assigned to this complaint.</small>
|
||||
<strong>{{ _("No recipient available")}}</strong>
|
||||
<br><small>{{ _("No staff or department manager assigned to this complaint")}}.</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -1049,7 +1049,7 @@
|
||||
<!-- Optional Additional Message -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">
|
||||
Additional Message (Optional)
|
||||
{{ _("Additional Message (Optional)")}}
|
||||
</label>
|
||||
<textarea id="additionalMessage" class="form-control" rows="3"
|
||||
placeholder="Add any additional notes to send..."></textarea>
|
||||
@ -1057,11 +1057,11 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
{{ _("Cancel") }}
|
||||
</button>
|
||||
<button type="button" id="sendNotificationBtn" class="btn btn-primary"
|
||||
onclick="sendNotification()">
|
||||
<i class="bi bi-send me-1"></i>Send Email
|
||||
<i class="bi bi-send me-1"></i>{{ _("Send Email")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -150,7 +150,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rating in top_physicians %}
|
||||
{% for rating in top_staff %}
|
||||
<tr onclick="window.location=''" style="cursor: pointer;">
|
||||
<td>
|
||||
{% if forloop.counter == 1 %}
|
||||
@ -164,13 +164,13 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ rating.physician.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.physician.license_number }}</small>
|
||||
<strong>{{ rating.staff }}</strong><br>
|
||||
<small class="text-muted">{{ rating.staff.license_number }}</small>
|
||||
</td>
|
||||
<td>{{ rating.physician.specialization }}</td>
|
||||
<td>{{ rating.staff.specialization }}</td>
|
||||
<td>
|
||||
{% if rating.physician.department %}
|
||||
{{ rating.physician.department.name }}
|
||||
{% if rating.staff.department %}
|
||||
{{ rating.staff.department.name }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
|
||||
@ -227,10 +227,10 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if stage.physician %}
|
||||
{% if stage.staff %}
|
||||
<div class="stage-meta-item">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% 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>
|
||||
{% if forloop.counter <= 3 %}
|
||||
<strong class="text-primary">#{{ forloop.counter }}</strong>
|
||||
@ -134,10 +134,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ rating.physician.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.physician.license_number }}</small>
|
||||
<strong>{{ rating.staff.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.staff.license_number }}</small>
|
||||
</td>
|
||||
<td>{{ rating.physician.specialization }}</td>
|
||||
<td>{{ rating.staff.specialization }}</td>
|
||||
<td>
|
||||
<strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong>
|
||||
</td>
|
||||
@ -152,7 +152,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<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">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
|
||||
@ -109,23 +109,23 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% 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>
|
||||
<strong>{{ rating.year }}-{{ rating.month|stringformat:"02d" }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ rating.physician.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.physician.license_number }}</small>
|
||||
<strong>{{ rating.staff.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.staff.license_number }}</small>
|
||||
</td>
|
||||
<td>{{ rating.physician.specialization }}</td>
|
||||
<td>{{ rating.staff.specialization }}</td>
|
||||
<td>
|
||||
{% if rating.physician.department %}
|
||||
{{ rating.physician.department.name }}
|
||||
{% if rating.staff.department %}
|
||||
{{ rating.staff.department.name }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ rating.physician.hospital.name }}</td>
|
||||
<td>{{ rating.staff.hospital.name }}</td>
|
||||
<td>
|
||||
<h5 class="mb-0">{{ rating.average_rating|floatformat:2 }}</h5>
|
||||
</td>
|
||||
@ -159,7 +159,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<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">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% 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>
|
||||
{% if forloop.counter <= 3 %}
|
||||
<strong class="text-primary">#{{ forloop.counter }}</strong>
|
||||
@ -133,17 +133,17 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ rating.physician.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.physician.license_number }}</small>
|
||||
<strong>{{ rating.staff.get_full_name }}</strong><br>
|
||||
<small class="text-muted">{{ rating.staff.license_number }}</small>
|
||||
</td>
|
||||
<td>
|
||||
{% if rating.physician.department %}
|
||||
{{ rating.physician.department.name }}
|
||||
{% if rating.staff.department %}
|
||||
{{ rating.staff.department.name }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ rating.physician.hospital.name }}</td>
|
||||
<td>{{ rating.staff.hospital.name }}</td>
|
||||
<td>
|
||||
<strong class="text-success">{{ rating.average_rating|floatformat:2 }}</strong>
|
||||
</td>
|
||||
@ -151,7 +151,7 @@
|
||||
<span class="badge bg-light text-dark">{{ rating.total_surveys }}</span>
|
||||
</td>
|
||||
<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">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user