diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py
index d8d815beb..cdd36eab5 100644
--- a/src/pretix/base/services/checkin.py
+++ b/src/pretix/base/services/checkin.py
@@ -67,6 +67,14 @@ from pretix.helpers.jsonlogic_query import (
def _build_time(t=None, value=None, ev=None):
if t == "custom":
return dateutil.parser.parse(value)
+ elif t == "customtime":
+ parsed = dateutil.parser.parse(value)
+ return now().astimezone(ev.timezone).replace(
+ hour=parsed.hour,
+ minute=parsed.minute,
+ second=parsed.second,
+ microsecond=parsed.microsecond,
+ )
elif t == 'date_from':
return ev.date_from
elif t == 'date_to':
@@ -354,7 +362,15 @@ class SQLLogic:
if operator == 'buildTime':
if values[0] == "custom":
- return Value(dateutil.parser.parse(values[1]))
+ return Value(dateutil.parser.parse(values[1]).astimezone(pytz.UTC))
+ elif values[0] == "customtime":
+ parsed = dateutil.parser.parse(values[1])
+ return Value(now().astimezone(self.list.event.timezone).replace(
+ hour=parsed.hour,
+ minute=parsed.minute,
+ second=parsed.second,
+ microsecond=parsed.microsecond,
+ ).astimezone(pytz.UTC))
elif values[0] == 'date_from':
return Coalesce(
F(f'subevent__date_from'),
@@ -382,7 +398,7 @@ class SQLLogic:
return int(values[1])
elif operator == 'var':
if values[0] == 'now':
- return Value(now())
+ return Value(now().astimezone(pytz.UTC))
elif values[0] == 'product':
return F('item_id')
elif values[0] == 'variation':
diff --git a/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html b/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html
index 2120e5dff..54ab0e45d 100644
--- a/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html
+++ b/src/pretix/control/templates/pretixcontrol/checkin/list_edit.html
@@ -119,6 +119,7 @@
+
diff --git a/src/pretix/helpers/jsonlogic_boolalg.py b/src/pretix/helpers/jsonlogic_boolalg.py
index 58162d840..bd9a87157 100644
--- a/src/pretix/helpers/jsonlogic_boolalg.py
+++ b/src/pretix/helpers/jsonlogic_boolalg.py
@@ -75,12 +75,14 @@ def convert_to_dnf(rules):
rules = _distribute_or_over_and(rules)
operator = list(rules.keys())[0]
values = rules[operator]
+ no_list = False
if not isinstance(values, list):
values = [values]
+ no_list = True
rules = {
operator: [
convert_to_dnf(v) for v in values
- ] if len(values) > 1 else convert_to_dnf(values[0])
+ ] if not no_list else convert_to_dnf(values[0])
}
if old_rules == rules:
break
diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
index b1ca71695..68d18bbad 100644
--- a/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules.js
@@ -99,7 +99,8 @@ $(document).ready(function () {
date_from: gettext('Event start'),
date_to: gettext('Event end'),
date_admission: gettext('Event admission'),
- date_custom: gettext('custom time'),
+ date_custom: gettext('custom date and time'),
+ date_customtime: gettext('custom time'),
date_tolerance: gettext('Tolerance (minutes)'),
condition_add: gettext('Add condition'),
minutes: gettext('minutes'),
@@ -119,4 +120,4 @@ $(document).ready(function () {
},
}
})
-});
\ No newline at end of file
+});
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 44176683e..035edc3de 100644
--- a/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules/checkin-rule.vue
@@ -27,11 +27,14 @@
+
+
@@ -56,6 +59,7 @@
components: {
LookupSelect2: LookupSelect2.default,
Datetimefield: Datetimefield.default,
+ Timefield: Timefield.default,
},
props: {
rule: Object,
diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules/timefield.vue b/src/pretix/static/pretixcontrol/js/ui/checkinrules/timefield.vue
new file mode 100644
index 000000000..93641dc70
--- /dev/null
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules/timefield.vue
@@ -0,0 +1,55 @@
+
+
+
+
diff --git a/src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue b/src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue
index 546783821..94b81af99 100644
--- a/src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue
+++ b/src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue
@@ -22,6 +22,9 @@
{{ df(rightoperand.buildTime[1]) }}
+
+ {{ tf(rightoperand.buildTime[1]) }}
+
{{ this.$root.texts[rightoperand.buildTime[0]] }}
@@ -139,6 +142,10 @@
df (val) {
const format = $("body").attr("data-datetimeformat")
return moment(val).format(format)
+ },
+ tf (val) {
+ const format = $("body").attr("data-timeformat")
+ return moment(val).format(format)
}
},
}
diff --git a/src/tests/base/test_checkin.py b/src/tests/base/test_checkin.py
index 11536bcb5..09d798623 100644
--- a/src/tests/base/test_checkin.py
+++ b/src/tests/base/test_checkin.py
@@ -25,7 +25,7 @@ from decimal import Decimal
import pytest
from django.conf import settings
-from django.utils.timezone import now
+from django.utils.timezone import now, override
from django_scopes import scope
from freezegun import freeze_time
@@ -621,16 +621,33 @@ def test_rules_time_isbefore_with_tolerance(event, position, clist):
def test_rules_time_isafter_custom_time(event, position, clist):
# Ticket is valid starting at a custom time
event.settings.timezone = 'Europe/Berlin'
- clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T22:00:00.000Z"]}, None]}
+ clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["customtime", "22:00:00"]}, None]}
clist.save()
- with freeze_time("2020-01-01 21:55:00"):
+ with freeze_time("2020-01-01 21:55:00+01:00"), override(event.timezone):
assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
with pytest.raises(CheckInError) as excinfo:
perform_checkin(position, clist, {})
assert excinfo.value.code == 'rules'
- assert 'Only allowed after 23:00' in str(excinfo.value)
+ assert 'Only allowed after 22:00' in str(excinfo.value)
- with freeze_time("2020-01-01 22:05:00"):
+ with freeze_time("2020-01-01 22:05:00+01:00"):
+ assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
+ perform_checkin(position, clist, {})
+
+
+@pytest.mark.django_db
+def test_rules_time_isafter_custom_datetime(event, position, clist):
+ # Ticket is valid starting at a custom time
+ event.settings.timezone = 'Europe/Berlin'
+ clist.rules = {"isAfter": [{"var": "now"}, {"buildTime": ["custom", "2020-01-01T23:00:00.000+01:00"]}, None]}
+ clist.save()
+ with freeze_time("2020-01-01 21:55:00+00:00"):
+ assert not OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
+ with pytest.raises(CheckInError) as excinfo:
+ perform_checkin(position, clist, {})
+ assert excinfo.value.code == 'rules'
+
+ with freeze_time("2020-01-01 22:05:00+00:00"):
assert OrderPosition.objects.filter(SQLLogic(clist).apply(clist.rules), pk=position.pk).exists()
perform_checkin(position, clist, {})