366 lines
14 KiB
Python
366 lines
14 KiB
Python
"""
|
||
Модуль управления складскими запасами интернет-магазина
|
||
Содержит функции для работы с товарами, фильтрации, анализа и отчётности
|
||
"""
|
||
|
||
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() |