mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
@@ -24,6 +24,8 @@ from .admin_servers import router as admin_servers_router
|
||||
from .admin_stats import router as admin_stats_router
|
||||
from .admin_ban_system import router as admin_ban_system_router
|
||||
from .admin_broadcasts import router as admin_broadcasts_router
|
||||
from .admin_promocodes import router as admin_promocodes_router
|
||||
from .admin_promocodes import promo_groups_router as admin_promo_groups_router
|
||||
from .media import router as media_router
|
||||
|
||||
# Main cabinet router
|
||||
@@ -57,5 +59,7 @@ router.include_router(admin_servers_router)
|
||||
router.include_router(admin_stats_router)
|
||||
router.include_router(admin_ban_system_router)
|
||||
router.include_router(admin_broadcasts_router)
|
||||
router.include_router(admin_promocodes_router)
|
||||
router.include_router(admin_promo_groups_router)
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
626
app/cabinet/routes/admin_promocodes.py
Normal file
626
app/cabinet/routes/admin_promocodes.py
Normal file
@@ -0,0 +1,626 @@
|
||||
"""Admin promocodes routes for cabinet."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database.crud.promocode import (
|
||||
create_promocode,
|
||||
delete_promocode,
|
||||
get_promocode_by_code,
|
||||
get_promocode_by_id,
|
||||
get_promocode_statistics,
|
||||
get_promocodes_count,
|
||||
get_promocodes_list,
|
||||
update_promocode,
|
||||
)
|
||||
from app.database.crud.promo_group import (
|
||||
count_promo_group_members,
|
||||
count_promo_groups,
|
||||
create_promo_group,
|
||||
delete_promo_group,
|
||||
get_promo_group_by_id,
|
||||
get_promo_groups_with_counts,
|
||||
update_promo_group,
|
||||
)
|
||||
from app.database.models import PromoCode, PromoCodeType, PromoCodeUse, PromoGroup, User
|
||||
|
||||
from ..dependencies import get_cabinet_db, get_current_admin_user
|
||||
|
||||
router = APIRouter(prefix="/admin/promocodes", tags=["Admin Promocodes"])
|
||||
|
||||
|
||||
# ============== Schemas ==============
|
||||
|
||||
class PromoCodeResponse(BaseModel):
|
||||
id: int
|
||||
code: str
|
||||
type: PromoCodeType
|
||||
balance_bonus_kopeks: int
|
||||
balance_bonus_rubles: float
|
||||
subscription_days: int
|
||||
max_uses: int
|
||||
current_uses: int
|
||||
uses_left: int
|
||||
is_active: bool
|
||||
is_valid: bool
|
||||
first_purchase_only: bool
|
||||
valid_from: datetime
|
||||
valid_until: Optional[datetime] = None
|
||||
promo_group_id: Optional[int] = None
|
||||
created_by: Optional[int] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class PromoCodeListResponse(BaseModel):
|
||||
items: list[PromoCodeResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
|
||||
|
||||
class PromoCodeRecentUse(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
user_username: Optional[str] = None
|
||||
user_full_name: Optional[str] = None
|
||||
user_telegram_id: Optional[int] = None
|
||||
used_at: datetime
|
||||
|
||||
|
||||
class PromoCodeDetailResponse(PromoCodeResponse):
|
||||
total_uses: int
|
||||
today_uses: int
|
||||
recent_uses: list[PromoCodeRecentUse] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PromoCodeCreateRequest(BaseModel):
|
||||
code: str = Field(..., min_length=1, max_length=50)
|
||||
type: PromoCodeType
|
||||
balance_bonus_kopeks: int = 0
|
||||
subscription_days: int = 0
|
||||
max_uses: int = Field(default=1, ge=0)
|
||||
valid_from: Optional[datetime] = None
|
||||
valid_until: Optional[datetime] = None
|
||||
is_active: bool = True
|
||||
first_purchase_only: bool = False
|
||||
promo_group_id: Optional[int] = None
|
||||
|
||||
|
||||
class PromoCodeUpdateRequest(BaseModel):
|
||||
code: Optional[str] = Field(default=None, min_length=1, max_length=50)
|
||||
type: Optional[PromoCodeType] = None
|
||||
balance_bonus_kopeks: Optional[int] = None
|
||||
subscription_days: Optional[int] = None
|
||||
max_uses: Optional[int] = Field(default=None, ge=0)
|
||||
valid_from: Optional[datetime] = None
|
||||
valid_until: Optional[datetime] = None
|
||||
is_active: Optional[bool] = None
|
||||
first_purchase_only: Optional[bool] = None
|
||||
promo_group_id: Optional[int] = None
|
||||
|
||||
|
||||
# ============== PromoGroup Schemas ==============
|
||||
|
||||
class PromoGroupResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
server_discount_percent: int
|
||||
traffic_discount_percent: int
|
||||
device_discount_percent: int
|
||||
period_discounts: dict[int, int] = Field(default_factory=dict)
|
||||
auto_assign_total_spent_kopeks: Optional[int] = None
|
||||
apply_discounts_to_addons: bool
|
||||
is_default: bool
|
||||
members_count: int = 0
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class PromoGroupListResponse(BaseModel):
|
||||
items: list[PromoGroupResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
|
||||
|
||||
class PromoGroupCreateRequest(BaseModel):
|
||||
name: str
|
||||
server_discount_percent: int = 0
|
||||
traffic_discount_percent: int = 0
|
||||
device_discount_percent: int = 0
|
||||
period_discounts: Optional[dict[int, int]] = None
|
||||
auto_assign_total_spent_kopeks: Optional[int] = None
|
||||
apply_discounts_to_addons: bool = True
|
||||
is_default: bool = False
|
||||
|
||||
|
||||
class PromoGroupUpdateRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
server_discount_percent: Optional[int] = None
|
||||
traffic_discount_percent: Optional[int] = None
|
||||
device_discount_percent: Optional[int] = None
|
||||
period_discounts: Optional[dict[int, int]] = None
|
||||
auto_assign_total_spent_kopeks: Optional[int] = None
|
||||
apply_discounts_to_addons: Optional[bool] = None
|
||||
is_default: Optional[bool] = None
|
||||
|
||||
|
||||
# ============== Helpers ==============
|
||||
|
||||
def _normalize_datetime(value: Optional[datetime]) -> Optional[datetime]:
|
||||
if value is None:
|
||||
return None
|
||||
if value.tzinfo is not None and value.utcoffset() is not None:
|
||||
return value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
if value.tzinfo is not None:
|
||||
return value.replace(tzinfo=None)
|
||||
return value
|
||||
|
||||
|
||||
def _serialize_promocode(promocode: PromoCode) -> PromoCodeResponse:
|
||||
promo_type = PromoCodeType(promocode.type)
|
||||
return PromoCodeResponse(
|
||||
id=promocode.id,
|
||||
code=promocode.code,
|
||||
type=promo_type,
|
||||
balance_bonus_kopeks=promocode.balance_bonus_kopeks,
|
||||
balance_bonus_rubles=round(promocode.balance_bonus_kopeks / 100, 2),
|
||||
subscription_days=promocode.subscription_days,
|
||||
max_uses=promocode.max_uses,
|
||||
current_uses=promocode.current_uses,
|
||||
uses_left=promocode.uses_left,
|
||||
is_active=promocode.is_active,
|
||||
is_valid=promocode.is_valid,
|
||||
first_purchase_only=promocode.first_purchase_only,
|
||||
valid_from=promocode.valid_from,
|
||||
valid_until=promocode.valid_until,
|
||||
promo_group_id=promocode.promo_group_id,
|
||||
created_by=promocode.created_by,
|
||||
created_at=promocode.created_at,
|
||||
updated_at=promocode.updated_at,
|
||||
)
|
||||
|
||||
|
||||
def _serialize_recent_use(use: PromoCodeUse) -> PromoCodeRecentUse:
|
||||
return PromoCodeRecentUse(
|
||||
id=use.id,
|
||||
user_id=use.user_id,
|
||||
user_username=getattr(use, "user_username", None),
|
||||
user_full_name=getattr(use, "user_full_name", None),
|
||||
user_telegram_id=getattr(use, "user_telegram_id", None),
|
||||
used_at=use.used_at,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_period_discounts(group: PromoGroup) -> dict[int, int]:
|
||||
raw = group.period_discounts or {}
|
||||
normalized: dict[int, int] = {}
|
||||
if isinstance(raw, dict):
|
||||
for key, value in raw.items():
|
||||
try:
|
||||
normalized[int(key)] = int(value)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return normalized
|
||||
|
||||
|
||||
def _serialize_promo_group(group: PromoGroup, members_count: int = 0) -> PromoGroupResponse:
|
||||
return PromoGroupResponse(
|
||||
id=group.id,
|
||||
name=group.name,
|
||||
server_discount_percent=group.server_discount_percent,
|
||||
traffic_discount_percent=group.traffic_discount_percent,
|
||||
device_discount_percent=group.device_discount_percent,
|
||||
period_discounts=_normalize_period_discounts(group),
|
||||
auto_assign_total_spent_kopeks=group.auto_assign_total_spent_kopeks,
|
||||
apply_discounts_to_addons=group.apply_discounts_to_addons,
|
||||
is_default=group.is_default,
|
||||
members_count=members_count,
|
||||
created_at=getattr(group, "created_at", None),
|
||||
updated_at=getattr(group, "updated_at", None),
|
||||
)
|
||||
|
||||
|
||||
def _validate_create_payload(payload: PromoCodeCreateRequest) -> None:
|
||||
code = payload.code.strip()
|
||||
if not code:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Code must not be empty")
|
||||
|
||||
normalized_valid_from = _normalize_datetime(payload.valid_from)
|
||||
normalized_valid_until = _normalize_datetime(payload.valid_until)
|
||||
|
||||
if payload.type == PromoCodeType.BALANCE and payload.balance_bonus_kopeks <= 0:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Balance bonus must be positive for balance promo codes"
|
||||
)
|
||||
|
||||
if payload.type in {PromoCodeType.SUBSCRIPTION_DAYS, PromoCodeType.TRIAL_SUBSCRIPTION}:
|
||||
if payload.subscription_days <= 0:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Subscription days must be positive for this promo code type"
|
||||
)
|
||||
|
||||
if normalized_valid_from and normalized_valid_until and normalized_valid_from > normalized_valid_until:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"valid_from cannot be greater than valid_until"
|
||||
)
|
||||
|
||||
|
||||
def _validate_update_payload(payload: PromoCodeUpdateRequest, promocode: PromoCode) -> None:
|
||||
if payload.code is not None and not payload.code.strip():
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Code must not be empty")
|
||||
|
||||
if payload.type is not None:
|
||||
new_type = payload.type
|
||||
else:
|
||||
new_type = PromoCodeType(promocode.type)
|
||||
|
||||
balance_bonus = (
|
||||
payload.balance_bonus_kopeks
|
||||
if payload.balance_bonus_kopeks is not None
|
||||
else promocode.balance_bonus_kopeks
|
||||
)
|
||||
subscription_days = (
|
||||
payload.subscription_days
|
||||
if payload.subscription_days is not None
|
||||
else promocode.subscription_days
|
||||
)
|
||||
|
||||
if new_type == PromoCodeType.BALANCE and balance_bonus <= 0:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Balance bonus must be positive for balance promo codes"
|
||||
)
|
||||
|
||||
if new_type in {PromoCodeType.SUBSCRIPTION_DAYS, PromoCodeType.TRIAL_SUBSCRIPTION}:
|
||||
if subscription_days <= 0:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Subscription days must be positive for this promo code type"
|
||||
)
|
||||
|
||||
valid_from = (
|
||||
_normalize_datetime(payload.valid_from)
|
||||
if payload.valid_from is not None
|
||||
else promocode.valid_from
|
||||
)
|
||||
valid_until = (
|
||||
_normalize_datetime(payload.valid_until)
|
||||
if payload.valid_until is not None
|
||||
else promocode.valid_until
|
||||
)
|
||||
|
||||
if valid_from and valid_until and valid_from > valid_until:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"valid_from cannot be greater than valid_until"
|
||||
)
|
||||
|
||||
if payload.max_uses is not None and payload.max_uses != 0 and payload.max_uses < promocode.current_uses:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"max_uses cannot be less than current uses"
|
||||
)
|
||||
|
||||
|
||||
# ============== Promocode Endpoints ==============
|
||||
|
||||
@router.get("", response_model=PromoCodeListResponse)
|
||||
async def list_promocodes(
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
is_active: Optional[bool] = Query(default=None),
|
||||
) -> PromoCodeListResponse:
|
||||
"""Get list of all promocodes."""
|
||||
total = await get_promocodes_count(db, is_active=is_active) or 0
|
||||
promocodes = await get_promocodes_list(db, offset=offset, limit=limit, is_active=is_active)
|
||||
|
||||
return PromoCodeListResponse(
|
||||
items=[_serialize_promocode(promocode) for promocode in promocodes],
|
||||
total=int(total),
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{promocode_id}", response_model=PromoCodeDetailResponse)
|
||||
async def get_promocode(
|
||||
promocode_id: int,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoCodeDetailResponse:
|
||||
"""Get promocode details with usage statistics."""
|
||||
promocode = await get_promocode_by_id(db, promocode_id)
|
||||
if not promocode:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found")
|
||||
|
||||
stats = await get_promocode_statistics(db, promocode_id)
|
||||
base = _serialize_promocode(promocode)
|
||||
recent_uses = [
|
||||
_serialize_recent_use(use)
|
||||
for use in stats.get("recent_uses", [])
|
||||
]
|
||||
|
||||
return PromoCodeDetailResponse(
|
||||
**base.model_dump(),
|
||||
total_uses=stats.get("total_uses", 0),
|
||||
today_uses=stats.get("today_uses", 0),
|
||||
recent_uses=recent_uses,
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=PromoCodeResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_promocode_endpoint(
|
||||
payload: PromoCodeCreateRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoCodeResponse:
|
||||
"""Create a new promocode."""
|
||||
_validate_create_payload(payload)
|
||||
|
||||
normalized_code = payload.code.strip().upper()
|
||||
normalized_valid_from = _normalize_datetime(payload.valid_from)
|
||||
normalized_valid_until = _normalize_datetime(payload.valid_until)
|
||||
|
||||
existing = await get_promocode_by_code(db, normalized_code)
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Promo code with this code already exists"
|
||||
)
|
||||
|
||||
promocode = await create_promocode(
|
||||
db,
|
||||
code=normalized_code,
|
||||
type=payload.type,
|
||||
balance_bonus_kopeks=payload.balance_bonus_kopeks,
|
||||
subscription_days=payload.subscription_days,
|
||||
max_uses=payload.max_uses,
|
||||
valid_until=normalized_valid_until,
|
||||
created_by=admin.id,
|
||||
)
|
||||
|
||||
update_fields = {}
|
||||
if normalized_valid_from is not None:
|
||||
update_fields["valid_from"] = normalized_valid_from
|
||||
if payload.is_active is not None and payload.is_active != promocode.is_active:
|
||||
update_fields["is_active"] = payload.is_active
|
||||
if normalized_valid_until is not None:
|
||||
update_fields["valid_until"] = normalized_valid_until
|
||||
if payload.first_purchase_only:
|
||||
update_fields["first_purchase_only"] = payload.first_purchase_only
|
||||
if payload.promo_group_id is not None:
|
||||
update_fields["promo_group_id"] = payload.promo_group_id
|
||||
|
||||
if update_fields:
|
||||
promocode = await update_promocode(db, promocode, **update_fields)
|
||||
|
||||
return _serialize_promocode(promocode)
|
||||
|
||||
|
||||
@router.patch("/{promocode_id}", response_model=PromoCodeResponse)
|
||||
async def update_promocode_endpoint(
|
||||
promocode_id: int,
|
||||
payload: PromoCodeUpdateRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoCodeResponse:
|
||||
"""Update an existing promocode."""
|
||||
promocode = await get_promocode_by_id(db, promocode_id)
|
||||
if not promocode:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found")
|
||||
|
||||
_validate_update_payload(payload, promocode)
|
||||
|
||||
updates: dict[str, Any] = {}
|
||||
|
||||
if payload.code is not None:
|
||||
normalized_code = payload.code.strip().upper()
|
||||
if normalized_code != promocode.code:
|
||||
existing = await get_promocode_by_code(db, normalized_code)
|
||||
if existing and existing.id != promocode_id:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Promo code with this code already exists"
|
||||
)
|
||||
updates["code"] = normalized_code
|
||||
|
||||
if payload.type is not None:
|
||||
updates["type"] = payload.type.value
|
||||
|
||||
if payload.balance_bonus_kopeks is not None:
|
||||
updates["balance_bonus_kopeks"] = payload.balance_bonus_kopeks
|
||||
|
||||
if payload.subscription_days is not None:
|
||||
updates["subscription_days"] = payload.subscription_days
|
||||
|
||||
if payload.max_uses is not None:
|
||||
updates["max_uses"] = payload.max_uses
|
||||
|
||||
if payload.valid_from is not None:
|
||||
updates["valid_from"] = _normalize_datetime(payload.valid_from)
|
||||
|
||||
if payload.valid_until is not None:
|
||||
updates["valid_until"] = _normalize_datetime(payload.valid_until)
|
||||
|
||||
if payload.is_active is not None:
|
||||
updates["is_active"] = payload.is_active
|
||||
|
||||
if payload.first_purchase_only is not None:
|
||||
updates["first_purchase_only"] = payload.first_purchase_only
|
||||
|
||||
if payload.promo_group_id is not None:
|
||||
updates["promo_group_id"] = payload.promo_group_id
|
||||
|
||||
if not updates:
|
||||
return _serialize_promocode(promocode)
|
||||
|
||||
promocode = await update_promocode(db, promocode, **updates)
|
||||
return _serialize_promocode(promocode)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{promocode_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
response_class=Response,
|
||||
)
|
||||
async def delete_promocode_endpoint(
|
||||
promocode_id: int,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> Response:
|
||||
"""Delete a promocode."""
|
||||
promocode = await get_promocode_by_id(db, promocode_id)
|
||||
if not promocode:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found")
|
||||
|
||||
success = await delete_promocode(db, promocode)
|
||||
if not success:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Failed to delete promo code")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# ============== PromoGroup Endpoints ==============
|
||||
|
||||
promo_groups_router = APIRouter(prefix="/admin/promo-groups", tags=["Admin Promo Groups"])
|
||||
|
||||
|
||||
@promo_groups_router.get("", response_model=PromoGroupListResponse)
|
||||
async def list_promo_groups(
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
) -> PromoGroupListResponse:
|
||||
"""Get list of all promo groups."""
|
||||
total = await count_promo_groups(db)
|
||||
groups_with_counts = await get_promo_groups_with_counts(
|
||||
db,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
return PromoGroupListResponse(
|
||||
items=[_serialize_promo_group(group, members_count=count) for group, count in groups_with_counts],
|
||||
total=total,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
|
||||
@promo_groups_router.get("/{group_id}", response_model=PromoGroupResponse)
|
||||
async def get_promo_group(
|
||||
group_id: int,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoGroupResponse:
|
||||
"""Get promo group details."""
|
||||
group = await get_promo_group_by_id(db, group_id)
|
||||
if not group:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo group not found")
|
||||
|
||||
members_count = await count_promo_group_members(db, group_id)
|
||||
return _serialize_promo_group(group, members_count=members_count)
|
||||
|
||||
|
||||
@promo_groups_router.post("", response_model=PromoGroupResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_promo_group_endpoint(
|
||||
payload: PromoGroupCreateRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoGroupResponse:
|
||||
"""Create a new promo group."""
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
try:
|
||||
group = await create_promo_group(
|
||||
db,
|
||||
name=payload.name,
|
||||
server_discount_percent=payload.server_discount_percent,
|
||||
traffic_discount_percent=payload.traffic_discount_percent,
|
||||
device_discount_percent=payload.device_discount_percent,
|
||||
period_discounts=payload.period_discounts,
|
||||
auto_assign_total_spent_kopeks=payload.auto_assign_total_spent_kopeks,
|
||||
apply_discounts_to_addons=payload.apply_discounts_to_addons,
|
||||
is_default=payload.is_default,
|
||||
)
|
||||
except IntegrityError:
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Promo group with this name already exists",
|
||||
)
|
||||
|
||||
return _serialize_promo_group(group, members_count=0)
|
||||
|
||||
|
||||
@promo_groups_router.patch("/{group_id}", response_model=PromoGroupResponse)
|
||||
async def update_promo_group_endpoint(
|
||||
group_id: int,
|
||||
payload: PromoGroupUpdateRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> PromoGroupResponse:
|
||||
"""Update a promo group."""
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
group = await get_promo_group_by_id(db, group_id)
|
||||
if not group:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo group not found")
|
||||
|
||||
try:
|
||||
group = await update_promo_group(
|
||||
db,
|
||||
group,
|
||||
name=payload.name,
|
||||
server_discount_percent=payload.server_discount_percent,
|
||||
traffic_discount_percent=payload.traffic_discount_percent,
|
||||
device_discount_percent=payload.device_discount_percent,
|
||||
period_discounts=payload.period_discounts,
|
||||
auto_assign_total_spent_kopeks=payload.auto_assign_total_spent_kopeks,
|
||||
apply_discounts_to_addons=payload.apply_discounts_to_addons,
|
||||
is_default=payload.is_default,
|
||||
)
|
||||
except IntegrityError:
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Promo group with this name already exists",
|
||||
)
|
||||
|
||||
members_count = await count_promo_group_members(db, group_id)
|
||||
return _serialize_promo_group(group, members_count=members_count)
|
||||
|
||||
|
||||
@promo_groups_router.delete("/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_promo_group_endpoint(
|
||||
group_id: int,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
) -> Response:
|
||||
"""Delete a promo group."""
|
||||
group = await get_promo_group_by_id(db, group_id)
|
||||
if not group:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo group not found")
|
||||
|
||||
success = await delete_promo_group(db, group)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
"Cannot delete default promo group"
|
||||
)
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
Reference in New Issue
Block a user