From f8a57efee0585da3f660d292b274313182b885fc Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Fri, 16 Jan 2026 16:24:35 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-16 16:24:35) --- .last-branch | 2 +- .../src/backend/app/ticketing_utils.py | 56 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/.last-branch b/.last-branch index 5f6e439..37b5b86 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260116-11-autotask-ticket-sync-legacy +v20260116-12-autotask-ticket-sync-circular-import-fix diff --git a/containers/backupchecks/src/backend/app/ticketing_utils.py b/containers/backupchecks/src/backend/app/ticketing_utils.py index 02b8aed..23557c6 100644 --- a/containers/backupchecks/src/backend/app/ticketing_utils.py +++ b/containers/backupchecks/src/backend/app/ticketing_utils.py @@ -1,13 +1,59 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, date, timezone from typing import Iterable, Optional +from zoneinfo import ZoneInfo + +from flask import current_app + from sqlalchemy import text from .database import db -from .models import Job, JobRun, Ticket, TicketJobRun, TicketScope -from .main.routes_shared import _get_ui_timezone_name, _to_amsterdam_date +from .models import Job, JobRun, SystemSettings, Ticket, TicketJobRun, TicketScope + + +def _get_ui_timezone_name() -> str: + """Return the configured UI timezone name (IANA), with a safe fallback. + + NOTE: This must not import from any routes_* modules to avoid circular imports. + """ + + try: + settings = SystemSettings.query.first() + name = (getattr(settings, "ui_timezone", None) or "").strip() + if name: + return name + except Exception: + pass + + try: + return (current_app.config.get("TIMEZONE") or "Europe/Amsterdam").strip() + except Exception: + return "Europe/Amsterdam" + + +def _to_ui_date(dt_utc_naive: datetime | None) -> date | None: + """Convert a naive UTC datetime to a UI-local date.""" + if not dt_utc_naive: + return None + + try: + tz = ZoneInfo(_get_ui_timezone_name()) + except Exception: + tz = None + + if not tz: + return dt_utc_naive.date() + + try: + if dt_utc_naive.tzinfo is None: + dt_utc = dt_utc_naive.replace(tzinfo=timezone.utc) + else: + dt_utc = dt_utc_naive.astimezone(timezone.utc) + return dt_utc.astimezone(tz).date() + except Exception: + return dt_utc_naive.date() def ensure_internal_ticket_for_job( @@ -38,7 +84,7 @@ def ensure_internal_ticket_for_job( ticket_code=code, title=title, description=description, - active_from_date=_to_amsterdam_date(active_from_dt) or _to_amsterdam_date(start_dt) or start_dt.date(), + active_from_date=_to_ui_date(active_from_dt) or _to_ui_date(start_dt) or start_dt.date(), start_date=start_dt, resolved_at=None, ) @@ -122,7 +168,7 @@ def link_open_internal_tickets_to_run(*, run: JobRun, job: Job) -> None: return ui_tz = _get_ui_timezone_name() - run_date = _to_amsterdam_date(getattr(run, "run_at", None)) or _to_amsterdam_date(datetime.utcnow()) + run_date = _to_ui_date(getattr(run, "run_at", None)) or _to_ui_date(datetime.utcnow()) # Find open tickets scoped to this job for the run date window. # This matches the logic used by Job Details and Run Checks indicators.