from rest_framework import serializers from .models import Refund, Order, Invoice 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 .models import ( Category, Product, ProductImage, DiscountCode, Refund, Order, OrderItem, Carrier, Payment, ) User = get_user_model() class UserBriefSerializer(serializers.ModelSerializer): class Meta: model = User fields = ["id", "first_name", "last_name", "email"] class ProductBriefSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ["id", "name", "price"] class OrderItemReadSerializer(serializers.ModelSerializer): product = ProductBriefSerializer(read_only=True) total_price = serializers.SerializerMethodField() def get_total_price(self, obj): return obj.get_total_price(list(obj.order.discount.all()) if getattr(obj, "order", None) else None) class Meta: model = OrderItem fields = ["id", "product", "quantity", "total_price"] read_only_fields = ["id", "total_price"] class CarrierReadSerializer(serializers.ModelSerializer): class Meta: model = Carrier fields = ["id", "shipping_method", "weight", "returning"] read_only_fields = ["id"] class PaymentReadSerializer(serializers.Serializer): payment_method = serializers.CharField(read_only=True) stripe_id = serializers.IntegerField(source="stripe.id", read_only=True, allow_null=True) class OrderReadSerializer(serializers.ModelSerializer): items = OrderItemReadSerializer(many=True, read_only=True) carrier = CarrierReadSerializer(read_only=True) payment = PaymentReadSerializer(source="payment", read_only=True) user = serializers.SerializerMethodField() def get_user(self, obj): request = self.context.get("request") if hasattr(self, "context") else None if request and getattr(request, "user", None) and request.user.is_authenticated and obj.user: return UserBriefSerializer(obj.user).data return None class Meta: model = Order fields = [ "id", "status", "total_price", "currency", "first_name", "last_name", "email", "phone", "address", "city", "postal_code", "country", "note", "created_at", "updated_at", "carrier", "payment", "user", "items", ] read_only_fields = [ "id", "status", "total_price", "currency", "created_at", "updated_at", ] class OrderMiniSerializer(serializers.ModelSerializer): amount = serializers.DecimalField(max_digits=10, decimal_places=2, source="total_price", read_only=True) shipping_method = serializers.CharField(source="carrier.shipping_method", read_only=True) class Meta: model = Order fields = ["id", "amount", "status", "email", "shipping_method"] read_only_fields = fields # ----------------- CREATE PAYLOAD SERIALIZERS (PUBLIC) ----------------- class OrderItemCreateSerializer(serializers.Serializer): product_id = serializers.IntegerField(label="Product ID") quantity = serializers.IntegerField(min_value=1, label="Quantity") class CarrierCreateSerializer(serializers.Serializer): shipping_method = serializers.ChoiceField( choices=Carrier.SHIPPING.choices, label="Shipping Method", help_text="Choose 'store' (pickup) or 'packeta'", ) weight = serializers.DecimalField( required=False, max_digits=10, decimal_places=2, label="Weight (kg)", ) class PaymentCreateSerializer(serializers.Serializer): payment_method = serializers.ChoiceField( choices=Payment.PAYMENT.choices, label="Payment Method", help_text="Choose 'shop', 'stripe' or 'cash_on_delivery'", ) class OrderCreateSerializer(serializers.Serializer): # Customer/billing first_name = serializers.CharField(max_length=100, label="First Name") last_name = serializers.CharField(max_length=100, label="Last Name") email = serializers.EmailField(label="Email") phone = serializers.CharField(max_length=20, required=False, allow_blank=True, label="Phone") address = serializers.CharField(max_length=255, label="Address") city = serializers.CharField(max_length=100, label="City") postal_code = serializers.CharField(max_length=20, label="Postal Code") country = serializers.CharField(max_length=100, required=False, default="Czech Republic", label="Country") note = serializers.CharField(required=False, allow_blank=True, label="Note") # Nested items = OrderItemCreateSerializer(many=True) carrier = CarrierCreateSerializer() payment = PaymentCreateSerializer() discount_codes = serializers.ListField( child=serializers.CharField(), required=False, allow_empty=True, label="Discount Codes" ) def validate(self, attrs): if not attrs.get("items"): raise serializers.ValidationError({"items": "At least one item is required."}) return attrs # ----------------- 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 = [ "id", "name", "description", "code", "category", "price", "url", "stock", "is_active", "limited_to", "default_carrier", "created_at", "updated_at", ] read_only_fields = ["created_at", "updated_at"] class DiscountCodeSerializer(serializers.ModelSerializer): class Meta: model = DiscountCode fields = [ "id", "code", "description", "percent", "amount", "valid_from", "valid_to", "active", "usage_limit", "used_count", "specific_products", "specific_categories", ] read_only_fields = ["used_count"] class RefundSerializer(serializers.ModelSerializer): class Meta: model = Refund fields = [ "id", "order", "reason_choice", "reason_text", "verified", "created_at", ] read_only_fields = ["id", "verified", "created_at"]