Добавлена защита от повторного списания + команда исправления дубликатов
Проблема: Сигнал post_save срабатывает несколько раз, создавая дубликаты Sale для одного заказа. Решения: 1. Добавлена проверка Sale.objects.filter(order=instance).exists() перед созданием продаж (inventory/signals.py:74-75) 2. Создана management команда fix_duplicate_sales для исправления существующих дубликатов 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
167
check_order.py
Normal file
167
check_order.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для проверки заказа и его складских операций
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Настройка Django
|
||||
sys.path.insert(0, '/c/Users/team_/Desktop/test_qwen/myproject')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from orders.models import Order
|
||||
from inventory.models import Reservation, Sale, Stock, StockBatch, Warehouse
|
||||
|
||||
# Устанавливаем схему тенанта
|
||||
connection.set_schema('buba')
|
||||
|
||||
# Получаем заказ
|
||||
try:
|
||||
order = Order.objects.get(order_number='101')
|
||||
|
||||
print("=" * 60)
|
||||
print(f"ЗАКАЗ: {order.order_number}")
|
||||
print("=" * 60)
|
||||
print(f"ID: {order.id}")
|
||||
print(f"Статус: {order.status}")
|
||||
print(f"Склад самовывоза: {order.pickup_warehouse}")
|
||||
print(f"Создан: {order.created_at}")
|
||||
print(f"Обновлен: {order.updated_at}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ТОВАРЫ В ЗАКАЗЕ (OrderItem)")
|
||||
print("=" * 60)
|
||||
items = order.items.all()
|
||||
if items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
print(f" ID: {item.id}")
|
||||
print(f" Товар: {product.name if product else 'НЕТ ТОВАРА!'}")
|
||||
print(f" Количество: {item.quantity}")
|
||||
print(f" Цена: {item.price}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ ТОВАРОВ В ЗАКАЗЕ!")
|
||||
|
||||
print("=" * 60)
|
||||
print("РЕЗЕРВЫ (Reservation)")
|
||||
print("=" * 60)
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
if reservations:
|
||||
for res in reservations:
|
||||
print(f" ID: {res.id}")
|
||||
print(f" Товар: {res.product.name}")
|
||||
print(f" Склад: {res.warehouse.name}")
|
||||
print(f" Количество: {res.quantity}")
|
||||
print(f" Статус: {res.status}")
|
||||
print(f" Создан: {res.reserved_at}")
|
||||
if res.converted_at:
|
||||
print(f" Конвертирован: {res.converted_at}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ РЕЗЕРВОВ!")
|
||||
|
||||
print("=" * 60)
|
||||
print("ПРОДАЖИ (Sale)")
|
||||
print("=" * 60)
|
||||
sales = Sale.objects.filter(order=order)
|
||||
if sales:
|
||||
for sale in sales:
|
||||
print(f" ID: {sale.id}")
|
||||
print(f" Товар: {sale.product.name}")
|
||||
print(f" Склад: {sale.warehouse.name}")
|
||||
print(f" Количество: {sale.quantity}")
|
||||
print(f" Цена продажи: {sale.sale_price}")
|
||||
print(f" Обработано: {sale.processed}")
|
||||
print(f" Дата: {sale.date}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ ПРОДАЖ (Sale не созданы!)")
|
||||
|
||||
print("=" * 60)
|
||||
print("СКЛАДЫ")
|
||||
print("=" * 60)
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
if warehouse:
|
||||
print(f" Используется склад: {warehouse.name} (ID: {warehouse.id})")
|
||||
print(f" Активен: {warehouse.is_active}")
|
||||
else:
|
||||
print(" ⚠️ НЕТ ДОСТУПНЫХ СКЛАДОВ!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ОСТАТКИ НА СКЛАДЕ (StockBatch)")
|
||||
print("=" * 60)
|
||||
if warehouse and items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
print(f"\nТовар: {product.name}")
|
||||
batches = StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
)
|
||||
if batches:
|
||||
total = sum(b.quantity for b in batches)
|
||||
print(f" Всего доступно: {total}")
|
||||
for batch in batches:
|
||||
print(f" Партия ID {batch.id}: {batch.quantity} шт (себестоимость: {batch.cost_price})")
|
||||
else:
|
||||
print(f" ⚠️ НЕТ ПАРТИЙ НА СКЛАДЕ!")
|
||||
|
||||
# Проверяем Stock
|
||||
try:
|
||||
stock = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
print(f" Stock.quantity_available: {stock.quantity_available}")
|
||||
print(f" Stock.quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" Stock.quantity_free: {stock.quantity_free}")
|
||||
except Stock.DoesNotExist:
|
||||
print(f" ⚠️ Stock запись не существует!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ДИАГНОСТИКА")
|
||||
print("=" * 60)
|
||||
|
||||
# Проверка условий для срабатывания сигнала
|
||||
if order.status != 'completed':
|
||||
print(" ⚠️ СТАТУС НЕ 'completed'! Сигнал НЕ СРАБОТАЕТ")
|
||||
print(f" Текущий статус: {order.status}")
|
||||
else:
|
||||
print(" ✅ Статус 'completed' - условие выполнено")
|
||||
|
||||
if not warehouse:
|
||||
print(" ⚠️ НЕТ СКЛАДА! Сигнал выйдет на строке 76-77")
|
||||
else:
|
||||
print(f" ✅ Склад есть: {warehouse.name}")
|
||||
|
||||
if not items:
|
||||
print(" ⚠️ НЕТ ТОВАРОВ! Сигнал ничего не сделает")
|
||||
else:
|
||||
print(f" ✅ Товаров в заказе: {items.count()}")
|
||||
|
||||
# Проверка наличия товара на складе
|
||||
if warehouse and items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
batches = StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
)
|
||||
total = sum(b.quantity for b in batches)
|
||||
if total < item.quantity:
|
||||
print(f" ⚠️ НЕДОСТАТОЧНО ТОВАРА '{product.name}'!")
|
||||
print(f" Нужно: {item.quantity}, доступно: {total}")
|
||||
else:
|
||||
print(f" ✅ Товара '{product.name}' достаточно: {total} >= {item.quantity}")
|
||||
|
||||
except Order.DoesNotExist:
|
||||
print(f"⚠️ ЗАКАЗ С НОМЕРОМ '101' НЕ НАЙДЕН В ТЕНАНТЕ 'buba'!")
|
||||
except Exception as e:
|
||||
print(f"❌ ОШИБКА: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
74
myproject/check_order_101.py
Normal file
74
myproject/check_order_101.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from django.db import connection
|
||||
from orders.models import Order
|
||||
from inventory.models import Reservation, Sale, Stock, StockBatch, Warehouse
|
||||
|
||||
# Устанавливаем схему тенанта
|
||||
connection.set_schema('buba')
|
||||
|
||||
# Получаем заказ
|
||||
try:
|
||||
order = Order.objects.get(order_number='101')
|
||||
|
||||
print("=" * 60)
|
||||
print(f"ЗАКАЗ: {order.order_number}")
|
||||
print("=" * 60)
|
||||
print(f"ID: {order.id}")
|
||||
print(f"Статус: '{order.status}'")
|
||||
print(f"Склад самовывоза: {order.pickup_warehouse}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ТОВАРЫ В ЗАКАЗЕ")
|
||||
print("=" * 60)
|
||||
items = order.items.all()
|
||||
print(f"Количество товаров: {items.count()}")
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
print(f" - {product.name if product else 'НЕТ!'}: {item.quantity} шт, цена: {item.price}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("РЕЗЕРВЫ")
|
||||
print("=" * 60)
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
print(f"Количество резервов: {reservations.count()}")
|
||||
for res in reservations:
|
||||
print(f" - {res.product.name}: {res.quantity} шт, статус: '{res.status}'")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ПРОДАЖИ (Sale)")
|
||||
print("=" * 60)
|
||||
sales = Sale.objects.filter(order=order)
|
||||
print(f"Количество продаж: {sales.count()}")
|
||||
if sales:
|
||||
for sale in sales:
|
||||
print(f" - {sale.product.name}: {sale.quantity} шт, обработано: {sale.processed}")
|
||||
else:
|
||||
print(" ⚠️ ПРОДАЖ НЕТ!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ДИАГНОСТИКА")
|
||||
print("=" * 60)
|
||||
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
|
||||
print(f"Статус заказа: '{order.status}' (тип: {type(order.status).__name__})")
|
||||
print(f"Условие: order.status != 'completed' = {order.status != 'completed'}")
|
||||
print(f"Склад: {warehouse.name if warehouse else 'НЕ НАЙДЕН!'}")
|
||||
|
||||
# Проверяем наличие товара
|
||||
if warehouse and items:
|
||||
print("\nОстатки на складе:")
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
batches = StockBatch.objects.filter(product=product, warehouse=warehouse, is_active=True)
|
||||
total = sum(b.quantity for b in batches)
|
||||
print(f" - {product.name}: {total} доступно (нужно {item.quantity})")
|
||||
if total < item.quantity:
|
||||
print(f" ⚠️ НЕДОСТАТОЧНО! (не хватает {item.quantity - total})")
|
||||
|
||||
except Order.DoesNotExist:
|
||||
print("⚠️ ЗАКАЗ 101 НЕ НАЙДЕН В ТЕНАНТЕ 'buba'!")
|
||||
except Exception as e:
|
||||
print(f"❌ ОШИБКА: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
60
myproject/fix_order_119.py
Normal file
60
myproject/fix_order_119.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Скрипт для исправления дубликатов продаж в заказе 119
|
||||
"""
|
||||
from django.db import connection, transaction
|
||||
from orders.models import Order
|
||||
from inventory.models import Sale, SaleBatchAllocation, Stock, StockBatch
|
||||
|
||||
connection.set_schema('buba')
|
||||
|
||||
order = Order.objects.get(order_number='119')
|
||||
sales = Sale.objects.filter(order=order).order_by('date')
|
||||
|
||||
print(f"Заказ {order.order_number}: найдено {sales.count()} продаж")
|
||||
|
||||
if sales.count() <= 1:
|
||||
print("Дубликатов нет, всё в порядке")
|
||||
else:
|
||||
# Оставляем первую продажу, остальные удаляем
|
||||
first_sale = sales.first()
|
||||
duplicate_sales = sales.exclude(id=first_sale.id)
|
||||
|
||||
print(f"\nОставляем продажу ID {first_sale.id}")
|
||||
print(f"Удаляем {duplicate_sales.count()} дубликатов:")
|
||||
|
||||
with transaction.atomic():
|
||||
for sale in duplicate_sales:
|
||||
print(f" - Продажа ID {sale.id}: {sale.product.name} x {sale.quantity}")
|
||||
|
||||
# Получаем SaleBatchAllocation для восстановления товара
|
||||
allocations = SaleBatchAllocation.objects.filter(sale=sale)
|
||||
|
||||
# Восстанавливаем товар в партиях
|
||||
for alloc in allocations:
|
||||
batch = alloc.batch
|
||||
print(f" Восстанавливаем партию ID {batch.id}: +{alloc.quantity}")
|
||||
batch.quantity += alloc.quantity
|
||||
batch.is_active = True
|
||||
batch.save()
|
||||
|
||||
# Удаляем продажу (каскадно удалятся и SaleBatchAllocation)
|
||||
sale.delete()
|
||||
|
||||
# Обновляем Stock
|
||||
for item in order.items.all():
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
if warehouse:
|
||||
stock, _ = Stock.objects.get_or_create(product=product, warehouse=warehouse)
|
||||
stock.refresh_from_batches()
|
||||
print(f"\nStock обновлен для {product.name}:")
|
||||
print(f" quantity_available: {stock.quantity_available}")
|
||||
print(f" quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" quantity_free: {stock.quantity_free}")
|
||||
|
||||
print("\n✅ Дубликаты удалены, товар восстановлен на складе")
|
||||
|
||||
# Проверяем результат
|
||||
sales_after = Sale.objects.filter(order=order)
|
||||
print(f"\nПосле исправления: {sales_after.count()} продаж")
|
||||
@@ -70,6 +70,10 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
||||
if instance.status.code != 'completed':
|
||||
return # Только для статуса 'completed'
|
||||
|
||||
# Защита от повторного списания: проверяем, не созданы ли уже Sale для этого заказа
|
||||
if Sale.objects.filter(order=instance).exists():
|
||||
return # Продажи уже созданы, выходим
|
||||
|
||||
# Определяем склад (используем склад самовывоза из заказа или первый активный)
|
||||
warehouse = instance.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
|
||||
|
||||
76
myproject/orders/management/commands/fix_duplicate_sales.py
Normal file
76
myproject/orders/management/commands/fix_duplicate_sales.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection, transaction
|
||||
from orders.models import Order
|
||||
from inventory.models import Sale, SaleBatchAllocation, Stock, StockBatch, Warehouse
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Исправляет дубликаты продаж для указанного заказа'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('order_number', type=str, help='Номер заказа')
|
||||
parser.add_argument('--tenant', type=str, default='buba', help='Схема тенанта')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
order_number = options['order_number']
|
||||
tenant = options['tenant']
|
||||
|
||||
connection.set_schema(tenant)
|
||||
|
||||
try:
|
||||
order = Order.objects.get(order_number=order_number)
|
||||
except Order.DoesNotExist:
|
||||
self.stdout.write(self.style.ERROR(f'Заказ {order_number} не найден в тенанте {tenant}'))
|
||||
return
|
||||
|
||||
sales = Sale.objects.filter(order=order).order_by('date')
|
||||
|
||||
self.stdout.write(f"Заказ {order.order_number}: найдено {sales.count()} продаж")
|
||||
|
||||
if sales.count() <= 1:
|
||||
self.stdout.write(self.style.SUCCESS("Дубликатов нет, всё в порядке"))
|
||||
return
|
||||
|
||||
# Оставляем первую продажу, остальные удаляем
|
||||
first_sale = sales.first()
|
||||
duplicate_sales = sales.exclude(id=first_sale.id)
|
||||
|
||||
self.stdout.write(f"\nОставляем продажу ID {first_sale.id}")
|
||||
self.stdout.write(f"Удаляем {duplicate_sales.count()} дубликатов:")
|
||||
|
||||
with transaction.atomic():
|
||||
for sale in duplicate_sales:
|
||||
self.stdout.write(f" - Продажа ID {sale.id}: {sale.product.name} x {sale.quantity}")
|
||||
|
||||
# Получаем SaleBatchAllocation для восстановления товара
|
||||
allocations = SaleBatchAllocation.objects.filter(sale=sale)
|
||||
|
||||
# Восстанавливаем товар в партиях
|
||||
for alloc in allocations:
|
||||
batch = alloc.batch
|
||||
self.stdout.write(f" Восстанавливаем партию ID {batch.id}: +{alloc.quantity}")
|
||||
batch.quantity += alloc.quantity
|
||||
batch.is_active = True
|
||||
batch.save()
|
||||
|
||||
# Удаляем продажу (каскадно удалятся и SaleBatchAllocation)
|
||||
sale.delete()
|
||||
|
||||
# Обновляем Stock
|
||||
for item in order.items.all():
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
if warehouse:
|
||||
stock, _ = Stock.objects.get_or_create(product=product, warehouse=warehouse)
|
||||
stock.refresh_from_batches()
|
||||
self.stdout.write(f"\nStock обновлен для {product.name}:")
|
||||
self.stdout.write(f" quantity_available: {stock.quantity_available}")
|
||||
self.stdout.write(f" quantity_reserved: {stock.quantity_reserved}")
|
||||
self.stdout.write(f" quantity_free: {stock.quantity_free}")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("\n✅ Дубликаты удалены, товар восстановлен на складе"))
|
||||
|
||||
# Проверяем результат
|
||||
sales_after = Sale.objects.filter(order=order)
|
||||
self.stdout.write(f"\nПосле исправления: {sales_after.count()} продаж")
|
||||
Reference in New Issue
Block a user