forked from CGM_Public/pretix_original
Vouchers: Allow to set all addons or bundles as included (#3322)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -63,7 +63,8 @@ class VoucherSerializer(I18nAwareModelSerializer):
|
||||
model = Voucher
|
||||
fields = ('id', 'code', 'max_usages', 'redeemed', 'min_usages', 'valid_until', 'block_quota',
|
||||
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
|
||||
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat')
|
||||
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat', 'all_addons_included',
|
||||
'all_bundles_included')
|
||||
read_only_fields = ('id', 'redeemed')
|
||||
list_serializer_class = VoucherListSerializer
|
||||
|
||||
|
||||
23
src/pretix/base/migrations/0240_auto_20230516_1119.py
Normal file
23
src/pretix/base/migrations/0240_auto_20230516_1119.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.18 on 2023-05-16 11:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0239_giftcard_info'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='voucher',
|
||||
name='all_addons_included',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='voucher',
|
||||
name='all_bundles_included',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -2832,8 +2832,12 @@ class CartPosition(AbstractPosition):
|
||||
if self.is_bundled:
|
||||
bundle = self.addon_to.item.bundles.filter(bundled_item=self.item, bundled_variation=self.variation).first()
|
||||
if bundle:
|
||||
listed_price = bundle.designated_price
|
||||
price_after_voucher = bundle.designated_price
|
||||
if self.addon_to.voucher_id and self.addon_to.voucher.all_bundles_included:
|
||||
listed_price = Decimal('0.00')
|
||||
price_after_voucher = Decimal('0.00')
|
||||
else:
|
||||
listed_price = bundle.designated_price
|
||||
price_after_voucher = bundle.designated_price
|
||||
|
||||
if listed_price != self.listed_price or price_after_voucher != self.price_after_voucher:
|
||||
self.listed_price = listed_price
|
||||
|
||||
@@ -296,6 +296,14 @@ class Voucher(LoggedModel):
|
||||
verbose_name=_("Shows hidden products that match this voucher"),
|
||||
default=True
|
||||
)
|
||||
all_addons_included = models.BooleanField(
|
||||
verbose_name=_("Offer all add-on products for free when redeeming this voucher"),
|
||||
default=False
|
||||
)
|
||||
all_bundles_included = models.BooleanField(
|
||||
verbose_name=_("Include all bundled products without a designated price when redeeming this voucher"),
|
||||
default=False
|
||||
)
|
||||
|
||||
objects = ScopedManager(organizer='event__organizer')
|
||||
|
||||
|
||||
@@ -512,7 +512,10 @@ class CartManager:
|
||||
if cp.is_bundled:
|
||||
bundle = cp.addon_to.item.bundles.filter(bundled_item=cp.item, bundled_variation=cp.variation).first()
|
||||
if bundle:
|
||||
listed_price = bundle.designated_price or Decimal('0.00')
|
||||
if cp.addon_to.voucher_id and cp.addon_to.voucher.all_bundles_included:
|
||||
listed_price = Decimal('0.00')
|
||||
else:
|
||||
listed_price = bundle.designated_price
|
||||
else:
|
||||
listed_price = cp.price
|
||||
price_after_voucher = listed_price
|
||||
@@ -712,6 +715,11 @@ class CartManager:
|
||||
else:
|
||||
bundle_quotas = []
|
||||
|
||||
if voucher and voucher.all_bundles_included:
|
||||
bundled_price = Decimal('0.00')
|
||||
else:
|
||||
bundled_price = bundle.designated_price
|
||||
|
||||
bop = self.AddOperation(
|
||||
count=bundle.count,
|
||||
item=bitem,
|
||||
@@ -722,8 +730,8 @@ class CartManager:
|
||||
subevent=subevent,
|
||||
bundled=[],
|
||||
seat=None,
|
||||
listed_price=bundle.designated_price,
|
||||
price_after_voucher=bundle.designated_price,
|
||||
listed_price=bundled_price,
|
||||
price_after_voucher=bundled_price,
|
||||
custom_price_input=None,
|
||||
custom_price_input_is_net=False,
|
||||
voucher_ignored=False,
|
||||
@@ -809,7 +817,6 @@ class CartManager:
|
||||
quota_diff = Counter() # Quota -> Number of usages
|
||||
operations = []
|
||||
available_categories = defaultdict(set) # CartPos -> Category IDs to choose from
|
||||
price_included = defaultdict(dict) # CartPos -> CategoryID -> bool(price is included)
|
||||
toplevel_cp = self.positions.filter(
|
||||
addon_to__isnull=True
|
||||
).prefetch_related(
|
||||
@@ -819,7 +826,6 @@ class CartManager:
|
||||
# Prefill some of the cache containers
|
||||
for cp in toplevel_cp:
|
||||
available_categories[cp.pk] = {iao.addon_category_id for iao in cp.item.addons.all()}
|
||||
price_included[cp.pk] = {iao.addon_category_id: iao.price_included for iao in cp.item.addons.all()}
|
||||
cpcache[cp.pk] = cp
|
||||
for a in cp.addons.all():
|
||||
if not a.is_bundled:
|
||||
|
||||
@@ -1892,7 +1892,7 @@ class OrderChangeManager:
|
||||
input_addons[op.id][a['item'], a['variation']] = a.get('count', 1)
|
||||
selected_addons[op.id, item.category_id][a['item'], a['variation']] = a.get('count', 1)
|
||||
|
||||
if price_included[op.pk].get(item.category_id):
|
||||
if price_included[op.pk].get(item.category_id) or (op.voucher_id and op.voucher.all_addons_included):
|
||||
price = TAXED_ZERO
|
||||
else:
|
||||
price = get_price(
|
||||
|
||||
@@ -110,6 +110,8 @@ def is_included_for_free(item: Item, addon_to: AbstractPosition):
|
||||
return True
|
||||
except ItemAddOn.DoesNotExist:
|
||||
pass
|
||||
if addon_to.voucher_id and addon_to.voucher.all_addons_included:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,8 @@ class VoucherForm(I18nModelForm):
|
||||
localized_fields = '__all__'
|
||||
fields = [
|
||||
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
|
||||
'comment', 'max_usages', 'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'budget'
|
||||
'comment', 'max_usages', 'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'all_addons_included',
|
||||
'all_bundles_included', 'budget'
|
||||
]
|
||||
field_classes = {
|
||||
'valid_until': SplitDateTimeField,
|
||||
@@ -308,7 +309,8 @@ class VoucherBulkForm(VoucherForm):
|
||||
localized_fields = '__all__'
|
||||
fields = [
|
||||
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
|
||||
'max_usages', 'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'budget'
|
||||
'max_usages', 'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'all_addons_included',
|
||||
'all_bundles_included', 'budget'
|
||||
]
|
||||
field_classes = {
|
||||
'valid_until': SplitDateTimeField,
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
{% bootstrap_field form.tag layout="control" %}
|
||||
{% bootstrap_field form.comment layout="control" %}
|
||||
{% bootstrap_field form.show_hidden_items layout="control" %}
|
||||
{% bootstrap_field form.all_addons_included layout="control" %}
|
||||
{% bootstrap_field form.all_bundles_included layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Send out emails" %}</legend>
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
{% bootstrap_field form.tag layout="control" %}
|
||||
{% bootstrap_field form.comment layout="control" %}
|
||||
{% bootstrap_field form.show_hidden_items layout="control" %}
|
||||
{% bootstrap_field form.all_addons_included layout="control" %}
|
||||
{% bootstrap_field form.all_bundles_included layout="control" %}
|
||||
</fieldset>
|
||||
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
|
||||
</div>
|
||||
|
||||
@@ -585,7 +585,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
if items:
|
||||
formsetentry['categories'].append({
|
||||
'category': iao.addon_category,
|
||||
'price_included': iao.price_included,
|
||||
'price_included': iao.price_included or (cartpos.voucher_id and cartpos.voucher.all_addons_included),
|
||||
'multi_allowed': iao.multi_allowed,
|
||||
'min_count': iao.min_count,
|
||||
'max_count': iao.max_count,
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
from {{ minprice }}
|
||||
{% endblocktrans %}
|
||||
{% elif not item.min_price and not item.max_price %}
|
||||
{% if not item.mandatory_priced_addons %}
|
||||
{% if not item.mandatory_priced_addons or voucher.all_addons_included %}
|
||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
@@ -207,7 +207,7 @@
|
||||
</div>
|
||||
<p>
|
||||
{% elif not var.display_price.gross %}
|
||||
{% if not item.mandatory_priced_addons or var.original_price %}
|
||||
{% if not item.mandatory_priced_addons or var.original_price or voucher.all_addons_included %}
|
||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||
{% endif %}
|
||||
{% elif event.settings.display_net_prices %}
|
||||
@@ -349,7 +349,7 @@
|
||||
</div>
|
||||
<p>
|
||||
{% elif not item.display_price.gross %}
|
||||
{% if not item.mandatory_priced_addons or item.original_price %}
|
||||
{% if not item.mandatory_priced_addons or item.original_price or voucher.all_addons_included %}
|
||||
<span class="text-uppercase">{% trans "free" context "price" %}</span>
|
||||
{% endif %}
|
||||
{% elif event.settings.display_net_prices %}
|
||||
|
||||
@@ -1361,7 +1361,7 @@ class OrderChangeMixin:
|
||||
if items:
|
||||
p.addon_form['categories'].append({
|
||||
'category': iao.addon_category,
|
||||
'price_included': iao.price_included,
|
||||
'price_included': iao.price_included or (p.voucher_id and p.voucher.all_addons_included),
|
||||
'multi_allowed': iao.multi_allowed,
|
||||
'min_count': iao.min_count,
|
||||
'max_count': iao.max_count,
|
||||
|
||||
Reference in New Issue
Block a user