fix(inventory): создавать Sale после применения скидок в POS checkout
Добавлен механизм skip_sale_creation на базе thread-local storage для управления моментом создания Sale через сигнал. Проблема: сигнал create_sale_on_order_completion срабатывал при Order.objects.create(status=completed) до применения скидок. Решение: пропускать сигнал во время создания заказа, затем явно создавать Sale после применения всех скидок через order.save(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
Подключаются при создании, изменении и удалении заказов.
|
||||
"""
|
||||
|
||||
import threading
|
||||
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
|
||||
@@ -19,6 +20,26 @@ from inventory.services import SaleProcessor
|
||||
from inventory.services.batch_manager import StockBatchManager
|
||||
# InventoryProcessor больше не используется в сигналах - обработка вызывается явно через view
|
||||
|
||||
# ============================================================================
|
||||
# Thread-local storage для временных флагов управления сигналами
|
||||
# ============================================================================
|
||||
_skip_sale_creation = threading.local()
|
||||
|
||||
|
||||
def skip_sale_creation():
|
||||
"""Установить флаг для пропуска создания Sale в сигнале."""
|
||||
_skip_sale_creation.value = True
|
||||
|
||||
|
||||
def reset_sale_creation():
|
||||
"""Сбросить флаг пропуска создания Sale."""
|
||||
_skip_sale_creation.value = False
|
||||
|
||||
|
||||
def is_skip_sale_creation():
|
||||
"""Проверить, установлен ли флаг пропуска создания Sale."""
|
||||
return getattr(_skip_sale_creation, 'value', False)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# pre_save сигнал для сохранения предыдущего статуса Order
|
||||
@@ -278,10 +299,22 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
||||
3. Для каждого товара создаем Sale (автоматический FIFO-список)
|
||||
4. ТОЛЬКО после успешного создания Sale обновляем резервы на 'converted_to_sale'
|
||||
5. Обновляем флаг is_returned
|
||||
|
||||
ПРИМЕЧАНИЕ: Если у Order установлен атрибут skip_sale_creation=True,
|
||||
создание Sale пропускается (используется в POS для создания Sale после применения скидок).
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# === ПРОВЕРКА: Пропуск создания Sale по флагу ===
|
||||
# Используется в POS checkout, где Sale создаётся явно после применения скидок
|
||||
if is_skip_sale_creation():
|
||||
logger.info(
|
||||
f"ℹ️ Заказ {instance.order_number}: skip_sale_creation=True (thread-local), "
|
||||
f"пропускаем автоматическое создание Sale"
|
||||
)
|
||||
return
|
||||
|
||||
if created:
|
||||
return # Только для обновлений
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import logging
|
||||
from products.models import Product, ProductCategory, ProductKit, KitItem
|
||||
from inventory.models import Showcase, Reservation, Warehouse, Stock
|
||||
from inventory.services import ShowcaseManager
|
||||
from inventory.signals import skip_sale_creation, reset_sale_creation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1526,13 +1527,17 @@ def pos_checkout(request):
|
||||
|
||||
# Атомарная операция
|
||||
with db_transaction.atomic():
|
||||
# ВАЖНО: Устанавливаем флаг для пропуска автоматического создания Sale в сигнале.
|
||||
# Sale будет создан ЯВНО после применения всех скидок.
|
||||
skip_sale_creation()
|
||||
|
||||
# 1. Создаём заказ с текущей датой и временем в локальном часовом поясе (Europe/Minsk)
|
||||
from django.utils import timezone as tz
|
||||
from orders.models import Delivery
|
||||
now_utc = tz.now() # Текущее время в UTC
|
||||
now_local = tz.localtime(now_utc) # Конвертируем в локальный часовой пояс (Europe/Minsk)
|
||||
current_time = now_local.time() # Извлекаем время в минском часовом поясе
|
||||
|
||||
|
||||
order = Order.objects.create(
|
||||
customer=customer,
|
||||
status=completed_status, # Сразу "Выполнен"
|
||||
@@ -1714,6 +1719,11 @@ def pos_checkout(request):
|
||||
cart_key = f'pos:cart:{request.user.id}:{warehouse_id}'
|
||||
cache.delete(cart_key)
|
||||
|
||||
# 7. Явно создаём Sale после применения всех скидок
|
||||
# Сбрасываем флаг пропуска и вызываем save() для активации сигнала
|
||||
reset_sale_creation()
|
||||
order.save() # Триггерит сигнал create_sale_on_order_completion
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'order_number': order.order_number,
|
||||
|
||||
Reference in New Issue
Block a user