from typing import Optional from datetime import datetime from django.db.transaction import atomic from helpdesk.repositories.ticket_repository import TicketRepository from helpdesk.models import Ticket, TicketStatus from django.contrib.auth import get_user_model class TicketNotFoundError(Exception): """Raised when the requested Ticket is not found.""" pass class NotificationService: """Placeholder สำหรับ NotificationService""" def send_assignment_notification(self, ticket_id, user_id): pass class TicketService: def __init__(self, ticket_repo: Optional[TicketRepository] = None, notification_service=None): # รองรับ DI ในการทดสอบ — หากไม่ส่งมา จะสร้าง repo ใหม่สำหรับ production self.ticket_repo = ticket_repo or TicketRepository() self.notification_service = notification_service @atomic def update_ticket_on_new_message( self, ticket_id: int, content: str, timestamp: datetime, sender_is_agent: bool, ) -> Ticket: """ อัปเดต Ticket เมื่อมีข้อความใหม่เข้ามาใน Chat - ถ้าไม่พบ Ticket จะโยน TicketNotFoundError """ ticket = self.ticket_repo.get_ticket_by_id(ticket_id) if ticket is None: raise TicketNotFoundError(f"Ticket with id={ticket_id} not found.") update_data = { "last_message_at": timestamp, "last_message_content": content, "is_read": True if sender_is_agent else False, } return self.ticket_repo.update_ticket(ticket, update_data) def update_ticket_status(self, ticket_id: int, new_status: str) -> Ticket: """อัปเดตสถานะของ Ticket และตรวจสอบ Business Rule""" ticket = self.ticket_repo.get_ticket_by_id(ticket_id) if ticket is None: raise TicketNotFoundError(f"Ticket with id={ticket_id} not found.") # Business Rule ห้ามเปลี่ยนจาก CLOSED -> OPEN if ticket.status == TicketStatus.CLOSED and new_status == TicketStatus.OPEN: raise ValueError("Cannot transition from CLOSED to OPEN") # ตรวจสอบว่า new_status เป็นค่าที่ถูกต้องใน enum หรือไม่ (เผื่อกรณี View ส่ง string ที่ผิดมา) if new_status not in TicketStatus: raise ValueError(f"Invalid status value: {new_status}") updated_ticket = self.ticket_repo.update_ticket(ticket, {"status": new_status}) # คืน updated_ticket return updated_ticket def get_inbox_summary(self): return self.ticket_repo.get_unified_inbox_list() def get_ticket_detail(self, ticket_id: int) -> Ticket: ticket = self.ticket_repo.get_ticket_by_id(ticket_id) if ticket is None: raise TicketNotFoundError(f"Ticket with id={ticket_id} not found.") return ticket def get_ticket_ref(self, ticket_id: int) -> dict: ticket = self.ticket_repo.get_ticket_by_id(ticket_id) return { "id": ticket.id, "last_message_at": ticket.last_message_at, "last_message_content": ticket.last_message_content, "is_read": ticket.is_read } @atomic def assign_ticket_to_user(self, ticket_id: int, assignee_id: int): """ มอบหมาย Ticket ให้กับผู้ใช้ (Agent) และเรียก NotificationService ถ้ามี """ ticket = self.ticket_repo.get_ticket_by_id(ticket_id) if not ticket: raise TicketNotFoundError(f"Ticket with id={ticket_id} not found.") # ดึง User Object จาก ID User = get_user_model() try: assigned_user = User.objects.get(pk=assignee_id) except User.DoesNotExist: raise ValueError("User not found") updated_ticket = self.ticket_repo.update_ticket(ticket, {"assigned_to": assigned_user}) if self.notification_service: self.notification_service.send_assignment_notification( ticket_id=updated_ticket.id, user_id=assigned_user.id ) # คืน updated_ticket return updated_ticket ticket_service = TicketService()