Skip to content

Commit

Permalink
Invoice status updating improvements (#4083)
Browse files Browse the repository at this point in the history
Fixes #3208. Implemented an improvement for the picklist where radio
buttons will be shown when there are less than 4 options. Otherwise the
prompt will be like it was before.

This PR also implements HTMX for the invoice updating prompt, allowing
invoices in table to be updated w/o refreshing the pages

Co-authored-by: Fredrik Jonsson <[email protected]>
  • Loading branch information
wes-otf and frjo authored Nov 1, 2024
1 parent 05f2a43 commit 11f8bb6
Show file tree
Hide file tree
Showing 15 changed files with 459 additions and 238 deletions.
2 changes: 1 addition & 1 deletion hypha/apply/activity/adapters/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,4 @@ def get_users_for_groups(groups, user_queryset=None, exact_match=False):
user_queryset = user_queryset.filter(groups__name=groups.pop().name)
return get_users_for_groups(groups, user_queryset=user_queryset)
else:
return user_queryset
return user_queryset if user_queryset is not None else set()
17 changes: 12 additions & 5 deletions hypha/apply/projects/forms/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.files.base import ContentFile
from django.db import transaction
from django.db.models.fields.files import FieldFile
from django.forms.widgets import RadioSelect
from django.utils.translation import gettext_lazy as _
from django_file_form.forms import FileFormMixin

Expand Down Expand Up @@ -69,16 +70,22 @@ class ChangeInvoiceStatusForm(forms.ModelForm):
class Meta:
fields = ["status", "paid_date", "comment"]
model = Invoice
widgets = {"status": RadioSelect}

def __init__(self, instance, user, *args, **kwargs):
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
instance = kwargs.pop("instance")
super().__init__(*args, **kwargs, instance=instance)
self.initial["comment"] = ""
status_field = self.fields["status"]

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

self.initial["comment"] = ""
if len(invoice_choices) > 4:
self.fields["status"] = forms.TypedChoiceField(choices=invoice_choices)
else:
self.fields["status"].choices = invoice_choices


class InvoiceBaseForm(forms.ModelForm):
class Meta:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,95 +18,13 @@
<tr>
<th class="data-block__table-date">{% trans "Date submitted" %}</th>
<th class="min-w-[180px] w-[15%]">{% trans "Invoice date" %}</th>
<th class="data-block__table-amount">{% trans "Invoice No." %}</th>
<th class="data-block__table-amount">{% trans "Invoice no." %}</th>
<th class="data-block__table-status">{% trans "Status" %}</th>
<th class="data-block__table-update"></th>
</tr>
</thead>
<tbody>
{% for invoice in object.invoices.not_rejected %}
{% display_invoice_status_for_user user invoice as invoice_status %}
<tr>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Date submitted" %}: </span>{{ invoice.requested_at.date }}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Invoice date" %}: </span>{% if invoice.invoice_date %}{{ invoice.invoice_date }}{% else %} {{ invoice.requested_at.date }} {% endif %}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Invoice number" %}: </span>{{ invoice.invoice_number }}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Status" %}: </span>{{ invoice_status }}</td>
<td class="flex flex-wrap justify-center py-4 px-0 gap-2 xl:flex-nowrap">
<a class="data-block__action-icon-link" href="{{ invoice.get_absolute_url }}" >
{% heroicon_micro "eye" aria_hidden=true class="me-1" %}
{% trans "View" %}
</a>
{% can_edit invoice user as user_can_edit_request %}
{% if user_can_edit_request %}
<a class="data-block__action-icon-link" href="{% url "apply:projects:invoice-edit" pk=invoice.project.pk invoice_pk=invoice.pk %}">
{% heroicon_micro "pencil-square" aria_hidden=true class="me-1" %}
{% trans "Edit" %}
</a>
{% endif %}

{% can_delete invoice user as user_can_delete_request %}
{% if user.is_applicant and user_can_delete_request %}
<a class="data-block__action-icon-link text-red-500" href="{% url 'apply:projects:invoice-delete' pk=invoice.project.pk invoice_pk=invoice.pk %}">
{% heroicon_micro "trash" aria_hidden=true class="me-1" %}
{% trans "Delete" %}
</a>
{% endif %}
{% can_change_status invoice user as can_change_invoice_status %}
{% if can_change_invoice_status %}
<a
data-fancybox
data-src="#change-invoice-status-{{ invoice.id }}"
id="update_invoice_status-{{ invoice.id }}"
class="data-block__button button button--primary"
href="#"
>
{% trans "Update Status" %}
</a>
<div class="modal" id="change-invoice-status-{{ invoice.id }}">
{% get_invoice_form invoice user as invoice_form %}
{% get_invoice_form_id invoice_form invoice as invoice_form_id %}
<h4 class="modal__project-header-bar">{% trans "Update Invoice status" %}</h4>
<p>{% trans "Current status" %}: {{ invoice_status }}</p>
{% trans "Update Status" as update %}
{% include 'funds/includes/delegated_form_base.html' with form=invoice_form value=update action=invoice.get_absolute_url form_id=invoice_form_id %}
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const invoice_form = document.querySelector('[id^={{ invoice_form_id }}')
const invoice_status = invoice_form.querySelector('#id_status');
const paid_field = invoice_form.querySelector('.id_paid_date');
var paid_date = invoice_form.querySelector('#id_paid_date');

function updatePaidDate(){
if (invoice_status.value === 'paid') {
paid_field.style.display = 'block';
if (!paid_date.value) {
// Get today's date
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(now.getDate()).padStart(2, '0');

// Format the date as YYYY-MM-DD
const today = `${year}-${month}-${day}`;

paid_date.value = today;
}
} else {
paid_field.style.display = 'none';
paid_date.value = '';
}
}

updatePaidDate();

invoice_status.onchange = updatePaidDate;
});
</script>
{% endif %}
</td>
</tr>
{% endfor %}
<tbody hx-get="{% url 'apply:projects:partial-invoices-status' pk=object.pk %}" hx-trigger="invoicesUpdated from:body">
{% include "application_projects/partials/invoice_status_table.html" with invoices=object.invoices.not_rejected rejected=False %}
</tbody>
</table>
{% else %}
Expand All @@ -123,27 +41,13 @@ <h4 class="modal__project-header-bar">{% trans "Update Invoice status" %}</h4>
<tr>
<th class="data-block__table-date">{% trans "Date submitted" %}</th>
<th class="min-w-[180px] w-[15%]">{% trans "Invoice date" %}</th>
<th class="data-block__table-amount">{% trans "Invoice number" %}</th>
<th class="data-block__table-amount">{% trans "Invoice no." %}</th>
<th class="data-block__table-status">{% trans "Status" %}</th>
<th class="data-block__table-update"></th>
</tr>
</thead>
<tbody>
{% for invoice in object.invoices.rejected %}
{% display_invoice_status_for_user user invoice as invoice_status %}
<tr>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Date submitted" %}: </span>{{ invoice.requested_at.date }}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Invoice date" %}: </span>{% if invoice.invoice_date %}{{ invoice.invoice_date }}{% else %} {{ invoice.requested_at.date }} {% endif %}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Invoice number" %}: </span>{{ invoice.invoice_number }}</td>
<td class="py-4 px-2"><span class="data-block__mobile-label">{% trans "Status" %}: </span>{{ invoice_status }}</td>
<td class="flex justify-end py-4 px-0">
<a class="data-block__action-icon-link" href="{{ invoice.get_absolute_url }}" >
{% heroicon_mini "eye" size=16 aria_hidden=true class="me-1" %}
{% trans "View" %}
</a>
</td>
</tr>
{% endfor %}
<tbody id="rejected-invoice-table" hx-get="{% url 'apply:projects:partial-rejected-invoices-status' pk=object.pk %}" hx-trigger="rejectedInvoicesUpdated from:body">
{% include "application_projects/partials/invoice_status_table.html" with invoices=object.invoices.rejected rejected=True %}
</tbody>
</table>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% load i18n %}

{% modal_title %}Update Invoice Status{% endmodal_title %}

<form class="p-4"
method="POST"
action="{{ request.path }}"
hx-post="{{ request.path }}"
>
<p class="mt-0 text-fg-muted">{% trans "Current status" %}: {{ invoice_status }}</p>

{% include 'funds/includes/dialog_form_base.html' with form=form value=value form_id=form_id %}
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,8 @@
{% endif %}
{% endblock %}

{% 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"
{% endif %}
id="update_invoice_status"
class="button button--bottom-space button--primary button--full-width{% if not user_can_change_status %} button--tooltip-disabled{% endif %}"
href="#"
>
{% trans "Update Invoice Status" %}
</a>
{% if user_can_change_status %}
<div class="modal" id="change-status">
<h4 class="modal__project-header-bar">{% trans "Update Invoice status" %}</h4>
<p>{% trans "Current status" %}: {{ object.status_display }}</p>
{% trans "Update Status" as update %}
{% 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/fancybox.css' %}">
{% endblock %}

{% block extra_js %}
{{ block.super }}
<script src="{% static 'js/jquery.fancybox.min.js' %}"></script>
<script src="{% static 'js/fancybox-global.js' %}"></script>
<script src="{% static 'js/deliverables.js' %}"></script>

<script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,9 @@
{% endif %}
<p class="card__text"><b>{% trans "Fund" %}:</b> {{ object.project.submission.page }}</p>
</div>
<div class="flex card card--solid" x-data="{ collapsed: true }">
<div class="flex-none">
<p><b>{% trans "Status" %}: </b></p>
</div>
<div class="ps-2">
{% extract_status latest_activity user as latest_activity_status %}
{% get_comment_for_invoice_action object latest_activity as latest_activity_comment %}
<p>{{ latest_activity_status }} {% if user.is_applicant and latest_activity.user != user %} ({{ ORG_SHORT_NAME }}){% else %}({{ latest_activity.user }}){% endif %}
<span class="text-gray-400">{{ latest_activity.timestamp }}</span>
{% if latest_activity_comment %}
<svg class="icon icon--request-changes"><use xlink:href="#request-changes"></use></svg>
<a href="{% url 'apply:projects:detail' pk=object.project.id %}#communications#{{ latest_activity_comment.id }}" class="font-bold" target="_blank">{% trans "View comment" %}</a>
{% endif %}
</p>
{% for activity in activities %}
{% extract_status activity user as activity_status %}
{% get_comment_for_invoice_action object activity as activity_comment %}
<p x-show="!collapsed">{{ activity_status }} {% if user.is_applicant and activity.user != user %} ({{ ORG_SHORT_NAME }}){% else %}({{ activity.user }}){% endif %}
<span class="text-gray-400">{{ activity.timestamp }}</span>
{% if activity_comment %}
<svg class="icon icon--request-changes"><use xlink:href="#request-changes"></use></svg>
<a href="{% url 'apply:projects:detail' pk=object.project.id %}#communications#{{ activity_comment.id }}" class="font-bold" target="_blank">{% trans "View comment" %}</a>
{% endif %}
</p>
{% endfor %}
</div>
<button
type="button"
class="flex-1 font-bold transition-opacity text-light-blue text-end hover:opacity-70"
x-on:click="collapsed = ! collapsed"
>
<span x-show="collapsed">
{% trans "Show" %}
{% heroicon_mini "arrow-down" class="inline align-text-bottom" aria_hidden=true %}
</span>
<span x-show="!collapsed">
{% trans "Hide" %}
{% heroicon_mini "arrow-up" class="inline align-text-bottom" aria_hidden=true %}
</span>

</button>
<div hx-get="{% url 'apply:projects:partial-invoice-status' pk=object.project.pk invoice_pk=object.pk %}" hx-trigger="load, invoicesUpdated from:body" class="flex card card--solid" x-data="{ collapsed: true }">
<div class="flex-none content-center min-h-10"><div class="h-4 min-w-96 bg-gray-400 rounded-full animate-pulse"></div></div>
<div class="ps-2"></div>
</div>

<div class="card card--solid">
Expand All @@ -93,34 +55,8 @@ <h5 class="card__heading">{% trans "Supporting Documents" %}</h5>
<aside class="sidebar">
{% block deliverables %}
{% endblock %}
<div class="sticky js-actions-sidebar sidebar__inner sidebar__inner--light-blue sidebar__inner--actions top-8" data-testid="sidebar-primary-actions">
<h5>{% trans "Actions to take" %}</h5>
{% block actions %}
{% can_edit object user as user_can_edit_request %}
{% if user.is_apply_staff or user.is_applicant %}
<a
{% if not user_can_edit_request %}
data-tooltip="{% trans "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:invoice-edit" pk=object.project.pk invoice_pk=object.pk %}"
{% else %}
"#"
{% endif %}
>
{% trans "Edit Invoice" %}
</a>
{% endif %}
{% 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:invoice-delete' pk=object.project.pk invoice_pk=object.pk %}">
{% trans "Delete Invoice" %}
</a>
{% endif %}
{% endblock %}
<div hx-get="{% url 'apply:projects:partial-invoice-detail-actions' pk=object.project.pk invoice_pk=object.pk %}" hx-trigger="invoicesUpdated from:body" class="sticky js-actions-sidebar sidebar__inner sidebar__inner--light-blue sidebar__inner--actions top-8" data-testid="sidebar-primary-actions">
{% include "application_projects/partials/invoice_detail_actions.html" %}
</div>
</aside>
</div>
Expand Down
Loading

0 comments on commit 11f8bb6

Please sign in to comment.