Repository: svthalia/concrexit

Issue:
Export payment batch per topic / event
In GitLab by @JobDoesburg on Feb 12, 2020, 20:39

### One-sentence description

Export payment batch per topic / event

### Motivation

Required for bookkeeping of Thalia Pay

### Desired functionality

Use the `payment_topic` from Payments, provided by `Payable` models

### Suggested implementation

Extend #1044 with a second way of exporting


Discussion:

Resolving patch:
diff --git a/website/payments/admin.py b/website/payments/admin.py
--- a/website/payments/admin.py
+++ b/website/payments/admin.py
@@ -387,6 +387,13 @@ def get_urls(self) -> list:
                 self.admin_site.admin_view(admin_views.BatchExportAdminView.as_view()),
                 name="payments_batch_export",
             ),
+            path(
+                "<uuid:pk>/export-topic/",
+                self.admin_site.admin_view(
+                    admin_views.BatchTopicExportAdminView.as_view()
+                ),
+                name="payments_batch_export_topic",
+            ),
             path(
                 "new_filled/",
                 self.admin_site.admin_view(
diff --git a/website/payments/admin_views.py b/website/payments/admin_views.py
--- a/website/payments/admin_views.py
+++ b/website/payments/admin_views.py
@@ -6,7 +6,7 @@
 from django.contrib.admin.utils import model_ngettext
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.auth.decorators import permission_required
-from django.db.models import Sum
+from django.db.models import Sum, Count, Min, Max
 from django.http import HttpResponse
 from django.core.exceptions import SuspiciousOperation, DisallowedRedirect
 from django.shortcuts import redirect
@@ -148,6 +148,54 @@ def post(self, request, *args, **kwargs):
         return response
 
 
+@method_decorator(staff_member_required, name="dispatch")
+@method_decorator(
+    permission_required("payments.process_batches"), name="dispatch",
+)
+class BatchTopicExportAdminView(View):
+    """
+    View that exports a batch per topic
+    """
+
+    def post(self, request, *args, **kwargs):
+        batch = Batch.objects.get(pk=kwargs["pk"])
+
+        response = HttpResponse(content_type="text/csv")
+        response["Content-Disposition"] = 'attachment;filename="batch-topic.csv"'
+        writer = csv.writer(response)
+        headers = [
+            _("Topic"),
+            _("No. of payments"),
+            _("First payment"),
+            _("Last payment"),
+            _("Total amount"),
+        ]
+        writer.writerow([capfirst(x) for x in headers])
+
+        topic_rows = (
+            batch.payments_set.values("topic")
+            .annotate(
+                total=Sum("amount"),
+                count=Count("paid_by"),
+                min_date=Min("created_at"),
+                max_date=Max("created_at"),
+            )
+            .order_by("topic")
+        )
+
+        for row in topic_rows:
+            writer.writerow(
+                [
+                    row["topic"],
+                    row["count"],
+                    timezone.localtime(row["min_date"]).date(),
+                    timezone.localtime(row["max_date"]).date(),
+                    f"{row['total']:.2f}",
+                ]
+            )
+        return response
+
+
 @method_decorator(staff_member_required, name="dispatch")
 @method_decorator(
     permission_required("payments.process_batches"), name="dispatch",


Test patch:
diff --git a/website/payments/tests/test_admin_views.py b/website/payments/tests/test_admin_views.py
--- a/website/payments/tests/test_admin_views.py
+++ b/website/payments/tests/test_admin_views.py
@@ -330,6 +330,119 @@ def test_post(self):
         )
 
 
+@override_settings(SUSPEND_SIGNALS=True)
+@freeze_time("2020-01-01")
+class BatchTopicExportAdminViewTest(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.batch = Batch.objects.create()
+        cls.user = Member.objects.create(
+            username="test1",
+            first_name="Test1",
+            last_name="Example",
+            email="test1@example.org",
+        )
+        Profile.objects.create(user=cls.user)
+
+    def setUp(self):
+        self.client = Client()
+        self.client.force_login(self.user)
+        self.view = admin_views.BatchTopicExportAdminView()
+
+    def _give_user_permissions(self):
+        content_type = ContentType.objects.get_for_model(Batch)
+        permissions = Permission.objects.filter(
+            content_type__app_label=content_type.app_label,
+        )
+        for p in permissions:
+            self.user.user_permissions.add(p)
+        self.user.is_staff = True
+        self.user.save()
+
+        self.client.logout()
+        self.client.force_login(self.user)
+
+    def test_permission(self):
+        url = f"/admin/payments/batch/{self.batch.id}/export-topic/"
+        response = self.client.post(url)
+        self.assertRedirects(response, "/admin/login/?next=%s" % url)
+
+        self._give_user_permissions()
+
+        response = self.client.post(url)
+        self.assertEqual(response.status_code, 200)
+
+    def test_post(self):
+        self._give_user_permissions()
+
+        self.user2 = Member.objects.create(
+            username="test2",
+            first_name="Test2",
+            last_name="Example",
+            email="test1@example.org",
+        )
+        Profile.objects.create(user=self.user2)
+
+        BankAccount.objects.create(
+            last_used=timezone.now(),
+            owner=self.user,
+            iban="DE75512108001245126199",
+            mandate_no="2",
+            valid_from=timezone.now(),
+        )
+        BankAccount.objects.create(
+            last_used=timezone.now(),
+            owner=self.user2,
+            iban="NL02ABNA0123456789",
+            mandate_no="1",
+            valid_from=timezone.now(),
+        )
+
+        Payment.objects.bulk_create(
+            [
+                Payment(
+                    amount=1,
+                    paid_by=self.user,
+                    type=Payment.TPAY,
+                    batch=self.batch,
+                    topic="test1",
+                ),
+                Payment(
+                    amount=2,
+                    paid_by=self.user,
+                    type=Payment.TPAY,
+                    batch=self.batch,
+                    topic="test2",
+                ),
+                Payment(
+                    amount=4,
+                    paid_by=self.user2,
+                    type=Payment.TPAY,
+                    batch=self.batch,
+                    topic="test1",
+                ),
+                Payment(
+                    amount=2,
+                    paid_by=self.user2,
+                    type=Payment.TPAY,
+                    batch=self.batch,
+                    topic="test2",
+                ),
+            ]
+        )
+
+        response = self.client.post(
+            f"/admin/payments/batch/{self.batch.id}/export-topic/"
+        )
+
+        self.assertEqual(
+            response.content,
+            b"Topic,No. of payments,First payment,Last payment,Total amount\r\n"
+            b"test1,2,2020-01-01,2020-01-01,5.00\r\n"
+            b"test2,2,2020-01-01,2020-01-01,4.00\r\n",
+        )
+
+
 @override_settings(SUSPEND_SIGNALS=True)
 class BatchNewFilledAdminViewTest(TestCase):
     @classmethod