#!/usr/bin/env python3 """Reservierungssystem - Flask Backend""" import os import json from datetime import datetime, timedelta from functools import wraps from flask import Flask, request, jsonify, render_template, send_from_directory, g from flask_cors import CORS from database import get_db, init_db, generate_booking_number, log_change from utils.ollama_client import ( parse_email_with_ollama, generate_confirmation_email, suggest_table_for_group ) app = Flask(__name__, template_folder='templates', static_folder='static') CORS(app) # Auth Blueprint app.secret_key = "dev-secret-change-in-production" from login_routes import auth_bp app.register_blueprint(auth_bp) # Konfiguration DEFAULT_OPEN_HOUR = 10 # 10:00 DEFAULT_CLOSE_HOUR = 23 # 23:00 RESERVATION_DURATION = 120 # Minuten def init_app(): """App initialisieren""" init_db() # Standard-Daten einfügen with get_db() as db: # Default Raum room = db.execute("SELECT id FROM rooms LIMIT 1").fetchone() if not room: cursor = db.execute( "INSERT INTO rooms (name, capacity, color) VALUES (?, ?, ?)", ("Hauptsaal", 50, "#3498db") ) room_id = cursor.lastrowid # Default Bereich cursor = db.execute( "INSERT INTO areas (room_id, name) VALUES (?, ?)", (room_id, "Fensterbereich") ) area_id = cursor.lastrowid # Beispiel-Tische tables = [ ("T1", 2, 50, 50), ("T2", 4, 200, 50), ("T3", 4, 350, 50), ("T4", 6, 500, 50), ("T5", 8, 50, 200), ("T6", 2, 200, 200), ] for name, seats, x, y in tables: db.execute( """INSERT INTO tables (area_id, name, seats, x, y) VALUES (?, ?, ?, ?, ?)""", (area_id, name, seats, x, y) ) db.commit() print("✓ Standard-Daten erstellt") # API-Routen @app.route('/') def index(): """Hauptseite - Dashboard""" return render_template('index.html') @app.route('/api/health') def health(): """Health-Check""" return jsonify({"status": "ok", "timestamp": datetime.now().isoformat()}) # Dashboard-Daten @app.route('/api/dashboard') def dashboard(): """Dashboard-Daten laden""" today = datetime.now().date().isoformat() with get_db() as db: # Heutige Reservierungen today_count = db.execute( "SELECT COUNT(*) FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending')", (today,) ).fetchone()[0] # Unbearbeitete E-Mails pending_emails = db.execute( "SELECT COUNT(*) FROM emails WHERE status = 'needs_review'" ).fetchone()[0] # Freie Tische heute total_tables = db.execute( "SELECT COUNT(*) FROM tables WHERE is_active = 1" ).fetchone()[0] # Heutige Gäste gesamt guests_today = db.execute( "SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending')", (today,) ).fetchone()[0] # Heutige Reservierungen (Liste) today_reservations = db.execute(''' SELECT r.*, g.name as guest_name, g.phone FROM reservations r LEFT JOIN guests g ON r.guest_id = g.id WHERE r.date = ? AND r.status IN ('confirmed', 'pending') ORDER BY r.time_from ''', (today,)).fetchall() return jsonify({ "today_count": today_count, "pending_emails": pending_emails, "total_tables": total_tables, "guests_today": guests_today, "today_reservations": [dict(r) for r in today_reservations] }) # Gäste-Verwaltung @app.route('/api/guests', methods=['GET', 'POST']) def guests(): """Gäste auflisten oder neuen Gast erstellen""" if request.method == 'GET': search = request.args.get('search', '') with get_db() as db: if search: rows = db.execute(''' SELECT g.*, t.name as preferred_table FROM guests g LEFT JOIN tables t ON g.preferred_table_id = t.id WHERE g.name LIKE ? OR g.phone LIKE ? OR g.email LIKE ? ORDER BY g.name ''', (f'%{search}%', f'%{search}%', f'%{search}%')).fetchall() else: rows = db.execute(''' SELECT g.*, t.name as preferred_table FROM guests g LEFT JOIN tables t ON g.preferred_table_id = t.id ORDER BY g.name ''').fetchall() return jsonify([dict(r) for r in rows]) # POST: Neuen Gast erstellen data = request.get_json() with get_db() as db: cursor = db.execute(''' INSERT INTO guests (name, phone, email, notes) VALUES (?, ?, ?, ?) ''', (data.get('name'), data.get('phone'), data.get('email'), data.get('notes'))) db.commit() return jsonify({"id": cursor.lastrowid, "message": "Gast erstellt"}) @app.route('/api/guests/', methods=['GET', 'PUT', 'DELETE']) def guest_detail(guest_id): """Einzelnen Gast verwalten""" with get_db() as db: if request.method == 'GET': # Gast mit Historie guest = db.execute('SELECT * FROM guests WHERE id = ?', (guest_id,)).fetchone() if not guest: return jsonify({"error": "Gast nicht gefunden"}), 404 # Letzte Reservierungen history = db.execute(''' SELECT booking_number, date, time_from, guests FROM reservations WHERE guest_id = ? ORDER BY date DESC LIMIT 5 ''', (guest_id,)).fetchall() result = dict(guest) result['reservation_history'] = [dict(h) for h in history] return jsonify(result) elif request.method == 'PUT': data = request.get_json() db.execute(''' UPDATE guests SET name = ?, phone = ?, email = ?, preferred_table_id = ?, notes = ? WHERE id = ? ''', (data.get('name'), data.get('phone'), data.get('email'), data.get('preferred_table_id'), data.get('notes'), guest_id)) db.commit() return jsonify({"message": "Gast aktualisiert"}) elif request.method == 'DELETE': db.execute('DELETE FROM guests WHERE id = ?', (guest_id,)) db.commit() return jsonify({"message": "Gast gelöscht"}) # Tische und Räume @app.route('/api/rooms') def rooms(): """Alle Räume mit Bereichen und Tischen""" with get_db() as db: rooms = db.execute('SELECT * FROM rooms').fetchall() result = [] for room in rooms: room_dict = dict(room) areas = db.execute('SELECT * FROM areas WHERE room_id = ?', (room['id'],)).fetchall() room_dict['areas'] = [] for area in areas: area_dict = dict(area) tables = db.execute( 'SELECT * FROM tables WHERE area_id = ? AND is_active = 1', (area['id'],) ).fetchall() area_dict['tables'] = [dict(t) for t in tables] room_dict['areas'].append(area_dict) result.append(room_dict) return jsonify(result) # Reservierungen @app.route('/api/reservations', methods=['GET', 'POST']) def reservations(): """Reservierungen auflisten oder neu erstellen""" if request.method == 'GET': date = request.args.get('date') status = request.args.get('status', 'confirmed,pending') with get_db() as db: query = ''' SELECT r.*, g.name as guest_name, g.phone, g.email FROM reservations r LEFT JOIN guests g ON r.guest_id = g.id WHERE r.status IN ({}) '''.format(','.join(f'"{s}"' for s in status.split(','))) params = [] if date: query += ' AND r.date = ?' params.append(date) query += ' ORDER BY r.time_from' rows = db.execute(query, params).fetchall() return jsonify([dict(r) for r in rows]) # POST: Neue Reservierung data = request.get_json() # Gast finden oder erstellen guest_id = data.get('guest_id') if not guest_id and data.get('email'): with get_db() as db: guest = db.execute('SELECT id FROM guests WHERE email = ?', (data['email'],)).fetchone() if guest: guest_id = guest['id'] # Buchungsnummer generieren booking_number = generate_booking_number() with get_db() as db: cursor = db.execute(''' INSERT INTO reservations (booking_number, guest_id, table_ids, date, time_from, time_to, guests, occasion, notes, source, phone_caller_name, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( booking_number, guest_id, json.dumps(data.get('table_ids', [])), data.get('date'), data.get('time_from'), data.get('time_to'), data.get('guests'), data.get('occasion'), data.get('notes'), data.get('source', 'manual'), data.get('phone_caller_name'), data.get('created_by', 'system') )) # Gast-Zähler aktualisieren if guest_id: db.execute(''' UPDATE guests SET visit_count = visit_count + 1, last_visit = ? WHERE id = ? ''', (data.get('date'), guest_id)) db.commit() return jsonify({ "id": cursor.lastrowid, "booking_number": booking_number, "message": "Reservierung erstellt" }) @app.route('/api/reservations/', methods=['GET', 'PUT', 'DELETE']) def reservation_detail(res_id): """Einzelne Reservierung verwalten""" with get_db() as db: if request.method == 'GET': # Reservierung mit Historie res = db.execute(''' SELECT r.*, g.name as guest_name, g.phone, g.email FROM reservations r LEFT JOIN guests g ON r.guest_id = g.id WHERE r.id = ? ''', (res_id,)).fetchone() if not res: return jsonify({"error": "Reservierung nicht gefunden"}), 404 # Änderungshistorie history = db.execute(''' SELECT * FROM reservation_history WHERE reservation_id = ? ORDER BY changed_at DESC ''', (res_id,)).fetchall() result = dict(res) result['change_history'] = [dict(h) for h in history] return jsonify(result) elif request.method == 'PUT': data = request.get_json() # Alte Werte für Historie old = db.execute('SELECT * FROM reservations WHERE id = ?', (res_id,)).fetchone() # Felder aktualisieren update_fields = [] params = [] if 'date' in data: update_fields.append('date = ?') params.append(data['date']) log_change(db, res_id, old['booking_number'], 'date', old['date'], data['date']) if 'time_from' in data: update_fields.append('time_from = ?') params.append(data['time_from']) log_change(db, res_id, old['booking_number'], 'time_from', old['time_from'], data['time_from']) if 'guests' in data: update_fields.append('guests = ?') params.append(data['guests']) log_change(db, res_id, old['booking_number'], 'guests', old['guests'], data['guests']) if 'table_ids' in data: update_fields.append('table_ids = ?') params.append(json.dumps(data['table_ids'])) if 'status' in data: update_fields.append('status = ?') params.append(data['status']) if update_fields: params.append(res_id) db.execute(f''' UPDATE reservations SET {', '.join(update_fields)} WHERE id = ? ''', params) db.commit() return jsonify({"message": "Reservierung aktualisiert"}) elif request.method == 'DELETE': db.execute('UPDATE reservations SET status = "cancelled" WHERE id = ?', (res_id,)) db.commit() return jsonify({"message": "Reservierung storniert"}) # E-Mail-Verarbeitung @app.route('/api/emails', methods=['GET', 'POST']) def emails(): """E-Mails verwalten""" if request.method == 'GET': status = request.args.get('status', 'needs_review') with get_db() as db: rows = db.execute(''' SELECT e.*, r.booking_number as linked_booking FROM emails e LEFT JOIN reservations r ON e.linked_reservation_id = r.id WHERE e.status = ? ORDER BY e.received_at DESC ''', (status,)).fetchall() return jsonify([dict(r) for r in rows]) # POST: Neue E-Mail verarbeiten data = request.get_json() # Mit Ollama parsen parsed = parse_email_with_ollama( data.get('subject', ''), data.get('body', ''), data.get('sender_name', '') ) with get_db() as db: cursor = db.execute(''' INSERT INTO emails (message_id, subject, body, sender, sender_email, parsed_json, confidence, action_type, status, booking_number_found, received_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( data.get('message_id'), data.get('subject'), data.get('body'), data.get('sender_name'), data.get('sender_email'), json.dumps(parsed), parsed.get('confidence', 0), parsed.get('intent', 'unknown'), 'auto_processed' if not parsed.get('needs_review') else 'needs_review', parsed.get('booking_number'), datetime.now() )) db.commit() email_id = cursor.lastrowid # Wenn keine Review nötig, automatisch verarbeiten if not parsed.get('needs_review') and parsed.get('intent') in ['new', 'modification']: process_email_reservation(db, email_id, parsed) return jsonify({ "email_id": email_id, "parsed": parsed, "status": "processed" if not parsed.get('needs_review') else "needs_review" }) def process_email_reservation(db, email_id, parsed_data): """E-Mail automatisch in Reservierung umwandeln""" intent = parsed_data.get('intent') booking_number = parsed_data.get('booking_number') if intent == 'modification' and booking_number: # Bestehende Reservierung ändern existing = db.execute( 'SELECT id FROM reservations WHERE booking_number = ?', (booking_number,) ).fetchone() if existing: # Felder aktualisieren updates = [] params = [] if parsed_data.get('date'): updates.append('date = ?') params.append(parsed_data['date']) if parsed_data.get('time'): updates.append('time_from = ?') params.append(parsed_data['time']) if parsed_data.get('guests'): updates.append('guests = ?') params.append(parsed_data['guests']) if updates: params.extend([existing['id']]) db.execute(f''' UPDATE reservations SET {', '.join(updates)} WHERE id = ? ''', params) db.execute(''' UPDATE emails SET linked_reservation_id = ?, status = 'confirmed' WHERE id = ? ''', (existing['id'], email_id)) elif intent == 'new': # Neue Reservierung erstellen # Gast finden oder erstellen guest_id = None if parsed_data.get('email'): guest = db.execute( 'SELECT id FROM guests WHERE email = ?', (parsed_data['email'],) ).fetchone() if guest: guest_id = guest['id'] else: cursor = db.execute(''' INSERT INTO guests (name, phone, email) VALUES (?, ?, ?) ''', (parsed_data.get('name'), parsed_data.get('phone'), parsed_data['email'])) guest_id = cursor.lastrowid # Neue Buchungsnummer new_booking = generate_booking_number() cursor = db.execute(''' INSERT INTO reservations (booking_number, guest_id, table_ids, date, time_from, guests, occasion, notes, source, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( new_booking, guest_id, json.dumps([]), # Tische später zuweisen parsed_data.get('date'), parsed_data.get('time'), parsed_data.get('guests', 2), parsed_data.get('occasion'), parsed_data.get('notes'), 'email', 'confirmed' )) res_id = cursor.lastrowid db.execute(''' UPDATE emails SET linked_reservation_id = ?, status = 'confirmed' WHERE id = ? ''', (res_id, email_id)) # Verfügbarkeit prüfen @app.route('/api/availability') def check_availability(): """Verfügbare Tische für Zeitfenster prüfen""" date = request.args.get('date') time_from = request.args.get('time_from') time_to = request.args.get('time_to') guests = int(request.args.get('guests', 2)) if not all([date, time_from]): return jsonify({"error": "Datum und Uhrzeit erforderlich"}), 400 if not time_to: # Standard-Dauer 2 Stunden from datetime import datetime as dt tf = dt.strptime(time_from, '%H:%M') time_to = (tf + timedelta(minutes=120)).strftime('%H:%M') with get_db() as db: # Alle aktiven Tische laden all_tables = db.execute(''' SELECT t.*, a.name as area_name FROM tables t JOIN areas a ON t.area_id = a.id WHERE t.is_active = 1 ''').fetchall() # Belegte Tische in diesem Zeitraum occupied = db.execute(''' SELECT DISTINCT json_each.value as table_id FROM reservations r, json_each(r.table_ids) WHERE r.date = ? AND r.status IN ('confirmed', 'pending') AND NOT (r.time_to <= ? OR r.time_from >= ?) ''', (date, time_from, time_to)).fetchall() occupied_ids = {row['table_id'] for row in occupied} # Verfügbare Tische filtern available = [dict(t) for t in all_tables if t['id'] not in occupied_ids and t['seats'] >= guests] # Nach Größe sortieren (beste Passung zuerst) available.sort(key=lambda x: (x['seats'] - guests, x['seats'])) return jsonify({ "available_tables": available, "total": len(all_tables), "occupied": len(occupied_ids) }) # Statistiken @app.route('/api/stats') def statistics(): """Statistiken für Dashboard""" from_date = request.args.get('from', (datetime.now() - timedelta(days=30)).date().isoformat()) to_date = request.args.get('to', datetime.now().date().isoformat()) with get_db() as db: # Gesamtreservierungen total = db.execute(''' SELECT COUNT(*) as count, COALESCE(SUM(guests), 0) as guests FROM reservations WHERE date BETWEEN ? AND ? AND status IN ('confirmed', 'completed') ''', (from_date, to_date)).fetchone() # Durchschnittliche Gruppengröße avg_size = db.execute(''' SELECT AVG(guests) as avg FROM reservations WHERE date BETWEEN ? AND ? AND status IN ('confirmed', 'completed') ''', (from_date, to_date)).fetchone() # Nach Wochentag by_day = db.execute(''' SELECT strftime('%w', date) as day, COUNT(*) as count FROM reservations WHERE date BETWEEN ? AND ? AND status IN ('confirmed', 'completed') GROUP BY day ''', (from_date, to_date)).fetchall() # Stammgäste (mehr als 2 Besuche) regulars = db.execute(''' SELECT COUNT(*) FROM guests WHERE visit_count >= 2 ''').fetchone()[0] return jsonify({ "period": {"from": from_date, "to": to_date}, "total_reservations": total['count'], "total_guests": total['guests'], "average_group_size": round(avg_size['avg'], 1) if avg_size['avg'] else 0, "by_day": {row['day']: row['count'] for row in by_day}, "regular_guests": regulars }) if __name__ == '__main__': init_app() port = int(os.environ.get('PORT', 8080)) print(f"🍽️ Reservierungssystem starting on port {port}") app.run(host='0.0.0.0', port=port, debug=False)# NEUE API ENDPOINTS FÜR ROOM BOOKINGS # Diese werden ans Ende der main.py angehängt # ============================================================================= # ROOM BOOKING APIs (NEU) # ============================================================================= @app.route('/api/rooms-with-bookings', methods=['GET']) def rooms_with_bookings(): """Räume mit aktuellen Raumbuchungen laden""" date = request.args.get('date', datetime.now().date().isoformat()) with get_db() as db: rooms = db.execute('SELECT * FROM rooms').fetchall() result = [] for room in rooms: room_dict = dict(room) # Bereiche und Tische laden areas = db.execute('SELECT * FROM areas WHERE room_id = ?', (room['id'],)).fetchall() room_dict['areas'] = [] for area in areas: area_dict = dict(area) tables = db.execute( 'SELECT * FROM tables WHERE area_id = ? AND is_active = 1', (area['id'],) ).fetchall() area_dict['tables'] = [dict(t) for t in tables] room_dict['areas'].append(area_dict) # Raumbuchungen für das Datum laden bookings = db.execute(''' SELECT * FROM room_bookings WHERE room_id = ? AND date = ? AND status = 'confirmed' ORDER BY time_from ''', (room['id'], date)).fetchall() room_dict['bookings'] = [dict(b) for b in bookings] result.append(room_dict) return jsonify(result) @app.route('/api/room-bookings', methods=['GET', 'POST']) def room_bookings(): """Raumbuchungen auflisten oder erstellen""" if request.method == 'GET': date = request.args.get('date') room_id = request.args.get('room_id') with get_db() as db: query = ''' SELECT rb.*, r.name as room_name, r.capacity as room_capacity FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE 1=1 ''' params = [] if date: query += ' AND rb.date = ?' params.append(date) if room_id: query += ' AND rb.room_id = ?' params.append(room_id) query += ' ORDER BY rb.date DESC, rb.time_from' rows = db.execute(query, params).fetchall() return jsonify([dict(r) for r in rows]) # POST: Neue Raumbuchung erstellen data = request.get_json() # Validierung required = ['room_id', 'date', 'time_from', 'time_to', 'guests', 'name'] for field in required: if not data.get(field): return jsonify({"error": f"{field} ist erforderlich"}), 400 with get_db() as db: # Prüfe Verfügbarkeit existing = db.execute(''' SELECT COUNT(*) FROM room_bookings WHERE room_id = ? AND date = ? AND status = 'confirmed' AND ( (time_from < ? AND time_to > ?) OR (time_from < ? AND time_to > ?) OR (time_from >= ? AND time_to <= ?) ) ''', ( data['room_id'], data['date'], data['time_to'], data['time_from'], data['time_to'], data['time_from'], data['time_from'], data['time_to'] )).fetchone()[0] if existing > 0: return jsonify({"error": "Raum ist im gewählten Zeitraum bereits belegt"}), 409 # Kapazität prüfen room = db.execute('SELECT capacity FROM rooms WHERE id = ?', (data['room_id'],)).fetchone() if room and data['guests'] > room['capacity']: return jsonify({"error": f"Maximale Kapazität: {room['capacity']} Personen"}), 400 # Buchung erstellen cursor = db.execute(''' INSERT INTO room_bookings (room_id, date, time_from, time_to, guests, name, phone, email, event_type, notes, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( data['room_id'], data['date'], data['time_from'], data['time_to'], data['guests'], data['name'], data.get('phone'), data.get('email'), data.get('event_type'), data.get('notes'), data.get('status', 'confirmed') )) db.commit() return jsonify({ "id": cursor.lastrowid, "message": "Raumbuchung erstellt" }), 201 @app.route('/api/room-bookings/', methods=['GET', 'PUT', 'DELETE']) def room_booking_detail(booking_id): """Einzelne Raumbuchung verwalten""" with get_db() as db: if request.method == 'GET': booking = db.execute(''' SELECT rb.*, r.name as room_name, r.capacity as room_capacity FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE rb.id = ? ''', (booking_id,)).fetchone() if not booking: return jsonify({"error": "Raumbuchung nicht gefunden"}), 404 return jsonify(dict(booking)) elif request.method == 'PUT': data = request.get_json() update_fields = [] params = [] if 'date' in data: update_fields.append('date = ?') params.append(data['date']) if 'time_from' in data: update_fields.append('time_from = ?') params.append(data['time_from']) if 'time_to' in data: update_fields.append('time_to = ?') params.append(data['time_to']) if 'guests' in data: update_fields.append('guests = ?') params.append(data['guests']) if 'name' in data: update_fields.append('name = ?') params.append(data['name']) if 'phone' in data: update_fields.append('phone = ?') params.append(data['phone']) if 'email' in data: update_fields.append('email = ?') params.append(data['email']) if 'event_type' in data: update_fields.append('event_type = ?') params.append(data['event_type']) if 'notes' in data: update_fields.append('notes = ?') params.append(data['notes']) if 'status' in data: update_fields.append('status = ?') params.append(data['status']) if update_fields: params.append(booking_id) db.execute(f''' UPDATE room_bookings SET {', '.join(update_fields)} WHERE id = ? ''', params) db.commit() return jsonify({"message": "Raumbuchung aktualisiert"}) elif request.method == 'DELETE': db.execute('DELETE FROM room_bookings WHERE id = ?', (booking_id,)) db.commit() return jsonify({"message": "Raumbuchung gelöscht"}) @app.route('/api/room-availability', methods=['GET']) def room_availability(): """Verfügbarkeit für alle Räume prüfen""" date = request.args.get('date', datetime.now().date().isoformat()) time_from = request.args.get('time_from', '10:00') time_to = request.args.get('time_to', '12:00') with get_db() as db: rooms = db.execute('SELECT * FROM rooms').fetchall() result = [] for room in rooms: # Prüfe ob Raum belegt ist existing = db.execute(''' SELECT COUNT(*) FROM room_bookings WHERE room_id = ? AND date = ? AND status = 'confirmed' AND ( (time_from < ? AND time_to > ?) OR (time_from < ? AND time_to > ?) OR (time_from >= ? AND time_to <= ?) ) ''', (room['id'], date, time_to, time_from, time_to, time_from, time_from, time_to)).fetchone()[0] # Lade Tische im Raum areas = db.execute('SELECT id FROM areas WHERE room_id = ?', (room['id'],)).fetchall() area_ids = [a['id'] for a in areas] table_count = 0 if area_ids: placeholders = ','.join('?' for _ in area_ids) table_count = db.execute(f''' SELECT COUNT(*) FROM tables WHERE area_id IN ({placeholders}) AND is_active = 1 ''', area_ids).fetchone()[0] result.append({ 'room_id': room['id'], 'room_name': room['name'], 'capacity': room['capacity'], 'color': room['color'], 'available': existing == 0, 'table_count': table_count, 'date': date, 'time_from': time_from, 'time_to': time_to }) return jsonify(result) @app.route('/api/tables/', methods=['GET', 'POST']) def tables_by_area(area_id): """Tische für einen Bereich verwalten""" if request.method == 'GET': with get_db() as db: tables = db.execute( 'SELECT * FROM tables WHERE area_id = ? ORDER BY name', (area_id,) ).fetchall() return jsonify([dict(t) for t in tables]) # POST: Neuen Tisch erstellen data = request.get_json() with get_db() as db: cursor = db.execute(''' INSERT INTO tables (area_id, name, x, y, width, height, seats, shape, is_combinable) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( area_id, data.get('name', 'Neuer Tisch'), data.get('x', 0), data.get('y', 0), data.get('width', 100), data.get('height', 100), data.get('seats', 4), data.get('shape', 'rect'), data.get('is_combinable', 1) )) db.commit() return jsonify({"id": cursor.lastrowid, "message": "Tisch erstellt"}), 201 @app.route('/api/tables/', methods=['PUT', 'DELETE']) def table_detail(table_id): """Einzelnen Tisch verwalten""" with get_db() as db: if request.method == 'PUT': data = request.get_json() update_fields = [] params = [] if 'name' in data: update_fields.append('name = ?') params.append(data['name']) if 'x' in data: update_fields.append('x = ?') params.append(data['x']) if 'y' in data: update_fields.append('y = ?') params.append(data['y']) if 'seats' in data: update_fields.append('seats = ?') params.append(data['seats']) if 'is_active' in data: update_fields.append('is_active = ?') params.append(data['is_active']) if update_fields: params.append(table_id) db.execute(f''' UPDATE tables SET {', '.join(update_fields)} WHERE id = ? ''', params) db.commit() return jsonify({"message": "Tisch aktualisiert"}) elif request.method == 'DELETE': db.execute('UPDATE tables SET is_active = 0 WHERE id = ?', (table_id,)) db.commit() return jsonify({"message": "Tisch deaktiviert"}) # ============================================================================= # ERWEITERTE RESERVIERUNGS-ENDPOINTS # ============================================================================= @app.route('/api/reservations/check-availability', methods=['POST']) def check_reservation_availability(): """Verfügbarkeit für Reservierung prüfen (erweitert)""" data = request.get_json() date = data.get('date') time_from = data.get('time_from') time_to = data.get('time_to', '23:00') guests = data.get('guests', 2) preferred_room_id = data.get('room_id') if not all([date, time_from]): return jsonify({"error": "Datum und Uhrzeit erforderlich"}), 400 with get_db() as db: # Basis-Query für verfügbare Tische base_query = ''' SELECT t.*, a.name as area_name, r.id as room_id, r.name as room_name FROM tables t JOIN areas a ON t.area_id = a.id JOIN rooms r ON a.room_id = r.id WHERE t.is_active = 1 ''' params = [] if preferred_room_id: base_query += ' AND r.id = ?' params.append(preferred_room_id) base_query += ' AND t.seats >= ?' params.append(guests) all_tables = db.execute(base_query, params).fetchall() # Belegte Tische finden occupied_query = ''' SELECT DISTINCT json_each.value as table_id FROM reservations r, json_each(r.table_ids) WHERE r.date = ? AND r.status IN ('confirmed', 'pending') AND NOT (r.time_to <= ? OR r.time_from >= ?) ''' occupied = db.execute(occupied_query, (date, time_from, time_to)).fetchall() occupied_ids = {str(row['table_id']) for row in occupied} # Verfügbare Tische filtern available = [] for t in all_tables: if str(t['id']) not in occupied_ids: table_dict = dict(t) table_dict['is_available'] = True available.append(table_dict) # Nach Raum gruppieren rooms_with_tables = {} for t in available: room_id = t['room_id'] if room_id not in rooms_with_tables: rooms_with_tables[room_id] = { 'room_id': room_id, 'room_name': t['room_name'], 'tables': [] } rooms_with_tables[room_id]['tables'].append(t) return jsonify({ "date": date, "time_from": time_from, "time_to": time_to, "guests": guests, "available_tables": available, "available_count": len(available), "rooms": list(rooms_with_tables.values()) }) @app.route('/api/dashboard/extended', methods=['GET']) def dashboard_extended(): """Erweitertes Dashboard mit Raumbuchungen""" today = datetime.now().date().isoformat() with get_db() as db: # Standard-Dashboard-Daten today_count = db.execute( "SELECT COUNT(*) FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending')", (today,) ).fetchone()[0] # Raumbuchungen heute room_bookings_count = db.execute( "SELECT COUNT(*) FROM room_bookings WHERE date = ? AND status = 'confirmed'", (today,) ).fetchone()[0] # Unbearbeitete E-Mails pending_emails = db.execute( "SELECT COUNT(*) FROM emails WHERE status = 'needs_review'" ).fetchone()[0] # Freie Tische total_tables = db.execute( "SELECT COUNT(*) FROM tables WHERE is_active = 1" ).fetchone()[0] # Heutige Gäste guests_today = db.execute(''' SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date = ? AND status IN ('confirmed', 'pending') ''', (today,)).fetchone()[0] # Raumbuchungs-Gäste room_guests = db.execute(''' SELECT COALESCE(SUM(guests), 0) FROM room_bookings WHERE date = ? AND status = 'confirmed' ''', (today,)).fetchone()[0] # Aktuelle Reservierungen today_reservations = db.execute(''' SELECT r.*, g.name as guest_name, g.phone FROM reservations r LEFT JOIN guests g ON r.guest_id = g.id WHERE r.date = ? AND r.status IN ('confirmed', 'pending') ORDER BY r.time_from ''', (today,)).fetchall() # Aktuelle Raumbuchungen today_room_bookings = db.execute(''' SELECT rb.*, r.name as room_name FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE rb.date = ? AND rb.status = 'confirmed' ORDER BY rb.time_from ''', (today,)).fetchall() # Räume mit Auslastung rooms = db.execute('SELECT * FROM rooms').fetchall() rooms_data = [] for room in rooms: # Tische im Raum zählen area_ids = db.execute('SELECT id FROM areas WHERE room_id = ?', (room['id'],)).fetchall() table_count = 0 if area_ids: ids = [a['id'] for a in area_ids] placeholders = ','.join('?' for _ in ids) table_count = db.execute(f''' SELECT COUNT(*) FROM tables WHERE area_id IN ({placeholders}) AND is_active = 1 ''', ids).fetchone()[0] # Aktuelle Buchungen bookings = db.execute(''' SELECT COUNT(*) FROM room_bookings WHERE room_id = ? AND date = ? AND status = 'confirmed' ''', (room['id'], today)).fetchone()[0] rooms_data.append({ 'id': room['id'], 'name': room['name'], 'capacity': room['capacity'], 'color': room['color'], 'table_count': table_count, 'bookings_today': bookings }) return jsonify({ "today": today, "reservations_count": today_count, "room_bookings_count": room_bookings_count, "pending_emails": pending_emails, "total_tables": total_tables, "guests_today": guests_today, "room_guests_today": room_guests, "total_guests_today": guests_today + room_guests, "reservations": [dict(r) for r in today_reservations], "room_bookings": [dict(r) for r in today_room_bookings], "rooms": rooms_data })