Introduce original price (#905)

* Introduce original price

* Rebase and styling

* Widget
This commit is contained in:
Raphael Michel
2018-05-18 22:48:38 +02:00
committed by GitHub
parent c30ebdf287
commit fa326eba6f
12 changed files with 99 additions and 17 deletions

View File

@@ -57,6 +57,8 @@ max_per_order integer This product ca
checkin_attention boolean If ``True``, the check-in app should show a warning
that this ticket requires special attention if such
a product is being scanned.
original_price money (string) An original price, shown for comparison, not used
for price calculations.
has_variations boolean Shows whether or not this item has variations.
variations list of objects A list with one object for each variation of this item.
Can be empty. Only writable during creation,
@@ -93,7 +95,7 @@ addons list of objects Definition of a
.. versionchanged:: 1.16
The field ``internal_name`` has been added.
The field ``internal_name`` and ``original_price`` fields have been added.
Notes
-----
@@ -138,6 +140,7 @@ Endpoints
"name": {"en": "Standard ticket"},
"internal_name": "",
"default_price": "23.00",
"original_price": null,
"category": null,
"active": true,
"description": null,
@@ -221,6 +224,7 @@ Endpoints
"name": {"en": "Standard ticket"},
"internal_name": "",
"default_price": "23.00",
"original_price": null,
"category": null,
"active": true,
"description": null,
@@ -285,6 +289,7 @@ Endpoints
"name": {"en": "Standard ticket"},
"internal_name": "",
"default_price": "23.00",
"original_price": null,
"category": null,
"active": true,
"description": null,
@@ -336,6 +341,7 @@ Endpoints
"name": {"en": "Standard ticket"},
"internal_name": "",
"default_price": "23.00",
"original_price": null,
"category": null,
"active": true,
"description": null,
@@ -419,6 +425,7 @@ Endpoints
"name": {"en": "Ticket"},
"internal_name": "",
"default_price": "25.00",
"original_price": null,
"category": null,
"active": true,
"description": null,

View File

@@ -79,7 +79,7 @@ class ItemSerializer(I18nAwareModelSerializer):
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
'variations', 'addons')
'variations', 'addons', 'original_price')
read_only_fields = ('has_variations', 'picture')
def get_serializer_context(self):

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-05-11 12:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0091_auto_20180513_1641'),
]
operations = [
migrations.AddField(
model_name='item',
name='original_price',
field=models.DecimalField(blank=True, decimal_places=2,
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.',
max_digits=7, null=True, verbose_name='Original price'),
),
]

View File

@@ -191,6 +191,8 @@ class Item(LoggedModel):
:type min_per_order: int
:param checkin_attention: Requires special attention at check-in
:type checkin_attention: bool
:param original_price: The item's "original" price. Will not be used for any calculations, will just be shown.
:type original_price: decimal.Decimal
"""
event = models.ForeignKey(
@@ -311,8 +313,15 @@ class Item(LoggedModel):
'attention. You can use this for example for student tickets to indicate to the person at '
'check-in that the student ID card still needs to be checked.')
)
original_price = models.DecimalField(
verbose_name=_('Original price'),
blank=True, null=True,
max_digits=7, decimal_places=2,
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.')
)
# !!! Attention: If you add new fields here, also add them to the copying code in
# pretix/control/views/item.py if applicable.
# pretix/control/forms/item.py if applicable.
class Meta:
verbose_name = _("Product")

View File

@@ -226,6 +226,7 @@ class ItemCreateForm(I18nModelForm):
self.instance.max_per_order = self.cleaned_data['copy_from'].max_per_order
self.instance.checkin_attention = self.cleaned_data['copy_from'].checkin_attention
self.instance.free_price = self.cleaned_data['copy_from'].free_price
self.instance.original_price = self.cleaned_data['copy_from'].original_price
self.instance.position = (self.event.items.aggregate(p=Max('position'))['p'] or 0) + 1
instance = super().save(*args, **kwargs)
@@ -325,7 +326,8 @@ class ItemUpdateForm(I18nModelForm):
'allow_cancel',
'max_per_order',
'min_per_order',
'checkin_attention'
'checkin_attention',
'original_price'
]
field_classes = {
'available_from': forms.SplitDateTimeField,

View File

@@ -38,14 +38,13 @@
<legend>{% trans "Check-in" %}</legend>
{% bootstrap_field form.checkin_attention layout="control" %}
</fieldset>
{% if plugin_forms %}
<fieldset>
<legend>{% trans "Additional settings" %}</legend>
{% for f in plugin_forms %}
{% bootstrap_form f layout="control" %}
{% endfor %}
</fieldset>
{% endif %}
<fieldset>
<legend>{% trans "Additional settings" %}</legend>
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
{% for f in plugin_forms %}
{% bootstrap_form f layout="control" %}
{% endfor %}
</fieldset>
</div>
<div class="col-xs-12 col-lg-2">
<div class="panel panel-default">

View File

@@ -288,6 +288,10 @@
{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{% if item.original_price %}
<del>{{ item.original_price|money:event.currency }}</del>
<ins>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
@@ -307,6 +311,9 @@
{% else %}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price %}
</ins>
{% endif %}
{% if var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=var.display_price.rate name=var.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
@@ -383,6 +390,10 @@
</div>
</div>
<div class="col-md-2 col-xs-6 price">
{% if item.original_price %}
<del>{{ item.original_price|money:event.currency }}</del>
<ins>
{% endif %}
{% if item.free_price %}
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
@@ -400,6 +411,9 @@
{% else %}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price %}
</ins>
{% endif %}
{% if item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
<small>{% blocktrans trimmed with rate=item.display_price.rate name=item.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}

View File

@@ -177,6 +177,7 @@ class WidgetAPIProductList(View):
item.cached_availability[0],
item.cached_availability[1] if self.request.event.settings.show_quota_left else None
] if not item.has_variations else None,
'original_price': item.original_price,
'variations': [
{
'id': var.id,

View File

@@ -185,7 +185,10 @@ Vue.component('availbox', {
});
Vue.component('pricebox', {
template: ('<div class="pretix-widget-pricebox">'
+ '<span v-if="!free_price">{{ priceline }}</span>'
+ '<span v-if="!free_price && !original_price">{{ priceline }}</span>'
+ '<span v-if="!free_price && original_price">'
+ '<del class="pretix-widget-pricebox-original-price">{{ original_line }}</del> '
+ '<ins class="pretix-widget-pricebox-new-price">{{ priceline }}</ins></span>'
+ '<div v-if="free_price">'
+ '{{ $root.currency }} '
+ '<input type="number" class="pretix-widget-pricebox-price-input" placeholder="0" '
@@ -199,7 +202,8 @@ Vue.component('pricebox', {
props: {
price: Object,
free_price: Boolean,
field_name: String
field_name: String,
original_price: String
},
computed: {
display_price: function () {
@@ -209,6 +213,9 @@ Vue.component('pricebox', {
return roundTo(parseFloat(this.price.gross), 2).toFixed(2);
}
},
original_line: function () {
return this.$root.currency + " " + roundTo(parseFloat(this.original_price), 2).toFixed(2);
},
priceline: function () {
if (this.price.gross === "0.00") {
return strings.free;
@@ -247,7 +254,7 @@ Vue.component('variation', {
+ '</div>'
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="variation.price" :free_price="item.free_price"'
+ '<pricebox :price="variation.price" :free_price="item.free_price" :original_price="item.original_price"'
+ ' :field_name="\'price_\' + item.id + \'_\' + variation.id">'
+ '</pricebox>'
+ '</div>'
@@ -293,7 +300,7 @@ Vue.component('item', {
+ '<div class="pretix-widget-item-price-col">'
+ '<pricebox :price="item.price" :free_price="item.free_price" v-if="!item.has_variations"'
+ ' :field_name="\'price_\' + item.id">'
+ ' :field_name="\'price_\' + item.id" :original_price="item.original_price">'
+ '</pricebox>'
+ '<div class="pretix-widget-pricebox" v-if="item.has_variations">{{ pricerange }}</div>'
+ '</div>'

View File

@@ -64,6 +64,15 @@
margin: 0;
line-height: inherit;
}
.price del {
color: $text-muted;
}
.price ins {
color: $brand-success;
font-size: 18px;
font-weight: bold;
text-decoration: none;
}
}
.radio .variation-description {

View File

@@ -200,6 +200,16 @@
text-align: right;
}
del.pretix-widget-pricebox-original-price {
color: $text-muted;
}
ins.pretix-widget-pricebox-new-price {
font-size: 120%;
font-weight: bold;
text-decoration: none;
}
.pretix-widget-clear {
clear: both;
}

View File

@@ -231,7 +231,8 @@ TEST_ITEM_RES = {
"checkin_attention": False,
"has_variations": False,
"variations": [],
"addons": []
"addons": [],
"original_price": None
}