pretix-servicefees/pretix_servicefees/signals.py

228 lines
9.4 KiB
Python
Raw Permalink Normal View History

2022-09-19 17:33:27 +02:00
from collections import defaultdict
2018-02-27 23:12:01 +01:00
from decimal import Decimal
from django.dispatch import receiver
from django.http import HttpRequest
2022-09-19 17:33:51 +02:00
from django.urls import resolve, reverse
2024-02-14 11:42:48 +01:00
from django.utils.translation import gettext, gettext_lazy as _
from pretix.base.decimal import round_decimal
2024-02-14 11:42:48 +01:00
from pretix.base.models import CartPosition, Event, TaxRule
2018-02-27 23:12:01 +01:00
from pretix.base.models.orders import OrderFee
from pretix.base.settings import settings_hierarkey
2018-02-27 23:12:01 +01:00
from pretix.base.signals import order_fee_calculation
2018-03-03 21:06:14 +01:00
from pretix.base.templatetags.money import money_filter
2018-02-27 23:12:01 +01:00
from pretix.control.signals import nav_event_settings
2022-09-19 17:33:51 +02:00
from pretix.presale.signals import (
fee_calculation_for_cart, front_page_top, order_meta_from_request,
)
2019-05-20 14:54:27 +02:00
from pretix.presale.views import get_cart
from pretix.presale.views.cart import cart_session
2018-02-27 23:12:01 +01:00
@receiver(nav_event_settings, dispatch_uid='service_fee_nav_settings')
def navbar_settings(sender, request, **kwargs):
url = resolve(request.path_info)
return [{
'label': _('Service Fee'),
'url': reverse('plugins:pretix_servicefees:settings', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
}),
'active': url.namespace == 'plugins:pretix_servicefees' and url.url_name.startswith('settings'),
}]
def get_fees(event, total, invoice_address, mod='', request=None, positions=[], gift_cards=None, payment_requests=None):
2019-05-20 14:54:27 +02:00
if request is not None and not positions:
positions = get_cart(request)
skip_free = event.settings.get('service_fee_skip_free', as_type=bool)
if skip_free:
positions = [pos for pos in positions if pos.price != Decimal('0.00')]
2022-03-18 14:53:51 +01:00
skip_addons = event.settings.get('service_fee_skip_addons', as_type=bool)
if skip_addons:
positions = [pos for pos in positions if not pos.addon_to_id]
2019-05-20 14:54:27 +02:00
skip_non_admission = event.settings.get('service_fee_skip_non_admission', as_type=bool)
if skip_non_admission:
positions = [pos for pos in positions if pos.item.admission]
2019-05-20 14:54:27 +02:00
fee_per_ticket = event.settings.get('service_fee_per_ticket' + mod, as_type=Decimal)
if mod and fee_per_ticket is None:
fee_per_ticket = event.settings.get('service_fee_per_ticket', as_type=Decimal)
2019-04-03 14:58:57 +02:00
fee_abs = event.settings.get('service_fee_abs' + mod, as_type=Decimal)
if mod and fee_abs is None:
fee_abs = event.settings.get('service_fee_abs', as_type=Decimal)
fee_percent = event.settings.get('service_fee_percent' + mod, as_type=Decimal)
if mod and fee_percent is None:
fee_percent = event.settings.get('service_fee_percent', as_type=Decimal)
2019-05-20 14:54:27 +02:00
fee_per_ticket = Decimal("0") if fee_per_ticket is None else fee_per_ticket
fee_abs = Decimal("0") if fee_abs is None else fee_abs
fee_percent = Decimal("0") if fee_percent is None else fee_percent
if event.settings.get('service_fee_skip_if_gift_card', as_type=bool):
if payment_requests is not None:
for p in payment_requests:
if p['provider'] == 'giftcard':
total = max(0, total - Decimal(p['max_value'] or '0'))
else:
# pretix pre 4.15
gift_cards = gift_cards or []
if request:
cs = cart_session(request)
if cs.get('gift_cards'):
gift_cards = event.organizer.accepted_gift_cards.filter(pk__in=cs.get('gift_cards'), currency=event.currency)
summed = 0
for gc in gift_cards:
fval = Decimal(gc.value) # TODO: don't require an extra query
fval = min(fval, total - summed)
if fval > 0:
total -= fval
summed += fval
# This hack allows any payment provider to declare a setting service_fee_skip_if_{pprovname} in order to implement
# the skipping of service fees when they are paid in their entirety through said payment provider/method.
# It is notably used by the KulturPass, which is for all intents and purposes a giftcard but handled like a regular
# payment provider.
if payment_requests is not None:
for p in payment_requests:
if event.settings.get(f'service_fee_skip_if_{p["provider"]}', default=False, as_type=bool):
total = max(0, total - Decimal(p['max_value'] or '0'))
2019-05-20 14:54:27 +02:00
if (fee_per_ticket or fee_abs or fee_percent) and total != Decimal('0.00'):
2022-09-19 17:33:27 +02:00
tax_rule_zero = TaxRule.zero()
2019-05-20 14:54:27 +02:00
fee = round_decimal(fee_abs + total * (fee_percent / 100) + len(positions) * fee_per_ticket, event.currency)
2022-09-19 17:33:27 +02:00
split_taxes = event.settings.get('service_fee_split_taxes', as_type=bool)
if split_taxes:
# 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,
internal_type='',
value=price,
tax_rate=tax.rate,
tax_value=tax.tax,
tax_rule=tax_rule
))
return fees
2018-02-27 23:12:01 +01:00
return []
@receiver(fee_calculation_for_cart, dispatch_uid="service_fee_calc_cart")
def cart_fee(sender: Event, request: HttpRequest, invoice_address, total, **kwargs):
2018-10-25 14:26:21 +02:00
mod = ''
try:
2022-09-19 17:33:51 +02:00
from pretix_resellers.utils import (
ResellerException, get_reseller_and_user,
)
2018-10-25 14:26:21 +02:00
except ImportError:
pass
else:
try:
reseller, user = get_reseller_and_user(request)
2020-09-26 19:14:21 +02:00
config = reseller.configs.get(organizer_id=sender.organizer_id)
if config.skip_default_service_fees:
return []
2018-10-25 14:26:21 +02:00
except ResellerException:
pass
else:
mod = '_resellers'
return get_fees(sender, total, invoice_address, mod, request, payment_requests=kwargs.get('payment_requests'))
2018-02-27 23:12:01 +01:00
@receiver(order_fee_calculation, dispatch_uid="service_fee_calc_order")
2020-04-02 09:51:11 +02:00
def order_fee(sender: Event, positions, invoice_address, total, meta_info, gift_cards, **kwargs):
2018-10-25 14:26:21 +02:00
mod = ''
if meta_info.get('servicefees_reseller_id'):
mod = '_resellers'
try:
from pretix_resellers.models import Reseller
except ImportError:
pass
else:
r = Reseller.objects.get(pk=meta_info.get('servicefees_reseller_id'))
2020-09-26 19:14:21 +02:00
config = r.configs.get(organizer_id=sender.organizer_id)
if config.skip_default_service_fees:
return []
return get_fees(sender, total, invoice_address, mod, positions=positions, gift_cards=gift_cards,
payment_requests=kwargs.get('payment_requests'))
2018-03-03 21:06:14 +01:00
@receiver(front_page_top, dispatch_uid="service_fee_front_page_top")
def front_page_top_recv(sender: Event, **kwargs):
2019-04-03 17:08:41 +02:00
fees = []
2019-05-20 14:54:27 +02:00
fee_per_ticket = sender.settings.get('service_fee_per_ticket', as_type=Decimal)
if fee_per_ticket:
2020-03-09 14:12:09 +01:00
fees = fees + ["{} {}".format(money_filter(fee_per_ticket, sender.currency), gettext('per ticket'))]
2019-05-20 14:54:27 +02:00
2019-04-03 17:08:41 +02:00
fee_abs = sender.settings.get('service_fee_abs', as_type=Decimal)
if fee_abs:
2020-03-09 14:12:09 +01:00
fees = fees + ["{} {}".format(money_filter(fee_abs, sender.currency), gettext('per order'))]
2019-04-03 17:08:41 +02:00
fee_percent = sender.settings.get('service_fee_percent', as_type=Decimal)
if fee_percent:
2020-03-09 14:12:09 +01:00
fees = fees + ['{} % {}'.format(fee_percent, gettext('per order'))]
2019-04-03 17:08:41 +02:00
2019-05-20 14:54:27 +02:00
if fee_per_ticket or fee_abs or fee_percent:
2024-02-14 11:42:48 +01:00
return '<p>%s</p>' % gettext('A service fee of {} will be added to the order total.').format(
2020-03-09 14:12:09 +01:00
' {} '.format(gettext('plus')).join(fees)
2018-03-03 21:26:24 +01:00
)
2018-10-25 14:26:21 +02:00
@receiver(order_meta_from_request, dispatch_uid="servicefees_order_meta")
def order_meta_signal(sender: Event, request: HttpRequest, **kwargs):
meta = {}
try:
2022-09-19 17:33:51 +02:00
from pretix_resellers.utils import (
ResellerException, get_reseller_and_user,
)
2018-10-25 14:26:21 +02:00
except ImportError:
pass
else:
try:
reseller, user = get_reseller_and_user(request)
2018-10-25 14:26:21 +02:00
meta['servicefees_reseller_id'] = reseller.pk
except ResellerException:
pass
return meta
2022-03-18 14:53:51 +01:00
settings_hierarkey.add_default('service_fee_skip_addons', 'True', bool)
settings_hierarkey.add_default('service_fee_skip_free', 'True', bool)