diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 5258120dd..d4a0bfcd6 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -1,7 +1,7 @@ import json import logging from collections import Counter, namedtuple -from datetime import datetime, timedelta +from datetime import datetime, time, timedelta from decimal import Decimal from typing import List, Optional @@ -14,7 +14,7 @@ from django.db.models.functions import Greatest from django.dispatch import receiver from django.utils.formats import date_format from django.utils.functional import cached_property -from django.utils.timezone import now +from django.utils.timezone import make_aware, now from django.utils.translation import ugettext as _ from pretix.api.models import OAuthApplication @@ -34,6 +34,7 @@ from pretix.base.models.orders import ( from pretix.base.models.organizer import TeamAPIToken from pretix.base.models.tax import TaxedPrice from pretix.base.payment import BasePaymentProvider, PaymentException +from pretix.base.reldate import RelativeDateWrapper from pretix.base.services import tickets from pretix.base.services.invoices import ( generate_cancellation, generate_invoice, invoice_qualified, @@ -403,6 +404,16 @@ def _check_date(event: Event, now_dt: datetime): if event.presale_has_ended: raise OrderError(error_messages['ended']) + if not event.has_subevents: + tlv = event.settings.get('payment_term_last', as_type=RelativeDateWrapper) + if tlv: + term_last = make_aware(datetime.combine( + tlv.datetime(event).date(), + time(hour=23, minute=59, second=59) + ), event.timezone) + if term_last < now_dt: + raise OrderError(error_messages['ended']) + def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None): err = None @@ -457,6 +468,18 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio delete(cp) break + if cp.subevent: + tlv = event.settings.get('payment_term_last', as_type=RelativeDateWrapper) + if tlv: + term_last = make_aware(datetime.combine( + tlv.datetime(cp.subevent).date(), + time(hour=23, minute=59, second=59) + ), event.timezone) + if term_last < now_dt: + err = err or error_messages['some_subevent_ended'] + delete(cp) + break + if cp.subevent and cp.subevent.presale_has_ended: err = err or error_messages['some_subevent_ended'] delete(cp) diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 7bbd45e6a..38db6613e 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -1439,6 +1439,18 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase): doc = BeautifulSoup(response.rendered_content, "lxml") self.assertGreaterEqual(len(doc.select(".alert-danger")), 1) + def test_confirm_payment_period_over(self): + self.event.settings.payment_term_last = (now() - datetime.timedelta(days=1)).date().isoformat() + CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=now() + timedelta(minutes=10) + ) + self._set_session('payment', 'banktransfer') + + response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertGreaterEqual(len(doc.select(".alert-danger")), 1) + def test_confirm_require_voucher(self): self.ticket.require_voucher = True self.ticket.save() @@ -1719,6 +1731,24 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase): assert 'presale period for one of the events in your cart has ended.' in response.rendered_content assert not CartPosition.objects.filter(cart_id=self.session_key).exists() + def test_confirm_subevent_payment_period_over(self): + self.event.has_subevents = True + self.event.settings.display_net_prices = True + self.event.save() + self.event.settings.payment_term_last = 'RELDATE/1/23:59:59/date_from/' + se = self.event.subevents.create(name='Foo', date_from=now()) + CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=now() + timedelta(minutes=10), subevent=se + ) + self._set_session('payment', 'banktransfer') + + response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.rendered_content, "lxml") + self.assertGreaterEqual(len(doc.select(".alert-danger")), 1) + assert 'presale period for one of the events in your cart has ended.' in response.rendered_content + assert not CartPosition.objects.filter(cart_id=self.session_key).exists() + def test_confirm_subevent_ignore_series_dates(self): self.event.has_subevents = True self.event.date_to = now() - datetime.timedelta(days=1)