From 6f0d813cfcfd95930aad027c8e0b29c37a9beaac Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Tue, 29 Oct 2024 18:53:42 +0100 Subject: [PATCH] control: allow organizers to manually add fees to an existing order --- src/pretix/control/forms/orders.py | 43 +++++++++ .../templates/pretixcontrol/order/change.html | 94 ++++++++++++++++--- src/pretix/control/views/orders.py | 52 ++++++++-- 3 files changed, 166 insertions(+), 23 deletions(-) diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py index 70bab945e..e251d64ff 100644 --- a/src/pretix/control/forms/orders.py +++ b/src/pretix/control/forms/orders.py @@ -609,6 +609,49 @@ class OrderFeeChangeForm(forms.Form): change_decimal_field(self.fields['value'], instance.order.event.currency) +class OrderFeeAddForm(forms.Form): + fee_type = forms.ChoiceField(choices=OrderFee.FEE_TYPES) + value = forms.DecimalField( + max_digits=13, decimal_places=2, + localize=True, + label=_('Price'), + help_text=_("including all taxes"), + ) + tax_rule = forms.ModelChoiceField( + TaxRule.objects.none(), + required=False, + ) + description = forms.CharField(required=False) + + def __init__(self, *args, **kwargs): + order = kwargs.pop('order') + super().__init__(*args, **kwargs) + self.fields['tax_rule'].queryset = order.event.tax_rules.all() + change_decimal_field(self.fields['value'], order.event.currency) + + +class OrderFeeAddFormset(forms.BaseFormSet): + def __init__(self, *args, **kwargs): + self.order = kwargs.pop('order', None) + super().__init__(*args, **kwargs) + + def _construct_form(self, i, **kwargs): + kwargs['order'] = self.order + return super()._construct_form(i, **kwargs) + + @property + def empty_form(self): + form = self.form( + auto_id=self.auto_id, + prefix=self.add_prefix('__prefix__'), + empty_permitted=True, + use_required_attribute=False, + order=self.order, + ) + self.add_fields(form, None) + return form + + class OrderContactForm(forms.ModelForm): regenerate_secrets = forms.BooleanField(required=False, label=_('Invalidate secrets'), help_text=_('Regenerates the order and ticket secrets. You will ' diff --git a/src/pretix/control/templates/pretixcontrol/order/change.html b/src/pretix/control/templates/pretixcontrol/order/change.html index 645c9ab62..03d8b3398 100644 --- a/src/pretix/control/templates/pretixcontrol/order/change.html +++ b/src/pretix/control/templates/pretixcontrol/order/change.html @@ -296,11 +296,11 @@ {% endfor %} -
- {{ add_formset.management_form }} - {% bootstrap_formset_errors add_formset %} +
+ {{ add_position_formset.management_form }} + {% bootstrap_formset_errors add_position_formset %}
- {% for add_form in add_formset %} + {% for add_form in add_position_formset %}

@@ -351,25 +351,25 @@ {% trans "Add product" %}
- {{ add_formset.empty_form.id }} - {% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %} + {{ add_position_formset.empty_form.id }} + {% bootstrap_field add_position_formset.empty_form.DELETE form_group_class="" layout="inline" %}

- {% bootstrap_field add_formset.empty_form.itemvar layout="control" %} - {% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %} - {% if add_formset.empty_form.addon_to %} - {% bootstrap_field add_formset.empty_form.addon_to layout="control" %} + {% bootstrap_field add_position_formset.empty_form.itemvar layout="control" %} + {% bootstrap_field add_position_formset.empty_form.price addon_after=request.event.currency layout="control" %} + {% if add_position_formset.empty_form.addon_to %} + {% bootstrap_field add_position_formset.empty_form.addon_to layout="control" %} {% endif %} - {% if add_formset.empty_form.subevent %} - {% bootstrap_field add_formset.empty_form.subevent layout="control" %} + {% if add_position_formset.empty_form.subevent %} + {% bootstrap_field add_position_formset.empty_form.subevent layout="control" %} {% endif %} - {% if add_formset.empty_form.used_membership %} - {% bootstrap_field add_formset.empty_form.used_membership layout="control" %} + {% if add_position_formset.empty_form.used_membership %} + {% bootstrap_field add_position_formset.empty_form.used_membership layout="control" %} {% endif %} - {% bootstrap_field add_formset.empty_form.seat layout="control" %} + {% bootstrap_field add_position_formset.empty_form.seat layout="control" %}
@@ -438,6 +438,70 @@
{% endfor %} + +
+ {{ add_fee_formset.management_form }} + {% bootstrap_formset_errors add_fee_formset %} +
+ {% for add_form in add_fee_formset %} +
+
+

+ + {% trans "Add fee" %} +
+ {{ add_form.id }} + {% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %} +
+

+
+
+
+ {% bootstrap_field add_form.fee_type layout='control' %} + {% bootstrap_field add_form.value addon_after=request.event.currency layout='control' %} + {% bootstrap_field add_form.tax_rule layout='control' %} + {% bootstrap_field add_form.description layout='control' %} +
+
+
+ {% endfor %} +
+ +

+ +

+
+

diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index 274f69fa1..2a88c5ca8 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -125,7 +125,7 @@ from pretix.control.forms.orders import ( ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeChangeForm, OrderLocaleForm, OrderMailForm, OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm, OrderPositionMailForm, - OrderRefundForm, OtherOperationsForm, ReactivateOrderForm, + OrderRefundForm, OtherOperationsForm, ReactivateOrderForm, OrderFeeAddForm, OrderFeeAddFormset, ) from pretix.control.forms.rrule import RRuleForm from pretix.control.permissions import EventPermissionRequiredMixin @@ -1874,7 +1874,7 @@ class OrderChange(OrderView): data=self.request.POST if self.request.method == "POST" else None) @cached_property - def add_formset(self): + def add_position_formset(self): ff = formset_factory( OrderPositionAddForm, formset=OrderPositionAddFormset, can_order=False, can_delete=True, extra=0 @@ -1886,6 +1886,18 @@ class OrderChange(OrderView): data=self.request.POST if self.request.method == "POST" else None ) + @cached_property + def add_fee_formset(self): + ff = formset_factory( + OrderFeeAddForm, formset=OrderFeeAddFormset, + can_order=False, can_delete=True, extra=0 + ) + return ff( + prefix='add_fee', + order=self.order, + data=self.request.POST if self.request.method == "POST" else None + ) + @cached_property def items(self): return self.request.event.items.prefetch_related('variations', 'tax_rule').all() @@ -1914,7 +1926,8 @@ class OrderChange(OrderView): ctx = super().get_context_data(**kwargs) ctx['positions'] = self.positions ctx['fees'] = self.fees - ctx['add_formset'] = self.add_formset + ctx['add_position_formset'] = self.add_position_formset + ctx['add_fee_formset'] = self.add_fee_formset ctx['other_form'] = self.other_form ctx['use_revocation_list'] = self.request.event.ticket_secret_generator.use_revocation_list return ctx @@ -1929,12 +1942,12 @@ class OrderChange(OrderView): ) return True - def _process_add(self, ocm): - if not self.add_formset.is_valid(): + def _process_add_positions(self, ocm): + if not self.add_position_formset.is_valid(): return False else: - for f in self.add_formset.forms: - if f in self.add_formset.deleted_forms or not f.has_changed(): + for f in self.add_position_formset.forms: + if f in self.add_position_formset.deleted_forms or not f.has_changed(): continue if '-' in f.cleaned_data['itemvar']: @@ -1959,6 +1972,29 @@ class OrderChange(OrderView): return False return True + def _process_add_fees(self, ocm): + if not self.add_fee_formset.is_valid(): + return False + else: + for f in self.add_fee_formset.forms: + if f in self.add_fee_formset.deleted_forms or not f.has_changed(): + continue + + f = OrderFee( + fee_type=f.cleaned_data['fee_type'], + value=f.cleaned_data['value'], + order=ocm.order, + tax_rule=f.cleaned_data['tax_rule'], + description=f.cleaned_data['description'], + ) + f._calculate_tax() + try: + ocm.add_fee(f) + except OrderError as e: + f.custom_error = str(e) + return False + return True + def _process_fees(self, ocm): for f in self.fees: if not f.form.is_valid(): @@ -2061,7 +2097,7 @@ class OrderChange(OrderView): notify=notify, reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True ) - form_valid = self._process_add(ocm) and self._process_fees(ocm) and self._process_change(ocm) and self._process_other(ocm) + form_valid = self._process_add_positions(ocm) and self._process_add_fees(ocm) and self._process_fees(ocm) and self._process_change(ocm) and self._process_other(ocm) if not form_valid: messages.error(self.request, _('An error occurred. Please see the details below.'))