From 75fe3e33df954bc3f7c3a3e8118aa420260c7c7c Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Tue, 11 Mar 2025 14:50:13 +0100
Subject: [PATCH] =?UTF-8?q?feat(api):=20=E2=9C=A8=20add=20private=20subsid?=
 =?UTF-8?q?y=20payment=20api=20viewset?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 scipost_django/api/urls.py                    |  7 +-
 scipost_django/finances/api/filtersets.py     | 82 ++++++++++++++++++-
 scipost_django/finances/api/serializers.py    | 17 +++-
 scipost_django/finances/api/viewsets.py       | 59 ++++++++++++-
 .../commands/add_groups_and_permissions.py    | 10 +++
 5 files changed, 169 insertions(+), 6 deletions(-)

diff --git a/scipost_django/api/urls.py b/scipost_django/api/urls.py
index d0e0922b4..90e304b02 100644
--- a/scipost_django/api/urls.py
+++ b/scipost_django/api/urls.py
@@ -31,7 +31,11 @@ from organizations.api.viewsets import (
 )
 
 # finances
-from finances.api.viewsets import SubsidyFinAdminAPIViewSet, SubsidyPublicAPIViewSet
+from finances.api.viewsets import (
+    SubsidyFinAdminAPIViewSet,
+    SubsidyPublicAPIViewSet,
+    SubsidyPaymentPrivateAPIViewSet,
+)
 
 
 # Next two: old style, to be deprecated:
@@ -83,6 +87,7 @@ router.register("nap", OrganizationNAPViewSet, basename="organization_nap")
 router.register(
     "finadmin/subsidies", SubsidyFinAdminAPIViewSet, basename="subsidies_finadmin"
 )
+router.register("subsidies/payments", SubsidyPaymentPrivateAPIViewSet)
 router.register("subsidies", SubsidyPublicAPIViewSet)
 
 # Next two: old style, to be deprecated:
diff --git a/scipost_django/finances/api/filtersets.py b/scipost_django/finances/api/filtersets.py
index 3bb68b2ba..02e830a91 100644
--- a/scipost_django/finances/api/filtersets.py
+++ b/scipost_django/finances/api/filtersets.py
@@ -4,7 +4,7 @@ __license__ = "AGPL v3"
 
 from django_filters import rest_framework as df_filters
 
-from finances.models import Subsidy
+from finances.models import Subsidy, SubsidyPayment
 
 
 class SubsidyFinAdminAPIFilterSet(df_filters.FilterSet):
@@ -55,3 +55,83 @@ class SubsidyFinAdminAPIFilterSet(df_filters.FilterSet):
                 "range",
             ],
         }
+
+
+class SubsidyPaymentAPIFilterSet(df_filters.FilterSet):
+    class Meta:
+        model = SubsidyPayment
+        fields = {
+            "subsidy__organization__name": [
+                "icontains",
+                "contains",
+                "istartswith",
+                "iregex",
+                "regex",
+            ],
+            "subsidy__organization__acronym": [
+                "icontains",
+                "contains",
+                "istartswith",
+                "iregex",
+                "regex",
+            ],
+            "subsidy__organization__country": [
+                "icontains",
+            ],
+            "subsidy__description": [
+                "icontains",
+            ],
+            "subsidy__amount": ["gte", "lte", "range"],
+            "subsidy__date_from": [
+                "year",
+                "month",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "subsidy__date_until": [
+                "year",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "invoice__date": [
+                "year",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "proof_of_payment__date": [
+                "year",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "date_scheduled": [
+                "year",
+                "exact",
+                "year__gte",
+                "year__lte",
+                "year__range",
+                "gte",
+                "lte",
+                "range",
+            ],
+            "amount": ["gte", "lte", "range"],
+        }
diff --git a/scipost_django/finances/api/serializers.py b/scipost_django/finances/api/serializers.py
index 9d6f5e8eb..58e1fe2c4 100644
--- a/scipost_django/finances/api/serializers.py
+++ b/scipost_django/finances/api/serializers.py
@@ -6,7 +6,7 @@ from rest_framework import serializers
 
 from api.serializers import DynamicFieldsModelSerializer
 
-from finances.models import Subsidy
+from finances.models import Subsidy, SubsidyPayment
 from organizations.api.serializers import OrganizationPublicSerializer
 
 
@@ -32,3 +32,18 @@ class SubsidyFinAdminSerializer(DynamicFieldsModelSerializer):
             "renewable",
             "renewal_of",
         ]
+
+
+class SubsidyPaymentSerializer(DynamicFieldsModelSerializer):
+    subsidy = SubsidyFinAdminSerializer()
+
+    class Meta:
+        model = SubsidyPayment
+        fields = [
+            "subsidy",
+            "amount",
+            "date_scheduled",
+            "status",
+            "invoice_date",
+            "payment_date",
+        ]
diff --git a/scipost_django/finances/api/viewsets.py b/scipost_django/finances/api/viewsets.py
index d0be52cab..694c4be27 100644
--- a/scipost_django/finances/api/viewsets.py
+++ b/scipost_django/finances/api/viewsets.py
@@ -9,9 +9,12 @@ from rest_framework_csv import renderers as r
 from api.viewsets.base import ExtraFilteredReadOnlyModelViewSet
 from api.viewsets.mixins import FilteringOptionsActionMixin
 
-from finances.models import Subsidy
-from finances.api.filtersets import SubsidyFinAdminAPIFilterSet
-from finances.api.serializers import SubsidyFinAdminSerializer
+from finances.models import Subsidy, SubsidyPayment
+from finances.api.filtersets import (
+    SubsidyFinAdminAPIFilterSet,
+    SubsidyPaymentAPIFilterSet,
+)
+from finances.api.serializers import SubsidyFinAdminSerializer, SubsidyPaymentSerializer
 
 
 class CanManageSubsidies(BasePermission):
@@ -19,6 +22,11 @@ class CanManageSubsidies(BasePermission):
         return request.user.has_perm("scipost.can_manage_subsidies")
 
 
+class CanUseSubsidyPaymentAPI(BasePermission):
+    def has_permission(self, request, view):
+        return request.user.has_perm("scipost.can_use_private_api_subsidy_payments")
+
+
 class SubsidyFinAdminAPIViewSet(
     FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet
 ):
@@ -44,9 +52,54 @@ class SubsidyFinAdminAPIViewSet(
         "organization__acronym__icontains",
     ]
 
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .select_related("organization")
+            .prefetch_related("renewal_of")
+        )
+
 
 class SubsidyPublicAPIViewSet(SubsidyFinAdminAPIViewSet):
     queryset = Subsidy.objects.filter(amount_publicly_shown=True)
     permission_classes = [
         AllowAny,
     ]
+
+
+class SubsidyPaymentPrivateAPIViewSet(
+    FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet
+):
+    queryset = SubsidyPayment.objects.all()
+    permission_classes = [
+        CanUseSubsidyPaymentAPI,
+    ]
+    serializer_class = SubsidyPaymentSerializer
+    renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES) + (r.CSVRenderer,)
+    search_fields = [
+        "subsidy__organization__name",
+        "subsidy__organization__acronym",
+    ]
+    filterset_class = SubsidyPaymentAPIFilterSet
+    ordering_fields = [
+        "subsidy__organization__name",
+        "subsidy__amount",
+        "amount",
+        "date_scheduled",
+        "proof_of_payment__date",
+        "invoice__date",
+    ]
+
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .select_related(
+                "subsidy",
+                "subsidy__organization",
+                "proof_of_payment",
+                "invoice",
+            )
+            .prefetch_related("subsidy__renewal_of")
+        )
diff --git a/scipost_django/scipost/management/commands/add_groups_and_permissions.py b/scipost_django/scipost/management/commands/add_groups_and_permissions.py
index 262979120..8edb5a1c7 100644
--- a/scipost_django/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost_django/scipost/management/commands/add_groups_and_permissions.py
@@ -560,6 +560,16 @@ class Command(BaseCommand):
             content_type=content_type,
         )
 
+        # API private access
+        # These are likely to be granted on a per-user basis
+        can_use_private_api_subsidy_payments, created = (
+            Permission.objects.get_or_create(
+                codename="can_use_private_api_subsidy_payments",
+                name="Can use the private API for subsidy payments",
+                content_type=content_type,
+            )
+        )
+
         # Assign permissions to groups
         SciPostAdmin.permissions.set(
             [
-- 
GitLab