diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index f742801c83..4cda35d172 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -2242,6 +2242,9 @@ otherwise, such as splitting an order or changing fees. * ``patch_fees``: A list of objects with the two keys ``fee`` specifying an order fee ID and ``body`` specifying the desired changed values of the position (``value``). + * ``create_fees``: A list of objects describing new order fees with the fields ``fee_type``, ``value``, ``description``, + ``internal_type``, ``tax_rule`` + * ``cancel_fees``: A list of objects with the single key ``fee`` specifying an order fee ID. * ``recalculate_taxes``: If set to ``"keep_net"``, all taxes will be recalculated based on the tax rule and invoice diff --git a/src/pretix/api/serializers/orderchange.py b/src/pretix/api/serializers/orderchange.py index 8424eb0620..5998f7eebd 100644 --- a/src/pretix/api/serializers/orderchange.py +++ b/src/pretix/api/serializers/orderchange.py @@ -30,7 +30,7 @@ from rest_framework.exceptions import ValidationError from pretix.api.serializers.order import ( AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField, - OrderPositionCreateSerializer, + OrderFeeCreateSerializer, OrderPositionCreateSerializer, ) from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition from pretix.base.services.orders import OrderError @@ -104,6 +104,54 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize raise ValidationError(str(e)) +class OrderFeeCreateForExistingOrderSerializer(OrderFeeCreateSerializer): + order = serializers.SlugRelatedField(slug_field='code', queryset=Order.objects.none(), required=True, allow_null=False) + value = serializers.DecimalField(required=True, allow_null=False, decimal_places=2, + max_digits=13) + internal_type = serializers.CharField(required=False, default="") + + class Meta: + model = OrderFee + fields = ('order', 'fee_type', 'value', 'description', 'internal_type', 'tax_rule') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.context: + return + self.fields['order'].queryset = self.context['event'].orders.all() + self.fields['tax_rule'].queryset = self.context['event'].tax_rules.all() + if 'order' in self.context: + del self.fields['order'] + + def validate(self, data): + data = super().validate(data) + if 'order' in self.context: + data['order'] = self.context['order'] + return data + + def create(self, validated_data): + ocm = self.context['ocm'] + + try: + f = OrderFee( + order=validated_data['order'], + fee_type=validated_data['fee_type'], + value=validated_data.get('value'), + description=validated_data.get('description'), + internal_type=validated_data.get('internal_type'), + tax_rule=validated_data.get('tax_rule'), + ) + f._calculate_tax() + ocm.add_fee(f) + if self.context.get('commit', True): + ocm.commit() + return validated_data['order'].fees.order_by('-pk').first() + else: + return OrderFee() # fake to appease DRF + except OrderError as e: + raise ValidationError(str(e)) + + class OrderPositionInfoPatchSerializer(serializers.ModelSerializer): answers = AnswerSerializer(many=True) country = CompatibleCountryField(source='*') @@ -401,6 +449,9 @@ class OrderChangeOperationSerializer(serializers.Serializer): self.fields['split_positions'] = SelectPositionSerializer( many=True, required=False, context=self.context ) + self.fields['create_fees'] = OrderFeeCreateForExistingOrderSerializer( + many=True, required=False, context=self.context + ) self.fields['patch_fees'] = PatchFeeSerializer( many=True, required=False, context=self.context ) diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 75459370b9..d9ebee4d5a 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -63,7 +63,8 @@ from pretix.api.serializers.order import ( ) from pretix.api.serializers.orderchange import ( BlockNameSerializer, OrderChangeOperationSerializer, - OrderFeeChangeSerializer, OrderPositionChangeSerializer, + OrderFeeChangeSerializer, OrderFeeCreateForExistingOrderSerializer, + OrderPositionChangeSerializer, OrderPositionCreateForExistingOrderSerializer, OrderPositionInfoPatchSerializer, ) @@ -988,6 +989,12 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet): ocm.cancel_fee(r['fee']) canceled_fees.add(r['fee']) + for r in serializer.validated_data.get('create_fees', []): + pos_serializer = OrderFeeCreateForExistingOrderSerializer( + context={'ocm': ocm, 'commit': False, 'event': request.event, **self.get_serializer_context()}, + ) + pos_serializer.create(r) + for r in serializer.validated_data.get('patch_fees', []): if r['fee'] in canceled_fees: continue diff --git a/src/tests/api/test_order_change.py b/src/tests/api/test_order_change.py index b1c601a382..a956950f32 100644 --- a/src/tests/api/test_order_change.py +++ b/src/tests/api/test_order_change.py @@ -1797,6 +1797,13 @@ def test_order_change_cancel_and_create(token_client, organizer, event, order, q 'price': '99.99' }, ], + 'create_fees': [ + { + 'value': '5.99', + 'fee_type': 'service', + 'description': 'Service fee', + }, + ], 'cancel_fees': [ { 'fee': f.pk, @@ -1818,6 +1825,11 @@ def test_order_change_cancel_and_create(token_client, organizer, event, order, q assert p_new.price == Decimal('99.99') f.refresh_from_db() assert f.canceled + f_new = order.all_fees.get(fee_type=OrderFee.FEE_TYPE_SERVICE) + assert f_new.value == Decimal('5.99') + assert f_new.description == "Service fee" + assert f_new.internal_type == "" + assert f_new.tax_value == Decimal('0.00') @pytest.mark.django_db