New data model for default tax rule and new options for cancellation fees (#4962)

* New data model for default tax rule

* Remove misleading empty label when field is not optional

* Allow to split cancellation fee

* Fix API and tests

* Update migration

* Update src/tests/api/test_taxrules.py

Co-authored-by: luelista <weller@rami.io>

* Update src/tests/api/test_taxrules.py

Co-authored-by: luelista <weller@rami.io>

* Review note

* Update src/pretix/base/models/tax.py

Co-authored-by: luelista <weller@rami.io>

* Flip API behaviour for default

* Fix failing tests

* Fix failing test

* Split migration

---------

Co-authored-by: luelista <weller@rami.io>
This commit is contained in:
Raphael Michel
2025-06-30 16:47:09 +02:00
committed by GitHub
parent 090358833d
commit 14ed6982a5
34 changed files with 615 additions and 104 deletions

View File

@@ -68,7 +68,7 @@ from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _, gettext_noop
from django.views.generic import FormView, ListView
from django.views.generic import DetailView, FormView, ListView
from django.views.generic.base import TemplateView, View
from django.views.generic.detail import SingleObjectMixin
from i18nfield.strings import LazyI18nString
@@ -1274,6 +1274,8 @@ class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView
@transaction.atomic
def form_valid(self, form):
if not self.request.event.tax_rules.exists():
form.instance.default = True
form.instance.event = self.request.event
form.instance.custom_rules = json.dumps([
f.cleaned_data for f in self.formset.ordered_forms if f not in self.formset.deleted_forms
@@ -1354,6 +1356,50 @@ class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView
return super().form_invalid(form)
class TaxDefault(EventSettingsViewMixin, EventPermissionRequiredMixin, DetailView):
model = TaxRule
permission = 'can_change_event_settings'
def get_object(self, queryset=None) -> TaxRule:
try:
return self.request.event.tax_rules.get(
id=self.kwargs['rule']
)
except TaxRule.DoesNotExist:
raise Http404(_("The requested tax rule does not exist."))
def get(self, request, *args, **kwargs):
return self.http_method_not_allowed(request, *args, **kwargs)
@transaction.atomic
def post(self, request, *args, **kwargs):
messages.success(self.request, _('Your changes have been saved.'))
obj = self.get_object()
if not obj.default:
for tr in self.request.event.tax_rules.filter(default=True):
tr.log_action(
'pretix.event.taxrule.changed', user=self.request.user, data={
'default': False,
}
)
tr.default = False
tr.save(update_fields=['default'])
obj.log_action(
'pretix.event.taxrule.changed', user=self.request.user, data={
'default': True,
}
)
obj.default = True
obj.save(update_fields=['default'])
return redirect(self.get_success_url())
def get_success_url(self) -> str:
return reverse('control:event.settings.tax', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, CompatDeleteView):
model = TaxRule
template_name = 'pretixcontrol/event/tax_delete.html'

View File

@@ -181,8 +181,9 @@ class EventWizard(SafeSessionWizardView):
initial['location'] = self.clone_from.location
initial['timezone'] = self.clone_from.settings.timezone
initial['locale'] = self.clone_from.settings.locale
if self.clone_from.settings.tax_rate_default:
initial['tax_rate'] = self.clone_from.settings.tax_rate_default.rate
tax_rule = self.clone_from.cached_default_tax_rule
if tax_rule:
initial['tax_rate'] = tax_rule.rate
if 'organizer' in self.request.GET:
if step == 'foundation':
try:
@@ -325,10 +326,17 @@ class EventWizard(SafeSessionWizardView):
event.set_defaults()
if basics_data['tax_rate'] is not None:
if not event.settings.tax_rate_default or event.settings.tax_rate_default.rate != basics_data['tax_rate']:
event.settings.tax_rate_default = event.tax_rules.create(
if self.clone_from:
default_tax_rule = self.clone_from.cached_default_tax_rule
elif copy_data and copy_data['copy_from_event']:
default_tax_rule = from_event.cached_default_tax_rule
else:
default_tax_rule = None
if not default_tax_rule or default_tax_rule.rate != basics_data['tax_rate']:
event.tax_rules.create(
name=LazyI18nString.from_gettext(gettext('VAT')),
rate=basics_data['tax_rate']
rate=basics_data['tax_rate'],
default=not default_tax_rule,
)
event.settings.set('timezone', basics_data['timezone'])