Auto-commit local changes before build (2026-01-19 14:23:56)

This commit is contained in:
Ivo Oskamp 2026-01-19 14:23:56 +01:00
parent 890553f23e
commit 0500491621
4 changed files with 108 additions and 99 deletions

View File

@ -1 +1 @@
v20260119-10-runchecks-renderRun-alias
v20260119-11-restoredto--v20260119-06-runchecks-renderRun-fix

View File

@ -35,6 +35,7 @@ from ..models import (
Override,
User,
)
from ..ticketing_utils import ensure_internal_ticket_for_job, ensure_ticket_jobrun_links
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(
"main/run_checks.html",
rows=payload,
is_admin=(get_active_role() == "admin"),
include_reviewed=include_reviewed,
autotask_enabled=autotask_enabled,
)
@ -898,16 +895,7 @@ def api_run_checks_create_autotask_ticket():
if not run:
return jsonify({"status": "error", "message": "Run not found."}), 404
# Idempotent: if already created, return existing linkage.
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,
}
)
already_exists = bool(getattr(run, "autotask_ticket_id", None))
job = Job.query.get(run.job_id)
if not job:
@ -1006,14 +994,23 @@ def api_run_checks_create_autotask_ticket():
if priority_id:
payload["priority"] = int(priority_id)
client = None
try:
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)
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
ticket_number = None
if isinstance(created, dict):
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()
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(
{
"status": "ok",
"ticket_id": int(run.autotask_ticket_id) if run.autotask_ticket_id else None,
"ticket_number": run.autotask_ticket_number or "",
"already_exists": False,
"ticket_id": int(getattr(run, "autotask_ticket_id", None) or 0) or None,
"ticket_number": getattr(run, "autotask_ticket_number", None) or "",
"already_exists": already_exists,
}
)

View File

@ -435,10 +435,8 @@ def settings():
settings.ui_timezone = (request.form.get("ui_timezone") or "").strip() or "Europe/Amsterdam"
# Autotask integration
# Checkbox inputs are omitted from request.form when unchecked.
# Only apply the enabled toggle when the Autotask form was submitted.
if autotask_form_touched:
settings.autotask_enabled = "autotask_enabled" in request.form
if "autotask_enabled" in request.form:
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
if "autotask_environment" in request.form:
env_val = (request.form.get("autotask_environment") or "").strip().lower()

View File

@ -216,21 +216,12 @@
<div class="row g-2 align-items-start">
<div class="col-12 col-lg-6">
<div class="border rounded p-2">
{% if autotask_enabled %}
<div class="d-flex align-items-center justify-content-between">
<div class="fw-semibold">Autotask ticket</div>
<button type="button" class="btn btn-sm btn-outline-primary" id="rcm_autotask_create">Create</button>
</div>
<div class="mt-2 small" id="rcm_autotask_info"></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 class="col-12 col-lg-6">
@ -306,8 +297,6 @@
var currentRunId = null;
var currentPayload = null;
var autotaskEnabled = {{ 'true' if autotask_enabled else 'false' }};
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
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 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 rBody = document.getElementById('rcm_remark_body');
var rStatus = document.getElementById('rcm_remark_status');
function clearStatus() {
if (atStatus) atStatus.textContent = '';
if (tStatus) tStatus.textContent = '';
if (rStatus) rStatus.textContent = '';
}
function setDisabled(disabled) {
if (btnAutotask) btnAutotask.disabled = disabled;
if (btnTicket) btnTicket.disabled = disabled;
if (tCode) tCode.disabled = disabled;
if (btnRemark) btnRemark.disabled = disabled;
if (rBody) rBody.disabled = disabled;
}
@ -892,42 +874,6 @@ table.addEventListener('change', function (e) {
}
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) {
btnAutotask.addEventListener('click', function () {
if (!currentRunId) { alert('Select a run first.'); return; }
@ -1031,13 +977,7 @@ table.addEventListener('change', function (e) {
currentRunId = run.id || null;
if (window.__rcmClearCreateStatus) window.__rcmClearCreateStatus();
if (window.__rcmRenderAutotaskInfo) window.__rcmRenderAutotaskInfo(run);
if (window.__rcmSetCreateDisabled) {
if (autotaskEnabled) {
window.__rcmSetCreateDisabled(!currentRunId || !!run.autotask_ticket_id);
} else {
window.__rcmSetCreateDisabled(!currentRunId);
}
}
if (window.__rcmSetCreateDisabled) window.__rcmSetCreateDisabled(!currentRunId || !!run.autotask_ticket_id);
if (btnMarkSuccessOverride) {
var _rs = (run.status || '').toString().toLowerCase();
var _canOverride = !!currentRunId && !run.missed && (_rs.indexOf('override') === -1) && (_rs.indexOf('success') === -1);