Auto-commit local changes before build (2026-01-15 14:36:50)

This commit is contained in:
Ivo Oskamp 2026-01-15 14:36:50 +01:00
parent 49f6d41715
commit 49fd29a6f2
4 changed files with 123 additions and 1 deletions

View File

@ -1 +1 @@
v20260115-11-autotask-companyname-unwrap
v20260115-12-autotask-customers-refreshall-mappings

View File

@ -259,6 +259,73 @@ def api_customer_autotask_mapping_refresh(customer_id: int):
return jsonify({"status": "error", "message": str(exc) or "Refresh failed."}), 400
@main_bp.post("/api/customers/autotask-mapping/refresh-all")
@login_required
@roles_required("admin", "operator")
def api_customers_autotask_mapping_refresh_all():
"""Refresh mapping status for all customers that have an Autotask company ID."""
from ..integrations.autotask.client import AutotaskError
customers = Customer.query.filter(Customer.autotask_company_id.isnot(None)).all()
if not customers:
return jsonify({"status": "ok", "refreshed": 0, "counts": {"ok": 0, "renamed": 0, "missing": 0, "invalid": 0}})
try:
client = _get_autotask_client_or_raise()
except Exception as exc:
return jsonify({"status": "error", "message": str(exc) or "Autotask is not configured."}), 400
counts = {"ok": 0, "renamed": 0, "missing": 0, "invalid": 0}
refreshed = 0
for c in customers:
company_id = getattr(c, "autotask_company_id", None)
if not company_id:
continue
try:
company = client.get_company(int(company_id))
name = _normalize_company_name(company)
prev = (getattr(c, "autotask_company_name", None) or "").strip()
if prev and name and prev != name:
c.autotask_company_name = name
c.autotask_mapping_status = "renamed"
counts["renamed"] += 1
else:
c.autotask_company_name = name
c.autotask_mapping_status = "ok"
counts["ok"] += 1
c.autotask_last_sync_at = datetime.utcnow()
refreshed += 1
except AutotaskError as exc:
try:
code = getattr(exc, "status_code", None)
except Exception:
code = None
if code == 404:
c.autotask_mapping_status = "invalid"
counts["invalid"] += 1
else:
c.autotask_mapping_status = "missing"
counts["missing"] += 1
c.autotask_last_sync_at = datetime.utcnow()
refreshed += 1
except Exception:
c.autotask_mapping_status = "missing"
c.autotask_last_sync_at = datetime.utcnow()
counts["missing"] += 1
refreshed += 1
try:
db.session.commit()
return jsonify({"status": "ok", "refreshed": refreshed, "counts": counts})
except Exception as exc:
db.session.rollback()
return jsonify({"status": "error", "message": str(exc) or "Failed to refresh all mappings."}), 400
@main_bp.route("/customers/create", methods=["POST"])
@login_required
@roles_required("admin", "operator")

View File

@ -19,6 +19,11 @@
</form>
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('main.customers_export') }}">Export CSV</a>
{% if autotask_enabled and autotask_configured %}
<button type="button" class="btn btn-outline-secondary btn-sm" id="autotaskRefreshAllMappingsBtn" style="white-space: nowrap;">Refresh all Autotask mappings</button>
<span class="small text-muted" id="autotaskRefreshAllMappingsMsg"></span>
{% endif %}
</div>
{% endif %}
@ -219,6 +224,10 @@
var nameInput = document.getElementById("edit_customer_name");
var activeInput = document.getElementById("edit_customer_active");
// Top-level refresh-all (only present when integration is enabled/configured)
var refreshAllBtn = document.getElementById("autotaskRefreshAllMappingsBtn");
var refreshAllMsg = document.getElementById("autotaskRefreshAllMappingsMsg");
// Autotask mapping UI (only present when integration is enabled/configured)
var atCurrent = document.getElementById("autotaskCurrentMapping");
var atCurrentMeta = document.getElementById("autotaskCurrentMappingMeta");
@ -233,6 +242,20 @@
var currentCustomerId = null;
var selectedCompanyId = null;
function setRefreshAllMsg(text, isError) {
if (!refreshAllMsg) {
return;
}
refreshAllMsg.textContent = text || "";
if (isError) {
refreshAllMsg.classList.remove("text-muted");
refreshAllMsg.classList.add("text-danger");
} else {
refreshAllMsg.classList.remove("text-danger");
refreshAllMsg.classList.add("text-muted");
}
}
function setMsg(text, isError) {
if (!atMsg) {
return;
@ -302,6 +325,32 @@
return data;
}
if (refreshAllBtn) {
refreshAllBtn.addEventListener("click", async function () {
if (!confirm("Refresh mapping status for all mapped customers?")) {
return;
}
refreshAllBtn.disabled = true;
setRefreshAllMsg("Refreshing...", false);
try {
var data = await postJson("/api/customers/autotask-mapping/refresh-all", {});
var counts = (data && data.counts) ? data.counts : null;
if (counts) {
setRefreshAllMsg(
"Done. OK: " + (counts.ok || 0) + ", Renamed: " + (counts.renamed || 0) + ", Missing: " + (counts.missing || 0) + ", Invalid: " + (counts.invalid || 0) + ".",
false
);
} else {
setRefreshAllMsg("Done.", false);
}
window.location.reload();
} catch (e) {
setRefreshAllMsg(e && e.message ? e.message : "Refresh failed.", true);
refreshAllBtn.disabled = false;
}
});
}
var editButtons = document.querySelectorAll(".customer-edit-btn");
editButtons.forEach(function (btn) {
btn.addEventListener("click", function () {

View File

@ -94,6 +94,12 @@ Changes:
- Improved company lookup handling to support different response shapes (single item and collection wrappers).
- Ensured the cached Autotask company name is stored and displayed consistently after mapping and refresh.
## v20260115-12-autotask-customers-refreshall-mappings
- Added a “Refresh all Autotask mappings” button on the Customers page to validate all mapped customers in one action.
- Implemented a new backend endpoint to refresh mapping status for all customers with an Autotask Company ID and return a status summary (ok/renamed/missing/invalid).
- Updated the Customers UI to call the refresh-all endpoint, show a short result summary, and reload to reflect updated mapping states.
***
## v0.1.21