diff --git a/opentech/apply/dashboard/views.py b/opentech/apply/dashboard/views.py index 9b963a58f1b778eafe431a0da02875d55bd3a9d1..acfcb49296d9ffbfc4ddad2b5900102a53f5d551 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 0000000000000000000000000000000000000000..d0162caed2ce85338bd212f546daaff1f0ee59f0 --- /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 eb6b6d08b2ed0b39417749370499c7b1c4bbfbfd..c652a2a24c6f86a6b2198f19c6a388bce48b08e5 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 1768a12ccbd2d8def64cdbaaecea0488d5bc9110..a585a8b0b9d772df05f3d187b48e83f9a4615083 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 0000000000000000000000000000000000000000..b0ee1023a533aab5e979bdeee137f09d3cbe1121 --- /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())