Files
octopus/myproject/orders/models.py

136 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from accounts.models import CustomUser
from products.models import Product, ProductKit
class Customer(models.Model):
"""
Модель покупателя.
"""
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, null=True, blank=True,
related_name='customer', verbose_name="Пользователь")
first_name = models.CharField(max_length=100, verbose_name="Имя")
last_name = models.CharField(max_length=100, verbose_name="Фамилия")
email = models.EmailField(unique=True, verbose_name="Email")
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Телефон")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата регистрации")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
class Meta:
verbose_name = "Покупатель"
verbose_name_plural = "Покупатели"
indexes = [
models.Index(fields=['email']),
]
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.email})"
class Order(models.Model):
"""
Заказ клиента.
"""
STATUS_CHOICES = [
('created', 'Создан'),
('confirmed', 'Подтвержден'),
('assembled', 'Собран'),
('delivered', 'Доставлен'),
('cancelled', 'Отменен'),
]
customer = models.ForeignKey(Customer, on_delete=models.CASCADE,
related_name='orders', verbose_name="Клиент")
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='created',
verbose_name="Статус")
total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Общая сумма")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
class Meta:
verbose_name = "Заказ"
verbose_name_plural = "Заказы"
indexes = [
models.Index(fields=['status']),
models.Index(fields=['customer']),
models.Index(fields=['created_at']),
]
def __str__(self):
return f"Заказ #{self.id} - {self.customer}"
class OrderItem(models.Model):
"""
Строка заказа — может быть простым товаром или комплектом.
"""
order = models.ForeignKey(Order, on_delete=models.CASCADE,
related_name='items', verbose_name="Заказ")
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True,
related_name='order_items', verbose_name="Товар")
kit = models.ForeignKey(ProductKit, on_delete=models.CASCADE, null=True, blank=True,
related_name='order_items', verbose_name="Комплект")
quantity = models.DecimalField(max_digits=10, decimal_places=3, default=1,
verbose_name="Количество")
# Снапшот-поля (для истории и отчётов)
snapshot_name = models.CharField(max_length=200, verbose_name="Название (на момент заказа)")
snapshot_sku = models.CharField(max_length=100, blank=True, null=True,
verbose_name="Артикул (на момент заказа)")
sale_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Цена продажи")
cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Себестоимость")
composition_snapshot = models.JSONField(null=True, blank=True,
verbose_name="Состав комплекта (снапшот)")
class Meta:
verbose_name = "Позиция заказа"
verbose_name_plural = "Позиции заказов"
indexes = [
models.Index(fields=['order']),
]
def save(self, *args, **kwargs):
# Валидация: либо product, либо kit, но не оба
if self.product and self.kit:
raise ValueError("Нельзя одновременно указать товар и комплект")
if not self.product and not self.kit:
raise ValueError("Необходимо указать либо товар, либо комплект")
# Заполнение снапшот-полей
if self.product:
if not self.snapshot_name:
self.snapshot_name = self.product.name
if not self.snapshot_sku:
self.snapshot_sku = self.product.sku
if not self.sale_price:
self.sale_price = self.product.sale_price
if not self.cost_price:
self.cost_price = self.product.cost_price
elif self.kit:
if not self.snapshot_name:
self.snapshot_name = self.kit.name
if not self.sale_price or not self.cost_price:
# Здесь можно реализовать логику подсчета цены комплекта
# в зависимости от метода ценообразования
if self.kit.pricing_method == 'fixed' and self.kit.fixed_price:
self.sale_price = self.kit.fixed_price
# В реальном приложении нужно реализовать все методы ценообразования
if self.kit.pricing_method != 'fixed' and not self.composition_snapshot:
# Формирование снапшота состава комплекта
composition = []
for item in self.kit.kit_items.all():
composition.append({
"product_id": item.product.id,
"name": item.product.name,
"sku": item.product.sku,
"quantity": float(item.quantity),
"cost_price": float(item.product.cost_price),
"sale_price": float(item.product.sale_price)
})
self.composition_snapshot = composition
super().save(*args, **kwargs)
def __str__(self):
return f"{self.snapshot_name} x{self.quantity} в заказе #{self.order.id}"