diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index fe98d93e98..d345541506 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -1070,6 +1070,7 @@ Creating orders * ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product) * ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected) * ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID) + * ``add_to_reusable_medium`` (optional, causes the new ticket to be added to the given reusable medium, identified by its ID) * ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run) * ``answers`` diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 3fcf13ed34..351e3c87fd 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -1043,13 +1043,15 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer): requested_valid_from = serializers.DateTimeField(required=False, allow_null=True) use_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(), required=False, allow_null=True) + add_to_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(), + required=False, allow_null=True) class Meta: model = OrderPosition fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email', 'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled', 'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until', - 'requested_valid_from', 'use_reusable_medium', 'discount') + 'requested_valid_from', 'use_reusable_medium', 'add_to_reusable_medium', 'discount') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1061,6 +1063,8 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer): with scopes_disabled(): if 'use_reusable_medium' in self.fields: self.fields['use_reusable_medium'].queryset = ReusableMedium.objects.all() + if 'add_to_reusable_medium' in self.fields: + self.fields['add_to_reusable_medium'].queryset = ReusableMedium.objects.all() def validate_secret(self, secret): if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists(): @@ -1076,6 +1080,9 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer): ) return m + def validate_add_to_reusable_medium(self, m): + return self.validate_use_reusable_medium(m) + def validate_item(self, item): if item.event != self.context['event']: raise ValidationError( @@ -1149,6 +1156,13 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer): raise ValidationError( {'discount': ['You can only specify a discount if you do the price computation, but price is not set.']} ) + + if data.get('use_reusable_medium') and data.get('add_to_reusable_medium'): + raise ValidationError({ + 'use_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'], + 'add_to_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'], + }) + return data @@ -1585,7 +1599,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): pos_data['attendee_name_parts'] = { '_legacy': attendee_name } - pos = OrderPosition(**{k: v for k, v in pos_data.items() if k != 'answers' and k != '_quotas' and k != 'use_reusable_medium'}) + pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium', 'add_to_reusable_medium')}) if simulate: pos.order = order._wrapped else: @@ -1659,6 +1673,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): for pos_data in positions_data: answers_data = pos_data.pop('answers', []) use_reusable_medium = pos_data.pop('use_reusable_medium', None) + add_to_reusable_medium = pos_data.pop('add_to_reusable_medium', None) pos = pos_data['__instance'] pos._calculate_tax(invoice_address=ia) @@ -1700,8 +1715,17 @@ class OrderCreateSerializer(I18nAwareModelSerializer): answ.options.add(*options) if use_reusable_medium: - use_reusable_medium.linked_orderpositions.add(pos) + use_reusable_medium.linked_orderpositions.set([pos]) use_reusable_medium.log_action( + 'pretix.reusable_medium.linked_orderposition.changed', + data={ + 'by_order': order.code, + 'linked_orderposition': pos.pk, + } + ) + if add_to_reusable_medium: + add_to_reusable_medium.linked_orderpositions.add(pos) + add_to_reusable_medium.log_action( 'pretix.reusable_medium.linked_orderposition.added', data={ 'by_order': order.code,