diff --git a/.idea/InventoryManager.iml b/.idea/InventoryManager.iml
new file mode 100644
index 0000000..3d53442
--- /dev/null
+++ b/.idea/InventoryManager.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7a68218
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5ca585a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..94d17da
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1775248363717
+
+
+ 1775248363717
+
+
+
+
\ No newline at end of file
diff --git a/inventory_manager.py b/inventory_manager.py
new file mode 100644
index 0000000..b9e51db
--- /dev/null
+++ b/inventory_manager.py
@@ -0,0 +1,366 @@
+"""
+Модуль управления складскими запасами интернет-магазина
+Содержит функции для работы с товарами, фильтрации, анализа и отчётности
+"""
+
+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()
\ No newline at end of file
diff --git a/reports/inventory_report.txt b/reports/inventory_report.txt
new file mode 100644
index 0000000..e048a8f
--- /dev/null
+++ b/reports/inventory_report.txt
@@ -0,0 +1,15 @@
+ИНВЕНТАРИЗАЦИОННЫЙ ОТЧЁТ
+==================================================
+
+Общее количество товаров: 8
+Общая стоимость склада: 2,750,000.00 руб.
+
+Топ-3 категории по количеству товаров:
+ 1. Электроника: 4 товаров
+ 2. Мебель: 2 товаров
+ 3. Канцелярия: 2 товаров
+
+Товары с остатком менее 10 единиц:
+ - Стул офисный Ergohuman: 8 шт.
+ - Клавиатура Mechanical: 0 шт.
+ - Стол письменный: 5 шт.