Files
resolutionflow/backend/app/schemas/psa_connection.py
Michael Chihlas 5ade3be44e feat(psa): add PSA post preview, post-to-ticket, and post history endpoints
Three new session-scoped endpoints for posting session documentation
to linked PSA tickets:

- GET /sessions/{id}/psa-post/preview — generates PSA-formatted content,
  fetches ticket details and available statuses, counts previous posts
- POST /sessions/{id}/psa-post — posts note to CW ticket with optional
  status update, logs all actions in psa_post_log audit trail
- GET /sessions/{id}/psa-posts — returns post history for the session

New schemas: PsaPostRequest, PsaPostResponse, PsaPreviewResponse,
PsaPostLogResponse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 23:31:11 -04:00

109 lines
2.8 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)
client_id: str = Field(min_length=1)
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
client_id: 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