InventoryManager/inventory_manager.py
2026-04-04 00:27:11 +03:00

366 lines
14 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 csv
import os
from typing import List, Dict, Optional
from collections import Counter
def load_products(filepath: str) -> List[Dict]:
"""
Загружает данные о товарах из CSV-файла.
Args:
filepath: Путь к CSV-файлу
Returns:
Список словарей с данными о товарах. При отсутствии файла возвращает пустой список
"""
products = []
try:
with open(filepath, 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
product = {
'product_id': int(row['product_id']),
'name': row['name'],
'category': row['category'],
'quantity': int(row['quantity']),
'price': float(row['price']),
'supplier': row['supplier']
}
products.append(product)
except FileNotFoundError:
print(f"Ошибка: Файл {filepath} не найден")
return []
except Exception as e:
print(f"Ошибка при чтении файла: {e}")
return []
return products
def validate_product(product: Dict) -> bool:
"""
Проверяет корректность данных товара.
Args:
product: Словарь с данными товара
Returns:
True, если все поля присутствуют и корректны, иначе False
"""
required_fields = ['product_id', 'name', 'category', 'quantity', 'price', 'supplier']
for field in required_fields:
if field not in product:
return False
try:
if not isinstance(product['product_id'], int) or product['product_id'] <= 0:
return False
if not isinstance(product['quantity'], int) or product['quantity'] < 0:
return False
if not isinstance(product['price'], (int, float)) or product['price'] <= 0:
return False
if not isinstance(product['name'], str) or not product['name'].strip():
return False
if not isinstance(product['category'], str) or not product['category'].strip():
return False
if not isinstance(product['supplier'], str) or not product['supplier'].strip():
return False
except (TypeError, ValueError):
return False
return True
def filter_by_category(products: List[Dict], category: str) -> List[Dict]:
"""
Фильтрует товары по категории (регистронезависимо).
Args:
products: Список товаров
category: Название категории для фильтрации
Returns:
Новый список товаров указанной категории
"""
return [product for product in products
if product['category'].lower() == category.lower()]
def filter_by_quantity(products: List[Dict], min_quantity: int) -> List[Dict]:
"""
Фильтрует товары с остатком не ниже заданного.
Args:
products: Список товаров
min_quantity: Минимальное количество
Returns:
Отфильтрованный список товаров
"""
return [product for product in products
if product['quantity'] >= min_quantity]
def calculate_total_value(products: List[Dict]) -> float:
"""
Вычисляет общую стоимость всех товаров на складе.
Args:
products: Список товаров
Returns:
Сумма произведений quantity * price для каждого товара
"""
return sum(product['quantity'] * product['price'] for product in products)
def find_low_stock(products: List[Dict], threshold: int) -> List[Dict]:
"""
Находит товары с критически низким остатком.
Args:
products: Список товаров
threshold: Пороговое значение
Returns:
Список товаров с quantity <= threshold, отсортированный по возрастанию остатка
"""
low_stock = [product for product in products if product['quantity'] <= threshold]
return sorted(low_stock, key=lambda x: x['quantity'])
def group_by_supplier(products: List[Dict]) -> Dict[str, Dict]:
"""
Группирует товары по поставщикам.
Args:
products: Список товаров
Returns:
Словарь, где ключ — поставщик, значение — словарь со статистикой
"""
suppliers = {}
for product in products:
supplier = product['supplier']
if supplier not in suppliers:
suppliers[supplier] = {
'total_products': 0,
'total_value': 0.0
}
suppliers[supplier]['total_products'] += 1
suppliers[supplier]['total_value'] += product['quantity'] * product['price']
return suppliers
def get_most_expensive_product(products: List[Dict]) -> Optional[Dict]:
"""
Находит самый дорогой товар.
Args:
products: Список товаров
Returns:
Словарь товара с максимальной ценой, при пустом списке возвращает None
"""
if not products:
return None
return max(products, key=lambda x: x['price'])
def search_by_name(products: List[Dict], keyword: str) -> List[Dict]:
"""
Ищет товары по ключевому слову в названии (регистронезависимо).
Args:
products: Список товаров
keyword: Ключевое слово для поиска
Returns:
Список товаров, содержащих ключевое слово в названии
"""
keyword_lower = keyword.lower()
return [product for product in products
if keyword_lower in product['name'].lower()]
def save_inventory_report(products: List[Dict], filepath: str) -> bool:
"""
Сохраняет инвентаризационный отчёт в текстовый файл.
Args:
products: Список товаров
filepath: Путь для сохранения отчёта
Returns:
True при успешной записи, False при ошибке
"""
try:
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as file:
total_products = len(products)
total_value = calculate_total_value(products)
file.write("ИНВЕНТАРИЗАЦИОННЫЙ ОТЧЁТ\n")
file.write("=" * 50 + "\n\n")
file.write(f"Общее количество товаров: {total_products}\n")
file.write(f"Общая стоимость склада: {total_value:,.2f} руб.\n\n")
categories = Counter([p['category'] for p in products])
top_categories = categories.most_common(3)
file.write("Топ-3 категории по количеству товаров:\n")
for i, (category, count) in enumerate(top_categories, 1):
file.write(f" {i}. {category}: {count} товаров\n")
low_stock_items = [p for p in products if p['quantity'] < 10]
if low_stock_items:
file.write(f"\nТовары с остатком менее 10 единиц:\n")
for item in low_stock_items:
file.write(f" - {item['name']}: {item['quantity']} шт.\n")
else:
file.write(f"\nТоваров с остатком менее 10 единиц нет\n")
return True
except Exception as e:
print(f"Ошибка при сохранении отчёта: {e}")
return False
def create_products_csv():
"""
Создаёт файл data/products.csv с тестовыми данными
"""
os.makedirs('data', exist_ok=True)
data = [
['product_id', 'name', 'category', 'quantity', 'price', 'supplier'],
[1, 'Ноутбук Lenovo ThinkPad', 'Электроника', 15, 85000, 'ООО Компьютерные технологии'],
[2, 'Мышь Logitech MX Master', 'Электроника', 45, 12000, 'ООО Компьютерные технологии'],
[3, 'Стул офисный Ergohuman', 'Мебель', 8, 35000, 'МебельПро'],
[4, 'Монитор Dell UltraSharp', 'Электроника', 12, 45000, 'ООО Компьютерные технологии'],
[5, 'Клавиатура Mechanical', 'Электроника', 0, 8000, 'Аксессуары Плюс'],
[6, 'Стол письменный', 'Мебель', 5, 12000, 'МебельПро'],
[7, 'Блокнот A5', 'Канцелярия', 120, 250, 'КанцТрейд'],
[8, 'Ручка шариковая', 'Канцелярия', 500, 50, 'КанцТрейд']
]
with open('data/products.csv', 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
print("✓ Файл data/products.csv успешно создан!")
def main():
"""
Главная функция программы, выполняющая все шаги по анализу складских запасов.
"""
print("=" * 60)
print("УПРАВЛЕНИЕ СКЛАДСКИМИ ЗАПАСАМИ ИНТЕРНЕТ-МАГАЗИНА")
print("=" * 60)
print("\n[Подготовка] Создание файла с данными...")
create_products_csv()
print("\n1. Загрузка данных...")
products = load_products('data/products.csv')
if not products:
print("Не удалось загрузить данные. Программа завершена.")
return
print(f"Загружено товаров: {len(products)}")
print("\n2. Проверка корректности данных...")
valid_products = []
invalid_count = 0
for product in products:
if validate_product(product):
valid_products.append(product)
else:
invalid_count += 1
print(f" Некорректный товар: {product.get('name', 'Неизвестно')}")
print(f"Корректных товаров: {len(valid_products)}")
print(f"Некорректных записей пропущено: {invalid_count}")
if not valid_products:
print("Нет корректных товаров для анализа. Программа завершена.")
return
print("\n3. Общая статистика склада:")
print(f" Общее количество товаров на складе: {len(valid_products)}")
total_value = calculate_total_value(valid_products)
print(f" Общая стоимость склада: {total_value:,.2f} руб.")
print("\n4. Товары с низким остатком (порог 10 ед.):")
low_stock_items = find_low_stock(valid_products, 10)
if low_stock_items:
for item in low_stock_items:
print(f" - {item['name']}: {item['quantity']} шт.")
else:
print(" Товаров с низким остатком нет")
print("\n5. Анализ категории 'Электроника':")
electronics = filter_by_category(valid_products, "Электроника")
print(f" Количество товаров в категории: {len(electronics)}")
most_expensive = get_most_expensive_product(electronics)
if most_expensive:
print(f" Самый дорогой товар: {most_expensive['name']} "
f"({most_expensive['price']:,.2f} руб.)")
else:
print(" Товаров в категории нет")
print("\n6. Товары с остатком более 50 единиц:")
high_stock = filter_by_quantity(valid_products, 51)
print(f" Количество таких товаров: {len(high_stock)}")
print("\n7. Поиск товаров по ключевому слову 'ноутбук':")
search_results = search_by_name(valid_products, "ноутбук")
if search_results:
for product in search_results:
print(f" - {product['name']}")
else:
print(" Товары не найдены")
print("\n8. Статистика по поставщикам:")
suppliers_stats = group_by_supplier(valid_products)
for supplier, stats in suppliers_stats.items():
print(f" {supplier}:")
print(f" - Количество товаров: {stats['total_products']}")
print(f" - Общая стоимость: {stats['total_value']:,.2f} руб.")
print("\n9. Сохранение инвентаризационного отчёта...")
report_path = 'reports/inventory_report.txt'
if save_inventory_report(valid_products, report_path):
print(f" Отчёт успешно сохранён в файл: {report_path}")
else:
print(" Ошибка при сохранении отчёта")
print("\n" + "=" * 60)
print("ПРОГРАММА УСПЕШНО ЗАВЕРШЕНА")
print("=" * 60)
if __name__ == "__main__":
main()