Feature: QR-Kontingente für Partner — Bestellung, Übergabe, Rückverfolgung
Partner verteilen gedruckte QR-Codes (Sticker/Flyer); jeder physische Code
ist einzeln rückverfolgbar von Scan bis Registrierung.
Backend:
- partner_qr_batches + partner_qr_codes (Token 8-stellig, ohne 0/O/1/l/I),
users.referred_qr, partner_codes.owner_user_id (+Backfill über referred_by)
- /q/{token}: Scan zählen (scans, first/last_scan_at) → Redirect
/?ref=CODE&qr=TOKEN — dockt am bestehenden Referral-Flow an
- Registrierung: qr_token wird nur zugeordnet, wenn er zum eingelösten
Partner-Code gehört (Manipulationsschutz)
- Admin: Kontingent bestellen (max 500), Liste mit Scans/Registrierungen,
Löschen (Zweiklick), druckfertiges A4-PDF (segno+fpdf2, 3×4 Grid mit
Kurz-URL + laufender Nummer), Code-Besitzer zuordnen
- Partner-Self-Service: /partner/my-qr (+PDF) für Code-Besitzer
Frontend:
- Admin-Partner-Tab: Karte 'QR-Kontingente' (Bestellung, Stats, PDF, Besitzer)
- Partner-Profil: 'Meine QR-Codes' mit Scans/Registrierungen + PDF-Download
- boot.js/app.js speichern ?qr=, Registrierung schickt qr_token mit
Neu: segno==1.6.6 (pure-python QR). Tests: 5 neue (PDF, Scan-Zählung,
Attribution, Fremd-Token-Schutz, Self-Service). Suite: 51 passed.
This commit is contained in:
parent
cadfb24a8d
commit
f604ab7c4f
16 changed files with 621 additions and 23 deletions
|
|
@ -153,6 +153,7 @@ class RegisterRequest(BaseModel):
|
|||
password: str = Field(..., min_length=8, max_length=200)
|
||||
name: str = Field(..., min_length=2, max_length=40)
|
||||
ref_code: Optional[str] = Field(None, max_length=50)
|
||||
qr_token: Optional[str] = Field(None, max_length=20) # physischer Partner-QR (Sticker/Flyer)
|
||||
|
||||
|
||||
def _gen_referral_code() -> str:
|
||||
|
|
@ -227,6 +228,16 @@ async def register(data: RegisterRequest, response: Response, request: Request):
|
|||
|
||||
if redeemed:
|
||||
updates = {"referred_by": -partner["id"]}
|
||||
# QR-Rückverfolgung: Token muss zu einem Kontingent DIESES Codes gehören
|
||||
if data.qr_token:
|
||||
qr = conn.execute(
|
||||
"""SELECT q.token FROM partner_qr_codes q
|
||||
JOIN partner_qr_batches b ON b.id = q.batch_id
|
||||
WHERE q.token=? AND b.partner_code_id=?""",
|
||||
(data.qr_token.strip(), partner["id"])
|
||||
).fetchone()
|
||||
if qr:
|
||||
updates["referred_qr"] = qr["token"]
|
||||
if partner["grants_founder"]:
|
||||
total_founders = conn.execute(
|
||||
"SELECT COUNT(*) FROM users WHERE is_founder=1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue