Feat: Rabattsystem in Rechnungserstellung integriert (Gründer/Referral)
- _get_discount_info() Hilfsfunktion in admin.py (Gründer 100%, Referral-Stufen 20/30/50%, von Gründer eingeladen 50%)
- list_upgrade_requests liefert discount_pct + discount_reason pro User
- GET /admin/users/{user_id}/discount Endpoint
- _handle_upgrade_invoices nutzt Rabatt für amount_net/discount_pct/after_disc + passende Notiz
- scheduler.py _create_renewal_invoice_draft: inline Rabattberechnung + korrekte Beträge
- admin.js: Discount-Badge in Upgrade-Card, data-Attribute am Invoice-Button, _discountNote(), discount_pct + notes im Modal vorbelegt
This commit is contained in:
parent
db4d5cb1b6
commit
2163169b73
3 changed files with 132 additions and 11 deletions
|
|
@ -1137,13 +1137,28 @@ 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.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
|
||||
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()
|
||||
return [dict(r) for r in rows]
|
||||
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)
|
||||
|
||||
|
||||
@router.post("/upgrade-requests/{req_id}/fulfill")
|
||||
|
|
@ -1268,6 +1283,35 @@ 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
|
||||
|
|
@ -1302,17 +1346,31 @@ 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,?,0,0,?,?)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,0,0,?,?)
|
||||
""", (
|
||||
inv_number, req["user_id"], req["name"], req["email"], billing_address,
|
||||
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})",
|
||||
description, period, price, discount_pct, discount_amt, after_disc, after_disc, note,
|
||||
))
|
||||
invoice_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||
conn.execute(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue