Files
shoppy/shop/models.py
T
Pablo Moreno 90a16a6aef chore: blacked
2024-05-19 21:54:17 +02:00

370 lines
11 KiB
Python

from decimal import Decimal
from uuid import uuid4
from django.db import models
from django.utils import timezone
from django.utils.text import gettext_lazy as _
class TimestampedModel(models.Model):
creation_date = models.DateTimeField(
auto_now_add=True, verbose_name=_("Fecha de creación")
)
last_modification_date = models.DateTimeField(
auto_now=True, verbose_name=_("Fecha de última modificación")
)
class Meta:
abstract = True
class Tag(models.Model):
name = models.CharField(max_length=16)
def __str__(self):
return self.name
class Meta:
verbose_name = _("etiqueta")
verbose_name_plural = _("etiquetas")
class Product(TimestampedModel):
name = models.CharField(
max_length=96,
blank=False,
null=False,
verbose_name=_("Nombre"),
)
description = models.CharField(
max_length=1000,
blank=True,
default="",
verbose_name=_("Descripción"),
)
stock = models.DecimalField(
max_digits=13,
decimal_places=4,
default=Decimal("0"),
verbose_name=_("Stock"),
)
is_digital_asset = models.BooleanField(default=False)
url = models.URLField(blank=True, verbose_name=_("URL de descarga"))
images = models.ManyToManyField(
"files.FileUpload", blank=True, verbose_name=_("Imágenes")
)
tags = models.ManyToManyField("shop.Tag", blank=True, verbose_name=_("Etiquetas"))
brand = models.ForeignKey(
"shop.Brand",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name=_("Marca"),
)
def __str__(self):
return self.name
@property
def price(self):
return self.prices.filter(current=True).first()
class Meta:
verbose_name = _("producto")
verbose_name_plural = _("productos")
ordering = ("-id",)
class ProductPrice(TimestampedModel):
price = models.DecimalField(
max_digits=11, decimal_places=2, verbose_name=_("Precio")
)
date = models.DateTimeField(default=timezone.now, verbose_name=_("Fecha"))
product = models.ForeignKey(
"shop.Product",
on_delete=models.CASCADE,
verbose_name=_("Producto"),
related_name="prices",
)
tax = models.ForeignKey(
"shop.Tax",
on_delete=models.PROTECT,
verbose_name=_("Impuesto aplicable"),
)
current = models.BooleanField(default=False, verbose_name=_("Es el precio actual"))
def __str__(self):
return f"{self.price} - {self.tax.code}"
@property
def price_with_tax(self) -> Decimal:
tax_value = self.price * Decimal(self.tax.value / 100)
return round(self.price + tax_value, 2)
class Meta:
verbose_name = _("precio de producto")
verbose_name_plural = _("precio de producto")
class ProductBatch(TimestampedModel):
code = models.CharField(
max_length=32, unique=True, db_index=True, verbose_name=_("Código")
)
product = models.ForeignKey(
"shop.Product", on_delete=models.PROTECT, verbose_name=_("Producto")
)
quantity = models.DecimalField(
max_digits=13,
decimal_places=4,
default=Decimal("1"),
verbose_name=_("Cantidad"),
)
expiration_date = models.DateField(
blank=True, null=True, verbose_name=_("Fecha de caducidad / consumo preferente")
)
provider = models.ForeignKey(
"shop.Provider",
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("Proveedor"),
)
def __str__(self):
return f"{self.code} - {self.product.name} - {self.quantity}"
class Meta:
verbose_name = _("remesa de producto")
verbose_name_plural = _("remesas de productos")
class Tax(TimestampedModel):
code = models.CharField(
max_length=8, blank=False, unique=True, verbose_name=_("Código de impuesto")
)
value = models.PositiveIntegerField(
null=False, blank=False, verbose_name=_("Valor entero (porcentaje)")
)
def __str__(self):
return f"{self.code} - {self.value}%"
class Meta:
verbose_name = _("impuesto")
verbose_name_plural = _("impuestos")
class Customer(TimestampedModel):
vat_id = models.CharField(
max_length=32,
blank=False,
unique=True,
verbose_name=_("Documento de identidad"),
)
first_name = models.CharField(max_length=64, blank=False, verbose_name=_("Nombre"))
last_name = models.CharField(
max_length=64, blank=False, verbose_name=_("Apellidos")
)
email = models.EmailField(blank=False, verbose_name=_("E-mail"))
address = models.CharField(max_length=255, blank=False, verbose_name=_("Dirección"))
city = models.CharField(max_length=64, blank=False, verbose_name=_("Ciudad"))
state = models.CharField(max_length=64, blank=False, verbose_name=_("Región"))
country = models.CharField(max_length=64, blank=False, verbose_name=_("País"))
zip = models.CharField(max_length=32, blank=False, verbose_name=_("Código postal"))
def __str__(self):
return f"{self.vat_id} - {self.first_name} {self.last_name}"
class Meta:
verbose_name = _("cliente")
verbose_name_plural = _("clientes")
class Provider(TimestampedModel):
vat_id = models.CharField(
max_length=32,
blank=False,
unique=True,
verbose_name=_("Documento de identidad"),
)
name = models.CharField(max_length=64, blank=False, verbose_name=_("Nombre"))
email = models.EmailField(blank=False, verbose_name=_("E-mail de contacto"))
phone = models.CharField(
max_length=16,
blank=True,
null=True,
default="",
verbose_name=_("Teléfono de contacto"),
)
address = models.CharField(max_length=255, blank=False, verbose_name=_("Dirección"))
city = models.CharField(max_length=64, blank=False, verbose_name=_("Ciudad"))
state = models.CharField(max_length=64, blank=False, verbose_name=_("Región"))
country = models.CharField(max_length=64, blank=False, verbose_name=_("País"))
zip = models.CharField(max_length=32, blank=False, verbose_name=_("Código postal"))
def __str__(self):
return f"{self.vat_id} - {self.name}"
class Meta:
verbose_name = _("proveedor")
verbose_name_plural = _("proveedores")
class Brand(TimestampedModel):
name = models.CharField(
max_length=100, unique=True, blank=False, null=False, verbose_name=_("Nombre")
)
class Meta:
verbose_name = _("marca")
verbose_name_plural = _("marcas")
class OrderLine(TimestampedModel):
order = models.ForeignKey(
"shop.Order",
on_delete=models.CASCADE,
related_name="lines",
verbose_name=_("Pedido"),
)
product = models.ForeignKey(
"shop.Product",
on_delete=models.CASCADE,
null=False,
blank=False,
verbose_name=_("Producto"),
)
quantity = models.DecimalField(
max_digits=13,
decimal_places=4,
default=Decimal("1"),
null=False,
blank=False,
verbose_name=_("Cantidad"),
)
# Precio del producto en el momento en el que se crea el pedido
price = models.DecimalField(
max_digits=13, decimal_places=4, verbose_name=_("Precio")
)
# base_total = quantity * price
base_total = models.DecimalField(
max_digits=13,
decimal_places=4,
default=Decimal("0"),
null=False,
blank=False,
verbose_name=_("Total sin impuestos"),
)
# Valor entero del impuesto en el momento en el que se crea el pedido
tax_value = models.PositiveIntegerField(
null=False, blank=False, verbose_name=_("Valor de impuestos")
)
# taxes = price * quantity * (tax_value / 100)
taxes = models.DecimalField(
max_digits=13, decimal_places=4, verbose_name=_("Impuestos")
)
# total = base_total + taxes
total = models.DecimalField(
max_digits=13, decimal_places=4, verbose_name=_("Total")
)
def __str__(self):
return f"{self.product.name} - {self.quantity} - {self.price}"
class Meta:
verbose_name = _("línea de pedido")
verbose_name_plural = _("líneas de pedido")
class Order(TimestampedModel):
class Statuses(models.TextChoices):
STATUS_PENDING = "PEN", _("Pendiente de pago")
STATUS_PAID = "PAI", _("Pagado")
STATUS_READY = "RDY", _("Preparado")
STATUS_TO_BE_SENT = "TBS", _("Listo para ser enviado")
STATUS_SENT = "SNT", _("Enviado")
STATUS_DELIVERED = "DLV", _("Entregado")
STATUS_FINISHED = "FIN", _("Finalizado")
STATUS_CANCELED = "CAN", _("Cancelado")
STATUS_RETURNED = "RTN", _("Devuelto")
uuid = models.UUIDField(default=uuid4, verbose_name=_("UUID"), db_index=True)
status = models.CharField(
max_length=3, default=Statuses.STATUS_PENDING, verbose_name=_("Estado")
)
base_total = models.DecimalField(
default=Decimal("0"),
max_digits=13,
decimal_places=2,
verbose_name=_("Base imponible"),
)
total = models.DecimalField(
default=Decimal("0"), max_digits=13, decimal_places=2, verbose_name=_("Total")
)
# Customer reference
customer = models.ForeignKey(
"shop.Customer", on_delete=models.PROTECT, verbose_name=_("Cliente")
)
# Billing information
billing_address = models.CharField(
max_length=255, blank=False, verbose_name=_("Dirección de facturación")
)
billing_city = models.CharField(
max_length=64, blank=False, verbose_name=_("Ciudad de facturación")
)
billing_state = models.CharField(
max_length=64, blank=False, verbose_name=_("Región de facturación")
)
billing_country = models.CharField(
max_length=64, blank=False, verbose_name=_("País de facturación")
)
billing_zip = models.CharField(
max_length=32, blank=False, verbose_name=_("Código postal de facturación")
)
# Shipping information
shipping_address = models.CharField(
max_length=255, blank=False, verbose_name=_("Dirección")
)
shipping_city = models.CharField(
max_length=64, blank=False, verbose_name=_("Ciudad")
)
shipping_state = models.CharField(
max_length=64, blank=False, verbose_name=_("Región")
)
shipping_country = models.CharField(
max_length=64, blank=False, verbose_name=_("País")
)
shipping_zip = models.CharField(
max_length=32, blank=False, verbose_name=_("Código postal")
)
contact_phone = models.CharField(
max_length=32, blank=True, verbose_name=_("Teléfono de contacto")
)
def calculate_total_from_lines(self):
self.base_total = self.lines.aggregate(base_total=models.Sum("base_total")).get(
"base_total"
)
self.total = self.lines.aggregate(base_total=models.Sum("total")).get(
"base_total"
)
self.save()
class Meta:
verbose_name = _("pedido")
verbose_name_plural = _("pedidos")