diff --git a/src/pretix/base/invoicing/pdf.py b/src/pretix/base/invoicing/pdf.py index 182b2b3f4c..8606db4832 100644 --- a/src/pretix/base/invoicing/pdf.py +++ b/src/pretix/base/invoicing/pdf.py @@ -666,6 +666,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): has_taxes = any(il.tax_value for il in self.invoice.lines.all()) or self.invoice.reverse_charge header_dates = self._date_range_in_header() tz = self.invoice.event.timezone + has_multiple_service_dates = len(set( + (il.period_start, il.period_end) for il in self.invoice.lines.all() + )) > 1 + request_show_service_date = False story = [ NextPageTemplate('FirstPage'), @@ -741,7 +745,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): period_line = "" else: - period_line = f"\n{date_format(day(period_start), 'SHORT_DATE_FORMAT')} – {date_format(day(period_end), 'SHORT_DATE_FORMAT')}" + period_line = f"{date_format(day(period_start), 'SHORT_DATE_FORMAT')} – {date_format(day(period_end), 'SHORT_DATE_FORMAT')}" elif period_start or period_end: # It's a single-day period @@ -765,12 +769,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): period_line = "" else: - period_line = f"\n{date_format(delivery_day, 'SHORT_DATE_FORMAT')}" + period_line = date_format(delivery_day, 'SHORT_DATE_FORMAT') else: # No period known period_line = "" - description += period_line + if not has_multiple_service_dates and period_line: + # Group together at the end of the invoice + request_show_service_date = period_line + else: + description += "\n" + period_line + lines = list(lines) if has_taxes: if len(lines) > 1: @@ -884,6 +893,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): story.append(Spacer(1, 10 * mm)) + if request_show_service_date: + story.append(FontFallbackParagraph( + self._normalize(pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date)), + self.stylesheet['Normal'] + )) + if self.invoice.payment_provider_text: story.append(FontFallbackParagraph( self._normalize(self.invoice.payment_provider_text), diff --git a/src/pretix/base/migrations/0289_invoiceline_period.py b/src/pretix/base/migrations/0289_invoiceline_period.py index 79781801f4..8e6d3fb4a8 100644 --- a/src/pretix/base/migrations/0289_invoiceline_period.py +++ b/src/pretix/base/migrations/0289_invoiceline_period.py @@ -1,49 +1,60 @@ # Generated by Django 4.2.17 on 2025-09-08 08:14 - +from django.core.cache import cache from django.db import migrations -def set_default_cardtypes(apps, schema_editor): +def set_invoice_period(apps, schema_editor): EventSettingsStore = apps.get_model("pretixbase", "Event_SettingsStore") ev_seen = set() insert_queue = [] + flush_queue = [] + + def store(): + EventSettingsStore.objects.bulk_create( + insert_queue, + update_conflicts=True, + update_fields=["value"], + unique_fields=["object", "key"], + ) + for f in flush_queue: + cache.delete(f) + flush_queue.clear() + insert_queue.clear() # Existing events that use pretix-zugferd and have explicitly disabled delivery dates - for ev in EventSettingsStore.objects.filter(key="zugferd_include_delivery_date", value="False"): + for setting in EventSettingsStore.objects.filter(key="zugferd_include_delivery_date", value="False"): + flush_queue.append("hierarkey_{}_{}".format("event", setting.object_id)) insert_queue.append( EventSettingsStore( - object_id=ev.object_id, + object_id=setting.object_id, key="invoice_period", value="invoice_date", ) ) - ev_seen.add(ev.object_id) + ev_seen.add(setting.object_id) if len(insert_queue) > 1000: - EventSettingsStore.objects.bulk_create(insert_queue, ignore_conflicts=True) - insert_queue.clear() + store() # Existing events that previously hid their date on invoices - # Ignore series as it doesn't make sense for them - for ev in EventSettingsStore.objects.filter(key="show_dates_on_frontpage", value="False", - object__has_subevents=False): - if ev.object_id in ev_seen: + for setting in EventSettingsStore.objects.filter(key="show_dates_on_frontpage", value="False"): + if setting.object_id in ev_seen: continue + flush_queue.append("hierarkey_{}_{}".format("event", setting.object_id)) insert_queue.append( EventSettingsStore( - object_id=ev.object_id, + object_id=setting.object_id, key="invoice_period", value="auto_no_event", ) ) - ev_seen.add(ev.object_id) + ev_seen.add(setting.object_id) if len(insert_queue) > 1000: - EventSettingsStore.objects.bulk_create(insert_queue, ignore_conflicts=True) - insert_queue.clear() + store() - EventSettingsStore.objects.bulk_create(insert_queue, ignore_conflicts=True) + store() class Migration(migrations.Migration): @@ -62,4 +73,8 @@ class Migration(migrations.Migration): old_name="event_date_from", new_name="period_start", ), + migrations.RunPython( + set_invoice_period, + migrations.RunPython.noop, + ) ] diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py index d1a86a9c97..a5eb325f91 100644 --- a/src/pretix/base/services/invoices.py +++ b/src/pretix/base/services/invoices.py @@ -84,6 +84,7 @@ def build_invoice(invoice: Invoice) -> Invoice: min_period_start = None max_period_end = None + now_dt = now() with (language(invoice.locale, invoice.event.settings.region)): invoice.invoice_from = invoice.event.settings.get('invoice_address_from') @@ -272,7 +273,7 @@ def build_invoice(invoice: Invoice) -> Invoice: location=_location_oneliner(location) ) - period_start, period_end = _service_period_for_position(invoice, p) + period_start, period_end = _service_period_for_position(invoice, p, now_dt) min_period_start = min(min_period_start or period_start, period_start) max_period_end = min(max_period_end or period_end, period_end) @@ -376,7 +377,7 @@ def build_cancellation(invoice: Invoice): return invoice -def _service_period_for_position(invoice, position): +def _service_period_for_position(invoice, position, invoice_dt): if invoice.event.settings.invoice_period in ("auto", "auto_no_event"): if position.valid_from or position.valid_until: period_start = position.valid_from or now() @@ -384,12 +385,18 @@ def _service_period_for_position(invoice, position): elif memberships := list(position.granted_memberships.all()): period_start = min(m.date_start for m in memberships) period_end = max(m.date_end for m in memberships) - elif invoice.event.has_subevents and position.subevent: - period_start = position.subevent.date_from - period_end = position.subevent.date_to + elif invoice.event.has_subevents: + if position.subevent: + period_start = position.subevent.date_from + period_end = position.subevent.date_to + else: + # Currently impossible case, but might not be in the future and never makes + # sense to use the event date here + period_start = invoice_dt + period_end = invoice_dt elif invoice.event.settings.invoice_period == "auto_no_event": - period_start = now() - period_end = now() + period_start = invoice_dt + period_end = invoice_dt else: period_start = invoice.event.date_from period_end = invoice.event.date_to @@ -404,7 +411,7 @@ def _service_period_for_position(invoice, position): period_start = invoice.event.date_from period_end = invoice.event.date_to elif invoice.event.settings.invoice_period == "invoice_date": - period_start = period_end = now() + period_start = period_end = invoice_dt else: raise ValueError(f"Invalid invoice period setting '{invoice.event.settings.invoice_period}'") diff --git a/src/pretix/control/templates/pretixcontrol/event/invoicing.html b/src/pretix/control/templates/pretixcontrol/event/invoicing.html index c3cfa58675..52d9a7e9bb 100644 --- a/src/pretix/control/templates/pretixcontrol/event/invoicing.html +++ b/src/pretix/control/templates/pretixcontrol/event/invoicing.html @@ -16,6 +16,18 @@ {% bootstrap_field form.invoice_email_organizer layout="control" %} {% bootstrap_field form.invoice_language layout="control" %} {% bootstrap_field form.invoice_period layout="control" %} + + {% if not request.event.settings.show_dates_on_frontpage %} +
+
+ {% blocktrans trimmed %} + You configured that your shop is not an event and the event date should not be shown. + Therefore, we recommend that you set the date of service to a different option. + {% endblocktrans %} +
+
+ {% endif %} + {% bootstrap_field form.invoice_include_free layout="control" %} {% bootstrap_field form.invoice_show_payments layout="control" %} {% bootstrap_field form.invoice_reissue_after_modify layout="control" %}