diff --git a/hypha/apply/activity/messaging.py b/hypha/apply/activity/messaging.py
index 69559237e050c48a913d3f2d53b237e5237a4630..a40904b4261235d0bd87580e52d8d45eefe9ee78 100644
--- a/hypha/apply/activity/messaging.py
+++ b/hypha/apply/activity/messaging.py
@@ -62,9 +62,13 @@ neat_related = {
     MESSAGES.APPROVE_CONTRACT: 'contract',
     MESSAGES.UPLOAD_CONTRACT: 'contract',
     MESSAGES.REQUEST_PAYMENT: 'payment_request',
+    MESSAGES.CREATE_INVOICE: 'create_invoice',
     MESSAGES.UPDATE_PAYMENT_REQUEST_STATUS: 'payment_request',
+    MESSAGES.UPDATE_INVOICE_STATUS: 'invoice',
     MESSAGES.DELETE_PAYMENT_REQUEST: 'payment_request',
+    MESSAGES.DELETE_INVOICE: 'invoice',
     MESSAGES.UPDATE_PAYMENT_REQUEST: 'payment_request',
+    MESSAGES.UPDATE_INVOICE: 'invoice',
     MESSAGES.SUBMIT_REPORT: 'report',
     MESSAGES.SKIPPED_REPORT: 'report',
     MESSAGES.REPORT_FREQUENCY_CHANGED: 'config',
@@ -245,7 +249,9 @@ class ActivityAdapter(AdapterBase):
         MESSAGES.UPLOAD_CONTRACT: _('Uploaded a {contract.state} contract'),
         MESSAGES.APPROVE_CONTRACT: _('Approved contract'),
         MESSAGES.UPDATE_PAYMENT_REQUEST_STATUS: _('Updated Payment Request status to: {payment_request.status_display}'),
+        MESSAGES.UPDATE_INVOICE_STATUS: _('Updated Invoice status to: {invoice.status_display}'),
         MESSAGES.REQUEST_PAYMENT: _('Payment Request submitted'),
+        MESSAGES.CREATE_INVOICE: _('Invoice created'),
         MESSAGES.SUBMIT_REPORT: _('Submitted a report'),
         MESSAGES.SKIPPED_REPORT: 'handle_skipped_report',
         MESSAGES.REPORT_FREQUENCY_CHANGED: 'handle_report_frequency',
@@ -436,9 +442,13 @@ class SlackAdapter(AdapterBase):
         MESSAGES.UPLOAD_CONTRACT: _('{user} has uploaded a contract for <{link}|{source.title}>.'),
         MESSAGES.APPROVE_CONTRACT: _('{user} has approved contract for <{link}|{source.title}>.'),
         MESSAGES.REQUEST_PAYMENT: _('{user} has requested payment for <{link}|{source.title}>.'),
+        MESSAGES.CREATE_INVOICE: _('{user} has created invoice for <{link}|{source.title}>.'),
         MESSAGES.UPDATE_PAYMENT_REQUEST_STATUS: _('{user} has changed the status of <{link_related}|payment request> on <{link}|{source.title}> to {payment_request.status_display}.'),
+        MESSAGES.UPDATE_INVOICE_STATUS: _('{user} has changed the status of <{link_related}|invoice> on <{link}|{source.title}> to {invoice.status_display}.'),
         MESSAGES.DELETE_PAYMENT_REQUEST: _('{user} has deleted payment request from <{link}|{source.title}>.'),
+        MESSAGES.DELETE_INVOICE: _('{user} has deleted invoice from <{link}|{source.title}>.'),
         MESSAGES.UPDATE_PAYMENT_REQUEST: _('{user} has updated payment request for <{link}|{source.title}>.'),
+        MESSAGES.UPDATE_INVOICE: _('{user} has updated invoice for <{link}|{source.title}>.'),
         MESSAGES.SUBMIT_REPORT: _('{user} has submitted a report for <{link}|{source.title}>.'),
         MESSAGES.BATCH_DELETE_SUBMISSION: 'handle_batch_delete_submission'
     }
@@ -712,7 +722,9 @@ class EmailAdapter(AdapterBase):
         MESSAGES.UPDATED_VENDOR: 'handle_vendor_updated',
         MESSAGES.SENT_TO_COMPLIANCE: 'messages/email/sent_to_compliance.html',
         MESSAGES.UPDATE_PAYMENT_REQUEST: 'messages/email/payment_request_updated.html',
+        MESSAGES.UPDATE_INVOICE: 'handle_invoice_updated',
         MESSAGES.UPDATE_PAYMENT_REQUEST_STATUS: 'handle_payment_status_updated',
+        MESSAGES.UPDATE_INVOICE_STATUS: 'handle_invoice_status_updated',
         MESSAGES.SUBMIT_REPORT: 'messages/email/report_submitted.html',
         MESSAGES.SKIPPED_REPORT: 'messages/email/report_skipped.html',
         MESSAGES.REPORT_FREQUENCY_CHANGED: 'messages/email/report_frequency.html',
@@ -770,6 +782,19 @@ class EmailAdapter(AdapterBase):
             **kwargs,
         )
 
+    def handle_invoice_status_updated(self, related, **kwargs):
+        return self.render_message(
+            'messages/email/invoice_status_updated.html',
+            has_changes_requested=related.has_changes_requested,
+            **kwargs,
+        )
+
+    def handle_invoice_updated(self, **kwargs):
+        return self.render_message(
+            'messages/email/invoice_updated.html',
+            **kwargs,
+        )
+
     def handle_project_created(self, source, **kwargs):
         from hypha.apply.projects.models import ProjectSettings
         request = kwargs.get('request')
@@ -857,7 +882,7 @@ class EmailAdapter(AdapterBase):
 
             return [project_settings.compliance_email]
 
-        if message_type in {MESSAGES.SUBMIT_REPORT, MESSAGES.UPDATE_PAYMENT_REQUEST}:
+        if message_type in {MESSAGES.SUBMIT_REPORT, MESSAGES.UPDATE_PAYMENT_REQUEST, MESSAGES.UPDATE_INVOICE}:
             # Don't tell the user if they did these activities
             if user.is_applicant:
                 return []
diff --git a/hypha/apply/activity/migrations/0057_add_invoices.py b/hypha/apply/activity/migrations/0057_add_invoices.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae053359206857af397190456b472e1d381ccfc4
--- /dev/null
+++ b/hypha/apply/activity/migrations/0057_add_invoices.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.24 on 2021-07-07 00:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('activity', '0056_add_updated_vendor'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='event',
+            name='type',
+            field=models.CharField(choices=[('UPDATE_LEAD', 'Update Lead'), ('BATCH_UPDATE_LEAD', 'Batch Update Lead'), ('EDIT', 'Edit'), ('APPLICANT_EDIT', 'Applicant Edit'), ('NEW_SUBMISSION', 'New Submission'), ('SCREENING', 'Screening'), ('TRANSITION', 'Transition'), ('BATCH_TRANSITION', 'Batch Transition'), ('DETERMINATION_OUTCOME', 'Determination Outcome'), ('BATCH_DETERMINATION_OUTCOME', 'Batch Determination Outcome'), ('INVITED_TO_PROPOSAL', 'Invited To Proposal'), ('REVIEWERS_UPDATED', 'Reviewers Updated'), ('BATCH_REVIEWERS_UPDATED', 'Batch Reviewers Updated'), ('PARTNERS_UPDATED', 'Partners Updated'), ('PARTNERS_UPDATED_PARTNER', 'Partners Updated Partner'), ('READY_FOR_REVIEW', 'Ready For Review'), ('BATCH_READY_FOR_REVIEW', 'Batch Ready For Review'), ('NEW_REVIEW', 'New Review'), ('COMMENT', 'Comment'), ('PROPOSAL_SUBMITTED', 'Proposal Submitted'), ('OPENED_SEALED', 'Opened Sealed Submission'), ('REVIEW_OPINION', 'Review Opinion'), ('DELETE_SUBMISSION', 'Delete Submission'), ('DELETE_REVIEW', 'Delete Review'), ('CREATED_PROJECT', 'Created Project'), ('UPDATED_VENDOR', 'Updated Vendor'), ('UPDATE_PROJECT_LEAD', 'Update Project Lead'), ('EDIT_REVIEW', 'Edit Review'), ('SEND_FOR_APPROVAL', 'Send for Approval'), ('APPROVE_PROJECT', 'Project was Approved'), ('PROJECT_TRANSITION', 'Project was Transitioned'), ('REQUEST_PROJECT_CHANGE', 'Project change requested'), ('UPLOAD_DOCUMENT', 'Document was Uploaded to Project'), ('REMOVE_DOCUMENT', 'Document was Removed from Project'), ('UPLOAD_CONTRACT', 'Contract was Uploaded to Project'), ('APPROVE_CONTRACT', 'Contract was Approved'), ('REQUEST_PAYMENT', 'Payment was requested for Project'), ('CREATE_INVOICE', 'Invoice was created for Project'), ('UPDATE_PAYMENT_REQUEST_STATUS', 'Updated Payment Request Status'), ('DELETE_PAYMENT_REQUEST', 'Delete Payment Request'), ('SENT_TO_COMPLIANCE', 'Project was sent to Compliance'), ('UPDATE_PAYMENT_REQUEST', 'Updated Payment Request'), ('SUBMIT_REPORT', 'Submit Report'), ('SKIPPED_REPORT', 'Skipped Report'), ('REPORT_FREQUENCY_CHANGED', 'Report Frequency Changed'), ('REPORT_NOTIFY', 'Report Notify'), ('CREATE_REMINDER', 'Reminder Created'), ('DELETE_REMINDER', 'Reminder Deleted'), ('REVIEW_REMINDER', 'Reminde to Review'), ('BATCH_DELETE_SUBMISSION', 'Delete Batch Submissions')], max_length=50),
+        ),
+    ]
diff --git a/hypha/apply/activity/migrations/0058_add_project_invoicing.py b/hypha/apply/activity/migrations/0058_add_project_invoicing.py
new file mode 100644
index 0000000000000000000000000000000000000000..d244da829a7c1867bf38e8c95e6f40619ff48fe4
--- /dev/null
+++ b/hypha/apply/activity/migrations/0058_add_project_invoicing.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.24 on 2021-07-08 07:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('activity', '0057_add_invoices'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='event',
+            name='type',
+            field=models.CharField(choices=[('UPDATE_LEAD', 'Update Lead'), ('BATCH_UPDATE_LEAD', 'Batch Update Lead'), ('EDIT', 'Edit'), ('APPLICANT_EDIT', 'Applicant Edit'), ('NEW_SUBMISSION', 'New Submission'), ('SCREENING', 'Screening'), ('TRANSITION', 'Transition'), ('BATCH_TRANSITION', 'Batch Transition'), ('DETERMINATION_OUTCOME', 'Determination Outcome'), ('BATCH_DETERMINATION_OUTCOME', 'Batch Determination Outcome'), ('INVITED_TO_PROPOSAL', 'Invited To Proposal'), ('REVIEWERS_UPDATED', 'Reviewers Updated'), ('BATCH_REVIEWERS_UPDATED', 'Batch Reviewers Updated'), ('PARTNERS_UPDATED', 'Partners Updated'), ('PARTNERS_UPDATED_PARTNER', 'Partners Updated Partner'), ('READY_FOR_REVIEW', 'Ready For Review'), ('BATCH_READY_FOR_REVIEW', 'Batch Ready For Review'), ('NEW_REVIEW', 'New Review'), ('COMMENT', 'Comment'), ('PROPOSAL_SUBMITTED', 'Proposal Submitted'), ('OPENED_SEALED', 'Opened Sealed Submission'), ('REVIEW_OPINION', 'Review Opinion'), ('DELETE_SUBMISSION', 'Delete Submission'), ('DELETE_REVIEW', 'Delete Review'), ('CREATED_PROJECT', 'Created Project'), ('UPDATED_VENDOR', 'Updated Vendor'), ('UPDATE_PROJECT_LEAD', 'Update Project Lead'), ('EDIT_REVIEW', 'Edit Review'), ('SEND_FOR_APPROVAL', 'Send for Approval'), ('APPROVE_PROJECT', 'Project was Approved'), ('PROJECT_TRANSITION', 'Project was Transitioned'), ('REQUEST_PROJECT_CHANGE', 'Project change requested'), ('UPLOAD_DOCUMENT', 'Document was Uploaded to Project'), ('REMOVE_DOCUMENT', 'Document was Removed from Project'), ('UPLOAD_CONTRACT', 'Contract was Uploaded to Project'), ('APPROVE_CONTRACT', 'Contract was Approved'), ('REQUEST_PAYMENT', 'Payment was requested for Project'), ('CREATE_INVOICE', 'Invoice was created for Project'), ('UPDATE_PAYMENT_REQUEST_STATUS', 'Updated Payment Request Status'), ('UPDATE_INVOICE_STATUS', 'Updated Invoice Status'), ('DELETE_PAYMENT_REQUEST', 'Delete Payment Request'), ('DELETE_INVOICE', 'Delete Invoice'), ('SENT_TO_COMPLIANCE', 'Project was sent to Compliance'), ('UPDATE_PAYMENT_REQUEST', 'Updated Payment Request'), ('UPDATE_INVOICE', 'Updated Invoice'), ('SUBMIT_REPORT', 'Submit Report'), ('SKIPPED_REPORT', 'Skipped Report'), ('REPORT_FREQUENCY_CHANGED', 'Report Frequency Changed'), ('REPORT_NOTIFY', 'Report Notify'), ('CREATE_REMINDER', 'Reminder Created'), ('DELETE_REMINDER', 'Reminder Deleted'), ('REVIEW_REMINDER', 'Reminde to Review'), ('BATCH_DELETE_SUBMISSION', 'Delete Batch Submissions')], max_length=50),
+        ),
+    ]
diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py
index 75cf974f80b3715fb899ec07df90c4dda7f00b7f..41f4146fead0eed6264a0cc3f79fe0a67f199833 100644
--- a/hypha/apply/activity/options.py
+++ b/hypha/apply/activity/options.py
@@ -39,10 +39,14 @@ class MESSAGES(Enum):
     UPLOAD_CONTRACT = 'Contract was Uploaded to Project'
     APPROVE_CONTRACT = 'Contract was Approved'
     REQUEST_PAYMENT = 'Payment was requested for Project'
+    CREATE_INVOICE = 'Invoice was created for Project'
     UPDATE_PAYMENT_REQUEST_STATUS = 'Updated Payment Request Status'
+    UPDATE_INVOICE_STATUS = 'Updated Invoice Status'
     DELETE_PAYMENT_REQUEST = 'Delete Payment Request'
+    DELETE_INVOICE = 'Delete Invoice'
     SENT_TO_COMPLIANCE = 'Project was sent to Compliance'
     UPDATE_PAYMENT_REQUEST = 'Updated Payment Request'
+    UPDATE_INVOICE = 'Updated Invoice'
     SUBMIT_REPORT = 'Submit Report'
     SKIPPED_REPORT = 'Skipped Report'
     REPORT_FREQUENCY_CHANGED = 'Report Frequency Changed'
diff --git a/hypha/apply/activity/templates/messages/email/invoice_status_updated.html b/hypha/apply/activity/templates/messages/email/invoice_status_updated.html
new file mode 100644
index 0000000000000000000000000000000000000000..1f4704643ec72620b35e0e8130cc540b1537d0e3
--- /dev/null
+++ b/hypha/apply/activity/templates/messages/email/invoice_status_updated.html
@@ -0,0 +1,16 @@
+{% extends "messages/email/applicant_base.html" %}
+
+{% load i18n %}
+{% block content %}
+{% blocktrans %}An {{ ORG_SHORT_NAME }} staff member has updated your invoice for {{ source.title }} for period {{ invoice.date_from }} to {{ invoice.date_to }}.{% endblocktrans %}
+{% blocktrans %}It is now {{ invoice.get_status_display }}.{% endblocktrans %}
+
+{% if has_changes_requested %}
+{% trans "The staff member left this comment" %}:
+
+{{ payment_request.comment }}
+{% endif %}
+
+{% trans "Title" %}: {{ source.title }}
+{% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }}
+{% endblock %}
diff --git a/hypha/apply/activity/templates/messages/email/invoice_updated.html b/hypha/apply/activity/templates/messages/email/invoice_updated.html
new file mode 100644
index 0000000000000000000000000000000000000000..fe447dcf480b0fa8c67a0cbeeeeb282619f9bcd5
--- /dev/null
+++ b/hypha/apply/activity/templates/messages/email/invoice_updated.html
@@ -0,0 +1,11 @@
+{% extends "messages/email/applicant_base.html" %}
+
+{% load i18n %}
+{% block content %}
+
+{% blocktrans %}An {{ ORG_SHORT_NAME }} staff member has updated your invoice for {{ source.title }} for period {{ invoice.date_from }} to {{ invoice.date_to }}.{% endblocktrans %}
+{% blocktrans %}It is now {{ invoice.get_status_display }}.{% endblocktrans %}
+
+{% trans "Title" %}: {{ source.title }}
+{% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }}
+{% endblock %}
diff --git a/hypha/apply/projects/filters.py b/hypha/apply/projects/filters.py
index 83525d361f596d8b5d96caf6914a25bf9cd0ba63..403af6f0943d4146cfd66805c51b70d564560fed 100644
--- a/hypha/apply/projects/filters.py
+++ b/hypha/apply/projects/filters.py
@@ -11,7 +11,7 @@ from hypha.apply.funds.tables import (
     get_used_funds,
 )
 
-from .models.payment import REQUEST_STATUS_CHOICES, PaymentRequest
+from .models.payment import REQUEST_STATUS_CHOICES, Invoice, PaymentRequest
 from .models.project import CLOSING, IN_PROGRESS, PROJECT_STATUS_CHOICES, Project
 from .models.report import Report
 
@@ -32,6 +32,16 @@ class PaymentRequestListFilter(filters.FilterSet):
         model = PaymentRequest
 
 
+class InvoiceListFilter(filters.FilterSet):
+    fund = Select2ModelMultipleChoiceFilter(label=_('Funds'), queryset=get_used_funds, field_name='project__submission__page')
+    status = Select2MultipleChoiceFilter(label=_('Status'), choices=REQUEST_STATUS_CHOICES)
+    lead = Select2ModelMultipleChoiceFilter(label=_('Lead'), queryset=get_project_leads, field_name='project__lead')
+
+    class Meta:
+        fields = ['lead', 'fund', 'status']
+        model = Invoice
+
+
 class ProjectListFilter(filters.FilterSet):
     REPORTING_CHOICES = (
         (0, 'Up to date'),
diff --git a/hypha/apply/projects/forms/__init__.py b/hypha/apply/projects/forms/__init__.py
index 29c59a1b239b0c83a03cddcf47b68c74c8b3b858..ccc00a50980957c137a7d38d7963b7d4d8fb3690 100644
--- a/hypha/apply/projects/forms/__init__.py
+++ b/hypha/apply/projects/forms/__init__.py
@@ -1,6 +1,9 @@
 from .payment import (
+    ChangeInvoiceStatusForm,
     ChangePaymentRequestStatusForm,
+    CreateInvoiceForm,
     CreatePaymentRequestForm,
+    EditInvoiceForm,
     EditPaymentRequestForm,
     SelectDocumentForm,
 )
@@ -53,4 +56,7 @@ __all__ = [
     'CreateVendorFormStep4',
     'CreateVendorFormStep5',
     'CreateVendorFormStep6',
+    'CreateInvoiceForm',
+    'ChangeInvoiceStatusForm',
+    'EditInvoiceForm',
 ]
diff --git a/hypha/apply/projects/forms/payment.py b/hypha/apply/projects/forms/payment.py
index 4af94f103beb80bbf2cc44e0bf3b4ce4fe18a79e..3c6c8c8c9ce311e8f9852240bbb61d973b78d850 100644
--- a/hypha/apply/projects/forms/payment.py
+++ b/hypha/apply/projects/forms/payment.py
@@ -1,11 +1,13 @@
 import functools
+import json
 
 from django import forms
 from django.core.files.base import ContentFile
 from django.db import transaction
+from django.db.models.fields.files import FieldFile
 from django_file_form.forms import FileFormMixin
 
-from hypha.apply.stream_forms.fields import MultiFileField
+from hypha.apply.stream_forms.fields import MultiFileField, SingleFileField
 
 from ..models.payment import (
     CHANGES_REQUESTED,
@@ -14,8 +16,10 @@ from ..models.payment import (
     REQUEST_STATUS_CHOICES,
     SUBMITTED,
     UNDER_REVIEW,
+    Invoice,
     PaymentReceipt,
     PaymentRequest,
+    SupportingDocument,
 )
 from ..models.project import PacketFile
 
@@ -61,6 +65,40 @@ class ChangePaymentRequestStatusForm(forms.ModelForm):
         return cleaned_data
 
 
+class ChangeInvoiceStatusForm(forms.ModelForm):
+    name_prefix = 'change_invoice_status_form'
+
+    class Meta:
+        fields = ['status', 'comment', 'paid_value']
+        model = Invoice
+
+    def __init__(self, instance, *args, **kwargs):
+        super().__init__(instance=instance, *args, **kwargs)
+
+        self.initial['paid_value'] = self.instance.amount
+
+        status_field = self.fields['status']
+
+        possible_status_transitions_lut = {
+            CHANGES_REQUESTED: filter_request_choices([DECLINED]),
+            SUBMITTED: filter_request_choices([CHANGES_REQUESTED, UNDER_REVIEW, DECLINED]),
+            UNDER_REVIEW: filter_request_choices([PAID]),
+        }
+        status_field.choices = possible_status_transitions_lut.get(instance.status, [])
+
+        if instance.status != UNDER_REVIEW:
+            del self.fields['paid_value']
+
+    def clean(self):
+        cleaned_data = super().clean()
+        status = cleaned_data['status']
+        paid_value = cleaned_data.get('paid_value')
+
+        if paid_value and status != PAID:
+            self.add_error('paid_value', 'You can only set a value when moving to the Paid status.')
+        return cleaned_data
+
+
 class PaymentRequestBaseForm(forms.ModelForm):
     class Meta:
         fields = ['requested_value', 'invoice', 'date_from', 'date_to']
@@ -104,6 +142,53 @@ class CreatePaymentRequestForm(FileFormMixin, PaymentRequestBaseForm):
         return request
 
 
+class InvoiceBaseForm(forms.ModelForm):
+    class Meta:
+        fields = ['date_from', 'date_to', 'amount', 'document', 'message_for_pm']
+        model = Invoice
+        widgets = {
+            'date_from': forms.DateInput,
+            'date_to': forms.DateInput,
+        }
+
+    def __init__(self, user=None, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['amount'].widget.attrs['min'] = 0
+
+    def clean(self):
+        cleaned_data = super().clean()
+        date_from = cleaned_data['date_from']
+        date_to = cleaned_data['date_to']
+
+        if date_from > date_to:
+            self.add_error('date_from', 'Date From must be before Date To')
+
+        return cleaned_data
+
+
+class CreateInvoiceForm(FileFormMixin, InvoiceBaseForm):
+    document = SingleFileField(label='Invoice File', required=True)
+    supporting_documents = MultiFileField(
+        required=False,
+        help_text='Files that are related to the invoice. '
+                  'They could be xls, microsoft office documents, open office documents, pdfs, txt files.'
+    )
+
+    field_order = ['date_from', 'date_to', 'amount', 'document', 'supporting_documents', 'message_for_pm']
+
+    def save(self, commit=True):
+        invoice = super().save(commit=commit)
+
+        supporting_documents = self.cleaned_data['supporting_documents'] or []
+
+        SupportingDocument.objects.bulk_create(
+            SupportingDocument(invoice=invoice, document=document)
+            for document in supporting_documents
+        )
+
+        return invoice
+
+
 class EditPaymentRequestForm(FileFormMixin, PaymentRequestBaseForm):
     receipt_list = forms.ModelMultipleChoiceField(
         widget=forms.CheckboxSelectMultiple(attrs={'class': 'delete'}),
@@ -137,6 +222,32 @@ class EditPaymentRequestForm(FileFormMixin, PaymentRequestBaseForm):
         return request
 
 
+class EditInvoiceForm(FileFormMixin, InvoiceBaseForm):
+    document = SingleFileField(label='Invoice File', required=True)
+    supporting_documents = MultiFileField(required=False)
+
+    field_order = ['date_from', 'date_to', 'amount', 'document', 'supporting_documents', 'message_for_pm']
+
+    @transaction.atomic
+    def save(self, commit=True):
+        invoice = super().save(commit=commit)
+        not_deleted_original_filenames = [
+            file['name'] for file in json.loads(self.cleaned_data['supporting_documents-uploads'])
+        ]
+        for f in invoice.supporting_documents.all():
+            if f.document.name not in not_deleted_original_filenames:
+                f.document.delete()
+                f.delete()
+
+        for f in self.cleaned_data["supporting_documents"]:
+            if not isinstance(f, FieldFile):
+                try:
+                    SupportingDocument.objects.create(invoice=invoice, document=f)
+                finally:
+                    f.close()
+        return invoice
+
+
 class SelectDocumentForm(forms.ModelForm):
     document = forms.ChoiceField(
         label="Document",
diff --git a/hypha/apply/projects/migrations/0037_add_project_invoicing.py b/hypha/apply/projects/migrations/0037_add_project_invoicing.py
new file mode 100644
index 0000000000000000000000000000000000000000..996bb3761dac2541915ff64afb3c7ddbc325bb3c
--- /dev/null
+++ b/hypha/apply/projects/migrations/0037_add_project_invoicing.py
@@ -0,0 +1,45 @@
+# Generated by Django 2.2.24 on 2021-07-28 07:08
+
+from decimal import Decimal
+from django.conf import settings
+import django.core.files.storage
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import hypha.apply.projects.models.payment
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('application_projects', '0036_add_vendor'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Invoice',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date_from', models.DateTimeField()),
+                ('date_to', models.DateTimeField()),
+                ('amount', models.DecimalField(decimal_places=2, default=0, max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))])),
+                ('paid_value', models.DecimalField(decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))])),
+                ('document', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=hypha.apply.projects.models.payment.invoice_path)),
+                ('requested_at', models.DateTimeField(auto_now_add=True)),
+                ('message_for_pm', models.TextField(blank=True)),
+                ('comment', models.TextField(blank=True)),
+                ('status', models.TextField(choices=[('submitted', 'Submitted'), ('changes_requested', 'Changes Requested'), ('under_review', 'Under Review'), ('paid', 'Paid'), ('declined', 'Declined')], default='submitted')),
+                ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoices', to=settings.AUTH_USER_MODEL)),
+                ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoices', to='application_projects.Project')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='SupportingDocument',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('document', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to='supporting_documents')),
+                ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supporting_documents', to='application_projects.Invoice')),
+            ],
+        ),
+    ]
diff --git a/hypha/apply/projects/models/__init__.py b/hypha/apply/projects/models/__init__.py
index 760e37b3dde0c4ce821cdb564e19582e05508c66..71e3b7734417ad174372a0679d39667aef83f0ee 100644
--- a/hypha/apply/projects/models/__init__.py
+++ b/hypha/apply/projects/models/__init__.py
@@ -1,4 +1,10 @@
-from .payment import PaymentApproval, PaymentReceipt, PaymentRequest
+from .payment import (
+    Invoice,
+    PaymentApproval,
+    PaymentReceipt,
+    PaymentRequest,
+    SupportingDocument,
+)
 from .project import (
     Approval,
     Contract,
@@ -29,4 +35,6 @@ __all__ = [
     'Vendor',
     'BankInformation',
     'DueDiligenceDocument',
+    'Invoice',
+    'SupportingDocument',
 ]
diff --git a/hypha/apply/projects/models/payment.py b/hypha/apply/projects/models/payment.py
index b0a5ccfc8fcd1fe6e851b4a300a0e364924b1bbf..2b6a0a1da9a5a945b577fd15dab9ad638ac5d286 100644
--- a/hypha/apply/projects/models/payment.py
+++ b/hypha/apply/projects/models/payment.py
@@ -71,6 +71,115 @@ class PaymentRequestQueryset(models.QuerySet):
         return self.filter(status__in=[SUBMITTED, UNDER_REVIEW]).total_value('requested_value')
 
 
+class InvoiceQueryset(models.QuerySet):
+    def in_progress(self):
+        return self.exclude(status__in=[DECLINED, PAID])
+
+    def rejected(self):
+        return self.filter(status=DECLINED)
+
+    def not_rejected(self):
+        return self.exclude(status=DECLINED)
+
+    def total_value(self, field):
+        return self.aggregate(total=Coalesce(Sum(field), Value(0)))['total']
+
+    def paid_value(self):
+        return self.filter(status=PAID).total_value('paid_value')
+
+    def unpaid_value(self):
+        return self.filter(status__in=[SUBMITTED, UNDER_REVIEW]).total_value('requested_value')
+
+
+class Invoice(models.Model):
+    project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="invoices")
+    by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="invoices")
+    date_from = models.DateTimeField()
+    date_to = models.DateTimeField()
+    amount = models.DecimalField(
+        default=0,
+        max_digits=10,
+        decimal_places=2,
+        validators=[MinValueValidator(decimal.Decimal('0.01'))],
+    )
+    paid_value = models.DecimalField(
+        max_digits=10,
+        decimal_places=2,
+        validators=[MinValueValidator(decimal.Decimal('0.01'))],
+        null=True
+    )
+    document = models.FileField(upload_to=invoice_path, storage=PrivateStorage())
+    requested_at = models.DateTimeField(auto_now_add=True)
+    message_for_pm = models.TextField(blank=True)
+    comment = models.TextField(blank=True)
+    status = models.TextField(choices=REQUEST_STATUS_CHOICES, default=SUBMITTED)
+
+    objects = InvoiceQueryset.as_manager()
+
+    def __str__(self):
+        return f'Invoice requested for {self.project}'
+
+    @property
+    def has_changes_requested(self):
+        return self.status == CHANGES_REQUESTED
+
+    @property
+    def status_display(self):
+        return self.get_status_display()
+
+    def can_user_delete(self, user):
+        if user.is_applicant:
+            if self.status in (SUBMITTED, CHANGES_REQUESTED):
+                return True
+
+        if user.is_apply_staff:
+            if self.status in {SUBMITTED}:
+                return True
+
+        return False
+
+    def can_user_edit(self, user):
+        if user.is_applicant:
+            if self.status in {SUBMITTED, CHANGES_REQUESTED}:
+                return True
+
+        if user.is_apply_staff:
+            if self.status in {SUBMITTED}:
+                return True
+
+        return False
+
+    def can_user_change_status(self, user):
+        if not user.is_apply_staff:
+            return False  # Users can't change status
+
+        if self.status in {PAID, DECLINED}:
+            return False
+
+        return True
+
+    @property
+    def value(self):
+        return self.paid_value or self.amount
+
+    def get_absolute_url(self):
+        return reverse('apply:projects:invoices:detail', args=[self.pk])
+
+
+class SupportingDocument(models.Model):
+    document = models.FileField(
+        upload_to="supporting_documents", storage=PrivateStorage()
+    )
+    invoice = models.ForeignKey(
+        Invoice,
+        on_delete=models.CASCADE,
+        related_name='supporting_documents',
+    )
+
+    def __str__(self):
+        return self.invoice.name + ' -> ' + self.document.name
+
+
 class PaymentRequest(models.Model):
     project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="payment_requests")
     by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="payment_requests")
diff --git a/hypha/apply/projects/models/vendor.py b/hypha/apply/projects/models/vendor.py
index 83b0523d7ef6bdd1e14b38535783fd7e38b0e998..95c070f93c5e3437cb2b97a4007e4f69f2ea44f7 100644
--- a/hypha/apply/projects/models/vendor.py
+++ b/hypha/apply/projects/models/vendor.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.db import models
+from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel
 from wagtail.contrib.settings.models import BaseSetting, register_setting
@@ -68,6 +69,9 @@ class Vendor(models.Model):
     def __str__(self):
         return self.name
 
+    def get_absolute_url(self):
+        return reverse('apply:projects:vendor-detail', args=[self.pk])
+
 
 class DueDiligenceDocument(models.Model):
     document = models.FileField(
diff --git a/hypha/apply/projects/tables.py b/hypha/apply/projects/tables.py
index 58c9deb093c3f98c2da08d33a697321ca060019b..836eae078afac5f0e6292c6c78362096c191ed18 100644
--- a/hypha/apply/projects/tables.py
+++ b/hypha/apply/projects/tables.py
@@ -7,7 +7,7 @@ from django.db.models import F, Sum
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 
-from .models import PaymentRequest, Project, Report
+from .models import Invoice, PaymentRequest, Project, Report
 
 
 class BasePaymentRequestsTable(tables.Table):
@@ -70,6 +70,47 @@ class PaymentRequestsListTable(BasePaymentRequestsTable):
         return qs, True
 
 
+class BaseInvoiceTable(tables.Table):
+    project = tables.LinkColumn(
+        'funds:projects:invoices:detail',
+        verbose_name=_('Invoice reference'),
+        text=lambda r: textwrap.shorten(r.project.title, width=30, placeholder="..."),
+        args=[tables.utils.A('pk')],
+    )
+    status = tables.Column()
+    requested_at = tables.DateColumn(verbose_name=_('Submitted'))
+    amount = tables.Column(verbose_name=_('Value ({currency})').format(currency=settings.CURRENCY_SYMBOL))
+
+    def render_amount(self, value):
+        return intcomma(value)
+
+
+class InvoiceListTable(BaseInvoiceTable):
+    fund = tables.Column(verbose_name=_('Fund'), accessor='project.submission.page')
+    lead = tables.Column(verbose_name=_('Lead'), accessor='project.lead')
+
+    class Meta:
+        fields = [
+            'requested_at',
+            'project',
+            'amount',
+            'status',
+            'lead',
+            'fund',
+        ]
+        model = Invoice
+        orderable = True
+        order_by = ['-requested_at']
+        attrs = {'class': 'payment-requests-table'}
+
+    def order_value(self, qs, is_descending):
+        direction = '-' if is_descending else ''
+
+        qs = qs.order_by(f'{direction}paid_value', f'{direction}amount')
+
+        return qs, True
+
+
 class BaseProjectsTable(tables.Table):
     title = tables.LinkColumn(
         'funds:projects:detail',
diff --git a/hypha/apply/projects/templates/application_projects/includes/invoices.html b/hypha/apply/projects/templates/application_projects/includes/invoices.html
new file mode 100644
index 0000000000000000000000000000000000000000..6b3ab7c7ca3f70f99d46676a95c6b76d9bb709aa
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/includes/invoices.html
@@ -0,0 +1,79 @@
+{% load invoice_tools humanize %}
+
+<div id="payment-requests" class="data-block">
+    <div class="data-block__header">
+        <p class="data-block__title">Invoice Requests</p>
+        <a class="data-block__button button button--primary"
+           href="{% url "apply:projects:invoice" pk=object.pk %}">
+            Add Request
+        </a>
+    </div>
+    <div class="data-block__body">
+        <table class="data-block__table">
+            <thead>
+                <tr>
+                    <th class="data-block__table-amount">Amount ({{ CURRENCY_SYMBOL }})</th>
+                    <th class="data-block__table-status">Status</th>
+                    <th class="data-block__table-date">From</th>
+                    <th class="data-block__table-date">To</th>
+                    <th class="data-block__table-update"></th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for invoice in object.invoices.not_rejected %}
+                <tr>
+                    <td><span class="data-block__mobile-label">Amount: </span>{{ invoice.amount|intcomma }}</td>
+                    <td><span class="data-block__mobile-label">Status: </span>{{ invoice.get_status_display }}</td>
+                    <td><span class="data-block__mobile-label">From: </span>{{ invoice.date_from.date }}</td>
+                    <td><span class="data-block__mobile-label">To: </span>{{ invoice.date_to.date }}</td>
+                    <td>
+                        <a class="data-block__action-link" href="{{ invoice.get_absolute_url }}">View</a>
+                        {% can_edit invoice user as user_can_edit_request %}
+                        {% if user_can_edit_request %}
+                        <a class="data-block__action-link" href="{% url "apply:projects:invoices:edit" pk=invoice.pk %}">
+                            Edit
+                        </a>
+                        {% endif %}
+
+                        {% can_delete invoice user as user_can_delete_request %}
+                        {% if user_can_delete_request %}
+                        <a class="data-block__action-link" href="{% url 'apply:projects:invoices:delete' pk=invoice.pk %}">
+                            Delete
+                        </a>
+                        {% endif %}
+                    </td>
+                </tr>
+                {% empty %}
+                <tr>
+                    <td colspan="5">No active Invoices.</td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+
+        {% if object.invoices.rejected %}
+            <p class="data-block__rejected">
+                <a class="data-block__rejected-link js-payment-block-rejected-link" href="#">Show rejected</a>
+            </p>
+
+            <table class="data-block__table is-hidden js-payment-block-rejected-table">
+                <thead>
+                    <tr>
+                        <th class="data-block__table-amount">Amount</th>
+                        <th class="data-block__table-status">Status</th>
+                        <th class="data-block__table-view"></th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for invoice in object.invoices.rejected %}
+                    <tr>
+                        <td><span class="data-block__mobile-label">Amount: </span>{{ CURRENCY_SYMBOL }}{{ invoice.value }}</td>
+                        <td><span class="data-block__mobile-label">Status: </span>{{ invoice.get_status_display }}</td>
+                        <td><a href="{{ invoice.get_absolute_url }}">View</a></td>
+                    </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+        {% endif %}
+    </div>
+</div>
diff --git a/hypha/apply/projects/templates/application_projects/invoice_admin_detail.html b/hypha/apply/projects/templates/application_projects/invoice_admin_detail.html
new file mode 100644
index 0000000000000000000000000000000000000000..2d21b08eb1f0c754b12ea4fe636dec7a12b3d932
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/invoice_admin_detail.html
@@ -0,0 +1,36 @@
+{% extends "application_projects/invoice_detail.html" %}
+{% load static invoice_tools %}
+
+{% block actions %}
+    {{ block.super }}
+    {% can_change_status object user as user_can_change_status %}
+    <a
+        {% if user_can_change_status %}
+            data-fancybox
+            data-src="#change-status"
+        {% else %}
+            data-tooltip="Cannot change from 'Paid' or 'Declined' state"
+        {% endif %}
+        class="button button--bottom-space button--primary button--full-width{% if not user_can_change_status %} button--tooltip-disabled{% endif %}"
+        href="#"
+    >
+        Change Status
+    </a>
+    {% if user_can_change_status %}
+    <div class="modal" id="change-status">
+        <h4 class="modal__header-bar">Change status</h4>
+        <p>Current status: {{ object.get_status_display }}</p>
+        {% include 'funds/includes/delegated_form_base.html' with form=change_invoice_status value='Update'%}
+    </div>
+    {% endif %}
+{% endblock %}
+
+{% block extra_css %}
+<link rel="stylesheet" href="{% static 'css/apply/fancybox.css' %}">
+{% endblock %}
+
+{% block extra_js %}
+{{ block.super }}
+<script src="//cdnjs.cloudflare.com/ajax/libs/fancybox/3.4.1/jquery.fancybox.min.js"></script>
+<script src="{% static 'js/apply/fancybox-global.js' %}"></script>
+{% endblock %}
diff --git a/hypha/apply/projects/templates/application_projects/invoice_confirm_delete.html b/hypha/apply/projects/templates/application_projects/invoice_confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..3d5448fccc0213a937b69d88adc86a2320e5884b
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/invoice_confirm_delete.html
@@ -0,0 +1,37 @@
+
+{% extends "base-apply.html" %}
+{% load humanize invoice_tools %}
+
+{% block title %} Invoice: {{ object.project.title }}{% endblock %}
+{% block content %}
+<div class="admin-bar">
+    <div class="admin-bar__inner">
+        <a class="simplified__projects-link" href="{{ object.project.get_absolute_url }}">
+            Project
+        </a>
+        <h2 class="heading heading--no-margin">Delete Invoice</h2>
+        <h5 class="heading heading--no-margin">For: {{ object.project.title }}</h5>
+    </div>
+</div>
+
+<div class="wrapper wrapper--sidebar wrapper--outer-space-medium">
+    <div class="wrapper--sidebar--inner">
+
+        <div class="card card--solid">
+            <p class="card__text"><b>Status:</b> {{ object.get_status_display }}</p>
+            <p class="card__text"><b>Vendor:</b> {{ object.project.vendor.name }}</p>
+            <p class="card__text"><b>Invoice Number:</b> {{ object.pk }}</p>
+            <p class="card__text"><b>Period of Performance:</b> {{ object.date_from.date }} | {{ object.date_to.date }}</p>
+            <p class="card__text"><b>Total:</b> {{ CURRENCY_SYMBOL }}{{ object.amount|intcomma }}</p>
+
+        </div>
+        <div class="card card--solid">
+            <form method="post">{% csrf_token %}
+                <p>Are you sure you want to delete this invoice for {{ object.project.title }}?</p>
+                <button class="button button--primary" type="submit">Confirm</button>
+            </form>
+
+        </div>
+    </div>
+</div>
+{% endblock %}
diff --git a/hypha/apply/projects/templates/application_projects/invoice_detail.html b/hypha/apply/projects/templates/application_projects/invoice_detail.html
new file mode 100644
index 0000000000000000000000000000000000000000..741235d7000cba618c19d2856266a5a70c63e60f
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/invoice_detail.html
@@ -0,0 +1,66 @@
+{% extends "base-apply.html" %}
+{% load humanize invoice_tools %}
+
+{% block title %}Invoice Request: {{ object.project.title }}{% endblock %}
+{% block content %}
+<div class="admin-bar">
+    <div class="admin-bar__inner">
+        <a class="simplified__projects-link" href="{{ object.project.get_absolute_url }}">
+            Project
+        </a>
+        <h2 class="heading heading--no-margin">Invoice Request</h2>
+        <h5 class="heading heading--no-margin">For: {{ object.project.title }}</h5>
+    </div>
+</div>
+
+<div class="wrapper wrapper--sidebar wrapper--outer-space-medium">
+    <div class="wrapper--sidebar--inner">
+        <div class="card card--solid">
+            <p class="card__text"><b>Status:</b> {{ object.get_status_display }}</p>
+            <p class="card__text"><b>Vendor:</b> {{ object.project.vendor.name }}</p>
+            <p class="card__text"><b>Invoice Number:</b> {{ object.pk }}</p>
+            <p class="card__text"><b>Period of Performance:</b> {{ object.date_from.date }} | {{ object.date_to.date }}</p>
+            <p class="card__text"><b>Total:</b> {{ CURRENCY_SYMBOL }}{{ object.amount|intcomma }}</p>
+        </div>
+
+        <div class="card card--solid">
+            <div class="card__inner">
+                <h5 class="card__heading">Invoice</h5>
+                <p class="card__text"><a href="{% url "apply:projects:invoices:invoice-document" pk=object.pk %}">{{invoice.document.name}}</a></p>
+            </div>
+            <div class="card__inner">
+                <h5 class="card__heading">Supporting Documents</h5>
+                {% for document in object.supporting_documents.all %}
+                    <p class="card__text"><a href="{% url "apply:projects:invoices:supporting-document" pk=object.pk file_pk=document.pk %}">{{document.document.name}}</a></p>
+                {% endfor %}
+            </div>
+        </div>
+    </div>
+    <aside class="sidebar">
+        <div class="js-actions-sidebar sidebar__inner sidebar__inner--light-blue sidebar__inner--actions">
+            {% block actions %}
+                {% can_edit object user as user_can_edit_request %}
+                <a
+                    {% if not user_can_edit_request %}
+                        data-tooltip="Only editable when 'Submitted' or you have been requested to make changes"
+                    {% endif %}
+                    class="button button--bottom-space button--primary button--full-width{% if not user_can_edit_request %} button--tooltip-disabled{% endif %}"
+                    href={% if user_can_edit_request %}
+                        "{% url "apply:projects:invoices:edit" pk=object.pk %}"
+                    {% else %}
+                        "#"
+                    {% endif %}
+                >
+                    Edit
+                </a>
+                {% can_delete object user as user_can_delete_request %}
+                {% if user_can_delete_request %}
+                <a
+                    class="button button--bottom-space button--primary button--full-width"
+                    href="{% url 'apply:projects:invoices:delete' pk=object.pk %}">Delete</a>
+            {% endif %}
+            {% endblock %}
+        </div>
+    </aside>
+</div>
+{% endblock %}
diff --git a/hypha/apply/projects/templates/application_projects/invoice_form.html b/hypha/apply/projects/templates/application_projects/invoice_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..64a40ac0404bb8116d2e7781155a2031e7fc3d4d
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/invoice_form.html
@@ -0,0 +1,36 @@
+{% extends "base-apply.html" %}
+{% load static %}
+
+{% block title %}{% if object %}Edit{% else %}Create{% endif %} Invoice: {% if object %}{{ object.project.title }}{% else %}{{ project.title }}{% endif %}{% endblock %}
+{% block content %}
+<div class="admin-bar">
+    <div class="admin-bar__inner">
+        <h2 class="heading heading--no-margin">{% if object %}Editing{% else %}Create{% endif %} Invoice</h2>
+        <h5 class="heading heading--no-margin">{% if object %}{{ object.project.title }}{% else %}For: {{ project.title }}{% endif %}</h5>
+    </div>
+</div>
+
+{% include "forms/includes/form_errors.html" with form=form %}
+
+<div class="wrapper wrapper--light-grey-bg wrapper--form wrapper--sidebar">
+    <div class="wrapper--sidebar--inner">
+        <form class="form" action="" method="post" enctype="multipart/form-data">
+            {% csrf_token %}
+            {{ form.media }}
+
+            {% for field in form %}
+                {% if field.field %}
+                    {% include "forms/includes/field.html" %}
+                {% else %}
+                    {{ field }}
+                {% endif %}
+            {% endfor %}
+            <button class="button button--submit button--top-space button--primary" type="submit" name="save">Save</button>
+        </form>
+    </div>
+</div>
+{% endblock %}
+
+{% block extra_js %}
+<script src="{% static 'js/apply/list-input-files.js' %}"></script>
+{% endblock %}
diff --git a/hypha/apply/projects/templates/application_projects/invoice_list.html b/hypha/apply/projects/templates/application_projects/invoice_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..c837ec5b28bf98b0f029c4b4ed622565c70593d2
--- /dev/null
+++ b/hypha/apply/projects/templates/application_projects/invoice_list.html
@@ -0,0 +1,38 @@
+{% extends "base-apply.html" %}
+
+{% load render_table from django_tables2 %}
+{% load static %}
+
+{% block title %}Invoice Request{% endblock %}
+
+{% block content %}
+<div class="admin-bar">
+    <div class="admin-bar__inner wrapper--search">
+        {% block page_header %}
+            <div>
+                <h1 class="gamma heading heading--no-margin heading--bold">All Invoice Requests</h1>
+            </div>
+        {% endblock %}
+    </div>
+</div>
+
+<div class="wrapper wrapper--large wrapper--inner-space-medium">
+    {% if table %}
+    {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action use_batch_actions=True search_placeholder="invoice requests" %}
+    {% render_table table %}
+    {% else %}
+    <p>No Requests Available</p>
+    {% endif %}
+</div>
+
+{% endblock content %}
+
+{% block extra_css %}
+    <link rel="stylesheet" href="{% static 'css/apply/fancybox.css' %}">
+    {{ filter.form.media.css }}
+{% endblock %}
+
+{% block extra_js %}
+    {{ filter.form.media.js }}
+    <script src="{% static 'js/apply/submission-filters.js' %}"></script>
+{% endblock %}
diff --git a/hypha/apply/projects/templates/application_projects/project_detail.html b/hypha/apply/projects/templates/application_projects/project_detail.html
index fdce23c27a09ff58d3d8cb8b58d35670ca3ddd1a..66a5ce0825016f29d7940a8872f1729c21925b80 100644
--- a/hypha/apply/projects/templates/application_projects/project_detail.html
+++ b/hypha/apply/projects/templates/application_projects/project_detail.html
@@ -127,7 +127,7 @@
                 {% if object.can_request_funding %}
                 <div class="wrapper wrapper--outer-space-large">
                     {% include "application_projects/includes/funding_block.html" %}
-                    {% include "application_projects/includes/payment_requests.html" %}
+                    {% include "application_projects/includes/invoices.html" %}
                 </div>
                 {% endif %}
 
@@ -176,8 +176,8 @@
 
                     {% if object.can_request_funding %}
                     <a class="button button--primary button--bottom-space button--full-width"
-                       href="{% url "apply:projects:request" pk=object.pk %}">
-                        Add payment request
+                       href="{% url "apply:projects:invoice" pk=object.pk %}">
+                        Add Invoice
                     </a>
                     {% endif %}
 
@@ -192,7 +192,9 @@
                     <h5>Supporting Information</h5>
 
                     <p><a class="link link--bold" href="{{ object.submission.get_absolute_url }}">Proposal</a></p>
-
+                    {% if project.vendor %}
+                        <p><a class="link link--bold" href="{% url 'apply:projects:vendor-detail' pk=project.pk vendor_pk=project.vendor.pk %}">Contractor Setup Form</a></p>
+                    {% endif %}
                     {% if request.user.is_apply_staff %}
                     <p><a class="link link--bold" href="{% url 'apply:projects:simplified' pk=project.pk %}">Approval form</a></p>
                     {% endif %}
diff --git a/hypha/apply/projects/templatetags/invoice_tools.py b/hypha/apply/projects/templatetags/invoice_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..24e0abdb3d441ec41455741fcae0bde464fa30d6
--- /dev/null
+++ b/hypha/apply/projects/templatetags/invoice_tools.py
@@ -0,0 +1,36 @@
+import decimal
+
+from django import template
+
+register = template.Library()
+
+
+@register.simple_tag
+def can_change_status(invoice, user):
+    return invoice.can_user_change_status(user)
+
+
+@register.simple_tag
+def can_delete(invoice, user):
+    return invoice.can_user_delete(user)
+
+
+@register.simple_tag
+def can_edit(invoice, user):
+    return invoice.can_user_edit(user)
+
+
+@register.simple_tag
+def percentage(value, total):
+    if not total:
+        return decimal.Decimal(0)
+
+    unrounded_total = (value / total) * 100
+
+    # round using Decimal since we're dealing with currency
+    rounded_total = unrounded_total.quantize(
+        decimal.Decimal('0.0'),
+        rounding=decimal.ROUND_DOWN,
+    )
+
+    return rounded_total
diff --git a/hypha/apply/projects/tests/factories.py b/hypha/apply/projects/tests/factories.py
index 62ccf3fc653119ce1551ada118822b525867dac4..5c8bacdf5ab89a54ab055ff838647570aabd2766 100644
--- a/hypha/apply/projects/tests/factories.py
+++ b/hypha/apply/projects/tests/factories.py
@@ -12,7 +12,7 @@ from hypha.apply.stream_forms.testing.factories import (
 )
 from hypha.apply.users.tests.factories import StaffFactory, UserFactory
 
-from ..models.payment import PaymentReceipt, PaymentRequest
+from ..models.payment import Invoice, PaymentReceipt, PaymentRequest, SupportingDocument
 from ..models.project import (
     COMPLETE,
     IN_PROGRESS,
@@ -140,6 +140,20 @@ class PaymentRequestFactory(factory.DjangoModelFactory):
         model = PaymentRequest
 
 
+class InvoiceFactory(factory.DjangoModelFactory):
+    project = factory.SubFactory(ProjectFactory)
+    by = factory.SubFactory(UserFactory)
+    amount = factory.Faker('pydecimal', min_value=1, max_value=10000000, right_digits=2)
+
+    date_from = factory.Faker('date_time').generate({'tzinfo': pytz.utc})
+    date_to = factory.Faker('date_time').generate({'tzinfo': pytz.utc})
+
+    document = factory.django.FileField()
+
+    class Meta:
+        model = Invoice
+
+
 class PaymentReceiptFactory(factory.DjangoModelFactory):
     payment_request = factory.SubFactory(PaymentRequestFactory)
 
@@ -149,6 +163,15 @@ class PaymentReceiptFactory(factory.DjangoModelFactory):
         model = PaymentReceipt
 
 
+class SupportingDocumentFactory(factory.DjangoModelFactory):
+    invoice = factory.SubFactory(InvoiceFactory)
+
+    document = factory.django.FileField()
+
+    class Meta:
+        model = SupportingDocument
+
+
 class ReportConfigFactory(factory.DjangoModelFactory):
     project = factory.SubFactory(
         "hypha.apply.projects.tests.factories.ApprovedProjectFactory",
diff --git a/hypha/apply/projects/tests/test_views.py b/hypha/apply/projects/tests/test_views.py
index 5c29058b0858ccad111d32b99aeec3a98a3fde3f..013e86b1174e8ab687f251f8c4405014aea0081d 100644
--- a/hypha/apply/projects/tests/test_views.py
+++ b/hypha/apply/projects/tests/test_views.py
@@ -1,3 +1,4 @@
+import json
 from decimal import Decimal
 from io import BytesIO
 
@@ -28,12 +29,12 @@ from ..views.project import ContractsMixin, ProjectDetailSimplifiedView
 from .factories import (
     ContractFactory,
     DocumentCategoryFactory,
+    InvoiceFactory,
     PacketFileFactory,
-    PaymentReceiptFactory,
-    PaymentRequestFactory,
     ProjectFactory,
     ReportFactory,
     ReportVersionFactory,
+    SupportingDocumentFactory,
 )
 
 
@@ -916,9 +917,9 @@ class TestProjectDetailSimplifiedView(TestCase):
             ProjectDetailSimplifiedView.as_view()(request, pk=project.pk)
 
 
-class TestStaffDetailPaymentRequestStatus(BaseViewTestCase):
+class TestStaffDetailInvoiceStatus(BaseViewTestCase):
     base_view_name = 'detail'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = StaffFactory
 
     def get_kwargs(self, instance):
@@ -927,20 +928,20 @@ class TestStaffDetailPaymentRequestStatus(BaseViewTestCase):
         }
 
     def test_can(self):
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request)
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice)
         self.assertEqual(response.status_code, 200)
 
     def test_wrong_project_cant(self):
         other_project = ProjectFactory()
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request, url_kwargs={'pk': other_project.pk})
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice, url_kwargs={'pk': other_project.pk})
         self.assertEqual(response.status_code, 404)
 
 
-class TestFinanceDetailPaymentRequestStatus(BaseViewTestCase):
+class TestFinanceDetailInvoiceStatus(BaseViewTestCase):
     base_view_name = 'detail'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = FinanceFactory
 
     def get_kwargs(self, instance):
@@ -949,20 +950,20 @@ class TestFinanceDetailPaymentRequestStatus(BaseViewTestCase):
         }
 
     def test_can(self):
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request)
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice)
         self.assertEqual(response.status_code, 200)
 
     def test_wrong_project_cant(self):
         other_project = ProjectFactory()
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request, url_kwargs={'pk': other_project.pk})
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice, url_kwargs={'pk': other_project.pk})
         self.assertEqual(response.status_code, 404)
 
 
-class TestApplicantDetailPaymentRequestStatus(BaseViewTestCase):
+class TestApplicantDetailInvoiceStatus(BaseViewTestCase):
     base_view_name = 'detail'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = ApplicantFactory
 
     def get_kwargs(self, instance):
@@ -971,125 +972,127 @@ class TestApplicantDetailPaymentRequestStatus(BaseViewTestCase):
         }
 
     def test_can(self):
-        payment_request = PaymentRequestFactory(project__user=self.user)
-        response = self.get_page(payment_request)
+        invoice = InvoiceFactory(project__user=self.user)
+        response = self.get_page(invoice)
         self.assertEqual(response.status_code, 200)
 
     def test_other_cant(self):
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request)
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice)
         self.assertEqual(response.status_code, 403)
 
 
-class TestApplicantEditPaymentRequestView(BaseViewTestCase):
+class TestApplicantEditInvoiceView(BaseViewTestCase):
     base_view_name = 'edit'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = ApplicantFactory
 
     def get_kwargs(self, instance):
         return {'pk': instance.pk}
 
-    def test_editing_payment_remove_receipt(self):
-        payment_request = PaymentRequestFactory(project__user=self.user)
-        receipt = PaymentReceiptFactory(payment_request=payment_request)
+    def test_editing_invoice_remove_supporting_document(self):
+        invoice = InvoiceFactory(project__user=self.user)
+        SupportingDocumentFactory(invoice=invoice)
 
-        response = self.post_page(payment_request, {
-            'requested_value': payment_request.requested_value,
+        self.assertTrue(invoice.supporting_documents.exists())
+
+        response = self.post_page(invoice, {
+            'amount': invoice.amount,
             'date_from': '2018-08-15',
             'date_to': '2019-08-15',
             'comment': 'test comment',
             'invoice': '',
-            'receipt_list': [receipt.pk],
+            'supporting_documents-uploads': '[]',
         })
 
         self.assertEqual(response.status_code, 200)
-
-        self.assertFalse(payment_request.receipts.exists())
+        self.assertFalse(invoice.supporting_documents.exists())
 
     def test_editing_payment_keeps_receipts(self):
         project = ProjectFactory(user=self.user)
-        payment_request = PaymentRequestFactory(project=project)
-        receipt = PaymentReceiptFactory(payment_request=payment_request)
+        invoice = InvoiceFactory(project=project)
+        supporting_document = SupportingDocumentFactory(invoice=invoice)
 
-        requested_value = payment_request.requested_value
+        amount = invoice.amount
 
-        response = self.post_page(payment_request, {
-            'requested_value': requested_value + 1,
+        response = self.post_page(invoice, {
+            'amount': amount + 1,
             'date_from': '2018-08-15',
             'date_to': '2019-08-15',
             'comment': 'test comment',
             'invoice': '',
-            'receipt_list': [],
+            'supporting_documents-uploads': json.dumps([{"name": supporting_document.document.name, "size": supporting_document.document.size, "type": "existing"}]),
         })
 
         self.assertEqual(response.status_code, 200)
-        self.assertEqual(project.payment_requests.count(), 1)
+        self.assertEqual(project.invoices.count(), 1)
 
-        payment_request.refresh_from_db()
+        invoice.refresh_from_db()
 
-        self.assertEqual(project.payment_requests.first().pk, payment_request.pk)
+        self.assertEqual(project.invoices.first().pk, invoice.pk)
 
-        self.assertEqual(requested_value + Decimal("1"), payment_request.requested_value)
-        self.assertEqual(payment_request.receipts.first().file, receipt.file)
+        self.assertEqual(amount + Decimal("1"), invoice.amount)
+        self.assertEqual(invoice.supporting_documents.first().document, supporting_document.document)
 
 
-class TestStaffEditPaymentRequestView(BaseViewTestCase):
+class TestStaffEditInvoiceView(BaseViewTestCase):
     base_view_name = 'edit'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = StaffFactory
 
     def get_kwargs(self, instance):
         return {'pk': instance.pk}
 
-    def test_editing_payment_remove_receipt(self):
-        payment_request = PaymentRequestFactory()
-        receipt = PaymentReceiptFactory(payment_request=payment_request)
+    def test_editing_invoice_remove_supporting_document(self):
+        invoice = InvoiceFactory()
+        SupportingDocumentFactory(invoice=invoice)
 
-        response = self.post_page(payment_request, {
-            'requested_value': payment_request.requested_value,
+        response = self.post_page(invoice, {
+            'amount': invoice.amount,
             'date_from': '2018-08-15',
             'date_to': '2019-08-15',
             'comment': 'test comment',
             'invoice': '',
-            'receipt_list': [receipt.pk],
+            'supporting_documents-uploads': '[]',
         })
 
         self.assertEqual(response.status_code, 200)
 
-        self.assertFalse(payment_request.receipts.exists())
+        self.assertFalse(invoice.supporting_documents.exists())
 
-    def test_editing_payment_keeps_receipts(self):
+    def test_editing_invoice_keeps_supprting_document(self):
         project = ProjectFactory()
-        payment_request = PaymentRequestFactory(project=project)
-        receipt = PaymentReceiptFactory(payment_request=payment_request)
+        invoice = InvoiceFactory(project=project)
+        supporting_document = SupportingDocumentFactory(invoice=invoice)
 
-        requested_value = payment_request.requested_value
+        amount = invoice.amount
 
-        invoice = BytesIO(b'somebinarydata')
-        invoice.name = 'invoice.pdf'
+        document = BytesIO(b'somebinarydata')
+        document.name = 'invoice.pdf'
 
-        response = self.post_page(payment_request, {
-            'requested_value': requested_value + 1,
+        response = self.post_page(invoice, {
+            'amount': amount + 1,
             'date_from': '2018-08-15',
             'date_to': '2019-08-15',
             'comment': 'test comment',
-            'invoice': invoice,
-            'receipt_list': [receipt.pk],
+            'document': document,
+            'supporting_documents-uploads': json.dumps([{"name": supporting_document.document.name, "size": supporting_document.document.size, "type": "existing"}]),
         })
 
         self.assertEqual(response.status_code, 200)
-        self.assertEqual(project.payment_requests.count(), 1)
+        self.assertEqual(project.invoices.count(), 1)
 
-        payment_request.refresh_from_db()
+        invoice.refresh_from_db()
 
-        self.assertEqual(project.payment_requests.first().pk, payment_request.pk)
+        self.assertEqual(project.invoices.first().pk, invoice.pk)
 
-        self.assertEqual(requested_value + Decimal("1"), payment_request.requested_value)
+        self.assertEqual(amount + Decimal("1"), invoice.amount)
+        self.assertEqual(invoice.supporting_documents.first().document, supporting_document.document)
 
 
-class TestStaffChangePaymentRequestStatus(BaseViewTestCase):
+class TestStaffChangeInvoiceStatus(BaseViewTestCase):
     base_view_name = 'detail'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = StaffFactory
 
     def get_kwargs(self, instance):
@@ -1098,20 +1101,20 @@ class TestStaffChangePaymentRequestStatus(BaseViewTestCase):
         }
 
     def test_can(self):
-        payment_request = PaymentRequestFactory()
-        response = self.post_page(payment_request, {
-            'form-submitted-change_payment_status': '',
+        invoice = InvoiceFactory()
+        response = self.post_page(invoice, {
+            'form-submitted-change_invoice_status': '',
             'status': CHANGES_REQUESTED,
             'comment': 'this is a comment',
         })
         self.assertEqual(response.status_code, 200)
-        payment_request.refresh_from_db()
-        self.assertEqual(payment_request.status, CHANGES_REQUESTED)
+        invoice.refresh_from_db()
+        self.assertEqual(invoice.status, CHANGES_REQUESTED)
 
 
-class TestApplicantChangePaymentRequestStatus(BaseViewTestCase):
+class TestApplicantChangeInoviceStatus(BaseViewTestCase):
     base_view_name = 'detail'
-    url_name = 'funds:projects:payments:{}'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = ApplicantFactory
 
     def get_kwargs(self, instance):
@@ -1120,29 +1123,29 @@ class TestApplicantChangePaymentRequestStatus(BaseViewTestCase):
         }
 
     def test_can(self):
-        payment_request = PaymentRequestFactory(project__user=self.user)
-        response = self.post_page(payment_request, {
-            'form-submitted-change_payment_status': '',
+        invoice = InvoiceFactory(project__user=self.user)
+        response = self.post_page(invoice, {
+            'form-submitted-change_invoice_status': '',
             'status': CHANGES_REQUESTED,
         })
         self.assertEqual(response.status_code, 200)
-        payment_request.refresh_from_db()
-        self.assertEqual(payment_request.status, SUBMITTED)
+        invoice.refresh_from_db()
+        self.assertEqual(invoice.status, SUBMITTED)
 
     def test_other_cant(self):
-        payment_request = PaymentRequestFactory()
-        response = self.post_page(payment_request, {
-            'form-submitted-change_payment_status': '',
+        invoice = InvoiceFactory()
+        response = self.post_page(invoice, {
+            'form-submitted-change_invoice_status': '',
             'status': CHANGES_REQUESTED,
         })
         self.assertEqual(response.status_code, 403)
-        payment_request.refresh_from_db()
-        self.assertEqual(payment_request.status, SUBMITTED)
+        invoice.refresh_from_db()
+        self.assertEqual(invoice.status, SUBMITTED)
 
 
-class TestStaffPaymentRequestInvoicePrivateMedia(BaseViewTestCase):
-    base_view_name = 'invoice'
-    url_name = 'funds:projects:payments:{}'
+class TestStaffInoviceDocumentPrivateMedia(BaseViewTestCase):
+    base_view_name = 'invoice-document'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = StaffFactory
 
     def get_kwargs(self, instance):
@@ -1151,20 +1154,20 @@ class TestStaffPaymentRequestInvoicePrivateMedia(BaseViewTestCase):
         }
 
     def test_can_access(self):
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request)
-        self.assertContains(response, payment_request.invoice.read())
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice)
+        self.assertContains(response, invoice.document.read())
 
     def test_cant_access_if_project_wrong(self):
         other_project = ProjectFactory()
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request, url_kwargs={'pk': other_project.pk})
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice, url_kwargs={'pk': other_project.pk})
         self.assertEqual(response.status_code, 404)
 
 
-class TestApplicantPaymentRequestInvoicePrivateMedia(BaseViewTestCase):
-    base_view_name = 'invoice'
-    url_name = 'funds:projects:payments:{}'
+class TestApplicantInvoiceDocumentPrivateMedia(BaseViewTestCase):
+    base_view_name = 'invoice-document'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = ApplicantFactory
 
     def get_kwargs(self, instance):
@@ -1173,52 +1176,52 @@ class TestApplicantPaymentRequestInvoicePrivateMedia(BaseViewTestCase):
         }
 
     def test_can_access_own(self):
-        payment_request = PaymentRequestFactory(project__user=self.user)
-        response = self.get_page(payment_request)
-        self.assertContains(response, payment_request.invoice.read())
+        invoice = InvoiceFactory(project__user=self.user)
+        response = self.get_page(invoice)
+        self.assertContains(response, invoice.document.read())
 
     def test_cant_access_other(self):
-        payment_request = PaymentRequestFactory()
-        response = self.get_page(payment_request)
+        invoice = InvoiceFactory()
+        response = self.get_page(invoice)
         self.assertEqual(response.status_code, 403)
 
 
-class TestStaffPaymentRequestReceiptPrivateMedia(BaseViewTestCase):
-    base_view_name = 'receipt'
-    url_name = 'funds:projects:payments:{}'
+class TestStaffInvoiceSupportingDocumentPrivateMedia(BaseViewTestCase):
+    base_view_name = 'supporting-document'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = StaffFactory
 
     def get_kwargs(self, instance):
         return {
-            'pk': instance.payment_request.pk,
+            'pk': instance.invoice.pk,
             'file_pk': instance.pk,
         }
 
     def test_can_access(self):
-        payment_receipt = PaymentReceiptFactory()
-        response = self.get_page(payment_receipt)
-        self.assertContains(response, payment_receipt.file.read())
+        supporting_document = SupportingDocumentFactory()
+        response = self.get_page(supporting_document)
+        self.assertContains(response, supporting_document.document.read())
 
 
-class TestApplicantPaymentRequestReceiptPrivateMedia(BaseViewTestCase):
-    base_view_name = 'receipt'
-    url_name = 'funds:projects:payments:{}'
+class TestApplicantSupportingDocumentPrivateMedia(BaseViewTestCase):
+    base_view_name = 'supporting-document'
+    url_name = 'funds:projects:invoices:{}'
     user_factory = ApplicantFactory
 
     def get_kwargs(self, instance):
         return {
-            'pk': instance.payment_request.pk,
+            'pk': instance.invoice.pk,
             'file_pk': instance.pk,
         }
 
     def test_can_access_own(self):
-        payment_receipt = PaymentReceiptFactory(payment_request__project__user=self.user)
-        response = self.get_page(payment_receipt)
-        self.assertContains(response, payment_receipt.file.read())
+        supporting_document = SupportingDocumentFactory(invoice__project__user=self.user)
+        response = self.get_page(supporting_document)
+        self.assertContains(response, supporting_document.document.read())
 
     def test_cant_access_other(self):
-        payment_receipt = PaymentReceiptFactory()
-        response = self.get_page(payment_receipt)
+        supporting_document = SupportingDocumentFactory()
+        response = self.get_page(supporting_document)
         self.assertEqual(response.status_code, 403)
 
 
diff --git a/hypha/apply/projects/urls.py b/hypha/apply/projects/urls.py
index 35b4a9eccfe1f9f9f1341faba814667947510b58..0011fb3ad2bc27087d0b123ced53f95a10079dd3 100644
--- a/hypha/apply/projects/urls.py
+++ b/hypha/apply/projects/urls.py
@@ -2,10 +2,16 @@ from django.urls import include, path
 
 from .views import (
     ContractPrivateMediaView,
+    CreateInvoiceView,
     CreatePaymentRequestView,
     CreateVendorView,
+    DeleteInvoiceView,
     DeletePaymentRequestView,
+    EditInvoiceView,
     EditPaymentRequestView,
+    InvoiceListView,
+    InvoicePrivateMedia,
+    InvoiceView,
     PaymentRequestListView,
     PaymentRequestPrivateMedia,
     PaymentRequestView,
@@ -38,6 +44,7 @@ urlpatterns = [
         path('download/', ProjectDetailPDFView.as_view(), name='download'),
         path('simplified/', ProjectDetailSimplifiedView.as_view(), name='simplified'),
         path('request/', CreatePaymentRequestView.as_view(), name='request'),
+        path('invoice/', CreateInvoiceView.as_view(), name='invoice'),
         path('vendor/', CreateVendorView.as_view(), name='vendor'),
         path('vendor/<int:vendor_pk>/', VendorDetailView.as_view(), name='vendor-detail'),
         path('vendor/<int:vendor_pk>/documents/<int:file_pk>/', VendorPrivateMediaView.as_view(), name='vendor-documents'),
@@ -52,6 +59,16 @@ urlpatterns = [
             path('documents/receipt/<int:file_pk>/', PaymentRequestPrivateMedia.as_view(), name="receipt"),
         ])),
     ], 'payments'))),
+    path('invoices/', include(([
+        path('', InvoiceListView.as_view(), name='all'),
+        path('<int:pk>/', include([
+            path('', InvoiceView.as_view(), name='detail'),
+            path('edit/', EditInvoiceView.as_view(), name='edit'),
+            path('delete/', DeleteInvoiceView.as_view(), name='delete'),
+            path('documents/invoice/', InvoicePrivateMedia.as_view(), name="invoice-document"),
+            path('documents/supporting/<int:file_pk>/', InvoicePrivateMedia.as_view(), name="supporting-document"),
+        ])),
+    ], 'invoices'))),
     path('reports/', include(([
         path('', ReportListView.as_view(), name='all'),
         path('<int:pk>/', include([
diff --git a/hypha/apply/projects/views/__init__.py b/hypha/apply/projects/views/__init__.py
index 61a184d6d290b93f85e964e2d6820ecffb46a609..ee128ddce8e7315a59764417bc0f87db3b636f11 100644
--- a/hypha/apply/projects/views/__init__.py
+++ b/hypha/apply/projects/views/__init__.py
@@ -1,8 +1,15 @@
 from .payment import (
+    ChangeInvoiceStatusView,
     ChangePaymentRequestStatusView,
+    CreateInvoiceView,
     CreatePaymentRequestView,
+    DeleteInvoiceView,
     DeletePaymentRequestView,
+    EditInvoiceView,
     EditPaymentRequestView,
+    InvoiceListView,
+    InvoicePrivateMedia,
+    InvoiceView,
     PaymentRequestAdminView,
     PaymentRequestApplicantView,
     PaymentRequestListView,
@@ -44,6 +51,7 @@ from .report import (
 from .vendor import CreateVendorView, VendorDetailView, VendorPrivateMediaView
 
 __all__ = [
+    'ChangeInvoiceStatusView',
     'ChangePaymentRequestStatusView',
     'DeletePaymentRequestView',
     'PaymentRequestAdminView',
@@ -84,4 +92,10 @@ __all__ = [
     'CreateVendorView',
     'VendorDetailView',
     'VendorPrivateMediaView',
+    'CreateInvoiceView',
+    'InvoiceListView',
+    'InvoiceView',
+    'EditInvoiceView',
+    'DeleteInvoiceView',
+    'InvoicePrivateMedia',
 ]
diff --git a/hypha/apply/projects/views/payment.py b/hypha/apply/projects/views/payment.py
index 7016ff8b8959c20e36049783dadbd5257836a2be..950abc54c67781e329ff688faf971c944e167380 100644
--- a/hypha/apply/projects/views/payment.py
+++ b/hypha/apply/projects/views/payment.py
@@ -13,15 +13,18 @@ from hypha.apply.users.decorators import staff_or_finance_required
 from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegateableView, DelegatedViewMixin, ViewDispatcher
 
-from ..filters import PaymentRequestListFilter
+from ..filters import InvoiceListFilter, PaymentRequestListFilter
 from ..forms import (
+    ChangeInvoiceStatusForm,
     ChangePaymentRequestStatusForm,
+    CreateInvoiceForm,
     CreatePaymentRequestForm,
+    EditInvoiceForm,
     EditPaymentRequestForm,
 )
-from ..models.payment import PaymentRequest
+from ..models.payment import Invoice, PaymentRequest
 from ..models.project import Project
-from ..tables import PaymentRequestsListTable
+from ..tables import InvoiceListTable, PaymentRequestsListTable
 
 
 @method_decorator(login_required, name='dispatch')
@@ -41,6 +44,23 @@ class PaymentRequestAccessMixin(UserPassesTestMixin):
         return False
 
 
+@method_decorator(login_required, name='dispatch')
+class InvoiceAccessMixin(UserPassesTestMixin):
+    model = Invoice
+
+    def test_func(self):
+        if self.request.user.is_apply_staff:
+            return True
+
+        if self.request.user.is_finance:
+            return True
+
+        if self.request.user == self.get_object().project.user:
+            return True
+
+        return False
+
+
 @method_decorator(staff_or_finance_required, name='dispatch')
 class ChangePaymentRequestStatusView(DelegatedViewMixin, PaymentRequestAccessMixin, UpdateView):
     form_class = ChangePaymentRequestStatusForm
@@ -65,6 +85,30 @@ class ChangePaymentRequestStatusView(DelegatedViewMixin, PaymentRequestAccessMix
         return response
 
 
+@method_decorator(staff_or_finance_required, name='dispatch')
+class ChangeInvoiceStatusView(DelegatedViewMixin, InvoiceAccessMixin, UpdateView):
+    form_class = ChangeInvoiceStatusForm
+    context_name = 'change_invoice_status'
+
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs.pop('user')
+        return kwargs
+
+    def form_valid(self, form):
+        response = super().form_valid(form)
+
+        messenger(
+            MESSAGES.UPDATE_INVOICE_STATUS,
+            request=self.request,
+            user=self.request.user,
+            source=self.object.project,
+            related=self.object,
+        )
+
+        return response
+
+
 class DeletePaymentRequestView(DeleteView):
     model = PaymentRequest
 
@@ -93,6 +137,34 @@ class DeletePaymentRequestView(DeleteView):
         return self.project.get_absolute_url()
 
 
+class DeleteInvoiceView(DeleteView):
+    model = Invoice
+
+    def dispatch(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        if not self.object.can_user_delete(request.user):
+            raise PermissionDenied
+
+        return super().dispatch(request, *args, **kwargs)
+
+    @transaction.atomic()
+    def delete(self, request, *args, **kwargs):
+        response = super().delete(request, *args, **kwargs)
+
+        messenger(
+            MESSAGES.DELETE_INVOICE,
+            request=self.request,
+            user=self.request.user,
+            source=self.object.project,
+            related=self.object.project,
+        )
+
+        return response
+
+    def get_success_url(self):
+        return self.object.project.get_absolute_url()
+
+
 class PaymentRequestAdminView(PaymentRequestAccessMixin, DelegateableView, DetailView):
     form_views = [
         ChangePaymentRequestStatusView
@@ -110,6 +182,23 @@ class PaymentRequestView(ViewDispatcher):
     applicant_view = PaymentRequestApplicantView
 
 
+class InvoiceAdminView(InvoiceAccessMixin, DelegateableView, DetailView):
+    form_views = [
+        ChangeInvoiceStatusView
+    ]
+    template_name_suffix = '_admin_detail'
+
+
+class InvoiceApplicantView(InvoiceAccessMixin, DelegateableView, DetailView):
+    form_views = []
+
+
+class InvoiceView(ViewDispatcher):
+    admin_view = InvoiceAdminView
+    finance_view = InvoiceAdminView
+    applicant_view = InvoiceApplicantView
+
+
 class CreatePaymentRequestView(CreateView):
     model = PaymentRequest
     form_class = CreatePaymentRequestForm
@@ -143,6 +232,39 @@ class CreatePaymentRequestView(CreateView):
         return response
 
 
+class CreateInvoiceView(CreateView):
+    model = Invoice
+    form_class = CreateInvoiceForm
+
+    def dispatch(self, request, *args, **kwargs):
+        self.project = Project.objects.get(pk=kwargs['pk'])
+        if not request.user.is_apply_staff and not self.project.user == request.user:
+            return redirect(self.project)
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        return super().get_context_data(project=self.project, **kwargs)
+
+    def form_valid(self, form):
+        form.instance.project = self.project
+        form.instance.by = self.request.user
+
+        response = super().form_valid(form)
+
+        messenger(
+            MESSAGES.CREATE_INVOICE,
+            request=self.request,
+            user=self.request.user,
+            source=self.project,
+            related=self.object,
+        )
+
+        # Required for django-file-form: delete temporary files for the new files
+        # that are uploaded.
+        form.delete_temporary_files()
+        return response
+
+
 class EditPaymentRequestView(PaymentRequestAccessMixin, UpdateView):
     form_class = EditPaymentRequestForm
 
@@ -169,6 +291,40 @@ class EditPaymentRequestView(PaymentRequestAccessMixin, UpdateView):
         return response
 
 
+class EditInvoiceView(InvoiceAccessMixin, UpdateView):
+    form_class = EditInvoiceForm
+
+    def dispatch(self, request, *args, **kwargs):
+        invoice = self.get_object()
+        if not invoice.can_user_edit(request.user):
+            return redirect(invoice)
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_initial(self):
+        initial = super().get_initial()
+
+        initial["supporting_documents"] = [
+            document.document for document in self.object.supporting_documents.all()
+        ]
+        return initial
+
+    def form_valid(self, form):
+        response = super().form_valid(form)
+
+        messenger(
+            MESSAGES.UPDATE_INVOICE,
+            request=self.request,
+            user=self.request.user,
+            source=self.object.project,
+            related=self.object,
+        )
+
+        # Required for django-file-form: delete temporary files for the new files
+        # that are uploaded.
+        form.delete_temporary_files()
+        return response
+
+
 @method_decorator(login_required, name='dispatch')
 class PaymentRequestPrivateMedia(UserPassesTestMixin, PrivateMediaView):
     raise_exception = True
@@ -200,6 +356,37 @@ class PaymentRequestPrivateMedia(UserPassesTestMixin, PrivateMediaView):
         return False
 
 
+@method_decorator(login_required, name='dispatch')
+class InvoicePrivateMedia(UserPassesTestMixin, PrivateMediaView):
+    raise_exception = True
+
+    def dispatch(self, *args, **kwargs):
+        invoice_pk = self.kwargs['pk']
+        self.invoice = get_object_or_404(Invoice, pk=invoice_pk)
+
+        return super().dispatch(*args, **kwargs)
+
+    def get_media(self, *args, **kwargs):
+        file_pk = kwargs.get('file_pk')
+        if not file_pk:
+            return self.invoice.document
+
+        document = get_object_or_404(self.invoice.supporting_documents, pk=file_pk)
+        return document.document
+
+    def test_func(self):
+        if self.request.user.is_apply_staff:
+            return True
+
+        if self.request.user.is_finance:
+            return True
+
+        if self.request.user == self.invoice.project.user:
+            return True
+
+        return False
+
+
 @method_decorator(staff_or_finance_required, name='dispatch')
 class PaymentRequestListView(SingleTableMixin, FilterView):
     filterset_class = PaymentRequestListFilter
@@ -209,3 +396,14 @@ class PaymentRequestListView(SingleTableMixin, FilterView):
 
     def get_queryset(self):
         return PaymentRequest.objects.order_by('date_to')
+
+
+@method_decorator(staff_or_finance_required, name='dispatch')
+class InvoiceListView(SingleTableMixin, FilterView):
+    filterset_class = InvoiceListFilter
+    model = Invoice
+    table_class = InvoiceListTable
+    template_name = 'application_projects/invoice_list.html'
+
+    def get_queryset(self):
+        return Invoice.objects.order_by('date_to')
diff --git a/hypha/locale/en/LC_MESSAGES/django.po b/hypha/locale/en/LC_MESSAGES/django.po
index 0694fc5b73a32170269c6d9c501c5bd5d3e37aee..3ee745a91a5d1231efcbfde99a456d40ccbb6292 100644
--- a/hypha/locale/en/LC_MESSAGES/django.po
+++ b/hypha/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-07-08 06:43+0000\n"
+"POT-Creation-Date: 2021-07-28 07:03+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -23,413 +23,444 @@ msgstr ""
 msgid " as {role}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:224
+#: hypha/apply/activity/messaging.py:228
 #, python-brace-format
 msgid "Submitted {source.title} for {source.page.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:225 hypha/apply/activity/messaging.py:226
+#: hypha/apply/activity/messaging.py:229 hypha/apply/activity/messaging.py:230
 msgid "Edited"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:227 hypha/apply/activity/messaging.py:241
+#: hypha/apply/activity/messaging.py:231 hypha/apply/activity/messaging.py:245
 #, python-brace-format
 msgid "Lead changed from {old_lead} to {source.lead}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:228
+#: hypha/apply/activity/messaging.py:232
 #, python-brace-format
 msgid "Batch Lead changed to {new_lead}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:229
+#: hypha/apply/activity/messaging.py:233
 #, python-brace-format
 msgid "Sent a determination. Outcome: {determination.clean_outcome}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:231
+#: hypha/apply/activity/messaging.py:235
 msgid "Invited to submit a proposal"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:235
+#: hypha/apply/activity/messaging.py:239
 msgid "Submitted a review"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:236
+#: hypha/apply/activity/messaging.py:240
 msgid "Opened the submission while still sealed"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:238
+#: hypha/apply/activity/messaging.py:242
 #, python-brace-format
 msgid ""
 "{user} {opinion.opinion_display}s with {opinion.review.author}s review of "
 "{source}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:239
+#: hypha/apply/activity/messaging.py:243
 msgid "Created"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:240
+#: hypha/apply/activity/messaging.py:244
 #, python-brace-format
 msgid "Progressed from {old_stage} to {source.status_display}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:242
+#: hypha/apply/activity/messaging.py:246
 msgid "Requested approval"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:243
+#: hypha/apply/activity/messaging.py:247
 #: hypha/apply/determinations/options.py:12
 msgid "Approved"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:244
+#: hypha/apply/activity/messaging.py:248
 #, python-brace-format
 msgid "Requested changes for acceptance: \"{comment}\""
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:245
+#: hypha/apply/activity/messaging.py:249
 #, python-brace-format
 msgid "Uploaded a {contract.state} contract"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:246
+#: hypha/apply/activity/messaging.py:250
 msgid "Approved contract"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:247
+#: hypha/apply/activity/messaging.py:251
 #, python-brace-format
 msgid "Updated Payment Request status to: {payment_request.status_display}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:248
+#: hypha/apply/activity/messaging.py:252
+#, python-brace-format
+msgid "Updated Invoice status to: {invoice.status_display}"
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:253
 msgid "Payment Request submitted"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:249
+#: hypha/apply/activity/messaging.py:254
+msgid "Invoice created"
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:255
 msgid "Submitted a report"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:279
+#: hypha/apply/activity/messaging.py:285
 msgid "Reviewers updated."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:281 hypha/apply/activity/messaging.py:351
-#: hypha/apply/activity/messaging.py:508
+#: hypha/apply/activity/messaging.py:287 hypha/apply/activity/messaging.py:357
+#: hypha/apply/activity/messaging.py:518
 msgid "Added:"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:285 hypha/apply/activity/messaging.py:355
-#: hypha/apply/activity/messaging.py:512
+#: hypha/apply/activity/messaging.py:291 hypha/apply/activity/messaging.py:361
+#: hypha/apply/activity/messaging.py:522
 msgid "Removed:"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:291
+#: hypha/apply/activity/messaging.py:297
 msgid "Batch Reviewers Updated."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:293
+#: hypha/apply/activity/messaging.py:299
 #, python-brace-format
 msgid "{user} as {name}."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:311
+#: hypha/apply/activity/messaging.py:317
 #, python-brace-format
 msgid "Successfully deleted submissions: {title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:315
+#: hypha/apply/activity/messaging.py:321
 #, python-brace-format
 msgid "Progressed from {old_display} to {new_display}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:349
+#: hypha/apply/activity/messaging.py:355
 msgid "Partners updated."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:362
+#: hypha/apply/activity/messaging.py:368
 #, python-brace-format
 msgid ""
 "Updated reporting frequency. New schedule is: {new_schedule} starting on "
 "{schedule_start}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:400
+#: hypha/apply/activity/messaging.py:406
 #, python-brace-format
 msgid "Screening status from {old_status} to {new_status}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:407
+#: hypha/apply/activity/messaging.py:413
 #, python-brace-format
 msgid ""
 "A new submission has been submitted for {source.page.title}: <{link}|{source."
 "title}>"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:408
+#: hypha/apply/activity/messaging.py:414
 #, python-brace-format
 msgid ""
 "The lead of <{link}|{source.title}> has been updated from {old_lead} to "
 "{source.lead} by {user}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:410
+#: hypha/apply/activity/messaging.py:416
 #, python-brace-format
 msgid ""
 "A new {comment.visibility} comment has been posted on <{link}|{source.title}"
 "> by {user}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:411 hypha/apply/activity/messaging.py:412
+#: hypha/apply/activity/messaging.py:417 hypha/apply/activity/messaging.py:418
 #, python-brace-format
 msgid "{user} has edited <{link}|{source.title}>"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:415
+#: hypha/apply/activity/messaging.py:421
 #, python-brace-format
 msgid "{user} has updated the partners on <{link}|{source.title}>"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:416
+#: hypha/apply/activity/messaging.py:422
 #, python-brace-format
 msgid ""
 "{user} has updated the status of <{link}|{source.title}>: {old_phase."
 "display_name} → {source.phase}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:420
+#: hypha/apply/activity/messaging.py:426
 #, python-brace-format
 msgid "A proposal has been submitted for review: <{link}|{source.title}>"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:421
+#: hypha/apply/activity/messaging.py:427
 #, python-brace-format
 msgid ""
 "<{link}|{source.title}> by {source.user} has been invited to submit a "
 "proposal"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:422
+#: hypha/apply/activity/messaging.py:428
 #, python-brace-format
 msgid ""
 "{user} has submitted a review for <{link}|{source.title}>. Outcome: {review."
 "outcome},  Score: {review.get_score_display}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:424
+#: hypha/apply/activity/messaging.py:430
 #, python-brace-format
 msgid "{user} has opened the sealed submission: <{link}|{source.title}>"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:425
+#: hypha/apply/activity/messaging.py:431
 #, python-brace-format
 msgid ""
 "{user} {opinion.opinion_display}s with {opinion.review.author}s review of "
 "{source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:427
+#: hypha/apply/activity/messaging.py:433
 #, python-brace-format
 msgid "{user} has deleted {source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:428
+#: hypha/apply/activity/messaging.py:434
 #, python-brace-format
 msgid "{user} has deleted {review.author} review for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:429
+#: hypha/apply/activity/messaging.py:435
 #, python-brace-format
 msgid "{user} has created a Project: <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:430
+#: hypha/apply/activity/messaging.py:436
 #, python-brace-format
 msgid ""
 "The lead of project <{link}|{source.title}> has been updated from {old_lead} "
 "to {source.lead} by {user}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:431
+#: hypha/apply/activity/messaging.py:437
 #, python-brace-format
 msgid "{user} has edited {review.author} review for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:432
+#: hypha/apply/activity/messaging.py:438
 #, python-brace-format
 msgid "{user} has requested approval on project <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:433
+#: hypha/apply/activity/messaging.py:439
 #, python-brace-format
 msgid "{user} has approved project <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:434
+#: hypha/apply/activity/messaging.py:440
 #, python-brace-format
 msgid ""
 "{user} has requested changes for project acceptance on <{link}|{source.title}"
 ">."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:435
+#: hypha/apply/activity/messaging.py:441
 #, python-brace-format
 msgid "{user} has uploaded a contract for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:436
+#: hypha/apply/activity/messaging.py:442
 #, python-brace-format
 msgid "{user} has approved contract for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:437
+#: hypha/apply/activity/messaging.py:443
 #, python-brace-format
 msgid "{user} has requested payment for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:438
+#: hypha/apply/activity/messaging.py:444
+#, python-brace-format
+msgid "{user} has created invoice for <{link}|{source.title}>."
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:445
 #, python-brace-format
 msgid ""
 "{user} has changed the status of <{link_related}|payment request> on <{link}|"
 "{source.title}> to {payment_request.status_display}."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:439
+#: hypha/apply/activity/messaging.py:446
+#, python-brace-format
+msgid ""
+"{user} has changed the status of <{link_related}|invoice> on <{link}|{source."
+"title}> to {invoice.status_display}."
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:447
 #, python-brace-format
 msgid "{user} has deleted payment request from <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:440
+#: hypha/apply/activity/messaging.py:448
+#, python-brace-format
+msgid "{user} has deleted invoice from <{link}|{source.title}>."
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:449
 #, python-brace-format
 msgid "{user} has updated payment request for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:441
+#: hypha/apply/activity/messaging.py:450
+#, python-brace-format
+msgid "{user} has updated invoice for <{link}|{source.title}>."
+msgstr ""
+
+#: hypha/apply/activity/messaging.py:451
 #, python-brace-format
 msgid "{user} has submitted a report for <{link}|{source.title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:505
+#: hypha/apply/activity/messaging.py:515
 #, python-brace-format
 msgid "{user} has updated the reviewers on <{link}|{title}>."
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:521
+#: hypha/apply/activity/messaging.py:531
 #, python-brace-format
 msgid "{user} has batch changed lead to {new_lead} on: {submissions_text}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:532 hypha/apply/activity/messaging.py:947
+#: hypha/apply/activity/messaging.py:542 hypha/apply/activity/messaging.py:972
 #, python-brace-format
 msgid "{user} as {name},"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:537
+#: hypha/apply/activity/messaging.py:547
 #, python-brace-format
 msgid ""
 "{user} has batch added {reviewers_text} as reviewers on: {submissions_text}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:555
+#: hypha/apply/activity/messaging.py:565
 #, python-brace-format
 msgid "{user} has transitioned the following submissions: {submissions_links}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:565
+#: hypha/apply/activity/messaging.py:575
 #, python-brace-format
 msgid ""
 "A determination for <{link}|{submission_title}> was sent by email. Outcome: "
 "{determination_outcome}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:572
+#: hypha/apply/activity/messaging.py:582
 #, python-brace-format
 msgid ""
 "A determination for <{link}|{submission_title}> was saved without sending an "
 "email. Outcome: {determination_outcome}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:589
+#: hypha/apply/activity/messaging.py:599
 #, python-brace-format
 msgid "Determinations of {outcome} was sent for: {submissions_links}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:598
+#: hypha/apply/activity/messaging.py:608
 #, python-brace-format
 msgid "{user} has deleted submissions: {title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:612
+#: hypha/apply/activity/messaging.py:622
 #, python-brace-format
 msgid ""
 "<{link}|{title}> is ready for review. The following are assigned as "
 "reviewers: {reviewers}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:725
+#: hypha/apply/activity/messaging.py:737
 #, python-brace-format
 msgid "Application ready to review: {source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:727
+#: hypha/apply/activity/messaging.py:739
 msgid "Multiple applications are now ready for your review"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:729
+#: hypha/apply/activity/messaging.py:741
 #, python-brace-format
 msgid "Reminder: Application ready to review: {source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:732
+#: hypha/apply/activity/messaging.py:744
 #, python-brace-format
 msgid "Your application to {org_long_name}: {source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:734
+#: hypha/apply/activity/messaging.py:746
 #, python-brace-format
 msgid "Your {org_long_name} Project: {source.title}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:937
+#: hypha/apply/activity/messaging.py:962
 msgid "Successfully uploaded document"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:938
+#: hypha/apply/activity/messaging.py:963
 msgid "Successfully removed document"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:941
+#: hypha/apply/activity/messaging.py:966
 msgid "Reminder created"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:942
+#: hypha/apply/activity/messaging.py:967
 msgid "Reminder deleted"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:953
+#: hypha/apply/activity/messaging.py:978
 #, python-brace-format
 msgid "Batch reviewers added: {reviewers_text} to "
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:958
+#: hypha/apply/activity/messaging.py:983
 #, python-brace-format
 msgid ""
 "Successfully updated reporting frequency. They will now report "
 "{new_schedule} starting on {schedule_start}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:962
+#: hypha/apply/activity/messaging.py:987
 #, python-brace-format
 msgid "Successfully skipped a Report for {start_date} to {end_date}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:964
+#: hypha/apply/activity/messaging.py:989
 #, python-brace-format
 msgid "Successfully unskipped a Report for {start_date} to {end_date}"
 msgstr ""
 
-#: hypha/apply/activity/messaging.py:983
+#: hypha/apply/activity/messaging.py:1008
 #, python-brace-format
 msgid "Successfully determined as {outcome}: "
 msgstr ""
@@ -487,6 +518,8 @@ msgstr ""
 
 #: hypha/apply/activity/templates/messages/email/batch_ready_to_review.html:10
 #: hypha/apply/activity/templates/messages/email/contract_uploaded.html:7
+#: hypha/apply/activity/templates/messages/email/invoice_status_updated.html:14
+#: hypha/apply/activity/templates/messages/email/invoice_updated.html:9
 #: hypha/apply/activity/templates/messages/email/partners_update_applicant.html:9
 #: hypha/apply/activity/templates/messages/email/partners_update_partner.html:9
 #: hypha/apply/activity/templates/messages/email/payment_request_status_updated.html:14
@@ -500,6 +533,8 @@ msgstr ""
 
 #: hypha/apply/activity/templates/messages/email/batch_ready_to_review.html:11
 #: hypha/apply/activity/templates/messages/email/contract_uploaded.html:8
+#: hypha/apply/activity/templates/messages/email/invoice_status_updated.html:15
+#: hypha/apply/activity/templates/messages/email/invoice_updated.html:10
 #: hypha/apply/activity/templates/messages/email/partners_update_applicant.html:10
 #: hypha/apply/activity/templates/messages/email/partners_update_partner.html:10
 #: hypha/apply/activity/templates/messages/email/payment_request_status_updated.html:15
@@ -571,6 +606,25 @@ msgstr ""
 msgid "Here is the link to start creating your proposal"
 msgstr ""
 
+#: hypha/apply/activity/templates/messages/email/invoice_status_updated.html:5
+#: hypha/apply/activity/templates/messages/email/invoice_updated.html:6
+#, python-format
+msgid ""
+"An %(ORG_SHORT_NAME)s staff member has updated your invoice for "
+"%(source.title)s for period %(invoice.date_from)s to %(invoice.date_to)s."
+msgstr ""
+
+#: hypha/apply/activity/templates/messages/email/invoice_status_updated.html:6
+#: hypha/apply/activity/templates/messages/email/invoice_updated.html:7
+#, python-format
+msgid "It is now %(invoice.get_status_display)s."
+msgstr ""
+
+#: hypha/apply/activity/templates/messages/email/invoice_status_updated.html:9
+#: hypha/apply/activity/templates/messages/email/payment_request_status_updated.html:9
+msgid "The staff member left this comment"
+msgstr ""
+
 #: hypha/apply/activity/templates/messages/email/partners_update_applicant.html:5
 msgid "New partner(s) has been added to your submission."
 msgstr ""
@@ -598,10 +652,6 @@ msgstr ""
 msgid "It is now %(payment_request.get_status_display)s."
 msgstr ""
 
-#: hypha/apply/activity/templates/messages/email/payment_request_status_updated.html:9
-msgid "The staff member left this comment"
-msgstr ""
-
 #: hypha/apply/activity/templates/messages/email/ready_to_review.html:6
 msgid "This application is awaiting your review."
 msgstr ""
@@ -897,12 +947,12 @@ msgid "Draft"
 msgstr ""
 
 #: hypha/apply/determinations/models.py:107
-#: hypha/apply/projects/models/vendor.py:48 hypha/apply/review/models.py:163
+#: hypha/apply/projects/models/vendor.py:49 hypha/apply/review/models.py:163
 msgid "Creation time"
 msgstr ""
 
 #: hypha/apply/determinations/models.py:108
-#: hypha/apply/projects/models/vendor.py:49 hypha/apply/review/models.py:164
+#: hypha/apply/projects/models/vendor.py:50 hypha/apply/review/models.py:164
 msgid "Update time"
 msgstr ""
 
@@ -1014,8 +1064,8 @@ msgstr ""
 msgid "Requested amount"
 msgstr ""
 
-#: hypha/apply/funds/blocks.py:65 hypha/apply/projects/models/vendor.py:19
-#: hypha/apply/projects/models/vendor.py:56
+#: hypha/apply/funds/blocks.py:65 hypha/apply/projects/models/vendor.py:20
+#: hypha/apply/projects/models/vendor.py:57
 msgid "Address"
 msgstr ""
 
@@ -1031,7 +1081,8 @@ msgid "Take action"
 msgstr ""
 
 #: hypha/apply/funds/forms.py:143 hypha/apply/projects/filters.py:28
-#: hypha/apply/projects/filters.py:42 hypha/apply/projects/tables.py:49
+#: hypha/apply/projects/filters.py:38 hypha/apply/projects/filters.py:52
+#: hypha/apply/projects/tables.py:49 hypha/apply/projects/tables.py:90
 msgid "Lead"
 msgstr ""
 
@@ -1057,7 +1108,8 @@ msgid "Meta terms are hierarchical in nature."
 msgstr ""
 
 #: hypha/apply/funds/models/__init__.py:17 hypha/apply/funds/tables.py:74
-#: hypha/apply/projects/tables.py:48 hypha/apply/projects/tables.py:80
+#: hypha/apply/projects/tables.py:48 hypha/apply/projects/tables.py:89
+#: hypha/apply/projects/tables.py:121
 msgid "Fund"
 msgstr ""
 
@@ -1177,11 +1229,13 @@ msgid "Confirmation email"
 msgstr ""
 
 #: hypha/apply/funds/tables.py:71 hypha/apply/projects/tables.py:21
+#: hypha/apply/projects/tables.py:81
 msgid "Submitted"
 msgstr ""
 
 #: hypha/apply/funds/tables.py:72 hypha/apply/projects/filters.py:27
-#: hypha/apply/projects/filters.py:43 hypha/apply/projects/tables.py:79
+#: hypha/apply/projects/filters.py:37 hypha/apply/projects/filters.py:53
+#: hypha/apply/projects/tables.py:120
 #: hypha/apply/users/templates/wagtailusers/users/list.html:23
 #: hypha/public/partner/tables.py:51 hypha/public/partner/tables.py:97
 msgid "Status"
@@ -1223,8 +1277,8 @@ msgstr ""
 
 #: hypha/apply/funds/tables.py:257 hypha/apply/funds/tables.py:322
 #: hypha/apply/funds/tables.py:414 hypha/apply/funds/tables.py:448
-#: hypha/apply/projects/filters.py:26 hypha/apply/projects/filters.py:41
-#: hypha/public/home/models.py:125
+#: hypha/apply/projects/filters.py:26 hypha/apply/projects/filters.py:36
+#: hypha/apply/projects/filters.py:51 hypha/public/home/models.py:125
 msgid "Funds"
 msgstr ""
 
@@ -1563,7 +1617,7 @@ msgid ""
 "usages and try again!."
 msgstr ""
 
-#: hypha/apply/projects/filters.py:83
+#: hypha/apply/projects/filters.py:93
 msgid "Reporting Period"
 msgstr ""
 
@@ -1591,19 +1645,19 @@ msgstr ""
 msgid "Proposed End Date must be after Proposed Start Date"
 msgstr ""
 
-#: hypha/apply/projects/models/vendor.py:45
+#: hypha/apply/projects/models/vendor.py:46
 msgid "Yes, the account belongs to the organisation above"
 msgstr ""
 
-#: hypha/apply/projects/models/vendor.py:46
+#: hypha/apply/projects/models/vendor.py:47
 msgid "No, it is a personal bank account"
 msgstr ""
 
-#: hypha/apply/projects/tables.py:16
+#: hypha/apply/projects/tables.py:16 hypha/apply/projects/tables.py:76
 msgid "Invoice reference"
 msgstr ""
 
-#: hypha/apply/projects/tables.py:22
+#: hypha/apply/projects/tables.py:22 hypha/apply/projects/tables.py:82
 #, python-brace-format
 msgid "Value ({currency})"
 msgstr ""
@@ -1616,15 +1670,15 @@ msgstr ""
 msgid "Period End"
 msgstr ""
 
-#: hypha/apply/projects/tables.py:81
+#: hypha/apply/projects/tables.py:122
 msgid "Reporting"
 msgstr ""
 
-#: hypha/apply/projects/tables.py:83
+#: hypha/apply/projects/tables.py:124
 msgid "End Date"
 msgstr ""
 
-#: hypha/apply/projects/tables.py:84
+#: hypha/apply/projects/tables.py:125
 #, python-brace-format
 msgid "Fund Allocation ({currency})"
 msgstr ""