Skip to content

Commit beccbbc

Browse files
committed
Autoescalation model
Signed-off-by: drona-gyawali <dronarajgyawali@gmail.com>
1 parent 8ce6fd9 commit beccbbc

File tree

11 files changed

+191
-23
lines changed

11 files changed

+191
-23
lines changed

management/core/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
Written in 2025 by Dorna Raj Gyawali <dronarajgyawali@gmail.com>
2020
"""
2121

22-
from core.models import Agent, Customer, Department, Ticket, User
2322
from django.contrib import admin
2423
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
2524

25+
from core.models import Agent, Customer, Department, Ticket, User
26+
2627

2728
@admin.register(User)
2829
class UserAdmin(BaseUserAdmin):

management/core/api/viewset.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@
1111
import logging
1212
from datetime import datetime
1313

14-
from core import dumps, validators
15-
from core.constants import Status
16-
from core.models import Agent, Customer, Ticket
17-
from core.permissions import CanEditOwnOrAdmin
18-
from core.serializer import RegisterSerializer, TicketCreateSerializer
19-
from core.tasks import process_ticket_queue
2014
from django.db import transaction
2115
from django.shortcuts import get_object_or_404
2216
from django.utils import timezone
@@ -27,6 +21,13 @@
2721
from rest_framework.response import Response
2822
from rest_framework_simplejwt.tokens import RefreshToken
2923

24+
from core import dumps, validators
25+
from core.constants import Status
26+
from core.models import Agent, Customer, Ticket
27+
from core.permissions import CanEditOwnOrAdmin
28+
from core.serializer import RegisterSerializer, TicketCreateSerializer
29+
from core.tasks import process_ticket_queue
30+
3031
logger = logging.getLogger(__name__)
3132

3233

management/core/automation/state_machine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99

1010
class TicketStateMachine:
11-
def __init__(self, ticket_id):
12-
self.ticket_id = ticket_id
11+
def __init__(self, ticket):
12+
self.ticket_id = ticket.id
1313

1414
def can_state_change(self, new_status):
1515
transition = {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Generated by Django 5.2.1 on 2025-05-17 16:51
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("core", "0003_ticket_queued_at"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="ticket",
16+
name="status",
17+
field=models.CharField(
18+
choices=[
19+
("waiting", "Waiting"),
20+
("assigned", "Assigned"),
21+
("progress", "Progress"),
22+
("completed", "Completed"),
23+
("closed", "Closed"),
24+
],
25+
default="waiting",
26+
max_length=20,
27+
),
28+
),
29+
migrations.CreateModel(
30+
name="StatusChange",
31+
fields=[
32+
(
33+
"id",
34+
models.BigAutoField(
35+
auto_created=True,
36+
primary_key=True,
37+
serialize=False,
38+
verbose_name="ID",
39+
),
40+
),
41+
(
42+
"new_status",
43+
models.CharField(
44+
choices=[
45+
("waiting", "Waiting"),
46+
("assigned", "Assigned"),
47+
("progress", "Progress"),
48+
("completed", "Completed"),
49+
("closed", "Closed"),
50+
],
51+
default="waiting",
52+
max_length=20,
53+
),
54+
),
55+
("updated_at", models.DateTimeField(auto_now_add=True)),
56+
("new_queued_at", models.DateTimeField(blank=True, null=True)),
57+
(
58+
"new_agent",
59+
models.ForeignKey(
60+
blank=True,
61+
null=True,
62+
on_delete=django.db.models.deletion.SET_NULL,
63+
related_name="status_changes",
64+
to="core.agent",
65+
),
66+
),
67+
(
68+
"ticket",
69+
models.ForeignKey(
70+
on_delete=django.db.models.deletion.CASCADE,
71+
related_name="status_changes",
72+
to="core.ticket",
73+
),
74+
),
75+
],
76+
),
77+
migrations.CreateModel(
78+
name="AutoEscalate",
79+
fields=[
80+
(
81+
"id",
82+
models.BigAutoField(
83+
auto_created=True,
84+
primary_key=True,
85+
serialize=False,
86+
verbose_name="ID",
87+
),
88+
),
89+
(
90+
"ticket",
91+
models.ForeignKey(
92+
on_delete=django.db.models.deletion.CASCADE,
93+
related_name="auto_escalate",
94+
to="core.ticket",
95+
),
96+
),
97+
(
98+
"status_change",
99+
models.ForeignKey(
100+
on_delete=django.db.models.deletion.CASCADE,
101+
related_name="auto_escalate",
102+
to="core.statuschange",
103+
),
104+
),
105+
],
106+
),
107+
]

management/core/models.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
Written in 2025 by Dorna Raj Gyawali <dronarajgyawali@gmail.com>
1919
"""
2020

21-
from core.constants import Role, Status
22-
from core.dumps import CONTEXT_403
21+
from datetime import timezone
22+
2323
from django.conf import settings
2424
from django.contrib.auth.models import AbstractUser
2525
from django.db import models
2626
from django.forms import model_to_dict
2727

28+
from core.constants import Role, Status
29+
from core.dumps import CONTEXT_403
30+
2831

2932
class User(AbstractUser):
3033
role = models.CharField(
@@ -164,3 +167,53 @@ def save(self, *args, **kwargs):
164167

165168
def __str__(self):
166169
return self.ticket_id or f"TID-{self.pk}"
170+
171+
172+
class StatusChange(models.Model):
173+
ticket = models.ForeignKey(
174+
Ticket, on_delete=models.CASCADE, related_name="status_changes"
175+
)
176+
new_status = models.CharField(
177+
max_length=20, choices=Status.choices, default=Status.WAITING
178+
)
179+
updated_at = models.DateTimeField(auto_now_add=True)
180+
new_queued_at = models.DateTimeField(null=True, blank=True)
181+
new_agent = models.ForeignKey(
182+
Agent,
183+
on_delete=models.SET_NULL,
184+
related_name="status_changes",
185+
null=True,
186+
blank=True,
187+
)
188+
189+
190+
class AutoEscalate(models.Model):
191+
ticket = models.ForeignKey(
192+
Ticket, on_delete=models.CASCADE, related_name="auto_escalate"
193+
)
194+
status_change = models.ForeignKey(
195+
StatusChange, on_delete=models.CASCADE, related_name="auto_escalate"
196+
)
197+
198+
@classmethod
199+
def escalate_changes(cls, ticket_id, new_status, new_agent=None):
200+
try:
201+
ticket = Ticket.objects.get(ticket_id=ticket_id)
202+
status_change = StatusChange.objects.create(
203+
new_status=new_status,
204+
new_agent=new_agent,
205+
new_queued_at=timezone.now(),
206+
)
207+
cls.objects.create(ticket=ticket, status_changes=status_change)
208+
ticket.status = new_status
209+
if new_agent:
210+
ticket.agent = new_agent
211+
ticket.save()
212+
return {"success": True, "message": f"Ticket {ticket_id} escalated."}
213+
except Ticket.DoesNotExist:
214+
return {
215+
"success": False,
216+
"message": f"Ticket series {ticket_id} not found.",
217+
}
218+
except Exception as e:
219+
return {"success": False, "message": f"Error during escalation: {str(e)}"}

management/core/serializer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from core.models import Agent, Customer, Department, Role, Ticket
21
from django.contrib.auth import get_user_model
32
from rest_framework import serializers
43

4+
from core.models import Agent, Customer, Department, Role, Ticket
5+
56
User = get_user_model()
67

78

management/core/tasks.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from datetime import timedelta
22

33
from celery import shared_task
4-
from core.automation.state_machine import TicketStateMachine
5-
from core.constants import Status
6-
from core.models import Agent, Ticket
74
from django.db import transaction
85
from django.db.models import Q
96
from django.utils import timezone
107

8+
from core.automation.state_machine import TicketStateMachine
9+
from core.constants import Status
10+
from core.models import Agent, Ticket
11+
1112

1213
@shared_task(bind=True)
1314
def process_ticket_queue(self):
@@ -55,8 +56,8 @@ def delete_completed_tickets(self):
5556

5657

5758
@shared_task(bind=True)
58-
def process_state_changed(ticket_id, new_staus):
59+
def process_state_changed(ticket_id, new_status):
5960
with transaction.atomic:
6061
ticket_id = Ticket.objects.select_for_update.get(ticket_id=ticket_id)
6162
state_machine = TicketStateMachine(ticket_id)
62-
return state_machine.transition_to(new_staus)
63+
return state_machine.transition_to(new_status)

management/core/tests/test_api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99
from datetime import datetime, timedelta
1010
from unittest import mock
1111

12-
from core.constants import Status
13-
from core.models import Agent, Customer, Department, Ticket, User
1412
from django.db.models import F
1513
from django.test import TestCase
1614
from django.urls import reverse
1715
from django.utils.timezone import make_aware
1816
from rest_framework import status
1917
from rest_framework.test import APIClient
2018

19+
from core.constants import Status
20+
from core.models import Agent, Customer, Department, Ticket, User
21+
2122

2223
class ApiViewsTest(TestCase):
2324
"""Test cases for all API endpoints in a single class."""

management/core/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from core.api import viewset
21
from django.urls import path
32

3+
from core.api import viewset
4+
45
urlpatterns = [
56
path("customer/<int:pk>/detail/", viewset.customer_detail, name="customer_detail"),
67
path("agent/<int:pk>/detail/", viewset.agent_detail, name="agent_detail"),

management/main/asgi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from channels.auth import AuthMiddlewareStack
1313
from channels.routing import ProtocolTypeRouter, URLRouter
1414
from channels.security.websocket import AllowedHostsOriginValidator
15-
from chat import routing
1615
from django.core.asgi import get_asgi_application
1716

17+
from chat import routing
18+
1819
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
1920

2021
django_asgi_app = get_asgi_application()

0 commit comments

Comments
 (0)