From b794ea39e9c3066a0939fe7c58387e2f1b56d1a8 Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Tue, 5 Nov 2019 15:04:33 +0000
Subject: [PATCH] Add basic management command to trigger reminders about
 reports

---
 opentech/apply/dashboard/views.py             |  2 +-
 .../management/commands/notify_report_due.py  | 23 ++++++++++
 opentech/apply/projects/models.py             |  9 +++-
 opentech/apply/projects/tests/factories.py    |  9 ++++
 .../apply/projects/tests/test_commands.py     | 46 +++++++++++++++++++
 5 files changed, 87 insertions(+), 2 deletions(-)
 create mode 100644 opentech/apply/projects/management/commands/notify_report_due.py
 create mode 100644 opentech/apply/projects/tests/test_commands.py

diff --git a/opentech/apply/dashboard/views.py b/opentech/apply/dashboard/views.py
index 9b963a58f..acfcb4929 100644
--- a/opentech/apply/dashboard/views.py
+++ b/opentech/apply/dashboard/views.py
@@ -342,7 +342,7 @@ class ApplicantDashboardView(MultiTableMixin, TemplateView):
         return context
 
     def get_active_project_data(self, user):
-        return Project.objects.filter(user=user).in_progress().for_table()
+        return Project.objects.filter(user=user).active().for_table()
 
     def get_active_submissions(self, user):
         active_subs = ApplicationSubmission.objects.filter(
diff --git a/opentech/apply/projects/management/commands/notify_report_due.py b/opentech/apply/projects/management/commands/notify_report_due.py
new file mode 100644
index 000000000..d0162caed
--- /dev/null
+++ b/opentech/apply/projects/management/commands/notify_report_due.py
@@ -0,0 +1,23 @@
+from dateutil.relativedelta import relativedelta
+
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from opentech.apply.projects.models import Project
+
+
+class Command(BaseCommand):
+    help = 'Notify users that they have a report due soon'
+
+    def add_arguments(self, parser):
+        parser.add_argument('days', type=int)
+
+    def handle(self, *args, **options):
+        due_date = timezone.now().date() + relativedelta(days=options['days'])
+        for project in Project.objects.in_progress():
+            next_report = project.report_config.current_due_report()
+            if next_report.end_date == due_date:
+                # Notify about the due report
+                self.stdout.write(
+                    self.style.SUCCESS(f'Notified project: {project.id}')
+                )
diff --git a/opentech/apply/projects/models.py b/opentech/apply/projects/models.py
index eb6b6d08b..c652a2a24 100644
--- a/opentech/apply/projects/models.py
+++ b/opentech/apply/projects/models.py
@@ -285,9 +285,16 @@ PROJECT_STATUS_CHOICES = [
 
 
 class ProjectQuerySet(models.QuerySet):
-    def in_progress(self):
+    def active(self):
+        "Projects that are not finished"
         return self.exclude(status=COMPLETE)
 
+    def in_progress(self):
+        "Projects that users need to interact with, submitting reports or payment request"
+        return self.filter(
+            status__in=(IN_PROGRESS, CLOSING,)
+        )
+
     def complete(self):
         return self.filter(status=COMPLETE)
 
diff --git a/opentech/apply/projects/tests/factories.py b/opentech/apply/projects/tests/factories.py
index 1768a12cc..a585a8b0b 100644
--- a/opentech/apply/projects/tests/factories.py
+++ b/opentech/apply/projects/tests/factories.py
@@ -10,6 +10,7 @@ from opentech.apply.funds.tests.factories import ApplicationSubmissionFactory
 from opentech.apply.projects.models import (
     Contract,
     DocumentCategory,
+    IN_PROGRESS,
     PacketFile,
     PaymentReceipt,
     PaymentRequest,
@@ -97,6 +98,9 @@ class ProjectFactory(factory.DjangoModelFactory):
         in_approval = factory.Trait(
             is_locked=True,
         )
+        in_progress = factory.Trait(
+            status=IN_PROGRESS,
+        )
 
 
 class ContractFactory(factory.DjangoModelFactory):
@@ -155,6 +159,11 @@ class ReportConfigFactory(factory.DjangoModelFactory):
         model = ReportConfig
         django_get_or_create = ('project',)
 
+    class Params:
+        weeks = factory.Trait(
+            frequency=ReportConfig.WEEK,
+        )
+
 
 class ReportVersionFactory(factory.DjangoModelFactory):
     report = factory.SubFactory("opentech.apply.projects.tests.factories.ReportFactory")
diff --git a/opentech/apply/projects/tests/test_commands.py b/opentech/apply/projects/tests/test_commands.py
new file mode 100644
index 000000000..b0ee1023a
--- /dev/null
+++ b/opentech/apply/projects/tests/test_commands.py
@@ -0,0 +1,46 @@
+from io import StringIO
+
+from dateutil.relativedelta import relativedelta
+
+from django.core.management import call_command
+from django.test import TestCase
+from django.utils import timezone
+
+from .factories import (
+    ProjectFactory,
+    ReportConfigFactory,
+    ReportFactory,
+)
+
+
+class TestNotifyReportDue(TestCase):
+    def test_notify_report_due_in_7_days(self):
+        in_a_week = timezone.now() + relativedelta(days=7)
+        ReportConfigFactory(schedule_start=in_a_week, project__in_progress=True)
+        out = StringIO()
+        call_command('notify_report_due', 7, stdout=out)
+        self.assertIn('Notified project', out.getvalue())
+
+    def test_dont_notify_report_due_in_7_days_already_submitted(self):
+        in_a_week = timezone.now() + relativedelta(days=7)
+        config = ReportConfigFactory(schedule_start=in_a_week)
+        ReportFactory(
+            project=config.project,
+            is_submitted=True,
+            end_date=config.schedule_start,
+        )
+        out = StringIO()
+        call_command('notify_report_due', 7, stdout=out)
+        self.assertNotIn('Notified project', out.getvalue())
+
+    def test_dont_notify_project_not_in_progress(self):
+        ProjectFactory()
+        out = StringIO()
+        call_command('notify_report_due', 7, stdout=out)
+        self.assertNotIn('Notified project', out.getvalue())
+
+    def test_dont_notify_project_closed(self):
+        ProjectFactory()
+        out = StringIO()
+        call_command('notify_report_due', 7, stdout=out)
+        self.assertNotIn('Notified project', out.getvalue())
-- 
GitLab