feat: Add dedicated buyer dashboard, buyer role assignment, Bavarian White-Blue light/dark mode and mobile drawer

This commit is contained in:
2026-09-22 08:21:06 +02:00
parent 1ff5b3a31e
commit 0bdcbe7976
6 changed files with 825 additions and 67 deletions
+100 -1
View File
@@ -527,6 +527,82 @@ def download_calendar_ics(event_id: int, request: Request, db: Session = Depends
)
# --- EINKAUFS- & KASSEN-ZENTRALE (/einkauf) ---
@app.get("/einkauf", response_class=HTMLResponse)
def buyer_dashboard(
request: Request,
event_id: Optional[int] = None,
success: Optional[str] = None,
error: Optional[str] = None,
db: Session = Depends(get_db)
):
tenant = resolve_tenant(request, db)
if not tenant:
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)
# Alle Events des Mandanten
all_events = db.query(Event).filter(Event.tenant_id == tenant.id).order_by(Event.event_date.desc(), Event.id.desc()).all()
# Ausgewähltes Event bestimmen:
# 1. Prio: explizit übergebenes event_id
# 2. Prio: Event, bei dem current_user als buyer zugewiesen ist und status == 'open'
# 3. Prio: erstes offenes Event
# 4. Prio: neuestes Event
selected_event = None
if event_id:
selected_event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
if not selected_event:
for ev in all_events:
if ev.buyer_id == current_user.id and ev.status == "open":
selected_event = ev
break
if not selected_event:
for ev in all_events:
if ev.status == "open":
selected_event = ev
break
if not selected_event and all_events:
selected_event = all_events[0]
orders = []
item_sums = {}
total_revenue = Decimal("0.00")
total_paid = Decimal("0.00")
if selected_event:
orders = db.query(Order).filter(Order.event_id == selected_event.id).order_by(Order.created_at.asc()).all()
for ord_obj in orders:
total_revenue += ord_obj.total_price
if ord_obj.is_paid:
total_paid += ord_obj.total_price
for it in ord_obj.items:
pname = it.product.name if it.product else "Artikel"
unit = it.product.unit if it.product else "Stk"
if pname not in item_sums:
item_sums[pname] = {"quantity": 0, "unit": unit}
item_sums[pname]["quantity"] += it.quantity
return templates.TemplateResponse(request, "buyer.html", context={
"tenant": tenant,
"current_user": current_user,
"event": selected_event,
"all_events": all_events,
"orders": orders,
"item_sums": item_sums,
"total_revenue": total_revenue,
"total_paid": total_paid,
"success": success,
"error": error,
})
# --- ADMIN COCKPIT (/admin) ---
@app.get("/admin", response_class=HTMLResponse)
@@ -801,6 +877,7 @@ def admin_create_event(
deadline_date: str = Form(...),
deadline_time: str = Form(...),
sponsor_name: Optional[str] = Form(None),
buyer_id: Optional[str] = Form(None),
allow_guests: bool = Form(True),
force: bool = Form(False),
db: Session = Depends(get_db)
@@ -827,6 +904,13 @@ def admin_create_event(
status_code=status.HTTP_303_SEE_OTHER
)
buyer_id_int: Optional[int] = None
if buyer_id and buyer_id.strip():
try:
buyer_id_int = int(buyer_id.strip())
except ValueError:
pass
new_event = Event(
tenant_id=tenant.id,
title=title.strip(),
@@ -834,6 +918,7 @@ def admin_create_event(
deadline=deadline_dt,
status="open",
sponsor_name=sponsor_name.strip() if sponsor_name and sponsor_name.strip() else None,
buyer_id=buyer_id_int,
allow_guests=allow_guests,
guest_token=secrets.token_urlsafe(16)
)
@@ -854,6 +939,7 @@ def admin_edit_event(
deadline_time: str = Form(...),
status_val: str = Form(...),
sponsor_name: Optional[str] = Form(None),
buyer_id: Optional[str] = Form(None),
allow_guests: bool = Form(False),
db: Session = Depends(get_db)
):
@@ -871,11 +957,19 @@ def admin_edit_event(
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Ungültiges Datumsformat: {e}")
buyer_id_int: Optional[int] = None
if buyer_id and buyer_id.strip():
try:
buyer_id_int = int(buyer_id.strip())
except ValueError:
pass
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.buyer_id = buyer_id_int
event.allow_guests = allow_guests
if not event.guest_token:
event.guest_token = secrets.token_urlsafe(16)
@@ -1179,7 +1273,12 @@ def toggle_paid(order_id: int, request: Request, db: Session = Depends(get_db)):
if order:
order.is_paid = not order.is_paid
db.commit()
return RedirectResponse(url=f"/admin?event_id={order.event_id}", status_code=status.HTTP_303_SEE_OTHER)
ev_id = order.event_id if order else ""
referer = request.headers.get("referer", "")
if "/einkauf" in referer:
return RedirectResponse(url=f"/einkauf?event_id={ev_id}", status_code=status.HTTP_303_SEE_OTHER)
return RedirectResponse(url=f"/admin?event_id={ev_id}&tab=orders", status_code=status.HTTP_303_SEE_OTHER)
@app.get("/health")