From 13cc57e98b43414ef0333d551b9784dcf0f49328 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 1 Feb 2019 16:45:48 +0100 Subject: [PATCH] Add multi-sheet export for invoices --- src/pretix/base/exporter.py | 2 +- src/pretix/base/exporters/orderlist.py | 184 ++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 6 deletions(-) diff --git a/src/pretix/base/exporter.py b/src/pretix/base/exporter.py index 2372acef67..857cf5098c 100644 --- a/src/pretix/base/exporter.py +++ b/src/pretix/base/exporter.py @@ -154,7 +154,7 @@ class MultiSheetListExporter(ListExporter): @property def export_form_fields(self) -> dict: choices = [ - ('xlsx', _('Excel (.xlsx)')), + ('xlsx', _('Combined Excel (.xlsx)')), ] for s, l in self.sheets: choices += [ diff --git a/src/pretix/base/exporters/orderlist.py b/src/pretix/base/exporters/orderlist.py index 3e483cfcaa..6b0354a9b3 100644 --- a/src/pretix/base/exporters/orderlist.py +++ b/src/pretix/base/exporters/orderlist.py @@ -3,16 +3,18 @@ from decimal import Decimal import pytz from django import forms -from django.db.models import DateTimeField, Max, OuterRef, Subquery, Sum +from django.db.models import DateTimeField, F, Max, OuterRef, Subquery, Sum from django.dispatch import receiver -from django.utils.formats import localize +from django.utils.formats import date_format, localize from django.utils.translation import ugettext as _, ugettext_lazy -from pretix.base.models import InvoiceAddress, Order, OrderPosition +from pretix.base.models import ( + InvoiceAddress, InvoiceLine, Order, OrderPosition, +) from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund from pretix.base.settings import PERSON_NAME_SCHEMES -from ..exporter import ListExporter +from ..exporter import ListExporter, MultiSheetListExporter from ..signals import register_data_exporters @@ -208,7 +210,7 @@ class PaymentListExporter(ListExporter): headers = [ _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'), - _('Amount'), _('Payment method') + _('Status code'), _('Amount'), _('Payment method') ] yield headers @@ -225,6 +227,7 @@ class PaymentListExporter(ListExporter): obj.created.astimezone(tz).date().strftime('%Y-%m-%d'), d2, obj.get_state_display(), + obj.state, localize(obj.amount * (-1 if isinstance(obj, OrderRefund) else 1)), provider_names.get(obj.provider, obj.provider) ] @@ -263,6 +266,172 @@ class QuotaListExporter(ListExporter): return '{}_quotas'.format(self.event.slug) +class InvoiceDataExporter(MultiSheetListExporter): + identifier = 'invoicedata' + verbose_name = ugettext_lazy('Invoice data') + + @property + def sheets(self): + return ( + ('invoices', _('Invoices')), + ('lines', _('Invoice lines')), + ) + + def iterate_sheet(self, form_data, sheet): + if sheet == 'invoices': + yield [ + _('Invoice number'), + _('Date'), + _('Order code'), + _('E-mail address'), + _('Invoice type'), + _('Cancellation of'), + _('Language'), + _('Invoice sender:') + ' ' + _('Name'), + _('Invoice sender:') + ' ' + _('Address'), + _('Invoice sender:') + ' ' + _('ZIP code'), + _('Invoice sender:') + ' ' + _('City'), + _('Invoice sender:') + ' ' + _('Country'), + _('Invoice sender:') + ' ' + _('Tax ID'), + _('Invoice sender:') + ' ' + _('VAT ID'), + _('Invoice recipient:') + ' ' + _('Company'), + _('Invoice recipient:') + ' ' + _('Name'), + _('Invoice recipient:') + ' ' + _('Street address'), + _('Invoice recipient:') + ' ' + _('ZIP code'), + _('Invoice recipient:') + ' ' + _('City'), + _('Invoice recipient:') + ' ' + _('Country'), + _('Invoice recipient:') + ' ' + _('VAT ID'), + _('Invoice recipient:') + ' ' + _('Beneficiary'), + _('Invoice recipient:') + ' ' + _('Internal reference'), + _('Reverse charge'), + _('Shown foreign currency'), + _('Foreign currency rate'), + _('Total value (with taxes)'), + _('Total value (without taxes)'), + ] + qs = self.event.invoices.order_by('full_invoice_no').select_related( + 'order', 'refers' + ).annotate( + total_gross=Subquery( + InvoiceLine.objects.filter( + invoice=OuterRef('pk') + ).order_by().values('invoice').annotate( + s=Sum('gross_value') + ).values('s') + ), + total_net=Subquery( + InvoiceLine.objects.filter( + invoice=OuterRef('pk') + ).order_by().values('invoice').annotate( + s=Sum(F('gross_value') - F('tax_value')) + ).values('s') + ) + ) + for i in qs: + yield [ + i.full_invoice_no, + date_format(i.date, "SHORT_DATE_FORMAT"), + i.order.code, + i.order.email, + _('Cancellation') if i.is_cancellation else _('Invoice'), + i.refers.full_invoice_no if i.refers else '', + i.locale, + i.invoice_from_name, + i.invoice_from, + i.invoice_from_zipcode, + i.invoice_from_city, + i.invoice_from_country, + i.invoice_from_tax_id, + i.invoice_from_vat_id, + i.invoice_to_company, + i.invoice_to_name, + i.invoice_to_street or i.invoice_to, + i.invoice_to_zipcode, + i.invoice_to_city, + i.invoice_to_country, + i.invoice_to_vat_id, + i.invoice_to_beneficiary, + i.internal_reference, + _('Yes') if i.reverse_charge else _('No'), + i.foreign_currency_display, + i.foreign_currency_rate, + i.total_gross, + Decimal(i.total_net).quantize(Decimal('0.01')), + ] + elif sheet == 'lines': + yield [ + _('Invoice number'), + _('Line number'), + _('Description'), + _('Gross price'), + _('Net price'), + _('Tax value'), + _('Tax rate'), + _('Tax name'), + + _('Date'), + _('Order code'), + _('E-mail address'), + _('Invoice type'), + _('Cancellation of'), + _('Invoice sender:') + ' ' + _('Name'), + _('Invoice sender:') + ' ' + _('Address'), + _('Invoice sender:') + ' ' + _('ZIP code'), + _('Invoice sender:') + ' ' + _('City'), + _('Invoice sender:') + ' ' + _('Country'), + _('Invoice sender:') + ' ' + _('Tax ID'), + _('Invoice sender:') + ' ' + _('VAT ID'), + _('Invoice recipient:') + ' ' + _('Company'), + _('Invoice recipient:') + ' ' + _('Name'), + _('Invoice recipient:') + ' ' + _('Street address'), + _('Invoice recipient:') + ' ' + _('ZIP code'), + _('Invoice recipient:') + ' ' + _('City'), + _('Invoice recipient:') + ' ' + _('Country'), + _('Invoice recipient:') + ' ' + _('VAT ID'), + _('Invoice recipient:') + ' ' + _('Beneficiary'), + _('Invoice recipient:') + ' ' + _('Internal reference'), + ] + qs = InvoiceLine.objects.order_by('invoice__full_invoice_no', 'position').select_related( + 'invoice', 'invoice__order', 'invoice__refers' + ) + for l in qs: + i = l.invoice + yield [ + i.full_invoice_no, + l.position + 1, + l.description.replace("
", " - "), + l.gross_value, + l.net_value, + l.tax_value, + l.tax_rate, + l.tax_name, + date_format(i.date, "SHORT_DATE_FORMAT"), + i.order.code, + i.order.email, + _('Cancellation') if i.is_cancellation else _('Invoice'), + i.refers.full_invoice_no if i.refers else '', + i.invoice_from_name, + i.invoice_from, + i.invoice_from_zipcode, + i.invoice_from_city, + i.invoice_from_country, + i.invoice_from_tax_id, + i.invoice_from_vat_id, + i.invoice_to_company, + i.invoice_to_name, + i.invoice_to_street or i.invoice_to, + i.invoice_to_zipcode, + i.invoice_to_city, + i.invoice_to_country, + i.invoice_to_vat_id, + i.invoice_to_beneficiary, + i.internal_reference, + ] + + def get_filename(self): + return '{}_payments'.format(self.event.slug) + + @receiver(register_data_exporters, dispatch_uid="exporter_orderlist") def register_orderlist_exporter(sender, **kwargs): return OrderListExporter @@ -276,3 +445,8 @@ def register_paymentlist_exporter(sender, **kwargs): @receiver(register_data_exporters, dispatch_uid="exporter_quotalist") def register_quotalist_exporter(sender, **kwargs): return QuotaListExporter + + +@receiver(register_data_exporters, dispatch_uid="exporter_invoicedata") +def register_invoicedata_exporter(sender, **kwargs): + return InvoiceDataExporter