from django.db import models from django.conf import settings from django.utils import timezone from core.models.directories import directory_default class Ticket(models.Model): number = models.CharField('Номер', max_length=20, unique=True, editable=False) title = models.CharField('Заголовок', max_length=200) ticket_type = models.ForeignKey( 'core.Directory', on_delete=models.PROTECT, limit_choices_to={'directory_type__code': 'ticket_type', 'is_active': True}, related_name='tickets_by_type', verbose_name='Тип', ) priority = models.ForeignKey( 'core.Directory', on_delete=models.PROTECT, limit_choices_to={'directory_type__code': 'ticket_priority', 'is_active': True}, related_name='tickets_by_priority', verbose_name='Приоритет', default=directory_default('ticket_priority', 'medium'), ) status = models.ForeignKey( 'core.Directory', on_delete=models.PROTECT, limit_choices_to={'directory_type__code': 'ticket_status', 'is_active': True}, related_name='tickets_by_status', verbose_name='Статус', default=directory_default('ticket_status', 'new'), ) requester = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='created_tickets', verbose_name='Заявитель', ) assignee = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_tickets', verbose_name='Исполнитель', ) equipment = models.ForeignKey( 'it_manager.Equipment', on_delete=models.SET_NULL, null=True, blank=True, related_name='tickets', verbose_name='Оборудование', ) location = models.ForeignKey( 'locations.Location', on_delete=models.SET_NULL, null=True, blank=True, related_name='it_tickets', verbose_name='Локация', ) description = models.TextField('Описание проблемы') resolution = models.TextField('Решение', blank=True) created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True) resolved_at = models.DateTimeField('Решена в', null=True, blank=True) closed_at = models.DateTimeField('Закрыта в', null=True, blank=True) class Meta: verbose_name = 'Заявка' verbose_name_plural = 'Заявки' ordering = ['-created_at'] def __str__(self): return f'[{self.number}] {self.title}' def save(self, *args, **kwargs): if not self.number: self.number = self._generate_number() if self.status.code == 'resolved' and not self.resolved_at: self.resolved_at = timezone.now() if self.status.code == 'closed' and not self.closed_at: self.closed_at = timezone.now() super().save(*args, **kwargs) def _generate_number(self): today = timezone.now() prefix = f'IT-{today.year}{today.month:02d}' last = ( Ticket.objects .filter(number__startswith=prefix) .order_by('-number') .values_list('number', flat=True) .first() ) seq = int(last.split('-')[-1]) + 1 if last else 1 return f'{prefix}-{seq:04d}' @property def is_overdue(self): if self.status.code in ('resolved', 'closed'): return False sla_hours = {'critical': 4, 'high': 8, 'medium': 24, 'low': 72} limit = sla_hours.get(self.priority.code, 24) return (timezone.now() - self.created_at).total_seconds() / 3600 > limit class TicketComment(models.Model): ticket = models.ForeignKey( Ticket, on_delete=models.CASCADE, related_name='comments', verbose_name='Заявка', ) author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='ticket_comments', verbose_name='Автор', ) text = models.TextField('Комментарий') is_internal = models.BooleanField( 'Внутренний', default=False, help_text='Виден только сотрудникам IT', ) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = 'Комментарий' verbose_name_plural = 'Комментарии' ordering = ['created_at'] def __str__(self): return f'{self.ticket.number} / {self.author} / {self.created_at:%d.%m.%Y %H:%M}'