Files
hotelsync/backend/migrations/002_extend.sql
HotelSync ef1603d896 Add migration 002_extend.sql — extended database schema
Covers all entities present in the UI:
- Hotels: additional fields (address, phone, check_in/out times, is_active)
- room_categories table
- rooms: beds JSONB, extra_place, child_policy, housekeeping_status, allow_hourly; rename capacity→max_guests, price_per_night→base_rate
- guests table with loyalty points/tiers
- bookings: guest_id, payment_status, meal_plan, timestamps
- tariffs, discounts, loyalty_rules, loyalty_tiers
- reviews, maintenance_tasks
- pos_categories, pos_items, pos_orders
- document_templates, hotel_settings (key-value JSONB)
- All necessary indexes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:28:54 +03:00

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 IF EXISTS capacity TO max_guests;
ALTER TABLE rooms RENAME COLUMN IF EXISTS 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);