chore: initial import
This commit is contained in:
253
api/apps/commerce/serializers.py
Normal file
253
api/apps/commerce/serializers.py
Normal file
@@ -0,0 +1,253 @@
|
||||
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",
|
||||
)
|
||||
Reference in New Issue
Block a user