""" Admin Routes - Erweiterte Admin-Funktionen für das Reservierungssystem """ import os import json from datetime import datetime, timedelta from functools import wraps from flask import Blueprint, request, jsonify, session from database import get_db from auth import require_auth admin_bp = Blueprint('admin_extended', __name__, url_prefix='/api/admin') # ============================================================================= # SMTP KONFIGURATION # ============================================================================= @admin_bp.route('/smtp', methods=['GET', 'POST']) def smtp_config(): """SMTP-Konfiguration lesen oder speichern""" # Prüfe Auth if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 if request.method == 'GET': with get_db() as db: config = db.execute('SELECT * FROM config WHERE key LIKE "smtp_%"').fetchall() result = {row['key']: row['value'] for row in config} return jsonify({ 'host': result.get('smtp_host', ''), 'port': int(result.get('smtp_port', 587)) if result.get('smtp_port') else 587, 'user': result.get('smtp_user', ''), 'from_email': result.get('smtp_from', ''), 'from_name': result.get('smtp_from_name', 'Reservierungssystem'), 'security': result.get('smtp_security', 'tls'), 'enabled': result.get('smtp_enabled', 'false') == 'true' }) # POST: Konfiguration speichern data = request.get_json() with get_db() as db: configs = [ ('smtp_host', data.get('host', '')), ('smtp_port', str(data.get('port', 587))), ('smtp_user', data.get('user', '')), ('smtp_from', data.get('from_email', '')), ('smtp_from_name', data.get('from_name', 'Reservierungssystem')), ('smtp_security', data.get('security', 'tls')), ('smtp_enabled', 'true' if data.get('enabled') else 'false') ] for key, value in configs: db.execute(''' INSERT INTO config (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at ''', (key, value, datetime.now().isoformat())) if data.get('password'): db.execute(''' INSERT INTO config (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at ''', ('smtp_password', data.get('password'), datetime.now().isoformat())) db.commit() return jsonify({"message": "SMTP-Konfiguration gespeichert"}) @admin_bp.route('/smtp/test', methods=['POST']) def test_smtp_endpoint(): """SMTP-Verbindung testen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 try: import smtplib data = request.get_json() or {} host = data.get('host') port = data.get('port', 587) user = data.get('user') password = data.get('password') security = data.get('security', 'tls') if not all([host, user, password]): with get_db() as db: config = db.execute('SELECT * FROM config WHERE key LIKE "smtp_%"').fetchall() config_dict = {row['key']: row['value'] for row in config} host = host or config_dict.get('smtp_host') port = port or int(config_dict.get('smtp_port', 587)) user = user or config_dict.get('smtp_user') password = password or config_dict.get('smtp_password') if not all([host, user, password]): return jsonify({"error": "SMTP-Konfiguration unvollständig"}), 400 if security == 'ssl': server = smtplib.SMTP_SSL(host, port) else: server = smtplib.SMTP(host, port) if security == 'tls': server.starttls() server.login(user, password) server.quit() return jsonify({"message": "SMTP-Verbindung erfolgreich"}) except Exception as e: return jsonify({"error": f"SMTP-Fehler: {str(e)}"}), 500 # ============================================================================= # LLM KONFIGURATION (Ollama) # ============================================================================= @admin_bp.route('/llm-config', methods=['GET', 'POST']) def llm_config_endpoint(): """LLM-Konfiguration lesen oder speichern""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 if request.method == 'GET': with get_db() as db: config = db.execute('SELECT * FROM config WHERE key LIKE "llm_%" OR key = "ollama_url"').fetchall() result = {row['key']: row['value'] for row in config} return jsonify({ 'enabled': result.get('llm_enabled', 'false') == 'true', 'url': result.get('ollama_url', 'http://localhost:11434'), 'model': result.get('llm_model', 'llama2'), 'temperature': float(result.get('llm_temperature', 0.7)), 'max_tokens': int(result.get('llm_max_tokens', 500)), 'auto_reply': result.get('llm_auto_reply', 'false') == 'true' }) data = request.get_json() with get_db() as db: configs = [ ('ollama_url', data.get('url', 'http://localhost:11434')), ('llm_model', data.get('model', 'llama2')), ('llm_temperature', str(data.get('temperature', 0.7))), ('llm_max_tokens', str(data.get('max_tokens', 500))), ('llm_enabled', 'true' if data.get('enabled') else 'false'), ('llm_auto_reply', 'true' if data.get('auto_reply') else 'false') ] for key, value in configs: db.execute(''' INSERT INTO config (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at ''', (key, value, datetime.now().isoformat())) db.commit() return jsonify({"message": "LLM-Konfiguration gespeichert"}) @admin_bp.route('/llm-config/test', methods=['POST']) def test_llm_endpoint(): """Ollama-Verbindung testen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 try: import requests data = request.get_json() or {} url = data.get('url', 'http://localhost:11434') response = requests.get(f"{url}/api/tags", timeout=5) if response.status_code == 200: models = response.json().get('models', []) return jsonify({ "message": "Ollama-Verbindung erfolgreich", "available_models": [m.get('name') for m in models[:5]] }) else: return jsonify({"error": f"Ollama Fehler: {response.status_code}"}), 500 except Exception as e: return jsonify({"error": f"Verbindungsfehler: {str(e)}"}), 500 @admin_bp.route('/llm-config/models', methods=['GET']) def list_llm_models(): """Verfügbare Ollama-Modelle auflisten""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 try: import requests with get_db() as db: config = db.execute('SELECT value FROM config WHERE key = "ollama_url"').fetchone() url = config['value'] if config else 'http://localhost:11434' response = requests.get(f"{url}/api/tags", timeout=5) if response.status_code == 200: models = response.json().get('models', []) return jsonify([{ 'name': m.get('name'), 'size': m.get('size'), 'modified': m.get('modified_at') } for m in models]) else: return jsonify([ {'name': 'llama2', 'size': 0}, {'name': 'mistral', 'size': 0}, {'name': 'mixtral', 'size': 0}, {'name': 'neural-chat', 'size': 0} ]) except Exception: return jsonify([ {'name': 'llama2', 'size': 0}, {'name': 'mistral', 'size': 0} ]) # ============================================================================= # STATISTIKEN # ============================================================================= @admin_bp.route('/stats', methods=['GET']) def get_stats_endpoint(): """Statistiken für Dashboard""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 period = request.args.get('period', '30') try: days = int(period) except ValueError: days = 30 start_date = (datetime.now() - timedelta(days=days)).date().isoformat() end_date = datetime.now().date().isoformat() with get_db() as db: total_reservations = db.execute(''' SELECT COUNT(*) FROM reservations WHERE date >= ? AND date <= ? ''', (start_date, end_date)).fetchone()[0] by_status = db.execute(''' SELECT status, COUNT(*) as count FROM reservations WHERE date >= ? AND date <= ? GROUP BY status ''', (start_date, end_date)).fetchall() status_counts = {row['status']: row['count'] for row in by_status} today = datetime.now().date().isoformat() today_count = db.execute(''' SELECT COUNT(*) FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending') ''', (today,)).fetchone()[0] total_guests = db.execute(''' SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date >= ? AND date <= ? AND status = 'confirmed' ''', (start_date, end_date)).fetchone()[0] avg_guests = db.execute(''' SELECT COALESCE(AVG(guests), 0) FROM reservations WHERE date >= ? AND date <= ? AND status = 'confirmed' ''', (start_date, end_date)).fetchone()[0] popular_times = db.execute(''' SELECT time_from, COUNT(*) as count FROM reservations WHERE date >= ? AND date <= ? AND status = 'confirmed' GROUP BY time_from ORDER BY count DESC LIMIT 5 ''', (start_date, end_date)).fetchall() room_stats = [] # Deaktiviert - table_ids ist JSON Array, nicht Fremdschlüssel daily_stats = db.execute(''' SELECT date, COUNT(*) as count, SUM(guests) as guests FROM reservations WHERE date >= ? AND date <= ? AND status = 'confirmed' GROUP BY date ORDER BY date ''', (start_date, end_date)).fetchall() estimated_revenue = total_guests * 30 return jsonify({ 'period_days': days, 'start_date': start_date, 'end_date': end_date, 'total_reservations': total_reservations, 'today_count': today_count, 'total_guests': total_guests, 'average_group_size': round(avg_guests, 1), 'estimated_revenue': estimated_revenue, 'by_status': status_counts, 'popular_times': [dict(t) for t in popular_times], 'room_stats': [dict(r) for r in room_stats], 'daily_stats': [dict(d) for d in daily_stats] }) @admin_bp.route('/stats/overview', methods=['GET']) def get_stats_overview(): """Kompakte Übersichts-Statistiken für Dashboard""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 today = datetime.now().date().isoformat() with get_db() as db: today_stats = db.execute(''' SELECT COUNT(*) as reservations, COALESCE(SUM(guests), 0) as guests FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending') ''', (today,)).fetchone() week_start = (datetime.now() - timedelta(days=7)).date().isoformat() week_stats = db.execute(''' SELECT COUNT(*) as count FROM reservations WHERE date >= ? AND status = 'confirmed' ''', (week_start,)).fetchone() pending = db.execute(''' SELECT COUNT(*) FROM reservations WHERE date >= ? AND status = 'pending' ''', (today,)).fetchone()[0] return jsonify({ 'today_reservations': today_stats['reservations'], 'today_guests': today_stats['guests'], 'week_confirmed': week_stats['count'], 'pending_count': pending }) # ============================================================================= # RAUM-VERWALTUNG (Erweitert) # ============================================================================= @admin_bp.route('/rooms/', methods=['PUT', 'DELETE']) def admin_room_detail(room_id): """Raum aktualisieren oder löschen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 if request.method == 'PUT': data = request.get_json() with get_db() as db: db.execute(''' UPDATE rooms SET name = ?, capacity = ?, color = ?, open_time = ?, close_time = ? WHERE id = ? ''', ( data.get('name'), data.get('capacity'), data.get('color'), data.get('open_time', '10:00'), data.get('close_time', '22:00'), room_id )) db.commit() return jsonify({"message": "Raum aktualisiert"}) elif request.method == 'DELETE': with get_db() as db: has_bookings = db.execute(''' SELECT COUNT(*) FROM room_bookings WHERE room_id = ? AND date >= ? ''', (room_id, datetime.now().date().isoformat())).fetchone()[0] if has_bookings > 0: return jsonify({ "error": "Raum hat zukünftige Buchungen und kann nicht gelöscht werden" }), 400 db.execute('DELETE FROM rooms WHERE id = ?', (room_id,)) db.commit() return jsonify({"message": "Raum gelöscht"}) # ============================================================================= # TISCH-VERWALTUNG (Erweitert) # ============================================================================= @admin_bp.route('/tables', methods=['POST']) def create_table_endpoint(): """Neuen Tisch erstellen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 data = request.get_json() required = ['area_id', 'name', 'seats'] for field in required: if not data.get(field): return jsonify({"error": f"{field} ist erforderlich"}), 400 with get_db() as db: cursor = db.execute(''' INSERT INTO tables (area_id, name, seats, x, y, shape, is_active) VALUES (?, ?, ?, ?, ?, ?, 1) ''', ( data.get('area_id'), data.get('name'), data.get('seats'), data.get('x', 0), data.get('y', 0), data.get('shape', 'rectangle') )) db.commit() return jsonify({"id": cursor.lastrowid, "message": "Tisch erstellt"}), 201 @admin_bp.route('/tables/batch', methods=['POST']) def batch_update_tables(): """Mehrere Tische gleichzeitig aktualisieren (für Floorplan)""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 data = request.get_json() tables = data.get('tables', []) if not tables: return jsonify({"error": "Keine Tische übergeben"}), 400 with get_db() as db: for table in tables: db.execute(''' UPDATE tables SET x = ?, y = ?, width = ?, height = ? WHERE id = ? ''', ( table.get('x', 0), table.get('y', 0), table.get('width', 80), table.get('height', 80), table.get('id') )) db.commit() return jsonify({"message": f"{len(tables)} Tische aktualisiert"}) # ============================================================================= # RESERVIERUNGS-VERWALTUNG # ============================================================================= @admin_bp.route('/reservations//confirm', methods=['POST']) def confirm_reservation_endpoint(res_id): """Reservierung bestätigen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 with get_db() as db: db.execute(''' UPDATE reservations SET status = 'confirmed', updated_at = ? WHERE id = ? ''', (datetime.now().isoformat(), res_id)) db.commit() return jsonify({"message": "Reservierung bestätigt"}) @admin_bp.route('/reservations//cancel', methods=['POST']) def cancel_reservation_endpoint(res_id): """Reservierung stornieren""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 data = request.get_json() or {} reason = data.get('reason', '') with get_db() as db: db.execute(''' UPDATE reservations SET status = 'cancelled', updated_at = ?, notes = COALESCE(notes, '') || ? WHERE id = ? ''', (datetime.now().isoformat(), f"\nStorniert: {reason}", res_id)) db.commit() return jsonify({"message": "Reservierung storniert"}) @admin_bp.route('/reservations/pending', methods=['GET']) def get_pending_reservations(): """Ausstehende Reservierungen abrufen""" if not session.get('user_role') == 'admin': return jsonify({"error": "Unauthorized"}), 401 with get_db() as db: rows = db.execute(''' SELECT r.*, g.name as guest_name, g.email, g.phone FROM reservations r LEFT JOIN guests g ON r.guest_id = g.id WHERE r.status = 'pending' AND r.date >= ? ORDER BY r.date, r.time_from ''', (datetime.now().date().isoformat(),)).fetchall() return jsonify([dict(row) for row in rows])