Исправлено: резервирование и списание с учетом единиц продажи

- Проблема: при заказе 1 ветки резервировался 1 банч вместо 1/15

- Решение: используем quantity_in_base_units из OrderItem

- Изменения:

  - signals.py: reserve_stock_on_order_create использует quantity_in_base_units

  - signals.py: _create_or_update_reservation сохраняет sales_unit

  - signals.py: create_sale_on_order_completion берет quantity из резерва

  - sale_processor.py: уточнена документация параметра quantity
This commit is contained in:
2026-01-02 13:45:22 +03:00
parent 0d801680d7
commit c5e1ea06f9
2 changed files with 53 additions and 19 deletions

View File

@@ -149,8 +149,16 @@ def reserve_stock_on_order_create(sender, instance, created, **kwargs):
# Для каждого товара в заказе
for item in instance.items.all():
if item.product:
# Обычный товар - резервируем как раньше
_create_or_update_reservation(item, item.product, warehouse, Decimal(str(item.quantity)))
# Обычный товар - резервируем с учетом единиц продажи
# Используем quantity_in_base_units если заполнено, иначе quantity
reservation_quantity = item.quantity_in_base_units if item.quantity_in_base_units else Decimal(str(item.quantity))
_create_or_update_reservation(
item,
item.product,
warehouse,
reservation_quantity,
sales_unit=item.sales_unit
)
elif item.product_kit and item.kit_snapshot:
# Комплект - резервируем КОМПОНЕНТЫ из снимка
@@ -173,9 +181,17 @@ def reserve_stock_on_order_create(sender, instance, created, **kwargs):
)
def _create_or_update_reservation(order_item, product, warehouse, quantity, product_kit=None):
def _create_or_update_reservation(order_item, product, warehouse, quantity, product_kit=None, sales_unit=None):
"""
Вспомогательная функция для создания или обновления резерва.
Args:
order_item: Позиция заказа
product: Товар
warehouse: Склад
quantity: Количество (в базовых единицах)
product_kit: Комплект (для резервов компонентов)
sales_unit: Единица продажи (опционально)
"""
# Формируем фильтр для поиска существующего резерва
filter_kwargs = {
@@ -191,6 +207,8 @@ def _create_or_update_reservation(order_item, product, warehouse, quantity, prod
if existing_reservation:
# Резерв уже существует - обновляем его
existing_reservation.quantity = quantity
existing_reservation.quantity_base = quantity # quantity уже в базовых единицах
existing_reservation.sales_unit = sales_unit
existing_reservation.status = 'reserved'
existing_reservation.save()
else:
@@ -201,6 +219,8 @@ def _create_or_update_reservation(order_item, product, warehouse, quantity, prod
product_kit=product_kit,
warehouse=warehouse,
quantity=quantity,
quantity_base=quantity, # quantity уже в базовых единицах
sales_unit=sales_unit,
status='reserved'
)
@@ -377,17 +397,35 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
continue
try:
# Находим резерв для этого OrderItem
item_reservation = Reservation.objects.filter(
order_item=item,
product=product
).exclude(status='converted_to_sale').first()
if item_reservation:
# Используем quantity из резерва (уже в базовых единицах)
sale_quantity = item_reservation.quantity
else:
# Fallback: используем quantity_in_base_units из OrderItem
sale_quantity = item.quantity_in_base_units if item.quantity_in_base_units else Decimal(str(item.quantity))
logger.warning(
f"Не найден резерв для OrderItem {item.id}. "
f"Используем quantity_in_base_units: {sale_quantity}"
)
# Создаем Sale (с автоматическим FIFO-списанием)
sale = SaleProcessor.create_sale(
product=product,
warehouse=warehouse,
quantity=Decimal(str(item.quantity)),
quantity=sale_quantity,
sale_price=Decimal(str(item.price)),
order=instance,
document_number=instance.order_number
document_number=instance.order_number,
sales_unit=item.sales_unit # Передаем sales_unit в Sale
)
sales_created.append(sale)
logger.info(f"✓ Sale создан для {product.name}: {item.quantity} шт.")
logger.info(f"✓ Sale создан для {product.name}: {sale_quantity} шт. (базовых единиц)")
except ValueError as e:
# Логируем ошибку и прерываем процесс