v2.0: 3-Raum-System - Hauptraum, Saal A, Saal B mit 18 Tischen, Raum-Buchungen, API-Doku

This commit is contained in:
Peter
2026-05-16 12:15:46 +00:00
commit 1ae070f82f
35 changed files with 10640 additions and 0 deletions
+616
View File
@@ -0,0 +1,616 @@
package db
import (
"database/sql"
"fmt"
"reservation-system/backend/internal/models"
"time"
_ "github.com/mattn/go-sqlite3"
)
type DB struct {
conn *sql.DB
}
func New(dbPath string) (*DB, error) {
conn, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}
if err := conn.Ping(); err != nil {
return nil, err
}
db := &DB{conn: conn}
if err := db.Migrate(); err != nil {
return nil, err
}
return db, nil
}
func (db *DB) Migrate() error {
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
capacity INTEGER NOT NULL DEFAULT 0,
color TEXT DEFAULT '#3b82f6',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tables (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
x INTEGER DEFAULT 0,
y INTEGER DEFAULT 0,
width INTEGER DEFAULT 100,
height INTEGER DEFAULT 100,
max_guests INTEGER NOT NULL,
shape TEXT DEFAULT 'rect',
status TEXT DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS reservations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_id INTEGER NOT NULL,
date TEXT NOT NULL,
time_from TEXT NOT NULL,
time_to TEXT NOT NULL,
guests INTEGER NOT NULL,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
source TEXT DEFAULT 'manual',
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (table_id) REFERENCES tables(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS room_bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
date TEXT NOT NULL,
time_from TEXT NOT NULL,
time_to TEXT NOT NULL,
guests INTEGER NOT NULL,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
event_type TEXT,
notes TEXT,
status TEXT DEFAULT 'confirmed',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS table_merges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_table_id INTEGER NOT NULL,
child_table_id INTEGER NOT NULL,
merged_name TEXT,
active BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_table_id) REFERENCES tables(id) ON DELETE CASCADE,
FOREIGN KEY (child_table_id) REFERENCES tables(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS email_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host TEXT NOT NULL,
port INTEGER DEFAULT 993,
user TEXT NOT NULL,
password TEXT NOT NULL,
ssl BOOLEAN DEFAULT 1,
folder TEXT DEFAULT 'INBOX'
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_admin BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_reservations_date ON reservations(date);
CREATE INDEX IF NOT EXISTS idx_reservations_table ON reservations(table_id);
CREATE INDEX IF NOT EXISTS idx_tables_room ON tables(room_id);
CREATE INDEX IF NOT EXISTS idx_reservations_time ON reservations(time_from, time_to);
CREATE INDEX IF NOT EXISTS idx_room_bookings_date ON room_bookings(date);
CREATE INDEX IF NOT EXISTS idx_room_bookings_room ON room_bookings(room_id);
CREATE INDEX IF NOT EXISTS idx_room_bookings_time ON room_bookings(time_from, time_to);
`
_, err := db.conn.Exec(schema)
return err
}
func (db *DB) Close() error {
return db.conn.Close()
}
// Room Methods
func (db *DB) CreateRoom(room *models.Room) error {
result, err := db.conn.Exec(
"INSERT INTO rooms (name, capacity, color) VALUES (?, ?, ?)",
room.Name, room.Capacity, room.Color,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
room.ID = int(id)
room.CreatedAt = time.Now()
return nil
}
func (db *DB) GetRooms() ([]models.Room, error) {
rows, err := db.conn.Query("SELECT id, name, capacity, color, created_at FROM rooms ORDER BY name")
if err != nil {
return nil, err
}
defer rows.Close()
var rooms []models.Room
for rows.Next() {
var r models.Room
if err := rows.Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt); err != nil {
return nil, err
}
rooms = append(rooms, r)
}
return rooms, nil
}
func (db *DB) GetRoom(id int) (*models.Room, error) {
var r models.Room
err := db.conn.QueryRow(
"SELECT id, name, capacity, color, created_at FROM rooms WHERE id = ?",
id,
).Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return &r, err
}
func (db *DB) UpdateRoom(room *models.Room) error {
_, err := db.conn.Exec(
"UPDATE rooms SET name = ?, capacity = ?, color = ? WHERE id = ?",
room.Name, room.Capacity, room.Color, room.ID,
)
return err
}
func (db *DB) DeleteRoom(id int) error {
_, err := db.conn.Exec("DELETE FROM rooms WHERE id = ?", id)
return err
}
// Table Methods
func (db *DB) CreateTable(table *models.Table) error {
result, err := db.conn.Exec(
"INSERT INTO tables (room_id, x, y, width, height, max_guests, shape, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.Status,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
table.ID = int(id)
table.CreatedAt = time.Now()
return nil
}
func (db *DB) GetTablesByRoom(roomID int) ([]models.Table, error) {
rows, err := db.conn.Query(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.room_id = ? AND t.status = 'active'
ORDER BY t.id`, roomID)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []models.Table
for rows.Next() {
var t models.Table
if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil {
return nil, err
}
tables = append(tables, t)
}
return tables, nil
}
func (db *DB) GetAllTables() ([]models.Table, error) {
rows, err := db.conn.Query(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.status = 'active'
ORDER BY t.room_id, t.id`)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []models.Table
for rows.Next() {
var t models.Table
if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil {
return nil, err
}
tables = append(tables, t)
}
return tables, nil
}
func (db *DB) GetTable(id int) (*models.Table, error) {
var t models.Table
err := db.conn.QueryRow(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.id = ?`, id).Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName)
if err == sql.ErrNoRows {
return nil, nil
}
return &t, err
}
func (db *DB) UpdateTable(table *models.Table) error {
_, err := db.conn.Exec(
"UPDATE tables SET room_id = ?, x = ?, y = ?, width = ?, height = ?, max_guests = ?, shape = ? WHERE id = ?",
table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.ID,
)
return err
}
func (db *DB) DeleteTable(id int) error {
_, err := db.conn.Exec("UPDATE tables SET status = 'deleted' WHERE id = ?", id)
return err
}
// Reservation Methods
func (db *DB) CreateReservation(r *models.Reservation) error {
result, err := db.conn.Exec(
"INSERT INTO reservations (table_id, date, time_from, time_to, guests, name, phone, email, source, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Source, r.Notes,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
r.ID = int(id)
r.CreatedAt = time.Now()
return nil
}
func (db *DB) GetReservationsByDate(date string) ([]models.Reservation, error) {
rows, err := db.conn.Query(`
SELECT r.id, r.table_id, r.date, r.time_from, r.time_to, r.guests, r.name, r.phone, r.email, r.source, r.notes, r.created_at, r.table_id as table_name
FROM reservations r
WHERE r.date = ?
ORDER BY r.time_from`, date)
if err != nil {
return nil, err
}
defer rows.Close()
var reservations []models.Reservation
for rows.Next() {
var res models.Reservation
if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt, &res.TableName); err != nil {
return nil, err
}
reservations = append(reservations, res)
}
return reservations, nil
}
func (db *DB) GetReservationsByTable(tableID int, date string) ([]models.Reservation, error) {
rows, err := db.conn.Query(`
SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at
FROM reservations
WHERE table_id = ? AND date = ?
ORDER BY time_from`, tableID, date)
if err != nil {
return nil, err
}
defer rows.Close()
var reservations []models.Reservation
for rows.Next() {
var res models.Reservation
if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt); err != nil {
return nil, err
}
reservations = append(reservations, res)
}
return reservations, nil
}
func (db *DB) GetReservation(id int) (*models.Reservation, error) {
var r models.Reservation
err := db.conn.QueryRow(`
SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at
FROM reservations WHERE id = ?`, id).Scan(&r.ID, &r.TableID, &r.Date, &r.TimeFrom, &r.TimeTo, &r.Guests, &r.Name, &r.Phone, &r.Email, &r.Source, &r.Notes, &r.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return &r, err
}
func (db *DB) UpdateReservation(r *models.Reservation) error {
_, err := db.conn.Exec(
"UPDATE reservations SET table_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, notes = ? WHERE id = ?",
r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Notes, r.ID,
)
return err
}
func (db *DB) DeleteReservation(id int) error {
_, err := db.conn.Exec("DELETE FROM reservations WHERE id = ?", id)
return err
}
// Room Booking Methods (NEW)
func (db *DB) CreateRoomBooking(rb *models.RoomBooking) error {
result, err := db.conn.Exec(
"INSERT INTO room_bookings (room_id, date, time_from, time_to, guests, name, phone, email, event_type, notes, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
rb.ID = int(id)
rb.CreatedAt = time.Now()
return nil
}
func (db *DB) GetRoomBookingsByRoom(roomID int) ([]models.RoomBooking, error) {
rows, err := db.conn.Query(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.room_id = ?
ORDER BY rb.date DESC, rb.time_from`, roomID)
if err != nil {
return nil, err
}
defer rows.Close()
var bookings []models.RoomBooking
for rows.Next() {
var b models.RoomBooking
if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil {
return nil, err
}
bookings = append(bookings, b)
}
return bookings, nil
}
func (db *DB) GetRoomBookingsByDate(date string) ([]models.RoomBooking, error) {
rows, err := db.conn.Query(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.date = ?
ORDER BY rb.time_from`, date)
if err != nil {
return nil, err
}
defer rows.Close()
var bookings []models.RoomBooking
for rows.Next() {
var b models.RoomBooking
if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil {
return nil, err
}
bookings = append(bookings, b)
}
return bookings, nil
}
func (db *DB) GetRoomBooking(id int) (*models.RoomBooking, error) {
var rb models.RoomBooking
err := db.conn.QueryRow(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.id = ?`, id).Scan(&rb.ID, &rb.RoomID, &rb.Date, &rb.TimeFrom, &rb.TimeTo, &rb.Guests, &rb.Name, &rb.Phone, &rb.Email, &rb.EventType, &rb.Notes, &rb.Status, &rb.CreatedAt, &rb.RoomName)
if err == sql.ErrNoRows {
return nil, nil
}
return &rb, err
}
func (db *DB) UpdateRoomBooking(rb *models.RoomBooking) error {
_, err := db.conn.Exec(
"UPDATE room_bookings SET room_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, event_type = ?, notes = ?, status = ? WHERE id = ?",
rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status, rb.ID,
)
return err
}
func (db *DB) DeleteRoomBooking(id int) error {
_, err := db.conn.Exec("DELETE FROM room_bookings WHERE id = ?", id)
return err
}
// Availability Checks
func (db *DB) CheckAvailability(tableID int, date, timeFrom, timeTo string) (bool, error) {
var count int
err := db.conn.QueryRow(`
SELECT COUNT(*) FROM reservations
WHERE table_id = ? AND date = ?
AND (
(time_from < ? AND time_to > ?) OR
(time_from < ? AND time_to > ?) OR
(time_from >= ? AND time_to <= ?)
)`, tableID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count)
if err != nil {
return false, err
}
return count == 0, nil
}
func (db *DB) CheckRoomAvailability(roomID int, date, timeFrom, timeTo string) (bool, error) {
var count int
err := db.conn.QueryRow(`
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 <= ?)
)`, roomID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count)
if err != nil {
return false, err
}
return count == 0, nil
}
func (db *DB) GetTableMerges(parentTableID int) ([]models.TableMerge, error) {
rows, err := db.conn.Query(`
SELECT id, parent_table_id, child_table_id, merged_name, active, created_at
FROM table_merges
WHERE parent_table_id = ? AND active = 1`, parentTableID)
if err != nil {
return nil, err
}
defer rows.Close()
var merges []models.TableMerge
for rows.Next() {
var m models.TableMerge
if err := rows.Scan(&m.ID, &m.ParentTableID, &m.ChildTableID, &m.MergedName, &m.Active, &m.CreatedAt); err != nil {
return nil, err
}
merges = append(merges, m)
}
return merges, nil
}
func (db *DB) UnmergeTables(mergeID int) error {
var childID int
err := db.conn.QueryRow("SELECT child_table_id FROM table_merges WHERE id = ?", mergeID).Scan(&childID)
if err != nil {
return err
}
_, err = db.conn.Exec("UPDATE table_merges SET active = 0 WHERE id = ?", mergeID)
if err != nil {
return err
}
_, err = db.conn.Exec("UPDATE tables SET status = 'active' WHERE id = ?", childID)
return err
}
func (db *DB) GetEmailConfig() (*models.EmailConfig, error) {
var cfg models.EmailConfig
err := db.conn.QueryRow("SELECT id, host, port, user, password, ssl, folder FROM email_config LIMIT 1").Scan(
&cfg.ID, &cfg.Host, &cfg.Port, &cfg.Username, &cfg.Password, &cfg.SSL, &cfg.Folder,
)
if err == sql.ErrNoRows {
return nil, nil
}
return &cfg, err
}
func (db *DB) SaveEmailConfig(cfg *models.EmailConfig) error {
if cfg.ID > 0 {
_, err := db.conn.Exec(
"UPDATE email_config SET host = ?, port = ?, user = ?, password = ?, ssl = ?, folder = ? WHERE id = ?",
cfg.Host, cfg.Port, &cfg.Username, cfg.Password, cfg.SSL, cfg.Folder, cfg.ID,
)
return err
}
result, err := db.conn.Exec(
"INSERT INTO email_config (host, port, user, password, ssl, folder) VALUES (?, ?, ?, ?, ?, ?)",
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.SSL, cfg.Folder,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
cfg.ID = int(id)
return nil
}
func (db *DB) GetUserByUsername(username string) (*models.User, error) {
var u models.User
err := db.conn.QueryRow("SELECT id, username, password, is_admin FROM users WHERE username = ?", username).Scan(
&u.ID, &u.Username, &u.Password, &u.IsAdmin,
)
if err == sql.ErrNoRows {
return nil, nil
}
return &u, err
}
func (db *DB) CreateUser(user *models.User) error {
result, err := db.conn.Exec(
"INSERT INTO users (username, password, is_admin) VALUES (?, ?, ?)",
user.Username, user.Password, user.IsAdmin,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
user.ID = int(id)
return nil
}
func (db *DB) GetMergedTablesFor(parentTableID int) ([]int, error) {
rows, err := db.conn.Query("SELECT child_table_id FROM table_merges WHERE parent_table_id = ? AND active = 1", parentTableID)
if err != nil {
return nil, err
}
defer rows.Close()
var ids []int
for rows.Next() {
var id int
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, nil
}
func (db *DB) GetTotalGuestsForDate(date string) (int, error) {
var total int
err := db.conn.QueryRow("SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date = ?", date).Scan(&total)
return total, err
}
func (db *DB) CreateTableMerge(merge *models.TableMerge) error {
result, err := db.conn.Exec(
"INSERT INTO table_merges (parent_table_id, child_table_id, merged_name, active) VALUES (?, ?, ?, ?)",
merge.ParentTableID, merge.ChildTableID, merge.MergedName, merge.Active,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
merge.ID = int(id)
merge.CreatedAt = time.Now()
// Mark child table as merged
_, err = db.conn.Exec("UPDATE tables SET status = 'merged' WHERE id = ?", merge.ChildTableID)
return err
}
+616
View File
@@ -0,0 +1,616 @@
package db
import (
"database/sql"
"fmt"
"reservation-system/backend/internal/models"
"time"
_ "github.com/mattn/go-sqlite3"
)
type DB struct {
conn *sql.DB
}
func New(dbPath string) (*DB, error) {
conn, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}
if err := conn.Ping(); err != nil {
return nil, err
}
db := &DB{conn: conn}
if err := db.Migrate(); err != nil {
return nil, err
}
return db, nil
}
func (db *DB) Migrate() error {
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
capacity INTEGER NOT NULL DEFAULT 0,
color TEXT DEFAULT '#3b82f6',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tables (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
x INTEGER DEFAULT 0,
y INTEGER DEFAULT 0,
width INTEGER DEFAULT 100,
height INTEGER DEFAULT 100,
max_guests INTEGER NOT NULL,
shape TEXT DEFAULT 'rect',
status TEXT DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS reservations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_id INTEGER NOT NULL,
date TEXT NOT NULL,
time_from TEXT NOT NULL,
time_to TEXT NOT NULL,
guests INTEGER NOT NULL,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
source TEXT DEFAULT 'manual',
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (table_id) REFERENCES tables(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS room_bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
date TEXT NOT NULL,
time_from TEXT NOT NULL,
time_to TEXT NOT NULL,
guests INTEGER NOT NULL,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
event_type TEXT,
notes TEXT,
status TEXT DEFAULT 'confirmed',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS table_merges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_table_id INTEGER NOT NULL,
child_table_id INTEGER NOT NULL,
merged_name TEXT,
active BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_table_id) REFERENCES tables(id) ON DELETE CASCADE,
FOREIGN KEY (child_table_id) REFERENCES tables(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS email_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host TEXT NOT NULL,
port INTEGER DEFAULT 993,
user TEXT NOT NULL,
password TEXT NOT NULL,
ssl BOOLEAN DEFAULT 1,
folder TEXT DEFAULT 'INBOX'
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_admin BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_reservations_date ON reservations(date);
CREATE INDEX IF NOT EXISTS idx_reservations_table ON reservations(table_id);
CREATE INDEX IF NOT EXISTS idx_tables_room ON tables(room_id);
CREATE INDEX IF NOT EXISTS idx_reservations_time ON reservations(time_from, time_to);
CREATE INDEX IF NOT EXISTS idx_room_bookings_date ON room_bookings(date);
CREATE INDEX IF NOT EXISTS idx_room_bookings_room ON room_bookings(room_id);
CREATE INDEX IF NOT EXISTS idx_room_bookings_time ON room_bookings(time_from, time_to);
`
_, err := db.conn.Exec(schema)
return err
}
func (db *DB) Close() error {
return db.conn.Close()
}
// Room Methods
func (db *DB) CreateRoom(room *models.Room) error {
result, err := db.conn.Exec(
"INSERT INTO rooms (name, capacity, color) VALUES (?, ?, ?)",
room.Name, room.Capacity, room.Color,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
room.ID = int(id)
room.CreatedAt = time.Now()
return nil
}
func (db *DB) GetRooms() ([]models.Room, error) {
rows, err := db.conn.Query("SELECT id, name, capacity, color, created_at FROM rooms ORDER BY name")
if err != nil {
return nil, err
}
defer rows.Close()
var rooms []models.Room
for rows.Next() {
var r models.Room
if err := rows.Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt); err != nil {
return nil, err
}
rooms = append(rooms, r)
}
return rooms, nil
}
func (db *DB) GetRoom(id int) (*models.Room, error) {
var r models.Room
err := db.conn.QueryRow(
"SELECT id, name, capacity, color, created_at FROM rooms WHERE id = ?",
id,
).Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return &r, err
}
func (db *DB) UpdateRoom(room *models.Room) error {
_, err := db.conn.Exec(
"UPDATE rooms SET name = ?, capacity = ?, color = ? WHERE id = ?",
room.Name, room.Capacity, room.Color, room.ID,
)
return err
}
func (db *DB) DeleteRoom(id int) error {
_, err := db.conn.Exec("DELETE FROM rooms WHERE id = ?", id)
return err
}
// Table Methods
func (db *DB) CreateTable(table *models.Table) error {
result, err := db.conn.Exec(
"INSERT INTO tables (room_id, x, y, width, height, max_guests, shape, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.Status,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
table.ID = int(id)
table.CreatedAt = time.Now()
return nil
}
func (db *DB) GetTablesByRoom(roomID int) ([]models.Table, error) {
rows, err := db.conn.Query(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.room_id = ? AND t.status = 'active'
ORDER BY t.id`, roomID)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []models.Table
for rows.Next() {
var t models.Table
if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil {
return nil, err
}
tables = append(tables, t)
}
return tables, nil
}
func (db *DB) GetAllTables() ([]models.Table, error) {
rows, err := db.conn.Query(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.status = 'active'
ORDER BY t.room_id, t.id`)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []models.Table
for rows.Next() {
var t models.Table
if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil {
return nil, err
}
tables = append(tables, t)
}
return tables, nil
}
func (db *DB) GetTable(id int) (*models.Table, error) {
var t models.Table
err := db.conn.QueryRow(`
SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name
FROM tables t
JOIN rooms r ON t.room_id = r.id
WHERE t.id = ?`, id).Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName)
if err == sql.ErrNoRows {
return nil, nil
}
return &t, err
}
func (db *DB) UpdateTable(table *models.Table) error {
_, err := db.conn.Exec(
"UPDATE tables SET room_id = ?, x = ?, y = ?, width = ?, height = ?, max_guests = ?, shape = ? WHERE id = ?",
table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.ID,
)
return err
}
func (db *DB) DeleteTable(id int) error {
_, err := db.conn.Exec("UPDATE tables SET status = 'deleted' WHERE id = ?", id)
return err
}
// Reservation Methods
func (db *DB) CreateReservation(r *models.Reservation) error {
result, err := db.conn.Exec(
"INSERT INTO reservations (table_id, date, time_from, time_to, guests, name, phone, email, source, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Source, r.Notes,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
r.ID = int(id)
r.CreatedAt = time.Now()
return nil
}
func (db *DB) GetReservationsByDate(date string) ([]models.Reservation, error) {
rows, err := db.conn.Query(`
SELECT r.id, r.table_id, r.date, r.time_from, r.time_to, r.guests, r.name, r.phone, r.email, r.source, r.notes, r.created_at, r.table_id as table_name
FROM reservations r
WHERE r.date = ?
ORDER BY r.time_from`, date)
if err != nil {
return nil, err
}
defer rows.Close()
var reservations []models.Reservation
for rows.Next() {
var res models.Reservation
if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt, &res.TableName); err != nil {
return nil, err
}
reservations = append(reservations, res)
}
return reservations, nil
}
func (db *DB) GetReservationsByTable(tableID int, date string) ([]models.Reservation, error) {
rows, err := db.conn.Query(`
SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at
FROM reservations
WHERE table_id = ? AND date = ?
ORDER BY time_from`, tableID, date)
if err != nil {
return nil, err
}
defer rows.Close()
var reservations []models.Reservation
for rows.Next() {
var res models.Reservation
if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt); err != nil {
return nil, err
}
reservations = append(reservations, res)
}
return reservations, nil
}
func (db *DB) GetReservation(id int) (*models.Reservation, error) {
var r models.Reservation
err := db.conn.QueryRow(`
SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at
FROM reservations WHERE id = ?`, id).Scan(&r.ID, &r.TableID, &r.Date, &r.TimeFrom, &r.TimeTo, &r.Guests, &r.Name, &r.Phone, &r.Email, &r.Source, &r.Notes, &r.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return &r, err
}
func (db *DB) UpdateReservation(r *models.Reservation) error {
_, err := db.conn.Exec(
"UPDATE reservations SET table_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, notes = ? WHERE id = ?",
r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Notes, r.ID,
)
return err
}
func (db *DB) DeleteReservation(id int) error {
_, err := db.conn.Exec("DELETE FROM reservations WHERE id = ?", id)
return err
}
// Room Booking Methods (NEW)
func (db *DB) CreateRoomBooking(rb *models.RoomBooking) error {
result, err := db.conn.Exec(
"INSERT INTO room_bookings (room_id, date, time_from, time_to, guests, name, phone, email, event_type, notes, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
rb.ID = int(id)
rb.CreatedAt = time.Now()
return nil
}
func (db *DB) GetRoomBookingsByRoom(roomID int) ([]models.RoomBooking, error) {
rows, err := db.conn.Query(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.room_id = ?
ORDER BY rb.date DESC, rb.time_from`, roomID)
if err != nil {
return nil, err
}
defer rows.Close()
var bookings []models.RoomBooking
for rows.Next() {
var b models.RoomBooking
if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil {
return nil, err
}
bookings = append(bookings, b)
}
return bookings, nil
}
func (db *DB) GetRoomBookingsByDate(date string) ([]models.RoomBooking, error) {
rows, err := db.conn.Query(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.date = ?
ORDER BY rb.time_from`, date)
if err != nil {
return nil, err
}
defer rows.Close()
var bookings []models.RoomBooking
for rows.Next() {
var b models.RoomBooking
if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil {
return nil, err
}
bookings = append(bookings, b)
}
return bookings, nil
}
func (db *DB) GetRoomBooking(id int) (*models.RoomBooking, error) {
var rb models.RoomBooking
err := db.conn.QueryRow(`
SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name
FROM room_bookings rb
JOIN rooms r ON rb.room_id = r.id
WHERE rb.id = ?`, id).Scan(&rb.ID, &rb.RoomID, &rb.Date, &rb.TimeFrom, &rb.TimeTo, &rb.Guests, &rb.Name, &rb.Phone, &rb.Email, &rb.EventType, &rb.Notes, &rb.Status, &rb.CreatedAt, &rb.RoomName)
if err == sql.ErrNoRows {
return nil, nil
}
return &rb, err
}
func (db *DB) UpdateRoomBooking(rb *models.RoomBooking) error {
_, err := db.conn.Exec(
"UPDATE room_bookings SET room_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, event_type = ?, notes = ?, status = ? WHERE id = ?",
rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status, rb.ID,
)
return err
}
func (db *DB) DeleteRoomBooking(id int) error {
_, err := db.conn.Exec("DELETE FROM room_bookings WHERE id = ?", id)
return err
}
// Availability Checks
func (db *DB) CheckAvailability(tableID int, date, timeFrom, timeTo string) (bool, error) {
var count int
err := db.conn.QueryRow(`
SELECT COUNT(*) FROM reservations
WHERE table_id = ? AND date = ?
AND (
(time_from < ? AND time_to > ?) OR
(time_from < ? AND time_to > ?) OR
(time_from >= ? AND time_to <= ?)
)`, tableID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count)
if err != nil {
return false, err
}
return count == 0, nil
}
func (db *DB) CheckRoomAvailability(roomID int, date, timeFrom, timeTo string) (bool, error) {
var count int
err := db.conn.QueryRow(`
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 <= ?)
)`, roomID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count)
if err != nil {
return false, err
}
return count == 0, nil
}
func (db *DB) GetTableMerges(parentTableID int) ([]models.TableMerge, error) {
rows, err := db.conn.Query(`
SELECT id, parent_table_id, child_table_id, merged_name, active, created_at
FROM table_merges
WHERE parent_table_id = ? AND active = 1`, parentTableID)
if err != nil {
return nil, err
}
defer rows.Close()
var merges []models.TableMerge
for rows.Next() {
var m models.TableMerge
if err := rows.Scan(&m.ID, &m.ParentTableID, &m.ChildTableID, &m.MergedName, &m.Active, &m.CreatedAt); err != nil {
return nil, err
}
merges = append(merges, m)
}
return merges, nil
}
func (db *DB) UnmergeTables(mergeID int) error {
var childID int
err := db.conn.QueryRow("SELECT child_table_id FROM table_merges WHERE id = ?", mergeID).Scan(&childID)
if err != nil {
return err
}
_, err = db.conn.Exec("UPDATE table_merges SET active = 0 WHERE id = ?", mergeID)
if err != nil {
return err
}
_, err = db.conn.Exec("UPDATE tables SET status = 'active' WHERE id = ?", childID)
return err
}
func (db *DB) GetEmailConfig() (*models.EmailConfig, error) {
var cfg models.EmailConfig
err := db.conn.QueryRow("SELECT id, host, port, user, password, ssl, folder FROM email_config LIMIT 1").Scan(
&cfg.ID, &cfg.Host, &cfg.Port, &cfg.Username, &cfg.Password, &cfg.SSL, &cfg.Folder,
)
if err == sql.ErrNoRows {
return nil, nil
}
return &cfg, err
}
func (db *DB) SaveEmailConfig(cfg *models.EmailConfig) error {
if cfg.ID > 0 {
_, err := db.conn.Exec(
"UPDATE email_config SET host = ?, port = ?, user = ?, password = ?, ssl = ?, folder = ? WHERE id = ?",
cfg.Host, cfg.Port, &cfg.Username, cfg.Password, cfg.SSL, cfg.Folder, cfg.ID,
)
return err
}
result, err := db.conn.Exec(
"INSERT INTO email_config (host, port, user, password, ssl, folder) VALUES (?, ?, ?, ?, ?, ?)",
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.SSL, cfg.Folder,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
cfg.ID = int(id)
return nil
}
func (db *DB) GetUserByUsername(username string) (*models.User, error) {
var u models.User
err := db.conn.QueryRow("SELECT id, username, password, is_admin FROM users WHERE username = ?", username).Scan(
&u.ID, &u.Username, &u.Password, &u.IsAdmin,
)
if err == sql.ErrNoRows {
return nil, nil
}
return &u, err
}
func (db *DB) CreateUser(user *models.User) error {
result, err := db.conn.Exec(
"INSERT INTO users (username, password, is_admin) VALUES (?, ?, ?)",
user.Username, user.Password, user.IsAdmin,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
user.ID = int(id)
return nil
}
func (db *DB) GetMergedTablesFor(parentTableID int) ([]int, error) {
rows, err := db.conn.Query("SELECT child_table_id FROM table_merges WHERE parent_table_id = ? AND active = 1", parentTableID)
if err != nil {
return nil, err
}
defer rows.Close()
var ids []int
for rows.Next() {
var id int
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, nil
}
func (db *DB) GetTotalGuestsForDate(date string) (int, error) {
var total int
err := db.conn.QueryRow("SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date = ?", date).Scan(&total)
return total, err
}
func (db *DB) CreateTableMerge(merge *models.TableMerge) error {
result, err := db.conn.Exec(
"INSERT INTO table_merges (parent_table_id, child_table_id, merged_name, active) VALUES (?, ?, ?, ?)",
merge.ParentTableID, merge.ChildTableID, merge.MergedName, merge.Active,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
merge.ID = int(id)
merge.CreatedAt = time.Now()
// Mark child table as merged
_, err = db.conn.Exec("UPDATE tables SET status = 'merged' WHERE id = ?", merge.ChildTableID)
return err
}