Обновить service_desk/handlers.py
This commit is contained in:
+34
-20
@@ -44,9 +44,9 @@ from utils.texts import (
|
||||
sd_uploading_file,
|
||||
sd_file_attached,
|
||||
sd_file_upload_error,
|
||||
system_error_text,
|
||||
UNKNOWN_MAIN_CMD_TEXT,
|
||||
)
|
||||
from utils.texts import UNKNOWN_MAIN_CMD_TEXT
|
||||
# 🔌 Импортируем централизованную функцию сбора статистики из main
|
||||
from utils.stats_logger import log_menu_stats
|
||||
|
||||
# Таймаут ожидания вложений после текста (секунды)
|
||||
@@ -131,10 +131,14 @@ async def create_ticket_in_sd(requester_email: str, subject: str, description: s
|
||||
headers = {"authtoken": SD_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"}
|
||||
endpoint = f"{SD_URL}/api/v3/requests"
|
||||
html_desc = f"<p>{description.replace(chr(10), '<br>')}</p><br><hr><p style='color:#555;font-size:12px;'><i>Создано через TrueConf КЛЕВЕР</i></p>"
|
||||
|
||||
async def _send_req(email, current_city):
|
||||
payload = {"request": {"subject": subject, "description": html_desc, "requester": {"email_id": email}, "udf_fields": {"udf_pick_301": current_city}}}
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
return await client.post(endpoint, headers=headers, data={"input_data": json.dumps(payload)}, timeout=15.0)
|
||||
|
||||
# Безопасная обработка сбоев сети / отсуствия доступа к серверу SD
|
||||
try:
|
||||
resp = await _send_req(requester_email, city)
|
||||
data = resp.json() if resp.status_code in [200, 201] else {}
|
||||
if data.get("response_status", {}).get("status_code") != 2000:
|
||||
@@ -142,6 +146,11 @@ async def create_ticket_in_sd(requester_email: str, subject: str, description: s
|
||||
data = resp.json() if resp.status_code in [200, 201] else {}
|
||||
if data.get("response_status", {}).get("status_code") == 2000:
|
||||
return data.get("request", {}).get("id")
|
||||
except (httpx.RequestError, httpx.HTTPStatusError, Exception) as e:
|
||||
logger.error(f"❌ [SD Network Error] Не удалось подключиться к ServiceDesk: {e}")
|
||||
sd_workflow_logger.error(f"❌ [SD Network Error] {e}")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
async def process_and_upload_file(file_id: str, filename: str, ticket_id: str):
|
||||
@@ -219,6 +228,8 @@ def _extract_attachments_from_content(content, is_attachment_type=False):
|
||||
async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login):
|
||||
"""Логика генерации темы, отправки в SD и загрузки всех очередей вложений."""
|
||||
session["step"] = "creating_ticket"
|
||||
|
||||
try:
|
||||
ad_user = await asyncio.to_thread(get_ad_user_sync, login)
|
||||
sd_workflow_logger.info(f"👤 [AD Lookup] User: {login} -> Found: {ad_user is not None}")
|
||||
sender_email = ad_user.get("mail") if ad_user else DEFAULT_REQUESTER
|
||||
@@ -232,7 +243,7 @@ async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login
|
||||
if ticket_id:
|
||||
session["ticket_id"] = ticket_id
|
||||
|
||||
# 1. Загрузка основных вложений (накопленных до и во время таймаута)
|
||||
# 1. Загрузка основных вложений
|
||||
queued_files = list(session.get("files_queue", []))
|
||||
if queued_files:
|
||||
sd_workflow_logger.info(f"📎 [Upload] Uploading {len(queued_files)} file(s) to ticket #{ticket_id}")
|
||||
@@ -240,7 +251,7 @@ async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login
|
||||
await process_and_upload_file(f['file_id'], f['file_name'], ticket_id)
|
||||
sd_workflow_logger.info(f"✅ [Upload] All queued file(s) uploaded")
|
||||
|
||||
# 2. Загрузка вложений, прилетевших во время выполнения API-запросов (из "черной дыры")
|
||||
# 2. Загрузка вложений, прилетевших во время выполнения API-запросов
|
||||
post_files = list(session.get("post_create_queue", []))
|
||||
if post_files:
|
||||
sd_workflow_logger.info(f"📎 [Upload Extra] Uploading {len(post_files)} late file(s) to ticket #{ticket_id}")
|
||||
@@ -259,13 +270,18 @@ async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login
|
||||
else:
|
||||
session["step"] = "need_text"
|
||||
await msg.answer(system_error_text(EMOJI_DIGITS), parse_mode="html")
|
||||
except Exception as e:
|
||||
logger.exception(f"❌ [SD Ticket Error] {user_id}: {e}")
|
||||
sd_workflow_logger.error(f"❌ [SD Ticket Error] {user_id}: {e}")
|
||||
session["step"] = "need_text"
|
||||
await msg.answer(system_error_text(EMOJI_DIGITS), parse_mode="html")
|
||||
|
||||
async def _wait_for_attachments_and_create(user_id, session):
|
||||
"""Фоновый таймер с возможностью отмены (Debounce)."""
|
||||
try:
|
||||
await asyncio.sleep(ATTACHMENT_WAIT_TIMEOUT)
|
||||
except asyncio.CancelledError:
|
||||
return # Таймер сброшен, прерываем выполнение текущей таски
|
||||
return
|
||||
|
||||
if session.get("step") != "waiting_for_attachments":
|
||||
return
|
||||
@@ -285,6 +301,16 @@ async def _wait_for_attachments_and_create(user_id, session):
|
||||
except Exception:
|
||||
sd_workflow_logger.error(f"❌ [SD] Failed to send error message to {user_id}")
|
||||
|
||||
def _on_sd_timer_done(task: asyncio.Task, user_id: str):
|
||||
"""Безопасный коллбэк завершения таймера создания заявки"""
|
||||
try:
|
||||
task.result()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка в таске таймера SD для {user_id}: {e}")
|
||||
sd_workflow_logger.error(f"❌ [SD Timer Error] {user_id}: {e}")
|
||||
|
||||
@router.message()
|
||||
async def sd_module_handler(msg: Message):
|
||||
try:
|
||||
@@ -309,7 +335,7 @@ async def sd_module_handler(msg: Message):
|
||||
cmd = raw_num
|
||||
break
|
||||
|
||||
# --- ВЫХОД (Кнопки 0 и 9 возвращают в общее меню) ---
|
||||
# --- ВЫХОД ---
|
||||
if cmd in ["0", "9", "/start", "меню"]:
|
||||
log_menu_stats(user_id, "Service Desk", "Выход в главное меню")
|
||||
states.clear_state(user_id)
|
||||
@@ -327,29 +353,24 @@ async def sd_module_handler(msg: Message):
|
||||
await msg.answer(SD_UNKNOWN_CMD_TEXT, parse_mode="html")
|
||||
return
|
||||
|
||||
# Если только вложение без текста — сохраняем и ждем текст
|
||||
if not msg_text.strip() and inline_attachments:
|
||||
session["files_queue"].extend(inline_attachments)
|
||||
sd_workflow_logger.info(f"📎 [SD] Saved {len(inline_attachments)} attachment(s), waiting for text")
|
||||
await msg.answer(SD_TEXT_REQUIRED, parse_mode="html")
|
||||
return
|
||||
|
||||
# Если пришел текст (с вложениями или без)
|
||||
if inline_attachments:
|
||||
session["files_queue"].extend(inline_attachments)
|
||||
sd_workflow_logger.info(f"📎 [SD] Added {len(inline_attachments)} inline attachment(s) to queue")
|
||||
|
||||
# Настраиваем параметры сессии для ожидания
|
||||
session["step"] = "waiting_for_attachments"
|
||||
session["msg_text"] = msg_text
|
||||
session["msg"] = msg
|
||||
session["user_id"] = user_id
|
||||
session["login"] = login
|
||||
|
||||
# Сразу выдаем ОДНО сообщение пользователю, чтобы он видел реакцию бота
|
||||
await msg.answer(SD_CREATING, parse_mode="html")
|
||||
|
||||
# Запускаем фоновый таймер с механизмом сброса (Debounce)
|
||||
if "timer_task" in session and not session["timer_task"].done():
|
||||
session["timer_task"].cancel()
|
||||
session["timer_task"] = asyncio.create_task(_wait_for_attachments_and_create(user_id, session))
|
||||
@@ -357,7 +378,7 @@ async def sd_module_handler(msg: Message):
|
||||
sd_workflow_logger.info(f"⏳ [SD] Timer started: waiting {ATTACHMENT_WAIT_TIMEOUT}s for potentially more attachments...")
|
||||
return
|
||||
|
||||
# --- ШАГ 2: РЕЖИМ ОЖИДАНИЯ ДОП. ВЛОЖЕНИЙ (Сброс таймаута) ---
|
||||
# --- ШАГ 2: РЕЖИМ ОЖИДАНИЯ ДОП. ВЛОЖЕНИЙ ---
|
||||
if session["step"] == "waiting_for_attachments":
|
||||
if inline_attachments:
|
||||
session["files_queue"].extend(inline_attachments)
|
||||
@@ -366,7 +387,6 @@ async def sd_module_handler(msg: Message):
|
||||
session["msg_text"] = session["msg_text"] + " " + msg_text.strip()
|
||||
sd_workflow_logger.info(f"📝 [SD] Additional text appended")
|
||||
|
||||
# Перезапускаем таймер (пользователь активен, сдвигаем окно создания вперед)
|
||||
if "timer_task" in session and not session["timer_task"].done():
|
||||
session["timer_task"].cancel()
|
||||
session["timer_task"] = asyncio.create_task(_wait_for_attachments_and_create(user_id, session))
|
||||
@@ -374,7 +394,7 @@ async def sd_module_handler(msg: Message):
|
||||
sd_workflow_logger.info(f"🔄 [SD] Timer reset due to user activity. Waiting another {ATTACHMENT_WAIT_TIMEOUT}s")
|
||||
return
|
||||
|
||||
# --- ШАГ 3: ЗАЯВКА СОЗДАЕТСЯ (Перехват файлов из "черной дыры") ---
|
||||
# --- ШАГ 3: ЗАЯВКА СОЗДАЕТСЯ ---
|
||||
if session["step"] == "creating_ticket":
|
||||
if inline_attachments:
|
||||
if "post_create_queue" not in session:
|
||||
@@ -406,9 +426,3 @@ async def sd_module_handler(msg: Message):
|
||||
await msg.answer(system_error_text(EMOJI_DIGITS), parse_mode="html")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user