diff --git a/fix_product_in_stock.py b/fix_product_in_stock.py new file mode 100644 index 0000000..828f1e9 --- /dev/null +++ b/fix_product_in_stock.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +""" +Скрипт для исправления статуса Product.in_stock на основе текущих остатков в Stock. +Пересчитывает in_stock для всех товаров, которые имеют остатки на складе. +""" + +import os +import sys +import django + +# Добавляем путь к myproject +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject')) +os.chdir(os.path.join(os.path.dirname(__file__), 'myproject')) + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') +django.setup() + +from decimal import Decimal +from products.models import Product +from inventory.models import Stock + +def fix_product_in_stock(): + """ + Исправить статус in_stock для всех товаров. + + Логика: + - Если для товара есть Stock с quantity_available > 0 → in_stock = True + - Если нет таких Stock или все пусты → in_stock = False + """ + print("\n" + "="*80) + print("ИСПРАВЛЕНИЕ СТАТУСА НАЛИЧИЯ ТОВАРОВ") + print("="*80 + "\n") + + # Получаем все товары + all_products = Product.all_objects.all() + total = all_products.count() + updated = 0 + no_stock = 0 + + print(f"Всего товаров в системе: {total}\n") + + for product in all_products: + # Проверяем есть ли остаток + has_stock = Stock.objects.filter( + product=product, + quantity_available__gt=0 + ).exists() + + # Обновляем in_stock если статус изменился + if product.in_stock != has_stock: + Product.all_objects.filter(id=product.id).update(in_stock=has_stock) + status = "ДОБАВЛЕН В НАЛИЧИЕ" if has_stock else "УБРАН ИЗ НАЛИЧИЯ" + print(f"✓ {product.name:50} → {status}") + updated += 1 + else: + if not has_stock: + no_stock += 1 + + print("\n" + "="*80) + print(f"РЕЗУЛЬТАТЫ:") + print(f" - Всего товаров: {total}") + print(f" - Обновлено: {updated}") + print(f" - Товаров без наличия: {no_stock}") + print("="*80 + "\n") + + # Проверка + print("ПРОВЕРКА:") + in_stock_count = Product.all_objects.filter(in_stock=True).count() + out_of_stock_count = Product.all_objects.filter(in_stock=False).count() + print(f" - Товаров в наличии: {in_stock_count}") + print(f" - Товаров не в наличии: {out_of_stock_count}") + print("="*80 + "\n") + +if __name__ == '__main__': + try: + fix_product_in_stock() + except Exception as e: + print(f"\nОШИБКА: {e}") + import traceback + traceback.print_exc() diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 3c188c7..6acb901 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -4,7 +4,7 @@ Подключаются при создании, изменении и удалении заказов. """ -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import post_save, pre_delete, post_delete from django.dispatch import receiver from django.utils import timezone from decimal import Decimal @@ -378,14 +378,11 @@ def update_product_in_stock_on_stock_change(sender, instance, created, **kwargs) _update_product_in_stock(instance.product_id) -@receiver(pre_delete, sender=Stock) +@receiver(post_delete, sender=Stock) def update_product_in_stock_on_stock_delete(sender, instance, **kwargs): """ Сигнал: При удалении Stock записи обновляем Product.in_stock. + Используем post_delete чтобы правильно проверить остались ли ещё Stock записи. """ product_id = instance.product_id - - # Сначала удаляем Stock, потом проверяем остаток - # Используем post_delete был бы лучше, но pre_delete сработает раньше - # Поэтому нужно проверить есть ли ещё остатки до удаления _update_product_in_stock(product_id) diff --git a/myproject/products/management/commands/update_product_in_stock.py b/myproject/products/management/commands/update_product_in_stock.py new file mode 100644 index 0000000..9d13546 --- /dev/null +++ b/myproject/products/management/commands/update_product_in_stock.py @@ -0,0 +1,83 @@ +""" +Команда управления для пересчёта статуса in_stock всех товаров. + +Использование: + python manage.py update_product_in_stock + +Описание: + Пересчитывает Product.in_stock на основе текущих остатков в Stock. + Товар считается в наличии если есть хотя бы один Stock с quantity_available > 0. +""" + +from django.core.management.base import BaseCommand +from django.db.models import Q +from products.models import Product +from inventory.models import Stock + + +class Command(BaseCommand): + help = 'Пересчитать статус in_stock для всех товаров на основе остатков в Stock' + + def add_arguments(self, parser): + parser.add_argument( + '--verbose', + action='store_true', + help='Выводить подробную информацию о каждом товаре', + ) + + def handle(self, *args, **options): + verbose = options.get('verbose', False) + + self.stdout.write(self.style.SUCCESS('\n' + '='*80)) + self.stdout.write(self.style.SUCCESS('ПЕРЕСЧЁТ СТАТУСА НАЛИЧИЯ ТОВАРОВ')) + self.stdout.write(self.style.SUCCESS('='*80 + '\n')) + + # Получаем все товары (включая удалённые, если нужно) + all_products = Product.all_objects.all() + total = all_products.count() + updated_count = 0 + in_stock_count = 0 + out_of_stock_count = 0 + + self.stdout.write(f'Всего товаров: {total}\n') + + for product in all_products: + # Проверяем есть ли остаток на любом складе + has_stock = Stock.objects.filter( + product=product, + quantity_available__gt=0 + ).exists() + + # Обновляем в_наличии если статус изменился + old_status = product.in_stock + if product.in_stock != has_stock: + Product.all_objects.filter(id=product.id).update(in_stock=has_stock) + updated_count += 1 + status_text = 'В НАЛИЧИИ' if has_stock else 'НЕ В НАЛИЧИИ' + old_status_text = 'В НАЛИЧИИ' if old_status else 'НЕ В НАЛИЧИИ' + + if verbose: + self.stdout.write( + self.style.SUCCESS(f'✓ {product.name:60} {old_status_text} → {status_text}') + ) + + # Подсчитываем статистику + if has_stock: + in_stock_count += 1 + else: + out_of_stock_count += 1 + + self.stdout.write(self.style.SUCCESS('\n' + '='*80)) + self.stdout.write(self.style.SUCCESS('РЕЗУЛЬТАТЫ:')) + self.stdout.write(self.style.SUCCESS(f' Всего товаров: {total}')) + self.stdout.write(self.style.SUCCESS(f' Обновлено: {updated_count}')) + self.stdout.write(self.style.SUCCESS(f' В наличии: {in_stock_count}')) + self.stdout.write(self.style.SUCCESS(f' Не в наличии: {out_of_stock_count}')) + self.stdout.write(self.style.SUCCESS('='*80 + '\n')) + + if updated_count > 0: + self.stdout.write( + self.style.SUCCESS(f'✓ Успешно обновлено {updated_count} товаров') + ) + else: + self.stdout.write(self.style.WARNING('Нет товаров для обновления')) diff --git a/myproject/products/migrations/0004_fix_product_in_stock.py b/myproject/products/migrations/0004_fix_product_in_stock.py new file mode 100644 index 0000000..475d200 --- /dev/null +++ b/myproject/products/migrations/0004_fix_product_in_stock.py @@ -0,0 +1,43 @@ +# Generated migration to fix Product.in_stock based on Stock.quantity_available + +from django.db import migrations + + +def update_product_in_stock(apps, schema_editor): + """ + Пересчитать Product.in_stock на основе Stock.quantity_available. + Товар в наличии если есть хотя бы один Stock с quantity_available > 0. + """ + Product = apps.get_model('products', 'Product') + Stock = apps.get_model('inventory', 'Stock') + + # Получаем товары которые должны быть в наличии + products_with_stock = Stock.objects.filter( + quantity_available__gt=0 + ).values_list('product_id', flat=True).distinct() + + products_with_stock_ids = set(products_with_stock) + + # Обновляем все товары + for product in Product.objects.all(): + new_status = product.id in products_with_stock_ids + if product.in_stock != new_status: + product.in_stock = new_status + product.save(update_fields=['in_stock']) + + +def reverse_update(apps, schema_editor): + """Обратная миграция: сбросить все in_stock в False""" + Product = apps.get_model('products', 'Product') + Product.objects.all().update(in_stock=False) + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0003_add_product_in_stock'), + ] + + operations = [ + migrations.RunPython(update_product_in_stock, reverse_update), + ]