Invoice renderer: Group invoice lines even with addons (Z#23173618)

This commit is contained in:
Raphael Michel
2025-01-13 22:04:03 +01:00
parent 3ce6030122
commit b554fc1ad5
+62 -1
View File
@@ -62,6 +62,63 @@ from pretix.presale.style import get_fonts
logger = logging.getLogger(__name__)
def addon_aware_groupby(iterable, key, is_addon):
"""
We use groupby() to visually group identical lines on an invoice. For example, instead of
Product 1 5.00 EUR
Product 1 5.00 EUR
Product 1 5.00 EUR
Product 2 7.00 EUR
We want to print
3x Product 1 5.00 EUR = 15.00 EUR
Product 2 7.00 EUR
However, this fails for setups with addon-products since groupby() only groups consecutive
lines with the same identity. So in
Product 1 5.00 EUR
+ Addon 1 2.00 EUR
Product 1 5.00 EUR
+ Addon 1 2.00 EUR
Product 1 5.00 EUR
+ Addon 2 3.00 EUR
There is no consecutive repetition of the same entity. This function provides a specialised groupby which
understands the product/addon relationship and packs groups of these addons together if they are, in fact,
identical groups:
2x Product 1 5.00 EUR = 10.00 EUR
+ 2x Addon 1 2.00 EUR = 4.00 EUR
Product 1 5.00 EUR
+ Addon 2 3.00 EUR
"""
packed_groups = []
for i in iterable:
if is_addon(i):
packed_groups[-1][1].append(i)
else:
packed_groups.append((i, []))
# Each packed_groups element contains (parent product, list of addon products)
def _reorder(packed_groups):
# Emit the products as individual products again, reordered by "all parent products, then all addon products"
# within each group.
for group_grouper, grouped in groupby(packed_groups, key=lambda g: key(g[0]) + tuple(key(a) for a in g[1])):
grouped = list(grouped)
for grp in grouped:
yield grp[0]
for i in range(max(len(grp[1]) for grp in grouped)):
for grp in grouped:
if len(grp[1]) > i:
yield grp[1][i]
return groupby(_reorder(packed_groups), key)
class NumberedCanvas(Canvas):
def __init__(self, *args, **kwargs):
self.font_regular = kwargs.pop('font_regular')
@@ -635,7 +692,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
line.event_date_from, line.event_date_to)
total = Decimal('0.00')
for (description, tax_rate, tax_name, net_value, gross_value, *ignored), lines in groupby(self.invoice.lines.all(), key=_group_key):
for (description, tax_rate, tax_name, net_value, gross_value, *ignored), lines in addon_aware_groupby(
self.invoice.lines.all(),
key=_group_key,
is_addon=lambda l: l.description.startswith(" +"),
):
lines = list(lines)
if has_taxes:
if len(lines) > 1: