diff --git a/src/pretix/base/models/checkin.py b/src/pretix/base/models/checkin.py
index 36b2e7a204..0590d6b102 100644
--- a/src/pretix/base/models/checkin.py
+++ b/src/pretix/base/models/checkin.py
@@ -285,7 +285,7 @@ class CheckinList(LoggedModel):
}
allowed_vars = {
'product', 'variation', 'now', 'now_isoweekday', 'entries_number', 'entries_today', 'entries_days',
- 'minutes_since_last_entry', 'minutes_since_first_entry', 'gate',
+ 'minutes_since_last_entry', 'minutes_since_first_entry', 'gate', 'entry_status',
}
if not rules or not isinstance(rules, dict):
return rules
diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py
index 4281cfd752..661160922a 100644
--- a/src/pretix/base/services/checkin.py
+++ b/src/pretix/base/services/checkin.py
@@ -42,8 +42,8 @@ from dateutil.tz import datetime_exists
from django.core.files import File
from django.db import IntegrityError, transaction
from django.db.models import (
- BooleanField, Count, ExpressionWrapper, F, IntegerField, Max, Min,
- OuterRef, Q, Subquery, Value,
+ BooleanField, Case, Count, ExpressionWrapper, F, IntegerField, Max, Min,
+ OuterRef, Q, Subquery, TextField, Value, When,
)
from django.db.models.functions import Coalesce, TruncDate
from django.dispatch import receiver
@@ -273,6 +273,14 @@ def _logic_explain(rules, ev, rule_data, now_dt=None):
var_texts[vname] = _('Only allowed before {datetime}').format(datetime=compare_to_text)
elif operator == 'isAfter':
var_texts[vname] = _('Only allowed after {datetime}').format(datetime=compare_to_text)
+ elif var == 'entry_status':
+ var_weights[vname] = (20, 0)
+ if operator == '==' and rhs[0] == 'present':
+ var_texts[vname] = _('Attendee is checked out')
+ elif operator == '==' and rhs[0] == 'absent':
+ var_texts[vname] = _('Attendee is already checked in')
+ else:
+ var_texts[vname] = f'{var} not {operator} {rhs}'
elif var == 'product' or var == 'variation':
var_weights[vname] = (1000, 0)
var_texts[vname] = _('Ticket type not allowed')
@@ -507,6 +515,13 @@ class LazyRuleVars:
day=TruncDate('datetime', tzinfo=tz)
).values('day').distinct().count()
+ @cached_property
+ def entry_status(self):
+ last_checkin = self._position.checkins.filter(list=self._clist).order_by('datetime').last()
+ if not last_checkin or last_checkin.type == Checkin.TYPE_EXIT:
+ return "absent"
+ return "present"
+
@cached_property
def minutes_since_last_entry(self):
tz = self._clist.event.timezone
@@ -569,6 +584,8 @@ class SQLLogic:
'entries_days_since', 'entries_days_before'}
def operation_to_expression(self, rule):
+ if isinstance(rule, str):
+ return Value(rule)
if not isinstance(rule, dict):
return rule
@@ -770,6 +787,25 @@ class SQLLogic:
Value(-1),
output_field=IntegerField()
)
+ elif values[0] == 'entry_status':
+ sq_last_checkin = Subquery(
+ Checkin.objects.filter(
+ position_id=OuterRef('pk'),
+ list_id=self.list.pk,
+ ).order_by('-datetime').values('type')[:1]
+ )
+
+ return Case(
+ When(
+ condition=Equal(
+ sq_last_checkin,
+ Value(Checkin.TYPE_ENTRY)
+ ),
+ then=Value("present"),
+ ),
+ default=Value("absent"),
+ output_field=TextField()
+ )
else:
raise ValueError(f'Unknown operator {operator}')
diff --git a/src/pretix/helpers/jsonlogic_query.py b/src/pretix/helpers/jsonlogic_query.py
index 867546c7ec..8206d7d136 100644
--- a/src/pretix/helpers/jsonlogic_query.py
+++ b/src/pretix/helpers/jsonlogic_query.py
@@ -34,6 +34,7 @@ class Equal(Func):
arg_joiner = ' = '
arity = 2
function = ''
+ conditional = True
class GreaterThan(Func):
diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
index fc5996b48f..4d6ec8920f 100644
--- a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
@@ -35,6 +35,12 @@ $(function () {
'cardinality': 2,
},
},
+ 'enum_entry_status': {
+ '==': {
+ 'label': gettext('='),
+ 'cardinality': 2,
+ },
+ },
'int_by_datetime': {
'<': {
'label': '<',
@@ -109,6 +115,10 @@ $(function () {
'label': gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
'type': 'int',
},
+ 'entry_status': {
+ 'label': gettext('Current entry status'),
+ 'type': 'enum_entry_status',
+ },
'entries_number': {
'label': gettext('Number of previous entries'),
'type': 'int',
@@ -180,6 +190,8 @@ $(function () {
condition_add: gettext('Add condition'),
minutes: gettext('minutes'),
duplicate: gettext('Duplicate'),
+ status_present: pgettext('entry_status', 'present'),
+ status_absent: pgettext('entry_status', 'absent'),
},
hasRules: false,
};
diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue b/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue
index fe519ab56d..707138edd0 100644
--- a/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue
@@ -56,6 +56,11 @@