Files
remnawave-bedolaga-telegram…/app/database/crud/promo_offer_template.py
T
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
2026-01-24 17:45:27 +03:00

212 lines
8.7 KiB
Python

from __future__ import annotations
from collections.abc import Iterable
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import PromoOfferTemplate
UPDATED_TEMPLATE_MESSAGES = {
'extend_discount': (
'💎 Экономия {discount_percent}% при продлении\n\n'
'Скидка суммируется с промогруппой и действует один раз.\n'
'Срок действия предложения — {valid_hours} ч.\n'
'После активации скидка действует {active_discount_hours} ч.'
),
'purchase_discount': (
'🎯 Вернитесь со скидкой {discount_percent}%\n\n'
'Скидка суммируется с промогруппой и действует один раз.\n'
'Предложение действует {valid_hours} ч.\n'
'После активации скидка действует {active_discount_hours} ч.'
),
}
LEGACY_TEMPLATE_MESSAGES = {
'extend_discount': (
'💎 <b>Экономия {discount_percent}% при продлении</b>\n\n'
'Активируйте предложение и получите дополнительную скидку на оплату продления. '
'Она суммируется с вашими промогрупповыми скидками и действует один раз.\n'
'Срок действия предложения — {valid_hours} ч.\n'
'После активации скидка действует {active_discount_hours} ч.'
),
'purchase_discount': (
'🎯 <b>Вернитесь со скидкой {discount_percent}%</b>\n\n'
'После активации мы применим дополнительную скидку к вашей следующей оплате подписки. '
'Скидка суммируется с промогруппой и действует один раз.\n'
'Предложение действует {valid_hours} ч.\n'
'После активации скидка действует {active_discount_hours} ч.'
),
}
DEFAULT_TEMPLATES: tuple[dict, ...] = (
{
'offer_type': 'test_access',
'name': 'Тестовые сервера',
'message_text': (
'🔥 <b>Испытайте новые сервера</b>\n\n'
'Активируйте предложение и получите временный доступ к дополнительным сквадам на {test_duration_hours} ч.\n'
'Предложение действительно {valid_hours} ч.'
),
'button_text': '🚀 Попробовать серверы',
'valid_hours': 24,
'discount_percent': 0,
'bonus_amount_kopeks': 0,
'active_discount_hours': None,
'test_duration_hours': 24,
'test_squad_uuids': [],
},
{
'offer_type': 'extend_discount',
'name': 'Скидка на продление',
'message_text': UPDATED_TEMPLATE_MESSAGES['extend_discount'],
'button_text': '🎁 Получить скидку',
'valid_hours': 24,
'discount_percent': 20,
'bonus_amount_kopeks': 0,
'active_discount_hours': 24,
'test_duration_hours': None,
'test_squad_uuids': [],
},
{
'offer_type': 'purchase_discount',
'name': 'Скидка на покупку',
'message_text': UPDATED_TEMPLATE_MESSAGES['purchase_discount'],
'button_text': '🎁 Забрать скидку',
'valid_hours': 48,
'discount_percent': 25,
'bonus_amount_kopeks': 0,
'active_discount_hours': 48,
'test_duration_hours': None,
'test_squad_uuids': [],
},
)
def _format_template_fields(payload: dict) -> dict:
data = dict(payload)
data.setdefault('valid_hours', 24)
data.setdefault('discount_percent', 0)
data.setdefault('bonus_amount_kopeks', 0)
data.setdefault('active_discount_hours', None)
data.setdefault('test_duration_hours', None)
data.setdefault('test_squad_uuids', [])
return data
async def ensure_default_templates(db: AsyncSession, *, created_by: int | None = None) -> list[PromoOfferTemplate]:
templates: list[PromoOfferTemplate] = []
for template_data in DEFAULT_TEMPLATES:
result = await db.execute(
select(PromoOfferTemplate).where(PromoOfferTemplate.offer_type == template_data['offer_type'])
)
existing = result.scalars().first()
if existing:
new_message = UPDATED_TEMPLATE_MESSAGES.get(template_data['offer_type'])
legacy_message = LEGACY_TEMPLATE_MESSAGES.get(template_data['offer_type'])
should_update = False
if (new_message and legacy_message and existing.message_text == legacy_message) or (
new_message and ('{bonus_amount' in existing.message_text or 'Мы начислим' in existing.message_text)
):
should_update = True
if should_update and new_message:
existing.message_text = new_message
existing.updated_at = datetime.utcnow()
await db.flush()
target_active_hours = template_data.get('active_discount_hours')
if target_active_hours is not None and target_active_hours > 0 and not existing.active_discount_hours:
existing.active_discount_hours = target_active_hours
existing.updated_at = datetime.utcnow()
await db.flush()
templates.append(existing)
continue
payload = _format_template_fields(template_data)
template = PromoOfferTemplate(
name=payload['name'],
offer_type=payload['offer_type'],
message_text=payload['message_text'],
button_text=payload['button_text'],
valid_hours=payload['valid_hours'],
discount_percent=payload['discount_percent'],
bonus_amount_kopeks=payload['bonus_amount_kopeks'],
active_discount_hours=payload['active_discount_hours'],
test_duration_hours=payload['test_duration_hours'],
test_squad_uuids=payload['test_squad_uuids'],
is_active=True,
created_by=created_by,
)
db.add(template)
await db.flush()
templates.append(template)
await db.commit()
return templates
async def list_promo_offer_templates(db: AsyncSession) -> list[PromoOfferTemplate]:
result = await db.execute(select(PromoOfferTemplate).order_by(PromoOfferTemplate.offer_type, PromoOfferTemplate.id))
return result.scalars().all()
async def get_promo_offer_template_by_id(db: AsyncSession, template_id: int) -> PromoOfferTemplate | None:
result = await db.execute(select(PromoOfferTemplate).where(PromoOfferTemplate.id == template_id))
return result.scalar_one_or_none()
async def get_promo_offer_template_by_type(db: AsyncSession, offer_type: str) -> PromoOfferTemplate | None:
result = await db.execute(select(PromoOfferTemplate).where(PromoOfferTemplate.offer_type == offer_type))
return result.scalar_one_or_none()
async def update_promo_offer_template(
db: AsyncSession,
template: PromoOfferTemplate,
*,
name: str | None = None,
message_text: str | None = None,
button_text: str | None = None,
valid_hours: int | None = None,
discount_percent: int | None = None,
bonus_amount_kopeks: int | None = None,
active_discount_hours: int | None = None,
test_duration_hours: int | None = None,
test_squad_uuids: Iterable[str] | None = None,
is_active: bool | None = None,
) -> PromoOfferTemplate:
if name is not None:
template.name = name
if message_text is not None:
template.message_text = message_text
if button_text is not None:
template.button_text = button_text
if valid_hours is not None:
template.valid_hours = valid_hours
if discount_percent is not None:
template.discount_percent = discount_percent
if bonus_amount_kopeks is not None:
template.bonus_amount_kopeks = bonus_amount_kopeks
if active_discount_hours is not None:
template.active_discount_hours = active_discount_hours
if test_duration_hours is not None:
template.test_duration_hours = test_duration_hours
if test_squad_uuids is not None:
template.test_squad_uuids = list(test_squad_uuids)
if is_active is not None:
template.is_active = is_active
template.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(template)
return template