diff --git a/hypha/apply/activity/adapters/activity_feed.py b/hypha/apply/activity/adapters/activity_feed.py index bc1abce63e..0c0a92817a 100644 --- a/hypha/apply/activity/adapters/activity_feed.py +++ b/hypha/apply/activity/adapters/activity_feed.py @@ -54,6 +54,9 @@ class ActivityAdapter(AdapterBase): MESSAGES.UPDATE_PROJECT_TITLE: _( "updated the project title from {old_title} to {source.title}" ), + MESSAGES.UPDATE_PROJECT_CONTRACT_NUMBER: _( + "updated contract number to {source.contract_number}" + ), MESSAGES.UPDATE_PROJECT_LEAD: _("update Lead from {old_lead} to {source.lead}"), MESSAGES.SEND_FOR_APPROVAL: _("Requested approval"), MESSAGES.APPROVE_PAF: "handle_paf_assignment", diff --git a/hypha/apply/activity/adapters/slack.py b/hypha/apply/activity/adapters/slack.py index 3bdbe6afed..bef0014dc0 100644 --- a/hypha/apply/activity/adapters/slack.py +++ b/hypha/apply/activity/adapters/slack.py @@ -87,6 +87,9 @@ class SlackAdapter(AdapterBase): MESSAGES.UPDATE_PROJECT_TITLE: _( "The project title has been updated from <{link}|{old_title}> to <{link}|{source.title}> by {user}" ), + MESSAGES.UPDATE_PROJECT_CONTRACT_NUMBER: _( + "The contract number of project <{link}|{source.title}> has been updated to {source.contract_number} by {user}" + ), MESSAGES.EDIT_REVIEW: _( "{user} has edited {review.author} review for <{link}|{source.title_text_display}>" ), diff --git a/hypha/apply/activity/migrations/0095_alter_event_type.py b/hypha/apply/activity/migrations/0095_alter_event_type.py new file mode 100644 index 0000000000..8183ec4848 --- /dev/null +++ b/hypha/apply/activity/migrations/0095_alter_event_type.py @@ -0,0 +1,89 @@ +# Generated by Django 5.2.16 on 2026-07-15 09:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("activity", "0094_alter_event_type"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="type", + field=models.CharField( + choices=[ + ("UPDATE_LEAD", "updated lead"), + ("BATCH_UPDATE_LEAD", "batch updated lead"), + ("EDIT_SUBMISSION", "edited submission"), + ("APPLICANT_EDIT", "edited applicant"), + ("NEW_SUBMISSION", "submitted new submission"), + ("DRAFT_SUBMISSION", "submitted new draft submission"), + ("SCREENING", "screened"), + ("TRANSITION", "transitioned"), + ("BATCH_TRANSITION", "batch transitioned"), + ("DETERMINATION_OUTCOME", "sent determination outcome"), + ("BATCH_DETERMINATION_OUTCOME", "sent batch determination outcome"), + ("INVITED_TO_PROPOSAL", "invited to proposal"), + ("REVIEWERS_UPDATED", "updated reviewers"), + ("BATCH_REVIEWERS_UPDATED", "batch updated reviewers"), + ("READY_FOR_REVIEW", "marked ready for review"), + ("BATCH_READY_FOR_REVIEW", "marked batch ready for review"), + ("NEW_REVIEW", "added new review"), + ("COMMENT", "added comment"), + ("PROPOSAL_SUBMITTED", "submitted proposal"), + ("OPENED_SEALED", "opened sealed submission"), + ("REVIEW_OPINION", "reviewed opinion"), + ("DELETE_SUBMISSION", "deleted submission"), + ("ANONYMIZE_SUBMISSION", "anonymized submission"), + ("DELETE_REVIEW", "deleted review"), + ("DELETE_REVIEW_OPINION", "deleted review opinion"), + ("CREATED_PROJECT", "created project"), + ("UPDATE_PROJECT_LEAD", "updated project lead"), + ("UPDATE_PROJECT_TITLE", "updated project title"), + ( + "UPDATE_PROJECT_CONTRACT_NUMBER", + "updated project contract number", + ), + ("EDIT_REVIEW", "edited review"), + ("SEND_FOR_APPROVAL", "sent for approval"), + ("APPROVE_PROJECT", "approved project"), + ("ASSIGN_PAF_APPROVER", "assign project form approver"), + ("APPROVE_PAF", "approved project form"), + ("PROJECT_TRANSITION", "transitioned project"), + ("REQUEST_PROJECT_CHANGE", "requested project change"), + ("SUBMIT_CONTRACT_DOCUMENTS", "submitted contract documents"), + ("UPLOAD_DOCUMENT", "uploaded document to project"), + ("UPLOAD_CONTRACT", "uploaded contract to project"), + ("APPROVE_CONTRACT", "approved contract"), + ("CREATE_INVOICE", "created invoice for project"), + ("UPDATE_INVOICE_STATUS", "updated invoice status"), + ("APPROVE_INVOICE", "approve invoice"), + ("DELETE_INVOICE", "deleted invoice"), + ("SENT_TO_COMPLIANCE", "sent project to compliance"), + ("UPDATE_INVOICE", "updated invoice"), + ("SUBMIT_REPORT", "submitted report"), + ("DELETE_REPORT", "deleted report"), + ("SKIPPED_REPORT", "skipped report"), + ("REPORT_FREQUENCY_CHANGED", "changed report frequency"), + ("DISABLED_REPORTING", "disabled reporting"), + ("REPORT_NOTIFY", "notified report"), + ("REVIEW_REMINDER", "reminder to review"), + ("BATCH_DELETE_SUBMISSION", "batch deleted submissions"), + ("BATCH_ANONYMIZE_SUBMISSION", "batch anonymized submissions"), + ("BATCH_ARCHIVE_SUBMISSION", "batch archive submissions"), + ("BATCH_INVOICE_STATUS_UPDATE", "batch update invoice status"), + ("STAFF_ACCOUNT_CREATED", "created new account"), + ("STAFF_ACCOUNT_EDITED", "edited account"), + ("ARCHIVE_SUBMISSION", "archived submission"), + ("UNARCHIVE_SUBMISSION", "unarchived submission"), + ("REMOVE_TASK", "remove task"), + ("INVITE_COAPPLICANT", "invite co-applicant"), + ("UPDATE_AUTHOR", "updated author"), + ], + max_length=50, + verbose_name="verb", + ), + ), + ] diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py index b14da216f6..09e89ad2c2 100644 --- a/hypha/apply/activity/options.py +++ b/hypha/apply/activity/options.py @@ -38,6 +38,10 @@ class MESSAGES(TextChoices): CREATED_PROJECT = "CREATED_PROJECT", _("created project") UPDATE_PROJECT_LEAD = "UPDATE_PROJECT_LEAD", _("updated project lead") UPDATE_PROJECT_TITLE = "UPDATE_PROJECT_TITLE", _("updated project title") + UPDATE_PROJECT_CONTRACT_NUMBER = ( + "UPDATE_PROJECT_CONTRACT_NUMBER", + _("updated project contract number"), + ) EDIT_REVIEW = "EDIT_REVIEW", _("edited review") SEND_FOR_APPROVAL = "SEND_FOR_APPROVAL", _("sent for approval") APPROVE_PROJECT = "APPROVE_PROJECT", _("approved project") diff --git a/hypha/apply/projects/forms/__init__.py b/hypha/apply/projects/forms/__init__.py index c83844cdcc..f874f17084 100644 --- a/hypha/apply/projects/forms/__init__.py +++ b/hypha/apply/projects/forms/__init__.py @@ -19,6 +19,7 @@ SkipPAFApprovalProcessForm, StaffUploadContractForm, SubmitContractDocumentsForm, + UpdateProjectContractNumberForm, UpdateProjectDatesForm, UpdateProjectLeadForm, UpdateProjectTitleForm, @@ -46,6 +47,7 @@ "UploadContractDocumentForm", "StaffUploadContractForm", "UploadDocumentForm", + "UpdateProjectContractNumberForm", "UpdateProjectDatesForm", "UpdateProjectLeadForm", "CreateInvoiceForm", diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py index 917267ac8b..6dc0ccf89f 100644 --- a/hypha/apply/projects/forms/project.py +++ b/hypha/apply/projects/forms/project.py @@ -490,3 +490,12 @@ def __init__(self, *args, user=None, **kwargs): proposed_start.disabled = True proposed_start.required = False proposed_start.widget = proposed_start.hidden_widget() + + +class UpdateProjectContractNumberForm(forms.ModelForm): + class Meta: + fields = ["contract_number"] + model = Project + + def __init__(self, *args, user=None, **kwargs): + super().__init__(*args, **kwargs) diff --git a/hypha/apply/projects/migrations/0105_project_contract_number.py b/hypha/apply/projects/migrations/0105_project_contract_number.py new file mode 100644 index 0000000000..f19163ff89 --- /dev/null +++ b/hypha/apply/projects/migrations/0105_project_contract_number.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.16 on 2026-07-15 09:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("application_projects", "0104_invoice_tags"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="contract_number", + field=models.CharField( + blank=True, max_length=255, verbose_name="contract number" + ), + ), + ] diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py index bbf6075fe5..37b8e24540 100644 --- a/hypha/apply/projects/models/project.py +++ b/hypha/apply/projects/models/project.py @@ -258,6 +258,8 @@ class Project(BaseStreamForm, AccessFormData, models.Model): ) proposed_end = models.DateField(_("proposed end date"), null=True) + contract_number = models.CharField(_("contract number"), max_length=255, blank=True) + status = models.TextField( _("status"), choices=PROJECT_STATUS_CHOICES, default=DRAFT ) diff --git a/hypha/apply/projects/tables.py b/hypha/apply/projects/tables.py index 5ce1ff6f86..16605750a0 100644 --- a/hypha/apply/projects/tables.py +++ b/hypha/apply/projects/tables.py @@ -200,6 +200,7 @@ class BaseProjectsTable(tables.Table): verbose_name=_("Status"), accessor="get_status_display", order_by=("status",) ) fund = tables.Column(verbose_name=_("Fund"), accessor="submission__page") + contract_number = tables.Column(verbose_name=_("Contract nr."), default="-") reporting = tables.Column(verbose_name=_("Reporting"), accessor="pk") last_payment_request = RelativeTimeColumn() end_date = RelativeTimeColumn(verbose_name=_("End date"), accessor="proposed_end") @@ -315,6 +316,7 @@ class Meta: "status", "lead", "fund", + "contract_number", "reporting", "last_payment_request", "end_date", diff --git a/hypha/apply/projects/templates/application_projects/modals/project_contract_number_update.html b/hypha/apply/projects/templates/application_projects/modals/project_contract_number_update.html new file mode 100644 index 0000000000..8e838bc87d --- /dev/null +++ b/hypha/apply/projects/templates/application_projects/modals/project_contract_number_update.html @@ -0,0 +1,6 @@ +{% load i18n %} +{% trans "Update contract number" %} + +
+ {% include 'includes/dialog_form_base.html' with form=form value=value %} +
diff --git a/hypha/apply/projects/templates/application_projects/partials/project_information.html b/hypha/apply/projects/templates/application_projects/partials/project_information.html index 620500d218..b296e63559 100644 --- a/hypha/apply/projects/templates/application_projects/partials/project_information.html +++ b/hypha/apply/projects/templates/application_projects/partials/project_information.html @@ -27,7 +27,7 @@ hx-get="{% url 'apply:projects:project_dates_update' object.submission.id %}" hx-target="#htmx-modal" > - {% heroicon_micro "pencil-square" aria_hidden=true size=20 %} + {% heroicon_micro "pencil-square" class="text-gray-500" aria_hidden=true size=20 %} {% trans "edit dates" %} {% endif %} @@ -53,10 +53,43 @@ hx-get="{% url 'apply:projects:project_dates_update' object.submission.id %}" hx-target="#htmx-modal" > - {% heroicon_micro "pencil-square" aria_hidden=true size=20 %} + {% heroicon_micro "pencil-square" class="text-gray-500" aria_hidden=true size=20 %} {% trans "edit dates" %} {% endif %}
{{ object.proposed_end|date:"SHORT_DATE_FORMAT"|default:"-" }}
+ +
+
+ {% trans "Contract number" %} + {% if request.user.is_org_faculty %} + + {% heroicon_micro "pencil-square" class="text-gray-500" aria_hidden=true size=20 %} + {% trans "edit contract number" %} + + {% endif %} +
+
+ {{ object.contract_number|default:"-" }} + {% if object.contract_number %} + + {% endif %} +
+
diff --git a/hypha/apply/projects/urls.py b/hypha/apply/projects/urls.py index 2c545d8adf..105e816cbf 100644 --- a/hypha/apply/projects/urls.py +++ b/hypha/apply/projects/urls.py @@ -47,6 +47,7 @@ partial_project_lead, partial_project_title, partial_supporting_documents, + update_project_contract_number, update_project_dates, update_project_title, ) @@ -95,6 +96,11 @@ update_project_dates, name="project_dates_update", ), + path( + "contract-number/update/", + update_project_contract_number, + name="project_contract_number_update", + ), path( "edit/project-form", ProjectFormEditView.as_view(), name="edit_pf" ), diff --git a/hypha/apply/projects/views/__init__.py b/hypha/apply/projects/views/__init__.py index b18bb2df5e..2035e569c2 100644 --- a/hypha/apply/projects/views/__init__.py +++ b/hypha/apply/projects/views/__init__.py @@ -38,6 +38,7 @@ UploadContractDocumentView, UploadContractView, UploadDocumentView, + update_project_contract_number, update_project_dates, update_project_title, ) @@ -72,6 +73,7 @@ "UpdatePAFApproversView", "update_project_title", "update_project_dates", + "update_project_contract_number", "UploadContractDocumentView", "ChangePAFStatusView", "UploadDocumentView", diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py index f524a0cdf4..c8afa15c6e 100644 --- a/hypha/apply/projects/views/project.py +++ b/hypha/apply/projects/views/project.py @@ -84,6 +84,7 @@ SetPendingForm, SkipPAFApprovalProcessForm, SubmitContractDocumentsForm, + UpdateProjectContractNumberForm, UpdateProjectDatesForm, UpdateProjectLeadForm, UpdateProjectTitleForm, @@ -545,6 +546,48 @@ def update_project_dates(request, pk): return render(request, template_name, ctx) +@login_required +def update_project_contract_number(request, pk): + if not (request.user.is_org_faculty): + raise PermissionDenied + + project = get_object_or_404(Project, submission__id=pk) + template_name = "application_projects/modals/project_contract_number_update.html" + + form = UpdateProjectContractNumberForm(instance=project) + + if request.method == "POST": + form = UpdateProjectContractNumberForm(request.POST, instance=project) + + if form.is_valid(): + form.save() + messenger( + MESSAGES.UPDATE_PROJECT_CONTRACT_NUMBER, + request=request, + user=request.user, + source=project, + ) + + return HttpResponse( + status=204, + headers={ + "HX-Trigger": json.dumps( + { + "informationUpdated": None, + "showMessage": _("Contract number has been updated"), + } + ), + }, + ) + + ctx = { + "form": form, + "value": _("Update"), + "object": project, + } + return render(request, template_name, ctx) + + # CONTRACTS diff --git a/hypha/settings/base.py b/hypha/settings/base.py index cb65c34a15..add3895314 100644 --- a/hypha/settings/base.py +++ b/hypha/settings/base.py @@ -179,7 +179,9 @@ # Columns/filters to exclude from the projects table. # Each value hides both the table column and its matching filter (where one exists). # Possible values are: status, lead, fund, reporting, last_payment_request, end_date -PROJECTS_TABLE_EXCLUDED_FIELDS = env.list("PROJECTS_TABLE_EXCLUDED_FIELDS", []) +PROJECTS_TABLE_EXCLUDED_FIELDS = env.list( + "PROJECTS_TABLE_EXCLUDED_FIELDS", ["contract_number"] +) # No of co-applicants can be added to a submission (default 10) SUBMISSIONS_COAPPLICANT_INVITES_LIMIT = env.int(