Add tax splitting

This commit is contained in:
Richard Schreiber 2022-09-20 09:28:10 +02:00 committed by GitHub
commit 8343230a96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 17 deletions

View file

@ -1,17 +1,19 @@
from collections import defaultdict
from decimal import Decimal from decimal import Decimal
from django.urls import resolve, reverse
from django.dispatch import receiver from django.dispatch import receiver
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _, gettext, get_language from django.urls import resolve, reverse
from django.utils.translation import get_language, gettext, gettext_lazy as _
from pretix.base.decimal import round_decimal from pretix.base.decimal import round_decimal
from pretix.base.models import Event, Order, TaxRule from pretix.base.models import CartPosition, Event, Order, TaxRule
from pretix.base.models.orders import OrderFee from pretix.base.models.orders import OrderFee
from pretix.base.settings import settings_hierarkey from pretix.base.settings import settings_hierarkey
from pretix.base.signals import order_fee_calculation from pretix.base.signals import order_fee_calculation
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
from pretix.control.signals import nav_event_settings from pretix.control.signals import nav_event_settings
from pretix.presale.signals import fee_calculation_for_cart, front_page_top, order_meta_from_request from pretix.presale.signals import (
fee_calculation_for_cart, front_page_top, order_meta_from_request,
)
from pretix.presale.views import get_cart from pretix.presale.views import get_cart
from pretix.presale.views.cart import cart_session from pretix.presale.views.cart import cart_session
@ -76,17 +78,52 @@ def get_fees(event, total, invoice_address, mod='', request=None, positions=[],
summed += fval summed += fval
if (fee_per_ticket or fee_abs or fee_percent) and total != Decimal('0.00'): if (fee_per_ticket or fee_abs or fee_percent) and total != Decimal('0.00'):
tax_rule_zero = TaxRule.zero()
fee = round_decimal(fee_abs + total * (fee_percent / 100) + len(positions) * fee_per_ticket, event.currency) fee = round_decimal(fee_abs + total * (fee_percent / 100) + len(positions) * fee_per_ticket, event.currency)
tax_rule = event.settings.tax_rate_default or TaxRule.zero() split_taxes = event.settings.get('service_fee_split_taxes', as_type=bool)
tax = tax_rule.tax(fee, invoice_address=invoice_address, base_price_is='gross') if split_taxes:
return [OrderFee( # split taxes based on products ordered
d = defaultdict(lambda: Decimal('0.00'))
for p in positions:
if isinstance(p, CartPosition):
tr = p.item.tax_rule
else:
tr = p.tax_rule
d[tr] += p.price - p.tax_value
base_values = sorted([tuple(t) for t in d.items()], key=lambda t: t[0].rate)
sum_base = sum(t[1] for t in base_values)
if sum_base:
fee_values = [(t[0], round_decimal(fee * t[1] / sum_base, event.currency))
for t in base_values]
sum_fee = sum(t[1] for t in fee_values)
# If there are rounding differences, we fix them up, but always leaning to the benefit of the tax
# authorities
if sum_fee > fee:
fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee - sum_fee))
elif sum_fee < fee:
fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee - sum_fee))
else:
fee_values = [(event.settings.tax_rate_default or tax_rule_zero, fee)]
else:
fee_values = [(event.settings.tax_rate_default or tax_rule_zero, fee)]
fees = []
for tax_rule, price in fee_values:
tax_rule = tax_rule or tax_rule_zero
tax = tax_rule.tax(price, invoice_address=invoice_address, base_price_is='gross')
fees.append(OrderFee(
fee_type=OrderFee.FEE_TYPE_SERVICE, fee_type=OrderFee.FEE_TYPE_SERVICE,
internal_type='', internal_type='',
value=fee, value=price,
tax_rate=tax.rate, tax_rate=tax.rate,
tax_value=tax.tax, tax_value=tax.tax,
tax_rule=tax_rule tax_rule=tax_rule
)] ))
return fees
return [] return []
@ -94,7 +131,9 @@ def get_fees(event, total, invoice_address, mod='', request=None, positions=[],
def cart_fee(sender: Event, request: HttpRequest, invoice_address, total, **kwargs): def cart_fee(sender: Event, request: HttpRequest, invoice_address, total, **kwargs):
mod = '' mod = ''
try: try:
from pretix_resellers.utils import ResellerException, get_reseller_and_user from pretix_resellers.utils import (
ResellerException, get_reseller_and_user,
)
except ImportError: except ImportError:
pass pass
else: else:
@ -153,7 +192,9 @@ def front_page_top_recv(sender: Event, **kwargs):
def order_meta_signal(sender: Event, request: HttpRequest, **kwargs): def order_meta_signal(sender: Event, request: HttpRequest, **kwargs):
meta = {} meta = {}
try: try:
from pretix_resellers.utils import ResellerException, get_reseller_and_user from pretix_resellers.utils import (
ResellerException, get_reseller_and_user,
)
except ImportError: except ImportError:
pass pass
else: else:

View file

@ -14,6 +14,7 @@
{% bootstrap_field form.service_fee_skip_free layout="control" %} {% bootstrap_field form.service_fee_skip_free layout="control" %}
{% bootstrap_field form.service_fee_abs addon_after=request.event.currency layout="control" %} {% bootstrap_field form.service_fee_abs addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.service_fee_percent addon_after="%" layout="control" %} {% bootstrap_field form.service_fee_percent addon_after="%" layout="control" %}
{% bootstrap_field form.service_fee_split_taxes layout="control" %}
{% bootstrap_field form.service_fee_skip_if_gift_card layout="control" %} {% bootstrap_field form.service_fee_skip_if_gift_card layout="control" %}
</fieldset> </fieldset>
<fieldset> <fieldset>

View file

@ -46,6 +46,11 @@ class ServiceFeeSettingsForm(SettingsForm):
help_text=_('Note that regardless of this setting, a per-ticket fee will not be charged if the entire order is free.'), help_text=_('Note that regardless of this setting, a per-ticket fee will not be charged if the entire order is free.'),
required=False required=False
) )
service_fee_split_taxes = forms.BooleanField(
label=_('Split taxes proportionate to the tax rates and net values of the ordered products.'),
help_text=_('If not split based on ordered products, the tax rate falls back to the events base tax rate or no tax, if none is given.'),
required=False
)
service_fee_abs_resellers = forms.DecimalField( service_fee_abs_resellers = forms.DecimalField(
label=_('Fixed fee per order'), label=_('Fixed fee per order'),