Auto-commit local changes before build (2026-01-19 12:53:24)
This commit is contained in:
parent
dabec03f91
commit
07e6630a89
@ -1 +1 @@
|
|||||||
v20260119-03-autotask-reference-data-auto-refresh
|
v20260119-04-autotask-ticket-registration
|
||||||
|
|||||||
@ -430,3 +430,40 @@ class AutotaskClient:
|
|||||||
return items[0]
|
return items[0]
|
||||||
|
|
||||||
raise AutotaskError("Autotask did not return a created ticket object.")
|
raise AutotaskError("Autotask did not return a created ticket object.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_ticket(self, ticket_id: int) -> Dict[str, Any]:
|
||||||
|
"""Retrieve a Ticket by Autotask Ticket ID.
|
||||||
|
|
||||||
|
Uses GET /Tickets/{id}.
|
||||||
|
|
||||||
|
This is the authoritative retrieval method and is mandatory after creation,
|
||||||
|
because the create response does not reliably include the human-facing
|
||||||
|
ticket number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
tid = int(ticket_id)
|
||||||
|
except Exception:
|
||||||
|
raise AutotaskError("Invalid ticket id.")
|
||||||
|
|
||||||
|
if tid <= 0:
|
||||||
|
raise AutotaskError("Invalid ticket id.")
|
||||||
|
|
||||||
|
data = self._request("GET", f"Tickets/{tid}")
|
||||||
|
if isinstance(data, dict):
|
||||||
|
if "item" in data and isinstance(data.get("item"), dict):
|
||||||
|
return data["item"]
|
||||||
|
if "items" in data and isinstance(data.get("items"), list) and data.get("items"):
|
||||||
|
first = data.get("items")[0]
|
||||||
|
if isinstance(first, dict):
|
||||||
|
return first
|
||||||
|
# Some environments return the raw object
|
||||||
|
if "id" in data or "ticketNumber" in data or "number" in data:
|
||||||
|
return data
|
||||||
|
|
||||||
|
items = self._as_items_list(data)
|
||||||
|
if items:
|
||||||
|
return items[0]
|
||||||
|
|
||||||
|
raise AutotaskError("Autotask did not return a ticket object.")
|
||||||
|
|||||||
@ -35,6 +35,7 @@ from ..models import (
|
|||||||
Override,
|
Override,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
|
from ..ticketing_utils import ensure_internal_ticket_for_job, ensure_ticket_jobrun_links
|
||||||
|
|
||||||
|
|
||||||
def _build_autotask_client_from_settings():
|
def _build_autotask_client_from_settings():
|
||||||
@ -894,16 +895,7 @@ def api_run_checks_create_autotask_ticket():
|
|||||||
if not run:
|
if not run:
|
||||||
return jsonify({"status": "error", "message": "Run not found."}), 404
|
return jsonify({"status": "error", "message": "Run not found."}), 404
|
||||||
|
|
||||||
# Idempotent: if already created, return existing linkage.
|
already_exists = bool(getattr(run, "autotask_ticket_id", None))
|
||||||
if getattr(run, "autotask_ticket_id", None):
|
|
||||||
return jsonify(
|
|
||||||
{
|
|
||||||
"status": "ok",
|
|
||||||
"ticket_id": int(run.autotask_ticket_id),
|
|
||||||
"ticket_number": getattr(run, "autotask_ticket_number", None) or "",
|
|
||||||
"already_exists": True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
job = Job.query.get(run.job_id)
|
job = Job.query.get(run.job_id)
|
||||||
if not job:
|
if not job:
|
||||||
@ -1002,42 +994,125 @@ def api_run_checks_create_autotask_ticket():
|
|||||||
if priority_id:
|
if priority_id:
|
||||||
payload["priority"] = int(priority_id)
|
payload["priority"] = int(priority_id)
|
||||||
|
|
||||||
|
client = None
|
||||||
try:
|
try:
|
||||||
client = _build_autotask_client_from_settings()
|
client = _build_autotask_client_from_settings()
|
||||||
created = client.create_ticket(payload)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({"status": "error", "message": f"Autotask ticket creation failed: {exc}"}), 400
|
return jsonify({"status": "error", "message": f"Autotask client setup failed: {exc}"}), 400
|
||||||
|
|
||||||
ticket_id = created.get("id") if isinstance(created, dict) else None
|
ticket_id = getattr(run, "autotask_ticket_id", None)
|
||||||
ticket_number = None
|
ticket_number = getattr(run, "autotask_ticket_number", None)
|
||||||
if isinstance(created, dict):
|
|
||||||
ticket_number = created.get("ticketNumber") or created.get("number") or created.get("ticket_number")
|
|
||||||
|
|
||||||
|
# Create ticket only when missing.
|
||||||
if not ticket_id:
|
if not ticket_id:
|
||||||
return jsonify({"status": "error", "message": "Autotask did not return a ticket id."}), 400
|
try:
|
||||||
|
created = client.create_ticket(payload)
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"status": "error", "message": f"Autotask ticket creation failed: {exc}"}), 400
|
||||||
|
|
||||||
|
ticket_id = created.get("id") if isinstance(created, dict) else None
|
||||||
|
if isinstance(created, dict):
|
||||||
|
ticket_number = created.get("ticketNumber") or created.get("number") or created.get("ticket_number")
|
||||||
|
|
||||||
|
if not ticket_id:
|
||||||
|
return jsonify({"status": "error", "message": "Autotask did not return a ticket id."}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
run.autotask_ticket_id = int(ticket_id)
|
||||||
|
except Exception:
|
||||||
|
run.autotask_ticket_id = None
|
||||||
|
|
||||||
|
run.autotask_ticket_number = (str(ticket_number).strip() if ticket_number is not None else "") or None
|
||||||
|
run.autotask_ticket_created_at = datetime.utcnow()
|
||||||
|
run.autotask_ticket_created_by_user_id = current_user.id
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.session.add(run)
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({"status": "error", "message": f"Failed to store ticket reference: {exc}"}), 500
|
||||||
|
|
||||||
|
# Mandatory post-create (or repair) retrieval for Ticket Number.
|
||||||
|
if ticket_id and not (ticket_number or "").strip():
|
||||||
|
try:
|
||||||
|
fetched = client.get_ticket(int(ticket_id))
|
||||||
|
ticket_number = None
|
||||||
|
if isinstance(fetched, dict):
|
||||||
|
ticket_number = fetched.get("ticketNumber") or fetched.get("number") or fetched.get("ticket_number")
|
||||||
|
ticket_number = (str(ticket_number).strip() if ticket_number is not None else "") or None
|
||||||
|
except Exception as exc:
|
||||||
|
# Ticket ID is persisted, but internal propagation must not proceed without the ticket number.
|
||||||
|
return jsonify({"status": "error", "message": f"Autotask ticket created but ticket number retrieval failed: {exc}"}), 400
|
||||||
|
|
||||||
|
if not ticket_number:
|
||||||
|
return jsonify({"status": "error", "message": "Autotask ticket created but ticket number is not available."}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
run = JobRun.query.get(run_id)
|
||||||
|
if not run:
|
||||||
|
return jsonify({"status": "error", "message": "Run not found."}), 404
|
||||||
|
run.autotask_ticket_number = ticket_number
|
||||||
|
db.session.add(run)
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({"status": "error", "message": f"Failed to store ticket number: {exc}"}), 500
|
||||||
|
|
||||||
|
# Internal ticket + linking propagation (required for UI parity)
|
||||||
try:
|
try:
|
||||||
run.autotask_ticket_id = int(ticket_id)
|
run = JobRun.query.get(run_id)
|
||||||
except Exception:
|
if not run:
|
||||||
run.autotask_ticket_id = None
|
return jsonify({"status": "error", "message": "Run not found."}), 404
|
||||||
|
|
||||||
run.autotask_ticket_number = (str(ticket_number).strip() if ticket_number is not None else "") or None
|
ticket_id_int = int(getattr(run, "autotask_ticket_id", None) or 0)
|
||||||
run.autotask_ticket_created_at = datetime.utcnow()
|
ticket_number_str = (getattr(run, "autotask_ticket_number", None) or "").strip()
|
||||||
run.autotask_ticket_created_by_user_id = current_user.id
|
|
||||||
|
if ticket_id_int <= 0 or not ticket_number_str:
|
||||||
|
return jsonify({"status": "error", "message": "Autotask ticket reference is incomplete."}), 400
|
||||||
|
|
||||||
|
# Create/reuse internal ticket (code == Autotask Ticket Number)
|
||||||
|
internal_ticket = ensure_internal_ticket_for_job(
|
||||||
|
ticket_code=ticket_number_str,
|
||||||
|
title=subject,
|
||||||
|
description=description,
|
||||||
|
job=job,
|
||||||
|
active_from_dt=getattr(run, "run_at", None) or datetime.utcnow(),
|
||||||
|
start_dt=getattr(run, "autotask_ticket_created_at", None) or datetime.utcnow(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link ticket to all open runs for this job (reviewed_at IS NULL) and propagate PSA reference.
|
||||||
|
open_runs = JobRun.query.filter(JobRun.job_id == job.id, JobRun.reviewed_at.is_(None)).all()
|
||||||
|
run_ids_to_link: list[int] = []
|
||||||
|
|
||||||
|
for r in open_runs:
|
||||||
|
# Never overwrite an existing different Autotask ticket for a run.
|
||||||
|
existing_id = getattr(r, "autotask_ticket_id", None)
|
||||||
|
if existing_id and int(existing_id) != ticket_id_int:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not existing_id:
|
||||||
|
r.autotask_ticket_id = ticket_id_int
|
||||||
|
r.autotask_ticket_number = ticket_number_str
|
||||||
|
r.autotask_ticket_created_at = getattr(run, "autotask_ticket_created_at", None)
|
||||||
|
r.autotask_ticket_created_by_user_id = getattr(run, "autotask_ticket_created_by_user_id", None)
|
||||||
|
db.session.add(r)
|
||||||
|
|
||||||
|
run_ids_to_link.append(int(r.id))
|
||||||
|
|
||||||
|
ensure_ticket_jobrun_links(ticket_id=int(internal_ticket.id), run_ids=run_ids_to_link, link_source="autotask")
|
||||||
|
|
||||||
try:
|
|
||||||
db.session.add(run)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return jsonify({"status": "error", "message": f"Failed to store ticket reference: {exc}"}), 500
|
return jsonify({"status": "error", "message": f"Failed to propagate internal ticket linkage: {exc}"}), 500
|
||||||
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"ticket_id": int(run.autotask_ticket_id) if run.autotask_ticket_id else None,
|
"ticket_id": int(getattr(run, "autotask_ticket_id", None) or 0) or None,
|
||||||
"ticket_number": run.autotask_ticket_number or "",
|
"ticket_number": getattr(run, "autotask_ticket_number", None) or "",
|
||||||
"already_exists": False,
|
"already_exists": already_exists,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -223,6 +223,16 @@ Changes:
|
|||||||
- Aligned Autotask ticket creation and polling paths with the legacy manual ticket creation flow, without changing any UI behavior.
|
- Aligned Autotask ticket creation and polling paths with the legacy manual ticket creation flow, without changing any UI behavior.
|
||||||
- Ensured solution works consistently with Autotask integration enabled or disabled by relying exclusively on internal Ticket and TicketJobRun structures.
|
- Ensured solution works consistently with Autotask integration enabled or disabled by relying exclusively on internal Ticket and TicketJobRun structures.
|
||||||
|
|
||||||
|
## v20260119-04-autotask-ticket-registration
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Implemented reliable Autotask ticket number retrieval by enforcing a post-create GET on the created ticket, avoiding incomplete create responses.
|
||||||
|
- Added automatic creation or reuse of an internal Ticket based on the Autotask ticket number to preserve legacy ticket behavior.
|
||||||
|
- Ensured idempotent linking of the internal Ticket to all open job runs (reviewed_at IS NULL) for the same job, matching manual ticket functionality.
|
||||||
|
- Propagated Autotask ticket references (autotask_ticket_id and autotask_ticket_number) to all related open runs when a ticket is created.
|
||||||
|
- Added repair/propagation logic so runs that already have an Autotask ticket ID but lack internal linking are corrected automatically.
|
||||||
|
- Guaranteed that future runs for the same job inherit the existing Autotask and internal ticket associations.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.21
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user