HH/apps/dashboard/models.py
2026-03-28 14:03:56 +03:00

418 lines
15 KiB
Python

"""
Employee Evaluation Models
Models for tracking employee performance metrics and reports for the
PAD Department Weekly Dashboard evaluation system.
"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.core.models import UUIDModel, TimeStampedModel
class EvaluationNote(UUIDModel, TimeStampedModel):
"""
Notes for employee evaluation tracking.
Tracks notes categorized by type and sub-category for each staff member.
"""
CATEGORY_CHOICES = [
("non_medical", "Non-Medical"),
("medical", "Medical"),
("er", "ER"),
("hospital", "Hospital"),
]
SUBCATEGORY_CHOICES = [
("it_app", "IT - App"),
("lab", "LAB"),
("doctors_managers_reception", "Doctors/Managers/Reception"),
("hospital_general", "Hospital"),
("medical_reports", "Medical Reports"),
("doctors", "Doctors"),
("other", "Other"),
]
staff = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="evaluation_notes",
help_text="Staff member this note is for",
)
category = models.CharField(max_length=50, choices=CATEGORY_CHOICES, help_text="Note category")
sub_category = models.CharField(max_length=50, choices=SUBCATEGORY_CHOICES, help_text="Note sub-category")
count = models.IntegerField(default=1, help_text="Number of notes in this category")
note_date = models.DateField(help_text="Date the note was recorded")
description = models.TextField(blank=True, help_text="Optional description")
created_by = models.ForeignKey(
"accounts.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="notes_created",
help_text="User who created this note entry",
)
class Meta:
ordering = ["-note_date", "category", "sub_category"]
verbose_name = "Evaluation Note"
verbose_name_plural = "Evaluation Notes"
indexes = [
models.Index(fields=["staff", "note_date"]),
models.Index(fields=["category", "sub_category"]),
models.Index(fields=["note_date"]),
]
def __str__(self):
return f"{self.staff} - {self.get_category_display()} - {self.note_date}"
class ComplaintRequest(UUIDModel, TimeStampedModel):
"""
Tracks complaint request filling and status.
Monitors whether complaint requests were filled, on hold,
or came from barcode scanning.
"""
FILLING_TIME_CHOICES = [
("same_time", "Same Time"),
("within_6h", "Within 6 Hours"),
("6_to_24h", "6 to 24 Hours"),
("after_1_day", "After 1 Day"),
("not_mentioned", "Time Not Mentioned"),
]
NON_ACTIVATION_REASON_CHOICES = [
("converted_to_note", "تم تحويلها ملاحظة (Converted to observation)"),
("issue_resolved_immediately", "تم حل الاشكالية (Issue resolved immediately)"),
("not_meeting_conditions", "غير مستوفية للشروط (Does not meet conditions)"),
("raised_via_cchi", "تم رفع الشكوى عن طريق مجلس الضمان الصحي (Raised via CCHI)"),
("request_not_activated", "لم يتم تفعيل طلب الشكوى (Request not activated)"),
("complainant_withdrew", "بناء على طلب المشتكي (Per complainant request)"),
("complainant_retracted", "المشتكي تنازل عن الشكوى (Complainant retracted)"),
("duplicate", "مكررة (Duplicate)"),
("other", "Other"),
]
staff = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="complaint_requests_sent",
help_text="Staff member who sent/filled the request",
)
complaint = models.ForeignKey(
"complaints.Complaint",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="complaint_request_records",
help_text="Related complaint (if any)",
)
hospital = models.ForeignKey(
"organizations.Hospital",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="complaint_requests",
help_text="Hospital where the request was made",
)
patient_name = models.CharField(max_length=200, blank=True, help_text="Patient name")
file_number = models.CharField(max_length=100, blank=True, help_text="Patient file number")
complained_department = models.ForeignKey(
"organizations.Department",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="complaint_requests",
help_text="Department being complained about",
)
incident_date = models.DateField(null=True, blank=True, help_text="Date of the incident")
phone_number = models.CharField(max_length=20, blank=True, help_text="Patient phone number")
filled = models.BooleanField(default=False, help_text="Whether the request was filled")
on_hold = models.BooleanField(default=False, help_text="Whether the request is on hold")
not_filled = models.BooleanField(default=False, help_text="Whether the request was not filled")
from_barcode = models.BooleanField(default=False, help_text="Whether the request came from barcode scanning")
filling_time_category = models.CharField(
max_length=20, choices=FILLING_TIME_CHOICES, default="not_mentioned", help_text="When the request was filled"
)
request_date = models.DateField(help_text="Date of the request")
request_time = models.TimeField(null=True, blank=True, help_text="Time of the request")
form_sent_at = models.DateTimeField(null=True, blank=True, help_text="When complaint form was sent to patient")
form_sent_time = models.TimeField(null=True, blank=True, help_text="Time when form was sent")
filled_at = models.DateTimeField(null=True, blank=True, help_text="When the request was filled")
filled_time = models.TimeField(null=True, blank=True, help_text="Time when request was filled")
reason_non_activation = models.CharField(
max_length=50, choices=NON_ACTIVATION_REASON_CHOICES, blank=True, help_text="Reason complaint was not activated"
)
reason_non_activation_other = models.CharField(max_length=200, blank=True, help_text="Other reason details")
pr_observations = models.TextField(blank=True, help_text="PR team observations about this request")
notes = models.TextField(blank=True, help_text="Additional notes about the request")
class Meta:
ordering = ["-request_date", "-created_at"]
verbose_name = "Complaint Request"
verbose_name_plural = "Complaint Requests"
indexes = [
models.Index(fields=["staff", "request_date"]),
models.Index(fields=["filled", "on_hold"]),
models.Index(fields=["request_date"]),
models.Index(fields=["hospital", "request_date"]),
models.Index(fields=["reason_non_activation"]),
]
def __str__(self):
status = "Filled" if self.filled else "Not Filled"
if self.on_hold:
status = "On Hold"
return f"{self.staff} - {status} - {self.request_date}"
def mark_as_filled(self):
"""Mark the request as filled."""
from django.utils import timezone
self.filled = True
self.not_filled = False
self.filled_at = timezone.now()
self.save(update_fields=["filled", "not_filled", "filled_at"])
def mark_as_not_filled(self):
"""Mark the request as not filled."""
self.filled = False
self.not_filled = True
self.save(update_fields=["filled", "not_filled"])
class ReportCompletion(UUIDModel, TimeStampedModel):
"""
Tracks weekly report completion for employees.
Monitors which reports were completed by staff members.
"""
REPORT_TYPE_CHOICES = [
("complaint_report", "Complaint Report"),
("complaint_request_report", "Complaint Request Report"),
("observation_report", "Observation Report"),
("incoming_inquiries_report", "Incoming Inquiries Report"),
("outgoing_inquiries_report", "Outgoing Inquiries Report"),
("extension_report", "Extension Report"),
("escalated_complaints_report", "Escalated Complaints Report"),
]
staff = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="report_completions",
help_text="Staff member who should complete the report",
)
report_type = models.CharField(max_length=50, choices=REPORT_TYPE_CHOICES, help_text="Type of report")
is_completed = models.BooleanField(default=False, help_text="Whether the report is completed")
completed_at = models.DateTimeField(null=True, blank=True, help_text="When the report was completed")
week_start_date = models.DateField(help_text="Start date of the week this report is for")
notes = models.TextField(blank=True, help_text="Notes about the report completion")
class Meta:
ordering = ["-week_start_date", "report_type"]
verbose_name = "Report Completion"
verbose_name_plural = "Report Completions"
unique_together = [["staff", "report_type", "week_start_date"]]
indexes = [
models.Index(fields=["staff", "week_start_date"]),
models.Index(fields=["report_type", "is_completed"]),
models.Index(fields=["week_start_date"]),
]
def __str__(self):
status = "" if self.is_completed else ""
return f"{self.staff} - {self.get_report_type_display()} - Week of {self.week_start_date} {status}"
def mark_completed(self):
"""Mark this report as completed."""
from django.utils import timezone
self.is_completed = True
self.completed_at = timezone.now()
self.save(update_fields=["is_completed", "completed_at"])
def mark_incomplete(self):
"""Mark this report as not completed."""
self.is_completed = False
self.completed_at = None
self.save(update_fields=["is_completed", "completed_at"])
@classmethod
def get_completion_percentage(cls, staff, week_start_date):
"""
Get completion percentage for a staff member for a given week.
Returns:
float: Percentage of completed reports (0-100)
"""
total_reports = len(cls.REPORT_TYPE_CHOICES)
completed_count = cls.objects.filter(staff=staff, week_start_date=week_start_date, is_completed=True).count()
return (completed_count / total_reports) * 100 if total_reports > 0 else 0
class EscalatedComplaintLog(UUIDModel, TimeStampedModel):
"""
Logs escalated complaints with timing information.
Tracks when complaints were escalated relative to the 72-hour SLA.
"""
ESCALATION_TIMING_CHOICES = [
("before_72h", "Before 72 Hours"),
("exactly_72h", "72 Hours Exactly"),
("after_72h", "After 72 Hours"),
]
complaint = models.ForeignKey(
"complaints.Complaint",
on_delete=models.CASCADE,
related_name="escalation_logs",
help_text="The escalated complaint",
)
staff = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="escalated_complaints",
help_text="Staff member who the complaint is assigned to",
)
escalation_timing = models.CharField(
max_length=20,
choices=ESCALATION_TIMING_CHOICES,
help_text="When the complaint was escalated relative to 72h SLA",
)
escalated_at = models.DateTimeField(help_text="When the complaint was escalated")
is_resolved = models.BooleanField(default=False, help_text="Whether the escalated complaint was resolved")
resolved_at = models.DateTimeField(null=True, blank=True, help_text="When the escalated complaint was resolved")
resolution_notes = models.TextField(blank=True, help_text="Notes about the resolution")
week_start_date = models.DateField(help_text="Start date of the week this escalation is recorded for")
class Meta:
ordering = ["-escalated_at", "-created_at"]
verbose_name = "Escalated Complaint Log"
verbose_name_plural = "Escalated Complaint Logs"
indexes = [
models.Index(fields=["staff", "week_start_date"]),
models.Index(fields=["escalation_timing", "is_resolved"]),
models.Index(fields=["week_start_date"]),
]
def __str__(self):
status = "Resolved" if self.is_resolved else "Unresolved"
return f"{self.staff} - {self.get_escalation_timing_display()} - {status}"
def mark_resolved(self, notes=""):
"""Mark this escalated complaint as resolved."""
from django.utils import timezone
self.is_resolved = True
self.resolved_at = timezone.now()
if notes:
self.resolution_notes = notes
self.save(update_fields=["is_resolved", "resolved_at", "resolution_notes"])
class InquiryDetail(UUIDModel, TimeStampedModel):
"""
Detailed tracking of inquiries with types and statuses.
Extends the base Inquiry model with evaluation-specific tracking.
"""
INQUIRY_TYPE_CHOICES = [
("contact_doctor", "Contact the doctor"),
("sick_leave_reports", "Sick-Leave - Medical Reports"),
("blood_test", "Blood test result"),
("raise_complaint", "Raise a Complaint"),
("app_problem", "Problem with the app"),
("medication", "Ask about medication"),
("insurance_status", "Insurance request status"),
("general_question", "General question"),
("other", "Other"),
]
STATUS_CHOICES = [
("in_progress", "تحت الإجراء (In Progress)"),
("contacted", "تم التواصل (Contacted)"),
("contacted_no_response", "تم التواصل ولم يتم الرد (Contacted No Response)"),
]
RESPONSE_TIME_CHOICES = [
("24h", "24 Hours"),
("48h", "48 Hours"),
("72h", "72 Hours"),
("more_than_72h", "More than 72 Hours"),
]
inquiry = models.OneToOneField(
"complaints.Inquiry", on_delete=models.CASCADE, related_name="evaluation_detail", help_text="Related inquiry"
)
staff = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="inquiry_details",
help_text="Staff member handling the inquiry",
)
inquiry_type = models.CharField(max_length=50, choices=INQUIRY_TYPE_CHOICES, help_text="Type of inquiry")
is_outgoing = models.BooleanField(default=False, help_text="Whether this is an outgoing inquiry")
response_time_category = models.CharField(
max_length=20, choices=RESPONSE_TIME_CHOICES, blank=True, help_text="Response time category"
)
inquiry_status = models.CharField(
max_length=50, choices=STATUS_CHOICES, blank=True, help_text="Current status of the inquiry"
)
inquiry_date = models.DateField(help_text="Date of the inquiry")
notes = models.TextField(blank=True, help_text="Additional notes")
class Meta:
ordering = ["-inquiry_date", "-created_at"]
verbose_name = "Inquiry Detail"
verbose_name_plural = "Inquiry Details"
indexes = [
models.Index(fields=["staff", "inquiry_date"]),
models.Index(fields=["inquiry_type", "is_outgoing"]),
models.Index(fields=["inquiry_date"]),
]
def __str__(self):
direction = "Outgoing" if self.is_outgoing else "Incoming"
return f"{self.staff} - {direction} - {self.get_inquiry_type_display()} - {self.inquiry_date}"