From 494f792c0dc108783ae6fa7d5f7f276363afcce4 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Tue, 3 Feb 2026 16:06:14 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-02-03 16:06:14) --- .last-branch | 2 +- .../app/integrations/autotask/client.py | 74 +++++++++++++++++++ .../src/backend/app/main/routes_run_checks.py | 23 ++++++ docs/changelog.md | 7 ++ 4 files changed, 105 insertions(+), 1 deletion(-) diff --git a/.last-branch b/.last-branch index ae1578e..06a5a28 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260203-08-autotask-ticketnote-timezone-suffix +v20260203-09-autotask-resolution-from-note diff --git a/containers/backupchecks/src/backend/app/integrations/autotask/client.py b/containers/backupchecks/src/backend/app/integrations/autotask/client.py index 417433c..3b19942 100644 --- a/containers/backupchecks/src/backend/app/integrations/autotask/client.py +++ b/containers/backupchecks/src/backend/app/integrations/autotask/client.py @@ -558,6 +558,80 @@ class AutotaskClient: return {"id": tid} + def update_ticket_resolution(self, ticket_id: int, resolution_text: str) -> Dict[str, Any]: + """Update a Ticket's `resolution` field via PUT /Tickets. + + This follows the validated contract in `autotask_rest_api_postman_test_contract.md`: + - Always GET /Tickets/{id} first + - PUT /Tickets is a full update, so we copy stabilising fields and change only `resolution` + - Status must remain unchanged during the resolution write + + Raises AutotaskError on validation failures. + """ + + try: + tid = int(ticket_id) + except Exception: + tid = 0 + if tid <= 0: + raise AutotaskError("Invalid ticket id.") + + res_txt = str(resolution_text or "") + if not res_txt.strip(): + raise AutotaskError("Resolution text is empty.") + + t = self.get_ticket(tid) + if not isinstance(t, dict): + raise AutotaskError("Autotask did not return a ticket object.") + + stabilising_fields = [ + "id", + "companyID", + "queueID", + "title", + "priority", + "status", + "dueDateTime", + "ticketCategory", + "issueType", + "subIssueType", + "source", + "organizationalLevelAssociationID", + ] + + missing = [f for f in stabilising_fields if t.get(f) in (None, "")] + if missing: + raise AutotaskError( + "Cannot safely update ticket resolution because required fields are missing: " + ", ".join(missing) + ) + + existing = str(t.get("resolution") or "") + if existing.strip(): + if res_txt.strip() in existing: + new_res = existing + else: + new_res = existing.rstrip() + "\n\n" + res_txt + else: + new_res = res_txt + + payload: Dict[str, Any] = {k: t.get(k) for k in stabilising_fields} + # Ensure numeric ID is an int for PUT. + payload["id"] = int(t.get("id")) + # Explicitly keep status unchanged. + payload["status"] = t.get("status") + payload["resolution"] = new_res + + self.update_ticket(payload) + + # Verify persistence. + t2 = self.get_ticket(tid) + persisted = str((t2 or {}).get("resolution") or "") + if res_txt.strip() not in persisted: + raise AutotaskError("Ticket resolution update returned success, but verification failed.") + + return {"id": tid, "resolution_updated": True} + + def create_ticket_note(self, note_payload: Dict[str, Any]) -> Dict[str, Any]: """Create a user-visible note on a Ticket. diff --git a/containers/backupchecks/src/backend/app/main/routes_run_checks.py b/containers/backupchecks/src/backend/app/main/routes_run_checks.py index 40c58a7..3891ca7 100644 --- a/containers/backupchecks/src/backend/app/main/routes_run_checks.py +++ b/containers/backupchecks/src/backend/app/main/routes_run_checks.py @@ -1914,6 +1914,17 @@ def api_run_checks_autotask_resolve_note(): } ), 400 + # Also write the same information into the Ticket resolution field (validated safe update pattern). + try: + client.update_ticket_resolution(ticket_id, body) + except Exception as exc: + return jsonify( + { + "status": "error", + "message": f"Resolve note was created, but updating the ticket resolution field failed: {exc}", + } + ), 400 + return jsonify({"status": "ok", "message": "Resolve note posted to Autotask ticket."}) except AutotaskError as exc: @@ -1982,6 +1993,18 @@ def api_run_checks_autotask_resolve_note(): } ), 400 + # Also write the same information into the Ticket resolution field (validated safe update pattern). + try: + client.update_ticket_resolution(ticket_id, body) + except Exception as exc: + return jsonify( + { + "status": "error", + "message": f"Ticket note creation is not supported (HTTP 404) and the marker was appended to the description, " + f"but updating the ticket resolution field failed: {exc}", + } + ), 400 + return jsonify( { "status": "ok", diff --git a/docs/changelog.md b/docs/changelog.md index ccd2d5b..745eae9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -505,6 +505,13 @@ Changes: - Updated Autotask ticket note timestamps to use the configured Backupchecks timezone instead of UTC. - Added a timezone suffix to the timestamp in the ticket note (e.g. Europe/Amsterdam). - Ensured all user-visible timestamps written to Autotask follow the timezone setting from Backupchecks. + +## v20260203-09-autotask-resolution-from-note + +- When posting an Autotask “marked as resolved” note, the same text is now also written to the Ticket resolution field. +- Resolution updates follow the validated safe pattern: GET the current Ticket first, then PUT with stabilising fields while keeping the status unchanged. +- Added verification to ensure the resolution text is persisted after the update. + *** ## v0.1.21