Auto-commit local changes before build (2026-01-20 10:07:44)
This commit is contained in:
parent
4b506986a6
commit
5c0e1b08aa
@ -1 +1 @@
|
|||||||
v20260120-02-autotask-deleted-ticket-detection
|
v20260120-03-autotask-deletedby-name-runlink
|
||||||
|
|||||||
@ -483,6 +483,40 @@ class AutotaskClient:
|
|||||||
raise AutotaskError("Autotask did not return a ticket object.")
|
raise AutotaskError("Autotask did not return a ticket object.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource(self, resource_id: int) -> Dict[str, Any]:
|
||||||
|
"""Retrieve a Resource by Autotask Resource ID.
|
||||||
|
|
||||||
|
Uses GET /Resources/{id}.
|
||||||
|
|
||||||
|
Returns the resource object (fields depend on permissions).
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
rid = int(resource_id)
|
||||||
|
except Exception:
|
||||||
|
raise AutotaskError("Invalid resource id.")
|
||||||
|
|
||||||
|
if rid <= 0:
|
||||||
|
raise AutotaskError("Invalid resource id.")
|
||||||
|
|
||||||
|
data = self._request("GET", f"Resources/{rid}")
|
||||||
|
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
|
||||||
|
if "id" in data or "firstName" in data or "lastName" in data:
|
||||||
|
return data
|
||||||
|
|
||||||
|
items = self._as_items_list(data)
|
||||||
|
if items:
|
||||||
|
return items[0]
|
||||||
|
|
||||||
|
raise AutotaskError("Autotask did not return a resource object.")
|
||||||
|
|
||||||
|
|
||||||
def query_deleted_ticket_logs_by_ticket_ids(self, ticket_ids: List[int]) -> List[Dict[str, Any]]:
|
def query_deleted_ticket_logs_by_ticket_ids(self, ticket_ids: List[int]) -> List[Dict[str, Any]]:
|
||||||
"""Query DeletedTicketLogs for a set of ticket IDs.
|
"""Query DeletedTicketLogs for a set of ticket IDs.
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from .parsers import parse_mail_message
|
|||||||
from .parsers.veeam import extract_vspc_active_alarms_companies
|
from .parsers.veeam import extract_vspc_active_alarms_companies
|
||||||
from .email_utils import normalize_from_address, extract_best_html_from_eml, is_effectively_blank_html
|
from .email_utils import normalize_from_address, extract_best_html_from_eml, is_effectively_blank_html
|
||||||
from .job_matching import find_matching_job
|
from .job_matching import find_matching_job
|
||||||
|
from .ticketing_utils import link_open_internal_tickets_to_run
|
||||||
|
|
||||||
|
|
||||||
GRAPH_TOKEN_URL_TEMPLATE = "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
|
GRAPH_TOKEN_URL_TEMPLATE = "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
|
||||||
@ -248,6 +249,12 @@ def _store_messages(settings: SystemSettings, messages):
|
|||||||
db.session.add(mail)
|
db.session.add(mail)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
# Link any open internal tickets to this new run (legacy behavior).
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Immediately run parsers so Inbox / Jobs can show parsed metadata + objects.
|
# Immediately run parsers so Inbox / Jobs can show parsed metadata + objects.
|
||||||
try:
|
try:
|
||||||
parse_mail_message(mail)
|
parse_mail_message(mail)
|
||||||
@ -334,6 +341,12 @@ def _store_messages(settings: SystemSettings, messages):
|
|||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
# Link any open internal tickets to this new run (legacy behavior).
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
auto_approved_runs.append((job.customer_id, job.id, run.id, mail.id))
|
auto_approved_runs.append((job.customer_id, job.id, run.id, mail.id))
|
||||||
created_any = True
|
created_any = True
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from .routes_shared import _format_datetime, _log_admin_event, _send_mail_messag
|
|||||||
from ..email_utils import extract_best_html_from_eml, is_effectively_blank_html
|
from ..email_utils import extract_best_html_from_eml, is_effectively_blank_html
|
||||||
from ..parsers.veeam import extract_vspc_active_alarms_companies
|
from ..parsers.veeam import extract_vspc_active_alarms_companies
|
||||||
from ..models import MailObject
|
from ..models import MailObject
|
||||||
|
from ..ticketing_utils import link_open_internal_tickets_to_run
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
@ -294,6 +295,11 @@ def inbox_message_approve(message_id: int):
|
|||||||
if hasattr(run, 'storage_free_percent') and hasattr(msg, 'storage_free_percent'):
|
if hasattr(run, 'storage_free_percent') and hasattr(msg, 'storage_free_percent'):
|
||||||
run.storage_free_percent = msg.storage_free_percent
|
run.storage_free_percent = msg.storage_free_percent
|
||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Update mail message to reflect approval
|
# Update mail message to reflect approval
|
||||||
msg.job_id = job.id
|
msg.job_id = job.id
|
||||||
@ -537,6 +543,21 @@ def inbox_message_approve_vspc_companies(message_id: int):
|
|||||||
run.remark = getattr(msg, "overall_message", None)
|
run.remark = getattr(msg, "overall_message", None)
|
||||||
|
|
||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
created_runs.append(run)
|
created_runs.append(run)
|
||||||
|
|
||||||
@ -684,6 +705,21 @@ def inbox_message_approve_vspc_companies(message_id: int):
|
|||||||
run2.remark = getattr(other, "overall_message", None)
|
run2.remark = getattr(other, "overall_message", None)
|
||||||
db.session.add(run2)
|
db.session.add(run2)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run2, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run2, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run2, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
|
||||||
# Persist objects per company
|
# Persist objects per company
|
||||||
try:
|
try:
|
||||||
@ -1050,6 +1086,21 @@ def inbox_reparse_all():
|
|||||||
|
|
||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
||||||
created_any = True
|
created_any = True
|
||||||
|
|
||||||
@ -1109,6 +1160,21 @@ def inbox_reparse_all():
|
|||||||
run.storage_free_percent = msg.storage_free_percent
|
run.storage_free_percent = msg.storage_free_percent
|
||||||
|
|
||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
db.session.flush() # ensure run.id is available
|
db.session.flush() # ensure run.id is available
|
||||||
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
||||||
|
|
||||||
@ -1209,6 +1275,21 @@ def inbox_reparse_all():
|
|||||||
|
|
||||||
db.session.add(run)
|
db.session.add(run)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=run, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
db.session.flush()
|
||||||
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
auto_approved_runs.append((job.customer_id, job.id, run.id, msg.id))
|
||||||
|
|
||||||
msg.job_id = job.id
|
msg.job_id = job.id
|
||||||
|
|||||||
@ -38,6 +38,7 @@ from ..models import (
|
|||||||
TicketScope,
|
TicketScope,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
|
from ..ticketing_utils import link_open_internal_tickets_to_run
|
||||||
|
|
||||||
|
|
||||||
AUTOTASK_TERMINAL_STATUS_IDS = {5}
|
AUTOTASK_TERMINAL_STATUS_IDS = {5}
|
||||||
@ -211,6 +212,36 @@ def _poll_autotask_ticket_states_for_runs(*, run_ids: list[int]) -> None:
|
|||||||
continue
|
continue
|
||||||
deleted_map[tid_int] = it
|
deleted_map[tid_int] = it
|
||||||
|
|
||||||
|
# Resolve deletedByResourceID to display names (best-effort, cached per request).
|
||||||
|
resource_name_map: dict[int, tuple[str, str]] = {}
|
||||||
|
try:
|
||||||
|
resource_ids = set()
|
||||||
|
for item in deleted_map.values():
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
raw = item.get("deletedByResourceID") if "deletedByResourceID" in item else item.get("deletedByResourceId")
|
||||||
|
try:
|
||||||
|
rid = int(raw) if raw is not None else 0
|
||||||
|
except Exception:
|
||||||
|
rid = 0
|
||||||
|
if rid > 0:
|
||||||
|
resource_ids.add(rid)
|
||||||
|
|
||||||
|
for rid in sorted(resource_ids):
|
||||||
|
try:
|
||||||
|
r = client.get_resource(rid)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not isinstance(r, dict):
|
||||||
|
continue
|
||||||
|
fn = (r.get("firstName") or "").strip()
|
||||||
|
ln = (r.get("lastName") or "").strip()
|
||||||
|
if fn or ln:
|
||||||
|
resource_name_map[rid] = (fn, ln)
|
||||||
|
except Exception:
|
||||||
|
resource_name_map = {}
|
||||||
|
|
||||||
|
|
||||||
# Persist deleted audit fields on runs and resolve internal ticket as PSA-deleted.
|
# Persist deleted audit fields on runs and resolve internal ticket as PSA-deleted.
|
||||||
for tid, item in deleted_map.items():
|
for tid, item in deleted_map.items():
|
||||||
runs_for_ticket = ticket_to_runs.get(tid) or []
|
runs_for_ticket = ticket_to_runs.get(tid) or []
|
||||||
@ -239,6 +270,15 @@ def _poll_autotask_ticket_states_for_runs(*, run_ids: list[int]) -> None:
|
|||||||
rr.autotask_ticket_deleted_at = deleted_dt
|
rr.autotask_ticket_deleted_at = deleted_dt
|
||||||
if deleted_by_int and getattr(rr, "autotask_ticket_deleted_by_resource_id", None) is None:
|
if deleted_by_int and getattr(rr, "autotask_ticket_deleted_by_resource_id", None) is None:
|
||||||
rr.autotask_ticket_deleted_by_resource_id = deleted_by_int
|
rr.autotask_ticket_deleted_by_resource_id = deleted_by_int
|
||||||
|
try:
|
||||||
|
if deleted_by_int and deleted_by_int in resource_name_map:
|
||||||
|
fn, ln = resource_name_map.get(deleted_by_int) or ("", "")
|
||||||
|
if fn and getattr(rr, "autotask_ticket_deleted_by_first_name", None) is None:
|
||||||
|
rr.autotask_ticket_deleted_by_first_name = fn
|
||||||
|
if ln and getattr(rr, "autotask_ticket_deleted_by_last_name", None) is None:
|
||||||
|
rr.autotask_ticket_deleted_by_last_name = ln
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if ticket_number and not (getattr(rr, "autotask_ticket_number", None) or "").strip():
|
if ticket_number and not (getattr(rr, "autotask_ticket_number", None) or "").strip():
|
||||||
rr.autotask_ticket_number = str(ticket_number).strip()
|
rr.autotask_ticket_number = str(ticket_number).strip()
|
||||||
db.session.add(rr)
|
db.session.add(rr)
|
||||||
@ -669,6 +709,11 @@ def _ensure_missed_runs_for_job(job: Job, start_from: date, end_inclusive: date)
|
|||||||
mail_message_id=None,
|
mail_message_id=None,
|
||||||
)
|
)
|
||||||
db.session.add(miss)
|
db.session.add(miss)
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=miss, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
inserted += 1
|
inserted += 1
|
||||||
|
|
||||||
d = d + timedelta(days=1)
|
d = d + timedelta(days=1)
|
||||||
@ -750,6 +795,11 @@ def _ensure_missed_runs_for_job(job: Job, start_from: date, end_inclusive: date)
|
|||||||
mail_message_id=None,
|
mail_message_id=None,
|
||||||
)
|
)
|
||||||
db.session.add(miss)
|
db.session.add(miss)
|
||||||
|
db.session.flush()
|
||||||
|
try:
|
||||||
|
link_open_internal_tickets_to_run(run=miss, job=job)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
inserted += 1
|
inserted += 1
|
||||||
|
|
||||||
# Next month
|
# Next month
|
||||||
@ -1265,6 +1315,8 @@ def run_checks_details():
|
|||||||
"autotask_ticket_is_deleted": bool(getattr(run, "autotask_ticket_deleted_at", None)),
|
"autotask_ticket_is_deleted": bool(getattr(run, "autotask_ticket_deleted_at", None)),
|
||||||
"autotask_ticket_deleted_at": _format_datetime(getattr(run, "autotask_ticket_deleted_at", None)) if getattr(run, "autotask_ticket_deleted_at", None) else "",
|
"autotask_ticket_deleted_at": _format_datetime(getattr(run, "autotask_ticket_deleted_at", None)) if getattr(run, "autotask_ticket_deleted_at", None) else "",
|
||||||
"autotask_ticket_deleted_by_resource_id": getattr(run, "autotask_ticket_deleted_by_resource_id", None),
|
"autotask_ticket_deleted_by_resource_id": getattr(run, "autotask_ticket_deleted_by_resource_id", None),
|
||||||
|
"autotask_ticket_deleted_by_first_name": getattr(run, "autotask_ticket_deleted_by_first_name", None) or "",
|
||||||
|
"autotask_ticket_deleted_by_last_name": getattr(run, "autotask_ticket_deleted_by_last_name", None) or "",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1000,6 +1000,8 @@ def migrate_job_runs_autotask_ticket_deleted_fields() -> None:
|
|||||||
Columns:
|
Columns:
|
||||||
- job_runs.autotask_ticket_deleted_at (TIMESTAMP NULL)
|
- job_runs.autotask_ticket_deleted_at (TIMESTAMP NULL)
|
||||||
- job_runs.autotask_ticket_deleted_by_resource_id (INTEGER NULL)
|
- job_runs.autotask_ticket_deleted_by_resource_id (INTEGER NULL)
|
||||||
|
- job_runs.autotask_ticket_deleted_by_first_name (VARCHAR NULL)
|
||||||
|
- job_runs.autotask_ticket_deleted_by_last_name (VARCHAR NULL)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
table = "job_runs"
|
table = "job_runs"
|
||||||
@ -1024,8 +1026,19 @@ def migrate_job_runs_autotask_ticket_deleted_fields() -> None:
|
|||||||
print("[migrations] Adding job_runs.autotask_ticket_deleted_by_resource_id column...")
|
print("[migrations] Adding job_runs.autotask_ticket_deleted_by_resource_id column...")
|
||||||
conn.execute(text('ALTER TABLE "job_runs" ADD COLUMN autotask_ticket_deleted_by_resource_id INTEGER'))
|
conn.execute(text('ALTER TABLE "job_runs" ADD COLUMN autotask_ticket_deleted_by_resource_id INTEGER'))
|
||||||
|
|
||||||
|
if "autotask_ticket_deleted_by_first_name" not in cols:
|
||||||
|
print("[migrations] Adding job_runs.autotask_ticket_deleted_by_first_name column...")
|
||||||
|
conn.execute(text('ALTER TABLE "job_runs" ADD COLUMN autotask_ticket_deleted_by_first_name VARCHAR(128)'))
|
||||||
|
|
||||||
|
if "autotask_ticket_deleted_by_last_name" not in cols:
|
||||||
|
print("[migrations] Adding job_runs.autotask_ticket_deleted_by_last_name column...")
|
||||||
|
conn.execute(text('ALTER TABLE "job_runs" ADD COLUMN autotask_ticket_deleted_by_last_name VARCHAR(128)'))
|
||||||
|
|
||||||
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_by_resource_id ON "job_runs" (autotask_ticket_deleted_by_resource_id)'))
|
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_by_resource_id ON "job_runs" (autotask_ticket_deleted_by_resource_id)'))
|
||||||
|
|
||||||
|
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_by_first_name ON "job_runs" (autotask_ticket_deleted_by_first_name)'))
|
||||||
|
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_by_last_name ON "job_runs" (autotask_ticket_deleted_by_last_name)'))
|
||||||
|
|
||||||
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_at ON "job_runs" (autotask_ticket_deleted_at)'))
|
conn.execute(text('CREATE INDEX IF NOT EXISTS idx_job_runs_autotask_ticket_deleted_at ON "job_runs" (autotask_ticket_deleted_at)'))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[migrations] migrate_job_runs_autotask_ticket_deleted_fields failed (continuing): {exc}")
|
print(f"[migrations] migrate_job_runs_autotask_ticket_deleted_fields failed (continuing): {exc}")
|
||||||
|
|||||||
@ -283,7 +283,8 @@ class JobRun(db.Model):
|
|||||||
autotask_ticket_created_by_user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
autotask_ticket_created_by_user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
||||||
autotask_ticket_deleted_at = db.Column(db.DateTime, nullable=True)
|
autotask_ticket_deleted_at = db.Column(db.DateTime, nullable=True)
|
||||||
autotask_ticket_deleted_by_resource_id = db.Column(db.Integer, nullable=True)
|
autotask_ticket_deleted_by_resource_id = db.Column(db.Integer, nullable=True)
|
||||||
|
autotask_ticket_deleted_by_first_name = db.Column(db.String(128), nullable=True)
|
||||||
|
autotask_ticket_deleted_by_last_name = db.Column(db.String(128), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||||
|
|||||||
@ -883,17 +883,20 @@ table.addEventListener('change', function (e) {
|
|||||||
if (!atInfo) return;
|
if (!atInfo) return;
|
||||||
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
||||||
var isResolved = !!(run && run.autotask_ticket_is_resolved);
|
var isResolved = !!(run && run.autotask_ticket_is_resolved);
|
||||||
var origin = (run && run.autotask_ticket_resolved_origin) ? String(run.autotask_ticket_resolved_origin) : '';
|
var origin = (run && run.autotask_ticket_resolved_origin) ? String(run.autotask_ticket_resolved_origin) : ''; var isDeleted = !!(run && run.autotask_ticket_is_deleted);
|
||||||
var isDeleted = !!(run && run.autotask_ticket_is_deleted);
|
|
||||||
var deletedAt = (run && run.autotask_ticket_deleted_at) ? String(run.autotask_ticket_deleted_at) : '';
|
var deletedAt = (run && run.autotask_ticket_deleted_at) ? String(run.autotask_ticket_deleted_at) : '';
|
||||||
var deletedBy = (run && run.autotask_ticket_deleted_by_resource_id) ? String(run.autotask_ticket_deleted_by_resource_id) : '';
|
var deletedBy = (run && run.autotask_ticket_deleted_by_resource_id) ? String(run.autotask_ticket_deleted_by_resource_id) : '';
|
||||||
|
var deletedFn = (run && run.autotask_ticket_deleted_by_first_name) ? String(run.autotask_ticket_deleted_by_first_name) : '';
|
||||||
|
var deletedLn = (run && run.autotask_ticket_deleted_by_last_name) ? String(run.autotask_ticket_deleted_by_last_name) : '';
|
||||||
|
var deletedByName = (deletedFn || deletedLn) ? (String(deletedFn || '') + ' ' + String(deletedLn || '')).trim() : '';
|
||||||
|
|
||||||
if (num) {
|
if (num) {
|
||||||
var extra = '';
|
var extra = '';
|
||||||
if (isDeleted) {
|
if (isDeleted) {
|
||||||
var meta = '';
|
var meta = '';
|
||||||
if (deletedAt) meta += '<div class="text-muted">Deleted at: ' + escapeHtml(deletedAt) + '</div>';
|
if (deletedAt) meta += '<div class="text-muted">Deleted at: ' + escapeHtml(deletedAt) + '</div>';
|
||||||
if (deletedBy) meta += '<div class="text-muted">Deleted by resource ID: ' + escapeHtml(deletedBy) + '</div>';
|
if (deletedByName) meta += '<div class="text-muted">Deleted by: ' + escapeHtml(deletedByName) + '</div>';
|
||||||
|
else if (deletedBy) meta += '<div class="text-muted">Deleted by resource ID: ' + escapeHtml(deletedBy) + '</div>';
|
||||||
extra = '<div class="mt-1"><span class="badge bg-danger">Deleted in PSA</span></div>' + meta;
|
extra = '<div class="mt-1"><span class="badge bg-danger">Deleted in PSA</span></div>' + meta;
|
||||||
} else if (isResolved && origin === 'psa') {
|
} else if (isResolved && origin === 'psa') {
|
||||||
extra = '<div class="mt-1"><span class="badge bg-secondary">Resolved by PSA</span></div>';
|
extra = '<div class="mt-1"><span class="badge bg-secondary">Resolved by PSA</span></div>';
|
||||||
|
|||||||
@ -367,6 +367,21 @@ Changes:
|
|||||||
- Ensured resource lookup is executed only when a delete is detected to minimize API usage.
|
- Ensured resource lookup is executed only when a delete is detected to minimize API usage.
|
||||||
- No changes made to Job Details view; data is stored for future reporting use.
|
- No changes made to Job Details view; data is stored for future reporting use.
|
||||||
|
|
||||||
|
## v20260120-03-autotask-deletedby-name-runlink
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Extended deleted ticket audit handling by resolving DeletedByResourceID to resource details.
|
||||||
|
- Stored deleted-by audit information on job runs:
|
||||||
|
- autotask_ticket_deleted_by_first_name
|
||||||
|
- autotask_ticket_deleted_by_last_name
|
||||||
|
- Updated Run Checks UI to display:
|
||||||
|
- “Deleted by: <First name> <Last name>”
|
||||||
|
- Fallback to “Deleted by resource ID” when name data is unavailable.
|
||||||
|
- Ensured deletion date/time continues to be shown in Run Checks.
|
||||||
|
- Restored legacy ticket behavior by automatically linking new job runs to existing internal tickets (TicketJobRun).
|
||||||
|
- Ensured Autotask-linked tickets are inherited by new runs when an open ticket already exists for the job.
|
||||||
|
- No changes made to Job Details view; audit data is stored for future reporting.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.21
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user