From 4cff0c83c670c0e7cf9e458bcc5137563e592ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=91=D1=83=D1=80?= =?UTF-8?q?=D1=83=D0=BB=D0=B5=D0=B2?= Date: Thu, 27 Nov 2025 10:49:14 +0000 Subject: [PATCH] Upload New File --- main.py | 437 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..651b03a --- /dev/null +++ b/main.py @@ -0,0 +1,437 @@ +import sqlite3 +import pandas as pd +import csv +from datetime import datetime +import sys + + +class FurnitureDatabase: + """Класс для работы с базой данных""" + + def __init__(self, db_name='furniture_company.db'): + self.db_name = db_name + self.conn = None + self.cursor = None + + def connect(self): + """Подключение к базе данных SQLite""" + try: + self.conn = sqlite3.connect(self.db_name) + self.cursor = self.conn.cursor() + print("✅ База данных подключена") + return True + except Exception as e: + print(f"❌ Не удалось подключиться к базе данных: {e}") + return False + + def create_tables(self): + """Создание таблиц в базе данных""" + try: + # Таблица типов материалов + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS material_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL UNIQUE, + waste_percentage DECIMAL(5,4) NOT NULL + ) + ''') + + # Таблица материалов + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS materials ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255) NOT NULL UNIQUE, + type_id INTEGER NOT NULL REFERENCES material_types(id), + price DECIMAL(15,2) NOT NULL, + quantity_in_stock DECIMAL(10,2) NOT NULL, + min_quantity DECIMAL(10,2) NOT NULL, + quantity_per_package DECIMAL(10,2) NOT NULL, + unit VARCHAR(50) NOT NULL + ) + ''') + + # Таблица типов продукции + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS product_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL UNIQUE, + coefficient DECIMAL(5,2) NOT NULL + ) + ''') + + # Таблица продукции + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + article BIGINT NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + type_id INTEGER NOT NULL REFERENCES product_types(id), + min_partner_price DECIMAL(15,2) NOT NULL + ) + ''') + + # Таблица расхода материалов + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS material_consumption ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + material_id INTEGER NOT NULL REFERENCES materials(id), + product_id INTEGER NOT NULL REFERENCES products(id), + quantity_required DECIMAL(10,2) NOT NULL, + UNIQUE (material_id, product_id) + ) + ''') + + self.conn.commit() + print("✅ Таблицы созданы") + return True + + except Exception as e: + print(f"❌ Не удалось создать таблицы: {e}") + return False + + def import_initial_data(self): + """Импорт начальных данных""" + try: + # Импорт типов материалов + material_types = [ + ('Дерево', 0.0055), ('Древесная плита', 0.003), ('Текстиль', 0.0015), + ('Стекло', 0.0045), ('Металл', 0.001), ('Пластик', 0.0005) + ] + self.cursor.executemany('INSERT OR IGNORE INTO material_types (name, waste_percentage) VALUES (?, ?)', + material_types) + + # Импорт типов продукции + product_types = [ + ('Кресла', 1.95), ('Полки', 2.5), ('Стеллажи', 4.35), + ('Столы', 5.5), ('Тумбы', 7.6), ('Шкафы', 6.72) + ] + self.cursor.executemany('INSERT OR IGNORE INTO product_types (name, coefficient) VALUES (?, ?)', + product_types) + + # Импорт материалов + materials_data = [ + ('Цельный массив дерева Дуб 1000х600 мм', 1, 7450.00, 1500.00, 500.00, 7.20, 'м²'), + ('Клееный массив дерева Дуб 1000х600 мм', 1, 4520.00, 300.00, 500.00, 7.20, 'м²'), + ('Шпон облицовочный Дуб натуральный 2750х480 мм', 1, 2500.00, 2000.00, 1500.00, 19.80, 'м²'), + ('Фанера 2200х1000 мм', 2, 2245.00, 450.00, 1000.00, 52.80, 'м²'), + ('ДСП 2750х1830 мм', 2, 529.60, 1010.00, 1200.00, 181.08, 'м²'), + ('МДФ 2070х1400 мм', 2, 417.24, 1550.00, 1000.00, 87.00, 'м²'), + ('ДВП 2440х2050 мм', 2, 187.00, 1310.00, 1000.00, 190.00, 'м²'), + ('ХДФ 2800x2070 мм', 2, 370.96, 1400.00, 1200.00, 90.00, 'м²'), + ('Экокожа 140 см', 3, 1600.00, 1200.00, 1500.00, 100.00, 'пог.м'), + ('Велюр 140 см', 3, 1300.00, 1300.00, 1500.00, 100.00, 'пог.м'), + ('Шенилл 140 см', 3, 620.00, 950.00, 1500.00, 100.00, 'пог.м'), + ('Рогожка 140 см', 3, 720.00, 960.00, 1500.00, 100.00, 'пог.м'), + ('Закаленное стекло 2600х1800 мм', 4, 1148.00, 1000.00, 500.00, 196.56, 'м²'), + ('Металлокаркас для стула', 5, 1100.00, 300.00, 250.00, 5.00, 'шт'), + ('Металлокаркас каркас из профиля с траверсами для стола', 5, 6700.00, 100.00, 100.00, 1.00, 'шт'), + ('Колесо для мебели поворотное', 6, 10.59, 1500.00, 1000.00, 500.00, 'шт'), + ('Газ-лифт', 5, 730.00, 500.00, 250.00, 5.00, 'шт'), + ('Металлическая крестовина для офисных кресел', 5, 2700.00, 500.00, 250.00, 5.00, 'шт'), + ('Пластиковый комплект для стула', 6, 900.00, 300.00, 250.00, 100.00, 'шт'), + ('Кромка ПВХ', 6, 35.80, 1500.00, 1000.00, 100.00, 'м') + ] + self.cursor.executemany( + 'INSERT OR IGNORE INTO materials (name, type_id, price, quantity_in_stock, min_quantity, quantity_per_package, unit) VALUES (?, ?, ?, ?, ?, ?, ?)', + materials_data + ) + + # Импорт продукции + products_data = [ + (3028272, 'Кресло детское цвет Белый и Розовый', 1, 15324.99), + (3018556, 'Кресло офисное цвет Черный', 1, 21443.99), + (3549922, 'Кресло эргономичное цвет Темно-коричневый', 1, 24760.00), + (7028048, 'Полка настольная', 2, 2670.89), + (5759324, 'Стеллаж для документов цвет Дуб светлый 854х445х2105 мм', 3, 24899.00), + (5259474, 'Стеллаж цвет Белый с ящиками 854х445х2105 мм', 3, 16150.00), + (5118827, 'Стеллаж цвет Орех 400х370х2000 мм', 3, 2860.00), + (1029784, 'Стол для ноутбука на металлокаркасе 800х600х123 мм', 4, 14690.00), + (1028248, 'Стол компьютерный 700х600х500 мм', 4, 4105.89), + (1130981, 'Стол компьютерный на металлокаркасе 1400х600х750 мм', 4, 13899.00), + (1188827, 'Стол письменный 1100x750x600 мм', 4, 5148.00), + (1029272, 'Стол письменный с тумбочкой 4 ящика 1100x750x600 мм', 4, 15325.00), + (1016662, 'Стол руководителя письменный цвет Венге 1600x800x764 мм', 4, 43500.90), + (1658953, 'Стол руководителя письменный цвет Орех темный 2300x1000x750 мм', 4, 132500.00), + (6033136, 'Тумба выкатная 3 ящика', 5, 3750.00), + (6115947, 'Тумба офисная для оргтехники', 5, 2450.00), + (5559898, 'Узкий пенал стеллаж 5 полок цвет Орех 364х326x2000 мм', 3, 8348.00), + (4159043, 'Шкаф для книг 800x420x2000 мм', 6, 23390.99), + (4758375, 'Шкаф для одежды цвет Венге 602x420x2000 мм', 6, 12035.00), + (4588376, 'Шкаф-витрина 2 ящика 800x420x2000 мм', 6, 31991.00) + ] + self.cursor.executemany( + 'INSERT OR IGNORE INTO products (article, name, type_id, min_partner_price) VALUES (?, ?, ?, ?)', + products_data + ) + + # Импорт расхода материалов + consumption_data = [ + (4, 1, 0.85), (8, 1, 1.5), (10, 1, 1.5), (11, 1, 1.5), (13, 1, 1), + (15, 1, 5), (16, 1, 1), (17, 1, 1), (18, 1, 1), + (4, 2, 1.25), (9, 2, 3.04), (10, 2, 1.5), (11, 2, 2.5), (13, 2, 1), + (15, 2, 5), (16, 2, 1), (17, 2, 1), (18, 2, 1), + (4, 3, 1.85), (9, 3, 4.22), (8, 3, 1.5), (13, 3, 1), (15, 3, 5), + (16, 3, 1), (17, 3, 1), (18, 3, 1), + (5, 4, 3.33), (19, 4, 6), + (2, 5, 2.9), (3, 5, 1.7), (6, 5, 2.7), (7, 5, 1.8), + ] + self.cursor.executemany( + 'INSERT OR IGNORE INTO material_consumption (material_id, product_id, quantity_required) VALUES (?, ?, ?)', + consumption_data + ) + + self.conn.commit() + print("✅ Данные импортированы") + return True + + except Exception as e: + print(f"❌ Не удалось импортировать данные: {e}") + return False + + def get_materials_with_required_quantity(self): + """Получение списка материалов с расчетом требуемого количества""" + try: + query = """ + SELECT m.id, m.name, mt.name AS type_name, m.price, m.quantity_in_stock, + m.min_quantity, m.unit, COALESCE(SUM(mc.quantity_required), 0) AS total_required + FROM materials m + JOIN material_types mt ON m.type_id = mt.id + LEFT JOIN material_consumption mc ON m.id = mc.material_id + GROUP BY m.id, m.name, mt.name, m.price, m.quantity_in_stock, m.min_quantity, m.unit + ORDER BY m.name + """ + self.cursor.execute(query) + return self.cursor.fetchall() + except Exception as e: + print(f"❌ Не удалось загрузить данные материалов: {e}") + return [] + + def get_material_statistics(self): + """Получение статистики по материалам""" + try: + query = """ + SELECT + COUNT(*) as total_materials, + SUM(quantity_in_stock) as total_quantity, + SUM(price * quantity_in_stock) as total_value, + COUNT(CASE WHEN quantity_in_stock < min_quantity THEN 1 END) as low_stock_count + FROM materials + """ + self.cursor.execute(query) + return self.cursor.fetchone() + except Exception as e: + print(f"❌ Не удалось получить статистику: {e}") + return None + + def get_low_stock_materials(self): + """Получение материалов с низким запасом""" + try: + query = """ + SELECT m.name, m.quantity_in_stock, m.min_quantity, m.unit, mt.name as type_name + FROM materials m + JOIN material_types mt ON m.type_id = mt.id + WHERE m.quantity_in_stock < m.min_quantity + ORDER BY m.quantity_in_stock / m.min_quantity + """ + self.cursor.execute(query) + return self.cursor.fetchall() + except Exception as e: + print(f"❌ Не удалось получить материалы с низким запасом: {e}") + return [] + + def close(self): + """Закрытие соединения с базой данных""" + if self.conn: + self.conn.close() + print("✅ Соединение с базой данных закрыто") + + +def print_materials_table(materials): + """Красиво выводит таблицу материалов в консоль""" + if not materials: + print("❌ Нет данных о материалах") + return + + print("\n" + "=" * 120) + print( + f"{'Наименование':<30} {'Тип':<15} {'Цена':<10} {'На складе':<10} {'Мин.запас':<10} {'Ед.изм.':<8} {'Требуется':<10}") + print("=" * 120) + + low_stock_count = 0 + critical_stock_count = 0 + + for material in materials: + material_id, name, type_name, price, stock, min_stock, unit, required = material + + # Цветовая индикация + if stock < min_stock * 0.5: + indicator = "🔴" # Критический запас + critical_stock_count += 1 + elif stock < min_stock: + indicator = "🟡" # Низкий запас + low_stock_count += 1 + else: + indicator = "🟢" # Нормальный запас + + print( + f"{indicator} {name:<28} {type_name:<13} {price:<9.2f} {stock:<9.2f} {min_stock:<9.2f} {unit:<6} {required:<9.2f}") + + print("=" * 120) + print( + f"Всего материалов: {len(materials)} | 🟡 Низкий запас: {low_stock_count} | 🔴 Критический запас: {critical_stock_count}") + + +def print_statistics(stats): + """Выводит статистику""" + if not stats: + print("❌ Не удалось получить статистику") + return + + total_materials, total_quantity, total_value, low_stock_count = stats + + print("\n" + "=" * 50) + print("📊 СТАТИСТИКА СКЛАДА") + print("=" * 50) + print(f"📦 Всего позиций: {total_materials}") + print(f"📐 Общее количество: {total_quantity:.2f}") + print(f"💰 Общая стоимость: {total_value:,.2f} руб.") + print(f"⚠️ Материалов с низким запасом: {low_stock_count}") + print("=" * 50) + + +def print_low_stock_materials(materials): + """Выводит материалы с низким запасом""" + if not materials: + print("✅ Все материалы в норме! Нет позиций с низким запасом.") + return + + print("\n" + "=" * 80) + print("⚠️ МАТЕРИАЛЫ С НИЗКИМ ЗАПАСОМ") + print("=" * 80) + + for material in materials: + name, stock, min_stock, unit, type_name = material + percentage = (stock / min_stock * 100) + + if percentage < 50: + indicator = "🔴" + status = "КРИТИЧЕСКИЙ" + else: + indicator = "🟡" + status = "НИЗКИЙ" + + print( + f"{indicator} {name:<40} {stock:>6.1f} {unit:<4} (мин. {min_stock:>6.1f}) - {percentage:>5.1f}% - {status}") + + +def export_to_csv(materials, filename=None): + """Экспорт данных в CSV""" + if not filename: + filename = f"material_stock_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + + try: + with open(filename, 'w', encoding='utf-8') as f: + # Заголовки + f.write("Наименование;Тип;Цена;На складе;Мин.запас;Ед.изм.;Требуется\n") + + # Данные + for material in materials: + _, name, type_name, price, stock, min_stock, unit, required = material + f.write(f"{name};{type_name};{price:.2f};{stock:.2f};{min_stock:.2f};{unit};{required:.2f}\n") + + print(f"✅ Данные экспортированы в файл: {filename}") + return True + + except Exception as e: + print(f"❌ Ошибка при экспорте: {e}") + return False + + +def safe_input(prompt): + """Безопасный ввод с обработкой прерывания""" + try: + return input(prompt).strip() + except (KeyboardInterrupt, EOFError): + print("\n\n👋 Программа прервана пользователем. До свидания!") + sys.exit(0) + + +def main(): + """Главная функция приложения""" + print("🏭 СИСТЕМА УПРАВЛЕНИЯ СКЛАДОМ МАТЕРИАЛОВ") + print("Компания 'Образ плюс' - производство офисной мебели") + print("=" * 60) + + # Инициализация базы данных + db = FurnitureDatabase() + if not db.connect(): + return + + if not db.create_tables(): + return + + # Проверяем, есть ли данные в базе + db.cursor.execute("SELECT COUNT(*) FROM materials") + if db.cursor.fetchone()[0] == 0: + print("📥 Импорт начальных данных...") + db.import_initial_data() + + while True: + print("\n" + "=" * 40) + print("📋 ГЛАВНОЕ МЕНЮ") + print("=" * 40) + print("1. 📊 Показать все материалы") + print("2. ⚠️ Показать материалы с низким запасом") + print("3. 📈 Показать статистику") + print("4. 📥 Экспорт в CSV") + print("5. ❌ Выход") + print("=" * 40) + + choice = safe_input("\nВыберите действие (1-5): ") + + if choice == '1': + print("\n🔄 Загрузка данных...") + materials = db.get_materials_with_required_quantity() + print_materials_table(materials) + + elif choice == '2': + print("\n🔄 Поиск материалов с низким запасом...") + low_stock_materials = db.get_low_stock_materials() + print_low_stock_materials(low_stock_materials) + + elif choice == '3': + print("\n🔄 Расчет статистики...") + stats = db.get_material_statistics() + print_statistics(stats) + + elif choice == '4': + print("\n🔄 Подготовка данных для экспорта...") + materials = db.get_materials_with_required_quantity() + if materials: + filename = safe_input("Введите имя файла (или нажмите Enter для автоимени): ") + if not filename: + filename = None + export_to_csv(materials, filename) + else: + print("❌ Нет данных для экспорта") + + elif choice == '5': + print("\n👋 До свидания! Спасибо за использование системы.") + break + + else: + print("❌ Неверный выбор. Пожалуйста, выберите от 1 до 5.") + + # Пауза перед следующим шагом + if choice != '5': + safe_input("\nНажмите Enter чтобы продолжить...") + + db.close() + + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(f"\n❌ Произошла непредвиденная ошибка: {e}") + print("Пожалуйста, перезапустите программу.") \ No newline at end of file