""" PX Sources models - Manages origins of patient feedback This module implements the PX Source management system that: - Tracks sources of patient feedback (Complaints and Inquiries) - Supports bilingual naming (English/Arabic) - Enables status management """ from django.db import models from django.utils.translation import gettext_lazy as _ from apps.core.models import UUIDModel, TimeStampedModel class PXSource(UUIDModel, TimeStampedModel): """ PX Source model for managing feedback origins. Simple model with bilingual naming and active status management. """ # Code for API references code = models.CharField( max_length=50, unique=True, help_text="Unique code for API references", blank=True, default="" ) # Bilingual names name_en = models.CharField(max_length=200, help_text="Source name in English") name_ar = models.CharField(max_length=200, blank=True, help_text="Source name in Arabic") # Description description = models.TextField(blank=True, help_text="Detailed description") # Source type SOURCE_TYPE_CHOICES = [ ("internal", _("Internal")), ("external", _("External")), ("partner", _("Partner")), ("government", _("Government")), ("other", _("Other")), ] source_type = models.CharField( max_length=50, choices=SOURCE_TYPE_CHOICES, default="internal", db_index=True, help_text="Type of source" ) # Contact information for external sources contact_email = models.EmailField(blank=True, help_text="Contact email for external sources") contact_phone = models.CharField(max_length=20, blank=True, help_text="Contact phone for external sources") # Status is_active = models.BooleanField( default=True, db_index=True, help_text="Whether this source is active for selection" ) # Metadata metadata = models.JSONField(default=dict, blank=True, help_text="Additional metadata") # Cached usage stats total_complaints = models.IntegerField(default=0, editable=False, help_text="Cached total complaints count") total_inquiries = models.IntegerField(default=0, editable=False, help_text="Cached total inquiries count") class Meta: ordering = ["name_en"] verbose_name = "PX Source" verbose_name_plural = "PX Sources" indexes = [ models.Index(fields=["is_active", "name_en"]), ] def __str__(self): return f"{self.code} - {self.name_en}" def save(self, *args, **kwargs): # Auto-generate code if not provided if not self.code and self.name_en: # Create code from name (e.g., "Hospital A" -> "HOSP-A") words = self.name_en.upper().split() if len(words) >= 2: self.code = "-".join(word[:4] for word in words[:3]) else: self.code = self.name_en[:10].upper().replace(" ", "-") # Ensure uniqueness from django.db.models import Count base_code = self.code counter = 1 while PXSource.objects.filter(code=self.code).exclude(pk=self.pk).exists(): self.code = f"{base_code}-{counter}" counter += 1 super().save(*args, **kwargs) def get_localized_name(self, language=None): from django.utils.translation import get_language if language is None: language = get_language() if language == "ar" and self.name_ar: return self.name_ar return self.name_en def get_localized_description(self): """Get localized description""" return self.description def activate(self): """Activate this source""" if not self.is_active: self.is_active = True self.save(update_fields=["is_active"]) def deactivate(self): """Deactivate this source""" if self.is_active: self.is_active = False self.save(update_fields=["is_active"]) @classmethod def get_active_sources(cls): """ Get all active sources. Returns: QuerySet of active PXSource objects """ return cls.objects.filter(is_active=True).order_by("name_en") def update_usage_stats(self): """Update cached usage statistics""" from apps.complaints.models import Complaint, Inquiry self.total_complaints = Complaint.objects.filter(source=self).count() self.total_inquiries = Inquiry.objects.filter(source=self).count() self.save(update_fields=["total_complaints", "total_inquiries"]) def get_usage_stats(self, days=30): """Get usage statistics for the last N days""" from django.utils import timezone from datetime import timedelta cutoff = timezone.now() - timedelta(days=days) return { "total_usage": self.usage_records.filter(created_at__gte=cutoff).count(), "complaints": self.usage_records.filter(created_at__gte=cutoff, content_type__model="complaint").count(), "inquiries": self.usage_records.filter(created_at__gte=cutoff, content_type__model="inquiry").count(), } class SourceUser(UUIDModel, TimeStampedModel): """ Links users to PX Sources for management. A user can be a source manager for a specific PX Source, allowing them to create complaints and inquiries from that source. """ user = models.OneToOneField( "accounts.User", on_delete=models.CASCADE, related_name="source_user_profile", help_text="User who manages this source", ) source = models.ForeignKey( PXSource, on_delete=models.CASCADE, related_name="source_users", help_text="Source managed by this user" ) hospital = models.ForeignKey( "organizations.Hospital", on_delete=models.CASCADE, related_name="source_users", help_text="Hospital this source user belongs to", null=True, blank=True, ) # Status is_active = models.BooleanField(default=True, db_index=True, help_text="Whether this source user is active") # Permissions can_create_complaints = models.BooleanField(default=True, help_text="User can create complaints from this source") can_create_inquiries = models.BooleanField(default=True, help_text="User can create inquiries from this source") class Meta: ordering = ["source__name_en"] verbose_name = "Source User" verbose_name_plural = "Source Users" indexes = [ models.Index(fields=["user", "is_active"]), models.Index(fields=["source", "is_active"]), ] unique_together = [["user", "source"]] def __str__(self): return f"{self.user.email} - {self.source.name_en}" def activate(self): """Activate this source user""" if not self.is_active: self.is_active = True self.save(update_fields=["is_active"]) def deactivate(self): """Deactivate this source user""" if self.is_active: self.is_active = False self.save(update_fields=["is_active"]) @classmethod def get_active_source_user(cls, user): """ Get active source user for a user. Returns: SourceUser object or None """ return cls.objects.filter(user=user, is_active=True).first() class SourceUsage(UUIDModel, TimeStampedModel): """ Tracks usage of sources across the system. This model can be used to analyze which sources are most commonly used, track trends, and generate reports. """ source = models.ForeignKey(PXSource, on_delete=models.CASCADE, related_name="usage_records") # Related object (could be Complaint, Inquiry, or other feedback types) content_type = models.ForeignKey( "contenttypes.ContentType", on_delete=models.CASCADE, help_text="Type of related object" ) object_id = models.UUIDField(help_text="ID of related object") # Hospital context (optional) hospital = models.ForeignKey( "organizations.Hospital", on_delete=models.SET_NULL, null=True, blank=True, related_name="source_usage_records", help_text="Hospital where this source was used", ) # User who selected this source (optional) user = models.ForeignKey( "accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="source_usage_records", help_text="User who selected this source", ) class Meta: ordering = ["-created_at"] verbose_name = "Source Usage" verbose_name_plural = "Source Usages" indexes = [ models.Index(fields=["source", "-created_at"]), models.Index(fields=["content_type", "object_id"]), models.Index(fields=["hospital", "-created_at"]), models.Index(fields=["created_at"]), ] unique_together = [["content_type", "object_id"]] def __str__(self): return f"{self.source} - {self.created_at.strftime('%Y-%m-%d %H:%M')}"