import random from django.conf import settings from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from apps.profiles.models import Institution class TicketCategory(models.TextChoices): CULTURAL_AFFAIRS = 'cultural_affairs', _('Cultural & International Affairs') RELIGIOUS_INQUIRY = 'religious_inquiry', _('Religious & Theological Inquiry') INTER_CENTER_COOPERATION = 'inter_center_cooperation', _('Inter-Center Cooperation & Partnerships') FINANCIAL_LEGAL = 'financial_legal', _('Financial & Legal Affairs') TECHNICAL_SUPPORT = 'technical_support', _('Technical & Portal Support') GENERAL = 'general', _('General Inquiry') class TicketPriority(models.TextChoices): URGENT = 'urgent', _('Urgent') HIGH = 'high', _('High') MEDIUM = 'medium', _('Medium') LOW = 'low', _('Low') class TicketStatus(models.TextChoices): OPEN = 'open', _('Open') IN_PROGRESS = 'in_progress', _('In Progress') ANSWERED = 'answered', _('Official Response Provided') CLOSED = 'closed', _('Closed') class Ticket(models.Model): tracking_code = models.CharField( max_length=100, unique=True, blank=True, verbose_name=_('Tracking Code'), help_text=_('e.g. TK-RAZAVI-2026-AB12CD') ) subject = models.CharField(max_length=255, verbose_name=_('Subject / Title')) category = models.CharField( max_length=50, choices=TicketCategory.choices, default=TicketCategory.GENERAL, verbose_name=_('Category') ) priority = models.CharField( max_length=20, choices=TicketPriority.choices, default=TicketPriority.MEDIUM, verbose_name=_('Priority') ) status = models.CharField( max_length=20, choices=TicketStatus.choices, default=TicketStatus.OPEN, verbose_name=_('Status') ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='tickets', verbose_name=_('Submitter / User') ) institution = models.ForeignKey( Institution, on_delete=models.SET_NULL, null=True, blank=True, related_name='tickets', verbose_name=_('Affiliated Institution') ) assigned_expert = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_tickets', verbose_name=_('Assigned Expert / Officer') ) satisfaction_rating = models.PositiveSmallIntegerField( null=True, blank=True, verbose_name=_('Satisfaction Rating (1-5 Stars)') ) feedback_comment = models.TextField( blank=True, null=True, verbose_name=_('User Feedback Notes') ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At')) closed_at = models.DateTimeField(null=True, blank=True, verbose_name=_('Closed At')) class Meta: ordering = ('-created_at',) verbose_name = _('Ticket') verbose_name_plural = _('Tickets') def __str__(self): return f"{self.tracking_code} - {self.subject} ({self.get_status_display()})" def save(self, *args, **kwargs): if not self.tracking_code: year = timezone.now().year rand_code = ''.join(random.choices('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', k=6)) self.tracking_code = f"TK-RAZAVI-{year}-{rand_code}" super().save(*args, **kwargs) class TicketMessage(models.Model): ticket = models.ForeignKey( Ticket, on_delete=models.CASCADE, related_name='messages', verbose_name=_('Ticket') ) sender = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ticket_messages', verbose_name=_('Sender') ) is_official_response = models.BooleanField( default=False, verbose_name=_('Is Official Response from Astan / Expert') ) department_label = models.CharField( max_length=255, blank=True, default='', verbose_name=_('Department / Authority Label') ) content = models.TextField(verbose_name=_('Message Content')) attachment = models.FileField( upload_to='tickets/attachments/%Y/%m/', blank=True, null=True, verbose_name=_('Attachment') ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At')) class Meta: ordering = ('created_at',) verbose_name = _('Ticket Message') verbose_name_plural = _('Ticket Messages') def __str__(self): return f"Message on {self.ticket.tracking_code} by {self.sender} ({self.created_at})"