mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Allow to add positions to an existing order
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 %}">
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
Reference in New Issue
Block a user