Züchter-Bereich (Hub) + Settings-Partner-Karte raus + Admin: alle Code-Einlösungen mit Kanal

- Neue Seite #breeder-dashboard (Welten-Chip 'Züchter' role:breeder in HUND,
  ersetzt die Einzel-Chips Zuchtkartei + Wurfverw.; beide FABs wandern an den
  neuen Chip; Läufigkeit bleibt eigener Chip in HUND, Rene-Vorgabe):
  Zwinger-Karte (Name, verifiziert-Badge, Profil-Editor), Wurfverwaltung mit
  Wurf-Anzahl, Zuchtkartei mit Hunde-Anzahl. Einzelseiten bleiben erreichbar.
- Settings: Partner-Karte entfernt — der 🤝-Welten-Chip ist der Einstieg.
- Admin 'Aktive Codes': 👥 zeigt jetzt ALLE Einlösungen eines Codes mit
  Kanal-Badge (QR #seq aus Kontingent vs. Link/manuell), Datum und
  Bestätigt-Status — Endpoint /admin/partner/codes/{id}/registrations.
Suite: 55 passed.
This commit is contained in:
rene 2026-06-07 19:55:51 +02:00
parent 6a6a09d879
commit ed7c469c6a
11 changed files with 241 additions and 44 deletions

View file

@ -48,6 +48,28 @@ def list_partner_codes(user=Depends(require_admin)):
return [dict(r) for r in rows]
@router.get("/admin/partner/codes/{code_id}/registrations")
def code_registrations(code_id: int, user=Depends(require_admin)):
"""ALLE Einlösungen eines Partner-Codes — mit Kanal (QR-Sticker vs. Link/manuell).
Admin-only (personenbezogene Daten)."""
with db() as conn:
if not conn.execute(
"SELECT id FROM partner_codes WHERE id=?", (code_id,)
).fetchone():
raise HTTPException(404, "Partner-Code nicht gefunden.")
rows = conn.execute(
"""SELECT u.id, u.name, u.email, u.email_verified, u.created_at,
q.seq AS qr_seq, b.label AS qr_batch_label
FROM users u
LEFT JOIN partner_qr_codes q ON q.token = u.referred_qr
LEFT JOIN partner_qr_batches b ON b.id = q.batch_id
WHERE u.referred_by = ?
ORDER BY u.created_at DESC""",
(-code_id,)
).fetchall()
return [dict(r) for r in rows]
@router.post("/admin/partner/codes/{code_id}/toggle")
def toggle_partner_code(code_id: int, user=Depends(require_admin)):
"""Notbremse: Code pausieren/reaktivieren (z. B. wenn er im Internet kursiert).