""" Core models - Base models for inheritance across the application """ import uuid from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models class TimeStampedModel(models.Model): """ Abstract base model that provides self-updating 'created_at' and 'updated_at' fields. """ created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True ordering = ['-created_at'] class UUIDModel(models.Model): """ Abstract base model that uses UUID as primary key. All business models should inherit from this. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) class Meta: abstract = True class SoftDeleteModel(models.Model): """ Abstract base model that provides soft delete functionality. """ is_deleted = models.BooleanField(default=False, db_index=True) deleted_at = models.DateTimeField(null=True, blank=True) class Meta: abstract = True def soft_delete(self): """Soft delete the object""" from django.utils import timezone self.is_deleted = True self.deleted_at = timezone.now() self.save() def restore(self): """Restore a soft-deleted object""" self.is_deleted = False self.deleted_at = None self.save() class AuditEvent(UUIDModel, TimeStampedModel): """ Generic audit log for tracking important events across the system. Uses generic foreign key to link to any model. """ EVENT_TYPES = [ ('user_login', 'User Login'), ('user_logout', 'User Logout'), ('role_change', 'Role Change'), ('status_change', 'Status Change'), ('assignment', 'Assignment'), ('escalation', 'Escalation'), ('sla_breach', 'SLA Breach'), ('survey_sent', 'Survey Sent'), ('survey_completed', 'Survey Completed'), ('action_created', 'Action Created'), ('action_closed', 'Action Closed'), ('complaint_created', 'Complaint Created'), ('complaint_closed', 'Complaint Closed'), ('journey_started', 'Journey Started'), ('journey_completed', 'Journey Completed'), ('stage_completed', 'Stage Completed'), ('integration_event', 'Integration Event'), ('notification_sent', 'Notification Sent'), ('other', 'Other'), ] event_type = models.CharField(max_length=50, choices=EVENT_TYPES, db_index=True) user = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='audit_events' ) description = models.TextField() # Generic foreign key to link to any model content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) object_id = models.UUIDField(null=True, blank=True) content_object = GenericForeignKey('content_type', 'object_id') # Additional metadata metadata = models.JSONField(default=dict, blank=True) ip_address = models.GenericIPAddressField(null=True, blank=True) user_agent = models.TextField(blank=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['event_type', '-created_at']), models.Index(fields=['user', '-created_at']), models.Index(fields=['content_type', 'object_id']), ] def __str__(self): return f"{self.event_type} - {self.created_at.strftime('%Y-%m-%d %H:%M')}" class BaseChoices(models.TextChoices): """Base class for choice enums""" pass # Common status choices used across multiple apps class StatusChoices(BaseChoices): ACTIVE = 'active', 'Active' INACTIVE = 'inactive', 'Inactive' PENDING = 'pending', 'Pending' COMPLETED = 'completed', 'Completed' CANCELLED = 'cancelled', 'Cancelled' class PriorityChoices(BaseChoices): LOW = 'low', 'Low' MEDIUM = 'medium', 'Medium' HIGH = 'high', 'High' CRITICAL = 'critical', 'Critical' class SeverityChoices(BaseChoices): LOW = 'low', 'Low' MEDIUM = 'medium', 'Medium' HIGH = 'high', 'High' CRITICAL = 'critical', 'Critical' class TenantModel(models.Model): """ Abstract base model for tenant-aware models. Automatically filters by current hospital context. """ hospital = models.ForeignKey( 'organizations.Hospital', on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', db_index=True, help_text="Tenant hospital for this record" ) class Meta: abstract = True indexes = [ models.Index(fields=['hospital']), ]