diff --git a/scipost_django/finances/admin.py b/scipost_django/finances/admin.py index 4311d246c0c3a47a58ca50ba5dd87b4f4fd6ed74..1d09bb7a5ce68f5741c58fefb25de7fbbea019b7 100644 --- a/scipost_django/finances/admin.py +++ b/scipost_django/finances/admin.py @@ -73,6 +73,7 @@ class PubFracAdmin(admin.ModelAdmin): "doi_label_display", "fraction", "cf_value", + "compensated_by" ] autocomplete_fields = [ "organization", diff --git a/scipost_django/finances/allocate.py b/scipost_django/finances/allocate.py index 124ad376684dec5fc4ac1b993ef0077156a7f3fd..965f2ef9041434ca1ea0c6b4bbaf4a6090000535 100644 --- a/scipost_django/finances/allocate.py +++ b/scipost_django/finances/allocate.py @@ -36,7 +36,7 @@ The algorithms are implemented in the following order, def allocate_to_any_aff(subsidy: Subsidy): """ - Allocate the Subsidy to PubFracs with affiliation to Subsidy-giver. + 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( @@ -45,8 +45,30 @@ def allocate_to_any_aff(subsidy: Subsidy): publication__publication_date__year__lte=max_year, compensated_by__isnull=True, ) - print(f"{uncompensated_pubfracs.count() = }") for pubfrac in uncompensated_pubfracs.all(): if pubfrac.cf_value <= subsidy.remainder: pubfrac.compensated_by = subsidy pubfrac.save() + + +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, + publication__publication_date__year__gte=subsidy.date_from.year, + publication__publication_date__year__lte=max_year, + ) + for pubfrac in uncompensated_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, + ) + print(f"{pubfracs_for_pub.all() = }") + for pf in pubfracs_for_pub.all(): + if pf.cf_value <= subsidy.remainder: + pf.compensated_by = subsidy + pf.save() diff --git a/scipost_django/finances/models/subsidy.py b/scipost_django/finances/models/subsidy.py index c416a05d6f5a9bd76bf65771741d43bf9ce09d4d..2ad331eb5a6140173ffdcddcd59954373d7887f9 100644 --- a/scipost_django/finances/models/subsidy.py +++ b/scipost_django/finances/models/subsidy.py @@ -183,10 +183,12 @@ class Subsidy(models.Model): """ Allocate the funds according to the algorithm specific by the instance. """ - from finances.allocate import allocate_to_any_aff + from finances.allocate import allocate_to_any_aff, allocate_to_all_aff if self.algorithm == self.ALGORITHM_ANY_AFF: allocate_to_any_aff(self) + elif self.algorithm == self.ALGORITHM_ALL_AFF: + allocate_to_all_aff(self) @property def total_compensations(self): diff --git a/scipost_django/journals/models/publication.py b/scipost_django/journals/models/publication.py index 16a149325130b80ee141dbb5bc70ef4f87194c92..298c0318d94f3df045dfb045f26a96897581442c 100644 --- a/scipost_django/journals/models/publication.py +++ b/scipost_django/journals/models/publication.py @@ -324,6 +324,7 @@ class Publication(models.Model): def get_affiliations(self): """Returns the Organizations mentioned in author affiliations.""" from organizations.models import Organization + return Organization.objects.filter( publicationauthorstable__publication=self ).distinct() @@ -421,9 +422,11 @@ class Publication(models.Model): def recalculate_pubfracs(self): """Recalculates PubFracs using the balanced affiliations algorithm.""" - # First, rmove non-affiliation-related PubFracs + # First, remove non-affiliation-related PubFracs aff_ids = [aff.id for aff in self.get_affiliations()] - PubFrac.objects.filter(publication=self).exclude(pk__in=aff_ids).delete() + PubFrac.objects.filter(publication=self).exclude( + organization__pk__in=aff_ids + ).delete() # Now recreate according to the balanced affiliations algorithm nr_authors = self.authors.all().count() affiliations = self.get_affiliations() @@ -444,10 +447,17 @@ class Publication(models.Model): publication=self, organization=org, ).update( - fraction=Decimal(fraction[org.id]), + fraction=fraction[org.id], cf_value=value, ) + self.ensure_pubfracs_sum_to_1() + def ensure_pubfracs_sum_to_1(self): + """Ensure sum is 1 by putting any difference in the largest PubFrac.""" + pubfrac_max = self.pubfracs.order_by("-fraction").first() + sum_pubfracs = self.pubfracs.aggregate(Sum("fraction"))["fraction__sum"] + pubfrac_max.fraction += 1 - sum_pubfracs + pubfrac_max.save() @property def pubfracs_sum_to_1(self): @@ -458,7 +468,12 @@ class Publication(models.Model): def compensated_expenditures(self): """Compensated part of expenditures for this Publication.""" qs = self.pubfracs.filter(compensated_by__isnull=False) - return qs.aggregate(Sum("cf_value"))["cf_value__sum"] if qs.exists() else 0 + # Use the fraction to obtain an accurate result + return int( + qs.aggregate(Sum("fraction"))["fraction__sum"] * self.expenditures + if qs.exists() + else 0 + ) @property def uncompensated_expenditures(self): diff --git a/scipost_django/journals/templates/journals/publication_detail.html b/scipost_django/journals/templates/journals/publication_detail.html index b55c9f3c203b9132c06a7cd0f0b0649a39002d4a..6b1d9dff5afa1d4db03c62f89378157a3112fe7d 100644 --- a/scipost_django/journals/templates/journals/publication_detail.html +++ b/scipost_django/journals/templates/journals/publication_detail.html @@ -193,13 +193,15 @@ {% if pubfrac.compensated %} <td class="bg-success bg-opacity-25"> by <a href="{% url 'organizations:organization_detail' pk=pubfrac.compensated_by.organization.id %}">{{ pubfrac.compensated_by.organization }}</a> - (<a href="{% url 'finances:subsidy_details' pk=pubfrac.compensated_by.id %}"> +  (<a href="{% url 'finances:subsidy_details' pk=pubfrac.compensated_by.id %}"> see Subsidy details </a>) </td> <td class="bg-success bg-opacity-25">€0</td> {% else %} - <td><span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span></td> + <td class="bg-danger bg-opacity-25"> + <span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span> + </td> <td class="bg-danger bg-opacity-25">€{{ pubfrac.cf_value }}</td> {% endif %} </tr> @@ -208,9 +210,9 @@ <tr> <th>Totals</th> <td>1</td> - <td>{{ publication.expenditures }}</td> - <td class="{% if uncompensated == 0 %}bg-success{% else %}bg-danger{% endif %} bg-opacity-25">{{ publication.compensated_expenditures }}</td> - <td class="{% if uncompensated == 0 %}bg-success{% else %}bg-danger{% endif %} bg-opacity-25">{{ publication.uncompensated_expenditures }}</td> + <td>€{{ publication.expenditures }}</td> + <td class="{% if uncompensated == 0 %}bg-success{% else %}bg-danger{% endif %} bg-opacity-25">€{{ publication.compensated_expenditures }}</td> + <td class="{% if uncompensated == 0 %}bg-success{% else %}bg-danger{% endif %} bg-opacity-25">€{{ publication.uncompensated_expenditures }}</td> </tr> {% endwith %} </tbody>