from django.db import models from django.contrib.auth.models import User from django.utils import timezone from decimal import Decimal class Auction(models.Model): """Auktion für Custom-Design-Fursuits""" STATUS_CHOICES = [ ('draft', 'Entwurf'), ('active', 'Aktiv'), ('bidding', 'Bietphase'), ('ended', 'Beendet'), ('cancelled', 'Storniert'), ] title = models.CharField(max_length=200) description = models.TextField() starting_price = models.DecimalField(max_digits=10, decimal_places=2) current_price = models.DecimalField(max_digits=10, decimal_places=2) reserve_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) min_bid_increment = models.DecimalField(max_digits=10, decimal_places=2, default=10.00) # Custom Order Details fursuit_type = models.CharField(max_length=20, choices=[ ('partial', 'Partial'), ('fullsuit', 'Fullsuit'), ('head', 'Head Only'), ('paws', 'Paws'), ('tail', 'Tail'), ('other', 'Other'), ]) style = models.CharField(max_length=20, choices=[ ('toony', 'Toony'), ('semi_realistic', 'Semi-Realistic'), ('realistic', 'Realistic'), ('anime', 'Anime'), ('chibi', 'Chibi'), ]) # Timing start_time = models.DateTimeField() end_time = models.DateTimeField() created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) # Status status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft') winner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='won_auctions') # Images reference_images = models.FileField(upload_to='auctions/references/', null=True, blank=True) preview_image = models.ImageField(upload_to='auctions/previews/', null=True, blank=True) class Meta: ordering = ['-created'] def __str__(self): return f"Auktion: {self.title} - {self.current_price}€" @property def is_active(self): now = timezone.now() return self.status == 'active' and self.start_time <= now <= self.end_time @property def time_remaining(self): if not self.is_active: return None return self.end_time - timezone.now() @property def bid_count(self): return self.bids.count() @property def highest_bid(self): return self.bids.order_by('-amount').first() class AuctionBid(models.Model): """Gebot bei einer Auktion""" auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='bids') bidder = models.ForeignKey(User, on_delete=models.CASCADE) amount = models.DecimalField(max_digits=10, decimal_places=2) timestamp = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-timestamp'] unique_together = ['auction', 'bidder'] def __str__(self): return f"{self.bidder.username} - {self.amount}€" def save(self, *args, **kwargs): # Aktualisiere den aktuellen Preis der Auktion if self.amount > self.auction.current_price: self.auction.current_price = self.amount self.auction.save() super().save(*args, **kwargs) class AuctionWatchlist(models.Model): """Beobachtungsliste für Auktionen""" user = models.ForeignKey(User, on_delete=models.CASCADE) auction = models.ForeignKey(Auction, on_delete=models.CASCADE) added = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ['user', 'auction'] def __str__(self): return f"{self.user.username} beobachtet {self.auction.title}" class AuctionService: """Service-Klasse für Auktion-Logik""" @staticmethod def place_bid(auction, user, amount): """Gebot platzieren""" if not auction.is_active: raise ValueError("Auktion ist nicht aktiv") if amount <= auction.current_price: raise ValueError("Gebot muss höher als der aktuelle Preis sein") if auction.min_bid_increment and amount < auction.current_price + auction.min_bid_increment: raise ValueError(f"Mindestgebot: {auction.current_price + auction.min_bid_increment}€") # Erstelle oder aktualisiere Gebot bid, created = AuctionBid.objects.get_or_create( auction=auction, bidder=user, defaults={'amount': amount} ) if not created: bid.amount = amount bid.save() return bid @staticmethod def end_auction(auction): """Auktion beenden""" if auction.status != 'active': return highest_bid = auction.highest_bid if highest_bid: # Prüfe Reserve-Preis if auction.reserve_price and highest_bid.amount < auction.reserve_price: auction.status = 'ended' auction.save() return None # Reserve-Preis nicht erreicht auction.winner = highest_bid.bidder auction.status = 'ended' auction.save() # Erstelle Custom Order custom_order = CustomOrder.objects.create( user=highest_bid.bidder, fursuit_type=auction.fursuit_type, style=auction.style, character_name=f"Auktion: {auction.title}", character_description=auction.description, budget_range=f"Gewonnen für {highest_bid.amount}€", status='approved', quoted_price=highest_bid.amount ) return custom_order else: auction.status = 'ended' auction.save() return None @staticmethod def get_auction_stats(auction): """Statistiken für eine Auktion""" return { 'total_bids': auction.bid_count, 'unique_bidders': auction.bids.values('bidder').distinct().count(), 'current_price': auction.current_price, 'time_remaining': auction.time_remaining, 'is_active': auction.is_active, 'highest_bidder': auction.highest_bid.bidder.username if auction.highest_bid else None }