diff --git a/app/cabinet/routes/admin_bulk_actions.py b/app/cabinet/routes/admin_bulk_actions.py index a0b1e5c6..5b970898 100644 --- a/app/cabinet/routes/admin_bulk_actions.py +++ b/app/cabinet/routes/admin_bulk_actions.py @@ -502,6 +502,16 @@ async def _do_delete_subscription( tariff_name = sub.tariff.name if sub.tariff else f'#{sub.id}' + # Protect active paid subscriptions from accidental deletion + if sub.is_active and not sub.is_trial and not params.force_delete_active_paid: + return BulkUserResult( + user_id=user.id, + success=False, + message=f'Skipped: {tariff_name} is active and paid (enable force_delete_active_paid to override)', + username=user.username, + subscriptions=_build_subscription_info(getattr(user, 'subscriptions', None) or []), + ) + if dry_run: return BulkUserResult( user_id=user.id, diff --git a/app/cabinet/routes/admin_users.py b/app/cabinet/routes/admin_users.py index 54beef32..2b892696 100644 --- a/app/cabinet/routes/admin_users.py +++ b/app/cabinet/routes/admin_users.py @@ -158,6 +158,7 @@ def _build_user_list_item(user: User, spending_stats: dict = None) -> UserListIt tariff_id=s.tariff_id, tariff_name=s.tariff.name if s.tariff else None, status=s.status, + is_trial=bool(s.is_trial), end_date=s.end_date, days_remaining=s_days, traffic_used_gb=s.traffic_used_gb or 0.0, diff --git a/app/cabinet/schemas/bulk_actions.py b/app/cabinet/schemas/bulk_actions.py index 26587fd5..e7cbb2a6 100644 --- a/app/cabinet/schemas/bulk_actions.py +++ b/app/cabinet/schemas/bulk_actions.py @@ -29,6 +29,7 @@ class BulkActionParams(BaseModel): promo_group_id: int | None = None device_limit: int | None = Field(None, ge=1, le=50) delete_from_panel: bool = Field(default=True) + force_delete_active_paid: bool = Field(default=False) class BulkSubscriptionInfo(BaseModel): diff --git a/app/cabinet/schemas/users.py b/app/cabinet/schemas/users.py index 3b48e2f9..f151b7c2 100644 --- a/app/cabinet/schemas/users.py +++ b/app/cabinet/schemas/users.py @@ -89,6 +89,7 @@ class SubscriptionListItem(BaseModel): tariff_id: int | None = None tariff_name: str | None = None status: str + is_trial: bool = False end_date: datetime | None = None days_remaining: int = 0 traffic_used_gb: float = 0