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(), + }