diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 06a112a637..e4ff08b99c 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -1677,6 +1677,8 @@ DEFAULTS = { ('gte', _('Only allow changes if the resulting price is higher or equal than the previous price.')), ('gt', _('Only allow changes if the resulting price is higher than the previous price.')), ('eq', _('Only allow changes if the resulting price is equal to the previous price.')), + ('gte_paid', _('Allow changes regardless of price, as long as no refund is required (i.e. the resulting ' + 'price is not lower than what has already been paid).')), ('any', _('Allow changes regardless of price, even if this results in a refund.')), ) ), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 7edaa6ce4e..cf3df6112f 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -1595,6 +1595,8 @@ class OrderChangeMixin: raise OrderError(_('You may only change your order in a way that increases the total price.')) if ocm._totaldiff != Decimal('0.00') and pr == 'eq': raise OrderError(_('You may not change your order in a way that changes the total price.')) + if ocm._totaldiff < Decimal('0.00') and self.order.total + ocm._totaldiff < self.order.payment_refund_sum and pr == 'gte_paid': + raise OrderError(_('You may not change your order in a way that would require a refund.')) if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PAID: self.order.set_expires( diff --git a/src/tests/presale/test_order_change.py b/src/tests/presale/test_order_change.py index 4ac3876c18..b2d05d7554 100644 --- a/src/tests/presale/test_order_change.py +++ b/src/tests/presale/test_order_change.py @@ -1434,6 +1434,53 @@ class OrderChangeAddonsTest(BaseOrdersTest): self.order.refresh_from_db() assert self.order.total == Decimal('35.00') + def test_allow_user_price_gte_paid(self): + self.event.settings.change_allow_user_price = 'gte_paid' + 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() + self.order.payments.create(amount=Decimal("23"), provider="manual", state=OrderPayment.PAYMENT_STATE_CONFIRMED) + + response = self.client.post( + '/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), + { + }, + follow=True + ) + assert 'alert-danger' not in response.content.decode() + + with scopes_disabled(): + self.order.payments.create(amount=Decimal("12"), provider="manual", state=OrderPayment.PAYMENT_STATE_CONFIRMED) + + response = self.client.post( + '/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), + { + }, + follow=True + ) + assert 'alert-danger' in response.content.decode() + assert 'refund' 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), + { + 'confirm': 'true' + }, + follow=True + ) + assert 'alert-danger' in response.content.decode() + assert 'refund' in response.content.decode() + self.order.refresh_from_db() + assert self.order.total == Decimal('35.00') + def test_allow_user_price_eq(self): self.event.settings.change_allow_user_price = 'eq' response = self.client.post(