Skip to content
Draft
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: 5 additions & 1 deletion backend/kesaseteli/applications/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,10 +630,14 @@ class EmployerSummerVoucherAdmin(admin.ModelAdmin):
"target_group_display",
"application",
"masked_employee_ssn",
"employee_name",
"employee_school",
"employee_home_city",
"employee_postcode",
"created_at",
"modified_at",
]
exclude = ["employee_ssn"]
exclude = []

# custom property to list employee related fields.
employee_fields = [
Expand Down
10 changes: 5 additions & 5 deletions backend/kesaseteli/applications/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ class Meta:
]
read_only_fields = [
"ordering",
"employee_name",
"employee_school",
"employee_ssn",
"employee_home_city",
"employee_postcode",
]
list_serializer_class = EmployerSummerVoucherListSerializer

Expand Down Expand Up @@ -319,12 +324,7 @@ def validate(self, data):

REQUIRED_FIELDS_FOR_SUBMITTED_SUMMER_VOUCHERS = [
"summer_voucher_serial_number",
"employee_name",
"employee_school",
"employee_ssn",
"employee_phone_number",
"employee_home_city",
"employee_postcode",
"employment_postcode",
"employment_start_date",
"employment_end_date",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 5.1.15 on 2026-02-23 10:35

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('applications', '0048_employerapplication_add_timestamp_and_status_indexes'),
]

operations = [
migrations.RemoveField(
model_name='employersummervoucher',
name='employee_home_city',
),
migrations.RemoveField(
model_name='employersummervoucher',
name='employee_name',
),
migrations.RemoveField(
model_name='employersummervoucher',
name='employee_postcode',
),
migrations.RemoveField(
model_name='employersummervoucher',
name='employee_school',
),
migrations.RemoveField(
model_name='employersummervoucher',
name='employee_ssn',
),
migrations.RemoveField(
model_name='historicalemployersummervoucher',
name='employee_home_city',
),
migrations.RemoveField(
model_name='historicalemployersummervoucher',
name='employee_name',
),
migrations.RemoveField(
model_name='historicalemployersummervoucher',
name='employee_postcode',
),
migrations.RemoveField(
model_name='historicalemployersummervoucher',
name='employee_school',
),
migrations.RemoveField(
model_name='historicalemployersummervoucher',
name='employee_ssn',
),
]
111 changes: 71 additions & 40 deletions backend/kesaseteli/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,23 @@ class Meta:
]


class EmployerSummerVoucherQuerySet(models.QuerySet):
def select_youth_voucher(self):
return self.select_related(
"youth_summer_voucher", "youth_summer_voucher__youth_application"
)


class EmployerSummerVoucherManager(models.Manager):
def get_queryset(self):
return EmployerSummerVoucherQuerySet(
self.model, using=self._db
).select_youth_voucher()


class EmployerSummerVoucher(HistoricalModel, TimeStampedModel, UUIDModel):
objects = EmployerSummerVoucherManager()

application = models.ForeignKey(
EmployerApplication,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -1298,55 +1314,51 @@ class EmployerSummerVoucher(HistoricalModel, TimeStampedModel, UUIDModel):
),
)

employee_phone_number = models.CharField(
max_length=64,
blank=True,
verbose_name=_("employee phone number"),
)

@property
def target_group(self):
"""
Get the target group of the youth summer voucher.
NOTE: This is mostly for backward compatibility with the old target_group field.
"""
def employee_name(self) -> str:
return (
self.youth_summer_voucher.target_group if self.youth_summer_voucher else ""
self.youth_summer_voucher.youth_application.name
if self.youth_summer_voucher
else ""
)

# Mimicking Django's standard API. ChoiceField's magic is gone
# since target_group is now migrated to a custom property.
def get_target_group_display(self):
@property
def employee_school(self) -> str:
return (
self.youth_summer_voucher.get_target_group_display()
self.youth_summer_voucher.youth_application.school
if self.youth_summer_voucher
else ""
)

employee_name = models.CharField(
max_length=256,
blank=True,
verbose_name=_("employee name"),
)
employee_school = models.CharField(
max_length=256,
blank=True,
verbose_name=_("employee school"),
)
employee_ssn = EncryptedCharField(
max_length=32,
blank=True,
verbose_name=_("employee social security number"),
)
employee_phone_number = models.CharField(
max_length=64,
blank=True,
verbose_name=_("employee phone number"),
)
employee_home_city = models.CharField(
max_length=256,
blank=True,
verbose_name=_("employee home city"),
)
employee_postcode = models.CharField(
max_length=256,
blank=True,
verbose_name=_("employee postcode"),
)
@property
def employee_ssn(self) -> str:
return (
self.youth_summer_voucher.youth_application.social_security_number
if self.youth_summer_voucher
else ""
)

@property
def employee_home_city(self) -> str:
return (
self.youth_summer_voucher.youth_application.home_municipality
if self.youth_summer_voucher
else ""
)

@property
def employee_postcode(self) -> str:
return (
self.youth_summer_voucher.youth_application.postcode
if self.youth_summer_voucher
else ""
)

employment_postcode = models.CharField(
max_length=256,
Expand Down Expand Up @@ -1401,6 +1413,25 @@ def get_target_group_display(self):

ordering = models.IntegerField(default=0)

@property
def target_group(self):
"""
Get the target group of the youth summer voucher.
NOTE: This is mostly for backward compatibility with the old target_group field.
"""
return (
self.youth_summer_voucher.target_group if self.youth_summer_voucher else ""
)

# Mimicking Django's standard API. ChoiceField's magic is gone
# since target_group is now migrated to a custom property.
def get_target_group_display(self):
return (
self.youth_summer_voucher.get_target_group_display()
if self.youth_summer_voucher
else ""
)

@property
def summer_voucher_serial_number(self) -> str:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,37 @@ def employer_summer_voucher_admin():
@pytest.mark.django_db
def test_masked_employee_ssn(employer_summer_voucher_admin):
# Test with valid SSN
voucher = EmployerSummerVoucherFactory.build(employee_ssn="010101-1234")
voucher = EmployerSummerVoucherFactory.build(
youth_summer_voucher__youth_application__social_security_number="010101-1234"
)
assert employer_summer_voucher_admin.masked_employee_ssn(voucher) == "******1234"

# Test with another valid SSN
voucher = EmployerSummerVoucherFactory.build(employee_ssn="311299A9876")
voucher = EmployerSummerVoucherFactory.build(
youth_summer_voucher__youth_application__social_security_number="311299A9876"
)
assert employer_summer_voucher_admin.masked_employee_ssn(voucher) == "******9876"


@pytest.mark.django_db
def test_masked_employee_ssn_empty(employer_summer_voucher_admin):
# Test with empty SSN
voucher = EmployerSummerVoucherFactory.build(employee_ssn="")
voucher = EmployerSummerVoucherFactory.build(
youth_summer_voucher__youth_application__social_security_number=""
)
assert employer_summer_voucher_admin.masked_employee_ssn(voucher) == ""

# Test with None SSN
voucher = EmployerSummerVoucherFactory.build(employee_ssn=None)
voucher = EmployerSummerVoucherFactory.build(youth_summer_voucher=None)
assert employer_summer_voucher_admin.masked_employee_ssn(voucher) == ""


@pytest.mark.django_db
def test_masked_employee_ssn_short(employer_summer_voucher_admin):
# Test with short SSN
voucher = EmployerSummerVoucherFactory.build(employee_ssn="123")
voucher = EmployerSummerVoucherFactory.build(
youth_summer_voucher__youth_application__social_security_number="123"
)
assert employer_summer_voucher_admin.masked_employee_ssn(voucher) == "******123"


Expand Down
11 changes: 7 additions & 4 deletions backend/kesaseteli/applications/tests/test_excel_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
YouthApplicationFactory,
)
from common.urls import handler_403_url
from common.utils import getattr_nested
from shared.audit_log.models import AuditLogEntry


Expand Down Expand Up @@ -352,11 +353,13 @@ def employer_summer_voucher_sorting_key(voucher: EmployerSummerVoucher):
elif excel_field.model_fields == []:
assert output_column.value == excel_field.value
else:
query = EmployerSummerVoucher.objects.filter(pk=voucher.pk)
values_tuple = query.values_list(*excel_field.model_fields)[0]
assert output_column.value == excel_field.value % values_tuple, (
excel_field.title
values_tuple = tuple(
getattr_nested(voucher, attr_str.split("__"))
for attr_str in excel_field.model_fields
)
assert str(output_column.value) == str(
excel_field.value % values_tuple
), excel_field.title


@pytest.mark.django_db
Expand Down
Loading