234 lines
7.9 KiB
Python
234 lines
7.9 KiB
Python
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"}
|