Skip to content

Commit

Permalink
feat: add email reminder sent every 3 monthes to organizations admins…
Browse files Browse the repository at this point in the history
… having multiple members
  • Loading branch information
leo-naeka committed Jun 10, 2024
1 parent 2f59e94 commit 8ed09ab
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 1 deletion.
4 changes: 3 additions & 1 deletion clevercloud/cron.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@

"0 0 1 * * $ROOT/clevercloud/run_management_command.sh sync_cities --wet-run",
"0 0 2 * * $ROOT/clevercloud/crons/populate_metabase_emplois.sh --monthly",
"0 0 15 * * $ROOT/clevercloud/run_management_command.sh sync_romes_and_appellations --wet-run"
"0 0 15 * * $ROOT/clevercloud/run_management_command.sh sync_romes_and_appellations --wet-run",

"0 9 1-7 */3 1 $ROOT/clevercloud/run_management_command.sh send_check_authorized_members_email"
]
5 changes: 5 additions & 0 deletions itou/common_apps/organizations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class OrganizationAbstract(models.Model):
# This enables us to keep our internal primary key opaque and independent from any external logic.
uid = models.UUIDField(db_index=True, default=uuid.uuid4, unique=True)

active_members_email_reminder_last_sent_at = models.DateTimeField(
null=True,
verbose_name="date d'envoi du dernier rappel pour vérifier les membres actifs",
)

# Child class should have a "members" attribute, for example:
# members = models.ManyToManyField(
# settings.AUTH_USER_MODEL,
Expand Down
1 change: 1 addition & 0 deletions itou/communications/dispatch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
JobSeekerNotification,
PrescriberNotification,
PrescriberOrEmployerNotification,
PrescriberOrEmployerOrLaborInspectorNotification,
WithStructureMixin,
)
7 changes: 7 additions & 0 deletions itou/communications/dispatch/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ def is_manageable_by_user(self):
return super().is_manageable_by_user() and (self.user.is_prescriber or self.user.is_employer)


class PrescriberOrEmployerOrLaborInspectorNotification:
def is_manageable_by_user(self):
return super().is_manageable_by_user() and (
self.user.is_prescriber or self.user.is_employer or self.user.is_labor_inspector
)


class WithStructureMixin:
def is_manageable_by_user(self):
return super().is_manageable_by_user() and self.structure is not None
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.6 on 2024-06-05 09:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("companies", "0002_fix_job_app_contract_type_enum"),
]

operations = [
migrations.AddField(
model_name="company",
name="active_members_email_reminder_last_sent_at",
field=models.DateTimeField(
null=True, verbose_name="date d'envoi du dernier rappel pour vérifier les membres actifs"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.6 on 2024-06-05 09:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("institutions", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="institution",
name="active_members_email_reminder_last_sent_at",
field=models.DateTimeField(
null=True, verbose_name="date d'envoi du dernier rappel pour vérifier les membres actifs"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.6 on 2024-06-05 09:52

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("prescribers", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="prescriberorganization",
name="active_members_email_reminder_last_sent_at",
field=models.DateTimeField(
null=True, verbose_name="date d'envoi du dernier rappel pour vérifier les membres actifs"
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "layout/base_email_text_body.txt" %}
{% load format_filters %}
{% block body %}
Bonjour,

En tant qu’administrateur de l’organisation {{ structure.name }}, nous vous invitons à vérifier la liste des membres afin de vous assurer que seuls les collaborateurs qui travaillent au sein de cette organisation puissent accéder à votre espace de travail.

RDV sur votre espace des emplois de l’inclusion à la rubrique “Gérer les collaborateurs” :

Si un collaborateur a quitté votre organisation, vous devez le retirer des membres en cliquant sur le bouton d’action situé à droite, puis sur l’option “retirer de la structure”.

Pour des raisons de sécurité et si la configuration de votre organisation vous le permet, nous vous invitons à nommer plusieurs administrateurs.

Ce rappel automatique vous sera transmis tous les 3 mois, mais il est vivement recommandé d’effectuer cette action dès qu’un collaborateur quitte votre organisation.

Cordialement,
{% endblock body %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% extends "layout/base_email_text_subject.txt" %}
{% block subject %}
Rappel sécurité : vérifiez la liste des membres de l’organisation {{ structure.name }}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import datetime

from django.db import transaction
from django.db.models import Count, IntegerField, Prefetch, Q
from django.db.models.functions import Coalesce, ExtractDay, ExtractMonth, Least, Mod
from django.utils import timezone

from itou.companies.models import Company
from itou.institutions.models import Institution
from itou.prescribers.models import PrescriberOrganization
from itou.users.models import User
from itou.users.notifications import OrganizationActiveMembersReminderNotification
from itou.utils.command import BaseCommand


class Command(BaseCommand):
"""
Send an email reminder every 3 months asking admins of companies, organizations and institutions
having more than 1 member to review members access and ensure that only authorized members have
access to the organization data.
"""

def handle(self, *args, **options):
TODAY = datetime.date.today()

def get_query_for_model(model):
membership_attname = model.members.through._meta.get_field("user").remote_field.name
return (
model.objects.active()
.prefetch_related(
Prefetch(
"members",
queryset=User.objects.filter(
**{
"is_active": True,
f"{membership_attname}__is_active": True,
f"{membership_attname}__is_admin": True,
},
),
to_attr="admin_members",
)
)
.annotate(
last_sent_month_mod3=Mod(
ExtractMonth(Coalesce("active_members_email_reminder_last_sent_at", "created_at")),
3,
output_field=IntegerField(),
),
last_sent_day=Least(
ExtractDay(Coalesce("active_members_email_reminder_last_sent_at", "created_at")), 28
),
active_members_count=Count(
"members",
filter=Q(
**{
f"{membership_attname}__is_active": True,
f"{membership_attname}__user__is_active": True,
}
),
),
)
.filter(
last_sent_month_mod3=TODAY.month % 3,
last_sent_day=min(TODAY.day, 28),
active_members_count__gt=1,
)
.order_by("pk")
)

# Companies
for company in get_query_for_model(Company):
with transaction.atomic():
for member in self.admin_members:
OrganizationActiveMembersReminderNotification(member, company).send()
company.active_members_email_reminder_last_sent_at = timezone.now()
company.save(update_fields=["active_members_email_reminder_last_sent_at"])

# Prescriber organizations
for prescriber_organization in get_query_for_model(PrescriberOrganization):
with transaction.atomic():
for member in self.admin_members:
OrganizationActiveMembersReminderNotification(member, prescriber_organization).send()
prescriber_organization.active_members_email_reminder_last_sent_at = timezone.now()
prescriber_organization.save(update_fields=["active_members_email_reminder_last_sent_at"])

# Institutions
for institution in get_query_for_model(Institution):
with transaction.atomic():
for member in self.admin_members:
OrganizationActiveMembersReminderNotification(member, institution).send()
institution.active_members_email_reminder_last_sent_at = timezone.now()
institution.save(update_fields=["active_members_email_reminder_last_sent_at"])
13 changes: 13 additions & 0 deletions itou/users/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from itou.communications import NotificationCategory, registry as notifications_registry
from itou.communications.dispatch import EmailNotification, PrescriberOrEmployerOrLaborInspectorNotification


@notifications_registry.register
class OrganizationActiveMembersReminderNotification(
PrescriberOrEmployerOrLaborInspectorNotification, EmailNotification
):
name = "Rappel périodique pour s'assurer que les membres de sa structure sont bien actifs et autorisés"
category = NotificationCategory.MEMBERS_MANAGEMENT
subject_template = "users/email/member_deactivation_email_subject.txt"
body_template = "users/email/member_deactivation_email_body.txt"
can_be_disabled = False

0 comments on commit 8ed09ab

Please sign in to comment.