feat: PSA ticket management — /tickets page, detail panel, AI ticket creation #141
Reference in New Issue
Block a user
Delete Branch "feat/psa-ticket-management"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
PSA Ticket Management — full ticket management workflow integrated with ConnectWise.
Core features:
/ticketspage with URL-param filter state (board, status, priority, company, assigned, closed) and paginated listTicketDetailPanel— notes feed, configurations, related tickets, resource manager, optimistic status updatesNewTicketModal— AI Quick Create tab (natural language → pre-filled form via Claude) + Full Form tabTicketQueuedashboard widget updated: member mapping detection, 5-item cap, View All linkBackend:
ticket_service.pyservice layer for all PSA mutationssearch_ticketswith parallel CW count fetchFixes (pre-landing review):
company_idfilter now applied to CW query conditions (was silently ignored)/ticketswhen CW returns 403/permissions errorlinkedTicketrace condition guard in ResolutionAssistCW Security Role Requirements
The API member needs these permissions in their security role:
Reference:
docs/connectwise/CW_Security_Roles/api-member-security-roles.yamlPre-Landing Review
2 issues found and fixed:
[P2](confidence: 9/10) CW condition injection via unescaped query string[P2](confidence: 9/10)company_idfilter silently ignored in CW providerTest plan
npx tsc -b— verified)pytest tests/test_psa_tickets.py(requires VPS SSH — Python not available in code-server)/tickets— verify ticket list loads or shows CW permissions error banner🤖 Generated with Claude Code
- Add GET /boards/{board_id}/statuses endpoint — direct board-to-statuses lookup without ticket roundabout; used by filter bar and new ticket form - Fix TicketsPage and NewTicketModal to call getBoardStatuses(board_id) instead of misusing getTicketStatuses(ticket_id) with a board_id value - Fix list_members auth: was require_account_owner (owner/super_admin only) — changed to require_engineer_or_admin so engineers can see member list for ticket assignment - list_members: return [] on PSAError instead of 502 (Lesson 111 pattern) - get_ticket_statuses: return [] on PSAError instead of 502 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>- Status filter: aggregate statuses across all boards (deduped by name) when no board is selected. Backend accepts status_name and filters by status/name so the same status matches across boards. - Resource assignment: CW has no /service/tickets/{id}/members endpoint — assignees live in the ticket's comma-separated `resources` string field. Rewrote list/add/remove to read/PATCH that field via member identifier. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>owner, status PATCH verifies applyThe previous implementation PATCHed the `resources` string directly, which CW silently ignores because `resources` is a server-derived read-only field (it's populated from schedule entries of type/id=4, not freely writable). Per CW docs (openapi line 70949): "Please use the /schedule/entries?conditions=type/id=4 AND objectId={id} endpoint". Behavior per spec: - No owner + assign user → set owner (existing behavior kept) - Has owner + assign different user → POST /schedule/entries with type/id=4, member, objectId; owner untouched - User already assigned (owner or schedule entry) → idempotent no-op - Remove owner → clear owner (existing behavior kept) - Remove co-assignee → DELETE /schedule/entries/{entry_id} - list_resources now merges owner + schedule-entry members, deduped by id Required CW security role permission on the API member: - Service > Resource Scheduling > Add/Inquire/Delete Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>