""" Appreciation models - Send and track appreciation to users and physicians This module implements the appreciation system that: - Allows sending appreciation messages to users and physicians - Tracks appreciation statistics and leaderboards - Manages appreciation categories and badges - Integrates with the notification system """ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from apps.core.models import SoftDeleteModel, TimeStampedModel, UUIDModel class AppreciationStatus(models.TextChoices): """Appreciation status choices""" DRAFT = "draft", "Draft" ACTIVATED = "activated", "Activated" AI_ANALYZED = "ai_analyzed", "AI Analyzed" SENT = "sent", "Sent" ACKNOWLEDGED = "acknowledged", "Acknowledged" class AppreciationVisibility(models.TextChoices): """Appreciation visibility choices""" PRIVATE = "private", "Private" DEPARTMENT = "department", "Department" HOSPITAL = "hospital", "Hospital" PUBLIC = "public", "Public" class AppreciationCategory(UUIDModel, TimeStampedModel): """ Appreciation category with bilingual support. Pre-defined categories for sending appreciation (e.g., "Excellent Care", "Team Player"). Can be hospital-specific or system-wide. """ # Organization hospital = models.ForeignKey( "organizations.Hospital", on_delete=models.CASCADE, related_name="appreciation_categories", null=True, blank=True, help_text="Leave blank for system-wide categories", ) # Category details code = models.CharField(max_length=50, help_text="Unique code for this category") name_en = models.CharField(max_length=200) name_ar = models.CharField(max_length=200, blank=True) description_en = models.TextField(blank=True) description_ar = models.TextField(blank=True) # Visual elements icon = models.CharField(max_length=50, blank=True, help_text="Icon class (e.g., 'fa-heart', 'fa-star')") color = models.CharField(max_length=7, blank=True, help_text="Hex color code (e.g., '#FF5733')") # Display order order = models.IntegerField(default=0, help_text="Display order") # Status is_active = models.BooleanField(default=True) class Meta: ordering = ["hospital", "order", "name_en"] unique_together = [["hospital", "code"]] verbose_name_plural = "Appreciation Categories" indexes = [ models.Index(fields=["hospital", "is_active"]), models.Index(fields=["code"]), ] def __str__(self): hospital_name = self.hospital.name if self.hospital else "System-wide" return f"{hospital_name} - {self.name_en}" def get_localized_name(self): from django.utils.translation import get_language if get_language() == "ar" and self.name_ar: return self.name_ar return self.name_en def get_localized_description(self): from django.utils.translation import get_language if get_language() == "ar" and self.description_ar: return self.description_ar return self.description_en class Appreciation(UUIDModel, TimeStampedModel, SoftDeleteModel): """ Appreciation model for sending appreciation to users and physicians. Workflow: 1. DRAFT - Created but not yet sent 2. SENT - Sent to recipient (triggers notification) 3. ACKNOWLEDGED - Recipient has acknowledged/thanked sender Visibility: - PRIVATE - Only sender and recipient can view - DEPARTMENT - Visible to department members - HOSPITAL - Visible to all hospital staff - PUBLIC - Can be displayed publicly (e.g., on appreciation wall) """ # Sender sender = models.ForeignKey( "accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="sent_appreciations" ) # Recipient (Generic FK to User or Physician) recipient_content_type = models.ForeignKey( ContentType, on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciation_recipients" ) recipient_object_id = models.UUIDField(null=True, blank=True) recipient = GenericForeignKey("recipient_content_type", "recipient_object_id") # Organization context hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="appreciations") location = models.ForeignKey( "organizations.Location", on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciations", help_text="Location where the appreciation event occurred", ) main_section = models.ForeignKey( "organizations.MainSection", on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciations", help_text="Main section within the location", ) subsection = models.ForeignKey( "organizations.SubSection", on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciations", help_text="Specific subsection", ) department = models.ForeignKey( "organizations.Department", on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciations", help_text="Department context (if applicable)", ) # Category and message category = models.ForeignKey( AppreciationCategory, on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciations" ) message_en = models.TextField() message_ar = models.TextField(blank=True) # Visibility and status visibility = models.CharField( max_length=20, choices=AppreciationVisibility.choices, default=AppreciationVisibility.PRIVATE, db_index=True ) status = models.CharField( max_length=20, choices=AppreciationStatus.choices, default=AppreciationStatus.DRAFT, db_index=True ) # Anonymous option is_anonymous = models.BooleanField(default=False, help_text="Hide sender identity from recipient") # Timestamps activated_at = models.DateTimeField(null=True, blank=True) activated_by = models.ForeignKey( "accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="activated_appreciations" ) ai_analyzed_at = models.DateTimeField(null=True, blank=True) sent_at = models.DateTimeField(null=True, blank=True) acknowledged_at = models.DateTimeField(null=True, blank=True) # Send options send_to_manager = models.BooleanField(default=False, help_text="Send notification to department manager") send_to_department = models.BooleanField(default=True, help_text="Send notification to department") cc_list = models.JSONField(default=list, blank=True, help_text="List of user IDs to CC") custom_message = models.TextField(blank=True, help_text="Custom message to include when sending") ai_analysis = models.JSONField(default=dict, blank=True, help_text="AI analysis results") # Notification tracking notification_sent = models.BooleanField(default=False) notification_sent_at = models.DateTimeField(null=True, blank=True) # Metadata metadata = models.JSONField(default=dict, blank=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["status", "-created_at"]), models.Index(fields=["hospital", "status"]), models.Index(fields=["department", "-created_at"]), models.Index(fields=["sender", "-created_at"]), models.Index(fields=["recipient_content_type", "recipient_object_id", "-created_at"]), models.Index(fields=["visibility", "-created_at"]), ] def __str__(self): recipient_name = self.get_recipient_name() return f"Appreciation to {recipient_name} ({self.status})" def get_localized_message(self): from django.utils.translation import get_language if get_language() == "ar" and self.message_ar: return self.message_ar return self.message_en def get_recipient_name(self): """Get recipient's name""" try: return str(self.recipient) except: return "Unknown" def get_recipient_email(self): """Get recipient's email""" try: if hasattr(self.recipient, "email"): return self.recipient.email elif hasattr(self.recipient, "user"): return self.recipient.user.email except: pass return None def get_recipient_phone(self): """Get recipient's phone""" try: if hasattr(self.recipient, "phone"): return self.recipient.phone elif hasattr(self.recipient, "user"): return self.recipient.user.phone except: pass return None def activate(self, activated_by=None): from django.utils import timezone self.status = AppreciationStatus.ACTIVATED self.activated_at = timezone.now() self.activated_by = activated_by self.save(update_fields=["status", "activated_at", "activated_by"]) def mark_ai_analyzed(self, analysis_data): from django.utils import timezone self.status = AppreciationStatus.AI_ANALYZED self.ai_analyzed_at = timezone.now() self.ai_analysis = analysis_data self.save(update_fields=["status", "ai_analyzed_at", "ai_analysis"]) def send(self): """Send appreciation and trigger notification""" from django.utils import timezone self.status = AppreciationStatus.SENT self.sent_at = timezone.now() self.save(update_fields=["status", "sent_at"]) self.send_notification() def acknowledge(self): """Mark appreciation as acknowledged""" from django.utils import timezone self.status = AppreciationStatus.ACKNOWLEDGED self.acknowledged_at = timezone.now() self.save(update_fields=["status", "acknowledged_at"]) def send_notification(self): """Send notification to recipient""" # This will be implemented in signals.py pass class AppreciationBadge(UUIDModel, TimeStampedModel): """ Appreciation badge for gamification. Badges are awarded based on achievements (e.g., "10 Appreciations Received"). """ # Organization hospital = models.ForeignKey( "organizations.Hospital", on_delete=models.CASCADE, related_name="appreciation_badges", null=True, blank=True, help_text="Leave blank for system-wide badges", ) # Badge details code = models.CharField(max_length=50, unique=True, help_text="Unique badge code") name_en = models.CharField(max_length=200) name_ar = models.CharField(max_length=200, blank=True) description_en = models.TextField(blank=True) description_ar = models.TextField(blank=True) # Visual elements icon = models.CharField(max_length=50, blank=True, help_text="Icon class") color = models.CharField(max_length=7, blank=True, help_text="Hex color code") # Achievement criteria criteria_type = models.CharField( max_length=50, choices=[ ("received_count", "Total Appreciations Received"), ("received_month", "Appreciations Received in a Month"), ("streak_weeks", "Consecutive Weeks with Appreciation"), ("diverse_senders", "Appreciations from Different Senders"), ], db_index=True, ) criteria_value = models.IntegerField(help_text="Value to achieve (e.g., 10 for 10 appreciations)") # Display order order = models.IntegerField(default=0) # Status is_active = models.BooleanField(default=True) class Meta: ordering = ["hospital", "order", "name_en"] indexes = [ models.Index(fields=["hospital", "is_active"]), ] def __str__(self): hospital_name = self.hospital.name if self.hospital else "System-wide" return f"{hospital_name} - {self.name_en}" def get_localized_name(self): from django.utils.translation import get_language if get_language() == "ar" and self.name_ar: return self.name_ar return self.name_en def get_localized_description(self): from django.utils.translation import get_language if get_language() == "ar" and self.description_ar: return self.description_ar return self.description_en class UserBadge(UUIDModel, TimeStampedModel): """ User badge - tracks badges earned by users. Links users (or physicians) to badges they've earned. """ # Recipient (Generic FK to User or Physician) recipient_content_type = models.ForeignKey( ContentType, on_delete=models.SET_NULL, null=True, blank=True, related_name="earned_badges_recipients" ) recipient_object_id = models.UUIDField(null=True, blank=True) recipient = GenericForeignKey("recipient_content_type", "recipient_object_id") # Badge badge = models.ForeignKey(AppreciationBadge, on_delete=models.CASCADE, related_name="earned_by") # Achievement details earned_at = models.DateTimeField(auto_now_add=True) # Context appreciation_count = models.IntegerField(help_text="Count when badge was earned") # Metadata metadata = models.JSONField(default=dict, blank=True) class Meta: ordering = ["-earned_at"] indexes = [ models.Index(fields=["recipient_content_type", "recipient_object_id", "-earned_at"]), ] def __str__(self): recipient_name = self.get_recipient_name() return f"{recipient_name} - {self.badge.name_en}" def get_recipient_name(self): """Get recipient's name""" try: return str(self.recipient) except: return "Unknown" class AppreciationStats(UUIDModel, TimeStampedModel): """ Appreciation statistics - aggregated monthly. Provides monthly statistics for users and physicians. """ # Recipient (Generic FK to User or Physician) recipient_content_type = models.ForeignKey( ContentType, on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciation_stats_recipients" ) recipient_object_id = models.UUIDField(null=True, blank=True) recipient = GenericForeignKey("recipient_content_type", "recipient_object_id") # Time period year = models.IntegerField(db_index=True) month = models.IntegerField(db_index=True, help_text="1-12") # Organization hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="appreciation_stats") department = models.ForeignKey( "organizations.Department", on_delete=models.SET_NULL, null=True, blank=True, related_name="appreciation_stats" ) # Statistics received_count = models.IntegerField(default=0) sent_count = models.IntegerField(default=0) acknowledged_count = models.IntegerField(default=0) # Ranking hospital_rank = models.IntegerField(null=True, blank=True, help_text="Rank within hospital") department_rank = models.IntegerField(null=True, blank=True, help_text="Rank within department") # Breakdown by category category_breakdown = models.JSONField(default=dict, blank=True, help_text="Breakdown by category ID and count") # Metadata metadata = models.JSONField(default=dict, blank=True) class Meta: ordering = ["-year", "-month", "-received_count"] unique_together = [["recipient_content_type", "recipient_object_id", "year", "month"]] indexes = [ models.Index(fields=["hospital", "year", "month", "-received_count"]), models.Index(fields=["department", "year", "month", "-received_count"]), ] def __str__(self): recipient_name = self.get_recipient_name() return f"{recipient_name} - {self.year}-{self.month:02d}: {self.received_count} received" def get_recipient_name(self): """Get recipient's name""" try: return str(self.recipient) except: return "Unknown"