forked from CGM_Public/pretix_original
Allow customers to change to a different product variation (#1719)
This commit is contained in:
@@ -315,6 +315,51 @@ def base_placeholders(sender, **kwargs):
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_info_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.modify', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_products_change', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.change', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_cancel', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.cancel', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
|
||||
event,
|
||||
|
||||
@@ -378,9 +378,9 @@ class Item(LoggedModel):
|
||||
'but only for fixed bundles!')
|
||||
)
|
||||
allow_cancel = models.BooleanField(
|
||||
verbose_name=_('Allow product to be canceled'),
|
||||
verbose_name=_('Allow product to be canceled or changed'),
|
||||
default=True,
|
||||
help_text=_('If this is checked, the usual cancellation settings of this event apply. If this is unchecked, '
|
||||
help_text=_('If this is checked, the usual cancellation and order change settings of this event apply. If this is unchecked, '
|
||||
'orders containing this product can not be canceled by users but only by you.')
|
||||
)
|
||||
min_per_order = models.IntegerField(
|
||||
|
||||
@@ -434,6 +434,19 @@ class Order(LockModel, LoggedModel):
|
||||
self.status in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED) and self.count_positions
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def user_change_deadline(self):
|
||||
until = self.event.settings.get('change_allow_user_until', as_type=RelativeDateWrapper)
|
||||
if until:
|
||||
if self.event.has_subevents:
|
||||
terms = [
|
||||
until.datetime(se)
|
||||
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
|
||||
]
|
||||
return min(terms) if terms else None
|
||||
else:
|
||||
return until.datetime(self.event)
|
||||
|
||||
@cached_property
|
||||
def user_cancel_deadline(self):
|
||||
if self.status == Order.STATUS_PAID and self.total != Decimal('0.00'):
|
||||
@@ -466,6 +479,36 @@ class Order(LockModel, LoggedModel):
|
||||
fee += self.event.settings.cancel_allow_user_paid_keep
|
||||
return round_decimal(fee, self.event.currency)
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
def user_change_allowed(self) -> bool:
|
||||
"""
|
||||
Returns whether or not this order can be canceled by the user.
|
||||
"""
|
||||
from .checkin import Checkin
|
||||
|
||||
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID) or not self.count_positions:
|
||||
return False
|
||||
|
||||
if self.cancellation_requests.exists():
|
||||
return False
|
||||
positions = list(
|
||||
self.positions.all().annotate(
|
||||
has_variations=Exists(ItemVariation.objects.filter(item_id=OuterRef('item_id'))),
|
||||
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
|
||||
).select_related('item').prefetch_related('issued_gift_cards')
|
||||
)
|
||||
cancelable = all([op.item.allow_cancel and not op.has_checkin for op in positions])
|
||||
if not cancelable or not positions:
|
||||
return False
|
||||
for op in positions:
|
||||
if op.issued_gift_cards.all():
|
||||
return False
|
||||
if self.user_change_deadline and now() > self.user_change_deadline:
|
||||
return False
|
||||
|
||||
return self.event.settings.change_allow_user_variation and any([op.has_variations for op in positions])
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
def user_cancel_allowed(self) -> bool:
|
||||
@@ -474,7 +517,7 @@ class Order(LockModel, LoggedModel):
|
||||
"""
|
||||
from .checkin import Checkin
|
||||
|
||||
if self.cancellation_requests.exists():
|
||||
if self.cancellation_requests.exists() or not self.cancel_allowed():
|
||||
return False
|
||||
positions = list(
|
||||
self.positions.all().annotate(
|
||||
|
||||
@@ -1131,7 +1131,7 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
|
||||
try:
|
||||
order.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices,
|
||||
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True,
|
||||
)
|
||||
except SendMailException:
|
||||
logger.exception('Order changed email could not be sent')
|
||||
@@ -1869,9 +1869,10 @@ class OrderChangeManager:
|
||||
|
||||
def _reissue_invoice(self):
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if self.reissue_invoice and i and self._invoice_dirty:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
if invoice_qualified(self.order):
|
||||
if self.reissue_invoice and self._invoice_dirty:
|
||||
if i:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
if (i or self.event.settings.invoice_generate == 'True') and invoice_qualified(self.order):
|
||||
self._invoices.append(generate_invoice(self.order))
|
||||
|
||||
def _check_complete_cancel(self):
|
||||
|
||||
@@ -89,7 +89,7 @@ class QuotaAvailability:
|
||||
|
||||
def compute(self, now_dt=None):
|
||||
now_dt = now_dt or now()
|
||||
quotas = list(self._queue)
|
||||
quotas = list(set(self._queue))
|
||||
quotas_original = list(self._queue)
|
||||
self._queue.clear()
|
||||
if not quotas:
|
||||
|
||||
@@ -925,6 +925,46 @@ DEFAULTS = {
|
||||
"multiple event dates, the earliest date will be used."),
|
||||
)
|
||||
},
|
||||
'change_allow_user_variation': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Customers can change the variation of the products they purchased"),
|
||||
)
|
||||
},
|
||||
'change_allow_user_price': {
|
||||
'default': 'gt',
|
||||
'type': str,
|
||||
'form_class': forms.ChoiceField,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
'serializer_kwargs': dict(
|
||||
choices=(
|
||||
('gt', _('Only allow changes if the resulting price is higher or equal than the previous price.')),
|
||||
('eq', _('Only allow changes if the resulting price is equal to the previous price.')),
|
||||
('any', _('Allow changes regardless of price, even if this results in a refund.')),
|
||||
)
|
||||
),
|
||||
'form_kwargs': dict(
|
||||
label=_("Requirement for changed prices"),
|
||||
choices=(
|
||||
('gt', _('Only allow changes if the resulting price is higher or equal than the previous price.')),
|
||||
('eq', _('Only allow changes if the resulting price is equal to the previous price.')),
|
||||
('any', _('Allow changes regardless of price, even if this results in a refund.')),
|
||||
),
|
||||
widget=forms.RadioSelect,
|
||||
),
|
||||
},
|
||||
'change_allow_user_until': {
|
||||
'default': None,
|
||||
'type': RelativeDateWrapper,
|
||||
'form_class': RelativeDateTimeField,
|
||||
'serializer_class': SerializerRelativeDateTimeField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Do not allow changes after"),
|
||||
)
|
||||
},
|
||||
'cancel_allow_user': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
|
||||
@@ -37,7 +37,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
arg,
|
||||
floatformat(value, 2)
|
||||
)
|
||||
return format_currency(value, arg, locale=translation.get_language())
|
||||
return format_currency(value, arg, locale=translation.get_language()[:2])
|
||||
except:
|
||||
return '{} {}'.format(
|
||||
arg,
|
||||
|
||||
Reference in New Issue
Block a user