Добавлен автоматический промежуточный переход cancelled → draft → completed
Проблема: - Прямой переход cancelled → completed вызывал race condition между сигналами - Сигналы срабатывали в непредсказуемом порядке - ShowcaseItem и Reservation не успевали корректно обработаться - Букеты оставались в неправильном статусе Решение ПОД КАПОТОМ: - orders/models/order.py: Order.save() теперь перехватывает прямой переход cancelled → completed - Автоматически разбивает на два последовательных шага: 1. cancelled → draft: reserve_stock_on_uncancellation возвращает резервы и букеты в reserved 2. draft → completed: create_sale_on_order_completion корректно финализирует в sold - Каждый шаг вызывает super().save() в отдельной транзакции - Сигналы срабатывают последовательно в правильном порядке Преимущества: - Пользователь не замечает промежуточный переход (происходит мгновенно) - Не нужны сложные проверки порядка срабатывания сигналов - Гарантируется корректная работа всех существующих сигналов - Решение элегантное и не требует изменений в сигналах Flow теперь гарантированно работает: cancelled → draft → completed: Шаг 1: ShowcaseItem available → reserved ✅ Шаг 2: ShowcaseItem reserved → sold ✅ Шаг 1: Reservation order_item=None → привязаны ✅ Шаг 2: Sale создаются, резервы converted_to_sale ✅
This commit is contained in:
@@ -178,6 +178,8 @@ class Order(models.Model):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Генерируем уникальный номер заказа при создании (начиная с 100 для 3-значного поиска)
|
# Генерируем уникальный номер заказа при создании (начиная с 100 для 3-значного поиска)
|
||||||
if not self.order_number:
|
if not self.order_number:
|
||||||
@@ -188,6 +190,65 @@ class Order(models.Model):
|
|||||||
else:
|
else:
|
||||||
self.order_number = 100
|
self.order_number = 100
|
||||||
|
|
||||||
|
# === АВТОМАТИЧЕСКИЙ ПРОМЕЖУТОЧНЫЙ ПЕРЕХОД: cancelled → draft → completed ===
|
||||||
|
# При прямом переходе из отрицательного (cancelled) в положительный (completed) статус,
|
||||||
|
# делаем промежуточный переход через нейтральный статус 'draft'.
|
||||||
|
# Это гарантирует корректную работу всех сигналов:
|
||||||
|
# 1. cancelled → draft: витринные букеты available → reserved
|
||||||
|
# 2. draft → completed: резервы и букеты корректно финализируются в sold
|
||||||
|
if self.pk: # Только при редактировании
|
||||||
|
try:
|
||||||
|
old_instance = Order.objects.get(pk=self.pk)
|
||||||
|
old_status = old_instance.status
|
||||||
|
new_status = self.status
|
||||||
|
|
||||||
|
# Проверяем: переход от отрицательного к положительному?
|
||||||
|
if (old_status and old_status.is_negative_end and
|
||||||
|
new_status and new_status.is_positive_end):
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"🔄 Заказ #{self.order_number}: Обнаружен прямой переход "
|
||||||
|
f"{old_status.name} (отрицательный) → {new_status.name} (положительный). "
|
||||||
|
f"Выполняем автоматический промежуточный переход через 'draft'..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем статус 'draft'
|
||||||
|
try:
|
||||||
|
draft_status = OrderStatus.objects.get(code='draft', is_system=True)
|
||||||
|
except OrderStatus.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
f"Невозможно выполнить переход из '{old_status.name}' в '{new_status.name}': "
|
||||||
|
f"системный статус 'draft' не найден. Обратитесь к администратору."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Сохраняем целевой статус для второго шага
|
||||||
|
target_status = new_status
|
||||||
|
|
||||||
|
# ШАГ 1: cancelled → draft
|
||||||
|
logger.info(f" 📍 Шаг 1/2: {old_status.name} → draft")
|
||||||
|
self.status = draft_status
|
||||||
|
with transaction.atomic():
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Обновляем old_instance для следующего шага
|
||||||
|
old_instance.refresh_from_db()
|
||||||
|
|
||||||
|
# ШАГ 2: draft → completed
|
||||||
|
logger.info(f" 📍 Шаг 2/2: draft → {target_status.name}")
|
||||||
|
self.status = target_status
|
||||||
|
with transaction.atomic():
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"✅ Заказ #{self.order_number}: Промежуточный переход завершён успешно. "
|
||||||
|
f"Итоговый статус: {target_status.name}"
|
||||||
|
)
|
||||||
|
return # Выходим, т.к. save() уже вызван дважды
|
||||||
|
|
||||||
|
except Order.DoesNotExist:
|
||||||
|
# Заказ ещё не создан в БД (не должно произойти, но на всякий случай)
|
||||||
|
pass
|
||||||
|
|
||||||
# === ВАЛИДАЦИЯ: Проверяем доступность витринных комплектов ===
|
# === ВАЛИДАЦИЯ: Проверяем доступность витринных комплектов ===
|
||||||
# При переходе ИЗ cancelled к любому не-отменённому статусу
|
# При переходе ИЗ cancelled к любому не-отменённому статусу
|
||||||
if self.pk: # Только при редактировании
|
if self.pk: # Только при редактировании
|
||||||
|
|||||||
Reference in New Issue
Block a user