156 lines
5.7 KiB
Python
156 lines
5.7 KiB
Python
from rest_framework import serializers
|
|
from rest_framework.validators import UniqueValidator
|
|
|
|
from trznice.utils import RoundedDateTimeField
|
|
from .models import Product, EventProduct
|
|
from booking.models import Event
|
|
# from booking.serializers import EventSerializer
|
|
|
|
class ProductSerializer(serializers.ModelSerializer):
|
|
code = serializers.CharField(
|
|
required=False,
|
|
allow_null=True,
|
|
allow_blank=True,
|
|
help_text="Unikátní číselný kód produktu (volitelné)",
|
|
)
|
|
events = serializers.SerializerMethodField(help_text="Seznam akcí (eventů), ve kterých se tento produkt prodává.")
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = ["id", "name", "code", "events"]
|
|
read_only_fields = ["id"]
|
|
extra_kwargs = {
|
|
"name": {
|
|
"help_text": "Název zboží (max. 255 znaků).",
|
|
"required": True,
|
|
},
|
|
"code": {
|
|
"help_text": "Unikátní kód zboží (např. 'FOOD-001'). Volitelné; pokud vyplněno, musí být jedinečný.",
|
|
"required": False,
|
|
"allow_null": True,
|
|
"allow_blank": True,
|
|
},
|
|
}
|
|
|
|
def validate_name(self, value):
|
|
value = value.strip()
|
|
|
|
if not value:
|
|
raise serializers.ValidationError("Název Zboží (Produktu) nemůže být prázdný.")
|
|
|
|
if len(value) > 255:
|
|
raise serializers.ValidationError("Název nesmí být delší než 255 znaků.")
|
|
return value
|
|
|
|
def validate_code(self, value):
|
|
# Accept empty/null
|
|
if value in (None, ""):
|
|
return None
|
|
# Uniqueness manual check (since we removed built-in validator to permit null/blank)
|
|
qs = Product.objects.filter(code=value)
|
|
if self.instance:
|
|
qs = qs.exclude(pk=self.instance.pk)
|
|
if qs.exists():
|
|
raise serializers.ValidationError("Produkt s tímto kódem už existuje.")
|
|
return value
|
|
|
|
def get_events(self, obj):
|
|
# Expect prefetch: event_products__event
|
|
events = []
|
|
# Access prefetched related if available to avoid N+1
|
|
event_products = getattr(obj, 'event_products_all', None)
|
|
if event_products is None:
|
|
# Fallback query (should be avoided if queryset is optimized)
|
|
event_products = obj.event_products.select_related('event').all()
|
|
for ep in event_products:
|
|
if ep.event_id and hasattr(ep, 'event'):
|
|
events.append({"id": ep.event_id, "name": ep.event.name})
|
|
return events
|
|
|
|
|
|
class EventProductSerializer(serializers.ModelSerializer):
|
|
product = ProductSerializer(read_only=True)
|
|
product_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=Product.objects.all(), write_only=True
|
|
)
|
|
|
|
start_selling_date = RoundedDateTimeField()
|
|
end_selling_date = RoundedDateTimeField()
|
|
|
|
class Meta:
|
|
model = EventProduct
|
|
fields = [
|
|
'id',
|
|
'product', # nested read-only
|
|
'product_id', # required in POST/PUT
|
|
'event',
|
|
'start_selling_date',
|
|
'end_selling_date',
|
|
]
|
|
|
|
read_only_fields = ["id", "product"]
|
|
extra_kwargs = {
|
|
"product": {
|
|
"help_text": "Detail zboží (jen pro čtení).",
|
|
"required": False,
|
|
"read_only": True,
|
|
},
|
|
"product_id": {
|
|
"help_text": "ID zboží, které je povoleno prodávat na akci.",
|
|
"required": True,
|
|
"write_only": True,
|
|
},
|
|
"event": {
|
|
"help_text": "ID akce (Event), pro kterou je zboží povoleno.",
|
|
"required": True,
|
|
},
|
|
"start_selling_date": {
|
|
"help_text": "Začátek prodeje v rámci akce (musí spadat do [event.start, event.end]).",
|
|
"required": True,
|
|
},
|
|
"end_selling_date": {
|
|
"help_text": "Konec prodeje v rámci akce (po start_selling_date, také v rámci [event.start, event.end]).",
|
|
"required": True,
|
|
},
|
|
}
|
|
|
|
|
|
def create(self, validated_data):
|
|
validated_data["product"] = validated_data.pop("product_id")
|
|
return super().create(validated_data)
|
|
|
|
def validate(self, data):
|
|
product = data.get("product_id")
|
|
event = data.get("event")
|
|
start = data.get("start_selling_date")
|
|
end = data.get("end_selling_date")
|
|
|
|
if start >= end:
|
|
raise serializers.ValidationError("Datum začátku prodeje musí být dříve než jeho konec.")
|
|
|
|
if event and (start < event.start or end > event.end):
|
|
raise serializers.ValidationError("Prodej zboží musí být v rámci trvání akce.")
|
|
|
|
# When updating, exclude self instance
|
|
instance_id = self.instance.id if self.instance else None
|
|
|
|
# Check for overlapping EventProducts for the same product/event
|
|
overlapping = EventProduct.objects.exclude(id=instance_id).filter(
|
|
event=event,
|
|
product_id=product,
|
|
start_selling_date__lt=end,
|
|
end_selling_date__gt=start,
|
|
)
|
|
if overlapping.exists():
|
|
raise serializers.ValidationError("Toto zboží už se prodává v tomto období na této akci.")
|
|
|
|
# # Check for duplicate product-event pair
|
|
# duplicate = EventProduct.objects.exclude(id=instance_id).filter(
|
|
# event=event,
|
|
# product_id=product,
|
|
# )
|
|
# if duplicate.exists():
|
|
# raise serializers.ValidationError(f"V rámci akce {event} už je {product} zaregistrováno.")
|
|
|
|
return data
|