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 %}
+