diff --git a/.env.example b/.env.example
index c78d8a82..2a801dfb 100644
--- a/.env.example
+++ b/.env.example
@@ -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 # Обязательна ли подписка на канал
diff --git a/README.md b/README.md
index 87da0ea8..7f65c08a 100644
--- a/README.md
+++ b/README.md
@@ -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. **Создайте приватный канал** или группу для уведомлений
diff --git a/app/bot.py b/app/bot.py
index a13e2237..c23bf06d 100644
--- a/app/bot.py
+++ b/app/bot.py
@@ -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 платежей")
diff --git a/app/config.py b/app/config.py
index 811c9999..8c8e6f17 100644
--- a/app/config.py
+++ b/app/config.py
@@ -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
diff --git a/app/handlers/admin/main.py b/app/handlers/admin/main.py
index ac86a658..a26fd7b3 100644
--- a/app/handlers/admin/main.py
+++ b/app/handlers/admin/main.py
@@ -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,
diff --git a/app/handlers/admin/reports.py b/app/handlers/admin/reports.py
deleted file mode 100644
index d6f795e6..00000000
--- a/app/handlers/admin/reports.py
+++ /dev/null
@@ -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",
- )
diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py
index e3c41abf..7b5f1549 100644
--- a/app/keyboards/admin.py
+++ b/app/keyboards/admin.py
@@ -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)
diff --git a/app/services/admin_notification_service.py b/app/services/admin_notification_service.py
index f817c40a..1882e35b 100644
--- a/app/services/admin_notification_service.py
+++ b/app/services/admin_notification_service.py
@@ -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 = {
diff --git a/app/services/report_service.py b/app/services/report_service.py
deleted file mode 100644
index 45735ff7..00000000
--- a/app/services/report_service.py
+++ /dev/null
@@ -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} {info.title} ({info.caption})",
- "",
- "🎯 Триалы",
- f"• Активных сейчас: {stats['total_trials']}",
- f"• Новых за период: {stats['new_trials']}",
- "",
- "💎 Платные подписки",
- f"• Активных сейчас: {stats['total_paid']}",
- f"• Новых за период: {stats['new_paid']}",
- "",
- "💳 Пополнения",
- 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()
diff --git a/locales/en.json b/locales/en.json
index 3b108db8..40656662 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -130,7 +130,6 @@
"ADMIN_MONITORING": "🔍 Monitoring",
"ADMIN_PANEL": "\n⚙️ Administration panel\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❌ Autopay failed\n\nWe couldn't charge the renewal payment.\nBalance available: {balance}\nRequired: {required}\n\nPlease top up your balance and renew manually.\n",
diff --git a/locales/ru.json b/locales/ru.json
index a56fdd58..eb8d1ea4 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -7,7 +7,6 @@
"ADMIN_MONITORING": "🔍 Мониторинг",
"ADMIN_PANEL": "\n⚙️ Административная панель\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": "Включен - подписка продлится автоматически",
diff --git a/main.py b/main.py
index 17c3de48..4be13cde 100644
--- a/main.py
+++ b/main.py
@@ -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()