From 87d92bddb4226d475914029b62b6fe200a289d88 Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Fri, 27 Sep 2024 15:30:24 +0200
Subject: [PATCH] add `autorejection_date` to Submissions

add auto-rejection date extension button for edadmin/authors

fixes #335
---
 .../_submission_maintenance_card_general.html | 36 ++++++++----
 scipost_django/edadmin/views/preassignment.py | 20 ++++---
 .../0138_journal_autoreject_period.py         | 18 ++++++
 scipost_django/journals/models/journal.py     |  1 +
 .../personal_page/_hx_submissions.html        |  3 +
 scipost_django/submissions/forms/__init__.py  |  4 ++
 .../0163_submission_autorejection_date.py     | 37 ++++++++++++
 .../submissions/models/submission.py          | 13 +++++
 .../_submission_details_summary_contents.html |  8 +++
 scipost_django/submissions/urls/__init__.py   | 10 ++++
 scipost_django/submissions/views/__init__.py  | 56 ++++++++++++++++++-
 11 files changed, 182 insertions(+), 24 deletions(-)
 create mode 100644 scipost_django/journals/migrations/0138_journal_autoreject_period.py
 create mode 100644 scipost_django/submissions/migrations/0163_submission_autorejection_date.py

diff --git a/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html b/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html
index d4cf1c43b..fd7ceb96f 100644
--- a/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html
+++ b/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html
@@ -20,25 +20,37 @@
                 hx-prompt="What is the reason for putting the submission on hold? (will be added as a submission internal note)"
                 {% endif %}
                 hx-target="#submission-{{ submission.id }}-details">
-                {% if submission.on_hold %}
-                Take off hold
-                {% else %}
-                Put on hold
-                {% endif %}
+            {% if submission.on_hold %}
+            Take off hold
+            {% else %}
+            Put on hold
+            {% endif %}
         </button>
       </li>
+      {% if submission.autorejection_date %}
+        <li>
+          <div>
+            Extend autorejection date by:
+          </div>
+          <ul role="menu" class="d-flex gap-3 list-unstyled">
+            <li role="menuitem"><a href="{% url 'submissions:extend_autorejection_date' submission.preprint.identifier_w_vn_nr 7 %}">7 days</a></li>
+            <li role="menuitem"><a href="{% url 'submissions:extend_autorejection_date' submission.preprint.identifier_w_vn_nr 14 %}">14 days</a></li>
+            <li role="menuitem"><a href="{% url 'submissions:extend_autorejection_date' submission.preprint.identifier_w_vn_nr 28 %}">28 days</a></li>
+          </ul>
+        </li>
+      {% endif %}
       {% if 'Proceedings' in submission.submitted_to.name %}
-	<li id="submission-{{ submission.id }}-update-target-proceedings">
-	  {% include "submissions/admin/_submission_update_target_proceedings.html" with submission=submission %}
-	</li>
+        <li id="submission-{{ submission.id }}-update-target-proceedings">
+          {% include "submissions/admin/_submission_update_target_proceedings.html" with submission=submission %}
+        </li>
       {% endif %}
       {% if submission.preprint.has_file %}
-	<li id="submission-{{ submission.id }}-update-preprint-file">
-	  {% include "submissions/admin/_submission_update_preprint_file.html" with submission=submission %}
-	</li>
+        <li id="submission-{{ submission.id }}-update-preprint-file">
+          {% include "submissions/admin/_submission_update_preprint_file.html" with submission=submission %}
+        </li>
       {% endif %}
       {% if submission.editor_in_charge and perms.scipost.can_reassign_submissions %}
-	<li><a href="{% url 'submissions:reassign_submission' submission.preprint.identifier_w_vn_nr %}">Reassign Editor-in-charge</a></li>
+        <li><a href="{% url 'submissions:reassign_submission' submission.preprint.identifier_w_vn_nr %}">Reassign Editor-in-charge</a></li>
       {% endif %}
       {% if submission.editor_in_charge %}
         <li class="pb-2"><a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='StoE' %}">Send a communication to the Editor-in-charge</a></li>
diff --git a/scipost_django/edadmin/views/preassignment.py b/scipost_django/edadmin/views/preassignment.py
index 20fb718e4..086d3759c 100644
--- a/scipost_django/edadmin/views/preassignment.py
+++ b/scipost_django/edadmin/views/preassignment.py
@@ -6,6 +6,7 @@ from django.contrib.auth.decorators import login_required, user_passes_test
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404, render, redirect
 from django.urls import reverse
+from django.utils import timezone
 
 from colleges.permissions import is_edadmin
 from mails.utils import DirectMailUtil
@@ -103,9 +104,9 @@ def _hx_author_profile_row(request, identifier_w_vn_nr, order: int):
         "edadmin/preassignment/_hx_author_profile_row.html",
         context,
     )
-    response[
-        "HX-Trigger-After-Settle"
-    ] = f"submission-{submission.pk}-author-profiles-details-updated"
+    response["HX-Trigger-After-Settle"] = (
+        f"submission-{submission.pk}-author-profiles-details-updated"
+    )
     return response
 
 
@@ -159,19 +160,21 @@ def _hx_submission_preassignment_decision(request, identifier_w_vn_nr):
     form = SubmissionPreassignmentDecisionForm(request.POST or None)
     if form.is_valid():
         if form.cleaned_data["choice"] == "pass":
-            Submission.objects.filter(pk=submission.id).update(
-                status=Submission.SEEKING_ASSIGNMENT
+            submission.status = Submission.SEEKING_ASSIGNMENT
+            submission.autorejection_date = (
+                timezone.now() + submission.submitted_to.autoreject_period
             )
+            submission.save()
             # send authors admission passed email
             mail_util = DirectMailUtil(
                 "authors/preassignment_completed",
                 submission=submission,
                 comments_for_authors=form.cleaned_data["comments_for_authors"],
             )
+
         else:  # inadmissible, inform authors and set status to PREASSIGNMENT_FAILED
-            Submission.objects.filter(pk=submission.id).update(
-                status=Submission.PREASSIGNMENT_FAILED
-            )
+            submission.status = Submission.PREASSIGNMENT_FAILED
+            submission.save()
             # send authors admission failed email
             mail_util = DirectMailUtil(
                 "authors/preassignment_failed",
@@ -179,7 +182,6 @@ def _hx_submission_preassignment_decision(request, identifier_w_vn_nr):
                 comments_for_authors=form.cleaned_data["comments_for_authors"],
             )
         mail_util.send_mail()
-        submission.refresh_from_db()
         response = HttpResponse()
         # trigger refresh of pool listing
         response["HX-Trigger-After-Settle"] = "search-conditions-updated"
diff --git a/scipost_django/journals/migrations/0138_journal_autoreject_period.py b/scipost_django/journals/migrations/0138_journal_autoreject_period.py
new file mode 100644
index 000000000..7d4b5aa12
--- /dev/null
+++ b/scipost_django/journals/migrations/0138_journal_autoreject_period.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.15 on 2024-09-27 09:21
+
+import datetime
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("journals", "0137_alter_journal_alternative_journals"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="journal",
+            name="autoreject_period",
+            field=models.DurationField(default=datetime.timedelta(days=28)),
+        ),
+    ]
diff --git a/scipost_django/journals/models/journal.py b/scipost_django/journals/models/journal.py
index 092f29930..97138a20e 100644
--- a/scipost_django/journals/models/journal.py
+++ b/scipost_django/journals/models/journal.py
@@ -84,6 +84,7 @@ class Journal(models.Model):
     )
 
     refereeing_period = models.DurationField(default=datetime.timedelta(days=28))
+    autoreject_period = models.DurationField(default=datetime.timedelta(days=28))
 
     style = models.TextField(
         blank=True,
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
index 40351b2ff..693b7ce73 100644
--- a/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
@@ -45,6 +45,9 @@
 			<li><a class="btn btn-primary my-1 px-1 py-0" href="{% url 'submissions:accept_puboffer' sub.preprint.identifier_w_vn_nr %}">Accept offer for publication in {{ sub.editorial_decision.for_journal }} (one-click action)</a></li>
 		      {% endif %}
 		    {% endif %}
+        {% if sub.editor_in_charge is None and sub.autorejection_date is not None %}
+          <li><a href="{% url 'submissions:extend_autorejection_date' sub.preprint.identifier_w_vn_nr %}">Extend auto-rejection date (by {{ sub.submitted_to.autoreject_period.days }} days)</a></li>
+        {% endif %}
 		    <li><a href="{% url 'submissions:withdraw_manuscript' sub.preprint.identifier_w_vn_nr %}"><span class="text-danger">Withdraw</span> (leads to confirmation page)</a></li>
 		  {% endif %}
 		</ul>
diff --git a/scipost_django/submissions/forms/__init__.py b/scipost_django/submissions/forms/__init__.py
index 29f4a329d..23b6c53a8 100644
--- a/scipost_django/submissions/forms/__init__.py
+++ b/scipost_django/submissions/forms/__init__.py
@@ -226,6 +226,7 @@ class SubmissionPoolSearchForm(forms.Form):
     orderby = forms.ChoiceField(
         label="Order by",
         choices=(
+            ("autorejection_date", "Auto-rejection in"),
             ("submission_date", "Submission date"),
             ("latest_activity", "Latest activity"),
         ),
@@ -2323,6 +2324,7 @@ class WithdrawSubmissionForm(forms.Form):
                 open_for_commenting=False,
                 open_for_reporting=False,
                 status=Submission.WITHDRAWN,
+                autorejection_date=None,
                 latest_activity=timezone.now(),
             )
             self.submission.get_other_versions().update(visible_public=False)
@@ -2422,6 +2424,7 @@ class EditorialAssignmentForm(forms.ModelForm):
                     status=Submission.IN_REFEREEING,
                     editor_in_charge=self.request.user.contributor,
                     reporting_deadline=None,
+                    autorejection_date=None,
                     open_for_reporting=True,
                     open_for_commenting=True,
                     visible_public=True,
@@ -2441,6 +2444,7 @@ class EditorialAssignmentForm(forms.ModelForm):
                     status=Submission.REFEREEING_CLOSED,
                     editor_in_charge=self.request.user.contributor,
                     reporting_deadline=timezone.now(),
+                    autorejection_date=None,
                     open_for_reporting=False,
                     open_for_commenting=True,
                     visible_public=visible_public,
diff --git a/scipost_django/submissions/migrations/0163_submission_autorejection_date.py b/scipost_django/submissions/migrations/0163_submission_autorejection_date.py
new file mode 100644
index 000000000..7634ec1b1
--- /dev/null
+++ b/scipost_django/submissions/migrations/0163_submission_autorejection_date.py
@@ -0,0 +1,37 @@
+# Generated by Django 4.2.15 on 2024-09-27 09:21
+
+from django.db import migrations, models
+from django.utils import timezone
+
+
+def add_autorejection_date(apps, schema_editor):
+    Submission = apps.get_model("submissions", "Submission")
+    for submission in Submission.objects.filter(
+        status="seeking_assignment",
+        autorejection_date__isnull=True,
+    ):
+        submission.autorejection_date = (
+            timezone.now() + submission.submitted_to.autoreject_period
+            if submission.submitted_to
+            else None
+        )
+        submission.save()
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("submissions", "0162_alter_refereeinvitation_referee"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="submission",
+            name="autorejection_date",
+            field=models.DateField(
+                blank=True, null=True, verbose_name="autorejection date"
+            ),
+        ),
+        migrations.RunPython(
+            add_autorejection_date, reverse_code=migrations.RunPython.noop
+        ),
+    ]
diff --git a/scipost_django/submissions/models/submission.py b/scipost_django/submissions/models/submission.py
index dc7c6dbae..56a462701 100644
--- a/scipost_django/submissions/models/submission.py
+++ b/scipost_django/submissions/models/submission.py
@@ -430,6 +430,9 @@ class Submission(models.Model):
     completion_date = models.DateField(
         verbose_name="completion date", null=True, blank=True
     )
+    autorejection_date = models.DateField(
+        verbose_name="autorejection date", null=True, blank=True
+    )
     latest_activity = models.DateTimeField(auto_now=True)
     update_search_index = models.BooleanField(default=True)
 
@@ -740,6 +743,16 @@ class Submission(models.Model):
         """Check if Submission has fixed EICRecommendation asking for revision."""
         return self.status == self.AWAITING_RESUBMISSION
 
+    @property
+    def has_extended_autorejection_date(self):
+        """
+        Check if Submission has had its autorejection date extended, by looking for the corresponding event.
+        """
+        return self.events.filter(
+            text__icontains="autorejection date",
+            text__contains="extended",
+        ).exists()
+
     @property
     def reporting_deadline_has_passed(self):
         """Check if Submission has passed its reporting deadline."""
diff --git a/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html b/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html
index 440612606..3faf5e5fb 100644
--- a/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html
+++ b/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html
@@ -60,6 +60,14 @@
           <br />
           {{ submission.submission_date|date:'Y-m-d' }}
         </div>
+
+        {% if submission.autorejection_date is not None %}
+          <div class="col">
+            <small class="text-muted">Auto-rejection</small>
+            <br />
+            in {{ submission.autorejection_date|timeuntil }}
+          </div>
+        {% endif %}
       {% endif %}
 
       <div class="col">
diff --git a/scipost_django/submissions/urls/__init__.py b/scipost_django/submissions/urls/__init__.py
index 45a0fec75..6a6e6a4a3 100644
--- a/scipost_django/submissions/urls/__init__.py
+++ b/scipost_django/submissions/urls/__init__.py
@@ -358,6 +358,16 @@ urlpatterns = [
         views.withdraw_manuscript,
         name="withdraw_manuscript",
     ),
+    path(
+        "<identifier:identifier_w_vn_nr>/extend_autorejection_date/<int:days>",
+        views.extend_autorejection_date,
+        name="extend_autorejection_date",
+    ),
+    path(
+        "<identifier:identifier_w_vn_nr>/extend_autorejection_date",
+        views.extend_autorejection_date,
+        name="extend_autorejection_date",
+    ),
     path(
         "update_authors_assignment/<identifier:identifier_w_vn_nr>/<int:nrweeks>",
         views.update_authors_assignment,
diff --git a/scipost_django/submissions/views/__init__.py b/scipost_django/submissions/views/__init__.py
index 66e116ac0..411d33033 100644
--- a/scipost_django/submissions/views/__init__.py
+++ b/scipost_django/submissions/views/__init__.py
@@ -610,6 +610,58 @@ def withdraw_manuscript(request, identifier_w_vn_nr):
     return render(request, "submissions/withdraw_manuscript.html", context)
 
 
+@login_required
+def extend_autorejection_date(
+    request, identifier_w_vn_nr: str, days: int | None = None
+):
+    """
+    Extend the auto-rejection date of a Submission.
+
+    Accessible to EdAdmin (always) or authors of the Submission (for first extension).
+    """
+    submission = get_object_or_404(
+        Submission, preprint__identifier_w_vn_nr=identifier_w_vn_nr
+    )
+
+    if submission.autorejection_date is None:
+        raise PermissionDenied("This Submission has no autorejection date.")
+
+    is_submission_author = request.user.contributor in submission.authors.all()
+    if not (is_edadmin(request.user) or is_submission_author):
+        raise PermissionDenied("You are not allowed to extend the autorejection date.")
+
+    # Compute the new auto-rejection date if days is given, otherwise use the default
+    # For authors, ignore the days even if given
+    extension_date = (
+        submission.autorejection_date + timedelta(days=days)
+        if days is not None and is_edadmin(request.user)
+        else submission.autorejection_date + submission.submitted_to.autoreject_period
+    )
+
+    if extension_date < submission.autorejection_date:
+        raise PermissionDenied("You cannot set an earlier date.")
+
+    if is_submission_author and submission.has_extended_autorejection_date:
+        raise PermissionDenied(
+            "You are not allowed to extend the autorejection date again."
+        )
+
+    submission.autorejection_date = extension_date
+    submission.save()
+
+    submission.add_general_event(
+        f"The autorejection date has been extended to {extension_date}."
+    )
+    messages.success(request, "The autorejection date has been extended.")
+
+    return redirect(
+        reverse(
+            "submissions:submission",
+            kwargs={"identifier_w_vn_nr": identifier_w_vn_nr},
+        )
+    )
+
+
 # Marked for deprecation
 class SubmissionListView(PaginationMixin, ListView):
     """List all publicly available Submissions."""
@@ -882,9 +934,7 @@ def report_attachment(request, identifier_w_vn_nr, report_nr):
     if not report.is_vetted:
         # Only Admins and EICs are allowed to see non-vetted Report attachments.
         if (
-            not Submission.objects.in_pool_filter_for_eic(
-                request.user, historical=True, latest=False
-            )
+            not Submission.objects.in_pool_filter_for_eic(request.user)
             .filter(preprint__identifier_w_vn_nr=identifier_w_vn_nr)
             .exists()
         ):
-- 
GitLab