diff --git a/scipost_django/api/urls.py b/scipost_django/api/urls.py index d0e0922b49f3d42ad15083c765f6f25da162277f..90e304b029b89602b489ec9b2feac15f6c94d0a6 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 3bb68b2ba5f9231d678279d17aec5d302b14efe7..02e830a91451d0d32e43acb9477bb8f0648018f0 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 9d6f5e8ebac86b8deb354446594904491318e77e..58e1fe2c47aa2629f1c23996e661eedb1296ac81 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 d0be52caba18b390e44007ec628cf99dfe60a060..694c4be27670fb3459dd04ad3e4b7c25386e4ad8 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 26297912095272abd7607b4116ab8d36390cda28..8edb5a1c78c1ddfb2285fd208c0bc54ed2677187 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( [