21.11.1/main.py
Никита Бурулев 4cff0c83c6 Upload New File
2025-11-27 10:49:14 +00:00

437 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("Пожалуйста, перезапустите программу.")