Compare commits

...

6 Commits

Author SHA1 Message Date
Mira Weller
4619901a37 docs: update fee_type options 2025-02-25 14:16:32 +01:00
Mira Weller
ee921a6331 docs: None -> null 2025-02-25 14:04:27 +01:00
Mira Weller
3310e9670b Add create_fees to example 2025-02-25 13:57:33 +01:00
Mira Weller
93570d42c7 Consistent order of examples 2025-02-25 13:57:01 +01:00
Mira Weller
598527073c Fix example in docs 2025-02-25 13:22:59 +01:00
Raphael Michel
d011651565 API: Allow to add a fee to an order (#4806) 2025-02-10 17:24:46 +01:00
4 changed files with 100 additions and 17 deletions

View File

@@ -75,8 +75,9 @@ positions list of objects List of order p
fees list of objects List of fees included in the order total. By default, only
non-canceled fees are included.
├ id integer Internal ID of the fee record
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
``other``)
├ fee_type string Type of fee (currently ``payment``, ``shipping``,
``service``, ``cancellation``, ``insurance``, ``late``,
``other``, ``giftcard``)
├ value money (string) Fee amount
├ description string Human-readable string with more details (can be empty)
├ internal_type string Internal string (i.e. ID of the payment provider),
@@ -2244,6 +2245,9 @@ otherwise, such as splitting an order or changing fees.
* ``cancel_fees``: A list of objects with the single key ``fee`` specifying an order fee ID.
* ``create_fees``: A list of objects describing new order fees with the fields ``fee_type``, ``value``, ``description``,
``internal_type``, ``tax_rule``
* ``recalculate_taxes``: If set to ``"keep_net"``, all taxes will be recalculated based on the tax rule and invoice
address, the net price will be kept. If set to ``"keep_gross"``, the gross price will be kept. If set to ``null``
(the default) the taxes are not recalculated.
@@ -2263,17 +2267,12 @@ otherwise, such as splitting an order or changing fees.
Content-Type: application/json
{
"cancel_positions": [
{
"position": 12373
}
],
"patch_positions": [
{
"position": 12374,
"body": {
"item": 12,
"variation": None,
"variation": null,
"subevent": 562,
"seat": "seat-guid-2",
"price": "99.99",
@@ -2281,6 +2280,11 @@ otherwise, such as splitting an order or changing fees.
}
}
],
"cancel_positions": [
{
"position": 12373
}
],
"split_positions": [
{
"position": 12375
@@ -2289,7 +2293,7 @@ otherwise, such as splitting an order or changing fees.
"create_positions": [
{
"item": 12,
"variation": None,
"variation": null,
"subevent": 562,
"seat": "seat-guid-2",
"price": "99.99",
@@ -2297,12 +2301,7 @@ otherwise, such as splitting an order or changing fees.
"attendee_name": "Peter",
}
],
"cancel_fees": [
{
"fee": 49
}
],
"change_fees": [
"patch_fees": [
{
"fee": 51,
"body": {
@@ -2310,6 +2309,20 @@ otherwise, such as splitting an order or changing fees.
}
}
],
"cancel_fees": [
{
"fee": 49
}
],
"create_fees": [
{
"fee_type": "other",
"value": "1.50",
"description": "Example Fee",
"internal_type": "",
"tax_rule": 15
}
],
"reissue_invoice": true,
"send_email": true,
"recalculate_taxes": "keep_gross"

View File

@@ -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
)

View File

@@ -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

View File

@@ -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