feat: добавить выбор шаблона Service Desk перед созданием заявки

This commit is contained in:
2026-07-15 12:24:23 +07:00
parent fc0288d13d
commit efda2c8ae9
+99 -5
View File
@@ -67,6 +67,47 @@ logger = logging.getLogger(__name__)
router = Router() router = Router()
sd_sessions = {} sd_sessions = {}
async def get_sd_templates():
url = f"{SD_URL}/api/v3/request_templates"
headers = {"authtoken": SD_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"}
input_data = {"list_info": {"row_count": 100, "start_index": 1}}
params = {"input_data": json.dumps(input_data)}
try:
async with httpx.AsyncClient(verify=False) as client:
response = await client.get(url, headers=headers, params=params, timeout=30.0)
if response.status_code != 200:
sd_workflow_logger.error(f"Ошибка API шаблонов: HTTP {response.status_code} - {response.text}")
return []
data = response.json()
all_templates = data.get("request_templates", [])
pre_filtered = [
t for t in all_templates
if t.get("status") == "ACTIVE"
and not t.get("inactive", False)
and str(t.get("id")) != "2"
]
sd_workflow_logger.info(f"Шаблонов в системе: {len(all_templates)} | Активных: {len(pre_filtered)}")
async def check_template(t):
try:
t_resp = await client.get(url + "/" + str(t["id"]), headers=headers, timeout=15.0)
if t_resp.status_code == 200:
detailed = t_resp.json().get("request_template", {})
return t if detailed.get("show_to_requester") is True else None
except Exception as e:
sd_workflow_logger.warning(f"Ошибка деталей шаблона ID {t['id']}: {e}")
return None
tasks = [check_template(t) for t in pre_filtered]
results = await asyncio.gather(*tasks)
active_templates = [t for t in results if t is not None]
sd_workflow_logger.info(f"Шаблоны с show_to_requester=True: {len(active_templates)}")
for temp in active_templates:
sd_workflow_logger.info(f" ID: {temp['id']} | Name: {temp['name']}")
return active_templates
except Exception as e:
sd_workflow_logger.error(f"Ошибка загрузки шаблонов: {e}")
return []
# ================================================ # ================================================
# БИЗНЕС-ЛОГИКА # БИЗНЕС-ЛОГИКА
# ================================================ # ================================================
@@ -128,14 +169,16 @@ async def generate_smart_subject(text: str) -> str:
sd_workflow_logger.error(f"📝 [Subject Gen Error] Exception: {e}\n{traceback.format_exc()}") sd_workflow_logger.error(f"📝 [Subject Gen Error] Exception: {e}\n{traceback.format_exc()}")
return "Заявка из КЛЕВЕР" return "Заявка из КЛЕВЕР"
async def create_ticket_in_sd(requester_email: str, subject: str, description: str, city: str): async def create_ticket_in_sd(requester_email: str, subject: str, description: str, city: str, template_id: str = None):
headers = {"authtoken": SD_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"} headers = {"authtoken": SD_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"}
endpoint = f"{SD_URL}/api/v3/requests" 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>" 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): 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}}} req_payload = {"request": {"subject": subject, "description": html_desc, "requester": {"email_id": email}, "udf_fields": {"udf_pick_301": current_city}}}
if template_id:
req_payload["request"]["request_template"] = {"id": template_id}
async with httpx.AsyncClient(verify=False) as client: async with httpx.AsyncClient(verify=False) as client:
return await client.post(endpoint, headers=headers, data={"input_data": json.dumps(payload)}, timeout=15.0) return await client.post(endpoint, headers=headers, data={"input_data": json.dumps(req_payload)}, timeout=15.0)
resp = await _send_req(requester_email, city) resp = await _send_req(requester_email, city)
data = resp.json() if resp.status_code in [200, 201] else {} data = resp.json() if resp.status_code in [200, 201] else {}
if data.get("response_status", {}).get("status_code") != 2000: if data.get("response_status", {}).get("status_code") != 2000:
@@ -220,6 +263,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): async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login):
"""Логика генерации темы, отправки в SD и загрузки всех очередей вложений.""" """Логика генерации темы, отправки в SD и загрузки всех очередей вложений."""
session["step"] = "creating_ticket" session["step"] = "creating_ticket"
template_id = session.get("template_id")
sd_workflow_logger.info(f"[Create] Template ID: {template_id}")
try: try:
ad_user = await asyncio.to_thread(get_ad_user_sync, login) 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}") sd_workflow_logger.info(f"👤 [AD Lookup] User: {login} -> Found: {ad_user is not None}")
@@ -228,7 +273,7 @@ async def _create_ticket_and_attach_files(user_id, msg_text, session, msg, login
subject = await generate_smart_subject(msg_text) subject = await generate_smart_subject(msg_text)
sd_workflow_logger.info(f"📝 [Subject Gen] Text: {msg_text[:50]}... -> Subject: {subject}") sd_workflow_logger.info(f"📝 [Subject Gen] Text: {msg_text[:50]}... -> Subject: {subject}")
ticket_id = await create_ticket_in_sd(sender_email, subject, msg_text, city) ticket_id = await create_ticket_in_sd(sender_email, subject, msg_text, city, template_id)
sd_workflow_logger.info(f"🎫 [Ticket Created] ID: {ticket_id}") sd_workflow_logger.info(f"🎫 [Ticket Created] ID: {ticket_id}")
if ticket_id: if ticket_id:
@@ -290,6 +335,27 @@ async def sd_module_handler(msg: Message):
if states.get_state(user_id) != "SD_MODE": if states.get_state(user_id) != "SD_MODE":
return return
# Инициализация шаблонов при первом входе в SD_MODE
if user_id not in sd_sessions:
sd_sessions[user_id] = {"step": "need_text", "files_queue": [], "post_create_queue": [], "templates": [], "template_names": {}, "template_menu_text": ""}
templates = await get_sd_templates()
if templates:
sd_sessions[user_id]["templates"] = templates
sd_sessions[user_id]["template_names"] = {str(i+1): str(t["id"]) for i, t in enumerate(templates)}
template_menu_text = ""
for i, t in enumerate(templates):
template_menu_text += f"{EMOJI_DIGITS[str(i+1)]} - {t['name']}\n"
sd_sessions[user_id]["template_menu_text"] = template_menu_text.strip()
sd_workflow_logger.info(f"[Template] Loaded {len(templates)} templates for user {user_id}")
sd_sessions[user_id]["step"] = "choose_template"
await msg.answer(
f"Выберите шаблон для заявки:\n\n"
f"{template_menu_text}\n\n"
f"Или отправьте пропустить для создания без шаблона.",
parse_mode="html"
)
return
msg_text = _extract_text_from_content(msg.content) msg_text = _extract_text_from_content(msg.content)
is_attachment = hasattr(msg.type, "name") and msg.type.name == "ATTACHMENT" is_attachment = hasattr(msg.type, "name") and msg.type.name == "ATTACHMENT"
inline_attachments = _extract_attachments_from_content(msg.content, is_attachment) inline_attachments = _extract_attachments_from_content(msg.content, is_attachment)
@@ -315,8 +381,36 @@ async def sd_module_handler(msg: Message):
return return
if user_id not in sd_sessions: if user_id not in sd_sessions:
sd_sessions[user_id] = {"step": "need_text", "files_queue": [], "post_create_queue": []} sd_sessions[user_id] = {"step": "need_text", "files_queue": [], "post_create_queue": [], "templates": [], "template_names": {}}
session = sd_sessions[user_id] session = sd_sessions[user_id]
# --- ШАГ 0: ВЫБОР ШАБЛОНА ---
if session["step"] == "choose_template":
if cmd in ["skip", "пропустить", "без шаблона"]:
sd_workflow_logger.info("[Template] User skipped template selection")
session["step"] = "need_text"
await msg.answer(SD_MAIN_MENU_TEXT, parse_mode="html")
return
if cmd in session.get("template_names", {}):
template_id = session["template_names"][cmd]
template_name = next((t["name"] for t in session.get("templates", []) if str(t["id"]) == str(template_id)), "Неизвестно")
sd_workflow_logger.info(f"[Template] User selected: {template_id} ({template_name})")
session["template_id"] = template_id
session["step"] = "need_text"
await msg.answer(
f"Выбран шаблон: {template_name}\n\n"
f"Теперь опишите проблему текстом (можно прикрепить файлы):",
parse_mode="html"
)
return
else:
await msg.answer(
f"Неверный номер. Выберите из списка:\n\n"
f"{session.get('template_menu_text', '')}\n\n"
f"Или отправьте пропустить для создания без шаблона.",
parse_mode="html"
)
return
# --- ШАГ 1: ОЖИДАНИЕ ТЕКСТА --- # --- ШАГ 1: ОЖИДАНИЕ ТЕКСТА ---
if session["step"] == "need_text": if session["step"] == "need_text":