from django.db import models from django.db.models import Q from infrasynth.shared.enums import ChannelType from infrasynth.tenancy.mixins import GlobalOrTenantModel, TenantOwnedModel class NotificationTemplate(GlobalOrTenantModel): """A template. ``tenant IS NULL`` is the platform default; a tenant row overrides it.""" slug = models.SlugField(max_length=100) name = models.CharField(max_length=200) channel = models.CharField(max_length=20, choices=ChannelType.choices) subject_template = models.CharField(max_length=500) body_template = models.TextField() is_html = models.BooleanField(default=False) namespace = models.CharField(max_length=100, default="", blank=True) metadata = models.JSONField(default=dict) class Meta: db_table = "notifications_template" constraints = [ models.UniqueConstraint( fields=["tenant", "slug", "namespace"], name="uniq_template_slug_namespace_per_tenant", ), models.UniqueConstraint( fields=["slug", "namespace"], condition=Q(tenant__isnull=True), name="uniq_global_template_slug_namespace", ), ] class NotificationDispatch(TenantOwnedModel): class Status(models.TextChoices): PENDING = "pending", "Pending" SENT = "sent", "Sent" FAILED = "failed", "Failed" RETRYING = "retrying", "Retrying" template = models.ForeignKey( NotificationTemplate, on_delete=models.SET_NULL, null=True, blank=True, related_name="dispatches", ) recipient = models.CharField(max_length=255) channel = models.CharField(max_length=20, choices=ChannelType.choices) subject = models.CharField(max_length=500) body = models.TextField() status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) error_message = models.TextField(blank=True, default="") attempt = models.PositiveSmallIntegerField(default=0) next_retry_at = models.DateTimeField(null=True, blank=True) context_snapshot = models.JSONField(default=dict, blank=True) created_at = models.DateTimeField(auto_now_add=True) completed_at = models.DateTimeField(null=True, blank=True) request_id = models.UUIDField(null=True, blank=True) class Meta: db_table = "notifications_dispatch" indexes = [models.Index(fields=["tenant_id", "status"])] class ChannelConfig(TenantOwnedModel): slug = models.SlugField(max_length=100) channel_type = models.CharField(max_length=20, choices=ChannelType.choices) display_name = models.CharField(max_length=200) config = models.JSONField(default=dict) is_active = models.BooleanField(default=True) priority = models.PositiveSmallIntegerField(default=0) class Meta: db_table = "notifications_channel_config" constraints = [ models.UniqueConstraint(fields=["tenant", "slug"], name="uniq_channel_config_slug_per_tenant"), ]