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,
|
||||
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,42 +994,125 @@ def api_run_checks_create_autotask_ticket():
|
||||
if priority_id:
|
||||
payload["priority"] = int(priority_id)
|
||||
|
||||
client = None
|
||||
try:
|
||||
client = _build_autotask_client_from_settings()
|
||||
created = client.create_ticket(payload)
|
||||
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_number = None
|
||||
if isinstance(created, dict):
|
||||
ticket_number = created.get("ticketNumber") or created.get("number") or created.get("ticket_number")
|
||||
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:
|
||||
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:
|
||||
run.autotask_ticket_id = int(ticket_id)
|
||||
except Exception:
|
||||
run.autotask_ticket_id = None
|
||||
run = JobRun.query.get(run_id)
|
||||
if not run:
|
||||
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
|
||||
run.autotask_ticket_created_at = datetime.utcnow()
|
||||
run.autotask_ticket_created_by_user_id = current_user.id
|
||||
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")
|
||||
|
||||
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
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user