Skip to content

Commit

Permalink
Send reservation reminders
Browse files Browse the repository at this point in the history
Add a management command to send reservation reminders for reservations
that start within 24 hours.
  • Loading branch information
japauliina committed Dec 20, 2024
1 parent f4b2863 commit 0360ce7
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 0 deletions.
3 changes: 3 additions & 0 deletions locale/fi/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -2202,3 +2202,6 @@ msgstr "Takaisin"
#, python-brace-format
msgid "Missing information required by SAP: {}"
msgstr "SAP:n vaatimia tietoja puuttuu: {}"

msgid "Reservation reminder"
msgstr "Muistutus varauksesta"
3 changes: 3 additions & 0 deletions locale/sv/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1789,3 +1789,6 @@ msgid ""
msgstr ""
"anger om användaren är en General Administrator med särskilda behörigheter "
"för många objekt inom Respa"

msgid "Reservation reminder"
msgstr "Bokningspåminnelse"
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions notifications/management/commands/send_reservation_reminders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from datetime import timedelta

from django.core.management import BaseCommand
from django.utils import timezone

from resources.models import Reservation

logger = logging.getLogger(__name__)


def send_reservation_reminder(reservation):
reservation.send_reservation_mail(notification_type="reservation_reminder")
reservation.reminder_sent = True
reservation.save()


class Command(BaseCommand):
help = "Send reservation reminders"

def handle(self, *args, **options):
"""
Send reservation reminders for reservations that start in the next 24 hours.
"""

reservations = Reservation.objects.filter(
begin__gte=timezone.now(),
begin__lte=timezone.now() + timedelta(days=1),
reminder_sent=False,
)

logger.info(f"Sending reminders for {reservations.count()} reservations")

for reservation in reservations:
send_reservation_reminder(reservation)
71 changes: 71 additions & 0 deletions notifications/migrations/0012_add_reservation_reminder_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Generated by Django 2.2.11 on 2024-12-19 08:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("notifications", "0011_create_invoice_requested_notifications"),
]

operations = [
migrations.AlterField(
model_name="notificationtemplate",
name="type",
field=models.CharField(
choices=[
("reservation_requested", "Reservation requested"),
(
"reservation_requested_official",
"Reservation requested official",
),
("reservation_requested", "Reservation invoice requested"),
(
"reservation_requested_official",
"Reservation invoice requested official",
),
("reservation_cancelled", "Reservation cancelled"),
(
"reservation_cancelled_official",
"Reservation cancelled official",
),
("reservation_confirmed", "Reservation confirmed"),
(
"reservation_confirmed_official",
"Reservation confirmed official",
),
("reservation_created", "Reservation created"),
("reservation_changed", "Reservation changed"),
("reservation_denied", "Reservation denied"),
("reservation_denied_official", "Reservation denied official"),
(
"reservation_created_with_access_code",
"Reservation created with access code",
),
(
"reservation_access_code_created",
"Access code was created for a reservation",
),
("paid_reservation_approved", "Paid reservation approved"),
(
"paid_reservation_approved_official",
"Paid reservation approved official",
),
("catering_order_created", "Catering order created"),
("catering_order_modified", "Catering order modified"),
("catering_order_deleted", "Catering order deleted"),
("reservation_comment_created", "Reservation comment created"),
(
"catering_order_comment_created",
"Catering order comment created",
),
("reservation_reminder", "Reservation reminder"),
],
db_index=True,
max_length=100,
unique=True,
verbose_name="Type",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Generated by Django 2.2.11 on 2024-12-19 08:35

from django.db import migrations
from django.template import Context, Template

BODY_TEMPLATES = {
"en": """
Hello,
{{ message }}
{% verbatim %}
Resource: {{ resource }}
Unit: {{ unit }}
Starts: {{ begin }}
Ends: {{ end }}
{% endverbatim %}
This is an automated message, please don't reply to this.
Best regards,
Varaamo
""",
"fi": """
Hei,
{{ message }}
{% verbatim %}
Tila: {{ resource }}
Toimipiste: {{ unit }}
Alkaa: {{ begin }}
Päättyy: {{ end }}
{% endverbatim %}
Tämä on automaattinen viesti, joten ethän vastaa tähän.
Ystävällisin terveisin,
Varaamo
""",
}


def forwards(apps, schema_editor):
NotificationTemplate = apps.get_model("notifications", "NotificationTemplate")
NotificationTemplateTranslation = apps.get_model(
"notifications",
"NotificationTemplateTranslation",
)

notification, created = NotificationTemplate.objects.get_or_create(
type="reservation_reminder"
)
if created:
translations = []
for language, subject, message in [
(
"en",
"Upcoming reservation",
"You have a reservation coming up soon:",
),
(
"fi",
"Muistathan lähestyvän varauksesi",
"Sinulla on pian varaus:",
),
]:
body = Template(BODY_TEMPLATES[language]).render(
Context(
{
"message": message,
}
)
)
translations.append(
NotificationTemplateTranslation(
master_id=notification.id,
language_code=language,
subject=subject,
body=body,
)
)
NotificationTemplateTranslation.objects.bulk_create(translations)


class Migration(migrations.Migration):

dependencies = [
("notifications", "0012_add_reservation_reminder_type"),
]

operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]
3 changes: 3 additions & 0 deletions notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class NotificationType:
RESERVATION_COMMENT_CREATED = "reservation_comment_created"
CATERING_ORDER_COMMENT_CREATED = "catering_order_comment_created"

RESERVATION_REMINDER = "reservation_reminder"


class NotificationTemplateException(Exception):
pass
Expand Down Expand Up @@ -107,6 +109,7 @@ class NotificationTemplate(TranslatableModel):
NotificationType.CATERING_ORDER_COMMENT_CREATED,
_("Catering order comment created"),
),
(NotificationType.RESERVATION_REMINDER, _("Reservation reminder")),
)

type = models.CharField(
Expand Down
69 changes: 69 additions & 0 deletions notifications/tests/test_send_reservation_reminders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from datetime import timedelta
from unittest import mock

import pytest
from django.core.management import call_command
from django.utils import timezone

from resources.models import Reservation, Resource, ResourceType, Unit


@pytest.fixture
def reservation():
resource_type = ResourceType.objects.get_or_create(main_type="space", name="space")[0]
unit = Unit.objects.create(name="Test unit")
resource = Resource.objects.create(
name="Test resource",
type=resource_type,
unit=unit,
)
reservation = Reservation.objects.create(
resource=resource,
begin=timezone.now() + timedelta(hours=23),
end=timezone.now() + timedelta(hours=24),
reminder_sent=False,
)
return reservation


@pytest.mark.django_db
def test_send_reservation_reminder(reservation):
"""
Test that the reminder is sent and the reminder_sent field is updated.
"""
with mock.patch(
"resources.models.Reservation.send_reservation_mail"
) as mock_send_mail:
call_command("send_reservation_reminders")
reservation.refresh_from_db()
assert reservation.reminder_sent is True
mock_send_mail.assert_called_once_with(notification_type="reservation_reminder")


@pytest.mark.django_db
def test_no_reminder_if_already_sent(reservation):
"""
Test that the reminder is not sent if it has already been sent.
"""
reservation.reminder_sent = True
reservation.save()
with mock.patch(
"resources.models.Reservation.send_reservation_mail"
) as mock_send_mail:
call_command("send_reservation_reminders")
mock_send_mail.assert_not_called()


@pytest.mark.django_db
def test_no_reminder_for_reservation_not_within_24_hours(reservation):
"""
Test that the reminder is not sent if the reservation is not in the next 24 hours.
"""
reservation.begin = timezone.now() + timedelta(days=2)
reservation.end = timezone.now() + timedelta(days=2, hours=1)
reservation.save()
with mock.patch(
"notifications.management.commands.send_reservation_reminders.send_reservation_reminder"
) as mock_send_reminder:
call_command("send_reservation_reminders")
mock_send_reminder.assert_not_called()
18 changes: 18 additions & 0 deletions resources/migrations/0123_reservation_reminder_sent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.11 on 2024-12-18 13:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('resources', '0122_auto_20241218_1457'),
]

operations = [
migrations.AddField(
model_name='reservation',
name='reminder_sent',
field=models.BooleanField(default=False, verbose_name='Reminder sent'),
),
]
1 change: 1 addition & 0 deletions resources/models/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class Reservation(ModifiableModel):
state = models.CharField(
max_length=32, choices=STATE_CHOICES, verbose_name=_("State"), default=CREATED
)
reminder_sent = models.BooleanField(default=False, verbose_name=_("Reminder sent"))
approver = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Approver"),
Expand Down

0 comments on commit 0360ce7

Please sign in to comment.