from rest_framework import serializers from thirdparty.stripe.client import StripeClient from .models import Refund, Order, Invoice, Review class RefundCreatePublicSerializer(serializers.Serializer): email = serializers.EmailField() invoice_number = serializers.CharField(required=False, allow_blank=True) order_id = serializers.IntegerField(required=False) # Optional reason fields reason_choice = serializers.ChoiceField( choices=Refund.Reason.choices, required=False ) reason_text = serializers.CharField(required=False, allow_blank=True) def validate(self, attrs): email = attrs.get("email") invoice_number = (attrs.get("invoice_number") or "").strip() order_id = attrs.get("order_id") if not invoice_number and not order_id: raise serializers.ValidationError( "Provide either invoice_number or order_id." ) order = None if invoice_number: try: invoice = Invoice.objects.get(invoice_number=invoice_number) order = invoice.order except Invoice.DoesNotExist: raise serializers.ValidationError({"invoice_number": "Invoice not found."}) except Order.DoesNotExist: raise serializers.ValidationError({"invoice_number": "Order for invoice not found."}) if order_id and order is None: try: order = Order.objects.get(id=order_id) except Order.DoesNotExist: raise serializers.ValidationError({"order_id": "Order not found."}) # Verify email matches order's email or user's email if not order: raise serializers.ValidationError("Order could not be resolved.") order_email = (order.email or "").strip().lower() user_email = (getattr(order.user, "email", "") or "").strip().lower() provided = email.strip().lower() if provided not in {order_email, user_email}: raise serializers.ValidationError({"email": "Email does not match the order."}) attrs["order"] = order return attrs def create(self, validated_data): order = validated_data["order"] reason_choice = validated_data.get("reason_choice") or Refund.Reason.OTHER reason_text = validated_data.get("reason_text", "") refund = Refund.objects.create( order=order, reason_choice=reason_choice, reason_text=reason_text, ) return refund from rest_framework import serializers from drf_spectacular.utils import extend_schema_field from decimal import Decimal from django.contrib.auth import get_user_model from django.db import transaction from django.core.exceptions import ValidationError from .models import ( Category, Product, ProductImage, DiscountCode, Refund, Order, OrderItem, Carrier, Payment, Cart, CartItem, Wishlist, ) from thirdparty.stripe.models import StripeModel from thirdparty.zasilkovna.serializers import ZasilkovnaPacketSerializer from thirdparty.zasilkovna.models import ZasilkovnaPacket User = get_user_model() # ----------------- CREATING ORDER SERIALIZER ----------------- #correct # -- CARRIER -- class OrderCarrierSerializer(serializers.ModelSerializer): # vstup: jen ID adresy z widgetu (write-only) packeta_address_id = serializers.IntegerField(required=False, write_only=True) # výstup: serializovaný packet zasilkovna = ZasilkovnaPacketSerializer(many=True, read_only=True) class Meta: model = Carrier fields = ["shipping_method", "state", "zasilkovna", "shipping_price", "packeta_address_id"] read_only_fields = ["state", "shipping_price", "zasilkovna"] def create(self, validated_data): packeta_address_id = validated_data.pop("packeta_address_id", None) carrier = Carrier.objects.create(**validated_data) if packeta_address_id is not None: # vytvoříme nový packet s danou addressId packet = ZasilkovnaPacket.objects.create(addressId=packeta_address_id) carrier.zasilkovna.add(packet) return carrier #correct # -- ORDER ITEMs -- class OrderItemCreateSerializer(serializers.Serializer): product_id = serializers.IntegerField() quantity = serializers.IntegerField(min_value=1, default=1) def validate(self, attrs): product_id = attrs.get("product_id") try: product = Product.objects.get(pk=product_id) except Product.DoesNotExist: raise serializers.ValidationError({"product_id": "Product not found."}) attrs["product"] = product return attrs # -- PAYMENT -- class PaymentSerializer(serializers.ModelSerializer): stripe_session_id = serializers.CharField(source='stripe.stripe_session_id', read_only=True) stripe_payment_intent = serializers.CharField(source='stripe.stripe_payment_intent', read_only=True) stripe_session_url = serializers.URLField(source='stripe.stripe_session_url', read_only=True) class Meta: model = Payment fields = [ "id", "payment_method", "stripe", "stripe_session_id", "stripe_payment_intent", "stripe_session_url", ] read_only_fields = [ "id", "stripe", "stripe_session_id", "stripe_payment_intent", "stripe_session_url", ] def create(self, validated_data): order = self.context.get("order") # musíš ho předat při inicializaci serializeru carrier = self.context.get("carrier") with transaction.atomic(): payment = Payment.objects.create( order=order, carrier=carrier, **validated_data ) # pokud je Stripe, vytvoříme checkout session if payment.payment_method == Payment.PAYMENT.SHOP and carrier.shipping_method != Carrier.SHIPPING.STORE: raise serializers.ValidationError("Platba v obchodě je možná pouze pro osobní odběr.") elif payment.payment_method == Payment.PAYMENT.CASH_ON_DELIVERY and carrier.shipping_method == Carrier.SHIPPING.STORE: raise ValidationError("Dobírka není možná pro osobní odběr.") if payment.payment_method == Payment.PAYMENT.STRIPE: session = StripeClient.create_checkout_session(order) stripe_instance = StripeModel.objects.create( stripe_session_id=session.id, stripe_payment_intent=session.payment_intent, stripe_session_url=session.url, status=StripeModel.STATUS_CHOICES.PENDING ) payment.stripe = stripe_instance payment.save(update_fields=["stripe"]) return payment # -- ORDER CREATE SERIALIZER -- class OrderCreateSerializer(serializers.Serializer): # Customer/billing data (optional when authenticated) first_name = serializers.CharField(required=False) last_name = serializers.CharField(required=False) email = serializers.EmailField(required=False) phone = serializers.CharField(required=False, allow_blank=True) address = serializers.CharField(required=False) city = serializers.CharField(required=False) postal_code = serializers.CharField(required=False) country = serializers.CharField(required=False, default="Czech Republic") note = serializers.CharField(required=False, allow_blank=True) # Nested structures #produkty items = OrderItemCreateSerializer(many=True) #doprava/vyzvednutí + zasilkovna input (serializer) carrier = OrderCarrierSerializer() payment = PaymentSerializer() #slevové kódy discount_codes = serializers.ListField( child=serializers.CharField(), required=False, allow_empty=True ) def validate(self, attrs): request = self.context.get("request") #kontrola jestli je uzivatel valid/prihlasen is_auth = bool(getattr(getattr(request, "user", None), "is_authenticated", False)) # pokud není, tak se musí vyplnit povinné údaje required_fields = [ "first_name", "last_name", "email", "address", "city", "postal_code", ] if not is_auth: missing_fields = [] # přidame fieldy, které nejsou vyplněné for field in required_fields: if attrs.get(field) not in required_fields: missing_fields.append(field) if missing_fields: raise serializers.ValidationError({"billing": f"Missing fields: {', '.join(missing_fields)}"}) # pokud chybí itemy: if not attrs.get("items"): raise serializers.ValidationError({"items": "At least one item is required."}) return attrs def create(self, validated_data): items_data = validated_data.pop("items", []) carrier_data = validated_data.pop("carrier") payment_data = validated_data.pop("payment") codes = validated_data.pop("discount_codes", []) request = self.context.get("request") user = getattr(request, "user", None) is_auth = bool(getattr(user, "is_authenticated", False)) with transaction.atomic(): # Create Order (user data imported on save if user is set) order = Order( user=user if is_auth else None, first_name=validated_data.get("first_name", ""), last_name=validated_data.get("last_name", ""), email=validated_data.get("email", ""), phone=validated_data.get("phone", ""), address=validated_data.get("address", ""), city=validated_data.get("city", ""), postal_code=validated_data.get("postal_code", ""), country=validated_data.get("country", "Czech Republic"), note=validated_data.get("note", ""), ) # Order.save se postara o to jestli má doplnit data z usera order.save() # Vytvoření Carrier skrz serializer carrier = OrderCarrierSerializer(data=carrier_data) carrier.is_valid(raise_exception=True) carrier = carrier.save() order.carrier = carrier order.save(update_fields=["carrier", "updated_at"]) # will recalc total later # Vytvořit Order Items individualně, aby se spustila kontrola položek na skladu for item in items_data: product = item["product"] # OrderItemCreateSerializer.validate quantity = int(item.get("quantity", 1)) OrderItem.objects.create(order=order, product=product, quantity=quantity) # -- Slevové kódy -- if codes: discounts = list(DiscountCode.objects.filter(code__in=codes)) if discounts: order.discount.add(*discounts) # -- Payment -- payment_serializer = PaymentSerializer( data=payment_data, context={"order": order, "carrier": carrier} ) payment_serializer.is_valid(raise_exception=True) payment = payment_serializer.save() # přiřadíme k orderu order.payment = payment order.save(update_fields=["payment"]) return order # ----------------- ADMIN/READ MODELS ----------------- class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = [ "id", "name", "url", "parent", "description", "image", ] class ProductImageSerializer(serializers.ModelSerializer): class Meta: model = ProductImage fields = [ "id", "product", "image", "alt_text", "is_main", ] class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = "__all__" read_only_fields = ["created_at", "updated_at"] class DiscountCodeSerializer(serializers.ModelSerializer): class Meta: model = DiscountCode fields = "__all__" read_only_fields = ["used_count"] class RefundSerializer(serializers.ModelSerializer): class Meta: model = Refund fields = "__all__" read_only_fields = ["id", "verified", "created_at"] # ----------------- READ SERIALIZERS USED BY VIEWS ----------------- class ZasilkovnaPacketReadSerializer(ZasilkovnaPacketSerializer): class Meta(ZasilkovnaPacketSerializer.Meta): fields = getattr(ZasilkovnaPacketSerializer.Meta, "fields", None) class CarrierReadSerializer(serializers.ModelSerializer): zasilkovna = ZasilkovnaPacketReadSerializer(many=True, read_only=True) class Meta: model = Carrier fields = ["shipping_method", "state", "zasilkovna", "shipping_price"] read_only_fields = fields class ProductMiniSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ["id", "name", "price"] class OrderItemReadSerializer(serializers.ModelSerializer): product = ProductMiniSerializer(read_only=True) class Meta: model = OrderItem fields = ["id", "product", "quantity"] read_only_fields = fields class PaymentReadSerializer(serializers.ModelSerializer): class Meta: model = Payment fields = ["payment_method"] read_only_fields = fields class OrderMiniSerializer(serializers.ModelSerializer): status = serializers.ChoiceField( choices=Order.OrderStatus.choices, read_only=True ) class Meta: model = Order fields = ["id", "status", "total_price", "created_at"] read_only_fields = fields class OrderReadSerializer(serializers.ModelSerializer): status = serializers.ChoiceField( choices=Order.OrderStatus.choices, read_only=True ) items = OrderItemReadSerializer(many=True, read_only=True) carrier = CarrierReadSerializer(read_only=True) payment = PaymentReadSerializer(read_only=True) discount_codes = serializers.SerializerMethodField() class Meta: model = Order fields = [ "id", "status", "total_price", "currency", "user", "first_name", "last_name", "email", "phone", "address", "city", "postal_code", "country", "note", "created_at", "updated_at", "items", "carrier", "payment", "discount_codes", ] read_only_fields = fields def get_discount_codes(self, obj: Order): return list(obj.discount.values_list("code", flat=True)) class ReviewSerializerPublic(serializers.ModelSerializer): class Meta: model = Review fields = "__all__" read_only_fields = ['user', 'created_at', 'updated_at'] # ----------------- CART SERIALIZERS ----------------- class CartItemSerializer(serializers.ModelSerializer): product_name = serializers.CharField(source='product.name', read_only=True) product_price = serializers.DecimalField(source='product.price', max_digits=10, decimal_places=2, read_only=True) subtotal = serializers.SerializerMethodField() class Meta: model = CartItem fields = ['id', 'product', 'product_name', 'product_price', 'quantity', 'subtotal', 'added_at'] read_only_fields = ['id', 'added_at'] def get_subtotal(self, obj): return obj.get_subtotal() def validate_quantity(self, value): if value < 1: raise serializers.ValidationError("Quantity must be at least 1") return value class CartItemCreateSerializer(serializers.Serializer): product_id = serializers.IntegerField() quantity = serializers.IntegerField(min_value=1, default=1) def validate_product_id(self, value): try: Product.objects.get(pk=value, is_active=True) except Product.DoesNotExist: raise serializers.ValidationError("Product not found or inactive.") return value class CartSerializer(serializers.ModelSerializer): items = CartItemSerializer(many=True, read_only=True) total = serializers.SerializerMethodField() items_count = serializers.SerializerMethodField() class Meta: model = Cart fields = ['id', 'user', 'items', 'total', 'items_count', 'created_at', 'updated_at'] read_only_fields = ['id', 'user', 'created_at', 'updated_at'] def get_total(self, obj): return obj.get_total() def get_items_count(self, obj): return obj.get_items_count() # ----------------- WISHLIST SERIALIZERS ----------------- class ProductMiniForWishlistSerializer(serializers.ModelSerializer): """Minimal product info for wishlist display""" class Meta: model = Product fields = ['id', 'name', 'price', 'is_active', 'stock'] class WishlistSerializer(serializers.ModelSerializer): products = ProductMiniForWishlistSerializer(many=True, read_only=True) products_count = serializers.SerializerMethodField() class Meta: model = Wishlist fields = ['id', 'user', 'products', 'products_count', 'created_at', 'updated_at'] read_only_fields = ['id', 'user', 'created_at', 'updated_at'] def get_products_count(self, obj): return obj.get_products_count() class WishlistProductActionSerializer(serializers.Serializer): """For adding/removing products from wishlist""" product_id = serializers.IntegerField() def validate_product_id(self, value): try: Product.objects.get(pk=value, is_active=True) except Product.DoesNotExist: raise serializers.ValidationError("Product not found or inactive.") return value