|
@@ -0,0 +1,183 @@
|
|
|
|
|
+CREATE TABLE TourFirm (
|
|
|
|
|
+ firm_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ firm_name VARCHAR(100) NOT NULL,
|
|
|
|
|
+ people_count INT NOT NULL CHECK (people_count > 0)
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE RoomType (
|
|
|
|
|
+ room_type_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ type_name VARCHAR(50) NOT NULL UNIQUE
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE Room (
|
|
|
|
|
+ room_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ room_type_id INT NOT NULL REFERENCES RoomType(room_type_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ room_number INT NOT NULL UNIQUE CHECK (room_number > 0),
|
|
|
|
|
+ occupancy INT NOT NULL CHECK (occupancy > 0),
|
|
|
|
|
+ rating DECIMAL(3,1) NOT NULL DEFAULT 0.0
|
|
|
|
|
+ CHECK (rating >= 0.0 AND rating <= 5.0)
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE Client (
|
|
|
|
|
+ client_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ firm_id INT NOT NULL REFERENCES TourFirm(firm_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ last_name VARCHAR(60) NOT NULL,
|
|
|
|
|
+ first_name VARCHAR(60) NOT NULL,
|
|
|
|
|
+ patronymic VARCHAR(60),
|
|
|
|
|
+ phone_number VARCHAR(20) NOT NULL
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE Booking (
|
|
|
|
|
+ booking_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ client_id INT NOT NULL REFERENCES Client(client_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ room_id INT NOT NULL REFERENCES Room(room_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ check_in_date DATE NOT NULL,
|
|
|
|
|
+ check_out_date DATE NOT NULL,
|
|
|
|
|
+ price_per_night DECIMAL(10,2) NOT NULL CHECK (price_per_night > 0),s
|
|
|
|
|
+ CHECK (check_out_date > check_in_date)
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE Service (
|
|
|
|
|
+ service_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ service_name VARCHAR(100) NOT NULL UNIQUE
|
|
|
|
|
+);
|
|
|
|
|
+CREATE TABLE RoomTypeService (
|
|
|
|
|
+ room_type_id INT NOT NULL REFERENCES RoomType(room_type_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ service_id INT NOT NULL REFERENCES Service(service_id)
|
|
|
|
|
+ ON DELETE RESTRICT ON UPDATE CASCADE,
|
|
|
|
|
+ PRIMARY KEY (room_type_id, service_id)
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+SELECT * FROM booking
|
|
|
|
|
+
|
|
|
|
|
+--Расчетный атрибут — итоговая стоимость бронирования
|
|
|
|
|
+--total_cost = price_per_night * (check_out_date - check_in_date)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+CREATE OR REPLACE VIEW BookingCostView AS
|
|
|
|
|
+SELECT
|
|
|
|
|
+ b.booking_id,
|
|
|
|
|
+ c.last_name || ' ' || c.first_name || ' ' || COALESCE(c.patronymic, '') AS client_full_name,
|
|
|
|
|
+ r.room_number,
|
|
|
|
|
+ rt.type_name AS room_type,
|
|
|
|
|
+ b.check_in_date,
|
|
|
|
|
+ b.check_out_date,
|
|
|
|
|
+ (b.check_out_date - b.check_in_date) AS nights,
|
|
|
|
|
+ b.price_per_night,
|
|
|
|
|
+ b.price_per_night * (b.check_out_date - b.check_in_date) AS total_cost
|
|
|
|
|
+FROM Booking b
|
|
|
|
|
+JOIN Client c ON c.client_id = b.client_id
|
|
|
|
|
+JOIN Room r ON r.room_id = b.room_id
|
|
|
|
|
+JOIN RoomType rt ON rt.room_type_id = r.room_type_id;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+--Проверочный запрос
|
|
|
|
|
+
|
|
|
|
|
+SELECT * FROM BookingCostView ORDER BY total_cost DESC LIMIT 10;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+--Возвращает итоговую стоимость конкретного бронирования
|
|
|
|
|
+--Использование в приложении: при отображении карточки брони
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+CREATE OR REPLACE FUNCTION get_booking_cost(p_booking_id INT)
|
|
|
|
|
+RETURNS DECIMAL(10,2)
|
|
|
|
|
+LANGUAGE plpgsql
|
|
|
|
|
+AS $$
|
|
|
|
|
+DECLARE
|
|
|
|
|
+ v_cost DECIMAL(10,2);
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ SELECT price_per_night * (check_out_date - check_in_date)
|
|
|
|
|
+ INTO v_cost
|
|
|
|
|
+ FROM Booking
|
|
|
|
|
+ WHERE booking_id = p_booking_id;
|
|
|
|
|
+
|
|
|
|
|
+ IF NOT FOUND THEN
|
|
|
|
|
+ RAISE EXCEPTION 'Бронирование с ID % не найдено', p_booking_id;
|
|
|
|
|
+ END IF;
|
|
|
|
|
+
|
|
|
|
|
+ RETURN v_cost;
|
|
|
|
|
+END;
|
|
|
|
|
+$$;
|
|
|
|
|
+
|
|
|
|
|
+SELECT get_booking_cost(2);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+-- Создаёт новое бронирование
|
|
|
|
|
+
|
|
|
|
|
+CREATE OR REPLACE PROCEDURE create_booking(
|
|
|
|
|
+ p_client_id INT,
|
|
|
|
|
+ p_room_id INT,
|
|
|
|
|
+ p_check_in_date DATE,
|
|
|
|
|
+ p_check_out_date DATE,
|
|
|
|
|
+ p_price_per_night DECIMAL(10,2)
|
|
|
|
|
+)
|
|
|
|
|
+LANGUAGE plpgsql
|
|
|
|
|
+AS $$
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ IF p_check_out_date <= p_check_in_date THEN
|
|
|
|
|
+ RAISE EXCEPTION 'Дата выезда должна быть позже даты заезда';
|
|
|
|
|
+ END IF;
|
|
|
|
|
+
|
|
|
|
|
+ IF NOT EXISTS (SELECT 1 FROM Client WHERE client_id = p_client_id) THEN
|
|
|
|
|
+ RAISE EXCEPTION 'Клиент с ID % не найден', p_client_id;
|
|
|
|
|
+ END IF;
|
|
|
|
|
+
|
|
|
|
|
+ IF NOT EXISTS (SELECT 1 FROM Room WHERE room_id = p_room_id) THEN
|
|
|
|
|
+ RAISE EXCEPTION 'Комната с ID % не найдена', p_room_id;
|
|
|
|
|
+ END IF;
|
|
|
|
|
+
|
|
|
|
|
+ IF EXISTS (
|
|
|
|
|
+ SELECT 1 FROM Booking
|
|
|
|
|
+ WHERE room_id = p_room_id
|
|
|
|
|
+ AND check_in_date < p_check_out_date
|
|
|
|
|
+ AND check_out_date > p_check_in_date
|
|
|
|
|
+ ) THEN
|
|
|
|
|
+ RAISE EXCEPTION 'Комната % уже забронирована на указанные даты', p_room_id;
|
|
|
|
|
+ END IF;
|
|
|
|
|
+
|
|
|
|
|
+ INSERT INTO Booking (client_id, room_id, check_in_date, check_out_date, price_per_night)
|
|
|
|
|
+ VALUES (p_client_id, p_room_id, p_check_in_date, p_check_out_date, p_price_per_night);
|
|
|
|
|
+
|
|
|
|
|
+ RAISE NOTICE 'Бронирование успешно создано для клиента ID %', p_client_id;
|
|
|
|
|
+END;
|
|
|
|
|
+$$;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+CALL create_booking(3, 5, '2026-03-01', '2026-03-014', 2500.00);
|
|
|
|
|
+
|
|
|
|
|
+SELECT setval('tourfirm_firm_id_seq', (SELECT MAX(firm_id) FROM TourFirm));
|
|
|
|
|
+SELECT setval('client_client_id_seq', (SELECT MAX(client_id) FROM Client));
|
|
|
|
|
+SELECT setval('room_room_id_seq', (SELECT MAX(room_id) FROM Room));
|
|
|
|
|
+SELECT setval('roomtype_room_type_id_seq',(SELECT MAX(room_type_id) FROM RoomType));
|
|
|
|
|
+SELECT setval('service_service_id_seq', (SELECT MAX(service_id) FROM Service));
|
|
|
|
|
+SELECT setval('booking_booking_id_seq', (SELECT MAX(booking_id) FROM Booking));
|
|
|
|
|
+
|
|
|
|
|
+--Логирование бронирований
|
|
|
|
|
+
|
|
|
|
|
+CREATE TABLE AuditLog (
|
|
|
|
|
+ log_id SERIAL PRIMARY KEY,
|
|
|
|
|
+ tbl_name VARCHAR(50),
|
|
|
|
|
+ action VARCHAR(10),
|
|
|
|
|
+ changed_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
|
+ old_data JSONB,
|
|
|
|
|
+ new_data JSONB
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+CREATE OR REPLACE FUNCTION audit_booking()
|
|
|
|
|
+RETURNS TRIGGER LANGUAGE plpgsql AS $$
|
|
|
|
|
+BEGIN
|
|
|
|
|
+ INSERT INTO AuditLog (tbl_name, action, old_data, new_data)
|
|
|
|
|
+ VALUES (
|
|
|
|
|
+ TG_TABLE_NAME, TG_OP,
|
|
|
|
|
+ CASE WHEN TG_OP = 'DELETE' THEN row_to_json(OLD)::jsonb ELSE NULL END,
|
|
|
|
|
+ CASE WHEN TG_OP != 'DELETE' THEN row_to_json(NEW)::jsonb ELSE NULL END
|
|
|
|
|
+ );
|
|
|
|
|
+ RETURN NEW;
|
|
|
|
|
+END;
|
|
|
|
|
+$$;
|
|
|
|
|
+
|
|
|
|
|
+CREATE TRIGGER trg_audit_booking
|
|
|
|
|
+AFTER INSERT OR UPDATE OR DELETE ON Booking
|
|
|
|
|
+FOR EACH ROW EXECUTE FUNCTION audit_booking();
|