Memberships: Check valid_from/valid_until for parallel usage (#3975)

This commit is contained in:
Raphael Michel
2024-03-15 16:40:41 +01:00
committed by GitHub
parent b6221ab6d9
commit 9f794290dc
5 changed files with 272 additions and 16 deletions

View File

@@ -29,8 +29,8 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from pretix.base.models import (
AbstractPosition, Customer, Event, Item, Membership, Order, OrderPosition,
SubEvent,
AbstractPosition, CartPosition, Customer, Event, Item, Membership, Order,
OrderPosition, SubEvent,
)
from pretix.helpers import OF_SELF
@@ -82,7 +82,8 @@ def create_membership(customer: Customer, position: OrderPosition):
)
def validate_memberships_in_order(customer: Customer, positions: List[AbstractPosition], event: Event, lock=False, ignored_order: Order = None, testmode=False):
def validate_memberships_in_order(customer: Customer, positions: List[AbstractPosition], event: Event, lock=False, ignored_order: Order = None, testmode=False,
valid_from_not_chosen=False):
"""
Validate that a set of cart or order positions. This currently does not validate
@@ -132,7 +133,11 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
qs = qs.exclude(order_id=ignored_order.pk)
m._used_at_dates = [
(op.subevent or op.order.event).date_from
for op in qs
for op in qs if not op.valid_from or not op.valid_until
]
m._used_for_ranges = [
(op.valid_from, op.valid_until)
for op in qs if op.valid_from or op.valid_until
]
for p in applicable_positions:
@@ -192,13 +197,42 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
m.usages += 1
if not m.membership_type.allow_parallel_usage:
df = ev.date_from
if any(abs(df - d) < timedelta(minutes=1) for d in m._used_at_dates):
raise ValidationError(
_('You are trying to use a membership of type "{type}" for an event taking place at {date}, '
'however you already used the same membership for a different ticket at the same time.').format(
type=m.membership_type.name,
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
if isinstance(p, (OrderPosition, CartPosition)):
# override_ variants are for usage of fake cart in OrderChangeManager
valid_from = getattr(p, 'override_valid_from', p.valid_from)
valid_until = getattr(p, 'override_valid_until', p.valid_until)
else: # future safety, not technically defined on AbstractPosition
valid_from = None
valid_until = None
if (valid_from or valid_until) and not (p.item.validity_dynamic_start_choice and valid_from_not_chosen):
for used_range in m._used_for_ranges:
if valid_from and valid_from > used_range[1]:
continue
if valid_until and valid_until < used_range[0]:
continue
raise ValidationError(
_('You are trying to use a membership of type "{type}" for a ticket valid from {valid_from} '
'until {valid_until}, however you already used the same membership for a different ticket '
'that overlaps with this time frame ({conflict_from} {conflict_until}).').format(
type=m.membership_type.name,
valid_from=date_format(valid_from.astimezone(tz), 'SHORT_DATETIME_FORMAT') if valid_from else _('start'),
valid_until=date_format(valid_until.astimezone(tz), 'SHORT_DATETIME_FORMAT') if valid_until else _('open end'),
conflict_from=date_format(used_range[0].astimezone(tz), 'SHORT_DATETIME_FORMAT') if used_range[0] else _('start'),
conflict_until=date_format(used_range[1].astimezone(tz), 'SHORT_DATETIME_FORMAT') if used_range[1] else _('open end'),
)
)
)
m._used_at_dates.append(ev.date_from)
m._used_for_ranges.append((p.valid_from, p.valid_until))
if not valid_from or not valid_until:
df = ev.date_from
if any(abs(df - d) < timedelta(minutes=1) for d in m._used_at_dates):
raise ValidationError(
_('You are trying to use a membership of type "{type}" for an event taking place at {date}, '
'however you already used the same membership for a different ticket at the same time.').format(
type=m.membership_type.name,
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
)
m._used_at_dates.append(ev.date_from)