diff --git a/app/database/models.py b/app/database/models.py
index 26c4f860..d7f93679 100644
--- a/app/database/models.py
+++ b/app/database/models.py
@@ -571,7 +571,8 @@ class Subscription(Base):
return 0.0
def extend_subscription(self, days: int):
-
+ from datetime import timedelta, datetime
+
if self.end_date > datetime.utcnow():
self.end_date = self.end_date + timedelta(days=days)
else:
diff --git a/app/external/remnawave_api.py b/app/external/remnawave_api.py
index 19f42d91..8949dad9 100644
--- a/app/external/remnawave_api.py
+++ b/app/external/remnawave_api.py
@@ -4,6 +4,7 @@ import ssl
import base64
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Union, Any
+from urllib.parse import urlparse
import aiohttp
import logging
from dataclasses import dataclass
diff --git a/app/handlers/admin/main.py b/app/handlers/admin/main.py
index 3be7d267..aeaf15e1 100644
--- a/app/handlers/admin/main.py
+++ b/app/handlers/admin/main.py
@@ -128,6 +128,7 @@ async def show_support_submenu(
# Moderators have access only to tickets and not to settings
is_moderator_only = (not settings.is_admin(callback.from_user.id) and SupportSettingsService.is_moderator(callback.from_user.id))
+ from app.keyboards.admin import get_admin_support_submenu_keyboard
kb = get_admin_support_submenu_keyboard(db_user.language)
if is_moderator_only:
# Rebuild keyboard to include only tickets and back to main menu
diff --git a/app/handlers/admin/support_settings.py b/app/handlers/admin/support_settings.py
index 5ab4d3af..adbf3ea7 100644
--- a/app/handlers/admin/support_settings.py
+++ b/app/handlers/admin/support_settings.py
@@ -4,7 +4,6 @@ import html
import contextlib
from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
-from aiogram.fsm.state import State, StatesGroup
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import User
@@ -153,6 +152,24 @@ async def toggle_sla(callback: types.CallbackQuery, db_user: User, db: AsyncSess
await show_support_settings(callback, db_user, db)
+from app.states import SupportSettingsStates
+
+@admin_required
+@error_handler
+async def start_set_sla_minutes(callback: types.CallbackQuery, db_user: User, db: AsyncSession, state: FSMContext):
+ await callback.message.edit_text(
+ "⏳ Настройка SLA\n\nВведите количество минут ожидания ответа (целое число > 0):",
+ parse_mode="HTML",
+ reply_markup=types.InlineKeyboardMarkup(
+ inline_keyboard=[[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_support_settings")]]
+ )
+ )
+ await state.set_state(SupportSettingsStates.waiting_for_desc) # temporary reuse replaced below
+ # we'll manage separate state below
+
+
+from aiogram.fsm.state import State, StatesGroup
+
class SupportAdvancedStates(StatesGroup):
waiting_for_sla_minutes = State()
waiting_for_moderator_id = State()
diff --git a/app/handlers/admin/welcome_text.py b/app/handlers/admin/welcome_text.py
index f4c2b353..aca691f9 100644
--- a/app/handlers/admin/welcome_text.py
+++ b/app/handlers/admin/welcome_text.py
@@ -3,6 +3,7 @@ from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
+from app.config import settings
from app.database.models import User
from app.states import AdminStates
from app.keyboards.admin import get_welcome_text_keyboard, get_admin_main_keyboard
@@ -43,16 +44,16 @@ async def show_welcome_text_panel(
db_user: User,
db: AsyncSession
):
- welcome_settings = await get_current_welcome_text_settings(db)
- status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
- status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
+ settings = await get_current_welcome_text_settings(db)
+ status_emoji = "🟢" if settings['is_enabled'] else "🔴"
+ status_text = "включено" if settings['is_enabled'] else "отключено"
await callback.message.edit_text(
f"👋 Управление приветственным текстом\n\n"
f"{status_emoji} Статус: {status_text}\n\n"
f"Здесь вы можете управлять текстом, который показывается новым пользователям после регистрации.\n\n"
f"💡 Доступные плейсхолдеры для автозамены:",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
await callback.answer()
@@ -88,11 +89,11 @@ async def show_current_welcome_text(
db_user: User,
db: AsyncSession
):
- welcome_settings = await get_current_welcome_text_settings(db)
- current_text = welcome_settings['text']
- is_enabled = welcome_settings['is_enabled']
-
- if not welcome_settings['id']:
+ settings = await get_current_welcome_text_settings(db)
+ current_text = settings['text']
+ is_enabled = settings['is_enabled']
+
+ if not settings['id']:
status = "📝 Используется стандартный текст:"
else:
status = "📝 Текущий приветственный текст:"
@@ -120,7 +121,7 @@ async def show_placeholders_help(
db_user: User,
db: AsyncSession
):
- welcome_settings = await get_current_welcome_text_settings(db)
+ settings = await get_current_welcome_text_settings(db)
placeholders = get_available_placeholders()
placeholders_text = "\n".join([f"• {key}\n {desc}" for key, desc in placeholders.items()])
@@ -136,7 +137,7 @@ async def show_placeholders_help(
await callback.message.edit_text(
help_text,
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
await callback.answer()
@@ -148,12 +149,12 @@ async def show_formatting_help(
db_user: User,
db: AsyncSession
):
- welcome_settings = await get_current_welcome_text_settings(db)
+ settings = await get_current_welcome_text_settings(db)
formatting_info = get_telegram_formatting_info()
await callback.message.edit_text(
formatting_info,
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
await callback.answer()
@@ -166,8 +167,8 @@ async def start_edit_welcome_text(
db_user: User,
db: AsyncSession
):
- welcome_settings = await get_current_welcome_text_settings(db)
- current_text = welcome_settings['text']
+ settings = await get_current_welcome_text_settings(db)
+ current_text = settings['text']
placeholders = get_available_placeholders()
placeholders_text = "\n".join([f"• {key} - {desc}" for key, desc in placeholders.items()])
@@ -205,9 +206,9 @@ async def process_welcome_text_edit(
success = await set_welcome_text(db, new_text, db_user.id)
if success:
- welcome_settings = await get_current_welcome_text_settings(db)
- status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
- status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
+ settings = await get_current_welcome_text_settings(db)
+ status_emoji = "🟢" if settings['is_enabled'] else "🔴"
+ status_text = "включено" if settings['is_enabled'] else "отключено"
placeholders = get_available_placeholders()
placeholders_text = "\n".join([f"• {key}" for key in placeholders.keys()])
@@ -218,14 +219,14 @@ async def process_welcome_text_edit(
f"Новый текст:\n"
f"{new_text}\n\n"
f"💡 Будут заменяться плейсхолдеры: {placeholders_text}",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
else:
- welcome_settings = await get_current_welcome_text_settings(db)
+ settings = await get_current_welcome_text_settings(db)
await message.answer(
"❌ Ошибка при сохранении текста. Попробуйте еще раз.",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled'])
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled'])
)
await state.clear()
@@ -241,9 +242,9 @@ async def reset_welcome_text(
success = await set_welcome_text(db, default_text, db_user.id)
if success:
- welcome_settings = await get_current_welcome_text_settings(db)
- status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
- status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
+ settings = await get_current_welcome_text_settings(db)
+ status_emoji = "🟢" if settings['is_enabled'] else "🔴"
+ status_text = "включено" if settings['is_enabled'] else "отключено"
await callback.message.edit_text(
f"✅ Приветственный текст сброшен на стандартный!\n\n"
@@ -251,14 +252,14 @@ async def reset_welcome_text(
f"Стандартный текст:\n"
f"{default_text}\n\n"
f"💡 Плейсхолдер {{user_name}} будет заменяться на имя пользователя",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
else:
- welcome_settings = await get_current_welcome_text_settings(db)
+ settings = await get_current_welcome_text_settings(db)
await callback.message.edit_text(
"❌ Ошибка при сбросе текста. Попробуйте еще раз.",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled'])
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled'])
)
await callback.answer()
@@ -280,14 +281,14 @@ async def show_preview_welcome_text(
test_user = TestUser()
preview_text = await get_welcome_text_for_user(db, test_user)
- welcome_settings = await get_current_welcome_text_settings(db)
+ settings = await get_current_welcome_text_settings(db)
if preview_text:
await callback.message.edit_text(
f"👁️ Предварительный просмотр\n\n"
f"Как будет выглядеть текст для пользователя 'Иван' (@test_user):\n\n"
f"{preview_text}",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
else:
@@ -295,7 +296,7 @@ async def show_preview_welcome_text(
f"👁️ Предварительный просмотр\n\n"
f"🔴 Приветственные сообщения отключены.\n"
f"Новые пользователи не будут получать приветственный текст после регистрации.",
- reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
+ reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
parse_mode="HTML"
)
diff --git a/app/handlers/menu.py b/app/handlers/menu.py
index a191d537..7c8d56d4 100644
--- a/app/handlers/menu.py
+++ b/app/handlers/menu.py
@@ -10,6 +10,7 @@ from app.database.crud.user import get_user_by_telegram_id, update_user
from app.keyboards.inline import get_main_menu_keyboard, get_language_selection_keyboard
from app.localization.texts import get_texts, get_rules
from app.database.models import User
+from app.utils.user_utils import mark_user_as_had_paid_subscription
from app.database.crud.user_message import get_random_active_message
from app.services.subscription_checkout_service import (
has_subscription_checkout_draft,
@@ -64,6 +65,17 @@ async def show_main_menu(
await callback.answer()
+async def mark_user_as_had_paid_subscription(
+ db: AsyncSession,
+ user: User
+) -> None:
+ if not user.has_had_paid_subscription:
+ user.has_had_paid_subscription = True
+ user.updated_at = datetime.utcnow()
+ await db.commit()
+ logger.info(f"🎯 Пользователь {user.telegram_id} отмечен как имевший платную подписку")
+
+
async def show_service_rules(
callback: types.CallbackQuery,
db_user: User,
diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py
index 8c2ae0b4..f0b21c87 100644
--- a/app/handlers/subscription.py
+++ b/app/handlers/subscription.py
@@ -146,6 +146,12 @@ async def _prepare_subscription_summary(
data: Dict[str, Any],
texts,
) -> Tuple[str, Dict[str, Any]]:
+ from app.utils.pricing_utils import (
+ calculate_months_from_days,
+ format_period_description,
+ validate_pricing_calculation,
+ apply_percentage_discount,
+ )
summary_data = dict(data)
countries = await _get_available_countries(db_user.promo_group_id)
@@ -1122,6 +1128,7 @@ async def return_to_saved_cart(
)
return
+ from app.utils.pricing_utils import calculate_months_from_days, format_period_description
countries = await _get_available_countries(db_user.promo_group_id)
selected_countries_names = []
@@ -1326,6 +1333,7 @@ async def apply_countries_changes(
db: AsyncSession,
state: FSMContext
):
+ from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
logger.info(f"🔧 Применение изменений стран")
@@ -1626,6 +1634,7 @@ async def confirm_change_devices(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
new_devices_count = int(callback.data.split('_')[2])
texts = get_texts(db_user.language)
@@ -1739,6 +1748,7 @@ async def execute_change_devices(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
callback_parts = callback.data.split('_')
new_devices_count = int(callback_parts[3])
@@ -1869,6 +1879,7 @@ async def show_devices_page(
page: int = 1
):
+ from app.utils.pagination import paginate_list
texts = get_texts(db_user.language)
devices_per_page = 5
@@ -1967,6 +1978,7 @@ async def handle_single_device_reset(
if response and 'response' in response:
devices_list = response['response'].get('devices', [])
+ from app.utils.pagination import paginate_list
devices_per_page = 5
pagination = paginate_list(devices_list, page=page, per_page=devices_per_page)
@@ -2272,6 +2284,7 @@ async def confirm_add_devices(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
devices_count = int(callback.data.split('_')[2])
texts = get_texts(db_user.language)
@@ -2407,6 +2420,11 @@ async def confirm_extend_subscription(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import (
+ calculate_months_from_days,
+ validate_pricing_calculation,
+ apply_percentage_discount,
+ )
from app.services.admin_notification_service import AdminNotificationService
days = int(callback.data.split('_')[2])
@@ -3016,6 +3034,7 @@ async def select_country(
return
period_base_price = PERIOD_PRICES[data['period_days']]
+ from app.utils.pricing_utils import apply_percentage_discount
discounted_base_price, _ = apply_percentage_discount(
period_base_price,
@@ -3151,6 +3170,7 @@ async def confirm_purchase(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import calculate_months_from_days, validate_pricing_calculation
from app.services.admin_notification_service import AdminNotificationService
data = await state.get_data()
@@ -3870,6 +3890,7 @@ async def create_paid_subscription_with_traffic_mode(
traffic_gb: Optional[int] = None
):
from app.config import settings
+ from app.database.crud.subscription import create_paid_subscription
if traffic_gb is None:
if settings.is_traffic_fixed():
@@ -5330,6 +5351,7 @@ async def confirm_switch_traffic(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
new_traffic_gb = int(callback.data.split('_')[2])
texts = get_texts(db_user.language)
@@ -5443,6 +5465,7 @@ async def execute_switch_traffic(
db_user: User,
db: AsyncSession
):
+ from app.utils.pricing_utils import get_remaining_months
callback_parts = callback.data.split('_')
new_traffic_gb = int(callback_parts[3])
@@ -5527,6 +5550,7 @@ def get_traffic_switch_keyboard(
subscription_end_date: datetime = None,
discount_percent: int = 0,
) -> InlineKeyboardMarkup:
+ from app.utils.pricing_utils import get_remaining_months
from app.config import settings
months_multiplier = 1
diff --git a/app/handlers/tickets.py b/app/handlers/tickets.py
index f4f5e301..a6445a00 100644
--- a/app/handlers/tickets.py
+++ b/app/handlers/tickets.py
@@ -412,6 +412,7 @@ async def show_my_tickets(
# Добавим кнопку перехода к закрытым
keyboard.inline_keyboard.insert(0, [types.InlineKeyboardButton(text=texts.t("VIEW_CLOSED_TICKETS", "🟢 Закрытые тикеты"), callback_data="my_tickets_closed")])
# Всегда используем фото-рендер с логотипом (утилита сама сделает фоллбек при необходимости)
+ from app.utils.photo_message import edit_or_answer_photo
await edit_or_answer_photo(
callback=callback,
caption=texts.t("MY_TICKETS_TITLE", "📋 Ваши тикеты:"),
@@ -455,6 +456,7 @@ async def show_my_tickets_closed(
data = [{'id': t.id, 'title': t.title, 'status_emoji': t.status_emoji} for t in tickets]
kb = get_my_tickets_keyboard(data, current_page=current_page, total_pages=total_pages, language=db_user.language, page_prefix="my_tickets_closed_page_")
kb.inline_keyboard.insert(0, [types.InlineKeyboardButton(text=texts.t("BACK_TO_OPEN_TICKETS", "🔴 Открытые тикеты"), callback_data="my_tickets")])
+ from app.utils.photo_message import edit_or_answer_photo
await edit_or_answer_photo(
callback=callback,
caption=texts.t("CLOSED_TICKETS_TITLE", "🟢 Закрытые тикеты:"),
diff --git a/app/services/admin_notification_service.py b/app/services/admin_notification_service.py
index 1aa3efeb..ad3ba206 100644
--- a/app/services/admin_notification_service.py
+++ b/app/services/admin_notification_service.py
@@ -701,7 +701,7 @@ class AdminNotificationService:
if not payment_method:
return '💰 С баланса'
- return method_names.get(payment_method, '💰 С баланса')
+ return method_names.get(payment_method, f'💰 С баланса')
def _format_traffic(self, traffic_gb: int) -> str:
if traffic_gb == 0:
@@ -748,32 +748,40 @@ class AdminNotificationService:
if details.get("auto_enabled", False):
icon = "⚠️"
title = "АВТОМАТИЧЕСКОЕ ВКЛЮЧЕНИЕ ТЕХРАБОТ"
+ alert_type = "warning"
else:
icon = "🔧"
title = "ВКЛЮЧЕНИЕ ТЕХРАБОТ"
+ alert_type = "info"
elif event_type == "disable":
icon = "✅"
title = "ОТКЛЮЧЕНИЕ ТЕХРАБОТ"
+ alert_type = "success"
elif event_type == "api_status":
if status == "online":
icon = "🟢"
title = "API REMNAWAVE ВОССТАНОВЛЕНО"
+ alert_type = "success"
else:
icon = "🔴"
title = "API REMNAWAVE НЕДОСТУПНО"
+ alert_type = "error"
elif event_type == "monitoring":
if status == "started":
icon = "🔍"
title = "МОНИТОРИНГ ЗАПУЩЕН"
+ alert_type = "info"
else:
icon = "⏹️"
title = "МОНИТОРИНГ ОСТАНОВЛЕН"
+ alert_type = "info"
else:
icon = "ℹ️"
title = "СИСТЕМА ТЕХРАБОТ"
+ alert_type = "info"
message_parts = [f"{icon} {title}", ""]
@@ -963,6 +971,103 @@ class AdminNotificationService:
logger.error(f"Ошибка отправки уведомления о статусе панели Remnawave: {e}")
return False
+ async def send_remnawave_panel_status_notification(
+ self,
+ status: str,
+ details: Dict[str, Any] = None
+ ) -> bool:
+ if not self._is_enabled():
+ return False
+
+ try:
+ details = details or {}
+
+ status_config = {
+ "online": {"icon": "🟢", "title": "ПАНЕЛЬ REMNAWAVE ДОСТУПНА", "alert_type": "success"},
+ "offline": {"icon": "🔴", "title": "ПАНЕЛЬ REMNAWAVE НЕДОСТУПНА", "alert_type": "error"},
+ "degraded": {"icon": "🟡", "title": "ПАНЕЛЬ REMNAWAVE РАБОТАЕТ СО СБОЯМИ", "alert_type": "warning"},
+ "maintenance": {"icon": "🔧", "title": "ПАНЕЛЬ REMNAWAVE НА ОБСЛУЖИВАНИИ", "alert_type": "info"}
+ }
+
+ config = status_config.get(status, status_config["offline"])
+
+ message_parts = [
+ f"{config['icon']} {config['title']}",
+ ""
+ ]
+
+ if details.get("api_url"):
+ message_parts.append(f"🔗 URL: {details['api_url']}")
+
+ if details.get("response_time"):
+ message_parts.append(f"⚡ Время отклика: {details['response_time']} сек")
+
+ if details.get("last_check"):
+ last_check = details["last_check"]
+ if isinstance(last_check, str):
+ from datetime import datetime
+ last_check = datetime.fromisoformat(last_check)
+ message_parts.append(f"🕐 Последняя проверка: {last_check.strftime('%H:%M:%S')}")
+
+ if status == "online":
+ if details.get("uptime"):
+ message_parts.append(f"⏱️ Время работы: {details['uptime']}")
+
+ if details.get("users_online"):
+ message_parts.append(f"👥 Пользователей онлайн: {details['users_online']}")
+
+ message_parts.append("")
+ message_parts.append("✅ Все системы работают нормально.")
+
+ elif status == "offline":
+ if details.get("error"):
+ error_msg = str(details["error"])[:150]
+ message_parts.append(f"❌ Ошибка: {error_msg}")
+
+ if details.get("consecutive_failures"):
+ message_parts.append(f"🔄 Неудачных попыток: {details['consecutive_failures']}")
+
+ message_parts.append("")
+ message_parts.append("⚠️ Панель недоступна. Проверьте соединение и статус сервера.")
+
+ elif status == "degraded":
+ if details.get("issues"):
+ issues = details["issues"]
+ if isinstance(issues, list):
+ message_parts.append("⚠️ Обнаруженные проблемы:")
+ for issue in issues[:3]:
+ message_parts.append(f" • {issue}")
+ else:
+ message_parts.append(f"⚠️ Проблема: {issues}")
+
+ message_parts.append("")
+ message_parts.append("Панель работает, но возможны задержки или сбои.")
+
+ elif status == "maintenance":
+ if details.get("maintenance_reason"):
+ message_parts.append(f"🔧 Причина: {details['maintenance_reason']}")
+
+ if details.get("estimated_duration"):
+ message_parts.append(f"⏰ Ожидаемая длительность: {details['estimated_duration']}")
+
+ if details.get("manual_message"):
+ message_parts.append(f"💬 Сообщение: {details['manual_message']}")
+
+ message_parts.append("")
+ message_parts.append("Панель временно недоступна для обслуживания.")
+
+ from datetime import datetime
+ message_parts.append("")
+ message_parts.append(f"⏰ {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
+
+ message = "\n".join(message_parts)
+
+ return await self._send_message(message)
+
+ except Exception as e:
+ logger.error(f"Ошибка отправки уведомления о статусе панели Remnawave: {e}")
+ return False
+
async def send_subscription_update_notification(
self,
db: AsyncSession,
diff --git a/app/services/notification_settings_service.py b/app/services/notification_settings_service.py
index 959bf93e..a19edffd 100644
--- a/app/services/notification_settings_service.py
+++ b/app/services/notification_settings_service.py
@@ -1,4 +1,5 @@
import json
+import json
import logging
from copy import deepcopy
from pathlib import Path
diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py
index 9acaf501..1a02a08f 100644
--- a/app/services/remnawave_service.py
+++ b/app/services/remnawave_service.py
@@ -987,6 +987,15 @@ class RemnaWaveService:
logger.error(f"Error removing users from squad: {e}")
return False
+ async def delete_squad(self, squad_uuid: str) -> bool:
+ try:
+ async with self.get_api_client() as api:
+ response = await api.delete_internal_squad(squad_uuid)
+ return response
+ except Exception as e:
+ logger.error(f"Error deleting squad: {e}")
+ return False
+
async def get_all_inbounds(self) -> List[Dict]:
try:
async with self.get_api_client() as api:
@@ -1021,6 +1030,15 @@ class RemnaWaveService:
logger.error(f"Error renaming squad: {e}")
return False
+ async def create_squad(self, name: str, inbound_uuids: List[str]) -> bool:
+ try:
+ async with self.get_api_client() as api:
+ squad = await api.create_internal_squad(name, inbound_uuids)
+ return squad is not None
+ except Exception as e:
+ logger.error(f"Error creating squad: {e}")
+ return False
+
async def get_node_user_usage_by_range(self, node_uuid: str, start_date, end_date) -> List[Dict[str, Any]]:
try:
async with self.get_api_client() as api:
diff --git a/app/utils/user_utils.py b/app/utils/user_utils.py
index e2a73c49..8292f5e1 100644
--- a/app/utils/user_utils.py
+++ b/app/utils/user_utils.py
@@ -189,7 +189,7 @@ async def get_detailed_referral_list(db: AsyncSession, user_id: int, limit: int
and_(
Transaction.user_id == referral.id,
Transaction.type == TransactionType.DEPOSIT.value,
- Transaction.is_completed.is_(True)
+ Transaction.is_completed == True
)
)
)