244 lines
8.3 KiB
Python
244 lines
8.3 KiB
Python
import uuid
|
|
from datetime import timedelta
|
|
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
|
|
|
|
def default_ticket_draft_expiry():
|
|
return timezone.now() + timedelta(hours=24)
|
|
|
|
|
|
class TicketStatus(models.TextChoices):
|
|
PENDING_INFO = "pending_info", "Pending info"
|
|
PENDING = "pending", "Pending"
|
|
IN_PROGRESS = "in_progress", "In progress"
|
|
PENDING_CONFIRM = "pending_confirm", "Pending confirm"
|
|
RESOLVED = "resolved", "Resolved"
|
|
CLOSED = "closed", "Closed"
|
|
|
|
|
|
class TicketPriority(models.TextChoices):
|
|
LOW = "low", "Low"
|
|
MEDIUM = "medium", "Medium"
|
|
HIGH = "high", "High"
|
|
URGENT = "urgent", "Urgent"
|
|
|
|
|
|
class TicketCategory(models.Model):
|
|
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL)
|
|
name = models.CharField(max_length=120)
|
|
code = models.CharField(max_length=80, unique=True)
|
|
description = models.TextField(blank=True)
|
|
enabled = models.BooleanField(default=True)
|
|
sort_order = models.PositiveIntegerField(default=0)
|
|
|
|
class Meta:
|
|
ordering = ["sort_order", "name"]
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
|
|
class Ticket(models.Model):
|
|
ticket_no = models.CharField(max_length=32, unique=True)
|
|
title = models.CharField(max_length=200)
|
|
description = models.TextField()
|
|
category = models.ForeignKey(TicketCategory, null=True, blank=True, on_delete=models.SET_NULL)
|
|
status = models.CharField(
|
|
max_length=32,
|
|
choices=TicketStatus.choices,
|
|
default=TicketStatus.PENDING,
|
|
)
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=TicketPriority.choices,
|
|
default=TicketPriority.MEDIUM,
|
|
)
|
|
problem_performance = models.TextField(blank=True)
|
|
problem_object = models.CharField(max_length=160, blank=True)
|
|
submitter_name = models.CharField(max_length=80, blank=True)
|
|
submitter_org = models.CharField(max_length=160, blank=True)
|
|
ai_solution = models.TextField(blank=True)
|
|
resolution_summary = models.TextField(blank=True)
|
|
is_resolved = models.BooleanField(default=False)
|
|
replied_to_customer = models.BooleanField(default=False)
|
|
impact_scope = models.CharField(max_length=80, blank=True)
|
|
environment = models.CharField(max_length=80, blank=True)
|
|
related_system = models.CharField(max_length=120, blank=True)
|
|
requester = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="requested_tickets",
|
|
)
|
|
assignee = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="assigned_tickets",
|
|
)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="created_tickets",
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=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:
|
|
ordering = ["-created_at"]
|
|
indexes = [
|
|
models.Index(fields=["ticket_no"]),
|
|
models.Index(fields=["status", "priority"]),
|
|
models.Index(fields=["created_at"]),
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.ticket_no} {self.title}"
|
|
|
|
|
|
class TicketComment(models.Model):
|
|
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="comments")
|
|
content = models.TextField()
|
|
comment_type = models.CharField(max_length=40, default="user")
|
|
visibility = models.CharField(max_length=40, default="internal")
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["created_at"]
|
|
|
|
|
|
class TicketAttachment(models.Model):
|
|
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="attachments")
|
|
file_name = models.CharField(max_length=255)
|
|
file_type = models.CharField(max_length=80, blank=True)
|
|
mime_type = models.CharField(max_length=120, blank=True)
|
|
file_size = models.PositiveBigIntegerField(default=0)
|
|
storage_path = models.CharField(max_length=500)
|
|
ocr_text = models.TextField(blank=True)
|
|
parsed_text = models.TextField(blank=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
class TicketDraft(models.Model):
|
|
public_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name="ticket_drafts",
|
|
)
|
|
status = models.CharField(max_length=24, default="editing")
|
|
revision = models.PositiveIntegerField(default=1)
|
|
source_text = models.TextField(blank=True)
|
|
current_fields = models.JSONField(default=dict)
|
|
last_ai_fields = models.JSONField(default=dict)
|
|
missing_fields = models.JSONField(default=list)
|
|
questions = models.JSONField(default=list)
|
|
guided_rounds = models.PositiveSmallIntegerField(default=0)
|
|
latest_agent_run = models.ForeignKey(
|
|
"agents.AgentRun",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="ticket_drafts",
|
|
)
|
|
latest_artifact = models.ForeignKey(
|
|
"agents.AgentArtifact",
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="ticket_drafts",
|
|
)
|
|
confirmed_ticket = models.OneToOneField(
|
|
Ticket,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.PROTECT,
|
|
related_name="source_draft",
|
|
)
|
|
decision = models.CharField(max_length=24, blank=True)
|
|
error_code = models.CharField(max_length=80, blank=True)
|
|
error_message = models.TextField(blank=True)
|
|
expires_at = models.DateTimeField(default=default_ticket_draft_expiry)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
confirmed_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ["-updated_at"]
|
|
indexes = [
|
|
models.Index(fields=["created_by", "status", "updated_at"]),
|
|
models.Index(fields=["status", "expires_at"]),
|
|
]
|
|
|
|
|
|
class TicketDraftMessage(models.Model):
|
|
draft = models.ForeignKey(TicketDraft, on_delete=models.CASCADE, related_name="messages")
|
|
role = models.CharField(max_length=24)
|
|
content = models.TextField()
|
|
round_number = models.PositiveSmallIntegerField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["created_at"]
|
|
|
|
|
|
class TicketDraftImage(models.Model):
|
|
public_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
|
draft = models.ForeignKey(TicketDraft, on_delete=models.CASCADE, related_name="images")
|
|
file_name = models.CharField(max_length=255)
|
|
mime_type = models.CharField(max_length=120)
|
|
file_size = models.PositiveBigIntegerField()
|
|
sha256 = models.CharField(max_length=64)
|
|
storage_path = models.CharField(max_length=500)
|
|
status = models.CharField(max_length=24, default="temporary")
|
|
confirmed_attachment = models.OneToOneField(
|
|
TicketAttachment,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="source_draft_image",
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["created_at"]
|
|
|
|
|
|
class TicketEvent(models.Model):
|
|
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="events")
|
|
event_type = models.CharField(max_length=80)
|
|
from_value = models.JSONField(default=dict, blank=True)
|
|
to_value = models.JSONField(default=dict, blank=True)
|
|
operator = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
remark = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["created_at"]
|