Обновления и новые функции: изменение шаблона клиента, обновление сигналов инвентаря, добавление снимков наборов и элементов заказа, обновление моделей заказов и продуктов
This commit is contained in:
@@ -59,10 +59,13 @@ def reserve_stock_on_order_create(sender, instance, created, **kwargs):
|
||||
|
||||
Процесс:
|
||||
1. Проверяем, новый ли заказ (создан только что)
|
||||
2. Для каждого товара в заказе создаем Reservation
|
||||
3. Статус резерва = 'reserved'
|
||||
4. Проверяем на существующие резервы (защита от дубликатов)
|
||||
2. Для обычных товаров - создаём резерв напрямую
|
||||
3. Для комплектов - резервируем компоненты (группируя одинаковые товары)
|
||||
4. Статус резерва = 'reserved'
|
||||
5. Проверяем на существующие резервы (защита от дубликатов)
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
if not created:
|
||||
return # Только для новых заказов
|
||||
|
||||
@@ -76,33 +79,63 @@ def reserve_stock_on_order_create(sender, instance, created, **kwargs):
|
||||
|
||||
# Для каждого товара в заказе
|
||||
for item in instance.items.all():
|
||||
# Определяем товар (может быть product или product_kit)
|
||||
product = item.product if item.product else item.product_kit
|
||||
if item.product:
|
||||
# Обычный товар - резервируем как раньше
|
||||
_create_or_update_reservation(item, item.product, warehouse, Decimal(str(item.quantity)))
|
||||
|
||||
if product:
|
||||
# ЗАЩИТА ОТ ДУБЛИКАТОВ: Проверяем, нет ли уже резерва для этой позиции
|
||||
existing_reservation = Reservation.objects.filter(
|
||||
order_item=item,
|
||||
product=product,
|
||||
warehouse=warehouse
|
||||
).first()
|
||||
elif item.product_kit and item.kit_snapshot:
|
||||
# Комплект - резервируем КОМПОНЕНТЫ из снимка
|
||||
# Группируем одинаковые товары для создания одного резерва
|
||||
product_quantities = defaultdict(Decimal)
|
||||
|
||||
if existing_reservation:
|
||||
# Резерв уже существует - обновляем его вместо создания нового
|
||||
existing_reservation.quantity = Decimal(str(item.quantity))
|
||||
existing_reservation.status = 'reserved'
|
||||
existing_reservation.save()
|
||||
else:
|
||||
# Резерва нет - создаем новый
|
||||
Reservation.objects.create(
|
||||
order_item=item,
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
quantity=Decimal(str(item.quantity)),
|
||||
status='reserved'
|
||||
for kit_item in item.kit_snapshot.items.select_related('original_product'):
|
||||
if kit_item.original_product:
|
||||
# Суммируем количество: qty компонента * qty комплектов в заказе
|
||||
product_quantities[kit_item.original_product_id] += (
|
||||
kit_item.quantity * Decimal(str(item.quantity))
|
||||
)
|
||||
|
||||
# Создаём по одному резерву на каждый уникальный товар
|
||||
from products.models import Product
|
||||
for product_id, total_qty in product_quantities.items():
|
||||
product = Product.objects.get(pk=product_id)
|
||||
_create_or_update_reservation(
|
||||
item, product, warehouse, total_qty, product_kit=item.product_kit
|
||||
)
|
||||
|
||||
|
||||
def _create_or_update_reservation(order_item, product, warehouse, quantity, product_kit=None):
|
||||
"""
|
||||
Вспомогательная функция для создания или обновления резерва.
|
||||
"""
|
||||
# Формируем фильтр для поиска существующего резерва
|
||||
filter_kwargs = {
|
||||
'order_item': order_item,
|
||||
'product': product,
|
||||
'warehouse': warehouse,
|
||||
}
|
||||
if product_kit:
|
||||
filter_kwargs['product_kit'] = product_kit
|
||||
|
||||
existing_reservation = Reservation.objects.filter(**filter_kwargs).first()
|
||||
|
||||
if existing_reservation:
|
||||
# Резерв уже существует - обновляем его
|
||||
existing_reservation.quantity = quantity
|
||||
existing_reservation.status = 'reserved'
|
||||
existing_reservation.save()
|
||||
else:
|
||||
# Резерва нет - создаём новый
|
||||
Reservation.objects.create(
|
||||
order_item=order_item,
|
||||
product=product,
|
||||
product_kit=product_kit,
|
||||
warehouse=warehouse,
|
||||
quantity=quantity,
|
||||
status='reserved'
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Order)
|
||||
@transaction.atomic
|
||||
def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
||||
@@ -907,47 +940,95 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs):
|
||||
return # Для витринных комплектов не создаем новые резервы
|
||||
|
||||
# Обычный товар или постоянный комплект
|
||||
# Ищем резерв для этой позиции в ЛЮБОМ статусе (не только 'reserved')
|
||||
# Ищем резервы для этой позиции в ЛЮБОМ статусе (не только 'reserved')
|
||||
# КРИТИЧНО: Убран фильтр status='reserved' для предотвращения дубликатов
|
||||
reservation = Reservation.objects.filter(
|
||||
order_item=instance
|
||||
).first()
|
||||
reservations = Reservation.objects.filter(order_item=instance)
|
||||
|
||||
if reservations.exists():
|
||||
if instance.product:
|
||||
# Обычный товар - один резерв, обновляем количество напрямую
|
||||
reservation = reservations.first()
|
||||
old_quantity = reservation.quantity
|
||||
reservation.quantity = Decimal(str(instance.quantity))
|
||||
reservation.save(update_fields=['quantity'])
|
||||
|
||||
if reservation:
|
||||
# Резерв существует - обновляем ТОЛЬКО количество
|
||||
# НЕ меняем статус! (может быть 'converted_to_sale', 'reserved', 'released')
|
||||
old_quantity = reservation.quantity
|
||||
reservation.quantity = Decimal(str(instance.quantity))
|
||||
reservation.save(update_fields=['quantity'])
|
||||
|
||||
logger.info(
|
||||
f"✓ Резерв #{reservation.id} обновлён: quantity {old_quantity} → {reservation.quantity} "
|
||||
f"(статус: {reservation.status}, OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
else:
|
||||
# Резерва нет - создаем новый ТОЛЬКО для обычных товаров (не комплектов)
|
||||
if not instance.product:
|
||||
# Это обычный комплект (не витринный) - не создаем резерв на уровне комплекта
|
||||
logger.info(
|
||||
f"ℹ Обычный комплект '{instance.product_kit.name}': резервы управляются на уровне компонентов"
|
||||
f"✓ Резерв #{reservation.id} обновлён: quantity {old_quantity} → {reservation.quantity} "
|
||||
f"(статус: {reservation.status}, OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
return
|
||||
|
||||
# Создаем резерв для обычного товара
|
||||
|
||||
elif instance.product_kit and instance.kit_snapshot:
|
||||
# Комплект - несколько резервов (по одному на компонент)
|
||||
# Обновляем количество каждого резерва пропорционально изменению количества комплектов
|
||||
from collections import defaultdict
|
||||
|
||||
# Собираем ожидаемые количества компонентов
|
||||
product_quantities = defaultdict(Decimal)
|
||||
for kit_item in instance.kit_snapshot.items.select_related('original_product'):
|
||||
if kit_item.original_product:
|
||||
product_quantities[kit_item.original_product_id] = (
|
||||
kit_item.quantity * Decimal(str(instance.quantity))
|
||||
)
|
||||
|
||||
# Обновляем каждый резерв
|
||||
for reservation in reservations:
|
||||
expected_qty = product_quantities.get(reservation.product_id, Decimal('0'))
|
||||
if expected_qty > 0:
|
||||
old_quantity = reservation.quantity
|
||||
reservation.quantity = expected_qty
|
||||
reservation.save(update_fields=['quantity'])
|
||||
|
||||
logger.info(
|
||||
f"✓ Резерв #{reservation.id} ({reservation.product.name}) обновлён: "
|
||||
f"quantity {old_quantity} → {reservation.quantity} "
|
||||
f"(статус: {reservation.status}, OrderItem #{instance.id})"
|
||||
)
|
||||
else:
|
||||
# Резерва нет - создаем новый
|
||||
from collections import defaultdict
|
||||
|
||||
warehouse = instance.order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
|
||||
if warehouse:
|
||||
product = instance.product
|
||||
if not warehouse:
|
||||
logger.warning(f"⚠ Не найден склад для резервирования (OrderItem #{instance.id})")
|
||||
return
|
||||
|
||||
if instance.product:
|
||||
# Обычный товар - создаем один резерв
|
||||
reservation = Reservation.objects.create(
|
||||
order_item=instance,
|
||||
product=product,
|
||||
product=instance.product,
|
||||
warehouse=warehouse,
|
||||
quantity=Decimal(str(instance.quantity)),
|
||||
status='reserved'
|
||||
)
|
||||
|
||||
|
||||
logger.info(
|
||||
f"✓ Создан новый резерв #{reservation.id}: {product.name}, quantity={reservation.quantity} "
|
||||
f"✓ Создан новый резерв #{reservation.id}: {instance.product.name}, quantity={reservation.quantity} "
|
||||
f"(OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
|
||||
elif instance.product_kit and instance.kit_snapshot:
|
||||
# Обычный комплект - резервируем КОМПОНЕНТЫ из снимка
|
||||
# Группируем одинаковые товары
|
||||
product_quantities = defaultdict(Decimal)
|
||||
|
||||
for kit_item in instance.kit_snapshot.items.select_related('original_product'):
|
||||
if kit_item.original_product:
|
||||
product_quantities[kit_item.original_product_id] += (
|
||||
kit_item.quantity * Decimal(str(instance.quantity))
|
||||
)
|
||||
|
||||
# Создаём резервы для компонентов
|
||||
from products.models import Product
|
||||
for product_id, total_qty in product_quantities.items():
|
||||
product = Product.objects.get(pk=product_id)
|
||||
_create_or_update_reservation(
|
||||
instance, product, warehouse, total_qty, product_kit=instance.product_kit
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"✓ Комплект '{instance.product_kit.name}': создано {len(product_quantities)} резервов компонентов "
|
||||
f"(OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user