import sqlite3 import pandas as pd import os import sys from datetime import datetime BASE_DIR = os.path.dirname(os.path.abspath(__file__)) RESOURCE_DIR = os.path.join(BASE_DIR, "Resource") DB_PATH = os.path.join(BASE_DIR, "partners.db") def create_tables(db_connection): """Создает все необходимые таблицы в базе данных""" cursor = db_connection.cursor() cursor.executescript(''' -- Типы контрагентов CREATE TABLE IF NOT EXISTS ContractorType ( id INTEGER PRIMARY KEY AUTOINCREMENT, type_name VARCHAR(100) NOT NULL UNIQUE ); -- Контрагенты/Клиенты CREATE TABLE IF NOT EXISTS Clients ( id INTEGER PRIMARY KEY AUTOINCREMENT, contractor_type_id INTEGER REFERENCES ContractorType(id), name VARCHAR(255) NOT NULL, director VARCHAR(255), email VARCHAR(255), phone VARCHAR(50), address TEXT, inn VARCHAR(20) UNIQUE, rating DECIMAL(3,2) DEFAULT 0.0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Категории продукции CREATE TABLE IF NOT EXISTS Categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_name VARCHAR(100) NOT NULL UNIQUE, coefficient DECIMAL(5,2) NOT NULL DEFAULT 1.0 ); -- Таблица материалов CREATE TABLE IF NOT EXISTS Materials ( id INTEGER PRIMARY KEY AUTOINCREMENT, material_name VARCHAR(100) NOT NULL UNIQUE, description TEXT, category VARCHAR(50), color VARCHAR(50), density DECIMAL(10,2), thickness DECIMAL(10,2), supplier VARCHAR(255), country VARCHAR(100), wear_class VARCHAR(20), price_per_unit DECIMAL(15,2), unit VARCHAR(20) DEFAULT 'шт', in_stock INTEGER DEFAULT 0, production_time_days INTEGER DEFAULT 1, daily_capacity INTEGER DEFAULT 10, cost_price DECIMAL(15,2), markup_percent DECIMAL(5,2) DEFAULT 30, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Каталог продукции CREATE TABLE IF NOT EXISTS Catalog ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER REFERENCES Categories(id), material_id INTEGER REFERENCES Materials(id), product_name VARCHAR(255) NOT NULL, article VARCHAR(100) UNIQUE NOT NULL, description TEXT, min_price DECIMAL(15,2) NOT NULL, quantity_in_stock INTEGER DEFAULT 0, production_time_days INTEGER DEFAULT 1, daily_capacity INTEGER DEFAULT 10, cost_price DECIMAL(15,2), profit_per_unit DECIMAL(15,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Таблица корзин CREATE TABLE IF NOT EXISTS Cart ( id INTEGER PRIMARY KEY AUTOINCREMENT, client_id INTEGER REFERENCES Clients(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status VARCHAR(50) DEFAULT 'активна' ); -- Таблица товаров в корзине CREATE TABLE IF NOT EXISTS CartItems ( id INTEGER PRIMARY KEY AUTOINCREMENT, cart_id INTEGER REFERENCES Cart(id), product_id INTEGER REFERENCES Catalog(id), quantity INTEGER NOT NULL CHECK (quantity > 0), price_at_moment DECIMAL(15,2) NOT NULL, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Заказы CREATE TABLE IF NOT EXISTS Orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, client_id INTEGER REFERENCES Clients(id), order_date DATE NOT NULL, total_amount DECIMAL(15,2) NOT NULL, status VARCHAR(50) DEFAULT 'Новый', production_days INTEGER DEFAULT 1, production_start_date DATE, production_end_date DATE, production_status VARCHAR(50) DEFAULT 'Ожидает', assembly_percent INTEGER DEFAULT 15, delivery_percent INTEGER DEFAULT 10, assembly_cost DECIMAL(15,2) DEFAULT 0, delivery_cost DECIMAL(15,2) DEFAULT 0, base_amount DECIMAL(15,2) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Состав заказа CREATE TABLE IF NOT EXISTS OrderItems ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL REFERENCES Orders(id), product_id INTEGER NOT NULL REFERENCES Catalog(id), quantity INTEGER NOT NULL CHECK (quantity > 0), price_at_moment DECIMAL(15,2) NOT NULL ); -- Индексы для оптимизации CREATE INDEX IF NOT EXISTS idx_client_type ON Clients(contractor_type_id); CREATE INDEX IF NOT EXISTS idx_client_inn ON Clients(inn); CREATE INDEX IF NOT EXISTS idx_catalog_category ON Catalog(category_id); CREATE INDEX IF NOT EXISTS idx_catalog_material ON Catalog(material_id); CREATE INDEX IF NOT EXISTS idx_catalog_article ON Catalog(article); CREATE INDEX IF NOT EXISTS idx_material_name ON Materials(material_name); CREATE INDEX IF NOT EXISTS idx_material_category ON Materials(category); CREATE INDEX IF NOT EXISTS idx_material_supplier ON Materials(supplier); CREATE INDEX IF NOT EXISTS idx_cart_client ON Cart(client_id); CREATE INDEX IF NOT EXISTS idx_cart_status ON Cart(status); CREATE INDEX IF NOT EXISTS idx_cart_items_cart ON CartItems(cart_id); CREATE INDEX IF NOT EXISTS idx_order_client ON Orders(client_id); CREATE INDEX IF NOT EXISTS idx_order_date ON Orders(order_date); CREATE INDEX IF NOT EXISTS idx_order_status ON Orders(status); CREATE INDEX IF NOT EXISTS idx_production_status ON Orders(production_status); CREATE INDEX IF NOT EXISTS idx_orderitems_order ON OrderItems(order_id); CREATE INDEX IF NOT EXISTS idx_orderitems_product ON OrderItems(product_id); ''') db_connection.commit() print("✅ Таблицы успешно созданы") def calculate_discount(total_quantity): """Рассчитывает скидку на основе общего количества заказанной продукции""" if total_quantity >= 300000: return 15, '15% (более 300000 ед.)' elif total_quantity >= 50000: return 10, '10% (50000-300000 ед.)' elif total_quantity >= 10000: return 5, '5% (10000-50000 ед.)' else: return 0, '0% (до 10000 ед.)' def get_clients(db_path=DB_PATH, sort_by='rating', ascending=False): """Получает данные о клиентах с возможностью сортировки""" try: conn = sqlite3.connect(db_path) query = """ SELECT ct.type_name AS "Тип клиента", c.name AS "Наименование клиента", c.director AS "Директор", c.email AS "Электронная почта", c.phone AS "Телефон", c.address AS "Адрес", c.inn AS "ИНН", c.rating AS "Рейтинг", c.created_at AS "Дата создания", COALESCE(SUM(oi.quantity), 0) as total_quantity, COALESCE(SUM(o.total_amount), 0) as total_amount FROM Clients c JOIN ContractorType ct ON c.contractor_type_id = ct.id LEFT JOIN Orders o ON c.id = o.client_id LEFT JOIN OrderItems oi ON o.id = oi.order_id GROUP BY c.id """ if sort_by == 'discount': query += " ORDER BY total_quantity" elif sort_by == 'rating': query += " ORDER BY c.rating" elif sort_by == 'name': query += " ORDER BY c.name" elif sort_by == 'type': query += " ORDER BY ct.type_name, c.name" else: query += " ORDER BY c.rating" query += " DESC" if not ascending else " ASC" df = pd.read_sql_query(query, conn) conn.close() if not df.empty: df['Скидка_процент'] = df['total_quantity'].apply(lambda x: calculate_discount(x)[0]) df['Скидка_текст'] = df['total_quantity'].apply(lambda x: calculate_discount(x)[1]) df['Общая_сумма'] = df['total_amount'].apply(lambda x: f"{x:,.2f} руб.") return df except Exception as e: print(f"❌ Ошибка: {e}") return None def get_orders_history(db_path=DB_PATH, limit=50, sort_by='date_desc'): """Получает историю заказов с разными вариантами сортировки""" try: conn = sqlite3.connect(db_path) query = """ SELECT o.order_date AS "Дата заказа", c.name AS "Клиент", GROUP_CONCAT(cat.product_name || ' x' || oi.quantity, ', ') AS "Товары", SUM(oi.quantity) AS "Количество", o.total_amount AS "Сумма заказа", o.status AS "Статус" FROM Orders o JOIN Clients c ON o.client_id = c.id LEFT JOIN OrderItems oi ON o.id = oi.order_id LEFT JOIN Catalog cat ON oi.product_id = cat.id GROUP BY o.id """ if sort_by == 'date_desc': query += " ORDER BY o.order_date DESC, o.created_at DESC" elif sort_by == 'date_asc': query += " ORDER BY o.order_date ASC, o.created_at ASC" elif sort_by == 'quantity_desc': query += " ORDER BY SUM(oi.quantity) DESC" elif sort_by == 'amount_desc': query += " ORDER BY o.total_amount DESC" elif sort_by == 'status': query += " ORDER BY o.status, o.order_date DESC" else: query += " ORDER BY o.order_date DESC, o.created_at DESC" query += " LIMIT ?" df = pd.read_sql_query(query, conn, params=(limit,)) conn.close() return df except Exception as e: print(f"❌ Ошибка: {e}") return None def print_client_cards(clients_df): """Выводит карточки клиентов в красивом формате""" if clients_df is None or clients_df.empty: print("📭 Нет данных о клиентах") return for index, row in clients_df.iterrows(): print("╔" + "═" * 79 + "╗") print(f"║ {'КАРТОЧКА КЛИЕНТА':^77} ║") print("╠" + "═" * 79 + "╣") type_text = f"Тип: {row['Тип клиента']}" name_text = f"Наименование: {row['Наименование клиента']}" director_text = f"Директор: {row['Директор']}" rating_text = f"Рейтинг: {row['Рейтинг']}" quantity_text = f"Заказано единиц: {row['total_quantity']:,}" amount_text = f"На сумму: {row['total_amount']:,.2f} руб." discount_text = f"Скидка: {row['Скидка_текст']}" email_text = f"Email: {row['Электронная почта']}" phone_text = f"Телефон: {row['Телефон']}" inn_text = f"ИНН: {row['ИНН']}" address = row['Адрес'] if row['Адрес'] else 'Не указан' if len(address) > 70: address = address[:67] + "..." address_text = f"Адрес: {address}" print(f"║ {type_text:<77} ║") print(f"║ {name_text:<77} ║") print(f"║ {director_text:<77} ║") print(f"║ {rating_text:<77} ║") print(f"║ {quantity_text:<77} ║") print(f"║ {amount_text:<77} ║") print(f"║ {discount_text:<77} ║") print(f"║ {'':<77} ║") print(f"║ {email_text:<77} ║") print(f"║ {phone_text:<77} ║") print(f"║ {inn_text:<77} ║") print(f"║ {address_text:<77} ║") print("╚" + "═" * 79 + "╝") print() def print_orders_cards(orders_df): """Выводит карточки заказов в виде таблицы""" if orders_df is None or orders_df.empty: print("📭 Нет данных о заказах") return print("╔" + "═" * 167 + "╗") print(f"║ {'ИСТОРИЯ ЗАКАЗОВ':^165} ║") print("╠" + "═" * 167 + "╣") headers = ["Дата", "Клиент", "Товары", "Кол-во", "Статус", "Сумма"] print("║ {:<12} {:<25} {:<55} {:>12} {:<15} {:>15} ║".format( headers[0], headers[1], headers[2], headers[3], headers[4], headers[5] )) print("╠" + "═" * 167 + "╣") for index, row in orders_df.iterrows(): date = str(row['Дата заказа'])[:10] client = str(row['Клиент'])[:25] if len(str(row['Клиент'])) > 25 else str(row['Клиент']) products = str(row['Товары'])[:55] if len(str(row['Товары'])) > 55 else str(row['Товары']) quantity = f"{int(row['Количество']):,}" status = str(row['Статус'])[:15] if len(str(row['Статус'])) > 15 else str(row['Статус']) amount = f"{float(row['Сумма заказа']):,.2f}" line = f"║ {date:<12} {client:<25} {products:<55} {quantity:>12} {status:<15} {amount:>15} ║" print(line) print("╚" + "═" * 167 + "╝") total_quantity = orders_df['Количество'].sum() total_amount = orders_df['Сумма заказа'].sum() print(f"📊 ИТОГО: {len(orders_df)} заказов, {total_quantity:,.0f} единиц, общая сумма: {total_amount:,.2f} руб.") status_stats = orders_df['Статус'].value_counts() print("📊 Статусы заказов:") for status, count in status_stats.items(): print(f" - {status}: {count}") def simple_choice(options): """Упрощенная функция выбора""" print("\n" + "=" * 60) print("ВЫБЕРИТЕ ОПЦИЮ:") print("=" * 60) for i, option in enumerate(options, 1): print(f"{i}. {option}") while True: try: choice = input(f"\nВведите номер (1-{len(options)}): ").strip() if choice.isdigit(): choice_num = int(choice) if 1 <= choice_num <= len(options): return options[choice_num - 1] print(f"❌ Пожалуйста, введите число от 1 до {len(options)}") except KeyboardInterrupt: print("\n👋 Программа прервана пользователем.") sys.exit(0) def safe_float(value, default=None): """Безопасное преобразование в float""" if pd.isna(value) or value == '-': return default try: return float(value) except (ValueError, TypeError): return default def safe_int(value, default=None): """Безопасное преобразование в int""" if pd.isna(value) or value == '-': return default try: return int(float(value)) except (ValueError, TypeError): return default def load_data_from_files(): """Загружает данные из Excel файлов в базу данных""" conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() try: print("\n📁 Введите имена файлов (или нажмите Enter для значений по умолчанию):") clients_file = input("Файл с клиентами (Enter = Clients_import.xlsx): ").strip() or "Clients_import.xlsx" categories_file = input("Файл с категориями (Enter = Categories_import.xlsx): ").strip() or "Categories_import.xlsx" materials_file = input("Файл с материалами (Enter = Materials_import.xlsx): ").strip() or "Materials_import.xlsx" catalog_file = input("Файл с каталогом (Enter = Products_import.xlsx): ").strip() or "Products_import.xlsx" orders_file = input("Файл с заказами (Enter = Orders_import.xlsx): ").strip() or "Orders_import.xlsx" orderitems_file = input("Файл с составом заказов (Enter = OrderItems_import.xlsx): ").strip() or "OrderItems_import.xlsx" # Формируем полные пути clients_file = os.path.join(RESOURCE_DIR, os.path.basename(clients_file)) categories_file = os.path.join(RESOURCE_DIR, os.path.basename(categories_file)) materials_file = os.path.join(RESOURCE_DIR, os.path.basename(materials_file)) catalog_file = os.path.join(RESOURCE_DIR, os.path.basename(catalog_file)) orders_file = os.path.join(RESOURCE_DIR, os.path.basename(orders_file)) orderitems_file = os.path.join(RESOURCE_DIR, os.path.basename(orderitems_file)) print(f"\n📂 Загружаем файлы из папки: {RESOURCE_DIR}") # 1. Загрузка типов контрагентов print("\n1. 📊 Загрузка типов контрагентов...") clients_df = pd.read_excel(clients_file) if 'Тип партнера' in clients_df.columns: contractor_types = clients_df['Тип партнера'].unique() for contractor_type in contractor_types: cursor.execute("INSERT OR IGNORE INTO ContractorType (type_name) VALUES (?)", (contractor_type,)) print(f" Загружено {len(contractor_types)} типов") # 2. Загрузка клиентов print("2. 👥 Загрузка клиентов...") loaded_clients = 0 for _, row in clients_df.iterrows(): type_value = row.get('Тип партнера', 'Клиент') cursor.execute("SELECT id FROM ContractorType WHERE type_name = ?", (type_value,)) result = cursor.fetchone() contractor_type_id = result[0] if result else 1 cursor.execute("""INSERT OR IGNORE INTO Clients (contractor_type_id, name, director, email, phone, address, inn, rating) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", (contractor_type_id, row.get('Наименование партнера', ''), row.get('Директор', ''), row.get('Электронная почта партнера', ''), row.get('Телефон партнера', ''), row.get('Юридический адрес партнера', ''), row.get('ИНН', ''), safe_float(row.get('Рейтинг', 0.0), 0.0)) ) loaded_clients += 1 print(f" Загружено {loaded_clients} клиентов") # 3. Загрузка категорий print("3. 📦 Загрузка категорий...") categories_df = pd.read_excel(categories_file) loaded_categories = 0 for _, row in categories_df.iterrows(): cursor.execute("INSERT OR IGNORE INTO Categories (category_name, coefficient) VALUES (?, ?)", (row.get('Тип продукции', ''), safe_float(row.get('Коэффициент типа продукции', 1.0), 1.0)) ) loaded_categories += 1 print(f" Загружено {loaded_categories} категорий") # 4. Загрузка материалов print("4. 📦 Загрузка материалов...") materials_df = pd.read_excel(materials_file) loaded_materials = 0 for _, row in materials_df.iterrows(): cursor.execute(""" INSERT OR IGNORE INTO Materials (material_name, description, category, color, density, thickness, supplier, country, wear_class, price_per_unit, unit, in_stock, production_time_days, daily_capacity, markup_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( row.get('Наименование материала', ''), row.get('Описание', ''), row.get('Категория', ''), row.get('Цвет', ''), safe_float(row.get('Плотность', None)), safe_float(row.get('Толщина', None)), row.get('Поставщик', ''), row.get('Страна', ''), row.get('Класс износостойкости', ''), safe_float(row.get('Цена за ед.', 0), 0), row.get('Единица', 'шт'), safe_int(row.get('В наличии', 0), 0), 1, 10, 30 )) loaded_materials += 1 print(f" ✅ Загружено {loaded_materials} материалов") # 5. Загрузка каталога print("5. 🏷️ Загрузка каталога продукции...") catalog_df = pd.read_excel(catalog_file) loaded_products = 0 for index, row in catalog_df.iterrows(): prod_days = 1 if 'production_time_days' in catalog_df.columns: val = row['production_time_days'] if not pd.isna(val): try: prod_days = int(float(val)) except: prod_days = 1 cursor.execute(""" INSERT OR IGNORE INTO Catalog (category_id, material_id, product_name, article, description, min_price, quantity_in_stock, production_time_days, daily_capacity) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( safe_int(row.get('category_id', 1), 1), safe_int(row.get('material_id', None)), row.get('product_name', ''), row.get('article', ''), row.get('description', ''), safe_float(row.get('min_price', 0), 0), safe_int(row.get('quantity_in_stock', 0), 0), prod_days, 10 )) loaded_products += 1 print(f" ✅ Загружено {loaded_products} товаров") # 6. Загрузка заказов print("6. 📝 Загрузка заказов...") orders_df = pd.read_excel(orders_file) loaded_orders = 0 for _, row in orders_df.iterrows(): # Преобразование дат date_val = row.get('order_date') if pd.isna(date_val): order_date = datetime.now().strftime('%Y-%m-%d') else: if hasattr(date_val, 'strftime'): order_date = date_val.strftime('%Y-%m-%d') else: order_date = str(date_val) prod_start = row.get('production_start_date') if pd.isna(prod_start): prod_start = None else: if hasattr(prod_start, 'strftime'): prod_start = prod_start.strftime('%Y-%m-%d') else: prod_start = str(prod_start) prod_end = row.get('production_end_date') if pd.isna(prod_end): prod_end = None else: if hasattr(prod_end, 'strftime'): prod_end = prod_end.strftime('%Y-%m-%d') else: prod_end = str(prod_end) cursor.execute(""" INSERT INTO Orders (id, client_id, order_date, total_amount, status, production_days, production_start_date, production_end_date, production_status, assembly_percent, delivery_percent, assembly_cost, delivery_cost, base_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( safe_int(row.get('id')), safe_int(row.get('client_id')), order_date, safe_float(row.get('total_amount'), 0), row.get('status', 'Новый'), safe_int(row.get('production_days', 1)), prod_start, prod_end, row.get('production_status', 'Ожидает'), safe_int(row.get('assembly_percent', 15)), safe_int(row.get('delivery_percent', 10)), safe_float(row.get('assembly_cost', 0), 0), safe_float(row.get('delivery_cost', 0), 0), safe_float(row.get('base_amount', 0), 0) )) loaded_orders += 1 print(f" ✅ Загружено {loaded_orders} заказов") # 7. Загрузка состава заказов (OrderItems) print("7. 📦 Загрузка состава заказов...") orderitems_df = pd.read_excel(orderitems_file) loaded_items = 0 for _, row in orderitems_df.iterrows(): cursor.execute(""" INSERT INTO OrderItems (order_id, product_id, quantity, price_at_moment) VALUES (?, ?, ?, ?) """, ( safe_int(row.get('order_id')), safe_int(row.get('product_id')), safe_int(row.get('quantity'), 1), safe_float(row.get('price_at_moment'), 0) )) loaded_items += 1 print(f" ✅ Загружено {loaded_items} позиций") conn.commit() print(f"✅ Данные успешно загружены!") except Exception as e: print(f"❌ Ошибка при загрузке данных: {e}") import traceback traceback.print_exc() conn.rollback() finally: conn.close() def display_menu(): """Главное меню программы""" menu_options = [ 'КАРТОЧКИ КЛИЕНТОВ (сортировка по рейтингу)', 'КАРТОЧКИ КЛИЕНТОВ (сортировка по скидкам)', 'ИСТОРИЯ ЗАКАЗОВ', 'ЗАПОЛНЕНИЕ БД из файлов', 'ВЫХОД' ] while True: choice = simple_choice(menu_options) if choice == 'КАРТОЧКИ КЛИЕНТОВ (сортировка по рейтингу)': print("\n🎯 КАРТОЧКИ КЛИЕНТОВ (сортировка по рейтингу)") print("═" * 80) clients = get_clients(sort_by='rating', ascending=False) print_client_cards(clients) elif choice == 'КАРТОЧКИ КЛИЕНТОВ (сортировка по скидкам)': print("\n📊 КАРТОЧКИ КЛИЕНТОВ (сортировка по скидкам)") print("═" * 80) clients = get_clients(sort_by='discount', ascending=False) print_client_cards(clients) elif choice == 'ИСТОРИЯ ЗАКАЗОВ': print("\n📈 ИСТОРИЯ ЗАКАЗОВ") print("═" * 80) orders = get_orders_history(limit=50) print_orders_cards(orders) elif choice == 'ЗАПОЛНЕНИЕ БД из файлов': if os.path.exists(DB_PATH): os.remove(DB_PATH) print("🗑️ База данных удалена") else: print("ℹ️ Создаем новую базу") conn = sqlite3.connect(DB_PATH) create_tables(conn) conn.close() load_data_from_files() elif choice == 'ВЫХОД': print("\n👋 До свидания!") break if __name__ == "__main__": display_menu()