Исправление ошибок в редактировании комплектов: валидация, верстка, расчет цены

This commit is contained in:
2026-01-21 10:16:37 +03:00
parent 2dc36b3d01
commit e138a28475
12 changed files with 1447 additions and 658 deletions

View File

@@ -163,7 +163,11 @@ class ProductKit(BaseProductEntity):
total = Decimal('0')
for item in self.kit_items.all():
qty = item.quantity or Decimal('1')
if item.product:
if item.sales_unit:
# Для sales_unit используем цену единицы продажи
unit_price = item.sales_unit.actual_price or Decimal('0')
total += unit_price * qty
elif item.product:
# Используем зафиксированную цену если есть, иначе актуальную цену товара
if item.unit_price is not None:
unit_price = item.unit_price
@@ -213,7 +217,11 @@ class ProductKit(BaseProductEntity):
# Пересчитаем базовую цену из компонентов
total = Decimal('0')
for item in self.kit_items.all():
if item.product:
if item.sales_unit:
actual_price = item.sales_unit.actual_price or Decimal('0')
qty = item.quantity or Decimal('1')
total += actual_price * qty
elif item.product:
actual_price = item.product.actual_price or Decimal('0')
qty = item.quantity or Decimal('1')
total += actual_price * qty
@@ -382,8 +390,8 @@ class ProductKit(BaseProductEntity):
class KitItem(models.Model):
"""
Состав комплекта: связь между ProductKit и Product или ProductVariantGroup.
Позиция может быть либо конкретным товаром, либо группой вариантов.
Состав комплекта: связь между ProductKit и Product, ProductVariantGroup или ProductSalesUnit.
Позиция может быть либо конкретным товаром, либо группой вариантов, либо конкретной единицей продажи.
"""
kit = models.ForeignKey(ProductKit, on_delete=models.CASCADE, related_name='kit_items',
verbose_name="Комплект")
@@ -403,6 +411,14 @@ class KitItem(models.Model):
related_name='kit_items',
verbose_name="Группа вариантов"
)
sales_unit = models.ForeignKey(
'ProductSalesUnit',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='kit_items',
verbose_name="Единица продажи"
)
quantity = models.DecimalField(max_digits=10, decimal_places=3, null=True, blank=True, verbose_name="Количество")
unit_price = models.DecimalField(
max_digits=10,
@@ -428,21 +444,46 @@ class KitItem(models.Model):
return f"{self.kit.name} - {self.get_display_name()}"
def clean(self):
"""Валидация: должен быть указан либо product, либо variant_group (но не оба)"""
if self.product and self.variant_group:
raise ValidationError(
"Нельзя указывать одновременно товар и группу вариантов. Выберите что-то одно."
)
if not self.product and not self.variant_group:
"""Валидация: должна быть указана группа вариантов ИЛИ (товар [плюс опционально единица продажи])"""
has_variant = bool(self.variant_group)
has_product = bool(self.product)
has_sales_unit = bool(self.sales_unit)
# 1. Проверка на пустоту
if not (has_variant or has_product or has_sales_unit):
raise ValidationError(
"Необходимо указать либо товар, либо группу вариантов."
)
# 2. Несовместимость: Группа вариантов VS Товар/Единица
if has_variant and (has_product or has_sales_unit):
raise ValidationError(
"Нельзя указывать группу вариантов одновременно с товаром или единицей продажи."
)
# 3. Зависимость: Если есть sales_unit, должен быть product
if has_sales_unit and not has_product:
raise ValidationError(
"Если указана единица продажи, должен быть выбран соответствующий товар."
)
# 4. Проверка принадлежности
if has_sales_unit and has_product and self.sales_unit.product != self.product:
raise ValidationError(
"Выбранная единица продажи не принадлежит указанному товару."
)
def get_display_name(self):
"""Возвращает строку для отображения названия компонента"""
if self.variant_group:
# Приоритет: сначала единица продажи, затем товар, затем группа вариантов
if self.sales_unit:
return f"[Единица продажи] {self.sales_unit.name}"
elif self.product:
return self.product.name
elif self.variant_group:
return f"[Варианты] {self.variant_group.name}"
return self.product.name if self.product else "Не указан"
return "Не указан"
def has_priorities_set(self):
"""Проверяет, настроены ли приоритеты замены для данного компонента"""
@@ -452,10 +493,16 @@ class KitItem(models.Model):
"""
Возвращает список доступных товаров для этого компонента.
Если указана единица продажи - возвращает товар, к которому она относится.
Если указан конкретный товар - возвращает его.
Если указаны приоритеты - возвращает товары в порядке приоритета.
Если не указаны приоритеты - возвращает все активные товары из группы вариантов.
"""
# Приоритет: сначала единица продажи, затем товар, затем группа вариантов
if self.sales_unit:
# Если указана единица продажи, возвращаем товар, к которому она относится
return [self.sales_unit.product]
if self.product:
# Если указан конкретный товар, возвращаем только его
return [self.product]