feat: protect active paid subscriptions from bulk delete

- Backend: _do_delete_subscription refuses to delete active paid subs
  unless force_delete_active_paid=true is explicitly passed
- Backend: add force_delete_active_paid to BulkActionParams (default false)
- Backend: add is_trial to SubscriptionListItem schema + populate it
This commit is contained in:
Fringg
2026-04-29 11:31:08 +03:00
parent 443a826402
commit 51dfc3a1a2
4 changed files with 13 additions and 0 deletions
+10
View File
@@ -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,
+1
View File
@@ -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,
+1
View File
@@ -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):
+1
View File
@@ -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