Refactor order creation and add configuration endpoints
Refactored order creation logic to use new serializers and transaction handling, improving validation and modularity. Introduced admin and public endpoints for shop configuration with sensitive fields protected. Enhanced Zásilkovna (Packeta) integration, including packet widget template, new API fields, and improved error handling. Added django-silk for profiling, updated requirements and settings, and improved frontend Orval config for API client generation.
This commit is contained in:
@@ -228,7 +228,14 @@ class Carrier(models.Model):
|
||||
|
||||
returning = models.BooleanField(default=False, help_text="Zda je tato zásilka na vrácení")
|
||||
|
||||
shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk is None:
|
||||
|
||||
if self.shipping_price is None:
|
||||
self.shipping_price = self.get_price()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_price(self):
|
||||
@@ -289,28 +296,8 @@ class Payment(models.Model):
|
||||
StripeModel, on_delete=models.CASCADE, null=True, blank=True, related_name="payment"
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.order:
|
||||
order = Order.objects.get(payment=self)
|
||||
|
||||
#validace platebních metod
|
||||
if self.payment_method == self.PAYMENT.SHOP and Carrier.objects.get(orders=order).shipping_method != Carrier.SHIPPING.STORE:
|
||||
raise ValueError("Platba v obchodě je možná pouze pro osobní odběr.")
|
||||
|
||||
#validace dobírky (jestli není použitá pro osobní odběr)
|
||||
elif self.payment_method == self.PAYMENT.CASH_ON_DELIVERY and Carrier.objects.get(orders=order).shipping_method == Carrier.SHIPPING.STORE:
|
||||
raise ValueError("Dobírka není možná pro osobní odběr.")
|
||||
|
||||
#vytvoření platebních metod pokud nový objekt
|
||||
if not self.pk:
|
||||
if self.payment_method == self.PAYMENT.STRIPE:
|
||||
self.stripe = StripePayment.objects.create(amount=order.total_price)
|
||||
else:
|
||||
self.stripe = None
|
||||
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
# ------------------ SLEVOVÉ KÓDY ------------------
|
||||
@@ -563,7 +550,7 @@ class Refund(models.Model):
|
||||
"return_reason": return_reason,
|
||||
}
|
||||
|
||||
#TODO: přesunou zásilkovna field tady taky (zkontrolovat jestli jsou views napojené a použít metodu send z carrier)
|
||||
|
||||
carrier = models.OneToOneField(
|
||||
"Carrier",
|
||||
on_delete=models.CASCADE,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from backend.thirdparty.stripe.client import StripeClient
|
||||
|
||||
from .models import Refund, Order, Invoice
|
||||
|
||||
|
||||
@@ -69,6 +71,9 @@ 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,
|
||||
@@ -82,155 +87,242 @@ from .models import (
|
||||
Payment,
|
||||
)
|
||||
|
||||
from thirdparty.stripe.models import StripeModel, StripePayment
|
||||
|
||||
from thirdparty.zasilkovna.serializers import ZasilkovnaPacketSerializer
|
||||
from thirdparty.zasilkovna.models import ZasilkovnaPacket
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# ----------------- CREATING ORDER SERIALIZER -----------------
|
||||
|
||||
class UserBriefSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "first_name", "last_name", "email"]
|
||||
#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 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"]
|
||||
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
|
||||
|
||||
|
||||
class PaymentReadSerializer(serializers.Serializer):
|
||||
payment_method = serializers.CharField(read_only=True)
|
||||
stripe_id = serializers.IntegerField(source="stripe.id", read_only=True, allow_null=True)
|
||||
#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
|
||||
|
||||
|
||||
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
|
||||
# -- PAYMENT --
|
||||
class PaymentSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
model = Payment
|
||||
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",
|
||||
"payment_method",
|
||||
"stripe",
|
||||
"stripe_session_id",
|
||||
"stripe_payment_intent",
|
||||
"stripe_session_url",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"status",
|
||||
"total_price",
|
||||
"currency",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"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")
|
||||
|
||||
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)
|
||||
with transaction.atomic():
|
||||
payment = Payment.objects.create(
|
||||
order=order,
|
||||
carrier=carrier,
|
||||
**validated_data
|
||||
)
|
||||
|
||||
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'",
|
||||
)
|
||||
# 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)
|
||||
|
||||
payment.stripe_session_id = session.id
|
||||
payment.stripe_payment_intent = session.payment_intent
|
||||
payment.stripe_session_url = session.url
|
||||
|
||||
payment.save(update_fields=[
|
||||
"stripe_session_id",
|
||||
"stripe_payment_intent",
|
||||
"stripe_session_url",
|
||||
])
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
# -- ORDER CREATE SERIALIZER --
|
||||
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")
|
||||
# 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
|
||||
# Nested structures
|
||||
#produkty
|
||||
items = OrderItemCreateSerializer(many=True)
|
||||
carrier = CarrierCreateSerializer()
|
||||
payment = PaymentCreateSerializer()
|
||||
|
||||
#doprava/vyzvednutí + zasilkovna input (serializer)
|
||||
carrier = OrderCarrierSerializer()
|
||||
|
||||
payment = PaymentSerializer()
|
||||
|
||||
#slevové kódy
|
||||
discount_codes = serializers.ListField(
|
||||
child=serializers.CharField(), required=False, allow_empty=True, label="Discount Codes"
|
||||
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 -----------------
|
||||
|
||||
@@ -262,55 +354,102 @@ class ProductImageSerializer(serializers.ModelSerializer):
|
||||
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",
|
||||
]
|
||||
fields = "__all__"
|
||||
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",
|
||||
]
|
||||
fields = "__all__"
|
||||
read_only_fields = ["used_count"]
|
||||
|
||||
|
||||
class RefundSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Refund
|
||||
fields = [
|
||||
"id",
|
||||
"order",
|
||||
"reason_choice",
|
||||
"reason_text",
|
||||
"verified",
|
||||
"created_at",
|
||||
]
|
||||
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):
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ["id", "status", "total_price", "created_at"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class OrderReadSerializer(serializers.ModelSerializer):
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ from .serializers import (
|
||||
RefundSerializer,
|
||||
)
|
||||
|
||||
|
||||
#FIXME: uravit view na nový order serializer
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Orders"], summary="List Orders (public)"),
|
||||
retrieve=extend_schema(tags=["Orders"], summary="Retrieve Order (public)"),
|
||||
@@ -63,7 +63,7 @@ class OrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Ge
|
||||
return OrderReadSerializer
|
||||
|
||||
@extend_schema(
|
||||
tags=["Orders"],
|
||||
tags=["Order"],
|
||||
summary="Create Order (public)",
|
||||
request=OrderCreateSerializer,
|
||||
responses={201: OrderReadSerializer},
|
||||
@@ -92,87 +92,10 @@ class OrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Ge
|
||||
],
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = OrderCreateSerializer(data=request.data)
|
||||
serializer = OrderCreateSerializer(data=request.data, context={"request": request})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
user = request.user
|
||||
|
||||
#VELMI DŮLEŽITELÉ: vše vytvořit v transakci, aby se nepřidávaly neúplné objednávky
|
||||
with transaction.atomic():
|
||||
|
||||
# Create base order (customer details only for now)
|
||||
order = Order.objects.create(
|
||||
user=user if getattr(user, "is_authenticated", False) else None,
|
||||
first_name=data["first_name"],
|
||||
last_name=data["last_name"],
|
||||
email=data["email"],
|
||||
phone=data.get("phone", ""),
|
||||
address=data["address"],
|
||||
city=data["city"],
|
||||
postal_code=data["postal_code"],
|
||||
country=data.get("country", "Czech Republic"),
|
||||
note=data.get("note", ""),
|
||||
)
|
||||
|
||||
# Carrier
|
||||
carrier_payload = data["carrier"]
|
||||
carrier = Carrier.objects.create(
|
||||
shipping_method=carrier_payload["shipping_method"],
|
||||
weight=carrier_payload.get("weight"),
|
||||
)
|
||||
order.carrier = carrier
|
||||
order.save(update_fields=["carrier", "updated_at"]) # recalc later after items
|
||||
|
||||
# Items
|
||||
items_payload = data["items"]
|
||||
order_items = []
|
||||
for item in items_payload:
|
||||
product = Product.objects.get(pk=item["product_id"]) # raises 404 if missing
|
||||
qty = int(item["quantity"])
|
||||
order_items.append(OrderItem(order=order, product=product, quantity=qty))
|
||||
OrderItem.objects.bulk_create(order_items)
|
||||
|
||||
# Discount codes (optional)
|
||||
codes = data.get("discount_codes") or []
|
||||
if codes:
|
||||
discounts = list(DiscountCode.objects.filter(code__in=codes))
|
||||
order.discount.add(*discounts)
|
||||
|
||||
# Recalculate now that items/discounts/carrier are linked
|
||||
order.save()
|
||||
|
||||
# Payment and validation
|
||||
pay_payload = data["payment"]
|
||||
payment_method = pay_payload["payment_method"]
|
||||
|
||||
# Validate combos (mirror of Payment.save but here we have order)
|
||||
if payment_method == Payment.PAYMENT.SHOP and order.carrier.shipping_method != Carrier.SHIPPING.STORE:
|
||||
return Response(
|
||||
{"payment": "Platba v obchodě je možná pouze pro osobní odběr."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if payment_method == Payment.PAYMENT.CASH_ON_DELIVERY and order.carrier.shipping_method == Carrier.SHIPPING.STORE:
|
||||
return Response(
|
||||
{"payment": "Dobírka není možná pro osobní odběr."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Create payment WITHOUT triggering Payment.save (which expects reverse link first)
|
||||
payment = Payment(payment_method=payment_method)
|
||||
# Bypass custom save by bulk_create
|
||||
Payment.objects.bulk_create([payment])
|
||||
order.payment = payment
|
||||
order.save(update_fields=["payment", "updated_at"])
|
||||
|
||||
# If Stripe, create StripePayment now and attach
|
||||
if payment_method == Payment.PAYMENT.STRIPE:
|
||||
from thirdparty.stripe.models import StripePayment
|
||||
|
||||
stripe_obj = StripePayment.objects.create(amount=order.total_price)
|
||||
payment.stripe = stripe_obj
|
||||
payment.save(update_fields=["stripe"])
|
||||
|
||||
out = self.get_serializer(order)
|
||||
order = serializer.save()
|
||||
out = OrderReadSerializer(order)
|
||||
return Response(out.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
# -- List mini orders -- (public) --
|
||||
|
||||
Reference in New Issue
Block a user