v2.0: 3-Raum-System - Hauptraum, Saal A, Saal B mit 18 Tischen, Raum-Buchungen, API-Doku
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Datenbank-Modelle für Reservierungssystem"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import contextmanager
|
||||
|
||||
DB_PATH = "/data/reservations.db"
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
try:
|
||||
yield conn
|
||||
conn.commit()
|
||||
except:
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def init_db():
|
||||
"""Datenbank initialisieren"""
|
||||
with get_db() as db:
|
||||
db.executescript('''
|
||||
-- Gäste-Adressbuch
|
||||
CREATE TABLE IF NOT EXISTS guests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
email TEXT UNIQUE,
|
||||
preferred_table_id INTEGER,
|
||||
notes TEXT,
|
||||
visit_count INTEGER DEFAULT 0,
|
||||
last_visit DATE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Räume
|
||||
CREATE TABLE IF NOT EXISTS rooms (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
capacity INTEGER,
|
||||
color TEXT DEFAULT '#3498db'
|
||||
);
|
||||
|
||||
-- Bereiche innerhalb von Räumen
|
||||
CREATE TABLE IF NOT EXISTS areas (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
room_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
available_from TIME DEFAULT '10:00',
|
||||
available_to TIME DEFAULT '23:00',
|
||||
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tische
|
||||
CREATE TABLE IF NOT EXISTS tables (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
area_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
x INTEGER DEFAULT 0,
|
||||
y INTEGER DEFAULT 0,
|
||||
width INTEGER DEFAULT 100,
|
||||
height INTEGER DEFAULT 100,
|
||||
shape TEXT DEFAULT 'rect' CHECK(shape IN ('rect', 'circle', 'oval')),
|
||||
seats INTEGER DEFAULT 4,
|
||||
is_combinable BOOLEAN DEFAULT 1,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tisch-Kombinationen (Zusammenlegungen)
|
||||
CREATE TABLE IF NOT EXISTS table_combinations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
parent_table_id INTEGER NOT NULL,
|
||||
child_table_ids TEXT NOT NULL, -- JSON-Array
|
||||
total_seats INTEGER,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
FOREIGN KEY (parent_table_id) REFERENCES tables(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Reservierungen
|
||||
CREATE TABLE IF NOT EXISTS reservations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
booking_number TEXT UNIQUE NOT NULL, -- RES-20250512-001
|
||||
guest_id INTEGER,
|
||||
table_ids TEXT NOT NULL, -- JSON-Array
|
||||
date DATE NOT NULL,
|
||||
time_from TIME NOT NULL,
|
||||
time_to TIME,
|
||||
guests INTEGER NOT NULL,
|
||||
occasion TEXT,
|
||||
notes TEXT,
|
||||
source TEXT DEFAULT 'email' CHECK(source IN ('email', 'phone', 'web', 'walk-in')),
|
||||
phone_caller_name TEXT, -- Für Telefon-Buchungen
|
||||
status TEXT DEFAULT 'confirmed' CHECK(status IN ('confirmed', 'pending', 'cancelled', 'completed')),
|
||||
email_thread_id TEXT,
|
||||
created_by TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (guest_id) REFERENCES guests(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Änderungshistorie
|
||||
CREATE TABLE IF NOT EXISTS reservation_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
reservation_id INTEGER NOT NULL,
|
||||
booking_number TEXT NOT NULL,
|
||||
changed_field TEXT NOT NULL,
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
changed_by TEXT,
|
||||
changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
reason TEXT,
|
||||
FOREIGN KEY (reservation_id) REFERENCES reservations(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- E-Mails
|
||||
CREATE TABLE IF NOT EXISTS emails (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message_id TEXT UNIQUE NOT NULL,
|
||||
thread_id TEXT,
|
||||
subject TEXT,
|
||||
body TEXT,
|
||||
sender TEXT,
|
||||
sender_email TEXT,
|
||||
parsed_json TEXT, -- JSON mit extrahierten Daten
|
||||
confidence REAL,
|
||||
action_type TEXT CHECK(action_type IN ('new', 'modification', 'cancellation', 'unknown')),
|
||||
status TEXT DEFAULT 'new' CHECK(status IN ('new', 'auto_processed', 'needs_review', 'confirmed', 'failed')),
|
||||
linked_reservation_id INTEGER,
|
||||
booking_number_found TEXT, -- Extrahierte Buchungsnummer
|
||||
auto_reply_sent BOOLEAN DEFAULT 0,
|
||||
auto_reply_status TEXT,
|
||||
received_at DATETIME,
|
||||
processed_at DATETIME,
|
||||
FOREIGN KEY (linked_reservation_id) REFERENCES reservations(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Indizes
|
||||
CREATE INDEX IF NOT EXISTS idx_reservations_date ON reservations(date);
|
||||
CREATE INDEX IF NOT EXISTS idx_reservations_booking ON reservations(booking_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_reservations_guest ON reservations(guest_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_reservations_status ON reservations(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_emails_thread ON emails(thread_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_emails_status ON emails(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_emails_booking ON emails(booking_number_found);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_booking ON reservation_history(booking_number);
|
||||
|
||||
-- Trigger für updated_at
|
||||
CREATE TRIGGER IF NOT EXISTS update_reservations_timestamp
|
||||
AFTER UPDATE ON reservations
|
||||
BEGIN
|
||||
UPDATE reservations SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
''')
|
||||
|
||||
def generate_booking_number(date=None):
|
||||
"""Generiere Buchungsnummer RES-YYYY-MM-DD-XXX"""
|
||||
if date is None:
|
||||
date = datetime.now()
|
||||
|
||||
date_str = date.strftime('%Y-%m-%d')
|
||||
|
||||
with get_db() as db:
|
||||
# Zähle bestehende Reservierungen an diesem Tag
|
||||
count = db.execute(
|
||||
"SELECT COUNT(*) FROM reservations WHERE date = ?",
|
||||
(date_str,)
|
||||
).fetchone()[0]
|
||||
|
||||
return f"RES-{date_str}-{count + 1:03d}"
|
||||
|
||||
def log_change(db, reservation_id, booking_number, field, old_val, new_val, reason=None):
|
||||
"""Änderung in Historie speichern"""
|
||||
db.execute('''
|
||||
INSERT INTO reservation_history
|
||||
(reservation_id, booking_number, changed_field, old_value, new_value, reason)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (reservation_id, booking_number, field, str(old_val), str(new_val), reason))
|
||||
Reference in New Issue
Block a user