Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket/bb 23550 bb 24371 reminder mails #6033

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bluebottle/test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ def setUpClass(cls):
cls.tenant = get_tenant_model().objects.get(schema_name='test')
connection.set_tenant(cls.tenant)

def assertHasMail(self, subject):
for email in mail.outbox:
if subject in email.subject:
return
self.fail('No email with subject "{}" found'.format(subject))


class APITestCase(BluebottleTestCase):
"""
Expand Down
4 changes: 4 additions & 0 deletions bluebottle/time_based/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,10 @@ class PeriodicActivity(RegistrationActivity):
)
url_pattern = "{}/{}/activities/details/periodic/{}/{}"

@property
def active_registrations(self):
return self.registrations.filter(status__in=["new", "accepted"])

@property
def required_fields(self):
return super().required_fields + ["duration", "period"]
Expand Down
14 changes: 14 additions & 0 deletions bluebottle/time_based/notifications/registrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class ManagerRegistrationRestartedNotification(ManagerRegistrationNotification):
template = "messages/registrations/manager_registration_restarted"


class ManagerReminderNotification(ManagerRegistrationNotification):
subject = pgettext(
"email", '⏰ Reminder: Keep your participant list up-to-date!'
)
template = "messages/registrations/manager_reminder"


class UserRegistrationNotification(TransitionMessage):
context = {
'title': 'activity.title',
Expand Down Expand Up @@ -139,6 +146,13 @@ def get_context(self, recipient):
return context


class UserReminderNotification(UserRegistrationNotification):
subject = pgettext(
"email", '⏰ Reminder: Update your participation!'
)
template = "messages/registrations/user_reminder"


class DeadlineUserAppliedNotification(UserRegistrationNotification):
subject = pgettext('email', 'You have applied to the activity "{title}"')
template = 'messages/registrations/deadline/user_applied'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "mails/messages/participant_base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans with manager=obj.activity.owner.full_name context 'email' %}
<p>
This is a reminder to check the list of participants for your activity, "{{ title }}".
It’s a good time to make sure everything is up-to-date and that everyone on the list is still active.
</p>
<p>
If you see any changes, like participants who are no longer involved, please update their status.
This will ensure the correct hours are reported.
</p>
<p>Thank you!</p>
{% endblocktrans %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "mails/messages/participant_base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans with manager=obj.activity.owner.full_name context 'email' %}
<p>We just wanted to check if everything is up-to-date with your participation in "{{ title }}".</p>
<p>
If you are no longer participating, please update your participation.
This helps us manage the activity better and keep our reports accurate.
</p>
<p>Thank you!</p>
{% endblocktrans %}
{% endblock %}
133 changes: 133 additions & 0 deletions bluebottle/time_based/tests/test_periodic_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ def setUp(self):
self.initiative.save()

self.activity = self.factory.create(
title='A nice time',
initiative=self.initiative,
review=False,
start=date.today() + timedelta(days=5),
Expand Down Expand Up @@ -684,6 +685,138 @@ def test_cancelled(self):

self.assertEqual(self.activity.status, 'expired')

def test_reminder_monthly_email(self):
self.activity.deadline = date.today() + timedelta(weeks=100)
self.activity.period = 'months'
self.activity.save()
registration = PeriodicRegistrationFactory.create(activity=self.activity)
registration.states.accept(save=True)

mail.outbox = []
for n in range(1, 6):
month = self.activity.start + timedelta(days=n * 31)
self.run_task(month)
if n == 1:
self.assertEqual(registration.participants.count(), 2)
self.assertEqual(self.activity.slots.count(), 2)
self.assertEqual(len(mail.outbox), 0)
if n == 2:
self.assertEqual(registration.participants.count(), 3)
self.assertEqual(self.activity.slots.count(), 3)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, '⏰ Reminder: Update your participation!')
if n == 3:
self.assertEqual(registration.participants.count(), 4)
self.assertEqual(self.activity.slots.count(), 4)
self.assertEqual(len(mail.outbox), 3)
self.assertEqual(mail.outbox[0].subject, '⏰ Reminder: Update your participation!')
self.assertHasMail('⏰ Reminder: Keep your participant list up-to-date!')
if n == 4:
self.assertEqual(registration.participants.count(), 5)
self.assertEqual(self.activity.slots.count(), 5)
self.assertEqual(len(mail.outbox), 5)
registration.states.stop(save=True)
self.assertHasMail('Your contribution to the activity "A nice time" has been stopped')
self.assertHasMail('A participant for your activity "A nice time" has stopped')
if n == 5:
self.assertEqual(registration.participants.count(), 5)
self.assertEqual(self.activity.slots.count(), 6)
self.assertEqual(len(mail.outbox), 7)
if n == 6:
self.assertEqual(registration.participants.count(), 5)
self.assertEqual(self.activity.slots.count(), 7)
self.assertEqual(len(mail.outbox), 7)

def test_reminder_weekly_email(self):
self.activity.deadline = date.today() + timedelta(weeks=10)
self.activity.save()
registration = PeriodicRegistrationFactory.create(activity=self.activity)
registration.states.accept(save=True)

mail.outbox = []
for n in range(1, 7):
week = self.activity.start + timedelta(weeks=n)
self.run_task(week)
if n == 2:
self.assertEqual(registration.participants.count(), 3)
self.assertEqual(self.activity.slots.count(), 3)
self.assertEqual(len(mail.outbox), 1)
self.assertHasMail('⏰ Reminder: Update your participation!')
if n == 3:
self.assertEqual(registration.participants.count(), 4)
self.assertEqual(self.activity.slots.count(), 4)
self.assertEqual(len(mail.outbox), 3)
self.assertHasMail('⏰ Reminder: Keep your participant list up-to-date!')
if n == 4:
self.assertEqual(registration.participants.count(), 5)
self.assertEqual(self.activity.slots.count(), 5)
self.assertEqual(len(mail.outbox), 5)
if n == 5:
self.assertEqual(registration.participants.count(), 6)
self.assertEqual(self.activity.slots.count(), 6)
self.assertEqual(len(mail.outbox), 7)
self.assertHasMail('⏰ Reminder: Keep your participant list up-to-date!')

registration.states.stop(save=True)
self.assertHasMail('Your contribution to the activity "A nice time" has been stopped')
self.assertHasMail('A participant for your activity "A nice time" has stopped')
if n == 6:
self.assertEqual(registration.participants.count(), 6)
self.assertEqual(self.activity.slots.count(), 7)
self.assertEqual(len(mail.outbox), 9)
if n == 7:
self.assertEqual(registration.participants.count(), 6)
self.assertEqual(self.activity.slots.count(), 8)
self.assertEqual(len(mail.outbox), 9)

def test_reminder_daily_email(self):
activity = PeriodicActivityFactory.create(
title='A nice time',
initiative=self.initiative,
review=True,
start=date.today(),
deadline=date.today() + timedelta(days=40),
registration_deadline=None,
period="days"
)
activity.states.publish(save=True)
start = now()
registration = PeriodicRegistrationFactory.create(activity=activity)
registration.states.accept(save=True)
mail.outbox = []

for n in range(1, 20):
day = start + timedelta(days=n)
self.run_task(day)
if n == 3:
self.assertEqual(activity.slots.count(), 4)
self.assertEqual(registration.participants.count(), 4)
self.assertEqual(len(mail.outbox), 0)
if n == 4:
self.assertEqual(activity.slots.count(), 5)
self.assertEqual(registration.participants.count(), 5)
self.assertEqual(len(mail.outbox), 2)
self.assertHasMail('⏰ Reminder: Update your participation!')
self.assertHasMail('⏰ Reminder: Keep your participant list up-to-date!')
if n == 5:
self.assertEqual(activity.slots.count(), 6)
self.assertEqual(registration.participants.count(), 6)
self.assertEqual(len(mail.outbox), 2)
elif n == 7:
self.assertEqual(len(mail.outbox), 4)
elif n == 10:
self.assertEqual(len(mail.outbox), 6)
elif n == 13:
self.assertEqual(len(mail.outbox), 8)
registration.states.stop(save=True)
self.assertEqual(len(mail.outbox), 10)
self.assertHasMail('Your contribution to the activity "A nice time" has been stopped')
self.assertHasMail('A participant for your activity "A nice time" has stopped')
elif n == 16:
self.assertEqual(len(mail.outbox), 10)
elif n == 20:
self.assertEqual(len(mail.outbox), 10)


class ScheduleSlotTestCase(BluebottleTestCase):

Expand Down
40 changes: 38 additions & 2 deletions bluebottle/time_based/triggers/participants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
CreateScheduleContributionEffect,
CreateTimeContributionEffect,
CreateRegistrationEffect,
CreatePeriodicPreparationTimeContributionEffect, CreateScheduleSlotEffect,
)
CreatePeriodicPreparationTimeContributionEffect, CreateScheduleSlotEffect, )
from bluebottle.time_based.messages import (
ParticipantAddedNotification,
)
Expand All @@ -35,6 +34,7 @@
UserParticipantWithdrewNotification,
ManagerParticipantWithdrewNotification, UserScheduledNotification,
)
from bluebottle.time_based.notifications.registrations import UserReminderNotification
from bluebottle.time_based.states import (
ParticipantStateMachine,
DeadlineParticipantStateMachine,
Expand Down Expand Up @@ -457,6 +457,30 @@ def registration_is_accepted(effect):
and effect.instance.registration.status == "accepted"
)

def send_daily_reminder(effect):
"""Is daily recurring, after 4 iterations send every day"""
if not effect.instance.registration:
return False
period = effect.instance.activity.period
count = effect.instance.registration.participants.exclude(id=effect.instance.id).count()
return period == 'days' and count >= 4 and (count - 4) % 3 == 0

def send_weekly_reminder(effect):
"""Is weekly recurring, after 2 iterations send every week"""
if not effect.instance.registration:
return False
period = effect.instance.activity.period
count = effect.instance.registration.participants.exclude(id=effect.instance.id).count()
return period == "weeks" and count > 1

def send_monthly_reminder(effect):
"""Is monthly recurring, after 2 iterations send every month"""
if not effect.instance.registration:
return False
period = effect.instance.activity.period
count = effect.instance.registration.participants.exclude(id=effect.instance.id).count()
return period == "months" and count > 1

triggers = RegistrationParticipantTriggers.triggers + [
TransitionTrigger(
PeriodicParticipantStateMachine.initiate,
Expand All @@ -467,6 +491,18 @@ def registration_is_accepted(effect):
PeriodicParticipantStateMachine.succeed,
conditions=[slot_is_finished],
),
NotificationEffect(
UserReminderNotification,
conditions=[send_daily_reminder],
),
NotificationEffect(
UserReminderNotification,
conditions=[send_weekly_reminder],
),
NotificationEffect(
UserReminderNotification,
conditions=[send_monthly_reminder],
),
],
),
TransitionTrigger(
Expand Down
36 changes: 36 additions & 0 deletions bluebottle/time_based/triggers/slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
CreateTeamSlotParticipantsEffect, SetContributionsStartEffect
)
from bluebottle.time_based.models import PeriodicSlot, ScheduleSlot, TeamScheduleSlot
from bluebottle.time_based.notifications.registrations import ManagerReminderNotification
from bluebottle.time_based.notifications.teams import UserTeamDetailsChangedNotification
from bluebottle.time_based.states import (
ScheduleSlotStateMachine,
Expand All @@ -31,11 +32,46 @@

@register(PeriodicSlot)
class PeriodicSlotTriggers(TriggerManager):

def has_participants(effect):
return effect.instance.activity.active_registrations.count() > 0

def send_daily_reminder(effect):
"""Is daily recurring, at 5 iterations send every 3 days"""
period = effect.instance.activity.period
count = effect.instance.activity.slots.exclude(id=effect.instance.id).count()
return period == 'days' and count >= 4 and (count - 4) % 3 == 0

def send_weekly_reminder(effect):
"""Is weekly recurring, at 3 iterations send every week"""
period = effect.instance.activity.period
count = effect.instance.activity.slots.exclude(id=effect.instance.id).count()
return period == "weeks" and count > 2

def send_monthly_reminder(effect):
"""Is monthly recurring, at 3 iterations send every month"""
period = effect.instance.activity.period
count = effect.instance.activity.slots.exclude(id=effect.instance.id).count()
return period == "months" and count > 2

triggers = [
TransitionTrigger(
PeriodicSlotStateMachine.initiate,
effects=[
CreatePeriodicParticipantsEffect,
NotificationEffect(
ManagerReminderNotification,
conditions=[has_participants, send_daily_reminder],
),
NotificationEffect(
ManagerReminderNotification,
conditions=[has_participants, send_weekly_reminder],
),
NotificationEffect(
ManagerReminderNotification,
conditions=[has_participants, send_monthly_reminder],
),

]
),
TransitionTrigger(
Expand Down
Loading