Fix: Show resolved tickets only on runs where they were linked

Previous fix removed ALL resolved tickets from display, breaking
audit trail. Users need to see which tickets were associated with
historical runs, even after resolution.

Solution: Two-source ticket display
1. Direct links (ticket_job_runs): Always show, even if resolved
   - Preserves audit trail
   - Shows tickets that were explicitly linked to runs
2. Active window (ticket_scopes): Only show unresolved
   - Prevents resolved tickets from appearing on NEW runs
   - Uses active_from_date without date-based resolved logic

Changes:
- Added direct_ticket_links map to fetch linked tickets per run
- Query ticket_job_runs for audit trail tickets
- Modified ticket_codes building to use both sources
- Removed date-based resolved_date comparison (resd >= rd)

Result:
- Run 1 with ticket → ticket resolved → ticket still visible on Run 1
- Run 2 created → ticket NOT shown on Run 2 (correctly filtered)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Ivo Oskamp 2026-02-10 10:53:45 +01:00
parent a9cae0f8f5
commit 43502ae6f3
2 changed files with 59 additions and 5 deletions

View File

@ -168,23 +168,61 @@ def job_detail(job_id: int):
.all() .all()
) )
# Tickets: mark runs that fall within the ticket active window # Tickets: mark runs that fall within the ticket active window OR have direct links
ticket_rows = [] ticket_rows = []
ticket_open_count = 0 ticket_open_count = 0
ticket_total_count = 0 ticket_total_count = 0
# Map of run_id -> list of directly linked ticket codes (for audit trail)
direct_ticket_links = {}
remark_rows = [] remark_rows = []
remark_open_count = 0 remark_open_count = 0
remark_total_count = 0 remark_total_count = 0
run_dates = [] run_dates = []
run_date_map = {} run_date_map = {}
run_ids = []
for r in runs: for r in runs:
rd = _to_amsterdam_date(r.run_at) or _to_amsterdam_date(datetime.utcnow()) rd = _to_amsterdam_date(r.run_at) or _to_amsterdam_date(datetime.utcnow())
run_date_map[r.id] = rd run_date_map[r.id] = rd
run_ids.append(r.id)
if rd: if rd:
run_dates.append(rd) run_dates.append(rd)
# Get directly linked tickets for these runs (audit trail - show even if resolved)
if run_ids:
try:
rows = (
db.session.execute(
text(
"""
SELECT tjr.job_run_id, t.ticket_code, t.resolved_at
FROM ticket_job_runs tjr
JOIN tickets t ON t.id = tjr.ticket_id
WHERE tjr.job_run_id = ANY(:run_ids)
"""
),
{"run_ids": run_ids},
)
.mappings()
.all()
)
for rr in rows:
run_id = rr.get("job_run_id")
code = (rr.get("ticket_code") or "").strip()
resolved_at = rr.get("resolved_at")
if run_id not in direct_ticket_links:
direct_ticket_links[run_id] = []
direct_ticket_links[run_id].append({
"ticket_code": code,
"resolved_at": resolved_at,
"is_direct_link": True
})
except Exception:
pass
# Get active (unresolved) tickets for future runs
if run_dates: if run_dates:
min_date = min(run_dates) min_date = min(run_dates)
max_date = max(run_dates) max_date = max(run_dates)
@ -210,7 +248,12 @@ def job_detail(job_id: int):
active_from = rr.get("active_from_date") active_from = rr.get("active_from_date")
resolved_at = rr.get("resolved_at") resolved_at = rr.get("resolved_at")
resolved_date = _to_amsterdam_date(resolved_at) if resolved_at else None resolved_date = _to_amsterdam_date(resolved_at) if resolved_at else None
ticket_rows.append({"active_from_date": active_from, "resolved_date": resolved_date, "ticket_code": rr.get("ticket_code")}) ticket_rows.append({
"active_from_date": active_from,
"resolved_date": resolved_date,
"ticket_code": rr.get("ticket_code"),
"is_direct_link": False
})
except Exception: except Exception:
ticket_rows = [] ticket_rows = []
@ -333,11 +376,22 @@ def job_detail(job_id: int):
ticket_codes = [] ticket_codes = []
remark_items = [] remark_items = []
# First: add directly linked tickets (audit trail - always show)
if r.id in direct_ticket_links:
for tlink in direct_ticket_links[r.id]:
code = tlink.get("ticket_code", "")
if code and code not in ticket_codes:
ticket_codes.append(code)
has_ticket = True
# Second: add active window tickets (only unresolved)
if rd and ticket_rows: if rd and ticket_rows:
for tr in ticket_rows: for tr in ticket_rows:
if tr.get("is_direct_link"):
continue # Skip, already added above
af = tr.get("active_from_date") af = tr.get("active_from_date")
resd = tr.get("resolved_date") # Only check active_from, resolved tickets already filtered by query
if af and af <= rd and (resd is None or resd >= rd): if af and af <= rd:
has_ticket = True has_ticket = True
code = (tr.get("ticket_code") or "").strip() code = (tr.get("ticket_code") or "").strip()
if code and code not in ticket_codes: if code and code not in ticket_codes:

View File

@ -7,7 +7,7 @@ This file documents all changes made to this project via Claude Code.
### Fixed ### Fixed
- Fixed Autotask ticket not being automatically linked to new runs when internal ticket is resolved by implementing independent Autotask propagation strategy (now checks for most recent non-deleted and non-resolved Autotask ticket on job regardless of internal ticket status, ensuring PSA ticket reference persists across runs until explicitly resolved or deleted) - Fixed Autotask ticket not being automatically linked to new runs when internal ticket is resolved by implementing independent Autotask propagation strategy (now checks for most recent non-deleted and non-resolved Autotask ticket on job regardless of internal ticket status, ensuring PSA ticket reference persists across runs until explicitly resolved or deleted)
- Fixed internal and Autotask tickets being linked to new runs even after being resolved by removing date-based "open" logic from ticket query (tickets now only link to new runs if they are genuinely unresolved, not based on run date comparisons) - Fixed internal and Autotask tickets being linked to new runs even after being resolved by removing date-based "open" logic from ticket query (tickets now only link to new runs if they are genuinely unresolved, not based on run date comparisons)
- Fixed Job Details page showing resolved tickets in the Tickets column by removing date-based logic from display query (tickets and remarks now only show if genuinely unresolved, matching the linking behavior) - Fixed Job Details page showing resolved tickets for ALL runs by implementing two-source ticket display: directly linked tickets (via ticket_job_runs) are always shown for audit trail, while active window tickets (via scope query) are only shown if unresolved, preserving historical ticket links while preventing resolved tickets from appearing on new runs
### Changed ### Changed
- Added debug logging to ticket linking function to troubleshoot resolved ticket propagation issues (writes to AuditLog table with event_type "ticket_link_debug", visible on Logging page, logs EVERY run import to show whether tickets were found and their resolved_at status, uses commit instead of flush to ensure persistence) - Added debug logging to ticket linking function to troubleshoot resolved ticket propagation issues (writes to AuditLog table with event_type "ticket_link_debug", visible on Logging page, logs EVERY run import to show whether tickets were found and their resolved_at status, uses commit instead of flush to ensure persistence)