Merge pull request #273 from Fr1ngg/revert-272-4r6xny-bedolaga/add-daily-summary-report-feature

Revert "Add automated admin reports"
This commit is contained in:
Egor
2025-09-24 07:25:53 +03:00
committed by GitHub
12 changed files with 5 additions and 504 deletions

View File

@@ -14,8 +14,6 @@ ADMIN_NOTIFICATIONS_ENABLED=true
ADMIN_NOTIFICATIONS_CHAT_ID=-1001234567890 # Замени на ID твоего канала (-100) - ПРЕФИКС ЗАКРЫТОГО КАНАЛА! ВСТАВИТЬ СВОЙ ID СРАЗУ ПОСЛЕ (-100) БЕЗ ПРОБЕЛОВ!
ADMIN_NOTIFICATIONS_TOPIC_ID=123 # Опционально: ID топика
ADMIN_NOTIFICATIONS_TICKET_TOPIC_ID=126 # Опционально: ID топика для тикетов
ADMIN_REPORTS_TOPIC_ID=130 # Опционально: отдельный ID топика для отчетов
ADMIN_REPORTS_TIME_MOSCOW=09:00 # Время ежедневного отчета (МСК)
# Обязательная подписка на канал
CHANNEL_SUB_ID= # Опционально ID твоего канала (-100)
CHANNEL_IS_REQUIRED_SUB=false # Обязательна ли подписка на канал

View File

@@ -254,8 +254,6 @@ ADMIN_NOTIFICATIONS_ENABLED=true
ADMIN_NOTIFICATIONS_CHAT_ID=-1001234567890 # Замени на ID твоего канала (-100) - ПРЕФИКС ЗАКРЫТОГО КАНАЛА! ВСТАВИТЬ СВОЙ ID СРАЗУ ПОСЛЕ (-100) БЕЗ ПРОБЕЛОВ!
ADMIN_NOTIFICATIONS_TOPIC_ID=123 # Опционально: ID топика
ADMIN_NOTIFICATIONS_TICKET_TOPIC_ID=126 # Опционально: ID топика для тикетов
ADMIN_REPORTS_TOPIC_ID=130 # Опционально: отдельный ID топика для отчетов
ADMIN_REPORTS_TIME_MOSCOW=09:00 # Время ежедневного отчета (МСК)
# Обязательная подписка на канал
CHANNEL_SUB_ID= # Опционально ID твоего канала (-100)
CHANNEL_IS_REQUIRED_SUB=false # Обязательна ли подписка на канал
@@ -971,12 +969,8 @@ docker compose down -v --remove-orphans
ADMIN_NOTIFICATIONS_ENABLED=true
ADMIN_NOTIFICATIONS_CHAT_ID=-1001234567890 # ID канала/группы
ADMIN_NOTIFICATIONS_TOPIC_ID=123 # ID топика (опционально)
ADMIN_REPORTS_TOPIC_ID=130 # ID топика для отчетов (опционально)
ADMIN_REPORTS_TIME_MOSCOW=09:00 # Время ежедневного отчета (МСК)
```
> ⚙️ Бот автоматически отправит ежедневный отчет за предыдущие сутки в указанное время (по МСК). Если `ADMIN_REPORTS_TOPIC_ID` не задан, отчеты будут приходить в основной топик уведомлений. В админ-панели доступен раздел «Отчеты» для ручной отправки ежедневных, недельных и месячных сводок.
#### 2. Создание канала
1. **Создайте приватный канал** или группу для уведомлений

View File

@@ -38,7 +38,6 @@ from app.handlers.admin import (
backup as admin_backup,
welcome_text as admin_welcome_text,
tickets as admin_tickets,
reports as admin_reports,
)
from app.handlers.stars_payments import register_stars_handlers
@@ -140,7 +139,6 @@ async def setup_bot() -> tuple[Bot, Dispatcher]:
admin_backup.register_handlers(dp)
admin_welcome_text.register_welcome_text_handlers(dp)
admin_tickets.register_handlers(dp)
admin_reports.register_handlers(dp)
common.register_handlers(dp)
register_stars_handlers(dp)
logger.info("⭐ Зарегистрированы обработчики Telegram Stars платежей")

View File

@@ -2,7 +2,6 @@ import os
import re
import html
from collections import defaultdict
from datetime import time as dt_time
from typing import List, Optional, Union, Dict
from pydantic_settings import BaseSettings
from pydantic import field_validator, Field
@@ -27,8 +26,6 @@ class Settings(BaseSettings):
ADMIN_NOTIFICATIONS_CHAT_ID: Optional[str] = None
ADMIN_NOTIFICATIONS_TOPIC_ID: Optional[int] = None
ADMIN_NOTIFICATIONS_TICKET_TOPIC_ID: Optional[int] = None
ADMIN_REPORTS_TOPIC_ID: Optional[int] = None
ADMIN_REPORTS_TIME_MOSCOW: str = "09:00"
CHANNEL_SUB_ID: Optional[str] = None
CHANNEL_LINK: Optional[str] = None
@@ -640,26 +637,6 @@ class Settings(BaseSettings):
return (self.ADMIN_NOTIFICATIONS_ENABLED and
self.get_admin_notifications_chat_id() is not None)
def get_admin_reports_topic_id(self) -> Optional[int]:
if not self.ADMIN_REPORTS_TOPIC_ID:
return None
try:
return int(self.ADMIN_REPORTS_TOPIC_ID)
except (ValueError, TypeError):
return None
def get_admin_reports_time(self) -> dt_time:
raw_value = (self.ADMIN_REPORTS_TIME_MOSCOW or "09:00").strip()
try:
hours_str, minutes_str = raw_value.split(":", maxsplit=1)
hours = max(0, min(23, int(hours_str)))
minutes = max(0, min(59, int(minutes_str)))
return dt_time(hour=hours, minute=minutes)
except (ValueError, AttributeError):
return dt_time(hour=9, minute=0)
def get_backup_send_chat_id(self) -> Optional[int]:
if not self.BACKUP_SEND_CHAT_ID:
return None

View File

@@ -12,8 +12,7 @@ from app.keyboards.admin import (
get_admin_communications_submenu_keyboard,
get_admin_support_submenu_keyboard,
get_admin_settings_submenu_keyboard,
get_admin_system_submenu_keyboard,
get_admin_reports_keyboard,
get_admin_system_submenu_keyboard
)
from app.localization.texts import get_texts
from app.handlers.admin import support_settings as support_settings_handlers
@@ -145,23 +144,6 @@ async def show_support_submenu(
await callback.answer()
@admin_required
@error_handler
async def show_reports_submenu(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession
):
texts = get_texts(db_user.language)
await callback.message.edit_text(
f"📈 **{texts.ADMIN_REPORTS}**\n\n" + texts.ADMIN_REPORTS_MENU_HINT,
reply_markup=get_admin_reports_keyboard(db_user.language),
parse_mode="Markdown"
)
await callback.answer()
# Moderator panel entry (from main menu quick button)
async def show_moderator_panel(
callback: types.CallbackQuery,
@@ -424,11 +406,6 @@ def register_handlers(dp: Dispatcher):
show_support_audit,
F.data.in_(["admin_support_audit"]) | F.data.startswith("admin_support_audit_page_")
)
dp.callback_query.register(
show_reports_submenu,
F.data == "admin_submenu_reports"
)
dp.callback_query.register(
show_settings_submenu,

View File

@@ -1,95 +0,0 @@
import logging
from aiogram import Dispatcher, types, F
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import User
from app.localization.texts import get_texts
from app.services.report_service import report_service, ReportPeriod
from app.utils.decorators import admin_required, error_handler
logger = logging.getLogger(__name__)
async def _send_report(
callback: types.CallbackQuery,
db_user: User,
period: ReportPeriod,
success_message: str,
error_message: str,
):
success, _ = await report_service.send_report(period)
if success:
logger.info("Админ %s отправил отчет %s", db_user.id, period.value)
await callback.answer(success_message)
else:
logger.error("Не удалось отправить отчет %s по запросу админа %s", period.value, db_user.id)
await callback.answer(error_message, show_alert=True)
@admin_required
@error_handler
async def send_daily_report(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
texts = get_texts(db_user.language)
await _send_report(
callback,
db_user,
ReportPeriod.DAILY,
texts.ADMIN_REPORTS_SENT,
texts.ADMIN_REPORTS_ERROR,
)
@admin_required
@error_handler
async def send_weekly_report(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
texts = get_texts(db_user.language)
await _send_report(
callback,
db_user,
ReportPeriod.WEEKLY,
texts.ADMIN_REPORTS_SENT,
texts.ADMIN_REPORTS_ERROR,
)
@admin_required
@error_handler
async def send_monthly_report(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
texts = get_texts(db_user.language)
await _send_report(
callback,
db_user,
ReportPeriod.MONTHLY,
texts.ADMIN_REPORTS_SENT,
texts.ADMIN_REPORTS_ERROR,
)
def register_handlers(dp: Dispatcher) -> None:
dp.callback_query.register(
send_daily_report,
F.data == "admin_report_daily",
)
dp.callback_query.register(
send_weekly_report,
F.data == "admin_report_weekly",
)
dp.callback_query.register(
send_monthly_report,
F.data == "admin_report_monthly",
)

View File

@@ -10,7 +10,6 @@ def get_admin_main_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👥 Юзеры/Подписки", callback_data="admin_submenu_users")],
[InlineKeyboardButton(text="💰 Промокоды/Статистика", callback_data="admin_submenu_promo")],
[InlineKeyboardButton(text=texts.ADMIN_REPORTS, callback_data="admin_submenu_reports")],
[InlineKeyboardButton(text="🛟 Поддержка", callback_data="admin_submenu_support")],
[InlineKeyboardButton(text="📨 Сообщения", callback_data="admin_submenu_communications")],
[InlineKeyboardButton(text="⚙️ Настройки", callback_data="admin_submenu_settings")],
@@ -92,17 +91,6 @@ def get_admin_support_submenu_keyboard(language: str = "ru") -> InlineKeyboardMa
])
def get_admin_reports_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
texts = get_texts(language)
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=texts.ADMIN_REPORTS_DAILY, callback_data="admin_report_daily")],
[InlineKeyboardButton(text=texts.ADMIN_REPORTS_WEEKLY, callback_data="admin_report_weekly")],
[InlineKeyboardButton(text=texts.ADMIN_REPORTS_MONTHLY, callback_data="admin_report_monthly")],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_panel")]
])
def get_admin_settings_submenu_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
texts = get_texts(language)

View File

@@ -20,7 +20,6 @@ class AdminNotificationService:
self.chat_id = getattr(settings, 'ADMIN_NOTIFICATIONS_CHAT_ID', None)
self.topic_id = getattr(settings, 'ADMIN_NOTIFICATIONS_TOPIC_ID', None)
self.ticket_topic_id = getattr(settings, 'ADMIN_NOTIFICATIONS_TICKET_TOPIC_ID', None)
self.reports_topic_id = settings.get_admin_reports_topic_id()
self.enabled = getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False)
async def _get_referrer_info(self, db: AsyncSession, referred_by_id: Optional[int]) -> str:
@@ -307,18 +306,11 @@ class AdminNotificationService:
logger.error(f"Ошибка отправки уведомления о продлении: {e}")
return False
async def _send_message(
self,
text: str,
reply_markup: types.InlineKeyboardMarkup | None = None,
*,
ticket_event: bool = False,
topic_id: Optional[int] = None
) -> bool:
async def _send_message(self, text: str, reply_markup: types.InlineKeyboardMarkup | None = None, *, ticket_event: bool = False) -> bool:
if not self.chat_id:
logger.warning("ADMIN_NOTIFICATIONS_CHAT_ID не настроен")
return False
try:
message_kwargs = {
'chat_id': self.chat_id,
@@ -329,9 +321,7 @@ class AdminNotificationService:
# route to ticket-specific topic if provided
thread_id = None
if topic_id:
thread_id = topic_id
elif ticket_event and self.ticket_topic_id:
if ticket_event and self.ticket_topic_id:
thread_id = self.ticket_topic_id
elif self.topic_id:
thread_id = self.topic_id
@@ -339,7 +329,7 @@ class AdminNotificationService:
message_kwargs['message_thread_id'] = thread_id
if reply_markup is not None:
message_kwargs['reply_markup'] = reply_markup
await self.bot.send_message(**message_kwargs)
logger.info(f"Уведомление отправлено в чат {self.chat_id}")
return True
@@ -356,16 +346,6 @@ class AdminNotificationService:
def _is_enabled(self) -> bool:
return self.enabled and bool(self.chat_id)
def is_enabled(self) -> bool:
return self._is_enabled()
async def send_report_message(self, text: str, *, topic_id: Optional[int] = None) -> bool:
if not self._is_enabled():
return False
effective_topic = topic_id or self.reports_topic_id or self.topic_id
return await self._send_message(text, topic_id=effective_topic)
def _get_payment_method_display(self, payment_method: Optional[str]) -> str:
method_names = {

View File

@@ -1,284 +0,0 @@
import asyncio
import logging
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional, Tuple
from zoneinfo import ZoneInfo
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.database import AsyncSessionLocal
from app.database.models import (
Subscription,
SubscriptionStatus,
Transaction,
TransactionType,
)
from app.services.admin_notification_service import AdminNotificationService
logger = logging.getLogger(__name__)
class ReportPeriod(Enum):
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
@dataclass
class ReportPeriodInfo:
start_msk: datetime
end_msk: datetime
title: str
caption: str
range_caption: str
emoji: str
class ReportService:
def __init__(self) -> None:
self.notification_service: Optional[AdminNotificationService] = None
self._task: Optional[asyncio.Task] = None
self._stop_event = asyncio.Event()
self._moscow_tz = ZoneInfo("Europe/Moscow")
self._utc_tz = ZoneInfo("UTC")
def set_notification_service(self, service: AdminNotificationService) -> None:
self.notification_service = service
async def start(self) -> Optional[asyncio.Task]:
if self._task and not self._task.done():
return self._task
if not self.notification_service or not self.notification_service.is_enabled():
logger.info("Сервис отчетов не запущен: админ-уведомления отключены или не настроен чат")
return None
self._stop_event.clear()
self._task = asyncio.create_task(self._scheduler_loop())
logger.info("Сервис отчетов запущен")
return self._task
async def stop(self) -> None:
if not self._task:
return
self._stop_event.set()
try:
await self._task
finally:
self._task = None
self._stop_event.clear()
logger.info("Сервис отчетов остановлен")
async def send_report(self, period: ReportPeriod) -> Tuple[bool, str]:
text, _ = await self.generate_report(period)
if not text:
return False, text
if not self.notification_service or not self.notification_service.is_enabled():
logger.warning("Отчет не отправлен: сервис админ-уведомлений недоступен")
return False, text
success = await self.notification_service.send_report_message(text)
if success:
logger.info("Отчет %s отправлен", period.value)
else:
logger.error("Не удалось отправить отчет %s", period.value)
return success, text
async def generate_report(self, period: ReportPeriod) -> Tuple[str, dict]:
info = self._get_period_info(period)
if not info:
return "", {}
start_utc = info.start_msk.astimezone(self._utc_tz).replace(tzinfo=None)
end_utc = info.end_msk.astimezone(self._utc_tz).replace(tzinfo=None)
async with AsyncSessionLocal() as session:
stats = await self._collect_stats(session, start_utc, end_utc)
text = self._format_report(info, stats)
return text, stats
async def _scheduler_loop(self) -> None:
while not self._stop_event.is_set():
next_run = self._get_next_run_datetime()
now_utc = datetime.now(self._utc_tz)
wait_seconds = max(0, (next_run - now_utc).total_seconds())
try:
await asyncio.wait_for(self._stop_event.wait(), timeout=wait_seconds)
break
except asyncio.TimeoutError:
pass
if self._stop_event.is_set():
break
try:
await self.send_report(ReportPeriod.DAILY)
except Exception as error: # pragma: no cover - defensive logging
logger.error("Ошибка отправки ежедневного отчета: %s", error, exc_info=True)
async def _collect_stats(self, session: AsyncSession, start: datetime, end: datetime) -> dict:
now_utc = datetime.utcnow()
total_trials_query = select(func.count()).select_from(Subscription).where(
Subscription.is_trial.is_(True),
Subscription.end_date > now_utc,
Subscription.status.in_([
SubscriptionStatus.ACTIVE.value,
SubscriptionStatus.TRIAL.value,
]),
)
total_trials = (await session.scalar(total_trials_query)) or 0
total_paid_query = select(func.count()).select_from(Subscription).where(
Subscription.is_trial.is_(False),
Subscription.end_date > now_utc,
Subscription.status == SubscriptionStatus.ACTIVE.value,
)
total_paid = (await session.scalar(total_paid_query)) or 0
new_trials_query = select(func.count()).select_from(Subscription).where(
Subscription.is_trial.is_(True),
Subscription.start_date >= start,
Subscription.start_date < end,
)
new_trials = (await session.scalar(new_trials_query)) or 0
new_paid_query = select(func.count()).select_from(Subscription).where(
Subscription.is_trial.is_(False),
Subscription.start_date >= start,
Subscription.start_date < end,
)
new_paid = (await session.scalar(new_paid_query)) or 0
payments_query = select(
func.count(Transaction.id),
func.coalesce(func.sum(Transaction.amount_kopeks), 0),
).where(
Transaction.type == TransactionType.DEPOSIT.value,
Transaction.is_completed.is_(True),
Transaction.created_at >= start,
Transaction.created_at < end,
)
payments_count, payments_sum = (await session.execute(payments_query)).one()
return {
"total_trials": int(total_trials),
"total_paid": int(total_paid),
"new_trials": int(new_trials),
"new_paid": int(new_paid),
"payments_count": int(payments_count or 0),
"payments_sum": int(payments_sum or 0),
"period_start": start,
"period_end": end,
}
def _format_report(self, info: ReportPeriodInfo, stats: dict) -> str:
now_msk = datetime.now(self._moscow_tz)
end_display = info.end_msk - timedelta(seconds=1)
period_range = (
f"{info.start_msk.strftime('%d.%m.%Y %H:%M')}"
f"{end_display.strftime('%d.%m.%Y %H:%M')}"
)
lines = [
f"{info.emoji} <b>{info.title}</b> ({info.caption})",
"",
"🎯 <b>Триалы</b>",
f"• Активных сейчас: {stats['total_trials']}",
f"• Новых за период: {stats['new_trials']}",
"",
"💎 <b>Платные подписки</b>",
f"• Активных сейчас: {stats['total_paid']}",
f"• Новых за период: {stats['new_paid']}",
"",
"💳 <b>Пополнения</b>",
f"• Количество платежей: {stats['payments_count']}",
f"• Сумма: {settings.format_price(stats['payments_sum'])}",
"",
f"🕒 Период (МСК): {period_range}",
f"📅 Сформировано: {now_msk.strftime('%d.%m.%Y %H:%M')}",
]
return "\n".join(lines)
def _get_period_info(self, period: ReportPeriod) -> Optional[ReportPeriodInfo]:
now_msk = datetime.now(self._moscow_tz)
if period is ReportPeriod.DAILY:
target_date = now_msk.date() - timedelta(days=1)
start_msk = datetime.combine(target_date, datetime.min.time(), tzinfo=self._moscow_tz)
end_msk = start_msk + timedelta(days=1)
caption = start_msk.strftime('%d.%m.%Y')
return ReportPeriodInfo(
start_msk=start_msk,
end_msk=end_msk,
title="Ежедневный отчет",
caption=caption,
range_caption=caption,
emoji="🗓️",
)
if period is ReportPeriod.WEEKLY:
end_msk = datetime.combine(now_msk.date(), datetime.min.time(), tzinfo=self._moscow_tz)
start_msk = end_msk - timedelta(days=7)
caption = (
f"{start_msk.strftime('%d.%m.%Y')}"
f"{(end_msk - timedelta(days=1)).strftime('%d.%m.%Y')}"
)
return ReportPeriodInfo(
start_msk=start_msk,
end_msk=end_msk,
title="Еженедельный отчет",
caption=caption,
range_caption=caption,
emoji="🗓️",
)
if period is ReportPeriod.MONTHLY:
current_month_start = datetime(now_msk.year, now_msk.month, 1, tzinfo=self._moscow_tz)
end_msk = current_month_start
previous_month_last_day = current_month_start - timedelta(days=1)
start_msk = datetime(
previous_month_last_day.year,
previous_month_last_day.month,
1,
tzinfo=self._moscow_tz,
)
caption = (
f"{start_msk.strftime('%d.%m.%Y')}"
f"{previous_month_last_day.strftime('%d.%m.%Y')}"
)
return ReportPeriodInfo(
start_msk=start_msk,
end_msk=end_msk,
title="Ежемесячный отчет",
caption=caption,
range_caption=caption,
emoji="📆",
)
logger.warning("Неизвестный период отчета: %s", period)
return None
def _get_next_run_datetime(self) -> datetime:
dispatch_time = settings.get_admin_reports_time()
now_msk = datetime.now(self._moscow_tz)
run_msk = datetime.combine(now_msk.date(), dispatch_time, tzinfo=self._moscow_tz)
if run_msk <= now_msk:
run_msk += timedelta(days=1)
return run_msk.astimezone(self._utc_tz)
report_service = ReportService()

View File

@@ -130,7 +130,6 @@
"ADMIN_MONITORING": "🔍 Monitoring",
"ADMIN_PANEL": "\n⚙ <b>Administration panel</b>\n\nSelect a section to manage:\n",
"ADMIN_PROMOCODES": "🎫 Promo codes",
"ADMIN_REPORTS": "📈 Reports",
"ADMIN_REFERRALS": "🤝 Referral program",
"ADMIN_REMNAWAVE": "🖥️ Remnawave",
"ADMIN_RULES": "📋 Rules",
@@ -258,12 +257,6 @@
"ADMIN_PROMO_GROUP_DELETED": "Promo group “{name}” deleted.",
"ADMIN_SUBSCRIPTIONS": "📱 Subscriptions",
"ADMIN_USERS": "👥 Users",
"ADMIN_REPORTS_MENU_HINT": "Choose which report to send to the admin topic.",
"ADMIN_REPORTS_DAILY": "📅 Daily report (yesterday)",
"ADMIN_REPORTS_WEEKLY": "🗓️ Weekly report",
"ADMIN_REPORTS_MONTHLY": "📆 Monthly report",
"ADMIN_REPORTS_SENT": "✅ Report sent to the admin topic.",
"ADMIN_REPORTS_ERROR": "❌ Failed to send the report. Check notification settings.",
"AUTOPAY_DISABLED_TEXT": "Disabled — don't forget to renew manually!",
"AUTOPAY_ENABLED_TEXT": "Enabled — the subscription will renew automatically",
"AUTOPAY_FAILED": "\n❌ <b>Autopay failed</b>\n\nWe couldn't charge the renewal payment.\nBalance available: {balance}\nRequired: {required}\n\nPlease top up your balance and renew manually.\n",

View File

@@ -7,7 +7,6 @@
"ADMIN_MONITORING": "🔍 Мониторинг",
"ADMIN_PANEL": "\n⚙ <b>Административная панель</b>\n\nВыберите раздел для управления:\n",
"ADMIN_PROMOCODES": "🎫 Промокоды",
"ADMIN_REPORTS": "📈 Отчеты",
"ADMIN_REFERRALS": "🤝 Партнерка",
"ADMIN_REMNAWAVE": "🖥️ Remnawave",
"ADMIN_RULES": "📋 Правила",
@@ -135,12 +134,6 @@
"ADMIN_PROMO_GROUP_DELETED": "Промогруппа «{name}» удалена.",
"ADMIN_SUBSCRIPTIONS": "📱 Подписки",
"ADMIN_USERS": "👥 Пользователи",
"ADMIN_REPORTS_MENU_HINT": "Выберите период отчета для отправки в админ-топик.",
"ADMIN_REPORTS_DAILY": "📅 Отчет за вчера",
"ADMIN_REPORTS_WEEKLY": "🗓️ Отчет за неделю",
"ADMIN_REPORTS_MONTHLY": "📆 Отчет за месяц",
"ADMIN_REPORTS_SENT": "✅ Отчет отправлен в админ-топик.",
"ADMIN_REPORTS_ERROR": "❌ Не удалось отправить отчет. Проверьте настройки уведомлений.",
"AUTOPAY_BUTTON": "💳 Автоплатёж",
"AUTOPAY_DISABLED_TEXT": "Отключен - не забудьте продлить вручную!",
"AUTOPAY_ENABLED_TEXT": "Включен - подписка продлится автоматически",

18
main.py
View File

@@ -20,7 +20,6 @@ from app.external.pal24_webhook import start_pal24_webhook_server, Pal24WebhookS
from app.database.universal_migration import run_universal_migration
from app.services.backup_service import backup_service
from app.localization.loader import ensure_locale_templates
from app.services.report_service import report_service
class GracefulExit:
@@ -61,7 +60,6 @@ async def main():
monitoring_task = None
maintenance_task = None
version_check_task = None
reports_task = None
polling_task = None
try:
@@ -98,9 +96,6 @@ async def main():
version_service.set_notification_service(admin_notification_service)
logger.info(f"📄 Сервис версий настроен для репозитория: {version_service.repo}")
logger.info(f"📦 Текущая версия: {version_service.current_version}")
report_service.set_notification_service(admin_notification_service)
reports_task = await report_service.start()
logger.info("🔗 Бот подключен к сервисам мониторинга и техработ")
@@ -227,13 +222,6 @@ async def main():
if settings.is_version_check_enabled():
logger.info("🔄 Перезапуск сервиса проверки версий...")
version_check_task = asyncio.create_task(version_service.start_periodic_check())
if reports_task and reports_task.done():
exception = reports_task.exception()
if exception:
logger.error(f"Сервис отчетов завершился с ошибкой: {exception}")
new_task = await report_service.start()
reports_task = new_task if new_task else None
if polling_task.done():
exception = polling_task.exception()
@@ -289,12 +277,6 @@ async def main():
except asyncio.CancelledError:
pass
logger.info(" Остановка сервиса отчетов...")
try:
await report_service.stop()
except Exception as e:
logger.error(f"Ошибка остановки сервиса отчетов: {e}")
logger.info(" Остановка сервиса бекапов...")
try:
await backup_service.stop_auto_backup()