mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Optionally allow self-service order changes after check-in
This commit is contained in:
@@ -626,7 +626,10 @@ class Order(LockModel, LoggedModel):
|
||||
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 self.event.settings.change_allow_user_if_checked_in:
|
||||
cancelable = all([op.item.allow_cancel for op in positions])
|
||||
else:
|
||||
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:
|
||||
|
||||
@@ -189,6 +189,7 @@ error_messages = {
|
||||
'min'
|
||||
),
|
||||
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
||||
'addon_already_checked_in': gettext_lazy('You cannot remove the position %(addon)s since it has already been checked in.'),
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1896,6 +1897,12 @@ class OrderChangeManager:
|
||||
for a in current_addons[cp][k][:current_num - input_num]:
|
||||
if a.canceled:
|
||||
continue
|
||||
if a.checkins.exists():
|
||||
raise OrderError(
|
||||
error_messages['addon_already_checked_in'] % {
|
||||
'addon': str(a.item.name),
|
||||
}
|
||||
)
|
||||
self.cancel(a)
|
||||
|
||||
def _check_seats(self):
|
||||
|
||||
@@ -1484,6 +1484,19 @@ DEFAULTS = {
|
||||
label=_("Do not allow changes after"),
|
||||
)
|
||||
},
|
||||
'change_allow_user_if_checked_in': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Allow change even though the ticket has already been checked in"),
|
||||
help_text=_("By default, order changes are disabled after any ticket in the order has been checked in. "
|
||||
"If you check this box, this requirement is lifted. It is still not possible to remove an "
|
||||
"add-on product that has already been checked in individually. Use with care, and preferably "
|
||||
"only in combination with a limitation on price changes above."),
|
||||
)
|
||||
},
|
||||
'change_allow_attendee': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
|
||||
@@ -690,6 +690,7 @@ class CancelSettingsForm(SettingsForm):
|
||||
'change_allow_user_price',
|
||||
'change_allow_user_until',
|
||||
'change_allow_user_addons',
|
||||
'change_allow_user_if_checked_in',
|
||||
'change_allow_attendee',
|
||||
]
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
{% bootstrap_field form.change_allow_user_addons layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_until layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_price layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_if_checked_in layout="control" %}
|
||||
{% bootstrap_field form.change_allow_attendee layout="control" %}
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
|
||||
@@ -122,6 +122,25 @@ class OrderChangeVariationTest(BaseOrdersTest):
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
def test_change_with_checkin(self):
|
||||
with scopes_disabled():
|
||||
shirt_pos = OrderPosition.objects.create(
|
||||
order=self.order,
|
||||
item=self.shirt,
|
||||
variation=self.shirt_red,
|
||||
price=Decimal("14"),
|
||||
)
|
||||
shirt_pos.checkins.create(list=self.event.checkin_lists.create(name="Test"))
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 302
|
||||
self.event.settings.change_allow_user_if_checked_in = True
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
def test_change_variation_paid(self):
|
||||
self.event.settings.change_allow_user_variation = True
|
||||
self.event.settings.change_allow_user_price = 'any'
|
||||
@@ -746,6 +765,40 @@ class OrderChangeAddonsTest(BaseOrdersTest):
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == Decimal('23.00')
|
||||
|
||||
def test_remove_addon_checked_in(self):
|
||||
with scopes_disabled():
|
||||
self.event.settings.change_allow_user_if_checked_in = True
|
||||
op = 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"}
|
||||
)
|
||||
op.checkins.create(list=self.event.checkin_lists.create(name="Test"))
|
||||
self.order.total += Decimal("12")
|
||||
self.order.save()
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert 'Workshop 1' in response.content.decode()
|
||||
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert doc.select(f'input[name=cp_{self.ticket_pos.pk}_item_{self.workshop1.pk}]')[0].attrs['checked']
|
||||
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
{
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
assert 'You cannot remove the position' in response.content.decode()
|
||||
|
||||
def test_increase_existing_addon_free_price_net(self):
|
||||
self.event.settings.display_net_prices = True
|
||||
self.iao.multi_allowed = True
|
||||
|
||||
Reference in New Issue
Block a user