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") ordering = ("name",) class Product(TimestampedModel): sku = models.CharField( max_length=64, blank=False, null=False, unique=True, verbose_name=_("Código de referencia"), ) name = models.CharField( max_length=96, blank=False, null=False, verbose_name=_("Nombre"), ) description = models.TextField( 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") ordering = ("-date", ) 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") ordering = ("id", ) 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")