diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py index 0053f4c3009bf2884d99efdba6de38925bec4a07..eb6b6d08b2ed0b39417749370499c7b1c4bbfbfd 100644 --- a/opentech/apply/projects/models.py +++ b/opentech/apply/projects/models.py @@ -328,7 +328,11 @@ class ProjectQuerySet(models.QuerySet): ) def for_table(self): - return self.with_amount_paid().with_last_payment() + return self.with_amount_paid().with_last_payment().select_related( + 'report_config', + 'submission__page', + 'lead', + ) class Project(BaseStreamForm, AccessFormData, models.Model): @@ -613,11 +617,17 @@ class ReportConfig(models.Model): return f"Every week on { weekday }" return f"Every {self.occurrence} weeks on { weekday }" + def is_up_to_date(self): + return len(self.project.reports.to_do()) == 0 + + def outstanding_reports(self): + return len(self.project.reports.to_do()) + + def has_very_late_reports(self): + return self.project.reports.any_very_late() + def past_due_reports(self): - return self.project.reports.filter( - Q(current__isnull=True) & Q(skipped=False), - end_date__lt=timezone.now().date(), - ).order_by('end_date') + return self.project.reports.to_do() def last_report(self): today = timezone.now().date() @@ -677,6 +687,18 @@ class ReportQueryset(models.QuerySet): Q(current__isnull=False) | Q(skipped=True), ) + def to_do(self): + today = timezone.now().date() + return self.filter( + current__isnull=True, + skipped=False, + end_date__lt=today, + ).order_by('end_date') + + def any_very_late(self): + two_weeks_ago = timezone.now().date() - relativedelta(weeks=2) + return self.to_do().filter(end_date__lte=two_weeks_ago) + def submitted(self): return self.filter(current__isnull=False) @@ -744,11 +766,10 @@ class Report(models.Model): @property def is_very_late(self): - more_than_one_report_late = self.project.reports.filter( - Q(end_date__gt=self.end_date) & Q(end_date__lt=timezone.now().date()) - ).count() >= 1 + two_weeks_ago = timezone.now().date() - relativedelta(weeks=2) + two_weeks_late = self.end_date < two_weeks_ago not_submitted = not self.current - return not_submitted and more_than_one_report_late + return not_submitted and two_weeks_late @property def can_submit(self): diff --git a/opentech/apply/projects/tables.py b/opentech/apply/projects/tables.py index 4be6c4998d028bd32e1dda276aaba6db41bd6ee6..dc07414b3c84cd08f2eedd791f63a043c9de6834 100644 --- a/opentech/apply/projects/tables.py +++ b/opentech/apply/projects/tables.py @@ -3,6 +3,7 @@ import textwrap import django_tables2 as tables from django.db.models import F, Sum from django.contrib.humanize.templatetags.humanize import intcomma +from django.utils.safestring import mark_safe from .models import PaymentRequest, Project, Report @@ -71,6 +72,7 @@ class BaseProjectsTable(tables.Table): ) status = tables.Column(verbose_name='Status', accessor='get_status_display', order_by=('status',)) fund = tables.Column(verbose_name='Fund', accessor='submission.page') + reporting = tables.Column(verbose_name='Reporting', accessor='pk') last_payment_request = tables.DateColumn() end_date = tables.DateColumn(verbose_name='End Date', accessor='proposed_end') fund_allocation = tables.Column(verbose_name='Fund Allocation', accessor='value') @@ -78,6 +80,21 @@ class BaseProjectsTable(tables.Table): def render_fund_allocation(self, record): return f'${intcomma(record.amount_paid)} / ${intcomma(record.value)}' + def render_reporting(self, record): + if not hasattr(record, 'report_config'): + return '-' + + if record.report_config.is_up_to_date(): + return 'Up to date' + + if record.report_config.has_very_late_reports(): + display = '<svg class="icon"><use xlink:href="#exclamation-point"></use></svg>' + else: + display = '' + + display += f'{ record.report_config.outstanding_reports() } outstanding' + return mark_safe(display) + class ProjectsDashboardTable(BaseProjectsTable): class Meta: @@ -85,12 +102,14 @@ class ProjectsDashboardTable(BaseProjectsTable): 'title', 'status', 'fund', + 'reporting', 'last_payment_request', 'end_date', 'fund_allocation', ] model = Project orderable = False + attrs = {'class': 'projects-table'} class ProjectsListTable(BaseProjectsTable): @@ -100,6 +119,7 @@ class ProjectsListTable(BaseProjectsTable): 'status', 'lead', 'fund', + 'reporting', 'last_payment_request', 'end_date', 'fund_allocation', @@ -107,6 +127,7 @@ class ProjectsListTable(BaseProjectsTable): model = Project orderable = True order_by = ('-end_date',) + attrs = {'class': 'projects-table'} def order_fund_allocation(self, qs, is_descending): direction = '-' if is_descending else '' diff --git a/opentech/apply/projects/tests/test_models.py b/opentech/apply/projects/tests/test_models.py index a214c1e344a88bb54190a19788eb971d32865932..3ba12a70e5cc743b25bb01a38b544ce42e1292fb 100644 --- a/opentech/apply/projects/tests/test_models.py +++ b/opentech/apply/projects/tests/test_models.py @@ -303,10 +303,8 @@ class TestReport(TestCase): ReportFactory(project=report.project) self.assertFalse(report.is_very_late) - def test_late_if_two_ahead(self): - report = ReportFactory(end_date=self.from_today(-3)) - ReportFactory(end_date=self.from_today(-2), project=report.project) - ReportFactory(project=report.project) + def test_late_if_two_weeks_behind(self): + report = ReportFactory(end_date=self.from_today(-15)) self.assertTrue(report.is_very_late) def test_not_late_if_two_ahead_but_one_in_future(self): diff --git a/opentech/apply/projects/views/project.py b/opentech/apply/projects/views/project.py index aa81828677434395b5c2ae337647a7e8970b1980..4f5c8966c8f37f084e6c9a0bf4f1fab306a54f46 100644 --- a/opentech/apply/projects/views/project.py +++ b/opentech/apply/projects/views/project.py @@ -601,13 +601,10 @@ class ProjectEditView(ViewDispatcher): @method_decorator(staff_required, name='dispatch') class ProjectListView(SingleTableMixin, FilterView): filterset_class = ProjectListFilter - model = Project + queryset = Project.objects.for_table() table_class = ProjectsListTable template_name = 'application_projects/project_list.html' - def get_queryset(self): - return Project.objects.for_table() - @method_decorator(staff_required, name='dispatch') class ProjectOverviewView(TemplateView): diff --git a/opentech/static_src/src/sass/apply/components/_projects-table.scss b/opentech/static_src/src/sass/apply/components/_projects-table.scss new file mode 100644 index 0000000000000000000000000000000000000000..c4ba256df7f6bdd460bb540d81328a6c93df87ef --- /dev/null +++ b/opentech/static_src/src/sass/apply/components/_projects-table.scss @@ -0,0 +1,10 @@ +.projects-table { + .reporting { + .icon { + margin-right: 0.3rem; + width: 25px; + height: 25px; + fill: $color--tomato; + } + } +} diff --git a/opentech/static_src/src/sass/apply/main.scss b/opentech/static_src/src/sass/apply/main.scss index dc091325ef24a8ad6de6146124ebda5ec63eda17..cbba6b86e8c3fccff9dfdc25cd02e8b216e030d5 100644 --- a/opentech/static_src/src/sass/apply/main.scss +++ b/opentech/static_src/src/sass/apply/main.scss @@ -38,6 +38,7 @@ @import 'components/nav'; @import 'components/pagination'; @import 'components/profile'; +@import 'components/projects-table'; @import 'components/related-sidebar'; @import 'components/responsive-table'; @import 'components/reviewer-dash-box';