diff --git a/src/pretix/plugins/banktransfer/migrations/0004_auto_20170619_1125.py b/src/pretix/plugins/banktransfer/migrations/0004_auto_20170619_1125.py
new file mode 100644
index 0000000000..c53d7917fd
--- /dev/null
+++ b/src/pretix/plugins/banktransfer/migrations/0004_auto_20170619_1125.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.2 on 2017-06-19 11:25
+from __future__ import unicode_literals
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0062_auto_20170602_0948'),
+ ('banktransfer', '0003_banktransaction_comment'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='bankimportjob',
+ name='organizer',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Organizer'),
+ ),
+ migrations.AddField(
+ model_name='banktransaction',
+ name='organizer',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Organizer'),
+ ),
+ migrations.AlterField(
+ model_name='bankimportjob',
+ name='event',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event'),
+ ),
+ migrations.AlterField(
+ model_name='banktransaction',
+ name='event',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='banktransaction',
+ unique_together=set([('event', 'organizer', 'checksum')]),
+ ),
+ ]
diff --git a/src/pretix/plugins/banktransfer/models.py b/src/pretix/plugins/banktransfer/models.py
index 250d991972..ee12af0aa7 100644
--- a/src/pretix/plugins/banktransfer/models.py
+++ b/src/pretix/plugins/banktransfer/models.py
@@ -16,10 +16,18 @@ class BankImportJob(models.Model):
(STATE_COMPLETED, 'completed'),
)
- event = models.ForeignKey('pretixbase.Event')
+ event = models.ForeignKey('pretixbase.Event', null=True)
+ organizer = models.ForeignKey('pretixbase.Organizer', null=True)
created = models.DateTimeField(auto_now_add=True)
state = models.CharField(max_length=32, choices=STATES, default=STATE_PENDING)
+ @property
+ def owner_kwargs(self):
+ if self.event:
+ return {'event': self.event}
+ else:
+ return {'organizer': self.organizer}
+
class BankTransaction(models.Model):
STATE_UNCHECKED = 'imported'
@@ -40,7 +48,8 @@ class BankTransaction(models.Model):
(STATE_DISCARDED, 'manually discarded'),
)
- event = models.ForeignKey('pretixbase.Event')
+ event = models.ForeignKey('pretixbase.Event', null=True)
+ organizer = models.ForeignKey('pretixbase.Organizer', null=True)
import_job = models.ForeignKey('BankImportJob', related_name='transactions')
state = models.CharField(max_length=32, choices=STATES, default=STATE_UNCHECKED)
message = models.TextField()
@@ -66,4 +75,4 @@ class BankTransaction(models.Model):
self.reference = ""
class Meta:
- unique_together = ('event', 'checksum')
+ unique_together = ('event', 'organizer', 'checksum')
diff --git a/src/pretix/plugins/banktransfer/signals.py b/src/pretix/plugins/banktransfer/signals.py
index f5c5c1e5a4..423caa24c5 100644
--- a/src/pretix/plugins/banktransfer/signals.py
+++ b/src/pretix/plugins/banktransfer/signals.py
@@ -4,7 +4,7 @@ from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from pretix.base.signals import register_payment_providers
-from pretix.control.signals import html_head, nav_event
+from pretix.control.signals import html_head, nav_event, nav_organizer
from .payment import BankTransfer
@@ -32,6 +32,25 @@ def control_nav_import(sender, request=None, **kwargs):
]
+@receiver(nav_organizer, dispatch_uid="payment_banktransfer_organav")
+def control_nav_orga_import(sender, request=None, **kwargs):
+ url = resolve(request.path_info)
+ if not request.user.has_organizer_permission(request.organizer, 'can_change_orders'):
+ return []
+ if not request.organizer.events.filter(plugins__icontains='pretix.plugins.banktransfer'):
+ return []
+ return [
+ {
+ 'label': _('Import bank data'),
+ 'url': reverse('plugins:banktransfer:import', kwargs={
+ 'organizer': request.organizer.slug,
+ }),
+ 'active': (url.namespace == 'plugins:banktransfer' and url.url_name == 'import'),
+ 'icon': 'upload',
+ }
+ ]
+
+
@receiver(html_head, dispatch_uid="banktransfer_html_head")
def html_head_presale(sender, request=None, **kwargs):
url = resolve(request.path_info)
diff --git a/src/pretix/plugins/banktransfer/tasks.py b/src/pretix/plugins/banktransfer/tasks.py
index 00739c7a40..0211271433 100644
--- a/src/pretix/plugins/banktransfer/tasks.py
+++ b/src/pretix/plugins/banktransfer/tasks.py
@@ -6,10 +6,11 @@ from decimal import Decimal
from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.db import transaction
+from django.db.models import Q
from django.utils.translation import ugettext_noop
from pretix.base.i18n import language
-from pretix.base.models import Event, Order, Quota
+from pretix.base.models import Event, Order, Organizer, Quota
from pretix.base.services.async import TransactionAwareTask
from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import SendMailException
@@ -21,17 +22,33 @@ from .models import BankImportJob, BankTransaction
logger = logging.getLogger(__name__)
-def _handle_transaction(event: Event, trans: BankTransaction, code: str):
- try:
- trans.order = event.orders.get(code=code)
- except Order.DoesNotExist:
- normalized_code = Order.normalize_code(code)
+def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
+ slug: str=None):
+ if event:
try:
- trans.order = event.orders.get(code=normalized_code)
+ trans.order = event.orders.get(code=code)
except Order.DoesNotExist:
- trans.state = BankTransaction.STATE_NOMATCH
- trans.save()
- return
+ normalized_code = Order.normalize_code(code)
+ try:
+ trans.order = event.orders.get(code=normalized_code)
+ except Order.DoesNotExist:
+ trans.state = BankTransaction.STATE_NOMATCH
+ trans.save()
+ return
+ else:
+ qs = Order.objects.filter(event__organizer=organizer)
+ if slug:
+ qs = qs.filter(event__slug__iexact=slug)
+ try:
+ trans.order = qs.get(code=code)
+ except Order.DoesNotExist:
+ normalized_code = Order.normalize_code(code)
+ try:
+ trans.order = qs.get(code=normalized_code)
+ except Order.DoesNotExist:
+ trans.state = BankTransaction.STATE_NOMATCH
+ trans.save()
+ return
if trans.order.status == Order.STATUS_PAID:
trans.state = BankTransaction.STATE_DUPLICATE
@@ -63,9 +80,11 @@ def _handle_transaction(event: Event, trans: BankTransaction, code: str):
trans.save()
-def _get_unknown_transactions(event: Event, job: BankImportJob, data: list):
+def _get_unknown_transactions(job: BankImportJob, data: list, event: Event=None, organizer: Organizer=None):
amount_pattern = re.compile("[^0-9.-]")
- known_checksums = set(t['checksum'] for t in BankTransaction.objects.filter(event=event).values('checksum'))
+ known_checksums = set(t['checksum'] for t in BankTransaction.objects.filter(
+ Q(event=event) if event else Q(organizer=organizer)
+ ).values('checksum'))
transactions = []
for row in data:
@@ -83,7 +102,7 @@ def _get_unknown_transactions(event: Event, job: BankImportJob, data: list):
logger.exception('Could not parse amount of transaction: {}'.format(amount))
amount = Decimal("0.00")
- trans = BankTransaction(event=event, import_job=job,
+ trans = BankTransaction(event=event, organizer=organizer, import_job=job,
payer=row.get('payer', ''),
reference=row['reference'],
amount=amount,
@@ -99,29 +118,41 @@ def _get_unknown_transactions(event: Event, job: BankImportJob, data: list):
@app.task(base=TransactionAwareTask, bind=True, max_retries=5, default_retry_delay=1)
-def process_banktransfers(self, event: int, job: int, data: list) -> None:
+def process_banktransfers(self, job: int, data: list) -> None:
with language("en"): # We'll translate error messages at display time
- event = Event.objects.get(pk=event)
job = BankImportJob.objects.get(pk=job)
job.state = BankImportJob.STATE_RUNNING
job.save()
+ prefixes = []
try:
# Delete left-over transactions from a failed run before so they can reimported
- BankTransaction.objects.filter(event=event, state=BankTransaction.STATE_UNCHECKED).delete()
+ BankTransaction.objects.filter(state=BankTransaction.STATE_UNCHECKED, **job.owner_kwargs).delete()
- transactions = _get_unknown_transactions(event, job, data)
+ transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
code_len = settings.ENTROPY['order_code']
- pattern = re.compile(event.slug.upper() + "[ \-_]*([A-Z0-9]{%s})" % code_len)
+ if job.event:
+ pattern = re.compile(job.event.slug.upper() + "[ \-_]*([A-Z0-9]{%s})" % code_len)
+ else:
+ if not prefixes:
+ prefixes = [e.slug.upper().replace(".", r"\.").replace("-", r"\-")
+ for e in job.organizer.events.all()]
+ pattern = re.compile("(%s)[ \-_]*([A-Z0-9]{%s})" % ("|".join(prefixes), code_len))
for trans in transactions:
match = pattern.search(trans.reference.upper())
if match:
- code = match.group(1)
- with transaction.atomic():
- _handle_transaction(event, trans, code)
+ if job.event:
+ code = match.group(1)
+ with transaction.atomic():
+ _handle_transaction(trans, code, event=job.event)
+ else:
+ slug = match.group(1)
+ code = match.group(2)
+ with transaction.atomic():
+ _handle_transaction(trans, code, organizer=job.organizer, slug=slug)
else:
trans.state = BankTransaction.STATE_NOMATCH
trans.save()
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html
index 260dfa2e63..c35908324e 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html
@@ -1,4 +1,4 @@
-{% extends "pretixplugins/banktransfer/import_base.html" %}
+{% extends basetpl %}
{% load i18n %}
{% block inner %}
{% blocktrans trimmed %}
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base.html
index b391089e7d..91c0e35b0a 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base.html
@@ -6,5 +6,4 @@
{% trans "Import bank data" %}
{% block inner %}
{% endblock %}
-
{% endblock %}
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base_organizer.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base_organizer.html
new file mode 100644
index 0000000000..e9dd37e7f5
--- /dev/null
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base_organizer.html
@@ -0,0 +1,4 @@
+{% extends "pretixcontrol/organizers/base.html" %}
+{% load i18n %}
+{% load static %}
+{% block title %}{% trans "Import bank data" %}{% endblock %}
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
index 4bd4ec9c5b..40f680ef80 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
@@ -1,6 +1,7 @@
-{% extends "pretixplugins/banktransfer/import_base.html" %}
+{% extends basetpl %}
{% load i18n %}
{% load bootstrap3 %}
+{% load static %}
{% block inner %}
{% if no_more_payments %}
@@ -47,6 +48,16 @@
{% trans "Unresolved transactions" %}
+ {% if request.event %}
+
+ {% blocktrans %}
+ On this page, you can import banking data on a per-event level. You also only see
+ unmatched transactions imported directly for this event.
+ {% endblocktrans %}
+ {% trans "Go to organizer-level import" %}
+
+ {% endif %}
{% endif %}
+
{% endblock %}
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/job_detail.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/job_detail.html
index c7422b5244..b6967f559b 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/job_detail.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/job_detail.html
@@ -1,5 +1,6 @@
-{% extends "pretixplugins/banktransfer/import_base.html" %}
+{% extends basetpl %}
{% load i18n %}
+{% load staticfiles %}
{% block inner %}
{% trans "Import result" %}
{% if job.state == "running" or job.state == "pending" %}
@@ -37,11 +38,12 @@
{% if transactions_ignored or transactions_invalid %}
-
{% trans "Review invalid and ignored payments" %} »
{% endif %}
{% endif %}
+
{% endblock %}
diff --git a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/transaction_list.html b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/transaction_list.html
index c873e7f475..ccd2f1807e 100644
--- a/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/transaction_list.html
+++ b/src/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/transaction_list.html
@@ -2,7 +2,7 @@
{% load staticfiles %}
{% csrf_token %}
-
+
|
@@ -83,9 +83,13 @@
{% if trans.order %}
-
- {{ trans.order.code }}
+
+ {% if not request.event %}
+ {{ trans.order.event.slug|upper }}-{{ trans.order.code }}
+ {% else %}
+ {{ trans.order.code }}
+ {% endif %}
{% endif %}
|
diff --git a/src/pretix/plugins/banktransfer/urls.py b/src/pretix/plugins/banktransfer/urls.py
index 3bc5b580c7..55c15b8119 100644
--- a/src/pretix/plugins/banktransfer/urls.py
+++ b/src/pretix/plugins/banktransfer/urls.py
@@ -3,10 +3,19 @@ from django.conf.urls import url
from . import views
urlpatterns = [
- url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/import/', views.ImportView.as_view(),
+ url(r'^control/organizer/(?P[^/]+)/banktransfer/import/',
+ views.OrganizerImportView.as_view(),
+ name='import'),
+ url(r'^control/organizer/(?P[^/]+)/banktransfer/job/(?P\d+)/',
+ views.OrganizerJobDetailView.as_view(), name='import.job'),
+ url(r'^control/organizer/(?P[^/]+)/banktransfer/action/',
+ views.OrganizerActionView.as_view(), name='import.action'),
+
+ url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/import/',
+ views.EventImportView.as_view(),
name='import'),
url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/job/(?P\d+)/',
- views.JobDetailView.as_view(), name='import.job'),
+ views.EventJobDetailView.as_view(), name='import.job'),
url(r'^control/event/(?P[^/]+)/(?P[^/]+)/banktransfer/action/',
- views.ActionView.as_view(), name='import.action'),
+ views.EventActionView.as_view(), name='import.action'),
]
diff --git a/src/pretix/plugins/banktransfer/views.py b/src/pretix/plugins/banktransfer/views.py
index 262c0a92ae..80007898b4 100644
--- a/src/pretix/plugins/banktransfer/views.py
+++ b/src/pretix/plugins/banktransfer/views.py
@@ -17,7 +17,10 @@ from pretix.base.models import Order, Quota
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid
from pretix.base.settings import SettingsSandbox
-from pretix.control.permissions import EventPermissionRequiredMixin
+from pretix.control.permissions import (
+ EventPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
+)
+from pretix.control.views.organizer import OrganizerDetailViewMixin
from pretix.plugins.banktransfer import csvimport, mt940import
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
from pretix.plugins.banktransfer.tasks import process_banktransfers
@@ -25,7 +28,7 @@ from pretix.plugins.banktransfer.tasks import process_banktransfers
logger = logging.getLogger('pretix.plugins.banktransfer')
-class ActionView(EventPermissionRequiredMixin, View):
+class ActionView(View):
permission = 'can_change_orders'
def _discard(self, trans):
@@ -89,7 +92,10 @@ class ActionView(EventPermissionRequiredMixin, View):
def _assign(self, trans, code):
try:
- trans.order = self.request.event.orders.get(code=code)
+ if '-' in code:
+ trans.order = self.order_qs().get(code=code.split('-')[1], event__slug__iexact=code.split('-')[0])
+ else:
+ trans.order = self.order_qs().get(code=code.split('-')[-1])
except Order.DoesNotExist:
return JsonResponse({
'status': 'error',
@@ -109,7 +115,10 @@ class ActionView(EventPermissionRequiredMixin, View):
for k, v in request.POST.items():
if not k.startswith('action_'):
continue
- trans = get_object_or_404(BankTransaction, id=k.split('_')[1], event=self.request.event)
+ if 'event' in kwargs:
+ trans = get_object_or_404(BankTransaction, id=k.split('_')[1], event=request.event)
+ else:
+ trans = get_object_or_404(BankTransaction, id=k.split('_')[1], organizer=request.organizer)
if v == 'discard' and trans.state in (BankTransaction.STATE_INVALID, BankTransaction.STATE_ERROR,
BankTransaction.STATE_NOMATCH, BankTransaction.STATE_DUPLICATE):
@@ -140,52 +149,64 @@ class ActionView(EventPermissionRequiredMixin, View):
if len(query) < 2:
return JsonResponse({'results': []})
- qs = self.request.event.orders.filter(Q(code__icontains=query) | Q(code__icontains=Order.normalize_code(query)))
+ qs = self.order_qs().filter(Q(code__icontains=query) | Q(code__icontains=Order.normalize_code(query))).select_related('event')
return JsonResponse({
'results': [
{
- 'code': o.code,
+ 'code': o.event.slug.upper() + '-' + o.code,
'status': o.get_status_display(),
- 'total': localize(o.total) + ' ' + self.request.event.currency
+ 'total': localize(o.total) + ' ' + o.event.currency
} for o in qs
]
})
+ def order_qs(self):
+ return self.request.event.orders
-class JobDetailView(EventPermissionRequiredMixin, DetailView):
+
+class JobDetailView(DetailView):
template_name = 'pretixplugins/banktransfer/job_detail.html'
permission = 'can_change_orders'
context_objectname = 'job'
def redirect_form(self):
- return redirect(reverse('plugins:banktransfer:import', kwargs={
- 'event': self.request.event.slug,
- 'organizer': self.request.event.organizer.slug,
- }))
+ kwargs = {
+ 'organizer': self.request.organizer.slug,
+ }
+ if 'event' in self.kwargs:
+ kwargs['event'] = self.kwargs['event']
+ return redirect(reverse('plugins:banktransfer:import', kwargs=kwargs))
def redirect_back(self):
- return redirect(reverse('plugins:banktransfer:import.job', kwargs={
- 'event': self.request.event.slug,
- 'organizer': self.request.event.organizer.slug,
+ kwargs = {
+ 'organizer': self.request.organizer.slug,
'job': self.kwargs['job']
- }))
+ }
+ if 'event' in self.kwargs:
+ kwargs['event'] = self.kwargs['event']
+ return redirect(reverse('plugins:banktransfer:import.job', kwargs=kwargs))
- def get_object(self, queryset=None):
- return get_object_or_404(BankImportJob, id=self.kwargs['job'], event=self.request.event)
+ @cached_property
+ def job(self):
+ if 'event' in self.kwargs:
+ kwargs = {'event': self.request.event}
+ else:
+ kwargs = {'organizer': self.request.organizer}
+ return get_object_or_404(BankImportJob, id=self.kwargs['job'], **kwargs)
def get(self, request, *args, **kwargs):
if 'ajax' in request.GET:
- self.object = self.get_object()
return JsonResponse({
- 'state': self.object.state
+ 'state': self.job.state
})
- return super().get(request, *args, **kwargs)
+ context = self.get_context_data()
+ return self.render_to_response(context)
def get_context_data(self, **kwargs):
- ctx = super().get_context_data()
+ ctx = {}
- qs = self.object.transactions.select_related('order')
+ qs = self.job.transactions.select_related('order', 'order__event')
ctx['transactions_valid'] = qs.filter(state=BankTransaction.STATE_VALID).count()
ctx['transactions_invalid'] = qs.filter(state__in=[
@@ -194,21 +215,33 @@ class JobDetailView(EventPermissionRequiredMixin, DetailView):
ctx['transactions_ignored'] = qs.filter(state__in=[
BankTransaction.STATE_DUPLICATE, BankTransaction.STATE_NOMATCH
]).count()
- ctx['job'] = self.object
+ ctx['job'] = self.job
+ ctx['organizer'] = self.request.organizer
+
+ if 'event' in self.kwargs:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base.html'
+ else:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base_organizer.html'
return ctx
-class ImportView(EventPermissionRequiredMixin, ListView):
+class ImportView(ListView):
template_name = 'pretixplugins/banktransfer/import_form.html'
permission = 'can_change_orders'
context_object_name = 'transactions_unhandled'
paginate_by = 30
def get_queryset(self):
- qs = BankTransaction.objects.filter(
- event=self.request.event
- ).select_related('order').filter(state__in=[
+ if 'event' in self.kwargs:
+ qs = BankTransaction.objects.filter(
+ Q(event=self.request.event)
+ )
+ else:
+ qs = BankTransaction.objects.filter(
+ Q(organizer=self.request.organizer)
+ )
+ qs = qs.select_related('order').filter(state__in=[
BankTransaction.STATE_INVALID, BankTransaction.STATE_ERROR,
BankTransaction.STATE_DUPLICATE, BankTransaction.STATE_NOMATCH
])
@@ -245,12 +278,12 @@ class ImportView(EventPermissionRequiredMixin, ListView):
else:
messages.error(self.request, _('We were unable to detect the file type of this import. Please '
- 'contact support for help.'))
+ 'contact support for help.'))
return self.redirect_back()
@cached_property
def settings(self):
- return SettingsSandbox('payment', 'banktransfer', self.request.event)
+ return SettingsSandbox('payment', 'banktransfer', getattr(self.request, 'event', self.request.organizer))
def process_mt940(self):
try:
@@ -261,6 +294,7 @@ class ImportView(EventPermissionRequiredMixin, ListView):
return self.redirect_back()
def process_csv_file(self):
+ o = getattr(self.request, 'event', self.request.organizer)
try:
data = csvimport.get_rows_from_file(self.request.FILES['file'])
except csv.Error as e: # TODO: narrow down
@@ -273,8 +307,8 @@ class ImportView(EventPermissionRequiredMixin, ListView):
messages.error(self.request, _('I\'m sorry, but we detected this file as empty. Please '
'contact support for help.'))
- if self.request.event.settings.get('banktransfer_csvhint') is not None:
- hint = self.request.event.settings.get('banktransfer_csvhint', as_type=dict)
+ if o.settings.get('banktransfer_csvhint') is not None:
+ hint = o.settings.get('banktransfer_csvhint', as_type=dict)
try:
parsed, good = csvimport.parse(data, hint)
@@ -304,8 +338,9 @@ class ImportView(EventPermissionRequiredMixin, ListView):
logger.error('Parsing hint failed: ' + str(e))
messages.error(self.request, _('We were unable to process your input.'))
return self.assign_view(data)
+ o = getattr(self.request, 'event', self.request.organizer)
try:
- self.request.event.settings.set('banktransfer_csvhint', hint)
+ o.settings.set('banktransfer_csvhint', hint)
except Exception as e: # TODO: narrow down
logger.error('Import using stored hint failed: ' + str(e))
pass
@@ -321,44 +356,117 @@ class ImportView(EventPermissionRequiredMixin, ListView):
return super().get(self.request)
def assign_view(self, parsed):
- return render(self.request, 'pretixplugins/banktransfer/import_assign.html', {
- 'rows': parsed
- })
+ ctx = {'rows': parsed}
+ if 'event' in self.kwargs:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base.html'
+ else:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base_organizer.html'
+ ctx['organizer'] = self.request.organizer
+ return render(self.request, 'pretixplugins/banktransfer/import_assign.html', ctx)
@cached_property
def job_running(self):
- return BankImportJob.objects.filter(
- event=self.request.event, state=BankImportJob.STATE_RUNNING,
+ if 'event' in self.kwargs:
+ qs = BankImportJob.objects.filter(
+ Q(event=self.request.event) | Q(organizer=self.request.organizer)
+ )
+ else:
+ qs = BankImportJob.objects.filter(
+ Q(organizer=self.request.organizer)
+ )
+ return qs.filter(
+ state=BankImportJob.STATE_RUNNING,
created__lte=now() - timedelta(minutes=30) # safety timeout
).first()
def redirect_back(self):
- return redirect(reverse('plugins:banktransfer:import', kwargs={
- 'event': self.request.event.slug,
- 'organizer': self.request.event.organizer.slug,
- }))
+ kwargs = {
+ 'organizer': self.request.organizer.slug
+ }
+ if 'event' in self.kwargs:
+ kwargs['event'] = self.kwargs['event']
+ return redirect(reverse('plugins:banktransfer:import', kwargs=kwargs))
def start_processing(self, parsed):
if self.job_running:
- messages.error(self.request, _('An import is currently being processed, please try again in a few minutes.'))
+ messages.error(self.request,
+ _('An import is currently being processed, please try again in a few minutes.'))
return self.redirect_back()
- job = BankImportJob.objects.create(event=self.request.event)
+ if 'event' in self.kwargs:
+ job = BankImportJob.objects.create(event=self.request.event, organizer=self.request.organizer)
+ else:
+ job = BankImportJob.objects.create(organizer=self.request.organizer)
process_banktransfers.apply_async(kwargs={
- 'event': self.request.event.pk,
'job': job.pk,
'data': parsed
})
- return redirect(reverse('plugins:banktransfer:import.job', kwargs={
- 'event': self.request.event.slug,
- 'organizer': self.request.event.organizer.slug,
+ kwargs = {
+ 'organizer': self.request.organizer.slug,
'job': job.pk
- }))
+ }
+ if 'event' in self.kwargs:
+ kwargs['event'] = self.kwargs['event']
+ return redirect(reverse('plugins:banktransfer:import.job', kwargs=kwargs))
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['job_running'] = self.job_running
ctx['no_more_payments'] = False
- if self.request.event.settings.get('payment_term_last'):
- if now() > self.request.event.payment_term_last:
- ctx['no_more_payments'] = True
+
+ if 'event' in self.kwargs:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base.html'
+ if self.request.event.settings.get('payment_term_last'):
+ if now() > self.request.event.payment_term_last:
+ ctx['no_more_payments'] = True
+ else:
+ ctx['basetpl'] = 'pretixplugins/banktransfer/import_base_organizer.html'
+ ctx['organizer'] = self.request.organizer
return ctx
+
+
+class OrganizerBanktransferView:
+ def dispatch(self, request, *args, **kwargs):
+ if len(request.organizer.events.order_by('currency').values_list('currency', flat=True).distinct()) > 1:
+ messages.error(request, _('Please perform per-event bank imports as this organizer has events with '
+ 'multuple currencies.'))
+ return redirect('control:organizer', organizer=request.organizer.slug)
+ return super().dispatch(request, *args, **kwargs)
+
+
+class EventImportView(EventPermissionRequiredMixin, ImportView):
+ permission = 'can_change_orders'
+
+
+class OrganizerImportView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
+ ImportView):
+ permission = 'can_change_orders'
+
+
+class EventJobDetailView(EventPermissionRequiredMixin, JobDetailView):
+ permission = 'can_change_orders'
+
+
+class OrganizerJobDetailView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
+ JobDetailView):
+ permission = 'can_change_orders'
+
+
+class EventActionView(EventPermissionRequiredMixin, ActionView):
+ permission = 'can_change_orders'
+
+
+class OrganizerActionView(OrganizerBanktransferView, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin,
+ ActionView):
+ permission = 'can_change_orders'
+
+ def order_qs(self):
+ all = self.request.user.teams.filter(organizer=self.request.organizer, can_change_orders=True,
+ can_view_orders=True, all_events=True).exists()
+ if self.request.user.is_superuser or all:
+ return Order.objects.filter(event__organizer=self.request.organizer)
+ else:
+ return Order.objects.filter(
+ event_id__in=self.request.user.teams.filter(
+ organizer=self.request.organizer, can_change_orders=True, can_view_orders=True
+ ).values_list('limit_events__id', flat=True)
+ )
diff --git a/src/tests/plugins/banktransfer/test_actions.py b/src/tests/plugins/banktransfer/test_actions.py
index 69ca64198b..773f519cde 100644
--- a/src/tests/plugins/banktransfer/test_actions.py
+++ b/src/tests/plugins/banktransfer/test_actions.py
@@ -208,3 +208,68 @@ def test_retry_paid(env, client):
assert trans.state == BankTransaction.STATE_ERROR
env[3].refresh_from_db()
assert env[3].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_assign_order_organizer(env, client):
+ job = BankImportJob.objects.create(organizer=env[0].organizer)
+ trans = BankTransaction.objects.create(organizer=env[0].organizer, import_job=job, payer='Foo',
+ state=BankTransaction.STATE_NOMATCH,
+ amount=23, date='unknown')
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ r = json.loads(client.post('/control/organizer/{}/banktransfer/action/'.format(env[0].organizer.slug), {
+ 'action_{}'.format(trans.pk): 'assign:{}'.format(env[2].code),
+ }).content.decode('utf-8'))
+ assert r['status'] == 'ok'
+ trans.refresh_from_db()
+ assert trans.state == BankTransaction.STATE_VALID
+ env[2].refresh_from_db()
+ assert env[2].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_assign_order_organizer_full_code(env, client):
+ job = BankImportJob.objects.create(organizer=env[0].organizer)
+ trans = BankTransaction.objects.create(organizer=env[0].organizer, import_job=job, payer='Foo',
+ state=BankTransaction.STATE_NOMATCH,
+ amount=23, date='unknown')
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ r = json.loads(client.post('/control/organizer/{}/banktransfer/action/'.format(env[0].organizer.slug), {
+ 'action_{}'.format(trans.pk): 'assign:{}-{}'.format(env[0].slug.upper(), env[2].code),
+ }).content.decode('utf-8'))
+ assert r['status'] == 'ok'
+ trans.refresh_from_db()
+ assert trans.state == BankTransaction.STATE_VALID
+ env[2].refresh_from_db()
+ assert env[2].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_assign_order_organizer_no_permission(env, client):
+ job = BankImportJob.objects.create(organizer=env[0].organizer)
+ trans = BankTransaction.objects.create(organizer=env[0].organizer, import_job=job, payer='Foo',
+ state=BankTransaction.STATE_NOMATCH,
+ amount=23, date='unknown')
+ team = env[1].teams.first()
+ team.can_change_orders = False
+ team.save()
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ r = client.post('/control/organizer/{}/banktransfer/action/'.format(env[0].organizer.slug), {
+ 'action_{}'.format(trans.pk): 'assign:{}-{}'.format(env[0].slug.upper(), env[2].code),
+ })
+ assert r.status_code == 403
+
+
+@pytest.mark.django_db
+def test_assign_order_organizer_no_permission_for_event(env, client):
+ job = BankImportJob.objects.create(organizer=env[0].organizer)
+ trans = BankTransaction.objects.create(organizer=env[0].organizer, import_job=job, payer='Foo',
+ state=BankTransaction.STATE_NOMATCH,
+ amount=23, date='unknown')
+ team = env[1].teams.first()
+ team.limit_events.clear()
+ client.login(email='dummy@dummy.dummy', password='dummy')
+ r = json.loads(client.post('/control/organizer/{}/banktransfer/action/'.format(env[0].organizer.slug), {
+ 'action_{}'.format(trans.pk): 'assign:{}-{}'.format(env[0].slug.upper(), env[2].code),
+ }).content.decode('utf-8'))
+ assert r['status'] == 'error'
diff --git a/src/tests/plugins/banktransfer/test_import.py b/src/tests/plugins/banktransfer/test_import.py
index 17963f6e00..7f21d472ea 100644
--- a/src/tests/plugins/banktransfer/test_import.py
+++ b/src/tests/plugins/banktransfer/test_import.py
@@ -90,9 +90,14 @@ def job(env):
return BankImportJob.objects.create(event=env[0]).pk
+@pytest.fixture
+def orga_job(env):
+ return BankImportJob.objects.create(organizer=env[0].organizer).pk
+
+
@pytest.mark.django_db
def test_mark_paid(env, job):
- process_banktransfers(env[0].pk, job, [{
+ process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY1234S',
'date': '2016-01-26',
@@ -104,7 +109,7 @@ def test_mark_paid(env, job):
@pytest.mark.django_db
def test_check_amount(env, job):
- process_banktransfers(env[0].pk, job, [{
+ process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY1Z3AS',
'date': '2016-01-26',
@@ -116,7 +121,7 @@ def test_check_amount(env, job):
@pytest.mark.django_db
def test_ignore_canceled(env, job):
- process_banktransfers(env[0].pk, job, [{
+ process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY6789Z',
'date': '2016-01-26',
@@ -128,7 +133,7 @@ def test_ignore_canceled(env, job):
@pytest.mark.django_db
def test_autocorrection(env, job):
- process_banktransfers(env[0].pk, job, [{
+ process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY12345',
'amount': '23.00',
@@ -142,7 +147,7 @@ def test_autocorrection(env, job):
def test_huge_amount(env, job):
env[2].total = Decimal('23000.00')
env[2].save()
- process_banktransfers(env[0].pk, job, [{
+ process_banktransfers(job, [{
'payer': 'Karla Kundin',
'reference': 'Bestellung DUMMY12345',
'amount': '23.000,00',
@@ -150,3 +155,45 @@ def test_huge_amount(env, job):
}])
env[2].refresh_from_db()
assert env[2].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_mark_paid_organizer(env, orga_job):
+ process_banktransfers(orga_job, [{
+ 'payer': 'Karla Kundin',
+ 'reference': 'Bestellung DUMMY-1234S',
+ 'date': '2016-01-26',
+ 'amount': '23.00'
+ }])
+ env[2].refresh_from_db()
+ assert env[2].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_mark_paid_organizer_weird_slug(env, orga_job):
+ env[0].slug = 'du.m-y'
+ env[0].save()
+ process_banktransfers(orga_job, [{
+ 'payer': 'Karla Kundin',
+ 'reference': 'Bestellung DU.M-Y-1234S',
+ 'date': '2016-01-26',
+ 'amount': '23.00'
+ }])
+ env[2].refresh_from_db()
+ assert env[2].status == Order.STATUS_PAID
+
+
+@pytest.mark.django_db
+def test_wrong_event_organizer(env, orga_job):
+ Event.objects.create(
+ organizer=env[0].organizer, name='Wrong', slug='wrong',
+ date_from=now(), plugins='pretix.plugins.banktransfer'
+ )
+ process_banktransfers(orga_job, [{
+ 'payer': 'Karla Kundin',
+ 'reference': 'Bestellung WRONG-1234S',
+ 'date': '2016-01-26',
+ 'amount': '23.00'
+ }])
+ env[2].refresh_from_db()
+ assert env[2].status == Order.STATUS_PENDING