Исправлен race condition в списании партий товара
- Добавлен параметр lock в get_batches_for_fifo() для блокировки строк - Используется select_for_update() в write_off_by_fifo() для предотвращения параллельной перезаписи quantity при одновременном списании из одной партии - Защита от потери данных при параллельном завершении заказов
This commit is contained in:
@@ -20,7 +20,7 @@ class StockBatchManager:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_batches_for_fifo(product, warehouse):
|
def get_batches_for_fifo(product, warehouse, lock=False):
|
||||||
"""
|
"""
|
||||||
Получить все активные партии товара на складе для FIFO списания.
|
Получить все активные партии товара на складе для FIFO списания.
|
||||||
Возвращает ВСЕ партии с quantity > 0, отсортированные по created_at.
|
Возвращает ВСЕ партии с quantity > 0, отсортированные по created_at.
|
||||||
@@ -29,17 +29,25 @@ class StockBatchManager:
|
|||||||
Args:
|
Args:
|
||||||
product: объект Product
|
product: объект Product
|
||||||
warehouse: объект Warehouse
|
warehouse: объект Warehouse
|
||||||
|
lock: bool - использовать ли select_for_update() для блокировки строк
|
||||||
|
(защита от race condition при параллельном списании)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
QuerySet отсортированных партий
|
QuerySet отсортированных партий
|
||||||
"""
|
"""
|
||||||
return StockBatch.objects.filter(
|
queryset = StockBatch.objects.filter(
|
||||||
product=product,
|
product=product,
|
||||||
warehouse=warehouse,
|
warehouse=warehouse,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
quantity__gt=0 # Только партии с остатком
|
quantity__gt=0 # Только партии с остатком
|
||||||
).order_by('created_at') # FIFO: старые первыми
|
).order_by('created_at') # FIFO: старые первыми
|
||||||
|
|
||||||
|
if lock:
|
||||||
|
# Блокируем строки для предотвращения race condition
|
||||||
|
queryset = queryset.select_for_update()
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_batch(product, warehouse, quantity, cost_price):
|
def create_batch(product, warehouse, quantity, cost_price):
|
||||||
"""
|
"""
|
||||||
@@ -127,8 +135,10 @@ class StockBatchManager:
|
|||||||
|
|
||||||
total_reserved = reservation_filter.aggregate(total=Sum('quantity_base'))['total'] or Decimal('0')
|
total_reserved = reservation_filter.aggregate(total=Sum('quantity_base'))['total'] or Decimal('0')
|
||||||
|
|
||||||
# Получаем партии по FIFO
|
# Получаем партии по FIFO с блокировкой строк
|
||||||
batches = StockBatchManager.get_batches_for_fifo(product, warehouse)
|
# Используем select_for_update() для предотвращения race condition
|
||||||
|
# при параллельном списании из одной партии
|
||||||
|
batches = StockBatchManager.get_batches_for_fifo(product, warehouse, lock=True)
|
||||||
|
|
||||||
# Проходим партии, списывая товар
|
# Проходим партии, списывая товар
|
||||||
# Если есть exclude_transformation, сначала списываем из зарезервированного товара трансформации
|
# Если есть exclude_transformation, сначала списываем из зарезервированного товара трансформации
|
||||||
|
|||||||
Reference in New Issue
Block a user