from decimal import Decimal from django.db import transaction from rest_framework import serializers from . import models class ProductCategorySerializer(serializers.ModelSerializer): class Meta: model = models.ProductCategory fields = ( "id", "name", "slug", "description", "display_order", "is_active", ) class ProductSerializer(serializers.ModelSerializer): category = ProductCategorySerializer(read_only=True) category_id = serializers.PrimaryKeyRelatedField( source="category", queryset=models.ProductCategory.objects.all(), required=False, allow_null=True, write_only=True, ) class Meta: model = models.Product fields = ( "id", "name", "slug", "short_description", "description", "ingredients", "allergens", "price", "inventory_quantity", "max_per_order", "is_active", "is_featured", "hero_image", "category", "category_id", ) class DeliveryZoneSerializer(serializers.ModelSerializer): is_active = serializers.BooleanField(required=False) class Meta: model = models.DeliveryZone fields = ("id", "label", "postal_code", "is_active") class DeliveryOverrideCodeSerializer(serializers.ModelSerializer): can_use = serializers.SerializerMethodField() class Meta: model = models.DeliveryOverrideCode fields = ( "id", "code", "label", "message_to_customer", "max_uses", "usage_count", "is_active", "can_use", ) read_only_fields = ("usage_count", "can_use") def get_can_use(self, obj: models.DeliveryOverrideCode) -> bool: return obj.can_use() class OrderItemSerializer(serializers.ModelSerializer): line_subtotal = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) class Meta: model = models.OrderItem fields = ( "id", "product", "product_name", "product_slug", "unit_price", "quantity", "line_subtotal", ) read_only_fields = ("product_name", "product_slug", "unit_price", "line_subtotal") class OrderSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True) subtotal_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) tax_amount = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True) delivery_fee_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) discount_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) total_amount = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True) class Meta: model = models.Order fields = ( "id", "customer_name", "customer_email", "customer_phone", "fulfillment_method", "status", "scheduled_date", "scheduled_time_slot", "delivery_address_line1", "delivery_address_line2", "delivery_city", "delivery_state", "delivery_postal_code", "delivery_override_code", "subtotal_amount", "tax_amount", "delivery_fee_amount", "discount_amount", "total_amount", "notes", "items", "created_at", "updated_at", ) read_only_fields = ("status", "created_at", "updated_at") def create(self, validated_data): items_data = validated_data.pop("items", []) delivery_fee_amount = Decimal(validated_data.pop("delivery_fee_amount", Decimal("0"))) discount_amount = Decimal(validated_data.pop("discount_amount", Decimal("0"))) tax_amount = Decimal(validated_data.pop("tax_amount", Decimal("0"))) if not items_data: raise serializers.ValidationError("Order requires at least one item.") with transaction.atomic(): subtotal = Decimal("0") order_items = [] for item_data in items_data: product = item_data.get("product") if product is None: raise serializers.ValidationError("Each order item must reference a product.") quantity = item_data.get("quantity", 1) if quantity > product.inventory_quantity: raise serializers.ValidationError( f"Insufficient inventory for {product.name}. Requested {quantity}, " f"available {product.inventory_quantity}." ) line_subtotal = product.price * quantity subtotal += line_subtotal order_items.append( { "product": product, "product_name": product.name, "product_slug": product.slug, "unit_price": product.price, "quantity": quantity, "line_subtotal": line_subtotal, } ) product.inventory_quantity -= quantity product.save(update_fields=["inventory_quantity"]) total_amount = subtotal + tax_amount + delivery_fee_amount - discount_amount order = models.Order.objects.create( subtotal_amount=subtotal, tax_amount=tax_amount, delivery_fee_amount=delivery_fee_amount, discount_amount=discount_amount, total_amount=total_amount, **validated_data, ) models.OrderItem.objects.bulk_create( [models.OrderItem(order=order, **item) for item in order_items] ) return order class OrderAdminSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True, read_only=True) subtotal_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) tax_amount = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True) delivery_fee_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) discount_amount = serializers.DecimalField( max_digits=8, decimal_places=2, read_only=True ) total_amount = serializers.DecimalField(max_digits=8, decimal_places=2, read_only=True) class Meta: model = models.Order fields = ( "id", "customer_name", "customer_email", "customer_phone", "fulfillment_method", "status", "scheduled_date", "scheduled_time_slot", "delivery_address_line1", "delivery_address_line2", "delivery_city", "delivery_state", "delivery_postal_code", "delivery_override_code", "subtotal_amount", "tax_amount", "delivery_fee_amount", "discount_amount", "total_amount", "notes", "items", "created_at", "updated_at", ) read_only_fields = ( "subtotal_amount", "tax_amount", "delivery_fee_amount", "discount_amount", "total_amount", "created_at", "updated_at", )