diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po
index 28677f6d9..fe623d0b6 100644
--- a/locale/fi/LC_MESSAGES/django.po
+++ b/locale/fi/LC_MESSAGES/django.po
@@ -2262,6 +2262,11 @@ msgctxt "EmailType"
msgid "Seasonal reservation rejected single"
msgstr "Kausivaraus hylätty yksittäinen"
+#: tilavarauspalvelu/enums.py
+msgctxt "EmailType"
+msgid "Staff notification application section cancelled"
+msgstr "Käsittelijä ilmoitus kausivaraushakemuksen osan perumisesta"
+
#: tilavarauspalvelu/enums.py
msgctxt "EmailType"
msgid "Staff notification reservation made"
@@ -2948,16 +2953,22 @@ msgid "Seasonal Booking"
msgstr "Kausivaraus"
#: tilavarauspalvelu/integrations/email/template_context/application.py
-#: tilavarauspalvelu/integrations/email/template_context/reservation.py
msgctxt "Email"
-msgid "Day"
-msgstr "Päivä"
+msgid "The customer has canceled the seasonal booking"
+msgstr "Asiakas on perunut kausivarauksen"
+
+#: tilavarauspalvelu/integrations/email/template_context/application.py
+msgctxt "Email"
+msgid ""
+"The customer has canceled all space reservations included in the seasonal "
+"booking"
+msgstr "Asiakas on perunut kaikki kausivaraukseen kuuluvat tilavaraukset"
#: tilavarauspalvelu/integrations/email/template_context/application.py
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
msgctxt "Email"
-msgid "Time"
-msgstr "Kellonaika"
+msgid "You can view the booking at"
+msgstr "Voit tarkistaa varauksen tiedot osoitteessa"
#: tilavarauspalvelu/integrations/email/template_context/common.py
msgctxt "Email"
@@ -3203,6 +3214,16 @@ msgid ""
"changed"
msgstr "Kausivaraukseesi kuuluvan tilavarauksen ajankohta on muuttunut"
+#: tilavarauspalvelu/integrations/email/template_context/reservation.py
+msgctxt "Email"
+msgid "Day"
+msgstr "Päivä"
+
+#: tilavarauspalvelu/integrations/email/template_context/reservation.py
+msgctxt "Email"
+msgid "Time"
+msgstr "Kellonaika"
+
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
#, python-format
msgctxt "Email"
@@ -3222,11 +3243,6 @@ msgctxt "Email"
msgid "Reservee name"
msgstr "Varaajan nimi"
-#: tilavarauspalvelu/integrations/email/template_context/reservation.py
-msgctxt "Email"
-msgid "You can view the booking at"
-msgstr "Voit tarkistaa varauksen tiedot osoitteessa"
-
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
#, python-format
msgctxt "Email"
diff --git a/locale/sv/LC_MESSAGES/django.po b/locale/sv/LC_MESSAGES/django.po
index 2d621b503..d60d2707a 100644
--- a/locale/sv/LC_MESSAGES/django.po
+++ b/locale/sv/LC_MESSAGES/django.po
@@ -2196,6 +2196,11 @@ msgctxt "EmailType"
msgid "Seasonal reservation rejected single"
msgstr ""
+#: tilavarauspalvelu/enums.py
+msgctxt "EmailType"
+msgid "Staff notification application section cancelled"
+msgstr ""
+
#: tilavarauspalvelu/enums.py
msgctxt "EmailType"
msgid "Staff notification reservation made"
@@ -2884,16 +2889,22 @@ msgid "Seasonal Booking"
msgstr "Säsongsbokning"
#: tilavarauspalvelu/integrations/email/template_context/application.py
-#: tilavarauspalvelu/integrations/email/template_context/reservation.py
msgctxt "Email"
-msgid "Day"
-msgstr "Dag"
+msgid "The customer has canceled the seasonal booking"
+msgstr "Kunden har avbokat säsongsbokningen"
+
+#: tilavarauspalvelu/integrations/email/template_context/application.py
+msgctxt "Email"
+msgid ""
+"The customer has canceled all space reservations included in the seasonal "
+"booking"
+msgstr "Kunden har avbokat alla lokalbokningar som ingår i säsongsbokningen"
#: tilavarauspalvelu/integrations/email/template_context/application.py
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
msgctxt "Email"
-msgid "Time"
-msgstr "Tid"
+msgid "You can view the booking at"
+msgstr "Du kan se bokningen på"
#: tilavarauspalvelu/integrations/email/template_context/common.py
msgctxt "Email"
@@ -3140,6 +3151,16 @@ msgid ""
"changed"
msgstr "Tiden för lokalbokningen som ingår i din säsongsbokning har ändrats"
+#: tilavarauspalvelu/integrations/email/template_context/reservation.py
+msgctxt "Email"
+msgid "Day"
+msgstr "Dag"
+
+#: tilavarauspalvelu/integrations/email/template_context/reservation.py
+msgctxt "Email"
+msgid "Time"
+msgstr "Tid"
+
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
#, python-format
msgctxt "Email"
@@ -3157,11 +3178,6 @@ msgctxt "Email"
msgid "Reservee name"
msgstr "Bokare"
-#: tilavarauspalvelu/integrations/email/template_context/reservation.py
-msgctxt "Email"
-msgid "You can view the booking at"
-msgstr "Du kan se bokningen på"
-
#: tilavarauspalvelu/integrations/email/template_context/reservation.py
#, python-format
msgctxt "Email"
diff --git a/templates/email/html/partials/application_section_name.jinja b/templates/email/html/partials/application_section_name.jinja
index 33380b2e7..7d704a2cd 100644
--- a/templates/email/html/partials/application_section_name.jinja
+++ b/templates/email/html/partials/application_section_name.jinja
@@ -1,3 +1,3 @@
-{{ seasonal_booking_label | safe}}: {{ application_section_name | safe }}, {{ application_round_name | safe }}
+{{ seasonal_booking_label | safe}}: {{ application_section_name | safe }}, {{ application_round_name | safe }}
diff --git a/templates/email/html/staff_notification_application_section_cancelled.jinja b/templates/email/html/staff_notification_application_section_cancelled.jinja
new file mode 100644
index 000000000..9a922810b
--- /dev/null
+++ b/templates/email/html/staff_notification_application_section_cancelled.jinja
@@ -0,0 +1,20 @@
+{% extends "email/html/partials/base.jinja" %}
+
+{% block content_main %}
+ {{ text_reservation_cancelled | sentence | safe }}
+ {{ cancel_reason_label | safe }}: {{ cancel_reason | safe }}
+ {% include "email/html/partials/application_section_name.jinja" %}
+
+
+ {{ view_booking_at_label | safe }}:
+ {% for series in cancelled_reservation_series %}
+ {{ series.weekday | safe }}: {{ series.time | safe }}
+ {{ series.url | safe }}
+ {% endfor %}
+{% endblock %}
+
+{% block content_secondary %}{% endblock %}
+
+{% block content_closing %}
+ {% include "email/html/partials/closing_staff.jinja" %}
+{% endblock %}
diff --git a/templates/email/text/staff_notification_application_section_cancelled.jinja b/templates/email/text/staff_notification_application_section_cancelled.jinja
new file mode 100644
index 000000000..da8c82682
--- /dev/null
+++ b/templates/email/text/staff_notification_application_section_cancelled.jinja
@@ -0,0 +1,14 @@
+{{ salutation | safe }},
+
+{{ text_reservation_cancelled | sentence | safe }}
+
+{{ cancel_reason_label | safe }}: {{ cancel_reason | safe }}
+
+{% include "email/text/partials/application_section_name.jinja" %}
+
+{{ view_booking_at_label | safe }}:
+
+{% for series in cancelled_reservation_series %}{{ series.weekday | safe }} {{ series.time | safe }}
+{{ series.url | safe }}
+{% endfor %}
+{% include "email/text/partials/closing_staff.jinja" %}
diff --git a/tests/test_graphql_api/test_recurring_reservation/test_cancel_series.py b/tests/test_graphql_api/test_recurring_reservation/test_cancel_series.py
index 14ec152f8..eda1c83b1 100644
--- a/tests/test_graphql_api/test_recurring_reservation/test_cancel_series.py
+++ b/tests/test_graphql_api/test_recurring_reservation/test_cancel_series.py
@@ -6,6 +6,7 @@
from freezegun import freeze_time
from tilavarauspalvelu.enums import ReservationStateChoice, ReservationTypeChoice
+from tilavarauspalvelu.integrations.email.main import EmailService
from utils.date_utils import local_date, local_datetime
from tests.factories import (
@@ -14,6 +15,7 @@
ReservationCancelReasonFactory,
UserFactory,
)
+from tests.helpers import patch_method
from .helpers import CANCEL_SECTION_SERIES_MUTATION, create_reservation_series
@@ -23,6 +25,8 @@
]
+@patch_method(EmailService.send_application_section_cancelled)
+@patch_method(EmailService.send_staff_notification_application_section_cancelled)
@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__cancel_whole_remaining(graphql):
reason = ReservationCancelReasonFactory.create()
@@ -59,6 +63,9 @@ def test_recurring_reservations__cancel_section_series__cancel_whole_remaining(g
assert response.first_query_object == {"cancelled": 5, "future": 5}
assert reservation_series.reservations.count() == 9
+ assert EmailService.send_application_section_cancelled.called is True
+ assert EmailService.send_staff_notification_application_section_cancelled.called is True
+
@freeze_time(local_datetime(year=2024, month=1, day=1))
def test_recurring_reservations__cancel_section_series__cancel_details_not_required(graphql):
diff --git a/tests/test_integrations/test_email/test_application_context.py b/tests/test_integrations/test_email/test_application_context.py
index 30602667b..45ebf0801 100644
--- a/tests/test_integrations/test_email/test_application_context.py
+++ b/tests/test_integrations/test_email/test_application_context.py
@@ -9,6 +9,9 @@
get_context_for_application_received,
get_context_for_application_section_cancelled,
)
+from tilavarauspalvelu.integrations.email.template_context.application import (
+ get_context_for_staff_notification_application_section_cancelled,
+)
from tests.helpers import TranslationsFromPOFiles
from tests.test_integrations.test_email.helpers import (
@@ -24,6 +27,9 @@
CLOSING_POLITE_CONTEXT_EN,
CLOSING_POLITE_CONTEXT_FI,
CLOSING_POLITE_CONTEXT_SV,
+ CLOSING_STAFF_CONTEXT_EN,
+ CLOSING_STAFF_CONTEXT_FI,
+ CLOSING_STAFF_CONTEXT_SV,
SEASONAL_RESERVATION_CHECK_BOOKING_DETAILS_LINK_EN,
SEASONAL_RESERVATION_CHECK_BOOKING_DETAILS_LINK_FI,
SEASONAL_RESERVATION_CHECK_BOOKING_DETAILS_LINK_SV,
@@ -348,3 +354,122 @@ def test_get_context_for_application_section_cancelled_sv():
**CLOSING_CONTEXT_SV,
**AUTOMATIC_REPLY_CONTEXT_SV,
}
+
+
+# type: EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED #####################################################
+
+
+@freeze_time("2024-01-01")
+def test_get_context_for_staff_notification_application_section_cancelled__en():
+ with TranslationsFromPOFiles():
+ context = get_context_for_staff_notification_application_section_cancelled(
+ application_section_name="[HAKEMUKSEN OSAN NIMI]",
+ application_round_name="[KAUSIVARAUSKIERROKSEN NIMI]",
+ cancel_reason="[PERUUTUKSEN SYY]",
+ language="en",
+ )
+
+ assert context == {
+ "email_recipient_name": None,
+ "title": "The customer has canceled the seasonal booking",
+ "text_reservation_cancelled": (
+ "The customer has canceled all space reservations included in the seasonal booking"
+ ),
+ "seasonal_booking_label": "Seasonal Booking",
+ "application_section_name": "[HAKEMUKSEN OSAN NIMI]",
+ "application_round_name": "[KAUSIVARAUSKIERROKSEN NIMI]",
+ "cancel_reason_label": "Reason",
+ "cancel_reason": "[PERUUTUKSEN SYY]",
+ "view_booking_at_label": "You can view the booking at",
+ "cancelled_reservation_series": [
+ {
+ "weekday": "Monday",
+ "time": "13:00-15:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/1234",
+ },
+ {
+ "weekday": "Tuesday",
+ "time": "21:00-22:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/5678",
+ },
+ ],
+ **BASE_TEMPLATE_CONTEXT_EN,
+ **CLOSING_CONTEXT_EN,
+ **CLOSING_STAFF_CONTEXT_EN,
+ }
+
+
+@freeze_time("2024-01-01")
+def test_get_context_for_staff_notification_application_section_cancelled__fi():
+ with TranslationsFromPOFiles():
+ context = get_context_for_staff_notification_application_section_cancelled(
+ application_section_name="[HAKEMUKSEN OSAN NIMI]",
+ application_round_name="[KAUSIVARAUSKIERROKSEN NIMI]",
+ cancel_reason="[PERUUTUKSEN SYY]",
+ language="fi",
+ )
+
+ assert context == {
+ "email_recipient_name": None,
+ "title": "Asiakas on perunut kausivarauksen",
+ "text_reservation_cancelled": "Asiakas on perunut kaikki kausivaraukseen kuuluvat tilavaraukset",
+ "seasonal_booking_label": "Kausivaraus",
+ "application_section_name": "[HAKEMUKSEN OSAN NIMI]",
+ "application_round_name": "[KAUSIVARAUSKIERROKSEN NIMI]",
+ "cancel_reason_label": "Syy",
+ "cancel_reason": "[PERUUTUKSEN SYY]",
+ "view_booking_at_label": "Voit tarkistaa varauksen tiedot osoitteessa",
+ "cancelled_reservation_series": [
+ {
+ "weekday": "Monday",
+ "time": "13:00-15:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/1234",
+ },
+ {
+ "weekday": "Tuesday",
+ "time": "21:00-22:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/5678",
+ },
+ ],
+ **BASE_TEMPLATE_CONTEXT_FI,
+ **CLOSING_CONTEXT_FI,
+ **CLOSING_STAFF_CONTEXT_FI,
+ }
+
+
+@freeze_time("2024-01-01")
+def test_get_context_for_staff_notification_application_section_cancelled_sv():
+ with TranslationsFromPOFiles():
+ context = get_context_for_staff_notification_application_section_cancelled(
+ application_section_name="[HAKEMUKSEN OSAN NIMI]",
+ application_round_name="[KAUSIVARAUSKIERROKSEN NIMI]",
+ cancel_reason="[PERUUTUKSEN SYY]",
+ language="sv",
+ )
+
+ assert context == {
+ "email_recipient_name": None,
+ "title": "Kunden har avbokat säsongsbokningen",
+ "text_reservation_cancelled": "Kunden har avbokat alla lokalbokningar som ingår i säsongsbokningen",
+ "seasonal_booking_label": "Säsongsbokning",
+ "application_section_name": "[HAKEMUKSEN OSAN NIMI]",
+ "application_round_name": "[KAUSIVARAUSKIERROKSEN NIMI]",
+ "cancel_reason_label": "Orsak",
+ "cancel_reason": "[PERUUTUKSEN SYY]",
+ "view_booking_at_label": "Du kan se bokningen på",
+ "cancelled_reservation_series": [
+ {
+ "weekday": "Monday",
+ "time": "13:00-15:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/1234",
+ },
+ {
+ "weekday": "Tuesday",
+ "time": "21:00-22:00",
+ "url": "https://fake.varaamo.hel.fi/kasittely/reservations/5678",
+ },
+ ],
+ **BASE_TEMPLATE_CONTEXT_SV,
+ **CLOSING_CONTEXT_SV,
+ **CLOSING_STAFF_CONTEXT_SV,
+ }
diff --git a/tests/test_integrations/test_email/test_email_service.py b/tests/test_integrations/test_email/test_email_service.py
index ec9480425..f3bd55e5a 100644
--- a/tests/test_integrations/test_email/test_email_service.py
+++ b/tests/test_integrations/test_email/test_email_service.py
@@ -1221,6 +1221,47 @@ def test_email_service__send_seasonal_reservation_rejected_single(outbox):
assert sorted(outbox[0].bcc) == ["reservee@email.com", "user@email.com"]
+# type: EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED ########################################################################
+
+
+@override_settings(SEND_EMAILS=True)
+@freeze_time("2024-01-01")
+def test_email_service__send_staff_notification_application_section_cancelled_email(outbox):
+ unit = UnitFactory.create(name="foo", name_en="foo")
+
+ UserFactory.create_with_unit_role(
+ units=[unit],
+ email="admin1@email.com",
+ reservation_notification=ReservationNotification.ALL,
+ preferred_language="fi",
+ )
+ UserFactory.create_with_unit_role(
+ units=[unit],
+ email="admin2@email.com",
+ reservation_notification=ReservationNotification.ALL,
+ preferred_language="en",
+ )
+
+ application_section = ApplicationSectionFactory.create_in_status_handled(application__user__email="user@email.com")
+ create_reservation_series(
+ reservation_unit__unit=unit,
+ user=application_section.application.user,
+ allocated_time_slot__reservation_unit_option__application_section=application_section,
+ reservations__reservee_email="reservee@email.com",
+ )
+
+ with TranslationsFromPOFiles():
+ EmailService.send_staff_notification_application_section_cancelled(application_section=application_section)
+
+ assert len(outbox) == 2
+
+ assert outbox[0].subject == "Asiakas on perunut kausivarauksen"
+ assert sorted(outbox[0].bcc) == ["admin1@email.com"]
+
+ assert outbox[1].subject == "The customer has canceled the seasonal booking"
+ assert sorted(outbox[1].bcc) == ["admin2@email.com"]
+
+
# type: EmailType.STAFF_NOTIFICATION_RESERVATION_MADE ##################################################################
diff --git a/tests/test_integrations/test_email/test_render_html.py b/tests/test_integrations/test_email/test_render_html.py
index 600898bd9..f1d814624 100644
--- a/tests/test_integrations/test_email/test_render_html.py
+++ b/tests/test_integrations/test_email/test_render_html.py
@@ -922,6 +922,43 @@ def test_render_seasonal_reservation_rejected_single__html():
# Staff ################################################################################################################
+@freeze_time("2024-01-01 12:00:00+02:00")
+def test_render_staff_notification_application_section_cancelled_email__html():
+ context = get_mock_data(email_type=EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED, language="en")
+ html_content = render_html(email_type=EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED, context=context)
+ text_content = html_email_to_text(html_content)
+
+ assert text_content == cleandoc(
+ """
+ ![](https://makasiini.hel.ninja/helsinki-logos/helsinki-logo-black.png)
+
+ **Varaamo**
+
+ **Hi,**
+
+ The customer has canceled all space reservations included in the seasonal booking.
+
+ Reason: [PERUUTUKSEN SYY]
+ Seasonal Booking: [HAKEMUKSEN OSAN NIMI], [KAUSIVARAUSKIERROKSEN NIMI]
+ You can view the booking at:
+ Monday: 13:00-15:00
+
+ Tuesday: 21:00-22:00
+
+
+ Kind regards
+ Varaamo
+ This is an automated message, please do not reply.
+
+ ![](https://makasiini.hel.ninja/helsinki-logos/helsinki-logo-black.png)
+
+ **Varaamo**
+
+ (C) City of Helsinki 2024
+ """
+ )
+
+
@freeze_time("2024-01-01 12:00:00+02:00")
def test_render_reservation_staff_notification_reservation_made__html():
context = get_mock_data(email_type=EmailType.STAFF_NOTIFICATION_RESERVATION_MADE, language="en")
diff --git a/tests/test_integrations/test_email/test_render_text.py b/tests/test_integrations/test_email/test_render_text.py
index 717881595..a50894936 100644
--- a/tests/test_integrations/test_email/test_render_text.py
+++ b/tests/test_integrations/test_email/test_render_text.py
@@ -732,6 +732,36 @@ def test_render_seasonal_reservation_rejected_single__text():
# Staff ################################################################################################################
+@freeze_time("2024-01-01 12:00:00+02:00")
+def test_render_staff_notification_application_section_cancelled_email_text():
+ context = get_mock_data(email_type=EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED, language="en")
+ text_content = render_text(email_type=EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED, context=context)
+
+ assert text_content == cleandoc(
+ """
+ Hi,
+
+ The customer has canceled all space reservations included in the seasonal booking.
+
+ Reason: [PERUUTUKSEN SYY]
+
+ Seasonal Booking: [HAKEMUKSEN OSAN NIMI], [KAUSIVARAUSKIERROKSEN NIMI]
+
+ You can view the booking at:
+
+ Monday 13:00-15:00
+ https://fake.varaamo.hel.fi/kasittely/reservations/1234
+ Tuesday 21:00-22:00
+ https://fake.varaamo.hel.fi/kasittely/reservations/5678
+
+ Kind regards
+ Varaamo
+
+ This is an automated message, please do not reply.
+ """
+ )
+
+
@freeze_time("2024-01-01 12:00:00+02:00")
def test_render_reservation_staff_notification_reservation_made__text():
context = get_mock_data(email_type=EmailType.STAFF_NOTIFICATION_RESERVATION_MADE, language="en")
diff --git a/tilavarauspalvelu/admin/email_template/forms.py b/tilavarauspalvelu/admin/email_template/forms.py
index e932caa61..380288396 100644
--- a/tilavarauspalvelu/admin/email_template/forms.py
+++ b/tilavarauspalvelu/admin/email_template/forms.py
@@ -88,6 +88,8 @@ def select_tester_form(*, email_type: EmailType) -> type[BaseEmailTemplateForm]
return SeasonalReservationRejectedSingleTemplateTesterForm
# Staff
+ case EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED:
+ return StaffNotificationApplicationSectionCancelledTemplateTesterForm
case EmailType.STAFF_NOTIFICATION_RESERVATION_MADE:
return StaffNotificationReservationMadeEmailTemplateTesterForm
case EmailType.STAFF_NOTIFICATION_RESERVATION_REQUIRES_HANDLING:
@@ -561,3 +563,18 @@ def to_context(self) -> EmailContext:
reservation_name=self.cleaned_data["reservation_name"],
reservation_id=self.cleaned_data["reservation_id"],
)
+
+
+class StaffNotificationApplicationSectionCancelledTemplateTesterForm(EmailRecipientFormMixin, BaseEmailTemplateForm):
+ application_section_name = forms.CharField(initial="[HAKEMUKSEN OSAN NIMI]")
+ application_round_name = forms.CharField(initial="[KAUSIVARAUSKIERROKSEN NIMI]")
+ cancel_reason = forms.CharField(initial="[PERUUTUKSEN SYY]", widget=text_widget)
+
+ def to_context(self) -> EmailContext:
+ return get_context_for_application_section_cancelled(
+ **super().get_context_params(),
+ email_recipient_name=self.cleaned_data["email_recipient_name"],
+ application_section_name=self.cleaned_data["application_section_name"],
+ application_round_name=self.cleaned_data["application_round_name"],
+ cancel_reason=self.cleaned_data["cancel_reason"],
+ )
diff --git a/tilavarauspalvelu/admin/email_template/utils.py b/tilavarauspalvelu/admin/email_template/utils.py
index 1d031e5bf..f502b0df2 100644
--- a/tilavarauspalvelu/admin/email_template/utils.py
+++ b/tilavarauspalvelu/admin/email_template/utils.py
@@ -27,6 +27,9 @@
get_context_for_staff_notification_reservation_requires_handling,
get_context_for_user_anonymization,
)
+from tilavarauspalvelu.integrations.email.template_context.application import (
+ get_context_for_staff_notification_application_section_cancelled,
+)
from utils.date_utils import local_datetime
if TYPE_CHECKING:
@@ -250,6 +253,13 @@ def get_mock_data(*, email_type: EmailType, language: Lang, **kwargs: Any) -> Em
# Staff ########################################################################################################
+ case EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED:
+ return get_context_for_staff_notification_application_section_cancelled(
+ application_section_name=application_section_name,
+ application_round_name=application_round_name,
+ cancel_reason=cancel_reason,
+ language=language,
+ )
case EmailType.STAFF_NOTIFICATION_RESERVATION_MADE:
return get_context_for_staff_notification_reservation_made(
reservee_name=reservee_name,
diff --git a/tilavarauspalvelu/api/graphql/types/application_section/serializers.py b/tilavarauspalvelu/api/graphql/types/application_section/serializers.py
index f15e19e93..44865c736 100644
--- a/tilavarauspalvelu/api/graphql/types/application_section/serializers.py
+++ b/tilavarauspalvelu/api/graphql/types/application_section/serializers.py
@@ -273,9 +273,10 @@ def save(self, **kwargs: Any) -> CancellationOutput:
.distinct()
)
+ cancellable_reservations_count = cancellable_reservations.count()
data = CancellationOutput(
expected_cancellations=future_reservations.count(),
- actual_cancellations=cancellable_reservations.count(),
+ actual_cancellations=cancellable_reservations_count,
)
cancellable_reservations.update(
@@ -284,8 +285,9 @@ def save(self, **kwargs: Any) -> CancellationOutput:
cancel_details=self.validated_data.get("cancel_details", ""),
)
- if cancellable_reservations.count():
+ if cancellable_reservations_count:
EmailService.send_application_section_cancelled(application_section=self.instance)
+ EmailService.send_staff_notification_application_section_cancelled(application_section=self.instance)
return data
diff --git a/tilavarauspalvelu/enums.py b/tilavarauspalvelu/enums.py
index deb31fbea..769ae53dd 100644
--- a/tilavarauspalvelu/enums.py
+++ b/tilavarauspalvelu/enums.py
@@ -305,6 +305,10 @@ class EmailType(models.TextChoices):
"""Staff rejects a single reservation in a seasonal reservation series"""
# Staff
+ STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED = (
+ "staff_notification_application_section_cancelled",
+ pgettext_lazy("EmailType", "Staff notification application section cancelled"),
+ )
STAFF_NOTIFICATION_RESERVATION_MADE = (
"staff_notification_reservation_made",
pgettext_lazy("EmailType", "Staff notification reservation made"),
diff --git a/tilavarauspalvelu/integrations/email/main.py b/tilavarauspalvelu/integrations/email/main.py
index fa0e53862..1ef607dc3 100644
--- a/tilavarauspalvelu/integrations/email/main.py
+++ b/tilavarauspalvelu/integrations/email/main.py
@@ -43,6 +43,7 @@
get_context_for_staff_notification_reservation_requires_handling,
get_context_for_user_anonymization,
)
+from .template_context.application import get_context_for_staff_notification_application_section_cancelled
if TYPE_CHECKING:
from tilavarauspalvelu.models import ApplicationSection, RecurringReservation, Reservation
@@ -166,6 +167,33 @@ def send_application_section_cancelled(
email = EmailData.build(recipients, context, email_type)
send_emails_in_batches_task.delay(email_data=email)
+ @staticmethod
+ def send_staff_notification_application_section_cancelled(application_section: ApplicationSection) -> None:
+ """Sends an email to Staff that the whole application section was cancelled by the user"""
+ reservation: Reservation | None = application_section.actions.get_last_reservation()
+ if reservation is None:
+ return
+
+ recipients_by_language = get_reservation_staff_notification_recipients_by_language(reservation)
+ if not recipients_by_language:
+ SentryLogger.log_message(
+ "No recipients for staff notification application section cancelled email",
+ details={"reservation": reservation.pk},
+ )
+ return
+
+ emails: list[EmailData] = []
+ email_type = EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED
+
+ for language, recipients in recipients_by_language.items():
+ context = get_context_for_staff_notification_application_section_cancelled(
+ application_section, language=language
+ )
+ email = EmailData.build(recipients, context, email_type)
+ emails.append(email)
+
+ send_multiple_emails_in_batches_task.delay(emails=emails)
+
# Permissions ######################################################################################################
@staticmethod
diff --git a/tilavarauspalvelu/integrations/email/template_context/application.py b/tilavarauspalvelu/integrations/email/template_context/application.py
index 0d730ea3d..a1d0c07b3 100644
--- a/tilavarauspalvelu/integrations/email/template_context/application.py
+++ b/tilavarauspalvelu/integrations/email/template_context/application.py
@@ -4,6 +4,8 @@
from django.utils.translation import pgettext
+from tilavarauspalvelu.enums import WeekdayChoice
+from tilavarauspalvelu.models import RecurringReservation
from tilavarauspalvelu.translation import get_attr_by_language, get_translated
from .common import (
@@ -11,9 +13,12 @@
get_contex_for_base_template,
get_contex_for_closing,
get_contex_for_closing_polite,
+ get_contex_for_closing_staff,
get_contex_for_seasonal_reservation_check_details_url,
get_my_applications_ext_link,
+ get_staff_reservations_ext_link,
params_for_application_section_info,
+ params_for_reservation_series_info,
)
if TYPE_CHECKING:
@@ -148,3 +153,82 @@ def get_context_for_application_section_cancelled(
**get_contex_for_seasonal_reservation_check_details_url(language=language),
**get_contex_for_closing(language=language),
}
+
+
+# type: EmailType.STAFF_NOTIFICATION_APPLICATION_SECTION_CANCELLED #####################################################
+
+
+@overload
+def get_context_for_staff_notification_application_section_cancelled(
+ application_section: ApplicationSection, *, language: Lang, **data: Any
+) -> EmailContext: ...
+
+
+@overload
+def get_context_for_staff_notification_application_section_cancelled(
+ *,
+ language: Lang,
+ cancel_reason: str,
+ application_section_name: str,
+ application_round_name: str,
+) -> EmailContext: ...
+
+
+@get_translated
+def get_context_for_staff_notification_application_section_cancelled(
+ application_section: ApplicationSection | None = None,
+ *,
+ language: Lang,
+ **data: Any,
+) -> EmailContext:
+ if application_section is not None:
+ reservation = application_section.actions.get_last_reservation()
+
+ reservation_series = RecurringReservation.objects.filter(
+ allocated_time_slot__reservation_unit_option__application_section=application_section
+ ).prefetch_related("reservations")
+
+ reservation_series_data = [
+ {
+ **params_for_reservation_series_info(reservation_series=series),
+ "reservation_url": get_staff_reservations_ext_link(
+ reservation_id=series.reservations.values_list("pk").last()
+ ),
+ }
+ for series in reservation_series
+ ]
+
+ data: dict[str, Any] = {
+ "cancel_reason": get_attr_by_language(reservation.cancel_reason, "reason", language),
+ "cancelled_reservation_series": reservation_series_data,
+ **params_for_application_section_info(application_section=application_section, language=language),
+ }
+ else:
+ data["cancelled_reservation_series"] = [
+ {
+ "weekday": WeekdayChoice.MONDAY.label,
+ "time": "13:00-15:00",
+ "url": get_staff_reservations_ext_link(reservation_id=1234),
+ },
+ {
+ "weekday": WeekdayChoice.TUESDAY.label,
+ "time": "21:00-22:00",
+ "url": get_staff_reservations_ext_link(reservation_id=5678),
+ },
+ ]
+
+ return {
+ "title": pgettext("Email", "The customer has canceled the seasonal booking"),
+ "text_reservation_cancelled": pgettext(
+ "Email", "The customer has canceled all space reservations included in the seasonal booking"
+ ),
+ "cancel_reason_label": pgettext("Email", "Reason"),
+ "cancel_reason": data["cancel_reason"],
+ "seasonal_booking_label": pgettext("Email", "Seasonal Booking"),
+ "view_booking_at_label": pgettext("Email", "You can view the booking at"),
+ "application_section_name": data["application_section_name"],
+ "application_round_name": data["application_round_name"],
+ "cancelled_reservation_series": data["cancelled_reservation_series"],
+ **get_contex_for_base_template(),
+ **get_contex_for_closing_staff(),
+ }