diff --git a/handlers/notifications/general_notifications.py b/handlers/notifications/general_notifications.py index a3591449..f37bf4e9 100644 --- a/handlers/notifications/general_notifications.py +++ b/handlers/notifications/general_notifications.py @@ -1,9 +1,7 @@ import asyncio - from datetime import datetime, timedelta import pytz - from aiogram import Bot, Router from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker @@ -46,7 +44,7 @@ from handlers.notifications.notify_kb import ( build_notification_expired_kb, build_notification_kb, ) -from handlers.tariffs.tariff_display import GB, get_effective_limits_for_key +from handlers.tariffs.tariff_display import GB, get_effective_limits_for_key, resolve_price_to_charge from handlers.texts import ( KEY_CANNOT_RENEW_CURRENT, KEY_DELETED_MSG, @@ -379,11 +377,12 @@ async def handle_expired_keys( if notify_renew_expired_enabled: try: - balance = await get_balance(session, tg_id) - tariffs = await get_tariffs_for_cluster(session, server_id) - tariff = tariffs[0] if tariffs else None + tariff = None + tariff_id = getattr(key, "tariff_id", None) + if tariff_id and await check_tariff_exists(session, int(tariff_id)): + tariff = await get_tariff_by_id(session, int(tariff_id)) - if tariff and balance >= tariff["price_rub"]: + if tariff: selected_device_limit = getattr(key, "selected_device_limit", None) selected_traffic_limit = getattr(key, "selected_traffic_limit", None) selected_traffic_gb = int(selected_traffic_limit) if selected_traffic_limit is not None else None @@ -540,9 +539,17 @@ async def process_auto_renew_or_notify( logger.warning(f"[AUTO_RENEW] Ошибка при получении дополнительных групп: {error}") if current_tariff and current_tariff["group_code"] not in forbidden_groups: - stored_price = getattr(key, "selected_price_rub", None) - renewal_cost = float(stored_price) if stored_price is not None else float(current_tariff["price_rub"]) - if balance >= renewal_cost: + renewal_cost = await resolve_price_to_charge( + conn, + { + "tariff_id": current_tariff.get("id"), + "selected_device_limit": getattr(key, "selected_device_limit", None), + "selected_traffic_limit": getattr(key, "selected_traffic_limit", None), + "selected_price_rub": getattr(key, "selected_price_rub", None), + }, + ) + + if renewal_cost is not None and balance >= renewal_cost: selected_tariff = current_tariff else: selected_tariff = None @@ -612,8 +619,18 @@ async def process_auto_renew_or_notify( current_expiry = key.expiry_time duration_days = selected_tariff["duration_days"] - stored_price = getattr(key, "selected_price_rub", None) - renewal_cost = float(stored_price) if stored_price is not None else float(selected_tariff["price_rub"]) + renewal_cost = await resolve_price_to_charge( + conn, + { + "tariff_id": selected_tariff.get("id"), + "selected_device_limit": getattr(key, "selected_device_limit", None), + "selected_traffic_limit": getattr(key, "selected_traffic_limit", None), + "selected_price_rub": getattr(key, "selected_price_rub", None), + }, + ) + if renewal_cost is None: + logger.warning(f"[AUTO_RENEW] Не удалось определить стоимость продления для {email}. Продление отменено.") + return selected_device_limit = getattr(key, "selected_device_limit", None) selected_traffic_limit = getattr(key, "selected_traffic_limit", None) diff --git a/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so b/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so index 28493a3c..3f0777d4 100644 Binary files a/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so and b/handlers/payments/gift.cpython-312-x86_64-linux-gnu.so differ diff --git a/handlers/tariffs/tariff_display.py b/handlers/tariffs/tariff_display.py index 890f2585..37aca293 100644 --- a/handlers/tariffs/tariff_display.py +++ b/handlers/tariffs/tariff_display.py @@ -19,6 +19,7 @@ async def get_effective_limits_for_key( selected_device_limit: int | None, selected_traffic_gb: int | None, ) -> tuple[int, int]: + """Возвращает лимиты устройств и трафика с учётом выбранных значений.""" tariff = await get_tariff_by_id(session, int(tariff_id)) if tariff_id else None if tariff: @@ -46,6 +47,7 @@ async def get_effective_limits_for_key( async def resolve_price_to_charge(session: AsyncSession, state_data: dict[str, Any]) -> int | None: + """Считает цену к списанию по состоянию, с учётом конфигуратора и наценок.""" price = state_data.get("selected_price_rub") if price is not None: try: @@ -62,12 +64,81 @@ async def resolve_price_to_charge(session: AsyncSession, state_data: dict[str, A return None try: - return int(tariff.get("price_rub") or 0) + base_price = int(tariff.get("price_rub") or 0) except (TypeError, ValueError): return None + if not bool(tariff.get("configurable")): + return base_price + + cfg = normalize_tariff_config(tariff) + + device_options = cfg.get("device_options") or [] + traffic_options_gb = cfg.get("traffic_options_gb") or [] + + try: + base_device_limit = int(min(device_options)) if device_options else int(tariff.get("device_limit") or 0) + except (TypeError, ValueError): + base_device_limit = 0 + + try: + base_traffic_gb = int(min(traffic_options_gb)) if traffic_options_gb else int(tariff.get("traffic_limit") or 0) + except (TypeError, ValueError): + base_traffic_gb = 0 + + selected_device_limit = state_data.get("selected_device_limit") + selected_traffic_gb = state_data.get("selected_traffic_limit") + + try: + device_target = int(selected_device_limit) if selected_device_limit is not None else base_device_limit + except (TypeError, ValueError): + device_target = base_device_limit + + try: + traffic_target_gb = int(selected_traffic_gb) if selected_traffic_gb is not None else base_traffic_gb + except (TypeError, ValueError): + traffic_target_gb = base_traffic_gb + + try: + device_step_rub = int(cfg.get("device_step_rub") or 0) + except (TypeError, ValueError): + device_step_rub = 0 + + try: + traffic_step_rub = int(cfg.get("traffic_step_rub") or 0) + except (TypeError, ValueError): + traffic_step_rub = 0 + + device_overrides = cfg.get("device_overrides") or {} + traffic_overrides = cfg.get("traffic_overrides") or {} + + device_add_rub = 0 + if device_target > base_device_limit: + override_value = device_overrides.get(str(device_target), device_overrides.get(device_target)) + if override_value is not None: + try: + device_add_rub = int(override_value) + except (TypeError, ValueError): + device_add_rub = 0 + else: + device_add_rub = (device_target - base_device_limit) * device_step_rub + + traffic_add_rub = 0 + if traffic_target_gb > base_traffic_gb: + override_value = traffic_overrides.get(str(traffic_target_gb), traffic_overrides.get(traffic_target_gb)) + if override_value is not None: + try: + traffic_add_rub = int(override_value) + except (TypeError, ValueError): + traffic_add_rub = 0 + else: + traffic_add_rub = (traffic_target_gb - base_traffic_gb) * traffic_step_rub + + return int(base_price + device_add_rub + traffic_add_rub) + async def resolve_vless_enabled(session: AsyncSession, tariff_id: int | None) -> bool: + """Проверяет, включён ли VLESS в тарифе.""" if not tariff_id: return False @@ -84,6 +155,7 @@ async def get_key_tariff_display( selected_device_limit_override: int | None = None, selected_traffic_gb_override: int | None = None, ) -> tuple[str, str, int, int, bool]: + """Возвращает отображение тарифа и эффективные лимиты, приоритет — данные панели.""" tariff_id = key_record.get("tariff_id") if not tariff_id: return "", "", 0, 0, False @@ -287,6 +359,7 @@ async def build_key_created_message( selected_device_limit: int | None = None, selected_traffic_gb: int | None = None, ) -> str: + """Собирает сообщение об успешном создании ключа с отображением выбранных лимитов.""" tariff_id = key_record.get("tariff_id") tariff = await get_tariff_by_id(session, int(tariff_id)) if tariff_id else None diff --git a/utils/versioning.py b/utils/versioning.py index 711d56a5..8c2f7219 100644 --- a/utils/versioning.py +++ b/utils/versioning.py @@ -92,4 +92,4 @@ def get_git_commit_number() -> str: def get_version() -> str: - return f"v.5.1-a151229 {get_git_commit_number()}" + return f"v.5.1-b221229 {get_git_commit_number()}"