diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py index aa3a067ee8d7959ddf066d61976b795f905edda7..cf57a18aa716665101ab5b25f67361c8228f29ee 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 0000000000000000000000000000000000000000..2cf409d8ff3ffb021901f7f39b5dba0caefe72fd --- /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 6ffa87fcdd8ceafd919245ab953c88cfaa9e8374..e632dcb31427e276b98c2ddbec1096718bed56a1 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 0731c0c913f178d230d79dabf06ad278eb69a448..2b9d1e357e9b65c203b07d3882e392b44e4499da 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 a7647c32217d2c1c0999c25fcb263f6c967880e4..d4f90b5afdb44181891858a470d70c1fa4bd8eed 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