Files
vontor-cz/backend/commerce/models.py
2025-11-12 02:12:41 +01:00

215 lines
7.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 django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
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)
currency = models.CharField(max_length=10, default="czk")
url = models.SlugField(unique=True)
stock = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
#limitka
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"
# Dopravci a způsoby dopravy
class Carrier(models.Model):
name = models.CharField(max_length=100) # název dopravce (Zásilkovna, Česká pošta…)
base_price = models.DecimalField(max_digits=10, decimal_places=2, default=0) # základní cena dopravy
delivery_time = models.CharField(max_length=100, blank=True) # např. "23 pracovní dny"
is_active = models.BooleanField(default=True)
# pole pro logo
logo = models.ImageField(upload_to="carriers/", blank=True, null=True)
# pole pro propojení s externím API (např. ID služby u Zásilkovny)
external_id = models.CharField(max_length=50, blank=True, null=True)
def __str__(self):
return f"{self.name} ({self.base_price} Kč)"
class DiscountCode(models.Model):
code = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=255, blank=True)
# sleva v procentech (0100)
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")
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="orders", on_delete=models.CASCADE
)
status = models.CharField(
max_length=20, choices=Status.choices, default=Status.PENDING
)
# 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)
discount = models.ManyToOneRel("DiscountCode", null=True, blank=True, on_delete=models.PROTECT)
def __str__(self):
return f"Order #{self.id} - {self.user.email} ({self.status})"
def calculate_total_price(self):
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
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.")
else:
return ValueError("Invalid discount code.")
def __str__(self):
return f"{self.product.name} x{self.quantity}"