"""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 company_id: str | None = None board_name: str | None = None board_id: int | None = None status_name: str | None = None status_id: int | None = None priority_name: str | None = None priority_id: int | 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