diff --git a/myproject/inventory/services/sale_processor.py b/myproject/inventory/services/sale_processor.py index 16dea59..580a6ac 100644 --- a/myproject/inventory/services/sale_processor.py +++ b/myproject/inventory/services/sale_processor.py @@ -43,16 +43,17 @@ class SaleProcessor: item_discount = Decimal(str(item.discount_amount)) if item.discount_amount else Decimal('0') # Скидка на заказ (распределяется пропорционально доле позиции в заказе) - # subtotal - сумма позиций С учётом скидок на позиции (без скидки на заказ) + # Вычисляем как разницу между subtotal и total_amount (так как discount_amount может быть 0) order_total = order.subtotal if hasattr(order, 'subtotal') else Decimal('0') - if order_total > 0 and order.discount_amount: - order_discount = Decimal(str(order.discount_amount)) - item_order_discount = order_discount * (item_subtotal / order_total) - else: - item_order_discount = Decimal('0') + # Скидка = subtotal - (total_amount - delivery) (вычитаем доставку, если есть) + delivery_cost = Decimal(str(order.delivery.cost)) if hasattr(order, 'delivery') and order.delivery else Decimal('0') + order_discount = (order_total - (Decimal(str(order.total_amount)) - delivery_cost)) if order_total > 0 else Decimal('0') - total_discount = item_discount + item_order_discount + total_discount = item_discount + order_discount if total_discount and item.quantity > 0: + # Распределяем общую скидку пропорционально доле позиции + item_order_discount = order_discount * (item_subtotal / order_total) if order_total > 0 else Decimal('0') + total_discount = item_discount + item_order_discount price_with_discount = (item_subtotal - total_discount) / Decimal(str(item.quantity)) else: price_with_discount = Decimal(str(item.price)) diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 486ec54..e50a7d5 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -267,7 +267,7 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): создается операция Sale и резервы преобразуются в продажу. КРИТИЧНО: Резервы обновляются ТОЛЬКО ПОСЛЕ успешного создания Sale! - + ВАЛИДАЦИЯ: - Запрещаем переход в положительный финальный статус для заказов с is_returned=True, у которых нет резервов (товар уже продан в другом заказе). @@ -281,7 +281,7 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): """ import logging logger = logging.getLogger(__name__) - + if created: return # Только для обновлений @@ -325,6 +325,13 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): if not is_positive_end: return # Только для положительных финальных статусов (completed и т.п.) + # === ЗАЩИТА ОТ ПРЕЖДЕВРЕМЕННОГО СОЗДАНИЯ SALE === + # Проверяем, есть ли уже Sale для этого заказа + if Sale.objects.filter(order=instance).exists(): + logger.info(f"✓ Заказ {instance.order_number}: Sale уже существуют, пропускаем") + update_is_returned_flag(instance) + return + # === ЗАЩИТА ОТ RACE CONDITION: Проверяем предыдущий статус === # Если уже были в completed и снова переходим в completed (например completed → draft → completed), # проверяем наличие Sale чтобы избежать дублирования @@ -334,7 +341,6 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): f"🔄 Заказ {instance.order_number}: повторный переход в положительный статус " f"({previous_status.name} → {instance.status.name}). Проверяем Sale..." ) - # Проверяем есть ли уже Sale if Sale.objects.filter(order=instance).exists(): logger.info( f"✓ Заказ {instance.order_number}: Sale уже существуют, пропускаем создание" @@ -342,15 +348,6 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): update_is_returned_flag(instance) return - # Защита от повторного списания: проверяем, не созданы ли уже Sale для этого заказа - if Sale.objects.filter(order=instance).exists(): - # Продажи уже созданы — просто обновляем флаг is_returned и выходим - logger.info( - f"✓ Заказ {instance.order_number}: Sale уже существуют (проверка до создания)" - ) - update_is_returned_flag(instance) - return - # Проверяем наличие резервов для этого заказа # Ищем резервы в статусах 'reserved' (новые) и 'released' (после отката) # Исключаем уже обработанные 'converted_to_sale' @@ -484,19 +481,24 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): item_subtotal = Decimal(str(item.price)) * Decimal(str(item.quantity)) # Скидка на позицию - item_discount = Decimal(str(item.discount_amount)) if item.discount_amount else Decimal('0') + item_discount = Decimal(str(item.discount_amount)) if item.discount_amount is not None else Decimal('0') # Скидка на заказ (распределяется пропорционально доле позиции в заказе) - # subtotal - сумма позиций С учётом скидок на позиции (без скидки на заказ) + # ВАЖНО: Обновляем Order из БД, чтобы получить актуальный total_amount после применения скидок + instance.refresh_from_db() order_total = instance.subtotal if hasattr(instance, 'subtotal') else Decimal('0') - if order_total > 0 and instance.discount_amount: - order_discount = Decimal(str(instance.discount_amount)) + # Скидка = subtotal - (total_amount - delivery) (вычитаем доставку, если есть) + delivery_cost = Decimal(str(instance.delivery.cost)) if hasattr(instance, 'delivery') and instance.delivery else Decimal('0') + order_discount_amount = (order_total - (Decimal(str(instance.total_amount)) - delivery_cost)) if order_total > 0 else Decimal('0') + + if order_total > 0 and order_discount_amount > 0: # Пропорциональная часть скидки заказа для этой позиции - item_order_discount = order_discount * (item_subtotal / order_total) + item_order_discount = order_discount_amount * (item_subtotal / order_total) else: item_order_discount = Decimal('0') total_discount = item_discount + item_order_discount + if total_discount and item.quantity > 0: price_with_discount = (item_subtotal - total_discount) / Decimal(str(item.quantity)) else: