diff --git a/backend/main.py b/backend/main.py index bb813c5..b460a2b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -408,7 +408,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "971" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "970" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/admin.py b/backend/routes/admin.py index 47f363b..c0ef2b7 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -1137,28 +1137,13 @@ async def list_upgrade_requests(user=Depends(require_admin)): with db() as conn: rows = conn.execute(""" SELECT r.id, r.user_id, r.tier, r.message, r.created_at, r.fulfilled_at, - u.name, u.email, u.billing_address, - u.is_founder, u.is_founder_pending, u.referred_by, - COALESCE((SELECT COUNT(*) FROM users WHERE referred_by=u.id), 0) AS referral_count + u.name, u.email, u.billing_address FROM upgrade_requests r JOIN users u ON u.id = r.user_id ORDER BY r.fulfilled_at IS NOT NULL, r.created_at DESC LIMIT 100 """).fetchall() - result = [] - for r in rows: - d = dict(r) - d_info = _get_discount_info(conn, r["user_id"]) - d["discount_pct"] = d_info["discount_pct"] - d["discount_reason"] = d_info["reason"] - result.append(d) - return result - - -@router.get("/users/{user_id}/discount") -def get_user_discount(user_id: int, admin=Depends(require_admin)): - with db() as conn: - return _get_discount_info(conn, user_id) + return [dict(r) for r in rows] @router.post("/upgrade-requests/{req_id}/fulfill") @@ -1283,35 +1268,6 @@ async def fulfill_upgrade_request(req_id: int, user=Depends(require_admin)): return {"ok": True, "tier": req["tier"], "user": req["name"]} -def _get_discount_info(conn, user_id: int) -> dict: - """Berechnet Rabatt für einen User basierend auf Gründer-Status und Referrals.""" - row = conn.execute( - """SELECT u.is_founder, u.is_founder_pending, u.referred_by, - COALESCE((SELECT COUNT(*) FROM users WHERE referred_by=u.id), 0) AS referral_count - FROM users u WHERE u.id=?""", - (user_id,) - ).fetchone() - if not row: - return {"discount_pct": 0, "reason": None, "referral_count": 0} - - if row["is_founder"] or row["is_founder_pending"]: - return {"discount_pct": 100, "reason": "founder", "referral_count": row["referral_count"]} - - referred_by = row["referred_by"] or 0 - if referred_by > 0: - referrer = conn.execute( - "SELECT is_founder, is_founder_pending FROM users WHERE id=?", (referred_by,) - ).fetchone() - if referrer and (referrer["is_founder"] or referrer["is_founder_pending"]): - return {"discount_pct": 50, "reason": "referred_by_founder", "referral_count": row["referral_count"]} - - count = row["referral_count"] - for threshold, pct in [(50, 50), (20, 30), (10, 20)]: - if count >= threshold: - return {"discount_pct": pct, "reason": "referral", "referral_count": count} - return {"discount_pct": 0, "reason": None, "referral_count": count} - - async def _handle_upgrade_invoices(req: dict, new_tier_label: str): """Storniert offene Rechnungen des alten Tiers und legt neuen Entwurf an.""" from routes.invoices import _next_invoice_number @@ -1346,31 +1302,17 @@ async def _handle_upgrade_invoices(req: dict, new_tier_label: str): ).fetchone() billing_address = billing["billing_address"] if billing else None - disc_info = _get_discount_info(conn, req["user_id"]) - discount_pct = disc_info["discount_pct"] - discount_amt = round(price * discount_pct / 100, 2) - after_disc = round(price - discount_amt, 2) - - _AGB = "Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen." - if disc_info["reason"] == "founder": - note = f"Gründer-Sonderkonditionen: {new_tier_label} kostenfrei als Dankeschön für deine Unterstützung als Gründer! {_AGB}" - elif disc_info["reason"] == "referred_by_founder": - note = f"Willkommen in der Gründer-Community! Als persönlich von einem Gründer eingeladenes Mitglied erhältst du dauerhaft {discount_pct}% Rabatt. {_AGB}" - elif disc_info["reason"] == "referral": - note = f"Herzlichen Dank für deine Unterstützung! Für {disc_info['referral_count']} geworbene Freunde erhältst du {discount_pct}% Rabatt. {_AGB}" - else: - note = f"{_AGB} (Upgrade von {req.get('old_tier','Standard')} auf {new_tier_label})" - inv_number = _next_invoice_number(conn) conn.execute(""" INSERT INTO invoices (invoice_number, user_id, recipient_name, recipient_email, recipient_address, description, service_period, amount_net, discount_pct, discount_amount, amount_after_discount, tax_rate, tax_amount, amount_gross, notes) - VALUES (?,?,?,?,?,?,?,?,?,?,?,0,0,?,?) + VALUES (?,?,?,?,?,?,?,?,0,0,?,0,0,?,?) """, ( inv_number, req["user_id"], req["name"], req["email"], billing_address, - description, period, price, discount_pct, discount_amt, after_disc, after_disc, note, + description, period, price, price, price, + f"Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen. (Upgrade von {req.get('old_tier','Standard')} auf {new_tier_label})", )) invoice_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0] conn.execute( diff --git a/backend/scheduler.py b/backend/scheduler.py index 19c293e..edb9963 100644 --- a/backend/scheduler.py +++ b/backend/scheduler.py @@ -246,49 +246,6 @@ async def _create_renewal_invoice_draft(user: dict, expires: date, tier_label: s ).fetchone() billing_address = row["billing_address"] if row else None - # Rabatt berechnen (inline, da kein Admin-Import möglich) - disc_row = conn.execute( - """SELECT u.is_founder, u.is_founder_pending, u.referred_by, - COALESCE((SELECT COUNT(*) FROM users WHERE referred_by=u.id), 0) AS referral_count - FROM users u WHERE u.id=?""", - (user["id"],) - ).fetchone() - discount_pct = 0 - discount_reason = None - referral_count = 0 - if disc_row: - referral_count = disc_row["referral_count"] - if disc_row["is_founder"] or disc_row["is_founder_pending"]: - discount_pct = 100 - discount_reason = "founder" - elif (disc_row["referred_by"] or 0) > 0: - ref = conn.execute( - "SELECT is_founder, is_founder_pending FROM users WHERE id=?", - (disc_row["referred_by"],) - ).fetchone() - if ref and (ref["is_founder"] or ref["is_founder_pending"]): - discount_pct = 50 - discount_reason = "referred_by_founder" - if not discount_reason: - for thr, pct in [(50, 50), (20, 30), (10, 20)]: - if referral_count >= thr: - discount_pct = pct - discount_reason = "referral" - break - - discount_amt = round(price * discount_pct / 100, 2) - after_disc = round(price - discount_amt, 2) - - _AGB = "Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen." - if discount_reason == "founder": - notes = f"Gründer-Sonderkonditionen: {tier_label} kostenfrei als Dankeschön für deine Unterstützung als Gründer! {_AGB} (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})" - elif discount_reason == "referred_by_founder": - notes = f"Willkommen in der Gründer-Community! Als persönlich von einem Gründer eingeladenes Mitglied erhältst du dauerhaft {discount_pct}% Rabatt. {_AGB} (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})" - elif discount_reason == "referral": - notes = f"Herzlichen Dank für deine Unterstützung! Für {referral_count} geworbene Freunde erhältst du {discount_pct}% Rabatt. {_AGB} (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})" - else: - notes = f"{_AGB} (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})" - invoice_number = _next_invoice_number(conn) description = f"{tier_label} Jahresabo (Verlängerung)" conn.execute(""" @@ -296,11 +253,12 @@ async def _create_renewal_invoice_draft(user: dict, expires: date, tier_label: s (invoice_number, user_id, recipient_name, recipient_email, recipient_address, description, service_period, amount_net, discount_pct, discount_amount, amount_after_discount, tax_rate, tax_amount, amount_gross, notes) - VALUES (?,?,?,?,?,?,?,?,?,?,?,0,0,?,?) + VALUES (?,?,?,?,?,?,?,?,0,0,?,0,0,?,?) """, ( invoice_number, user["id"], user["name"], user["email"], billing_address, description, period, - price, discount_pct, discount_amt, after_disc, after_disc, notes, + price, price, price, + f"Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen. (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})", )) conn.execute( "INSERT INTO invoice_items (invoice_id, description, quantity, unit_price, total) VALUES (?,?,1,?,?)", diff --git a/backend/static/js/app.js b/backend/static/js/app.js index f5eac7c..467c710 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '971'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '970'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index d3b5fe3..3a898ae 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -3531,14 +3531,8 @@ window.Page_admin = (() => {