diff --git a/VERSION b/VERSION index 1e36b91..6ee69cb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1258 \ No newline at end of file +1259 \ No newline at end of file diff --git a/backend/routes/partner.py b/backend/routes/partner.py index ba3802e..23f0bcd 100644 --- a/backend/routes/partner.py +++ b/backend/routes/partner.py @@ -583,9 +583,12 @@ def _qr_batch_stats(conn, batch_id: int) -> dict: 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 + WHERE q2.batch_id = ? AND u.email_verified = 0) AS attempts, + (SELECT COUNT(DISTINCT q3.id) FROM partner_qr_codes q3 + JOIN users u ON u.referred_qr = q3.token AND u.email_verified = 1 + WHERE q3.batch_id = ?) AS codes_used FROM partner_qr_codes q WHERE q.batch_id = ?""", - (batch_id, batch_id, batch_id) + (batch_id, batch_id, batch_id, batch_id) ).fetchone() return dict(row) @@ -756,18 +759,44 @@ def my_qr_batches(user=Depends(require_partner)): ) +def _require_own_batch(conn, batch_id: int, user: dict): + own = conn.execute( + """SELECT b.id FROM partner_qr_batches b + JOIN partner_codes pc ON pc.id = b.partner_code_id + WHERE b.id=? AND pc.owner_user_id=?""", + (batch_id, user["id"]) + ).fetchone() + if not own and user.get("rolle") != "admin": + raise HTTPException(403, "Kein Zugriff auf dieses Kontingent.") + + +@router.get("/partner/my-qr/{batch_id}/codes") +def my_qr_batch_codes(batch_id: int, user=Depends(require_partner)): + """Einzel-Code-Status fürs eigene Kontingent: welcher Sticker ist verbraucht? + Keine personenbezogenen Daten — nur Zähler und Zeitstempel.""" + with db() as conn: + _require_own_batch(conn, batch_id, user) + rows = conn.execute( + """SELECT q.seq, q.token, q.scans, q.last_scan_at, + (SELECT COUNT(*) FROM users u + WHERE u.referred_qr = q.token AND u.email_verified = 1) AS registrations, + (SELECT COUNT(*) FROM users u + WHERE u.referred_qr = q.token AND u.email_verified = 0) AS attempts, + (SELECT MIN(u.created_at) FROM users u + WHERE u.referred_qr = q.token AND u.email_verified = 1) AS first_registration_at + FROM partner_qr_codes q + WHERE q.batch_id = ? + ORDER BY q.seq""", + (batch_id,) + ).fetchall() + return [dict(r) for r in rows] + + @router.get("/partner/my-qr/{batch_id}/pdf") def qr_batch_pdf_partner(batch_id: int, user=Depends(require_partner)): from fastapi.responses import Response with db() as conn: - own = conn.execute( - """SELECT b.id FROM partner_qr_batches b - JOIN partner_codes pc ON pc.id = b.partner_code_id - WHERE b.id=? AND pc.owner_user_id=?""", - (batch_id, user["id"]) - ).fetchone() - if not own and user.get("rolle") != "admin": - raise HTTPException(403, "Kein Zugriff auf dieses Kontingent.") + _require_own_batch(conn, batch_id, user) pdf = _qr_batch_pdf(conn, batch_id) return Response(content=pdf, media_type="application/pdf", headers={"Content-Disposition": f'attachment; filename="banyaro-qr-{batch_id}.pdf"'}) diff --git a/backend/static/index.html b/backend/static/index.html index 99aaef9..08c14dc 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@
banyaro.app/q/${UI.escape(c.token)}
+ ${c.scans} Scan${c.scans === 1 ? '' : 's'}
+ ${used
+ ? `● verbraucht`
+ : scanned
+ ? `◐ gescannt`
+ : `○ frei`}
+