intra_max_chatbot/bot/handlers/create_ticket.py
2026-03-26 00:25:37 +03:00

180 lines
6 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.states import TicketStates
from database.queries.ticket_attachments import add_attachment
from database.queries.tickets import create_ticket
import external.client as ext
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
) -> None:
await context.set_state(TicketStates.waiting_description)
await context.set_data({"pending_attachments": []})
await event.answer(notification="")
chat_id = event.message.recipient.chat_id if event.message else None
if chat_id is None:
return
await event.bot.send_message(
chat_id=chat_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, _ = 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 event.bot.send_message(
chat_id=chat_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 event.bot.send_message(
chat_id=chat_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,
)
for att in data.get("pending_attachments", []):
await add_attachment(
ticket_id=ticket_id,
attachment_type=att["type"],
attachment_url=att.get("url"),
)
await context.clear()
await event.answer(notification="Заявка создана!")
await event.bot.send_message(
chat_id=chat_id,
text=(
f"✅ Заявка №{ticket_id} успешно создана!\n"
"Наши специалисты свяжутся с вами в ближайшее время."
),
attachments=[main_menu_kb()],
)
# Fire-and-forget: отправка во внешнюю систему
loop = asyncio.get_event_loop()
asyncio.create_task(
loop.run_in_executor(None, ext.sync_ticket, ticket_id)
)