Tax list exporter: Add sheets with reports by country and company

This commit is contained in:
Raphael Michel
2020-12-08 22:01:52 +01:00
parent b51bd2118e
commit a5f806d975

View File

@@ -1,6 +1,7 @@
import copy import copy
import tempfile import tempfile
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from datetime import date, datetime, timedelta
from decimal import Decimal from decimal import Decimal
import pytz import pytz
@@ -9,17 +10,19 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.db import models from django.db import models
from django.db.models import Max, OuterRef, Subquery, Sum from django.db.models import DateTimeField, Max, OuterRef, Subquery, Sum
from django.template.defaultfilters import floatformat from django.template.defaultfilters import floatformat, time
from django.utils.formats import date_format, localize from django.utils.formats import date_format, localize
from django.utils.timezone import get_current_timezone, now from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext from django.utils.translation import gettext as _, gettext_lazy, pgettext
from django_countries.fields import Country
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER from reportlab.lib.enums import TA_CENTER
from reportlab.platypus import PageBreak from reportlab.platypus import PageBreak
from pretix.base.decimal import round_decimal from pretix.base.decimal import round_decimal
from pretix.base.exporter import BaseExporter, ListExporter from pretix.base.exporter import BaseExporter, MultiSheetListExporter
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import Order, OrderPosition from pretix.base.models import Order, OrderPosition
from pretix.base.models.event import SubEvent from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee, OrderPayment from pretix.base.models.orders import OrderFee, OrderPayment
@@ -503,10 +506,18 @@ class OrderTaxListReportPDF(Report):
return story return story
class OrderTaxListReport(ListExporter): class OrderTaxListReport(MultiSheetListExporter):
identifier = 'ordertaxeslist' identifier = 'ordertaxeslist'
verbose_name = gettext_lazy('List of orders with taxes') verbose_name = gettext_lazy('List of orders with taxes')
@property
def sheets(self):
return (
('orders', _('Orders')),
('countries', _('Taxes by country')),
('companies', _('Business customers')),
)
@property @property
def export_form_fields(self): def export_form_fields(self):
f = super().export_form_fields f = super().export_form_fields
@@ -531,12 +542,192 @@ class OrderTaxListReport(ListExporter):
widget=forms.RadioSelect, widget=forms.RadioSelect,
required=False required=False
)), )),
('date_axis',
forms.ChoiceField(
label=_('Date filter'),
choices=(
('', _('Filter by…')),
('order_date', _('Order date')),
('last_payment_date', _('Date of last successful payment')),
),
required=False,
)),
('date_from', forms.DateField(
label=_('Date from'),
required=False,
widget=DatePickerWidget,
)),
('date_until', forms.DateField(
label=_('Date until'),
required=False,
widget=DatePickerWidget,
))
] ]
)) ))
return f return f
def iterate_list(self, form_data): def filter_qs(self, qs, form_data):
tz = pytz.timezone(self.event.settings.timezone) date_from = form_data.get('date_from')
date_until = form_data.get('date_until')
date_filter = form_data.get('date_axis')
if date_from and isinstance(date_from, date):
date_from = make_aware(datetime.combine(
date_from,
time(hour=0, minute=0, second=0, microsecond=0)
), self.event.timezone)
if date_until and isinstance(date_until, date):
date_until = make_aware(datetime.combine(
date_until + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), self.event.timezone)
if date_filter == 'order_date':
if date_from:
qs = qs.filter(order__datetime__gte=date_from)
if date_until:
qs = qs.filter(order__datetime__lt=date_until)
elif date_filter == 'last_payment_date':
p_date = OrderPayment.objects.filter(
order=OuterRef('order'),
state__in=[OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED],
payment_date__isnull=False
).values('order').annotate(
m=Max('payment_date')
).values('m').order_by()
qs = qs.annotate(payment_date=Subquery(p_date, output_field=DateTimeField()))
if date_from:
qs = qs.filter(payment_date__gte=date_from)
if date_until:
qs = qs.filter(payment_date__lt=date_until)
return qs
def iterate_sheet(self, form_data, sheet):
if sheet == 'orders':
yield from self.iterate_orders(form_data)
elif sheet == 'countries':
yield from self.iterate_countries(form_data)
elif sheet == 'companies':
yield from self.iterate_companies(form_data)
def _combine(self, *qs, keys=tuple()):
cache = {}
def kf(r):
return tuple(r[k] for k in keys)
for q in qs:
for r in q:
if kf(r) not in cache:
cache[kf(r)] = {
'prices': Decimal('0.00'),
'tax_values': Decimal('0.00'),
}
cache[kf(r)]['prices'] += (r['prices'] or Decimal('0.00'))
cache[kf(r)]['tax_values'] += (r['tax_values'] or Decimal('0.00'))
return [
dict(**{kname: k[i] for i, kname in enumerate(keys)}, **v)
for k, v in sorted(cache.items(), key=lambda item: item[0])
]
def iterate_countries(self, form_data):
keys = (
'order__invoice_address__country',
'tax_rate',
)
opqs = self.filter_qs(OrderPosition.objects, form_data).filter(
order__status__in=form_data['status'],
order__event=self.event,
).values(*keys).annotate(
prices=Sum('price'),
tax_values=Sum('tax_value')
)
ofqs = self.filter_qs(OrderFee.objects, form_data).filter(
order__status__in=form_data['status'],
order__event=self.event,
).values(*keys).annotate(
prices=Sum('value'),
tax_values=Sum('tax_value')
)
yield [
_('Country code'),
_('Country'),
_('Tax rate'),
_('Gross'),
_('Tax')
]
res = self._combine(opqs, ofqs, keys=keys)
for r in res:
yield [
str(r['order__invoice_address__country']),
Country(r['order__invoice_address__country']).name,
r['tax_rate'],
r['prices'],
r['tax_values'],
]
def iterate_companies(self, form_data):
keys = (
'order__invoice_address__country',
'tax_rate',
'order__invoice_address__company',
'order__invoice_address__street',
'order__invoice_address__zipcode',
'order__invoice_address__city',
'order__invoice_address__state',
'order__invoice_address__vat_id',
'order__invoice_address__custom_field',
)
opqs = self.filter_qs(OrderPosition.objects, form_data).filter(
order__status__in=form_data['status'],
order__event=self.event,
order__invoice_address__is_business=True,
).values(*keys).annotate(
prices=Sum('price'),
tax_values=Sum('tax_value')
)
ofqs = self.filter_qs(OrderFee.objects, form_data).filter(
order__status__in=form_data['status'],
order__event=self.event,
order__invoice_address__is_business=True,
).values(*keys).annotate(
prices=Sum('value'),
tax_values=Sum('tax_value')
)
yield [
_('Country code'),
_('Country'),
_('Tax rate'),
_('Company'),
_('Address'),
_('ZIP code'),
_('City'),
pgettext('address', 'State'),
_('VAT ID'),
self.event.settings.invoice_address_custom_field or 'Custom field',
_('Gross'),
_('Tax')
]
res = self._combine(opqs, ofqs, keys=keys)
for r in res:
yield [
str(r['order__invoice_address__country']),
Country(r['order__invoice_address__country']).name,
r['tax_rate'],
r['order__invoice_address__company'],
r['order__invoice_address__street'],
r['order__invoice_address__zipcode'],
r['order__invoice_address__city'],
r['order__invoice_address__state'],
r['order__invoice_address__vat_id'],
r['order__invoice_address__custom_field'],
r['prices'],
r['tax_values'],
]
def iterate_orders(self, form_data):
tz = self.event.timezone
tax_rates = set( tax_rates = set(
a for a a for a
@@ -568,7 +759,7 @@ class OrderTaxListReport(ListExporter):
).values( ).values(
'm' 'm'
).order_by() ).order_by()
qs = OrderPosition.objects.filter( qs = self.filter_qs(OrderPosition.objects, form_data).filter(
order__status__in=form_data['status'], order__status__in=form_data['status'],
order__event=self.event, order__event=self.event,
).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values( ).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values(