API: Allow to add a fee to an order (#4806)

This commit is contained in:
Raphael Michel
2025-02-10 17:24:46 +01:00
parent 0079be68d3
commit d011651565
4 changed files with 75 additions and 2 deletions

View File

@@ -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 * ``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``). ``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. * ``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 * ``recalculate_taxes``: If set to ``"keep_net"``, all taxes will be recalculated based on the tax rule and invoice

View File

@@ -30,7 +30,7 @@ from rest_framework.exceptions import ValidationError
from pretix.api.serializers.order import ( from pretix.api.serializers.order import (
AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField, AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField,
OrderPositionCreateSerializer, OrderFeeCreateSerializer, OrderPositionCreateSerializer,
) )
from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition
from pretix.base.services.orders import OrderError from pretix.base.services.orders import OrderError
@@ -104,6 +104,54 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
raise ValidationError(str(e)) 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): class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True) answers = AnswerSerializer(many=True)
country = CompatibleCountryField(source='*') country = CompatibleCountryField(source='*')
@@ -401,6 +449,9 @@ class OrderChangeOperationSerializer(serializers.Serializer):
self.fields['split_positions'] = SelectPositionSerializer( self.fields['split_positions'] = SelectPositionSerializer(
many=True, required=False, context=self.context many=True, required=False, context=self.context
) )
self.fields['create_fees'] = OrderFeeCreateForExistingOrderSerializer(
many=True, required=False, context=self.context
)
self.fields['patch_fees'] = PatchFeeSerializer( self.fields['patch_fees'] = PatchFeeSerializer(
many=True, required=False, context=self.context many=True, required=False, context=self.context
) )

View File

@@ -63,7 +63,8 @@ from pretix.api.serializers.order import (
) )
from pretix.api.serializers.orderchange import ( from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer, BlockNameSerializer, OrderChangeOperationSerializer,
OrderFeeChangeSerializer, OrderPositionChangeSerializer, OrderFeeChangeSerializer, OrderFeeCreateForExistingOrderSerializer,
OrderPositionChangeSerializer,
OrderPositionCreateForExistingOrderSerializer, OrderPositionCreateForExistingOrderSerializer,
OrderPositionInfoPatchSerializer, OrderPositionInfoPatchSerializer,
) )
@@ -988,6 +989,12 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
ocm.cancel_fee(r['fee']) ocm.cancel_fee(r['fee'])
canceled_fees.add(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', []): for r in serializer.validated_data.get('patch_fees', []):
if r['fee'] in canceled_fees: if r['fee'] in canceled_fees:
continue continue

View File

@@ -1797,6 +1797,13 @@ def test_order_change_cancel_and_create(token_client, organizer, event, order, q
'price': '99.99' 'price': '99.99'
}, },
], ],
'create_fees': [
{
'value': '5.99',
'fee_type': 'service',
'description': 'Service fee',
},
],
'cancel_fees': [ 'cancel_fees': [
{ {
'fee': f.pk, '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') assert p_new.price == Decimal('99.99')
f.refresh_from_db() f.refresh_from_db()
assert f.canceled 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 @pytest.mark.django_db