from django.db import models from django.conf import settings from django.utils import timezone from django.utils.translation import gettext_lazy as _ from decimal import Decimal from thirdparty.zasilkovna.models import PacketaShipment class Category(models.Model): name = models.CharField(max_length=100) #adresa kategorie např: /category/elektronika/mobily/ url = models.SlugField(unique=True) #kategorie se můžou skládat pod sebe parent = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.CASCADE, related_name='subcategories' ) description = models.TextField(blank=True) #ikona image = models.ImageField(upload_to='categories/', blank=True) class Meta: verbose_name_plural = "Categories" def __str__(self): return self.name class Product(models.Model): name = models.CharField(max_length=200) description = models.TextField(blank=True) code = models.CharField(max_length=100, unique=True, blank=True, null=True) category = models.ForeignKey(Category, related_name='products', on_delete=models.PROTECT) price = models.DecimalField(max_digits=10, decimal_places=2) url = models.SlugField(unique=True) stock = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=True) #limitka (volitelné) limited_to = models.DateTimeField(null=True, blank=True) default_carrier = models.ForeignKey( "Carrier", on_delete=models.SET_NULL, null=True, blank=True, related_name="default_for_products" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @property def available(self): return self.is_active and self.stock > 0 def __str__(self): return f"{self.name} ({self.price} {self.currency.upper()})" #obrázek pro produkty class ProductImage(models.Model): product = models.ForeignKey(Product, related_name='images', on_delete=models.CASCADE) image = models.ImageField(upload_to='products/') alt_text = models.CharField(max_length=150, blank=True) is_main = models.BooleanField(default=False) def __str__(self): return f"{self.product.name} image" # ------------------ OBJENDÁVKOVÉ MODELY (dole) ------------------ # Dopravci a způsoby dopravy class Carrier(models.Model): class Role(models.TextChoices): ZASILKOVNA = "packeta", "Zásilkovna" STORE = "store", "Osobní odběr" choice = models.CharField(max_length=20, choices=Role.choices, default=Role.STORE) zasilkovna = models.ForeignKey( 'thirdparty.zasilkovna.Zasilkovna', on_delete=models.DO_NOTHING, null=True, blank=True, related_name="carriers" ) def __str__(self): return f"{self.name} ({self.base_price} Kč)" def save(self, *args, **kwargs): #zásilkovna instance je vytvořena if self.choice == self.Role.ZASILKOVNA: self.packeta = PacketaShipment.objects.create() elif self.choice == self.Role.STORE: self.packeta = None super().save(*args, **kwargs) class DiscountCode(models.Model): code = models.CharField(max_length=50, unique=True) description = models.CharField(max_length=255, blank=True) # sleva v procentech (0–100) percent = models.DecimalField(max_digits=5, decimal_places=2, help_text="Např. 10.00 = 10% sleva") # nebo fixní částka amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text="Fixní sleva v CZK") valid_from = models.DateTimeField(default=timezone.now) valid_to = models.DateTimeField(null=True, blank=True) active = models.BooleanField(default=True) usage_limit = models.PositiveIntegerField(null=True, blank=True) used_count = models.PositiveIntegerField(default=0) specific_products = models.ManyToManyField( Product, blank=True, related_name="discount_codes" ) specific_categories = models.ManyToManyField( Category, blank=True, related_name="discount_codes" ) def is_valid(self): now = timezone.now() if not self.active: return False if self.valid_to and self.valid_to < now: return False if self.usage_limit and self.used_count >= self.usage_limit: return False return True def __str__(self): return f"{self.code} ({self.percent}% or {self.amount} CZK)" class Order(models.Model): class Status(models.TextChoices): PENDING = "pending", _("Čeká na platbu") PAID = "paid", _("Zaplaceno") CANCELLED = "cancelled", _("Zrušeno") SHIPPED = "shipped", _("Odesláno") #COMPLETED = "completed", _("Dokončeno") status = models.CharField( max_length=20, choices=Status.choices, default=Status.PENDING ) user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="orders", on_delete=models.CASCADE ) carrier = models.ForeignKey( Carrier, on_delete=models.CASCADE, null=True, blank=True, related_name="orders" ) #itemy order_items = models.ManyToManyField( 'OrderItem', related_name='orders', ) # Stored order grand total; recalculated on save total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0) currency = models.CharField(max_length=10, default="CZK") # fakturační údaje (zkopírované z user profilu při objednávce) first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) email = models.EmailField() phone = models.CharField(max_length=20, blank=True) address = models.CharField(max_length=255) city = models.CharField(max_length=100) postal_code = models.CharField(max_length=20) country = models.CharField(max_length=100, default="Czech Republic") note = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) # Oprava: ManyToOneRel není pole. Potřebujeme iterovat přes self.discount.all(), proto ManyToManyField. discount = models.ManyToManyField("DiscountCode", blank=True, related_name="orders") def __str__(self): return f"Order #{self.id} - {self.user.email} ({self.status})" def calculate_total_price(self): if self.discount.exists(): for discount in self.discount.all(): total = Decimal('0.0') # getting all prices from order items (with discount applied if valid) for item in self.items.all(): total = total + item.get_total_price(discount) return total else: total = Decimal('0.0') # getting all prices from order items (without discount) for item in self.items.all(): total = total + (item.product.price * item.quantity) return total def save(self, *args, **kwargs): # Keep total_price always in sync with items and discount self.total_price = self.calculate_total_price() super().save(*args, **kwargs) class OrderItem(models.Model): order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE) product = models.ForeignKey("products.Product", on_delete=models.PROTECT) quantity = models.PositiveIntegerField(default=1) def get_total_price(self, discount_object:DiscountCode): """Calculate total price for this item, applying discount if valid.""" if discount_object and discount_object.is_valid(): if (self.product in discount_object.specific_products.all() or self.product.category in discount_object.specific_categories.all()): if discount_object.percent: return (self.quantity * self.product.price) * (Decimal('1.0') - discount_object.percent / Decimal('100')) elif discount_object.amount: return (self.quantity * self.product.price) - discount_object.amount else: raise ValueError("Discount code must have either percent or amount defined.") elif not discount_object: return self.quantity * self.product.price else: return ValueError("Invalid discount code.") def __str__(self): return f"{self.product.name} x{self.quantity}"