Skip to content

Commit

Permalink
feat: parametrize youth summer voucher email templates & update for 2024
Browse files Browse the repository at this point in the history
also:
 - change employer summer vouchers to use previous year's voucher value
   until 13th of February (i.e. last day before the new year's youth
   summer voucher application period starts)

refs YJDH-691
  • Loading branch information
karisal-anders committed Feb 13, 2024
1 parent 72ed278 commit 32a0e1c
Show file tree
Hide file tree
Showing 11 changed files with 970 additions and 350 deletions.
392 changes: 276 additions & 116 deletions backend/kesaseteli/applications/locale/en/LC_MESSAGES/django.po

Large diffs are not rendered by default.

330 changes: 236 additions & 94 deletions backend/kesaseteli/applications/locale/fi/LC_MESSAGES/django.po

Large diffs are not rendered by default.

379 changes: 269 additions & 110 deletions backend/kesaseteli/applications/locale/sv/LC_MESSAGES/django.po

Large diffs are not rendered by default.

106 changes: 99 additions & 7 deletions backend/kesaseteli/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from django.template.loader import get_template
from django.urls import reverse
from django.utils import timezone, translation
from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import gettext, gettext_lazy as _, pgettext
from encrypted_fields.fields import EncryptedCharField, SearchField
from localflavor.generic.models import IBANField
from requests.exceptions import ReadTimeout
Expand Down Expand Up @@ -884,7 +884,7 @@ def _template_image(filename, content_id) -> MIMEImage:

def youth_summer_voucher_logo(self, language) -> MIMEImage:
return YouthSummerVoucher._template_image(
filename=f"youth_summer_voucher-350e-{language}.png",
filename=f"youth_summer_voucher-{self.voucher_value_in_euros}e-{language}.png",
content_id="youth_summer_voucher_logo",
)

Expand All @@ -894,6 +894,89 @@ def helsinki_logo(self) -> MIMEImage:
content_id="helsinki_logo",
)

@staticmethod
def _value_with_euro_sign(value_in_euros: int) -> str:
"""
Given value with euro sign
Example:
_value_with_euro_sign(350) -> "350€" with Finnish translation active
"""
return _("%(value_in_currency)d%(currency_sign)s") % {
"value_in_currency": value_in_euros,
"currency_sign": "€",
}

@staticmethod
def voucher_value_in_euros_in_year(year: int) -> int:
"""
Voucher value in euros in given year
"""
return 325 if year < 2024 else 350

@property
def voucher_value_in_euros(self) -> int:
"""
Voucher value in euros in youth summer voucher's year
"""
return YouthSummerVoucher.voucher_value_in_euros_in_year(self.year)

@property
def voucher_value_with_euro_sign(self) -> str:
"""
Voucher value with euro sign in youth summer voucher's year
"""
return self._value_with_euro_sign(self.voucher_value_in_euros)

@property
def summer_job_period_localized_string(self) -> str:
"""
Summer job period as a string
"""
return pgettext(
"Summer job period in youth summer voucher email (d.m.–d.m.y)",
"1.6.–15.8.%(year)d",
) % {"year": self.year}

@property
def employer_summer_voucher_application_end_date_localized_string(self) -> str:
"""
Employer summer voucher application end date as a localized string
"""
return pgettext(
"Employer summer voucher application end date in youth summer voucher email (d.m.y)",
"30.11.%(year)d",
) % {"year": self.year}

@property
def min_work_hours(self) -> int:
"""
Minimum work hours for summer job in youth summer voucher's year
"""
return 60

@staticmethod
def min_work_compensation_in_euros_in_year(year: int) -> int:
"""
Minimum work compensation for summer job in euros in given year
"""
return 400 if year < 2024 else 500

@property
def min_work_compensation_in_euros(self) -> int:
"""
Minimum work compensation for summer job in euros in youth summer voucher's year
"""
return YouthSummerVoucher.min_work_compensation_in_euros_in_year(self.year)

@property
def min_work_compensation_with_euro_sign(self) -> str:
"""
Minimum work compensation for summer job with euro sign in youth summer
voucher's year
"""
return self._value_with_euro_sign(self.min_work_compensation_in_euros)

def send_youth_summer_voucher_email(
self, language, send_to_youth=True, send_to_handler=True
) -> bool:
Expand Down Expand Up @@ -921,6 +1004,13 @@ def send_youth_summer_voucher_email(
"phone_number": self.youth_application.phone_number,
"email": self.youth_application.email,
"year": self.year,
"voucher_value_with_euro_sign": self.voucher_value_with_euro_sign,
"summer_job_period_localized_string": self.summer_job_period_localized_string,
"employer_summer_voucher_application_end_date_localized_string": (
self.employer_summer_voucher_application_end_date_localized_string
),
"min_work_hours": self.min_work_hours,
"min_work_compensation_with_euro_sign": self.min_work_compensation_with_euro_sign,
}
return send_mail_with_error_logging(
subject=self.email_subject(language),
Expand Down Expand Up @@ -1115,12 +1205,14 @@ class EmployerSummerVoucher(HistoricalModel, TimeStampedModel, UUIDModel):

@property
def value_in_euros(self) -> int:
if self.created_at.date() < date(2024, 6, 1):
# Use 2023 year's value (325e) for late coming employer applications in 2024
# before 2024's summer job period starts (i.e. 1st of June 2024).
return 325
created_date = self.created_at.date()
year, month, day = created_date.year, created_date.month, created_date.day
if (month, day) < (2, 14):
# Use previous year's value for late coming employer applications before the
# youths' period for applying for summer vouchers starts on Feb 14
return YouthSummerVoucher.voucher_value_in_euros_in_year(year - 1)
else:
return 350
return YouthSummerVoucher.voucher_value_in_euros_in_year(year)

@property
def last_submitted_at(self) -> Optional[datetime]:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<tr style="background-color:#e1f6ea;margin:0 0 0 0;padding:0 0 0 0">
<td colspan="2">
<img alt="{% trans 'Helsinki, Kesäseteli, 350€' %}" src="cid:youth_summer_voucher_logo">
<img alt="{% blocktranslate %}Helsinki, Kesäseteli, {{voucher_value_with_euro_sign}}{% endblocktranslate %}" src="cid:youth_summer_voucher_logo">
</td>
</tr>

Expand All @@ -29,15 +29,12 @@ <h2>{% trans 'Kesäsetelisi, ole hyvä' %}</h2>
<p>
<strong>{{first_name}} {{last_name}}</strong><br/>
<strong>{% trans 'Kesäsetelinumero:' %}</strong> {{summer_voucher_serial_number}}<br/>
<strong>{% trans 'Kesäsetelin summa:' %}</strong> {% trans '350€' %}<br/>
<strong>{% trans 'Kesäsetelin summa:' %}</strong> {{voucher_value_with_euro_sign}}<br/>
<strong>{% trans 'Postinumero:' %}</strong> {{postcode}}<br/>
<strong>{% trans 'Koulu:' %}</strong> {{school}}<br/>
<strong>{% trans 'Puhelinnumero:' %}</strong> {{phone_number}}<br/>
<strong>{% trans 'Sähköpostiosoite:' %}</strong> {{email}}<br/>
</p>
<p>
{% trans 'Kun olet löytänyt kesätyöpaikan ja tehnyt työsopimuksen, voit välittää tämän sähköpostiviestin työnantajalle tai antaa heille yllä olevan kesäsetelinumeron.' %}
</p>
</td>
</tr>

Expand All @@ -48,13 +45,13 @@ <h2>{% trans 'Ohjeita työnhakijalle' %}</h2>
{% trans 'Kesäseteli helpottaa sinua kesätyöpaikan löytämisessä: viime vuonna tyypillisiä työtehtäviä olivat kesäleirin ohjaaja, myymälän hyllyttäjä, ravintolan saliapulainen, puutarhatyöntekijä, lastenhoitaja, verkkosivujen ja somen sisällöntuottaja ja kioskimyyjä.' %}
{% trans 'Erikoisia kesätöitä olivat esimerkiksi peliohjelmointi, äänitiedostojen editointi ja nahkapajan apulainen!' %}
</p>
<p>
{% trans 'Kun olet löytänyt kesätyöpaikan ja tehnyt työsopimuksen, voit välittää tämän sähköpostiviestin työnantajalle tai antaa heille yllä olevan kesäsetelinumeron.' %}
</p>
<p>
<strong>{% trans 'Työpäivät ja palkka:' %}</strong>
<ul>
<li>{% trans 'Kesäsetelillä tehdään 60 työtuntia.' %}</li>
<li>{% trans 'Työtunteja on vähintään 18h/viikko.' %}</li>
<li>{% trans 'Työnantaja maksaa sinulle palkkaa vähintään 500 euroa (60 työtunnilta).' %} {% trans 'Summa pitää sisällään lomakorvauksen.' %} {% trans 'Lisäksi työnantaja maksaa normaalit työnantajamaksut.' %}</li>
<li>{% trans 'Kesätyöjakso voi olla tuntimäärältään pidempi, tällöin 60 työtuntia ylittävän osuuden palkan on oltava vähintään alan työehtosopimuksen mukainen vähimmäispalkka.' %}</li>
<li>{% blocktranslate %}Kesätyön vähimmäisvaatimuksena on {{min_work_hours}} tuntia töitä ja siitä vähintään {{min_work_compensation_with_euro_sign}} palkka, ellei alan työehtosopimus edellytä korkeampaa palkkaa. Lisäksi työnantaja maksaa normaalit työnantajamaksut, lomakorvauksen ja mahdolliset lisät.{% endblocktranslate %}</li>
</ul>
</p>
<p>
Expand All @@ -63,9 +60,7 @@ <h2>{% trans 'Ohjeita työnhakijalle' %}</h2>
<hr/>
<h2>{% trans 'Ohjeita työnantajalle' %}</h2>
<p>
{% trans 'Kesäseteli on nuorten, yrittäjien ja Helsingin kaupungin yhteinen hanke.' %}
{% trans 'Kaupunki korvaa 350 euroa työnantajalle, joka palkkaa Kesäseteliin oikeutetun nuoren töihin 1.6.–15.8.2024 välisenä aikana.' %}
{% trans 'Kesätyön vähimmäisvaatimuksina ovat 60 tunnin työaika ja 500 euron palkka.' %}
{% blocktranslate %}Kaupunki korvaa kesätyön jälkeen {{voucher_value_with_euro_sign}} työnantajalle, joka palkkaa Kesäseteliin oikeutetun nuoren töihin {{summer_job_period_localized_string}} välisenä aikana. Korvaus haetaan Kesäsetelin sivuilta olevasta linkistä ennen {{employer_summer_voucher_application_end_date_localized_string}}.{% endblocktranslate %}
</p>
<p>
{% trans 'Muut käyttöehdot voit lukea täältä:' %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,27 @@

{{first_name}} {{last_name}}
{% trans 'Kesäsetelinumero:' %} {{summer_voucher_serial_number}}
{% trans 'Kesäsetelin summa:' %} {% trans '350€' %}
{% trans 'Kesäsetelin summa:' %} {{voucher_value_with_euro_sign}}
{% trans 'Postinumero:' %} {{postcode}}
{% trans 'Koulu:' %} {{school}}
{% trans 'Puhelinnumero:' %} {{phone_number}}
{% trans 'Sähköpostiosoite:' %} {{email}}

{% trans 'Kun olet löytänyt kesätyöpaikan ja tehnyt työsopimuksen, voit välittää tämän sähköpostiviestin työnantajalle tai antaa heille yllä olevan kesäsetelinumeron.' %}
------------------------------------------------------------------------
{% trans 'Ohjeita työnhakijalle' %}:

{% trans 'Kesäseteli helpottaa sinua kesätyöpaikan löytämisessä: viime vuonna tyypillisiä työtehtäviä olivat kesäleirin ohjaaja, myymälän hyllyttäjä, ravintolan saliapulainen, puutarhatyöntekijä, lastenhoitaja, verkkosivujen ja somen sisällöntuottaja ja kioskimyyjä.' %} {% trans 'Erikoisia kesätöitä olivat esimerkiksi peliohjelmointi, äänitiedostojen editointi ja nahkapajan apulainen!' %}

{% trans 'Kun olet löytänyt kesätyöpaikan ja tehnyt työsopimuksen, voit välittää tämän sähköpostiviestin työnantajalle tai antaa heille yllä olevan kesäsetelinumeron.' %}

{% trans 'Työpäivät ja palkka:' %}
- {% trans 'Kesäsetelillä tehdään 60 työtuntia.' %}
- {% trans 'Työtunteja on vähintään 18h/viikko.' %}
- {% trans 'Työnantaja maksaa sinulle palkkaa vähintään 500 euroa (60 työtunnilta).' %} {% trans 'Summa pitää sisällään lomakorvauksen.' %} {% trans 'Lisäksi työnantaja maksaa normaalit työnantajamaksut.' %}
- {% trans 'Kesätyöjakso voi olla tuntimäärältään pidempi, tällöin 60 työtuntia ylittävän osuuden palkan on oltava vähintään alan työehtosopimuksen mukainen vähimmäispalkka.' %}
- {% blocktranslate %}Kesätyön vähimmäisvaatimuksena on {{min_work_hours}} tuntia töitä ja siitä vähintään {{min_work_compensation_with_euro_sign}} palkka, ellei alan työehtosopimus edellytä korkeampaa palkkaa. Lisäksi työnantaja maksaa normaalit työnantajamaksut, lomakorvauksen ja mahdolliset lisät.{% endblocktranslate %}

{% trans 'Lue lisää' %}:
{% trans 'https://nuorten.helsinki/opiskelu-ja-tyo/kesaseteli/nuorelle/' %}
------------------------------------------------------------------------
{% trans 'Ohjeita työnantajalle' %}:

{% trans 'Kesäseteli on nuorten, yrittäjien ja Helsingin kaupungin yhteinen hanke.' %} {% trans 'Kaupunki korvaa 350 euroa työnantajalle, joka palkkaa Kesäseteliin oikeutetun nuoren töihin 1.6.–15.8.2024 välisenä aikana.' %} {% trans 'Kesätyön vähimmäisvaatimuksina ovat 60 tunnin työaika ja 500 euron palkka.' %}
{% blocktranslate %}Kaupunki korvaa kesätyön jälkeen {{voucher_value_with_euro_sign}} työnantajalle, joka palkkaa Kesäseteliin oikeutetun nuoren töihin {{summer_job_period_localized_string}} välisenä aikana. Korvaus haetaan Kesäsetelin sivuilta olevasta linkistä ennen {{employer_summer_voucher_application_end_date_localized_string}}.{% endblocktranslate %}

{% trans 'Muut käyttöehdot voit lukea täältä:' %}
{% trans 'https://nuorten.helsinki/opiskelu-ja-tyo/kesaseteli/tyonantajalle-2/' %}
Expand Down
4 changes: 2 additions & 2 deletions backend/kesaseteli/applications/tests/test_excel_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ def employer_summer_voucher_sorting_key(voucher: EmployerSummerVoucher):
(date(2022, 6, 1), 325),
(date(2023, 6, 1), 325),
(date(2024, 1, 1), 325),
(date(2024, 5, 31), 325),
(date(2024, 6, 1), 350),
(date(2024, 2, 13), 325),
(date(2024, 2, 14), 350),
(date(2024, 12, 31), 350),
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from django.contrib.auth.models import AnonymousUser
from django.core import mail
from django.test import override_settings
from django.utils import timezone
from django.utils import timezone, translation
from django.utils.html import strip_tags
from django.utils.timezone import localdate
from freezegun import freeze_time
Expand Down Expand Up @@ -68,6 +68,27 @@
from shared.common.tests.test_validators import get_invalid_postcode_values
from shared.common.tests.utils import normalize_whitespace

# Mandatory fields of YouthSummerVoucher in youth summer voucher email
MANDATORY_YOUTH_SUMMER_VOUCHER_FIELDS_IN_VOUCHER_EMAIL = [
"employer_summer_voucher_application_end_date_localized_string",
"min_work_compensation_with_euro_sign",
"min_work_hours",
"summer_job_period_localized_string",
"summer_voucher_serial_number",
"voucher_value_with_euro_sign",
"year",
]

# Mandatory fields of YouthApplication in youth summer voucher email
MANDATORY_YOUTH_APPLICATION_FIELDS_IN_VOUCHER_EMAIL = [
"email",
"first_name",
"last_name",
"phone_number",
"postcode",
"school",
]


def create_same_person_previous_year_accepted_application(
app: YouthApplication,
Expand Down Expand Up @@ -1678,6 +1699,60 @@ def test_youth_summer_voucher_email_plaintext_html_similarity(api_client, langua
), "Email's plaintext and HTML content should be very similar"


@pytest.mark.django_db
@override_settings(
NEXT_PUBLIC_MOCK_FLAG=True,
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
)
@pytest.mark.parametrize("language", get_supported_languages())
def test_youth_summer_voucher_email_html_content(api_client, language):
acceptable_youth_application = AcceptableYouthApplicationFactory(language=language)
api_client.patch(
get_accept_url(acceptable_youth_application.pk),
data=json.dumps(get_test_handling_data()),
content_type="application/json",
)
assert len(mail.outbox) > 0
youth_summer_voucher_email = mail.outbox[-1]
html_msg = youth_summer_voucher_email.alternatives[0][0]
acceptable_youth_application.refresh_from_db()
voucher = acceptable_youth_application.youth_summer_voucher

with translation.override(acceptable_youth_application.language):
for field in MANDATORY_YOUTH_SUMMER_VOUCHER_FIELDS_IN_VOUCHER_EMAIL:
assert str(getattr(voucher, field)) in html_msg

for field in MANDATORY_YOUTH_APPLICATION_FIELDS_IN_VOUCHER_EMAIL:
assert str(getattr(acceptable_youth_application, field)) in html_msg


@pytest.mark.django_db
@override_settings(
NEXT_PUBLIC_MOCK_FLAG=True,
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
)
@pytest.mark.parametrize("language", get_supported_languages())
def test_youth_summer_voucher_email_plaintext_content(api_client, language):
acceptable_youth_application = AcceptableYouthApplicationFactory(language=language)
api_client.patch(
get_accept_url(acceptable_youth_application.pk),
data=json.dumps(get_test_handling_data()),
content_type="application/json",
)
assert len(mail.outbox) > 0
youth_summer_voucher_email = mail.outbox[-1]
plaintext_msg = youth_summer_voucher_email.body
acceptable_youth_application.refresh_from_db()
voucher = acceptable_youth_application.youth_summer_voucher

with translation.override(acceptable_youth_application.language):
for field in MANDATORY_YOUTH_SUMMER_VOUCHER_FIELDS_IN_VOUCHER_EMAIL:
assert str(getattr(voucher, field)) in plaintext_msg

for field in MANDATORY_YOUTH_APPLICATION_FIELDS_IN_VOUCHER_EMAIL:
assert str(getattr(acceptable_youth_application, field)) in plaintext_msg


@pytest.mark.django_db
@override_settings(
NEXT_PUBLIC_MOCK_FLAG=True,
Expand Down

0 comments on commit 32a0e1c

Please sign in to comment.