Files
schmankerl/app/main.py
T

234 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from datetime import datetime
from typing import Optional
from decimal import Decimal
from fastapi import FastAPI, Request, Depends, HTTPException, Form, status
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from .database import get_db
from .models import Tenant, User, Product, Event, Order, OrderItem
app = FastAPI(title="Schmankerl – Das moderne Brotzeitportal")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
def resolve_tenant(request: Request, db: Session) -> Optional[Tenant]:
"""
Ermittelt den Mandanten anhand des Host-Headers (Subdomain)
z.B. demo.dev.waiswuaschd.de -> slug='demo'
"""
host = request.headers.get("host", "").split(":")[0].lower()
parts = host.split(".")
slug = "demo"
if len(parts) >= 4 and parts[-3:] == ["waiswuaschd", "de", ""]:
slug = parts[0]
elif len(parts) >= 3 and parts[1] == "dev" and parts[2] == "waiswuaschd":
slug = parts[0]
elif parts and parts[0] not in ["localhost", "127", "dev"]:
slug = parts[0]
tenant = db.query(Tenant).filter(Tenant.slug == slug, Tenant.is_active == True).first()
if not tenant:
tenant = db.query(Tenant).filter(Tenant.slug == "demo").first()
return tenant
@app.get("/", response_class=HTMLResponse)
def index(request: Request, db: Session = Depends(get_db)):
tenant = resolve_tenant(request, db)
if not tenant:
return templates.TemplateResponse(request, "not_found_tenant.html", context={})
# Aktuelle offene Runde suchen
event = db.query(Event).filter(
Event.tenant_id == tenant.id,
Event.status == "open"
).order_by(Event.deadline.asc()).first()
# Alle aktiven Produkte des Mandanten
products = db.query(Product).filter(
Product.tenant_id == tenant.id,
Product.is_active == True
).order_by(Product.sort_order.asc(), Product.id.asc()).all()
# Bestehende Bestellungen für die Event-Runde zählen
order_count = 0
if event:
order_count = db.query(Order).filter(Order.event_id == event.id).count()
return templates.TemplateResponse(request, "index.html", context={
"tenant": tenant,
"event": event,
"products": products,
"order_count": order_count,
"now": datetime.now()
})
@app.post("/order")
async def place_order(
request: Request,
first_name: str = Form(...),
last_name: str = Form(...),
email: str = Form(...),
event_id: int = Form(...),
db: Session = Depends(get_db)
):
tenant = resolve_tenant(request, db)
if not tenant:
raise HTTPException(status_code=404, detail="Mandant nicht gefunden")
event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first()
if not event or event.status != "open":
raise HTTPException(status_code=400, detail="Diese Bestellrunde ist bereits geschlossen")
if event.deadline < datetime.now():
raise HTTPException(status_code=400, detail="Die Bestellfrist ist leider abgelaufen!")
# User ermitteln oder anlegen
clean_email = email.strip().lower()
user = db.query(User).filter(User.tenant_id == tenant.id, User.email == clean_email).first()
if not user:
username = clean_email.split("@")[0]
user = User(
tenant_id=tenant.id,
email=clean_email,
username=username,
first_name=first_name.strip(),
last_name=last_name.strip(),
role="user"
)
db.add(user)
db.flush()
# Formulardaten auslesen (Mengen je Produkt)
form_data = await request.form()
order_items = []
total_price = Decimal("0.00")
products = db.query(Product).filter(Product.tenant_id == tenant.id, Product.is_active == True).all()
for prod in products:
qty_str = form_data.get(f"qty_{prod.id}", "0")
try:
qty = int(qty_str)
except ValueError:
qty = 0
if qty > 0:
item_total = prod.price * qty
total_price += item_total
order_items.append(OrderItem(
product_id=prod.id,
quantity=qty,
unit_price=prod.price
))
if not order_items:
raise HTTPException(status_code=400, detail="Bitte wähle mindestens ein Produkt aus!")
# Bestehende Bestellung des Users in diesem Event aktualisieren oder neu anlegen
existing_order = db.query(Order).filter(
Order.tenant_id == tenant.id,
Order.event_id == event.id,
Order.user_id == user.id
).first()
if existing_order:
db.query(OrderItem).filter(OrderItem.order_id == existing_order.id).delete()
existing_order.total_price = total_price
for item in order_items:
item.order_id = existing_order.id
db.add(item)
db.commit()
order_id = existing_order.id
else:
new_order = Order(
tenant_id=tenant.id,
event_id=event.id,
user_id=user.id,
total_price=total_price,
is_paid=False
)
db.add(new_order)
db.flush()
for item in order_items:
item.order_id = new_order.id
db.add(item)
db.commit()
order_id = new_order.id
return RedirectResponse(url=f"/order/{order_id}/success", status_code=status.HTTP_303_SEE_OTHER)
@app.get("/order/{order_id}/success", response_class=HTMLResponse)
def order_success(order_id: int, request: Request, db: Session = Depends(get_db)):
tenant = resolve_tenant(request, db)
order = db.query(Order).filter(Order.id == order_id).first()
if not order:
raise HTTPException(status_code=404, detail="Bestellung nicht gefunden")
return templates.TemplateResponse(request, "order_success.html", context={
"tenant": tenant,
"order": order
})
@app.get("/admin", response_class=HTMLResponse)
def admin_dashboard(request: Request, db: Session = Depends(get_db)):
tenant = resolve_tenant(request, db)
if not tenant:
return templates.TemplateResponse(request, "not_found_tenant.html", context={})
# Neuestes Event finden
event = db.query(Event).filter(Event.tenant_id == tenant.id).order_by(Event.id.desc()).first()
orders = []
item_sums = {}
total_revenue = Decimal("0.00")
total_paid = Decimal("0.00")
if event:
orders = db.query(Order).filter(Order.event_id == 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, "admin.html", context={
"tenant": tenant,
"event": event,
"orders": orders,
"item_sums": item_sums,
"total_revenue": total_revenue,
"total_paid": total_paid
})
@app.post("/admin/toggle-paid/{order_id}")
def toggle_paid(order_id: int, request: Request, db: Session = Depends(get_db)):
order = db.query(Order).filter(Order.id == order_id).first()
if order:
order.is_paid = not order.is_paid
db.commit()
return RedirectResponse(url="/admin", status_code=status.HTTP_303_SEE_OTHER)
@app.get("/health")
def health():
return {"status": "ok", "app": "Schmankerl"}