Sprint 2: Ticket Engine + SLA + Photo Uploads
- Ticket CRUD with full 15-status lifecycle enforcement
- Auto-generated ticket numbers (PAV-YYYY-NNNNN)
- Status transitions validated and auto-logged to timeline
- Reopen with 7-day window + auto-escalation to Ama
- SLA engine: priority-based deadlines and breach detection
- Photo uploads to uploads/ with multipart support
- Category system seeded from PRD §7 taxonomy (20 top-level)
- Flat and tree category listing endpoints
- SLA status check endpoint (/api/tickets/{id}/sla)
- Paginated ticket listing with filters
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Pydantic schemas for ticket CRUD, timeline, photos, and SLA."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ── Category ─────────────────────────────────────────────────────────
|
||||
class CategoryOut(BaseModel):
|
||||
id: int
|
||||
type: str
|
||||
name: str
|
||||
parent_id: int | None = None
|
||||
sla_urgency: str | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class CategoryTreeOut(CategoryOut):
|
||||
children: list[CategoryTreeOut] = []
|
||||
|
||||
|
||||
# ── Ticket ───────────────────────────────────────────────────────────
|
||||
class TicketCreate(BaseModel):
|
||||
unit_id: int | None = None
|
||||
category_id: int | None = None
|
||||
priority: str | None = None # urgent, high, medium, low
|
||||
reporter: str | None = None
|
||||
reported_via: str | None = None # whatsapp, phone, walk-in, qr, agent
|
||||
description: str | None = None
|
||||
assigned_to: int | None = None
|
||||
|
||||
|
||||
class TicketUpdate(BaseModel):
|
||||
status: str | None = None
|
||||
unit_id: int | None = None
|
||||
category_id: int | None = None
|
||||
priority: str | None = None
|
||||
reporter: str | None = None
|
||||
reported_via: str | None = None
|
||||
description: str | None = None
|
||||
assigned_to: int | None = None
|
||||
eta: datetime | None = None
|
||||
cost: Decimal | None = None
|
||||
parts_used: str | None = None
|
||||
|
||||
|
||||
class TicketTimelineOut(BaseModel):
|
||||
id: int
|
||||
ticket_id: int
|
||||
from_status: str | None = None
|
||||
to_status: str | None = None
|
||||
note: str | None = None
|
||||
user_id: int | None = None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TicketPhotoOut(BaseModel):
|
||||
id: int
|
||||
ticket_id: int
|
||||
photo_url: str
|
||||
is_before: bool
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TicketBrief(BaseModel):
|
||||
"""Summary view for list endpoints."""
|
||||
id: int
|
||||
ticket_number: str
|
||||
status: str
|
||||
priority: str | None = None
|
||||
unit_id: int | None = None
|
||||
category_id: int | None = None
|
||||
assigned_to: int | None = None
|
||||
reporter: str | None = None
|
||||
description: str | None = None
|
||||
sla_deadline: datetime | None = None
|
||||
reopen_count: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TicketOut(TicketBrief):
|
||||
"""Full ticket detail with relations."""
|
||||
closed_at: datetime | None = None
|
||||
eta: datetime | None = None
|
||||
cost: Decimal | None = None
|
||||
parts_used: str | None = None
|
||||
customer_rating: int | None = None
|
||||
timeline: list[TicketTimelineOut] = []
|
||||
photos: list[TicketPhotoOut] = []
|
||||
sla_status: dict | None = None
|
||||
|
||||
|
||||
class TicketListResponse(BaseModel):
|
||||
items: list[TicketBrief]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
# ── SLA ──────────────────────────────────────────────────────────────
|
||||
class SLAStatusOut(BaseModel):
|
||||
priority: str | None = None
|
||||
response_deadline: str | None = None
|
||||
resolution_deadline: str | None = None
|
||||
response_breached: bool = False
|
||||
resolution_breached: bool = False
|
||||
current_status: str | None = None
|
||||
Reference in New Issue
Block a user