From 08622db77fdface11b67d4e8da9b6ff609b98a3a Mon Sep 17 00:00:00 2001
From: Sandeep Chauhan <sandeepsajan0@gmail.com>
Date: Tue, 28 May 2024 22:37:30 +0530
Subject: [PATCH] Add Signed and Approved option to contract upload for staff
 and Contracting team (#3938)

Fixes #3923
---
 hypha/apply/projects/forms/project.py         |  5 +-
 .../0084_contract_signed_and_approved.py      | 22 +++++++
 hypha/apply/projects/models/project.py        |  8 ++-
 .../includes/contracting_documents.html       |  2 +-
 hypha/apply/projects/views/project.py         | 59 ++++++++++++++++---
 5 files changed, 85 insertions(+), 11 deletions(-)
 create mode 100644 hypha/apply/projects/migrations/0084_contract_signed_and_approved.py

diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py
index aa3a067ee..cf57a18aa 100644
--- a/hypha/apply/projects/forms/project.py
+++ b/hypha/apply/projects/forms/project.py
@@ -371,9 +371,12 @@ class SkipPAFApprovalProcessForm(forms.ModelForm):
 
 class UploadContractForm(FileFormMixin, forms.ModelForm):
     file = SingleFileField(label=_("Contract"), required=True)
+    signed_and_approved = forms.BooleanField(
+        label=_("Signed and approved"), required=False
+    )
 
     class Meta:
-        fields = ["file"]
+        fields = ["file", "signed_and_approved"]
         model = Contract
 
     def save(self, commit=True):
diff --git a/hypha/apply/projects/migrations/0084_contract_signed_and_approved.py b/hypha/apply/projects/migrations/0084_contract_signed_and_approved.py
new file mode 100644
index 000000000..2cf409d8f
--- /dev/null
+++ b/hypha/apply/projects/migrations/0084_contract_signed_and_approved.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.2.11 on 2024-05-23 03:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        (
+            "application_projects",
+            "0083_rename_projectapprovalform_projectform_and_more",
+        ),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="contract",
+            name="signed_and_approved",
+            field=models.BooleanField(
+                default=False, verbose_name="Signed and approved"
+            ),
+        ),
+    ]
diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py
index 6ffa87fcd..e632dcb31 100644
--- a/hypha/apply/projects/models/project.py
+++ b/hypha/apply/projects/models/project.py
@@ -10,7 +10,7 @@ from django.contrib.contenttypes.fields import GenericRelation
 from django.core.exceptions import ValidationError
 from django.core.validators import MinValueValidator
 from django.db import models
-from django.db.models import Count, F, Max, OuterRef, Subquery, Sum, Value
+from django.db.models import Count, F, Max, OuterRef, Q, Subquery, Sum, Value
 from django.db.models.functions import Cast, Coalesce
 from django.db.models.signals import post_delete
 from django.dispatch.dispatcher import receiver
@@ -580,7 +580,9 @@ class PAFApprovals(models.Model):
 
 class ContractQuerySet(models.QuerySet):
     def approved(self):
-        return self.filter(signed_by_applicant=True, approver__isnull=False)
+        return self.filter(
+            Q(signed_by_applicant=True) | Q(signed_and_approved=True)
+        ).filter(approver__isnull=False)
 
 
 class Contract(models.Model):
@@ -596,6 +598,8 @@ class Contract(models.Model):
 
     file = models.FileField(upload_to=contract_path, storage=PrivateStorage())
 
+    signed_and_approved = models.BooleanField("Signed and approved", default=False)
+
     signed_by_applicant = models.BooleanField("Counter Signed?", default=False)
     uploaded_by_contractor_at = models.DateTimeField(null=True)
     uploaded_by_applicant_at = models.DateTimeField(null=True)
diff --git a/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html b/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html
index 0731c0c91..2b9d1e357 100644
--- a/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html
+++ b/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html
@@ -219,7 +219,7 @@
             {% trans "Upload" as upload %}
         {% else %}
             <h4 class="modal__project-header-bar">{% trans "Upload Signed Contract" %}</h4>
-            <p><i><b>{% trans "The signed contract will be sent to Applicant once you submit." %}</b></i></p>
+            <p><i><b>{% trans "The signed contract will be sent to Applicant once you submit. But if you select Signed and approved then it will directly be approved and project will move to Invoicing and Reporting." %}</b></i></p>
             <br>
             {% trans "Submit" as upload %}
         {% endif %}
diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py
index a7647c322..d4f90b5af 100644
--- a/hypha/apply/projects/views/project.py
+++ b/hypha/apply/projects/views/project.py
@@ -555,6 +555,12 @@ class UploadContractView(DelegatedViewMixin, CreateView):
         kwargs.pop("user")
         return kwargs
 
+    def get_form(self, *args, **kwargs):
+        form = super().get_form(*args, **kwargs)
+        if self.request.user.is_applicant:
+            form.fields.pop("signed_and_approved")
+        return form
+
     def form_valid(self, form):
         project = self.kwargs["object"]
 
@@ -571,8 +577,7 @@ class UploadContractView(DelegatedViewMixin, CreateView):
                 _("Countersigned contract uploaded"),
                 extra_tags=PROJECT_ACTION_MESSAGE_TAG,
             )
-        elif self.request.user.is_contracting:
-            # :todo: update same date when staff uploads the contract(with STAFF_UPLOAD_CONTRACT setting)
+        elif self.request.user.is_contracting or self.request.user.is_apply_staff:
             form.instance.uploaded_by_contractor_at = timezone.now()
             messages.success(
                 self.request,
@@ -582,13 +587,25 @@ class UploadContractView(DelegatedViewMixin, CreateView):
 
         response = super().form_valid(form)
 
-        if self.request.user != project.user:
+        contract_signed_and_approved = form.cleaned_data.get("signed_and_approved")
+        if contract_signed_and_approved:
+            form.instance.approver = self.request.user
+            form.instance.approved_at = timezone.now()
+            form.instance.signed_and_approved = contract_signed_and_approved
+            form.instance.save(
+                update_fields=["approver", "approved_at", "signed_and_approved"]
+            )
+
+            project.status = INVOICING_AND_REPORTING
+            project.save(update_fields=["status"])
+            old_stage = CONTRACTING
+
             messenger(
-                MESSAGES.UPLOAD_CONTRACT,
+                MESSAGES.PROJECT_TRANSITION,
                 request=self.request,
                 user=self.request.user,
                 source=project,
-                related=form.instance,
+                related=old_stage,
             )
             # remove Project waiting contract task for contracting/staff group
             if settings.STAFF_UPLOAD_CONTRACT:
@@ -603,12 +620,40 @@ class UploadContractView(DelegatedViewMixin, CreateView):
                     user_group=Group.objects.filter(name=CONTRACTING_GROUP_NAME),
                     related_obj=project,
                 )
-            # add Project waiting contract document task for applicant
+            # add Project waiting invoice task for applicant
             add_task_to_user(
-                code=PROJECT_WAITING_CONTRACT_DOCUMENT,
+                code=PROJECT_WAITING_INVOICE,
                 user=project.user,
                 related_obj=project,
             )
+        else:
+            if self.request.user != project.user:
+                messenger(
+                    MESSAGES.UPLOAD_CONTRACT,
+                    request=self.request,
+                    user=self.request.user,
+                    source=project,
+                    related=form.instance,
+                )
+                # remove Project waiting contract task for contracting/staff group
+                if settings.STAFF_UPLOAD_CONTRACT:
+                    remove_tasks_for_user_group(
+                        code=PROJECT_WAITING_CONTRACT,
+                        user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                        related_obj=project,
+                    )
+                else:
+                    remove_tasks_for_user_group(
+                        code=PROJECT_WAITING_CONTRACT,
+                        user_group=Group.objects.filter(name=CONTRACTING_GROUP_NAME),
+                        related_obj=project,
+                    )
+                # add Project waiting contract document task for applicant
+                add_task_to_user(
+                    code=PROJECT_WAITING_CONTRACT_DOCUMENT,
+                    user=project.user,
+                    related_obj=project,
+                )
 
         return response
 
-- 
GitLab