Fix Autotask resolution update for tickets without issueType/subIssueType/source

Tickets may have issueType, subIssueType, and source fields with value 0 or null
(meaning 'not set'). The previous validation incorrectly required these fields
to have non-null values, causing "required fields are missing" errors.

Now only id and status are required. Other stabilising fields are copied to the
PUT payload when present in the GET response, preserving current values (including 0).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ivo Oskamp 2026-02-05 15:18:32 +01:00
parent 8baf4e8ff1
commit 19b2776eed
2 changed files with 36 additions and 24 deletions

View File

@ -598,27 +598,32 @@ class AutotaskClient:
raise AutotaskError("Autotask did not return a ticket object.") raise AutotaskError("Autotask did not return a ticket object.")
def _pick(d: Dict[str, Any], keys: List[str]) -> Any: 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: for k in keys:
if k in d and d.get(k) not in (None, ""): if k in d:
return d.get(k) return d.get(k)
return None 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) # 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_issue_type = _pick(ticket, ["issueType", "issueTypeID", "issueTypeId"])
resolved_sub_issue_type = _pick(ticket, ["subIssueType", "subIssueTypeID", "subIssueTypeId"]) resolved_sub_issue_type = _pick(ticket, ["subIssueType", "subIssueTypeID", "subIssueTypeId"])
resolved_source = _pick(ticket, ["source", "sourceID", "sourceId"]) resolved_source = _pick(ticket, ["source", "sourceID", "sourceId"])
resolved_status = _pick(ticket, ["status", "statusID", "statusId"]) resolved_status = _pick(ticket, ["status", "statusID", "statusId"])
# Only id and status are truly required - others may be 0/null in Autotask
missing: List[str] = [] missing: List[str] = []
if _pick(ticket, ["id"]) in (None, ""): if _pick(ticket, ["id"]) is None:
missing.append("id") missing.append("id")
if resolved_issue_type in (None, ""): if resolved_status is 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, ""):
missing.append("status") missing.append("status")
if missing: if missing:
@ -628,27 +633,28 @@ class AutotaskClient:
payload: Dict[str, Any] = { payload: Dict[str, Any] = {
"id": int(ticket.get("id")), "id": int(ticket.get("id")),
"issueType": resolved_issue_type,
"subIssueType": resolved_sub_issue_type,
"source": resolved_source,
# Keep status unchanged # Keep status unchanged
"status": resolved_status, "status": resolved_status,
"resolution": str(resolution_text or ""), "resolution": str(resolution_text or ""),
} }
# Copy other stabilising fields when available (helps avoid tenant-specific validation errors) # Copy stabilising fields when present in the response (even if value is 0/null)
optional_fields = [ # This preserves the current ticket classification without unintended changes.
"companyID", stabilising_fields = [
"queueID", ("issueType", ["issueType", "issueTypeID", "issueTypeId"]),
"title", ("subIssueType", ["subIssueType", "subIssueTypeID", "subIssueTypeId"]),
"priority", ("source", ["source", "sourceID", "sourceId"]),
"dueDateTime", ("companyID", ["companyID"]),
"ticketCategory", ("queueID", ["queueID"]),
"organizationalLevelAssociationID", ("title", ["title"]),
("priority", ["priority"]),
("dueDateTime", ["dueDateTime"]),
("ticketCategory", ["ticketCategory"]),
("organizationalLevelAssociationID", ["organizationalLevelAssociationID"]),
] ]
for f in optional_fields: for field_name, keys in stabilising_fields:
if f in ticket: if _has_field(ticket, keys):
payload[f] = ticket.get(f) payload[field_name] = _pick(ticket, keys)
return self.update_ticket(payload) return self.update_ticket(payload)

View File

@ -4,6 +4,12 @@ This file documents all changes made to this project via Claude Code.
## [2026-02-05] ## [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 ### Added
- Restored Autotask PSA integration from branch `v20260203-13-autotask-resolution-item-wrapper`: - Restored Autotask PSA integration from branch `v20260203-13-autotask-resolution-item-wrapper`:
- `integrations/autotask/client.py` - Autotask REST API client with full support for: - `integrations/autotask/client.py` - Autotask REST API client with full support for: