187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
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
|
|
} |