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"
|
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
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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}"
|
||||||
|
)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
Binary file not shown.
@ -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 "تفاصيل الملاحظة"
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 %}
|
||||||
|
|||||||
@ -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 %}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user