You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.8 KiB
149 lines
4.8 KiB
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})"
|