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