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


import os
import random
import sqlite3
from datetime import datetime, timedelta

DB_NAME = "electronics_store.db"

# ==============================================================================
# 1. ИНИЦИАЛИЗАЦИЯ СХЕМЫ БАЗЫ ДАННЫХ
# ==============================================================================

CREATE_TABLES_SQL = """
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS Category (
    category_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT
);

CREATE TABLE IF NOT EXISTS Brand (
    brand_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT
);

CREATE TABLE IF NOT EXISTS Brand_Category (
    brand_id INTEGER NOT NULL,
    category_id INTEGER NOT NULL,
    PRIMARY KEY (brand_id, category_id),
    FOREIGN KEY (brand_id) REFERENCES Brand(brand_id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES Category(category_id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS Supplier (
    supplier_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    contact_person VARCHAR(100),
    phone VARCHAR(20),
    email VARCHAR(100),
    address TEXT
);

CREATE TABLE IF NOT EXISTS User (
    user_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    phone VARCHAR(20),
    password_hash VARCHAR(255) NOT NULL,
    address TEXT
);

CREATE TABLE IF NOT EXISTS Product (
    product_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    stock INTEGER NOT NULL DEFAULT 0,
    category_id INTEGER NOT NULL,
    brand_id INTEGER NOT NULL,
    supplier_id INTEGER NOT NULL,
    warranty VARCHAR(50),
    FOREIGN KEY (category_id) REFERENCES Category(category_id),
    FOREIGN KEY (brand_id) REFERENCES Brand(brand_id),
    FOREIGN KEY (supplier_id) REFERENCES Supplier(supplier_id)
);

CREATE TABLE IF NOT EXISTS Orders (
    order_id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    order_date DATETIME NOT NULL,
    status VARCHAR(20) NOT NULL,
    total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    delivery_address TEXT,
    FOREIGN KEY (user_id) REFERENCES User(user_id)
);

CREATE TABLE IF NOT EXISTS Order_Item (
    order_item_id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL DEFAULT 1,
    price DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES Product(product_id)
);

CREATE TABLE IF NOT EXISTS Delivery (
    delivery_id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id INTEGER UNIQUE NOT NULL,
    courier VARCHAR(100),
    tracking_number VARCHAR(50),
    delivery_date DATETIME,
    status VARCHAR(20) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE CASCADE
);
"""


def get_connection():
    conn = sqlite3.connect(DB_NAME)
    conn.execute("PRAGMA foreign_keys = ON;")
    conn.row_factory = sqlite3.Row
    return conn


def init_db():
    with get_connection() as conn:
        conn.executescript(CREATE_TABLES_SQL)


# ==============================================================================
# 2. ГЕНЕРАЦИЯ СЛУЧАЙНЫХ ДАННЫХ (SEEDING)
# ==============================================================================


def seed_data():
    conn = get_connection()
    cursor = conn.cursor()

    # Проверка, заполнена ли уже БД
    cursor.execute("SELECT COUNT(*) FROM User;")
    if cursor.fetchone()[0] > 0:
        print("[!] База данных уже содержит данные. Пропуск генерации.")
        conn.close()
        return

    print("[+] Генерация реалистичных случайных данных...")

    # 1. Категории
    categories = [
        ("Смартфоны", "Мобильные телефоны и аксессуары"),
        ("Ноутбуки", "Портативные компьютеры и ультрабуки"),
        ("Телевизоры", "Smart TV, OLED и 4K панели"),
        ("Аудио", "Наушники, акустика и колонки"),
        ("Умный дом", "Датчики, розетки и хабы"),
    ]
    cursor.executemany(
        "INSERT INTO Category (name, description) VALUES (?, ?)", categories
    )

    # 2. Бренды
    brands = [
        ("Apple", "Американская корпорация"),
        ("Samsung", "Южнокорейский гигант электроники"),
        ("Sony", "Японский производитель мультимедиа"),
        ("Xiaomi", "Китайский бренд доступной техники"),
        ("ASUS", "Тайваньский производитель ПК"),
    ]
    cursor.executemany(
        "INSERT INTO Brand (name, description) VALUES (?, ?)", brands
    )

    # 3. Связь Бренд-Категория
    brand_categories = [
        (1, 1),
        (1, 2),
        (1, 4),  # Apple: Смартфоны, Ноутбуки, Аудио
        (2, 1),
        (2, 2),
        (2, 3),  # Samsung: Смартфоны, Ноутбуки, ТВ
        (3, 3),
        (3, 4),  # Sony: ТВ, Аудио
        (4, 1),
        (4, 4),
        (4, 5),  # Xiaomi: Смартфоны, Аудио, Умный дом
        (5, 2),  # ASUS: Ноутбуки
    ]
    cursor.executemany(
        "INSERT INTO Brand_Category (brand_id, category_id) VALUES (?, ?)",
        brand_categories,
    )

    # 4. Поставщики
    suppliers = [
        (
            "ООО ТехноИмпорт",
            "Иван Петров",
            "+79991112233",
            "info@technoimport.ru",
            "г. Москва, ул. Складская, 10",
        ),
        (
            "АО ЭлектроСнаб",
            "Елена Сидорова",
            "+79992223344",
            "sales@electrosnab.ru",
            "г. Санкт-Петербург, Невский пр., 50",
        ),
        (
            "Global Tech Ltd",
            "Дмитрий Ковалев",
            "+79993334455",
            "contact@globaltech.com",
            "г. Новосибирск, ул. Ленина, 12",
        ),
    ]
    cursor.executemany(
        "INSERT INTO Supplier (name, contact_person, phone, email, address) VALUES (?, ?, ?, ?, ?)",
        suppliers,
    )

    # 5. Пользователи
    first_names = ["Алексей", "Мария", "Сергей", "Анна", "Михаил", "Ольга"]
    last_names = [
        "Иванов",
        "Смирнова",
        "Кузнецов",
        "Попова",
        "Васильев",
        "Соколова",
    ]
    cities = ["Москва", "Казань", "Екатеринбург", "Нижний Новгород", "Самара"]

    users = []
    for i in range(1, 10):
        fname = random.choice(first_names)
        lname = random.choice(last_names)
        name = f"{fname} {lname}"
        email = f"user{i}_{random.randint(100, 999)}@example.com"
        phone = f"+7900{random.randint(1000000, 9999999)}"
        pwd_hash = f"hash_{random.randint(100000, 999999)}"
        address = f"г. {random.choice(cities)}, ул. Лесная, д. {random.randint(1, 100)}"
        users.append((name, email, phone, pwd_hash, address))

    cursor.executemany(
        "INSERT INTO User (name, email, phone, password_hash, address) VALUES (?, ?, ?, ?, ?)",
        users,
    )

    # 6. Товары
    product_templates = [
        ("Смартфон Pro", 1, [1, 2, 4], 60000, 120000, "12 месяцев"),
        ("Ультрабук Slim", 2, [1, 2, 5], 80000, 200000, "24 месяца"),
        ("Телевизор 4K 55'", 3, [2, 3], 45000, 110000, "12 месяцев"),
        ("Беспроводные наушники", 4, [1, 3, 4], 5000, 30000, "12 месяцев"),
        ("Умный хаб", 5, [4], 3000, 8000, "6 месяцев"),
    ]

    products = []
    for name_prefix, cat_id, available_brands, min_p, max_p, warranty in product_templates:
        for _ in range(2):  # по 2 товара каждого вида
            b_id = random.choice(available_brands)
            s_id = random.randint(1, len(suppliers))
            full_name = f"{name_prefix} v{random.randint(1, 5)}"
            price = round(random.uniform(min_p, max_p), 2)
            stock = random.randint(5, 50)
            desc = f"Высококачественный {full_name} с гарантийным обслуживанием."
            products.append(
                (
                    full_name,
                    desc,
                    price,
                    stock,
                    cat_id,
                    b_id,
                    s_id,
                    warranty,
                )
            )

    cursor.executemany(
        """INSERT INTO Product (name, description, price, stock, category_id, brand_id, supplier_id, warranty) 
           VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
        products,
    )

    # 7. Заказы, Позиции заказа, Доставка
    statuses_order = ["Новый", "В обработке", "Завершен", "Отменен"]
    statuses_delivery = ["В пути", "Доставлено", "Ожидает курьера"]
    couriers = [
        "СДЭК",
        "Яндекс Доставка",
        "Почта России",
        "Собственная служба",
    ]

    for order_id_iter in range(1, 11):
        u_id = random.randint(1, len(users))
        days_ago = random.randint(1, 30)
        order_date = datetime.now() - timedelta(
            days=days_ago, hours=random.randint(1, 10)
        )
        status = random.choice(statuses_order)
        delivery_addr = f"г. {random.choice(cities)}, ул. Центральная, д. {random.randint(1, 50)}"

        cursor.execute(
            "INSERT INTO Orders (user_id, order_date, status, total_amount, delivery_address) VALUES (?, ?, ?, 0, ?)",
            (u_id, order_date.strftime("%Y-%m-%d %H:%M:%S"), status, delivery_addr),
        )
        order_id = cursor.lastrowid

        # Позиции заказа
        num_items = random.randint(1, 3)
        total_sum = 0.0
        chosen_products = random.sample(range(1, len(products) + 1), num_items)

        for p_id in chosen_products:
            cursor.execute(
                "SELECT price FROM Product WHERE product_id = ?", (p_id,)
            )
            price = cursor.fetchone()[0]
            qty = random.randint(1, 2)
            total_sum += float(price) * qty

            cursor.execute(
                "INSERT INTO Order_Item (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)",
                (order_id, p_id, qty, price),
            )

        # Обновляем итоговую сумму заказа
        cursor.execute(
            "UPDATE Orders SET total_amount = ? WHERE order_id = ?",
            (total_sum, order_id),
        )

        # Создаем доставку для завершенных или обрабатываемых заказов
        if status in ["В обработке", "Завершен"]:
            del_status = (
                "Доставлено" if status == "Завершен" else random.choice(statuses_delivery)
            )
            del_date = order_date + timedelta(days=random.randint(1, 3))
            track_num = f"TRK{random.randint(100000, 999999)}"
            cursor.execute(
                """INSERT INTO Delivery (order_id, courier, tracking_number, delivery_date, status)
                   VALUES (?, ?, ?, ?, ?)""",
                (
                    order_id,
                    random.choice(couriers),
                    track_num,
                    del_date.strftime("%Y-%m-%d %H:%M:%S"),
                    del_status,
                ),
            )

    conn.commit()
    conn.close()
    print("[+] Генерация тестовых данных успешно завершена!")


# ==============================================================================
# 3. МЕНЮ И CRUD-ОПЕРАЦИИ
# ==============================================================================

TABLES = [
    "Category",
    "Brand",
    "Brand_Category",
    "Supplier",
    "User",
    "Product",
    "Orders",
    "Order_Item",
    "Delivery",
]


def print_table(rows, columns):
    """Красивый вывод табличных данных в консоль"""
    if not rows:
        print("\n[i] Записи не найдены.")
        return

    col_widths = {col: len(col) for col in columns}
    for row in rows:
        for col in columns:
            val_str = str(row[col]) if row[col] is not None else "NULL"
            col_widths[col] = max(col_widths[col], len(val_str))

    header = " | ".join(col.ljust(col_widths[col]) for col in columns)
    separator = "-+-".join("-" * col_widths[col] for col in columns)

    print("\n" + header)
    print(separator)
    for row in rows:
        line = " | ".join(
            (str(row[col]) if row[col] is not None else "NULL").ljust(
                col_widths[col]
            )
            for col in columns
        )
        print(line)
    print()


def get_table_schema(table_name):
    """Получить информацию о столбцах таблицы"""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(f"PRAGMA table_info({table_name});")
    columns = cursor.fetchall()
    conn.close()
    return columns  # list of tuples: (cid, name, type, notnull, dflt_value, pk)


def handle_read(table_name):
    """Чтение (READ) данных"""
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(f"SELECT * FROM {table_name}")
    rows = cursor.fetchall()
    if rows:
        columns = rows[0].keys()
        print(f"\n=== Содержимое таблицы: {table_name} ===")
        print_table(rows, columns)
    else:
        print(f"\n[i] Таблица {table_name} пуста.")
    conn.close()


def handle_create(table_name):
    """Создание (CREATE) записи"""
    cols_info = get_table_schema(table_name)
    fields = []
    values = []

    print(f"\n--- Добавление записи в {table_name} ---")
    for col in cols_info:
        col_name = col[1]
        is_pk = col[5]

        # Пропускаем автоинкрементный PK (если он один)
        if is_pk and "INT" in col[2].upper():
            continue

        val = input(f"Введите '{col_name}' ({col[2]}): ").strip()
        if val == "":
            values.append(None)
        else:
            values.append(val)
        fields.append(col_name)

    placeholders = ", ".join(["?"] * len(fields))
    fields_str = ", ".join(fields)
    sql = f"INSERT INTO {table_name} ({fields_str}) VALUES ({placeholders})"

    try:
        conn = get_connection()
        cursor = conn.cursor()
        cursor.execute(sql, values)
        conn.commit()
        print("[+] Запись успешно добавлена!")
    except Exception as e:
        print(f"[!] Ошибка при добавлении: {e}")
    finally:
        conn.close()


def handle_update(table_name):
    """Обновление (UPDATE) записи"""
    cols_info = get_table_schema(table_name)
    pk_cols = [col[1] for col in cols_info if col[5] > 0]

    if not pk_cols:
        print("[!] Таблица не имеет первичного ключа для обновления.")
        return

    handle_read(table_name)
    print(f"--- Обновление записи в {table_name} ---")

    where_clauses = []
    where_values = []
    for pk in pk_cols:
        val = input(f"Введите значение PK '{pk}' для изменяемой записи: ").strip()
        where_clauses.append(f"{pk} = ?")
        where_values.append(val)

    set_clauses = []
    update_values = []

    print("Введите новые значения (оставьте пустым, чтобы не менять):")
    for col in cols_info:
        col_name = col[1]
        if col_name in pk_cols:
            continue

        val = input(f"Новое значение для '{col_name}': ")
        if val != "":
            set_clauses.append(f"{col_name} = ?")
            update_values.append(val)

    if not set_clauses:
        print("[i] Ничего не изменено.")
        return

    sql = f"UPDATE {table_name} SET {', '.join(set_clauses)} WHERE {' AND '.join(where_clauses)}"
    full_params = update_values + where_values

    try:
        conn = get_connection()
        cursor = conn.cursor()
        cursor.execute(sql, full_params)
        conn.commit()
        if cursor.rowcount > 0:
            print("[+] Запись успешно обновлена!")
        else:
            print("[!] Запись с указанным PK не найдена.")
    except Exception as e:
        print(f"[!] Ошибка при обновлении: {e}")
    finally:
        conn.close()


def handle_delete(table_name):
    """Удаление (DELETE) записи"""
    cols_info = get_table_schema(table_name)
    pk_cols = [col[1] for col in cols_info if col[5] > 0]

    if not pk_cols:
        print("[!] Не удалось определить первичный ключ.")
        return

    handle_read(table_name)
    print(f"--- Удаление записи из {table_name} ---")

    where_clauses = []
    where_values = []
    for pk in pk_cols:
        val = input(f"Введите значение PK '{pk}' для удаления: ").strip()
        where_clauses.append(f"{pk} = ?")
        where_values.append(val)

    sql = f"DELETE FROM {table_name} WHERE {' AND '.join(where_clauses)}"

    try:
        conn = get_connection()
        cursor = conn.cursor()
        cursor.execute(sql, where_values)
        conn.commit()
        if cursor.rowcount > 0:
            print("[+] Запись успешно удалена!")
        else:
            print("[!] Запись не найдена.")
    except Exception as e:
        print(f"[!] Ошибка при удалении: {e}")
    finally:
        conn.close()


def table_menu(table_name):
    """Подменю CRUD-операций для конкретной таблицы"""
    while True:
        print(f"\n=== Управление таблицей: [{table_name}] ===")
        print("1. Посмотреть записи (READ)")
        print("2. Добавить запись (CREATE)")
        print("3. Изменить запись (UPDATE)")
        print("4. Удалить запись (DELETE)")
        print("0. Назад в главное меню")

        choice = input("Выберите действие (0-4): ").strip()

        if choice == "1":
            handle_read(table_name)
        elif choice == "2":
            handle_create(table_name)
        elif choice == "3":
            handle_update(table_name)
        elif choice == "4":
            handle_delete(table_name)
        elif choice == "0":
            break
        else:
            print("[!] Неверный ввод, повторите попытку.")


def main_menu():
    """Главное меню программы"""
    init_db()
    seed_data()

    while True:
        print("\n==========================================")
        print("   СУБД: Магазин электроники (Main Menu)  ")
        print("==========================================")
        print("Выберите таблицу для работы:")
        for idx, tbl in enumerate(TABLES, 1):
            print(f"{idx}. {tbl}")
        print("0. Выход из программы")

        choice = input("\nВведите номер (0-9): ").strip()

        if choice == "0":
            print("\nЗавершение работы. До свидания!")
            break
        elif choice.isdigit() and 1 <= int(choice) <= len(TABLES):
            selected_table = TABLES[int(choice) - 1]
            table_menu(selected_table)
        else:
            print("[!] Ошибка ввода. Укажите число из списка.")


if __name__ == "__main__":
    main_menu()