fix: use requester name (CN) instead of email for SD API - fallback to AI when name fails
This commit is contained in:
+51
-25
@@ -106,35 +106,46 @@ def get_ad_user_info(user_id: str) -> dict | None:
|
|||||||
return 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.
|
Автономная прямая отправка HTTP-запроса в Service Desk API v3.
|
||||||
Автоматически ищет ID группы и техника по именам из config.
|
Умеет привязывать пользователя по ФИО, Email или Fallback.
|
||||||
"""
|
"""
|
||||||
# Если запрашивает системный ИИ — ищем по ИМЕНИ
|
# 1. Формируем структуру заявителя (приоритет: ФИО -> Email -> ИИ)
|
||||||
if requester_email == DEFAULT_REQUESTER or requester_email == "ai@sibcem.ru":
|
if requester_cn and requester_cn != "N/A":
|
||||||
requester_payload = {"name": "Искусственный Интеллект"}
|
requester_payload = {"name": requester_cn}
|
||||||
else:
|
elif requester_email and requester_email not in (DEFAULT_REQUESTER, "ai@sibcem.ru"):
|
||||||
requester_payload = {"email_id": requester_email}
|
requester_payload = {"email_id": requester_email}
|
||||||
|
else:
|
||||||
|
requester_payload = {"name": "Искусственный Интеллект"}
|
||||||
|
|
||||||
# Ищем ID группы
|
# 2. Поиск ID группы (пробуем /groups и /group)
|
||||||
group_id = None
|
group_id = None
|
||||||
try:
|
try:
|
||||||
url_groups = f"{SD_URL.rstrip('/')}/api/v3/groups"
|
|
||||||
headers = {"TECHNICIAN_KEY": SD_TOKEN}
|
headers = {"TECHNICIAN_KEY": SD_TOKEN}
|
||||||
async with httpx.AsyncClient(verify=False) as client:
|
async with httpx.AsyncClient(verify=False) as client:
|
||||||
resp = await client.get(url_groups, headers=headers, timeout=10.0)
|
for endpoint in ["/api/v3/groups", "/api/v3/group"]:
|
||||||
if resp.status_code == 200:
|
url_groups = f"{SD_URL.rstrip('/')}{endpoint}"
|
||||||
groups = resp.json().get("groups", [])
|
resp = await client.get(url_groups, headers=headers, timeout=10.0)
|
||||||
for g in groups:
|
if resp.status_code == 200:
|
||||||
if g.get("name") == SD_INSTRUCT_GROUP_NAME:
|
groups = resp.json().get("groups", []) or resp.json().get("group", [])
|
||||||
group_id = g.get("id")
|
for g in groups:
|
||||||
logger.info(f"SD group found: name={SD_INSTRUCT_GROUP_NAME}, id={group_id}")
|
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
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch SD groups: {e}")
|
logger.warning(f"Failed to fetch SD groups: {e}")
|
||||||
|
|
||||||
# Ищем ID техника
|
# 3. Поиск ID техника
|
||||||
tech_id = None
|
tech_id = None
|
||||||
try:
|
try:
|
||||||
url_techs = f"{SD_URL.rstrip('/')}/api/v3/technicians"
|
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:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch SD technicians: {e}")
|
logger.warning(f"Failed to fetch SD technicians: {e}")
|
||||||
|
|
||||||
|
# 4. Формируем тело запроса
|
||||||
payload = {
|
payload = {
|
||||||
"request": {
|
"request": {
|
||||||
"subject": subject,
|
"subject": subject,
|
||||||
@@ -161,7 +173,6 @@ async def _send_sd_api_request(requester_email: str, subject: str, description:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# Добавляем группу и исполнителя если найдены
|
|
||||||
if group_id:
|
if group_id:
|
||||||
payload["request"]["group"] = {"id": group_id}
|
payload["request"]["group"] = {"id": group_id}
|
||||||
if tech_id:
|
if tech_id:
|
||||||
@@ -178,9 +189,24 @@ async def _send_sd_api_request(requester_email: str, subject: str, description:
|
|||||||
res_json = response.json()
|
res_json = response.json()
|
||||||
ticket_id = res_json.get("request", {}).get("id")
|
ticket_id = res_json.get("request", {}).get("id")
|
||||||
return str(ticket_id) if ticket_id else None
|
return str(ticket_id) if ticket_id else None
|
||||||
else:
|
|
||||||
logger.error(f"Service Desk API Error ({response.status_code}): {response.text}")
|
# Fallback: Если по ФИО не нашло, пробуем через DEFAULT_REQUESTER
|
||||||
return None
|
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:
|
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"
|
subject = "Запрос доступа к Trueconf"
|
||||||
|
|
||||||
# Получаем email из AD
|
# Получаем данные пользователя из AD
|
||||||
ad_info = get_ad_user_info(user_id)
|
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 "Кемерово"
|
city = ad_info.get("city", "Кемерово") if ad_info else "Кемерово"
|
||||||
|
|
||||||
description = (
|
description = (
|
||||||
@@ -225,10 +251,10 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"Calling SD API directly: requester={requester_email}, city={city}")
|
logger.info(f"Calling SD API directly: cn='{cn}', email='{requester_email}', city='{city}'")
|
||||||
ticket_id = await _send_sd_api_request(requester_email, subject, description, city)
|
ticket_id = await _send_sd_api_request(cn, requester_email, subject, description, city)
|
||||||
if ticket_id:
|
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:
|
else:
|
||||||
logger.error(f"Failed to create SD ticket for {cn}")
|
logger.error(f"Failed to create SD ticket for {cn}")
|
||||||
return ticket_id
|
return ticket_id
|
||||||
|
|||||||
Reference in New Issue
Block a user