mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
@@ -9,7 +9,7 @@ from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
from pytz import common_timezones, timezone
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.base.models import Event, Organizer, TaxRule
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.control.forms import ExtFileField, SlugWidget
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
@@ -58,6 +58,13 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
choices=settings.LANGUAGES,
|
||||
label=_("Default language"),
|
||||
)
|
||||
tax_rate = forms.DecimalField(
|
||||
label=_("Sales tax rate"),
|
||||
help_text=_("Do you need to pay sales tax on your tickets? In this case, please enter the applicable tax rate "
|
||||
"here in percent. If you have a more complicated tax situation, you can add more tax rates and "
|
||||
"detailled configuration later."),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@@ -375,10 +382,12 @@ class PaymentSettingsForm(SettingsForm):
|
||||
"configured above."),
|
||||
required=False
|
||||
)
|
||||
tax_rate_default = forms.DecimalField(
|
||||
label=_('Tax rate for payment fees'),
|
||||
help_text=_("The tax rate that applies for additional fees you configured for single payment methods "
|
||||
"(in percent)."),
|
||||
tax_rate_default = forms.ModelChoiceField(
|
||||
queryset=TaxRule.objects.none(),
|
||||
label=_('Tax rule for payment fees'),
|
||||
required=False,
|
||||
help_text=_("The tax rule that applies for additional fees you configured for single payment methods. This "
|
||||
"will set the tax rate and reverse charge rules, other settings of the tax rule are ignored.")
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
@@ -392,6 +401,10 @@ class PaymentSettingsForm(SettingsForm):
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['tax_rate_default'].queryset = self.obj.tax_rules.all()
|
||||
|
||||
|
||||
class ProviderForm(SettingsForm):
|
||||
"""
|
||||
@@ -777,3 +790,9 @@ class CommentForm(I18nModelForm):
|
||||
'class': 'helper-width-100',
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class TaxRuleForm(I18nModelForm):
|
||||
class Meta:
|
||||
model = TaxRule
|
||||
fields = ['name', 'rate', 'price_includes_tax', 'eu_reverse_charge', 'home_country']
|
||||
|
||||
@@ -138,6 +138,8 @@ class ItemCreateForm(I18nModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['category'].queryset = self.instance.event.categories.all()
|
||||
self.fields['tax_rule'].queryset = self.instance.event.tax_rules.all()
|
||||
self.fields['tax_rule'].empty_label = _('No taxation')
|
||||
self.fields['copy_from'] = forms.ModelChoiceField(
|
||||
label=_("Copy product information"),
|
||||
queryset=self.event.items.all(),
|
||||
@@ -250,7 +252,7 @@ class ItemCreateForm(I18nModelForm):
|
||||
'category',
|
||||
'admission',
|
||||
'default_price',
|
||||
'tax_rate',
|
||||
'tax_rule',
|
||||
'allow_cancel'
|
||||
]
|
||||
|
||||
@@ -259,6 +261,7 @@ class ItemUpdateForm(I18nModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['category'].queryset = self.instance.event.categories.all()
|
||||
self.fields['tax_rule'].queryset = self.instance.event.tax_rules.all()
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
@@ -272,7 +275,7 @@ class ItemUpdateForm(I18nModelForm):
|
||||
'picture',
|
||||
'default_price',
|
||||
'free_price',
|
||||
'tax_rate',
|
||||
'tax_rule',
|
||||
'available_from',
|
||||
'available_until',
|
||||
'require_voucher',
|
||||
|
||||
@@ -7,7 +7,9 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator
|
||||
from pretix.base.models import Item, ItemAddOn, Order, OrderPosition
|
||||
from pretix.base.models import (
|
||||
InvoiceAddress, Item, ItemAddOn, Order, OrderPosition,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.services.pricing import get_price
|
||||
|
||||
@@ -66,6 +68,22 @@ class SubEventChoiceField(forms.ModelChoiceField):
|
||||
p, self.instance.order.event.currency)
|
||||
|
||||
|
||||
class OtherOperationsForm(forms.Form):
|
||||
recalculate_taxes = forms.BooleanField(
|
||||
label=_('Re-calculate taxes'),
|
||||
required=False,
|
||||
help_text=_(
|
||||
'This operation re-checks if taxes should be paid to the items due to e.g. configured reverse charge rules '
|
||||
'and changes the prices and tax values accordingly. This is useful e.g. after an invoice address change. '
|
||||
'Use with care and only if you need to. Note that rounding differences might occur in this procedure.'
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop('order')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class OrderPositionAddForm(forms.Form):
|
||||
do = forms.BooleanField(
|
||||
label=_('Add a new product to the order'),
|
||||
@@ -83,7 +101,7 @@ class OrderPositionAddForm(forms.Form):
|
||||
required=False,
|
||||
max_digits=10, decimal_places=2,
|
||||
label=_('Gross price'),
|
||||
help_text=_("Keep empty for the product's default price")
|
||||
help_text=_("Including taxes, if any. Keep empty for the product's default price")
|
||||
)
|
||||
subevent = forms.ModelChoiceField(
|
||||
SubEvent.objects.none(),
|
||||
@@ -95,6 +113,12 @@ class OrderPositionAddForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
order = kwargs.pop('order')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
try:
|
||||
ia = order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
|
||||
choices = []
|
||||
for i in order.event.items.prefetch_related('variations').all():
|
||||
pname = str(i.name)
|
||||
@@ -103,12 +127,12 @@ class OrderPositionAddForm(forms.Form):
|
||||
variations = list(i.variations.all())
|
||||
if variations:
|
||||
for v in variations:
|
||||
p = get_price(i, v, invoice_address=ia)
|
||||
choices.append(('%d-%d' % (i.pk, v.pk),
|
||||
'%s – %s (%s %s)' % (pname, v.value, localize(v.price),
|
||||
order.event.currency)))
|
||||
'%s – %s (%s %s)' % (pname, v.value, p, order.event.currency)))
|
||||
else:
|
||||
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(i.default_price),
|
||||
order.event.currency)))
|
||||
p = get_price(i, invoice_address=ia)
|
||||
choices.append((str(i.pk), '%s (%s %s)' % (pname, p, order.event.currency)))
|
||||
self.fields['itemvar'].choices = choices
|
||||
if ItemAddOn.objects.filter(base_item__event=order.event).exists():
|
||||
self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related(
|
||||
@@ -150,6 +174,12 @@ class OrderPositionChangeForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.pop('instance')
|
||||
initial = kwargs.get('initial', {})
|
||||
|
||||
try:
|
||||
ia = instance.order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
|
||||
if instance:
|
||||
try:
|
||||
if instance.variation:
|
||||
@@ -159,7 +189,10 @@ class OrderPositionChangeForm(forms.Form):
|
||||
except Item.DoesNotExist:
|
||||
pass
|
||||
|
||||
initial['price'] = instance.price
|
||||
if instance.item.tax_rule and not instance.item.tax_rule.price_includes_tax:
|
||||
initial['price'] = instance.price - instance.tax_value
|
||||
else:
|
||||
initial['price'] = instance.price
|
||||
initial['subevent'] = instance.subevent
|
||||
|
||||
kwargs['initial'] = initial
|
||||
@@ -169,20 +202,24 @@ class OrderPositionChangeForm(forms.Form):
|
||||
self.fields['subevent'].queryset = instance.order.event.subevents.all()
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
|
||||
choices = []
|
||||
for i in instance.order.event.items.prefetch_related('variations').all():
|
||||
pname = str(i.name)
|
||||
if not i.is_available():
|
||||
pname += ' ({})'.format(_('inactive'))
|
||||
variations = list(i.variations.all())
|
||||
|
||||
if variations:
|
||||
for v in variations:
|
||||
p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent)
|
||||
p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent,
|
||||
invoice_address=ia)
|
||||
choices.append(('%d-%d' % (i.pk, v.pk),
|
||||
'%s – %s (%s %s)' % (pname, v.value, localize(p),
|
||||
instance.order.event.currency)))
|
||||
else:
|
||||
p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent)
|
||||
p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent,
|
||||
invoice_address=ia)
|
||||
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(p),
|
||||
instance.order.event.currency)))
|
||||
self.fields['itemvar'].choices = choices
|
||||
|
||||
@@ -146,6 +146,9 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.question.added': _('The question has been added.'),
|
||||
'pretix.event.question.deleted': _('The question has been deleted.'),
|
||||
'pretix.event.question.changed': _('The question has been modified.'),
|
||||
'pretix.event.taxrule.added': _('The tax rule has been added.'),
|
||||
'pretix.event.taxrule.deleted': _('The tax rule has been deleted.'),
|
||||
'pretix.event.taxrule.changed': _('The tax rule has been changed.'),
|
||||
'pretix.event.settings': _('The event settings have been changed.'),
|
||||
'pretix.event.tickets.settings': _('The ticket download settings have been changed.'),
|
||||
'pretix.event.plugins.enabled': _('A plugin has been enabled.'),
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
{% bootstrap_field form.invoice_address_asked layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_address_required layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_name_required layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_generate layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_address_vatid layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_numbers_consecutive layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_numbers_prefix layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_generate layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_renderer layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_language layout="horizontal" %}
|
||||
{% bootstrap_field form.invoice_include_free layout="horizontal" %}
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
{% trans "Email" %}
|
||||
</a>
|
||||
</li>
|
||||
<li {% if "event.settings.tax" in url_name %}class="active"{% endif %}>
|
||||
<a href="{% url 'control:event.settings.tax' organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
{% trans "Tax rules" %}
|
||||
</a>
|
||||
</li>
|
||||
<li {% if "event.settings.invoice" == url_name %}class="active"{% endif %}>
|
||||
<a href="{% url 'control:event.settings.invoice' organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
{% trans "Invoicing" %}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{% extends "pretixcontrol/event/settings_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Delete tax rule" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<legend>{% trans "Delete tax rule" %}</legend>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% if possible %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete the tax rule <strong>{{ taxrule }}</strong>?{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p>{% blocktrans %}You cannot delete a tax rule that is in use for a product or has been in use for any existing orders.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:event.settings.tax" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn
|
||||
btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
{% if possible %}
|
||||
<button type="submit" class="btn btn-danger btn-save">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,41 @@
|
||||
{% extends "pretixcontrol/event/settings_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}
|
||||
{% if rule %}
|
||||
{% blocktrans with name=rule.name %}Tax rule: {{ name }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "Tax rule" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block inside %}
|
||||
{% if rule %}
|
||||
<legend>{% blocktrans with name=rule.name %}Tax rule: {{ name }}{% endblocktrans %}</legend>
|
||||
{% else %}
|
||||
<legend>{% trans "Tax rule" %}</legend>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.rate layout="horizontal" %}
|
||||
<legend>{% trans "Advanced settings" %}</legend>
|
||||
<div class="alert alert-warning">
|
||||
<span class="fa fa-w fa-legal fa-4x pull-left"></span>
|
||||
{% blocktrans trimmed with docs="https://docs.pretix.eu/en/latest/user/events/taxes.html" %}
|
||||
These settings are intended for advanced users. See the <a href="{{ docs }}">documentation</a>
|
||||
for more information. Note that we are not responsible for the correct handling
|
||||
of taxes in your ticket shop. If in doubt, please contact a lawyer or tax consultant.
|
||||
{% endblocktrans %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% bootstrap_field form.price_includes_tax layout="horizontal" %}
|
||||
{% bootstrap_field form.eu_reverse_charge layout="horizontal" %}
|
||||
{% bootstrap_field form.home_country layout="horizontal" %}
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,51 @@
|
||||
{% extends "pretixcontrol/event/settings_base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Tax rules" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<legend>{% trans "Tax rules" %}</legend>
|
||||
{% if taxrules|length == 0 %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You haven't created any tax rules yet.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
<a href="{% url "control:event.settings.tax.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new tax rule" %}
|
||||
</a>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Rate" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tr in taxrules %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong><a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}">
|
||||
{{ tr.name }}
|
||||
</a></strong>
|
||||
</td>
|
||||
<td>{{ tr.rate }} %</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.settings.tax.edit" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.settings.tax.delete" organizer=request.event.organizer.slug event=request.event.slug rule=tr.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
@@ -33,6 +33,7 @@
|
||||
{% bootstrap_field form.date_to layout="horizontal" %}
|
||||
{% bootstrap_field form.location layout="horizontal" %}
|
||||
{% bootstrap_field form.currency layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Display settings" %}</legend>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Price settings" %}</legend>
|
||||
{% bootstrap_field form.default_price layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rule layout="horizontal" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Price settings" %}</legend>
|
||||
{% bootstrap_field form.default_price layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rate layout="horizontal" %}
|
||||
{% bootstrap_field form.tax_rule layout="horizontal" %}
|
||||
{% bootstrap_field form.free_price layout="horizontal" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -103,8 +103,18 @@
|
||||
{% if position.form.operation.value == "price" %}checked="checked"{% endif %}>
|
||||
{% trans "Change price to" %}
|
||||
{% bootstrap_field position.form.price layout='inline' %}
|
||||
{% if request.event.settings.display_net_prices %}
|
||||
<em>{% trans "Enter a gross price including taxes." %}</em>
|
||||
{% if position.apply_tax %}
|
||||
{% if position.item.tax_rule and not position.item.tax_rule.price_includes_tax %}
|
||||
{% blocktrans trimmed with rate=position.item.tax_rule.rate name=position.item.tax_rule.name %}
|
||||
<strong>plus</strong> {{ rate }}% {{ name }}
|
||||
{% endblocktrans %}
|
||||
{% elif position.item.tax_rule %}
|
||||
{% blocktrans trimmed with rate=position.item.tax_rule.rate name=position.item.tax_rule.name %}
|
||||
<strong>incl.</strong> {{ rate }}% {{ name }}
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans "no taxes apply" %}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
@@ -150,6 +160,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default items">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Other operations
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
{% bootstrap_form_errors other_form %}
|
||||
{% if other_form.custom_error %}
|
||||
<div class="alert alert-danger">
|
||||
{{ other_form.custom_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field other_form.recalculate_taxes layout='horizontal' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<a class="btn btn-default btn-lg"
|
||||
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
{% endblocktrans %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% blocktrans asvar s_taxes %}taxes{% endblocktrans %}
|
||||
<h1>
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
Order details: {{ code }}
|
||||
@@ -239,16 +240,22 @@
|
||||
{% if event.settings.display_net_prices %}
|
||||
<strong>{{ event.currency }} {{ line.net_price|floatformat:2 }}</strong>
|
||||
{% if line.tax_rate %}
|
||||
<br /><small>{% blocktrans trimmed with rate=line.tax_rate %}
|
||||
<strong>plus</strong> {{ rate }}% taxes
|
||||
{% endblocktrans %}</small>
|
||||
<br />
|
||||
<small>
|
||||
{% blocktrans trimmed with rate=line.tax_rate taxname=line.tax_rule.name|default:s_taxes %}
|
||||
<strong>plus</strong> {{ rate }}% {{ taxname }}
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<strong>{{ event.currency }} {{ line.price|floatformat:2 }}</strong>
|
||||
{% if line.tax_rate %}
|
||||
<br /><small>{% blocktrans trimmed with rate=line.tax_rate %}
|
||||
incl. {{ rate }}% taxes
|
||||
{% endblocktrans %}</small>
|
||||
<br />
|
||||
<small>
|
||||
{% blocktrans trimmed with rate=line.tax_rate taxname=line.tax_rule.name|default:s_taxes %}
|
||||
incl. {{ rate }}% {{ taxname }}
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -265,17 +272,21 @@
|
||||
<strong>{{ event.currency }} {{ order.payment_fee_net|floatformat:2 }}</strong>
|
||||
{% if order.payment_fee_tax_rate %}
|
||||
<br/>
|
||||
<small>{% blocktrans trimmed with rate=order.payment_fee_tax_rate %}
|
||||
<strong>plus</strong> {{ rate }}% taxes
|
||||
{% endblocktrans %}</small>
|
||||
<small>
|
||||
{% blocktrans trimmed with rate=order.payment_fee_tax_rate taxname=order.payment_fee_tax_rule.name|default:s_taxes %}
|
||||
<strong>plus</strong> {{ rate }}% {{ taxname }}
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<strong>{{ event.currency }} {{ items.payment_fee|floatformat:2 }}</strong>
|
||||
{% if order.payment_fee_tax_rate %}
|
||||
<br/>
|
||||
<small>{% blocktrans trimmed with rate=order.payment_fee_tax_rate %}
|
||||
incl. {{ rate }}% taxes
|
||||
{% endblocktrans %}</small>
|
||||
<small>
|
||||
{% blocktrans trimmed with rate=order.payment_fee_tax_rate taxname=order.payment_fee_tax_rule.name|default:s_taxes %}
|
||||
incl. {{ rate }}% {{ taxname }}
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -358,7 +369,20 @@
|
||||
<dd>{{ order.invoice_address.country.name|default:order.invoice_address.country_old }}</dd>
|
||||
{% if request.event.settings.invoice_address_vatid %}
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>{{ order.invoice_address.vat_id }}</dd>
|
||||
<dd>
|
||||
{{ order.invoice_address.vat_id }}
|
||||
{% if order.invoice_address.vat_id_validated %}
|
||||
<span class="fa fa-check" data-toggle="tooltip" title="{% blocktrans trimmed %}Valid EU VAT ID{% endblocktrans %}"></span>
|
||||
{% else %}
|
||||
<form class="form-inline helper-display-inline" method="post"
|
||||
action="{% url "control:event.order.checkvatid" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default btn-xs">
|
||||
{% trans "Check" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +69,10 @@ urlpatterns = [
|
||||
url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
|
||||
url(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'),
|
||||
url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),
|
||||
url(r'^settings/tax/$', event.TaxList.as_view(), name='event.settings.tax'),
|
||||
url(r'^settings/tax/(?P<rule>\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'),
|
||||
url(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'),
|
||||
url(r'^settings/tax/(?P<rule>\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'),
|
||||
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
|
||||
url(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
|
||||
url(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
|
||||
@@ -131,6 +135,8 @@ urlpatterns = [
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/answer/(?P<answer>[^/]+)/$',
|
||||
orders.AnswerDownload.as_view(),
|
||||
name='event.order.download.answer'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/checkvatid', orders.OrderCheckVATID.as_view(),
|
||||
name='event.order.checkvatid'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
||||
name='event.order.extend'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(),
|
||||
|
||||
@@ -9,22 +9,24 @@ from django.core.files import File
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse,
|
||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
|
||||
JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils import translation
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import FormView, ListView
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.views.generic import DeleteView, FormView, ListView
|
||||
from django.views.generic.base import TemplateView, View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from pytz import timezone
|
||||
|
||||
from pretix.base.models import (
|
||||
CachedTicket, Event, Item, ItemVariation, LogEntry, Order, RequiredAction,
|
||||
Voucher,
|
||||
CachedTicket, Event, Item, ItemVariation, LogEntry, Order, OrderPosition,
|
||||
RequiredAction, TaxRule, Voucher,
|
||||
)
|
||||
from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import build_preview_invoice_pdf
|
||||
@@ -32,13 +34,13 @@ from pretix.base.signals import event_live_issues, register_ticket_outputs
|
||||
from pretix.control.forms.event import (
|
||||
CommentForm, DisplaySettingsForm, EventSettingsForm, EventUpdateForm,
|
||||
InvoiceSettingsForm, MailSettingsForm, PaymentSettingsForm, ProviderForm,
|
||||
TicketSettingsForm,
|
||||
TaxRuleForm, TicketSettingsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.presale.style import regenerate_css
|
||||
|
||||
from . import UpdateView
|
||||
from . import CreateView, UpdateView
|
||||
from ..logdisplay import OVERVIEW_BLACKLIST
|
||||
|
||||
|
||||
@@ -787,3 +789,129 @@ class EventComment(EventPermissionRequiredMixin, View):
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug
|
||||
})
|
||||
|
||||
|
||||
class TaxList(EventPermissionRequiredMixin, ListView):
|
||||
model = TaxRule
|
||||
context_object_name = 'taxrules'
|
||||
paginate_by = 30
|
||||
template_name = 'pretixcontrol/event/tax_index.html'
|
||||
permission = 'can_change_event_settings'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.tax_rules.all()
|
||||
|
||||
|
||||
class TaxCreate(EventPermissionRequiredMixin, CreateView):
|
||||
model = TaxRule
|
||||
form_class = TaxRuleForm
|
||||
template_name = 'pretixcontrol/event/tax_edit.html'
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'taxrule'
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.settings.tax', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
def get_initial(self):
|
||||
return {
|
||||
'name': LazyI18nString.from_gettext(ugettext('VAT'))
|
||||
}
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.event = self.request.event
|
||||
messages.success(self.request, _('The new tax rule has been created.'))
|
||||
ret = super().form_valid(form)
|
||||
form.instance.log_action('pretix.event.taxrule.added', user=self.request.user, data=dict(form.cleaned_data))
|
||||
return ret
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class TaxUpdate(EventPermissionRequiredMixin, UpdateView):
|
||||
model = TaxRule
|
||||
form_class = TaxRuleForm
|
||||
template_name = 'pretixcontrol/event/tax_edit.html'
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'rule'
|
||||
|
||||
def get_object(self, queryset=None) -> TaxRule:
|
||||
try:
|
||||
return self.request.event.tax_rules.get(
|
||||
id=self.kwargs['rule']
|
||||
)
|
||||
except TaxRule.DoesNotExist:
|
||||
raise Http404(_("The requested tax rule does not exist."))
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
if form.has_changed():
|
||||
self.object.log_action(
|
||||
'pretix.event.taxrule.changed', user=self.request.user, data={
|
||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||
}
|
||||
)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.settings.tax', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class TaxDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
model = TaxRule
|
||||
template_name = 'pretixcontrol/event/tax_delete.html'
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'taxrule'
|
||||
|
||||
def get_object(self, queryset=None) -> TaxRule:
|
||||
try:
|
||||
return self.request.event.tax_rules.get(
|
||||
id=self.kwargs['rule']
|
||||
)
|
||||
except TaxRule.DoesNotExist:
|
||||
raise Http404(_("The requested tax rule does not exist."))
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
if self.is_allowed():
|
||||
self.object.log_action(action='pretix.event.taxrule.deleted', user=request.user)
|
||||
self.object.delete()
|
||||
messages.success(self.request, _('The selected tax rule has been deleted.'))
|
||||
else:
|
||||
messages.error(self.request, _('The selected tax rule can not be deleted.'))
|
||||
return redirect(success_url)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.settings.tax', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
def is_allowed(self) -> bool:
|
||||
o = self.object
|
||||
return (
|
||||
not self.request.event.orders.filter(payment_fee_tax_rule=o).exists()
|
||||
and not OrderPosition.objects.filter(tax_rule=o, order__event=self.request.event).exists()
|
||||
and not self.request.event.items.filter(tax_rule=o).exists()
|
||||
and self.request.event.settings.tax_rate_default != o
|
||||
)
|
||||
|
||||
def get_context_data(self, *args, **kwargs) -> dict:
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['possible'] = self.is_allowed()
|
||||
return context
|
||||
|
||||
@@ -775,6 +775,13 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
|
||||
'item': self.object.id,
|
||||
})
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
trs = list(self.request.event.tax_rules.all())
|
||||
if len(trs) == 1:
|
||||
initial['tax_rule'] = trs[0]
|
||||
return initial
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
|
||||
@@ -6,10 +6,11 @@ from django.http import JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.generic import ListView
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.models import Event, Team
|
||||
from pretix.control.forms.event import (
|
||||
@@ -118,6 +119,12 @@ class EventWizard(SessionWizardView):
|
||||
active=True
|
||||
)
|
||||
|
||||
if basics_data['tax_rate']:
|
||||
event.settings.tax_rate_default = event.tax_rules.create(
|
||||
name=LazyI18nString.from_gettext(ugettext('VAT')),
|
||||
rate=basics_data['tax_rate']
|
||||
)
|
||||
|
||||
logdata = {}
|
||||
for f in form_list:
|
||||
logdata.update({
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
import pytz
|
||||
import vat_moss.id
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -25,6 +27,7 @@ from pretix.base.models import (
|
||||
generate_position_secret, generate_secret,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.tax import EU_COUNTRIES
|
||||
from pretix.base.services.export import export
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
|
||||
@@ -42,12 +45,15 @@ from pretix.control.forms.filter import EventOrderFilterForm
|
||||
from pretix.control.forms.orders import (
|
||||
CommentForm, ExporterForm, ExtendForm, OrderContactForm, OrderLocaleForm,
|
||||
OrderMailForm, OrderPositionAddForm, OrderPositionChangeForm,
|
||||
OtherOperationsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrderList(EventPermissionRequiredMixin, ListView):
|
||||
model = Order
|
||||
@@ -144,7 +150,7 @@ class OrderDetail(OrderView):
|
||||
cartpos = queryset.order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation', 'addon_to'
|
||||
'item', 'variation', 'addon_to', 'tax_rule'
|
||||
).prefetch_related(
|
||||
'item__questions', 'answers', 'answers__question', 'checkins'
|
||||
).order_by('positionid')
|
||||
@@ -277,6 +283,54 @@ class OrderInvoiceCreate(OrderView):
|
||||
return HttpResponseNotAllowed(['POST'])
|
||||
|
||||
|
||||
class OrderCheckVATID(OrderView):
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
try:
|
||||
ia = self.order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
messages.error(self.request, _('No VAT ID specified.'))
|
||||
return redirect(self.get_order_url())
|
||||
else:
|
||||
if not ia.vat_id:
|
||||
messages.error(self.request, _('No VAT ID specified.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
if not ia.country:
|
||||
messages.error(self.request, _('No country specified.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
if str(ia.country) not in EU_COUNTRIES:
|
||||
messages.error(self.request, _('VAT ID could not be checked since a non-EU country has been '
|
||||
'specified.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
if ia.vat_id[:2] != str(ia.country):
|
||||
messages.error(self.request, _('Your VAT ID does not match the selected country.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
result = vat_moss.id.validate(ia.vat_id)
|
||||
if result:
|
||||
country_code, normalized_id, company_name = result
|
||||
ia.vat_id_validated = True
|
||||
ia.vat_id = normalized_id
|
||||
ia.save()
|
||||
except vat_moss.errors.InvalidError:
|
||||
messages.error(self.request, _('This VAT ID is not valid.'))
|
||||
except vat_moss.errors.WebServiceUnavailableError:
|
||||
logger.exception('VAT ID checking failed for country {}'.format(ia.country))
|
||||
messages.error(self.request, _('The VAT ID could not be checked, as the VAT checking service of '
|
||||
'the country is currently not available.'))
|
||||
else:
|
||||
messages.success(self.request, _('This VAT ID is valid.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
def get(self, *args, **kwargs): # NOQA
|
||||
return HttpResponseNotAllowed(['POST'])
|
||||
|
||||
|
||||
class OrderInvoiceRegenerate(OrderView):
|
||||
permission = 'can_change_orders'
|
||||
|
||||
@@ -458,6 +512,11 @@ class OrderChange(OrderView):
|
||||
return self._redirect_back()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def other_form(self):
|
||||
return OtherOperationsForm(prefix='other', order=self.order,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
|
||||
@cached_property
|
||||
def add_form(self):
|
||||
return OrderPositionAddForm(prefix='add', order=self.order,
|
||||
@@ -469,14 +528,28 @@ class OrderChange(OrderView):
|
||||
for p in positions:
|
||||
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
try:
|
||||
ia = self.order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
p.apply_tax = p.item.tax_rule and p.item.tax_rule.tax_applicable(invoice_address=ia)
|
||||
return positions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['positions'] = self.positions
|
||||
ctx['add_form'] = self.add_form
|
||||
ctx['other_form'] = self.other_form
|
||||
return ctx
|
||||
|
||||
def _process_other(self, ocm):
|
||||
if not self.other_form.is_valid():
|
||||
return False
|
||||
else:
|
||||
if self.other_form.cleaned_data['recalculate_taxes']:
|
||||
ocm.recalculate_taxes()
|
||||
return True
|
||||
|
||||
def _process_add(self, ocm):
|
||||
if not self.add_form.is_valid():
|
||||
return False
|
||||
@@ -534,7 +607,7 @@ class OrderChange(OrderView):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
ocm = OrderChangeManager(self.order, self.request.user)
|
||||
form_valid = self._process_add(ocm) and self._process_change(ocm)
|
||||
form_valid = self._process_add(ocm) and self._process_change(ocm) and self._process_other(ocm)
|
||||
|
||||
if not form_valid:
|
||||
messages.error(self.request, _('An error occured. Please see the details below.'))
|
||||
|
||||
Reference in New Issue
Block a user