Files
reservierungssystem/app/main.py
T

1129 lines
40 KiB
Python

#!/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)
# 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/<int:guest_id>', 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/<int:res_id>', 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/<int:booking_id>', 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/<int:area_id>', 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/<int:table_id>', 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
})