diff --git a/containers/backupchecks/src/backend/app/integrations/autotask/client.py b/containers/backupchecks/src/backend/app/integrations/autotask/client.py index 3a02e6b..e95058a 100644 --- a/containers/backupchecks/src/backend/app/integrations/autotask/client.py +++ b/containers/backupchecks/src/backend/app/integrations/autotask/client.py @@ -598,27 +598,32 @@ class AutotaskClient: raise AutotaskError("Autotask did not return a ticket object.") def _pick(d: Dict[str, Any], keys: List[str]) -> Any: + """Pick the first available value from a list of possible field names. + + Returns the value (including 0) if found, or None if not present. + Note: 0 is a valid Autotask picklist value meaning 'not set'. + """ for k in keys: - if k in d and d.get(k) not in (None, ""): + if k in d: return d.get(k) return None + def _has_field(d: Dict[str, Any], keys: List[str]) -> bool: + """Check if any of the field names exist in the dict.""" + return any(k in d for k in keys) + # Required stabilising fields for safe resolution updates (validated via Postman tests) + # Note: These fields may have value 0 (not set) which is valid and must be preserved. resolved_issue_type = _pick(ticket, ["issueType", "issueTypeID", "issueTypeId"]) resolved_sub_issue_type = _pick(ticket, ["subIssueType", "subIssueTypeID", "subIssueTypeId"]) resolved_source = _pick(ticket, ["source", "sourceID", "sourceId"]) resolved_status = _pick(ticket, ["status", "statusID", "statusId"]) + # Only id and status are truly required - others may be 0/null in Autotask missing: List[str] = [] - if _pick(ticket, ["id"]) in (None, ""): + if _pick(ticket, ["id"]) is None: missing.append("id") - if resolved_issue_type in (None, ""): - missing.append("issueType") - if resolved_sub_issue_type in (None, ""): - missing.append("subIssueType") - if resolved_source in (None, ""): - missing.append("source") - if resolved_status in (None, ""): + if resolved_status is None: missing.append("status") if missing: @@ -628,27 +633,28 @@ class AutotaskClient: payload: Dict[str, Any] = { "id": int(ticket.get("id")), - "issueType": resolved_issue_type, - "subIssueType": resolved_sub_issue_type, - "source": resolved_source, # Keep status unchanged "status": resolved_status, "resolution": str(resolution_text or ""), } - # Copy other stabilising fields when available (helps avoid tenant-specific validation errors) - optional_fields = [ - "companyID", - "queueID", - "title", - "priority", - "dueDateTime", - "ticketCategory", - "organizationalLevelAssociationID", + # Copy stabilising fields when present in the response (even if value is 0/null) + # This preserves the current ticket classification without unintended changes. + stabilising_fields = [ + ("issueType", ["issueType", "issueTypeID", "issueTypeId"]), + ("subIssueType", ["subIssueType", "subIssueTypeID", "subIssueTypeId"]), + ("source", ["source", "sourceID", "sourceId"]), + ("companyID", ["companyID"]), + ("queueID", ["queueID"]), + ("title", ["title"]), + ("priority", ["priority"]), + ("dueDateTime", ["dueDateTime"]), + ("ticketCategory", ["ticketCategory"]), + ("organizationalLevelAssociationID", ["organizationalLevelAssociationID"]), ] - for f in optional_fields: - if f in ticket: - payload[f] = ticket.get(f) + for field_name, keys in stabilising_fields: + if _has_field(ticket, keys): + payload[field_name] = _pick(ticket, keys) return self.update_ticket(payload) diff --git a/docs/changelog-claude.md b/docs/changelog-claude.md index d41ad62..e8358f7 100644 --- a/docs/changelog-claude.md +++ b/docs/changelog-claude.md @@ -4,6 +4,12 @@ This file documents all changes made to this project via Claude Code. ## [2026-02-05] +### Fixed +- Autotask ticket resolution update now correctly handles tickets where `issueType`, + `subIssueType`, or `source` fields are not set (value 0 or null). Previously this + caused "required fields are missing" errors. Now only `id` and `status` are required; + other stabilising fields are copied when present. + ### Added - Restored Autotask PSA integration from branch `v20260203-13-autotask-resolution-item-wrapper`: - `integrations/autotask/client.py` - Autotask REST API client with full support for: