mirror of
https://github.com/pretix/pretix.git
synced 2026-08-13 11:17:01 +00:00
Order expiration: Allow to configure a delay in days (#3425)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
co-authored by
Richard Schreiber
parent
104607d34e
commit
c6bb3e71bf
@@ -728,6 +728,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'payment_term_minutes',
|
||||
'payment_term_last',
|
||||
'payment_term_expire_automatically',
|
||||
'payment_term_expire_delay_days',
|
||||
'payment_term_accept_late',
|
||||
'payment_explanation',
|
||||
'payment_pending_hidden',
|
||||
|
||||
@@ -896,6 +896,28 @@ class Order(LockModel, LoggedModel):
|
||||
), tz)
|
||||
return term_last
|
||||
|
||||
@property
|
||||
def payment_term_expire_date(self):
|
||||
delay = self.event.settings.get('payment_term_expire_delay_days', as_type=int)
|
||||
if not delay: # performance saver + backwards compatibility
|
||||
return self.expires
|
||||
|
||||
term_last = self.payment_term_last
|
||||
if term_last and self.expires > term_last: # backwards compatibility
|
||||
return self.expires
|
||||
|
||||
expires = self.expires.date() + timedelta(days=delay)
|
||||
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
expires = make_aware(datetime.combine(
|
||||
expires,
|
||||
time(hour=23, minute=59, second=59)
|
||||
), tz)
|
||||
if term_last:
|
||||
return min(expires, term_last)
|
||||
else:
|
||||
return expires
|
||||
|
||||
def _can_be_paid(self, count_waitinglist=True, ignore_date=False, force=False) -> Union[bool, str]:
|
||||
error_messages = {
|
||||
'late_lastdate': _("The payment can not be accepted as the last date of payments configured in the "
|
||||
|
||||
@@ -1270,12 +1270,12 @@ def expire_orders(sender, **kwargs):
|
||||
Exists(
|
||||
OrderFee.objects.filter(order_id=OuterRef('pk'), fee_type=OrderFee.FEE_TYPE_CANCELLATION)
|
||||
)
|
||||
).select_related('event').order_by('event_id')
|
||||
).prefetch_related('event').order_by('event_id')
|
||||
for o in qs:
|
||||
if o.event_id != event_id:
|
||||
expire = o.event.settings.get('payment_term_expire_automatically', as_type=bool)
|
||||
event_id = o.event_id
|
||||
if expire:
|
||||
if expire and now() >= o.payment_term_expire_date:
|
||||
mark_order_expired(o)
|
||||
|
||||
|
||||
|
||||
@@ -893,6 +893,28 @@ DEFAULTS = {
|
||||
"the pool and can be ordered by other people."),
|
||||
)
|
||||
},
|
||||
'payment_term_expire_delay_days': {
|
||||
'default': '0',
|
||||
'type': int,
|
||||
'form_class': forms.IntegerField,
|
||||
'serializer_class': serializers.IntegerField,
|
||||
'form_kwargs': dict(
|
||||
label=_('Expiration delay'),
|
||||
help_text=_("The order will only actually expire this many days after the expiration date communicated "
|
||||
"to the customer. However, this will not delay beyond the \"last date of payments\" "
|
||||
"configured above, which is always enforced. The delay may also end on a weekend regardless "
|
||||
"of the other settings above."),
|
||||
# Every order in between the official expiry date and the delayed expiry date has a performance penalty
|
||||
# for the cron job, so we limit this feature to 30 days to prevent arbitrary numbers of orders needing
|
||||
# to be checked.
|
||||
min_value=0,
|
||||
max_value=30,
|
||||
),
|
||||
'serializer_kwargs': dict(
|
||||
min_value=0,
|
||||
max_value=30,
|
||||
),
|
||||
},
|
||||
'payment_pending_hidden': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
|
||||
@@ -749,6 +749,7 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
'payment_term_minutes',
|
||||
'payment_term_last',
|
||||
'payment_term_expire_automatically',
|
||||
'payment_term_expire_delay_days',
|
||||
'payment_term_accept_late',
|
||||
'payment_pending_hidden',
|
||||
'payment_explanation',
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
{% bootstrap_field form.payment_term_minutes layout="control" %}
|
||||
{% bootstrap_field form.payment_term_last layout="control" %}
|
||||
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
|
||||
{% trans "days" context "unit" as days %}
|
||||
{% bootstrap_field form.payment_term_expire_delay_days layout="control" addon_after=days %}
|
||||
{% bootstrap_field form.payment_term_accept_late layout="control" %}
|
||||
{% bootstrap_field form.payment_pending_hidden layout="control" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import zoneinfo
|
||||
from datetime import date, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@@ -31,6 +32,7 @@ from django.test import TestCase
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import scope
|
||||
from freezegun import freeze_time
|
||||
from tests.testdummy.signals import FoobazSalesChannel
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
@@ -406,6 +408,56 @@ def test_expiring_auto_disabled(event):
|
||||
assert o2.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_expiring_auto_delayed(event):
|
||||
event.settings.set('payment_term_expire_delay_days', 3)
|
||||
event.settings.set('payment_term_last', date(2023, 7, 2))
|
||||
event.settings.set('timezone', 'Europe/Berlin')
|
||||
o1 = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
||||
expires=datetime(2023, 6, 30, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
||||
total=0,
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='FO2', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
||||
expires=datetime(2023, 6, 28, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
|
||||
total=0,
|
||||
)
|
||||
assert o1.payment_term_expire_date == o1.expires + timedelta(days=2) # limited by term_last
|
||||
assert o2.payment_term_expire_date == o2.expires + timedelta(days=3)
|
||||
with freeze_time("2023-06-29T00:01:00+02:00"):
|
||||
expire_orders(None)
|
||||
o1 = Order.objects.get(id=o1.id)
|
||||
assert o1.status == Order.STATUS_PENDING
|
||||
o2 = Order.objects.get(id=o2.id)
|
||||
assert o2.status == Order.STATUS_PENDING
|
||||
|
||||
with freeze_time("2023-07-01T23:50:00+02:00"):
|
||||
expire_orders(None)
|
||||
o1 = Order.objects.get(id=o1.id)
|
||||
assert o1.status == Order.STATUS_PENDING
|
||||
o2 = Order.objects.get(id=o2.id)
|
||||
assert o2.status == Order.STATUS_PENDING
|
||||
|
||||
with freeze_time("2023-07-02T00:01:00+02:00"):
|
||||
expire_orders(None)
|
||||
o1 = Order.objects.get(id=o1.id)
|
||||
assert o1.status == Order.STATUS_PENDING
|
||||
o2 = Order.objects.get(id=o2.id)
|
||||
assert o2.status == Order.STATUS_EXPIRED
|
||||
|
||||
with freeze_time("2023-07-03T00:01:00+02:00"):
|
||||
expire_orders(None)
|
||||
o1 = Order.objects.get(id=o1.id)
|
||||
assert o1.status == Order.STATUS_EXPIRED
|
||||
o2 = Order.objects.get(id=o2.id)
|
||||
assert o2.status == Order.STATUS_EXPIRED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_do_not_expire_if_approval_pending(event):
|
||||
o1 = Order.objects.create(
|
||||
|
||||
Reference in New Issue
Block a user