diff --git a/app/cabinet/routes/subscription_modules/traffic.py b/app/cabinet/routes/subscription_modules/traffic.py index 4a186a43..8c5553ba 100644 --- a/app/cabinet/routes/subscription_modules/traffic.py +++ b/app/cabinet/routes/subscription_modules/traffic.py @@ -9,6 +9,7 @@ POST /subscription/traffic/save-cart from __future__ import annotations +import math from datetime import UTC, datetime from typing import Any @@ -478,7 +479,7 @@ async def save_traffic_cart( from app.utils.pricing_utils import calculate_prorated_price as _calc_prorated now = datetime.now(UTC) - days_left = max(1, (subscription.end_date - now).days) + days_left = max(1, math.ceil((subscription.end_date - now).total_seconds() / 86400)) prorated_price, _ = _calc_prorated( base_price_kopeks, subscription.end_date, diff --git a/app/database/crud/subscription.py b/app/database/crud/subscription.py index d05ce143..28496601 100644 --- a/app/database/crud/subscription.py +++ b/app/database/crud/subscription.py @@ -1,3 +1,4 @@ +import math import secrets from collections.abc import Iterable from datetime import UTC, datetime, timedelta @@ -1296,7 +1297,7 @@ async def add_subscription_servers( if paid_prices is None: now = datetime.now(UTC) - days_remaining = max(1, (subscription.end_date - now).days) + days_remaining = max(1, math.ceil((subscription.end_date - now).total_seconds() / 86400)) paid_prices = [] from app.database.models import ServerSquad diff --git a/app/handlers/subscription/common.py b/app/handlers/subscription/common.py index 3d00b2d1..c33f86b2 100644 --- a/app/handlers/subscription/common.py +++ b/app/handlers/subscription/common.py @@ -1,6 +1,7 @@ import asyncio import base64 import html as html_mod +import math import re import time from datetime import UTC, datetime @@ -545,7 +546,7 @@ def get_traffic_switch_keyboard( # Считаем по дням (как в кабинете и подтверждении) if subscription_end_date: now = datetime.now(UTC) - days_left = max(1, (subscription_end_date - now).days) + days_left = max(1, math.ceil((subscription_end_date - now).total_seconds() / 86400)) price_multiplier = days_left / 30 period_text = f' (за {days_left} дн.)' if days_left > 1 else ' (за 1 день)' else: diff --git a/app/handlers/subscription/countries.py b/app/handlers/subscription/countries.py index 43beab0f..4613ebd1 100644 --- a/app/handlers/subscription/countries.py +++ b/app/handlers/subscription/countries.py @@ -1,4 +1,5 @@ import html +import math from datetime import UTC, datetime from aiogram import types @@ -266,7 +267,7 @@ async def apply_countries_changes(callback: types.CallbackQuery, db_user: User, logger.info('🔧 Добавлено: Удалено', added=added, removed=removed) now = datetime.now(UTC) - days_to_pay = max(1, (subscription.end_date - now).days) + days_to_pay = max(1, math.ceil((subscription.end_date - now).total_seconds() / 86400)) period_hint_days = days_to_pay if days_to_pay > 0 else None diff --git a/app/handlers/subscription/traffic.py b/app/handlers/subscription/traffic.py index f6ee44e6..f2c7e61d 100644 --- a/app/handlers/subscription/traffic.py +++ b/app/handlers/subscription/traffic.py @@ -1,3 +1,4 @@ +import math from datetime import UTC, datetime from aiogram import types @@ -807,7 +808,7 @@ async def confirm_switch_traffic( new_price_per_month = settings.get_traffic_price(new_traffic_gb) now = datetime.now(UTC) - days_remaining = max(1, (subscription.end_date - now).days) + days_remaining = max(1, math.ceil((subscription.end_date - now).total_seconds() / 86400)) period_hint_days = days_remaining if days_remaining > 0 else None traffic_discount_percent = PricingEngine.get_addon_discount_percent( db_user, @@ -911,7 +912,7 @@ async def execute_switch_traffic( base_traffic = current_traffic - purchased_traffic old_price_per_month = settings.get_traffic_price(base_traffic) new_price_per_month = settings.get_traffic_price(new_traffic_gb) - days_remaining = max(1, (subscription.end_date - datetime.now(UTC)).days) + days_remaining = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) traffic_discount_percent = PricingEngine.get_addon_discount_percent( db_user, 'traffic', @@ -936,7 +937,7 @@ async def execute_switch_traffic( await callback.answer('⚠️ Ошибка списания средств', show_alert=True) return - days_remaining = max(1, (subscription.end_date - datetime.now(UTC)).days) + days_remaining = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) await create_transaction( db=db, user_id=db_user.id, diff --git a/app/services/subscription_auto_purchase_service.py b/app/services/subscription_auto_purchase_service.py index ac9678b5..9a5ecef8 100644 --- a/app/services/subscription_auto_purchase_service.py +++ b/app/services/subscription_auto_purchase_service.py @@ -3,6 +3,7 @@ from __future__ import annotations import html +import math from dataclasses import dataclass from datetime import UTC, datetime, timedelta @@ -1533,7 +1534,7 @@ async def _auto_add_devices( # Recompute price fresh under lock (pricing config may have changed since cart was saved) devices_price_per_month = devices_to_add * tariff_device_price - days_left = max(1, (subscription.end_date - datetime.now(UTC)).days) + days_left = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) devices_discount_percent = PricingEngine.get_addon_discount_percent( user, 'devices', diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 9b47b0a5..b78fc2f7 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -5755,7 +5755,7 @@ async def update_subscription_servers_endpoint( subscription.end_date, ) else: - charged_days = max(1, (subscription.end_date - datetime.now(UTC)).days) + charged_days = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) added_server_ids = [catalog[uuid].get('server_id') for uuid in added if catalog[uuid].get('server_id') is not None] added_server_prices = [ @@ -5933,7 +5933,7 @@ async def update_subscription_traffic_endpoint( }, ) - days_remaining = max(1, (subscription.end_date - datetime.now(UTC)).days) + days_remaining = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) period_hint_days = days_remaining # Lock user BEFORE discount computation to prevent TOCTOU on promo group @@ -6111,7 +6111,7 @@ async def update_subscription_devices_endpoint( chargeable_diff = new_chargeable - current_chargeable price_per_month = chargeable_diff * tariff_device_price - days_remaining = max(1, (subscription.end_date - datetime.now(UTC)).days) + days_remaining = max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400)) period_hint_days = days_remaining # Lock user BEFORE price computation to prevent TOCTOU on promo discount @@ -6159,7 +6159,7 @@ async def update_subscription_devices_endpoint( user_id=user.id, type=TransactionType.SUBSCRIPTION_PAYMENT, amount_kopeks=price_to_charge, - description=f'{description} за {charged_days or max(1, (subscription.end_date - datetime.now(UTC)).days)} дн.', + description=f'{description} за {charged_days or max(1, math.ceil((subscription.end_date - datetime.now(UTC)).total_seconds() / 86400))} дн.', ) if price_to_charge > 0: