v1.6.0: max_bot fixes — feature keys, flush→commit, test-run, categories, broadcast page, proxy error handling, deploy scripts

This commit is contained in:
2026-05-24 07:50:38 +03:00
parent bd048ea23d
commit 493e0b37a1
127 changed files with 6082 additions and 65 deletions
+148
View File
@@ -0,0 +1,148 @@
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.models import (
BotNotification, BotNotificationSubscription, BotUser,
)
from app.settings_cache import SettingsCache
try:
from app.models.models import User
except ImportError:
User = None
async def _get_max_user_id_by_role(db: AsyncSession, role: str) -> int | None:
if User is None:
return None
result = await db.execute(
select(User.max_user_id).where(
User.role == role, User.is_active == True,
User.max_user_id.isnot(None)
).limit(1)
)
return result.scalar_one_or_none()
async def _get_user_max_ids_by_role(db: AsyncSession, role: str) -> list[int]:
if User is None:
return []
result = await db.execute(
select(User.max_user_id).where(
User.role == role, User.is_active == True,
User.max_user_id.isnot(None)
)
)
return [row[0] for row in result.fetchall() if row[0]]
async def _log_notification(db: AsyncSession, ntype: str, recipient_type: str,
recipient_max_user_id: int | None, title: str, body: str,
related_type: str = "", related_id: int | None = None):
n = BotNotification(
type=ntype, recipient_type=recipient_type,
recipient_max_user_id=recipient_max_user_id,
title=title, body=body,
related_type=related_type, related_id=related_id
)
db.add(n)
await db.flush()
return n
async def notify_operator(db: AsyncSession, max_api, text: str, title: str = "",
related_type: str = "", related_id: int | None = None):
max_ids = await _get_user_max_ids_by_role(db, "owner")
sent_count = 0
for mid in max_ids:
try:
await max_api.send_message(user_id=mid, text=text, format="markdown")
sent_count += 1
except Exception:
pass
await _log_notification(
db, "operator_alert", "operator", max_ids[0] if max_ids else None,
title or "Уведомление оператору", text,
related_type, related_id
)
return sent_count
async def notify_level2(db: AsyncSession, max_api, text: str, title: str = "",
related_type: str = "", related_id: int | None = None):
max_ids = await _get_user_max_ids_by_role(db, "engineer")
sent_count = 0
for mid in max_ids:
try:
await max_api.send_message(user_id=mid, text=text, format="markdown")
sent_count += 1
except Exception:
pass
await _log_notification(
db, "level2_alert", "level2", max_ids[0] if max_ids else None,
title or "Уведомление инженеру", text,
related_type, related_id
)
return sent_count
async def notify_client_status_change(db: AsyncSession, max_api,
bot_user: BotUser, conv_id: int,
new_status: str):
text = f"Статус вашего обращения №{conv_id} изменён на «{new_status}»."
try:
await max_api.send_message(user_id=bot_user.max_user_id, text=text, format="markdown")
except Exception:
pass
await _log_notification(
db, "status_change", "client", bot_user.max_user_id,
"Статус обращения", text, "conversation", conv_id
)
async def notify_ticket_created(db: AsyncSession, ticket, max_api):
ticket_text = (
f"🆕 **Новый тикет {ticket.ticket_number}**\n"
f"Приоритет: {ticket.priority}\n"
f"Описание: {ticket.description[:200]}\n"
f"Обращение №{ticket.id}"
)
await notify_level2(
db, max_api, ticket_text,
title=f"Тикет {ticket.ticket_number}",
related_type="ticket", related_id=ticket.id
)
async def notify_handoff_escalation(db: AsyncSession, max_api,
bot_user: BotUser, conv_id: int,
text: str = ""):
msg = (
f"🔴 **Эскалация оператору**\n"
f"Пользователь: {bot_user.first_name or ''} (ID {bot_user.max_user_id})\n"
f"Обращение №{conv_id}\n"
f"Текст: {text[:200] if text else ''}"
)
await notify_operator(
db, max_api, msg,
title="Эскалация",
related_type="conversation", related_id=conv_id
)
async def get_subscribed_users(db: AsyncSession, notify_type: str = "broadcast") -> list[BotUser]:
col_map = {
"broadcast": BotNotificationSubscription.notify_on_broadcast,
"status_change": BotNotificationSubscription.notify_on_status_change,
"reply": BotNotificationSubscription.notify_on_reply,
}
col = col_map.get(notify_type)
if col is None:
return []
result = await db.execute(
select(BotUser).join(
BotNotificationSubscription,
BotNotificationSubscription.user_id == BotUser.id
).where(col == True)
)
return list(result.scalars().all())