92 lines
3.7 KiB
Python
92 lines
3.7 KiB
Python
import uuid
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy.orm import selectinload
|
|
from tickets.orm import SupportTicket, TicketMessage
|
|
from tickets.models import TicketCreate, TicketUpdate, MessageCreate
|
|
from shared.exceptions import NotFoundError
|
|
|
|
|
|
async def create_ticket(db: AsyncSession, data: TicketCreate) -> SupportTicket:
|
|
ticket = SupportTicket(**data.model_dump())
|
|
db.add(ticket)
|
|
await db.commit()
|
|
return await _get_ticket(db, ticket.id, include_internal=True)
|
|
|
|
|
|
async def get_ticket(db: AsyncSession, ticket_id: uuid.UUID, include_internal: bool = True) -> SupportTicket:
|
|
return await _get_ticket(db, ticket_id, include_internal)
|
|
|
|
|
|
async def list_tickets(
|
|
db: AsyncSession,
|
|
status: str | None, priority: str | None, customer_id: str | None,
|
|
page: int, limit: int,
|
|
) -> tuple[list[SupportTicket], int]:
|
|
limit = min(100, max(1, limit))
|
|
offset = (max(1, page) - 1) * limit
|
|
|
|
q = select(SupportTicket).options(selectinload(SupportTicket.messages))
|
|
if status: q = q.where(SupportTicket.status == status)
|
|
if priority: q = q.where(SupportTicket.priority == priority)
|
|
if customer_id: q = q.where(SupportTicket.customer_id == customer_id)
|
|
|
|
total = (await db.execute(select(func.count()).select_from(q.subquery()))).scalar()
|
|
rows = (await db.execute(q.order_by(SupportTicket.created_at.desc()).limit(limit).offset(offset))).scalars().all()
|
|
return rows, total
|
|
|
|
|
|
async def update_ticket(db: AsyncSession, ticket_id: uuid.UUID, data: TicketUpdate) -> SupportTicket:
|
|
ticket = await _get_ticket(db, ticket_id)
|
|
for field, value in data.model_dump(exclude_unset=True).items():
|
|
setattr(ticket, field, value)
|
|
await db.commit()
|
|
return await _get_ticket(db, ticket_id)
|
|
|
|
|
|
async def add_message(db: AsyncSession, ticket_id: uuid.UUID, data: MessageCreate) -> SupportTicket:
|
|
ticket = await _get_ticket(db, ticket_id)
|
|
msg = TicketMessage(ticket_id=ticket_id, **data.model_dump())
|
|
db.add(msg)
|
|
|
|
# Auto-advance ticket status based on who replied (skip if already resolved/closed)
|
|
if ticket.status not in ("resolved", "closed"):
|
|
if data.sender_type == "staff" and not data.is_internal:
|
|
ticket.status = "waiting_on_customer"
|
|
elif data.sender_type == "customer":
|
|
ticket.status = "waiting_on_staff"
|
|
|
|
await db.commit()
|
|
return await _get_ticket(db, ticket_id)
|
|
|
|
|
|
async def escalate_to_issue(db: AsyncSession, ticket_id: uuid.UUID, entry_id: uuid.UUID) -> SupportTicket:
|
|
ticket = await _get_ticket(db, ticket_id)
|
|
ticket.linked_entry_id = entry_id
|
|
await db.commit()
|
|
return await _get_ticket(db, ticket_id)
|
|
|
|
|
|
async def list_by_customer(db: AsyncSession, customer_id: str) -> list[SupportTicket]:
|
|
q = select(SupportTicket).options(selectinload(SupportTicket.messages)).where(
|
|
SupportTicket.customer_id == customer_id
|
|
).order_by(SupportTicket.created_at.desc())
|
|
return (await db.execute(q)).scalars().all()
|
|
|
|
|
|
async def list_by_device(db: AsyncSession, device_id: str) -> list[SupportTicket]:
|
|
q = select(SupportTicket).options(selectinload(SupportTicket.messages)).where(
|
|
SupportTicket.device_id == device_id
|
|
).order_by(SupportTicket.created_at.desc())
|
|
return (await db.execute(q)).scalars().all()
|
|
|
|
|
|
async def _get_ticket(db: AsyncSession, ticket_id: uuid.UUID, include_internal: bool = True) -> SupportTicket:
|
|
q = select(SupportTicket).options(selectinload(SupportTicket.messages)).where(SupportTicket.id == ticket_id)
|
|
result = (await db.execute(q)).scalar_one_or_none()
|
|
if not result:
|
|
raise NotFoundError("Ticket")
|
|
if not include_internal:
|
|
result.messages = [m for m in result.messages if not m.is_internal]
|
|
return result
|