feat: add payment tracking for orders
- Add payment method selection (PayPal/Bar) to order form - Store payment_method and payment_status in database - Add payment status badges in admin orders view - Add "mark as paid" functionality for admins - PayPal account configurable via PAYPAL_ACCOUNT env variable - Frontend loads PayPal account dynamically from /wawi/config endpoint - Update email notifications to include payment method Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
57
wawi/app.py
57
wawi/app.py
@@ -167,6 +167,7 @@ def init_db() -> None:
|
||||
ensure_price_column(db)
|
||||
ensure_image_column(db)
|
||||
ensure_orders_columns(db)
|
||||
ensure_payment_columns(db)
|
||||
ensure_indexes(db)
|
||||
ensure_admin_user(db)
|
||||
|
||||
@@ -208,6 +209,22 @@ def ensure_orders_columns(db: sqlite3.Connection) -> None:
|
||||
db.commit()
|
||||
|
||||
|
||||
def ensure_payment_columns(db: sqlite3.Connection) -> None:
|
||||
"""Fügt Zahlungs-Spalten nachträglich zur orders-Tabelle hinzu."""
|
||||
cols = db.execute("PRAGMA table_info(orders)").fetchall()
|
||||
names = {c["name"] for c in cols}
|
||||
if "payment_method" not in names:
|
||||
db.execute("ALTER TABLE orders ADD COLUMN payment_method TEXT DEFAULT 'bar'")
|
||||
if "payment_status" not in names:
|
||||
db.execute("ALTER TABLE orders ADD COLUMN payment_status TEXT DEFAULT 'unpaid'")
|
||||
if "paid_at" not in names:
|
||||
db.execute("ALTER TABLE orders ADD COLUMN paid_at TEXT")
|
||||
if "paid_by" not in names:
|
||||
db.execute("ALTER TABLE orders ADD COLUMN paid_by TEXT")
|
||||
db.commit()
|
||||
logger.info("Zahlungs-Spalten für orders überprüft/erstellt")
|
||||
|
||||
|
||||
def ensure_indexes(db: sqlite3.Connection) -> None:
|
||||
"""Erstellt Indizes für bessere Query-Performance (idempotent)."""
|
||||
# Index für items.artikel (häufig gesucht/gefiltert)
|
||||
@@ -648,6 +665,14 @@ def proxy_bestand():
|
||||
return jsonify(build_bestand())
|
||||
|
||||
|
||||
@bp.route("/config", methods=["GET"])
|
||||
def config():
|
||||
"""Öffentliche Konfiguration für Frontend (z. B. PayPal-Konto)."""
|
||||
return jsonify({
|
||||
"paypal_account": os.environ.get("PAYPAL_ACCOUNT", "")
|
||||
})
|
||||
|
||||
|
||||
def build_bestand() -> list[dict]:
|
||||
"""Aggregiert DB‑Zeilen in die Struktur der Live‑Bestand Ansicht."""
|
||||
rows = get_db().execute(
|
||||
@@ -724,11 +749,15 @@ def order():
|
||||
if int(data.get("menge") or 0) <= 0:
|
||||
return jsonify({"error": "Menge muss größer als 0 sein."}), 400
|
||||
|
||||
payment_method = (data.get("payment_method") or "bar").strip().lower()
|
||||
if payment_method not in ("paypal", "bar"):
|
||||
payment_method = "bar"
|
||||
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO orders (name, handy, mannschaft, artikel, groesse, menge, notiz, created_at, done, completed_by, completed_at, canceled, canceled_by, canceled_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, NULL, NULL, 0, NULL, NULL)
|
||||
INSERT INTO orders (name, handy, mannschaft, artikel, groesse, menge, notiz, created_at, done, completed_by, completed_at, canceled, canceled_by, canceled_at, payment_method, payment_status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, NULL, NULL, 0, NULL, NULL, ?, 'unpaid')
|
||||
""",
|
||||
(
|
||||
data.get("name"),
|
||||
@@ -739,6 +768,7 @@ def order():
|
||||
int(data.get("menge") or 0),
|
||||
data.get("notiz"),
|
||||
now_iso(),
|
||||
payment_method,
|
||||
),
|
||||
)
|
||||
db.commit()
|
||||
@@ -759,6 +789,7 @@ def order():
|
||||
msg["From"] = smtp_from
|
||||
recipients = [a.strip() for a in to_addr.split(",") if a.strip()]
|
||||
msg["To"] = ", ".join(recipients)
|
||||
payment_label = "PayPal (Familie & Freunde)" if payment_method == "paypal" else "Bar/Abholung"
|
||||
body = (
|
||||
"Neue Bestellung:\n"
|
||||
f"Name: {data.get('name')}\n"
|
||||
@@ -767,6 +798,7 @@ def order():
|
||||
f"Artikel: {data.get('artikel')}\n"
|
||||
f"Größe: {data.get('groesse')}\n"
|
||||
f"Menge: {data.get('menge')}\n"
|
||||
f"Zahlungsart: {payment_label}\n"
|
||||
f"Notiz: {data.get('notiz') or '-'}\n"
|
||||
"WaWi: https://hellas.welker.me/wawi\n"
|
||||
)
|
||||
@@ -799,7 +831,7 @@ def orders():
|
||||
"""Bestellliste in der Verwaltung."""
|
||||
rows = get_db().execute(
|
||||
"""
|
||||
SELECT id, name, handy, mannschaft, artikel, groesse, menge, notiz, created_at, done, completed_by, completed_at, canceled, canceled_by, canceled_at
|
||||
SELECT id, name, handy, mannschaft, artikel, groesse, menge, notiz, created_at, done, completed_by, completed_at, canceled, canceled_by, canceled_at, payment_method, payment_status, paid_at, paid_by
|
||||
FROM orders
|
||||
ORDER BY id DESC
|
||||
LIMIT 500
|
||||
@@ -861,6 +893,25 @@ def complete_order(order_id: int):
|
||||
return redirect(url_for("bp.orders"))
|
||||
|
||||
|
||||
@bp.route("/orders/mark_paid/<int:order_id>", methods=["POST"])
|
||||
@login_required
|
||||
def mark_paid(order_id: int):
|
||||
"""Bestellung als bezahlt markieren."""
|
||||
user = session.get("user") or "unknown"
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE orders
|
||||
SET payment_status = 'paid', paid_by = ?, paid_at = ?
|
||||
WHERE id = ? AND payment_status != 'paid'
|
||||
""",
|
||||
(user, now_iso(), order_id),
|
||||
)
|
||||
db.commit()
|
||||
logger.info(f"Bestellung #{order_id} als bezahlt markiert von {user}")
|
||||
return redirect(url_for("bp.orders"))
|
||||
|
||||
|
||||
@bp.route("/orders/cancel/<int:order_id>", methods=["POST"])
|
||||
@login_required
|
||||
def cancel_order(order_id: int):
|
||||
|
||||
Reference in New Issue
Block a user