146 lines
9.2 KiB
Python
146 lines
9.2 KiB
Python
|
|
"""Remember the customer end of an AnyDesk session, never the technician end."""
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
|
||
|
|
from fastapi import HTTPException
|
||
|
|
|
||
|
|
from app.core.database import execute_query
|
||
|
|
|
||
|
|
|
||
|
|
def customer_machine_id(device_info):
|
||
|
|
if isinstance(device_info, str):
|
||
|
|
device_info = json.loads(device_info)
|
||
|
|
value = re.sub(r'[\s-]', '', str((device_info or {}).get('to_id') or ''))
|
||
|
|
return value if re.fullmatch(r'[0-9]{1,64}', value) else None
|
||
|
|
|
||
|
|
|
||
|
|
def remember_machine(session_id, customer_id, contact_id, hardware_asset_id=None):
|
||
|
|
"""Caller owns the transaction including the selected session's update."""
|
||
|
|
rows = execute_query('SELECT device_info,hardware_asset_id FROM anydesk_sessions WHERE id=%s', (session_id,))
|
||
|
|
if not rows:
|
||
|
|
raise HTTPException(404, 'Sessionen findes ikke')
|
||
|
|
machine_id = customer_machine_id(rows[0]['device_info'])
|
||
|
|
if not machine_id:
|
||
|
|
raise HTTPException(409, 'Sessionen mangler et entydigt AnyDesk-ID for kundens maskine')
|
||
|
|
customer = execute_query('SELECT id FROM customers WHERE id=%s AND is_active=TRUE', (customer_id,))
|
||
|
|
if not customer:
|
||
|
|
raise HTTPException(409, 'Vælg et aktivt firma for at gemme AnyDesk-koblingen')
|
||
|
|
if contact_id is not None:
|
||
|
|
contact = execute_query('''SELECT c.id FROM contacts c
|
||
|
|
JOIN contact_companies cc ON cc.contact_id=c.id
|
||
|
|
WHERE c.id=%s AND cc.customer_id=%s AND c.is_active=TRUE''', (contact_id, customer_id))
|
||
|
|
if not contact:
|
||
|
|
raise HTTPException(409, 'Kontaktpersonen skal være aktiv og tilknyttet det valgte firma')
|
||
|
|
hardware_id = _remember_hardware(machine_id, customer_id, contact_id,
|
||
|
|
hardware_asset_id or rows[0].get('hardware_asset_id'))
|
||
|
|
execute_query('UPDATE anydesk_sessions SET hardware_asset_id=%s WHERE id=%s', (hardware_id, session_id))
|
||
|
|
return machine_id
|
||
|
|
|
||
|
|
|
||
|
|
def _remember_hardware(machine_id, customer_id, contact_id, selected_id):
|
||
|
|
# Serialize registrations of this ID so repeated/concurrent saves reuse one PC.
|
||
|
|
execute_query("SELECT pg_advisory_xact_lock(hashtextextended(%s,0))", ('anydesk-hardware:' + machine_id,))
|
||
|
|
matches = execute_query('''SELECT id,anydesk_id,current_owner_customer_id,current_owner_type
|
||
|
|
FROM hardware_assets WHERE deleted_at IS NULL
|
||
|
|
AND regexp_replace(COALESCE(anydesk_id,''),'[[:space:]-]','','g')=%s FOR UPDATE''', (machine_id,))
|
||
|
|
if len(matches) > 1:
|
||
|
|
raise HTTPException(409, 'AnyDesk-ID findes på flere hardwareposter. Ret dubletterne før registrering.')
|
||
|
|
if selected_id is not None:
|
||
|
|
selected = execute_query('''SELECT id,anydesk_id,current_owner_customer_id,current_owner_type
|
||
|
|
FROM hardware_assets WHERE id=%s AND deleted_at IS NULL FOR UPDATE''', (selected_id,))
|
||
|
|
if not selected:
|
||
|
|
raise HTTPException(409, 'Den valgte hardware findes ikke')
|
||
|
|
if matches and matches[0]['id'] != selected_id:
|
||
|
|
raise HTTPException(409, 'AnyDesk-ID findes allerede på en anden hardwarepost')
|
||
|
|
if selected[0].get('anydesk_id') and customer_machine_id({'to_id': selected[0]['anydesk_id']}) != machine_id:
|
||
|
|
raise HTTPException(409, 'Den valgte hardware har et andet AnyDesk-ID')
|
||
|
|
matches = selected
|
||
|
|
elif not matches and contact_id is not None:
|
||
|
|
matches = execute_query('''SELECT h.id,h.anydesk_id,h.current_owner_customer_id,h.current_owner_type
|
||
|
|
FROM hardware_assets h JOIN hardware_contacts hc ON hc.hardware_id=h.id
|
||
|
|
WHERE hc.contact_id=%s AND h.current_owner_customer_id=%s AND h.deleted_at IS NULL
|
||
|
|
AND h.status='active' AND h.asset_type IN ('pc','laptop')
|
||
|
|
AND NULLIF(BTRIM(h.anydesk_id),'') IS NULL FOR UPDATE OF h''', (contact_id, customer_id))
|
||
|
|
if len(matches) > 1:
|
||
|
|
raise HTTPException(409, 'Kontaktpersonen har flere computere uden AnyDesk-ID. Vælg den rigtige hardware først.')
|
||
|
|
if matches:
|
||
|
|
hardware = matches[0]
|
||
|
|
if hardware['current_owner_customer_id'] not in (None, customer_id):
|
||
|
|
raise HTTPException(409, 'Hardwaren tilhører et andet firma. Ret ejerskabet på hardwarekortet først.')
|
||
|
|
if hardware['current_owner_customer_id'] is None and hardware.get('current_owner_type') not in (None, 'customer'):
|
||
|
|
raise HTTPException(409, 'Hardwarens ejerskab skal afklares på hardwarekortet først.')
|
||
|
|
if contact_id is not None:
|
||
|
|
other_contacts = execute_query('''SELECT hc.contact_id FROM hardware_contacts hc
|
||
|
|
JOIN contacts c ON c.id=hc.contact_id AND c.is_active=TRUE
|
||
|
|
WHERE hc.hardware_id=%s AND hc.contact_id<>%s''', (hardware['id'], contact_id))
|
||
|
|
if other_contacts:
|
||
|
|
raise HTTPException(409, 'Hardwaren er koblet til en anden kontaktperson. Ret koblingen på hardwarekortet først.')
|
||
|
|
hardware_id = hardware['id']
|
||
|
|
execute_query('''UPDATE hardware_assets SET anydesk_id=%s,anydesk_link=%s,
|
||
|
|
current_owner_type=COALESCE(current_owner_type,'customer'),current_owner_customer_id=%s,updated_at=NOW() WHERE id=%s''',
|
||
|
|
(machine_id, 'anydesk:' + machine_id, customer_id, hardware_id))
|
||
|
|
else:
|
||
|
|
hardware_id = execute_query('''INSERT INTO hardware_assets
|
||
|
|
(asset_type,model,current_owner_type,current_owner_customer_id,status,anydesk_id,anydesk_link,notes)
|
||
|
|
VALUES('pc','PC','customer',%s,'active',%s,%s,'Oprettet fra AnyDesk-session') RETURNING id''',
|
||
|
|
(customer_id, machine_id, 'anydesk:' + machine_id))[0]['id']
|
||
|
|
if not matches or matches[0]['current_owner_customer_id'] is None:
|
||
|
|
execute_query('''INSERT INTO hardware_ownership_history
|
||
|
|
(hardware_id,owner_type,owner_customer_id,start_date,notes)
|
||
|
|
VALUES(%s,'customer',%s,CURRENT_DATE,'Registreret fra AnyDesk-session')''', (hardware_id, customer_id))
|
||
|
|
if contact_id is not None:
|
||
|
|
execute_query('''INSERT INTO hardware_contacts(hardware_id,contact_id,role,source)
|
||
|
|
VALUES(%s,%s,'primary','anydesk') ON CONFLICT(hardware_id,contact_id) DO NOTHING''',
|
||
|
|
(hardware_id, contact_id))
|
||
|
|
return hardware_id
|
||
|
|
|
||
|
|
|
||
|
|
def apply_machine_links(machine_id=None):
|
||
|
|
"""Hardware is authoritative. Ambiguous machine IDs are never auto-linked."""
|
||
|
|
rows = execute_query('''UPDATE anydesk_sessions s SET hardware_asset_id=COALESCE(s.hardware_asset_id,m.id),
|
||
|
|
customer_id=COALESCE(s.customer_id,m.customer_id),contact_id=COALESCE(s.contact_id,m.contact_id),updated_at=NOW()
|
||
|
|
FROM (WITH hardware AS (
|
||
|
|
SELECT regexp_replace(anydesk_id,'[[:space:]-]','','g') AS machine,
|
||
|
|
MIN(id) AS id
|
||
|
|
FROM hardware_assets WHERE deleted_at IS NULL AND anydesk_id IS NOT NULL
|
||
|
|
GROUP BY regexp_replace(anydesk_id,'[[:space:]-]','','g') HAVING COUNT(*)=1
|
||
|
|
), links AS (
|
||
|
|
SELECT h.id,h.current_owner_customer_id AS customer_id,hw.machine,
|
||
|
|
CASE WHEN COUNT(DISTINCT con.id)=1 THEN MIN(con.id) ELSE NULL END AS contact_id
|
||
|
|
FROM hardware hw JOIN hardware_assets h ON h.id=hw.id
|
||
|
|
JOIN customers c ON c.id=h.current_owner_customer_id AND c.is_active=TRUE
|
||
|
|
LEFT JOIN hardware_contacts hc ON hc.hardware_id=h.id
|
||
|
|
LEFT JOIN contacts con ON con.id=hc.contact_id AND con.is_active=TRUE AND EXISTS (
|
||
|
|
SELECT 1 FROM contact_companies cc WHERE cc.contact_id=con.id AND cc.customer_id=c.id)
|
||
|
|
WHERE hw.machine ~ '^[0-9]+$' AND (%s IS NULL OR hw.machine=%s)
|
||
|
|
GROUP BY h.id,h.current_owner_customer_id,hw.machine
|
||
|
|
) SELECT * FROM links) m
|
||
|
|
WHERE regexp_replace(COALESCE(s.device_info->>'to_id',''),'[[:space:]-]','','g')=m.machine
|
||
|
|
AND (s.hardware_asset_id IS NULL OR s.hardware_asset_id=m.id)
|
||
|
|
AND ((s.customer_id IS NULL AND s.contact_id IS NULL) OR s.customer_id=m.customer_id)
|
||
|
|
AND (s.hardware_asset_id IS NULL OR s.customer_id IS NULL OR (s.contact_id IS NULL AND m.contact_id IS NOT NULL))
|
||
|
|
RETURNING s.id''', (machine_id, machine_id))
|
||
|
|
return len(rows or []) + _apply_legacy_links(machine_id)
|
||
|
|
|
||
|
|
|
||
|
|
def _apply_legacy_links(machine_id=None):
|
||
|
|
"""Fill only unassigned sessions or a missing contact for the same customer."""
|
||
|
|
table = execute_query("SELECT to_regclass('anydesk_machine_links') AS present")
|
||
|
|
if not table or not table[0]['present']:
|
||
|
|
return 0
|
||
|
|
rows = execute_query('''UPDATE anydesk_sessions s SET
|
||
|
|
customer_id=COALESCE(s.customer_id,m.customer_id),
|
||
|
|
contact_id=COALESCE(s.contact_id,m.contact_id),updated_at=NOW()
|
||
|
|
FROM anydesk_machine_links m JOIN customers c ON c.id=m.customer_id AND c.is_active=TRUE
|
||
|
|
LEFT JOIN contacts con ON con.id=m.contact_id
|
||
|
|
WHERE regexp_replace(COALESCE(s.device_info->>'to_id',''), '[[:space:]-]', '', 'g')=m.anydesk_id
|
||
|
|
AND (%s IS NULL OR m.anydesk_id=%s)
|
||
|
|
AND NOT EXISTS (SELECT 1 FROM hardware_assets h WHERE h.deleted_at IS NULL
|
||
|
|
AND regexp_replace(COALESCE(h.anydesk_id,''),'[[:space:]-]','','g')=m.anydesk_id)
|
||
|
|
AND (m.contact_id IS NULL OR (con.is_active=TRUE AND EXISTS (
|
||
|
|
SELECT 1 FROM contact_companies cc WHERE cc.contact_id=m.contact_id AND cc.customer_id=m.customer_id)))
|
||
|
|
AND ((s.customer_id IS NULL AND s.contact_id IS NULL)
|
||
|
|
OR (s.customer_id=m.customer_id AND s.contact_id IS NULL AND m.contact_id IS NOT NULL))
|
||
|
|
RETURNING s.id''', (machine_id, machine_id))
|
||
|
|
return len(rows or [])
|