""" Обработчики создания заявки. Поток: 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()], )