From 2e5e8c16c151b582a312c6037e1aa1a9def42c4a Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Tue, 20 Feb 2024 14:16:42 +0100
Subject: [PATCH] allow custom refusal reason for ref. invitations

fix #64 (first part)
---
 .../scipost/referee-accept-or-refuse.js       | 20 +++++++----
 scipost_django/submissions/forms/__init__.py  | 34 +++++++++++++++++++
 .../migrations/0147_auto_20240220_1404.py     | 28 +++++++++++++++
 .../submissions/models/assignment.py          |  2 ++
 .../submissions/models/referee_invitation.py  |  1 +
 .../submissions/_refereeing_status_card.html  |  3 ++
 scipost_django/submissions/views/__init__.py  |  9 +++--
 .../templates/email/eic/referee_response.html |  3 ++
 .../confirmation_invitation_response.html     |  3 ++
 9 files changed, 94 insertions(+), 9 deletions(-)
 create mode 100644 scipost_django/submissions/migrations/0147_auto_20240220_1404.py

diff --git a/scipost_django/scipost/static/scipost/referee-accept-or-refuse.js b/scipost_django/scipost/static/scipost/referee-accept-or-refuse.js
index 8bd1a0e9e..60912463d 100644
--- a/scipost_django/scipost/static/scipost/referee-accept-or-refuse.js
+++ b/scipost_django/scipost/static/scipost/referee-accept-or-refuse.js
@@ -1,10 +1,18 @@
-$(document).ready(function(){
-    $('[name="accept"]').on('change', function() {
-	if($('[name="accept"]:checked').val() == 'False') {
+$(document).ready(function () {
+    $('[name="accept"]').on('change', function () {
+        if ($('[name="accept"]:checked').val() == 'False') {
             $('#id_refusal_reason').parents('.form-group').show();
-	}
-	else {
+        }
+        else {
             $('#id_refusal_reason').parents('.form-group').hide();
-	}
+        }
+    }).trigger('change');
+    $('[name="refusal_reason"]').on('change', function () {
+        if ($('[name="refusal_reason"]').val() == 'OTH') {
+            $('#id_other_refusal_reason').parents('.form-group').show();
+        }
+        else {
+            $('#id_other_refusal_reason').parents('.form-group').hide();
+        }
     }).trigger('change');
 });
diff --git a/scipost_django/submissions/forms/__init__.py b/scipost_django/submissions/forms/__init__.py
index c24875bd3..0eac0a986 100644
--- a/scipost_django/submissions/forms/__init__.py
+++ b/scipost_django/submissions/forms/__init__.py
@@ -2266,6 +2266,40 @@ class ConsiderRefereeInvitationForm(forms.Form):
     refusal_reason = forms.ChoiceField(
         choices=EditorialAssignment.REFUSAL_REASONS, required=False
     )
+    other_refusal_reason = forms.CharField(
+        required=False,
+        widget=forms.Textarea(
+            {
+                "placeholder": "Please shortly describe your reason for declining. (255 characters max)"
+            }
+        ),
+        max_length=255,
+    )
+
+    def clean(self):
+        accepted = self.cleaned_data.get("accept", None)
+        reason = self.cleaned_data.get("refusal_reason", None)
+        other_refusal_reason = self.cleaned_data.get("other_refusal_reason", None)
+
+        if accepted == "False":
+            if reason is None:
+                self.add_error(
+                    "refusal_reason", "Please select a reason for declining."
+                )
+            if reason == "other" and other_refusal_reason is None:
+                self.add_error(
+                    "other_refusal_reason", "Please specify your reason for declining."
+                )
+            elif reason != "other" and other_refusal_reason is not None:
+                self.add_error(
+                    "other_refusal_reason",
+                    'Please select "Other" to specify your reason for declining.',
+                )
+        elif reason is not None:
+            self.add_error(
+                "refusal_reason",
+                "You cannot select a refusal reason if you accept.",
+            )
 
 
 class SetRefereeingDeadlineForm(forms.Form):
diff --git a/scipost_django/submissions/migrations/0147_auto_20240220_1404.py b/scipost_django/submissions/migrations/0147_auto_20240220_1404.py
new file mode 100644
index 000000000..4e3f17904
--- /dev/null
+++ b/scipost_django/submissions/migrations/0147_auto_20240220_1404.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.18 on 2024-02-20 13:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('submissions', '0146_submission_lifetime_dates'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='refereeinvitation',
+            name='other_refusal_reason',
+            field=models.CharField(blank=True, max_length=255, null=True),
+        ),
+        migrations.AlterField(
+            model_name='editorialassignment',
+            name='refusal_reason',
+            field=models.CharField(blank=True, choices=[('OFE', 'Outside of my field of expertise'), ('BUS', 'Too busy'), ('VAC', 'Away on vacation'), ('COI', 'Conflict of interest: coauthor in last 5 years'), ('CCC', 'Conflict of interest: close colleague'), ('CCM', 'Conflict of interest: close competitor'), ('COT', 'Conflict of interest: other'), ('NIR', 'Cannot give an impartial assessment'), ('NIE', 'Not interested enough'), ('DNP', 'SciPost should desk reject this paper'), ('OTH', 'Other')], max_length=3, null=True),
+        ),
+        migrations.AlterField(
+            model_name='refereeinvitation',
+            name='refusal_reason',
+            field=models.CharField(blank=True, choices=[('OFE', 'Outside of my field of expertise'), ('BUS', 'Too busy'), ('VAC', 'Away on vacation'), ('COI', 'Conflict of interest: coauthor in last 5 years'), ('CCC', 'Conflict of interest: close colleague'), ('CCM', 'Conflict of interest: close competitor'), ('COT', 'Conflict of interest: other'), ('NIR', 'Cannot give an impartial assessment'), ('NIE', 'Not interested enough'), ('DNP', 'SciPost should desk reject this paper'), ('OTH', 'Other')], max_length=3, null=True),
+        ),
+    ]
diff --git a/scipost_django/submissions/models/assignment.py b/scipost_django/submissions/models/assignment.py
index f93656a7e..f73e5c39d 100644
--- a/scipost_django/submissions/models/assignment.py
+++ b/scipost_django/submissions/models/assignment.py
@@ -27,6 +27,7 @@ class EditorialAssignment(SubmissionRelatedObjectMixin, models.Model):
     REFUSE_NOT_IMPARTIAL = "NIR"
     REFUSE_NOT_INTERESTED = "NIE"
     REFUSE_DESK_REJECT = "DNP"
+    REFUSE_OTHER = "OTH"
     REFUSAL_REASONS = (
         (REFUSE_OUTSIDE_EXPERTISE, "Outside of my field of expertise"),
         (REFUSE_TOO_BUSY, "Too busy"),
@@ -41,6 +42,7 @@ class EditorialAssignment(SubmissionRelatedObjectMixin, models.Model):
             REFUSE_DESK_REJECT,
             "SciPost should desk reject this paper",
         ),
+        (REFUSE_OTHER, "Other"),
     )
 
     STATUS_PREASSIGNED = "preassigned"
diff --git a/scipost_django/submissions/models/referee_invitation.py b/scipost_django/submissions/models/referee_invitation.py
index 5063e8aa5..15a8c7cce 100644
--- a/scipost_django/submissions/models/referee_invitation.py
+++ b/scipost_django/submissions/models/referee_invitation.py
@@ -71,6 +71,7 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
         blank=True,
         null=True,
     )
+    other_refusal_reason = models.CharField(max_length=255, blank=True, null=True)
     fulfilled = models.BooleanField(
         default=False
     )  # True if a Report has been submitted
diff --git a/scipost_django/submissions/templates/submissions/_refereeing_status_card.html b/scipost_django/submissions/templates/submissions/_refereeing_status_card.html
index e3109323b..72092d1ea 100644
--- a/scipost_django/submissions/templates/submissions/_refereeing_status_card.html
+++ b/scipost_django/submissions/templates/submissions/_refereeing_status_card.html
@@ -47,5 +47,8 @@
     <h3>Your Referee Invitation</h3>
     <p>You have declined to contribute a Report. Nonetheless, we thank you very much for considering this refereeing invitation.</p>
     <p>Reason: {{ invitation.get_refusal_reason_display }}</p>
+    {% if invitation.refusal_reason == 'OTH' %}
+      <p>{{ invitation.other_refusal_reason }}</p>
+    {% endif %}
   {% endif %}
 </div>
diff --git a/scipost_django/submissions/views/__init__.py b/scipost_django/submissions/views/__init__.py
index 2f19bc680..ba971351b 100644
--- a/scipost_django/submissions/views/__init__.py
+++ b/scipost_django/submissions/views/__init__.py
@@ -1418,6 +1418,7 @@ def accept_or_decline_ref_invitations(request, invitation_id=None):
             invitation.accepted = False
             decision_string = "declined"
             invitation.refusal_reason = form.cleaned_data["refusal_reason"]
+            invitation.other_refusal_reason = form.cleaned_data["other_refusal_reason"]
             messages.success(
                 request,
                 (
@@ -2964,9 +2965,11 @@ def submissions_versus_fellows(submissions):
                         "fellows_senior": fellows_senior,
                         "fellows_regular": fellows_regular,
                         "fellows_guest": fellows_guest,
-                        "ratio": nr_streams / fellows_total
-                        if fellows_total > 0
-                        else nr_streams,
+                        "ratio": (
+                            nr_streams / fellows_total
+                            if fellows_total > 0
+                            else nr_streams
+                        ),
                     }
                 )
     return sorted(stats, key=lambda tup: tup["ratio"], reverse=True)
diff --git a/scipost_django/templates/email/eic/referee_response.html b/scipost_django/templates/email/eic/referee_response.html
index bde0688f6..a0296d4d1 100644
--- a/scipost_django/templates/email/eic/referee_response.html
+++ b/scipost_django/templates/email/eic/referee_response.html
@@ -3,6 +3,9 @@
 <p>
     Referee {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.get_title_display }} {{ invitation.first_name }} {{ invitation.last_name }}{% endif %} has {% if invitation.accepted %}accepted{% else %}declined (due to reason: {{ invitation.get_refusal_reason_display }}){% endif %} to referee Submission
 </p>
+{% if invitation.other_refusal_reason %}
+  <p>Their "other" refusal reason states: {{ invitation.other_refusal_reason }}</p>
+{% endif %}
 <p>
   {{ invitation.submission.title }}
   <br/>
diff --git a/scipost_django/templates/email/referees/confirmation_invitation_response.html b/scipost_django/templates/email/referees/confirmation_invitation_response.html
index d8183c155..327356917 100644
--- a/scipost_django/templates/email/referees/confirmation_invitation_response.html
+++ b/scipost_django/templates/email/referees/confirmation_invitation_response.html
@@ -3,6 +3,9 @@
 <p>
   We hereby confirm your choice to {% if invitation.accepted %}accept{% else %}decline (due to reason: {{ invitation.get_refusal_reason_display }}){% endif %} to referee Submission
 </p>
+{% if invitation.other_refusal_reason %}
+  <p>Your "other" refusal reason states: {{ invitation.other_refusal_reason }}</p>
+{% endif %}
 <p>
   {{ invitation.submission.title }}
   <br/>
-- 
GitLab