Add new settings invoice_regenerate_allowed (#2071)

This commit is contained in:
Raphael Michel
2021-07-01 14:51:08 +02:00
committed by GitHub
parent 0c6971ff5f
commit 9089b630ed
9 changed files with 50 additions and 15 deletions

View File

@@ -275,6 +275,7 @@ class OrganizerSettingsSerializer(SettingsSerializer):
default_fields = [
'customer_accounts',
'customer_accounts_link_by_email',
'invoice_regenerate_allowed',
'contact_mail',
'imprint_url',
'organizer_info_text',

View File

@@ -1451,8 +1451,14 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
inv = self.get_object()
if inv.canceled:
raise ValidationError('The invoice has already been canceled.')
if not inv.event.settings.invoice_regenerate_allowed:
raise PermissionDenied('Invoices may not be changed after they are created.')
elif inv.shredded:
raise PermissionDenied('The invoice file is no longer stored on the server.')
elif inv.sent_to_organizer:
raise PermissionDenied('The invoice file has already been exported.')
elif now().astimezone(self.request.event.timezone).date() - inv.date > datetime.timedelta(days=1):
raise PermissionDenied('The invoice file is too old to be regenerated.')
else:
inv = regenerate_invoice(inv)
inv.order.log_action(

View File

@@ -323,7 +323,7 @@ def generate_invoice(order: Order, trigger_pdf=True):
order=order,
event=order.event,
organizer=order.event.organizer,
date=timezone.now().date(),
date=timezone.now().astimezone(order.event.timezone).date(),
)
invoice = build_invoice(invoice)
if trigger_pdf:

View File

@@ -744,6 +744,18 @@ DEFAULTS = {
"changes made through the backend."),
)
},
'invoice_regenerate_allowed': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Allow to update existing invoices"),
help_text=_("By default, invoices can never again be changed once they are issued. In most countries, we "
"recommend to leave this option turned off and always issue a new invoice if a change needs "
"to be made."),
)
},
'invoice_generate_sales_channels': {
'default': json.dumps(['web']),
'type': list

View File

@@ -286,6 +286,7 @@ class OrganizerSettingsForm(SettingsForm):
auto_fields = [
'customer_accounts',
'customer_accounts_link_by_email',
'invoice_regenerate_allowed',
'contact_mail',
'imprint_url',
'organizer_info_text',

View File

@@ -236,14 +236,16 @@
{% if i.is_cancellation %}{% trans "Cancellation" context "invoice" %}{% else %}{% trans "Invoice" %}{% endif %}
{{ i.number }}</a> ({{ i.date|date:"SHORT_DATE_FORMAT" }})
{% if not i.canceled %}
<form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
{% csrf_token %}
<button class="btn btn-default btn-xs" data-toggle="tooltip"
title="{% trans 'Rebuild the invoice with updated data but the same invoice number.' %}">
{% trans "Regenerate" %}
</button>
</form>
{% if request.event.settings.invoice_regenerate_allowed %}
<form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.regeninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">
{% csrf_token %}
<button class="btn btn-default btn-xs" data-toggle="tooltip"
title="{% trans 'Rebuild the invoice with updated data but the same invoice number.' %}">
{% trans "Regenerate" %}
</button>
</form>
{% endif %}
{% if not i.is_cancellation %}
<form class="form-inline helper-display-inline" method="post"
action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=i.pk %}">

View File

@@ -81,6 +81,10 @@
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
{% bootstrap_field sform.giftcard_length layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Invoices" %}</legend>
{% bootstrap_field sform.invoice_regenerate_allowed layout="control" %}
</fieldset>
</div>
</div>
<div class="col-xs-12 col-lg-2">

View File

@@ -1330,8 +1330,14 @@ class OrderInvoiceRegenerate(OrderView):
except Invoice.DoesNotExist:
messages.error(self.request, _('Unknown invoice.'))
else:
if not inv.event.settings.invoice_regenerate_allowed:
messages.error(self.request, _('Invoices may not be changed after they are created.'))
if inv.canceled:
messages.error(self.request, _('The invoice has already been canceled.'))
elif inv.sent_to_organizer:
messages.error(self.request, _('The invoice file has already been exported.'))
elif now().astimezone(self.request.event.timezone).date() - inv.date > timedelta(days=1):
messages.error(self.request, _('The invoice file is too old to be regenerated.'))
elif inv.shredded:
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:

View File

@@ -25,6 +25,7 @@ import json
from decimal import Decimal
from unittest import mock
import freezegun
import pytest
from django.core import mail as djmail
from django.core.files.base import ContentFile
@@ -1112,15 +1113,17 @@ def test_invoice_detail(token_client, organizer, event, item, invoice):
@pytest.mark.django_db
def test_invoice_regenerate(token_client, organizer, event, invoice):
organizer.settings.invoice_regenerate_allowed = True
with scopes_disabled():
InvoiceAddress.objects.filter(order=invoice.order).update(company="ACME Ltd")
resp = token_client.post('/api/v1/organizers/{}/events/{}/invoices/{}/regenerate/'.format(
organizer.slug, event.slug, invoice.number
))
assert resp.status_code == 204
invoice.refresh_from_db()
assert "ACME Ltd" in invoice.invoice_to
with freezegun.freeze_time("2017-12-10"):
resp = token_client.post('/api/v1/organizers/{}/events/{}/invoices/{}/regenerate/'.format(
organizer.slug, event.slug, invoice.number
))
assert resp.status_code == 204
invoice.refresh_from_db()
assert "ACME Ltd" in invoice.invoice_to
@pytest.mark.django_db