Add conditional ticket status update based on time entries

Implements API contract section 9: ticket resolution workflow with
conditional status updates based on time entry existence.

- Added query_time_entries_by_ticket_id() for POST /TimeEntries/query
- update_ticket_resolution_safe() now checks time entries:
  * No time entries: sets status to 5 (Complete)
  * Has time entries: keeps current status (ticket remains open)

This ensures tickets are only auto-closed when appropriate per the
validated Autotask API workflow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Ivo Oskamp 2026-02-05 16:35:28 +01:00
parent 88421badbd
commit 9b905fa24f
2 changed files with 44 additions and 4 deletions

View File

@ -559,11 +559,16 @@ class AutotaskClient:
def update_ticket_resolution_safe(self, ticket_id: int, resolution_text: str) -> Dict[str, Any]:
"""Safely update the Ticket 'resolution' field without changing status.
"""Safely update the Ticket 'resolution' field with conditional status update.
Autotask Tickets require a full PUT update; therefore we must:
- GET /Tickets/{id} to retrieve current stabilising fields (including classification/routing)
- PUT /Tickets with those stabilising fields unchanged, and only update 'resolution'
- Query time entries for the ticket
- PUT /Tickets with stabilising fields and conditional status
Status logic (per API contract section 9):
- If NO time entries exist: set status to 5 (Complete)
- If time entries exist: keep current status unchanged
IMPORTANT:
- GET /Tickets/{id} returns the ticket object under the 'item' envelope in most tenants.
@ -639,14 +644,23 @@ class AutotaskClient:
"Cannot safely update ticket resolution because required fields are missing: " + ", ".join(missing)
)
# Check for time entries as per API contract section 9
# If no time entries exist, we can set status to 5 (Complete)
# If time entries exist, status remains unchanged
time_entries = self.query_time_entries_by_ticket_id(int(ticket_id))
has_time_entries = len(time_entries) > 0
# Determine final status based on time entry check
# Status 5 = Complete (sets completedDate and resolvedDateTime)
final_status = resolved_status if has_time_entries else 5
# Build payload with exact values from GET response (including null if that's what we got)
payload: Dict[str, Any] = {
"id": int(ticket_id),
"issueType": resolved_issue_type,
"subIssueType": resolved_sub_issue_type,
"source": resolved_source,
# Keep status unchanged
"status": resolved_status,
"status": final_status,
"resolution": str(resolution_text or ""),
}
@ -955,3 +969,22 @@ class AutotaskClient:
if limit and isinstance(limit, int) and limit > 0:
return items[: int(limit)]
return items
def query_time_entries_by_ticket_id(self, ticket_id: int) -> List[Dict[str, Any]]:
"""Query TimeEntries for a specific ticket.
Uses POST /TimeEntries/query as per API contract section 6.
Returns list of time entry items. Empty list if no time entries exist.
"""
try:
tid = int(ticket_id)
except Exception:
tid = 0
if tid <= 0:
return []
payload = {"filter": [{"op": "eq", "field": "ticketID", "value": tid}]}
data = self._request("POST", "TimeEntries/query", json_body=payload)
return self._as_items_list(data)

View File

@ -4,6 +4,13 @@ This file documents all changes made to this project via Claude Code.
## [2026-02-05]
### Added
- Autotask conditional ticket status update based on time entries (API contract section 9):
- `query_time_entries_by_ticket_id()` - Query time entries for a ticket via POST /TimeEntries/query
- `update_ticket_resolution_safe()` now checks for time entries and conditionally sets status:
- If NO time entries exist: sets status to 5 (Complete) with completedDate and resolvedDateTime
- If time entries exist: keeps current status unchanged (ticket remains open)
### Fixed
- Autotask ticket resolution update now correctly preserves exact field values from GET response in PUT payload.
The `issueType`, `subIssueType`, and `source` fields are copied with their exact values (including null)