155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
from django.db import models
|
||
from django.core.exceptions import ValidationError
|
||
from products.models import Product, ProductKit
|
||
from simple_history.models import HistoricalRecords
|
||
from .order import Order
|
||
|
||
|
||
class OrderItem(models.Model):
|
||
"""
|
||
Позиция (товар) в заказе.
|
||
Хранит информацию о товаре или комплекте, количестве и цене на момент заказа.
|
||
"""
|
||
order = models.ForeignKey(
|
||
Order,
|
||
on_delete=models.CASCADE,
|
||
related_name='items',
|
||
verbose_name="Заказ"
|
||
)
|
||
|
||
# Товар или комплект (один из двух должен быть заполнен)
|
||
product = models.ForeignKey(
|
||
Product,
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='order_items',
|
||
verbose_name="Товар"
|
||
)
|
||
|
||
product_kit = models.ForeignKey(
|
||
ProductKit,
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='order_items',
|
||
verbose_name="Комплект товаров"
|
||
)
|
||
|
||
quantity = models.PositiveIntegerField(
|
||
default=1,
|
||
verbose_name="Количество"
|
||
)
|
||
|
||
price = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=2,
|
||
verbose_name="Цена за единицу",
|
||
help_text="Цена на момент создания заказа (фиксируется)"
|
||
)
|
||
|
||
is_custom_price = models.BooleanField(
|
||
default=False,
|
||
verbose_name="Цена изменена вручную",
|
||
help_text="True если цена была изменена вручную при создании заказа"
|
||
)
|
||
|
||
# Витринные продажи
|
||
is_from_showcase = models.BooleanField(
|
||
default=False,
|
||
verbose_name="С витрины",
|
||
help_text="True если товар продан с витрины"
|
||
)
|
||
|
||
showcase = models.ForeignKey(
|
||
'inventory.Showcase',
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='order_items',
|
||
verbose_name="Витрина",
|
||
help_text="Витрина, с которой был продан товар"
|
||
)
|
||
|
||
# Временные метки
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="Дата добавления"
|
||
)
|
||
|
||
# История изменений
|
||
history = HistoricalRecords()
|
||
|
||
class Meta:
|
||
verbose_name = "Позиция заказа"
|
||
verbose_name_plural = "Позиции заказа"
|
||
indexes = [
|
||
models.Index(fields=['order']),
|
||
models.Index(fields=['product']),
|
||
models.Index(fields=['product_kit']),
|
||
models.Index(fields=['is_from_showcase']),
|
||
models.Index(fields=['showcase']),
|
||
]
|
||
|
||
def __str__(self):
|
||
item_name = ""
|
||
if self.product:
|
||
item_name = self.product.name
|
||
elif self.product_kit:
|
||
item_name = self.product_kit.name
|
||
return f"{item_name} x{self.quantity} в заказе #{self.order.order_number}"
|
||
|
||
def clean(self):
|
||
"""Валидация модели"""
|
||
super().clean()
|
||
|
||
# Проверка: должен быть заполнен либо product, либо product_kit
|
||
if not self.product and not self.product_kit:
|
||
raise ValidationError(
|
||
'Необходимо указать либо товар, либо комплект товаров'
|
||
)
|
||
|
||
# Проверка: не должны быть заполнены оба поля одновременно
|
||
if self.product and self.product_kit:
|
||
raise ValidationError(
|
||
'Нельзя указать одновременно и товар, и комплект'
|
||
)
|
||
|
||
def save(self, *args, **kwargs):
|
||
# Автоматически фиксируем цену при создании, если она не указана
|
||
if not self.price:
|
||
if self.product:
|
||
self.price = self.product.price
|
||
elif self.product_kit:
|
||
self.price = self.product_kit.price
|
||
super().save(*args, **kwargs)
|
||
|
||
def get_total_price(self):
|
||
"""Возвращает общую стоимость позиции"""
|
||
return self.price * self.quantity
|
||
|
||
@property
|
||
def item_name(self):
|
||
"""Название товара/комплекта"""
|
||
if self.product:
|
||
return self.product.name
|
||
elif self.product_kit:
|
||
return self.product_kit.name
|
||
return "Не указано"
|
||
|
||
@property
|
||
def original_price(self):
|
||
"""Оригинальная цена товара/комплекта из каталога"""
|
||
if self.product:
|
||
return self.product.actual_price
|
||
elif self.product_kit:
|
||
return self.product_kit.actual_price
|
||
return None
|
||
|
||
@property
|
||
def price_difference(self):
|
||
"""Разница между установленной ценой и оригинальной"""
|
||
if self.is_custom_price and self.original_price:
|
||
return self.price - self.original_price
|
||
return None
|