diff --git a/scipost_django/finances/admin.py b/scipost_django/finances/admin.py index 1d09bb7a5ce68f5741c58fefb25de7fbbea019b7..01e9213701f63513abead779faf6c5530293a7f9 100644 --- a/scipost_django/finances/admin.py +++ b/scipost_django/finances/admin.py @@ -29,6 +29,18 @@ class SubsidyAttachmentInline(admin.TabularInline): @admin.register(Subsidy) class SubsidyAdmin(admin.ModelAdmin): + list_display = [ + "organization_name_short", + "orgtype_display", + "amount", + "status", + "date_from", + "date_until", + "total_compensations", + ] + list_filter = [ + "organization__orgtype", + ] inlines = [ SubsidyPaymentInline, SubsidyAttachmentInline, @@ -43,6 +55,14 @@ class SubsidyAdmin(admin.ModelAdmin): "organization__acronym", ] + @admin.display(description="org name short") + def organization_name_short(self, obj): + return obj.organization.name[:40] + + @admin.display(description='org type') + def orgtype_display(self, obj): + return obj.organization.get_orgtype_display() + @admin.register(SubsidyAttachment) class SubsidyAttachmentAdmin(admin.ModelAdmin): diff --git a/scipost_django/finances/allocate.py b/scipost_django/finances/allocate.py index 6284cfeb3c950f6f253354b0eba788743c4779ce..3ecdd65cac9b38ab57b858c09fc32a331f1a93a1 100644 --- a/scipost_django/finances/allocate.py +++ b/scipost_django/finances/allocate.py @@ -3,6 +3,7 @@ __license__ = "AGPL v3" from .models import Subsidy, PubFrac +from .managers import PubFracQuerySet """ @@ -34,21 +35,26 @@ The algorithms are implemented in the following order, """ +def compensate(subsidy: Subsidy, pubfracs: PubFracQuerySet): + """ + Allocate subsidy to unallocated pubfracs in queryset, up to depletion. + """ + for pubfrac in pubfracs.uncompensated(): + if pubfrac.cf_value <= subsidy.remainder: + pubfrac.compensated_by = subsidy + pubfrac.save() + + def allocate_to_any_aff(subsidy: Subsidy): """ Allocate to PubFracs with affiliation to Subsidy-giver. """ max_year = subsidy.date_until.year if subsidy.date_until else subsidy.date_from.year - uncompensated_pubfracs = PubFrac.objects.filter( - organization=subsidy.organization, + pubfracs = subsidy.organization.pubfracs.filter( publication__publication_date__year__gte=subsidy.date_from.year, publication__publication_date__year__lte=max_year, - compensated_by__isnull=True, ) - for pubfrac in uncompensated_pubfracs.all(): - if pubfrac.cf_value <= subsidy.remainder: - pubfrac.compensated_by = subsidy - pubfrac.save() + compensate(subsidy, pubfracs) def allocate_to_all_aff(subsidy: Subsidy): @@ -56,18 +62,13 @@ def allocate_to_all_aff(subsidy: Subsidy): Allocate to all PubFracs of Publications with at least one aff to Subsidy-giver. """ max_year = subsidy.date_until.year if subsidy.date_until else subsidy.date_from.year - uncompensated_pubfracs = PubFrac.objects.filter( - organization=subsidy.organization, + pubfracs = subsidy.organization.pubfracs.filter( publication__publication_date__year__gte=subsidy.date_from.year, publication__publication_date__year__lte=max_year, ) - for pubfrac in uncompensated_pubfracs.all(): + for pubfrac in pubfracs.all(): # retrieve all uncompensated PubFracs for the relevant Publication pubfracs_for_pub = PubFrac.objects.filter( publication__doi_label=pubfrac.publication.doi_label, - compensated_by__isnull=True, ) - for pf in pubfracs_for_pub.all(): - if pf.cf_value <= subsidy.remainder: - pf.compensated_by = subsidy - pf.save() + compensate(subsidy, pubfracs_for_pub) diff --git a/scipost_django/finances/managers.py b/scipost_django/finances/managers.py index cf400aa15beea884ffa115a38893ac182e628eb2..d2adcc17aad33f3110634cbcaf46cc53aabbf20f 100644 --- a/scipost_django/finances/managers.py +++ b/scipost_django/finances/managers.py @@ -32,3 +32,11 @@ class SubsidyAttachmentQuerySet(models.QuerySet): def orphaned(self): return self.filter(subsidy__isnull=True) + + +class PubFracQuerySet(models.QuerySet): + def uncompensated(self): + return self.filter(compensated_by__isnull=True) + + def compensated(self): + return self.exclude(compensated_by__isnull=True) diff --git a/scipost_django/finances/models/pubfrac.py b/scipost_django/finances/models/pubfrac.py index d347408e79ab1ec8386dc67753f39a65eaa66bf3..cdfa9ff45ab9b1a3bcbe60306268ee83e3e82335 100644 --- a/scipost_django/finances/models/pubfrac.py +++ b/scipost_django/finances/models/pubfrac.py @@ -6,6 +6,8 @@ from decimal import Decimal from django.db import models +from ..managers import PubFracQuerySet + class PubFrac(models.Model): """ @@ -43,6 +45,8 @@ class PubFrac(models.Model): max_digits=16, decimal_places=3, blank=True, null=True ) + objects = PubFracQuerySet.as_manager() + class Meta: unique_together = (("organization", "publication"),) verbose_name = "PubFrac" diff --git a/scipost_django/finances/models/subsidy.py b/scipost_django/finances/models/subsidy.py index fb42e135461ab9279517fdc3b2bac3a51891a58c..17d9e6733ea19c2e37805ed73af8b8e649b598ba 100644 --- a/scipost_django/finances/models/subsidy.py +++ b/scipost_django/finances/models/subsidy.py @@ -10,8 +10,13 @@ from django.db.models import Sum from django.urls import reverse from django.utils.html import format_html -from ..constants import SUBSIDY_TYPES, SUBSIDY_TYPE_SPONSORSHIPAGREEMENT, SUBSIDY_STATUS -from ..managers import SubsidyQuerySet +from ..constants import ( + SUBSIDY_TYPES, + SUBSIDY_TYPE_SPONSORSHIPAGREEMENT, + SUBSIDY_STATUS, + SUBSIDY_WITHDRAWN, +) +from ..managers import SubsidyQuerySet, PubFracQuerySet class Subsidy(models.Model): @@ -180,6 +185,18 @@ class Subsidy(models.Model): """ return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"] + @property + def allocatable(self): + """ + Determine whether this Subsidy can be allocated. + """ + implemented_algorithms = [self.ALGORITHM_ANY_AFF, self.ALGORITHM_ALL_AFF] + return ( + self.status != SUBSIDY_WITHDRAWN + and self.algorithm != self.ALGORITHM_RESERVES + and self.algorithm in implemented_algorithms + ) + def allocate(self): """ Allocate the funds according to the algorithm specific by the instance. diff --git a/scipost_django/finances/templates/finances/_subsidy_details.html b/scipost_django/finances/templates/finances/_subsidy_details.html index 1c3df29fb2185bd9afd7662e92fe663e748a763e..6722e3012c6379dcd201291529d75b07ac9b5439 100644 --- a/scipost_django/finances/templates/finances/_subsidy_details.html +++ b/scipost_django/finances/templates/finances/_subsidy_details.html @@ -116,8 +116,10 @@ <h3 class="highlight">Expenditures compensated by this Subsidy</h3> - {% if 'edadmin' in user_roles %} - <a class="btn btn-primary" href="{% url 'finances:allocate_subsidy' subsidy_id=subsidy.id %}">Allocate this Subsidy</a> + {% if 'finadmin' in user_roles %} + {% if subsidy.allocatable %} + <a class="btn btn-primary" href="{% url 'finances:allocate_subsidy' subsidy_id=subsidy.id %}">Allocate this Subsidy</a> + {% endif %} {% endif %} <div class="row">