152 lines
5.2 KiB
Python

import uuid
from decimal import Decimal
from django.core.validators import MinValueValidator
from django.db import models
class SaleEventType(models.TextChoices):
SALE = "sale", "Продажа"
RETURN = "return", "Возврат"
class Sale(models.Model):
"""Normalized sale/order event supplied by an external or internal system."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
source = models.ForeignKey(
"commerce.SourceSystem",
on_delete=models.PROTECT,
related_name="sales",
verbose_name="Источник",
)
source_record = models.ForeignKey(
"commerce.SourceRecord",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="sales",
verbose_name="Исходная запись",
)
external_id = models.CharField("Внешний ID", max_length=255)
event_type = models.CharField(
"Тип операции",
max_length=16,
choices=SaleEventType.choices,
default=SaleEventType.SALE,
)
number = models.CharField("Номер", max_length=128, blank=True)
channel = models.CharField("Канал продаж", max_length=64, blank=True, db_index=True)
status = models.CharField("Статус источника", max_length=128, blank=True)
counts_as_sale = models.BooleanField(
"Учитывать в продажах",
default=True,
help_text="Отключается для отменённых или тестовых заказов.",
)
currency = models.CharField("Валюта", max_length=3, default="RUB")
occurred_at = models.DateTimeField("Дата операции", db_index=True)
source_updated_at = models.DateTimeField(
"Изменено в источнике", null=True, blank=True
)
created_at = models.DateTimeField("Создано", auto_now_add=True)
updated_at = models.DateTimeField("Обновлено", auto_now=True)
class Meta:
verbose_name = "Продажа"
verbose_name_plural = "Продажи"
ordering = ["-occurred_at", "-created_at"]
constraints = [
models.UniqueConstraint(
fields=["source", "external_id"],
name="commerce_sale_source_external",
)
]
indexes = [
models.Index(
fields=["counts_as_sale", "occurred_at"],
name="commerce_sale_counted_date",
)
]
def __str__(self):
return self.number or f"{self.source.code}:{self.external_id}"
class SaleLine(models.Model):
"""Product-level monetary fact within a sale."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
sale = models.ForeignKey(
Sale,
on_delete=models.CASCADE,
related_name="lines",
verbose_name="Продажа",
)
product = models.ForeignKey(
"commerce.Product",
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="sale_lines",
verbose_name="Товар",
)
external_id = models.CharField("Внешний ID позиции", max_length=255)
external_product_id = models.CharField(
"Внешний ID товара", max_length=255, blank=True
)
external_sku = models.CharField("Внешний артикул", max_length=128, blank=True)
name = models.CharField("Название в источнике", max_length=255, blank=True)
quantity = models.DecimalField(
"Количество",
max_digits=18,
decimal_places=4,
default=Decimal("1"),
validators=[MinValueValidator(Decimal("0"))],
)
gross_amount = models.DecimalField(
"Сумма до скидки",
max_digits=18,
decimal_places=2,
default=Decimal("0"),
validators=[MinValueValidator(Decimal("0"))],
)
discount_amount = models.DecimalField(
"Скидка",
max_digits=18,
decimal_places=2,
default=Decimal("0"),
validators=[MinValueValidator(Decimal("0"))],
)
cost_amount = models.DecimalField(
"Себестоимость",
max_digits=18,
decimal_places=2,
default=Decimal("0"),
validators=[MinValueValidator(Decimal("0"))],
)
metadata = models.JSONField("Дополнительные данные", default=dict, blank=True)
class Meta:
verbose_name = "Позиция продажи"
verbose_name_plural = "Позиции продаж"
constraints = [
models.UniqueConstraint(
fields=["sale", "external_id"],
name="commerce_sale_line_identity",
),
models.CheckConstraint(
condition=models.Q(discount_amount__lte=models.F("gross_amount")),
name="commerce_sale_line_discount_lte_gross",
),
]
indexes = [
models.Index(fields=["product", "sale"], name="commerce_line_product_sale")
]
@property
def net_amount(self):
return self.gross_amount - self.discount_amount
def __str__(self):
return self.name or self.external_sku or self.external_id