""" 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 TimeStampedModel, UUIDModel class AppreciationStatus(models.TextChoices): """Appreciation status choices""" DRAFT = 'draft', 'Draft' 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}" class Appreciation(UUIDModel, TimeStampedModel): """ 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' ) 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 sent_at = models.DateTimeField(null=True, blank=True) acknowledged_at = models.DateTimeField(null=True, blank=True) # 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=['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_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 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']) # Trigger notification 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']), 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}" 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"