исправление чего-то
This commit is contained in:
parent
9f32da9a0a
commit
547a4a1dcb
11 changed files with 298 additions and 86 deletions
|
|
@ -2,13 +2,14 @@
|
||||||
Обработчики создания заявки.
|
Обработчики создания заявки.
|
||||||
|
|
||||||
Поток:
|
Поток:
|
||||||
1. Нажатие "Создать заявку" → переход в waiting_description
|
1. "Создать заявку" → waiting_description
|
||||||
2. Первое сообщение с текстом → сохранение описания, переход в collecting_attachments
|
2. Первое сообщение → сохранение описания, collecting_attachments
|
||||||
3. Дополнительные сообщения → накопление вложений
|
3. Доп. сообщения → накопление вложений
|
||||||
4. Нажатие "Готово" → сохранение заявки в БД, подтверждение
|
4. "Готово" → предпросмотр + кнопки подтверждения, confirming
|
||||||
|
5a. "Подтвердить" → сохранение в БД + Intradesk, сообщение с номером, главное меню
|
||||||
|
5b. "Продолжить редактирование" → обратно в collecting_attachments
|
||||||
|
5c. "Отменить создание" → очистка состояния, главное меню
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from maxapi.context import BaseContext
|
from maxapi.context import BaseContext
|
||||||
from maxapi.dispatcher import Router
|
from maxapi.dispatcher import Router
|
||||||
from maxapi.enums.attachment import AttachmentType
|
from maxapi.enums.attachment import AttachmentType
|
||||||
|
|
@ -20,19 +21,20 @@ from maxapi.types.attachments.attachment import (
|
||||||
from maxapi.types.updates.message_callback import MessageCallback
|
from maxapi.types.updates.message_callback import MessageCallback
|
||||||
from maxapi.types.updates.message_created import MessageCreated
|
from maxapi.types.updates.message_created import MessageCreated
|
||||||
|
|
||||||
from bot.keyboards import done_ticket_kb, main_menu_kb
|
from bot.handlers.menu import build_main_menu_text
|
||||||
|
from bot.keyboards import confirm_ticket_kb, done_ticket_kb, main_menu_kb
|
||||||
from bot.logging_setup import get_logger
|
from bot.logging_setup import get_logger
|
||||||
from bot.send_helper import bot_send
|
from bot.send_helper import bot_send
|
||||||
|
from bot.states import TicketStates
|
||||||
from database.connection import get_db
|
from database.connection import get_db
|
||||||
from database.queries.intradesk import get_intradesk_contact_id_for_phone
|
from database.queries.intradesk import get_intradesk_contact_id_for_phone
|
||||||
from database.queries.organizations import get_organization
|
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.ticket_attachments import add_attachment
|
||||||
from database.queries.tickets import create_ticket, set_intradesk_task_id
|
from database.queries.tickets import create_ticket, set_intradesk_task_id
|
||||||
from external.intradesk_api import create_intradesk_task
|
from external.intradesk_api import create_intradesk_task
|
||||||
|
|
||||||
|
logger = get_logger("bot.handlers.create_ticket")
|
||||||
|
|
||||||
router = Router(router_id="create_ticket")
|
router = Router(router_id="create_ticket")
|
||||||
|
|
||||||
_MEDIA_TYPES = {
|
_MEDIA_TYPES = {
|
||||||
|
|
@ -53,14 +55,67 @@ def _extract_attachments(event: MessageCreated) -> list[dict]:
|
||||||
att_type = getattr(att, "type", None)
|
att_type = getattr(att, "type", None)
|
||||||
if att_type not in _MEDIA_TYPES:
|
if att_type not in _MEDIA_TYPES:
|
||||||
continue
|
continue
|
||||||
url = None
|
url: str | None = None
|
||||||
|
filename: str | None = None
|
||||||
|
duration: int | None = None
|
||||||
payload = getattr(att, "payload", None)
|
payload = getattr(att, "payload", None)
|
||||||
if isinstance(payload, (PhotoAttachmentPayload, OtherAttachmentPayload)):
|
if isinstance(payload, PhotoAttachmentPayload):
|
||||||
url = payload.url
|
url = payload.url
|
||||||
result.append({"type": str(att_type), "url": url})
|
elif isinstance(payload, OtherAttachmentPayload):
|
||||||
|
url = payload.url
|
||||||
|
filename = (
|
||||||
|
getattr(payload, "filename", None)
|
||||||
|
or getattr(payload, "name", None)
|
||||||
|
or getattr(payload, "title", None)
|
||||||
|
)
|
||||||
|
if att_type == AttachmentType.AUDIO:
|
||||||
|
duration = (
|
||||||
|
getattr(payload, "duration", None)
|
||||||
|
or getattr(payload, "duration_ms", None)
|
||||||
|
)
|
||||||
|
result.append({"type": str(att_type), "url": url, "filename": filename, "duration": duration})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _att_label(att: dict) -> str:
|
||||||
|
"""Человекочитаемое описание вложения для предпросмотра."""
|
||||||
|
t = att.get("type", "")
|
||||||
|
if AttachmentType.IMAGE.value in t or "PHOTO" in t.upper():
|
||||||
|
return "🖼 Фото"
|
||||||
|
if AttachmentType.VIDEO.value in t:
|
||||||
|
return "📹 Видео"
|
||||||
|
if AttachmentType.AUDIO.value in t:
|
||||||
|
dur = att.get("duration")
|
||||||
|
if dur:
|
||||||
|
mins, secs = divmod(int(dur), 60)
|
||||||
|
return f"🎤 Голосовое ({mins:02d}:{secs:02d})"
|
||||||
|
return "🎤 Голосовое"
|
||||||
|
if AttachmentType.FILE.value in t:
|
||||||
|
fname = att.get("filename")
|
||||||
|
return f"📎 {fname}" if fname else "📎 Файл"
|
||||||
|
return "📎 Вложение"
|
||||||
|
|
||||||
|
|
||||||
|
def _preview_text(org_name: str | None, description: str, pending_atts: list[dict]) -> str:
|
||||||
|
"""Формирует текст предпросмотра заявки."""
|
||||||
|
task_name = org_name or description[:80]
|
||||||
|
lines = [
|
||||||
|
"📋 Предпросмотр заявки:",
|
||||||
|
"",
|
||||||
|
f"📄 {task_name}",
|
||||||
|
"",
|
||||||
|
description[:500] + ("…" if len(description) > 500 else ""),
|
||||||
|
]
|
||||||
|
if pending_atts:
|
||||||
|
lines += ["", "📎 Вложения:"]
|
||||||
|
for att in pending_atts:
|
||||||
|
lines.append(f" • {_att_label(att)}")
|
||||||
|
lines += ["", "Всё верно? Подтвердите или вернитесь к редактированию."]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 1: начало создания ────────────────────────────────────────────────────
|
||||||
|
|
||||||
@router.message_callback(F.callback.payload == "create_ticket")
|
@router.message_callback(F.callback.payload == "create_ticket")
|
||||||
async def cb_create_ticket(
|
async def cb_create_ticket(
|
||||||
event: MessageCallback, context: BaseContext, bot_user: dict
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
|
|
@ -87,6 +142,8 @@ async def cb_create_ticket(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 2: первое сообщение с описанием ──────────────────────────────────────
|
||||||
|
|
||||||
@router.message_created(states=TicketStates.waiting_description)
|
@router.message_created(states=TicketStates.waiting_description)
|
||||||
async def on_ticket_description(
|
async def on_ticket_description(
|
||||||
event: MessageCreated, context: BaseContext
|
event: MessageCreated, context: BaseContext
|
||||||
|
|
@ -99,10 +156,7 @@ async def on_ticket_description(
|
||||||
text = (body.text or "").strip() if body else ""
|
text = (body.text or "").strip() if body else ""
|
||||||
attachments = _extract_attachments(event)
|
attachments = _extract_attachments(event)
|
||||||
|
|
||||||
await context.update_data(
|
await context.update_data(description=text, pending_attachments=attachments)
|
||||||
description=text,
|
|
||||||
pending_attachments=attachments,
|
|
||||||
)
|
|
||||||
await context.set_state(TicketStates.collecting_attachments)
|
await context.set_state(TicketStates.collecting_attachments)
|
||||||
|
|
||||||
await bot_send(
|
await bot_send(
|
||||||
|
|
@ -117,6 +171,8 @@ async def on_ticket_description(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 3: дополнительные вложения/текст ─────────────────────────────────────
|
||||||
|
|
||||||
@router.message_created(states=TicketStates.collecting_attachments)
|
@router.message_created(states=TicketStates.collecting_attachments)
|
||||||
async def on_ticket_attachment(
|
async def on_ticket_attachment(
|
||||||
event: MessageCreated, context: BaseContext
|
event: MessageCreated, context: BaseContext
|
||||||
|
|
@ -133,10 +189,11 @@ async def on_ticket_attachment(
|
||||||
pending = data.get("pending_attachments", [])
|
pending = data.get("pending_attachments", [])
|
||||||
pending.extend(new_atts)
|
pending.extend(new_atts)
|
||||||
data["pending_attachments"] = pending
|
data["pending_attachments"] = pending
|
||||||
|
|
||||||
await context.set_data(data)
|
await context.set_data(data)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 4: "Готово" → предпросмотр ───────────────────────────────────────────
|
||||||
|
|
||||||
@router.message_callback(
|
@router.message_callback(
|
||||||
F.callback.payload == "done_ticket",
|
F.callback.payload == "done_ticket",
|
||||||
states=[TicketStates.waiting_description, TicketStates.collecting_attachments],
|
states=[TicketStates.waiting_description, TicketStates.collecting_attachments],
|
||||||
|
|
@ -162,33 +219,104 @@ async def cb_done_ticket(
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
ticket_id = await create_ticket(
|
await event.answer(notification="")
|
||||||
org_id=bot_user["org_id"],
|
|
||||||
created_by=bot_user["id"],
|
|
||||||
description=description,
|
|
||||||
)
|
|
||||||
pending_atts = data.get("pending_attachments", [])
|
pending_atts = data.get("pending_attachments", [])
|
||||||
logger.info(
|
|
||||||
"✅ ticket created | ticket_id=%s user_id=%s org_id=%s attachments=%s",
|
org = await get_organization(bot_user["org_id"])
|
||||||
ticket_id, bot_user["max_user_id"], bot_user["org_id"], len(pending_atts),
|
org_name = org["name"] if org else None
|
||||||
|
|
||||||
|
await context.set_state(TicketStates.confirming)
|
||||||
|
|
||||||
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
|
text=_preview_text(org_name, description, pending_atts),
|
||||||
|
attachments=[confirm_ticket_kb()],
|
||||||
)
|
)
|
||||||
|
|
||||||
for att in pending_atts:
|
|
||||||
await add_attachment(
|
# ── Шаг 5б: "Продолжить редактирование" ──────────────────────────────────────
|
||||||
ticket_id=ticket_id,
|
|
||||||
attachment_type=att["type"],
|
@router.message_callback(
|
||||||
attachment_url=att.get("url"),
|
F.callback.payload == "edit_ticket",
|
||||||
|
states=TicketStates.confirming,
|
||||||
|
)
|
||||||
|
async def cb_edit_ticket(
|
||||||
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
|
) -> None:
|
||||||
|
await event.answer(notification="")
|
||||||
|
await context.set_state(TicketStates.collecting_attachments)
|
||||||
|
|
||||||
|
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"
|
||||||
|
"Нажмите «Готово» когда будете готовы."
|
||||||
|
),
|
||||||
|
attachments=[done_ticket_kb()],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 5в: "Отменить создание" ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
@router.message_callback(
|
||||||
|
F.callback.payload == "cancel_ticket",
|
||||||
|
states=TicketStates.confirming,
|
||||||
|
)
|
||||||
|
async def cb_cancel_ticket(
|
||||||
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
|
) -> None:
|
||||||
|
await event.answer(notification="Создание отменено")
|
||||||
await context.clear()
|
await context.clear()
|
||||||
await event.answer(notification="Заявка создана!")
|
|
||||||
|
|
||||||
# ── Отправка в Intradesk ──────────────────────────────────────────────────
|
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=await build_main_menu_text(bot_user),
|
||||||
|
attachments=[main_menu_kb()],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Шаг 5а: "Подтвердить" → создание ─────────────────────────────────────────
|
||||||
|
|
||||||
|
@router.message_callback(
|
||||||
|
F.callback.payload == "confirm_ticket",
|
||||||
|
states=TicketStates.confirming,
|
||||||
|
)
|
||||||
|
async def cb_confirm_ticket(
|
||||||
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
|
) -> None:
|
||||||
|
# Защита от двойного нажатия: читаем данные и сбрасываем состояние атомарно.
|
||||||
|
# Повторный tap уже не пройдёт фильтр states=confirming.
|
||||||
|
data = await context.get_data()
|
||||||
|
await context.clear()
|
||||||
|
|
||||||
|
description = (data.get("description") or "").strip()
|
||||||
|
pending_atts = data.get("pending_attachments", [])
|
||||||
|
|
||||||
|
chat_id = event.message.recipient.chat_id if event.message else None
|
||||||
|
if chat_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
await event.answer(notification="Отправляем заявку…")
|
||||||
|
|
||||||
|
# ── Данные организации ────────────────────────────────────────────────────
|
||||||
org = await get_organization(bot_user["org_id"])
|
org = await get_organization(bot_user["org_id"])
|
||||||
org_name = org["name"] if org else None
|
org_name = org["name"] if org else None
|
||||||
intradesk_id: int | None = org.get("intradesk_id") if org else None
|
intradesk_id: int | None = org.get("intradesk_id") if org else None
|
||||||
|
|
||||||
# Находим id контактного лица в Intradesk по телефону пользователя
|
|
||||||
contact_person_id: int | None = None
|
contact_person_id: int | None = None
|
||||||
if intradesk_id and bot_user.get("phone"):
|
if intradesk_id and bot_user.get("phone"):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
@ -196,48 +324,75 @@ async def cb_done_ticket(
|
||||||
db, intradesk_id, bot_user["phone"]
|
db, intradesk_id, bot_user["phone"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ссылки на вложения (Max CDN) — добавятся в описание задачи
|
# ── Сохраняем локально ────────────────────────────────────────────────────
|
||||||
|
ticket_id = await create_ticket(
|
||||||
|
org_id=bot_user["org_id"],
|
||||||
|
created_by=bot_user["id"],
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
for att in pending_atts:
|
||||||
|
await add_attachment(
|
||||||
|
ticket_id=ticket_id,
|
||||||
|
attachment_type=att["type"],
|
||||||
|
attachment_url=att.get("url"),
|
||||||
|
)
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Отправка в Intradesk (ждём, чтобы получить номер) ────────────────────
|
||||||
|
task_name = org_name or description[:150]
|
||||||
att_urls = [a["url"] for a in pending_atts if a.get("url")]
|
att_urls = [a["url"] for a in pending_atts if a.get("url")]
|
||||||
|
|
||||||
async def _create_in_intradesk():
|
intradesk_result = await create_intradesk_task(
|
||||||
result = await create_intradesk_task(
|
name=task_name,
|
||||||
name=description[:150],
|
|
||||||
description=description,
|
description=description,
|
||||||
client_intradesk_id=intradesk_id,
|
client_intradesk_id=intradesk_id,
|
||||||
contact_person_id=contact_person_id,
|
contact_person_id=contact_person_id,
|
||||||
attachment_urls=att_urls or None,
|
attachment_urls=att_urls or None,
|
||||||
)
|
)
|
||||||
if result:
|
|
||||||
iid = result.get("id")
|
intradesk_number: int | None = None
|
||||||
|
if intradesk_result:
|
||||||
|
iid = (
|
||||||
|
intradesk_result.get("id") or intradesk_result.get("Id")
|
||||||
|
or intradesk_result.get("ID")
|
||||||
|
)
|
||||||
|
intradesk_number = (
|
||||||
|
intradesk_result.get("tasknumber") or intradesk_result.get("number")
|
||||||
|
or intradesk_result.get("Number") or intradesk_result.get("TaskNumber")
|
||||||
|
)
|
||||||
if iid:
|
if iid:
|
||||||
await set_intradesk_task_id(ticket_id, int(iid))
|
await set_intradesk_task_id(ticket_id, int(iid))
|
||||||
logger.info("Intradesk task created: id=%s for ticket_id=%s", iid, ticket_id)
|
logger.info(
|
||||||
|
"Intradesk task created: id=%s number=%s for ticket_id=%s",
|
||||||
asyncio.create_task(_create_in_intradesk())
|
iid, intradesk_number, ticket_id,
|
||||||
|
|
||||||
# ── Подтверждение пользователю ────────────────────────────────────────────
|
|
||||||
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Наши специалисты свяжутся с вами в ближайшее время."
|
lines = ["✅ Заявка успешно создана!"]
|
||||||
|
if intradesk_number:
|
||||||
|
lines.append(f"Номер заявки: #{intradesk_number}")
|
||||||
|
lines += ["", f"📄 {task_name}", ""]
|
||||||
|
lines.append(description[:500] + ("…" if len(description) > 500 else ""))
|
||||||
|
if pending_atts:
|
||||||
|
lines += ["", "📎 Вложения:"]
|
||||||
|
for att in pending_atts:
|
||||||
|
lines.append(f" • {_att_label(att)}")
|
||||||
|
lines += ["", "Наши специалисты свяжутся с вами в ближайшее время."]
|
||||||
|
|
||||||
await bot_send(
|
await bot_send(
|
||||||
event.bot,
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
user_id=bot_user["max_user_id"],
|
user_id=bot_user["max_user_id"],
|
||||||
text=confirm_text,
|
text="\n".join(lines),
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_text = f"🏠 {org_name}\nВыберите действие:" if org_name else "🏠 Главное меню\nВыберите действие:"
|
|
||||||
|
|
||||||
await bot_send(
|
await bot_send(
|
||||||
event.bot,
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
user_id=bot_user["max_user_id"],
|
user_id=bot_user["max_user_id"],
|
||||||
text=menu_text,
|
text=await build_main_menu_text(bot_user),
|
||||||
attachments=[main_menu_kb()],
|
attachments=[main_menu_kb()],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,43 @@ from maxapi.context import BaseContext
|
||||||
|
|
||||||
from bot.keyboards import main_menu_kb
|
from bot.keyboards import main_menu_kb
|
||||||
from bot.send_helper import bot_reply
|
from bot.send_helper import bot_reply
|
||||||
|
from database.connection import get_db
|
||||||
|
from database.queries.intradesk import (
|
||||||
|
count_active_tasks_for_local_org,
|
||||||
|
count_tasks_for_local_org,
|
||||||
|
)
|
||||||
from database.queries.organizations import get_organization
|
from database.queries.organizations import get_organization
|
||||||
|
|
||||||
router = Router(router_id="menu")
|
router = Router(router_id="menu")
|
||||||
|
|
||||||
|
|
||||||
async def _main_menu_text(bot_user: dict) -> str:
|
async def build_main_menu_text(bot_user: dict) -> str:
|
||||||
|
"""Формирует текст главного меню с именем объекта и статистикой заявок."""
|
||||||
org_id = bot_user.get("org_id")
|
org_id = bot_user.get("org_id")
|
||||||
|
org_name: str | None = None
|
||||||
|
|
||||||
if org_id:
|
if org_id:
|
||||||
org = await get_organization(org_id)
|
org = await get_organization(org_id)
|
||||||
org_name = org["name"] if org else None
|
org_name = org["name"] if org else None
|
||||||
else:
|
|
||||||
org_name = None
|
|
||||||
|
|
||||||
|
lines: list[str] = []
|
||||||
if org_name:
|
if org_name:
|
||||||
return f"🏠 {org_name}\nВыберите действие:"
|
lines.append(f"🏢 {org_name}")
|
||||||
return "🏠 Главное меню\nВыберите действие:"
|
else:
|
||||||
|
lines.append("🏠 Главное меню")
|
||||||
|
|
||||||
|
if org_id:
|
||||||
|
db = get_db()
|
||||||
|
total = await count_tasks_for_local_org(db, org_id)
|
||||||
|
active = await count_active_tasks_for_local_org(db, org_id)
|
||||||
|
|
||||||
|
if total == 0:
|
||||||
|
lines.append("Заявок пока нет.")
|
||||||
|
else:
|
||||||
|
lines.append(f"Заявок: {total} (активных: {active})")
|
||||||
|
|
||||||
|
lines.append("\nВыберите действие:")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@router.message_callback(F.callback.payload == "main_menu")
|
@router.message_callback(F.callback.payload == "main_menu")
|
||||||
|
|
@ -31,7 +52,7 @@ async def cb_main_menu(
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.answer(notification="")
|
await event.answer(notification="")
|
||||||
|
|
||||||
text = await _main_menu_text(bot_user)
|
text = await build_main_menu_text(bot_user)
|
||||||
await bot_reply(
|
await bot_reply(
|
||||||
event,
|
event,
|
||||||
text=text,
|
text=text,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ from database.queries.bot_users import (
|
||||||
)
|
)
|
||||||
from database.queries.org_users import find_by_phone
|
from database.queries.org_users import find_by_phone
|
||||||
from database.queries.message_log import log_message
|
from database.queries.message_log import log_message
|
||||||
from database.queries.organizations import get_organization
|
|
||||||
|
|
||||||
router = Router(router_id="start")
|
router = Router(router_id="start")
|
||||||
|
|
||||||
|
|
@ -224,13 +223,8 @@ async def on_contact_shared(
|
||||||
)
|
)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
|
|
||||||
org = await get_organization(org_user["org_id"])
|
from bot.handlers.menu import build_main_menu_text # local import to avoid circular
|
||||||
org_name = org["name"] if org else None
|
menu_text = "✅ Вы успешно авторизованы!\n\n" + await build_main_menu_text(bot_user)
|
||||||
menu_text = (
|
|
||||||
f"✅ Вы успешно авторизованы!\n\n🏠 {org_name}\nВыберите действие:"
|
|
||||||
if org_name
|
|
||||||
else "✅ Вы успешно авторизованы! Выберите действие:"
|
|
||||||
)
|
|
||||||
|
|
||||||
await bot_send(
|
await bot_send(
|
||||||
event.bot,
|
event.bot,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from maxapi.filters import F
|
||||||
from maxapi.types.updates.message_callback import MessageCallback
|
from maxapi.types.updates.message_callback import MessageCallback
|
||||||
|
|
||||||
from bot.filters import PayloadStartsWith
|
from bot.filters import PayloadStartsWith
|
||||||
from bot.keyboards import intradesk_tasks_list_kb, iticket_detail_kb
|
from bot.keyboards import intradesk_tasks_list_kb, iticket_detail_kb, menu_only_kb
|
||||||
from bot.send_helper import bot_reply
|
from bot.send_helper import bot_reply
|
||||||
from database.connection import get_db
|
from database.connection import get_db
|
||||||
from database.queries.intradesk import (
|
from database.queries.intradesk import (
|
||||||
|
|
@ -70,11 +70,11 @@ async def cb_view_tickets(
|
||||||
) -> None:
|
) -> None:
|
||||||
await event.answer(notification="")
|
await event.answer(notification="")
|
||||||
text, tasks, total = await _tasks_page_text(bot_user["org_id"], 0)
|
text, tasks, total = await _tasks_page_text(bot_user["org_id"], 0)
|
||||||
kb = intradesk_tasks_list_kb(tasks, page=0, total=total) if tasks else None
|
kb = intradesk_tasks_list_kb(tasks, page=0, total=total) if tasks else menu_only_kb()
|
||||||
await bot_reply(
|
await bot_reply(
|
||||||
event,
|
event,
|
||||||
text=text,
|
text=text,
|
||||||
attachments=[kb] if kb else None,
|
attachments=[kb],
|
||||||
user_id=bot_user["max_user_id"],
|
user_id=bot_user["max_user_id"],
|
||||||
chat_id=event.message.recipient.chat_id if event.message else None,
|
chat_id=event.message.recipient.chat_id if event.message else None,
|
||||||
)
|
)
|
||||||
|
|
@ -90,11 +90,11 @@ async def cb_itickets_page(
|
||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
page = 0
|
page = 0
|
||||||
text, tasks, total = await _tasks_page_text(bot_user["org_id"], page)
|
text, tasks, total = await _tasks_page_text(bot_user["org_id"], page)
|
||||||
kb = intradesk_tasks_list_kb(tasks, page=page, total=total) if tasks else None
|
kb = intradesk_tasks_list_kb(tasks, page=page, total=total) if tasks else menu_only_kb()
|
||||||
await bot_reply(
|
await bot_reply(
|
||||||
event,
|
event,
|
||||||
text=text,
|
text=text,
|
||||||
attachments=[kb] if kb else None,
|
attachments=[kb],
|
||||||
user_id=bot_user["max_user_id"],
|
user_id=bot_user["max_user_id"],
|
||||||
chat_id=event.message.recipient.chat_id if event.message else None,
|
chat_id=event.message.recipient.chat_id if event.message else None,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,26 @@ def contact_request_kb() -> Attachment:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_ticket_kb() -> Attachment:
|
||||||
|
"""Клавиатура подтверждения создания заявки."""
|
||||||
|
return (
|
||||||
|
InlineKeyboardBuilder()
|
||||||
|
.row(CallbackButton(text="✅ Подтвердить", payload="confirm_ticket"))
|
||||||
|
.row(CallbackButton(text="✏️ Продолжить редактирование", payload="edit_ticket"))
|
||||||
|
.row(CallbackButton(text="❌ Отменить создание", payload="cancel_ticket"))
|
||||||
|
.as_markup()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def menu_only_kb() -> Attachment:
|
||||||
|
"""Клавиатура с единственной кнопкой возврата в главное меню."""
|
||||||
|
return (
|
||||||
|
InlineKeyboardBuilder()
|
||||||
|
.row(CallbackButton(text="🏠 Главное меню", payload="main_menu"))
|
||||||
|
.as_markup()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def done_ticket_kb() -> Attachment:
|
def done_ticket_kb() -> Attachment:
|
||||||
return (
|
return (
|
||||||
InlineKeyboardBuilder()
|
InlineKeyboardBuilder()
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,4 @@ class AuthStates(StatesGroup):
|
||||||
class TicketStates(StatesGroup):
|
class TicketStates(StatesGroup):
|
||||||
waiting_description = State() # Ожидаем первое сообщение с описанием
|
waiting_description = State() # Ожидаем первое сообщение с описанием
|
||||||
collecting_attachments = State() # Накапливаем доп. файлы/текст
|
collecting_attachments = State() # Накапливаем доп. файлы/текст
|
||||||
|
confirming = State() # Предпросмотр — ждём подтверждения
|
||||||
|
|
|
||||||
BIN
data/support.db
BIN
data/support.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -330,6 +330,23 @@ async def count_tasks_for_local_org(
|
||||||
return row[0] if row else 0
|
return row[0] if row else 0
|
||||||
|
|
||||||
|
|
||||||
|
async def count_active_tasks_for_local_org(
|
||||||
|
db: aiosqlite.Connection, local_org_id: int
|
||||||
|
) -> int:
|
||||||
|
"""Количество незакрытых заявок организации (closed_at IS NULL)."""
|
||||||
|
async with db.execute(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM intradesk_tasks it
|
||||||
|
JOIN organizations o ON o.intradesk_id = it.client_id
|
||||||
|
WHERE o.id = ? AND it.closed_at IS NULL
|
||||||
|
""",
|
||||||
|
(local_org_id,),
|
||||||
|
) as cur:
|
||||||
|
row = await cur.fetchone()
|
||||||
|
return row[0] if row else 0
|
||||||
|
|
||||||
|
|
||||||
async def get_task_for_local_org(
|
async def get_task_for_local_org(
|
||||||
db: aiosqlite.Connection, task_id: int, local_org_id: int
|
db: aiosqlite.Connection, task_id: int, local_org_id: int
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
|
|
|
||||||
12
external/intradesk_api.py
vendored
12
external/intradesk_api.py
vendored
|
|
@ -138,11 +138,15 @@ async def create_intradesk_task(
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
result = resp.json()
|
result = resp.json()
|
||||||
logger.info(
|
# API может вернуть как "Number"/"Id" (PascalCase), так и "number"/"id"
|
||||||
"create_intradesk_task ok: id=%s number=%s",
|
task_id = result.get("id") or result.get("Id") or result.get("ID")
|
||||||
result.get("id"),
|
task_number = (
|
||||||
result.get("tasknumber") or result.get("number"),
|
result.get("tasknumber")
|
||||||
|
or result.get("number")
|
||||||
|
or result.get("Number")
|
||||||
|
or result.get("TaskNumber")
|
||||||
)
|
)
|
||||||
|
logger.info("create_intradesk_task ok: id=%s number=%s", task_id, task_number)
|
||||||
return result
|
return result
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("create_intradesk_task error: %s", exc)
|
logger.exception("create_intradesk_task error: %s", exc)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue