243 lines
8.7 KiB
Python
243 lines
8.7 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 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()],
|
||
)
|