From b3ba6f17f08f3fb03e76795cc9e10dbfac2f1f4f Mon Sep 17 00:00:00 2001 From: Kara Engelhardt Date: Mon, 20 Apr 2026 14:00:09 +0200 Subject: [PATCH] Always regenerate dirty invoices in backend OrderChange view if reissue_invoice is set (Z#23230731) --- src/pretix/base/services/orders.py | 37 +++++++++++++++++------------- src/pretix/control/views/orders.py | 7 +++++- src/tests/base/test_orders.py | 25 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index ba3e199b98..826899c7de 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -1651,7 +1651,7 @@ class OrderChangeManager: raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.") return self._positions - def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False): + def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False, force_reissue_invoice=False): self.order = order self.user = user self.auth = auth @@ -1659,6 +1659,7 @@ class OrderChangeManager: self.split_order = None self.reissue_invoice = reissue_invoice self.allow_blocked_seats = allow_blocked_seats + self.force_reissue_invoice = force_reissue_invoice self._committed = False self._totaldiff_guesstimate = 0 self._quotadiff = Counter() @@ -2902,25 +2903,29 @@ class OrderChangeManager: } ) + def invoice_should_be_generated_now(self): + i = self.order.invoices.filter(is_cancellation=False).last() + return ( + self.event.settings.invoice_generate == "True" or ( + self.event.settings.invoice_generate == "paid" and + self.open_payment is not None and + self.open_payment.payment_provider.requires_invoice_immediately + ) or ( + self.event.settings.invoice_generate == "paid" and + self.order.status == Order.STATUS_PAID + ) or ( + # Backwards-compatible behaviour + (self.event.settings.invoice_generate not in ("True", "paid") or self.force_reissue_invoice) and + i and + not i.canceled + ) + ) + def _reissue_invoice(self): i = self.order.invoices.filter(is_cancellation=False).last() if self.reissue_invoice and self._invoice_dirty: order_now_qualified = invoice_qualified(self.order) - invoice_should_be_generated_now = ( - self.event.settings.invoice_generate == "True" or ( - self.event.settings.invoice_generate == "paid" and - self.open_payment is not None and - self.open_payment.payment_provider.requires_invoice_immediately - ) or ( - self.event.settings.invoice_generate == "paid" and - self.order.status == Order.STATUS_PAID - ) or ( - # Backwards-compatible behaviour - self.event.settings.invoice_generate not in ("True", "paid") and - i and - not i.canceled - ) - ) + invoice_should_be_generated_now = self.invoice_should_be_generated_now() invoice_should_be_generated_later = not invoice_should_be_generated_now and ( self.event.settings.invoice_generate in ("True", "paid") ) diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index d0bb06cc45..41ab10386f 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -1949,8 +1949,11 @@ class OrderChange(OrderView): @cached_property def other_form(self): - return OtherOperationsForm(prefix='other', order=self.order, + form = OtherOperationsForm(prefix='other', order=self.order, data=self.request.POST if self.request.method == "POST" else None) + ocm = OrderChangeManager(self.order) + form.fields['reissue_invoice'].initial = ocm.invoice_should_be_generated_now() + return form @cached_property def add_position_formset(self): @@ -2177,6 +2180,8 @@ class OrderChange(OrderView): notify=notify, reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True, allow_blocked_seats=True, + force_reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else False, + ) form_valid = (self._process_add_fees(ocm) and self._process_add_positions(ocm) and diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py index 8f39fc08e0..4a1cbbb72b 100644 --- a/src/tests/base/test_orders.py +++ b/src/tests/base/test_orders.py @@ -2531,6 +2531,31 @@ class OrderChangeManagerTests(BaseOrderChangeManagerTestCase, TestCase): ).confirm() assert self.order.invoices.count() == 3 + @classscope(attr='o') + def test_force_reissue_invoice_paid(self): + self.event.settings.invoice_generate = "paid" + generate_invoice(self.order) + assert self.order.invoices.count() == 1 + self.ocm.force_reissue_invoice = True + self.ocm.add_position(self.ticket, None, Decimal('2.00')) + self.ocm.commit() + assert self.order.invoices.count() == 3 + + @classscope(attr='o') + def test_force_reissue_invoice_paid_only_after_payment_only_if_enabled(self): + self.event.settings.invoice_generate = "False" + assert self.order.invoices.count() == 0 + self.ocm.force_reissue_invoice = True + self.ocm.add_position(self.ticket, None, Decimal('2.00')) + self.ocm.commit() + assert self.order.invoices.count() == 0 + self.order.refresh_from_db() + assert not self.order.invoice_dirty + self.order.payments.create( + provider='manual', amount=self.order.total + ).confirm() + assert self.order.invoices.count() == 0 + @classscope(attr='o') def test_reissue_invoice_paid_only_after_payment_only_if_enabled(self): self.event.settings.invoice_generate = "False"