212 lines
8.3 KiB
Python
212 lines
8.3 KiB
Python
from rest_framework import serializers
|
|
from .models import (
|
|
SupportTicket, TicketStatus, TicketPriority, TicketCategory,
|
|
TicketMessage, TicketActivity, KnowledgeBaseCategory,
|
|
KnowledgeBaseArticle, SupportSettings
|
|
)
|
|
from .email_service import SupportEmailService
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TicketStatusSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TicketStatus
|
|
fields = ['id', 'name', 'color', 'description', 'is_closed', 'display_order']
|
|
|
|
|
|
class TicketPrioritySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TicketPriority
|
|
fields = ['id', 'name', 'level', 'color', 'description', 'sla_hours']
|
|
|
|
|
|
class TicketCategorySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TicketCategory
|
|
fields = ['id', 'name', 'description', 'color', 'icon', 'display_order']
|
|
|
|
|
|
class TicketMessageSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TicketMessage
|
|
fields = [
|
|
'id', 'ticket', 'message_type', 'content', 'author_name',
|
|
'author_email', 'is_internal', 'created_at', 'updated_at',
|
|
'attachments', 'is_read'
|
|
]
|
|
read_only_fields = ['created_at', 'updated_at']
|
|
|
|
|
|
class TicketActivitySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TicketActivity
|
|
fields = [
|
|
'id', 'activity_type', 'description', 'user_name',
|
|
'old_value', 'new_value', 'created_at'
|
|
]
|
|
read_only_fields = ['created_at']
|
|
|
|
|
|
class SupportTicketSerializer(serializers.ModelSerializer):
|
|
status_name = serializers.CharField(source='status.name', read_only=True)
|
|
status_color = serializers.CharField(source='status.color', read_only=True)
|
|
priority_name = serializers.CharField(source='priority.name', read_only=True)
|
|
priority_color = serializers.CharField(source='priority.color', read_only=True)
|
|
category_name = serializers.CharField(source='category.name', read_only=True)
|
|
messages = TicketMessageSerializer(many=True, read_only=True)
|
|
activities = TicketActivitySerializer(many=True, read_only=True)
|
|
|
|
class Meta:
|
|
model = SupportTicket
|
|
fields = [
|
|
'id', 'ticket_number', 'title', 'description', 'ticket_type',
|
|
'user_name', 'user_email', 'user_phone', 'company',
|
|
'category', 'category_name', 'priority', 'priority_name', 'priority_color',
|
|
'status', 'status_name', 'status_color', 'assigned_to', 'assigned_at',
|
|
'created_at', 'updated_at', 'closed_at', 'last_activity',
|
|
'first_response_at', 'sla_deadline', 'tags', 'is_escalated',
|
|
'escalation_reason', 'attachments', 'messages', 'activities'
|
|
]
|
|
read_only_fields = [
|
|
'ticket_number', 'created_at', 'updated_at', 'last_activity',
|
|
'assigned_at', 'closed_at', 'first_response_at', 'sla_deadline'
|
|
]
|
|
|
|
|
|
class SupportTicketCreateSerializer(serializers.ModelSerializer):
|
|
"""Simplified serializer for creating tickets from public form"""
|
|
ticket_number = serializers.CharField(read_only=True)
|
|
|
|
class Meta:
|
|
model = SupportTicket
|
|
fields = [
|
|
'ticket_number', 'title', 'description', 'ticket_type', 'user_name',
|
|
'user_email', 'user_phone', 'company', 'category'
|
|
]
|
|
read_only_fields = ['ticket_number']
|
|
|
|
def validate_user_email(self, value):
|
|
"""
|
|
Validate email format and optionally check if registered
|
|
"""
|
|
from .models import RegisteredEmail
|
|
from django.core.validators import validate_email
|
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
|
|
# Basic email format validation
|
|
try:
|
|
validate_email(value)
|
|
except DjangoValidationError:
|
|
raise serializers.ValidationError("Please enter a valid email address.")
|
|
|
|
# Optional: Check if email is registered (for analytics/tracking)
|
|
# But don't block ticket creation if not registered
|
|
try:
|
|
registered_email = RegisteredEmail.objects.get(email=value)
|
|
if not registered_email.is_active:
|
|
# Log this but don't block the ticket
|
|
logger.warning(f'Ticket submitted by deactivated registered email: {value}')
|
|
except RegisteredEmail.DoesNotExist:
|
|
# This is fine - unregistered users can submit tickets
|
|
logger.info(f'Ticket submitted by unregistered email: {value}')
|
|
|
|
return value
|
|
|
|
def create(self, validated_data):
|
|
from .models import RegisteredEmail
|
|
|
|
# Set default status and priority if not set
|
|
if not validated_data.get('status'):
|
|
default_status = TicketStatus.objects.filter(name='Open').first()
|
|
if default_status:
|
|
validated_data['status'] = default_status
|
|
|
|
if not validated_data.get('priority'):
|
|
default_priority = TicketPriority.objects.filter(name='Medium').first()
|
|
if default_priority:
|
|
validated_data['priority'] = default_priority
|
|
|
|
ticket = SupportTicket.objects.create(**validated_data)
|
|
|
|
# Create initial activity
|
|
TicketActivity.objects.create(
|
|
ticket=ticket,
|
|
activity_type='created',
|
|
description=f'Ticket created by {ticket.user_name}',
|
|
user_name=ticket.user_name
|
|
)
|
|
|
|
# Update registered email statistics (only if email is registered)
|
|
try:
|
|
registered_email = RegisteredEmail.objects.get(email=validated_data['user_email'])
|
|
registered_email.increment_ticket_count()
|
|
logger.info(f'Updated ticket count for registered email: {validated_data["user_email"]}')
|
|
except RegisteredEmail.DoesNotExist:
|
|
# This is normal now - unregistered users can submit tickets
|
|
logger.info(f'Ticket created by unregistered email: {validated_data["user_email"]}')
|
|
|
|
# Send email notifications
|
|
try:
|
|
email_results = SupportEmailService.send_ticket_created_emails(ticket)
|
|
logger.info(f'Email notifications sent for ticket {ticket.ticket_number}: {email_results}')
|
|
except Exception as e:
|
|
logger.error(f'Failed to send email notifications for ticket {ticket.ticket_number}: {str(e)}')
|
|
# Don't fail ticket creation if emails fail
|
|
|
|
return ticket
|
|
|
|
|
|
class TicketStatusCheckSerializer(serializers.Serializer):
|
|
"""Serializer for checking ticket status by ticket number"""
|
|
ticket_number = serializers.CharField(max_length=20)
|
|
|
|
|
|
class KnowledgeBaseCategorySerializer(serializers.ModelSerializer):
|
|
article_count = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = KnowledgeBaseCategory
|
|
fields = [
|
|
'id', 'name', 'slug', 'description', 'icon', 'color',
|
|
'display_order', 'article_count'
|
|
]
|
|
|
|
def get_article_count(self, obj):
|
|
return obj.articles.filter(is_published=True).count()
|
|
|
|
|
|
class KnowledgeBaseArticleListSerializer(serializers.ModelSerializer):
|
|
category_name = serializers.CharField(source='category.name', read_only=True)
|
|
category_slug = serializers.CharField(source='category.slug', read_only=True)
|
|
|
|
class Meta:
|
|
model = KnowledgeBaseArticle
|
|
fields = [
|
|
'id', 'title', 'slug', 'category', 'category_name', 'category_slug',
|
|
'summary', 'is_featured', 'view_count', 'helpful_count',
|
|
'not_helpful_count', 'created_at', 'updated_at', 'published_at'
|
|
]
|
|
|
|
|
|
class KnowledgeBaseArticleDetailSerializer(serializers.ModelSerializer):
|
|
category_name = serializers.CharField(source='category.name', read_only=True)
|
|
category_slug = serializers.CharField(source='category.slug', read_only=True)
|
|
|
|
class Meta:
|
|
model = KnowledgeBaseArticle
|
|
fields = [
|
|
'id', 'title', 'slug', 'category', 'category_name', 'category_slug',
|
|
'content', 'summary', 'meta_description', 'keywords',
|
|
'is_featured', 'view_count', 'helpful_count', 'not_helpful_count',
|
|
'created_at', 'updated_at', 'published_at'
|
|
]
|
|
|
|
|
|
class SupportSettingsSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = SupportSettings
|
|
fields = ['id', 'setting_name', 'setting_value', 'description', 'is_active']
|
|
|