HH/apps/px_sources/models.py
2026-02-25 04:47:05 +03:00

318 lines
9.3 KiB
Python

"""
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,
db_index=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']),
models.Index(fields=['code']),
models.Index(fields=['source_type']),
]
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='en'):
"""Get localized name based on 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"
)
# 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')}"