From 74ee5009c26224a39d190d3fc4c570ceb40184fc Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sat, 14 Mar 2026 23:28:46 -0400 Subject: [PATCH] feat(psa): implement post_note and update_ticket_status in CW provider Replaces NotImplementedError stubs with working implementations: - post_note maps note_type to CW flag fields (internalAnalysisFlag, resolutionFlag, detailDescriptionFlag) with appropriate visibility and notification settings - update_ticket_status uses CW JSON Patch format to update status Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/services/psa/connectwise/provider.py | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/backend/app/services/psa/connectwise/provider.py b/backend/app/services/psa/connectwise/provider.py index 615d2c89..cce2bacf 100644 --- a/backend/app/services/psa/connectwise/provider.py +++ b/backend/app/services/psa/connectwise/provider.py @@ -155,7 +155,7 @@ class ConnectWiseProvider(PSAProvider): status=data.get("status", {}).get("name") if data.get("status") else None, ) - # ── Stubs for later slices ──────────────────────────────────────── + # ── Notes & status updates ─────────────────────────────────────── async def post_note( self, @@ -164,12 +164,71 @@ class ConnectWiseProvider(PSAProvider): note_type: str, member_id: str | None = None, ) -> PSANote: - raise NotImplementedError("Implemented in Slice 4") + """Post a note to a CW ticket. + + Maps ResolutionFlow note types to CW flag fields: + - internal_analysis → internalAnalysisFlag (internal only) + - resolution → resolutionFlag (internal, triggers notifications) + - description → detailDescriptionFlag (external, triggers notifications) + """ + from app.services.psa.types import NoteType + + flags = { + NoteType.INTERNAL_ANALYSIS: { + "internalAnalysisFlag": True, + "resolutionFlag": False, + "detailDescriptionFlag": False, + "internalFlag": True, + "processNotifications": False, + }, + NoteType.RESOLUTION: { + "internalAnalysisFlag": False, + "resolutionFlag": True, + "detailDescriptionFlag": False, + "internalFlag": True, + "processNotifications": True, + }, + NoteType.DESCRIPTION: { + "internalAnalysisFlag": False, + "resolutionFlag": False, + "detailDescriptionFlag": True, + "internalFlag": False, + "processNotifications": True, + }, + } + + note_flags = flags.get(note_type, flags[NoteType.INTERNAL_ANALYSIS]) + + body: dict = { + "text": text, + **note_flags, + } + + if member_id: + body["member"] = {"id": int(member_id)} + + data = await self.client.post( + f"/service/tickets/{ticket_id}/notes", json_body=body + ) + + return PSANote( + id=str(data.get("id", "")), + text=data.get("text", ""), + note_type=note_type, + created_at=data.get("dateCreated"), + ) async def update_ticket_status( self, ticket_id: str, status_id: int ) -> PSATicket: - raise NotImplementedError("Implemented in Slice 4") + """Update a CW ticket's status using JSON Patch format.""" + patch_body = [ + {"op": "replace", "path": "status", "value": {"id": status_id}} + ] + data = await self.client.patch( + f"/service/tickets/{ticket_id}", json_body=patch_body + ) + return self._map_ticket(data) async def list_members(self) -> list[PSAMember]: raise NotImplementedError("Implemented in Slice 5")