forked from CGM_Public/pretix_original
Allow to change fees in existing orders (#1472)
* Allow to change fees in existing orders * Add tests * Add special case for payment options * Fix PK reference in tests
This commit is contained in:
@@ -400,9 +400,27 @@ class OrderPositionChangeForm(forms.Form):
|
||||
self.fields['itemvar'].choices = choices
|
||||
change_decimal_field(self.fields['price'], instance.order.event.currency)
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data.get('operation') == 'price' and not self.cleaned_data.get('price', '') != '':
|
||||
raise ValidationError(_('You need to enter a price if you want to change the product price.'))
|
||||
|
||||
class OrderFeeChangeForm(forms.Form):
|
||||
value = forms.DecimalField(
|
||||
required=False,
|
||||
max_digits=10, decimal_places=2,
|
||||
localize=True,
|
||||
label=_('New price (gross)')
|
||||
)
|
||||
operation_cancel = forms.BooleanField(
|
||||
required=False,
|
||||
label=_('Remove this fee')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.pop('instance')
|
||||
initial = kwargs.get('initial', {})
|
||||
|
||||
initial['value'] = instance.value
|
||||
kwargs['initial'] = initial
|
||||
super().__init__(*args, **kwargs)
|
||||
change_decimal_field(self.fields['value'], instance.order.event.currency)
|
||||
|
||||
|
||||
class OrderContactForm(forms.ModelForm):
|
||||
|
||||
@@ -65,6 +65,15 @@ def _display_order_changed(event: Event, logentry: LogEntry):
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.feevalue':
|
||||
return text + ' ' + _('A fee was changed from {old_price} to {new_price}.').format(
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.cancelfee':
|
||||
return text + ' ' + _('A fee of {old_price} was removed.').format(
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.cancel':
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
|
||||
@@ -250,11 +250,67 @@
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<button type="button" class="btn btn-primary" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add product" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% for fee in fees %}
|
||||
<div class="panel panel-default items">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<strong>{{ fee.get_fee_type_display }}</strong>
|
||||
{% if fee.description %}
|
||||
– {{ fee.description }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-order-change">
|
||||
{% bootstrap_form_errors fee.form %}
|
||||
{% if fee.custom_error %}
|
||||
<div class="alert alert-danger">
|
||||
{{ fee.custom_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-sm-5 col-sm-offset-3">
|
||||
<strong>{% trans "Current value" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<strong>{% trans "Change to" %}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Price" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
{{ fee.value|money:request.event.currency }}
|
||||
{% if fee.tax_rate %}
|
||||
<br>
|
||||
<small>
|
||||
({{ fee.net_value|money:request.event.currency }}
|
||||
+ {{ fee.tax_rate }}%)
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-4 field-container">
|
||||
{% bootstrap_field fee.form.value addon_after=request.event.currency layout='inline' %}
|
||||
<small><strong>{% trans "including all taxes" %}</strong></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% bootstrap_field fee.form.operation_cancel layout='inline' %}
|
||||
{% if fee.fee_type == "payment" %}
|
||||
<em class="text-danger">
|
||||
{% trans "Manually modifying payment fees is discouraged since they might automatically be on subsequent order changes or when choosing a different payment method." %}
|
||||
</em>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="panel panel-default items">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
|
||||
@@ -71,9 +71,9 @@ from pretix.control.forms.filter import (
|
||||
)
|
||||
from pretix.control.forms.orders import (
|
||||
CancelForm, CommentForm, ConfirmPaymentForm, ExporterForm, ExtendForm,
|
||||
MarkPaidForm, OrderContactForm, OrderLocaleForm, OrderMailForm,
|
||||
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
|
||||
OrderRefundForm, OtherOperationsForm,
|
||||
MarkPaidForm, OrderContactForm, OrderFeeChangeForm, OrderLocaleForm,
|
||||
OrderMailForm, OrderPositionAddForm, OrderPositionAddFormset,
|
||||
OrderPositionChangeForm, OrderRefundForm, OtherOperationsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
@@ -1209,6 +1209,19 @@ class OrderChange(OrderView):
|
||||
data=self.request.POST if self.request.method == "POST" else None
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def fees(self):
|
||||
fees = list(self.order.fees.all())
|
||||
for f in fees:
|
||||
f.form = OrderFeeChangeForm(prefix='of-{}'.format(f.pk), instance=f,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
try:
|
||||
ia = self.order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
f.apply_tax = self.request.event.settings.tax_rate_default and self.request.event.settings.tax_rate_default.tax_applicable(invoice_address=ia)
|
||||
return fees
|
||||
|
||||
@cached_property
|
||||
def positions(self):
|
||||
positions = list(self.order.positions.all())
|
||||
@@ -1225,6 +1238,7 @@ class OrderChange(OrderView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['positions'] = self.positions
|
||||
ctx['fees'] = self.fees
|
||||
ctx['add_formset'] = self.add_formset
|
||||
ctx['other_form'] = self.other_form
|
||||
return ctx
|
||||
@@ -1266,6 +1280,24 @@ class OrderChange(OrderView):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _process_fees(self, ocm):
|
||||
for f in self.fees:
|
||||
if not f.form.is_valid():
|
||||
return False
|
||||
|
||||
try:
|
||||
if f.form.cleaned_data['operation_cancel']:
|
||||
ocm.cancel_fee(f)
|
||||
continue
|
||||
|
||||
if f.form.cleaned_data['value'] != f.value:
|
||||
ocm.change_fee(f, f.form.cleaned_data['value'])
|
||||
|
||||
except OrderError as e:
|
||||
f.custom_error = str(e)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _process_change(self, ocm):
|
||||
for p in self.positions:
|
||||
if not p.form.is_valid():
|
||||
@@ -1318,7 +1350,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_change(ocm) and self._process_other(ocm)
|
||||
form_valid = self._process_add(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.'))
|
||||
|
||||
Reference in New Issue
Block a user