72b6879f4b
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
import datetime
|
||
import logging
|
||
from sqlalchemy import select
|
||
from app.database import async_session
|
||
from app.models import BotUser, BotConversation, BotMessage, BotTicket, BotTicketMessage, BotTicketStatus
|
||
from app.max_api import max_api
|
||
from app.settings_cache import settings_cache
|
||
from app.config_reader import config_reader
|
||
from app.email_sender import email_sender
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def handle_inquiry(user_id: int, conv_id: int, text: str) -> None:
|
||
async with async_session() as db:
|
||
result = await db.execute(
|
||
select(BotConversation).where(BotConversation.id == conv_id)
|
||
)
|
||
conv = result.scalar_one_or_none()
|
||
if not conv:
|
||
return
|
||
|
||
user_result = await db.execute(select(BotUser).where(BotUser.id == user_id))
|
||
user = user_result.scalar_one_or_none()
|
||
|
||
conv.inquiry_text = text
|
||
conv.state = "escalated"
|
||
await db.commit()
|
||
|
||
await _finalize_ticket(user_id, conv, user, text)
|
||
|
||
|
||
async def _finalize_ticket(
|
||
user_id: int,
|
||
conv: BotConversation,
|
||
user: BotUser,
|
||
inquiry_text: str,
|
||
) -> None:
|
||
async with async_session() as db:
|
||
user_name = user.first_name or user.last_name or ""
|
||
ticket_title = f"Обращение от {user_name or 'клиента'} ({user_id})"
|
||
if len(inquiry_text) > 100:
|
||
ticket_title = inquiry_text[:97] + "..."
|
||
|
||
ticket = BotTicket(
|
||
user_id=user_id,
|
||
title=ticket_title,
|
||
description=inquiry_text,
|
||
priority="normal",
|
||
status="Новая",
|
||
created_by="bot",
|
||
)
|
||
db.add(ticket)
|
||
await db.commit()
|
||
await db.refresh(ticket)
|
||
|
||
msg = BotTicketMessage(
|
||
ticket_id=ticket.id,
|
||
direction="incoming",
|
||
text=inquiry_text,
|
||
)
|
||
db.add(msg)
|
||
|
||
status_log = BotTicketStatus(
|
||
ticket_id=ticket.id,
|
||
new_status="Новая",
|
||
changed_by="bot",
|
||
)
|
||
db.add(status_log)
|
||
|
||
conv_result = await db.execute(
|
||
select(BotConversation).where(BotConversation.id == conv.id)
|
||
)
|
||
conv_obj = conv_result.scalar_one_or_none()
|
||
if conv_obj:
|
||
conv_obj.state = "completed"
|
||
|
||
await db.commit()
|
||
|
||
async with async_session() as db2:
|
||
user_obj = await db2.get(BotUser, user_id)
|
||
if user_obj:
|
||
user_obj.total_tickets = (user_obj.total_tickets or 0) + 1
|
||
await db2.commit()
|
||
|
||
user_info = f"ID: {user_id}\nИмя: {user.first_name or 'не указано'} {user.last_name or ''}"
|
||
contact = user.phone or "не указан"
|
||
history = await _get_conversation_history(conv.id)
|
||
|
||
support_email = await settings_cache.get("support_email", "zakaz@aegisone.ru")
|
||
email_subject = await settings_cache.get(
|
||
"email_subject",
|
||
"Обращение через Виртуального помощника AegisOne Engineering",
|
||
)
|
||
|
||
email_sender.send_ticket_escalation(
|
||
to=support_email,
|
||
subject=email_subject,
|
||
user_info=user_info,
|
||
contact=contact,
|
||
inquiry=inquiry_text,
|
||
history=history,
|
||
)
|
||
|
||
await max_api.send_message(
|
||
user_id,
|
||
f"Зафиксировала: {inquiry_text}\n\n"
|
||
f"Ваша заявка отправлена инженеру. В ближайшее время он обязательно "
|
||
f"свяжется с вами.",
|
||
)
|
||
|
||
|
||
async def _get_conversation_history(conv_id: int) -> str:
|
||
async with async_session() as db:
|
||
result = await db.execute(
|
||
select(BotMessage)
|
||
.where(BotMessage.conversation_id == conv_id)
|
||
.order_by(BotMessage.id)
|
||
)
|
||
messages = result.scalars().all()
|
||
|
||
lines = []
|
||
for msg in messages:
|
||
direction = "Клиент" if msg.direction == "incoming" else "Бот"
|
||
time_str = msg.created_at.strftime("%d.%m.%Y %H:%M") if msg.created_at else ""
|
||
lines.append(f"[{time_str}] {direction}: {msg.text or ''}")
|
||
|
||
return "\n".join(lines)
|