fix(telefoni): show legacy call history when telefoni_opkald is empty

This commit is contained in:
Christian 2026-05-16 13:23:56 +02:00
parent 6a68aecafa
commit c5478b7e29
3 changed files with 95 additions and 14 deletions

10
RELEASE_NOTES_v2.3.7.md Normal file
View File

@ -0,0 +1,10 @@
# Release Notes: v2.3.7
**Date:** 16. maj 2026
## Changes
- fallback to mission_call_state when telefoni_opkald is empty
- legacy rows shown read-only in telefoni UI
## Files changed
- app/modules/telefoni/backend/router.py
- app/modules/telefoni/templates/log.html

View File

@ -735,8 +735,69 @@ async def list_calls(
"""
params.extend([limit, offset])
rows = execute_query(query, tuple(params))
return rows or []
rows = execute_query(query, tuple(params)) or []
if rows:
return rows
# Fallback: legacy mission call history (read-only rows) for environments
# where historical calls were stored before telefoni_opkald was populated.
if user_id is not None:
return []
legacy_where = []
legacy_params = []
if date_from:
legacy_where.append("m.started_at >= %s")
legacy_params.append(date_from)
if date_to:
legacy_where.append("m.started_at <= %s")
legacy_params.append(date_to)
legacy_where_sql = ("WHERE " + " AND ".join(legacy_where)) if legacy_where else ""
legacy_query = f"""
SELECT
-ROW_NUMBER() OVER (ORDER BY m.started_at DESC, m.call_id) AS id,
m.call_id AS callid,
NULL::INTEGER AS bruger_id,
CASE
WHEN LOWER(COALESCE(m.state, '')) IN ('outbound', 'udgaaende') THEN 'outbound'
ELSE 'inbound'
END AS direction,
m.caller_number AS ekstern_nummer,
m.caller_number AS display_number,
NULL::VARCHAR AS intern_extension,
NULL::INTEGER AS kontakt_id,
NULL::INTEGER AS sag_id,
m.started_at,
m.ended_at,
CASE
WHEN m.started_at IS NOT NULL AND m.ended_at IS NOT NULL
THEN GREATEST(EXTRACT(EPOCH FROM (m.ended_at - m.started_at))::int, 0)
ELSE NULL
END AS duration_sec,
m.updated_at AS created_at,
NULL::VARCHAR AS username,
NULL::VARCHAR AS full_name,
COALESCE(NULLIF(TRIM(m.contact_name), ''), NULL) AS contact_name,
COALESCE(NULLIF(TRIM(m.company_name), ''), NULL) AS contact_company,
NULL::VARCHAR AS sag_titel,
'legacy_mission'::VARCHAR AS source
FROM mission_call_state m
{legacy_where_sql}
ORDER BY m.started_at DESC, m.call_id
LIMIT %s OFFSET %s
"""
legacy_params.extend([limit, offset])
try:
legacy_rows = execute_query(legacy_query, tuple(legacy_params)) or []
except Exception:
legacy_rows = []
if without_case:
return [r for r in legacy_rows if not r.get("sag_id")]
return legacy_rows
@router.patch("/telefoni/calls/{call_id}")

View File

@ -851,6 +851,8 @@ async function loadCalls() {
}
tbody.innerHTML = rows.map(r => {
const callId = Number(r.id);
const canMutateCall = Number.isInteger(callId) && callId > 0;
const started = r.started_at ? new Date(r.started_at) : null;
const dateTxt = started ? started.toLocaleString('da-DK') : '-';
const userTxt = escapeHtml(r.full_name || r.username || '-');
@ -866,30 +868,38 @@ async function loadCalls() {
const contactHtml = r.kontakt_id
? `<div class="d-flex align-items-center gap-2 flex-wrap">
<a href="/contacts/${r.kontakt_id}">${escapeHtml(r.contact_name || ('Kontakt #' + r.kontakt_id))}</a>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="openLinkContactModal(${Number(r.id)})">Skift</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="unlinkContact(${Number(r.id)})">Fjern</button>
${canMutateCall
? `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="openLinkContactModal(${callId})">Skift</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="unlinkContact(${callId})">Fjern</button>`
: '<span class="badge bg-light text-muted border">Historik</span>'}
</div>
${r.contact_company ? `<div class="text-muted small">${escapeHtml(r.contact_company)}</div>` : ''}`
: `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="openLinkContactModal(${Number(r.id)})" title="Vælg kontakt/firma">
: (canMutateCall
? `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="openLinkContactModal(${callId})" title="Vælg kontakt/firma">
<i class="bi bi-three-dots"></i>
</button>`;
</button>`
: '<span class="text-muted small">Historisk opkald</span>');
const numberForTitle = (r.display_number || r.ekstern_nummer || '').trim();
const createQs = new URLSearchParams();
if (r.kontakt_id) createQs.set('contact_id', String(r.kontakt_id));
createQs.set('telefoni_opkald_id', String(r.id));
if (canMutateCall) createQs.set('telefoni_opkald_id', String(callId));
createQs.set('title', `Telefonsamtale ${numberForTitle || 'ukendt nummer'}`);
const sagHtml = r.sag_id
? `<div class="d-flex gap-2 align-items-center flex-wrap">
<a href="/sag/${r.sag_id}/v3">${escapeHtml(r.sag_titel || ('Sag #' + r.sag_id))}</a>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="linkExistingCase(${Number(r.id)})">Skift link</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="unlinkCase(${Number(r.id)})">Fjern link</button>
${canMutateCall
? `<button type="button" class="btn btn-sm btn-outline-secondary" onclick="linkExistingCase(${callId})">Skift link</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="unlinkCase(${callId})">Fjern link</button>`
: '<span class="badge bg-light text-muted border">Historik</span>'}
</div>`
: `<div class="d-flex gap-2">
: (canMutateCall
? `<div class="d-flex gap-2">
<a class="btn btn-sm btn-outline-primary" href="/sag/new?${createQs.toString()}">Opret sag</a>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="linkExistingCase(${Number(r.id)})">Link sag</button>
</div>`;
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="linkExistingCase(${callId})">Link sag</button>
</div>`
: '<span class="text-muted small">Ingen sag</span>');
return `
<tr>