Add cross-company ticket search for overarching issues
The "Link existing ticket" dialog now searches across all companies when a ticket number is entered, enabling linking of overarching issues. Changes: - Added query_tickets_by_number() to Autotask client - Route searches both customer's company and cross-company when ticket number detected - Results are combined and deduplicated (customer tickets shown first) - Enables linking multi-company infrastructure issues to any job Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e6847b8ec3
commit
077e1fb176
@ -970,6 +970,47 @@ class AutotaskClient:
|
|||||||
return items[: int(limit)]
|
return items[: int(limit)]
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def query_tickets_by_number(
|
||||||
|
self,
|
||||||
|
ticket_number: str,
|
||||||
|
*,
|
||||||
|
exclude_status_ids: Optional[List[int]] = None,
|
||||||
|
limit: int = 10,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""Query Tickets by ticket number across all companies.
|
||||||
|
|
||||||
|
Uses POST /Tickets/query.
|
||||||
|
|
||||||
|
This is useful for linking overarching issues that span multiple companies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tnum = (ticket_number or "").strip()
|
||||||
|
if not tnum:
|
||||||
|
return []
|
||||||
|
|
||||||
|
flt: List[Dict[str, Any]] = [
|
||||||
|
{"op": "eq", "field": "ticketNumber", "value": tnum},
|
||||||
|
]
|
||||||
|
|
||||||
|
ex: List[int] = []
|
||||||
|
for x in exclude_status_ids or []:
|
||||||
|
try:
|
||||||
|
v = int(x)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if v > 0:
|
||||||
|
ex.append(v)
|
||||||
|
if ex:
|
||||||
|
flt.append({"op": "notIn", "field": "status", "value": ex})
|
||||||
|
|
||||||
|
data = self._request("POST", "Tickets/query", json_body={"filter": flt})
|
||||||
|
items = self._as_items_list(data)
|
||||||
|
|
||||||
|
# Respect limit if tenant returns more.
|
||||||
|
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]]:
|
def query_time_entries_by_ticket_id(self, ticket_id: int) -> List[Dict[str, Any]]:
|
||||||
"""Query TimeEntries for a specific ticket.
|
"""Query TimeEntries for a specific ticket.
|
||||||
|
|
||||||
|
|||||||
@ -1576,6 +1576,11 @@ def api_run_checks_autotask_existing_tickets():
|
|||||||
"""List open (non-terminal) Autotask tickets for the selected run's customer.
|
"""List open (non-terminal) Autotask tickets for the selected run's customer.
|
||||||
|
|
||||||
Phase 2.2: used by the Run Checks modal to link an existing PSA ticket.
|
Phase 2.2: used by the Run Checks modal to link an existing PSA ticket.
|
||||||
|
|
||||||
|
Search behaviour:
|
||||||
|
- Always searches tickets for the customer's company
|
||||||
|
- If search term looks like a ticket number (starts with T + digits), also searches
|
||||||
|
across all companies to enable linking overarching issues
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1640,20 +1645,43 @@ def api_run_checks_autotask_existing_tickets():
|
|||||||
# Best-effort; list will still work without labels.
|
# Best-effort; list will still work without labels.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# First: query tickets for this customer's company
|
||||||
tickets = client.query_tickets_for_company(
|
tickets = client.query_tickets_for_company(
|
||||||
int(customer.autotask_company_id),
|
int(customer.autotask_company_id),
|
||||||
search=q,
|
search=q,
|
||||||
exclude_status_ids=sorted(AUTOTASK_TERMINAL_STATUS_IDS),
|
exclude_status_ids=sorted(AUTOTASK_TERMINAL_STATUS_IDS),
|
||||||
limit=75,
|
limit=75,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Second: if search looks like a ticket number, also search across all companies
|
||||||
|
# This allows linking overarching issues that span multiple companies
|
||||||
|
cross_company_tickets = []
|
||||||
|
if q and q.upper().startswith("T") and any(ch.isdigit() for ch in q):
|
||||||
|
try:
|
||||||
|
cross_company_tickets = client.query_tickets_by_number(
|
||||||
|
q,
|
||||||
|
exclude_status_ids=sorted(AUTOTASK_TERMINAL_STATUS_IDS),
|
||||||
|
limit=10,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# Best-effort; main company query already succeeded
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({"status": "error", "message": f"Autotask ticket lookup failed: {exc}"}), 400
|
return jsonify({"status": "error", "message": f"Autotask ticket lookup failed: {exc}"}), 400
|
||||||
|
|
||||||
|
# Combine and deduplicate results
|
||||||
|
seen_ids = set()
|
||||||
items = []
|
items = []
|
||||||
for t in tickets or []:
|
|
||||||
|
def add_ticket(t):
|
||||||
if not isinstance(t, dict):
|
if not isinstance(t, dict):
|
||||||
continue
|
return
|
||||||
tid = t.get("id")
|
tid = t.get("id")
|
||||||
|
if tid in seen_ids:
|
||||||
|
return
|
||||||
|
seen_ids.add(tid)
|
||||||
|
|
||||||
tnum = (t.get("ticketNumber") or t.get("number") or "")
|
tnum = (t.get("ticketNumber") or t.get("number") or "")
|
||||||
title = (t.get("title") or "")
|
title = (t.get("title") or "")
|
||||||
st = t.get("status")
|
st = t.get("status")
|
||||||
@ -1672,6 +1700,14 @@ def api_run_checks_autotask_existing_tickets():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add company tickets first (primary results)
|
||||||
|
for t in tickets or []:
|
||||||
|
add_ticket(t)
|
||||||
|
|
||||||
|
# Then add cross-company tickets (secondary results for ticket number search)
|
||||||
|
for t in cross_company_tickets or []:
|
||||||
|
add_ticket(t)
|
||||||
|
|
||||||
# Sort: newest-ish first. Autotask query ordering isn't guaranteed, so we provide a stable sort.
|
# Sort: newest-ish first. Autotask query ordering isn't guaranteed, so we provide a stable sort.
|
||||||
items.sort(key=lambda x: (x.get("ticketNumber") or ""), reverse=True)
|
items.sort(key=lambda x: (x.get("ticketNumber") or ""), reverse=True)
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,14 @@ This file documents all changes made to this project via Claude Code.
|
|||||||
|
|
||||||
## [2026-02-05]
|
## [2026-02-05]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Autotask "Link existing ticket" now supports cross-company ticket search:
|
||||||
|
- Added `query_tickets_by_number()` to search tickets by number across all companies
|
||||||
|
- When searching with a ticket number (e.g., "T20260205.0001"), results include:
|
||||||
|
- Tickets from the customer's company (primary results)
|
||||||
|
- Matching tickets from other companies (for overarching issues)
|
||||||
|
- Enables linking tickets for multi-company infrastructure issues
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Autotask resolve confirmation and note messages now correctly indicate ticket closure status:
|
- Autotask resolve confirmation and note messages now correctly indicate ticket closure status:
|
||||||
- Frontend confirmation dialog explains conditional closure based on time entries
|
- Frontend confirmation dialog explains conditional closure based on time entries
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user