Allow to add positions to an existing order

This commit is contained in:
Raphael Michel
2017-06-19 15:22:57 +02:00
parent 3ada10c3f4
commit 123d2f6120
9 changed files with 262 additions and 23 deletions

View File

@@ -7,7 +7,7 @@ from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, Order
from pretix.base.models import Item, ItemAddOn, Order, OrderPosition
class ExtendForm(I18nModelForm):
@@ -55,6 +55,52 @@ class CommentForm(I18nModelForm):
}
class OrderPositionAddForm(forms.Form):
do = forms.BooleanField(
label=_('Add a new product to the order'),
required=False
)
itemvar = forms.ChoiceField(
label=_('Product')
)
addon_to = forms.ModelChoiceField(
OrderPosition.objects.none(),
required=False,
label=_('Add-on to'),
)
price = forms.DecimalField(
required=False,
max_digits=10, decimal_places=2,
label=_('Gross price'),
help_text=_("Keep empty for the product's default price")
)
def __init__(self, *args, **kwargs):
order = kwargs.pop('order')
super().__init__(*args, **kwargs)
choices = []
for i in order.event.items.prefetch_related('variations').all():
pname = str(i.name)
if not i.is_available():
pname += ' ({})'.format(_('inactive'))
variations = list(i.variations.all())
if variations:
for v in variations:
choices.append(('%d-%d' % (i.pk, v.pk),
'%s %s (%s %s)' % (pname, v.value, localize(v.price),
order.event.currency)))
else:
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(i.default_price),
order.event.currency)))
self.fields['itemvar'].choices = choices
if ItemAddOn.objects.filter(base_item__event=order.event).exists():
self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related(
'item', 'variation'
)
else:
del self.fields['addon_to']
class OrderPositionChangeForm(forms.Form):
itemvar = forms.ChoiceField()
price = forms.DecimalField(

View File

@@ -6,7 +6,7 @@ from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from i18nfield.strings import LazyI18nString
from pretix.base.models import Event, ItemVariation, LogEntry
from pretix.base.models import Event, ItemVariation, LogEntry, OrderPosition
from pretix.base.signals import logentry_display
OVERVIEW_BLACKLIST = [
@@ -51,6 +51,26 @@ def _display_order_changed(event: Event, logentry: LogEntry):
old_price=formats.localize(Decimal(data['old_price'])),
currency=event.currency
)
elif logentry.action_type == 'pretix.event.order.changed.add':
item = str(event.items.get(pk=data['item']))
if data['variation']:
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
if data['addon_to']:
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
return text + ' ' + _('Position #{posid} created: {item} ({price} {currency}) as an add-on to '
'position #{addon_to}.').format(
posid=data.get('positionid', '?'),
item=item, addon_to=addon_to.positionid,
price=formats.localize(Decimal(data['price'])),
currency=event.currency
)
else:
return text + ' ' + _('Position #{posid} created: {item} ({price} {currency}).').format(
posid=data.get('positionid', '?'),
item=item,
price=formats.localize(Decimal(data['price'])),
currency=event.currency
)
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")

View File

@@ -22,8 +22,7 @@
<p>
{% blocktrans trimmed %}
The user will receive a notification about the change but in the case of new required questions, the user
will not be forced to answer them. You cannot use this form to add something to the order, please create
a second order instead.
will not be forced to answer them.
{% endblocktrans %}
</p>
<p>
@@ -31,13 +30,15 @@
If an invoice is attached to the order, a cancellation will be created together with a new invoice.
{% endblocktrans %}
</p>
<div class="alert alert-warning">
<div class="alert alert-warning"><strong>
{% blocktrans trimmed %}
Please use this tool carefully. Changes you make here are not reversible. In most cases it is easier to
cancel the order completely and create a new one.
Please use this tool carefully. Changes you make here are not reversible. Also, if you change an order
manually, not all constraints (e.g. on required add-ons) will be checked. Therefore, you might construct
an order that would not be able to exist otherwise.
In most cases it is easier to cancel the order completely and create a new one.
{% endblocktrans %}
</div>
<form method="post" class="form-horizontal" href="">
</strong></div>
<form method="post" href="">
{% csrf_token %}
{% for position in positions %}
<div class="panel panel-default items">
@@ -107,6 +108,29 @@
</div>
</div>
{% endfor %}
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
Add product
</h3>
</div>
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_form_errors add_form %}
{% if add_form.custom_error %}
<div class="alert alert-danger">
{{ add_form.custom_error }}
</div>
{% endif %}
{% bootstrap_field add_form.do layout='horizontal' %}
{% bootstrap_field add_form.itemvar layout='horizontal' %}
{% bootstrap_field add_form.price layout='horizontal' %}
{% if add_form.addon_to %}
{% bootstrap_field add_form.addon_to layout='horizontal' %}
{% endif %}
</div>
</div>
</div>
<div class="form-group submit-group">
<a class="btn btn-default btn-lg"
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">

View File

@@ -33,7 +33,7 @@ from pretix.base.signals import (
from pretix.base.views.async import AsyncAction
from pretix.control.forms.orders import (
CommentForm, ExporterForm, ExtendForm, OrderContactForm, OrderLocaleForm,
OrderPositionChangeForm,
OrderPositionAddForm, OrderPositionChangeForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -166,7 +166,7 @@ class OrderDetail(OrderView):
cartpos = queryset.order_by(
'item', 'variation'
).select_related(
'item', 'variation'
'item', 'variation', 'addon_to'
).prefetch_related(
'item__questions', 'answers', 'answers__question', 'checkins'
).order_by('positionid')
@@ -181,6 +181,8 @@ class OrderDetail(OrderView):
p.cache_answers()
positions.append(p)
positions.sort(key=lambda p: p.sort_key)
return {
'positions': positions,
'raw': cartpos,
@@ -460,6 +462,11 @@ class OrderChange(OrderView):
return self._redirect_back()
return super().dispatch(request, *args, **kwargs)
@cached_property
def add_form(self):
return OrderPositionAddForm(prefix='add', order=self.order,
data=self.request.POST if self.request.method == "POST" else None)
@cached_property
def positions(self):
positions = list(self.order.positions.all())
@@ -471,15 +478,37 @@ class OrderChange(OrderView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['positions'] = self.positions
ctx['add_form'] = self.add_form
return ctx
def post(self, *args, **kwargs):
ocm = OrderChangeManager(self.order, self.request.user)
form_valid = True
def _process_add(self, ocm):
if not self.add_form.is_valid():
return False
else:
if self.add_form.cleaned_data['do']:
if '-' in self.add_form.cleaned_data['itemvar']:
itemid, varid = self.add_form.cleaned_data['itemvar'].split('-')
else:
itemid, varid = self.add_form.cleaned_data['itemvar'], None
item = Item.objects.get(pk=itemid, event=self.request.event)
if varid:
variation = ItemVariation.objects.get(pk=varid, item=item)
else:
variation = None
try:
ocm.add_position(item, variation,
self.add_form.cleaned_data['price'],
self.add_form.cleaned_data['addon_to'])
except OrderError as e:
self.add_form.custom_error = str(e)
return False
return True
def _process_change(self, ocm):
for p in self.positions:
if not p.form.is_valid():
form_valid = False
break
return False
try:
if p.form.cleaned_data['operation'] == 'product':
@@ -501,8 +530,12 @@ class OrderChange(OrderView):
except OrderError as e:
p.custom_error = str(e)
form_valid = False
break
return False
return True
def post(self, *args, **kwargs):
ocm = OrderChangeManager(self.order, self.request.user)
form_valid = self._process_add(ocm) and self._process_change(ocm)
if not form_valid:
messages.error(self.request, _('An error occured. Please see the details below.'))