199 lines
6.6 KiB
Python
199 lines
6.6 KiB
Python
"""
|
||
Обработчики создания заявки.
|
||
|
||
Поток:
|
||
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 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
|
||
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, 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,
|
||
)
|
||
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(data.get("pending_attachments", [])),
|
||
)
|
||
|
||
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 bot_send(
|
||
event.bot,
|
||
chat_id=chat_id,
|
||
user_id=bot_user["max_user_id"],
|
||
text=(
|
||
f"✅ Заявка №{ticket_id} успешно создана!\n"
|
||
"Наши специалисты свяжутся с вами в ближайшее время."
|
||
),
|
||
attachments=[main_menu_kb()],
|
||
)
|
||
|
||
# Fire-and-forget: отправка во внешнюю систему
|
||
async def _sync_external():
|
||
loop = asyncio.get_event_loop()
|
||
await loop.run_in_executor(None, ext.sync_ticket, ticket_id)
|
||
|
||
asyncio.create_task(_sync_external())
|