Skip to content
Merged
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
24 changes: 24 additions & 0 deletions backend/kesaseteli/applications/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,30 @@ class AttachmentType(models.TextChoices):
PAYSLIP = "payslip", _("payslip")


class OrganizationType(models.TextChoices):
COMPANY = "company", _("Company")
ASSOCIATION = "association", _("Association")
PARISH = "parish", _("Parish")
OTHER = "other", _("Other")


class JobType(models.TextChoices):
SPORTS_AND_LEISURE = "sports_and_leisure", _("Sports and leisure")
MAINTENANCE_AND_CONSTRUCTION = (
"maintenance_and_construction",
_("Maintenance and construction"),
)
RESTAURANT_AND_CAFE = "restaurant_and_cafe", _("Restaurant and cafe sector")
RETAIL = "retail", _("Retail sector")
OFFICE_AND_MEDIA = "office_and_media", _("Office and media")
GARDENING_AND_AGRICULTURE = (
"gardening_and_agriculture",
_("Gardening and agricultural work"),
)
SERVICE = "service", _("Service sector")
OTHER = "other", _("Other")


class HiredWithoutVoucherAssessment(models.TextChoices):
YES = "yes", _("yes")
NO = "no", _("no")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Generated by Django 5.1.15 on 2026-02-03 12:47

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("applications", "0044_remove_employersummervoucher_target_group_and_more"),
]

operations = [
migrations.AddField(
model_name="employersummervoucher",
name="job_type",
field=models.CharField(
blank=True,
choices=[
("sports_and_leisure", "Sports and leisure"),
("maintenance_and_construction", "Maintenance and construction"),
("restaurant_and_cafe", "Restaurant and cafe sector"),
("retail", "Retail sector"),
("office_and_media", "Office and media"),
("gardening_and_agriculture", "Gardening and agricultural work"),
("service", "Service sector"),
("other", "Other"),
],
max_length=64,
verbose_name="job type",
),
),
migrations.AddField(
model_name="historicalemployersummervoucher",
name="job_type",
field=models.CharField(
blank=True,
choices=[
("sports_and_leisure", "Sports and leisure"),
("maintenance_and_construction", "Maintenance and construction"),
("restaurant_and_cafe", "Restaurant and cafe sector"),
("retail", "Retail sector"),
("office_and_media", "Office and media"),
("gardening_and_agriculture", "Gardening and agricultural work"),
("service", "Service sector"),
("other", "Other"),
],
max_length=64,
verbose_name="job type",
),
),
]
7 changes: 7 additions & 0 deletions backend/kesaseteli/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
EmailTemplateType,
EmployerApplicationStatus,
HiredWithoutVoucherAssessment,
JobType,
VtjTestCase,
YouthApplicationStatus,
)
Expand Down Expand Up @@ -1370,6 +1371,12 @@ def get_target_group_display(self):
employment_description = models.TextField(
verbose_name=_("employment description"), blank=True
)
job_type = models.CharField(
max_length=64,
verbose_name=_("job type"),
blank=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, that since NULL is not allowed, this blank string means that "question is seen, but left unanswered".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, intentionally allowed empty string. This helps with e.g. old data and with the current way of handling employer applications/summer vouchers in the employer UI & backend (it saves partial data so data can be not set).

Also it's Django's best practice not to allow null for CharFields:

Avoid using null on string-based fields such as CharField and TextField.

The requirement can be handled at the UI level.

choices=JobType.choices,
)
hired_without_voucher_assessment = models.CharField(
max_length=32,
verbose_name=_("hired without voucher assessment"),
Expand Down
6 changes: 6 additions & 0 deletions backend/kesaseteli/common/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
EmployerApplicationStatus,
get_supported_languages,
HiredWithoutVoucherAssessment,
JobType,
OrganizationType,
VtjTestCase,
YouthApplicationStatus,
)
Expand Down Expand Up @@ -49,6 +51,9 @@ class CompanyFactory(SaveAfterPostGenerationMixin, factory.django.DjangoModelFac
street_address = factory.Faker("street_address")
postcode = factory.Faker("postcode")
city = factory.Faker("city")
organization_type = factory.Faker(
"random_element", elements=OrganizationType.values
)
ytj_json = factory.Faker("json")

class Meta:
Expand Down Expand Up @@ -101,6 +106,7 @@ class EmployerSummerVoucherFactory(
"pydecimal", left_digits=4, right_digits=2, min_value=1
)
employment_description = factory.Faker("sentence")
job_type = factory.Faker("random_element", elements=JobType.values)
hired_without_voucher_assessment = factory.Faker(
"random_element", elements=HiredWithoutVoucherAssessment.values
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.1.15 on 2026-02-03 12:47

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("companies", "0006_alter_company_options"),
]

operations = [
migrations.AddField(
model_name="company",
name="organization_type",
field=models.CharField(
blank=True,
choices=[
("company", "Company"),
("association", "Association"),
("parish", "Parish"),
("other", "Other"),
],
max_length=64,
verbose_name="organization type",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.1.15 on 2026-02-03 12:54

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("companies", "0007_company_organization_type"),
]

operations = [
migrations.AlterField(
model_name="company",
name="business_id",
field=models.CharField(
max_length=64, unique=True, verbose_name="business id"
),
),
]
20 changes: 18 additions & 2 deletions backend/kesaseteli/companies/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from django.db import models
from django.utils.translation import gettext_lazy as _

from applications.enums import OrganizationType
from shared.models.abstract_models import UUIDModel


class Company(UUIDModel):
name = models.CharField(max_length=256, verbose_name=_("name"))
business_id = models.CharField(max_length=64, verbose_name=_("business id"))
business_id = models.CharField(
max_length=64, unique=True, verbose_name=_("business id")
)
company_form = models.CharField(
max_length=64, blank=True, verbose_name=_("company form")
)
Expand All @@ -17,7 +20,20 @@ class Company(UUIDModel):
)
postcode = models.CharField(max_length=256, blank=True, verbose_name=_("postcode"))
city = models.CharField(max_length=256, blank=True, verbose_name=_("city"))

organization_type = models.CharField(
# Rationale for this field:
#
# Organization type is similar to company_form data which comes from VTJ,
# but has specific choices requested by product owner for reporting purposes.
#
# For example parish (i.e. seurakunta in Finnish) is a category which
# is at least not evidently mappable from company_form value, and thus
# needs to be asked from the user.
max_length=64,
verbose_name=_("organization type"),
blank=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, that since NULL is not allowed, this blank string means that "question is seen, but left unanswered".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as in comment #3867 (comment)

choices=OrganizationType.choices,
)
ytj_json = models.JSONField(blank=True, null=True, verbose_name=_("ytj json"))

class Meta:
Expand Down
4 changes: 2 additions & 2 deletions backend/kesaseteli/companies/tests/test_company_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_get_mock_company_not_found_from_ytj(api_client):
for field in [
f
for f in Company._meta.fields
if f.name not in ["id", "name", "business_id", "ytj_json"]
if f.name not in ["id", "name", "business_id", "organization_type", "ytj_json"]
]:
assert response.data[field.name] == ""

Expand Down Expand Up @@ -154,7 +154,7 @@ def test_get_company_not_found_from_ytj(api_client, requests_mock, user):
for field in [
f
for f in Company._meta.fields
if f.name not in ["id", "name", "business_id", "ytj_json"]
if f.name not in ["id", "name", "business_id", "organization_type", "ytj_json"]
]:
assert response.data[field.name] == ""

Expand Down
18 changes: 18 additions & 0 deletions frontend/kesaseteli/employer/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,31 @@
"primary_target_group": "9th grader or TUVA grader",
"secondary_target_group": "Other age group"
},
"organization_type": {
"company": "Company",
"association": "Association",
"parish": "Parish",
"other": "Other"
},
"job_type": {
"sports_and_leisure": "Sports and leisure time",
"maintenance_and_construction": "Maintenance and construction",
"restaurant_and_cafe": "Restaurant and cafe sector",
"retail": "Retail sector",
"office_and_media": "Office and media work",
"gardening_and_agriculture": "Gardening and agricultural work",
"service": "Service sector",
"other": "Other"
},
"hired_without_voucher_assessment": {
"yes": "Yes",
"no": "No",
"maybe": "Maybe"
}
},
"inputs": {
"organization_type": "Organization type",
"job_type": "Main category of work tasks",
"contact_person_name": "Name of the contact person",
"contact_person_email": "Email of the contact person",
"street_address": "Company street address",
Expand Down
18 changes: 18 additions & 0 deletions frontend/kesaseteli/employer/public/locales/fi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,31 @@
"primary_target_group": "9. luokkalainen tai TUVA-luokkalainen",
"secondary_target_group": "Jokin muu ikäryhmä"
},
"organization_type": {
"company": "Yritys",
"association": "Yhdistys",
"parish": "Seurakunta",
"other": "Muu"
},
"job_type": {
"sports_and_leisure": "Liikunta- ja vapaa-aika",
"maintenance_and_construction": "Huolto- ja rakennustehtävät",
"restaurant_and_cafe": "Ravintola- ja kahvila-ala",
"retail": "Kaupan ala",
"office_and_media": "Toimisto- ja mediatyö",
"gardening_and_agriculture": "Puutarha- ja maataloustyö",
"service": "Palveluala",
"other": "Muu"
},
"hired_without_voucher_assessment": {
"yes": "Kyllä",
"no": "En",
"maybe": "Ehkä"
}
},
"inputs": {
"organization_type": "Organisaatiotyyppi",
"job_type": "Työtehtävien pääluokka",
"contact_person_name": "Yhteyshenkilön nimi",
"contact_person_email": "Yhteyshenkilön sähköposti",
"street_address": "Työpaikan lähiosoite",
Expand Down
18 changes: 18 additions & 0 deletions frontend/kesaseteli/employer/public/locales/sv/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,31 @@
"primary_target_group": "Niondeklassist eller TUVAklassist",
"secondary_target_group": "Annan åldersgrupp"
},
"organization_type": {
"company": "Företag",
"association": "Förening",
"parish": "Församling",
"other": "Annat"
},
"job_type": {
"sports_and_leisure": "Sport och fritid",
"maintenance_and_construction": "Underhåll och byggande",
"restaurant_and_cafe": "Restaurang och café",
"retail": "Detaljhandel",
"office_and_media": "Kontor och media",
"gardening_and_agriculture": "Trädgårdsarbete och jordbruk",
"service": "Tjänster",
"other": "Annat"
},
"hired_without_voucher_assessment": {
"yes": "Jo",
"no": "Nej",
"maybe": "Kanske"
}
},
"inputs": {
"organization_type": "Organisationstyp",
"job_type": "Huvudkategori av arbetsuppgifter",
"contact_person_name": "Kontakt personens namn",
"contact_person_email": "Kontakt personens epost",
"street_address": "Arbetsplatsens gatuadress",
Expand Down
Loading