rate configurator/ purchase of conditions/ fixed payments/ refactoring/ major improvements and fixes

This commit is contained in:
Vladless
2025-11-29 19:08:01 +03:00
parent 28cfe91175
commit f2d14d69b0
90 changed files with 5888 additions and 799 deletions
+6 -3
View File
@@ -1,10 +1,12 @@
from database import async_session_maker
from .settings.buttons_config import BUTTONS_CONFIG, load_buttons_config, update_buttons_config
from .settings.notifications_config import NOTIFICATIONS_CONFIG, load_notifications_config, update_notifications_config
from .settings.management_config import MANAGEMENT_CONFIG, load_management_config, update_management_config
from .settings.modes_config import MODES_CONFIG, load_modes_config, update_modes_config
from .settings.payments_config import PAYMENTS_CONFIG, load_payments_config, update_payments_config
from .settings.money_config import MONEY_CONFIG, load_money_config, update_money_config
from .settings.management_config import MANAGEGENT_CONFIG, load_management_config, update_management_config
from .settings.notifications_config import NOTIFICATIONS_CONFIG, load_notifications_config, update_notifications_config
from .settings.payments_config import PAYMENTS_CONFIG, load_payments_config, update_payments_config
from .settings.tariffs_config import TARIFFS_CONFIG, load_tariffs_config, update_tariffs_config
async def bootstrap() -> None:
@@ -15,4 +17,5 @@ async def bootstrap() -> None:
await load_payments_config(session)
await load_money_config(session)
await load_management_config(session)
await load_tariffs_config(session)
await session.commit()
+1 -1
View File
@@ -1 +1 @@
PAYMENT_SYSTEMS_EXCLUDED = ("referral", "coupon", "cashback", "admin")
PAYMENT_SYSTEMS_EXCLUDED = ("referral", "coupon", "cashback", "admin")
+6 -1
View File
@@ -2,10 +2,12 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_BUTTONS_CONFIG
BUTTONS_CONFIG: dict[str, bool] = DEFAULT_BUTTONS_CONFIG.copy()
BUTTONS_CONFIG.setdefault("ANDROID_TV_BUTTON_ENABLE", False)
async def load_buttons_config(session: AsyncSession) -> None:
@@ -15,6 +17,7 @@ async def load_buttons_config(session: AsyncSession) -> None:
if setting is None:
buttons_config = DEFAULT_BUTTONS_CONFIG.copy()
buttons_config.setdefault("ANDROID_TV_BUTTON_ENABLE", False)
setting = Setting(
key="BUTTONS_CONFIG",
value=buttons_config,
@@ -25,6 +28,7 @@ async def load_buttons_config(session: AsyncSession) -> None:
stored = setting.value or {}
buttons_config = DEFAULT_BUTTONS_CONFIG.copy()
buttons_config.update(stored)
buttons_config.setdefault("ANDROID_TV_BUTTON_ENABLE", False)
setting.value = buttons_config
BUTTONS_CONFIG.clear()
@@ -47,10 +51,11 @@ async def update_buttons_config(session: AsyncSession, new_values: dict[str, boo
else:
setting.value = new_values
await session.flush()
await session.commit()
buttons_config = DEFAULT_BUTTONS_CONFIG.copy()
buttons_config.update(new_values)
buttons_config.setdefault("ANDROID_TV_BUTTON_ENABLE", False)
BUTTONS_CONFIG.clear()
BUTTONS_CONFIG.update(buttons_config)
+11 -10
View File
@@ -4,21 +4,23 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_MANAGEMENT_CONFIG
MANAGEGENT_CONFIG: dict[str, Any] = DEFAULT_MANAGEMENT_CONFIG.copy()
MANAGEMENT_CONFIG: dict[str, Any] = DEFAULT_MANAGEMENT_CONFIG.copy()
MANAGEMENT_SETTING_KEY = "MANAGEGENT_CONFIG"
async def load_management_config(session: AsyncSession) -> None:
stmt = select(Setting).where(Setting.key == "management")
stmt = select(Setting).where(Setting.key == MANAGEMENT_SETTING_KEY)
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
if setting is None:
management_config = DEFAULT_MANAGEMENT_CONFIG.copy()
setting = Setting(
key="management",
key=MANAGEMENT_SETTING_KEY,
value=management_config,
description="Конфигурация управления ботом",
)
@@ -29,19 +31,19 @@ async def load_management_config(session: AsyncSession) -> None:
management_config.update(stored)
setting.value = management_config
MANAGEGENT_CONFIG.clear()
MANAGEGENT_CONFIG.update(management_config)
MANAGEMENT_CONFIG.clear()
MANAGEMENT_CONFIG.update(management_config)
await session.flush()
async def update_management_config(session: AsyncSession, new_values: dict[str, Any]) -> None:
stmt = select(Setting).where(Setting.key == "management")
stmt = select(Setting).where(Setting.key == MANAGEMENT_SETTING_KEY)
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
if setting is None:
setting = Setting(
key="management",
key=MANAGEMENT_SETTING_KEY,
value=new_values,
description="Конфигурация управления ботом",
)
@@ -49,11 +51,10 @@ async def update_management_config(session: AsyncSession, new_values: dict[str,
else:
setting.value = new_values
await session.flush()
await session.commit()
management_config = DEFAULT_MANAGEMENT_CONFIG.copy()
management_config.update(new_values)
MANAGEGENT_CONFIG.clear()
MANAGEGENT_CONFIG.update(management_config)
MANAGEMENT_CONFIG.clear()
MANAGEMENT_CONFIG.update(management_config)
+2 -1
View File
@@ -2,6 +2,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_MODES_CONFIG
@@ -47,7 +48,7 @@ async def update_modes_config(session: AsyncSession, new_values: dict[str, bool]
else:
setting.value = new_values
await session.flush()
await session.commit()
modes_config = DEFAULT_MODES_CONFIG.copy()
modes_config.update(new_values)
+2 -1
View File
@@ -4,6 +4,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_MONEY_CONFIG
@@ -49,7 +50,7 @@ async def update_money_config(session: AsyncSession, new_values: dict[str, Any])
else:
setting.value = new_values
await session.flush()
await session.commit()
money_config = DEFAULT_MONEY_CONFIG.copy()
money_config.update(new_values)
+2 -1
View File
@@ -4,6 +4,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_NOTIFICATIONS_CONFIG
@@ -49,7 +50,7 @@ async def update_notifications_config(session: AsyncSession, new_values: dict[st
else:
setting.value = new_values
await session.flush()
await session.commit()
notifications_config = DEFAULT_NOTIFICATIONS_CONFIG.copy()
notifications_config.update(new_values)
+2 -1
View File
@@ -2,6 +2,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
from ..defaults import DEFAULT_PAYMENTS_CONFIG
@@ -47,7 +48,7 @@ async def update_payments_config(session: AsyncSession, new_values: dict[str, bo
else:
setting.value = new_values
await session.flush()
await session.commit()
payments_config = DEFAULT_PAYMENTS_CONFIG.copy()
payments_config.update(new_values)
+192
View File
@@ -0,0 +1,192 @@
from math import ceil
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
TARIFFS_CONFIG: dict[str, Any] = {
"ALLOW_DOWNGRADE": True,
"KEY_ADDONS_PACK_MODE": "all",
}
async def load_tariffs_config(session: AsyncSession) -> None:
"""Загружает конфиг тарифов из БД."""
stmt = select(Setting).where(Setting.key == "TARIFFS_CONFIG")
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
if setting is None:
tariffs_config = TARIFFS_CONFIG.copy()
setting = Setting(
key="TARIFFS_CONFIG",
value=tariffs_config,
description="Конфигурация тарифов",
)
session.add(setting)
else:
stored = setting.value or {}
tariffs_config = TARIFFS_CONFIG.copy()
tariffs_config.update(stored)
setting.value = tariffs_config
TARIFFS_CONFIG.clear()
TARIFFS_CONFIG.update(tariffs_config)
await session.flush()
async def update_tariffs_config(session: AsyncSession, new_values: dict[str, Any]) -> None:
"""Обновляет конфиг тарифов."""
stmt = select(Setting).where(Setting.key == "TARIFFS_CONFIG")
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
if setting is None:
setting = Setting(
key="TARIFFS_CONFIG",
value=new_values,
description="Конфигурация тарифов",
)
session.add(setting)
else:
setting.value = new_values
await session.commit()
tariffs_config = TARIFFS_CONFIG.copy()
tariffs_config.update(new_values)
TARIFFS_CONFIG.clear()
TARIFFS_CONFIG.update(tariffs_config)
def calc_extra_devices_price(tariff: dict[str, Any], device_limit: int) -> int:
base_device_limit = int(tariff.get("device_limit") or 1)
extra_devices = max(0, device_limit - base_device_limit)
if extra_devices <= 0:
return 0
step_price = int(tariff.get("device_step_rub") or 0)
overrides = tariff.get("device_overrides") or {}
override_total = overrides.get(str(device_limit))
if override_total is not None:
return int(ceil(float(override_total)))
return int(ceil(extra_devices * step_price))
def calc_extra_traffic_price(tariff: dict[str, Any], traffic_gb: int | None) -> int:
if traffic_gb is None:
return 0
traffic_limit_bytes = tariff.get("traffic_limit")
if traffic_limit_bytes:
base_traffic_gb = ceil(traffic_limit_bytes / 1024 / 1024 / 1024)
else:
base_traffic_gb = 0
step_price = int(tariff.get("traffic_step_rub") or 0)
overrides = tariff.get("traffic_overrides") or {}
override_total = overrides.get(str(traffic_gb))
if override_total is not None:
return int(ceil(float(override_total)))
if traffic_gb == 0:
return 0
extra_gb = max(0, traffic_gb - base_traffic_gb)
if extra_gb <= 0:
return 0
return int(ceil(extra_gb * step_price))
def calculate_config_price(
tariff: dict[str, Any],
duration_days: int,
device_limit: int,
traffic_gb: int | None,
) -> int:
base_duration = int(tariff.get("duration_days") or 0) or duration_days or 30
if base_duration <= 0:
base_duration = duration_days or 30
base_price = int(tariff.get("price_rub") or 0)
duration_multiplier = duration_days / base_duration
base_price_scaled = base_price * duration_multiplier
extra_devices_price = calc_extra_devices_price(tariff, device_limit)
extra_traffic_price = calc_extra_traffic_price(tariff, traffic_gb)
total = base_price_scaled + extra_devices_price + extra_traffic_price
return int(ceil(total))
def normalize_tariff_config(tariff: dict[str, Any]) -> dict[str, Any]:
raw_duration_options = tariff.get("duration_options") or []
duration_options: list[int] = []
for value in raw_duration_options:
try:
v = int(value)
except (TypeError, ValueError):
continue
if v > 0:
duration_options.append(v)
if not duration_options:
base_duration = int(tariff.get("duration_days") or 0) or 30
duration_options = [base_duration]
duration_options = sorted(set(duration_options))
raw_device_options = tariff.get("device_options") or []
device_options: list[int] = []
for value in raw_device_options:
try:
v = int(value)
except (TypeError, ValueError):
continue
if v > 0:
device_options.append(v)
if not device_options:
base_device_limit = int(tariff.get("device_limit") or 0)
if base_device_limit > 0:
device_options = [base_device_limit]
else:
device_options = []
device_options = sorted(set(device_options))
raw_traffic_options = tariff.get("traffic_options_gb")
traffic_options_gb: list[int] | None
if raw_traffic_options is None:
traffic_options_gb = None
else:
traffic_values: list[int] = []
has_unlimited = False
for value in raw_traffic_options:
try:
v = int(value)
except (TypeError, ValueError):
continue
if v == 0:
has_unlimited = True
elif v > 0:
traffic_values.append(v)
if not traffic_values and not has_unlimited:
traffic_options_gb = None
else:
unique_values = sorted(set(traffic_values))
if has_unlimited:
traffic_options_gb = [0] + unique_values
else:
traffic_options_gb = unique_values
return {
"duration_options": duration_options,
"device_options": device_options,
"traffic_options_gb": traffic_options_gb,
}