from django.db import models from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import User from django.utils.text import slugify from decimal import Decimal from django.core.validators import MinValueValidator from django.utils import timezone # Create your models here. class FursuitGallery(models.Model): name = models.CharField(_('Name'), max_length=200) slug = models.SlugField(unique=True) description = models.TextField(_('Description')) description_en = models.TextField(_('Description (English)')) created_at = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class Meta: verbose_name = _('Fursuit Gallery') verbose_name_plural = _('Fursuit Galleries') def __str__(self): return self.name class GalleryImage(models.Model): gallery = models.ForeignKey(FursuitGallery, on_delete=models.CASCADE, related_name='images') image = models.ImageField(upload_to='gallery/') title = models.CharField(_('Title'), max_length=200, blank=True) description = models.TextField(_('Description'), blank=True) order = models.PositiveIntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['order', 'created_at'] class Category(models.Model): name = models.CharField(_('Name'), max_length=100) slug = models.SlugField(_('Slug'), max_length=100, unique=True) description = models.TextField(_('Beschreibung'), blank=True) image = models.ImageField(_('Bild'), upload_to='categories/', blank=True) parent = models.ForeignKey('self', verbose_name=_('Übergeordnete Kategorie'), on_delete=models.CASCADE, null=True, blank=True, related_name='children') class Meta: verbose_name = _('Kategorie') verbose_name_plural = _('Kategorien') ordering = ['name'] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class ProductType(models.Model): name = models.CharField(_('Name'), max_length=100) has_sizes = models.BooleanField(_('Hat Größen'), default=False) has_colors = models.BooleanField(_('Hat Farben'), default=False) has_custom_design = models.BooleanField(_('Erlaubt Custom Design'), default=False) requires_measurements = models.BooleanField(_('Benötigt Maße'), default=False) class Meta: verbose_name = _('Produkttyp') verbose_name_plural = _('Produkttypen') def __str__(self): return self.name class Product(models.Model): category = models.ForeignKey(Category, verbose_name=_('Kategorie'), on_delete=models.CASCADE, related_name='products') product_type = models.ForeignKey(ProductType, verbose_name=_('Produkttyp'), on_delete=models.CASCADE) name = models.CharField(_('Name'), max_length=200) slug = models.SlugField(_('Slug'), max_length=200, unique=True, default='') description = models.TextField(_('Beschreibung')) price = models.DecimalField(_('Preis'), max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))], default=Decimal('0.00')) stock = models.PositiveIntegerField(_('Lagerbestand'), default=0) available = models.BooleanField(_('Verfügbar'), default=True) created = models.DateTimeField(_('Erstellt'), default=timezone.now) updated = models.DateTimeField(_('Aktualisiert'), auto_now=True) image = models.ImageField(_('Hauptbild'), upload_to='products/') class Meta: verbose_name = _('Produkt') verbose_name_plural = _('Produkte') ordering = ['-created'] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class ProductImage(models.Model): product = models.ForeignKey(Product, verbose_name=_('Produkt'), on_delete=models.CASCADE, related_name='images') image = models.ImageField(_('Bild'), upload_to='products/') alt_text = models.CharField(_('Alternativer Text'), max_length=200, blank=True) is_feature = models.BooleanField(_('Ist Hauptbild'), default=False) created = models.DateTimeField(_('Erstellt'), default=timezone.now) class Meta: verbose_name = _('Produktbild') verbose_name_plural = _('Produktbilder') ordering = ['-is_feature', '-created'] class ProductVariant(models.Model): SIZE_CHOICES = [ ('XS', 'Extra Small'), ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ('XL', 'Extra Large'), ('XXL', '2X Large'), ('XXXL', '3X Large'), ] product = models.ForeignKey(Product, verbose_name=_('Produkt'), on_delete=models.CASCADE, related_name='variants') size = models.CharField(_('Größe'), max_length=10, choices=SIZE_CHOICES, blank=True) color = models.CharField(_('Farbe'), max_length=50, blank=True) sku = models.CharField(_('Artikelnummer'), max_length=50, unique=True) price_adjustment = models.DecimalField(_('Preisanpassung'), max_digits=10, decimal_places=2, default=0) stock = models.PositiveIntegerField(_('Lagerbestand'), default=0) image = models.ImageField(_('Variantenbild'), upload_to='variants/', blank=True) class Meta: verbose_name = _('Produktvariante') verbose_name_plural = _('Produktvarianten') unique_together = ['product', 'size', 'color'] def __str__(self): variant_parts = [] if self.size: variant_parts.append(f"Größe: {self.size}") if self.color: variant_parts.append(f"Farbe: {self.color}") return f"{self.product.name} ({', '.join(variant_parts)})" class CustomDesign(models.Model): STATUS_CHOICES = [ ('pending', _('Ausstehend')), ('approved', _('Genehmigt')), ('rejected', _('Abgelehnt')), ('in_progress', _('In Bearbeitung')), ('completed', _('Abgeschlossen')), ] product = models.ForeignKey(Product, verbose_name=_('Produkt'), on_delete=models.CASCADE, related_name='custom_designs') customer = models.ForeignKey('auth.User', verbose_name=_('Kunde'), on_delete=models.CASCADE) design_file = models.FileField(_('Design-Datei'), upload_to='designs/') notes = models.TextField(_('Anmerkungen')) created = models.DateTimeField(_('Erstellt'), default=timezone.now) status = models.CharField(_('Status'), max_length=20, choices=STATUS_CHOICES, default='pending') class Meta: verbose_name = _('Custom Design') verbose_name_plural = _('Custom Designs') ordering = ['-created'] def __str__(self): return f"Custom Design für {self.product.name} von {self.customer.username}" class DesignTemplate(models.Model): name = models.CharField(_('Name'), max_length=200) name_en = models.CharField(_('Name (English)'), max_length=200) description = models.TextField(_('Description')) description_en = models.TextField(_('Description (English)')) image = models.ImageField(upload_to='designs/') model_3d = models.FileField(_('3D Model'), upload_to='3d_models/', null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class CustomerDesign(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) name = models.CharField(_('Design Name'), max_length=200) design_file = models.FileField(upload_to='customer_designs/') notes = models.TextField(_('Notes'), blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.user.username} - {self.name}" class Order(models.Model): STATUS_CHOICES = [ ('pending', _('Pending')), ('confirmed', _('Confirmed')), ('in_progress', _('In Progress')), ('completed', _('Completed')), ('cancelled', _('Cancelled')), ] PAYMENT_METHODS = [ ('paypal', 'PayPal'), ('credit_card', _('Credit Card')), ('bank_transfer', _('Bank Transfer')), ] user = models.ForeignKey(User, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS) customer_design = models.ForeignKey(CustomerDesign, on_delete=models.SET_NULL, null=True, blank=True) design_template = models.ForeignKey(DesignTemplate, on_delete=models.SET_NULL, null=True, blank=True) special_instructions = models.TextField(blank=True) # Nur für Fursuits measurements = models.JSONField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) total_price = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00')) def __str__(self): return f"Order {self.id} - {self.product.name}" class OrderProgress(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='progress_updates') title = models.CharField(_('Title'), max_length=200) description = models.TextField(_('Description')) image = models.ImageField(upload_to='progress/', null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] def __str__(self): return f"Progress update for Order {self.order.id}" class Cart(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def get_total(self): return sum(item.get_subtotal() for item in self.items.all()) def __str__(self): return f"Cart #{self.id} - {self.user.username}" class CartItem(models.Model): cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.PositiveIntegerField(default=1) size = models.CharField(max_length=20, blank=True, null=True) notes = models.TextField(blank=True, null=True) custom_design = models.ImageField(upload_to='custom_designs/', blank=True, null=True) added_at = models.DateTimeField(auto_now_add=True) def get_subtotal(self): return self.product.base_price * self.quantity class Meta: ordering = ['-added_at'] def __str__(self): return f"{self.quantity}x {self.product.name} in Cart #{self.cart.id}" class ShippingAddress(models.Model): COUNTRY_CHOICES = [ ('DE', 'Deutschland'), ('AT', 'Österreich'), ('CH', 'Schweiz'), ] user = models.ForeignKey(User, on_delete=models.CASCADE) first_name = models.CharField(_('First Name'), max_length=100) last_name = models.CharField(_('Last Name'), max_length=100) email = models.EmailField(_('Email')) address = models.CharField(_('Address'), max_length=200) city = models.CharField(_('City'), max_length=100) zip = models.CharField(_('ZIP Code'), max_length=10) country = models.CharField(_('Country'), max_length=2, choices=COUNTRY_CHOICES) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.first_name} {self.last_name}, {self.city}" def get_full_address(self): return f"{self.address}, {self.zip} {self.city}, {self.get_country_display()}" class Checkout(models.Model): STATUS_CHOICES = [ ('address', _('Shipping Address')), ('payment', _('Payment Method')), ('confirm', _('Confirmation')), ('completed', _('Completed')), ] user = models.ForeignKey(User, on_delete=models.CASCADE) cart = models.OneToOneField(Cart, on_delete=models.CASCADE) shipping_address = models.ForeignKey(ShippingAddress, on_delete=models.SET_NULL, null=True) payment_method = models.CharField(_('Payment Method'), max_length=20, choices=Order.PAYMENT_METHODS, null=True) status = models.CharField(_('Status'), max_length=20, choices=STATUS_CHOICES, default='address') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Checkout #{self.id} - {self.user.username}" def get_total(self): cart_total = self.cart.get_total() if cart_total < 200: return cart_total + Decimal('5.99') return cart_total class PayPalPayment(models.Model): order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='paypal_payment') payment_id = models.CharField(_('PayPal Payment ID'), max_length=100) payer_id = models.CharField(_('PayPal Payer ID'), max_length=100) status = models.CharField(_('Payment Status'), max_length=20, choices=[ ('pending', _('Pending')), ('completed', _('Completed')), ('failed', _('Failed')), ('refunded', _('Refunded')), ]) amount = models.DecimalField(_('Amount'), max_digits=10, decimal_places=2) currency = models.CharField(_('Currency'), max_length=3, default='EUR') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"PayPal Payment {self.payment_id} for Order #{self.order.id}" class PaymentError(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='payment_errors') error_code = models.CharField(_('Error Code'), max_length=100) error_message = models.TextField(_('Error Message')) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Payment Error for Order #{self.order.id}: {self.error_code}" class ContactMessage(models.Model): name = models.CharField(_('Name'), max_length=100) email = models.EmailField(_('E-Mail')) subject = models.CharField(_('Betreff'), max_length=200) message = models.TextField(_('Nachricht')) created_at = models.DateTimeField(_('Erstellt am'), auto_now_add=True) class Meta: verbose_name = _('Kontaktnachricht') verbose_name_plural = _('Kontaktnachrichten') ordering = ['-created_at'] def __str__(self): return f"{self.subject} - {self.name}"