forked from CGM_Public/pretix_original
New concept for fee handling (#610)
* New concept for fee handling * More usages * Remove all usages, make all tests pass * API changes * Small fixes * Fix order of invoice lines * Rebase migration
This commit is contained in:
@@ -61,7 +61,13 @@ class JSONExporter(BaseExporter):
|
||||
'status': order.status,
|
||||
'user': order.email,
|
||||
'datetime': order.datetime,
|
||||
'payment_fee': order.payment_fee,
|
||||
'fees': [
|
||||
{
|
||||
'type': fee.fee_type,
|
||||
'description': fee.description,
|
||||
'value': fee.value,
|
||||
} for fee in order.fees.all()
|
||||
],
|
||||
'total': order.total,
|
||||
'positions': [
|
||||
{
|
||||
@@ -82,7 +88,7 @@ class JSONExporter(BaseExporter):
|
||||
} for position in order.positions.all()
|
||||
]
|
||||
} for order in
|
||||
self.event.orders.all().prefetch_related('positions', 'positions__answers')
|
||||
self.event.orders.all().prefetch_related('positions', 'positions__answers', 'fees')
|
||||
],
|
||||
'quotas': [
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.utils.formats import localize
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from pretix.base.models import InvoiceAddress, Order, OrderPosition
|
||||
from pretix.base.models.orders import OrderFee
|
||||
|
||||
from ..exporter import BaseExporter
|
||||
from ..signals import register_data_exporters
|
||||
@@ -35,7 +36,10 @@ class OrderListExporter(BaseExporter):
|
||||
|
||||
def _get_all_tax_rates(self, qs):
|
||||
tax_rates = set(
|
||||
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True).distinct().order_by()
|
||||
a for a
|
||||
in OrderFee.objects.filter(
|
||||
order__event=self.event
|
||||
).values_list('tax_rate', flat=True).distinct().order_by()
|
||||
)
|
||||
tax_rates |= set(
|
||||
a for a
|
||||
@@ -59,7 +63,7 @@ class OrderListExporter(BaseExporter):
|
||||
headers = [
|
||||
_('Order code'), _('Order total'), _('Status'), _('Email'), _('Order date'),
|
||||
_('Company'), _('Name'), _('Address'), _('ZIP code'), _('City'), _('Country'), _('VAT ID'),
|
||||
_('Payment date'), _('Payment type'), _('Payment method fee'),
|
||||
_('Payment date'), _('Payment type'), _('Fees'),
|
||||
]
|
||||
|
||||
for tr in tax_rates:
|
||||
@@ -78,6 +82,16 @@ class OrderListExporter(BaseExporter):
|
||||
for k, v in self.event.get_payment_providers().items()
|
||||
}
|
||||
|
||||
full_fee_sum_cache = {
|
||||
o['order__id']: o['grosssum'] for o in
|
||||
OrderFee.objects.values('tax_rate', 'order__id').order_by().annotate(grosssum=Sum('value'))
|
||||
}
|
||||
fee_sum_cache = {
|
||||
(o['order__id'], o['tax_rate']): o for o in
|
||||
OrderFee.objects.values('tax_rate', 'order__id').order_by().annotate(
|
||||
taxsum=Sum('tax_value'), grosssum=Sum('value')
|
||||
)
|
||||
}
|
||||
sum_cache = {
|
||||
(o['order__id'], o['tax_rate']): o for o in
|
||||
OrderPosition.objects.values('tax_rate', 'order__id').order_by().annotate(
|
||||
@@ -109,19 +123,18 @@ class OrderListExporter(BaseExporter):
|
||||
row += [
|
||||
order.payment_date.astimezone(tz).strftime('%Y-%m-%d') if order.payment_date else '',
|
||||
provider_names.get(order.payment_provider, order.payment_provider),
|
||||
localize(order.payment_fee)
|
||||
localize(full_fee_sum_cache.get(order.id) or Decimal('0.00'))
|
||||
]
|
||||
|
||||
for tr in tax_rates:
|
||||
taxrate_values = sum_cache.get((order.id, tr), {'grosssum': Decimal('0.00'), 'taxsum': Decimal('0.00')})
|
||||
if tr == order.payment_fee_tax_rate and order.payment_fee_tax_value:
|
||||
taxrate_values['grosssum'] += order.payment_fee
|
||||
taxrate_values['taxsum'] += order.payment_fee_tax_value
|
||||
fee_taxrate_values = fee_sum_cache.get((order.id, tr), {'grosssum': Decimal('0.00'), 'taxsum': Decimal('0.00')})
|
||||
|
||||
row += [
|
||||
localize(taxrate_values['grosssum']),
|
||||
localize(taxrate_values['grosssum'] - taxrate_values['taxsum']),
|
||||
localize(taxrate_values['taxsum']),
|
||||
localize(taxrate_values['grosssum'] + fee_taxrate_values['grosssum']),
|
||||
localize(taxrate_values['grosssum'] - taxrate_values['taxsum']
|
||||
+ fee_taxrate_values['grosssum'] - fee_taxrate_values['taxsum']),
|
||||
localize(taxrate_values['taxsum'] + fee_taxrate_values['taxsum']),
|
||||
]
|
||||
|
||||
row.append(', '.join([i.number for i in order.invoices.all()]))
|
||||
|
||||
72
src/pretix/base/migrations/0076_orderfee.py
Normal file
72
src/pretix/base/migrations/0076_orderfee.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-28 14:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def fee_converter(app, schema_editor):
|
||||
OrderFee = app.get_model('pretixbase', 'OrderFee')
|
||||
Order = app.get_model('pretixbase', 'Order')
|
||||
|
||||
of = []
|
||||
for o in Order.objects.exclude(payment_fee=Decimal('0.00')).iterator():
|
||||
of.append(OrderFee(
|
||||
order=o,
|
||||
value=o.payment_fee,
|
||||
fee_type='payment',
|
||||
tax_rate=o.payment_fee_tax_rate,
|
||||
tax_rule=o.payment_fee_tax_rule,
|
||||
tax_value=o.payment_fee_tax_value,
|
||||
internal_type=o.payment_provider
|
||||
))
|
||||
if len(of) > 900:
|
||||
OrderFee.objects.bulk_create(of)
|
||||
of = []
|
||||
OrderFee.objects.bulk_create(of)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0075_auto_20170828_0901'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrderFee',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Value')),
|
||||
('description', models.CharField(blank=True, max_length=190)),
|
||||
('internal_type', models.CharField(blank=True, max_length=255)),
|
||||
('fee_type', models.CharField(choices=[('payment', 'Payment method fee'), ('shipping', 'Shipping fee')], max_length=100)),
|
||||
('tax_rate', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='Tax rate')),
|
||||
('tax_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Tax value')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='fees', to='pretixbase.Order', verbose_name='Order')),
|
||||
('tax_rule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.TaxRule')),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(
|
||||
fee_converter, migrations.RunPython.noop
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='payment_fee',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='payment_fee_tax_rate',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='payment_fee_tax_rule',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='payment_fee_tax_value',
|
||||
),
|
||||
]
|
||||
41
src/pretix/base/migrations/0077_auto_20170829_1126.py
Normal file
41
src/pretix/base/migrations/0077_auto_20170829_1126.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-29 11:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def assign_positions(app, schema_editor):
|
||||
Invoice = app.get_model('pretixbase', 'Invoice')
|
||||
|
||||
for i in Invoice.objects.iterator():
|
||||
for j, l in enumerate(i.lines.all()):
|
||||
l.position = j
|
||||
l.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0076_orderfee'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invoiceline',
|
||||
name='position',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderfee',
|
||||
name='fee_type',
|
||||
field=models.CharField(choices=[('payment', 'Payment fee'), ('shipping', 'Shipping fee'), ('other', 'Other fees')], max_length=100),
|
||||
),
|
||||
migrations.RunPython(
|
||||
assign_positions, migrations.RunPython.noop
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='invoiceline',
|
||||
options={'ordering': ('position', 'pk')},
|
||||
),
|
||||
]
|
||||
@@ -168,6 +168,7 @@ class InvoiceLine(models.Model):
|
||||
:type tax_name: str
|
||||
"""
|
||||
invoice = models.ForeignKey('Invoice', related_name='lines')
|
||||
position = models.PositiveIntegerField(default=0)
|
||||
description = models.TextField()
|
||||
gross_value = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
tax_value = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
||||
@@ -177,3 +178,6 @@ class InvoiceLine(models.Model):
|
||||
@property
|
||||
def net_value(self):
|
||||
return self.gross_value - self.tax_value
|
||||
|
||||
class Meta:
|
||||
ordering = ('position', 'pk')
|
||||
|
||||
@@ -78,12 +78,6 @@ class Order(LoggedModel):
|
||||
:type payment_date: datetime
|
||||
:param payment_provider: The payment provider selected by the user
|
||||
:type payment_provider: str
|
||||
:param payment_fee: The payment fee calculated at checkout time
|
||||
:type payment_fee: decimal.Decimal
|
||||
:param payment_fee_tax_value: The absolute amount of tax included in the payment fee
|
||||
:type payment_fee_tax_value: decimal.Decimal
|
||||
:param payment_fee_tax_rate: The tax rate applied to the payment fee (in percent)
|
||||
:type payment_fee_tax_rate: decimal.Decimal
|
||||
:param payment_info: Arbitrary information stored by the payment provider
|
||||
:type payment_info: str
|
||||
:param total: The total amount of the order, including the payment fee
|
||||
@@ -149,23 +143,6 @@ class Order(LoggedModel):
|
||||
max_length=255,
|
||||
verbose_name=_("Payment provider")
|
||||
)
|
||||
payment_fee = models.DecimalField(
|
||||
decimal_places=2, max_digits=10,
|
||||
default=0, verbose_name=_("Payment method fee")
|
||||
)
|
||||
payment_fee_tax_rate = models.DecimalField(
|
||||
decimal_places=2, max_digits=10,
|
||||
verbose_name=_("Payment method fee tax rate")
|
||||
)
|
||||
payment_fee_tax_value = models.DecimalField(
|
||||
decimal_places=2, max_digits=10,
|
||||
default=0, verbose_name=_("Payment method fee tax")
|
||||
)
|
||||
payment_fee_tax_rule = models.ForeignKey(
|
||||
'TaxRule',
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
payment_info = models.TextField(
|
||||
verbose_name=_("Payment information"),
|
||||
null=True, blank=True
|
||||
@@ -224,43 +201,11 @@ class Order(LoggedModel):
|
||||
self.assign_code()
|
||||
if not self.datetime:
|
||||
self.datetime = now()
|
||||
if self.payment_fee_tax_rate is None:
|
||||
self._calculate_tax()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def _calculate_tax(self):
|
||||
"""
|
||||
Calculates the taxes on the payment fees and sets the parameters payment_fee_tax_rate
|
||||
and payment_fee_tax_value accordingly.
|
||||
"""
|
||||
if self.event.settings.tax_rate_default:
|
||||
tr = self.event.settings.tax_rate_default
|
||||
tax = tr.tax(self.payment_fee, base_price_is='gross')
|
||||
rate, tax = tax.rate, tax.tax
|
||||
|
||||
try:
|
||||
ia = self.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
if not tr.tax_applicable(ia):
|
||||
rate = 0
|
||||
tax = 0
|
||||
|
||||
self.payment_fee_tax_rate = rate
|
||||
self.payment_fee_tax_value = tax
|
||||
self.payment_fee_tax_rule = tr
|
||||
else:
|
||||
self.payment_fee_tax_rate = Decimal('0.00')
|
||||
self.payment_fee_tax_value = Decimal('0.00')
|
||||
self.payment_fee_tax_rule = None
|
||||
|
||||
@property
|
||||
def payment_fee_net(self):
|
||||
return self.payment_fee - self.payment_fee_tax_value
|
||||
|
||||
@cached_property
|
||||
def tax_total(self):
|
||||
return (self.positions.aggregate(s=Sum('tax_value'))['s'] or 0) + self.payment_fee_tax_value
|
||||
return (self.positions.aggregate(s=Sum('tax_value'))['s'] or 0) + (self.fees.aggregate(s=Sum('tax_value'))['s'] or 0)
|
||||
|
||||
@property
|
||||
def net_total(self):
|
||||
@@ -669,6 +614,91 @@ class AbstractPosition(models.Model):
|
||||
else self.variation.quotas.filter(subevent=self.subevent))
|
||||
|
||||
|
||||
class OrderFee(models.Model):
|
||||
"""
|
||||
An OrderFee objet represents a fee that is added to the order total independently of
|
||||
the actual positions. This might for example be a payment or a shipping fee.
|
||||
"""
|
||||
FEE_TYPE_PAYMENT = "payment"
|
||||
FEE_TYPE_SHIPPING = "shipping"
|
||||
FEE_TYPE_OTHER = "other"
|
||||
FEE_TYPES = (
|
||||
(FEE_TYPE_PAYMENT, _("Payment fee")),
|
||||
(FEE_TYPE_SHIPPING, _("Shipping fee")),
|
||||
(FEE_TYPE_OTHER, _("Other fees")),
|
||||
)
|
||||
|
||||
value = models.DecimalField(
|
||||
decimal_places=2, max_digits=10,
|
||||
verbose_name=_("Value")
|
||||
)
|
||||
order = models.ForeignKey(
|
||||
Order,
|
||||
verbose_name=_("Order"),
|
||||
related_name='fees',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
fee_type = models.CharField(
|
||||
max_length=100, choices=FEE_TYPES
|
||||
)
|
||||
description = models.CharField(max_length=190, blank=True)
|
||||
internal_type = models.CharField(max_length=255, blank=True)
|
||||
tax_rate = models.DecimalField(
|
||||
max_digits=7, decimal_places=2,
|
||||
verbose_name=_('Tax rate')
|
||||
)
|
||||
tax_rule = models.ForeignKey(
|
||||
'TaxRule',
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
tax_value = models.DecimalField(
|
||||
max_digits=10, decimal_places=2,
|
||||
verbose_name=_('Tax value')
|
||||
)
|
||||
|
||||
@property
|
||||
def net_value(self):
|
||||
return self.value - self.tax_value
|
||||
|
||||
def __str__(self):
|
||||
if self.description:
|
||||
return '{} - {}'.format(self.get_fee_type_display(), self.description)
|
||||
else:
|
||||
return self.get_fee_type_display()
|
||||
|
||||
def __repr__(self):
|
||||
return '<OrderFee: type %s, value %d>' % (
|
||||
self.fee_type, self.value
|
||||
)
|
||||
|
||||
def _calculate_tax(self):
|
||||
try:
|
||||
ia = self.order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
|
||||
if not self.tax_rule and self.fee_type == "payment" and self.order.event.settings.tax_rate_default:
|
||||
self.tax_rule = self.order.event.settings.tax_rate_default
|
||||
|
||||
if self.tax_rule:
|
||||
if self.tax_rule.tax_applicable(ia):
|
||||
tax = self.tax_rule.tax(self.value, base_price_is='gross')
|
||||
self.tax_rate = tax.rate
|
||||
self.tax_value = tax.tax
|
||||
else:
|
||||
self.tax_value = Decimal('0.00')
|
||||
self.tax_rate = Decimal('0.00')
|
||||
else:
|
||||
self.tax_value = Decimal('0.00')
|
||||
self.tax_rate = Decimal('0.00')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.tax_rate is None:
|
||||
self._calculate_tax()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class OrderPosition(AbstractPosition):
|
||||
"""
|
||||
An OrderPosition is one line of an order, representing one ordered item
|
||||
|
||||
@@ -14,7 +14,8 @@ from pretix.base.models import (
|
||||
CartPosition, Event, InvoiceAddress, Item, ItemVariation, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice
|
||||
from pretix.base.models.orders import OrderFee
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
||||
from pretix.base.services.async import ProfiledTask
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.pricing import get_price
|
||||
@@ -626,6 +627,40 @@ def update_tax_rates(event: Event, cart_id: str, invoice_address: InvoiceAddress
|
||||
return totaldiff
|
||||
|
||||
|
||||
def get_fees(event, total, invoice_address, provider):
|
||||
fees = []
|
||||
|
||||
if total == 0:
|
||||
return fees
|
||||
|
||||
if provider:
|
||||
provider = event.get_payment_providers().get(provider)
|
||||
if provider:
|
||||
payment_fee = provider.calculate_fee(total)
|
||||
|
||||
if payment_fee:
|
||||
payment_fee_tax_rule = event.settings.tax_rate_default or TaxRule.zero()
|
||||
if payment_fee_tax_rule.tax_applicable(invoice_address):
|
||||
payment_fee_tax = payment_fee_tax_rule.tax(payment_fee, base_price_is='gross')
|
||||
fees.append(OrderFee(
|
||||
fee_type="PAYMENT",
|
||||
value=payment_fee,
|
||||
tax_rate=payment_fee_tax.rate,
|
||||
tax_value=payment_fee_tax.tax,
|
||||
tax_rule=payment_fee_tax_rule
|
||||
))
|
||||
else:
|
||||
fees.append(OrderFee(
|
||||
fee_type="PAYMENT",
|
||||
value=payment_fee,
|
||||
tax_rate=Decimal('0.00'),
|
||||
tax_value=Decimal('0.00'),
|
||||
tax_rule=payment_fee_tax_rule
|
||||
))
|
||||
|
||||
return fees
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en',
|
||||
invoice_address: int=None) -> None:
|
||||
|
||||
@@ -99,7 +99,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
reverse_charge = False
|
||||
|
||||
positions.sort(key=lambda p: p.sort_key)
|
||||
for p in positions:
|
||||
for i, p in enumerate(positions):
|
||||
if not invoice.event.settings.invoice_include_free and p.price == Decimal('0.00') and not p.addon_c:
|
||||
continue
|
||||
|
||||
@@ -109,7 +109,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
if p.addon_to_id:
|
||||
desc = " + " + desc
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=desc,
|
||||
position=i, invoice=invoice, description=desc,
|
||||
gross_value=p.price, tax_value=p.tax_value,
|
||||
tax_rate=p.tax_rate, tax_name=p.tax_rule.name if p.tax_rule else ''
|
||||
)
|
||||
@@ -127,13 +127,19 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
)
|
||||
invoice.save()
|
||||
|
||||
if invoice.order.payment_fee:
|
||||
offset = len(positions)
|
||||
for i, fee in enumerate(invoice.order.fees.all()):
|
||||
fee_title = _(fee.get_fee_type_display())
|
||||
if fee.description:
|
||||
fee_title += " - " + fee.description
|
||||
InvoiceLine.objects.create(
|
||||
position=i + offset,
|
||||
invoice=invoice,
|
||||
description=_('Payment via {method}').format(method=str(payment_provider.verbose_name)),
|
||||
gross_value=invoice.order.payment_fee, tax_value=invoice.order.payment_fee_tax_value,
|
||||
tax_rate=invoice.order.payment_fee_tax_rate,
|
||||
tax_name=invoice.order.payment_fee_tax_rule.name if invoice.order.payment_fee_tax_rule else ''
|
||||
description=fee_title,
|
||||
gross_value=fee.value,
|
||||
tax_value=fee.tax_value,
|
||||
tax_rate=fee.tax_rate,
|
||||
tax_name=fee.tax_rule.name if fee.tax_rule else ''
|
||||
)
|
||||
|
||||
return invoice
|
||||
|
||||
@@ -23,7 +23,7 @@ from pretix.base.models import (
|
||||
User, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.orders import CachedTicket, InvoiceAddress
|
||||
from pretix.base.models.orders import CachedTicket, InvoiceAddress, OrderFee
|
||||
from pretix.base.models.tax import TaxedPrice
|
||||
from pretix.base.payment import BasePaymentProvider
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
@@ -342,14 +342,23 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
raise OrderError(err, errargs)
|
||||
|
||||
|
||||
def _get_fees(positions: List[CartPosition], payment_provider: BasePaymentProvider):
|
||||
fees = []
|
||||
total = sum([c.price for c in positions])
|
||||
payment_fee = payment_provider.calculate_fee(total)
|
||||
if payment_fee:
|
||||
fees.append(OrderFee(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=payment_fee))
|
||||
|
||||
return fees
|
||||
|
||||
|
||||
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
|
||||
payment_provider: BasePaymentProvider, locale: str=None, address: int=None,
|
||||
meta_info: dict=None):
|
||||
from datetime import time
|
||||
|
||||
total = sum([c.price for c in positions])
|
||||
payment_fee = payment_provider.calculate_fee(total)
|
||||
total += payment_fee
|
||||
fees = _get_fees(positions, payment_provider)
|
||||
total = sum([c.price for c in positions]) + sum([c.value for c in fees])
|
||||
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(days=event.settings.get('payment_term_days', as_type=int))
|
||||
@@ -387,7 +396,6 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
expires=expires,
|
||||
locale=locale,
|
||||
total=total,
|
||||
payment_fee=payment_fee,
|
||||
payment_provider=payment_provider.identifier,
|
||||
meta_info=json.dumps(meta_info or {}),
|
||||
)
|
||||
@@ -398,9 +406,13 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
address.order = order
|
||||
address.save()
|
||||
|
||||
order._calculate_tax() # Might have changed due to new invoice address
|
||||
order.save()
|
||||
|
||||
for fee in fees:
|
||||
fee.order = order
|
||||
fee._calculate_tax()
|
||||
fee.save()
|
||||
|
||||
OrderPosition.transform_cart_positions(positions, order)
|
||||
order.log_action('pretix.event.order.placed')
|
||||
|
||||
@@ -825,15 +837,21 @@ class OrderChangeManager:
|
||||
})
|
||||
|
||||
def _recalculate_total_and_payment_fee(self):
|
||||
self.order.total = sum([p.price for p in self.order.positions.all()])
|
||||
payment_fee = Decimal('0.00')
|
||||
if self.order.total != 0:
|
||||
prov = self._get_payment_provider()
|
||||
if prov:
|
||||
payment_fee = prov.calculate_fee(self.order.total)
|
||||
self.order.payment_fee = payment_fee
|
||||
self.order.total += payment_fee
|
||||
self.order._calculate_tax()
|
||||
|
||||
if payment_fee:
|
||||
fee = self.order.fees.get_or_create(fee_type=OrderFee.FEE_TYPE_PAYMENT, defaults={'value': 0})[0]
|
||||
fee.value = payment_fee
|
||||
fee._calculate_tax()
|
||||
fee.save()
|
||||
else:
|
||||
self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).delete()
|
||||
|
||||
self.order.total = sum([p.price for p in self.order.positions.all()]) + sum([f.value for f in self.order.fees.all()])
|
||||
self.order.save()
|
||||
|
||||
def _reissue_invoice(self):
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.orders import OrderFee
|
||||
|
||||
|
||||
class DummyObject:
|
||||
@@ -157,33 +158,35 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
|
||||
|
||||
# Payment fees
|
||||
payment_cat_obj = DummyObject()
|
||||
payment_cat_obj.name = _('Payment method fees')
|
||||
payment_cat_obj.name = _('Fees')
|
||||
payment_items = []
|
||||
|
||||
if not subevent:
|
||||
counters = event.orders.values('payment_provider', 'status').annotate(
|
||||
cnt=Count('id'), payment_fee=Sum('payment_fee'), tax_value=Sum('payment_fee_tax_value')
|
||||
).order_by()
|
||||
counters = OrderFee.objects.filter(
|
||||
order__event=event
|
||||
).values(
|
||||
'fee_type', 'internal_type', 'order__status'
|
||||
).annotate(cnt=Count('id'), value=Sum('value'), tax_value=Sum('tax_value')).order_by()
|
||||
|
||||
num_canceled = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'], o['payment_fee'] - o['tax_value'])
|
||||
for o in counters if o['status'] == Order.STATUS_CANCELED
|
||||
(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value'])
|
||||
for o in counters if o['order__status'] == Order.STATUS_CANCELED
|
||||
}
|
||||
num_refunded = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'], o['payment_fee'] - o['tax_value'])
|
||||
for o in counters if o['status'] == Order.STATUS_REFUNDED
|
||||
(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value'])
|
||||
for o in counters if o['order__status'] == Order.STATUS_REFUNDED
|
||||
}
|
||||
num_pending = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'], o['payment_fee'] - o['tax_value'])
|
||||
for o in counters if o['status'] == Order.STATUS_PENDING
|
||||
(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value'])
|
||||
for o in counters if o['order__status'] == Order.STATUS_PENDING
|
||||
}
|
||||
num_expired = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'], o['payment_fee'] - o['tax_value'])
|
||||
for o in counters if o['status'] == Order.STATUS_EXPIRED
|
||||
(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value'])
|
||||
for o in counters if o['order__status'] == Order.STATUS_EXPIRED
|
||||
}
|
||||
num_paid = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'], o['payment_fee'] - o['tax_value'])
|
||||
for o in counters if o['status'] == Order.STATUS_PAID
|
||||
(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value'])
|
||||
for o in counters if o['order__status'] == Order.STATUS_PAID
|
||||
}
|
||||
num_total = dictsum(num_pending, num_paid)
|
||||
|
||||
@@ -191,11 +194,15 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
|
||||
k: v.verbose_name
|
||||
for k, v in event.get_payment_providers().items()
|
||||
}
|
||||
names = dict(OrderFee.FEE_TYPES)
|
||||
|
||||
for pprov, total in num_total.items():
|
||||
ppobj = DummyObject()
|
||||
ppobj.name = provider_names.get(pprov, pprov)
|
||||
ppobj.provider = pprov
|
||||
if pprov[0] == OrderFee.FEE_TYPE_PAYMENT:
|
||||
ppobj.name = '{} - {}'.format(names[OrderFee.FEE_TYPE_PAYMENT], provider_names.get(pprov[1], pprov[1]))
|
||||
else:
|
||||
ppobj.name = '{} - {}'.format(names[OrderFee.FEE_TYPE_PAYMENT], pprov[1])
|
||||
ppobj.provider = pprov[1]
|
||||
ppobj.has_variations = False
|
||||
ppobj.num_total = total
|
||||
ppobj.num_canceled = num_canceled.get(pprov, (0, 0, 0))
|
||||
|
||||
Reference in New Issue
Block a user