first push

This commit is contained in:
2026-01-18 11:59:48 +03:30
commit 107854bde3
1100 changed files with 291872 additions and 0 deletions

0
ticket/__init__.py Normal file
View File

3
ticket/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
ticket/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class TicketConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ticket'

97
ticket/bucket.py Normal file
View File

@@ -0,0 +1,97 @@
from botocore.exceptions import NoCredentialsError
import boto3
import logging
from PIL import Image
import io
import base64
LIARA_ENDPOINT = 'https://storage.c2.liara.space'
LIARA_BUCKET_NAME = 'ticket-rasadyar'
LIARA_ACCESS_KEY = "gvqohestrakmqi6n"
LIARA_SECRET_KEY = '7240fdd8-59bc-4f02-b5e6-4a124e37fa0e'
RASADYAR_ENDPOINT = 'https://s3.rasadyar.com'
RASADYAR_BUCKET_NAME = 'rasadyar'
RASADYAR_ACCESS_KEY = "zG3ewsbYsTqCmuws"
RASADYAR_SECRET_KEY = 'RInUMB78zlQZp6CNf8+sRoSh2cNDHcGQhXrLnTJ1AuI='
def upload_to_liara(image, name):
s3 = boto3.client(
's3',
endpoint_url=RASADYAR_ENDPOINT,
aws_access_key_id=RASADYAR_ACCESS_KEY,
aws_secret_access_key=RASADYAR_SECRET_KEY
)
s3.upload_fileobj(
image,
RASADYAR_BUCKET_NAME,
name,
ExtraArgs={'ACL': 'public-read'} # دسترسی عمومی
)
return f"{RASADYAR_ENDPOINT}/{RASADYAR_BUCKET_NAME}/{name}"
def connect():
logging.basicConfig(level=logging.INFO)
try:
s3 = boto3.client(
's3',
endpoint_url=LIARA_ENDPOINT,
aws_access_key_id=LIARA_ACCESS_KEY,
aws_secret_access_key=LIARA_SECRET_KEY
)
except Exception as exc:
logging.info(exc)
return s3
def upload_object_resize_to_liara(image_data, object_name):
try:
imgdata = base64.b64decode(image_data)
img = Image.open(io.BytesIO(imgdata))
img.thumbnail((500, 500))
buffer = io.BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)
s3_resource = boto3.resource(
's3',
endpoint_url=LIARA_ENDPOINT,
aws_access_key_id=LIARA_ACCESS_KEY,
aws_secret_access_key=LIARA_SECRET_KEY
)
bucket = s3_resource.Bucket(LIARA_BUCKET_NAME)
bucket.put_object(
ACL='public-read',
Body=buffer,
Key=object_name,
ContentType='image/png'
)
return f"{LIARA_ENDPOINT}/{LIARA_BUCKET_NAME}/{object_name}"
except Exception as e:
raise Exception(f"خطا در آپلود فایل: {e}")
def delete_file_from_liara(file_name):
try:
s3 = boto3.client(
's3',
endpoint_url=LIARA_ENDPOINT,
aws_access_key_id=LIARA_ACCESS_KEY,
aws_secret_access_key=LIARA_SECRET_KEY
)
s3.delete_object(Bucket=LIARA_BUCKET_NAME, Key=file_name)
except NoCredentialsError:
raise Exception("اعتبارنامه‌های AWS معتبر نیستند")
except Exception as e:
raise Exception(f"خطا در آپلود فایل: {e}")

127
ticket/customer_views.py Normal file
View File

@@ -0,0 +1,127 @@
import random
from rest_framework import viewsets
from oauth2_provider.contrib.rest_framework import (
TokenHasReadWriteScope,
)
from .models import (
Ticket,
TicketContent,
Answer,
Question,
SupportUnit,
Group,
SystemUserProfile
)
from .serializers import (
TicketSerializer,
TicketContentSerializer,
TicketAnswerSerializer,
TicketQuestionSerializer,
SupportUnitSerializer
)
from rest_framework.response import Response
from .helper import upload_listed_image
from rest_framework import status
from datetime import datetime
class CustomerTicketViewSet(viewsets.ModelViewSet):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
permission_classes = [TokenHasReadWriteScope]
def list(self, request, *args, **kwargs):
# get user object
user = SystemUserProfile.objects.get(user=request.user)
# different style of ticket information
if 'pending' in request.GET:
ticket = self.queryset.filter(customer=user, state='pending')
serializer = self.serializer_class(ticket, many=True)
elif 'responded' in request.GET:
ticket = self.queryset.filter(customer=user, state='responded')
serializer = self.serializer_class(ticket, many=True)
elif 'closed' in request.GET:
ticket = self.queryset.filter(customer=user, state='closed')
serializer = self.serializer_class(ticket, many=True)
elif 'all' in request.GET:
ticket = self.queryset.filter(customer=user)
serializer = self.serializer_class(ticket, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, *args, **kwargs):
ticket = self.queryset.get(key=request.data['key'])
serializer = self.serializer_class(ticket)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
if 'ticket_key' in request.data.keys():
ticket_key = request.data['ticket_key']
request.data.pop('ticket_key')
else:
ticket_key = None
# get group unit information
group = Group.objects.get(
name__exact=request.data['support_unit']
)
request.data.pop('support_unit') # pop this block from body data
# get support unit object information
support_unit = SupportUnit.objects.get(
unit=group
)
# ticket number
ticket_id = random.randint(1000000, 9999999)
# process list of images
req = upload_listed_image(req=request, field='image')
# create ticket content
content_serializer = TicketContentSerializer(data=req.data)
if content_serializer.is_valid():
content_obj = content_serializer.create(validated_data=req.data)
# create question from customer
question = Question(
questioner=SystemUserProfile.objects.get(user=request.user),
content=content_obj
)
question.save()
if ticket_key is not None:
ticket = Ticket.objects.get(key=ticket_key)
ticket.question.add(question)
ticket.state = 'pending'
ticket.save()
else:
# create final ticket
ticket = Ticket(
customer=SystemUserProfile.objects.get(user=request.user),
support_unit=support_unit,
state='pending',
ticket_id=ticket_id
)
ticket.save()
ticket.question.add(question)
serializer = self.serializer_class(ticket)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
ticket = self.queryset.get(key=request.data['ticket_key']) # contains ticket object
request.data.pop('ticket_key')
# send ticket object to serializer for update
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
ticket_updated = serializer.update(validated_data=request.data, instance=ticket)
serializer = self.serializer_class(ticket_updated)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors)
def destroy(self, request, *args, **kwargs):
pass

75
ticket/helper.py Normal file
View File

@@ -0,0 +1,75 @@
import base64
import io
import os
import random
import string
import boto3
from PIL import Image
from ticket.bucket import RASADYAR_ENDPOINT, RASADYAR_BUCKET_NAME, RASADYAR_ACCESS_KEY, RASADYAR_SECRET_KEY
ARVAN_TICKET_GALLERY = "https://profileimagedefault.s3.ir-thr-at1.arvanstorage.ir/"
# ARVAN_TICKET_GALLERY = "https://ticketgallery.s3.ir-thr-at1.arvanstorage.com/"
ARVAN_User_Image_URL = 'https://profileimagedefault.s3.ir-thr-at1.arvanstorage.ir/'
def send_image_to_server(image):
ran = ''.join(random.choices(string.ascii_uppercase + string.digits, k=15))
imgdata = base64.b64decode(image)
img = Image.open(io.BytesIO(imgdata))
img.thumbnail((500, 500))
buffer = io.BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)
s3_resource = boto3.resource(
's3',
endpoint_url=RASADYAR_ENDPOINT,
aws_access_key_id=RASADYAR_ACCESS_KEY,
aws_secret_access_key=RASADYAR_SECRET_KEY
)
bucket = s3_resource.Bucket(RASADYAR_BUCKET_NAME)
bucket.put_object(
ACL='public-read',
Body=buffer,
Key=ran + '.jpg',
ContentType='image/png'
)
image_url = f"{RASADYAR_ENDPOINT}/{RASADYAR_BUCKET_NAME}/{ran + '.jpg'}"
return image_url
def upload_listed_image(req=None, field=None):
image_list = []
if req.data[field] != []:
for item in req.data[field]:
image_list.append(send_image_to_server(item))
req.data.pop(field)
req.data[field] = image_list
elif req.data[field] == "":
req.data[field] = "empty"
return req
def send_image_to_server_for_poultry_science(image, name):
s3 = boto3.client(
's3',
endpoint_url=RASADYAR_ENDPOINT,
aws_access_key_id=RASADYAR_ACCESS_KEY,
aws_secret_access_key=RASADYAR_SECRET_KEY
)
s3.upload_fileobj(
image,
RASADYAR_BUCKET_NAME,
name,
ExtraArgs={'ACL': 'public-read'} # دسترسی عمومی
)
return f"{RASADYAR_ENDPOINT}/{RASADYAR_BUCKET_NAME}/{name}"

View File

@@ -0,0 +1,150 @@
# Generated by Django 3.2.13 on 2023-09-18 19:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('authentication', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Answer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('state', models.CharField(max_length=100, null=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Question',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('state', models.CharField(max_length=100, null=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SupportUnit',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('name', models.CharField(max_length=100, null=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supportunit_createdby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='supportunit_modifiedby', to=settings.AUTH_USER_MODEL)),
('unit', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='support_unit', to='auth.group')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TicketContent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('title', models.CharField(max_length=200, null=True)),
('content', models.TextField(max_length=2000, null=True)),
('image', models.JSONField(default=dict)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketcontent_createdby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketcontent_modifiedby', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('ticket_id', models.BigIntegerField(default=0)),
('state', models.CharField(max_length=100, null=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_createdby', to=settings.AUTH_USER_MODEL)),
('customer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_customer', to='authentication.systemuserprofile')),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_modifiedby', to=settings.AUTH_USER_MODEL)),
('operator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_operator', to='authentication.systemuserprofile')),
('question', models.ManyToManyField(related_name='ticket_question', to='ticket.Question')),
('respond', models.ManyToManyField(related_name='ticket_respond', to='ticket.Answer')),
('support_unit', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_unit', to='ticket.supportunit')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='question',
name='content',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questioner_content', to='ticket.ticketcontent'),
),
migrations.AddField(
model_name='question',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='question_createdby', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='question',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='question_modifiedby', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='question',
name='questioner',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='questioner', to='authentication.systemuserprofile'),
),
migrations.AddField(
model_name='answer',
name='content',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answer_content', to='ticket.ticketcontent'),
),
migrations.AddField(
model_name='answer',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answer_createdby', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='answer',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answer_modifiedby', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='answer',
name='question',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answer_question', to='ticket.question'),
),
migrations.AddField(
model_name='answer',
name='responder',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answer', to='authentication.systemuserprofile'),
),
]

View File

@@ -0,0 +1,66 @@
# Generated by Django 3.2.13 on 2024-06-12 09:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('authentication', '0035_auto_20240612_0954'),
('ticket', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='answer',
name='key',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AlterField(
model_name='question',
name='key',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AlterField(
model_name='supportunit',
name='key',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AlterField(
model_name='ticket',
name='key',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.AlterField(
model_name='ticketcontent',
name='key',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.CreateModel(
name='TicketSupport',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('userlocation', models.CharField(max_length=250, null=True)),
('title', models.CharField(max_length=100, null=True)),
('picture', models.CharField(max_length=250, null=True)),
('text', models.TextField(null=True)),
('ticket_id', models.BigIntegerField(default=0)),
('state', models.CharField(default='open', max_length=100)),
('type', models.CharField(max_length=100, null=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketsupport_createdby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketsupport_modifiedby', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_support', to='authentication.systemuserprofile')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-06-12 09:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0002_auto_20240612_0954'),
]
operations = [
migrations.AlterField(
model_name='ticketsupport',
name='ticket_id',
field=models.BigIntegerField(default=1),
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.2.13 on 2024-06-12 19:36
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authentication', '0037_auto_20240612_1936'),
('ticket', '0003_alter_ticketsupport_ticket_id'),
]
operations = [
migrations.RenameField(
model_name='ticketsupport',
old_name='state',
new_name='status',
),
migrations.CreateModel(
name='MessageSupport',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='authentication.systemuserprofile')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='ticket.messagesupport')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='ticket.ticketsupport')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-06-15 14:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0004_auto_20240612_1936'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='sender',
field=models.CharField(default='user', max_length=50),
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 3.2.13 on 2024-06-15 14:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0005_messagesupport_sender'),
]
operations = [
migrations.RemoveField(
model_name='ticketsupport',
name='picture',
),
migrations.RemoveField(
model_name='ticketsupport',
name='text',
),
migrations.RemoveField(
model_name='ticketsupport',
name='userlocation',
),
migrations.AddField(
model_name='messagesupport',
name='picture',
field=models.CharField(max_length=250, null=True),
),
migrations.AddField(
model_name='messagesupport',
name='user_location',
field=models.CharField(max_length=250, null=True),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.2.13 on 2024-08-30 10:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authentication', '0045_auto_20240830_1034'),
('ticket', '0006_auto_20240615_1418'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='to_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='to_user_ticket', to='authentication.systemuserprofile'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-08-31 08:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0007_ticketsupport_to_user'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='send_message',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-09-07 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0008_messagesupport_send_message'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='is_read',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.2.13 on 2024-09-07 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ticket', '0009_ticketsupport_is_read'),
]
operations = [
migrations.RemoveField(
model_name='ticketsupport',
name='is_read',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-09-07 13:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0010_remove_ticketsupport_is_read'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='last_message',
field=models.CharField(max_length=100, null=True),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 3.2.13 on 2024-09-07 14:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ticket', '0011_ticketsupport_last_message'),
]
operations = [
migrations.RemoveField(
model_name='messagesupport',
name='user_location',
),
migrations.RemoveField(
model_name='ticketsupport',
name='type',
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.2.13 on 2024-09-10 13:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('ticket', '0012_auto_20240907_1417'),
]
operations = [
migrations.CreateModel(
name='TicketPermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('from_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_role', to='auth.group')),
('to_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_role', to='auth.group')),
],
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 3.2.13 on 2024-09-11 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('ticket', '0013_ticketpermission'),
]
operations = [
migrations.RemoveField(
model_name='ticketpermission',
name='from_role',
),
migrations.RemoveField(
model_name='ticketpermission',
name='to_role',
),
migrations.AddField(
model_name='ticketpermission',
name='role',
field=models.CharField(max_length=200, null=True),
),
migrations.AddField(
model_name='ticketpermission',
name='roles',
field=models.ManyToManyField(null=True, related_name='to_role', to='auth.Group'),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 3.2.13 on 2024-09-12 11:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('authentication', '0047_auto_20240912_1105'),
('ticket', '0014_auto_20240911_0853'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='to_role',
field=models.ManyToManyField(null=True, related_name='to_role_ticket', to='auth.Group'),
),
migrations.AddField(
model_name='ticketsupport',
name='type_ticket',
field=models.CharField(default='single', max_length=100),
),
migrations.RemoveField(
model_name='ticketsupport',
name='to_user',
),
migrations.AddField(
model_name='ticketsupport',
name='to_user',
field=models.ManyToManyField(null=True, related_name='to_user_ticket', to='authentication.SystemUserProfile'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-09-12 11:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0015_auto_20240912_1105'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='picture',
field=models.CharField(max_length=250, null=True),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 3.2.13 on 2024-09-12 11:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0048_auto_20240912_1118'),
('auth', '0012_alter_user_first_name_max_length'),
('ticket', '0016_ticketsupport_picture'),
]
operations = [
migrations.AlterField(
model_name='ticketpermission',
name='roles',
field=models.ManyToManyField(related_name='to_role', to='auth.Group'),
),
migrations.AlterField(
model_name='ticketsupport',
name='to_role',
field=models.ManyToManyField(related_name='to_role_ticket', to='auth.Group'),
),
migrations.AlterField(
model_name='ticketsupport',
name='to_user',
field=models.ManyToManyField(related_name='to_user_ticket', to='authentication.SystemUserProfile'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.2.13 on 2024-09-12 12:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ticket', '0017_auto_20240912_1118'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='parent',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='ticket.ticketsupport'),
),
migrations.AddField(
model_name='ticketsupport',
name='read_only',
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.2.13 on 2024-09-14 09:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ticket', '0018_auto_20240912_1258'),
]
operations = [
migrations.RemoveField(
model_name='ticketsupport',
name='picture',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-09-14 09:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0019_remove_ticketsupport_picture'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='role',
field=models.CharField(max_length=100, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-09-14 10:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0020_ticketsupport_role'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='last_seen',
field=models.DateTimeField(null=True),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.2.13 on 2024-09-17 09:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0021_messagesupport_last_seen'),
]
operations = [
migrations.RemoveField(
model_name='ticketsupport',
name='id',
),
migrations.AlterField(
model_name='ticketsupport',
name='ticket_id',
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.2.13 on 2024-12-28 09:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ticket', '0022_auto_20240917_0951'),
]
operations = [
migrations.RemoveField(
model_name='messagesupport',
name='ticket',
),
migrations.DeleteModel(
name='TicketSupport',
),
]

View File

@@ -0,0 +1,45 @@
# Generated by Django 3.2.13 on 2024-12-28 09:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0012_alter_user_first_name_max_length'),
('authentication', '0054_auto_20241228_0926'),
('ticket', '0023_auto_20241228_0919'),
]
operations = [
migrations.CreateModel(
name='TicketSupport',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('create_date', models.DateTimeField(auto_now_add=True)),
('modify_date', models.DateTimeField(auto_now=True)),
('trash', models.BooleanField(default=False)),
('title', models.CharField(max_length=100, null=True)),
('ticket_id', models.IntegerField(null=True, unique=True)),
('status', models.CharField(default='open', max_length=100)),
('last_message', models.CharField(max_length=100, null=True)),
('type_ticket', models.CharField(default='single', max_length=100)),
('read_only', models.BooleanField(default=True)),
('role', models.CharField(max_length=100, null=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketsupport_createdby', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticketsupport_modifiedby', to=settings.AUTH_USER_MODEL)),
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='ticket.ticketsupport')),
('to_role', models.ManyToManyField(related_name='to_role_ticket', to='auth.Group')),
('to_user', models.ManyToManyField(related_name='to_user_ticket', to='authentication.SystemUserProfile')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_support', to='authentication.systemuserprofile')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.13 on 2024-12-28 09:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ticket', '0024_ticketsupport'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='ticket',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='ticket.ticketsupport'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-12-29 21:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0025_messagesupport_ticket'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='file',
field=models.TextField(null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2024-12-31 21:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0026_messagesupport_file'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='unread_message',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.13 on 2025-01-18 14:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0056_auto_20250118_1418'),
('ticket', '0027_ticketsupport_unread_message'),
]
operations = [
migrations.AddField(
model_name='messagesupport',
name='read_by',
field=models.ManyToManyField(blank=True, related_name='read_messages', to='authentication.SystemUserProfile'),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 3.2.13 on 2025-01-26 20:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authentication', '0058_auto_20250126_2046'),
('ticket', '0028_messagesupport_read_by'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='is_referred',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='ticketsupport',
name='referred_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='referred_by_me', to='authentication.systemuserprofile'),
),
migrations.AddField(
model_name='ticketsupport',
name='referred_to',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='referred_tickets', to='authentication.systemuserprofile'),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 3.2.13 on 2025-01-26 21:22
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authentication', '0059_auto_20250126_2122'),
('ticket', '0029_auto_20250126_2046'),
]
operations = [
migrations.RemoveField(
model_name='ticketsupport',
name='referred_to',
),
migrations.AddField(
model_name='ticketsupport',
name='referred_to',
field=models.ManyToManyField(related_name='referred_tickets', to='authentication.SystemUserProfile'),
),
migrations.AlterField(
model_name='ticketsupport',
name='user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_support', to='authentication.systemuserprofile'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2025-02-01 12:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0030_auto_20250126_2122'),
]
operations = [
migrations.AddField(
model_name='ticketsupport',
name='referred_date',
field=models.DateTimeField(auto_now=True, null=True),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 3.2.13 on 2025-05-25 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0031_ticketsupport_referred_date'),
]
operations = [
migrations.CreateModel(
name='TicketClosePermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False)),
('day', models.SmallIntegerField(default=1)),
],
),
]

View File

190
ticket/models.py Normal file
View File

@@ -0,0 +1,190 @@
from django.db import models
from authentication.models import (
SystemUserProfile,
BaseModel,
Group
)
from panel.models import ProvinceOperator
# Create your models here.
class Ticket(BaseModel):
customer = models.ForeignKey(
SystemUserProfile,
on_delete=models.CASCADE,
related_name='ticket_customer',
null=True
)
operator = models.ForeignKey(
SystemUserProfile,
on_delete=models.CASCADE,
related_name='ticket_operator',
null=True
)
question = models.ManyToManyField(
'Question',
related_name="ticket_question",
)
respond = models.ManyToManyField(
'Answer',
related_name="ticket_respond"
)
support_unit = models.ForeignKey(
'SupportUnit',
on_delete=models.CASCADE,
related_name="ticket_unit",
null=True
)
ticket_id = models.BigIntegerField(default=0)
state = models.CharField(max_length=100, null=True)
def save(self, *args, **kwargs):
super(Ticket, self).save(*args, **kwargs)
class TicketContent(BaseModel):
title = models.CharField(max_length=200, null=True)
content = models.TextField(max_length=2000, null=True)
image = models.JSONField(default=dict)
def save(self, *args, **kwargs):
super(TicketContent, self).save(*args, **kwargs)
class Question(BaseModel):
questioner = models.ForeignKey(
SystemUserProfile,
on_delete=models.CASCADE,
related_name="questioner",
null=True
)
content = models.ForeignKey(
TicketContent,
on_delete=models.CASCADE,
related_name="questioner_content"
)
state = models.CharField(max_length=100, null=True)
def save(self, *args, **kwargs):
super(Question, self).save(*args, **kwargs)
class Answer(BaseModel):
responder = models.ForeignKey(
SystemUserProfile,
on_delete=models.CASCADE,
related_name="answer",
null=True
)
question = models.ForeignKey(
Question,
on_delete=models.CASCADE,
related_name='answer_question',
null=True
)
content = models.ForeignKey(
TicketContent,
on_delete=models.CASCADE,
related_name='answer_content',
null=True
)
state = models.CharField(max_length=100, null=True)
def save(self, *args, **kwargs):
super(Answer, self).save(*args, **kwargs)
class SupportUnit(BaseModel):
name = models.CharField(max_length=100, null=True)
unit = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name="support_unit",
null=True
)
def save(self, *args, **kwargs):
super(SupportUnit, self).save(*args, **kwargs)
class TicketSupport(BaseModel):
user = models.ForeignKey(
SystemUserProfile,
on_delete=models.SET_NULL,
related_name='ticket_support',
null=True
)
referred_to = models.ManyToManyField(
SystemUserProfile,
related_name='referred_tickets',
)
referred_by = models.ForeignKey(
SystemUserProfile,
on_delete=models.SET_NULL,
related_name='referred_by_me',
null=True,
)
to_user=models.ManyToManyField(
SystemUserProfile,
related_name='to_user_ticket',
)
to_role = models.ManyToManyField(
Group,
related_name='to_role_ticket',
)
parent=models.ForeignKey('TicketSupport',on_delete=models.CASCADE,null=True)
title = models.CharField(max_length=100, null=True)
ticket_id = models.IntegerField(unique=True, null=True)
status = models.CharField(max_length=100, default='open')
last_message = models.CharField(max_length=100, null=True)
type_ticket = models.CharField(default='single',max_length=100)
read_only = models.BooleanField(default=True)
role = models.CharField(max_length=100, null=True)
unread_message=models.BooleanField(default=False)
is_referred = models.BooleanField(default=False)
referred_date = models.DateTimeField(auto_now=True,null=True)
def save(self, *args, **kwargs):
if not self.ticket_id:
last_ticket = TicketSupport.objects.order_by('-ticket_id').first()
self.ticket_id = last_ticket.ticket_id + 1 if last_ticket else 1
super(TicketSupport, self).save(*args, **kwargs)
class MessageSupport(models.Model):
ticket = models.ForeignKey(TicketSupport, related_name='messages', on_delete=models.CASCADE,null=True)
created_by = models.ForeignKey(SystemUserProfile, on_delete=models.CASCADE)
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies', on_delete=models.CASCADE)
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
sender = models.CharField(max_length=50,default='user')
picture = models.CharField(max_length=250, null=True)
send_message = models.BooleanField(default=False)
last_seen=models.DateTimeField(null=True)
file=models.TextField( null=True)
read_by = models.ManyToManyField(SystemUserProfile, related_name='read_messages', blank=True)
def save(self, *args, **kwargs):
super(MessageSupport,self).save(*args, **kwargs)
class TicketPermission(models.Model):
role=models.CharField(max_length=200,null=True)
roles=models.ManyToManyField(Group,related_name='to_role')
def save(self, *args, **kwargs):
super(TicketPermission,self).save(*args, **kwargs)
class TicketClosePermission(models.Model):
active = models.BooleanField(default=False)
day = models.SmallIntegerField(default=1)
def save(self, *args, **kwargs):
super(TicketClosePermission, self).save(*args, **kwargs)

117
ticket/operator_views.py Normal file
View File

@@ -0,0 +1,117 @@
from rest_framework import viewsets
from oauth2_provider.contrib.rest_framework import (
TokenHasReadWriteScope,
)
from .models import (
Ticket,
TicketContent,
Answer,
Question,
SupportUnit,
Group,
SystemUserProfile
)
from .serializers import (
TicketSerializer,
TicketContentSerializer,
TicketAnswerSerializer,
TicketQuestionSerializer,
SupportUnitSerializer
)
from rest_framework.response import Response
from .helper import upload_listed_image
from rest_framework import status
from datetime import datetime
class OperatorTicketViewSet(viewsets.ModelViewSet):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
permission_classes = [TokenHasReadWriteScope]
def ticket_by_flag(self, request, state):
ticket_list = []
roles = SystemUserProfile.objects.get(user=request.user).role.all()
if state is not None:
ticket = self.queryset.filter(state=state)
else:
ticket = self.queryset.all()
for item in ticket:
for role in roles:
if item.support_unit.unit == role:
ticket_list.append(item)
return ticket_list
def list(self, request, *args, **kwargs):
# get user object
user = SystemUserProfile.objects.get(user=request.user)
# different style of ticket information
if 'pending' in request.GET:
ticket_list = self.ticket_by_flag(request=request, state='pending')
serializer = self.serializer_class(ticket_list, many=True)
elif 'responded' in request.GET:
ticket = self.queryset.filter(operator=user, state='responded')
serializer = self.serializer_class(ticket, many=True)
elif 'closed' in request.GET:
ticket = self.queryset.filter(operator=user, state='closed')
serializer = self.serializer_class(ticket, many=True)
elif 'all' in request.GET:
ticket_list = self.ticket_by_flag(request=request, state=None)
serializer = self.serializer_class(ticket_list, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, *args, **kwargs):
ticket = self.queryset.get(key=request.data['key'])
serializer = self.serializer_class(ticket)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
ticket = self.queryset.get(key=request.data['ticket_key']) # contains ticket object
request.data.pop('ticket_key')
# get question object
# question = Question.objects.get(key=request.data['question_key'])
# request.data.pop('question_key')
question = ticket.question.all().last()
# create list of images
req = upload_listed_image(req=request, field='image')
# create content object information
content_serializer = TicketContentSerializer(data=req.data)
if content_serializer.is_valid():
content_obj = content_serializer.create(validated_data=req.data)
# create responder object information
answer = Answer(
responder=SystemUserProfile.objects.get(user=request.user),
content=content_obj,
question=question
)
answer.save()
# set respond for ticket questioner
ticket.operator = SystemUserProfile.objects.get(user=request.user)
ticket.state = 'responded'
ticket.save()
ticket.respond.add(answer)
serializer = self.serializer_class(ticket)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
ticket = self.queryset.get(key=request.data['ticket_key']) # contains ticket object
request.data.pop('ticket_key')
# send ticket object to serializer for update
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
ticket_updated = serializer.update(validated_data=request.data, instance=ticket)
serializer = self.serializer_class(ticket_updated)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors)
def destroy(self, request, *args, **kwargs):
pass

163
ticket/serializers.py Normal file
View File

@@ -0,0 +1,163 @@
from rest_framework import serializers
from authentication.models import SystemUserProfile
from .models import (
Ticket,
TicketContent,
Question,
Answer,
SupportUnit, TicketSupport, MessageSupport, TicketPermission, TicketClosePermission
)
from authentication.serializer.serializer import (
SystemUserProfileSerializer,
GroupSerializer, SystemUserProfileBaseInfoForTicketSerializer
)
class TicketContentSerializer(serializers.ModelSerializer):
class Meta:
model = TicketContent
exclude = (
'id',
'modify_date',
'created_by',
'modified_by',
'trash',
)
class TicketQuestionSerializer(serializers.ModelSerializer):
content = TicketContentSerializer(required=False)
class Meta:
model = Question
fields = (
'key',
'content'
)
class TicketAnswerSerializer(serializers.ModelSerializer):
content = TicketContentSerializer(required=False)
# question = TicketQuestionSerializer(required=False)
class Meta:
model = Answer
fields = (
'key',
'content',
# 'question'
)
class SupportUnitSerializer(serializers.ModelSerializer):
unit = GroupSerializer(required=False)
class Meta:
model = SupportUnit
fields = (
'unit',
)
class TicketSerializer(serializers.ModelSerializer):
customer = SystemUserProfileSerializer(required=False)
operator = SystemUserProfileSerializer(required=False)
support_unit = SupportUnitSerializer(required=False)
# question = TicketQuestionSerializer(required=False, many=True)
# respond = TicketAnswerSerializer(required=False, many=True)
title = serializers.SerializerMethodField('get_title')
data = serializers.SerializerMethodField('get_responds')
class Meta:
model = Ticket
exclude = (
'id',
'created_by',
'modified_by',
'trash',
'question',
'respond',
)
def get_title(self, instance):
title = instance.question.all()
title = title[0].content.title
return title
def get_responds(self, instance):
data = []
for i in instance.question.all():
question = TicketQuestionSerializer(i).data
question['type'] = 'customer'
data.append(question)
for j in instance.respond.filter(question=i):
answer = TicketAnswerSerializer(j).data
answer['type'] = 'operator'
data.append(answer)
return data
def update(self, instance, validated_data):
instance.state = validated_data.get('state', instance.state)
instance.save()
return instance
class SystemUserProfileForTicketPermissionSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUserProfile
fields=['key','fullname','mobile']
class TicketClosePermissionSerializer(serializers.ModelSerializer):
class Meta:
model = TicketClosePermission
fields = '__all__'
class TicketSupportSerializer(serializers.ModelSerializer):
user = SystemUserProfileForTicketPermissionSerializer(read_only=True)
to_user = SystemUserProfileForTicketPermissionSerializer(read_only=True,many=True)
referred_by = SystemUserProfileForTicketPermissionSerializer(read_only=True)
referred_to = SystemUserProfileForTicketPermissionSerializer(read_only=True,many=True)
to_role = GroupSerializer(read_only=True,many=True)
class Meta:
model = TicketSupport
fields = '__all__'
class MessageSupportSerializer(serializers.ModelSerializer):
created_by = SystemUserProfileForTicketPermissionSerializer(read_only=True)
parent = SystemUserProfileForTicketPermissionSerializer(read_only=True)
read_by = SystemUserProfileForTicketPermissionSerializer(read_only=True,many=True)
ticket= TicketSupportSerializer(read_only=True)
class Meta:
model = MessageSupport
fields = '__all__'
class MessageSupportWithoutReadBySerializer(serializers.ModelSerializer):
created_by = SystemUserProfileForTicketPermissionSerializer(read_only=True)
parent = SystemUserProfileForTicketPermissionSerializer(read_only=True)
# read_by = SystemUserProfileForTicketPermissionSerializer(read_only=True,many=True)
class Meta:
model = MessageSupport
exclude = ('read_by',)
depth=1
class TicketPermissionSerializer(serializers.ModelSerializer):
roles=serializers.SerializerMethodField()
class Meta:
model = TicketPermission
fields = ['id','role','roles']
def get_roles(self,obj):
permissions = TicketPermission.objects.filter(role=obj.role).prefetch_related('roles')
roles = [role.name for permission in permissions for role in permission.roles.all()]
return roles

3
ticket/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
ticket/urls.py Normal file
View File

@@ -0,0 +1,22 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from . import customer_views, operator_views,views
from .views import get_num_message, closed_unread_ticket, get_unread_ticket_for_dashboard
router = DefaultRouter()
router.register('create_ticket', customer_views.CustomerTicketViewSet, basename='create_ticket')
router.register('respond', operator_views.OperatorTicketViewSet, basename='respond')
router.register(r'ticket', views.TicketSupportViewSet, basename='ticket')
router.register(r'message', views.MessageSupportViewSet, basename='message')
router.register(r'message-for-role', views.MessageForRoleViewSet, basename='message_for_role')
router.register(r'ticket-permission', views.TicketPermissionViewSet, basename='ticket-permission')
router.register(r'get-user-from-role', views.GetUserFromRoleViewSet, basename='get_user_from_role')
router.register(r'ticket-close-permission', views.TicketClosePermissionViewSet, basename='ticket-close-permission')
urlpatterns = [
path('', include(router.urls)),
path('get_num_message/', get_num_message),
path('closed_unread_ticket/', closed_unread_ticket),
path('get_unread_ticket_for_dashboard/', get_unread_ticket_for_dashboard),
]

512
ticket/views.py Normal file
View File

@@ -0,0 +1,512 @@
import datetime
import random
import string
import requests
from django.db.models import Q, Count
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import api_view, permission_classes
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from authentication.models import SystemUserProfile, Group
from authentication.sms_management import ticket_answered
from helper_eata import token, chat_id
from panel.filterset import TicketsFilterSet
from panel.helper import build_query
from ticket.models import TicketSupport, MessageSupport, TicketPermission, TicketClosePermission
from ticket.serializers import TicketSupportSerializer, MessageSupportSerializer, TicketPermissionSerializer, \
SystemUserProfileForTicketPermissionSerializer, MessageSupportWithoutReadBySerializer, \
TicketClosePermissionSerializer
from .bucket import upload_to_liara
from .helper import send_image_to_server
class CustomPagination(PageNumberPagination):
page_size = 10
def update_unread_ticket(tickets, user):
user_roles = list(user.role.all())
unread_ticket_ids = MessageSupport.objects.filter(
Q(ticket__user=user) |
Q(ticket__to_user=user) |
Q(ticket__to_role__in=user_roles) |
Q(ticket__referred_by=user) |
Q(ticket__referred_to=user.id),
~Q(created_by=user),
~Q(read_by=user.id),
ticket__in=tickets
).values_list('ticket_id', flat=True).distinct()
unread_set = set(unread_ticket_ids)
for ticket in tickets:
ticket.unread_message = ticket.id in unread_set
TicketSupport.objects.bulk_update(tickets, ['unread_message'])
class TicketSupportViewSet(viewsets.ModelViewSet):
queryset = TicketSupport.objects.filter(trash=False).select_related(
'user', 'referred_by'
).prefetch_related(
'to_role', 'referred_to', 'to_user').order_by('-unread_message', '-create_date')
serializer_class = TicketSupportSerializer
permission_classes = [TokenHasReadWriteScope]
pagination_class = CustomPagination
filterset_class = TicketsFilterSet
def list(self, request, *args, **kwargs):
user = SystemUserProfile.objects.get(user=request.user)
role_list = list(user.role.values_list('id', flat=True).distinct())
role_check = any(role in ['AdminX', 'SuperAdmin'] for role in user.role.values_list('name', flat=True))
type_param = request.GET.get('type', None)
status = request.GET.get('status', 'open')
base_filter = Q(user=user) | Q(to_user=user.id) | Q(to_role__in=role_list) | Q(referred_to=user.id) | Q(
referred_by=user)
if role_check:
if type_param:
query = self.queryset.filter(type_ticket=type_param,
status=status).distinct() if type_param else self.queryset
else:
query = self.queryset.filter(status=status).distinct()
else:
if type_param:
query = self.queryset.filter(base_filter, type_ticket=type_param, status=status).distinct()
else:
query = self.queryset.filter(base_filter, status=status)
value = request.GET.get('value')
search = request.GET.get('search')
if value and search == 'filter':
if value != 'undefined' and value.strip():
query = query.filter(
build_query(self.filterset_class, value)
)
update_unread_ticket(query, user)
query = query.order_by('-unread_message', '-create_date')
page_size = request.query_params.get('page_size', None)
if page_size:
self.pagination_class.page_size = int(page_size)
page = self.paginate_queryset(query)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
ser_data = self.serializer_class(query, many=True)
return Response(ser_data.data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
user = SystemUserProfile.objects.get(user=request.user)
type_ticket = request.data.get('type_ticket')
read_only = request.data.get('read_only')
to_user_data = request.data.get('to_user')
if request.data.get('image') is not None:
image = send_image_to_server(request.data['image'])
else:
image = None
if request.FILES.get('file'):
file_obj = request.FILES.get('file')
file_url = upload_to_liara(file_obj, file_obj.name)
else:
file_url = None
if to_user_data:
to_users = SystemUserProfile.objects.filter(key__in=request.data['to_user'])
ticket = TicketSupport(
user=user,
title=request.data['title'],
status='open',
read_only=read_only,
type_ticket=type_ticket,
role=request.data.get('role'),
)
ticket.save()
ticket.to_user.set(to_users)
else:
to_role = Group.objects.filter(name__in=request.data.get('to_role'))
if ((SystemUserProfile.objects.filter(trash=False, role__in=to_role).count() < 2)):
return Response({'result': 'برای نقش انتخابی، لطفاً تیکت شخصی ثبت کنید!'},
status=status.HTTP_403_FORBIDDEN)
ticket = TicketSupport(
user=user,
title=request.data['title'],
status='open',
read_only=read_only,
type_ticket=type_ticket,
role=request.data.get('role'),
)
ticket.save()
ticket.to_role.set(to_role)
msg = MessageSupport(
ticket=ticket,
message=request.data['message'],
created_by=user,
sender=request.data.get('sender'),
picture=image,
file=file_url)
msg.save()
return Response({'msg': 'با موفقیت انجام شد.'}, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
ticket_id = request.data.get('ticket')
ticket = self.queryset.filter(ticket_id=int(ticket_id)).first()
if ticket:
if 'referred_to' in request.data:
referred_to = SystemUserProfile.objects.filter(key__in=request.data['referred_to'])
ticket.referred_by = SystemUserProfile.objects.get(user=request.user)
ticket.status = 'open'
ticket.is_referred = True
ticket.save()
referrer_name = []
for user in referred_to:
if not ticket.referred_to.filter(id=user.id).exists():
ticket.referred_to.add(user)
referrer_name.append(user.fullname)
if len(referrer_name) > 1:
names = ' و '.join(referrer_name)
else:
names = referrer_name[0] if referrer_name else ''
if referrer_name:
MessageSupport.objects.create(
ticket=ticket,
message=f"تیکت شماره {ticket.ticket_id} به ({names}) ارجاع داده شد.",
created_by=SystemUserProfile.objects.get(user=request.user),
sender='Admin'
)
return Response({'message': 'تیکت با موفقیت ارجاع داده شد.'}, status=200)
else:
ticket.status = 'closed'
ticket.save()
return Response({'result': 'تیکت با موفقیت پایان یافت.'}, status=status.HTTP_200_OK)
return Response({'result': 'تیکت وجود ندارد!'}, status=status.HTTP_400_BAD_REQUEST)
class MessageSupportViewSet(viewsets.ModelViewSet):
queryset = MessageSupport.objects.all().select_related('ticket').order_by('created_at')
serializer_class = MessageSupportSerializer
permission_classes = [TokenHasReadWriteScope]
def list(self, request, *args, **kwargs):
user = SystemUserProfile.objects.get(user=request.user)
ticket_id = request.GET.get('ticket')
role_list = list(user.role.values_list('id', flat=True).distinct())
role_check = any(
role in ['AdminX', 'SuperAdmin', 'ProvinceOperator'] for role in user.role.values_list('name', flat=True))
query = self.queryset.filter(ticket__ticket_id=int(ticket_id)).order_by('-created_at')
if query.filter(Q(ticket__user=user) | Q(ticket__to_user=user.id) | Q(ticket__to_role__in=role_list) | Q(
ticket__referred_by=user) | Q(ticket__referred_to=user.id)).exists():
unseen_messages = query.filter(~Q(created_by=user), last_seen__isnull=True)
unseen_messages.update(last_seen=datetime.datetime.now())
for message in query.filter(~Q(created_by=user)):
if not message.read_by.filter(id=user.id).exists():
message.read_by.add(user)
query = query.defer('message')
if role_check:
srz_data = self.serializer_class(query, many=True).data
else:
srz_data = MessageSupportWithoutReadBySerializer(query, many=True).data
return Response(srz_data)
def create(self, request, *args, **kwargs):
user = SystemUserProfile.objects.select_related('user').filter(user=request.user).first()
ticket_id = request.data.get('ticket')
ticket = TicketSupport.objects.get(ticket_id=int(ticket_id))
if ticket.status == 'closed':
return Response({'result': 'این تیکت بسته شده است!'}, status=status.HTTP_403_FORBIDDEN)
if ticket.read_only == False and ticket.type_ticket == 'public' and ticket.parent is None \
and ticket.user != user:
new_ticket = TicketSupport(
user=user,
title=ticket.title,
status='open',
read_only=False,
type_ticket='single',
parent=ticket,
last_message=request.data.get('sender'),
role=request.data.get('role'),
)
new_ticket.save()
new_ticket.to_user.add(ticket.user)
msg = MessageSupport(
ticket=new_ticket,
message=request.data.get('message'),
created_by=user,
sender=request.data.get('sender')
)
else:
msg = MessageSupport(
ticket=ticket,
message=request.data.get('message'),
created_by=user,
sender=request.data.get('sender')
)
ticket.last_message = request.data.get('sender')
ticket.save()
if request.data['send_message'] == True:
ticket_answered(ticket.user.mobile)
msg.send_message = True
if request.data.get('image') is not None:
# ran = ''.join(random.choices(string.ascii_uppercase + string.digits, k=15))
# upload_object_resize(image_data=request.data['image'], bucket_name="profileimagedefault",
# object_name="{0}.jpg".format(str(ran)))
msg.picture = send_image_to_server(request.data['image'])
if request.FILES.get('file'):
file_obj = request.FILES.get('file')
file_url = upload_to_liara(file_obj, file_obj.name)
msg.file = file_url
msg.save()
ser_data = self.serializer_class(msg)
return Response(ser_data.data, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
msg_id = request.data.get('message_id')
instance = self.get_queryset().get(id=msg_id)
request.data.pop('message_id')
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({'result': "با موفقیت انجام شد."}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class MessageForRoleViewSet(viewsets.ModelViewSet):
queryset = MessageSupport.objects.all()
serializer_class = MessageSupportSerializer
permission_classes = [TokenHasReadWriteScope]
def create(self, request, *args, **kwargs):
user_sender = SystemUserProfile.objects.get(user=request.user)
if 'role' in request.data.keys():
to_user = SystemUserProfile.objects.filter(trash=False, role__name__in=request.data['role'])
relations = [
TicketSupport(
user=user_sender,
title=request.data['title'],
status='open',
to_user=user
)
for user in to_user
]
TicketSupport.objects.bulk_create(relations)
return Response({'msg': 'با موفقیت ارسال شد'}, status=status.HTTP_201_CREATED)
class TicketPermissionViewSet(viewsets.ModelViewSet):
queryset = TicketPermission.objects.all()
serializer_class = TicketPermissionSerializer
permission_classes = [AllowAny]
def create(self, request, *args, **kwargs):
role = request.data['role']
group = request.data['roles']
roles = Group.objects.filter(name__in=group)
ticket = TicketPermission.objects.get(role=role)
ticket.roles.set(roles)
ser_data = self.serializer_class(ticket)
return Response(ser_data.data, status=status.HTTP_201_CREATED)
def list(self, request, *args, **kwargs):
if 'role' in request.GET:
role = request.GET['role']
query = self.queryset.get(role=role)
ser_data = self.serializer_class(query).data
return Response(ser_data, status=status.HTTP_200_OK)
else:
query = self.queryset
ser_data = self.serializer_class(query, many=True).data
return Response(ser_data, status=status.HTTP_200_OK)
class GetUserFromRoleViewSet(viewsets.ModelViewSet):
queryset = SystemUserProfile.objects.all()
serializer_class = SystemUserProfileForTicketPermissionSerializer
permission_classes = [TokenHasReadWriteScope]
def list(self, request, *args, **kwargs):
role = request.GET['role']
user = SystemUserProfile.objects.filter(~Q(user=request.user), trash=False, role__name=role, )
ser_data = self.serializer_class(user, many=True)
return Response(ser_data.data, status=status.HTTP_200_OK)
class TicketClosePermissionViewSet(viewsets.ModelViewSet):
queryset = TicketClosePermission.objects.all().first()
serializer_class = TicketClosePermissionSerializer
permission_classes = [TokenHasReadWriteScope]
@api_view(["GET"])
@csrf_exempt
@permission_classes([TokenHasReadWriteScope])
def get_num_message(request):
user = SystemUserProfile.objects.get(user=request.user)
message = MessageSupport.objects.filter(
Q(ticket__user=user) | Q(ticket__to_user=user) | Q(ticket__to_role__in=user.role.all()) | Q(
ticket__referred_by=user) | Q(ticket__referred_to=user.id)
, ~Q(created_by=user)).exclude(read_by=user.id).values('ticket').annotate(
ticket_count=Count('ticket', distinct=True)
).distinct()
state = True if message.count() > 0 else False
return Response({
'state': state,
'num': message.count(),
})
# @api_view(["PUT"])
# @csrf_exempt
# @permission_classes([AllowAny])
# def FileUploadView(request):
# # queryset = TicketSupport.objects.all()
# # permission_classes = [AllowAny]
# # serializer_class = TicketSupportSerializer
# #
# # def create(self, request, *args, **kwargs):
# file_obj = request.FILES.get('file')
# if not file_obj:
# return Response({"error": "فایلی ارسال نشده است"}, status=status.HTTP_400_BAD_REQUEST)
#
# try:
# file_url = upload_to_liara(file_obj, file_obj.name)
# return Response({"file_url": file_url}, status=status.HTTP_201_CREATED)
# except Exception as e:
# return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def bot_eitaa_for_bar():
url = f'https://eitaayar.ir/api/{token}/sendMessage'
data = {
"cookie": "ASP.NET_SessionId=w1jadrzcqgznxugnjee1xrkj; .ASPXAUTH=4CC27FD1BAB0CA729584056577AA51209F731F22CDF4B95F9FEE33AD12F1DFFF960C77B0A237D5733E336195A2CA6DB1C1F2CA2EDCD079EC0B3C3092A2BF1BA8811D7C44263B1E5EB84430C0906024A55D51AF1FE5852F406C358A051715BB9270475D58228F44380D12FA075A4DF0E029220F0C809AEDDDF58FFE9568064DD9D397B038D19DC1757232A92EA63571EA7B47E1706F677528E539301417093B523B6C2BFD340AA33DC32D50E7853E3D14D93924313B9B1EB36320C90A2303BB852841EE0DE1D844B281B4CD6D7E95D593CF2E2F0C3816687C529B7702863F75CD6549F49D346C7C88F22F033C75C357BE9E91AD170A2502731BC03AE2DF09F594417646FE332B8BAB70673E584A23AD45CADC285C554B66FD29DD989F85962891A11C06FC84832DD8FB03933CF26E190601D493B0430A742544B8BC0A8639E4E5788432AA401FAFFD3A1C6793FB24909A992405D07E53B81DF546AB07ED90314735518C37E9291F10E2B723B795B8BB838C2810CE103A4EF893211BFDBB605ADBE0E3B9B35438A5FE3506937D020EC2EF061B6D1E3AAFB3A9A138CC219A4A556CEB6A2E44C18D53C85666A5C0C9663F8314EFD97FFF5C1844C0FEB4362A781E2138785D832EEFD1AEDAD1271C2513A6F4EC1174EF107AC2E9FEFF8A9A111CC6D6189CCC66E93224EE4D30813AAD9DCDE1CFC530B08CB67827C351DCE4C2AFD4E3FB12E85EA0CD6E2D3D4CC8753D74E863A37C5675D82C9977518A942786EF055F0DBFC809B9F9B6A45B269526FC9D8B5E888B1C6E40C6ACE8EB37ED7E1F9F266A018936F3228A3C8E20C1BD869CC657D1CE19FFEA8C8B979DE5077E17AE5FA1F085B3A424C91DA19D3743871A2A9440937B2C4E076EC99AE4DEE1A7AF601448F2F763FC2DF85ED8902287DDD0ED5B9E649A4490900B4BF64F639CDD761326AC08D1B540CBD853B42F556FFB8FD5BC33A54E68125CD20278B191A1896BF955FFA1B4CC5308E12A84003B64B0EE532079563DC334FB9AEB9B742803EBE35C82CA14E5D0D84CA736AB9A2E07EECFF98657561C029ECE3C1249DA843B4236E41BD745E54B1A86ED2CE4EA0FDDBB3B4B2B97A87548B23298283F6D09094F7BC709FAABE59CC84138F83B083EAB15BE0B032481; leggedOut=-"
}
r = requests.post(url='https://pay.rasadyar.net/transporting-chickens/', data=data)
data = {
'chat_id': chat_id,
'text': r.status_code,
}
response = requests.post(url, data=data, verify=False)
return HttpResponse(response.status_code)
# def close_ticket_cron_job():
# ticket=TicketSupport.objects.filter(Q(unread_message=False,read_only=True)|Q(unread_message=True,read_only=False),trash=False,status='open')
# for t in ticket:
# two_day_ago =datetime.datetime.now().date() - datetime.timedelta(days=2)
# if (t.create_date.date() - datetime.datetime.now().date()).days > 2:
# t.status='closed'
# t.to_role=None
# t.title=None
# t.trash=False
# t.create_date=datetime.datetime.now()
# t.save()
# else:
# continue
@api_view(["GET"])
@csrf_exempt
@permission_classes([AllowAny])
def closed_unread_ticket(request):
user = SystemUserProfile.objects.filter(trash=False, role__name='AdminX').first()
two_day_ago = datetime.datetime.now() - datetime.timedelta(days=2)
ten_day_ago = datetime.datetime.now() - datetime.timedelta(days=10)
tickets = TicketSupport.objects.filter(
trash=False,
status='open',
type_ticket='single'
)
text_two_day_ago_message = 'به دلیل عدم دریافت پیام جدید در مدت 48 ساعت، این تیکت توسط سامانه به صورت خودکار بسته شد.'
text_ten_day_ago_message = 'به دلیل خوانده نشدن پیام در مدت 10 روز، این تیکت توسط سامانه به صورت خودکار بسته شد.'
for ticket in tickets:
message = MessageSupport.objects.filter(ticket=ticket).last()
if not message:
continue
if message.read_by.exists() and message.last_seen.date() <= two_day_ago.date():
msg = MessageSupport(
ticket=ticket,
message=text_two_day_ago_message,
created_by=user)
msg.save()
ticket.status = 'closed'
ticket.save()
if not message.read_by.exists() and message.created_at.date() <= ten_day_ago.date():
msg = MessageSupport(
ticket=ticket,
message=text_ten_day_ago_message,
created_by=user)
msg.save()
ticket.status = 'closed'
ticket.save()
return HttpResponse('ok')
def closed_unread_ticket_cron():
user = SystemUserProfile.objects.filter(trash=False, role__name='AdminX').first()
two_day_ago = datetime.datetime.now() - datetime.timedelta(days=2)
ten_day_ago = datetime.datetime.now() - datetime.timedelta(days=10)
tickets = TicketSupport.objects.filter(
trash=False,
status='open',
type_ticket='single'
)
text_two_day_ago_message = 'به دلیل عدم دریافت پیام جدید در مدت 48 ساعت، این تیکت توسط سامانه به صورت خودکار بسته شد.'
# text_ten_day_ago_message = 'به دلیل خوانده نشدن پیام در مدت 10 روز، این تیکت توسط سامانه به صورت خودکار بسته شد.'
for ticket in tickets:
message = MessageSupport.objects.filter(ticket=ticket).last()
if not message:
continue
if message.read_by.exists() and message.last_seen.date() <= two_day_ago.date():
msg = MessageSupport(
ticket=ticket,
message=text_two_day_ago_message,
created_by=user)
msg.save()
ticket.status = 'closed'
ticket.save()
# if not message.read_by.exists() and message.created_at.date() <= ten_day_ago.date():
# msg = MessageSupport(
# ticket=ticket,
# message=text_ten_day_ago_message,
# created_by=user)
# msg.save()
# ticket.status = 'closed'
# ticket.save()
@api_view(["GET"])
@csrf_exempt
@permission_classes([TokenHasReadWriteScope])
def get_unread_ticket_for_dashboard(request):
user = SystemUserProfile.objects.get(user=request.user)
role_list = list(user.role.values_list('id', flat=True).distinct())
base_filter = Q(user=user) | Q(to_user=user.id) | Q(to_role__in=role_list) | Q(referred_to=user.id) | Q(
referred_by=user)
query = TicketSupport.objects.filter(base_filter, status='open',trash=False).select_related(
'user', 'referred_by'
).prefetch_related(
'to_role', 'referred_to', 'to_user').order_by('-create_date')
update_unread_ticket(query, user)
query1 = query.filter(unread_message=True)
ser_data = TicketSupportSerializer(query1, many=True)
return Response(ser_data.data, status=status.HTTP_200_OK)