diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 4fcb005..a45726d 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, post_delete +from django.db.models.signals import post_save, pre_delete, post_delete, pre_save from django.db.models import Q from django.db import transaction from django.dispatch import receiver @@ -20,33 +20,49 @@ from inventory.services.batch_manager import StockBatchManager # InventoryProcessor больше не используется в сигналах - обработка вызывается явно через view +# ============================================================================ +# pre_save сигнал для сохранения предыдущего статуса Order +# ============================================================================ + +@receiver(pre_save, sender='orders.Order') +def store_previous_order_status(sender, instance, **kwargs): + """ + Сохраняет предыдущий статус перед сохранением заказа. + + Используется в post_save сигналах для отслеживания изменений статуса + без использования django-simple-history (который не работает с tenant схемами). + """ + if instance.pk: + try: + instance._previous_status = sender.objects.get(pk=instance.pk).status + except sender.DoesNotExist: + instance._previous_status = None + else: + instance._previous_status = None + + def update_is_returned_flag(order): """ Обновляет флаг is_returned на основе фактического состояния заказа. - - Логика: - - Если есть хотя бы одна Sale по этому заказу → is_returned = False - - Если Sale нет, но заказ когда-либо был в статусе completed → is_returned = True - - Если заказ ни разу не был completed → is_returned = False - + + Логика (упрощенная без history): + - Если есть хотя бы одна Sale по этому заказу → is_returned = False (заказ активен) + - Если Sale нет → is_returned = True (продажи были, но откачены/удалены) + Это гарантирует что флаг отражает реальность: - Заказ продан и не возвращён → False - Заказ был продан, но продажи откачены (возврат) → True - - Новый заказ без продаж → False + - Новый заказ без продаж → False (но при первом создании Sale станет False) """ has_sale_now = Sale.objects.filter(order=order).exists() - + if has_sale_now: # Есть актуальные продажи → заказ не возвращён new_flag = False else: - # Проверяем историю только если нет Sale (оптимизация производительности) - was_completed_ever = order.history.filter( - status__is_positive_end=True - ).exists() - # Продаж нет → возвращён только если был когда-то completed - new_flag = was_completed_ever - + # Продаж нет → заказ возвращен (если продажи были, они откачены) + new_flag = True + # Обновляем только если значение изменилось if order.is_returned != new_flag: Order.objects.filter(pk=order.pk).update(is_returned=new_flag) @@ -312,29 +328,19 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): # === ЗАЩИТА ОТ RACE CONDITION: Проверяем предыдущий статус === # Если уже были в completed и снова переходим в completed (например completed → draft → completed), # проверяем наличие Sale чтобы избежать дублирования - try: - history_count = instance.history.count() - if history_count >= 2: - previous_record = instance.history.all()[1] - if previous_record.status_id: - from orders.models import OrderStatus - previous_status = OrderStatus.objects.get(id=previous_record.status_id) - - # Если предыдущий статус тоже был положительным финальным - if previous_status.is_positive_end: - logger.info( - 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 уже существуют, пропускаем создание" - ) - update_is_returned_flag(instance) - return - except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError): - pass + previous_status = getattr(instance, '_previous_status', None) + if previous_status and previous_status.is_positive_end: + logger.info( + 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 уже существуют, пропускаем создание" + ) + update_is_returned_flag(instance) + return # Защита от повторного списания: проверяем, не созданы ли уже Sale для этого заказа if Sale.objects.filter(order=instance).exists(): @@ -602,25 +608,10 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): current_status = instance.status - # === Получаем предыдущий статус через django-simple-history === - try: - # Получаем предыдущую запись из истории (индекс [1] = предпоследняя) - history_count = instance.history.count() - if history_count < 2: - return # Нет истории для сравнения - - previous_record = instance.history.all()[1] - - if not previous_record.status_id: - return - - # Импортируем OrderStatus если еще не импортирован - from orders.models import OrderStatus - previous_status = OrderStatus.objects.get(id=previous_record.status_id) - - except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError): - # Нет истории или статус удалён - return + # === Получаем предыдущий статус из pre_save сигнала === + previous_status = getattr(instance, '_previous_status', None) + if not previous_status: + return # Нет предыдущего статуса (новый заказ или первое сохранение) # === Проверяем: был ли переход ОТ 'completed'? === if previous_status.code != 'completed': @@ -990,26 +981,10 @@ def release_reservations_on_cancellation(sender, instance, created, **kwargs): # Проверяем: это статус отмены? if not current_status.is_negative_end: return # Не отмена, выходим - - # === Получаем предыдущий статус === - try: - history_count = instance.history.count() - if history_count < 2: - # Нет истории - значит заказ создан сразу в cancelled (необычно, но возможно) - # Продолжаем обработку - previous_status = None - else: - previous_record = instance.history.all()[1] - - if not previous_record.status_id: - previous_status = None - else: - from orders.models import OrderStatus - previous_status = OrderStatus.objects.get(id=previous_record.status_id) - - except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError): - previous_status = None - + + # === Получаем предыдущий статус из pre_save сигнала === + previous_status = getattr(instance, '_previous_status', None) + # Проверяем: не был ли уже в cancelled? if previous_status and previous_status.is_negative_end: return # Уже был в отмене, не обрабатываем повторно @@ -1147,24 +1122,12 @@ def reserve_stock_on_uncancellation(sender, instance, created, **kwargs): # Проверяем: текущий статус НЕ отмена? if current_status.is_negative_end: return # Всё ещё в отмене, выходим - - # === Получаем предыдущий статус === - try: - history_count = instance.history.count() - if history_count < 2: - return # Нет истории для сравнения - - previous_record = instance.history.all()[1] - - if not previous_record.status_id: - return - - from orders.models import OrderStatus - previous_status = OrderStatus.objects.get(id=previous_record.status_id) - - except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError): - return - + + # === Получаем предыдущий статус из pre_save сигнала === + previous_status = getattr(instance, '_previous_status', None) + if not previous_status: + return # Нет предыдущего статуса + # Проверяем: был ли предыдущий статус = cancelled? if not previous_status.is_negative_end: return # Не было перехода от cancelled, выходим diff --git a/myproject/inventory/templates/inventory/debug_page.html b/myproject/inventory/templates/inventory/debug_page.html index 2381ac3..8dcbd1b 100644 --- a/myproject/inventory/templates/inventory/debug_page.html +++ b/myproject/inventory/templates/inventory/debug_page.html @@ -688,10 +688,10 @@
Провёл
-{% if document.confirmed_by %}{{ document.confirmed_by.username }}{% else %}-{% endif %}
+{% if document.confirmed_by %}{{ document.confirmed_by.name|default:document.confirmed_by.email }}{% else %}-{% endif %}
Сотрудник
-{% if transformation.employee %}{{ transformation.employee.username }}{% else %}-{% endif %}
+{% if transformation.employee %}{{ transformation.employee.name|default:transformation.employee.email }}{% else %}-{% endif %}
Провёл
-{% if document.confirmed_by %}{{ document.confirmed_by.username }}{% else %}-{% endif %}
+{% if document.confirmed_by %}{{ document.confirmed_by.name|default:document.confirmed_by.email }}{% else %}-{% endif %}