Skip to content

Commit

Permalink
Add status update batch action to invoices table (HyphaApp#3756)
Browse files Browse the repository at this point in the history
Fixes part of HyphaApp#3537
  • Loading branch information
sandeepsajan0 authored and wes-otf committed Apr 1, 2024
1 parent bdb1c2f commit 00a1c34
Show file tree
Hide file tree
Showing 15 changed files with 435 additions and 77 deletions.
17 changes: 17 additions & 0 deletions hypha/apply/activity/adapters/activity_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from hypha.apply.activity.options import MESSAGES
from hypha.apply.projects.utils import (
get_invoice_public_status,
get_invoice_status_display_value,
get_project_public_status,
get_project_status_display_value,
)
Expand Down Expand Up @@ -66,6 +67,7 @@ class ActivityAdapter(AdapterBase):
MESSAGES.DISABLED_REPORTING: _("Reporting disabled"),
MESSAGES.BATCH_DELETE_SUBMISSION: "handle_batch_delete_submission",
MESSAGES.BATCH_ARCHIVE_SUBMISSION: "handle_batch_archive_submission",
MESSAGES.BATCH_UPDATE_INVOICE_STATUS: "handle_batch_update_invoice_status",
MESSAGES.ARCHIVE_SUBMISSION: _(
"{user} has archived the submission: {source.title}"
),
Expand Down Expand Up @@ -164,6 +166,21 @@ def handle_batch_archive_submission(self, sources, **kwargs):
title=submissions_text
)

def handle_batch_update_invoice_status(self, sources, invoices, **kwargs):
invoice_numbers = ", ".join(
[
invoice.invoice_number if invoice.invoice_number else ""
for invoice in invoices
]
)
invoice_status = invoices[0].status if invoices else ""
return _(
"Successfully updated status to {invoice_status} for invoices: {invoice_numbers}"
).format(
invoice_status=get_invoice_status_display_value(invoice_status),
invoice_numbers=invoice_numbers,
)

def handle_paf_assignment(self, source, paf_approvals, **kwargs):
if hasattr(paf_approvals, "__iter__"): # paf_approvals has to be iterable
users = ", ".join(
Expand Down
1 change: 1 addition & 0 deletions hypha/apply/activity/adapters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
MESSAGES.CREATE_REMINDER: "reminder",
MESSAGES.DELETE_REMINDER: "reminder",
MESSAGES.REVIEW_REMINDER: "reminder",
MESSAGES.BATCH_UPDATE_INVOICE_STATUS: "invoices",
}


Expand Down
84 changes: 84 additions & 0 deletions hypha/apply/activity/migrations/0080_alter_event_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Generated by Django 4.2.9 on 2024-02-08 04:44

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("activity", "0079_alter_activity_visibility"),
]

operations = [
migrations.AlterField(
model_name="event",
name="type",
field=models.CharField(
choices=[
("UPDATE_LEAD", "updated lead"),
("BATCH_UPDATE_LEAD", "batch updated lead"),
("EDIT_SUBMISSION", "edited submission"),
("APPLICANT_EDIT", "edited applicant"),
("NEW_SUBMISSION", "submitted new submission"),
("DRAFT_SUBMISSION", "submitted new draft submission"),
("SCREENING", "screened"),
("TRANSITION", "transitioned"),
("BATCH_TRANSITION", "batch transitioned"),
("DETERMINATION_OUTCOME", "sent determination outcome"),
("BATCH_DETERMINATION_OUTCOME", "sent batch determination outcome"),
("INVITED_TO_PROPOSAL", "invited to proposal"),
("REVIEWERS_UPDATED", "updated reviewers"),
("BATCH_REVIEWERS_UPDATED", "batch updated reviewers"),
("PARTNERS_UPDATED", "updated partners"),
("PARTNERS_UPDATED_PARTNER", "partners updated partner"),
("READY_FOR_REVIEW", "marked ready for review"),
("BATCH_READY_FOR_REVIEW", "marked batch ready for review"),
("NEW_REVIEW", "added new review"),
("COMMENT", "added comment"),
("PROPOSAL_SUBMITTED", "submitted proposal"),
("OPENED_SEALED", "opened sealed submission"),
("REVIEW_OPINION", "reviewed opinion"),
("DELETE_SUBMISSION", "deleted submission"),
("DELETE_REVIEW", "deleted review"),
("DELETE_REVIEW_OPINION", "deleted review opinion"),
("CREATED_PROJECT", "created project"),
("UPDATED_VENDOR", "updated contracting information"),
("UPDATE_PROJECT_LEAD", "updated project lead"),
("EDIT_REVIEW", "edited review"),
("SEND_FOR_APPROVAL", "sent for approval"),
("APPROVE_PROJECT", "approved project"),
("ASSIGN_PAF_APPROVER", "assign paf approver"),
("APPROVE_PAF", "approved paf"),
("PROJECT_TRANSITION", "transitioned project"),
("REQUEST_PROJECT_CHANGE", "requested project change"),
("SUBMIT_CONTRACT_DOCUMENTS", "submitted contract documents"),
("UPLOAD_DOCUMENT", "uploaded document to project"),
("REMOVE_DOCUMENT", "removed document from project"),
("UPLOAD_CONTRACT", "uploaded contract to project"),
("APPROVE_CONTRACT", "approved contract"),
("CREATE_INVOICE", "created invoice for project"),
("UPDATE_INVOICE_STATUS", "updated invoice status"),
("APPROVE_INVOICE", "approve invoice"),
("DELETE_INVOICE", "deleted invoice"),
("SENT_TO_COMPLIANCE", "sent project to compliance"),
("UPDATE_INVOICE", "updated invoice"),
("SUBMIT_REPORT", "submitted report"),
("SKIPPED_REPORT", "skipped report"),
("REPORT_FREQUENCY_CHANGED", "changed report frequency"),
("DISABLED_REPORTING", "disabled reporting"),
("REPORT_NOTIFY", "notified report"),
("CREATE_REMINDER", "created reminder"),
("DELETE_REMINDER", "deleted reminder"),
("REVIEW_REMINDER", "reminder to review"),
("BATCH_DELETE_SUBMISSION", "batch deleted submissions"),
("BATCH_ARCHIVE_SUBMISSION", "batch archive submissions"),
("BATCH_INVOICE_STATUS_UPDATE", "batch update invoice status"),
("STAFF_ACCOUNT_CREATED", "created new account"),
("STAFF_ACCOUNT_EDITED", "edited account"),
("ARCHIVE_SUBMISSION", "archived submission"),
("UNARCHIVE_SUBMISSION", "unarchived submission"),
],
max_length=50,
verbose_name="verb",
),
),
]
4 changes: 4 additions & 0 deletions hypha/apply/activity/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class MESSAGES(TextChoices):
"BATCH_ARCHIVE_SUBMISSION",
_("batch archive submissions"),
)
BATCH_UPDATE_INVOICE_STATUS = (
"BATCH_INVOICE_STATUS_UPDATE",
_("batch update invoice status"),
)
STAFF_ACCOUNT_CREATED = "STAFF_ACCOUNT_CREATED", _("created new account")
STAFF_ACCOUNT_EDITED = "STAFF_ACCOUNT_EDITED", _("edited account")
ARCHIVE_SUBMISSION = "ARCHIVE_SUBMISSION", _("archived submission")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,42 @@ <h2 class="text-xl mb-2">
<div class="actions-bar__inner actions-bar__inner--batch-actions">
<p class="actions-bar__total"><span class="js-total-actions">0</span> {% trans "Selected" %}</p>

<button
data-fancybox
type="button"
data-src="#batch-progress"
class="button button--action js-batch-button js-batch-progress"
>
{% heroicon_mini "arrows-right-left" class="inline" aria_hidden=true %}
{% trans "Status" %}
</button>
{% if not project_actions %}
<button
data-fancybox
type="button"
data-src="#batch-progress"
class="button button--action js-batch-button js-batch-progress"
>
{% heroicon_mini "arrows-right-left" class="inline" aria_hidden=true %}
{% trans "Status" %}
</button>

<button data-fancybox data-src="#batch-update-lead" class="button button--action js-batch-button" type="button">
{% heroicon_micro "user-plus" class="inline" aria_hidden=true %}
{% trans "Lead" %}
</button>
<button data-fancybox data-src="#batch-update-lead" class="button button--action js-batch-button" type="button">
{% heroicon_micro "user-plus" class="inline" aria_hidden=true %}
{% trans "Lead" %}
</button>

<button data-fancybox data-src="#batch-update-reviewers" class="button button--action js-batch-button" type="button">
{% heroicon_micro "user-plus" class="inline" aria_hidden=true %}
{% trans "Reviewers" %}
</button>
<button data-fancybox data-src="#batch-update-reviewers" class="button button--action js-batch-button" type="button">
{% heroicon_micro "user-plus" class="inline" aria_hidden=true %}
{% trans "Reviewers" %}
</button>

<button data-fancybox data-src="#batch-delete-submission" class="button button--action js-batch-button" type="button">
{% heroicon_micro "trash" class="inline" aria_hidden=true %}
{% trans "Delete" %}
</button>
<button data-fancybox data-src="#batch-delete-submission" class="button button--action js-batch-button" type="button">
{% heroicon_micro "trash" class="inline" aria_hidden=true %}
{% trans "Delete" %}
</button>

{% if can_bulk_archive %}
<button data-fancybox data-src="#batch-archive-submission" class="button button--action js-batch-button" type="button">
{% heroicon_micro "archive-box-arrow-down" class="inline" aria_hidden=true %}
{% trans "Archive" %}
{% if can_bulk_archive %}
<button data-fancybox data-src="#batch-archive-submission" class="button button--action js-batch-button" type="button">
{% heroicon_micro "archive-box-arrow-down" class="inline" aria_hidden=true %}
{% trans "Archive" %}
</button>
{% endif %}
{% elif invoice_actions %}
<button data-fancybox data-src="#batch_update_invoice_status" class="button button--action js-batch-button js-batch-invoice-progress" type="button">
{% heroicon_mini "arrows-right-left" class="inline" aria_hidden=true %}
{% trans "Status" %}
</button>
{% endif %}
</div>
Expand Down Expand Up @@ -124,4 +131,9 @@ <h2 class="text-xl mb-2">
{% include "funds/includes/batch_progress_form.html" %}
{% include "funds/includes/batch_delete_submission_form.html" %}
{% include "funds/includes/batch_archive_submission_form.html" %}
{% if project_actions %}
{% if invoice_actions %}
{% include "application_projects/includes/batch_invoice_status_update.html" %}
{% endif %}
{% endif %}
{% endif %}
2 changes: 2 additions & 0 deletions hypha/apply/projects/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .payment import (
BatchUpdateInvoiceStatusForm,
ChangeInvoiceStatusForm,
CreateInvoiceForm,
EditInvoiceForm,
Expand Down Expand Up @@ -39,6 +40,7 @@
"ApproveContractForm",
"ApproversForm",
"AssignApproversForm",
"BatchUpdateInvoiceStatusForm",
"ChangePAFStatusForm",
"ChangeProjectStatusForm",
"CreateProjectForm",
Expand Down
118 changes: 75 additions & 43 deletions hypha/apply/projects/forms/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
invoice_status_user_choices,
)
from ..models.project import PacketFile
from ..utils import get_invoice_status_display_value


def filter_request_choices(choices, user_choices):
Expand All @@ -36,6 +37,50 @@ def filter_request_choices(choices, user_choices):
]


def get_invoice_possible_transition_for_user(user, invoice):
user_choices = invoice_status_user_choices(user)
possible_status_transitions_lut = {
SUBMITTED: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, APPROVED_BY_STAFF, DECLINED], user_choices
),
RESUBMITTED: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, APPROVED_BY_STAFF, DECLINED], user_choices
),
CHANGES_REQUESTED_BY_STAFF: filter_request_choices([DECLINED], user_choices),
APPROVED_BY_STAFF: filter_request_choices(
[
CHANGES_REQUESTED_BY_FINANCE,
APPROVED_BY_FINANCE,
],
user_choices,
),
CHANGES_REQUESTED_BY_FINANCE: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, DECLINED], user_choices
),
APPROVED_BY_FINANCE: filter_request_choices([PAID], user_choices),
PAID: filter_request_choices([PAYMENT_FAILED], user_choices),
PAYMENT_FAILED: filter_request_choices([PAID], user_choices),
}
if settings.INVOICE_EXTENDED_WORKFLOW:
possible_status_transitions_lut.update(
{
CHANGES_REQUESTED_BY_FINANCE_2: filter_request_choices(
[
CHANGES_REQUESTED_BY_FINANCE,
APPROVED_BY_FINANCE,
],
user_choices,
),
APPROVED_BY_FINANCE: filter_request_choices(
[CHANGES_REQUESTED_BY_FINANCE_2, APPROVED_BY_FINANCE_2],
user_choices,
),
APPROVED_BY_FINANCE_2: filter_request_choices([PAID], user_choices),
}
)
return possible_status_transitions_lut.get(invoice.status, [])


class ChangeInvoiceStatusForm(forms.ModelForm):
name_prefix = "change_invoice_status_form"

Expand All @@ -47,49 +92,10 @@ def __init__(self, instance, user, *args, **kwargs):
super().__init__(*args, **kwargs, instance=instance)
self.initial["comment"] = ""
status_field = self.fields["status"]
user_choices = invoice_status_user_choices(user)
possible_status_transitions_lut = {
SUBMITTED: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, APPROVED_BY_STAFF, DECLINED], user_choices
),
RESUBMITTED: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, APPROVED_BY_STAFF, DECLINED], user_choices
),
CHANGES_REQUESTED_BY_STAFF: filter_request_choices(
[DECLINED], user_choices
),
APPROVED_BY_STAFF: filter_request_choices(
[
CHANGES_REQUESTED_BY_FINANCE,
APPROVED_BY_FINANCE,
],
user_choices,
),
CHANGES_REQUESTED_BY_FINANCE: filter_request_choices(
[CHANGES_REQUESTED_BY_STAFF, DECLINED], user_choices
),
APPROVED_BY_FINANCE: filter_request_choices([PAID], user_choices),
PAID: filter_request_choices([PAYMENT_FAILED], user_choices),
PAYMENT_FAILED: filter_request_choices([PAID], user_choices),
}
if settings.INVOICE_EXTENDED_WORKFLOW:
possible_status_transitions_lut.update(
{
CHANGES_REQUESTED_BY_FINANCE_2: filter_request_choices(
[
CHANGES_REQUESTED_BY_FINANCE,
APPROVED_BY_FINANCE,
],
user_choices,
),
APPROVED_BY_FINANCE: filter_request_choices(
[CHANGES_REQUESTED_BY_FINANCE_2, APPROVED_BY_FINANCE_2],
user_choices,
),
APPROVED_BY_FINANCE_2: filter_request_choices([PAID], user_choices),
}
)
status_field.choices = possible_status_transitions_lut.get(instance.status, [])

status_field.choices = get_invoice_possible_transition_for_user(
user, invoice=instance
)


class InvoiceBaseForm(forms.ModelForm):
Expand Down Expand Up @@ -199,3 +205,29 @@ def clean_document(self):
@transaction.atomic()
def save(self, *args, **kwargs):
return super().save(*args, **kwargs)


class BatchUpdateInvoiceStatusForm(forms.Form):
invoice_action = forms.ChoiceField(label=_("Status"))
invoices = forms.CharField(
widget=forms.HiddenInput(attrs={"class": "js-invoices-id"})
)

def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super().__init__(*args, **kwargs)
if self.user.is_apply_staff:
self.fields["invoice_action"].choices = [
(DECLINED, get_invoice_status_display_value(DECLINED))
]
elif self.user.is_finance:
self.fields["invoice_action"].choices = [
(DECLINED, get_invoice_status_display_value(DECLINED)),
(PAID, get_invoice_status_display_value(PAID)),
(PAYMENT_FAILED, get_invoice_status_display_value(PAYMENT_FAILED)),
]

def clean_invoices(self):
value = self.cleaned_data["invoices"]
invoice_ids = [int(invoice) for invoice in value.split(",")]
return Invoice.objects.filter(id__in=invoice_ids)
6 changes: 6 additions & 0 deletions hypha/apply/projects/service_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,9 @@ def handle_tasks_on_invoice_update(old_status, invoice):
),
related_obj=invoice,
)


def batch_update_invoices_status(invoices, user, status):
for invoice in invoices:
invoice.status = status
invoice.save(update_fields=["status"])
Loading

0 comments on commit 00a1c34

Please sign in to comment.