diff --git a/bot/app/web/admin_api.py b/bot/app/web/admin_api.py index 4b6a201..277ab81 100644 --- a/bot/app/web/admin_api.py +++ b/bot/app/web/admin_api.py @@ -262,14 +262,34 @@ def _payment_traffic_gb_split(payment: Payment) -> Tuple[Optional[float], Option return None, None +def _payment_user_display_label(loaded_user: Any, payment_user_id: int) -> str: + """Human-facing name for payments tables: TG profile name, else email, else user id.""" + if loaded_user is None: + return str(payment_user_id) + tid = getattr(loaded_user, "telegram_id", None) + if tid is not None: + fn = (getattr(loaded_user, "first_name", None) or "").strip() + ln = (getattr(loaded_user, "last_name", None) or "").strip() + full = f"{fn} {ln}".strip() + if full: + return full + un = (getattr(loaded_user, "username", None) or "").strip() + if un: + return un if un.startswith("@") else f"@{un}" + return str(payment_user_id) + email = (getattr(loaded_user, "email", None) or "").strip() + if email: + return email + return str(payment_user_id) + + def _serialize_payment(payment: Payment) -> Dict[str, Any]: # Avoid lazy-loading `payment.user` outside an active SQLAlchemy session. # Some admin routes serialize payments after the session scope is closed. - user_label = str(payment.user_id) telegram_id = None loaded_user = payment.__dict__.get("user") + user_label = _payment_user_display_label(loaded_user, int(payment.user_id)) if loaded_user is not None: - user_label = loaded_user.username or loaded_user.first_name or str(payment.user_id) tid = getattr(loaded_user, "telegram_id", None) if tid is not None: try: @@ -1550,9 +1570,7 @@ async def admin_payments_export_route(request: web.Request) -> web.Response: ] ) for p in rows: - label = "" - if p.user: - label = p.user.username or p.user.first_name or "" + label = _payment_user_display_label(p.user, int(p.user_id)) if p.user else str(p.user_id) writer.writerow( [ p.payment_id, diff --git a/bot/app/web/frontend/src/admin/AdminPanel.svelte b/bot/app/web/frontend/src/admin/AdminPanel.svelte index a364c09..b7e64ac 100644 --- a/bot/app/web/frontend/src/admin/AdminPanel.svelte +++ b/bot/app/web/frontend/src/admin/AdminPanel.svelte @@ -262,7 +262,8 @@ function openPaymentUserCard(userId) { const uid = Number(userId); - if (!Number.isFinite(uid) || uid <= 0) return; + // Synthetic email-only users use negative user_id; still a valid admin target. + if (!Number.isFinite(uid) || uid === 0) return; const next = normalizeSection("users"); sidebarOpen = false; if (active !== next) { diff --git a/bot/app/web/frontend/src/admin/sections/PaymentsSection.svelte b/bot/app/web/frontend/src/admin/sections/PaymentsSection.svelte index e876712..c247596 100644 --- a/bot/app/web/frontend/src/admin/sections/PaymentsSection.svelte +++ b/bot/app/web/frontend/src/admin/sections/PaymentsSection.svelte @@ -72,7 +72,7 @@ $: paymentHeaders = [ at("id", {}, "ID"), at("user", {}, "Пользователь"), - at("payments_col_telegram_id", {}, "Telegram ID"), + at("payments_col_user_id", {}, "ID"), at("payments_col_traffic_regular", {}, "Основной трафик"), at("payments_col_traffic_premium", {}, "Премиум"), at("amount", {}, "Сумма"), @@ -104,7 +104,7 @@ {at("id", {}, "ID")} {at("user", {}, "Пользователь")} - {at("payments_col_telegram_id", {}, "Telegram ID")} + {at("payments_col_user_id", {}, "ID")} {at("payments_col_traffic_regular", {}, "Основной трафик")} {at("payments_col_traffic_premium", {}, "Премиум")} {at("amount", {}, "Сумма")} @@ -133,11 +133,8 @@ {p.user_label || p.user_id} - - {p.telegram_id != null ? p.telegram_id : "—"} + + {p.user_id != null && p.user_id !== "" ? p.user_id : "—"}