diff --git a/instruct/handlers.py b/instruct/handlers.py index 94f8875..77278cb 100644 --- a/instruct/handlers.py +++ b/instruct/handlers.py @@ -106,35 +106,46 @@ def get_ad_user_info(user_id: str) -> dict | None: return None -async def _send_sd_api_request(requester_email: str, subject: str, description: str, city: str) -> str | None: +async def _send_sd_api_request( + requester_cn: str, + requester_email: str, + subject: str, + description: str, + city: str +) -> str | None: """ Автономная прямая отправка HTTP-запроса в Service Desk API v3. - Автоматически ищет ID группы и техника по именам из config. + Умеет привязывать пользователя по ФИО, Email или Fallback. """ - # Если запрашивает системный ИИ — ищем по ИМЕНИ - if requester_email == DEFAULT_REQUESTER or requester_email == "ai@sibcem.ru": - requester_payload = {"name": "Искусственный Интеллект"} - else: + # 1. Формируем структуру заявителя (приоритет: ФИО -> Email -> ИИ) + if requester_cn and requester_cn != "N/A": + requester_payload = {"name": requester_cn} + elif requester_email and requester_email not in (DEFAULT_REQUESTER, "ai@sibcem.ru"): requester_payload = {"email_id": requester_email} + else: + requester_payload = {"name": "Искусственный Интеллект"} - # Ищем ID группы + # 2. Поиск ID группы (пробуем /groups и /group) group_id = None try: - url_groups = f"{SD_URL.rstrip('/')}/api/v3/groups" headers = {"TECHNICIAN_KEY": SD_TOKEN} async with httpx.AsyncClient(verify=False) as client: - resp = await client.get(url_groups, headers=headers, timeout=10.0) - if resp.status_code == 200: - groups = resp.json().get("groups", []) - for g in groups: - if g.get("name") == SD_INSTRUCT_GROUP_NAME: - group_id = g.get("id") - logger.info(f"SD group found: name={SD_INSTRUCT_GROUP_NAME}, id={group_id}") + for endpoint in ["/api/v3/groups", "/api/v3/group"]: + url_groups = f"{SD_URL.rstrip('/')}{endpoint}" + resp = await client.get(url_groups, headers=headers, timeout=10.0) + if resp.status_code == 200: + groups = resp.json().get("groups", []) or resp.json().get("group", []) + for g in groups: + if g.get("name") == SD_INSTRUCT_GROUP_NAME: + group_id = g.get("id") + logger.info(f"SD group found: name={SD_INSTRUCT_GROUP_NAME}, id={group_id}") + break + if group_id: break except Exception as e: logger.warning(f"Failed to fetch SD groups: {e}") - # Ищем ID техника + # 3. Поиск ID техника tech_id = None try: url_techs = f"{SD_URL.rstrip('/')}/api/v3/technicians" @@ -151,6 +162,7 @@ async def _send_sd_api_request(requester_email: str, subject: str, description: except Exception as e: logger.warning(f"Failed to fetch SD technicians: {e}") + # 4. Формируем тело запроса payload = { "request": { "subject": subject, @@ -161,7 +173,6 @@ async def _send_sd_api_request(requester_email: str, subject: str, description: } } } - # Добавляем группу и исполнителя если найдены if group_id: payload["request"]["group"] = {"id": group_id} if tech_id: @@ -178,9 +189,24 @@ async def _send_sd_api_request(requester_email: str, subject: str, description: res_json = response.json() ticket_id = res_json.get("request", {}).get("id") return str(ticket_id) if ticket_id else None - else: - logger.error(f"Service Desk API Error ({response.status_code}): {response.text}") - return None + + # Fallback: Если по ФИО не нашло, пробуем через DEFAULT_REQUESTER + elif response.status_code == 400 and requester_payload != {"name": "Искусственный Интеллект"}: + logger.warning( + f"ServiceDesk не смог привязать requester={requester_payload}. " + f"Повторяем запрос от имени системного заявителя..." + ) + payload["request"]["requester"] = {"name": "Искусственный Интеллект"} + payload["request"]["description"] = f"[Пользователь: {requester_cn}]\n\n{description}" + data_fb = {"input_data": json.dumps(payload)} + + resp_fb = await client.post(url, headers=headers, data=data_fb, timeout=15.0) + if resp_fb.status_code in (200, 201): + ticket_id = resp_fb.json().get("request", {}).get("id") + return str(ticket_id) if ticket_id else None + + logger.error(f"Service Desk API Error ({response.status_code}): {response.text}") + return None async def _find_existing_trueconf_ticket(requester_email: str) -> str | None: @@ -214,9 +240,9 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: """ subject = "Запрос доступа к Trueconf" - # Получаем email из AD + # Получаем данные пользователя из AD ad_info = get_ad_user_info(user_id) - requester_email = ad_info.get("mail") or DEFAULT_REQUESTER + requester_email = (ad_info.get("mail") if ad_info else None) or DEFAULT_REQUESTER city = ad_info.get("city", "Кемерово") if ad_info else "Кемерово" description = ( @@ -225,10 +251,10 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: ) try: - logger.info(f"Calling SD API directly: requester={requester_email}, city={city}") - ticket_id = await _send_sd_api_request(requester_email, subject, description, city) + logger.info(f"Calling SD API directly: cn='{cn}', email='{requester_email}', city='{city}'") + ticket_id = await _send_sd_api_request(cn, requester_email, subject, description, city) if ticket_id: - logger.info(f"SD ticket created for {cn}: #{ticket_id} (requester={requester_email})") + logger.info(f"SD ticket created for {cn}: #{ticket_id}") else: logger.error(f"Failed to create SD ticket for {cn}") return ticket_id