fix(inventory, orders, pos): удалена зависимость от django-simple-history для tenant-моделей
- Добавлен pre_save сигнал для Order вместо django-simple-history - Переписаны все функции signals.py без использования instance.history - Заменены .username на .name|default:.email для CustomUser в шаблонах - Исправлен CSRF-токен в POS для работы с CSRF_USE_SESSIONS=True Теперь создание заказов работает корректно в мультитенантной архитектуре.
This commit is contained in:
@@ -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, выходим
|
||||
|
||||
Reference in New Issue
Block a user