""" Core models - Base models for inheritance across the application """ import uuid from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import gettext_lazy as _ 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 SoftDeleteManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(is_deleted=False) class SoftDeleteModel(models.Model): is_deleted = models.BooleanField(default=False, db_index=True) deleted_at = models.DateTimeField(null=True, blank=True) deleted_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="deleted_%(class)s_set", ) objects = SoftDeleteManager() all_objects = models.Manager() class Meta: abstract = True def soft_delete(self, user=None): from django.utils import timezone self.is_deleted = True self.deleted_at = timezone.now() self.deleted_by = user self.save(update_fields=["is_deleted", "deleted_at", "deleted_by", "updated_at"]) def restore(self): self.is_deleted = False self.deleted_at = None self.deleted_by = None self.save(update_fields=["is_deleted", "deleted_at", "deleted_by", "updated_at"]) 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", help_text="Tenant hospital for this record", ) class Meta: abstract = True