Bank transfer: Allow dashes in event slug to be missing (Z#23216859) (#5682)

* Bank transfer: Allow dashes in event slug to be missing (Z#23216859)

* Update src/pretix/plugins/banktransfer/tasks.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Update src/pretix/plugins/banktransfer/tasks.py

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Apply suggestions from code review

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
This commit is contained in:
Raphael Michel
2025-12-05 10:54:03 +01:00
committed by GitHub
parent aa02cc7968
commit c491c8232e
2 changed files with 51 additions and 13 deletions

View File

@@ -34,8 +34,10 @@
import json import json
import logging import logging
import operator
import re import re
from decimal import Decimal from decimal import Decimal
from functools import reduce
import dateutil.parser import dateutil.parser
from celery.exceptions import MaxRetriesExceededError from celery.exceptions import MaxRetriesExceededError
@@ -117,20 +119,26 @@ def _find_order_for_code(base_qs, code):
pass pass
def _find_order_for_invoice_id(base_qs, prefix, number): def _find_order_for_invoice_id(base_qs, prefixes, number):
try: try:
# Working with __iregex here is an experiment, if this turns out to be too slow in production # Working with __iregex here is an experiment, if this turns out to be too slow in production
# we might need to switch to a different approach. # we might need to switch to a different approach.
r = [
Q(
prefix__istartswith=prefix, # redundant, but hopefully makes it a little faster
full_invoice_no__iregex=prefix + r'[\- ]*0*' + number
)
for prefix in set(prefixes)
]
return base_qs.select_related('order').get( return base_qs.select_related('order').get(
prefix__istartswith=prefix, # redundant, but hopefully makes it a little faster reduce(operator.or_, r)
full_invoice_no__iregex=prefix + r'[\- ]*0*' + number
).order ).order
except (Invoice.DoesNotExist, Invoice.MultipleObjectsReturned): except (Invoice.DoesNotExist, Invoice.MultipleObjectsReturned):
pass pass
@transaction.atomic @transaction.atomic
def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = None, organizer: Organizer = None): def _handle_transaction(trans: BankTransaction, matches: tuple, regex_match_to_slug, event: Event = None, organizer: Organizer = None):
orders = [] orders = []
if event: if event:
for slug, code in matches: for slug, code in matches:
@@ -139,18 +147,19 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N
if order.code not in {o.code for o in orders}: if order.code not in {o.code for o in orders}:
orders.append(order) orders.append(order)
else: else:
order = _find_order_for_invoice_id(Invoice.objects.filter(event=event), slug, code) order = _find_order_for_invoice_id(Invoice.objects.filter(event=event), (slug, regex_match_to_slug.get(slug, slug)), code)
if order and order.code not in {o.code for o in orders}: if order and order.code not in {o.code for o in orders}:
orders.append(order) orders.append(order)
else: else:
qs = Order.objects.filter(event__organizer=organizer) qs = Order.objects.filter(event__organizer=organizer)
for slug, code in matches: for slug, code in matches:
order = _find_order_for_code(qs.filter(event__slug__iexact=slug), code) original_slug = regex_match_to_slug.get(slug, slug)
order = _find_order_for_code(qs.filter(Q(event__slug__iexact=slug) | Q(event__slug__iexact=original_slug)), code)
if order: if order:
if order.code not in {o.code for o in orders}: if order.code not in {o.code for o in orders}:
orders.append(order) orders.append(order)
else: else:
order = _find_order_for_invoice_id(Invoice.objects.filter(event__organizer=organizer), slug, code) order = _find_order_for_invoice_id(Invoice.objects.filter(event__organizer=organizer), (slug, original_slug), code)
if order and order.code not in {o.code for o in orders}: if order and order.code not in {o.code for o in orders}:
orders.append(order) orders.append(order)
@@ -366,22 +375,37 @@ def process_banktransfers(self, job: int, data: list) -> None:
transactions = _get_unknown_transactions(job, data, **job.owner_kwargs) transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
# Match order codes # Match order codes
regex_match_to_slug = {}
code_len_agg = Order.objects.filter(event__organizer=job.organizer).annotate( code_len_agg = Order.objects.filter(event__organizer=job.organizer).annotate(
clen=Length('code') clen=Length('code')
).aggregate(min=Min('clen'), max=Max('clen')) ).aggregate(min=Min('clen'), max=Max('clen'))
if job.event: if job.event:
prefixes = {job.event.slug.upper()} prefixes = {job.event.slug.upper(), job.event.slug.upper().replace("-", "")}
if "-" in job.event.slug:
regex_match_to_slug[job.event.slug.upper().replace("-", "")] = job.event.slug
else: else:
prefixes = {e.slug.upper() for e in job.organizer.events.all()} prefixes = set()
for e in job.organizer.events.all():
prefixes.add(e.slug.upper())
if "-" in e.slug:
prefixes.add(e.slug.upper().replace("-", ""))
regex_match_to_slug[e.slug.upper().replace("-", "")] = e.slug
# Match invoice numbers # Match invoice numbers
inr_len_agg = Invoice.objects.filter(event__organizer=job.organizer).annotate( inr_len_agg = Invoice.objects.filter(event__organizer=job.organizer).annotate(
clen=Length('invoice_no') clen=Length('invoice_no')
).aggregate(min=Min('clen'), max=Max('clen')) ).aggregate(min=Min('clen'), max=Max('clen'))
if job.event: if job.event:
prefixes |= {p.rstrip(' -') for p in Invoice.objects.filter(event=job.event).distinct().values_list('prefix', flat=True)} invoice_prefixes = Invoice.objects.filter(event=job.event)
else: else:
prefixes |= {p.rstrip(' -') for p in Invoice.objects.filter(event__organizer=job.organizer).distinct().values_list('prefix', flat=True)} invoice_prefixes = Invoice.objects.filter(event__organizer=job.organizer)
for p in invoice_prefixes.order_by().distinct().values_list('prefix', flat=True):
prefix = p.rstrip(" -")
prefixes.add(prefix)
if "-" in prefix:
prefix_nodash = prefix.replace("-", "")
prefixes.add(prefix_nodash)
regex_match_to_slug[prefix_nodash] = prefix
pattern = re.compile( pattern = re.compile(
"(%s)[ \\-_]*([A-Z0-9]{%s,%s})" % ( "(%s)[ \\-_]*([A-Z0-9]{%s,%s})" % (
@@ -409,9 +433,9 @@ def process_banktransfers(self, job: int, data: list) -> None:
if matches: if matches:
if job.event: if job.event:
_handle_transaction(trans, matches, event=job.event) _handle_transaction(trans, matches, regex_match_to_slug, event=job.event)
else: else:
_handle_transaction(trans, matches, organizer=job.organizer) _handle_transaction(trans, matches, regex_match_to_slug, organizer=job.organizer)
else: else:
trans.state = BankTransaction.STATE_NOMATCH trans.state = BankTransaction.STATE_NOMATCH
trans.save() trans.save()

View File

@@ -385,6 +385,20 @@ def test_mark_paid_organizer_dash_in_slug(env, orga_job):
assert env[2].status == Order.STATUS_PAID assert env[2].status == Order.STATUS_PAID
@pytest.mark.django_db
def test_mark_paid_organizer_dash_in_slug_missing(env, orga_job):
env[0].slug = "foo-bar"
env[0].save()
process_banktransfers(orga_job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung FOOBAR1234S',
'date': '2016-01-26',
'amount': '23.00'
}])
env[2].refresh_from_db()
assert env[2].status == Order.STATUS_PAID
@pytest.mark.django_db @pytest.mark.django_db
def test_mark_paid_organizer_varying_order_code_length(env, orga_job): def test_mark_paid_organizer_varying_order_code_length(env, orga_job):
env[2].code = "123412341234" env[2].code = "123412341234"