diff --git a/scipost_django/finances/forms.py b/scipost_django/finances/forms.py index 3d13edceaf7e5ca2fba66c5134f55e1d2f36536c..163678983c83c3c3efae461926e297ff856204ab 100644 --- a/scipost_django/finances/forms.py +++ b/scipost_django/finances/forms.py @@ -58,7 +58,6 @@ class SubsidyForm(forms.ModelForm): class Meta: model = Subsidy fields = [ - "algorithm", "organization", "subsidy_type", "description", diff --git a/scipost_django/finances/management/commands/compensate_pubfracs.py b/scipost_django/finances/management/commands/compensate_pubfracs.py new file mode 100644 index 0000000000000000000000000000000000000000..2450a7e24a103dc0670054bf9fb0c6a4b04bfa13 --- /dev/null +++ b/scipost_django/finances/management/commands/compensate_pubfracs.py @@ -0,0 +1,19 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.core.management.base import BaseCommand + +from finances.models import Subsidy + + +class Command(BaseCommand): + help = "Applies compensations from Subsidies to PubFracs" + + def handle(self, *args, **kwargs): + # All Subsidy-givers first compensate themselves, starting from oldest Subsidy + for subsidy in Subsidy.objects.obtained().order_by("date_from"): + subsidy.compensate_own_pubfracs() + # then their children, again starting from oldest Subsidy + for subsidy in Subsidy.objects.obtained().order_by("date_from"): + subsidy.compensate_children_pubfracs() diff --git a/scipost_django/finances/migrations/0033_populate_pubfracs.py b/scipost_django/finances/migrations/0033_populate_pubfracs.py index d4a07844f03c1a91283da5339fa3ad8112aa0b64..17cec8580a14af4267e8434aba63db447f9d8c32 100644 --- a/scipost_django/finances/migrations/0033_populate_pubfracs.py +++ b/scipost_django/finances/migrations/0033_populate_pubfracs.py @@ -4,18 +4,18 @@ from django.db import migrations def populate_pubfracs(apps, schema_editor): - OrgPubFraction = apps.get_model("journals.OrgPubFraction") - PubFrac = apps.get_model("finances.PubFrac") - - # Copy all data from OrgPubFraction to the new PubFrac - for opf in OrgPubFraction.objects.all(): - pubfrac = PubFrac( - organization=opf.organization, - publication=opf.publication, - fraction=opf.fraction - ) - pubfrac.save() - + # OrgPubFraction = apps.get_model("journals.OrgPubFraction") + # PubFrac = apps.get_model("finances.PubFrac") + + # # Copy all data from OrgPubFraction to the new PubFrac + # for opf in OrgPubFraction.objects.all(): + # pubfrac = PubFrac( + # organization=opf.organization, + # publication=opf.publication, + # fraction=opf.fraction + # ) + # pubfrac.save() + pass # 2024-03-20 not needed anymore: PubFracs are algorithmically calculated class Migration(migrations.Migration): diff --git a/scipost_django/finances/migrations/0044_remove_subsidy_algorithm_and_more.py b/scipost_django/finances/migrations/0044_remove_subsidy_algorithm_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..7202f1dc7b5acdef2a6a6d26b6109eba7108dc69 --- /dev/null +++ b/scipost_django/finances/migrations/0044_remove_subsidy_algorithm_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.10 on 2024-03-20 15:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("finances", "0043_alter_pubfrac_cf_value"), + ] + + operations = [ + migrations.RemoveField( + model_name="subsidy", + name="algorithm", + ), + migrations.RemoveField( + model_name="subsidy", + name="algorithm_data", + ), + ] diff --git a/scipost_django/finances/models/subsidy.py b/scipost_django/finances/models/subsidy.py index 17d9e6733ea19c2e37805ed73af8b8e649b598ba..8467098b9d7b3870c531bb056588dbb1e09f35df 100644 --- a/scipost_django/finances/models/subsidy.py +++ b/scipost_django/finances/models/subsidy.py @@ -39,64 +39,6 @@ class Subsidy(models.Model): after which the object of the Subsidy is officially terminated. """ - ALGORITHM_ANY_AFF = "any_aff" - ALGORITHM_ANY_CTRY = "any_ctry" - ALGORITHM_ANY_ORGS = "any_orgs" - ALGORITHM_ANY_SPEC = "any_spec" - ALGORITHM_ALL_AFF = "all_aff" - ALGORITHM_ALL_CTRY = "all_ctry" - ALGORITHM_ALL_ORGS = "all_orgs" - ALGORITHM_ALL_SPEC = "all_spec" - ALGORITHM_ALL_FUND = "all_fund" - ALGORITHM_RESERVES = "reserves" - ALGORITHM_CHOICES = ( - (ALGORITHM_ANY_AFF, "Any PubFrac with affiliation to org"), - ( - ALGORITHM_ANY_CTRY, - "Any PubFrac with an affiliation in given list of countries", - ), - (ALGORITHM_ANY_ORGS, "Any PubFrac with an affiliation in given list of orgs"), - ( - ALGORITHM_ANY_SPEC, - "Any PubFrac of publication in given list of specialties", - ), - ( - ALGORITHM_ALL_AFF, - ( - "All PubFracs of publication with at least one author " - "with affiliation to org" - ), - ), - ( - ALGORITHM_ALL_CTRY, - ( - "All PubFracs of publications having at least one affiliation " - "in given list of countries" - ), - ), - ( - ALGORITHM_ALL_ORGS, - ( - "All PubFracs of publications having at least " - "one affiliation in given list of orgs" - ), - ), - ( - ALGORITHM_ALL_SPEC, - "All PubFracs of publication in given list of specialties", - ), - ( - ALGORITHM_ALL_FUND, - "All PubFracs of publication acknowledging org in Funders", - ), - (ALGORITHM_RESERVES, "Allocate to reserves fund"), - ) - algorithm = models.CharField( - max_length=32, - choices=ALGORITHM_CHOICES, - default=ALGORITHM_RESERVES, - ) - algorithm_data = models.JSONField(default=dict) organization = models.ForeignKey["Organization"]( "organizations.Organization", on_delete=models.CASCADE ) @@ -185,28 +127,37 @@ class Subsidy(models.Model): """ return self.amount == self.payments.aggregate(Sum("amount"))["amount__sum"] - @property - def allocatable(self): + def compensate(self, pubfracs: PubFracQuerySet): """ - Determine whether this Subsidy can be allocated. + Allocate subsidy to uncompensated pubfracs in queryset, up to depletion. """ - 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 - ) + for pubfrac in pubfracs.uncompensated(): + if pubfrac.cf_value <= self.remainder: + pubfrac.compensated_by = self + pubfrac.save() - def allocate(self): + def compensate_own_pubfracs(self): """ - Allocate the funds according to the algorithm specific by the instance. + Compensate PubFracs with direct affiliation to Subsidy-giver. """ - from finances.allocate import allocate_to_any_aff, allocate_to_all_aff + max_year = self.date_until.year if self.date_until else self.date_from.year + pubfracs = self.organization.pubfracs.filter( + publication__publication_date__year__gte=self.date_from.year, + publication__publication_date__year__lte=max_year, + ) + self.compensate(pubfracs) - if self.algorithm == self.ALGORITHM_ANY_AFF: - allocate_to_any_aff(self) - elif self.algorithm == self.ALGORITHM_ALL_AFF: - allocate_to_all_aff(self) + def compensate_children_pubfracs(self): + """ + Compensate PubFracs with direct affiliation to Subsidy-giver's children Organizations. + """ + max_year = self.date_until.year if self.date_until else self.date_from.year + for child in self.organization.children.all(): + pubfracs = child.pubfracs.filter( + publication__publication_date__year__gte=self.date_from.year, + publication__publication_date__year__lte=max_year, + ) + self.compensate(pubfracs) @property def total_compensations(self): diff --git a/scipost_django/organizations/models.py b/scipost_django/organizations/models.py index 4ffeb46ac0195f6bb94a1c97a43d40d23dd616de..826bffb632818d75fa31e2bf97e6f3af4a0134b2 100644 --- a/scipost_django/organizations/models.py +++ b/scipost_django/organizations/models.py @@ -4,7 +4,6 @@ __license__ = "AGPL v3" import datetime import hashlib -import pytz import random import string @@ -17,6 +16,15 @@ from django.urls import reverse from django_countries.fields import CountryField +from scipost.constants import TITLE_CHOICES +from scipost.fields import ChoiceArrayField +from scipost.models import Contributor +from affiliates.models import AffiliatePublication +from colleges.models import Fellowship +from finances.models import PubFrac +from journals.models import Journal, Publication +from profiles.models import Profile + from .constants import ( ORGANIZATION_TYPES, ORGTYPE_PRIVATE_BENEFACTOR, @@ -27,15 +35,6 @@ from .constants import ( ) from .managers import OrganizationQuerySet -from scipost.constants import TITLE_CHOICES -from scipost.fields import ChoiceArrayField -from scipost.models import Contributor -from affiliates.models import AffiliatePublication -from colleges.models import Fellowship -from finances.models import PubFrac -from journals.models import Journal, Publication -from profiles.models import Profile - class OrganizationLogo(models.Model): """