forked from CGM_Public/pretix_original
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 <schreiber@rami.io> * Update src/pretix/control/templates/pretixcontrol/checkin/reset.html Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/control/templates/pretixcontrol/checkin/reset.html Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/control/templates/pretixcontrol/checkin/lists.html Co-authored-by: Richard Schreiber <schreiber@rami.io> --------- Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -215,3 +215,9 @@ class CheckinListSimulatorForm(forms.Form):
|
|||||||
)
|
)
|
||||||
self.fields['gate'].widget.choices = self.fields['gate'].choices
|
self.fields['gate'].widget.choices = self.fields['gate'].choices
|
||||||
self.fields['gate'].label = _('Gate')
|
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.")
|
||||||
|
)
|
||||||
|
|||||||
@@ -722,6 +722,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
|
|||||||
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
|
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
|
||||||
'pretix.team.token.created': _('The token "{name}" has been created.'),
|
'pretix.team.token.created': _('The token "{name}" has been created.'),
|
||||||
'pretix.team.token.deleted': _('The token "{name}" has been revoked.'),
|
'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):
|
class CoreLogEntryType(LogEntryType):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -83,6 +83,13 @@
|
|||||||
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
<a href="{% url "control:organizer.devices" organizer=request.organizer.slug %}"
|
||||||
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
class="btn btn-default"><i class="fa fa-tablet"></i> {% trans "Connected devices" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if "can_change_orders" in request.eventpermset %}
|
||||||
|
<a href="{% url "control:event.orders.checkinlists.reset" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
class="btn btn-default">
|
||||||
|
<span class="fa fa-repeat"></span>
|
||||||
|
{% trans "Reset check-in" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Reset check-in" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Reset check-in" %}</h1>
|
||||||
|
<form action="" method="post" class="" data-asynctask>
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
{% 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 %}
|
||||||
|
</p>
|
||||||
|
<p class="alert alert-danger">
|
||||||
|
{% blocktrans trimmed count count=checkins %}
|
||||||
|
This will permanently delete <strong>1 check-in</strong>.
|
||||||
|
{% plural %}
|
||||||
|
This will permanently delete <strong>{{ count }} check-ins</strong>.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% blocktrans trimmed count count=printlogs %}
|
||||||
|
Additionally, <strong>1 print log</strong> will be deleted.
|
||||||
|
{% plural %}
|
||||||
|
Additionally, <strong>{{ count }} print logs</strong> will be deleted.
|
||||||
|
{% endblocktrans %}
|
||||||
|
<br>
|
||||||
|
<strong>
|
||||||
|
{% trans "This cannot be reverted!" %}
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% 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 %}
|
||||||
|
</p>
|
||||||
|
{% bootstrap_form form layout="inline" %}
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<a href="{% url "control:event.orders.checkinlists" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
class="btn btn-default btn-cancel">
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-danger btn-save">
|
||||||
|
{% trans "Proceed with reset" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -500,6 +500,18 @@
|
|||||||
{% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %}
|
{% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if staff_session %}
|
||||||
|
<div class="admin-only print-logs">
|
||||||
|
{% for pl in line.print_logs.all %}
|
||||||
|
<span class="fa fa-print"></span>
|
||||||
|
{{ pl.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
|
{{ pl.get_type_display }}
|
||||||
|
({{ pl.source }}{% if pl.device %}, #{{ pl.device.device_id }}{% endif %})
|
||||||
|
{% if not pl.successful %}<span class="fa fa-warning fa-fw"></span>{% endif %}
|
||||||
|
<br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if line.issued_gift_cards %}
|
{% if line.issued_gift_cards %}
|
||||||
<dl>
|
<dl>
|
||||||
{% for gc in line.issued_gift_cards.all %}
|
{% for gc in line.issued_gift_cards.all %}
|
||||||
|
|||||||
@@ -464,6 +464,7 @@ urlpatterns = [
|
|||||||
re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'),
|
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/$', 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/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/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'),
|
||||||
re_path(r'^checkinlists/(?P<list>\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'),
|
re_path(r'^checkinlists/(?P<list>\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'),
|
||||||
re_path(r'^checkinlists/(?P<list>\d+)/simulator$', checkin.CheckInListSimulator.as_view(), name='event.orders.checkinlists.simulator'),
|
re_path(r'^checkinlists/(?P<list>\d+)/simulator$', checkin.CheckInListSimulator.as_view(), name='event.orders.checkinlists.simulator'),
|
||||||
|
|||||||
@@ -50,15 +50,16 @@ from i18nfield.strings import LazyI18nString
|
|||||||
|
|
||||||
from pretix.api.views.checkin import _redeem_process
|
from pretix.api.views.checkin import _redeem_process
|
||||||
from pretix.base.media import MEDIA_TYPES
|
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.checkin import CheckinList
|
||||||
|
from pretix.base.models.orders import PrintLog
|
||||||
from pretix.base.services.checkin import (
|
from pretix.base.services.checkin import (
|
||||||
LazyRuleVars, _logic_annotate_for_graphic_explain,
|
LazyRuleVars, _logic_annotate_for_graphic_explain,
|
||||||
)
|
)
|
||||||
from pretix.base.signals import checkin_created
|
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 (
|
from pretix.control.forms.checkin import (
|
||||||
CheckinListForm, CheckinListSimulatorForm,
|
CheckinListForm, CheckinListSimulatorForm, CheckinResetForm,
|
||||||
)
|
)
|
||||||
from pretix.control.forms.filter import (
|
from pretix.control.forms.filter import (
|
||||||
CheckinFilterForm, CheckinListAttendeeFilterForm, CheckinListFilterForm,
|
CheckinFilterForm, CheckinListAttendeeFilterForm, CheckinListFilterForm,
|
||||||
@@ -570,3 +571,55 @@ class CheckInListSimulator(EventPermissionRequiredMixin, FormView):
|
|||||||
for q in self.result["questions"]:
|
for q in self.result["questions"]:
|
||||||
q["question"] = LazyI18nString(q["question"])
|
q["question"] = LazyI18nString(q["question"])
|
||||||
return self.get(self.request, self.args, self.kwargs)
|
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()
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.models.orders import (
|
from pretix.base.models.orders import (
|
||||||
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
|
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
|
||||||
|
PrintLog,
|
||||||
)
|
)
|
||||||
from pretix.base.models.tax import ask_for_vat_id
|
from pretix.base.models.tax import ask_for_vat_id
|
||||||
from pretix.base.payment import PaymentException
|
from pretix.base.payment import PaymentException
|
||||||
@@ -597,6 +598,7 @@ class OrderDetail(OrderView):
|
|||||||
'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media',
|
'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media',
|
||||||
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
|
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
|
||||||
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
|
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')
|
).order_by('positionid')
|
||||||
|
|
||||||
positions = []
|
positions = []
|
||||||
|
|||||||
@@ -636,6 +636,10 @@ details summary {
|
|||||||
.position-buttons {
|
.position-buttons {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
.print-logs {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
}
|
||||||
|
|
||||||
.pos-canceled * {
|
.pos-canceled * {
|
||||||
color: $brand-danger;
|
color: $brand-danger;
|
||||||
|
|||||||
Reference in New Issue
Block a user