From ef1603d8963bfc2a0a2ff5b6f0a00bb69e4ef916 Mon Sep 17 00:00:00 2001 From: HotelSync Date: Tue, 17 Mar 2026 13:28:54 +0300 Subject: [PATCH] =?UTF-8?q?Add=20migration=20002=5Fextend.sql=20=E2=80=94?= =?UTF-8?q?=20extended=20database=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- backend/migrations/002_extend.sql | 273 ++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 backend/migrations/002_extend.sql diff --git a/backend/migrations/002_extend.sql b/backend/migrations/002_extend.sql new file mode 100644 index 0000000..d226483 --- /dev/null +++ b/backend/migrations/002_extend.sql @@ -0,0 +1,273 @@ +-- 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);