feat: Paket 2 - Product catalog management, guest orders, sponsor rounds, password reset, user deletion, unlimited invite links and setup wizard
This commit is contained in:
+341
-88
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, date
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional, Dict, List
|
||||
from decimal import Decimal
|
||||
|
||||
from fastapi import FastAPI, Request, Depends, HTTPException, Form, status
|
||||
@@ -104,9 +104,6 @@ def index(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse(request, "not_found_tenant.html", context={})
|
||||
|
||||
current_user = get_current_user(request, db)
|
||||
if not current_user:
|
||||
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
# Aktuelle offene Runde suchen
|
||||
@@ -119,6 +116,11 @@ def index(request: Request, db: Session = Depends(get_db)):
|
||||
if not event:
|
||||
event = db.query(Event).filter(Event.tenant_id == tenant.id).order_by(Event.id.desc()).first()
|
||||
|
||||
# Wenn nicht eingeloggt und Event erlaubt keine Gäste -> Redirect zu Login
|
||||
if not current_user:
|
||||
if not event or not event.allow_guests:
|
||||
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
# Fristprüfung & Countdown
|
||||
is_closed = True
|
||||
seconds_remaining = 0
|
||||
@@ -136,7 +138,7 @@ def index(request: Request, db: Session = Depends(get_db)):
|
||||
# Bestehende Bestellung des Users in diesem Event
|
||||
existing_order = None
|
||||
user_items: Dict[int, int] = {}
|
||||
if event:
|
||||
if event and current_user:
|
||||
existing_order = db.query(Order).filter(
|
||||
Order.tenant_id == tenant.id,
|
||||
Order.event_id == event.id,
|
||||
@@ -175,10 +177,19 @@ def login_page(request: Request, db: Session = Depends(get_db), error: Optional[
|
||||
if current_user:
|
||||
return RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
# Prüfen, ob aktives Event Gäste erlaubt
|
||||
active_event = db.query(Event).filter(
|
||||
Event.tenant_id == tenant.id,
|
||||
Event.status == "open",
|
||||
Event.deadline > datetime.now()
|
||||
).first()
|
||||
allow_guests = (active_event.allow_guests if active_event else False)
|
||||
|
||||
return templates.TemplateResponse(request, "login.html", context={
|
||||
"tenant": tenant,
|
||||
"error": error,
|
||||
"success": success,
|
||||
"allow_guests": allow_guests,
|
||||
"current_user": None
|
||||
})
|
||||
|
||||
@@ -198,10 +209,10 @@ def login(
|
||||
return templates.TemplateResponse(request, "login.html", context={
|
||||
"tenant": tenant,
|
||||
"error": "Ungültige E-Mail-Adresse oder falsches Passwort.",
|
||||
"current_user": None
|
||||
"current_user": None,
|
||||
"allow_guests": False
|
||||
}, status_code=400)
|
||||
|
||||
# Session setzen
|
||||
request.session["user_id"] = user.id
|
||||
return RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@@ -251,9 +262,9 @@ def join_page(request: Request, token: str, db: Session = Depends(get_db)):
|
||||
error = None
|
||||
if not invitation:
|
||||
error = "Dieser Einladungslink ist ungültig oder gehört zu einem anderen Team."
|
||||
elif invitation.used_at is not None:
|
||||
elif not invitation.is_reusable and invitation.used_at is not None:
|
||||
error = "Dieser Einladungslink wurde bereits eingelöst. Bitte melde dich mit deinem Konto an."
|
||||
elif invitation.expires_at < datetime.now():
|
||||
elif invitation.expires_at and invitation.expires_at < datetime.now():
|
||||
error = "Dieser Einladungslink ist leider abgelaufen. Bitte frage deinen Admin nach einem neuen Link."
|
||||
|
||||
return templates.TemplateResponse(request, "join.html", context={
|
||||
@@ -281,8 +292,14 @@ def process_join(
|
||||
Invitation.tenant_id == tenant.id
|
||||
).first()
|
||||
|
||||
if not invitation or invitation.used_at is not None or invitation.expires_at < datetime.now():
|
||||
raise HTTPException(status_code=400, detail="Ungültiger oder abgelaufener Einladungslink")
|
||||
if not invitation:
|
||||
raise HTTPException(status_code=400, detail="Ungültiger Einladungslink")
|
||||
|
||||
if not invitation.is_reusable and invitation.used_at is not None:
|
||||
raise HTTPException(status_code=400, detail="Einladungslink wurde bereits verwendet")
|
||||
|
||||
if invitation.expires_at and invitation.expires_at < datetime.now():
|
||||
raise HTTPException(status_code=400, detail="Einladungslink ist abgelaufen")
|
||||
|
||||
clean_email = email.strip().lower()
|
||||
existing_user = db.query(User).filter(
|
||||
@@ -299,7 +316,6 @@ def process_join(
|
||||
"current_user": None
|
||||
}, status_code=400)
|
||||
|
||||
# Neuen Benutzer anlegen
|
||||
username = clean_email.split("@")[0]
|
||||
new_user = User(
|
||||
tenant_id=tenant.id,
|
||||
@@ -312,12 +328,13 @@ def process_join(
|
||||
)
|
||||
db.add(new_user)
|
||||
|
||||
# Einladung als verwendet markieren
|
||||
invitation.used_at = datetime.now()
|
||||
# Nur Einmal-Links als verwendet markieren; wiederverwendbare Links bleiben offen!
|
||||
if not invitation.is_reusable:
|
||||
invitation.used_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
# Direkt einloggen
|
||||
request.session["user_id"] = new_user.id
|
||||
return RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@@ -328,12 +345,13 @@ def process_join(
|
||||
async def place_order(
|
||||
request: Request,
|
||||
event_id: int = Form(...),
|
||||
guest_first_name: Optional[str] = Form(None),
|
||||
guest_last_name: Optional[str] = Form(None),
|
||||
guest_email: Optional[str] = Form(None),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
current_user = get_current_user(request, db)
|
||||
if not current_user:
|
||||
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
|
||||
if not event or event.status != "open":
|
||||
@@ -342,6 +360,33 @@ async def place_order(
|
||||
if event.deadline < datetime.now():
|
||||
raise HTTPException(status_code=400, detail="Die Bestellfrist für diese Runde ist leider abgelaufen!")
|
||||
|
||||
# Falls nicht eingeloggt -> Gast-Bestellung prüfen
|
||||
if not current_user:
|
||||
if not event.allow_guests:
|
||||
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
if not guest_first_name or not guest_first_name.strip():
|
||||
raise HTTPException(status_code=400, detail="Bitte gib als Gast deinen Vornamen an!")
|
||||
|
||||
fname = guest_first_name.strip()
|
||||
lname = guest_last_name.strip() if guest_last_name else "(Gast)"
|
||||
clean_email = (guest_email.strip().lower() if guest_email and guest_email.strip() else f"gast_{secrets.token_hex(4)}@{tenant.slug}.local")
|
||||
|
||||
guest_user = User(
|
||||
tenant_id=tenant.id,
|
||||
email=clean_email,
|
||||
username=f"gast_{secrets.token_hex(4)}",
|
||||
first_name=fname,
|
||||
last_name=lname,
|
||||
role="guest",
|
||||
password_hash=""
|
||||
)
|
||||
db.add(guest_user)
|
||||
db.commit()
|
||||
db.refresh(guest_user)
|
||||
current_user = guest_user
|
||||
request.session["user_id"] = guest_user.id
|
||||
|
||||
form_data = await request.form()
|
||||
order_items = []
|
||||
total_price = Decimal("0.00")
|
||||
@@ -468,12 +513,17 @@ def admin_dashboard(
|
||||
|
||||
# Teammitglieder & Einladungen laden
|
||||
team_members = db.query(User).filter(User.tenant_id == tenant.id).order_by(User.role.asc(), User.first_name.asc()).all()
|
||||
|
||||
# Offene Einladungen (entweder unbegrenzt oder expires_at in der Zukunft)
|
||||
now = datetime.now()
|
||||
pending_invitations = db.query(Invitation).filter(
|
||||
Invitation.tenant_id == tenant.id,
|
||||
Invitation.used_at == None,
|
||||
Invitation.expires_at > datetime.now()
|
||||
(Invitation.expires_at == None) | (Invitation.expires_at > now)
|
||||
).order_by(Invitation.created_at.desc()).all()
|
||||
|
||||
# Produkte des Mandanten laden
|
||||
products = db.query(Product).filter(Product.tenant_id == tenant.id).order_by(Product.sort_order.asc(), Product.id.asc()).all()
|
||||
|
||||
return templates.TemplateResponse(request, "admin.html", context={
|
||||
"tenant": tenant,
|
||||
"current_user": current_user,
|
||||
@@ -485,119 +535,90 @@ def admin_dashboard(
|
||||
"total_paid": total_paid,
|
||||
"team_members": team_members,
|
||||
"pending_invitations": pending_invitations,
|
||||
"products": products,
|
||||
"active_tab": tab,
|
||||
"now": datetime.now()
|
||||
"now": now
|
||||
})
|
||||
|
||||
|
||||
@app.post("/admin/events/new")
|
||||
def admin_create_event(
|
||||
# --- ADMIN USER MANAGEMENT ---
|
||||
|
||||
@app.post("/admin/users/{user_id}/reset-password")
|
||||
def admin_reset_password(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
title: str = Form(...),
|
||||
event_date: str = Form(...),
|
||||
deadline_date: str = Form(...),
|
||||
deadline_time: str = Form(...),
|
||||
new_password: str = Form(...),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
try:
|
||||
ev_date = datetime.strptime(event_date.strip(), "%Y-%m-%d").date()
|
||||
dl_str = f"{deadline_date.strip()} {deadline_time.strip()}"
|
||||
deadline_dt = datetime.strptime(dl_str, "%Y-%m-%d %H:%M")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Ungültiges Datumsformat: {e}")
|
||||
user = db.query(User).filter(User.id == user_id, User.tenant_id == tenant.id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Benutzer nicht gefunden")
|
||||
|
||||
new_event = Event(
|
||||
tenant_id=tenant.id,
|
||||
title=title.strip(),
|
||||
event_date=ev_date,
|
||||
deadline=deadline_dt,
|
||||
status="open"
|
||||
)
|
||||
db.add(new_event)
|
||||
db.commit()
|
||||
db.refresh(new_event)
|
||||
if not new_password or len(new_password.strip()) < 4:
|
||||
raise HTTPException(status_code=400, detail="Passwort muss mindestens 4 Zeichen lang sein")
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={new_event.id}&tab=event", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/events/{event_id}/edit")
|
||||
def admin_edit_event(
|
||||
event_id: int,
|
||||
request: Request,
|
||||
title: str = Form(...),
|
||||
event_date: str = Form(...),
|
||||
deadline_date: str = Form(...),
|
||||
deadline_time: str = Form(...),
|
||||
status_val: str = Form(...),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event nicht gefunden")
|
||||
|
||||
try:
|
||||
ev_date = datetime.strptime(event_date.strip(), "%Y-%m-%d").date()
|
||||
dl_str = f"{deadline_date.strip()} {deadline_time.strip()}"
|
||||
deadline_dt = datetime.strptime(dl_str, "%Y-%m-%d %H:%M")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Ungültiges Datumsformat: {e}")
|
||||
|
||||
event.title = title.strip()
|
||||
event.event_date = ev_date
|
||||
event.deadline = deadline_dt
|
||||
event.status = status_val
|
||||
user.password_hash = hash_password(new_password.strip())
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={event.id}&tab=event", status_code=status.HTTP_303_SEE_OTHER)
|
||||
return RedirectResponse(url="/admin?tab=team", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/events/{event_id}/toggle-status")
|
||||
def admin_toggle_event_status(
|
||||
event_id: int,
|
||||
@app.post("/admin/users/{user_id}/delete")
|
||||
def admin_delete_user(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
admin = require_admin(request, db)
|
||||
|
||||
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event nicht gefunden")
|
||||
if admin.id == user_id:
|
||||
raise HTTPException(status_code=400, detail="Du kannst dein eigenes Admin-Konto nicht löschen")
|
||||
|
||||
event.status = "closed" if event.status == "open" else "open"
|
||||
db.commit()
|
||||
user = db.query(User).filter(User.id == user_id, User.tenant_id == tenant.id).first()
|
||||
if user:
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={event.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
return RedirectResponse(url="/admin?tab=team", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# --- ADMIN INVITATIONS ---
|
||||
|
||||
@app.post("/admin/invitations/create")
|
||||
def admin_create_invitation(
|
||||
request: Request,
|
||||
email: Optional[str] = Form(None),
|
||||
role: str = Form("user"),
|
||||
valid_days: int = Form(14),
|
||||
valid_days: str = Form("14"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
admin = require_admin(request, db)
|
||||
|
||||
token = secrets.token_urlsafe(20)
|
||||
expires_at = datetime.now() + timedelta(days=valid_days)
|
||||
clean_email = email.strip().lower() if email and email.strip() else None
|
||||
|
||||
# Unbegrenzt prüfen
|
||||
if valid_days in ["0", "unlimited", "forever"]:
|
||||
expires_at = None
|
||||
is_reusable = True
|
||||
else:
|
||||
days = int(valid_days)
|
||||
expires_at = datetime.now() + timedelta(days=days)
|
||||
is_reusable = False
|
||||
|
||||
inv = Invitation(
|
||||
tenant_id=tenant.id,
|
||||
token=token,
|
||||
email=clean_email,
|
||||
role=role,
|
||||
created_by=admin.id,
|
||||
expires_at=expires_at
|
||||
expires_at=expires_at,
|
||||
is_reusable=is_reusable
|
||||
)
|
||||
db.add(inv)
|
||||
db.commit()
|
||||
@@ -622,6 +643,238 @@ def admin_revoke_invitation(
|
||||
return RedirectResponse(url="/admin?tab=team", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# --- ADMIN EVENT MANAGEMENT ---
|
||||
|
||||
@app.post("/admin/events/new")
|
||||
def admin_create_event(
|
||||
request: Request,
|
||||
title: str = Form(...),
|
||||
event_date: str = Form(...),
|
||||
deadline_date: str = Form(...),
|
||||
deadline_time: str = Form(...),
|
||||
sponsor_name: Optional[str] = Form(None),
|
||||
allow_guests: bool = Form(True),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
try:
|
||||
ev_date = datetime.strptime(event_date.strip(), "%Y-%m-%d").date()
|
||||
dl_str = f"{deadline_date.strip()} {deadline_time.strip()}"
|
||||
deadline_dt = datetime.strptime(dl_str, "%Y-%m-%d %H:%M")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Ungültiges Datumsformat: {e}")
|
||||
|
||||
new_event = Event(
|
||||
tenant_id=tenant.id,
|
||||
title=title.strip(),
|
||||
event_date=ev_date,
|
||||
deadline=deadline_dt,
|
||||
status="open",
|
||||
sponsor_name=sponsor_name.strip() if sponsor_name and sponsor_name.strip() else None,
|
||||
allow_guests=allow_guests
|
||||
)
|
||||
db.add(new_event)
|
||||
db.commit()
|
||||
db.refresh(new_event)
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={new_event.id}&tab=event", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/events/{event_id}/edit")
|
||||
def admin_edit_event(
|
||||
event_id: int,
|
||||
request: Request,
|
||||
title: str = Form(...),
|
||||
event_date: str = Form(...),
|
||||
deadline_date: str = Form(...),
|
||||
deadline_time: str = Form(...),
|
||||
status_val: str = Form(...),
|
||||
sponsor_name: Optional[str] = Form(None),
|
||||
allow_guests: bool = Form(False),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event nicht gefunden")
|
||||
|
||||
try:
|
||||
ev_date = datetime.strptime(event_date.strip(), "%Y-%m-%d").date()
|
||||
dl_str = f"{deadline_date.strip()} {deadline_time.strip()}"
|
||||
deadline_dt = datetime.strptime(dl_str, "%Y-%m-%d %H:%M")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Ungültiges Datumsformat: {e}")
|
||||
|
||||
event.title = title.strip()
|
||||
event.event_date = ev_date
|
||||
event.deadline = deadline_dt
|
||||
event.status = status_val
|
||||
event.sponsor_name = sponsor_name.strip() if sponsor_name and sponsor_name.strip() else None
|
||||
event.allow_guests = allow_guests
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={event.id}&tab=event", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/events/{event_id}/toggle-status")
|
||||
def admin_toggle_event_status(
|
||||
event_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Event nicht gefunden")
|
||||
|
||||
event.status = "closed" if event.status == "open" else "open"
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url=f"/admin?event_id={event.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# --- ADMIN PRODUCT CATALOG MANAGEMENT ---
|
||||
|
||||
@app.post("/admin/products/new")
|
||||
def admin_create_product(
|
||||
request: Request,
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
price: str = Form(...),
|
||||
unit: str = Form("Stück"),
|
||||
sort_order: int = Form(0),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
try:
|
||||
clean_price = Decimal(price.strip().replace(",", "."))
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Ungültiger Preis")
|
||||
|
||||
product = Product(
|
||||
tenant_id=tenant.id,
|
||||
name=name.strip(),
|
||||
description=description.strip(),
|
||||
price=clean_price,
|
||||
unit=unit.strip(),
|
||||
sort_order=sort_order,
|
||||
is_active=True
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url="/admin?tab=products", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/products/{product_id}/edit")
|
||||
def admin_edit_product(
|
||||
product_id: int,
|
||||
request: Request,
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
price: str = Form(...),
|
||||
unit: str = Form("Stück"),
|
||||
sort_order: int = Form(0),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
prod = db.query(Product).filter(Product.id == product_id, Product.tenant_id == tenant.id).first()
|
||||
if not prod:
|
||||
raise HTTPException(status_code=404, detail="Produkt nicht gefunden")
|
||||
|
||||
try:
|
||||
clean_price = Decimal(price.strip().replace(",", "."))
|
||||
except Exception:
|
||||
raise HTTPException(status_code=400, detail="Ungültiger Preis")
|
||||
|
||||
prod.name = name.strip()
|
||||
prod.description = description.strip()
|
||||
prod.price = clean_price
|
||||
prod.unit = unit.strip()
|
||||
prod.sort_order = sort_order
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url="/admin?tab=products", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/products/{product_id}/toggle")
|
||||
def admin_toggle_product(
|
||||
product_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
prod = db.query(Product).filter(Product.id == product_id, Product.tenant_id == tenant.id).first()
|
||||
if prod:
|
||||
prod.is_active = not prod.is_active
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url="/admin?tab=products", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
# --- ONBOARDING / SETUP WIZARD (/setup) ---
|
||||
|
||||
@app.get("/setup", response_class=HTMLResponse)
|
||||
def setup_page(request: Request, db: Session = Depends(get_db)):
|
||||
tenant = resolve_tenant(request, db)
|
||||
if not tenant:
|
||||
raise HTTPException(status_code=404, detail="Mandant nicht gefunden")
|
||||
|
||||
current_user = get_current_user(request, db)
|
||||
prod_count = db.query(Product).filter(Product.tenant_id == tenant.id).count()
|
||||
user_count = db.query(User).filter(User.tenant_id == tenant.id).count()
|
||||
|
||||
return templates.TemplateResponse(request, "setup.html", context={
|
||||
"tenant": tenant,
|
||||
"current_user": current_user,
|
||||
"prod_count": prod_count,
|
||||
"user_count": user_count
|
||||
})
|
||||
|
||||
|
||||
@app.post("/setup/seed-products")
|
||||
def seed_standard_products(request: Request, db: Session = Depends(get_db)):
|
||||
tenant = resolve_tenant(request, db)
|
||||
require_admin(request, db)
|
||||
|
||||
standard_items = [
|
||||
{"name": "Original Münchner Weißwürste", "desc": "Frisch vom Metzger, traditionell hergestellt", "price": Decimal("1.40"), "unit": "Stück", "order": 1},
|
||||
{"name": "Frische Laugenbreze", "desc": "Knusprig gebacken, ofenfrisch vom Bäcker", "price": Decimal("1.10"), "unit": "Stück", "order": 2},
|
||||
{"name": "Händlmaier Hausmachersenf", "desc": "Original bayerischer süßer Hausmachersenf", "price": Decimal("0.40"), "unit": "Portion", "order": 3},
|
||||
{"name": "Leberkassemmel (warm)", "desc": "Feiner bayerischer Leberkäse in der Kaisersemmel", "price": Decimal("2.80"), "unit": "Semmel", "order": 4},
|
||||
{"name": "Wiener Würstchen", "desc": "Knackige Wiener Würstchen (Paar)", "price": Decimal("2.60"), "unit": "Paar", "order": 5},
|
||||
{"name": "Alkoholfreies Weißbier (0,5l)", "desc": "Erfrischendes bayerisches Hefe-Weißbier alkoholfrei", "price": Decimal("1.80"), "unit": "Flasche", "order": 6},
|
||||
]
|
||||
|
||||
for item in standard_items:
|
||||
exists = db.query(Product).filter(Product.tenant_id == tenant.id, Product.name == item["name"]).first()
|
||||
if not exists:
|
||||
db.add(Product(
|
||||
tenant_id=tenant.id,
|
||||
name=item["name"],
|
||||
description=item["desc"],
|
||||
price=item["price"],
|
||||
unit=item["unit"],
|
||||
sort_order=item["order"],
|
||||
is_active=True
|
||||
))
|
||||
db.commit()
|
||||
|
||||
return RedirectResponse(url="/admin?tab=products", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@app.post("/admin/toggle-paid/{order_id}")
|
||||
def toggle_paid(order_id: int, request: Request, db: Session = Depends(get_db)):
|
||||
current_user = get_current_user(request, db)
|
||||
|
||||
Reference in New Issue
Block a user