Auto-commit local changes before build (2026-01-19 14:23:56)
This commit is contained in:
parent
890553f23e
commit
0500491621
@ -1 +1 @@
|
|||||||
v20260119-10-runchecks-renderRun-alias
|
v20260119-11-restoredto--v20260119-06-runchecks-renderRun-fix
|
||||||
|
|||||||
@ -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():
|
||||||
@ -696,15 +697,11 @@ def run_checks_page():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
settings = _get_or_create_settings()
|
|
||||||
autotask_enabled = bool(getattr(settings, "autotask_enabled", False))
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"main/run_checks.html",
|
"main/run_checks.html",
|
||||||
rows=payload,
|
rows=payload,
|
||||||
is_admin=(get_active_role() == "admin"),
|
is_admin=(get_active_role() == "admin"),
|
||||||
include_reviewed=include_reviewed,
|
include_reviewed=include_reviewed,
|
||||||
autotask_enabled=autotask_enabled,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -898,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:
|
||||||
@ -1006,14 +994,23 @@ 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()
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"status": "error", "message": f"Autotask client setup failed: {exc}"}), 400
|
||||||
|
|
||||||
|
ticket_id = getattr(run, "autotask_ticket_id", None)
|
||||||
|
ticket_number = getattr(run, "autotask_ticket_number", None)
|
||||||
|
|
||||||
|
# Create ticket only when missing.
|
||||||
|
if not ticket_id:
|
||||||
|
try:
|
||||||
created = client.create_ticket(payload)
|
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 ticket creation failed: {exc}"}), 400
|
||||||
|
|
||||||
ticket_id = created.get("id") if isinstance(created, dict) else None
|
ticket_id = created.get("id") if isinstance(created, dict) else None
|
||||||
ticket_number = None
|
|
||||||
if isinstance(created, dict):
|
if isinstance(created, dict):
|
||||||
ticket_number = created.get("ticketNumber") or created.get("number") or created.get("ticket_number")
|
ticket_number = created.get("ticketNumber") or created.get("number") or created.get("ticket_number")
|
||||||
|
|
||||||
@ -1036,12 +1033,86 @@ def api_run_checks_create_autotask_ticket():
|
|||||||
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 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:
|
||||||
|
run = JobRun.query.get(run_id)
|
||||||
|
if not run:
|
||||||
|
return jsonify({"status": "error", "message": "Run not found."}), 404
|
||||||
|
|
||||||
|
ticket_id_int = int(getattr(run, "autotask_ticket_id", None) or 0)
|
||||||
|
ticket_number_str = (getattr(run, "autotask_ticket_number", None) or "").strip()
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -435,10 +435,8 @@ def settings():
|
|||||||
settings.ui_timezone = (request.form.get("ui_timezone") or "").strip() or "Europe/Amsterdam"
|
settings.ui_timezone = (request.form.get("ui_timezone") or "").strip() or "Europe/Amsterdam"
|
||||||
|
|
||||||
# Autotask integration
|
# Autotask integration
|
||||||
# Checkbox inputs are omitted from request.form when unchecked.
|
if "autotask_enabled" in request.form:
|
||||||
# Only apply the enabled toggle when the Autotask form was submitted.
|
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
|
||||||
if autotask_form_touched:
|
|
||||||
settings.autotask_enabled = "autotask_enabled" in request.form
|
|
||||||
|
|
||||||
if "autotask_environment" in request.form:
|
if "autotask_environment" in request.form:
|
||||||
env_val = (request.form.get("autotask_environment") or "").strip().lower()
|
env_val = (request.form.get("autotask_environment") or "").strip().lower()
|
||||||
|
|||||||
@ -216,21 +216,12 @@
|
|||||||
<div class="row g-2 align-items-start">
|
<div class="row g-2 align-items-start">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="border rounded p-2">
|
<div class="border rounded p-2">
|
||||||
{% if autotask_enabled %}
|
|
||||||
<div class="d-flex align-items-center justify-content-between">
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
<div class="fw-semibold">Autotask ticket</div>
|
<div class="fw-semibold">Autotask ticket</div>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" id="rcm_autotask_create">Create</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" id="rcm_autotask_create">Create</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 small" id="rcm_autotask_info"></div>
|
<div class="mt-2 small" id="rcm_autotask_info"></div>
|
||||||
<div class="mt-2 small text-muted" id="rcm_autotask_status"></div>
|
<div class="mt-2 small text-muted" id="rcm_autotask_status"></div>
|
||||||
{% else %}
|
|
||||||
<div class="fw-semibold">New ticket</div>
|
|
||||||
<div class="d-flex gap-2 mt-1">
|
|
||||||
<input class="form-control form-control-sm" id="rcm_ticket_code" type="text" placeholder="Ticket number (e.g., T20260106.0001)" />
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" id="rcm_ticket_save">Add</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 small text-muted" id="rcm_ticket_status"></div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
@ -306,8 +297,6 @@
|
|||||||
var currentRunId = null;
|
var currentRunId = null;
|
||||||
var currentPayload = null;
|
var currentPayload = null;
|
||||||
|
|
||||||
var autotaskEnabled = {{ 'true' if autotask_enabled else 'false' }};
|
|
||||||
|
|
||||||
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
||||||
var btnMarkSuccessOverride = document.getElementById('rcm_mark_success_override');
|
var btnMarkSuccessOverride = document.getElementById('rcm_mark_success_override');
|
||||||
|
|
||||||
@ -854,24 +843,17 @@ table.addEventListener('change', function (e) {
|
|||||||
var atInfo = document.getElementById('rcm_autotask_info');
|
var atInfo = document.getElementById('rcm_autotask_info');
|
||||||
var atStatus = document.getElementById('rcm_autotask_status');
|
var atStatus = document.getElementById('rcm_autotask_status');
|
||||||
|
|
||||||
var btnTicket = document.getElementById('rcm_ticket_save');
|
|
||||||
var tCode = document.getElementById('rcm_ticket_code');
|
|
||||||
var tStatus = document.getElementById('rcm_ticket_status');
|
|
||||||
|
|
||||||
var btnRemark = document.getElementById('rcm_remark_save');
|
var btnRemark = document.getElementById('rcm_remark_save');
|
||||||
var rBody = document.getElementById('rcm_remark_body');
|
var rBody = document.getElementById('rcm_remark_body');
|
||||||
var rStatus = document.getElementById('rcm_remark_status');
|
var rStatus = document.getElementById('rcm_remark_status');
|
||||||
|
|
||||||
function clearStatus() {
|
function clearStatus() {
|
||||||
if (atStatus) atStatus.textContent = '';
|
if (atStatus) atStatus.textContent = '';
|
||||||
if (tStatus) tStatus.textContent = '';
|
|
||||||
if (rStatus) rStatus.textContent = '';
|
if (rStatus) rStatus.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDisabled(disabled) {
|
function setDisabled(disabled) {
|
||||||
if (btnAutotask) btnAutotask.disabled = disabled;
|
if (btnAutotask) btnAutotask.disabled = disabled;
|
||||||
if (btnTicket) btnTicket.disabled = disabled;
|
|
||||||
if (tCode) tCode.disabled = disabled;
|
|
||||||
if (btnRemark) btnRemark.disabled = disabled;
|
if (btnRemark) btnRemark.disabled = disabled;
|
||||||
if (rBody) rBody.disabled = disabled;
|
if (rBody) rBody.disabled = disabled;
|
||||||
}
|
}
|
||||||
@ -892,42 +874,6 @@ table.addEventListener('change', function (e) {
|
|||||||
}
|
}
|
||||||
window.__rcmRenderAutotaskInfo = renderAutotaskInfo;
|
window.__rcmRenderAutotaskInfo = renderAutotaskInfo;
|
||||||
|
|
||||||
function isValidTicketCode(code) {
|
|
||||||
return /^T\d{8}\.\d{4}$/.test(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btnTicket) {
|
|
||||||
btnTicket.addEventListener('click', function () {
|
|
||||||
if (!currentRunId) { alert('Select a run first.'); return; }
|
|
||||||
clearStatus();
|
|
||||||
var ticket_code = tCode ? (tCode.value || '').trim().toUpperCase() : '';
|
|
||||||
if (!ticket_code) {
|
|
||||||
if (tStatus) tStatus.textContent = 'Ticket number is required.';
|
|
||||||
else alert('Ticket number is required.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isValidTicketCode(ticket_code)) {
|
|
||||||
if (tStatus) tStatus.textContent = 'Invalid ticket number format. Expected TYYYYMMDD.####.';
|
|
||||||
else alert('Invalid ticket number format. Expected TYYYYMMDD.####.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tStatus) tStatus.textContent = 'Saving...';
|
|
||||||
apiJson('/api/tickets', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({job_run_id: currentRunId, ticket_code: ticket_code})
|
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
if (tCode) tCode.value = '';
|
|
||||||
if (tStatus) tStatus.textContent = '';
|
|
||||||
loadAlerts(currentRunId);
|
|
||||||
})
|
|
||||||
.catch(function (e) {
|
|
||||||
if (tStatus) tStatus.textContent = e.message || 'Failed.';
|
|
||||||
else alert(e.message || 'Failed.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btnAutotask) {
|
if (btnAutotask) {
|
||||||
btnAutotask.addEventListener('click', function () {
|
btnAutotask.addEventListener('click', function () {
|
||||||
if (!currentRunId) { alert('Select a run first.'); return; }
|
if (!currentRunId) { alert('Select a run first.'); return; }
|
||||||
@ -1031,13 +977,7 @@ table.addEventListener('change', function (e) {
|
|||||||
currentRunId = run.id || null;
|
currentRunId = run.id || null;
|
||||||
if (window.__rcmClearCreateStatus) window.__rcmClearCreateStatus();
|
if (window.__rcmClearCreateStatus) window.__rcmClearCreateStatus();
|
||||||
if (window.__rcmRenderAutotaskInfo) window.__rcmRenderAutotaskInfo(run);
|
if (window.__rcmRenderAutotaskInfo) window.__rcmRenderAutotaskInfo(run);
|
||||||
if (window.__rcmSetCreateDisabled) {
|
if (window.__rcmSetCreateDisabled) window.__rcmSetCreateDisabled(!currentRunId || !!run.autotask_ticket_id);
|
||||||
if (autotaskEnabled) {
|
|
||||||
window.__rcmSetCreateDisabled(!currentRunId || !!run.autotask_ticket_id);
|
|
||||||
} else {
|
|
||||||
window.__rcmSetCreateDisabled(!currentRunId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (btnMarkSuccessOverride) {
|
if (btnMarkSuccessOverride) {
|
||||||
var _rs = (run.status || '').toString().toLowerCase();
|
var _rs = (run.status || '').toString().toLowerCase();
|
||||||
var _canOverride = !!currentRunId && !run.missed && (_rs.indexOf('override') === -1) && (_rs.indexOf('success') === -1);
|
var _canOverride = !!currentRunId && !run.missed && (_rs.indexOf('override') === -1) && (_rs.indexOf('success') === -1);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user