diff --git a/doc/api/resources/invoices.rst b/doc/api/resources/invoices.rst index 5dde127c32..68e166d029 100644 --- a/doc/api/resources/invoices.rst +++ b/doc/api/resources/invoices.rst @@ -78,6 +78,12 @@ lines list of objects The actual invo an event series not created by a product (e.g. shipping or cancellation fees) as well as whenever the respective (sub)event has no end date set. +├ event_location string Location of the (sub)event this line was created for as it + was set during invoice creation. Can be ``null`` for all invoice + lines created before this was introduced as well as for lines in + an event series not created by a product (e.g. shipping or + cancellation fees) as well as whenever the respective (sub)event + has no location set. ├ attendee_name string Attendee name at time of invoice creation. Can be ``null`` if no name was set or if names are configured to not be added to invoices. ├ gross_value money (string) Price including taxes @@ -110,6 +116,10 @@ internal_reference string Customer's refe The attributes ``fee_type`` and ``fee_internal_type`` have been added. +.. versionchanged:: 4.1 + + The attribute ``lines.event_location`` has been added. + Endpoints --------- @@ -179,6 +189,7 @@ Endpoints "fee_internal_type": null, "event_date_from": "2017-12-27T10:00:00Z", "event_date_to": null, + "event_location": "Heidelberg", "attendee_name": null, "gross_value": "23.00", "tax_value": "0.00", @@ -267,6 +278,7 @@ Endpoints "fee_internal_type": null, "event_date_from": "2017-12-27T10:00:00Z", "event_date_to": null, + "event_location": "Heidelberg", "attendee_name": null, "gross_value": "23.00", "tax_value": "0.00", diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index 8793d07627..c1eca9204c 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -734,6 +734,7 @@ class EventSettingsSerializer(SettingsSerializer): 'invoice_numbers_prefix_cancellations', 'invoice_numbers_counter_length', 'invoice_attendee_name', + 'invoice_event_location', 'invoice_include_expire_date', 'invoice_address_explanation_text', 'invoice_email_attachment', diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 5c1168aa2a..17a14eb642 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -1428,7 +1428,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer): model = InvoiceLine fields = ('position', 'description', 'item', 'variation', 'attendee_name', 'event_date_from', 'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type', - 'fee_internal_type') + 'fee_internal_type', 'event_location') class InvoiceSerializer(I18nAwareModelSerializer): diff --git a/src/pretix/base/exporters/invoices.py b/src/pretix/base/exporters/invoices.py index a5f0096acc..ab39e8a1b6 100644 --- a/src/pretix/base/exporters/invoices.py +++ b/src/pretix/base/exporters/invoices.py @@ -324,7 +324,6 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter): _('Tax rate'), _('Tax name'), _('Event start date'), - _('Date'), _('Order code'), _('E-mail address'), @@ -348,6 +347,8 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter): _('Invoice recipient:') + ' ' + _('Beneficiary'), _('Invoice recipient:') + ' ' + _('Internal reference'), _('Payment providers'), + _('Event end date'), + _('Location'), ] p_providers = OrderPayment.objects.filter( @@ -406,7 +407,9 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter): ', '.join([ str(self.providers.get(p, p)) for p in sorted(set((l.payment_providers or '').split(','))) if p and p != 'free' - ]) + ]), + date_format(l.event_date_to, "SHORT_DATE_FORMAT") if l.event_date_to else "", + l.event_location or "", ] @cached_property diff --git a/src/pretix/base/migrations/0201_invoiceline_event_location.py b/src/pretix/base/migrations/0201_invoiceline_event_location.py new file mode 100644 index 0000000000..e69f7e6df5 --- /dev/null +++ b/src/pretix/base/migrations/0201_invoiceline_event_location.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2021-11-03 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0200_transaction'), + ] + + operations = [ + migrations.AddField( + model_name='invoiceline', + name='event_location', + field=models.TextField(null=True), + ), + ] diff --git a/src/pretix/base/models/invoices.py b/src/pretix/base/models/invoices.py index 836bccdf7e..ece97d6f96 100644 --- a/src/pretix/base/models/invoices.py +++ b/src/pretix/base/models/invoices.py @@ -329,6 +329,8 @@ class InvoiceLine(models.Model): :type event_date_from: datetime :param event_date_to: Event end date of the (sub)event at the time the invoice was created :type event_date_to: datetime + :param event_location: Event location of the (sub)event at the time the invoice was created + :type event_location: str :param item: The item this line refers to :type item: Item :param variation: The variation this line refers to @@ -346,6 +348,7 @@ class InvoiceLine(models.Model): subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT) event_date_from = models.DateTimeField(null=True) event_date_to = models.DateTimeField(null=True) + event_location = models.TextField(null=True, blank=True) item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT) variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT) attendee_name = models.TextField(null=True, blank=True) diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py index 7de971b41e..333cfe7d4d 100644 --- a/src/pretix/base/services/invoices.py +++ b/src/pretix/base/services/invoices.py @@ -69,6 +69,10 @@ from pretix.helpers.models import modelcopy logger = logging.getLogger(__name__) +def _location_oneliner(loc): + return ', '.join([l.strip() for l in loc.splitlines() if l and l.strip()]) + + @transaction.atomic def build_invoice(invoice: Invoice) -> Invoice: invoice.locale = invoice.event.settings.get('invoice_language', invoice.event.settings.locale) @@ -176,19 +180,38 @@ def build_invoice(invoice: Invoice) -> Invoice: reverse_charge = False positions.sort(key=lambda p: p.sort_key) + fees = list(invoice.order.fees.all()) + + locations = { + str((p.subevent or invoice.event).location) if (p.subevent or invoice.event).location else None + for p in positions + } + if fees and invoice.event.has_subevents: + locations.add(None) tax_texts = [] + + if invoice.event.settings.invoice_event_location and len(locations) == 1 and list(locations)[0] is not None: + tax_texts.append(pgettext("invoice", "Event location: {location}").format( + location=_location_oneliner(str(list(locations)[0])) + )) + for i, p in enumerate(positions): if not invoice.event.settings.invoice_include_free and p.price == Decimal('0.00') and not p.addon_c: continue + location = str((p.subevent or invoice.event).location) if (p.subevent or invoice.event).location else None + desc = str(p.item.name) if p.variation: desc += " - " + str(p.variation.value) if p.addon_to_id: desc = " + " + desc if invoice.event.settings.invoice_attendee_name and p.attendee_name: - desc += "
" + pgettext("invoice", "Attendee: {name}").format(name=p.attendee_name) + desc += "
" + pgettext("invoice", "Attendee: {name}").format( + name=p.attendee_name + ) + for recv, resp in invoice_line_text.send(sender=invoice.event, position=p): if resp: desc += "
" + resp @@ -204,6 +227,12 @@ def build_invoice(invoice: Invoice) -> Invoice: if invoice.event.has_subevents: desc += "
" + pgettext("subevent", "Date: {}").format(p.subevent) + + if invoice.event.settings.invoice_event_location and location and len(locations) > 1: + desc += "
" + pgettext("invoice", "Event location: {location}").format( + location=_location_oneliner(location) + ) + InvoiceLine.objects.create( position=i, invoice=invoice, @@ -216,6 +245,7 @@ def build_invoice(invoice: Invoice) -> Invoice: attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None, event_date_from=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from, event_date_to=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to, + event_location=location if invoice.event.settings.invoice_event_location else None, tax_rate=p.tax_rate, tax_name=p.tax_rule.name if p.tax_rule else '' ) @@ -228,7 +258,7 @@ def build_invoice(invoice: Invoice) -> Invoice: tax_texts.append(tax_text) offset = len(positions) - for i, fee in enumerate(invoice.order.fees.all()): + for i, fee in enumerate(fees): if fee.fee_type == OrderFee.FEE_TYPE_OTHER and fee.description: fee_title = fee.description else: @@ -242,6 +272,12 @@ def build_invoice(invoice: Invoice) -> Invoice: gross_value=fee.value, event_date_from=None if invoice.event.has_subevents else invoice.event.date_from, event_date_to=None if invoice.event.has_subevents else invoice.event.date_to, + event_location=( + None if invoice.event.has_subevents + else (str(invoice.event.location) + if invoice.event.settings.invoice_event_location and invoice.event.location + else None) + ), tax_value=fee.tax_value, tax_rate=fee.tax_rate, tax_name=fee.tax_rule.name if fee.tax_rule else '', diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index f5937b2edf..9c57ba4d38 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -310,6 +310,17 @@ DEFAULTS = { label=_("Show attendee names on invoices"), ) }, + 'invoice_event_location': { + 'default': 'False', + 'type': bool, + 'form_class': forms.BooleanField, + 'serializer_class': serializers.BooleanField, + 'form_kwargs': dict( + label=_("Show event location on invoices"), + help_text=_("The event location will be shown below the list of products if it is the same for all " + "lines. It will be shown on every line if there are different locations.") + ) + }, 'invoice_eu_currencies': { 'default': 'True', 'type': bool, diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 83e331acd9..7d8cc2bc7d 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -751,6 +751,7 @@ class InvoiceSettingsForm(SettingsForm): 'invoice_reissue_after_modify', 'invoice_generate', 'invoice_attendee_name', + 'invoice_event_location', 'invoice_include_expire_date', 'invoice_numbers_consecutive', 'invoice_numbers_prefix', diff --git a/src/pretix/control/templates/pretixcontrol/event/invoicing.html b/src/pretix/control/templates/pretixcontrol/event/invoicing.html index d05f0112c8..f573889f6e 100644 --- a/src/pretix/control/templates/pretixcontrol/event/invoicing.html +++ b/src/pretix/control/templates/pretixcontrol/event/invoicing.html @@ -48,6 +48,7 @@ {% trans "Invoice customization" %} {% bootstrap_field form.invoice_renderer layout="control" %} {% bootstrap_field form.invoice_attendee_name layout="control" %} + {% bootstrap_field form.invoice_event_location layout="control" %} {% bootstrap_field form.invoice_include_expire_date layout="control" %} {% bootstrap_field form.invoice_introductory_text layout="control" %} {% bootstrap_field form.invoice_additional_text layout="control" %} diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 502c1e8696..ad690604ac 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -1022,6 +1022,7 @@ TEST_INVOICE_RES = { "description": "Budget Ticket
Attendee: Peter", 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, + 'event_location': None, 'attendee_name': 'Peter', 'item': None, 'variation': None, @@ -1037,6 +1038,7 @@ TEST_INVOICE_RES = { "description": "Payment fee", 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, + 'event_location': None, 'attendee_name': None, 'fee_type': "payment", 'fee_internal_type': None, @@ -4440,6 +4442,7 @@ def test_order_create_invoice(token_client, organizer, event, order): 'description': 'Budget Ticket
Attendee: Peter', 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, + 'event_location': None, 'fee_type': None, 'fee_internal_type': None, 'attendee_name': 'Peter', @@ -4455,6 +4458,7 @@ def test_order_create_invoice(token_client, organizer, event, order): 'description': 'Payment fee', 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, + 'event_location': None, 'fee_type': "payment", 'fee_internal_type': None, 'attendee_name': None,