intra_max_chatbot/bot/handlers/create_ticket.py

243 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Обработчики создания заявки.
Поток:
1. Нажатие "Создать заявку" → переход в waiting_description
2. Первое сообщение с текстом → сохранение описания, переход в collecting_attachments
3. Дополнительные сообщения → накопление вложений
4. Нажатие "Готово" → сохранение заявки в БД, подтверждение
"""
import asyncio
from maxapi.context import BaseContext
from maxapi.dispatcher import Router
from maxapi.enums.attachment import AttachmentType
from maxapi.filters import F
from maxapi.types.attachments.attachment import (
OtherAttachmentPayload,
PhotoAttachmentPayload,
)
from maxapi.types.updates.message_callback import MessageCallback
from maxapi.types.updates.message_created import MessageCreated
from bot.keyboards import done_ticket_kb, main_menu_kb
from bot.logging_setup import get_logger
from bot.send_helper import bot_send
from database.connection import get_db
from database.queries.intradesk import get_intradesk_contact_id_for_phone
from database.queries.organizations import get_organization
from bot.states import TicketStates
logger = get_logger("bot.handlers.create_ticket")
from database.queries.ticket_attachments import add_attachment
from database.queries.tickets import create_ticket, set_intradesk_task_id
from external.intradesk_api import create_intradesk_task
router = Router(router_id="create_ticket")
_MEDIA_TYPES = {
AttachmentType.IMAGE,
AttachmentType.VIDEO,
AttachmentType.AUDIO,
AttachmentType.FILE,
}
def _extract_attachments(event: MessageCreated) -> list[dict]:
"""Извлекает метаданные вложений из сообщения."""
result = []
body = event.message.body
if not body or not body.attachments:
return result
for att in body.attachments:
att_type = getattr(att, "type", None)
if att_type not in _MEDIA_TYPES:
continue
url = None
payload = getattr(att, "payload", None)
if isinstance(payload, (PhotoAttachmentPayload, OtherAttachmentPayload)):
url = payload.url
result.append({"type": str(att_type), "url": url})
return result
@router.message_callback(F.callback.payload == "create_ticket")
async def cb_create_ticket(
event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
await context.set_state(TicketStates.waiting_description)
await context.set_data({"pending_attachments": []})
logger.info("📝 create_ticket started | user_id=%s", bot_user["max_user_id"])
await event.answer(notification="")
chat_id = event.message.recipient.chat_id if event.message else None
if chat_id is None:
return
await bot_send(
event.bot,
chat_id=chat_id,
user_id=bot_user["max_user_id"],
text=(
"📝 Опишите вашу проблему.\n\n"
"Вы также можете прикрепить файлы, фото или голосовые сообщения.\n"
"Когда закончите — нажмите кнопку «Готово»."
),
attachments=[done_ticket_kb()],
)
@router.message_created(states=TicketStates.waiting_description)
async def on_ticket_description(
event: MessageCreated, context: BaseContext
) -> None:
chat_id, user_id = event.get_ids()
if chat_id is None:
return
body = event.message.body
text = (body.text or "").strip() if body else ""
attachments = _extract_attachments(event)
await context.update_data(
description=text,
pending_attachments=attachments,
)
await context.set_state(TicketStates.collecting_attachments)
await bot_send(
event.bot,
chat_id=chat_id,
user_id=user_id,
text=(
"✏️ Описание принято. Вы можете добавить ещё файлы "
"или нажмите «Готово» для отправки заявки."
),
attachments=[done_ticket_kb()],
)
@router.message_created(states=TicketStates.collecting_attachments)
async def on_ticket_attachment(
event: MessageCreated, context: BaseContext
) -> None:
data = await context.get_data()
body = event.message.body
extra_text = (body.text or "").strip() if body else ""
new_atts = _extract_attachments(event)
if extra_text:
current = data.get("description", "")
data["description"] = (current + "\n" + extra_text).strip()
pending = data.get("pending_attachments", [])
pending.extend(new_atts)
data["pending_attachments"] = pending
await context.set_data(data)
@router.message_callback(
F.callback.payload == "done_ticket",
states=[TicketStates.waiting_description, TicketStates.collecting_attachments],
)
async def cb_done_ticket(
event: MessageCallback, context: BaseContext, bot_user: dict
) -> None:
data = await context.get_data()
description = (data.get("description") or "").strip()
chat_id = event.message.recipient.chat_id if event.message else None
if chat_id is None:
return
if not description:
await event.answer(notification="Сначала опишите проблему!")
await bot_send(
event.bot,
chat_id=chat_id,
user_id=bot_user["max_user_id"],
text="❗ Пожалуйста, сначала напишите описание проблемы.",
attachments=[done_ticket_kb()],
)
return
ticket_id = await create_ticket(
org_id=bot_user["org_id"],
created_by=bot_user["id"],
description=description,
)
pending_atts = data.get("pending_attachments", [])
logger.info(
"✅ ticket created | ticket_id=%s user_id=%s org_id=%s attachments=%s",
ticket_id, bot_user["max_user_id"], bot_user["org_id"], len(pending_atts),
)
for att in pending_atts:
await add_attachment(
ticket_id=ticket_id,
attachment_type=att["type"],
attachment_url=att.get("url"),
)
await context.clear()
await event.answer(notification="Заявка создана!")
# ── Отправка в Intradesk ──────────────────────────────────────────────────
org = await get_organization(bot_user["org_id"])
org_name = org["name"] if org else None
intradesk_id: int | None = org.get("intradesk_id") if org else None
# Находим id контактного лица в Intradesk по телефону пользователя
contact_person_id: int | None = None
if intradesk_id and bot_user.get("phone"):
db = get_db()
contact_person_id = await get_intradesk_contact_id_for_phone(
db, intradesk_id, bot_user["phone"]
)
# Ссылки на вложения (Max CDN) — добавятся в описание задачи
att_urls = [a["url"] for a in pending_atts if a.get("url")]
async def _create_in_intradesk():
result = await create_intradesk_task(
name=description[:150],
description=description,
client_intradesk_id=intradesk_id,
contact_person_id=contact_person_id,
attachment_urls=att_urls or None,
)
if result:
iid = result.get("id")
if iid:
await set_intradesk_task_id(ticket_id, int(iid))
logger.info("Intradesk task created: id=%s for ticket_id=%s", iid, ticket_id)
asyncio.create_task(_create_in_intradesk())
# ── Подтверждение пользователю ────────────────────────────────────────────
att_count = len(pending_atts)
confirm_text = (
f"✅ Заявка успешно создана!\n"
f"Описание: {description[:200]}{'' if len(description) > 200 else ''}"
)
if att_count:
confirm_text += f"\nВложений: {att_count}"
confirm_text += "\n\nНаши специалисты свяжутся с вами в ближайшее время."
await bot_send(
event.bot,
chat_id=chat_id,
user_id=bot_user["max_user_id"],
text=confirm_text,
)
menu_text = f"🏠 {org_name}\nВыберите действие:" if org_name else "🏠 Главное меню\nВыберите действие:"
await bot_send(
event.bot,
chat_id=chat_id,
user_id=bot_user["max_user_id"],
text=menu_text,
attachments=[main_menu_kb()],
)