Allow customers to change add-ons on existing orders (#2283)

This commit is contained in:
Raphael Michel
2021-11-19 14:59:54 +01:00
committed by GitHub
parent 34e4f7e0fc
commit 492288f437
19 changed files with 2511 additions and 687 deletions

View File

@@ -475,7 +475,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
'item__addons', 'item__addons__addon_category', 'addons', 'addons__variation',
).order_by('pk'):
formsetentry = {
'cartpos': cartpos,
'pos': cartpos,
'item': cartpos.item,
'variation': cartpos.variation,
'categories': []
@@ -582,13 +582,13 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
for i in category['items']:
if i.has_variations:
for v in i.available_variations:
val = int(self.request.POST.get(f'cp_{form["cartpos"].pk}_variation_{i.pk}_{v.pk}') or '0')
price = self.request.POST.get(f'cp_{form["cartpos"].pk}_variation_{i.pk}_{v.pk}_price') or '0'
val = int(self.request.POST.get(f'cp_{form["pos"].pk}_variation_{i.pk}_{v.pk}') or '0')
price = self.request.POST.get(f'cp_{form["pos"].pk}_variation_{i.pk}_{v.pk}_price') or '0'
if val:
selected[i, v] = val, price
else:
val = int(self.request.POST.get(f'cp_{form["cartpos"].pk}_item_{i.pk}') or '0')
price = self.request.POST.get(f'cp_{form["cartpos"].pk}_item_{i.pk}_price') or '0'
val = int(self.request.POST.get(f'cp_{form["pos"].pk}_item_{i.pk}') or '0')
price = self.request.POST.get(f'cp_{form["pos"].pk}_item_{i.pk}_price') or '0'
if val:
selected[i, None] = val, price
@@ -627,7 +627,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
validate_cart_addons.send(
sender=self.event,
addons={k: v[0] for k, v in selected.items()},
base_position=form["cartpos"],
base_position=form["pos"],
iao=category['iao']
)
except CartError as e:
@@ -648,7 +648,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
for (i, v), (c, price) in selected.items():
data.append({
'addon_to': f['cartpos'].pk,
'addon_to': f['pos'].pk,
'item': i.pk,
'variation': v.pk if v else None,
'count': c,

View File

@@ -57,7 +57,7 @@ class OrderPositionChangeForm(forms.Form):
pname = str(i)
variations = list(i.variations.all())
if variations:
if variations and event.settings.change_allow_user_variation:
current_quotas = (
instance.variation.quotas.filter(subevent=instance.subevent)
if instance.variation
@@ -126,6 +126,7 @@ class OrderPositionChangeForm(forms.Form):
else:
choices.append((str(i.pk), '%s' % pname))
self.fields['itemvar'].widget.attrs['disabled'] = True
self.fields['itemvar'].help_text = _('No other variations of this product exist.')
if event.settings.change_allow_user_variation:
self.fields['itemvar'].help_text = _('No other variations of this product exist.')
self.fields['itemvar'].choices = choices

View File

@@ -24,327 +24,19 @@
<i class="fa fa-angle-down collapse-indicator" aria-hidden="true"></i>
</h3>
</summary>
<div id="cp{{ form.cartpos.pk }}">
<div id="cp{{ form.pos.pk }}">
<div class="panel-body">
{% if form.cartpos.subevent %}
{% if form.pos.subevent %}
<p>
<span class="fa fa-calendar" aria-hidden="true"></span>
{{ form.cartpos.subevent.name }} &middot; {{ form.cartpos.subevent.get_date_range_display_as_html }}
{% if form.cartpos.event.settings.show_times %}
{{ form.pos.subevent.name }} &middot; {{ form.pos.subevent.get_date_range_display_as_html }}
{% if form.pos.event.settings.show_times %}
<span class="fa fa-clock-o" aria-hidden="true"></span>
{{ form.cartpos.subevent.date_from|date:"TIME_FORMAT" }}
{{ form.pos.subevent.date_from|date:"TIME_FORMAT" }}
{% endif %}
</p>
{% endif %}
{% for c in form.categories %}
<fieldset>
<legend>{{ c.category.name }}</legend>
{% if c.category.description %}
{{ c.category.description|rich_text }}
{% endif %}
{% if c.min_count == c.max_count %}
<p>
{% blocktrans trimmed count min_count=c.min_count %}
You need to choose exactly one option from this category.
{% plural %}
You need to choose {{ min_count }} options from this category.
{% endblocktrans %}
</p>
{% elif c.min_count == 0 and c.max_count >= c.items|length and not c.multi_allowed %}
{% elif c.min_count == 0 %}
<p>
{% blocktrans trimmed count max_count=c.max_count %}
You can choose {{ max_count }} option from this category.
{% plural %}
You can choose up to {{ max_count }} options from this category.
{% endblocktrans %}
</p>
{% else %}
<p>
{% blocktrans trimmed with min_count=c.min_count max_count=c.max_count %}
You can choose between {{ min_count }} and {{ max_count }} options from
this category.
{% endblocktrans %}
</p>
{% endif %}
{% for item in c.items %}
{% if item.has_variations %}
<article aria-labelledby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded %} details-open{% endif %}" id="item-{{ item.pk }}">
<div class="row-fluid product-row headline">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-legend">{{ item.name }}</h4>
{% if item.description %}
<div id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
<p>
<small>
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
{% endblocktrans %}
</small>
</p>
{% endif %}
</div>
</div>
<div class="col-md-2 col-xs-6 price">
<p>
{% if c.price_included %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% elif item.free_price %}
{% blocktrans trimmed with price=item.min_price|money:event.currency %}
from {{ price }}
{% endblocktrans %}
{% elif item.min_price != item.max_price %}
<span class="sr-only">
{% blocktrans trimmed with from_price=item.min_price|money:event.currency to_price=item.max_price|money:event.currency %}
from {{ from_price }} to {{ to_price }}
{% endblocktrans %}
</span>
<span aria-hidden="true">{{ item.min_price|money:event.currency }} {{ item.max_price|money:event.currency }}</span>
{% elif not item.min_price and not item.max_price %}
{% else %}
{{ item.min_price|money:event.currency }}
{% endif %}
</p>
</div>
<div class="col-md-2 col-xs-6 availability-box">
{% if not event.settings.show_variations_expanded %}
<button type="button" data-toggle="variations" class="btn btn-default btn-block js-only"
data-label-alt="{% trans "Hide variants" %}"
aria-expanded="false"
aria-label="{% blocktrans trimmed with item=item.name count=item.available_variations|length %}Show {{count}} variants of {{item}}{% endblocktrans %}">
{% trans "Show variants" %}
</button>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
<div class="variations {% if not event.settings.show_variations_expanded %}variations-collapsed{% endif %}">
{% for var in item.available_variations %}
<article aria-labelledby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row-fluid product-row variation">
<div class="col-md-8 col-xs-12">
<h5 id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-{{ var.pk }}-legend">{{ var }}</h5>
{% if var.description %}
<div id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-{{ var.pk }}-description" class="variation-description">
{{ var.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=var.cached_availability %}
{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{% if not c.price_included %}
{% if var.original_price %}
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ var.original_price.net|money:event.currency }}
{% else %}
{{ var.original_price.gross|money:event.currency }}
{% endif %}
</del>
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price"
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_{{ var.id }}_price"
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
step="any"
value="{% if event.settings.display_net_prices %}{{ var.initial_price.net|money_numberfield:event.currency }}{% else %}{{ var.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
>
</div>
{% elif not var.display_price.gross %}
{% elif event.settings.display_net_prices %}
{{ var.display_price.net|money:event.currency }}
{% else %}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price or var.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=var.display_price.rate|floatformat:-2 name=var.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% elif var.display_price.rate and var.display_price.gross %}
<small>{% blocktrans trimmed with rate=var.display_price.rate|floatformat:-2 name=var.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% endif %}
{% else %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% endif %}
</div>
{% if var.cached_availability.0 == 100 or var.initial %}
<div class="col-md-2 col-xs-6 availability-box available">
{% if c.max_count == 1 or not c.multi_allowed %}
<label class="item-checkbox-label">
<input type="checkbox" value="1"
{% if var.initial %}checked="checked"{% endif %}
id="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_{{ var.id }}"
data-exclusive-prefix="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_"
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}">
</label>
{% else %}
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if var.initial %}value="{{ var.initial }}"{% endif %}
max="{{ c.max_count }}"
id="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.cartpos.pk }}_variation_{{ item.id }}_{{ var.id }}"
aria-label="{% blocktrans with item=item.name var=var %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}">
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=var.display_price.gross avail=var.cached_availability.0 event=event item=item var=var %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endfor %}
</div>
</article>
{% else %}
<article aria-labelledby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-description"{% endif %} class="row-fluid product-row simple">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-legend">{{ item.name }}</h4>
{% if item.description %}
<div id="cp-{{ form.cartpos.pk }}-item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
<p>
<small>
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
{% endblocktrans %}
</small>
</p>
{% endif %}
</div>
</div>
<div class="col-md-2 col-xs-6 price">
<p>
{% if not c.price_included %}
{% if item.original_price %}
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ item.original_price.net|money:event.currency }}
{% else %}
{{ item.original_price.gross|money:event.currency }}
{% endif %}
</del>
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price" placeholder="0"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.cartpos.pk }}_item_{{ item.id }}_price"
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
value="{% if event.settings.display_net_prices %}{{ item.initial_price.net|money_numberfield:event.currency }}{% else %}{{ item.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
step="any">
</div>
{% elif not item.display_price.gross %}
{% elif event.settings.display_net_prices %}
{{ item.display_price.net|money:event.currency }}
{% else %}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=item.display_price.rate|floatformat:-2 name=item.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% elif item.display_price.rate and item.display_price.gross %}
<small>{% blocktrans trimmed with rate=item.display_price.rate|floatformat:-2 name=item.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% endif %}
{% else %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% endif %}
</p>
</div>
{% if item.cached_availability.0 == 100 or item.initial %}
<div class="col-md-2 col-xs-6 availability-box available">
{% if c.max_count == 1 or not c.multi_allowed %}
<label class="item-checkbox-label">
<input type="checkbox" value="1"
{% if item.initial %}checked="checked"{% endif %}
name="cp_{{ form.cartpos.pk }}_item_{{ item.id }}"
id="cp_{{ form.cartpos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
{% if item.description %} aria-describedby="cp-{{ form.cartpos.pk }}-item-{{ item.id }}-description"{% endif %}>
</label>
{% else %}
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ c.max_count }}"
{% if item.initial %}value="{{ item.initial }}"{% endif %}
name="cp_{{ form.cartpos.pk }}_item_{{ item.id }}"
id="cp_{{ form.cartpos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}"
{% if item.description %} aria-describedby="cp-{{ form.cartpos.pk }}-item-{{ item.id }}-description"{% endif %}>
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=item.display_price.gross avail=item.cached_availability.0 event=event item=item var=0 %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endif %}
{% endfor %}
</fieldset>
{% empty %}
<em>
{% trans "There are no add-ons available for this product." %}
</em>
{% endfor %}
{% include "pretixpresale/event/fragment_addon_choice.html" with form=form %}
</div>
</div>
</details>

View File

@@ -0,0 +1,316 @@
{% load i18n %}
{% load l10n %}
{% load eventurl %}
{% load money %}
{% load thumb %}
{% load eventsignal %}
{% load rich_text %}
{% for c in form.categories %}
<fieldset>
<legend>{{ c.category.name }}</legend>
{% if c.category.description %}
{{ c.category.description|rich_text }}
{% endif %}
{% if c.min_count == c.max_count %}
<p>
{% blocktrans trimmed count min_count=c.min_count %}
You need to choose exactly one option from this category.
{% plural %}
You need to choose {{ min_count }} options from this category.
{% endblocktrans %}
</p>
{% elif c.min_count == 0 and c.max_count >= c.items|length and not c.multi_allowed %}
{% elif c.min_count == 0 %}
<p>
{% blocktrans trimmed count max_count=c.max_count %}
You can choose {{ max_count }} option from this category.
{% plural %}
You can choose up to {{ max_count }} options from this category.
{% endblocktrans %}
</p>
{% else %}
<p>
{% blocktrans trimmed with min_count=c.min_count max_count=c.max_count %}
You can choose between {{ min_count }} and {{ max_count }} options from
this category.
{% endblocktrans %}
</p>
{% endif %}
{% for item in c.items %}
{% if item.has_variations %}
<article aria-labelledby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded %} details-open{% endif %}" id="item-{{ item.pk }}">
<div class="row-fluid product-row headline">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-legend">{{ item.name }}</h4>
{% if item.description %}
<div id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
<p>
<small>
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
{% endblocktrans %}
</small>
</p>
{% endif %}
</div>
</div>
<div class="col-md-2 col-xs-6 price">
<p>
{% if c.price_included %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% elif item.free_price %}
{% blocktrans trimmed with price=item.min_price|money:event.currency %}
from {{ price }}
{% endblocktrans %}
{% elif item.min_price != item.max_price %}
<span class="sr-only">
{% blocktrans trimmed with from_price=item.min_price|money:event.currency to_price=item.max_price|money:event.currency %}
from {{ from_price }} to {{ to_price }}
{% endblocktrans %}
</span>
<span aria-hidden="true">{{ item.min_price|money:event.currency }} {{ item.max_price|money:event.currency }}</span>
{% elif not item.min_price and not item.max_price %}
{% else %}
{{ item.min_price|money:event.currency }}
{% endif %}
</p>
</div>
<div class="col-md-2 col-xs-6 availability-box">
{% if not event.settings.show_variations_expanded %}
<button type="button" data-toggle="variations" class="btn btn-default btn-block js-only"
data-label-alt="{% trans "Hide variants" %}"
aria-expanded="false"
aria-label="{% blocktrans trimmed with item=item.name count=item.available_variations|length %}Show {{count}} variants of {{item}}{% endblocktrans %}">
{% trans "Show variants" %}
</button>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
<div class="variations {% if not event.settings.show_variations_expanded %}variations-collapsed{% endif %}">
{% for var in item.available_variations %}
<article aria-labelledby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row-fluid product-row variation">
<div class="col-md-8 col-xs-12">
<h5 id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-{{ var.pk }}-legend">{{ var }}</h5>
{% if var.description %}
<div id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-{{ var.pk }}-description" class="variation-description">
{{ var.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=var.cached_availability %}
{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{% if not c.price_included %}
{% if var.original_price %}
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ var.original_price.net|money:event.currency }}
{% else %}
{{ var.original_price.gross|money:event.currency }}
{% endif %}
</del>
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price"
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}_price"
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
step="any"
value="{% if event.settings.display_net_prices %}{{ var.initial_price.net|money_numberfield:event.currency }}{% else %}{{ var.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
>
</div>
{% elif not var.display_price.gross %}
{% elif event.settings.display_net_prices %}
{{ var.display_price.net|money:event.currency }}
{% else %}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price or var.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=var.display_price.rate|floatformat:-2 name=var.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% elif var.display_price.rate and var.display_price.gross %}
<small>{% blocktrans trimmed with rate=var.display_price.rate|floatformat:-2 name=var.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% endif %}
{% else %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% endif %}
</div>
{% if var.cached_availability.0 == 100 or var.initial %}
<div class="col-md-2 col-xs-6 availability-box available">
{% if c.max_count == 1 or not c.multi_allowed %}
<label class="item-checkbox-label">
<input type="checkbox" value="1"
{% if var.initial %}checked="checked"{% endif %}
id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
data-exclusive-prefix="cp_{{ form.pos.pk }}_variation_{{ item.id }}_"
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}">
</label>
{% else %}
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if var.initial %}value="{{ var.initial }}"{% endif %}
max="{{ c.max_count }}"
id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
aria-label="{% blocktrans with item=item.name var=var %}Quantity of {{ item }}, {{ var }} to order{% endblocktrans %}">
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=var.display_price.gross avail=var.cached_availability.0 event=event item=item var=var %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endfor %}
</div>
</article>
{% else %}
<article aria-labelledby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.pk }}-description"{% endif %} class="row-fluid product-row simple">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-legend">{{ item.name }}</h4>
{% if item.description %}
<div id="cp-{{ form.pos.pk }}-item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
<p>
<small>
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
{% endblocktrans %}
</small>
</p>
{% endif %}
</div>
</div>
<div class="col-md-2 col-xs-6 price">
<p>
{% if not c.price_included %}
{% if item.original_price %}
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ item.original_price.net|money:event.currency }}
{% else %}
{{ item.original_price.gross|money:event.currency }}
{% endif %}
</del>
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price" placeholder="0"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="cp_{{ form.pos.pk }}_item_{{ item.id }}_price"
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
value="{% if event.settings.display_net_prices %}{{ item.initial_price.net|money_numberfield:event.currency }}{% else %}{{ item.initial_price.gross|money_numberfield:event.currency }}{% endif %}"
step="any">
</div>
{% elif not item.display_price.gross %}
{% elif event.settings.display_net_prices %}
{{ item.display_price.net|money:event.currency }}
{% else %}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=item.display_price.rate|floatformat:-2 name=item.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% elif item.display_price.rate and item.display_price.gross %}
<small>{% blocktrans trimmed with rate=item.display_price.rate|floatformat:-2 name=item.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% endif %}
{% else %}
<span class="sr-only">{% trans "free" context "price" %}</span>
{% endif %}
</p>
</div>
{% if item.cached_availability.0 == 100 or item.initial %}
<div class="col-md-2 col-xs-6 availability-box available">
{% if c.max_count == 1 or not c.multi_allowed %}
<label class="item-checkbox-label">
<input type="checkbox" value="1"
{% if item.initial %}checked="checked"{% endif %}
name="cp_{{ form.pos.pk }}_item_{{ item.id }}"
id="cp_{{ form.pos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.id }}-description"{% endif %}>
</label>
{% else %}
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ c.max_count }}"
{% if item.initial %}value="{{ item.initial }}"{% endif %}
name="cp_{{ form.pos.pk }}_item_{{ item.id }}"
id="cp_{{ form.pos.pk }}_item_{{ item.id }}"
aria-label="{% blocktrans with item=item.name %}Quantity of {{ item }} to order{% endblocktrans %}"
{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.id }}-description"{% endif %}>
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=item.display_price.gross avail=item.cached_availability.0 event=event item=item var=0 %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endif %}
{% endfor %}
</fieldset>
{% empty %}
<em>
{% trans "There are no add-ons available for this product." %}
</em>
{% endfor %}

View File

@@ -2,7 +2,9 @@
{% load i18n %}
{% load bootstrap3 %}
{% load rich_text %}
{% block title %}{% trans "Modify order" %}{% endblock %}
{% block title %}{% blocktrans trimmed with code=order.code %}
Change order: {{ code }}
{% endblocktrans %}{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=order.code %}
@@ -11,7 +13,7 @@
</h2>
<form method="post" href="">
{% csrf_token %}
{% for position, positions in formgroups.items %}
{% for position, addon_positions in formgroups.items %}
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
@@ -21,43 +23,48 @@
{% endif %}
</h3>
</div>
<div class="panel-body">
<div class="panel-body addons">
<div class="form-order-change form-horizontal">
{% if position.subevent %}
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Date" context "subevent" %}
</label>
<div class="col-md-9 form-control-text">
<ul class="addon-list">
{{ pos.subevent.name }} &middot; {{ pos.subevent.get_date_range_display_as_html }}
{% if pos.event.settings.show_times %}
<span class="fa fa-clock-o" aria-hidden="true"></span>
{{ pos.subevent.date_from|date:"TIME_FORMAT" }}
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% for p in positions %}
{% if p.pk != position.pk %}
{# Add-Ons #}
<legend>+ {{ p.item.name }}{% if p.variation %} {{ p.variation.value }}{% endif %}</legend>
{% endif %}
{% if p.attendee_name %}
<div class="form-order-change-main">
{% if position.subevent %}
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Attendee name" %}
{% trans "Date" context "subevent" %}
</label>
<div class="col-md-9 form-control-text">
{{ p.attendee_name }}
<ul class="addon-list">
{{ pos.subevent.name }} &middot; {{ pos.subevent.get_date_range_display_as_html }}
{% if pos.event.settings.show_times %}
<span class="fa fa-clock-o" aria-hidden="true"></span>
{{ pos.subevent.date_from|date:"TIME_FORMAT" }}
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% bootstrap_form p.form layout="checkout" %}
{% endfor %}
{% for p in addon_positions %}
{% if p.pk != position.pk %}
{# Add-Ons #}
<legend>+ {{ p.item.name }}{% if p.variation %} {{ p.variation.value }}{% endif %}</legend>
{% endif %}
{% if p.attendee_name %}
<div class="form-group">
<label class="col-md-3 control-label">
{% trans "Attendee name" %}
</label>
<div class="col-md-9 form-control-text">
{{ p.attendee_name }}
</div>
</div>
{% endif %}
{% bootstrap_form p.form layout="checkout" %}
{% endfor %}
</div>
</div>
{% if position.addon_form %}
{% include "pretixpresale/event/fragment_addon_choice.html" with form=position.addon_form %}
{% endif %}
</div>
</div>
{% endfor %}
@@ -71,7 +78,7 @@
</div>
<div class="col-md-4 col-md-offset-4">
<button class="btn btn-block btn-primary btn-lg" type="submit">
{% trans "Save changes" %}
{% trans "Continue" %}
</button>
</div>
<div class="clearfix"></div>

View File

@@ -0,0 +1,219 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load classname %}
{% load eventurl %}
{% load money %}
{% block title %}{% blocktrans trimmed with code=order.code %}
Change order: {{ code }}
{% endblocktrans %}{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=order.code %}
Change order: {{ code }}
{% endblocktrans %}
</h2>
<form method="post" class="form-horizontal" href="">
{% csrf_token %}
<p>{% trans "Please confirm the following changes to your order." %}</p>
<div class="row-fluid">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Change summary" %}
</h3>
</div>
<table class="panel-body table table-hover">
{% for op in operations %}
{% if op|classname == "ItemOperation" %}
<tr>
<td>
{% if op.position.variation or op.variation %}
{% blocktrans trimmed with positionid=op.position.positionid old_item=op.position.item.name old_variation=op.position.variation new_item=op.item.name new_variation=op.variation %}
Change position #{{ positionid }} from "{{ old_item }} {{ old_variation }}
" to "{{ new_item }} {{ new_variation }}"
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with positionid=op.position.positionid old_item=op.position.item.name new_item=op.item.name %}
Change position #{{ positionid }} from "{{ old_item }}" to "{{ new_item }}"
{% endblocktrans %}
{% endif %}
{% if op.position.addon_to %}
<span class="text-muted">
<br>
<small>{% blocktrans with positionid=op.position.addon_to.positionid %}
Add-on product to position #{{ positionid }}{% endblocktrans %}</small>
</span>
{% endif %}
</td>
<td class="text-right flip">
</td>
</tr>
{% elif op|classname == "SubeventOperation" %}
<tr>
<td>
{% blocktrans trimmed with positionid=op.position.positionid old=op.position.subevent new=op.subevent %}
Change date of position #{{ positionid }} from "{{ old }}" to "{{ new }}"
{% endblocktrans %}
</td>
<td class="text-right flip">
</td>
</tr>
{% elif op|classname == "PriceOperation" %}
<tr>
<td>
{% blocktrans trimmed with positionid=op.position.positionid old=op.position.price new=op.price %}
Change price of position #{{ positionid }} from {{ old }} to {{ new }}
{% endblocktrans %}
{% if op.position.addon_to %}
<span class="text-muted">
<br>
<small>{% blocktrans with positionid=op.position.addon_to.positionid %}
Add-on product to position #{{ positionid }}{% endblocktrans %}</small>
</span>
{% endif %}
</td>
<td class="text-right flip">
{{ op.price_diff|money:request.event.currency }}
</td>
</tr>
{% elif op|classname == "AddOperation" %}
<tr>
<td>
{% if op.variation %}
{% blocktrans trimmed with positionid=op.position.positionid item=op.item.name variation=op.variation.value %}
Add position #{{ positionid }} ({{ item }} {{ variation }})
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with positionid=op.position.positionid item=op.item.name %}
Add position #{{ positionid }} ({{ item }})
{% endblocktrans %}
{% endif %}
{% if op.addon_to %}
<span class="text-muted">
<br>
<small>{% blocktrans with positionid=op.addon_to.positionid %}Add-on product
to position #{{ positionid }}{% endblocktrans %}</small>
</span>
{% endif %}
</td>
<td class="text-right flip">
{{ op.price.gross|money:request.event.currency }}
</td>
</tr>
{% elif op|classname == "CancelOperation" %}
<tr>
<td>
{% if op.position.variation %}
{% blocktrans trimmed with positionid=op.position.positionid item=op.position.item.name variation=op.position.variation.value %}
Remove position #{{ positionid }} ({{ item }} {{ variation }})
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with positionid=op.position.positionid item=op.position.item.name %}
Remove position #{{ positionid }} ({{ item }})
{% endblocktrans %}
{% endif %}
{% if op.position.addon_to %}
<span class="text-muted">
<br>
<small>{% blocktrans with positionid=op.position.addon_to.positionid %}
Add-on product to position #{{ positionid }}{% endblocktrans %}</small>
</span>
{% endif %}
</td>
<td class="text-right flip">
{{ op.price_diff|money:request.event.currency }}
</td>
</tr>
{% endif %}
{% endfor %}
<tfoot>
<tr>
<td><strong>{% trans "Total price change" %}</strong></td>
<td class="text-right flip">
<strong>
{{ totaldiff|money:request.event.currency }}
</strong>
</td>
</tr>
{% if totaldiff %}
<tr>
<td><strong>{% trans "New order total" %}</strong></td>
<td class="text-right flip">
{{ totaldiff|add:order.total|money:request.event.currency }}
</td>
</tr>
<tr>
<td><strong>{% trans "You already paid" %}</strong></td>
<td class="text-right flip">
{{ order.payment_refund_sum|money:request.event.currency }}
</td>
</tr>
<tr>
<td>
{% if new_pending_sum > 0 %}
<strong>{% trans "You will need to pay" %}</strong>
<br>
<span class="text-muted">
{% trans "Your entire order will be considered unpaid until you paid this difference." %}
</span>
{% else %}
<strong>{% trans "You will be refunded" %}</strong>
<br>
<span class="text-muted">
{% if request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "manually" %}
{% trans "The organizer will get in touch with you to clarify the details of your refund." %}
{% elif request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "force" %}
{% trans "The refund will be issued in form of a gift card that you can use for further purchases." %}
{% else %}
{% if can_auto_refund %}
{% blocktrans trimmed %}
The refund amount will automatically be sent back to your original payment method. Depending
on the payment method, please allow for up to two weeks before this appears on your
statement.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
With the payment method you used, the refund amount <strong>can not be sent back to you
automatically</strong>. Instead, the event organizer will need to initiate the transfer
manually. Please be patient as this might take a bit longer.
{% endblocktrans %}
{% endif %}
{% endif %}
</span>
{% endif %}
</td>
<td class="text-right flip">
<strong>
{{ new_pending_sum|money:request.event.currency }}
</strong>
</td>
</tr>
{% endif %}
</tfoot>
</table>
</div>
</div>
{% for k, l in request.POST.lists %}
{% for v in l %}
<input type="hidden" name="{{ k }}" value="{{ v }}">
{% endfor %}
{% endfor %}
<div class="row checkout-button-row">
<div class="col-md-4">
<a class="btn btn-block btn-default btn-lg"
href="{% eventurl request.event "presale:event.order.change" secret=order.secret order=order.code %}">
{% trans "Back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4">
<button class="btn btn-block btn-primary btn-lg" type="submit" name="confirm" value="true">
{% trans "Perform changes" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}

View File

@@ -38,19 +38,20 @@ import json
import mimetypes
import os
import re
from collections import OrderedDict
from collections import OrderedDict, defaultdict
from decimal import Decimal
from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import transaction
from django.db.models import Exists, OuterRef, Q, Sum
from django.http import (
FileResponse, Http404, HttpResponseRedirect, JsonResponse,
)
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.timezone import now
@@ -65,6 +66,7 @@ from pretix.base.models.orders import (
CachedCombinedTicket, InvoiceAddress, OrderFee, OrderPayment, OrderRefund,
QuestionAnswer,
)
from pretix.base.models.tax import TaxedPrice
from pretix.base.payment import PaymentException
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
@@ -72,7 +74,8 @@ from pretix.base.services.invoices import (
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderChangeManager, OrderError, cancel_order, change_payment_provider,
OrderChangeManager, OrderError, _try_auto_refund, cancel_order,
change_payment_provider, error_messages,
)
from pretix.base.services.pricing import get_price
from pretix.base.services.tickets import generate, invalidate_cache
@@ -90,6 +93,7 @@ from pretix.presale.signals import question_form_fields_overrides
from pretix.presale.views import (
CartMixin, EventViewMixin, iframe_entry_view_wrapper,
)
from pretix.presale.views.event import get_grouped_items
from pretix.presale.views.robots import NoSearchIndexViewMixin
@@ -1165,6 +1169,8 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView):
def formdict(self):
storage = OrderedDict()
for pos in self.positions:
if self.request.event.settings.change_allow_user_addons and pos.addon_to_id:
continue
if pos.addon_to_id:
if pos.addon_to not in storage:
storage[pos.addon_to] = []
@@ -1186,10 +1192,11 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView):
def positions(self):
positions = list(
self.order.positions.select_related('item', 'item__tax_rule').prefetch_related(
'item__variations',
'item__variations', 'addons',
)
)
quota_cache = {}
item_cache = {}
try:
ia = self.order.invoice_address
except InvoiceAddress.DoesNotExist:
@@ -1198,6 +1205,87 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView):
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p,
invoice_address=ia, event=self.request.event, quota_cache=quota_cache,
data=self.request.POST if self.request.method == "POST" else None)
if p.addon_to_id is None and self.request.event.settings.change_allow_user_addons:
p.addon_form = {
'pos': p,
'categories': []
}
current_addon_products = defaultdict(list)
for a in p.addons.all():
if a.canceled:
continue
if not a.is_bundled:
current_addon_products[a.item_id, a.variation_id].append(a)
for iao in p.item.addons.all():
ckey = '{}-{}'.format(p.subevent.pk if p.subevent else 0, iao.addon_category.pk)
if ckey not in item_cache:
# Get all items to possibly show
items, _btn = get_grouped_items(
self.request.event,
subevent=p.subevent,
voucher=None,
channel=self.order.sales_channel,
base_qs=iao.addon_category.items,
allow_addons=True,
quota_cache=quota_cache,
memberships=(
self.request.customer.usable_memberships(
for_event=p.subevent or self.request.event,
testmode=self.order.testmode
)
if self.order.customer else None
),
)
item_cache[ckey] = items
else:
items = item_cache[ckey]
for i in items:
i.allow_waitinglist = False
if i.has_variations:
for v in i.available_variations:
v.initial = len(current_addon_products[i.pk, v.pk])
if v.initial and i.free_price:
a = current_addon_products[i.pk, v.pk][0]
v.initial_price = TaxedPrice(
net=a.price - a.tax_value,
gross=a.price,
tax=a.tax_value,
name=a.item.tax_rule.name if a.item.tax_rule else "",
rate=a.tax_rate,
)
else:
v.initial_price = v.display_price
i.expand = any(v.initial for v in i.available_variations)
else:
i.initial = len(current_addon_products[i.pk, None])
if i.initial and i.free_price:
a = current_addon_products[i.pk, None][0]
i.initial_price = TaxedPrice(
net=a.price - a.tax_value,
gross=a.price,
tax=a.tax_value,
name=a.item.tax_rule.name if a.item.tax_rule else "",
rate=a.tax_rate,
)
else:
i.initial_price = i.display_price
if items:
p.addon_form['categories'].append({
'category': iao.addon_category,
'price_included': iao.price_included,
'multi_allowed': iao.multi_allowed,
'min_count': iao.min_count,
'max_count': iao.max_count,
'iao': iao,
'items': [i for i in items if not i.require_voucher]
})
return positions
def _process_change(self, ocm):
@@ -1241,7 +1329,56 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView):
return False
return True
def post(self, *args, **kwargs):
def _clean_category(self, form, category):
selected = {}
for i in category['items']:
if i.has_variations:
for v in i.available_variations:
val = int(self.request.POST.get(f'cp_{form["pos"].pk}_variation_{i.pk}_{v.pk}') or '0')
price = self.request.POST.get(f'cp_{form["pos"].pk}_variation_{i.pk}_{v.pk}_price') or '0'
if val:
selected[i, v] = val, price
else:
val = int(self.request.POST.get(f'cp_{form["pos"].pk}_item_{i.pk}') or '0')
price = self.request.POST.get(f'cp_{form["pos"].pk}_item_{i.pk}_price') or '0'
if val:
selected[i, None] = val, price
if sum(a[0] for a in selected.values()) > category['max_count']:
# TODO: Proper pluralization
raise ValidationError(
_(error_messages['addon_max_count']),
'addon_max_count',
{
'base': str(form['pos'].item.name),
'max': category['max_count'],
'cat': str(category['category'].name),
}
)
elif sum(a[0] for a in selected.values()) < category['min_count']:
# TODO: Proper pluralization
raise ValidationError(
_(error_messages['addon_min_count']),
'addon_min_count',
{
'base': str(form['pos'].item.name),
'min': category['min_count'],
'cat': str(category['category'].name),
}
)
elif any(sum(v[0] for k, v in selected.items() if k[0] == i) > 1 for i in category['items']) and not category['multi_allowed']:
raise ValidationError(
_(error_messages['addon_no_multi']),
'addon_no_multi',
{
'base': str(form['pos'].item.name),
'cat': str(category['category'].name),
}
)
return selected
def post(self, request, *args, **kwargs):
was_paid = self.order.status == Order.STATUS_PAID
ocm = OrderChangeManager(
self.order,
@@ -1249,28 +1386,106 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView):
notify=True,
reissue_invoice=True,
)
form_valid = self._process_change(ocm)
addons_data = []
for p in self.positions:
if p.addon_to_id or not hasattr(p, 'addon_form'):
continue
for c in p.addon_form['categories']:
try:
selected = self._clean_category(p.addon_form, c)
except ValidationError as e:
messages.error(request, e.message % e.params if e.params else e.message)
return self.get(request, *args, **kwargs)
for (i, v), (c, price) in selected.items():
addons_data.append({
'addon_to': p.pk,
'item': i.pk,
'variation': v.pk if v else None,
'count': c,
'price': price,
})
try:
ocm.set_addons(addons_data)
except OrderError as e:
messages.error(self.request, str(e))
form_valid = False
else:
form_valid = self._process_change(ocm)
if not form_valid:
messages.error(self.request, _('An error occurred. Please see the details below.'))
else:
try:
ocm.commit(check_quotas=True)
self._validate_total_diff(ocm)
except OrderError as e:
messages.error(self.request, str(e))
else:
if self.order.status != Order.STATUS_PAID and was_paid:
messages.success(self.request, _('The order has been changed. You can now proceed by paying the open amount of {amount}.').format(
amount=money_filter(self.order.pending_sum, self.request.event.currency)
))
return redirect(eventreverse(self.request.event, 'presale:event.order.pay.change', kwargs={
'order': self.order.code,
'secret': self.order.secret
}))
if "confirm" in request.POST:
try:
ocm.commit(check_quotas=True)
except OrderError as e:
messages.error(self.request, str(e))
else:
messages.success(self.request, _('The order has been changed.'))
if self.order.pending_sum < Decimal('0.00'):
auto_refund = (
not self.request.event.settings.cancel_allow_user_paid_require_approval
and self.request.event.settings.cancel_allow_user_paid_refund_as_giftcard != "manually"
)
refund_as_giftcard = self.request.event.settings.cancel_allow_user_paid_refund_as_giftcard == 'force'
if auto_refund:
try:
_try_auto_refund(self.order, refund_as_giftcard=refund_as_giftcard)
except OrderError as e:
messages.error(self.request, str(e))
if self.order.status != Order.STATUS_PAID and was_paid:
messages.success(self.request, _('The order has been changed. You can now proceed by paying the open amount of {amount}.').format(
amount=money_filter(self.order.pending_sum, self.request.event.currency)
))
return redirect(eventreverse(self.request.event, 'presale:event.order.pay.change', kwargs={
'order': self.order.code,
'secret': self.order.secret
}))
else:
messages.success(self.request, _('The order has been changed.'))
return redirect(self.get_order_url())
elif not ocm._operations:
messages.info(self.request, _('You did not make any changes.'))
return redirect(self.get_order_url())
else:
new_pending_sum = self.order.pending_sum + ocm._totaldiff
can_auto_refund = False
if new_pending_sum < Decimal('0.00'):
proposals = self.order.propose_auto_refunds(Decimal('-1.00') * new_pending_sum)
can_auto_refund = sum(proposals.values()) == Decimal('-1.00') * new_pending_sum
return self.get(*args, **kwargs)
return render(request, 'pretixpresale/event/order_change_confirm.html', {
'operations': ocm._operations,
'totaldiff': ocm._totaldiff,
'order': self.order,
'payment_refund_sum': self.order.payment_refund_sum,
'new_pending_sum': new_pending_sum,
'can_auto_refund': can_auto_refund,
})
return self.get(request, *args, **kwargs)
def _validate_total_diff(self, ocm):
if ocm._totaldiff < Decimal('0.00') and self.request.event.settings.change_allow_user_price == 'gte':
raise OrderError(_('You may not change your order in a way that reduces the total price.'))
if ocm._totaldiff <= Decimal('0.00') and self.request.event.settings.change_allow_user_price == 'gt':
raise OrderError(_('You may only change your order in a way that increases the total price.'))
if ocm._totaldiff != Decimal('0.00') and self.request.event.settings.change_allow_user_price == 'eq':
raise OrderError(_('You may not change your order in a way that changes the total price.'))
if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PAID:
self.order.set_expires(
now(),
self.order.event.subevents.filter(id__in=self.order.positions.values_list('subevent_id', flat=True))
)
if self.order.expires < now():
raise OrderError(_('You may not change your order in a way that increases the total price since '
'payments are no longer being accepted for this event.'))