- backend/README.md: full API and DB documentation - public/robots.txt: block all pages except root for SEO - index.html: add noindex meta tag for inner SPA pages - 002_extend.sql: fix RENAME COLUMN syntax (remove unsupported IF EXISTS) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
274 lines
15 KiB
SQL
274 lines
15 KiB
SQL
-- HotelSync Database Schema
|
|
-- Migration 002 — Extended Schema
|
|
|
|
-- ── Hotels: дополнительные поля ────────────────────────────────────────────
|
|
ALTER TABLE hotels
|
|
ADD COLUMN IF NOT EXISTS address TEXT,
|
|
ADD COLUMN IF NOT EXISTS phone VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS email VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS contact_person VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS logo_url TEXT,
|
|
ADD COLUMN IF NOT EXISTS check_in_time TIME NOT NULL DEFAULT '14:00',
|
|
ADD COLUMN IF NOT EXISTS check_out_time TIME NOT NULL DEFAULT '12:00',
|
|
ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT true;
|
|
|
|
-- ── Room categories ─────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS room_categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
color VARCHAR(20) NOT NULL DEFAULT '#6366f1',
|
|
amenities TEXT[] NOT NULL DEFAULT '{}',
|
|
sort_order INTEGER NOT NULL DEFAULT 99,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(hotel_id, name)
|
|
);
|
|
|
|
-- ── Rooms: дополнительные поля ──────────────────────────────────────────────
|
|
ALTER TABLE rooms
|
|
ADD COLUMN IF NOT EXISTS name VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS category_id UUID REFERENCES room_categories(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS bed_type VARCHAR(20) NOT NULL DEFAULT 'double'
|
|
CHECK (bed_type IN ('single','double','queen','king','twin')),
|
|
ADD COLUMN IF NOT EXISTS beds JSONB,
|
|
ADD COLUMN IF NOT EXISTS housekeeping_status VARCHAR(20) NOT NULL DEFAULT 'clean'
|
|
CHECK (housekeeping_status IN ('clean','dirty','cleaning','inspect')),
|
|
ADD COLUMN IF NOT EXISTS sort_order INTEGER NOT NULL DEFAULT 99,
|
|
ADD COLUMN IF NOT EXISTS allow_hourly BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS hourly_rate DECIMAL(10,2),
|
|
ADD COLUMN IF NOT EXISTS extra_place JSONB,
|
|
ADD COLUMN IF NOT EXISTS child_policy JSONB,
|
|
ADD COLUMN IF NOT EXISTS description TEXT,
|
|
ADD COLUMN IF NOT EXISTS photos TEXT[] NOT NULL DEFAULT '{}';
|
|
|
|
-- Переименуем старые поля для единообразия
|
|
ALTER TABLE rooms RENAME COLUMN capacity TO max_guests;
|
|
ALTER TABLE rooms RENAME COLUMN price_per_night TO base_rate;
|
|
|
|
-- ── Guests (база гостей) ────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS guests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
email VARCHAR(255),
|
|
phone VARCHAR(50),
|
|
passport VARCHAR(100),
|
|
birth_date DATE,
|
|
nationality VARCHAR(50),
|
|
gender VARCHAR(10) CHECK (gender IN ('male','female','other')),
|
|
city VARCHAR(100),
|
|
notes TEXT,
|
|
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
loyalty_points INTEGER NOT NULL DEFAULT 0,
|
|
loyalty_tier VARCHAR(20) NOT NULL DEFAULT 'standard'
|
|
CHECK (loyalty_tier IN ('standard','silver','gold','platinum')),
|
|
total_stays INTEGER NOT NULL DEFAULT 0,
|
|
total_spent DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Bookings: дополнительные поля ──────────────────────────────────────────
|
|
ALTER TABLE bookings
|
|
ADD COLUMN IF NOT EXISTS guest_id UUID REFERENCES guests(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS booking_number VARCHAR(20),
|
|
ADD COLUMN IF NOT EXISTS inquiry_status VARCHAR(20) NOT NULL DEFAULT 'confirmed'
|
|
CHECK (inquiry_status IN ('inquiry','confirmed','checked_in','checked_out','cancelled','no_show')),
|
|
ADD COLUMN IF NOT EXISTS payment_status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (payment_status IN ('pending','partial','paid','refunded')),
|
|
ADD COLUMN IF NOT EXISTS extra_adults INTEGER NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS extra_children INTEGER NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS extra_place_fee DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS meal_plan VARCHAR(20)
|
|
CHECK (meal_plan IN ('none','breakfast','half_board','full_board','all_inclusive')),
|
|
ADD COLUMN IF NOT EXISTS early_checkin BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS late_checkout BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS confirmed_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS checked_in_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS checked_out_at TIMESTAMPTZ;
|
|
|
|
-- ── Tariffs ─────────────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS tariffs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL DEFAULT 'percent'
|
|
CHECK (type IN ('percent','fixed','multiplier')),
|
|
value DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
applies_to VARCHAR(20) NOT NULL DEFAULT 'all'
|
|
CHECK (applies_to IN ('all','weekdays','weekends','specific')),
|
|
days_of_week INTEGER[] NOT NULL DEFAULT '{}',
|
|
date_from DATE,
|
|
date_to DATE,
|
|
min_nights INTEGER,
|
|
sort_order INTEGER NOT NULL DEFAULT 99,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Discounts ───────────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS discounts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL DEFAULT 'percent'
|
|
CHECK (type IN ('percent','fixed')),
|
|
value DECIMAL(10,2) NOT NULL,
|
|
code VARCHAR(50),
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
date_from DATE,
|
|
date_to DATE,
|
|
min_nights INTEGER,
|
|
min_guests INTEGER,
|
|
usage_limit INTEGER,
|
|
usage_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Loyalty rules ───────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS loyalty_rules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
event VARCHAR(30) NOT NULL
|
|
CHECK (event IN ('stay','review','birthday','referral','manual')),
|
|
points INTEGER NOT NULL DEFAULT 0,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS loyalty_tiers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(50) NOT NULL,
|
|
tier VARCHAR(20) NOT NULL
|
|
CHECK (tier IN ('standard','silver','gold','platinum')),
|
|
min_points INTEGER NOT NULL DEFAULT 0,
|
|
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(hotel_id, tier)
|
|
);
|
|
|
|
-- ── Reviews ─────────────────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS reviews (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
booking_id UUID REFERENCES bookings(id) ON DELETE SET NULL,
|
|
guest_id UUID REFERENCES guests(id) ON DELETE SET NULL,
|
|
guest_name VARCHAR(255) NOT NULL,
|
|
source VARCHAR(20) NOT NULL DEFAULT 'direct'
|
|
CHECK (source IN ('direct','booking_com','airbnb','google','tripadvisor')),
|
|
rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
|
|
text TEXT,
|
|
reply TEXT,
|
|
replied_at TIMESTAMPTZ,
|
|
is_public BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Maintenance tasks ───────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS maintenance_tasks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
room_id UUID REFERENCES rooms(id) ON DELETE SET NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
category VARCHAR(30) NOT NULL DEFAULT 'other'
|
|
CHECK (category IN ('plumbing','electrical','furniture','hvac','exterior','other')),
|
|
priority VARCHAR(10) NOT NULL DEFAULT 'medium'
|
|
CHECK (priority IN ('low','medium','high','urgent')),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'open'
|
|
CHECK (status IN ('open','in_progress','done','cancelled')),
|
|
assignee_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
cost DECIMAL(10,2),
|
|
due_date DATE,
|
|
completed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── POS: категории, позиции, заказы ────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS pos_categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 99,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS pos_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
category_id UUID REFERENCES pos_categories(id) ON DELETE SET NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
price DECIMAL(10,2) NOT NULL,
|
|
vat_rate SMALLINT NOT NULL DEFAULT 20,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
sort_order INTEGER NOT NULL DEFAULT 99,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS pos_orders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
room_id UUID REFERENCES rooms(id) ON DELETE SET NULL,
|
|
booking_id UUID REFERENCES bookings(id) ON DELETE SET NULL,
|
|
operator_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
items JSONB NOT NULL DEFAULT '[]',
|
|
subtotal DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
discount DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
payment_type VARCHAR(20) NOT NULL DEFAULT 'cash'
|
|
CHECK (payment_type IN ('cash','card','room_charge','online')),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'open'
|
|
CHECK (status IN ('open','paid','cancelled','refunded')),
|
|
fiscal_receipt_id VARCHAR(100),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Document templates ──────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS document_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(30) NOT NULL
|
|
CHECK (type IN ('registration_card','invoice','act','contract','other')),
|
|
content TEXT NOT NULL DEFAULT '',
|
|
is_default BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- ── Hotel settings (key-value для гибких настроек) ──────────────────────────
|
|
CREATE TABLE IF NOT EXISTS hotel_settings (
|
|
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
|
|
key VARCHAR(100) NOT NULL,
|
|
value JSONB NOT NULL DEFAULT 'null',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (hotel_id, key)
|
|
);
|
|
|
|
-- ── Indexes ──────────────────────────────────────────────────────────────────
|
|
CREATE INDEX IF NOT EXISTS idx_guests_hotel ON guests(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_guests_email ON guests(email);
|
|
CREATE INDEX IF NOT EXISTS idx_bookings_guest ON bookings(guest_id);
|
|
CREATE INDEX IF NOT EXISTS idx_bookings_number ON bookings(booking_number);
|
|
CREATE INDEX IF NOT EXISTS idx_tariffs_hotel ON tariffs(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_discounts_hotel ON discounts(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_reviews_hotel ON reviews(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_maintenance_hotel ON maintenance_tasks(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_pos_orders_hotel ON pos_orders(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_room_categories_hotel ON room_categories(hotel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_rooms_category ON rooms(category_id);
|