diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 37f538dfec..9c5112f617 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -41,6 +41,7 @@ payment_date date **DEPRECATED AN payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order total money (string) Total value of this order comment string Internal comment on this order +custom_followup_at date Internal date for a custom follow-up action checkin_attention boolean If ``true``, the check-in app should show a warning that this ticket requires special attention if a ticket of this order is scanned. @@ -123,6 +124,10 @@ last_modified datetime Last modificati The ``customer`` attribute has been added. +.. versionchanged:: 4.1 + + The ``custom_followup_at`` attribute has been added. + .. _order-position-resource: @@ -307,6 +312,7 @@ List of all orders "fees": [], "total": "23.00", "comment": "", + "custom_followup_at": null, "checkin_attention": false, "require_approval": false, "invoice_address": { @@ -478,6 +484,7 @@ Fetching individual orders "fees": [], "total": "23.00", "comment": "", + "custom_followup_at": null, "checkin_attention": false, "require_approval": false, "invoice_address": { @@ -644,6 +651,8 @@ Updating order fields * ``comment`` + * ``custom_followup_at`` + * ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address) **Example request**: @@ -817,6 +826,7 @@ Creating orders charge will be created), this is just informative in case you *handled the payment already*. * ``payment_date`` (optional) – Date and time of the completion of the payment. * ``comment`` (optional) + * ``custom_followup_at`` (optional) * ``checkin_attention`` (optional) * ``invoice_address`` (optional) diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index bad458092e..cc694a619e 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -654,7 +654,7 @@ class OrderSerializer(I18nAwareModelSerializer): model = Order fields = ( 'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date', - 'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads', + 'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads', 'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'url', 'customer' ) @@ -685,7 +685,7 @@ class OrderSerializer(I18nAwareModelSerializer): def update(self, instance, validated_data): # Even though all fields that shouldn't be edited are marked as read_only in the serializer # (hopefully), we'll be extra careful here and be explicit about the model fields we update. - update_fields = ['comment', 'checkin_attention', 'email', 'locale', 'phone'] + update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone'] if 'invoice_address' in validated_data: iadata = validated_data.pop('invoice_address') @@ -925,6 +925,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): min_length=5 ) comment = serializers.CharField(required=False, allow_blank=True) + custom_followup_at = serializers.DateField(required=False, allow_null=True) payment_provider = serializers.CharField(required=False, allow_null=True) payment_info = CompatibleJSONField(required=False) consume_carts = serializers.ListField(child=serializers.CharField(), required=False) @@ -943,7 +944,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer): model = Order fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel', 'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts', - 'force', 'send_email', 'simulate', 'customer') + 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at') def validate_payment_provider(self, pp): if pp is None: diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index f21bae3257..8e4a8aafcb 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -692,6 +692,16 @@ class OrderViewSet(viewsets.ModelViewSet): } ) + if 'custom_followup_at' in self.request.data and serializer.instance.custom_followup_at != self.request.data.get('custom_followup_at'): + serializer.instance.log_action( + 'pretix.event.order.custom_followup_at', + user=self.request.user, + auth=self.request.auth, + data={ + 'new_custom_followup_at': self.request.data.get('custom_followup_at') + } + ) + if 'checkin_attention' in self.request.data and serializer.instance.checkin_attention != self.request.data.get('checkin_attention'): serializer.instance.log_action( 'pretix.event.order.checkin_attention', diff --git a/src/pretix/base/exporters/orderlist.py b/src/pretix/base/exporters/orderlist.py index 135e8888a4..85a1a4995c 100644 --- a/src/pretix/base/exporters/orderlist.py +++ b/src/pretix/base/exporters/orderlist.py @@ -288,6 +288,7 @@ class OrderListExporter(MultiSheetListExporter): headers.append(_('Sales channel')) headers.append(_('Requires special attention')) headers.append(_('Comment')) + headers.append(_('Follow-up date')) headers.append(_('Positions')) headers.append(_('E-mail address verified')) headers.append(_('Payment providers')) @@ -393,6 +394,7 @@ class OrderListExporter(MultiSheetListExporter): row.append(order.sales_channel) row.append(_('Yes') if order.checkin_attention else _('No')) row.append(order.comment or "") + row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "") row.append(order.pcnt) row.append(_('Yes') if order.email_known_to_work else _('No')) row.append(', '.join([ @@ -574,6 +576,7 @@ class OrderListExporter(MultiSheetListExporter): _('Seat row'), _('Seat number'), _('Order comment'), + _('Follow-up date'), ] questions = list(Question.objects.filter(event__in=self.events)) @@ -677,6 +680,7 @@ class OrderListExporter(MultiSheetListExporter): row += ['', '', '', '', ''] row.append(order.comment) + row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "") acache = {} for a in op.answers.all(): # We do not want to localize Date, Time and Datetime question answers, as those can lead @@ -780,7 +784,7 @@ class PaymentListExporter(ListExporter): headers = [ _('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'), - _('Status code'), _('Amount'), _('Payment method'), _('Comment') + _('Status code'), _('Amount'), _('Payment method'), _('Comment'), ] yield headers diff --git a/src/pretix/base/migrations/0193_auto_20210611_1355.py b/src/pretix/base/migrations/0193_auto_20210611_1355.py new file mode 100644 index 0000000000..65c249348b --- /dev/null +++ b/src/pretix/base/migrations/0193_auto_20210611_1355.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.3 on 2021-06-11 13:55 + +import django.db.models.manager +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0192_checkin_more_fields'), + ] + + operations = [ + migrations.AlterModelManagers( + name='checkin', + managers=[ + ('all', django.db.models.manager.Manager()), + ], + ), + migrations.AddField( + model_name='order', + name='custom_followup_at', + field=models.DateField(blank=True, null=True), + ), + ] diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 71669d90c3..7e26c6fe06 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -60,7 +60,7 @@ from django.utils.crypto import get_random_string from django.utils.encoding import escape_uri_path from django.utils.formats import date_format from django.utils.functional import cached_property -from django.utils.timezone import make_aware, now +from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.translation import gettext_lazy as _, pgettext_lazy from django_countries.fields import Country from django_scopes import ScopedManager, scopes_disabled @@ -217,6 +217,11 @@ class Order(LockModel, LoggedModel): help_text=_("The text entered in this field will not be visible to the user and is available for your " "convenience.") ) + custom_followup_at = models.DateField( + verbose_name=_("Follow-up date"), + help_text=_('We\'ll show you this order to be due for a follow-up on this day.'), + null=True, blank=True + ) checkin_attention = models.BooleanField( verbose_name=_('Requires special attention'), default=False, @@ -300,6 +305,10 @@ class Order(LockModel, LoggedModel): """ return self.all_fees(manager='objects') + @property + def custom_followup_due(self): + return self.custom_followup_at and self.custom_followup_at <= now().astimezone(get_current_timezone()).date() + @cached_property @scopes_disabled() def count_positions(self): diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 6a7415b331..cc3a9b4ceb 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -205,6 +205,10 @@ class OrderFilterForm(FilterForm): ('na', _('Approved, payment pending')), ('pa', _('Approval pending')), )), + (_('Follow-up date'), ( + ('custom_followup_at', _('Follow-up configured')), + ('custom_followup_due', _('Follow-up due')), + )), ('testmode', _('Test mode')), ), required=False, @@ -324,6 +328,14 @@ class OrderFilterForm(FilterForm): status=Order.STATUS_PENDING, require_approval=False ) + elif s == 'custom_followup_at': + qs = qs.filter( + custom_followup_at__isnull=False + ) + elif s == 'custom_followup_due': + qs = qs.filter( + custom_followup_at__lte=now().astimezone(get_current_timezone()).date() + ) elif s == 'testmode': qs = qs.filter( testmode=True diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py index 9d07dc070d..e21b4dcdc1 100644 --- a/src/pretix/control/forms/orders.py +++ b/src/pretix/control/forms/orders.py @@ -230,12 +230,13 @@ class ExporterForm(forms.Form): class CommentForm(I18nModelForm): class Meta: model = Order - fields = ['comment', 'checkin_attention'] + fields = ['comment', 'checkin_attention', 'custom_followup_at'] widgets = { 'comment': forms.Textarea(attrs={ 'rows': 3, 'class': 'helper-width-100', }), + 'custom_followup_at': DatePickerWidget(), } diff --git a/src/pretix/control/logdisplay.py b/src/pretix/control/logdisplay.py index b021b710c7..19e2c2f5b7 100644 --- a/src/pretix/control/logdisplay.py +++ b/src/pretix/control/logdisplay.py @@ -360,6 +360,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs): 'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'), 'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'), 'pretix.event.order.comment': _('The order\'s internal comment has been updated.'), + 'pretix.event.order.custom_followup_at': _('The order\'s follow-up date has been updated.'), 'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been ' 'toggled.'), 'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'), diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html index 5ebaa0d421..a37eb767d2 100644 --- a/src/pretix/control/templates/pretixcontrol/order/index.html +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -890,10 +890,9 @@