feat(products): add support for product sales units
Add new models UnitOfMeasure and ProductSalesUnit to enable selling products in different units (e.g., bunches, kg). Update Product model with base_unit field and methods for unit conversions and availability. Extend Sale, Reservation, and OrderItem models with sales_unit fields and snapshots. Modify SaleProcessor to handle quantity conversions. Include admin interfaces for managing units. Add corresponding database migrations.
This commit is contained in:
@@ -55,22 +55,25 @@ class SaleProcessor:
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create_sale(product, warehouse, quantity, sale_price, order=None, document_number=None):
|
||||
def create_sale(product, warehouse, quantity, sale_price, order=None, document_number=None, sales_unit=None):
|
||||
"""
|
||||
Создать операцию продажи и произвести FIFO-списание.
|
||||
|
||||
Процесс:
|
||||
1. Создаем запись Sale
|
||||
2. Списываем товар по FIFO из партий
|
||||
3. Фиксируем распределение в SaleBatchAllocation для аудита
|
||||
2. Конвертируем количество в базовые единицы (если указана sales_unit)
|
||||
3. Списываем товар по FIFO из партий
|
||||
4. Фиксируем распределение в SaleBatchAllocation для аудита
|
||||
|
||||
Args:
|
||||
product: объект Product
|
||||
warehouse: объект Warehouse
|
||||
quantity: Decimal - количество товара
|
||||
quantity: Decimal - количество товара (в единицах продажи, если указана sales_unit)
|
||||
sale_price: Decimal - цена продажи
|
||||
order: (опционально) объект Order
|
||||
document_number: (опционально) номер документа
|
||||
sales_unit: (опционально) объект ProductSalesUnit - единица продажи.
|
||||
Если указана, quantity конвертируется в базовые единицы товара.
|
||||
|
||||
Returns:
|
||||
Объект Sale
|
||||
@@ -84,25 +87,36 @@ class SaleProcessor:
|
||||
if sale_price < 0:
|
||||
raise ValueError("Цена продажи не может быть отрицательной")
|
||||
|
||||
# Конвертируем количество в базовые единицы, если указана единица продажи
|
||||
if sales_unit:
|
||||
quantity_base = sales_unit.convert_to_base(quantity)
|
||||
unit_name_snapshot = sales_unit.name
|
||||
else:
|
||||
quantity_base = quantity
|
||||
unit_name_snapshot = ''
|
||||
|
||||
# Создаем запись Sale
|
||||
# ВАЖНО: Устанавливаем processed=True сразу, чтобы сигнал process_sale_fifo не сработал
|
||||
# (списание делаем вручную ниже, чтобы избежать двойного списания)
|
||||
sale = Sale.objects.create(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
quantity=quantity,
|
||||
quantity=quantity, # В единицах продажи (для истории/отчётов)
|
||||
quantity_base=quantity_base, # В базовых единицах (для списания)
|
||||
sale_price=sale_price,
|
||||
order=order,
|
||||
document_number=document_number,
|
||||
processed=True # Сразу отмечаем как обработанную
|
||||
processed=True, # Сразу отмечаем как обработанную
|
||||
sales_unit=sales_unit,
|
||||
unit_name_snapshot=unit_name_snapshot
|
||||
)
|
||||
|
||||
try:
|
||||
# Списываем товар по FIFO
|
||||
# Списываем товар по FIFO в БАЗОВЫХ единицах
|
||||
# exclude_order позволяет не считать резервы этого заказа как занятые
|
||||
# (резервы переводятся в converted_to_sale ПОСЛЕ создания Sale)
|
||||
allocations = StockBatchManager.write_off_by_fifo(
|
||||
product, warehouse, quantity, exclude_order=order
|
||||
product, warehouse, quantity_base, exclude_order=order
|
||||
)
|
||||
|
||||
# Фиксируем распределение для аудита
|
||||
|
||||
Reference in New Issue
Block a user