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