QR-Stats: Registrierungen (bestätigt) vs. Versuche (unbestätigt) + Account-Detail-Liste

Rene: Statistik zählte alles in einen Topf (3 statt 2) und zeigte nicht,
WER sich registriert hat. Jetzt:
- registrations = email_verified=1, attempts = unbestätigte Versuche —
  Versuche werden bei späterer Bestätigung automatisch zu Registrierungen
- Admin: 👥-Button pro Kontingent klappt Account-Liste auf (Name, E-Mail,
  Datum, ✓ bestätigt/ Versuch, Sticker-Nr #seq) — lazy geladen, admin-only
  (personenbezogene Daten); Partner sehen weiter nur Zahlen (Registr. +N)
- Test deckt Versuch→Bestätigung-Übergang und Detail-Endpoint ab
This commit is contained in:
rene 2026-06-07 18:43:18 +02:00
parent f604ab7c4f
commit 970480c1d6
9 changed files with 110 additions and 26 deletions

View file

@ -575,13 +575,17 @@ def _qr_new_token(conn) -> str:
def _qr_batch_stats(conn, batch_id: int) -> dict:
"""Registrierungen = E-Mail bestätigt; Versuche = registriert, aber (noch) unbestätigt."""
row = conn.execute(
"""SELECT COUNT(*) AS codes, COALESCE(SUM(q.scans),0) AS scans,
(SELECT COUNT(*) FROM users u
JOIN partner_qr_codes q2 ON q2.token = u.referred_qr
WHERE q2.batch_id = ?) AS registrations
WHERE q2.batch_id = ? AND u.email_verified = 1) AS registrations,
(SELECT COUNT(*) FROM users u
JOIN partner_qr_codes q2 ON q2.token = u.referred_qr
WHERE q2.batch_id = ? AND u.email_verified = 0) AS attempts
FROM partner_qr_codes q WHERE q.batch_id = ?""",
(batch_id, batch_id)
(batch_id, batch_id, batch_id)
).fetchone()
return dict(row)
@ -713,6 +717,27 @@ def delete_qr_batch(batch_id: int, user=Depends(require_admin)):
return None
@router.get("/admin/partner/qr-batches/{batch_id}/registrations")
def qr_batch_registrations(batch_id: int, user=Depends(require_admin)):
"""Accounts, die über dieses Kontingent kamen — inkl. unbestätigter Versuche.
Admin-only (personenbezogene Daten)."""
with db() as conn:
if not conn.execute(
"SELECT id FROM partner_qr_batches WHERE id=?", (batch_id,)
).fetchone():
raise HTTPException(404, "Kontingent nicht gefunden.")
rows = conn.execute(
"""SELECT u.id, u.name, u.email, u.email_verified, u.created_at,
q.seq, q.token
FROM users u
JOIN partner_qr_codes q ON q.token = u.referred_qr
WHERE q.batch_id = ?
ORDER BY u.created_at DESC""",
(batch_id,)
).fetchall()
return [dict(r) for r in rows]
@router.get("/admin/partner/qr-batches/{batch_id}/pdf")
def qr_batch_pdf_admin(batch_id: int, user=Depends(require_admin)):
from fastapi.responses import Response