diff --git a/myproject/inventory/services/batch_manager.py b/myproject/inventory/services/batch_manager.py index f7ff92a..7e1b3bc 100644 --- a/myproject/inventory/services/batch_manager.py +++ b/myproject/inventory/services/batch_manager.py @@ -20,7 +20,7 @@ class StockBatchManager: """ @staticmethod - def get_batches_for_fifo(product, warehouse): + def get_batches_for_fifo(product, warehouse, lock=False): """ Получить все активные партии товара на складе для FIFO списания. Возвращает ВСЕ партии с quantity > 0, отсортированные по created_at. @@ -29,16 +29,24 @@ class StockBatchManager: Args: product: объект Product warehouse: объект Warehouse + lock: bool - использовать ли select_for_update() для блокировки строк + (защита от race condition при параллельном списании) Returns: QuerySet отсортированных партий """ - return StockBatch.objects.filter( + queryset = StockBatch.objects.filter( product=product, warehouse=warehouse, is_active=True, quantity__gt=0 # Только партии с остатком ).order_by('created_at') # FIFO: старые первыми + + if lock: + # Блокируем строки для предотвращения race condition + queryset = queryset.select_for_update() + + return queryset @staticmethod 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') - # Получаем партии по FIFO - batches = StockBatchManager.get_batches_for_fifo(product, warehouse) + # Получаем партии по FIFO с блокировкой строк + # Используем select_for_update() для предотвращения race condition + # при параллельном списании из одной партии + batches = StockBatchManager.get_batches_for_fifo(product, warehouse, lock=True) # Проходим партии, списывая товар # Если есть exclude_transformation, сначала списываем из зарезервированного товара трансформации