From 6be1a7c14b97c32cb153310d42f43af890ea560e Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Sat, 6 Oct 2018 23:42:48 +0200
Subject: [PATCH] Introduce Subsidy model

---
 finances/admin.py                             |  4 +-
 finances/constants.py                         | 38 ++++++++++++++++
 finances/migrations/0002_subsidy.py           | 34 ++++++++++++++
 finances/models.py                            | 28 ++++++++++++
 .../finances/subsidy_confirm_delete.html      | 25 +++++++++++
 finances/templates/finances/subsidy_form.html | 21 +++++++++
 finances/templates/finances/subsidy_list.html | 20 +++++++++
 finances/urls.py                              | 23 ++++++++++
 finances/views.py                             | 44 ++++++++++++++++++-
 .../organizations/_organization_card.html     | 18 ++++----
 .../migrations/0011_auto_20181006_2341.py     | 23 ++++++++++
 .../commands/add_groups_and_permissions.py    |  4 ++
 12 files changed, 270 insertions(+), 12 deletions(-)
 create mode 100644 finances/constants.py
 create mode 100644 finances/migrations/0002_subsidy.py
 create mode 100644 finances/templates/finances/subsidy_confirm_delete.html
 create mode 100644 finances/templates/finances/subsidy_form.html
 create mode 100644 finances/templates/finances/subsidy_list.html
 create mode 100644 profiles/migrations/0011_auto_20181006_2341.py

diff --git a/finances/admin.py b/finances/admin.py
index effd61f10..c887dee02 100644
--- a/finances/admin.py
+++ b/finances/admin.py
@@ -4,7 +4,9 @@ __license__ = "AGPL v3"
 
 from django.contrib import admin
 
-from .models import WorkLog
+from .models import Subsidy, WorkLog
 
 
+admin.site.register(Subsidy)
+
 admin.site.register(WorkLog)
diff --git a/finances/constants.py b/finances/constants.py
new file mode 100644
index 000000000..e5f21aad2
--- /dev/null
+++ b/finances/constants.py
@@ -0,0 +1,38 @@
+__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+import datetime
+
+
+SUBSIDY_TYPE_GRANT = 'grant'
+SUBSIDY_TYPE_PARTNERAGREEMENT = 'partneragreement'
+SUBSIDY_TYPE_COLLABORATION = 'collaborationagreement'
+
+SUBSIDY_TYPES = (
+    (SUBSIDY_TYPE_GRANT, 'Grant'),
+    (SUBSIDY_TYPE_PARTNERAGREEMENT, 'Partner Agreement'),
+    (SUBSIDY_TYPE_COLLABORATION, 'Collaboration Agreement'),
+)
+
+
+SUBSIDY_PROMISED = 'promised'
+SUBSIDY_INVOICED = 'invoiced'
+SUBSIDY_RECEIVED = 'received'
+
+SUBSIDY_STATUS = (
+    (SUBSIDY_PROMISED, 'promised'),
+    (SUBSIDY_INVOICED, 'invoiced'),
+    (SUBSIDY_RECEIVED, 'received'),
+)
+
+
+SUBSIDY_DURATION = (
+    (datetime.timedelta(days=365), '1 year'),
+    (datetime.timedelta(days=730), '2 years'),
+    (datetime.timedelta(days=1095), '3 years'),
+    (datetime.timedelta(days=1460), '4 years'),
+    (datetime.timedelta(days=1825), '5 years'),
+    (datetime.timedelta(days=3650), '10 years'),
+    (datetime.timedelta(days=36500), 'Indefinite (100 years)'),
+)
diff --git a/finances/migrations/0002_subsidy.py b/finances/migrations/0002_subsidy.py
new file mode 100644
index 000000000..a66d36940
--- /dev/null
+++ b/finances/migrations/0002_subsidy.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-10-06 21:41
+from __future__ import unicode_literals
+
+import datetime
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organizations', '0002_populate_from_partners_org'),
+        ('finances', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Subsidy',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('subsidy_type', models.CharField(choices=[('grant', 'Grant'), ('partneragreement', 'Partner Agreement'), ('collaborationagreement', 'Collaboration Agreement')], max_length=256)),
+                ('description', models.CharField(max_length=256)),
+                ('amount', models.PositiveSmallIntegerField()),
+                ('status', models.CharField(choices=[('promised', 'promised'), ('invoiced', 'invoiced'), ('received', 'received')], max_length=32)),
+                ('date', models.DateField()),
+                ('duration', models.DurationField(blank=True, choices=[(datetime.timedelta(365), '1 year'), (datetime.timedelta(730), '2 years'), (datetime.timedelta(1095), '3 years'), (datetime.timedelta(1460), '4 years'), (datetime.timedelta(1825), '5 years'), (datetime.timedelta(3650), '10 years'), (datetime.timedelta(36500), 'Indefinite (100 years)')], null=True)),
+                ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organizations.Organization')),
+            ],
+            options={
+                'verbose_name_plural': 'subsidies',
+            },
+        ),
+    ]
diff --git a/finances/models.py b/finances/models.py
index 1343616c5..415efa041 100644
--- a/finances/models.py
+++ b/finances/models.py
@@ -8,9 +8,37 @@ from django.contrib.contenttypes.fields import GenericForeignKey
 from django.db import models
 from django.utils import timezone
 
+from .constants import SUBSIDY_TYPES, SUBSIDY_STATUS, SUBSIDY_DURATION
 from .utils import id_to_slug
 
 
+class Subsidy(models.Model):
+    """
+    A subsidy given to SciPost by an Organization.
+    Any fund given to SciPost, in any form, must be associated
+    to a corresponding Subsidy instance.
+
+    This can for example be:
+    - a Partners agreement
+    - an incidental grant
+    - a development grant for a specific purpose
+    - a Collaboration Agreement
+    """
+    organization = models.ForeignKey('organizations.Organization', on_delete=models.CASCADE)
+    subsidy_type = models.CharField(max_length=256, choices=SUBSIDY_TYPES)
+    description = models.CharField(max_length=256)
+    amount = models.PositiveSmallIntegerField()
+    status = models.CharField(max_length=32, choices=SUBSIDY_STATUS)
+    date = models.DateField()
+    duration = models.DurationField(choices=SUBSIDY_DURATION, blank=True, null=True)
+
+    class Meta:
+        verbose_name_plural = 'subsidies'
+
+    def __str__(self):
+        return '%s: %s, %s' % (self.date, self.organization, self.description)
+
+
 class WorkLog(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
     comments = models.TextField(blank=True)
diff --git a/finances/templates/finances/subsidy_confirm_delete.html b/finances/templates/finances/subsidy_confirm_delete.html
new file mode 100644
index 000000000..d50e6c7e5
--- /dev/null
+++ b/finances/templates/finances/subsidy_confirm_delete.html
@@ -0,0 +1,25 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Delete Subsidy{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+    <div class="col-12">
+        <h1 class="highlight">Delete Subsidy</h1>
+	{{ object }}
+    </div>
+</div>
+<div class="row">
+  <div class="col-12">
+      <form method="post">
+        {% csrf_token %}
+        <h3 class="mb-2">Are you sure you want to delete this Subsidy?</h3>
+        <input type="submit" class="btn btn-danger" value="Yes, delete it" />
+      </form>
+    </ul>
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/finances/templates/finances/subsidy_form.html b/finances/templates/finances/subsidy_form.html
new file mode 100644
index 000000000..5ed78daa7
--- /dev/null
+++ b/finances/templates/finances/subsidy_form.html
@@ -0,0 +1,21 @@
+{% extends 'profiles/base.html' %}
+
+{% load bootstrap %}
+
+{% block breadcrumb_items %}
+    {{ block.super }}
+    <span class="breadcrumb-item">{% if form.instance.id %}Update {{ form.instance }}{% else %}Add new Subsidy{% endif %}</span>
+{% endblock %}
+
+{% block pagetitle %}: Subsidies{% endblock pagetitle %}
+
+{% block content %}
+<div class="row">
+  <div class="col-12">
+    <form action="" method="post">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input type="submit" value="Submit" class="btn btn-primary">
+  </div>
+</div>
+{% endblock content %}
diff --git a/finances/templates/finances/subsidy_list.html b/finances/templates/finances/subsidy_list.html
new file mode 100644
index 000000000..1d5593cea
--- /dev/null
+++ b/finances/templates/finances/subsidy_list.html
@@ -0,0 +1,20 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: Subsidies{% endblock pagetitle %}
+
+
+{% block content %}
+
+<div class="row">
+  <div class="col-12">
+    <h3>Subsidies</h3>
+    <ul>
+      {% for subsidy in object_list %}
+      <li>{{ subsidy }}</li>
+      {% empty %}
+      <li>No Subsidy found</li>
+      {% endfor %}
+  </div>
+</div>
+
+{% endblock content %}
diff --git a/finances/urls.py b/finances/urls.py
index 7299cbd1e..865cd0f7a 100644
--- a/finances/urls.py
+++ b/finances/urls.py
@@ -8,6 +8,29 @@ from . import views
 
 urlpatterns = [
     url(r'^$', views.timesheets, name='finance'),
+
+    url(
+        r'^subsidies/$',
+        views.SubsidyListView.as_view(),
+        name='subsidies'
+    ),
+    url(
+        r'^subsidies/add/$',
+        views.SubsidyCreateView.as_view(),
+        name='subsidy_create'
+    ),
+    url(
+        r'^subsidies/(?P<pk>[0-9]+)/update/$',
+        views.SubsidyUpdateView.as_view(),
+        name='subsidy_update'
+        ),
+    url(
+        r'^subsidies/(?P<pk>[0-9]+)/delete/$',
+        views.SubsidyDeleteView.as_view(),
+        name='subsidy_delete'
+    ),
+
+
     url(r'^timesheets$', views.timesheets, name='timesheets'),
     url(r'^logs/(?P<slug>\d+)/delete$', views.LogDeleteView.as_view(), name='log_delete'),
 ]
diff --git a/finances/views.py b/finances/views.py
index 34123e7e2..11071a100 100644
--- a/finances/views.py
+++ b/finances/views.py
@@ -5,14 +5,54 @@ __license__ = "AGPL v3"
 from django.contrib import messages
 from django.contrib.auth.decorators import permission_required
 from django.contrib.auth.mixins import LoginRequiredMixin
+from django.core.urlresolvers import reverse_lazy
 from django.http import Http404
 from django.shortcuts import render
-from django.views.generic.edit import DeleteView
+from django.views.generic.detail import DetailView
+from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.views.generic.list import ListView
 
 from .forms import LogsMonthlyActiveFilter
-from .models import WorkLog
+from .models import Subsidy, WorkLog
 from .utils import slug_to_id
 
+from scipost.mixins import PermissionsMixin
+
+
+class SubsidyCreateView(PermissionsMixin, CreateView):
+    """
+    Create a new Subsidy.
+    """
+    permission_required = 'scipost.can_manage_subsidies'
+    model = Subsidy
+    fields = '__all__'
+    template_name = 'finances/subsidy_create.html'
+    success_url = reverse_lazy('finances:subsidies')
+
+
+class SubsidyUpdateView(PermissionsMixin, UpdateView):
+    """
+    Update a Subsidy.
+    """
+    permission_required = 'scipost.can_manage_subsidies'
+    model = Subsidy
+    fields = '__all__'
+    template_name = 'finances/subsidy_update.html'
+    success_url = reverse_lazy('finances:subsidies')
+
+
+class SubsidyDeleteView(PermissionsMixin, DeleteView):
+    """
+    Delete a Subsidy.
+    """
+    permission_required = 'scipost.can_manage_subsidies'
+    model = Subsidy
+    success_url = reverse_lazy('finances:subsidies')
+
+
+class SubsidyListView(ListView):
+    model = Subsidy
+
 
 @permission_required('scipost.can_view_timesheets', raise_exception=True)
 def timesheets(request):
diff --git a/organizations/templates/organizations/_organization_card.html b/organizations/templates/organizations/_organization_card.html
index a52c04bd7..0f42473c9 100644
--- a/organizations/templates/organizations/_organization_card.html
+++ b/organizations/templates/organizations/_organization_card.html
@@ -18,10 +18,10 @@
 	  <a class="nav-link" id="authors-{{ org.id }}-tab" data-toggle="tab" href="#authors-{{ org.id }}" role="tab" aria-controls="authors-{{ org.id }}" aria-selected="true">Associated Authors</a>
 	</li>
 	<li class="nav-item">
-	  <a class="nav-link" id="funders-{{ org.id }}-tab" data-toggle="tab" href="#funders-{{ org.id }}" role="tab" aria-controls="funders-{{ org.id }}" aria-selected="true">FundRef instances</a>
+	  <a class="nav-link" id="funders-{{ org.id }}-tab" data-toggle="tab" href="#funders-{{ org.id }}" role="tab" aria-controls="funders-{{ org.id }}" aria-selected="true">Funder Registry instances</a>
 	</li>
 	<li class="nav-item">
-	  <a class="nav-link" id="partnership-{{ org.id }}-tab" data-toggle="tab" href="#partnership-{{ org.id }}" role="tab" aria-controls="partnership-{{ org.id }}" aria-selected="true">Partnership history</a>
+	  <a class="nav-link" id="support-{{ org.id }}-tab" data-toggle="tab" href="#support-{{ org.id }}" role="tab" aria-controls="support-{{ org.id }}" aria-selected="true">Support history</a>
 	</li>
 	{% if perms.scipost.can_manage_organizations %}
 	<li class="nav-item">
@@ -82,25 +82,25 @@
 	</div>
 
 	<div class="tab-pane pt-4" id="funders-{{ org.id }}" role="tabpanel" aria-labelledby="funders-{{ org.id }}-tab">
-	  <h3>FundRef instances associated to this Organization:</h3>
+	  <h3>Funder Registry instances associated to this Organization:</h3>
 	  <ul>
 	    {% for funder in org.funder_set.all %}
 	    <li>{{ funder }}</li>
 	    {% empty %}
-	    <li>No FundRef instance found<br/><br/>
-	      <strong class="text-danger">Without a FundRef instance, we cannot record funding acknowledgements to this Organization at Crossref.</strong>
+	    <li>No Funder Registry instance found<br/><br/>
+	      <strong class="text-danger">Without a Funder Registry instance, we cannot record funding acknowledgements to this Organization with Crossref.</strong>
 	      <p>Are you a representative of this Organization? Please:</p>
 	      <ol>
-		<li>Make sure your Organization is included in Crossref's FundRef database</li>
-		<li>After inclusion, <a href="mailto:admin@scipost.org?subject=Inclusion of {{ organization }} {% if organization.acronym %}({{ organization.acronym }}){% endif %} in FundRef">contact our administration</a> with this information so that we can update our records.</li>
+		<li>Make sure your Organization is included in <a href="https://www.crossref.org/services/funder-registry/" target="_blank">Crossref's Funder Registry</a></li>
+		<li>After inclusion, <a href="mailto:admin@scipost.org?subject=Inclusion of {{ organization }} {% if organization.acronym %}({{ organization.acronym }}){% endif %} in the Funder Registry">contact our administration</a> with this information so that we can update our records.</li>
 	      </ol>
 	    </li>
 	    {% endfor %}
 	  </ul>
 	</div>
 
-	<div class="tab-pane pt-4" id="partnership-{{ org.id }}" role="tabpanel" aria-labelledby="partnership-{{ org.id }}-tab">
-	  <h3>Partnership history:</h3>
+	<div class="tab-pane pt-4" id="support-{{ org.id }}" role="tabpanel" aria-labelledby="support-{{ org.id }}-tab">
+	  <h3>Supporting Partner Agreements history:</h3>
 	  {% with agreement=org.partner.get_latest_active_agreement %}
 	  {% if agreement %}
 	  <p>This organization is currently a SciPost Supporting Partner.</p>
diff --git a/profiles/migrations/0011_auto_20181006_2341.py b/profiles/migrations/0011_auto_20181006_2341.py
new file mode 100644
index 000000000..af1221f59
--- /dev/null
+++ b/profiles/migrations/0011_auto_20181006_2341.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-10-06 21:41
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('profiles', '0010_auto_20181002_1114'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='profileemail',
+            options={'ordering': ['-primary', '-still_valid', 'email']},
+        ),
+        migrations.RemoveField(
+            model_name='profile',
+            name='email',
+        ),
+    ]
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index 928cd548f..f481674d1 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -291,6 +291,10 @@ class Command(BaseCommand):
             content_type=content_type)
 
         # Financial administration
+        can_manage_subsidies, created = Permission.objects.get_or_create(
+            codename='can_manage_subsidies',
+            name='Can manage subsidies',
+            content_type=content_type)
         can_view_timesheets, created = Permission.objects.get_or_create(
             codename='can_view_timesheets',
             name='Can view timesheets',
-- 
GitLab