From 7c19288b05f4a397bf5d80971040c5848b13b09b Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Mon, 12 Jun 2023 19:53:44 +0200
Subject: [PATCH] add bulk assignment of officer and supervisors

---
 scipost_django/production/forms.py            |  39 +++++++
 ...ionstream_actions_bulk_assign_officer.html |  15 +++
 ...uctionstream_details_summary_contents.html |   4 +-
 .../templates/production/production_new.html  |   9 +-
 scipost_django/production/urls.py             |   5 +
 scipost_django/production/views.py            | 108 +++++++++++++++++-
 6 files changed, 175 insertions(+), 5 deletions(-)
 create mode 100644 scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html

diff --git a/scipost_django/production/forms.py b/scipost_django/production/forms.py
index 5130b699f..b61c4da06 100644
--- a/scipost_django/production/forms.py
+++ b/scipost_django/production/forms.py
@@ -12,6 +12,7 @@ from django.db.models.functions import Greatest
 from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Layout, Div, Field, Submit
 from crispy_bootstrap5.bootstrap5 import FloatingField
+from django.urls import reverse
 
 from journals.models import Journal
 from markup.widgets import TextareaWithPreview
@@ -399,3 +400,41 @@ class ProductionStreamSearchForm(forms.Form):
             )
 
         return streams
+
+
+class BulkAssignOfficersForm(forms.Form):
+    officer = forms.ModelChoiceField(
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        ),
+        required=False,
+        empty_label="Unchanged",
+    )
+    supervisor = forms.ModelChoiceField(
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Supervisor"
+        ),
+        required=False,
+        empty_label="Unchanged",
+    )
+
+    def __init__(self, *args, **kwargs):
+        self.productionstreams = kwargs.pop("productionstreams", None)
+        super().__init__(*args, **kwargs)
+        self.helper = FormHelper()
+        self.helper.form_id = "productionstreams-bulk-action-form"
+        self.helper.attrs = {
+            "hx-post": reverse(
+                "production:_hx_productionstream_actions_bulk_assign_officers"
+            ),
+            "hx-target": "#productionstream-bulk-assign-officers-container",
+            "hx-swap": "outerHTML",
+            "hx-confirm": "Are you sure you want to assign the selected production streams to the selected officers?",
+        }
+        self.helper.layout = Layout(
+            Div(
+                Div(Field("supervisor"), css_class="col-6 col-md-4 col-lg-3"),
+                Div(Field("officer"), css_class="col-6 col-md-4 col-lg-3"),
+                css_class="row mb-0",
+            ),
+        )
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html b/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html
new file mode 100644
index 000000000..8a9836231
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html
@@ -0,0 +1,15 @@
+{% load crispy_forms_tags %}
+
+<div id="productionstream-bulk-assign-officers-container">
+    <h3>Bulk Assign Officers</h3>
+    <div>{% crispy form %}</div>
+    <div class="row mb-0">
+        <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty">
+            <div class="row m-0 d-none-empty">
+                <button id="productionstreams-bulk-action-form-button"
+                        class="btn btn-primary"
+                        form="productionstreams-bulk-action-form">Assign</button>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/scipost_django/production/templates/production/_productionstream_details_summary_contents.html b/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
index 4d97f82e5..8d651ee36 100644
--- a/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
+++ b/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
@@ -4,7 +4,7 @@
             <div class="col-auto">
                 <input type="checkbox"
                        class="form-check-input checkbox-lg"
-                       name="productionstream-bulk-action"
+                       name="productionstream-bulk-action-selected"
                        value="{{ productionstream.id }}"
                        id="productionstream-{{ productionstream.id }}-checkbox"
                        form="productionstreams-bulk-action-form">
@@ -60,7 +60,7 @@
     <div id="productionstream-{{ productionstream.id }}-summary-assignees"
          class="col-md-6"
          hx-get="{% url 'production:render_stream_assignees_status' productionstream.id %}"
-         hx-trigger="intersect once, submit from:#productionstream-{{ productionstream.id }}-details target:form delay:500, click from:#productionstream-{{ productionstream.id }}-details target:.proof-action-button delay:500">
+         hx-trigger="intersect once, submit from:#productionstream-{{ productionstream.id }}-details target:form delay:500, click from:#productionstream-{{ productionstream.id }}-details target:.proof-action-button delay:500, submit from:#productionstreams-filter-details target:#productionstreams-bulk-action-form delay:500">
 
         {% comment %} Placeholder while HTMX is loading {% endcomment %}
         <div class="row mb-0 placeholder-glow">
diff --git a/scipost_django/production/templates/production/production_new.html b/scipost_django/production/templates/production/production_new.html
index 39c92d651..2b6789910 100644
--- a/scipost_django/production/templates/production/production_new.html
+++ b/scipost_django/production/templates/production/production_new.html
@@ -13,7 +13,7 @@
 
   <h1>Production Streams</h1>
 
-  <details class="card my-4">
+  <details id="productionstreams-filter-details" class="card my-4">
     <summary class="card-header fs-6 d-inline-flex align-items-center">
       Search / Filter / Bulk Actions
       <div class="d-none d-sm-inline-flex ms-auto align-items-center">
@@ -45,6 +45,13 @@
 	
         <div id="search-productionstreams-form">{% crispy search_productionstreams_form %}</div>
       </form>
+
+      {% comment %} Bulk Action buttons {% endcomment %}
+      <hr>
+
+      <div hx-get="{% url 'production:_hx_productionstream_actions_bulk_assign_officers' %}"
+           hx-trigger="load"></div>
+ 
     </div>
   </details>
 
diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py
index 6e2b98594..7b27cd8ec 100644
--- a/scipost_django/production/urls.py
+++ b/scipost_django/production/urls.py
@@ -20,6 +20,11 @@ urlpatterns = [
         production_views._hx_productionstream_list,
         name="_hx_productionstream_list",
     ),
+    path(
+        "_hx_productionstream_actions_bulk_assign_officers",
+        production_views._hx_productionstream_actions_bulk_assign_officers,
+        name="_hx_productionstream_actions_bulk_assign_officers",
+    ),
     path(
         "productionstreams/<int:productionstream_id>/",
         include(
diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py
index 7f7c7631a..888be4415 100644
--- a/scipost_django/production/views.py
+++ b/scipost_django/production/views.py
@@ -31,6 +31,7 @@ from .models import (
     ProductionEventAttachment,
 )
 from .forms import (
+    BulkAssignOfficersForm,
     ProductionStreamSearchForm,
     ProductionEventForm,
     ProductionEventForm_deprec,
@@ -54,9 +55,11 @@ from .utils import proofs_slug_to_id, ProductionUtils
 @is_production_user()
 @permission_required("scipost.can_view_production", raise_exception=True)
 def production_new(request):
-    form = ProductionStreamSearchForm(user=request.user)
+    search_productionstreams_form = ProductionStreamSearchForm(user=request.user)
+    bulk_assign_officer_form = BulkAssignOfficersForm()
     context = {
-        "search_productionstreams_form": form,
+        "search_productionstreams_form": search_productionstreams_form,
+        "bulk_assign_officer_form": bulk_assign_officer_form,
     }
     return render(request, "production/production_new.html", context)
 
@@ -1327,3 +1330,104 @@ def render_stream_events(request, stream_id):
         "production/_productionstream_events.html",
         context,
     )
+
+
+def _hx_productionstream_actions_bulk_assign_officers(request):
+    if request.POST:
+        productionstream_ids = (
+            request.POST.getlist("productionstream-bulk-action-selected") or []
+        )
+        productionstreams = ProductionStream.objects.filter(pk__in=productionstream_ids)
+
+        form = BulkAssignOfficersForm(
+            request.POST,
+            productionstreams=productionstreams,
+            auto_id="productionstreams-bulk-action-form-%s",
+        )
+
+    else:
+        form = BulkAssignOfficersForm()
+
+    if form.is_valid():
+        # Create events, update permissions, send emails for each stream
+        if officer := form.cleaned_data["officer"]:
+            for productionstream in form.productionstreams:
+                old_officer = productionstream.officer
+
+                if old_officer == officer:
+                    continue
+
+                if productionstream.status in [
+                    constants.PRODUCTION_STREAM_INITIATED,
+                    constants.PROOFS_SOURCE_REQUESTED,
+                ]:
+                    productionstream.status = constants.PROOFS_TASKED
+
+                productionstream.officer = officer
+                productionstream.save()
+
+                event = ProductionEvent(
+                    stream=productionstream,
+                    event="assignment",
+                    comments=" tasked Production Officer with proofs production:",
+                    noted_to=officer,
+                    noted_by=request.user.production_user,
+                )
+                event.save()
+
+                # Update permissions
+                assign_perm("can_work_for_stream", officer.user, productionstream)
+                remove_perm("can_work_for_stream", old_officer.user, productionstream)
+
+                # Temp fix.
+                # TODO: Implement proper email
+                ProductionUtils.load({"request": request, "stream": productionstream})
+                ProductionUtils.email_assigned_production_officer()
+
+        if supervisor := form.cleaned_data["supervisor"]:
+            for productionstream in form.productionstreams:
+                old_supervisor = productionstream.supervisor
+
+                if old_supervisor == supervisor:
+                    continue
+
+                productionstream.supervisor = supervisor
+                productionstream.save()
+
+                event = ProductionEvent(
+                    stream=productionstream,
+                    event="assignment",
+                    comments=" assigned Production Supervisor:",
+                    noted_to=supervisor,
+                    noted_by=request.user.production_user,
+                )
+                event.save()
+
+                # Update permissions
+                assign_perm("can_work_for_stream", supervisor.user, productionstream)
+                assign_perm(
+                    "can_perform_supervisory_actions", supervisor.user, productionstream
+                )
+                remove_perm(
+                    "can_work_for_stream", old_supervisor.user, productionstream
+                )
+                remove_perm(
+                    "can_perform_supervisory_actions",
+                    old_supervisor.user,
+                    productionstream,
+                )
+
+                # Temp fix.
+                # TODO: Implement proper email
+                ProductionUtils.load({"request": request, "stream": productionstream})
+                ProductionUtils.email_assigned_supervisor()
+
+    context = {
+        "form": form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_actions_bulk_assign_officer.html",
+        context,
+    )
-- 
GitLab