diff --git a/doc/api/resources/item_variations.rst b/doc/api/resources/item_variations.rst index 2a356fe6d9..371e9a2462 100644 --- a/doc/api/resources/item_variations.rst +++ b/doc/api/resources/item_variations.rst @@ -18,6 +18,8 @@ default_price money (string) The price set d price money (string) The price used for this variation. This is either the same as ``default_price`` if that value is set or equal to the item's ``default_price`` (read-only). +free_price_suggestion money (string) A suggested price, used as a default value if + ``Item.free_price`` is set (or ``null``). original_price money (string) An original price, shown for comparison, not used for price calculations (or ``null``). active boolean If ``false``, this variation will not be sold or shown. @@ -53,6 +55,10 @@ meta_data object Values set for The ``meta_data`` and ``checkin_attention`` attributes have been added. +.. versionchanged:: 2023.9 + + The ``free_price_suggestion`` attribute has been added. + Endpoints --------- @@ -103,6 +109,7 @@ Endpoints "default_price": "223.00", "price": 223.0, "original_price": null, + "free_price_suggestion": null, "meta_data": {} }, { @@ -116,10 +123,16 @@ Endpoints "require_membership": false, "require_membership_hidden": false, "require_membership_types": [], + "sales_channels": ["web"], + "available_from": null, + "available_until": null, + "hide_without_voucher": false, "description": {}, "position": 1, - "default_price": null, - "price": 15.0, + "default_price": "223.00", + "price": 223.0, + "original_price": null, + "free_price_suggestion": null, "meta_data": {} } ] @@ -163,6 +176,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -231,6 +245,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -291,6 +306,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": false, "checkin_attention": false, "require_approval": false, diff --git a/doc/api/resources/items.rst b/doc/api/resources/items.rst index 366105fb81..78be63ddd5 100644 --- a/doc/api/resources/items.rst +++ b/doc/api/resources/items.rst @@ -29,6 +29,8 @@ free_price boolean If ``true``, they buy the product (however, the price can't be set lower than the price defined by ``default_price`` or otherwise). +free_price_suggestion money (string) A suggested price, used as a default value if + ``free_price`` is set (or ``null``). tax_rate decimal (string) The VAT rate to be applied for this item (read-only, set through ``tax_rule``). tax_rule integer The internal ID of the applied tax rule (or ``null``). @@ -123,6 +125,8 @@ variations list of objects A list with o ├ price money (string) The price used for this variation. This is either the same as ``default_price`` if that value is set or equal to the item's ``default_price``. +├ free_price_suggestion money (string) A suggested price, used as a default value if + ``free_price`` is set (or ``null``). ├ original_price money (string) An original price, shown for comparison, not used for price calculations (or ``null``). ├ active boolean If ``false``, this variation will not be sold or shown. @@ -196,6 +200,10 @@ meta_data object Values set fo The ``media_policy`` and ``media_type`` attributes have been added. +.. versionchanged:: 2023.9 + + The ``free_price_suggestion`` and ``variations[x].free_price_suggestion`` attributes have been added. + Notes ----- @@ -246,6 +254,7 @@ Endpoints "active": true, "description": null, "free_price": false, + "free_price_suggestion": null, "tax_rate": "0.00", "tax_rule": 1, "admission": false, @@ -291,6 +300,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -309,6 +319,7 @@ Endpoints "default_price": null, "price": "23.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -377,6 +388,7 @@ Endpoints "active": true, "description": null, "free_price": false, + "free_price_suggestion": null, "tax_rate": "0.00", "tax_rule": 1, "admission": false, @@ -422,6 +434,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -440,6 +453,7 @@ Endpoints "default_price": null, "price": "23.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -489,6 +503,7 @@ Endpoints "active": true, "description": null, "free_price": false, + "free_price_suggestion": null, "tax_rate": "0.00", "tax_rule": 1, "admission": false, @@ -533,6 +548,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -551,6 +567,7 @@ Endpoints "default_price": null, "price": "23.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -588,6 +605,7 @@ Endpoints "active": true, "description": null, "free_price": false, + "free_price_suggestion": null, "tax_rate": "0.00", "tax_rule": 1, "admission": false, @@ -633,6 +651,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -651,6 +670,7 @@ Endpoints "default_price": null, "price": "23.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -719,6 +739,7 @@ Endpoints "active": true, "description": null, "free_price": false, + "free_price_suggestion": null, "tax_rate": "0.00", "tax_rule": 1, "admission": false, @@ -764,6 +785,7 @@ Endpoints "default_price": "10.00", "price": "10.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, @@ -782,6 +804,7 @@ Endpoints "default_price": null, "price": "23.00", "original_price": null, + "free_price_suggestion": null, "active": true, "checkin_attention": false, "require_approval": false, diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index f8d72c7656..09e9777e01 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -59,7 +59,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer): class Meta: model = ItemVariation fields = ('id', 'value', 'active', 'description', - 'position', 'default_price', 'price', 'original_price', 'require_approval', + 'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval', 'require_membership', 'require_membership_types', 'require_membership_hidden', 'checkin_attention', 'available_from', 'available_until', 'sales_channels', 'hide_without_voucher', 'meta_data') @@ -83,7 +83,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer): class Meta: model = ItemVariation fields = ('id', 'value', 'active', 'description', - 'position', 'default_price', 'price', 'original_price', 'require_approval', + 'position', 'default_price', 'price', 'original_price', 'free_price_suggestion', 'require_approval', 'require_membership', 'require_membership_types', 'require_membership_hidden', 'checkin_attention', 'available_from', 'available_until', 'sales_channels', 'hide_without_voucher', 'meta_data') @@ -234,8 +234,8 @@ class ItemSerializer(I18nAwareModelSerializer): class Meta: model = Item fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description', - 'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission', 'personalized', - 'position', 'picture', 'available_from', 'available_until', + 'default_price', 'free_price', 'free_price_suggestion', 'tax_rate', 'tax_rule', 'admission', + 'personalized', 'position', 'picture', 'available_from', 'available_until', 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling', 'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations', 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets', diff --git a/src/pretix/base/migrations/0248_item_free_price_suggestion.py b/src/pretix/base/migrations/0248_item_free_price_suggestion.py new file mode 100644 index 0000000000..e1c5c2f590 --- /dev/null +++ b/src/pretix/base/migrations/0248_item_free_price_suggestion.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.4 on 2023-10-25 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("pretixbase", "0247_checkinlist"), + ] + + operations = [ + migrations.AddField( + model_name="item", + name="free_price_suggestion", + field=models.DecimalField(decimal_places=2, max_digits=13, null=True), + ), + migrations.AddField( + model_name="itemvariation", + name="free_price_suggestion", + field=models.DecimalField(decimal_places=2, max_digits=13, null=True), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index dff10a149d..44c2dbaff2 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -431,6 +431,12 @@ class Item(LoggedModel): "additional donations for your event. This is currently not supported for products that are " "bought as an add-on to other products.") ) + free_price_suggestion = models.DecimalField( + verbose_name=_("Suggested price"), + help_text=_("This price will be used as the default value of the input field. The user can choose a lower " + "value, but not lower than the price this product would have without the free price option."), + max_digits=13, decimal_places=2, null=True, blank=True, + ) tax_rule = models.ForeignKey( 'TaxRule', verbose_name=_('Sales tax'), @@ -1021,6 +1027,12 @@ class ItemVariation(models.Model): help_text=_('If set, this will be displayed next to the current price to show that the current price is a ' 'discounted one. This is just a cosmetic setting and will not actually impact pricing.') ) + free_price_suggestion = models.DecimalField( + verbose_name=_("Suggested price"), + help_text=_("This price will be used as the default value of the input field. The user can choose a lower " + "value, but not lower than the price this product would have without the free price option."), + max_digits=13, decimal_places=2, null=True, blank=True, + ) require_approval = models.BooleanField( verbose_name=_('Require approval'), default=False, diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 5a5ea3a6fa..aec549810e 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -577,6 +577,8 @@ class ItemUpdateForm(I18nModelForm): ) self.fields['category'].widget.choices = self.fields['category'].choices + self.fields['free_price_suggestion'].widget.attrs['data-display-dependency'] = '#id_free_price' + qs = self.event.organizer.membership_types.all() if qs: self.fields['require_membership_types'].queryset = qs @@ -664,6 +666,7 @@ class ItemUpdateForm(I18nModelForm): 'picture', 'default_price', 'free_price', + 'free_price_suggestion', 'tax_rule', 'available_from', 'available_until', @@ -797,6 +800,8 @@ class ItemVariationForm(I18nModelForm): del self.fields['require_membership'] del self.fields['require_membership_types'] + self.fields['free_price_suggestion'].widget.attrs['data-display-dependency'] = '#id_free_price' + self.meta_fields = [] meta_defaults = {} if self.instance.pk: @@ -829,6 +834,7 @@ class ItemVariationForm(I18nModelForm): 'value', 'active', 'default_price', + 'free_price_suggestion', 'original_price', 'description', 'require_approval', diff --git a/src/pretix/control/templates/pretixcontrol/item/include_variations.html b/src/pretix/control/templates/pretixcontrol/item/include_variations.html index 4baa808e74..d9db4fdc90 100644 --- a/src/pretix/control/templates/pretixcontrol/item/include_variations.html +++ b/src/pretix/control/templates/pretixcontrol/item/include_variations.html @@ -72,6 +72,7 @@ {% bootstrap_field form.active layout="control" %} {% bootstrap_field form.value layout="control" %} {% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %} + {% bootstrap_field form.free_price_suggestion addon_after=request.event.currency layout="control" %} {% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %} {% bootstrap_field form.description layout="control" %} {% if form.meta_fields %} @@ -170,6 +171,7 @@ {% bootstrap_field formset.empty_form.active layout="control" %} {% bootstrap_field formset.empty_form.value layout="control" %} {% bootstrap_field formset.empty_form.default_price addon_after=request.event.currency layout="control" %} + {% bootstrap_field formset.empty_form.free_price_suggestion addon_after=request.event.currency layout="control" %} {% bootstrap_field formset.empty_form.original_price addon_after=request.event.currency layout="control" %} {% bootstrap_field formset.empty_form.description layout="control" %} {% if formset.empty_form.meta_fields %} diff --git a/src/pretix/control/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html index 789fffb5ca..ae9e8f8766 100644 --- a/src/pretix/control/templates/pretixcontrol/item/index.html +++ b/src/pretix/control/templates/pretixcontrol/item/index.html @@ -147,6 +147,7 @@ {% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %} {% bootstrap_field form.tax_rule layout="control" %} {% bootstrap_field form.free_price layout="control" %} + {% bootstrap_field form.free_price_suggestion addon_after=request.event.currency layout="control" %} {% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index 32534742d5..8165bbd23d 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -570,7 +570,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep): rate=a.tax_rate, ) else: - v.initial_price = v.display_price + v.initial_price = v.suggested_price i.expand = any(v.initial for v in i.available_variations) else: i.initial = len(current_addon_products[i.pk, None]) @@ -584,7 +584,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep): rate=a.tax_rate, ) else: - i.initial_price = i.display_price + i.initial_price = i.suggested_price if items: formsetentry['categories'].append({ diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html b/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html index 8a0b2ed9a1..bb0bd8652f 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html @@ -139,7 +139,15 @@ 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 %}" + {% if var.initial_price.gross != var.display_price.gross %} + {% if event.settings.display_net_prices %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% else %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% endif %} + {% else %} + title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}" + {% endif %} 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 %}" > @@ -268,7 +276,15 @@ id="price-item-{{ form.pos.pk }}-{{ item.pk }}" 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 %}" + {% if item.initial_price.gross != item.display_price.gross %} + {% if event.settings.display_net_prices %} + title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% else %} + title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% endif %} + {% else %} + title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}" + {% endif %} 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"> diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html index 8bd295a81d..ebccea82ee 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html @@ -138,9 +138,17 @@ 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="price_{{ item.id }}_{{ var.id }}" - title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}" + {% if var.suggested_price.gross != var.display_price.gross %} + {% if event.settings.display_net_prices %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% else %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% endif %} + {% else %} + title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}" + {% endif %} step="any" - value="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}" + value="{% if event.settings.display_net_prices %}{{ var.suggested_price.net|money_numberfield:event.currency }}{% else %}{{ var.suggested_price.gross|money_numberfield:event.currency }}{% endif %}" >

@@ -284,8 +292,16 @@ {% if not ev.presale_is_running %}disabled{% endif %} 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="price_{{ item.id }}" - title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}" - value="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}" + {% if item.suggested_price.gross != item.display_price.gross %} + {% if event.settings.display_net_prices %} + title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% else %} + title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% endif %} + {% else %} + title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}" + {% endif %} + value="{% if event.settings.display_net_prices %}{{ item.suggested_price.net|money_numberfield:event.currency }}{% else %}{{ item.suggested_price.gross|money_numberfield:event.currency }}{% endif %}" step="any">

diff --git a/src/pretix/presale/templates/pretixpresale/event/voucher.html b/src/pretix/presale/templates/pretixpresale/event/voucher.html index ca8c69b605..5e0c0a591b 100644 --- a/src/pretix/presale/templates/pretixpresale/event/voucher.html +++ b/src/pretix/presale/templates/pretixpresale/event/voucher.html @@ -166,8 +166,16 @@ placeholder="0" min="{% if event.settings.display_net_prices %}{{ var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}" name="price_{{ item.id }}_{{ var.id }}" - title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}" - value="{% if event.settings.display_net_prices %}{{var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}" + {% if var.suggested_price.gross != var.display_price.gross %} + {% if event.settings.display_net_prices %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% else %} + title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}" + {% endif %} + {% else %} + title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}" + {% endif %} + value="{% if event.settings.display_net_prices %}{{var.suggested_price.net|stringformat:"0.2f" }}{% else %}{{ var.suggested_price.gross|stringformat:"0.2f" }}{% endif %}" step="any">

@@ -309,8 +317,16 @@

diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index 3e45a3840f..2d7f8371cb 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -300,6 +300,10 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require price = original_price item.display_price = item.tax(price, currency=event.currency, include_bundled=True) + if item.free_price and item.free_price_suggestion is not None: + item.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency, include_bundled=True) + else: + item.suggested_price = item.display_price if price != original_price: item.original_price = item.tax(original_price, currency=event.currency, include_bundled=True) @@ -346,6 +350,15 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require var.display_price = var.tax(price, currency=event.currency, include_bundled=True) + if item.free_price and var.free_price_suggestion is not None: + var.suggested_price = item.tax(max(price, var.free_price_suggestion), currency=event.currency, + include_bundled=True) + elif item.free_price and item.free_price_suggestion is not None: + var.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency, + include_bundled=True) + else: + var.suggested_price = var.display_price + if price != original_price: var.original_price = var.tax(original_price, currency=event.currency, include_bundled=True) else: diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index f773457235..8edb06fecb 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -1355,7 +1355,7 @@ class OrderChangeMixin: rate=a.tax_rate, ) else: - v.initial_price = v.display_price + v.initial_price = v.suggested_price i.expand = any(v.initial for v in i.available_variations) else: i.initial = len(current_addon_products[i.pk, None]) @@ -1369,7 +1369,7 @@ class OrderChangeMixin: rate=a.tax_rate, ) else: - i.initial_price = i.display_price + i.initial_price = i.suggested_price if items: p.addon_form['categories'].append({ diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index 84ad2e348c..405c187adf 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -274,6 +274,7 @@ class WidgetAPIProductList(EventListMixin, View): 'order_min': item.min_per_order, 'order_max': item.order_max if not item.has_variations else None, 'price': price_dict(item, item.display_price) if not item.has_variations else None, + 'suggested_price': price_dict(item, item.suggested_price) if not item.has_variations else None, 'min_price': item.min_price if item.has_variations else None, 'max_price': item.max_price if item.has_variations else None, 'allow_waitinglist': item.allow_waitinglist, @@ -296,6 +297,7 @@ class WidgetAPIProductList(EventListMixin, View): 'order_max': var.order_max, 'description': str(rich_text(var.description, safelinks=False)) if var.description else None, 'price': price_dict(item, var.display_price), + 'suggested_price': price_dict(item, var.suggested_price), 'original_price': ( ( var.original_price.net diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index 63f05d6260..9d73f8894c 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -336,7 +336,7 @@ Vue.component('pricebox', { + '

' + '{{ $root.currency }} ' + '' + '
' + '' @@ -347,6 +347,7 @@ Vue.component('pricebox', { price: Object, free_price: Boolean, field_name: String, + suggested_price: Object, original_price: String, mandatory_priced_addons: Boolean, }, @@ -365,6 +366,17 @@ Vue.component('pricebox', { return parseFloat(this.price.gross).toFixed(2); } }, + suggested_price_nonlocalized: function () { + var price = this.suggested_price; + if (price === null) { + price = this.price; + } + if (this.$root.display_net_prices) { + return parseFloat(price.net).toFixed(2); + } else { + return parseFloat(price.gross).toFixed(2); + } + }, original_line: function () { return this.$root.currency + " " + floatformat(parseFloat(this.original_price), 2); }, @@ -420,7 +432,7 @@ Vue.component('variation', { // Price + '
' + '' + '' + ' ' @@ -478,7 +490,7 @@ Vue.component('item', { // Price + '
' + '' + '' + '
{{ pricerange }}
' diff --git a/src/tests/api/test_items.py b/src/tests/api/test_items.py index 8944c931f2..ef3bb62cf1 100644 --- a/src/tests/api/test_items.py +++ b/src/tests/api/test_items.py @@ -285,6 +285,7 @@ TEST_ITEM_RES = { "bundles": [], "show_quota_left": None, "original_price": None, + "free_price_suggestion": None, "meta_data": { "day": "Tuesday" }, @@ -384,6 +385,7 @@ def test_item_detail_variations(token_client, organizer, event, team, item): "id": var.pk, "value": {"en": "Children"}, "default_price": None, + "free_price_suggestion": None, "price": "23.00", "active": True, "description": None, @@ -1313,6 +1315,7 @@ TEST_VARIATIONS_RES = { "available_until": None, "hide_without_voucher": False, "original_price": None, + "free_price_suggestion": None, "meta_data": {} } @@ -1334,6 +1337,7 @@ TEST_VARIATIONS_UPDATE = { "available_until": None, "hide_without_voucher": False, "original_price": None, + "free_price_suggestion": None, "meta_data": {} } diff --git a/src/tests/presale/test_widget.py b/src/tests/presale/test_widget.py index 8db0b33b77..590183ddc7 100644 --- a/src/tests/presale/test_widget.py +++ b/src/tests/presale/test_widget.py @@ -184,6 +184,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "order_min": None, "max_price": None, "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, "picture": None, "picture_fullsize": None, "has_variations": 0, @@ -204,6 +205,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "order_min": None, "max_price": "14.00", "price": None, + "suggested_price": None, "picture": None, "picture_fullsize": None, "has_variations": 4, @@ -219,6 +221,8 @@ class WidgetCartTest(CartTestMixin, TestCase): 'original_price': None, "price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "", + "rate": "19.00", "includes_mixed_tax_rate": False}, "description": None, "avail": [100, None], "order_max": 2 @@ -229,6 +233,8 @@ class WidgetCartTest(CartTestMixin, TestCase): 'original_price': None, "price": {"gross": "12.00", "net": "10.08", "tax": "1.92", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "12.00", "net": "10.08", "tax": "1.92", "name": "", + "rate": "19.00", "includes_mixed_tax_rate": False}, "description": None, "avail": [100, None], "order_max": 2 @@ -266,6 +272,8 @@ class WidgetCartTest(CartTestMixin, TestCase): "max_price": None, "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", + "includes_mixed_tax_rate": False}, "picture": None, "picture_fullsize": None, "has_variations": 0, @@ -312,6 +320,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "order_min": None, "max_price": "14.00", "price": None, + "suggested_price": None, "picture": None, "picture_fullsize": None, "has_variations": 4, @@ -327,6 +336,8 @@ class WidgetCartTest(CartTestMixin, TestCase): 'original_price': None, "price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "14.00", "net": "11.76", "tax": "2.24", "name": "", + "rate": "19.00", "includes_mixed_tax_rate": False}, "description": None, "avail": [100, None], "order_max": 2 @@ -374,6 +385,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "order_min": None, "max_price": None, "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, + "suggested_price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, "picture": None, "picture_fullsize": None, "has_variations": 0, @@ -439,6 +451,7 @@ class WidgetCartTest(CartTestMixin, TestCase): 'order_min': None, 'order_max': None, 'price': None, + 'suggested_price': None, 'min_price': '14.00', 'max_price': '14.00', 'free_price': False, @@ -459,6 +472,14 @@ class WidgetCartTest(CartTestMixin, TestCase): 'name': '', 'includes_mixed_tax_rate': False }, + 'suggested_price': { + 'gross': '14.00', + 'net': '11.76', + 'tax': '2.24', + 'rate': '19.00', + 'name': '', + 'includes_mixed_tax_rate': False + }, 'avail': [100, None] }, ]