From 423f0cbb9071d5fae4dd26eaa35cb4eb69d40eb3 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 18 Jul 2025 10:02:18 +0200 Subject: [PATCH] Add button to reset entire check-in stack (Z#23188730) (#5312) * Show print logs to admins * Add button to reset entire check-in stack (Z#23188730) * isort * Update src/pretix/control/templates/pretixcontrol/checkin/reset.html Co-authored-by: Richard Schreiber * Update src/pretix/control/templates/pretixcontrol/checkin/reset.html Co-authored-by: Richard Schreiber * Update src/pretix/control/templates/pretixcontrol/checkin/reset.html Co-authored-by: Richard Schreiber * Update src/pretix/control/templates/pretixcontrol/checkin/lists.html Co-authored-by: Richard Schreiber --------- Co-authored-by: Richard Schreiber --- src/pretix/control/forms/checkin.py | 6 ++ src/pretix/control/logdisplay.py | 1 + .../pretixcontrol/checkin/lists.html | 7 +++ .../pretixcontrol/checkin/reset.html | 50 ++++++++++++++++ .../templates/pretixcontrol/order/index.html | 12 ++++ src/pretix/control/urls.py | 1 + src/pretix/control/views/checkin.py | 59 ++++++++++++++++++- src/pretix/control/views/orders.py | 2 + .../static/pretixcontrol/scss/main.scss | 4 ++ 9 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/pretix/control/templates/pretixcontrol/checkin/reset.html diff --git a/src/pretix/control/forms/checkin.py b/src/pretix/control/forms/checkin.py index 207a3d78c5..32d4021e3a 100644 --- a/src/pretix/control/forms/checkin.py +++ b/src/pretix/control/forms/checkin.py @@ -215,3 +215,9 @@ class CheckinListSimulatorForm(forms.Form): ) self.fields['gate'].widget.choices = self.fields['gate'].choices self.fields['gate'].label = _('Gate') + + +class CheckinResetForm(forms.Form): + ok = forms.BooleanField( + label=_("I am sure that the check-in state of the entire event should be reset.") + ) diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index 58001cb368..11f1321abe 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -722,6 +722,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType): 'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'), 'pretix.team.token.created': _('The token "{name}" has been created.'), 'pretix.team.token.deleted': _('The token "{name}" has been revoked.'), + 'pretix.event.checkin.reset': _('The check-in and print log state has been reset.') }) class CoreLogEntryType(LogEntryType): pass diff --git a/src/pretix/control/templates/pretixcontrol/checkin/lists.html b/src/pretix/control/templates/pretixcontrol/checkin/lists.html index 4a96b051ff..220458244c 100644 --- a/src/pretix/control/templates/pretixcontrol/checkin/lists.html +++ b/src/pretix/control/templates/pretixcontrol/checkin/lists.html @@ -83,6 +83,13 @@ {% trans "Connected devices" %} {% endif %} + {% if "can_change_orders" in request.eventpermset %} + + + {% trans "Reset check-in" %} + + {% endif %}

diff --git a/src/pretix/control/templates/pretixcontrol/checkin/reset.html b/src/pretix/control/templates/pretixcontrol/checkin/reset.html new file mode 100644 index 0000000000..7ced9ce74d --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/checkin/reset.html @@ -0,0 +1,50 @@ +{% extends "pretixcontrol/items/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Reset check-in" %}{% endblock %} +{% block inside %} +

{% trans "Reset check-in" %}

+ + {% csrf_token %} +

+ {% blocktrans trimmed %} + With this feature, you can reset the entire check-in state of the event. + This will delete all check-in records as well as all records of printed tickets or badges. + We recommend to use this feature after testing your hardware setup but only before your + event started, and you admitted any real attendees or printed any real badges or tickets. + {% endblocktrans %} +

+

+ {% blocktrans trimmed count count=checkins %} + This will permanently delete 1 check-in. + {% plural %} + This will permanently delete {{ count }} check-ins. + {% endblocktrans %} + {% blocktrans trimmed count count=printlogs %} + Additionally, 1 print log will be deleted. + {% plural %} + Additionally, {{ count }} print logs will be deleted. + {% endblocktrans %} +
+ + {% trans "This cannot be reverted!" %} + +

+

+ {% blocktrans trimmed %} + The deleted entries will still show up in the "Order history" section, but for all other + purposes the system will behave as if they never existed. + {% endblocktrans %} +

+ {% bootstrap_form form layout="inline" %} +
+ + {% trans "Cancel" %} + + +
+ +{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html index 972b34e7bd..5f6dbe02d1 100644 --- a/src/pretix/control/templates/pretixcontrol/order/index.html +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -500,6 +500,18 @@ {% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %} {% endif %} + {% if staff_session %} + + {% endif %} {% if line.issued_gift_cards %}
{% for gc in line.issued_gift_cards.all %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index b7a0d0a753..cd6d39a3a3 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -464,6 +464,7 @@ urlpatterns = [ re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'), re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'), re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'), + re_path(r'^checkinlists/reset$', checkin.CheckInResetView.as_view(), name='event.orders.checkinlists.reset'), re_path(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'), re_path(r'^checkinlists/(?P\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'), re_path(r'^checkinlists/(?P\d+)/simulator$', checkin.CheckInListSimulator.as_view(), name='event.orders.checkinlists.simulator'), diff --git a/src/pretix/control/views/checkin.py b/src/pretix/control/views/checkin.py index d24ed7a50a..26ff3e4887 100644 --- a/src/pretix/control/views/checkin.py +++ b/src/pretix/control/views/checkin.py @@ -50,15 +50,16 @@ from i18nfield.strings import LazyI18nString from pretix.api.views.checkin import _redeem_process from pretix.base.media import MEDIA_TYPES -from pretix.base.models import Checkin, Order, OrderPosition +from pretix.base.models import Checkin, LogEntry, Order, OrderPosition from pretix.base.models.checkin import CheckinList +from pretix.base.models.orders import PrintLog from pretix.base.services.checkin import ( LazyRuleVars, _logic_annotate_for_graphic_explain, ) from pretix.base.signals import checkin_created -from pretix.base.views.tasks import AsyncPostView +from pretix.base.views.tasks import AsyncFormView, AsyncPostView from pretix.control.forms.checkin import ( - CheckinListForm, CheckinListSimulatorForm, + CheckinListForm, CheckinListSimulatorForm, CheckinResetForm, ) from pretix.control.forms.filter import ( CheckinFilterForm, CheckinListAttendeeFilterForm, CheckinListFilterForm, @@ -570,3 +571,55 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView): for q in self.result["questions"]: q["question"] = LazyI18nString(q["question"]) return self.get(self.request, self.args, self.kwargs) + + +class CheckInResetView(CheckInListQueryMixin, EventPermissionRequiredMixin, AsyncFormView): + form_class = CheckinResetForm + permission = "can_change_orders" + template_name = "pretixcontrol/checkin/reset.html" + + def get_error_url(self, *args): + return reverse( + "control:event.orders.checkinlists", + kwargs={ + "event": self.request.event.slug, + "organizer": self.request.organizer.slug, + }, + ) + + def get_success_url(self, *args): + return reverse( + "control:event.orders.checkinlists", + kwargs={ + "event": self.request.event.slug, + "organizer": self.request.organizer.slug, + }, + ) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['checkins'] = Checkin.all.filter(list__event=self.request.event).count() + ctx['printlogs'] = PrintLog.objects.filter(position__order__event=self.request.event).count() + return ctx + + def async_form_valid(self, task, form): + with transaction.atomic(): + qs = Checkin.all.filter(list__event=self.request.event).select_related("position", "position__order") + logentries = [] + for ci in qs: + if ci.position: + logentries.append(ci.position.order.log_action('pretix.event.checkin.reverted', data={ + 'position': ci.position.id, + 'positionid': ci.position.positionid, + 'list': ci.list_id, + 'web': True + }, user=self.request.user, save=False)) + + Order.objects.filter(pk__in=qs.values_list("position__order_id", flat=True)).update(last_modified=now()) + qs.delete() + LogEntry.objects.bulk_create(logentries) + + pl = PrintLog.objects.filter(position__order__event=self.request.event) + pl.delete() + self.request.event.log_action('pretix.event.checkin.reset', user=self.request.user) + self.request.event.cache.clear() diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index e1658b7873..65c77ef227 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -83,6 +83,7 @@ from pretix.base.models import ( ) from pretix.base.models.orders import ( CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund, + PrintLog, ) from pretix.base.models.tax import ask_for_vat_id from pretix.base.payment import PaymentException @@ -597,6 +598,7 @@ class OrderDetail(OrderView): 'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media', Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')), Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')), + Prefetch('print_logs', queryset=PrintLog.objects.select_related('device').order_by('datetime')), ).order_by('positionid') positions = [] diff --git a/src/pretix/static/pretixcontrol/scss/main.scss b/src/pretix/static/pretixcontrol/scss/main.scss index be9756f395..41a72dee01 100644 --- a/src/pretix/static/pretixcontrol/scss/main.scss +++ b/src/pretix/static/pretixcontrol/scss/main.scss @@ -636,6 +636,10 @@ details summary { .position-buttons { padding-left: 20px; } +.print-logs { + padding-left: 20px; + font-size: $font-size-small; +} .pos-canceled * { color: $brand-danger;