Skip to content

Commit

Permalink
siae_evaluations: send freeze notification when freeze happens
Browse files Browse the repository at this point in the history
and a reminder every 7 days after the 1st notification
  • Loading branch information
xavfernandez committed Jul 16, 2024
1 parent cb35e78 commit be3d3db
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 174 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import datetime

from dateutil.relativedelta import relativedelta
from django.db import transaction
from django.db.models import Exists, F, Max, OuterRef, Q
from django.db.models import Exists, F, OuterRef, Q
from django.utils import timezone

from itou.siae_evaluations import enums as evaluation_enums
from itou.siae_evaluations.emails import CampaignEmailFactory, SIAEEmailFactory
from itou.siae_evaluations.models import EvaluatedAdministrativeCriteria, EvaluatedSiae, EvaluationCampaign
from itou.utils.command import BaseCommand
Expand Down Expand Up @@ -65,43 +62,14 @@ def handle(self, **options):
f"Emailed second reminders to {len(emails)} SIAE which did not submit proofs to {campaign}."
)

# When SIAE submissions are frozen, notify institutions:
# - on the day submissions are frozen, and
# - 7 days after submissions have been frozen.
siae_subq = EvaluatedSiae.objects.filter(evaluation_campaign_id=OuterRef("pk"))
submissions_frozen = ~Exists(siae_subq.filter(submission_freezed_at=None))
has_siae_to_control = Exists(
siae_subq.filter(
Exists(
EvaluatedAdministrativeCriteria.objects.filter(
evaluated_job_application__evaluated_siae=OuterRef("pk"),
review_state=evaluation_enums.EvaluatedAdministrativeCriteriaState.PENDING,
submitted_at__isnull=False,
)
),
).filter(
Q(reviewed_at=None, evaluation_campaign__calendar__adversarial_stage_start__gt=today)
| Q(final_reviewed_at=None, evaluation_campaign__calendar__adversarial_stage_start__lte=today)
)
)
# When a campaign is frozen, a notification is sent to institutions synchronously
# Then every 7 days a reminder email is sent if there is still work to do
has_siae_to_control = Exists(EvaluatedSiae.objects.to_control_in_campaign(campaign_id=OuterRef("pk")))
for campaign in campaigns.filter(
Q(submission_freeze_notified_at=None)
| Q(submission_freeze_notified_at__date__lte=today - relativedelta(days=7)),
submissions_frozen,
has_siae_to_control,
submission_freeze_notified_at__date__lte=today - relativedelta(days=7),
):
action = None
if campaign.submission_freeze_notified_at is None:
send_email_messages([CampaignEmailFactory(campaign).submission_frozen()])
action = "Instructed"
else:
submission_frozen_at = EvaluatedSiae.objects.filter(evaluation_campaign=campaign).aggregate(
Max("submission_freezed_at")
)["submission_freezed_at__max"]
if submission_frozen_at - campaign.submission_freeze_notified_at <= datetime.timedelta(days=7):
send_email_messages([CampaignEmailFactory(campaign).submission_frozen_reminder()])
action = "Reminded"
if action:
self.stdout.write(f"{action}{campaign.institution}” to control SIAE during the submission freeze.")
campaign.submission_freeze_notified_at = timezone.now()
campaign.save(update_fields=["submission_freeze_notified_at"])
send_email_messages([CampaignEmailFactory(campaign).submission_frozen_reminder()])
campaign.submission_freeze_notified_at = timezone.now()
campaign.save(update_fields=["submission_freeze_notified_at"])
self.stdout.write(f"Reminded “{campaign.institution}” to control SIAE during the submission freeze.")
26 changes: 26 additions & 0 deletions itou/siae_evaluations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ def freeze(self, freeze_at):
EvaluatedSiae.objects.filter(evaluation_campaign=self, submission_freezed_at__isnull=True).update(
submission_freezed_at=freeze_at
)
if self.submission_freeze_notified_at is None:
# Notification hasn't been sent already
if EvaluatedSiae.objects.to_control_in_campaign(campaign_id=self.pk).exists():
# There are still SIAE to review
send_email_messages([CampaignEmailFactory(self).submission_frozen()])
self.submission_freeze_notified_at = timezone.now()
self.save(update_fields=["submission_freeze_notified_at"])

def transition_to_adversarial_phase(self):
now = timezone.now()
Expand Down Expand Up @@ -437,6 +444,25 @@ def did_not_send_proof(self):
)
)

def to_control_in_campaign(self, campaign_id):
today = timezone.localdate()
return (
self.filter(evaluation_campaign_id=campaign_id)
.filter(
Exists(
EvaluatedAdministrativeCriteria.objects.filter(
evaluated_job_application__evaluated_siae=OuterRef("pk"),
review_state=evaluation_enums.EvaluatedAdministrativeCriteriaState.PENDING,
submitted_at__isnull=False,
)
),
)
.filter(
Q(reviewed_at=None, evaluation_campaign__calendar__adversarial_stage_start__gt=today)
| Q(final_reviewed_at=None, evaluation_campaign__calendar__adversarial_stage_start__lte=today)
)
)


class EvaluatedSiae(models.Model):
evaluation_campaign = models.ForeignKey(
Expand Down
2 changes: 1 addition & 1 deletion tests/siae_evaluations/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def test_freeze(self, client):
+ 1 # Count the filtered results (paginator)
+ 1 # Count the full results
+ 1 # Fetch selected evaluation_campaigns
+ 2 # Update EvaluatedSiae for each selected campaign
+ 2 * 2 # For each campaign: update EvaluatedSiae & check if a notification is needed
):
response = client.post(
reverse("admin:siae_evaluations_evaluationcampaign_changelist"),
Expand Down
138 changes: 7 additions & 131 deletions tests/siae_evaluations/test_management_commands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import datetime

import pytest
from dateutil.relativedelta import relativedelta
from django.core.management import call_command
from django.utils import timezone
Expand Down Expand Up @@ -495,59 +494,12 @@ def test_second_notification_not_fired_for_siae_with_final_state(
assert mailoutbox == []

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_notification(self, capsys, django_capture_on_commit_callbacks, mailoutbox):
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=3),
institution__name="DDETS 01",
name="Campagne de test",
)
evaluated_siae = EvaluatedSiaeFactory(evaluation_campaign=campaign)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_siae, complete=True)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == "Instructed “DDETS 01” to control SIAE during the submission freeze.\n"
assert stderr == ""
[email] = mailoutbox
assert (
email.subject
== "[DEV] [Contrôle a posteriori] Contrôle des justificatifs à réaliser avant la clôture de phase"
)

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_adversarial(self, capsys, django_capture_on_commit_callbacks, mailoutbox):
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=6),
calendar__adversarial_stage_start=timezone.localdate() - relativedelta(weeks=3),
institution__name="DDETS 01",
name="Campagne de test",
)
evaluated_ = EvaluatedSiaeFactory(
evaluation_campaign=campaign, reviewed_at=timezone.now() - relativedelta(days=15)
)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_, complete=True)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == "Instructed “DDETS 01” to control SIAE during the submission freeze.\n"
assert stderr == ""
[email] = mailoutbox
assert (
email.subject
== "[DEV] [Contrôle a posteriori] Contrôle des justificatifs à réaliser avant la clôture de phase"
)

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_notification_ignores_already_notified(
self, capsys, django_capture_on_commit_callbacks, mailoutbox
def test_institution_submission_freeze_reminder(
self, capsys, django_capture_on_commit_callbacks, mailoutbox, snapshot
):
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=3),
submission_freeze_notified_at=timezone.now(),
submission_freeze_notified_at=timezone.now() - relativedelta(days=6),
institution__name="DDETS 01",
name="Campagne de test",
)
Expand All @@ -562,67 +514,17 @@ def test_institution_submission_freeze_notification_ignores_already_notified(
assert stderr == ""
assert mailoutbox == []

@freeze_time("2023-01-18 11:11:11")
@pytest.mark.parametrize("reviewed_at_offset", [relativedelta(days=-1), relativedelta(days=2)])
def test_institution_submission_freeze_notification_ignores_siae_evaluated(
self, django_capture_on_commit_callbacks, capsys, mailoutbox, reviewed_at_offset
):
reviewed_at = timezone.now() - reviewed_at_offset
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=3),
institution__name="DDETS 01",
name="Campagne de test",
)
evaluated_ = EvaluatedSiaeFactory(evaluation_campaign=campaign, reviewed_at=reviewed_at)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_, complete=True)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == ""
assert stderr == ""
assert mailoutbox == []

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_reminder(
self, capsys, django_capture_on_commit_callbacks, mailoutbox, snapshot
):
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=3),
submission_freeze_notified_at=timezone.now() - relativedelta(days=7),
institution__name="DDETS 01",
name="Campagne de test",
)
evaluated_ = EvaluatedSiaeFactory(evaluation_campaign=campaign)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_, complete=True)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
one_day_later = timezone.now() + relativedelta(days=1)
with freeze_time(one_day_later): # It has now been 7 days since last notification
with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == "Reminded “DDETS 01” to control SIAE during the submission freeze.\n"
assert stderr == ""
[email] = mailoutbox
assert email.subject == "[DEV] [Contrôle a posteriori] Rappel : Contrôle des justificatifs à finaliser"
assert email.body == snapshot()

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_no_reminder_without_docs(
self, capsys, django_capture_on_commit_callbacks, mailoutbox, snapshot
):
campaign = EvaluationCampaignFactory(evaluations_asked_at=timezone.now() - relativedelta(weeks=3))
evaluated_ = EvaluatedSiaeFactory(evaluation_campaign=campaign)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == ""
assert stderr == ""
assert [] == mailoutbox

@freeze_time("2023-01-18 11:11:11")
def test_institution_submission_freeze_notification_ignores_closed_campaigns(
self, capsys, django_capture_on_commit_callbacks, mailoutbox
Expand All @@ -643,29 +545,3 @@ def test_institution_submission_freeze_notification_ignores_closed_campaigns(
assert stdout == ""
assert stderr == ""
assert mailoutbox == []

@freeze_time("2023-01-18 11:11:11")
@pytest.mark.parametrize("adversarial_stage_start", [datetime.date(2023, 2, 1), datetime.date(2023, 1, 1)])
def test_institution_submission_freeze_ignores_final_reviewed_at(
self, django_capture_on_commit_callbacks, adversarial_stage_start, capsys, mailoutbox
):
campaign = EvaluationCampaignFactory(
evaluations_asked_at=timezone.now() - relativedelta(weeks=6),
calendar__adversarial_stage_start=adversarial_stage_start,
institution__name="DDETS 01",
name="Campagne de test",
)
evaluated_ = EvaluatedSiaeFactory(
evaluation_campaign=campaign,
reviewed_at=timezone.now() - relativedelta(days=20),
final_reviewed_at=timezone.now() - relativedelta(days=20),
)
EvaluatedJobApplicationFactory(evaluated_siae=evaluated_, complete=True)
campaign.freeze(timezone.now())

with django_capture_on_commit_callbacks(execute=True):
call_command("evaluation_campaign_notify")
stdout, stderr = capsys.readouterr()
assert stdout == ""
assert stderr == ""
assert mailoutbox == []
Loading

0 comments on commit be3d3db

Please sign in to comment.