diff --git a/MDfile/RELEASE_NOTES_v2.7.1.md b/MDfile/RELEASE_NOTES_v2.7.1.md new file mode 100644 index 0000000..82c06eb --- /dev/null +++ b/MDfile/RELEASE_NOTES_v2.7.1.md @@ -0,0 +1,20 @@ +# Release Notes: v2.7.1 + +**Dato:** 25. august 2026 + +## Overblik + +Version 2.7.1 retter linkning og varighed for ældre telefonopkald fra Mission Control. + +## Telefoni + +- Ældre opkald fra `mission_call_state` flyttes til den almindelige telefonilog, når en kontakt eller sag linkes. +- Alle linkede opkald bliver dermed synlige på sagen samt i Tidregistrering og Historik. +- Den stabile eksterne call-id og kildetype sendes med ved linkning. +- Forsinkede afslutningshændelser kan ikke længere skabe varigheder på flere måneder. +- Eksisterende urimelige varigheder over 12 timer skjules som ukendt varighed. + +## Verifikation + +- Telefoni- og sag-tests: **41 bestået**. +- Python-kompilering og diff-kontrol gennemført. diff --git a/VERSION b/VERSION index 24ba9a3..860487c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.0 +2.7.1 diff --git a/app/modules/sag/backend/router.py b/app/modules/sag/backend/router.py index b692c2a..6ad5ed7 100644 --- a/app/modules/sag/backend/router.py +++ b/app/modules/sag/backend/router.py @@ -3496,8 +3496,11 @@ def _get_case_linked_activities(case_ids: List[int]) -> List[Dict[str, Any]]: continue direction = str(row.get("direction") or "inbound").lower() duration_seconds = row.get("duration_sec") + if duration_seconds is not None and not 0 <= float(duration_seconds) <= 43200: + duration_seconds = None if duration_seconds is None and row.get("ended_at") and isinstance(started_at, datetime) and isinstance(row.get("ended_at"), datetime): - duration_seconds = max(0, int((row["ended_at"] - started_at).total_seconds())) + calculated_seconds = int((row["ended_at"] - started_at).total_seconds()) + duration_seconds = calculated_seconds if 0 <= calculated_seconds <= 43200 else None activities.append({ "id": f"call:{row.get('id')}", "source_id": row.get("id"), diff --git a/app/modules/sag/frontend/views.py b/app/modules/sag/frontend/views.py index fe26f51..bbc3c44 100644 --- a/app/modules/sag/frontend/views.py +++ b/app/modules/sag/frontend/views.py @@ -838,7 +838,13 @@ async def sag_detaljer(request: Request, sag_id: int): t.ekstern_nummer, t.started_at, t.ended_at, - t.duration_sec, + CASE + WHEN t.duration_sec BETWEEN 0 AND 43200 THEN t.duration_sec + WHEN t.duration_sec IS NULL AND t.started_at IS NOT NULL AND t.ended_at IS NOT NULL + AND t.ended_at - t.started_at BETWEEN INTERVAL '0 seconds' AND INTERVAL '12 hours' + THEN EXTRACT(EPOCH FROM (t.ended_at - t.started_at))::int + ELSE NULL + END AS duration_sec, u.username, u.full_name, CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS contact_name @@ -1193,7 +1199,13 @@ async def sag_detaljer_v3(request: Request, sag_id: int): t.ekstern_nummer, t.started_at, t.ended_at, - t.duration_sec, + CASE + WHEN t.duration_sec BETWEEN 0 AND 43200 THEN t.duration_sec + WHEN t.duration_sec IS NULL AND t.started_at IS NOT NULL AND t.ended_at IS NOT NULL + AND t.ended_at - t.started_at BETWEEN INTERVAL '0 seconds' AND INTERVAL '12 hours' + THEN EXTRACT(EPOCH FROM (t.ended_at - t.started_at))::int + ELSE NULL + END AS duration_sec, u.username, u.full_name, CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS contact_name diff --git a/app/modules/telefoni/backend/router.py b/app/modules/telefoni/backend/router.py index 7f84797..6906f0f 100644 --- a/app/modules/telefoni/backend/router.py +++ b/app/modules/telefoni/backend/router.py @@ -775,13 +775,13 @@ async def list_calls( t.sag_id, t.started_at, t.ended_at, - COALESCE( - t.duration_sec, - CASE - WHEN t.started_at IS NOT NULL AND t.ended_at IS NOT NULL THEN GREATEST(EXTRACT(EPOCH FROM (t.ended_at - t.started_at))::int, 0) - ELSE NULL - END - ) AS duration_sec, + CASE + WHEN t.duration_sec BETWEEN 0 AND 43200 THEN t.duration_sec + WHEN t.duration_sec IS NULL AND t.started_at IS NOT NULL AND t.ended_at IS NOT NULL + AND t.ended_at - t.started_at BETWEEN INTERVAL '0 seconds' AND INTERVAL '12 hours' + THEN EXTRACT(EPOCH FROM (t.ended_at - t.started_at))::int + ELSE NULL + END AS duration_sec, t.created_at, u.username, u.full_name, @@ -794,7 +794,8 @@ async def list_calls( ORDER BY cc.is_primary DESC NULLS LAST, cc.id ASC LIMIT 1 ) AS contact_company, - s.titel AS sag_titel + s.titel AS sag_titel, + 'telefoni'::VARCHAR AS source FROM telefoni_opkald t LEFT JOIN users u ON u.user_id = t.bruger_id LEFT JOIN contacts c ON c.id = t.kontakt_id @@ -903,6 +904,38 @@ async def list_calls( @router.patch("/telefoni/calls/{call_id}") async def update_call_links(call_id: int, data: TelefoniCallLinkUpdate): existing = execute_query_single("SELECT id FROM telefoni_opkald WHERE id = %s", (call_id,)) + if not existing and data.source == "legacy_mission" and data.callid: + promoted = execute_query( + """ + INSERT INTO telefoni_opkald + (callid, direction, ekstern_nummer, started_at, ended_at, duration_sec, raw_payload) + SELECT + m.call_id, + CASE WHEN LOWER(COALESCE(m.state, '')) IN ('outbound', 'udgaaende') THEN 'outbound' ELSE 'inbound' END, + m.caller_number, + m.started_at, + m.ended_at, + CASE + WHEN m.started_at IS NOT NULL AND m.ended_at IS NOT NULL + AND m.ended_at - m.started_at BETWEEN INTERVAL '0 seconds' AND INTERVAL '12 hours' + THEN EXTRACT(EPOCH FROM (m.ended_at - m.started_at))::int + ELSE NULL + END, + COALESCE(m.last_payload, '{}'::jsonb) + FROM mission_call_state m + WHERE m.call_id = %s + ON CONFLICT (callid) DO NOTHING + RETURNING id + """, + (data.callid.strip(),), + ) or [] + if promoted: + call_id = int(promoted[0]["id"]) + existing = {"id": call_id} + else: + existing = execute_query_single("SELECT id FROM telefoni_opkald WHERE callid = %s", (data.callid.strip(),)) + if existing: + call_id = int(existing["id"]) if not existing: raise HTTPException(status_code=404, detail="Call not found") diff --git a/app/modules/telefoni/backend/schemas.py b/app/modules/telefoni/backend/schemas.py index e93ff47..8ee4e56 100644 --- a/app/modules/telefoni/backend/schemas.py +++ b/app/modules/telefoni/backend/schemas.py @@ -5,6 +5,8 @@ from typing import Optional class TelefoniCallLinkUpdate(BaseModel): sag_id: Optional[int] = None kontakt_id: Optional[int] = None + callid: Optional[str] = None + source: Optional[str] = None class TelefoniUserMappingUpdate(BaseModel): diff --git a/app/modules/telefoni/backend/service.py b/app/modules/telefoni/backend/service.py index f93a130..71bff26 100644 --- a/app/modules/telefoni/backend/service.py +++ b/app/modules/telefoni/backend/service.py @@ -170,7 +170,9 @@ class TelefoniService: duration_sec = COALESCE( EXCLUDED.duration_sec, CASE - WHEN telefoni_opkald.started_at IS NOT NULL THEN GREATEST(EXTRACT(EPOCH FROM (NOW() - telefoni_opkald.started_at))::int, 0) + WHEN telefoni_opkald.started_at IS NOT NULL + AND NOW() - telefoni_opkald.started_at BETWEEN INTERVAL '0 seconds' AND INTERVAL '12 hours' + THEN EXTRACT(EPOCH FROM (NOW() - telefoni_opkald.started_at))::int ELSE NULL END ) diff --git a/app/modules/telefoni/templates/log.html b/app/modules/telefoni/templates/log.html index 6cc68d8..186c33c 100644 --- a/app/modules/telefoni/templates/log.html +++ b/app/modules/telefoni/templates/log.html @@ -63,6 +63,8 @@ {% for call in initial_calls %}