fix: hamburger menu vertical stack, lock paid orders, order cancellation with balance refund, deduplicate admin drawer
This commit is contained in:
+92
-1
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
import urllib.parse
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
from typing import Optional, Dict, List, Set
|
from typing import Optional, Dict, List, Set
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -130,7 +131,14 @@ def seed_demo_users_if_needed(tenant: Tenant, db: Session):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
def index(request: Request, event_id: Optional[int] = None, token: Optional[str] = None, db: Session = Depends(get_db)):
|
def index(
|
||||||
|
request: Request,
|
||||||
|
event_id: Optional[int] = None,
|
||||||
|
token: Optional[str] = None,
|
||||||
|
success: Optional[str] = None,
|
||||||
|
error: Optional[str] = None,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
tenant = resolve_tenant(request, db)
|
tenant = resolve_tenant(request, db)
|
||||||
if not tenant:
|
if not tenant:
|
||||||
return templates.TemplateResponse(request, "not_found_tenant.html", context={})
|
return templates.TemplateResponse(request, "not_found_tenant.html", context={})
|
||||||
@@ -207,6 +215,8 @@ def index(request: Request, event_id: Optional[int] = None, token: Optional[str]
|
|||||||
"existing_order": existing_order,
|
"existing_order": existing_order,
|
||||||
"user_items": user_items,
|
"user_items": user_items,
|
||||||
"order_count": order_count,
|
"order_count": order_count,
|
||||||
|
"success": success,
|
||||||
|
"error": error,
|
||||||
"now": now
|
"now": now
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -456,6 +466,11 @@ async def place_order(
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
if existing_order:
|
if existing_order:
|
||||||
|
if existing_order.is_paid:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Diese Bestellung wurde bereits als bezahlt verbucht und kann nicht mehr geändert werden. Bitte wende dich an deinen Organisator."
|
||||||
|
)
|
||||||
db.query(OrderItem).filter(OrderItem.order_id == existing_order.id).delete()
|
db.query(OrderItem).filter(OrderItem.order_id == existing_order.id).delete()
|
||||||
existing_order.total_price = total_price
|
existing_order.total_price = total_price
|
||||||
existing_order.guest_display_name = guest_name_str
|
existing_order.guest_display_name = guest_name_str
|
||||||
@@ -484,6 +499,43 @@ async def place_order(
|
|||||||
return RedirectResponse(url=f"/order/{order_id}/success", status_code=status.HTTP_303_SEE_OTHER)
|
return RedirectResponse(url=f"/order/{order_id}/success", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/order/cancel")
|
||||||
|
def cancel_order(
|
||||||
|
request: Request,
|
||||||
|
event_id: int = Form(...),
|
||||||
|
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:
|
||||||
|
raise HTTPException(status_code=404, detail="Event nicht gefunden")
|
||||||
|
|
||||||
|
if event.status != "open" or event.deadline < datetime.now():
|
||||||
|
raise HTTPException(status_code=400, detail="Die Frist ist abgelaufen. Bestellungen können nicht mehr storniert werden.")
|
||||||
|
|
||||||
|
order = db.query(Order).filter(
|
||||||
|
Order.tenant_id == tenant.id,
|
||||||
|
Order.event_id == event.id,
|
||||||
|
Order.user_id == current_user.id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not order:
|
||||||
|
return RedirectResponse(url=f"/?event_id={event.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
if order.is_paid:
|
||||||
|
raise HTTPException(status_code=400, detail="Bereits bezahlte Bestellungen können nur vom Organisator storniert werden.")
|
||||||
|
|
||||||
|
db.delete(order)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
success_msg = urllib.parse.quote("Deine Bestellung wurde erfolgreich storniert")
|
||||||
|
return RedirectResponse(url=f"/?event_id={event.id}&success={success_msg}", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/order/{order_id}/success", response_class=HTMLResponse)
|
@app.get("/order/{order_id}/success", response_class=HTMLResponse)
|
||||||
def order_success(order_id: int, request: Request, db: Session = Depends(get_db)):
|
def order_success(order_id: int, request: Request, db: Session = Depends(get_db)):
|
||||||
tenant = resolve_tenant(request, db)
|
tenant = resolve_tenant(request, db)
|
||||||
@@ -1522,6 +1574,45 @@ def toggle_paid(order_id: int, request: Request, db: Session = Depends(get_db)):
|
|||||||
return RedirectResponse(url=f"/admin?event_id={ev_id}&tab=orders", 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.post("/admin/orders/{order_id}/delete")
|
||||||
|
def admin_delete_order(
|
||||||
|
order_id: int,
|
||||||
|
request: Request,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
tenant = resolve_tenant(request, db)
|
||||||
|
actor = require_organizer_or_admin(request, db)
|
||||||
|
|
||||||
|
order = db.query(Order).filter(Order.id == order_id, Order.tenant_id == tenant.id).first()
|
||||||
|
if not order:
|
||||||
|
raise HTTPException(status_code=404, detail="Bestellung nicht gefunden")
|
||||||
|
|
||||||
|
ev_id = order.event_id
|
||||||
|
|
||||||
|
# Falls per Guthaben bezahlt, Betrag automatisch zurückerstatten!
|
||||||
|
if order.is_paid and order.payment_method == "balance" and order.user:
|
||||||
|
order.user.balance += order.total_price
|
||||||
|
ev_title = order.event.title if order.event else "Brotzeit"
|
||||||
|
tx = BalanceTransaction(
|
||||||
|
tenant_id=tenant.id,
|
||||||
|
user_id=order.user.id,
|
||||||
|
order_id=order.id,
|
||||||
|
amount=order.total_price,
|
||||||
|
transaction_type="refund",
|
||||||
|
description=f"Rückerstattung wegen Stornierung: {ev_title}",
|
||||||
|
created_by_id=actor.id
|
||||||
|
)
|
||||||
|
db.add(tx)
|
||||||
|
|
||||||
|
db.delete(order)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
referer = request.headers.get("referer", "")
|
||||||
|
if "/einkauf" in referer:
|
||||||
|
return RedirectResponse(url=f"/einkauf?event_id={ev_id}&success=Bestellung erfolgreich storniert und ggf. erstattet", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
return RedirectResponse(url=f"/admin?event_id={ev_id}&tab=orders&success=Bestellung erfolgreich storniert und ggf. erstattet", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok", "app": "Schmankerl"}
|
return {"status": "ok", "app": "Schmankerl"}
|
||||||
|
|||||||
+18
-1
@@ -133,12 +133,29 @@ body {
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hamburger-icon {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3.5px;
|
||||||
|
width: 16px;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-icon .bar,
|
||||||
.menu-toggle-btn .bar {
|
.menu-toggle-btn .bar {
|
||||||
display: block;
|
display: block;
|
||||||
width: 16px;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: var(--text-main);
|
background-color: var(--text-main);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle-btn:hover .hamburger-icon .bar,
|
||||||
|
.menu-toggle-btn:hover .bar {
|
||||||
|
background-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-btn-text {
|
.menu-btn-text {
|
||||||
|
|||||||
@@ -295,15 +295,25 @@
|
|||||||
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
|
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if event.sponsor_name %}
|
<div style="display: flex; align-items: center; justify-content: space-between; gap: 6px;">
|
||||||
<span class="badge-btn paid" style="cursor: default;">✓ Gesponsert</span>
|
{% if event.sponsor_name %}
|
||||||
{% else %}
|
<span class="badge-btn paid" style="cursor: default;">✓ Gesponsert</span>
|
||||||
<form action="/admin/toggle-paid/{{ order.id }}" method="POST" style="margin: 0;">
|
{% else %}
|
||||||
<button type="submit" class="badge-btn {% if order.is_paid %}paid{% else %}unpaid{% endif %}">
|
<form action="/admin/toggle-paid/{{ order.id }}" method="POST" style="margin: 0;">
|
||||||
{% if order.is_paid %}✓ Bezahlt{% else %}⏳ Offen{% endif %}
|
<button type="submit" class="badge-btn {% if order.is_paid %}paid{% else %}unpaid{% endif %}" title="Klick um Bezahlstatus umzuschalten (Guthaben wird automatisch erstattet)">
|
||||||
</button>
|
{% if order.is_paid %}
|
||||||
</form>
|
{% if order.payment_method == 'balance' %}✓ Guthaben{% else %}✓ Bezahlt{% endif %}
|
||||||
{% endif %}
|
{% else %}
|
||||||
|
⏳ Offen
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="/admin/orders/{{ order.id }}/delete" method="POST" style="margin: 0;" onsubmit="return confirm('Bestellung für {{ order.display_name|escape }} wirklich stornieren? Bereits bezahltes Guthaben wird sofort erstattet.');">
|
||||||
|
<button type="submit" class="btn-revoke-small" title="Bestellung stornieren / löschen" style="padding: 2px 6px; font-size: 0.8rem;">🗑️</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
+23
-21
@@ -23,9 +23,11 @@
|
|||||||
<!-- LEFT: Menu Toggle + Brand -->
|
<!-- LEFT: Menu Toggle + Brand -->
|
||||||
<div class="nav-left">
|
<div class="nav-left">
|
||||||
<button class="menu-toggle-btn" id="drawerToggleBtn" onclick="openDrawer()" aria-label="Menü öffnen" aria-expanded="false">
|
<button class="menu-toggle-btn" id="drawerToggleBtn" onclick="openDrawer()" aria-label="Menü öffnen" aria-expanded="false">
|
||||||
<span class="bar"></span>
|
<span class="hamburger-icon">
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
|
<span class="bar"></span>
|
||||||
|
</span>
|
||||||
<span class="menu-btn-text">Menü</span>
|
<span class="menu-btn-text">Menü</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -121,14 +123,6 @@
|
|||||||
<span class="nav-item-text">Brotzeit bestellen</span>
|
<span class="nav-item-text">Brotzeit bestellen</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if current_user and (current_user.role in ['admin', 'organizer']) %}
|
|
||||||
<a href="/einkauf" class="drawer-nav-item" onclick="closeDrawer()">
|
|
||||||
<span class="nav-item-icon">🛒</span>
|
|
||||||
<span class="nav-item-text">Einkaufsliste & Kasse</span>
|
|
||||||
<span class="nav-badge-pill">Einkauf</span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if current_user and current_user.role != 'guest' %}
|
{% if current_user and current_user.role != 'guest' %}
|
||||||
<div class="nav-group-title">KASSE & GUTHABEN</div>
|
<div class="nav-group-title">KASSE & GUTHABEN</div>
|
||||||
<a href="/kasse" class="drawer-nav-item" onclick="closeDrawer()">
|
<a href="/kasse" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
@@ -138,7 +132,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user and current_user.role in ['admin', 'organizer'] %}
|
{% if current_user and current_user.role in ['admin', 'organizer'] %}
|
||||||
<div class="nav-group-title">ORGANISATION</div>
|
<div class="nav-group-title">VERWALTUNG & ORGA</div>
|
||||||
|
<a href="/einkauf" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
|
<span class="nav-item-icon">🛒</span>
|
||||||
|
<span class="nav-item-text">Einkaufsliste & Kasse</span>
|
||||||
|
<span class="nav-badge-pill">Theke</span>
|
||||||
|
</a>
|
||||||
|
<a href="/admin?tab=orders" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
|
<span class="nav-item-icon">📋</span>
|
||||||
|
<span class="nav-item-text">Bestellungen & Abrechnung</span>
|
||||||
|
</a>
|
||||||
<a href="/admin?tab=event" class="drawer-nav-item" onclick="closeDrawer()">
|
<a href="/admin?tab=event" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
<span class="nav-item-icon">📅</span>
|
<span class="nav-item-icon">📅</span>
|
||||||
<span class="nav-item-text">Termine & Runden</span>
|
<span class="nav-item-text">Termine & Runden</span>
|
||||||
@@ -147,18 +150,17 @@
|
|||||||
<span class="nav-item-icon">🥨</span>
|
<span class="nav-item-icon">🥨</span>
|
||||||
<span class="nav-item-text">Sortiment & Preise</span>
|
<span class="nav-item-text">Sortiment & Preise</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin?tab=team" class="drawer-nav-item" onclick="closeDrawer()">
|
|
||||||
<span class="nav-item-icon">✉️</span>
|
|
||||||
<span class="nav-item-text">Einladungen & Gast-Links</span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if current_user and current_user.role == 'admin' %}
|
|
||||||
<div class="nav-group-title">SYSTEM-ADMINISTRATION</div>
|
|
||||||
<a href="/admin?tab=team" class="drawer-nav-item" onclick="closeDrawer()">
|
<a href="/admin?tab=team" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
<span class="nav-item-icon">👥</span>
|
<span class="nav-item-icon">👥</span>
|
||||||
<span class="nav-item-text">Team- & Rollenverwaltung</span>
|
<span class="nav-item-text">{% if current_user.role == 'admin' %}Team, Rollen & Einladungen{% else %}Team & Einladungen{% endif %}</span>
|
||||||
|
{% if current_user.role == 'admin' %}
|
||||||
<span class="nav-badge-pill admin">Admin</span>
|
<span class="nav-badge-pill admin">Admin</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% elif current_user and current_user.role == 'user' %}
|
||||||
|
<a href="/einkauf" class="drawer-nav-item" onclick="closeDrawer()">
|
||||||
|
<span class="nav-item-icon">🛒</span>
|
||||||
|
<span class="nav-item-text">Einkaufsliste (Theke)</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
+29
-23
@@ -247,34 +247,40 @@
|
|||||||
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
|
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if event.sponsor_name %}
|
<div style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
|
||||||
<span class="badge-btn paid" style="cursor: default;">✓ Gesponsert</span>
|
{% if event.sponsor_name %}
|
||||||
{% elif order.is_paid %}
|
<span class="badge-btn paid" style="cursor: default;">✓ Gesponsert</span>
|
||||||
<form action="/admin/toggle-paid/{{ order.id }}" method="POST" style="margin: 0; display: inline;">
|
{% elif order.is_paid %}
|
||||||
<button type="submit" class="badge-btn paid" title="Klick um Bezahlung zurückzusetzen">
|
<form action="/admin/toggle-paid/{{ order.id }}" method="POST" style="margin: 0; display: inline;">
|
||||||
{% if order.payment_method == 'balance' %}
|
<button type="submit" class="badge-btn paid" title="Klick um Bezahlung zurückzusetzen (Guthaben wird automatisch erstattet)">
|
||||||
✓ Bezahlt (Guthaben)
|
{% if order.payment_method == 'balance' %}
|
||||||
{% else %}
|
✓ Bezahlt (Guthaben)
|
||||||
✓ Bezahlt (Bar)
|
{% else %}
|
||||||
{% endif %}
|
✓ Bezahlt (Bar)
|
||||||
</button>
|
{% endif %}
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
|
||||||
{% if order.user and order.user.balance >= order.total_price %}
|
|
||||||
<form action="/kasse/charge-order/{{ order.id }}?method=balance" method="POST" style="margin: 0;">
|
|
||||||
<button type="submit" class="badge-btn paid" style="background: rgba(34, 197, 94, 0.15); color: var(--success);" title="Vom Guthaben des Mitarbeiters abbuchen">
|
|
||||||
💰 Abbuchen
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
||||||
|
{% if order.user and order.user.balance >= order.total_price %}
|
||||||
|
<form action="/kasse/charge-order/{{ order.id }}?method=balance" method="POST" style="margin: 0;">
|
||||||
|
<button type="submit" class="badge-btn paid" style="background: rgba(34, 197, 94, 0.15); color: var(--success);" title="Vom Guthaben des Mitarbeiters abbuchen">
|
||||||
|
💰 Abbuchen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<form action="/kasse/charge-order/{{ order.id }}?method=cash" method="POST" style="margin: 0;">
|
||||||
|
<button type="submit" class="badge-btn unpaid" title="Barzahlung annehmen">
|
||||||
|
💵 Bar bezahlt
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="/kasse/charge-order/{{ order.id }}?method=cash" method="POST" style="margin: 0;">
|
|
||||||
<button type="submit" class="badge-btn unpaid" title="Barzahlung annehmen">
|
<form action="/admin/orders/{{ order.id }}/delete" method="POST" style="margin: 0; display: inline;" onsubmit="return confirm('Bestellung für {{ order.display_name|escape }} wirklich stornieren? Bereits bezahltes Guthaben wird sofort erstattet.');">
|
||||||
💵 Bar bezahlt
|
<button type="submit" class="btn-revoke-small" title="Bestellung stornieren / löschen" style="padding: 2px 6px; font-size: 0.8rem;">🗑️</button>
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
+66
-11
@@ -3,6 +3,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert-success" style="margin-bottom: 20px;">
|
||||||
|
✓ {{ success }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert-error" style="margin-bottom: 20px;">
|
||||||
|
⚠️ {{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Multi-Event Picker (wenn mehrere anstehende Runden offen sind) -->
|
<!-- Multi-Event Picker (wenn mehrere anstehende Runden offen sind) -->
|
||||||
{% if open_events|length > 1 %}
|
{% if open_events|length > 1 %}
|
||||||
<div class="multi-event-banner">
|
<div class="multi-event-banner">
|
||||||
@@ -53,6 +65,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% set is_locked = is_closed or (existing_order and existing_order.is_paid) %}
|
||||||
|
|
||||||
<!-- Order Form -->
|
<!-- Order Form -->
|
||||||
<form action="/order" method="POST" class="order-form" id="orderForm">
|
<form action="/order" method="POST" class="order-form" id="orderForm">
|
||||||
<input type="hidden" name="event_id" value="{{ event.id }}">
|
<input type="hidden" name="event_id" value="{{ event.id }}">
|
||||||
@@ -65,7 +79,7 @@
|
|||||||
<div class="product-list">
|
<div class="product-list">
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
{% set current_qty = user_items.get(product.id, 0) %}
|
{% set current_qty = user_items.get(product.id, 0) %}
|
||||||
<div class="product-card {% if is_closed %}disabled{% endif %}" data-price="{{ product.price }}" data-id="{{ product.id }}">
|
<div class="product-card {% if is_locked %}disabled{% endif %}" data-price="{{ product.price }}" data-id="{{ product.id }}">
|
||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
<h3>{{ product.name }}</h3>
|
<h3>{{ product.name }}</h3>
|
||||||
<p class="product-desc">{{ product.description }}</p>
|
<p class="product-desc">{{ product.description }}</p>
|
||||||
@@ -79,9 +93,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quantity-control">
|
<div class="quantity-control">
|
||||||
<button type="button" class="qty-btn" onclick="changeQty({{ product.id }}, -1)" {% if is_closed %}disabled{% endif %}>-</button>
|
<button type="button" class="qty-btn" onclick="changeQty({{ product.id }}, -1)" {% if is_locked %}disabled{% endif %}>-</button>
|
||||||
<input type="number" id="qty_{{ product.id }}" name="qty_{{ product.id }}" value="{{ current_qty }}" min="0" max="50" class="qty-input" onchange="updateTotal()" {% if is_closed %}disabled{% endif %}>
|
<input type="number" id="qty_{{ product.id }}" name="qty_{{ product.id }}" value="{{ current_qty }}" min="0" max="50" class="qty-input" onchange="updateTotal()" {% if is_locked %}disabled{% endif %}>
|
||||||
<button type="button" class="qty-btn" onclick="changeQty({{ product.id }}, 1)" {% if is_closed %}disabled{% endif %}>+</button>
|
<button type="button" class="qty-btn" onclick="changeQty({{ product.id }}, 1)" {% if is_locked %}disabled{% endif %}>+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -125,11 +139,22 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% set is_locked = is_closed or (existing_order and existing_order.is_paid) %}
|
||||||
|
|
||||||
{% if existing_order %}
|
{% if existing_order %}
|
||||||
<div class="notice-box">
|
{% if existing_order.is_paid %}
|
||||||
ℹ️ Du hast bereits für diese Runde bestellt ({% if existing_order.is_paid %}<strong>Bereits bezahlt ✓</strong>{% else %}Noch offen{% endif %}).
|
<div class="alert-success" style="margin-bottom: 16px; border-left: 4px solid var(--success); font-size: 0.88rem; line-height: 1.4;">
|
||||||
{% if not is_closed %}Du kannst deine Mengen anpassen und aktualisieren.{% endif %}
|
🔒 <strong>Bestellung bereits bezahlt ({{ "%.2f"|format(existing_order.total_price) }} €)</strong><br>
|
||||||
</div>
|
Deine Bestellung wurde vom Organisator bereits als bezahlt verbucht
|
||||||
|
({% if existing_order.payment_method == 'balance' %}Guthaben abgebucht{% else %}Barzahlung vermerkt{% endif %}).
|
||||||
|
Änderungen sind gesperrt. Wende dich an deinen Organisator für Anpassungen.
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="notice-box" style="margin-bottom: 14px;">
|
||||||
|
ℹ️ Du hast bereits für diese Runde bestellt (Status: <strong>Noch offen</strong>).
|
||||||
|
{% if not is_closed %}Du kannst deine Mengen bis zum Fristende anpassen oder die Bestellung stornieren.{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr class="divider">
|
<hr class="divider">
|
||||||
@@ -145,8 +170,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<button type="submit" class="btn-submit" id="submitBtn" {% if is_closed %}disabled{% endif %}>
|
<button type="submit" class="btn-submit" id="submitBtn" {% if is_locked %}disabled{% endif %}>
|
||||||
{% if is_closed %}
|
{% if existing_order and existing_order.is_paid %}
|
||||||
|
🔒 Bestellung bereits bezahlt & gesperrt
|
||||||
|
{% elif is_closed %}
|
||||||
🔒 Bestellschluss vorüber
|
🔒 Bestellschluss vorüber
|
||||||
{% elif existing_order %}
|
{% elif existing_order %}
|
||||||
🥨 Bestellung aktualisieren
|
🥨 Bestellung aktualisieren
|
||||||
@@ -155,8 +182,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% if not is_closed %}
|
{% if existing_order and not existing_order.is_paid and not is_closed %}
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<button type="button" onclick="cancelMyOrder()" class="btn-secondary" style="width: 100%; border-color: var(--danger); color: var(--danger); font-size: 0.85rem; padding: 8px 12px; cursor: pointer;">
|
||||||
|
✕ Bestellung komplett stornieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not is_closed and not (existing_order and existing_order.is_paid) %}
|
||||||
<small class="form-hint">Du kannst deine Bestellung bis zum Bestellschluss jederzeit hier anpassen.</small>
|
<small class="form-hint">Du kannst deine Bestellung bis zum Bestellschluss jederzeit hier anpassen.</small>
|
||||||
|
{% elif existing_order and existing_order.is_paid %}
|
||||||
|
<small class="form-hint">Bereits abgerechnet – Änderungen nur über den Organisator.</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<small class="form-hint text-warning">Die Frist ist abgelaufen.</small>
|
<small class="form-hint text-warning">Die Frist ist abgelaufen.</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -261,6 +298,24 @@
|
|||||||
if (timerEl) timerEl.innerText = "Frist beendet";
|
if (timerEl) timerEl.innerText = "Frist beendet";
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
function cancelMyOrder() {
|
||||||
|
if (!confirm('Möchtest du deine Bestellung für diese Runde wirklich komplett stornieren?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.method = 'POST';
|
||||||
|
form.action = '/order/cancel';
|
||||||
|
|
||||||
|
const evInput = document.createElement('input');
|
||||||
|
evInput.type = 'hidden';
|
||||||
|
evInput.name = 'event_id';
|
||||||
|
evInput.value = '{{ event.id if event else "" }}';
|
||||||
|
form.appendChild(evInput);
|
||||||
|
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', updateTotal);
|
document.addEventListener('DOMContentLoaded', updateTotal);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user