"""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 [])