From d0794d7b947cc5f1150ff5b995315699d0b50240 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 13 Feb 2020 09:49:21 +0100 Subject: [PATCH] Optionally allow to automatically reissue an invoice after a data change --- src/pretix/api/serializers/event.py | 1 + src/pretix/base/settings.py | 12 +++++ src/pretix/control/forms/event.py | 1 + .../pretixcontrol/event/invoicing.html | 1 + .../pretixpresale/event/order_modify.html | 2 +- src/pretix/presale/views/order.py | 16 ++++++- src/tests/presale/test_orders.py | 44 +++++++++++++++++++ 7 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index 6e9d2fb976..bcd5f26c05 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -531,6 +531,7 @@ class EventSettingsSerializer(serializers.Serializer): 'invoice_name_required', 'invoice_address_not_asked_free', 'invoice_show_payments', + 'invoice_reissue_after_modify', 'invoice_include_free', 'invoice_generate', 'invoice_numbers_consecutive', diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index b2f880ebd3..cf4efa8934 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -443,6 +443,18 @@ DEFAULTS = { help_text=_("Invoices will never be automatically generated for free orders.") ) }, + 'invoice_reissue_after_modify': { + 'default': 'False', + 'type': bool, + 'form_class': forms.BooleanField, + 'serializer_class': serializers.BooleanField, + 'form_kwargs': dict( + label=_("Automatically cancel and reissue invoice on address changes"), + help_text=_("If customers change their invoice address on an existing order, the invoice will " + "automatically be canceled and a new invoice will be issued. This setting does not affect " + "changes made through the backend."), + ) + }, 'invoice_generate_sales_channels': { 'default': json.dumps(['web']), 'type': list diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 26c3710517..1953d24b13 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -571,6 +571,7 @@ class InvoiceSettingsForm(SettingsForm): 'invoice_address_not_asked_free', 'invoice_include_free', 'invoice_show_payments', + 'invoice_reissue_after_modify', 'invoice_generate', 'invoice_attendee_name', 'invoice_include_expire_date', diff --git a/src/pretix/control/templates/pretixcontrol/event/invoicing.html b/src/pretix/control/templates/pretixcontrol/event/invoicing.html index cb3d1a4243..66dbf42c11 100644 --- a/src/pretix/control/templates/pretixcontrol/event/invoicing.html +++ b/src/pretix/control/templates/pretixcontrol/event/invoicing.html @@ -15,6 +15,7 @@ {% bootstrap_field form.invoice_language layout="control" %} {% 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" %} {% bootstrap_field form.invoice_numbers_consecutive layout="control" %} {% bootstrap_field form.invoice_numbers_prefix layout="control" %} {% bootstrap_field form.invoice_numbers_prefix_cancellations layout="control" %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_modify.html b/src/pretix/presale/templates/pretixpresale/event/order_modify.html index 20e4ed55a6..c9826457f9 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order_modify.html +++ b/src/pretix/presale/templates/pretixpresale/event/order_modify.html @@ -13,7 +13,7 @@ {% csrf_token %}
{% if invoice_address_asked or event.settings.invoice_name_required %} - {% if invoice_address_asked and not request.GET.generate_invoice == "true" %} + {% if invoice_address_asked and not request.GET.generate_invoice == "true" and not event.settings.invoice_reissue_after_modify %}
{% blocktrans trimmed %} Modifying your invoice address will not automatically generate a new invoice. diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 3600e8bc2a..9b78a1182a 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -27,7 +27,8 @@ from pretix.base.models.orders import ( ) from pretix.base.payment import PaymentException from pretix.base.services.invoices import ( - generate_invoice, invoice_pdf, invoice_pdf_task, invoice_qualified, + generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task, + invoice_qualified, ) from pretix.base.services.mail import SendMailException from pretix.base.services.orders import ( @@ -669,6 +670,19 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem 'invoice': i.pk }) messages.success(self.request, _('The invoice has been generated.')) + elif self.request.event.settings.invoice_reissue_after_modify: + if self.invoice_form.changed_data: + inv = self.order.invoices.last() + if inv and not inv.canceled and not inv.shredded: + c = generate_cancellation(inv) + if self.order.status != Order.STATUS_CANCELED: + inv = generate_invoice(self.order) + else: + inv = c + self.order.log_action('pretix.event.order.invoice.reissued', data={ + 'invoice': inv.pk + }) + messages.success(self.request, _('The invoice has been reissued.')) invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk}) CachedTicket.objects.filter(order_position__order=self.order).delete() diff --git a/src/tests/presale/test_orders.py b/src/tests/presale/test_orders.py index 8eac5382d8..22900f004d 100644 --- a/src/tests/presale/test_orders.py +++ b/src/tests/presale/test_orders.py @@ -311,6 +311,50 @@ class OrdersTest(BaseOrdersTest): with scopes_disabled(): assert self.ticket_pos.answers.get(question=self.question).answer == 'ABC' + def test_modify_invoice_regenerate(self): + self.event.settings.set('invoice_reissue_after_modify', True) + self.event.settings.set('invoice_address_asked', True) + with scopes_disabled(): + generate_invoice(self.order) + + response = self.client.post( + '/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), { + '%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC', + }, follow=True) + self.assertRedirects(response, + '/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret), + target_status_code=200) + # Only questions changed + with scopes_disabled(): + assert self.order.invoices.count() == 1 + + response = self.client.post( + '/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), { + '%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC', + 'zipcode': '1234', + }, follow=True) + self.assertRedirects(response, + '/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret), + target_status_code=200) + with scopes_disabled(): + assert self.order.invoices.count() == 3 + + self.event.settings.set('invoice_reissue_after_modify', False) + + response = self.client.post( + '/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), { + '%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC', + 'zipcode': '54321', + }, follow=True) + self.assertRedirects(response, + '/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret), + target_status_code=200) + with scopes_disabled(): + assert self.order.invoices.count() == 3 + def test_orders_cancel_invalid(self): self.order.status = Order.STATUS_PAID self.order.save()