Skip to content

Commit

Permalink
Add a reporting table for projects (#4051)
Browse files Browse the repository at this point in the history
Fixes #4050

What a small line for such a large set of changes. Those supporting
changes include:

* Creating a view for the new Reporting Table
* Creating a current_report() method in report_config that is slightly
differnt than current_report_due (see inline doc)
* Adding a ProjectQuerySet method that does some intense subquerying to
be able to express the current_report in sql so that the status can be
filtered against


## Note from the cherry-picker/integrator

I see that the summary tables for reports was removed from
`apply/projects` and that the current dropdown in the main navigation
header points to `apply/project/reports`. This new table appears under
`apply/project/reporting` and is linked to in the original fork in which
this change occurred. As for integration here, the question is: do we
want to replace the original table or augment off to the side somehow?
Where should the link to this new table live?

Co-authored-by: Frank Duncan <[email protected]>
Co-authored-by: Fredrik Jonsson <[email protected]>
  • Loading branch information
3 people authored Nov 19, 2024
1 parent 2483330 commit 45f3a38
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ <h2 class="mb-2 text-xl">{{ heading }}</h2>
</a>
{% endif %}
{% endwith %}
{% if filter_classes != 'filters-open' %}
{% if 'filters-open' not in filter_classes %}
<button class="button js-toggle-filters">
{% heroicon_mini "adjustments-horizontal" class="inline align-text-bottom me-1 text-dark-blue" aria_hidden=true %}
{% trans "Filters" %}
Expand Down
11 changes: 11 additions & 0 deletions hypha/apply/projects/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ def decompress(self, value):
return [None, None]


class ReportingFilter(filters.FilterSet):
current_report_status = Select2MultipleChoiceFilter(
label=_("Status"),
choices=[
("Not started", "Not started"),
("In progress", "In progress"),
("Submitted", "Submitted"),
],
)


class ReportListFilter(filters.FilterSet):
reporting_period = filters.DateFromToRangeFilter(
label=_("Reporting Period"),
Expand Down
59 changes: 58 additions & 1 deletion hypha/apply/projects/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Count, F, Max, OuterRef, Q, Subquery, Sum, Value
from django.db.models import (
Case,
Count,
F,
Max,
OuterRef,
Q,
Subquery,
Sum,
Value,
When,
)
from django.db.models.functions import Cast, Coalesce
from django.db.models.signals import post_delete
from django.dispatch.dispatcher import receiver
Expand Down Expand Up @@ -102,6 +113,9 @@ def in_progress(self):
)
)

def invoicing_and_reporting(self):
return self.filter(status=INVOICING_AND_REPORTING)

def complete(self):
return self.filter(status=COMPLETE)

Expand Down Expand Up @@ -179,6 +193,49 @@ def for_table(self):
)
)

def for_reporting_table(self):
today = timezone.now().date()
Report = apps.get_model("application_projects", "Report")
return self.invoicing_and_reporting().annotate(
current_report_submitted_date=Subquery(
Report.objects.filter(
project=OuterRef("pk"), end_date__gt=today, current__isnull=False
)
.order_by("end_date")
.values("submitted")[:1],
output_field=models.DateField(),
),
current_report_status=Coalesce(
Subquery(
Report.objects.filter(
project=OuterRef("pk"),
)
.filter(
Q(
Q(end_date__gt=today),
Q(current__isnull=False) | Q(draft__isnull=False),
)
| Q(
end_date__lt=today,
current__isnull=True,
draft__isnull=False,
)
)
.order_by("end_date")
.annotate(
report_status=Case(
When(draft__isnull=False, then=Value("In progress")),
When(current__isnull=False, then=Value("Submitted")),
default=Value("Not started"),
)
)
.values("report_status")[:1],
output_field=models.CharField(),
),
Value("Not started"),
),
)


class Project(BaseStreamForm, AccessFormData, models.Model):
lead = models.ForeignKey(
Expand Down
12 changes: 12 additions & 0 deletions hypha/apply/projects/models/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,18 @@ def current_due_report(self):
)
return report

def current_report(self):
"""This is different from current_due_report as it will return a completed report
if that one is the current one."""
today = timezone.now().date()

last_report = self.last_report()

if last_report and last_report.end_date >= today:
return last_report

return self.current_due_report()

def next_date(self, last_date):
delta_frequency = self.frequency + "s"
delta = relativedelta(**{delta_frequency: self.occurrence})
Expand Down
46 changes: 46 additions & 0 deletions hypha/apply/projects/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import textwrap

import django_tables2 as tables
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -295,6 +296,51 @@ def order_end_date(self, qs, desc):
return qs.by_end_date(desc), True


class ReportingTable(tables.Table):
pk = tables.Column(
verbose_name=_("Project #"),
)
submission_id = tables.Column(
verbose_name=_("Submission #"),
)
title = tables.LinkColumn("funds:projects:detail", args=[tables.utils.A("pk")])
organization_name = tables.Column(
accessor="submission__organization_name", verbose_name="Organization name"
)
current_report_status = tables.Column(
attrs={"td": {"class": "status"}}, verbose_name="Status"
)

def render_current_report_status(self, value):
return format_html("<span>{}</span>", value)

current_report_submitted_date = tables.Column(
verbose_name="Submitted date", accessor="current_report_submitted_date__date"
)
current_report_due_date = tables.Column(
verbose_name="Due Date", accessor="report_config__current_report__end_date"
)
current_report_last_notified_date = tables.Column(
verbose_name="Last Notified",
accessor="report_config__current_report__notified__date",
)

class Meta:
fields = [
"pk",
"title",
"submission_id",
"organization_name",
"current_report_due_date",
"current_report_status",
"current_report_submitted_date",
"current_report_last_notified_date",
]
model = Project
orderable = True
attrs = {"class": "reporting-table"}


class ReportListTable(tables.Table):
project = tables.LinkColumn(
"funds:projects:reports:detail",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<div class="wrapper wrapper--large wrapper--inner-space-medium">
{% if table %}
{% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form use_search=False filter_action=filter_action filter_classes="filters--dates" %}
{% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form filter_action=filter_action filter_classes="filters-open filters--dates" %}
{% render_table table %}
{% else %}
<p>{% trans "No Reports Available." %}</p>
Expand Down
33 changes: 33 additions & 0 deletions hypha/apply/projects/templates/application_projects/reporting.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% extends "base-apply.html" %}

{% load render_table from django_tables2 %}
{% load i18n static %}

{% block title %}{% trans "Reporting" %}{% endblock %}

{% block content %}

{% adminbar %}
{% slot header %}{% trans "Reporting" %} ({{ table.rows|length }}){% endslot %}
{% slot sub_heading %}{% trans "View, Search and filter reporting statuses" %}{% endslot %}
{% endadminbar %}

<div class="wrapper wrapper--large wrapper--inner-space-medium">
{% if table %}
{% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form filter_action=filter_action filter_classes="filters-open" %}
{% render_table table %}
{% else %}
<p>{% trans "No Projects Currently Reporting." %}</p>
{% endif %}
</div>

{% endblock content %}

{% block extra_css %}
{{ filter.form.media.css }}
{% endblock %}

{% block extra_js %}
{{ filter.form.media.js }}
<script src="{% static 'js/submission-filters.js' %}"></script>
{% endblock %}
2 changes: 2 additions & 0 deletions hypha/apply/projects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ProjectSOWView,
RemoveDocumentView,
ReportDetailView,
ReportingView,
ReportListView,
ReportPrivateMedia,
ReportSkipView,
Expand Down Expand Up @@ -219,4 +220,5 @@
)
),
),
path("reporting/", ReportingView.as_view(), name="reporting"),
]
2 changes: 2 additions & 0 deletions hypha/apply/projects/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from .report import (
ReportDetailView,
ReportFrequencyUpdate,
ReportingView,
ReportListView,
ReportPrivateMedia,
ReportSkipView,
Expand Down Expand Up @@ -92,6 +93,7 @@
"ReportSkipView",
"ReportFrequencyUpdate",
"ReportListView",
"ReportingView",
"CreateInvoiceView",
"InvoiceListView",
"InvoiceView",
Expand Down
14 changes: 11 additions & 3 deletions hypha/apply/projects/views/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from hypha.apply.utils.views import DelegatedViewMixin

from ...stream_forms.models import BaseStreamForm
from ..filters import ReportListFilter
from ..filters import ReportingFilter, ReportListFilter
from ..forms import ReportEditForm, ReportFrequencyForm
from ..models import Report, ReportConfig, ReportPrivateFiles
from ..models import Project, Report, ReportConfig, ReportPrivateFiles
from ..permissions import has_permission
from ..tables import ReportListTable
from ..tables import ReportingTable, ReportListTable
from ..utils import get_placeholder_file


Expand Down Expand Up @@ -311,3 +311,11 @@ class ReportListView(SingleTableMixin, FilterView):
filterset_class = ReportListFilter
table_class = ReportListTable
template_name = "application_projects/report_list.html"


@method_decorator(staff_or_finance_required, name="dispatch")
class ReportingView(SingleTableMixin, FilterView):
queryset = Project.objects.for_reporting_table()
filterset_class = ReportingFilter
table_class = ReportingTable
template_name = "application_projects/reporting.html"
5 changes: 5 additions & 0 deletions hypha/core/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def get_primary_navigation_items(user):
"url": reverse_lazy("apply:projects:reports:all"),
"permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance",
},
{
"title": _("Reporting"),
"url": reverse_lazy("apply:projects:reporting"),
"permission_method": "hypha.apply.users.decorators.is_apply_staff_or_finance",
},
],
},
]
Expand Down
20 changes: 20 additions & 0 deletions hypha/static_src/sass/components/_projects-table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,23 @@
}
}
}

.reporting-table {
@include mixins.table-ordering-styles;

tbody {
td {
&.status {
span {
display: inline-block;
padding: 5px;
font-size: 13px;
font-weight: variables.$weight--bold;
color: variables.$color--white;
text-align: center;
background-color: variables.$color--dark-blue;
}
}
}
}
}

0 comments on commit 45f3a38

Please sign in to comment.