From 247c4c6c9c4e6548d403787aa81d177e31d9698e Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Mon, 29 Apr 2024 18:11:20 +0200 Subject: [PATCH] Do not remove unavailable addons when changing order (Z#23150855) (#4086) --- src/pretix/base/services/orders.py | 14 +++++++++ src/tests/presale/test_order_change.py | 42 +++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 297191e427..843c0bdaa3 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -2013,6 +2013,20 @@ class OrderChangeManager: for a in current_addons[cp][k][:current_num - input_num]: if a.canceled: continue + is_unavailable = ( + # If an item is no longer available due to time, it should usually also be no longer + # user-removable, because e.g. the stock has already been ordered. + # We always pass has_voucher=True because if a product now requires a voucher, it usually does + # not mean it should be unremovable for others. + # This also prevents accidental removal through the UI because a hidden product will no longer + # be part of the input. + (a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent)) + or (a.variation and self.order.sales_channel not in a.variation.sales_channels) + or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent) + or self.order.sales_channel not in item.sales_channels + ) + if is_unavailable: + continue if a.checkins.filter(list__consider_tickets_used=True).exists(): raise OrderError( error_messages['addon_already_checked_in'] % { diff --git a/src/tests/presale/test_order_change.py b/src/tests/presale/test_order_change.py index f4ebe78eca..c6ac22d8d2 100644 --- a/src/tests/presale/test_order_change.py +++ b/src/tests/presale/test_order_change.py @@ -993,6 +993,47 @@ class OrderChangeAddonsTest(BaseOrdersTest): self.order.refresh_from_db() assert self.order.total == Decimal('23.00') + def test_do_not_remove_unavailable_on_adding(self): + self.iao.max_count = 2 + self.iao.save() + self.workshop1.available_until = now() - datetime.timedelta(days=1) + self.workshop1.save() + with scopes_disabled(): + OrderPosition.objects.create( + order=self.order, + item=self.workshop1, + variation=None, + price=Decimal("12"), + addon_to=self.ticket_pos, + attendee_name_parts={'full_name': "Peter"} + ) + self.order.total += Decimal("12") + self.order.save() + + response = self.client.get( + '/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret) + ) + assert response.status_code == 200 + assert 'Workshop 1' not in response.content.decode() + + response = self.client.post( + '/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), + { + f'cp_{self.ticket_pos.pk}_variation_{self.workshop2.pk}_{self.workshop2a.pk}': '1' + }, + follow=True + ) + doc = BeautifulSoup(response.content.decode(), "lxml") + form_data = extract_form_fields(doc.select('.main-box form')[0]) + form_data['confirm'] = 'true' + response = self.client.post( + '/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), form_data, follow=True + ) + assert 'alert-success' in response.content.decode() + + with scopes_disabled(): + assert self.ticket_pos.addons.count() == 2 + def test_remove_addon_checked_in(self): with scopes_disabled(): self.event.settings.change_allow_user_if_checked_in = True @@ -1578,7 +1619,6 @@ class OrderChangeAddonsTest(BaseOrdersTest): }, follow=True ) - print(response.content.decode()) assert 'alert-danger' not in response.content.decode() response = self.client.post(