2026-02-14 02:26:29 +01:00
|
|
|
import logging
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Any, Optional
|
|
|
|
|
|
|
|
|
|
from app.core.database import execute_query, execute_query_single
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TelefoniService:
|
|
|
|
|
@staticmethod
|
|
|
|
|
def find_user_by_extension(extension: Optional[str]) -> Optional[int]:
|
|
|
|
|
if not extension:
|
|
|
|
|
return None
|
|
|
|
|
row = execute_query_single(
|
|
|
|
|
"SELECT user_id FROM users WHERE telefoni_aktiv = TRUE AND telefoni_extension = %s LIMIT 1",
|
|
|
|
|
(extension,),
|
|
|
|
|
)
|
|
|
|
|
return int(row["user_id"]) if row and row.get("user_id") is not None else None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def find_contact_by_phone_suffix(suffix8: Optional[str]) -> Optional[dict]:
|
|
|
|
|
if not suffix8:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
query = """
|
|
|
|
|
SELECT
|
|
|
|
|
c.id,
|
|
|
|
|
c.first_name,
|
|
|
|
|
c.last_name,
|
|
|
|
|
(
|
|
|
|
|
SELECT cu.name
|
|
|
|
|
FROM contact_companies cc
|
|
|
|
|
JOIN customers cu ON cu.id = cc.customer_id
|
|
|
|
|
WHERE cc.contact_id = c.id
|
|
|
|
|
ORDER BY cc.is_primary DESC NULLS LAST, cc.id ASC
|
|
|
|
|
LIMIT 1
|
|
|
|
|
) AS company
|
|
|
|
|
FROM contacts c
|
|
|
|
|
WHERE RIGHT(regexp_replace(COALESCE(c.phone, ''), '\\D', '', 'g'), 8) = %s
|
|
|
|
|
OR RIGHT(regexp_replace(COALESCE(c.mobile, ''), '\\D', '', 'g'), 8) = %s
|
|
|
|
|
ORDER BY c.id ASC
|
|
|
|
|
LIMIT 1
|
|
|
|
|
"""
|
|
|
|
|
row = execute_query_single(query, (suffix8, suffix8))
|
|
|
|
|
if not row:
|
|
|
|
|
return None
|
|
|
|
|
return {
|
|
|
|
|
"id": row["id"],
|
|
|
|
|
"name": f"{(row.get('first_name') or '').strip()} {(row.get('last_name') or '').strip()}".strip(),
|
|
|
|
|
"company": row.get("company"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def upsert_call(
|
|
|
|
|
*,
|
|
|
|
|
callid: str,
|
|
|
|
|
user_id: Optional[int],
|
|
|
|
|
direction: str,
|
|
|
|
|
ekstern_nummer: Optional[str],
|
|
|
|
|
intern_extension: Optional[str],
|
|
|
|
|
kontakt_id: Optional[int],
|
|
|
|
|
raw_payload: Any,
|
|
|
|
|
started_at: datetime,
|
|
|
|
|
) -> dict:
|
|
|
|
|
query = """
|
|
|
|
|
INSERT INTO telefoni_opkald
|
|
|
|
|
(callid, bruger_id, direction, ekstern_nummer, intern_extension, kontakt_id, started_at, raw_payload)
|
|
|
|
|
VALUES
|
|
|
|
|
(%s, %s, %s, %s, %s, %s, %s, %s::jsonb)
|
|
|
|
|
ON CONFLICT (callid)
|
|
|
|
|
DO UPDATE SET
|
|
|
|
|
raw_payload = EXCLUDED.raw_payload,
|
|
|
|
|
direction = EXCLUDED.direction,
|
|
|
|
|
intern_extension = COALESCE(telefoni_opkald.intern_extension, EXCLUDED.intern_extension),
|
|
|
|
|
ekstern_nummer = COALESCE(telefoni_opkald.ekstern_nummer, EXCLUDED.ekstern_nummer),
|
|
|
|
|
bruger_id = COALESCE(telefoni_opkald.bruger_id, EXCLUDED.bruger_id),
|
|
|
|
|
kontakt_id = COALESCE(telefoni_opkald.kontakt_id, EXCLUDED.kontakt_id),
|
|
|
|
|
started_at = LEAST(telefoni_opkald.started_at, EXCLUDED.started_at)
|
|
|
|
|
RETURNING *
|
|
|
|
|
"""
|
|
|
|
|
rows = execute_query(
|
|
|
|
|
query,
|
|
|
|
|
(
|
|
|
|
|
callid,
|
|
|
|
|
user_id,
|
|
|
|
|
direction,
|
|
|
|
|
ekstern_nummer,
|
|
|
|
|
intern_extension,
|
|
|
|
|
kontakt_id,
|
|
|
|
|
started_at,
|
|
|
|
|
raw_payload,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
return rows[0] if rows else {}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def terminate_call(callid: str, duration_sec: Optional[int]) -> bool:
|
|
|
|
|
if not callid:
|
|
|
|
|
return False
|
|
|
|
|
rows = execute_query(
|
|
|
|
|
"""
|
|
|
|
|
UPDATE telefoni_opkald
|
|
|
|
|
SET ended_at = NOW(),
|
|
|
|
|
duration_sec = %s
|
|
|
|
|
WHERE callid = %s
|
|
|
|
|
RETURNING id
|
|
|
|
|
""",
|
|
|
|
|
(duration_sec, callid),
|
|
|
|
|
)
|
|
|
|
|
return bool(rows)
|
2026-02-17 08:29:05 +01:00
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_contact_details(contact_id: int) -> dict:
|
|
|
|
|
"""
|
|
|
|
|
Get extended contact details including:
|
|
|
|
|
- Latest 3 open cases
|
|
|
|
|
- Last call date
|
|
|
|
|
"""
|
|
|
|
|
if not contact_id:
|
|
|
|
|
return {"recent_cases": [], "last_call": None}
|
|
|
|
|
|
|
|
|
|
# Get the 3 newest open cases for this contact
|
|
|
|
|
cases_query = """
|
|
|
|
|
SELECT
|
|
|
|
|
s.id,
|
|
|
|
|
s.titel,
|
|
|
|
|
s.created_at
|
|
|
|
|
FROM sag_sager s
|
|
|
|
|
INNER JOIN sag_kontakter sk ON s.id = sk.sag_id
|
|
|
|
|
WHERE sk.contact_id = %s
|
|
|
|
|
AND s.status = 'åben'
|
|
|
|
|
AND s.deleted_at IS NULL
|
|
|
|
|
AND sk.deleted_at IS NULL
|
|
|
|
|
ORDER BY s.created_at DESC
|
|
|
|
|
LIMIT 3
|
|
|
|
|
"""
|
|
|
|
|
cases = execute_query(cases_query, (contact_id,)) or []
|
|
|
|
|
|
|
|
|
|
# Get the most recent call for this contact
|
|
|
|
|
last_call_query = """
|
|
|
|
|
SELECT
|
|
|
|
|
t.started_at,
|
|
|
|
|
t.bruger_id,
|
|
|
|
|
t.duration_sec,
|
|
|
|
|
u.full_name,
|
|
|
|
|
u.username
|
|
|
|
|
FROM telefoni_opkald t
|
|
|
|
|
LEFT JOIN users u ON t.bruger_id = u.user_id
|
|
|
|
|
WHERE t.kontakt_id = %s
|
|
|
|
|
AND t.ended_at IS NOT NULL
|
|
|
|
|
ORDER BY t.started_at DESC
|
|
|
|
|
LIMIT 1
|
|
|
|
|
"""
|
|
|
|
|
last_call_row = execute_query_single(last_call_query, (contact_id,))
|
|
|
|
|
|
|
|
|
|
last_call_data = None
|
|
|
|
|
if last_call_row:
|
|
|
|
|
last_call_data = {
|
|
|
|
|
"started_at": last_call_row.get("started_at"),
|
|
|
|
|
"bruger_navn": last_call_row.get("full_name") or last_call_row.get("username"),
|
|
|
|
|
"duration_sec": last_call_row.get("duration_sec"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"recent_cases": [
|
|
|
|
|
{
|
|
|
|
|
"id": case["id"],
|
|
|
|
|
"titel": case["titel"],
|
|
|
|
|
"created_at": case["created_at"],
|
|
|
|
|
}
|
|
|
|
|
for case in cases
|
|
|
|
|
],
|
|
|
|
|
"last_call": last_call_data,
|
|
|
|
|
}
|