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:
2026-01-02 02:09:44 +03:00
parent ca308ae2a2
commit 5b68f14bb4
11 changed files with 764 additions and 15 deletions

View File

@@ -122,6 +122,31 @@ class Sale(models.Model):
date = models.DateTimeField(auto_now_add=True, verbose_name="Дата операции")
processed = models.BooleanField(default=False, verbose_name="Обработана (FIFO применена)")
# === ПОЛЯ ДЛЯ ЕДИНИЦ ПРОДАЖИ ===
sales_unit = models.ForeignKey(
'products.ProductSalesUnit',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='sales',
verbose_name="Единица продажи"
)
quantity_base = models.DecimalField(
max_digits=10,
decimal_places=6,
null=True,
blank=True,
verbose_name="Количество в базовых единицах",
help_text="Количество в единицах хранения товара (для списания со склада)"
)
unit_name_snapshot = models.CharField(
max_length=100,
blank=True,
default='',
verbose_name="Название единицы (snapshot)",
help_text="Название единицы продажи на момент продажи"
)
class Meta:
verbose_name = "Продажа"
verbose_name_plural = "Продажи"
@@ -133,7 +158,8 @@ class Sale(models.Model):
]
def __str__(self):
return f"Продажа {self.product.name}: {self.quantity} шт @ {self.sale_price}"
unit_info = f" ({self.unit_name_snapshot})" if self.unit_name_snapshot else ""
return f"Продажа {self.product.name}: {self.quantity}{unit_info} @ {self.sale_price}"
class SaleBatchAllocation(models.Model):
@@ -469,6 +495,24 @@ class Reservation(models.Model):
help_text="Резерв для входного товара трансформации (черновик)"
)
# === ПОЛЯ ДЛЯ ЕДИНИЦ ПРОДАЖИ ===
sales_unit = models.ForeignKey(
'products.ProductSalesUnit',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='reservations',
verbose_name="Единица продажи"
)
quantity_base = models.DecimalField(
max_digits=10,
decimal_places=6,
null=True,
blank=True,
verbose_name="Количество в базовых единицах",
help_text="Количество в единицах хранения товара"
)
class Meta:
verbose_name = "Резервирование"
verbose_name_plural = "Резервирования"