forked from CGM_Public/pretix_original
Reports: Add new "accounting report" (#3314)
This commit is contained in:
@@ -45,6 +45,14 @@
|
|||||||
{% bootstrap_field filter_form.date_until layout='inline' %}
|
{% bootstrap_field filter_form.date_until layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="text-danger">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Filtering this report by date is not recommended as it might lead to misleading information since this
|
||||||
|
report only sees the current state of any order, not any changes made to the order previously.
|
||||||
|
This date filter might be removed in the future.
|
||||||
|
Use the "Accounting report" in the export section instead.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<button class="btn btn-primary btn-lg" type="submit">
|
<button class="btn btn-primary btn-lg" type="submit">
|
||||||
<span class="fa fa-filter"></span>
|
<span class="fa fa-filter"></span>
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
category = pgettext_lazy('export_category', 'Check-in')
|
category = pgettext_lazy('export_category', 'Check-in')
|
||||||
description = gettext_lazy("Download a PDF version of a check-in list that can be used to check people in at the "
|
description = gettext_lazy("Download a PDF version of a check-in list that can be used to check people in at the "
|
||||||
"event without digital methods.")
|
"event without digital methods.")
|
||||||
|
numbered_canvas = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def export_form_fields(self):
|
def export_form_fields(self):
|
||||||
|
|||||||
822
src/pretix/plugins/reports/accountingreport.py
Normal file
822
src/pretix/plugins/reports/accountingreport.py
Normal file
@@ -0,0 +1,822 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import copy
|
||||||
|
import datetime
|
||||||
|
import tempfile
|
||||||
|
from collections import OrderedDict, defaultdict
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.db.models import F, Sum
|
||||||
|
from django.utils.formats import date_format, localize
|
||||||
|
from django.utils.html import escape
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||||
|
from reportlab.lib import colors, pagesizes
|
||||||
|
from reportlab.lib.enums import TA_CENTER, TA_RIGHT
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
from reportlab.platypus import (
|
||||||
|
KeepTogether, PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pretix.base.exporter import BaseExporter
|
||||||
|
from pretix.base.models import (
|
||||||
|
GiftCardTransaction, OrderFee, OrderPayment, OrderRefund, Transaction,
|
||||||
|
)
|
||||||
|
from pretix.base.templatetags.money import money_filter
|
||||||
|
from pretix.base.timeframes import (
|
||||||
|
DateFrameField,
|
||||||
|
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
|
||||||
|
)
|
||||||
|
from pretix.control.forms.filter import get_all_payment_providers
|
||||||
|
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ReportExporter(ReportlabExportMixin, BaseExporter):
|
||||||
|
pagesize = pagesizes.portrait(pagesizes.A4)
|
||||||
|
identifier = "accountingreport"
|
||||||
|
verbose_name = gettext_lazy("Accounting report")
|
||||||
|
description = gettext_lazy(
|
||||||
|
"Download a PDF report of all sales and payments within a given time frame."
|
||||||
|
)
|
||||||
|
category = pgettext_lazy("export_category", "Analysis")
|
||||||
|
filename = "accountingreport"
|
||||||
|
featured = True
|
||||||
|
numbered_canvas = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def export_form_fields(self) -> dict:
|
||||||
|
ff = OrderedDict(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"date_range",
|
||||||
|
DateFrameField(
|
||||||
|
label=_("Date range"),
|
||||||
|
include_future_frames=False,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"no_testmode",
|
||||||
|
forms.BooleanField(
|
||||||
|
label=_("Ignore test mode orders"),
|
||||||
|
required=False,
|
||||||
|
initial=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def describe_filters(self, form_data: dict):
|
||||||
|
filters = []
|
||||||
|
if self.is_multievent and self.events.count() == self.organizer.events.count():
|
||||||
|
filters.append(_("Events") + ": " + _("All"))
|
||||||
|
elif self.is_multievent:
|
||||||
|
filters.append(
|
||||||
|
_("Events") + ": " + ", ".join(str(i.name) for i in self.events)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
filters.append(
|
||||||
|
f'{_("Event")}: {self.event.name} ({self.event.get_date_range_display()})'
|
||||||
|
)
|
||||||
|
|
||||||
|
if form_data["date_range"]:
|
||||||
|
dt_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
if dt_start:
|
||||||
|
filters.append(
|
||||||
|
_("Begin")
|
||||||
|
+ ": "
|
||||||
|
+ date_format(
|
||||||
|
dt_start.astimezone(self.timezone), "SHORT_DATETIME_FORMAT"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if df_end:
|
||||||
|
filters.append(
|
||||||
|
_("End")
|
||||||
|
+ ": "
|
||||||
|
+ date_format(
|
||||||
|
(df_end - datetime.timedelta.resolution).astimezone(
|
||||||
|
self.timezone
|
||||||
|
),
|
||||||
|
"SHORT_DATETIME_FORMAT",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not form_data["no_testmode"]:
|
||||||
|
filters.append(_("Report includes test orders which may be deleted later!"))
|
||||||
|
|
||||||
|
if self._transaction_qs(form_data).filter(migrated=True).exists():
|
||||||
|
filters.append(
|
||||||
|
_(
|
||||||
|
"The report time frame includes data generated with an old software version that did not yet "
|
||||||
|
"store all data required to create this report. The report might therefore be inaccurate "
|
||||||
|
"with regards to orders that were changed in the time frame."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def _giftcard_transaction_qs(self, form_data, ignore_dates=False):
|
||||||
|
qs = GiftCardTransaction.objects.filter(
|
||||||
|
card__issuer=self.organizer,
|
||||||
|
)
|
||||||
|
if form_data["date_range"] and not ignore_dates:
|
||||||
|
df_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
if df_start:
|
||||||
|
qs = qs.filter(datetime__gte=df_start)
|
||||||
|
if df_end:
|
||||||
|
qs = qs.filter(datetime__lt=df_end)
|
||||||
|
if form_data["no_testmode"]:
|
||||||
|
qs = qs.filter(card__testmode=False)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def _transaction_qs(self, form_data, ignore_dates=False):
|
||||||
|
qs = Transaction.objects.filter(
|
||||||
|
order__event__in=self.events,
|
||||||
|
)
|
||||||
|
if form_data["date_range"] and not ignore_dates:
|
||||||
|
df_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
if df_start:
|
||||||
|
qs = qs.filter(datetime__gte=df_start)
|
||||||
|
if df_end:
|
||||||
|
qs = qs.filter(datetime__lt=df_end)
|
||||||
|
if form_data["no_testmode"]:
|
||||||
|
qs = qs.filter(order__testmode=False)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def _payment_qs(self, form_data, ignore_dates=False):
|
||||||
|
qs = OrderPayment.objects.filter(
|
||||||
|
order__event__in=self.events,
|
||||||
|
state__in=(
|
||||||
|
OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||||
|
OrderPayment.PAYMENT_STATE_REFUNDED,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if form_data["date_range"] and not ignore_dates:
|
||||||
|
(
|
||||||
|
df_start,
|
||||||
|
df_end,
|
||||||
|
) = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
if df_start:
|
||||||
|
qs = qs.filter(payment_date__gte=df_start)
|
||||||
|
if df_end:
|
||||||
|
qs = qs.filter(payment_date__lt=df_end)
|
||||||
|
if form_data["no_testmode"]:
|
||||||
|
qs = qs.filter(order__testmode=False)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def _refund_qs(self, form_data, ignore_dates=False):
|
||||||
|
qs = OrderRefund.objects.filter(
|
||||||
|
order__event__in=self.events, state=OrderRefund.REFUND_STATE_DONE
|
||||||
|
)
|
||||||
|
if form_data["date_range"] and not ignore_dates:
|
||||||
|
df_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
if df_start:
|
||||||
|
qs = qs.filter(execution_date__gte=df_start)
|
||||||
|
if df_end:
|
||||||
|
qs = qs.filter(execution_date__lt=df_end)
|
||||||
|
if form_data["no_testmode"]:
|
||||||
|
qs = qs.filter(order__testmode=False)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def _table_transactions(self, form_data):
|
||||||
|
tstyle = copy.copy(self.get_style())
|
||||||
|
tstyle.fontSize = 8
|
||||||
|
tstyle.leading = 10
|
||||||
|
tstyle_right = copy.copy(tstyle)
|
||||||
|
tstyle_right.alignment = TA_RIGHT
|
||||||
|
tstyle_bold = copy.copy(tstyle)
|
||||||
|
tstyle_bold.fontName = "OpenSansBd"
|
||||||
|
tstyle_bold_right = copy.copy(tstyle_bold)
|
||||||
|
tstyle_bold_right.alignment = TA_RIGHT
|
||||||
|
|
||||||
|
tdata = [
|
||||||
|
[
|
||||||
|
Paragraph(_("Event") + " / " + _("Product"), tstyle_bold),
|
||||||
|
Paragraph(_("Price"), tstyle_bold_right),
|
||||||
|
Paragraph(_("Tax rate"), tstyle_bold_right),
|
||||||
|
Paragraph("#", tstyle_bold_right),
|
||||||
|
Paragraph(_("Net total"), tstyle_bold_right),
|
||||||
|
Paragraph(_("Tax total"), tstyle_bold_right),
|
||||||
|
Paragraph(_("Gross total"), tstyle_bold_right),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
qs = (
|
||||||
|
self._transaction_qs(form_data)
|
||||||
|
.order_by(
|
||||||
|
"order__event__slug",
|
||||||
|
F("fee_type").asc(nulls_first=True),
|
||||||
|
F("internal_type").asc(nulls_first=True),
|
||||||
|
F("item__category__position").asc(nulls_first=True),
|
||||||
|
F("item__category_id").asc(nulls_first=True),
|
||||||
|
F("item__position").asc(nulls_last=True),
|
||||||
|
"item_id",
|
||||||
|
"variation__position",
|
||||||
|
"variation_id",
|
||||||
|
"price",
|
||||||
|
"tax_rate",
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
"order__event__slug",
|
||||||
|
"order__event__name",
|
||||||
|
"item_id",
|
||||||
|
"item__internal_name",
|
||||||
|
"item__name",
|
||||||
|
"variation__value",
|
||||||
|
"variation_id",
|
||||||
|
"fee_type",
|
||||||
|
"internal_type",
|
||||||
|
"price",
|
||||||
|
"tax_rate",
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
sum_cont=Sum("count"),
|
||||||
|
sum_price=Sum(F("count") * F("price")),
|
||||||
|
sum_tax=Sum(F("count") * F("tax_value")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tstyledata = []
|
||||||
|
fee_types = dict(OrderFee.FEE_TYPES)
|
||||||
|
|
||||||
|
sum_cnt_by_tax_rate = defaultdict(int)
|
||||||
|
sum_price_by_tax_rate = defaultdict(Decimal)
|
||||||
|
sum_tax_by_tax_rate = defaultdict(Decimal)
|
||||||
|
last_event_group = None
|
||||||
|
for r in qs:
|
||||||
|
if r["order__event__slug"] != last_event_group:
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(
|
||||||
|
"{} [{}]".format(
|
||||||
|
r["order__event__name"], r["order__event__slug"]
|
||||||
|
),
|
||||||
|
tstyle_bold,
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata.append(
|
||||||
|
("SPAN", (0, len(tdata) - 1), (-1, len(tdata) - 1)),
|
||||||
|
)
|
||||||
|
last_event_group = r["order__event__slug"]
|
||||||
|
|
||||||
|
if r["item_id"]:
|
||||||
|
if r["variation_id"]:
|
||||||
|
text = f'{r["item__internal_name"] or r["item__name"]} - {r["variation__value"]}'
|
||||||
|
else:
|
||||||
|
text = str(r["item__internal_name"] or r["item__name"])
|
||||||
|
elif r["fee_type"]:
|
||||||
|
if r["internal_type"]:
|
||||||
|
text = f'{fee_types.get(r["fee_type"], r["fee_type"])} - {r["internal_type"]}'
|
||||||
|
else:
|
||||||
|
text = fee_types.get(r["fee_type"], r["fee_type"])
|
||||||
|
else:
|
||||||
|
text = "?"
|
||||||
|
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(text, tstyle),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(r["price"], "EUR")
|
||||||
|
if r["price"] is not None
|
||||||
|
else "",
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
Paragraph(localize(r["tax_rate"].normalize()) + " %", tstyle_right),
|
||||||
|
Paragraph(str(r["sum_cont"]), tstyle_right),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(r["sum_price"] - r["sum_tax"], "EUR"), tstyle_right
|
||||||
|
),
|
||||||
|
Paragraph(money_filter(r["sum_tax"], "EUR"), tstyle_right),
|
||||||
|
Paragraph(money_filter(r["sum_price"], "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
sum_cnt_by_tax_rate[r["tax_rate"]] += r["sum_cont"]
|
||||||
|
sum_price_by_tax_rate[r["tax_rate"]] += r["sum_price"]
|
||||||
|
sum_tax_by_tax_rate[r["tax_rate"]] += r["sum_tax"]
|
||||||
|
|
||||||
|
if len(sum_tax_by_tax_rate) > 1:
|
||||||
|
for tax_rate in sorted(sum_tax_by_tax_rate.keys(), reverse=True):
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Sum"), tstyle),
|
||||||
|
Paragraph("", tstyle_right),
|
||||||
|
Paragraph(localize(tax_rate.normalize()) + " %", tstyle_right),
|
||||||
|
Paragraph("", tstyle_right),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
sum_price_by_tax_rate[tax_rate]
|
||||||
|
- sum_tax_by_tax_rate[tax_rate],
|
||||||
|
"EUR",
|
||||||
|
),
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(sum_tax_by_tax_rate[tax_rate], "EUR"), tstyle_right
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(sum_price_by_tax_rate[tax_rate], "EUR"),
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata += [
|
||||||
|
(
|
||||||
|
"LINEABOVE",
|
||||||
|
(0, -len(sum_tax_by_tax_rate) - 1),
|
||||||
|
(-1, -len(sum_tax_by_tax_rate) - 1),
|
||||||
|
0.5,
|
||||||
|
colors.black,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Sum"), tstyle_bold),
|
||||||
|
Paragraph("", tstyle_right),
|
||||||
|
Paragraph("", tstyle_right),
|
||||||
|
Paragraph("", tstyle_bold_right),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
sum(sum_price_by_tax_rate.values())
|
||||||
|
- sum(sum_tax_by_tax_rate.values()),
|
||||||
|
"EUR",
|
||||||
|
),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(sum(sum_tax_by_tax_rate.values()), "EUR"),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(sum(sum_price_by_tax_rate.values()), "EUR"),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata += [
|
||||||
|
("LINEBELOW", (0, 0), (-1, 0), 0.5, colors.black),
|
||||||
|
("LINEABOVE", (0, -1), (-1, -1), 0.5, colors.black),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||||
|
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("RIGHTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("LEFTPADDING", (0, 0), (0, -1), 0),
|
||||||
|
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
||||||
|
]
|
||||||
|
colwidths = [
|
||||||
|
a * (self.pagesize[0] - 20 * mm)
|
||||||
|
for a in [0.28, 0.1, 0.1, 0.1, 0.14, 0.14, 0.14]
|
||||||
|
]
|
||||||
|
table = Table(tdata, colWidths=colwidths, repeatRows=1)
|
||||||
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
return [table]
|
||||||
|
|
||||||
|
def _table_payments(self, form_data):
|
||||||
|
tstyle = copy.copy(self.get_style())
|
||||||
|
tstyle.fontSize = 8
|
||||||
|
tstyle.leading = 10
|
||||||
|
tstyle_right = copy.copy(tstyle)
|
||||||
|
tstyle_right.alignment = TA_RIGHT
|
||||||
|
tstyle_bold = copy.copy(tstyle)
|
||||||
|
tstyle_bold.fontName = "OpenSansBd"
|
||||||
|
tstyle_bold_right = copy.copy(tstyle_bold)
|
||||||
|
tstyle_bold_right.alignment = TA_RIGHT
|
||||||
|
|
||||||
|
tdata = [
|
||||||
|
[
|
||||||
|
Paragraph(_("Payment method"), tstyle_bold),
|
||||||
|
Paragraph(_("Payments"), tstyle_bold_right),
|
||||||
|
Paragraph(_("Refunds"), tstyle_bold_right),
|
||||||
|
Paragraph(_("Total"), tstyle_bold_right),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
p_qs = (
|
||||||
|
self._payment_qs(form_data)
|
||||||
|
.order_by(
|
||||||
|
"provider",
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
"provider",
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
sum_amount=Sum("amount"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r_qs = (
|
||||||
|
self._refund_qs(form_data)
|
||||||
|
.order_by(
|
||||||
|
"provider",
|
||||||
|
)
|
||||||
|
.values(
|
||||||
|
"provider",
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
sum_amount=Sum("amount"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tstyledata = []
|
||||||
|
provider_names = dict(get_all_payment_providers())
|
||||||
|
|
||||||
|
payments_by_provider = {r["provider"]: r["sum_amount"] for r in p_qs}
|
||||||
|
refunds_by_provider = {r["provider"]: r["sum_amount"] for r in r_qs}
|
||||||
|
|
||||||
|
providers = sorted(
|
||||||
|
list(set(payments_by_provider.keys()) | set(refunds_by_provider.keys()))
|
||||||
|
)
|
||||||
|
for p in providers:
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(provider_names.get(p, p), tstyle),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(payments_by_provider[p], "EUR")
|
||||||
|
if p in payments_by_provider
|
||||||
|
else "",
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(refunds_by_provider[p], "EUR")
|
||||||
|
if p in refunds_by_provider
|
||||||
|
else "",
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
payments_by_provider.get(p, Decimal("0.00"))
|
||||||
|
- refunds_by_provider.get(p, Decimal("0.00")),
|
||||||
|
"EUR",
|
||||||
|
),
|
||||||
|
tstyle_right,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Sum"), tstyle_bold),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
sum(payments_by_provider.values(), Decimal("0.00")), "EUR"
|
||||||
|
),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
sum(refunds_by_provider.values(), Decimal("0.00")), "EUR"
|
||||||
|
),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
Paragraph(
|
||||||
|
money_filter(
|
||||||
|
sum(payments_by_provider.values(), Decimal("0.00"))
|
||||||
|
- sum(refunds_by_provider.values(), Decimal("0.00")),
|
||||||
|
"EUR",
|
||||||
|
),
|
||||||
|
tstyle_bold_right,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata += [
|
||||||
|
("LINEBELOW", (0, 0), (-1, 0), 0.5, colors.black),
|
||||||
|
("LINEABOVE", (0, -1), (-1, -1), 0.5, colors.black),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||||
|
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("RIGHTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("LEFTPADDING", (0, 0), (0, -1), 0),
|
||||||
|
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
||||||
|
]
|
||||||
|
colwidths = [a * (self.pagesize[0] - 20 * mm) for a in [0.58, 0.14, 0.14, 0.14]]
|
||||||
|
table = Table(tdata, colWidths=colwidths, repeatRows=1)
|
||||||
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
return [table]
|
||||||
|
|
||||||
|
def _table_open_items(self, form_data):
|
||||||
|
tstyle = copy.copy(self.get_style())
|
||||||
|
tstyle.fontSize = 8
|
||||||
|
tstyle.leading = 10
|
||||||
|
tstyle_right = copy.copy(tstyle)
|
||||||
|
tstyle_right.alignment = TA_RIGHT
|
||||||
|
tstyle_center = copy.copy(tstyle)
|
||||||
|
tstyle_center.alignment = TA_CENTER
|
||||||
|
tstyle_bold = copy.copy(tstyle)
|
||||||
|
tstyle_bold.fontName = "OpenSansBd"
|
||||||
|
tstyle_bold_right = copy.copy(tstyle_bold)
|
||||||
|
tstyle_bold_right.alignment = TA_RIGHT
|
||||||
|
|
||||||
|
if form_data.get("date_range"):
|
||||||
|
(
|
||||||
|
df_start,
|
||||||
|
df_end,
|
||||||
|
) = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
df_start = df_end = None
|
||||||
|
|
||||||
|
tstyledata = []
|
||||||
|
tdata = []
|
||||||
|
|
||||||
|
if df_start:
|
||||||
|
tx_before = self._transaction_qs(form_data, ignore_dates=True).filter(
|
||||||
|
datetime__lt=df_start
|
||||||
|
).aggregate(s=Sum(F("count") * F("price")))["s"] or Decimal("0.00")
|
||||||
|
p_before = self._payment_qs(form_data, ignore_dates=True).filter(
|
||||||
|
payment_date__lt=df_start
|
||||||
|
).aggregate(s=Sum("amount"))["s"] or Decimal("0.00")
|
||||||
|
r_before = self._refund_qs(form_data, ignore_dates=True).filter(
|
||||||
|
execution_date__lt=df_start
|
||||||
|
).aggregate(s=Sum("amount"))["s"] or Decimal("0.00")
|
||||||
|
open_before = tx_before - p_before + r_before
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(
|
||||||
|
_("Pending payments at {datetime}").format(
|
||||||
|
datetime=date_format(
|
||||||
|
df_start - datetime.timedelta.resolution,
|
||||||
|
"SHORT_DATETIME_FORMAT",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tstyle,
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
Paragraph(money_filter(open_before, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
open_before = Decimal("0.00")
|
||||||
|
|
||||||
|
tx_during = self._transaction_qs(form_data).aggregate(
|
||||||
|
s=Sum(F("count") * F("price"))
|
||||||
|
)["s"] or Decimal("0.00")
|
||||||
|
p_during = self._payment_qs(form_data).aggregate(s=Sum("amount"))[
|
||||||
|
"s"
|
||||||
|
] or Decimal("0.00")
|
||||||
|
r_during = self._refund_qs(form_data).aggregate(s=Sum("amount"))[
|
||||||
|
"s"
|
||||||
|
] or Decimal("0.00")
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Orders"), tstyle),
|
||||||
|
Paragraph("+", tstyle_center),
|
||||||
|
Paragraph(money_filter(tx_during, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Payments"), tstyle),
|
||||||
|
Paragraph("-", tstyle_center),
|
||||||
|
Paragraph(money_filter(p_during, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Refunds"), tstyle),
|
||||||
|
Paragraph("+", tstyle_center),
|
||||||
|
Paragraph(money_filter(r_during, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
open_after = open_before + tx_during - p_during + r_during
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(
|
||||||
|
_("Pending payments at {datetime}").format(
|
||||||
|
datetime=date_format(
|
||||||
|
(df_end or now()) - datetime.timedelta.resolution,
|
||||||
|
"SHORT_DATETIME_FORMAT",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tstyle_bold,
|
||||||
|
),
|
||||||
|
Paragraph("=", tstyle_center),
|
||||||
|
Paragraph(money_filter(open_after, "EUR"), tstyle_bold_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata += [
|
||||||
|
("LINEABOVE", (0, -1), (-1, -1), 0.5, colors.black),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||||
|
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("RIGHTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("LEFTPADDING", (0, 0), (0, -1), 0),
|
||||||
|
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
||||||
|
]
|
||||||
|
colwidths = [a * (self.pagesize[0] - 20 * mm) for a in [0.8, 0.06, 0.14]]
|
||||||
|
table = Table(tdata, colWidths=colwidths)
|
||||||
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
return [table]
|
||||||
|
|
||||||
|
def _table_gift_cards(self, form_data):
|
||||||
|
tstyle = copy.copy(self.get_style())
|
||||||
|
tstyle.fontSize = 8
|
||||||
|
tstyle.leading = 10
|
||||||
|
tstyle_right = copy.copy(tstyle)
|
||||||
|
tstyle_right.alignment = TA_RIGHT
|
||||||
|
tstyle_bold = copy.copy(tstyle)
|
||||||
|
tstyle_bold.fontName = "OpenSansBd"
|
||||||
|
tstyle_bold_right = copy.copy(tstyle_bold)
|
||||||
|
tstyle_bold_right.alignment = TA_RIGHT
|
||||||
|
|
||||||
|
if form_data.get("date_range"):
|
||||||
|
df_start, df_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(
|
||||||
|
now(), form_data["date_range"], self.timezone
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
df_start = df_end = None
|
||||||
|
|
||||||
|
tstyledata = []
|
||||||
|
tdata = []
|
||||||
|
|
||||||
|
if df_start:
|
||||||
|
tx_before = self._giftcard_transaction_qs(
|
||||||
|
form_data, ignore_dates=True
|
||||||
|
).filter(datetime__lt=df_start).aggregate(s=Sum("value"))["s"] or Decimal(
|
||||||
|
"0.00"
|
||||||
|
)
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(
|
||||||
|
_("Total gift card value at {datetime}").format(
|
||||||
|
datetime=date_format(
|
||||||
|
df_start - datetime.timedelta.resolution,
|
||||||
|
"SHORT_DATETIME_FORMAT",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tstyle,
|
||||||
|
),
|
||||||
|
Paragraph(money_filter(tx_before, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
tx_before = Decimal("0.00")
|
||||||
|
|
||||||
|
tx_during = self._giftcard_transaction_qs(form_data).aggregate(s=Sum("value"))[
|
||||||
|
"s"
|
||||||
|
] or Decimal("0.00")
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(_("Gift card transactions"), tstyle),
|
||||||
|
Paragraph(money_filter(tx_during, "EUR"), tstyle_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
open_after = tx_before + tx_during
|
||||||
|
tdata.append(
|
||||||
|
[
|
||||||
|
Paragraph(
|
||||||
|
_("Total gift card value at {datetime}").format(
|
||||||
|
datetime=date_format(
|
||||||
|
(df_end or now()) - datetime.timedelta.resolution,
|
||||||
|
"SHORT_DATETIME_FORMAT",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tstyle_bold,
|
||||||
|
),
|
||||||
|
Paragraph(money_filter(open_after, "EUR"), tstyle_bold_right),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tstyledata += [
|
||||||
|
("LINEABOVE", (0, -1), (-1, -1), 0.5, colors.black),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||||
|
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 2),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("RIGHTPADDING", (0, 0), (-1, -1), 4),
|
||||||
|
("LEFTPADDING", (0, 0), (0, -1), 0),
|
||||||
|
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
||||||
|
]
|
||||||
|
colwidths = [a * (self.pagesize[0] - 20 * mm) for a in [0.86, 0.14]]
|
||||||
|
table = Table(tdata, colWidths=colwidths)
|
||||||
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
return [table]
|
||||||
|
|
||||||
|
def _render_pdf(self, form_data, output_file=None):
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".pdf") as f:
|
||||||
|
ReportlabExportMixin.register_fonts()
|
||||||
|
doc = self.get_doc_template()(
|
||||||
|
output_file or f.name,
|
||||||
|
pagesize=self.pagesize,
|
||||||
|
leftMargin=10 * mm,
|
||||||
|
rightMargin=10 * mm,
|
||||||
|
topMargin=20 * mm,
|
||||||
|
bottomMargin=15 * mm,
|
||||||
|
)
|
||||||
|
doc.addPageTemplates(
|
||||||
|
[
|
||||||
|
PageTemplate(
|
||||||
|
id="All",
|
||||||
|
frames=self.get_frames(doc),
|
||||||
|
onPage=self.on_page,
|
||||||
|
pagesize=self.pagesize,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
style_h1 = copy.copy(self.get_style())
|
||||||
|
style_h1.fontName = "OpenSansBd"
|
||||||
|
style_h1.fontSize = 14
|
||||||
|
style_h2 = copy.copy(self.get_style())
|
||||||
|
style_h2.fontName = "OpenSansBd"
|
||||||
|
style_h2.fontSize = 12
|
||||||
|
style_small = copy.copy(self.get_style())
|
||||||
|
style_small.fontSize = 8
|
||||||
|
style_small.leading = 10
|
||||||
|
|
||||||
|
story = [
|
||||||
|
Paragraph(self.verbose_name, style_h1),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
Paragraph(
|
||||||
|
"<br />".join(escape(f) for f in self.describe_filters(form_data)),
|
||||||
|
style_small,
|
||||||
|
),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
Paragraph(_("Orders"), style_h2),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
*self._table_transactions(form_data),
|
||||||
|
Spacer(0, 8 * mm),
|
||||||
|
Paragraph(_("Payments"), style_h2),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
*self._table_payments(form_data),
|
||||||
|
Spacer(0, 8 * mm),
|
||||||
|
KeepTogether(
|
||||||
|
[
|
||||||
|
Paragraph(_("Open items"), style_h2),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
*self._table_open_items(form_data),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
if (
|
||||||
|
self.is_multievent
|
||||||
|
and self.events.count() == self.organizer.events.count()
|
||||||
|
):
|
||||||
|
story += [
|
||||||
|
Spacer(0, 8 * mm),
|
||||||
|
KeepTogether(
|
||||||
|
[
|
||||||
|
Paragraph(_("Gift cards"), style_h2),
|
||||||
|
Spacer(0, 3 * mm),
|
||||||
|
*self._table_gift_cards(form_data),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
doc.build(story, canvasmaker=self.canvas_class(doc))
|
||||||
|
f.seek(0)
|
||||||
|
return (
|
||||||
|
f"{self.get_filename()}.pdf",
|
||||||
|
"application/pdf",
|
||||||
|
None if output_file else f.read(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
if self.is_multievent:
|
||||||
|
return f"{self.filename}-{self.organizer.slug}"
|
||||||
|
else:
|
||||||
|
return f"{self.filename}-{self.event.slug}"
|
||||||
|
|
||||||
|
def render(self, form_data: dict, output_file=None):
|
||||||
|
return self._render_pdf(form_data, output_file=output_file)
|
||||||
@@ -46,6 +46,7 @@ from django.db import models
|
|||||||
from django.db.models import DateTimeField, 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
|
||||||
from django.utils.formats import date_format, localize
|
from django.utils.formats import date_format, localize
|
||||||
|
from django.utils.html import format_html
|
||||||
from django.utils.timezone import get_current_timezone, now
|
from django.utils.timezone import get_current_timezone, now
|
||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
|
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
|
||||||
@@ -54,6 +55,7 @@ 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.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle
|
from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle
|
||||||
|
|
||||||
from pretix.base.decimal import round_decimal
|
from pretix.base.decimal import round_decimal
|
||||||
@@ -69,8 +71,46 @@ from pretix.base.timeframes import (
|
|||||||
from pretix.control.forms.filter import OverviewFilterForm
|
from pretix.control.forms.filter import OverviewFilterForm
|
||||||
|
|
||||||
|
|
||||||
|
class NumberedCanvas(Canvas):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.font_regular = kwargs.pop('font_regular')
|
||||||
|
self.x = kwargs.pop('x', 15 * mm)
|
||||||
|
self.y = kwargs.pop('y', 10 * mm)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._saved_page_states = []
|
||||||
|
|
||||||
|
def showPage(self):
|
||||||
|
self._saved_page_states.append(dict(self.__dict__))
|
||||||
|
self._startPage()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
num_pages = len(self._saved_page_states)
|
||||||
|
for state in self._saved_page_states:
|
||||||
|
self.__dict__.update(state)
|
||||||
|
self.draw_page_number(num_pages)
|
||||||
|
Canvas.showPage(self)
|
||||||
|
Canvas.save(self)
|
||||||
|
|
||||||
|
def draw_page_number(self, page_count):
|
||||||
|
self.saveState()
|
||||||
|
self.setFont(self.font_regular, 8)
|
||||||
|
self.drawString(self.x, self.y, _("Page %d of %d") % (self._pageNumber, page_count,))
|
||||||
|
self.restoreState()
|
||||||
|
|
||||||
|
|
||||||
class ReportlabExportMixin:
|
class ReportlabExportMixin:
|
||||||
multiBuild = False # noqa
|
multiBuild = False # noqa
|
||||||
|
numbered_canvas = False
|
||||||
|
|
||||||
|
def canvas_class(self, doc):
|
||||||
|
if self.numbered_canvas:
|
||||||
|
def _cl(*args, **kwargs):
|
||||||
|
kwargs['font_regular'] = 'OpenSans'
|
||||||
|
kwargs['x'] = doc.leftMargin
|
||||||
|
kwargs['y'] = 10 * mm
|
||||||
|
return NumberedCanvas(*args, **kwargs)
|
||||||
|
return _cl
|
||||||
|
return Canvas
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pagesize(self):
|
def pagesize(self):
|
||||||
@@ -115,9 +155,9 @@ class ReportlabExportMixin:
|
|||||||
PageTemplate(id='All', frames=self.get_frames(doc), onPage=self.on_page, pagesize=self.pagesize)
|
PageTemplate(id='All', frames=self.get_frames(doc), onPage=self.on_page, pagesize=self.pagesize)
|
||||||
])
|
])
|
||||||
if self.multiBuild:
|
if self.multiBuild:
|
||||||
doc.multiBuild(self.get_story(doc, form_data))
|
doc.multiBuild(self.get_story(doc, form_data), canvasmaker=self.canvas_class(doc))
|
||||||
else:
|
else:
|
||||||
doc.build(self.get_story(doc, form_data))
|
doc.build(self.get_story(doc, form_data), canvasmaker=self.canvas_class(doc))
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
@@ -156,7 +196,8 @@ class ReportlabExportMixin:
|
|||||||
|
|
||||||
tz = get_current_timezone()
|
tz = get_current_timezone()
|
||||||
canvas.setFont('OpenSans', 8)
|
canvas.setFont('OpenSans', 8)
|
||||||
canvas.drawString(doc.leftMargin, 10 * mm, _("Page %d") % (doc.page,))
|
if not self.numbered_canvas:
|
||||||
|
canvas.drawString(doc.leftMargin, 10 * mm, _("Page %d") % (doc.page,))
|
||||||
canvas.drawRightString(self.pagesize[0] - doc.rightMargin, 10 * mm,
|
canvas.drawRightString(self.pagesize[0] - doc.rightMargin, 10 * mm,
|
||||||
_("Created: %s") % date_format(now().astimezone(tz), 'SHORT_DATETIME_FORMAT'))
|
_("Created: %s") % date_format(now().astimezone(tz), 'SHORT_DATETIME_FORMAT'))
|
||||||
|
|
||||||
@@ -186,6 +227,7 @@ class ReportlabExportMixin:
|
|||||||
|
|
||||||
class Report(ReportlabExportMixin, BaseExporter):
|
class Report(ReportlabExportMixin, BaseExporter):
|
||||||
name = "report"
|
name = "report"
|
||||||
|
numbered_canvas = True
|
||||||
|
|
||||||
def verbose_name(self) -> str:
|
def verbose_name(self) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -203,7 +245,6 @@ class OverviewReport(Report):
|
|||||||
verbose_name = gettext_lazy('Order overview (PDF)')
|
verbose_name = gettext_lazy('Order overview (PDF)')
|
||||||
category = pgettext_lazy('export_category', 'Analysis')
|
category = pgettext_lazy('export_category', 'Analysis')
|
||||||
description = gettext_lazy('Download a PDF version of the key sales numbers per ticket type.')
|
description = gettext_lazy('Download a PDF version of the key sales numbers per ticket type.')
|
||||||
featured = True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pagesize(self):
|
def pagesize(self):
|
||||||
@@ -394,6 +435,12 @@ class OverviewReport(Report):
|
|||||||
label=_('Date range'),
|
label=_('Date range'),
|
||||||
include_future_frames=False,
|
include_future_frames=False,
|
||||||
required=False,
|
required=False,
|
||||||
|
help_text=format_html('<strong class="text-danger">{}</strong>', _(
|
||||||
|
'Filtering this report by date is not recommended as it might lead to misleading information since '
|
||||||
|
'this report only sees the current state of any order, not any changes made to the order previously. '
|
||||||
|
'This date filter might be removed in the future. '
|
||||||
|
'Use the "Accounting report" in the export section instead.'
|
||||||
|
))
|
||||||
)
|
)
|
||||||
return f.fields
|
return f.fields
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
#
|
#
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from pretix.base.signals import register_data_exporters
|
from pretix.base.signals import (
|
||||||
|
register_data_exporters, register_multievent_data_exporters,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_data_exporters, dispatch_uid="export_overview_report_pdf")
|
@receiver(register_data_exporters, dispatch_uid="export_overview_report_pdf")
|
||||||
@@ -40,3 +42,10 @@ def register_report_ordertaxlist(sender, **kwargs):
|
|||||||
def register_report_ordertaxlistpdf(sender, **kwargs):
|
def register_report_ordertaxlistpdf(sender, **kwargs):
|
||||||
from .exporters import OrderTaxListReportPDF
|
from .exporters import OrderTaxListReportPDF
|
||||||
return OrderTaxListReportPDF
|
return OrderTaxListReportPDF
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(register_data_exporters, dispatch_uid="export_accounting_report_pdf")
|
||||||
|
@receiver(register_multievent_data_exporters, dispatch_uid="multi_export_accounting_report_pdf")
|
||||||
|
def register_report_accounting_report_pdf(sender, **kwargs):
|
||||||
|
from .accountingreport import ReportExporter
|
||||||
|
return ReportExporter
|
||||||
|
|||||||
Reference in New Issue
Block a user