Rene: 'ungern jemandem auf ewig die Möglichkeit geben 50% Rabatt zu vergeben — bei 100 Gründern ein großer Faktor. Ich hätte jedem 25–50 Tickets gegeben.' - users.founder_referral_tickets (Default 25): Kontingent an 50%-Rabatten, die ein Gründer an Geworbene weitergeben kann. Technisch = die ersten N VERIFIZIERTEN Geworbenen (nach Anmeldedatum) bekommen 50%, danach 0. Unbestätigte verbrauchen kein Ticket. In scheduler.py (Rechnung) + admin.py (Vorschau) konsistent. - BUGFIX nebenbei: admin.py zeigte für referred_by_founder fälschlich 100% statt 50% (scheduler war korrekt) — jetzt beide 50%. - Admin: Grant-Formular bekommt Feld 'Gründer-Tickets' (0–200, Vorbelegung aus User-Stand); Endpoint /grant akzeptiert founder_tickets. - Gründer-Seite + Settings + Admin-Hilfe: 'sobald Bezahlfunktionen aktiv sind' raus (Pro kostet bereits); Vorteil 'lebenslang Pro gratis' + '25 Freunde zum halben Preis' (Ticket-Framing). - Tests: test_founder_tickets.py (Cap, Unverified-Schutz, 50%-Bugfix, Grant). Suite: 64 passed.
87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""Gründer-Tickets: 50%-Rabatt-Weitergabe ist pro Gründer auf sein Kontingent gedeckelt.
|
|
|
|
Hintergrund: Ein Gründer kann geworbenen Freunden 50% auf Pro schenken. Ohne Cap
|
|
könnten 100 Gründer unbegrenzt viele 50%-Rabatte vergeben — unkalkulierbare Liability.
|
|
Jeder Gründer hat daher ein Ticket-Kontingent (Standard 25), das die ersten N
|
|
verifizierten Geworbenen abdeckt.
|
|
"""
|
|
|
|
import secrets
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
def _make_founder(email, tickets=25):
|
|
from database import db
|
|
with db() as conn:
|
|
uid = conn.execute("SELECT id FROM users WHERE email=?", (email,)).fetchone()["id"]
|
|
conn.execute(
|
|
"UPDATE users SET is_founder=1, founder_number=99, founder_referral_tickets=? WHERE id=?",
|
|
(tickets, uid),
|
|
)
|
|
return uid
|
|
|
|
|
|
def _add_referred(founder_id, n, verified=True, base_minutes=0):
|
|
"""Legt n direkt in der DB an, die vom Gründer geworben wurden (mit gestaffeltem created_at)."""
|
|
from database import db
|
|
ids = []
|
|
with db() as conn:
|
|
for i in range(n):
|
|
ts = (datetime(2026, 1, 1) + timedelta(minutes=base_minutes + i)).isoformat()
|
|
conn.execute(
|
|
"""INSERT INTO users (email, name, pw_hash, referred_by, email_verified, created_at)
|
|
VALUES (?,?,?,?,?,?)""",
|
|
(f"ref-{secrets.token_hex(5)}@example.com", f"r{secrets.token_hex(3)}",
|
|
"x", founder_id, 1 if verified else 0, ts),
|
|
)
|
|
ids.append(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
|
return ids
|
|
|
|
|
|
def _discount(client, admin, uid):
|
|
r = client.get(f"/api/admin/users/{uid}/discount", headers=admin["headers"])
|
|
assert r.status_code == 200, r.text
|
|
return r.json()
|
|
|
|
|
|
def test_referred_by_founder_is_50_not_100(client, admin, user):
|
|
"""Bugfix-Absicherung: Geworbene eines Gründers bekommen 50%, nicht 100%."""
|
|
fid = _make_founder(user["email"], tickets=25)
|
|
friend = _add_referred(fid, 1)[0]
|
|
d = _discount(client, admin, friend)
|
|
assert d["discount_pct"] == 50
|
|
assert d["reason"] == "referred_by_founder"
|
|
|
|
|
|
def test_tickets_cap_the_50_percent(client, admin, user):
|
|
"""Mit 2 Tickets bekommen nur die ersten 2 Geworbenen 50%, der 3. nichts."""
|
|
fid = _make_founder(user["email"], tickets=2)
|
|
f1, f2, f3 = _add_referred(fid, 3)
|
|
assert _discount(client, admin, f1)["discount_pct"] == 50
|
|
assert _discount(client, admin, f2)["discount_pct"] == 50
|
|
d3 = _discount(client, admin, f3)
|
|
assert d3["discount_pct"] == 0
|
|
assert d3["reason"] is None
|
|
|
|
|
|
def test_unverified_dont_consume_tickets(client, admin, user):
|
|
"""Unbestätigte Geworbene verbrauchen kein Ticket — ein späterer bestätigter bekommt 50%."""
|
|
fid = _make_founder(user["email"], tickets=1)
|
|
# 2 unbestätigte zuerst, dann 1 bestätigter
|
|
_add_referred(fid, 2, verified=False, base_minutes=0)
|
|
later = _add_referred(fid, 1, verified=True, base_minutes=10)[0]
|
|
assert _discount(client, admin, later)["discount_pct"] == 50
|
|
|
|
|
|
def test_admin_grant_sets_tickets(client, admin, user):
|
|
"""Admin kann das Ticket-Kontingent über den Grant-Endpoint setzen."""
|
|
from database import db
|
|
with db() as conn:
|
|
uid = conn.execute("SELECT id FROM users WHERE email=?", (user["email"],)).fetchone()["id"]
|
|
r = client.post(f"/api/admin/partner/users/{uid}/grant", headers=admin["headers"],
|
|
json={"is_founder": 1, "founder_tickets": 50})
|
|
assert r.status_code == 200, r.text
|
|
assert r.json()["founder_referral_tickets"] == 50
|
|
with db() as conn:
|
|
val = conn.execute("SELECT founder_referral_tickets FROM users WHERE id=?", (uid,)).fetchone()[0]
|
|
assert val == 50
|