Загрузка данных


-- Создание таблиц

CREATE TABLE passengers (
    id SERIAL PRIMARY KEY,
    passport_number VARCHAR(20) NOT NULL UNIQUE,
    full_name VARCHAR(100) NOT NULL,
    address TEXT,
    phone VARCHAR(20),
    birth_date DATE
);

CREATE TABLE aircraft (
    id SERIAL PRIMARY KEY,
    registration_number VARCHAR(20) NOT NULL UNIQUE,
    model VARCHAR(50),
    service_life_years INTEGER, -- срок эксплуатации (лет)
    is_airworthy BOOLEAN DEFAULT TRUE -- исправен/не исправен (Да/Нет)
);

CREATE TABLE captains (
    id SERIAL PRIMARY KEY,
    personal_number VARCHAR(20) NOT NULL UNIQUE,
    full_name VARCHAR(100) NOT NULL,
    address TEXT,
    phone VARCHAR(20),
    flight_hours INTEGER DEFAULT 0 -- налет (часов)
);

-- Связь: у самолета один командир, командир закреплен за одним самолетом
ALTER TABLE aircraft ADD COLUMN captain_id INTEGER UNIQUE REFERENCES captains(id) ON DELETE SET NULL;

CREATE TABLE routes (
    id SERIAL PRIMARY KEY,
    route_code VARCHAR(10) NOT NULL UNIQUE,
    departure_airport VARCHAR(100) NOT NULL,
    arrival_airport VARCHAR(100) NOT NULL,
    distance_km INTEGER
);

CREATE TABLE flights (
    id SERIAL PRIMARY KEY,
    flight_number VARCHAR(20) NOT NULL UNIQUE,
    route_id INTEGER NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
    aircraft_id INTEGER NOT NULL REFERENCES aircraft(id) ON DELETE CASCADE,
    departure_datetime TIMESTAMP NOT NULL,
    arrival_datetime TIMESTAMP NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    is_cancelled BOOLEAN DEFAULT FALSE
);

CREATE TABLE tickets (
    id SERIAL PRIMARY KEY,
    passenger_id INTEGER NOT NULL REFERENCES passengers(id) ON DELETE CASCADE,
    flight_id INTEGER NOT NULL REFERENCES flights(id) ON DELETE CASCADE,
    seat_number VARCHAR(5),
    purchase_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(passenger_id, flight_id)
);

-- Индексы для производительности
CREATE INDEX idx_flights_route_id ON flights(route_id);
CREATE INDEX idx_flights_aircraft_id ON flights(aircraft_id);
CREATE INDEX idx_tickets_passenger_id ON tickets(passenger_id);
CREATE INDEX idx_tickets_flight_id ON tickets(flight_id);


import sys
import os
from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt
from dotenv import load_dotenv
import psycopg2
from psycopg2 import sql

# Загрузка переменных окружения
load_dotenv()

# Импорт сгенерированного интерфейса (создается через Qt Designer)
# В данном случае используется заглушка, так как UI-файл не предоставлен
# Для реальной работы нужно создать Ui_MainWindow через pyuic6
class Ui_MainWindow:
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        self.listWidget.setGeometry(QtWidgets.QRect(20, 20, 760, 540))
        MainWindow.setCentralWidget(self.centralwidget)


class DatabaseManager:
    """Класс для работы с базой данных"""
    
    def __init__(self):
        try:
            self.conn = psycopg2.connect(
                host=os.getenv('DB_HOST'),
                user=os.getenv('DB_USER'),
                password=os.getenv('DB_PASSWORD'),
                dbname=os.getenv('DB_NAME'),
                port=os.getenv('DB_PORT')
            )
            self.cur = self.conn.cursor()
            print("Подключение к БД успешно!")
        except Exception as e:
            print(f"Ошибка подключения к БД: {e}")
            sys.exit(1)
    
    def get_flights_by_passenger(self, passenger_id=1):
        """
        Получение всех рейсов, на которые куплены билеты у пассажира.
        Возвращает:
            - номер рейса
            - дата и время вылета
            - аэропорт назначения
            - цена
            - статус рейса (отменен/активен)
            - Ф.И.О. командира
            - модель самолета
        """
        sql_query = """
        SELECT 
            f.flight_number,
            f.departure_datetime,
            r.arrival_airport,
            f.price,
            f.is_cancelled,
            c.full_name AS captain_name,
            a.model AS aircraft_model
        FROM tickets t
        JOIN flights f ON t.flight_id = f.id
        JOIN routes r ON f.route_id = r.id
        JOIN aircraft a ON f.aircraft_id = a.id
        LEFT JOIN captains c ON a.captain_id = c.id
        WHERE t.passenger_id = %s
        ORDER BY f.departure_datetime DESC;
        """
        try:
            self.cur.execute(sql_query, (passenger_id,))
            return self.cur.fetchall()
        except Exception as e:
            print(f"Ошибка выполнения запроса: {e}")
            return []
    
    def get_all_passengers(self):
        """Получить список всех пассажиров для выбора"""
        self.cur.execute("SELECT id, full_name FROM passengers ORDER BY full_name")
        return self.cur.fetchall()
    
    def close(self):
        if self.cur:
            self.cur.close()
        if self.conn:
            self.conn.close()


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setWindowTitle("Авиакомпания 'Полет' - Рейсы пассажира")
        
        # Подключение к БД
        self.db = DatabaseManager()
        
        # Загрузка данных
        self.load_data()
    
    def load_data(self):
        """Загрузка и отображение рейсов для пассажира с ID=1"""
        rows = self.db.get_flights_by_passenger(passenger_id=1)
        
        self.listWidget.clear()
        
        if not rows:
            self.listWidget.addItem("Нет данных о рейсах для данного пассажира.")
            return
        
        for row in rows:
            flight_number = row[0]
            departure_dt = row[1].strftime("%d.%m.%Y %H:%M") if row[1] else "—"
            arrival_airport = row[2] or "—"
            price = f"{row[3]:.2f} руб." if row[3] else "—"
            status = "ОТМЕНЕН" if row[4] else "Активен"
            captain = row[5] or "Не назначен"
            aircraft = row[6] or "—"
            
            text = f"""
Рейс: {flight_number}
Дата и время вылета: {departure_dt}
Аэропорт назначения: {arrival_airport}
Цена: {price}
Статус: {status}
Командир: {captain}
Самолет: {aircraft}
{"-"*40}
"""
            self.listWidget.addItem(text.strip())
    
    def closeEvent(self, event):
        """Закрытие соединения с БД при завершении приложения"""
        self.db.close()
        event.accept()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())




-- Добавление тестовых данных

INSERT INTO passengers (passport_number, full_name, address, phone, birth_date) VALUES
('AB1234567', 'Иванов Иван Иванович', 'Москва, ул. Тверская, д.1', '+7-999-123-45-67', '1985-05-15'),
('CD7654321', 'Петрова Анна Сергеевна', 'Санкт-Петербург, Невский пр., д.10', '+7-999-234-56-78', '1990-08-22');

INSERT INTO captains (personal_number, full_name, address, phone, flight_hours) VALUES
('P001', 'Сидоров Петр Алексеевич', 'Москва, ул. Ленина, д.5', '+7-999-345-67-89', 3500),
('P002', 'Козлова Елена Михайловна', 'Санкт-Петербург, ул. Пушкина, д.15', '+7-999-456-78-90', 2800);

INSERT INTO aircraft (registration_number, model, service_life_years, is_airworthy, captain_id) VALUES
('RA-12345', 'Boeing 737-800', 10, TRUE, 1),
('RA-67890', 'Airbus A320', 8, TRUE, 2);

INSERT INTO routes (route_code, departure_airport, arrival_airport, distance_km) VALUES
('SU001', 'Шереметьево (SVO)', 'Пулково (LED)', 600),
('SU002', 'Домодедово (DME)', 'Кольцово (SVX)', 1400);

INSERT INTO flights (flight_number, route_id, aircraft_id, departure_datetime, arrival_datetime, price, is_cancelled) VALUES
('SU1001', 1, 1, '2026-06-20 09:00:00', '2026-06-20 10:30:00', 8500.00, FALSE),
('SU1002', 1, 1, '2026-06-21 14:00:00', '2026-06-21 15:30:00', 9200.00, FALSE),
('SU2001', 2, 2, '2026-06-22 08:00:00', '2026-06-22 11:00:00', 15000.00, TRUE);

INSERT INTO tickets (passenger_id, flight_id, seat_number) VALUES
(1, 1, '12A'),
(1, 3, '5C'),
(2, 2, '8B');