diff --git a/doc/api/resources/customers.rst b/doc/api/resources/customers.rst index 444542828..bf2dcd1db 100644 --- a/doc/api/resources/customers.rst +++ b/doc/api/resources/customers.rst @@ -14,6 +14,7 @@ The customer resource contains the following public fields: Field Type Description ===================================== ========================== ======================================================= identifier string Internal ID of the customer +external_identifier string External ID of the customer (or ``null``) email string Customer email address name string Name of this customer (or ``null``) name_parts object of strings Decomposition of name (i.e. given name, family name) @@ -24,6 +25,7 @@ last_login datetime Date and time o date_joined datetime Date and time of registration locale string Preferred language of the customer last_modified datetime Date and time of modification of the record +notes string Internal notes and comments (or ``null``) ===================================== ========================== ======================================================= .. versionadded:: 4.0 @@ -58,6 +60,7 @@ Endpoints "results": [ { "identifier": "8WSAJCJ", + "external_identifier": null, "email": "customer@example.org", "name": "John Doe", "name_parts": { @@ -69,7 +72,8 @@ Endpoints "last_login": null, "date_joined": "2021-04-06T13:44:22.809216Z", "locale": "de", - "last_modified": "2021-04-06T13:44:22.809377Z" + "last_modified": "2021-04-06T13:44:22.809377Z", + "notes": null } ] } @@ -103,6 +107,7 @@ Endpoints { "identifier": "8WSAJCJ", + "external_identifier": null, "email": "customer@example.org", "name": "John Doe", "name_parts": { @@ -114,7 +119,8 @@ Endpoints "last_login": null, "date_joined": "2021-04-06T13:44:22.809216Z", "locale": "de", - "last_modified": "2021-04-06T13:44:22.809377Z" + "last_modified": "2021-04-06T13:44:22.809377Z", + "notes": null } :param organizer: The ``slug`` field of the organizer to fetch @@ -150,6 +156,7 @@ Endpoints { "identifier": "8WSAJCJ", + "external_identifier": null, "email": "test@example.org", ... } @@ -193,6 +200,7 @@ Endpoints { "identifier": "8WSAJCJ", + "external_identifier": null, "email": "test@example.org", … } @@ -226,6 +234,7 @@ Endpoints { "identifier": "8WSAJCJ", + "external_identifier": null, "email": null, … } diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index b2c86ff56..dd81ed9ba 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -71,8 +71,8 @@ class CustomerSerializer(I18nAwareModelSerializer): class Meta: model = Customer - fields = ('identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined', - 'locale', 'last_modified') + fields = ('identifier', 'external_identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined', + 'locale', 'last_modified', 'notes') class MembershipTypeSerializer(I18nAwareModelSerializer): diff --git a/src/pretix/base/exporters/orderlist.py b/src/pretix/base/exporters/orderlist.py index 2ee2e3fa9..d03ebe2c5 100644 --- a/src/pretix/base/exporters/orderlist.py +++ b/src/pretix/base/exporters/orderlist.py @@ -259,7 +259,7 @@ class OrderListExporter(MultiSheetListExporter): payment_providers=Subquery(p_providers, output_field=CharField()), invoice_numbers=Subquery(i_numbers, output_field=CharField()), pcnt=Subquery(s, output_field=IntegerField()) - ).select_related('invoice_address') + ).select_related('invoice_address', 'customer') qs = self._date_filter(qs, form_data, rel='') @@ -268,8 +268,8 @@ class OrderListExporter(MultiSheetListExporter): tax_rates = self._get_all_tax_rates(qs) headers = [ - _('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'), _('Order date'), - _('Order time'), _('Company'), _('Name'), + _('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'), + _('Order date'), _('Order time'), _('Company'), _('Name'), ] name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None if name_scheme and len(name_scheme['fields']) > 1: @@ -294,6 +294,7 @@ class OrderListExporter(MultiSheetListExporter): headers.append(_('Follow-up date')) headers.append(_('Positions')) headers.append(_('E-mail address verified')) + headers.append(_('External customer ID')) headers.append(_('Payment providers')) if form_data.get('include_payment_amounts'): payment_methods = self._get_all_payment_methods(qs) @@ -400,6 +401,7 @@ class OrderListExporter(MultiSheetListExporter): 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(str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '') row.append(', '.join([ str(self.providers.get(p, p)) for p in sorted(set((order.payment_providers or '').split(','))) if p and p != 'free' @@ -428,7 +430,7 @@ class OrderListExporter(MultiSheetListExporter): order__event__in=self.events, ).annotate( payment_providers=Subquery(p_providers, output_field=CharField()), - ).select_related('order', 'order__invoice_address', 'tax_rule') + ).select_related('order', 'order__invoice_address', 'order__customer', 'tax_rule') if form_data['paid_only']: qs = qs.filter(order__status=Order.STATUS_PAID, canceled=False) @@ -459,6 +461,7 @@ class OrderListExporter(MultiSheetListExporter): _('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'), ] + headers.append(_('External customer ID')) headers.append(_('Payment providers')) yield headers @@ -502,6 +505,7 @@ class OrderListExporter(MultiSheetListExporter): ] except InvoiceAddress.DoesNotExist: row += [''] * (8 + (len(name_scheme['fields']) if name_scheme and len(name_scheme['fields']) > 1 else 0)) + row.append(str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '') row.append(', '.join([ str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(','))) if p and p != 'free' @@ -524,7 +528,7 @@ class OrderListExporter(MultiSheetListExporter): qs = base_qs.annotate( payment_providers=Subquery(p_providers, output_field=CharField()), ).select_related( - 'order', 'order__invoice_address', 'item', 'variation', + 'order', 'order__invoice_address', 'order__customer', 'item', 'variation', 'voucher', 'tax_rule' ).prefetch_related( 'answers', 'answers__question', 'answers__options' @@ -611,6 +615,7 @@ class OrderListExporter(MultiSheetListExporter): headers += [ _('Sales channel'), _('Order locale'), _('E-mail address verified'), + _('External customer ID'), _('Payment providers'), ] @@ -730,7 +735,8 @@ class OrderListExporter(MultiSheetListExporter): row += [ order.sales_channel, order.locale, - _('Yes') if order.email_known_to_work else _('No') + _('Yes') if order.email_known_to_work else _('No'), + str(order.customer.external_identifier) if order.customer and order.customer.external_identifier else '', ] row.append(', '.join([ str(self.providers.get(p, p)) for p in sorted(set((op.payment_providers or '').split(','))) diff --git a/src/pretix/base/migrations/0214_customer_notes_ext_id.py b/src/pretix/base/migrations/0214_customer_notes_ext_id.py new file mode 100644 index 000000000..57691b3f2 --- /dev/null +++ b/src/pretix/base/migrations/0214_customer_notes_ext_id.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2022-04-28 08:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0213_discount_condition_ignore_voucher_discounted'), + ] + + operations = [ + migrations.AddField( + model_name='customer', + name='external_identifier', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='customer', + name='notes', + field=models.TextField(null=True), + ), + ] diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 6952114ad..2a8c970a8 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -59,6 +59,8 @@ class Customer(LoggedModel): default=settings.LANGUAGE_CODE, verbose_name=_('Language')) last_modified = models.DateTimeField(auto_now=True) + external_identifier = models.CharField(max_length=255, verbose_name=_('External identifier'), null=True, blank=True) + notes = models.TextField(verbose_name=_('Notes'), null=True, blank=True) objects = ScopedManager(organizer='organizer') @@ -90,6 +92,8 @@ class Customer(LoggedModel): self.name_cached = '' self.email = None self.phone = None + self.external_identifier = None + self.notes = None self.save() self.all_logentries().update(data={}, shredded=True) self.orders.all().update(customer=None) diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index a57409ffe..b61012ba5 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -1265,7 +1265,8 @@ class CustomerFilterForm(FilterForm): orders = { 'email': 'email', 'identifier': 'identifier', - 'name_cached': 'name_cached', + 'name': 'name_cached', + 'external_identifier': 'external_identifier', } query = forms.CharField( label=_('Search query'), diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index bdae0bc22..576d5b07e 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -607,7 +607,7 @@ class CustomerUpdateForm(forms.ModelForm): class Meta: model = Customer - fields = ['is_active', 'name_parts', 'email', 'is_verified', 'phone', 'locale'] + fields = ['is_active', 'external_identifier', 'name_parts', 'email', 'is_verified', 'phone', 'locale', 'notes'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -651,7 +651,7 @@ class CustomerCreateForm(CustomerUpdateForm): class Meta: model = Customer - fields = ['identifier', 'is_active', 'name_parts', 'email', 'is_verified', 'locale'] + fields = ['is_active', 'identifier', 'external_identifier', 'name_parts', 'email', 'is_verified', 'phone', 'locale', 'notes'] class MembershipUpdateForm(forms.ModelForm): diff --git a/src/pretix/control/templates/pretixcontrol/organizers/customer.html b/src/pretix/control/templates/pretixcontrol/organizers/customer.html index fff361b1f..c19ea15ee 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/customer.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/customer.html @@ -27,6 +27,10 @@