diff --git a/VERSION b/VERSION index e8f6dcd..3db58c9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.18 +2.8.19 diff --git a/app/modules/telefoni/backend/router.py b/app/modules/telefoni/backend/router.py index ddbc19d..3ff53aa 100644 --- a/app/modules/telefoni/backend/router.py +++ b/app/modules/telefoni/backend/router.py @@ -270,6 +270,8 @@ async def yealink_established( local: Optional[str] = Query(None), active_user: Optional[str] = Query(None), called_number: Optional[str] = Query(None), + display_remote: Optional[str] = Query(None), + caller_id: Optional[str] = Query(None), token: Optional[str] = Query(None), ): """Yealink Action URL: Established""" @@ -306,6 +308,8 @@ async def yealink_established( caller_value = _sanitize(caller) or _sanitize(remote) callee_value = _sanitize(callee) called_number_value = _sanitize(called_number) + display_remote_value = _sanitize(display_remote) + caller_id_value = _sanitize(caller_id) local_extension = extract_extension(local_value) or local_value @@ -325,6 +329,8 @@ async def yealink_established( ] if direction == "outbound" else [ caller_value, _sanitize(remote), + display_remote_value, + caller_id_value, callee_value, called_number_value, ] @@ -335,18 +341,16 @@ async def yealink_established( ekstern_raw = candidate break - if not ekstern_raw: - for candidate in candidates: - if candidate: - ekstern_raw = candidate - break ekstern_normalized = normalize_external_number(ekstern_raw) ekstern_e164 = normalize_e164(ekstern_normalized or ekstern_raw) - ekstern_value = ekstern_normalized or ((ekstern_raw or "").strip() or None) + # A SIP URI such as sip:206@... is the forwarding extension, not the + # customer. Keep it in raw_payload for diagnostics, but never display or + # match it as an external caller number. + ekstern_value = ekstern_normalized or ekstern_e164 user_ids = TelefoniService.find_user_by_extension(local_extension) - kontakt = TelefoniService.find_contact_by_phone(ekstern_raw) + kontakt = TelefoniService.find_contact_by_phone(ekstern_value) kontakt_id = kontakt.get("id") if kontakt else None # Get extended contact details if we found a contact @@ -363,6 +367,8 @@ async def yealink_established( "local": local_value, "active_user": active_user, "called_number": called_number_value, + "display_remote": display_remote_value, + "caller_id": caller_id_value, "direction": direction, "client_ip": _get_client_ip(request), "user_agent": request.headers.get("user-agent"), diff --git a/app/settings/frontend/settings.html b/app/settings/frontend/settings.html index 8aa8e82..8e2e516 100644 --- a/app/settings/frontend/settings.html +++ b/app/settings/frontend/settings.html @@ -2643,7 +2643,7 @@ function buildYealinkActionUrls() { const base = baseRaw.replace(/\/$/, ''); const tokenPart = token ? `token=${encodeURIComponent(token)}&` : ''; - estEl.value = `${base}/api/v1/telefoni/established?${tokenPart}callid=$call_id&remote=$remote&local=$local&active_user=$active_user&called_number=$calledNumber`; + estEl.value = `${base}/api/v1/telefoni/established?${tokenPart}callid=$call_id&remote=$remote&display_remote=$display_remote&caller_id=$callerID&local=$local&active_user=$active_user&called_number=$calledNumber`; termEl.value = `${base}/api/v1/telefoni/terminated?${tokenPart}callid=$call_id&duration=$call_duration`; } diff --git a/tests/test_telefoni_call_logging.py b/tests/test_telefoni_call_logging.py index 7f77639..d31a08f 100644 --- a/tests/test_telefoni_call_logging.py +++ b/tests/test_telefoni_call_logging.py @@ -130,6 +130,71 @@ def test_reused_callids_only_conflict_while_the_call_is_active(): assert "ux_telefoni_opkald_active_callid" in migration +def test_forwarded_call_uses_yealink_displayed_caller_number(monkeypatch): + captured = {} + + monkeypatch.setattr(telefoni_router, "_validate_yealink_request", lambda *_: None) + monkeypatch.setattr(TelefoniService, "find_user_by_extension", staticmethod(lambda _extension: [])) + monkeypatch.setattr(TelefoniService, "find_contact_by_phone", staticmethod(lambda _number: None)) + monkeypatch.setattr( + TelefoniService, + "upsert_call", + staticmethod(lambda **kwargs: captured.update(kwargs) or {"id": 1}), + ) + monkeypatch.setattr(telefoni_router, "log_system_event", lambda **_kwargs: None) + + request = Request({"type": "http", "method": "GET", "path": "/", "headers": [], "client": ("127.0.0.1", 8001)}) + asyncio.run(telefoni_router.yealink_established( + request, + callid="116", + call_id=None, + caller=None, + callee=None, + remote="sip:206@pbx.sipserver.dk", + local="sip:210_99773@pbx.sipserver.dk", + active_user=None, + called_number=None, + display_remote="+45 49 13 27 73", + caller_id=None, + token=None, + )) + + assert captured["direction"] == "inbound" + assert captured["ekstern_nummer"] == "+4549132773" + + +def test_forwarded_call_does_not_store_forwarding_sip_uri(monkeypatch): + captured = {} + + monkeypatch.setattr(telefoni_router, "_validate_yealink_request", lambda *_: None) + monkeypatch.setattr(TelefoniService, "find_user_by_extension", staticmethod(lambda _extension: [])) + monkeypatch.setattr(TelefoniService, "find_contact_by_phone", staticmethod(lambda _number: None)) + monkeypatch.setattr( + TelefoniService, + "upsert_call", + staticmethod(lambda **kwargs: captured.update(kwargs) or {"id": 1}), + ) + monkeypatch.setattr(telefoni_router, "log_system_event", lambda **_kwargs: None) + + request = Request({"type": "http", "method": "GET", "path": "/", "headers": [], "client": ("127.0.0.1", 8001)}) + asyncio.run(telefoni_router.yealink_established( + request, + callid="117", + call_id=None, + caller=None, + callee=None, + remote="sip:206@pbx.sipserver.dk", + local="sip:210_99773@pbx.sipserver.dk", + active_user=None, + called_number=None, + display_remote=None, + caller_id=None, + token=None, + )) + + assert captured["ekstern_nummer"] is None + + def test_callid_recovery_uses_system_log_without_overwriting_existing_current_calls(): migration = Path("migrations/1047_telefoni_reused_callids.sql").read_text(encoding="utf-8")