From c42854c3a5972b183389da463909baf3fb9a0f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B5=D0=BD=D0=B8=D1=81=20=D0=9A=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=BE=D1=87=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Wed, 29 Jul 2026 01:28:10 +0700 Subject: [PATCH] fix: search existing tickets by subject without status filter, check status in code, get details for CN matching --- instruct/handlers.py | 63 ++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/instruct/handlers.py b/instruct/handlers.py index 98c5418..ec5c3b4 100644 --- a/instruct/handlers.py +++ b/instruct/handlers.py @@ -212,39 +212,58 @@ async def _send_sd_api_request( async def _find_existing_trueconf_ticket(cn: str, requester_email: str = None) -> str | None: """ Найти существующую открытую заявку с темой 'Запрос доступа к Trueconf' для пользователя. - Ищет по ФИО (name), затем по email, затем по системному имени с пометкой пользователя. + Ищет по ФИО (name), затем по email, затем по CN в описании. + Статусы: Open (Открыто) и InProgress (В работе). """ subject = "Запрос доступа к Trueconf" try: - url = f"{SD_URL.rstrip('/')}/api/v3/requests?filter=status:Open&fields=id,subject,requester,description" + # Забираем все заявки с темой (без фильтра по статусу — проверяем в коде) + url = f"{SD_URL.rstrip('/')}/api/v3/requests?filter=subject:{subject}&fields=id,subject,requester,status" headers = {"TECHNICIAN_KEY": SD_TOKEN} async with httpx.AsyncClient(verify=False) as client: resp = await client.get(url, headers=headers, timeout=10.0) if resp.status_code == 200: data = resp.json() requests_list = data.get("requests", []) + logger.info(f"Found {len(requests_list)} tickets with subject='{subject}', searching for CN={cn}") for req in requests_list: - if subject not in req.get("subject", ""): + req_status = req.get("status", {}) + status_name = req_status.get("name", "") if isinstance(req_status, dict) else str(req_status) + req_id = req.get("id") + req_name = req_status.get("requester", {}).get("name", "") if isinstance(req_status.get("requester"), dict) else "" + logger.info(f"Ticket {req_id}: status={status_name}") + + # Пропускаем закрытые заявки + if status_name in ("Resolved", "Rejected", "Closed", "Solved"): continue - requester = req.get("requester", {}) - # 1. Поиск по ФИО - req_name = requester.get("name", "") or requester.get("display_name", "") - if cn and cn in req_name: - ticket_id = req.get("id") - logger.info(f"Found existing Trueconf ticket #{ticket_id} for CN={cn} (by name)") - return str(ticket_id) - # 2. Поиск по email - req_email = requester.get("email", "") or requester.get("email_id", "") - if req_email == requester_email and requester_email and requester_email not in (DEFAULT_REQUESTER, "ai@sibcem.ru"): - ticket_id = req.get("id") - logger.info(f"Found existing Trueconf ticket #{ticket_id} for {requester_email} (by email)") - return str(ticket_id) - # 3. Поиск по системному заявщику с пометкой пользователя в описании - req_desc = req.get("description", "") or "" - if f"[Пользователь: {cn}]" in req_desc: - ticket_id = req.get("id") - logger.info(f"Found existing Trueconf ticket #{ticket_id} for CN={cn} (by description note)") - return str(ticket_id) + + # Получаем детали заявки для поиска по описанию + detail_url = f"{SD_URL.rstrip('/')}/api/v3/requests/{req_id}" + detail_resp = await client.get(detail_url, headers=headers, timeout=10.0) + if detail_resp.status_code == 200: + detail = detail_resp.json() + requester = detail.get("requester", {}) + req_name = requester.get("name", "") or requester.get("display_name", "") + req_email = requester.get("email", "") or requester.get("email_id", "") + req_desc = (detail.get("description", "") or "")[:500] + logger.info(f"Detail ticket {req_id}: name='{req_name}', email='{req_email}', desc_preview='{req_desc[:100]}'") + + # 1. Поиск по ФИО в имени заявителя + if cn and cn in req_name: + logger.info(f"Match ticket {req_id} for CN={cn} (by name: {req_name})") + return str(req_id) + # 2. Поиск по email + if req_email == requester_email and requester_email and requester_email not in (DEFAULT_REQUESTER, "ai@sibcem.ru"): + logger.info(f"Match ticket {req_id} for {requester_email} (by email)") + return str(req_id) + # 3. Поиск по системному заявщику с пометкой пользователя в описании + if f"[Пользователь: {cn}]" in req_desc: + logger.info(f"Match ticket {req_id} for CN={cn} (by description note)") + return str(req_id) + # 4. Поиск по CN в описании заявки + if cn in req_desc: + logger.info(f"Match ticket {req_id} for CN={cn} (CN in description)") + return str(req_id) except Exception as e: logger.warning(f"Failed to search existing tickets: {e}") return None