forked from CGM_Public/pretix_original
Order changes: Do not allow to double-book add-ons
This commit is contained in:
@@ -2094,6 +2094,43 @@ class OrderChangeManager:
|
|||||||
)
|
)
|
||||||
item_counts[item] += 1
|
item_counts[item] += 1
|
||||||
|
|
||||||
|
# Detect removed add-ons and create RemoveOperations
|
||||||
|
for cp, al in list(current_addons.items()):
|
||||||
|
for k, v in al.items():
|
||||||
|
input_num = input_addons[cp.id].get(k, 0)
|
||||||
|
current_num = len(current_addons[cp].get(k, []))
|
||||||
|
if input_num < current_num:
|
||||||
|
for a in current_addons[cp][k][:current_num - input_num]:
|
||||||
|
if a.canceled:
|
||||||
|
continue
|
||||||
|
is_unavailable = (
|
||||||
|
# If an item is no longer available due to time, it should usually also be no longer
|
||||||
|
# user-removable, because e.g. the stock has already been ordered.
|
||||||
|
# We always pass has_voucher=True because if a product now requires a voucher, it usually does
|
||||||
|
# not mean it should be unremovable for others.
|
||||||
|
# This also prevents accidental removal through the UI because a hidden product will no longer
|
||||||
|
# be part of the input.
|
||||||
|
(a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent))
|
||||||
|
or (a.variation and not a.variation.all_sales_channels and not a.variation.limit_sales_channels.contains(self.order.sales_channel))
|
||||||
|
or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent)
|
||||||
|
or (
|
||||||
|
not item.all_sales_channels and
|
||||||
|
not item.limit_sales_channels.contains(self.order.sales_channel)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if is_unavailable:
|
||||||
|
# "Re-select" add-on
|
||||||
|
selected_addons[cp.id, a.item.category_id][a.item_id, a.variation_id] += 1
|
||||||
|
continue
|
||||||
|
if a.checkins.filter(list__consider_tickets_used=True).exists():
|
||||||
|
raise OrderError(
|
||||||
|
error_messages['addon_already_checked_in'] % {
|
||||||
|
'addon': str(a.item.name),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.cancel(a)
|
||||||
|
item_counts[a.item] -= 1
|
||||||
|
|
||||||
# Check constraints on the add-on combinations
|
# Check constraints on the add-on combinations
|
||||||
for op in toplevel_op:
|
for op in toplevel_op:
|
||||||
item = op.item
|
item = op.item
|
||||||
@@ -2126,41 +2163,6 @@ class OrderChangeManager:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Detect removed add-ons and create RemoveOperations
|
|
||||||
for cp, al in list(current_addons.items()):
|
|
||||||
for k, v in al.items():
|
|
||||||
input_num = input_addons[cp.id].get(k, 0)
|
|
||||||
current_num = len(current_addons[cp].get(k, []))
|
|
||||||
if input_num < current_num:
|
|
||||||
for a in current_addons[cp][k][:current_num - input_num]:
|
|
||||||
if a.canceled:
|
|
||||||
continue
|
|
||||||
is_unavailable = (
|
|
||||||
# If an item is no longer available due to time, it should usually also be no longer
|
|
||||||
# user-removable, because e.g. the stock has already been ordered.
|
|
||||||
# We always pass has_voucher=True because if a product now requires a voucher, it usually does
|
|
||||||
# not mean it should be unremovable for others.
|
|
||||||
# This also prevents accidental removal through the UI because a hidden product will no longer
|
|
||||||
# be part of the input.
|
|
||||||
(a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent))
|
|
||||||
or (a.variation and not a.variation.all_sales_channels and not a.variation.limit_sales_channels.contains(self.order.sales_channel))
|
|
||||||
or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent)
|
|
||||||
or (
|
|
||||||
not item.all_sales_channels and
|
|
||||||
not item.limit_sales_channels.contains(self.order.sales_channel)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if is_unavailable:
|
|
||||||
continue
|
|
||||||
if a.checkins.filter(list__consider_tickets_used=True).exists():
|
|
||||||
raise OrderError(
|
|
||||||
error_messages['addon_already_checked_in'] % {
|
|
||||||
'addon': str(a.item.name),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.cancel(a)
|
|
||||||
item_counts[a.item] -= 1
|
|
||||||
|
|
||||||
for item, count in item_counts.items():
|
for item, count in item_counts.items():
|
||||||
if count == 0:
|
if count == 0:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -372,6 +372,19 @@
|
|||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if c.items_missing %}
|
||||||
|
<div class="product-appendix-row">
|
||||||
|
<p class="text-muted">
|
||||||
|
{% trans "There are currently selected products in this add-on category that currently cannot be changed because they are not on sale:" %}
|
||||||
|
</p>
|
||||||
|
<ul class="text-muted">
|
||||||
|
{% for itemvar, count in c.items_missing.items %}
|
||||||
|
<li>{{ count }}x {{ itemvar.0 }}{% if itemvar.1 %} – {{ itemvar.1 }}{% endif %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import logging
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import Counter, OrderedDict, defaultdict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
@@ -1431,11 +1431,13 @@ class OrderChangeMixin:
|
|||||||
'categories': []
|
'categories': []
|
||||||
}
|
}
|
||||||
current_addon_products = defaultdict(list)
|
current_addon_products = defaultdict(list)
|
||||||
|
current_addon_products_missing = Counter()
|
||||||
for a in p.addons.all():
|
for a in p.addons.all():
|
||||||
if a.canceled:
|
if a.canceled:
|
||||||
continue
|
continue
|
||||||
if not a.is_bundled:
|
if not a.is_bundled:
|
||||||
current_addon_products[a.item_id, a.variation_id].append(a)
|
current_addon_products[a.item_id, a.variation_id].append(a)
|
||||||
|
current_addon_products_missing[a.item, a.variation] += 1
|
||||||
|
|
||||||
for iao in p.item.addons.all():
|
for iao in p.item.addons.all():
|
||||||
ckey = '{}-{}'.format(p.subevent.pk if p.subevent else 0, iao.addon_category.pk)
|
ckey = '{}-{}'.format(p.subevent.pk if p.subevent else 0, iao.addon_category.pk)
|
||||||
@@ -1473,6 +1475,7 @@ class OrderChangeMixin:
|
|||||||
if i.has_variations:
|
if i.has_variations:
|
||||||
for v in i.available_variations:
|
for v in i.available_variations:
|
||||||
v.initial = len(current_addon_products[i.pk, v.pk])
|
v.initial = len(current_addon_products[i.pk, v.pk])
|
||||||
|
current_addon_products_missing[i, v] = 0
|
||||||
if v.initial and i.free_price:
|
if v.initial and i.free_price:
|
||||||
a = current_addon_products[i.pk, v.pk][0]
|
a = current_addon_products[i.pk, v.pk][0]
|
||||||
v.initial_price = TaxedPrice(
|
v.initial_price = TaxedPrice(
|
||||||
@@ -1488,6 +1491,7 @@ class OrderChangeMixin:
|
|||||||
i.expand = any(v.initial for v in i.available_variations)
|
i.expand = any(v.initial for v in i.available_variations)
|
||||||
else:
|
else:
|
||||||
i.initial = len(current_addon_products[i.pk, None])
|
i.initial = len(current_addon_products[i.pk, None])
|
||||||
|
current_addon_products_missing[i, None] = 0
|
||||||
if i.initial and i.free_price:
|
if i.initial and i.free_price:
|
||||||
a = current_addon_products[i.pk, None][0]
|
a = current_addon_products[i.pk, None][0]
|
||||||
i.initial_price = TaxedPrice(
|
i.initial_price = TaxedPrice(
|
||||||
@@ -1509,7 +1513,8 @@ class OrderChangeMixin:
|
|||||||
'min_count': iao.min_count,
|
'min_count': iao.min_count,
|
||||||
'max_count': iao.max_count,
|
'max_count': iao.max_count,
|
||||||
'iao': iao,
|
'iao': iao,
|
||||||
'items': [i for i in items if not i.require_voucher]
|
'items': [i for i in items if not i.require_voucher],
|
||||||
|
'items_missing': {k: v for k, v in current_addon_products_missing.items() if v},
|
||||||
})
|
})
|
||||||
|
|
||||||
return positions
|
return positions
|
||||||
|
|||||||
@@ -99,6 +99,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-appendix-row {
|
||||||
|
border-top: 1px solid $table-border-color;
|
||||||
|
border-bottom: 1px solid $table-border-color;
|
||||||
|
padding: 1.25*$line-height-computed 0;
|
||||||
|
& > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
article.item-with-variations {
|
article.item-with-variations {
|
||||||
margin: 0 -15px;
|
margin: 0 -15px;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
|||||||
Reference in New Issue
Block a user