Обновления и новые функции: изменение шаблона клиента, обновление сигналов инвентаря, добавление снимков наборов и элементов заказа, обновление моделей заказов и продуктов

This commit is contained in:
2025-12-18 00:14:24 +03:00
parent 56725e8092
commit 7b32cdcebf
9 changed files with 547 additions and 87 deletions

View File

@@ -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})"
)