- Add PSABoard type + list_boards() to CW provider (cached 1h) - Extend search_tickets with assigned_to_me, unassigned, board_ids, page, page_size - New GET /integrations/psa/boards endpoint - New TicketQueue dashboard component: My Tickets / Unassigned tabs, multi-select board filter, Load more pagination, Start Session per ticket - Add TicketQueue to QuickStartPage after active sessions - FlowPilotSessionPage auto-starts with ticket context when navigated from TicketQueue (psaTicketId + psaTicket in location.state) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
144 lines
3.7 KiB
Python
144 lines
3.7 KiB
Python
"""Pydantic schemas for PSA connection management."""
|
|
from __future__ import annotations
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class PsaConnectionCreate(BaseModel):
|
|
provider: str = Field(default="connectwise", pattern="^(connectwise|autotask)$")
|
|
display_name: str = Field(min_length=1, max_length=100)
|
|
site_url: str = Field(min_length=1, max_length=255)
|
|
company_id: str = Field(min_length=1, max_length=100)
|
|
public_key: str = Field(min_length=1)
|
|
private_key: str = Field(min_length=1)
|
|
# Note: client_id is NOT per-MSP — it's a product-level GUID from settings.CW_CLIENT_ID
|
|
|
|
|
|
class PsaConnectionUpdate(BaseModel):
|
|
display_name: str | None = None
|
|
site_url: str | None = None
|
|
company_id: str | None = None
|
|
public_key: str | None = None
|
|
private_key: str | None = None
|
|
|
|
|
|
class PsaConnectionResponse(BaseModel):
|
|
id: UUID
|
|
account_id: UUID
|
|
provider: str
|
|
display_name: str
|
|
site_url: str
|
|
company_id: str
|
|
is_active: bool
|
|
last_validated_at: datetime | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
public_key_hint: str
|
|
private_key_hint: str
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class PsaConnectionTestResponse(BaseModel):
|
|
success: bool
|
|
message: str
|
|
server_version: str | None = None
|
|
|
|
|
|
# ── Ticket search & status schemas ────────────────────────────────
|
|
|
|
|
|
class PSATicketSearchResult(BaseModel):
|
|
id: str
|
|
summary: str
|
|
company_name: str | None = None
|
|
board_name: str | None = None
|
|
status_name: str | None = None
|
|
priority_name: str | None = None
|
|
closed: bool = False
|
|
|
|
|
|
class PSATicketStatusItem(BaseModel):
|
|
id: int
|
|
name: str
|
|
is_closed: bool = False
|
|
|
|
|
|
# ── PSA post (note posting) schemas ──────────────────────────────
|
|
|
|
|
|
class PsaPostRequest(BaseModel):
|
|
note_type: str = Field(pattern="^(internal_analysis|resolution|description)$")
|
|
content: str = Field(min_length=1)
|
|
update_status_id: int | None = None
|
|
|
|
|
|
class PsaPostResponse(BaseModel):
|
|
id: str
|
|
session_id: str
|
|
ticket_id: str
|
|
note_type: str
|
|
status: str
|
|
external_note_id: str | None = None
|
|
error_message: str | None = None
|
|
status_changed_from: str | None = None
|
|
status_changed_to: str | None = None
|
|
posted_at: str
|
|
|
|
|
|
class PsaPreviewResponse(BaseModel):
|
|
content: str
|
|
ticket: PSATicketSearchResult
|
|
available_statuses: list[PSATicketStatusItem]
|
|
character_count: int
|
|
previous_posts: int
|
|
|
|
|
|
class PsaPostLogResponse(BaseModel):
|
|
id: str
|
|
ticket_id: str
|
|
note_type: str
|
|
status: str
|
|
error_message: str | None = None
|
|
status_changed_from: str | None = None
|
|
status_changed_to: str | None = None
|
|
posted_at: str
|
|
content_preview: str # first 200 chars
|
|
|
|
|
|
# ── Member mapping schemas ───────────────────────────────────────
|
|
|
|
|
|
class PsaMemberMappingResponse(BaseModel):
|
|
id: str | None = None # None for users without a mapping
|
|
user_id: str
|
|
user_email: str
|
|
user_name: str
|
|
external_member_id: str | None = None
|
|
external_member_name: str | None = None
|
|
matched_by: str | None = None
|
|
|
|
|
|
class PsaMemberMappingSaveRequest(BaseModel):
|
|
user_id: str
|
|
external_member_id: str
|
|
external_member_name: str
|
|
|
|
|
|
class PsaMemberResponse(BaseModel):
|
|
id: str
|
|
identifier: str
|
|
name: str
|
|
email: str | None = None
|
|
|
|
|
|
class AutoMatchResult(BaseModel):
|
|
matched: list[PsaMemberMappingResponse]
|
|
unmatched_users: int
|
|
|
|
|
|
class PSABoardResponse(BaseModel):
|
|
id: int
|
|
name: str
|