Optionally allow self-service order changes after check-in

This commit is contained in:
Raphael Michel
2023-03-17 09:22:44 +01:00
parent 369251b0b0
commit fdead71884
6 changed files with 79 additions and 1 deletions

View File

@@ -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:

View File

@@ -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):

View File

@@ -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,

View File

@@ -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',
]

View File

@@ -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>

View File

@@ -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