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