diff --git a/itou/eligibility/admin.py b/itou/eligibility/admin.py index 30d0ccdaaa..986a3226f2 100644 --- a/itou/eligibility/admin.py +++ b/itou/eligibility/admin.py @@ -181,7 +181,7 @@ def get_queryset(self, request): class GEIQEligibilityDiagnosisAdmin(AbstractEligibilityDiagnosisAdmin): raw_id_fields = AbstractEligibilityDiagnosisAdmin.raw_id_fields + ("author_geiq",) readonly_fields = AbstractEligibilityDiagnosisAdmin.readonly_fields + ( - "has_eligibility", + "is_valid", "allowance_amount", ) inlines = ( @@ -190,10 +190,6 @@ class GEIQEligibilityDiagnosisAdmin(AbstractEligibilityDiagnosisAdmin): PkSupportRemarkInline, ) - @admin.display(boolean=True, description="éligibilité GEIQ confirmée") - def has_eligibility(self, obj): - return obj.eligibility_confirmed - @admin.display(description="montant de l'aide") def allowance_amount(self, obj): return f"{obj.allowance_amount} EUR" diff --git a/itou/eligibility/models/geiq.py b/itou/eligibility/models/geiq.py index 0b1845332c..d6f403b940 100644 --- a/itou/eligibility/models/geiq.py +++ b/itou/eligibility/models/geiq.py @@ -37,10 +37,9 @@ class GEIQEligibilityDiagnosisQuerySet(CommonEligibilityDiagnosisQuerySet): def authored_by_prescriber_or_geiq(self, geiq): - # In ordering, priority is given to prescriber authored diagnoses return self.filter( models.Q(author_geiq=geiq) | models.Q(author_prescriber_organization__isnull=False) - ).order_by(models.F("author_prescriber_organization").desc(nulls_last=True), "-created_at") + ).order_by("-created_at") def diagnoses_for(self, job_seeker, for_geiq=None): # Get *all* GEIQ diagnoses for given job seeker (even expired) @@ -151,10 +150,6 @@ def save(self, *args, **kwargs): return result - @property - def eligibility_confirmed(self) -> bool: - return bool(self.allowance_amount) and self.is_valid - @property def allowance_amount(self) -> int: return geiq_allowance_amount(self.author.is_prescriber_with_authorized_org, self.administrative_criteria.all()) diff --git a/itou/templates/apply/includes/eligibility_badge.html b/itou/templates/apply/includes/eligibility_badge.html index ec5c883e17..531c3ef241 100644 --- a/itou/templates/apply/includes/eligibility_badge.html +++ b/itou/templates/apply/includes/eligibility_badge.html @@ -16,7 +16,7 @@ {% endif %} {% elif is_subject_to_geiq_eligibility_rules %} - {% if geiq_eligibility_diagnosis.eligibility_confirmed %} + {% if geiq_eligibility_diagnosis.is_valid and geiq_eligibility_diagnosis.allowance_amount %} Éligibilité GEIQ confirmée diff --git a/itou/templates/apply/includes/geiq/geiq_diagnosis_details.html b/itou/templates/apply/includes/geiq/geiq_diagnosis_details.html index 208d3ac680..9f32b39f56 100644 --- a/itou/templates/apply/includes/geiq/geiq_diagnosis_details.html +++ b/itou/templates/apply/includes/geiq/geiq_diagnosis_details.html @@ -1,5 +1,5 @@ -{% if diagnosis.is_valid %} - {% if diagnosis.eligibility_confirmed %} +{% if diagnosis.is_valid or job_application.state.is_accepted and diagnosis %} + {% if diagnosis.allowance_amount %}

Éligibilité public prioritaire GEIQ validée

{% else %} @@ -7,7 +7,7 @@

Éligibilité public prioritaire GEIQ validée

Éligibilité public prioritaire GEIQ non confirmée

{% endif %} - {% if diagnosis.eligibility_confirmed %} + {% if diagnosis.allowance_amount %}

Éligibilité GEIQ confirmée par {{ diagnosis.author.get_full_name }} ({{ diagnosis.author_structure.display_name }})

@@ -26,7 +26,7 @@

Situation administrative du candidat

{% endif %} {% endwith %} - {% if diagnosis.eligibility_confirmed %} + {% if diagnosis.allowance_amount %}

Durée de validité du diagnostic : du {{ diagnosis.created_at|date:"d/m/Y" }} au {{ diagnosis.expires_at|date:"d/m/Y" }}.

@@ -52,7 +52,7 @@

Situation administrative du candidat

{% endif %} - {% else %} + {% elif request.user.is_employer %} {# Existing GEIQ, diagnosis but no allowance #}
@@ -77,12 +77,12 @@

Situation administrative du candidat

{% endif %} {% else %} {# Diagnosis either does not exist or has expired : this part is for GEIQ only #} - {% if request.user.is_employer %} -
-
-
-

Éligibilité public prioritaire GEIQ non confirmée

-
+
+
+
+

Éligibilité public prioritaire GEIQ non confirmée

+
+ {% if request.user.is_employer %}
{% with back_and_next_url=request.get_full_path|urlencode %} @@ -90,24 +90,24 @@

Éligibilité public prioritaire GEIQ non confirmée {% endwith %}

-
- {# GEIQ eligibility diagnosis expired #} - {% if diagnosis and not diagnosis.is_valid %} -
-
-
- -
-
-

- Aide à l'accompagement GEIQ -

-

- Le diagnostic du candidat a expiré le {{ diagnosis.expires_at|date:"d F Y" }}. Si vous souhaitez bénéficier d’une aide à l’accompagnement, veuillez renseigner à nouveau la situation administrative du candidat. -

+ {# GEIQ eligibility diagnosis expired #} + {% if diagnosis and not diagnosis.is_valid %} +
+
+
+ +
+
+

+ Aide à l'accompagement GEIQ +

+

+ Le diagnostic du candidat a expiré le {{ diagnosis.expires_at|date:"d F Y" }}. Si vous souhaitez bénéficier d’une aide à l’accompagnement, veuillez renseigner à nouveau la situation administrative du candidat. +

+
-
+ {% endif %} {% endif %} - {% endif %} +
{% endif %} diff --git a/itou/templates/apply/process_details.html b/itou/templates/apply/process_details.html index 642100cc59..6b583c59a9 100644 --- a/itou/templates/apply/process_details.html +++ b/itou/templates/apply/process_details.html @@ -34,7 +34,7 @@

Informations générales

Informations personnelles

{% include "apply/includes/job_seeker_info.html" with job_seeker=job_application.job_seeker job_application=job_application can_view_personal_information=can_view_personal_information can_edit_personal_information=can_edit_personal_information request=request csrf_token=csrf_token SenderKind=SenderKind only %} - {% if job_application.to_company.kind == CompanyKind.GEIQ and geiq_eligibility_diagnosis %} + {% if job_application.to_company.kind == CompanyKind.GEIQ %} {# GEIQ eligibility details #} {% include "apply/includes/geiq/geiq_diagnosis_details.html" with diagnosis=geiq_eligibility_diagnosis %} {% else %} diff --git a/itou/www/apply/views/process_views.py b/itou/www/apply/views/process_views.py index 9382c776ac..dd8f2dc629 100644 --- a/itou/www/apply/views/process_views.py +++ b/itou/www/apply/views/process_views.py @@ -49,14 +49,17 @@ def check_waiting_period(job_application): raise PermissionDenied(apply_view_constants.ERROR_CANNOT_OBTAIN_NEW_FOR_PROXY) -def _get_geiq_eligibility_diagnosis_for_company(job_application): - # Get current GEIQ diagnosis or *last expired one* - return ( - job_application.geiq_eligibility_diagnosis - or GEIQEligibilityDiagnosis.objects.diagnoses_for( - job_application.job_seeker, job_application.to_company - ).first() - ) +def _get_geiq_eligibility_diagnosis(job_application, only_prescriber): + # Return the job_application diagnosis if it's accepted + if job_application.state.is_accepted: + # but not if the viewer is a prescriber and the diangosis was made by the company + if only_prescriber and job_application.geiq_eligibility_diagnosis.author_geiq: + return None + return job_application.geiq_eligibility_diagnosis + return GEIQEligibilityDiagnosis.objects.diagnoses_for( + job_application.job_seeker, + job_application.to_company if not only_prescriber else None, + ).first() @login_required @@ -86,10 +89,11 @@ def details_for_jobseeker(request, job_application_id, template_name="apply/proc ) back_url = get_safe_url(request, "back_url", fallback_url=reverse_lazy("apply:list_for_job_seeker")) - geiq_eligibility_diagnosis = None - if job_application.to_company.kind == CompanyKind.GEIQ: - geiq_eligibility_diagnosis = _get_geiq_eligibility_diagnosis_for_company(job_application) + geiq_eligibility_diagnosis = ( + job_application.to_company.kind == CompanyKind.GEIQ + and _get_geiq_eligibility_diagnosis(job_application, only_prescriber=False) + ) context = { "can_view_personal_information": request.user.can_view_personal_information(job_application.job_seeker), @@ -141,10 +145,11 @@ def details_for_company(request, job_application_id, template_name="apply/proces ) back_url = get_safe_url(request, "back_url", fallback_url=reverse_lazy("apply:list_for_siae")) - geiq_eligibility_diagnosis = None - if job_application.to_company.kind == CompanyKind.GEIQ: - geiq_eligibility_diagnosis = _get_geiq_eligibility_diagnosis_for_company(job_application) + geiq_eligibility_diagnosis = ( + job_application.to_company.kind == CompanyKind.GEIQ + and _get_geiq_eligibility_diagnosis(job_application, only_prescriber=False) + ) context = { "can_view_personal_information": True, # SIAE members have access to personal info @@ -206,10 +211,7 @@ def details_for_prescriber(request, job_application_id, template_name="apply/pro # Latest GEIQ diagnosis for this job seeker created by a *prescriber* geiq_eligibility_diagnosis = ( job_application.to_company.kind == CompanyKind.GEIQ - and GEIQEligibilityDiagnosis.objects.valid() - .filter(job_seeker=job_application.job_seeker, author_prescriber_organization__isnull=False) - .select_related("author", "author_geiq", "author_prescriber_organization") - .first() + and _get_geiq_eligibility_diagnosis(job_application, only_prescriber=True) ) # Refused applications information is providen to prescribers @@ -682,7 +684,7 @@ def delete_prior_action(request, job_application_id, prior_action_id): # GEIQ cannot require IAE eligibility diagnosis, but shared templates need this variable. "eligibility_diagnosis_by_siae_required": False, "geiq_eligibility_diagnosis": ( - _get_geiq_eligibility_diagnosis_for_company(job_application) + _get_geiq_eligibility_diagnosis(job_application, only_prescriber=False) if job_application.to_company.kind == CompanyKind.GEIQ else None ), @@ -751,7 +753,7 @@ def add_or_modify_prior_action(request, job_application_id, prior_action_id=None form.save() geiq_eligibility_diagnosis = None if state_update and job_application.to_company.kind == CompanyKind.GEIQ: - geiq_eligibility_diagnosis = _get_geiq_eligibility_diagnosis_for_company(job_application) + geiq_eligibility_diagnosis = _get_geiq_eligibility_diagnosis(job_application, only_prescriber=False) return render( request, "apply/includes/job_application_prior_action.html", diff --git a/tests/companies/factories.py b/tests/companies/factories.py index b2cd760dec..624eafdf72 100644 --- a/tests/companies/factories.py +++ b/tests/companies/factories.py @@ -1,4 +1,3 @@ -import functools import string import factory.fuzzy @@ -174,7 +173,9 @@ class CompanyWith4MembershipsFactory(CompanyFactory): membership4 = factory.RelatedFactory(CompanyMembershipFactory, "company", is_admin=False, user__is_active=False) -CompanyWithMembershipAndJobsFactory = functools.partial(CompanyFactory, with_membership=True, with_jobs=True) +class CompanyWithMembershipAndJobsFactory(CompanyFactory): + with_membership = True + with_jobs = True class SiaeConventionPendingGracePeriodFactory(SiaeConventionFactory): diff --git a/tests/eligibility/factories.py b/tests/eligibility/factories.py index 1cc8908f0c..498ed96dbb 100644 --- a/tests/eligibility/factories.py +++ b/tests/eligibility/factories.py @@ -1,6 +1,7 @@ import random import factory +from dateutil.relativedelta import relativedelta from django.utils import timezone from itou.companies.enums import CompanyKind @@ -23,7 +24,10 @@ class Params: ), author=factory.LazyAttribute(lambda obj: obj.author_prescriber_organization.members.first()), ) - expired = factory.Trait(expires_at=factory.LazyFunction(timezone.now)) + expired = factory.Trait( + expires_at=factory.LazyFunction(timezone.now), + created_at=factory.LazyAttribute(lambda obj: obj.expires_at - relativedelta(months=6)), + ) created_at = factory.LazyFunction(timezone.now) job_seeker = factory.SubFactory(JobSeekerFactory) diff --git a/tests/eligibility/test_geiq.py b/tests/eligibility/test_geiq.py index c3c7cee95f..8624a71cf5 100644 --- a/tests/eligibility/test_geiq.py +++ b/tests/eligibility/test_geiq.py @@ -3,7 +3,6 @@ from django.core.exceptions import ValidationError from django.db import IntegrityError, transaction from django.db.models import Max -from django.utils import timezone from itou.companies.enums import CompanyKind from itou.eligibility.enums import AdministrativeCriteriaAnnex, AdministrativeCriteriaLevel @@ -220,7 +219,7 @@ def test_geiq_administrative_criteria_validation( GEIQ_ALLOWANCE_AMOUNT_814 = 814 -def test_geiq_eligibility_diagnosis_allowance_and_eligibility( +def test_geiq_eligibility_diagnosis_allowance( administrative_criteria_annex_1, administrative_criteria_annex_2_level_1, administrative_criteria_annex_2_level_2, @@ -230,45 +229,33 @@ def test_geiq_eligibility_diagnosis_allowance_and_eligibility( annex=AdministrativeCriteriaAnnex.ANNEX_2, level=AdministrativeCriteriaLevel.LEVEL_2, )[:2] - diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True) # Prescriber author gets it all - assert diagnosis.eligibility_confirmed + diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True) assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_1400 diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) - - assert not diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == 0 - diagnosis.administrative_criteria.add(a2l2_crits[0]) - # One A2L2 is not enough - assert not diagnosis.eligibility_confirmed + diagnosis.administrative_criteria.add(a2l2_crits[0]) assert diagnosis.allowance_amount == 0 # Two is ok diagnosis.administrative_criteria.add(a2l2_crits[1]) - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_1400 # One L1 is enough to get max allowance diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) diagnosis.administrative_criteria.add(administrative_criteria_annex_2_level_1) - - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_1400 diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) diagnosis.administrative_criteria.add(administrative_criteria_annex_1) - - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_814 # Adding another criteria will max allowance diagnosis.administrative_criteria.add(administrative_criteria_annex_2_level_1) - - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_1400 # Special case of dual-annex criteria: @@ -276,20 +263,12 @@ def test_geiq_eligibility_diagnosis_allowance_and_eligibility( # Counts as Annex 1 criterion... diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) diagnosis.administrative_criteria.add(administrative_criteria_both_annexes) - - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_814 # ... and also as Annex 2 Level 2 criterion diagnosis.administrative_criteria.add(administrative_criteria_annex_2_level_2) - - assert diagnosis.eligibility_confirmed assert diagnosis.allowance_amount == GEIQ_ALLOWANCE_AMOUNT_1400 - # GEIQ eligibility is not confirmed with valid criteria and expired diagnosis - diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True, expires_at=timezone.now()) - assert not diagnosis.eligibility_confirmed - def test_create_duplicate_diagnosis_in_same_geiq(): diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) diff --git a/tests/job_applications/factories.py b/tests/job_applications/factories.py index 748400056c..917f3d4d72 100644 --- a/tests/job_applications/factories.py +++ b/tests/job_applications/factories.py @@ -9,6 +9,7 @@ from itou.eligibility.enums import AuthorKind from itou.job_applications import models from itou.job_applications.enums import ( + JobApplicationState, Prequalification, ProfessionalSituationExperience, SenderKind, @@ -65,8 +66,8 @@ class Params: eligibility_diagnosis=None, ) with_geiq_eligibility_diagnosis_from_prescriber = factory.Trait( + sent_by_authorized_prescriber_organisation=True, to_company=factory.SubFactory(CompanyFactory, with_membership=True, kind=CompanyKind.GEIQ), - sender=factory.LazyAttribute(lambda obj: obj.sender_prescriber_organization.members.first()), geiq_eligibility_diagnosis=factory.SubFactory( GEIQEligibilityDiagnosisFactory, job_seeker=factory.SelfAttribute("..job_seeker"), @@ -91,7 +92,7 @@ class Params: sender=factory.LazyAttribute(lambda obj: obj.sender_company.members.first()), ) was_hired = factory.Trait( - state="accepted", + state=JobApplicationState.ACCEPTED, to_company__with_jobs=True, hired_job=factory.SubFactory(JobDescriptionFactory, company=factory.SelfAttribute("..to_company")), ) diff --git a/tests/www/apply/test_geiq.py b/tests/www/apply/test_geiq.py index 8012b3177e..e6ea5aa620 100644 --- a/tests/www/apply/test_geiq.py +++ b/tests/www/apply/test_geiq.py @@ -1,309 +1,400 @@ -from dateutil.relativedelta import relativedelta +import pytest from django.urls import reverse -from django.utils import dateformat, timezone -from freezegun import freeze_time +from pytest_django.asserts import assertContains, assertNotContains, assertTemplateNotUsed, assertTemplateUsed from itou.companies.enums import CompanyKind -from itou.eligibility.models.geiq import GEIQAdministrativeCriteria -from itou.utils.session import SessionNamespace +from itou.job_applications.enums import JobApplicationState +from itou.users.enums import UserKind +from itou.www.apply.views.process_views import _get_geiq_eligibility_diagnosis from tests.companies.factories import CompanyWithMembershipAndJobsFactory from tests.eligibility.factories import GEIQEligibilityDiagnosisFactory from tests.job_applications.factories import JobApplicationFactory from tests.prescribers.factories import PrescriberOrganizationWithMembershipFactory from tests.users.factories import JobSeekerFactory, JobSeekerWithAddressFactory -from tests.utils.test import TestCase -@freeze_time("2024-02-15") -class JobApplicationGEIQEligibilityDetailsTest(TestCase): +@pytest.mark.ignore_unknown_variable_template_error("with_matomo_event") +class TestJobApplicationGEIQEligibilityDetails: + VALID_DIAGNOSIS = "Éligibilité public prioritaire GEIQ validée" + # this string does not depends on the diagnosis author + ALLOWANCE_AND_COMPANY = "à une aide financière de l’État s’élevant à 1400 €" + NO_ALLOWANCE = ( + "Les critères que vous avez sélectionnés ne vous permettent pas de bénéficier d’une aide financière de l’État." + ) + NO_VALID_DIAGNOSIS = "Éligibilité public prioritaire GEIQ non confirmée" EXPIRED_DIAGNOSIS_EXPLANATION = "Le diagnostic du candidat a expiré" - @classmethod - def setUpTestData(cls): - cls.geiq = CompanyWithMembershipAndJobsFactory(kind=CompanyKind.GEIQ) - cls.valid_diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True) - cls.expired_diagnosis = GEIQEligibilityDiagnosisFactory( - from_prescriber=True, expires_at=timezone.now() - relativedelta(months=1) - ) - cls.prescriber_org = cls.valid_diagnosis.author_prescriber_organization - cls.author = cls.prescriber_org.members.first() - cls.job_seeker = cls.valid_diagnosis.job_seeker - cls.job_application = JobApplicationFactory( - to_company=cls.geiq, - job_seeker=cls.job_seeker, - ) - cls.url = reverse( - "apply:details_for_company", - kwargs={"job_application_id": cls.job_application.pk}, - ) - - def test_with_geiq_eligibility_details(self): - self.client.force_login(self.geiq.members.first()) - response = self.client.get(self.url) - - assert response.status_code == 200 - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - - def test_without_geiq_eligibility_details(self): - # No GEIQ diagnosis for this job seeker / job application - job_application = JobApplicationFactory(to_company=self.geiq) - self.client.force_login(self.geiq.members.first()) - response = self.client.get( - reverse( - "apply:details_for_company", - kwargs={"job_application_id": job_application.pk}, - ) - ) - - assert response.status_code == 200 - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - - def test_details_as_geiq_with_valid_eligibility_diagnosis(self): - diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) + def get_response(self, client, job_application, user): + client.force_login(user) + url_name = { + UserKind.EMPLOYER: "apply:details_for_company", + UserKind.PRESCRIBER: "apply:details_for_prescriber", + UserKind.JOB_SEEKER: "apply:details_for_jobseeker", + }[user.kind] + url = reverse(url_name, kwargs={"job_application_id": job_application.pk}) + return client.get(url) + + def test_with_valid_diagnosis(self, client): + diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True) job_application = JobApplicationFactory( - to_company=diagnosis.author_geiq, - geiq_eligibility_diagnosis=diagnosis, + to_company__kind=CompanyKind.GEIQ, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, eligibility_diagnosis=None, ) - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get( - reverse( - "apply:details_for_company", - kwargs={"job_application_id": job_application.pk}, - ) - ) - - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - self.assertContains( - response, - "Éligibilité public prioritaire GEIQ non confirmée", - ) - self.assertContains( - response, - "Les critères que vous avez sélectionnés ne vous permettent pas de bénéficier d’une aide financière de l’État.", # noqa: E501 - ) - self.assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) - # More in `test_allowance_details_for_geiq` - - def test_details_as_geiq_with_expired_eligibility_diagnosis(self): - diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, expired=True) + # as employer, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.VALID_DIAGNOSIS) + assertContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.NO_ALLOWANCE) + + # as job seeker, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.NO_ALLOWANCE) + + # as a prescriber, I see my diagnosis + assert diagnosis.author.is_prescriber + response = self.get_response(client, job_application, diagnosis.author) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.NO_ALLOWANCE) + + def test_with_expired_diagnosis(self, client): + diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True, expired=True) job_application = JobApplicationFactory( - to_company=diagnosis.author_geiq, - geiq_eligibility_diagnosis=diagnosis, + to_company__kind=CompanyKind.GEIQ, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, eligibility_diagnosis=None, ) - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get( - reverse( - "apply:details_for_company", - kwargs={"job_application_id": job_application.pk}, - ) - ) - - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - self.assertContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) - self.assertContains( - response, - "Éligibilité public prioritaire GEIQ non confirmée", - ) - - def test_details_as_authorized_prescriber_with_valid_diagnosis(self): - self.client.force_login(self.geiq.members.first()) - response = self.client.get(self.url) + # as employer, I see the prescriber diagnosis isn't valid anymore + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.NO_ALLOWANCE) + assertContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + # as job seeker, I see the prescriber diagnosis isn't valid anymore without further details + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + # as a prescriber, I see the prescriber diagnosis isn't valid anymore + assert diagnosis.author.is_prescriber + response = self.get_response(client, job_application, diagnosis.author) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.NO_ALLOWANCE) + assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + def test_without_diagnosis(self, client): + # No GEIQ diagnosis for this job seeker / job application + job_application = JobApplicationFactory(to_company__kind=CompanyKind.GEIQ) - self.assertContains( - response, - f"Éligibilité GEIQ confirmée par " - f"{self.author.get_full_name()} ({self.prescriber_org.display_name})", - ) - self.assertContains(response, "Durée de validité du diagnostic : du 15/02/2024 au 15/08/2024") + # as employer, I see there's no diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.NO_VALID_DIAGNOSIS) - def test_details_as_authorized_prescriber_with_expired_diagnosis(self): - job_application = JobApplicationFactory(to_company=self.geiq, job_seeker=self.expired_diagnosis.job_seeker) - url = reverse("apply:details_for_company", kwargs={"job_application_id": job_application.pk}) + # as job seeker, I see there's no diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.NO_VALID_DIAGNOSIS) - self.client.force_login(self.geiq.members.first()) - response = self.client.get(url) + # as a prescriber, I don't see anything + assert job_application.sender.is_prescriber + response = self.get_response(client, job_application, job_application.sender) + assertContains(response, self.NO_VALID_DIAGNOSIS) - self.assertContains( - response, - f"{self.EXPIRED_DIAGNOSIS_EXPLANATION} le {dateformat.format(self.expired_diagnosis.expires_at, 'd F Y')}", + def test_accepted_job_app_with_valid_diagnosis(self, client): + diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True) + job_application = JobApplicationFactory( + to_company__kind=CompanyKind.GEIQ, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, + eligibility_diagnosis=None, + geiq_eligibility_diagnosis=diagnosis, + was_hired=True, ) - def test_allowance_details_for_prescriber(self): - # Allowance amount is constant when diagnosis is made by an authorized prescriber - self.client.force_login(self.geiq.members.first()) - response = self.client.get(self.url) - - self.assertContains( - response, - "Ce diagnostic émis par un prescripteur habilité vous donnera droit en cas d’embauche", + # as employer, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.VALID_DIAGNOSIS) + assertContains(response, self.ALLOWANCE_AND_COMPANY) + + # as job seeker, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + + # as a prescriber, I see my diagnosis + assert diagnosis.author.is_prescriber + response = self.get_response(client, job_application, diagnosis.author) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + + def test_accepted_job_app_with_expired_diagnosis(self, client): + diagnosis = GEIQEligibilityDiagnosisFactory(from_prescriber=True, expired=True) + # Create a valid diangosis to check we don't use this one in the display + GEIQEligibilityDiagnosisFactory( + job_seeker=diagnosis.job_seeker, + author=diagnosis.author, + author_kind=diagnosis.author_kind, + author_prescriber_organization=diagnosis.author_prescriber_organization, ) - self.assertContains( - response, - f"une aide financière de l’État s’élevant à {self.valid_diagnosis.allowance_amount} € ", + job_application = JobApplicationFactory( + to_company__kind=CompanyKind.GEIQ, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, + eligibility_diagnosis=None, + geiq_eligibility_diagnosis=diagnosis, + was_hired=True, ) - def test_allowance_details_for_geiq(self): - # Slightly more complex than for prescribers : - # allowance amount is variable in function of chosen administrative criteria + # as employer, I still see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.VALID_DIAGNOSIS) + assertContains(response, self.ALLOWANCE_AND_COMPANY) + assert response.context["geiq_eligibility_diagnosis"] == diagnosis + + # as job seeker, I still see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assert response.context["geiq_eligibility_diagnosis"] == diagnosis + + # as a prescriber, I still see my diagnosis + assert diagnosis.author.is_prescriber + response = self.get_response(client, job_application, diagnosis.author) + assertContains(response, self.VALID_DIAGNOSIS) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assert response.context["geiq_eligibility_diagnosis"] == diagnosis + + def test_with_valid_diagnosis_no_allowance(self, client): diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) - job_application = JobApplicationFactory(to_company=diagnosis.author_geiq, job_seeker=diagnosis.job_seeker) - url = reverse("apply:details_for_company", kwargs={"job_application_id": job_application.pk}) - - # Annex 2, level 2 criteria: no allowance for GEIQ - diagnosis.administrative_criteria.set([GEIQAdministrativeCriteria.objects.get(pk=17)]) - diagnosis.save() - - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get(url) - - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - self.assertContains(response, "Éligibilité public prioritaire GEIQ non confirmée") - self.assertContains(response, "Renseignée par") - self.assertContains(response, "Situation administrative du candidat") - self.assertContains(response, "Mettre à jour") - self.assertContains( - response, - "Les critères que vous avez sélectionnés ne vous permettent pas de " - "bénéficier d’une aide financière de l’État.", + job_application = JobApplicationFactory( + to_company=diagnosis.author_geiq, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, + eligibility_diagnosis=None, ) - diagnosis.administrative_criteria.set( - [ - GEIQAdministrativeCriteria.objects.get(pk=18), - GEIQAdministrativeCriteria.objects.get(pk=17), - ] - ) - # Diagnosis must be saved to reset cached properties (allowance amount) - diagnosis.save() - response = self.client.get(url) - - self.assertTemplateUsed(response, "apply/includes/geiq/geiq_diagnosis_details.html") - self.assertContains(response, "Éligibilité public prioritaire GEIQ validée") - self.assertContains(response, "Éligibilité GEIQ confirmée par") - self.assertContains(response, "Situation administrative du candidat") - self.assertContains( - response, - "Les critères que vous avez sélectionnés vous donnent droit en cas d’embauche", - ) - self.assertContains( - response, - f"une aide financière de l’État s’élevant à {diagnosis.allowance_amount} € ", + # as employer, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertContains(response, self.NO_ALLOWANCE) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + # as job seeker, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.NO_ALLOWANCE) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + def test_with_expired_diagnosis_no_allowance(self, client): + diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, expired=True) + job_application = JobApplicationFactory( + to_company=diagnosis.author_geiq, + job_seeker=diagnosis.job_seeker, + sender=diagnosis.author, + eligibility_diagnosis=None, ) - -class TestJobSeekerGeoDetailsForGEIQDiagnosis(TestCase): + # as employer, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.to_company.members.first()) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.NO_ALLOWANCE) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + # as job seeker, I see the prescriber diagnosis + response = self.get_response(client, job_application, job_application.job_seeker) + assertContains(response, self.NO_VALID_DIAGNOSIS) + assertNotContains(response, self.NO_ALLOWANCE) + assertNotContains(response, self.ALLOWANCE_AND_COMPANY) + assertNotContains(response, self.EXPIRED_DIAGNOSIS_EXPLANATION) + + +def test_get_geiq_eligibility_diagnosis(subtests): + expired_prescriber_diagnosis = GEIQEligibilityDiagnosisFactory( + from_prescriber=True, + expired=True, + ) + job_seeker = expired_prescriber_diagnosis.job_seeker + expired_company_diagnosis = GEIQEligibilityDiagnosisFactory( + from_geiq=True, + expired=True, + job_seeker=job_seeker, + ) + geiq = expired_company_diagnosis.author_geiq + newer_company_diagnosis = GEIQEligibilityDiagnosisFactory( + from_geiq=True, + job_seeker=job_seeker, + author_geiq=geiq, + ) + valid_prescriber_diagnosis = GEIQEligibilityDiagnosisFactory( + from_prescriber=True, + job_seeker=job_seeker, + ) + + # The new prescriber diagnosis changed the valid_company_diagnosis expiry date : it's now expired + newer_company_diagnosis.refresh_from_db() + assert not newer_company_diagnosis.is_valid + + new_job_application = JobApplicationFactory( + to_company=geiq, + job_seeker=job_seeker, + eligibility_diagnosis=None, + ) + accepted_job_application_with_company_diagnosis = JobApplicationFactory( + to_company=geiq, + job_seeker=job_seeker, + geiq_eligibility_diagnosis=expired_company_diagnosis, + eligibility_diagnosis=None, + state=JobApplicationState.ACCEPTED, + ) + accepted_job_application_with_prescriber_diagnosis = JobApplicationFactory( + to_company=geiq, + job_seeker=job_seeker, + geiq_eligibility_diagnosis=expired_prescriber_diagnosis, + eligibility_diagnosis=None, + state=JobApplicationState.ACCEPTED, + ) + + # on accepted job application: + # the hiring company and jobseeker get the linked diagnosis + # a prescriber only sees the diagnosis if it was created by a prescriber + assert ( + _get_geiq_eligibility_diagnosis(accepted_job_application_with_company_diagnosis, only_prescriber=False) + == expired_company_diagnosis + ) + assert ( + _get_geiq_eligibility_diagnosis(accepted_job_application_with_company_diagnosis, only_prescriber=True) is None + ) + assert ( + _get_geiq_eligibility_diagnosis(accepted_job_application_with_prescriber_diagnosis, only_prescriber=False) + == expired_prescriber_diagnosis + ) + assert ( + _get_geiq_eligibility_diagnosis(accepted_job_application_with_prescriber_diagnosis, only_prescriber=True) + == expired_prescriber_diagnosis + ) + + # On not accepted job application: if there's a valid prescriber diagnosis, return it + assert _get_geiq_eligibility_diagnosis(new_job_application, only_prescriber=False) == valid_prescriber_diagnosis + assert _get_geiq_eligibility_diagnosis(new_job_application, only_prescriber=True) == valid_prescriber_diagnosis + + # If there's no prescriber valid diagnosis : + # the hiring company and jobseeker get the most recent diagnosis + # a prescriber get the most revent among prescriber diangoses + valid_prescriber_diagnosis.delete() + _valid_diagnois_from_other_company = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=job_seeker) + assert _get_geiq_eligibility_diagnosis(new_job_application, only_prescriber=False) == newer_company_diagnosis + assert _get_geiq_eligibility_diagnosis(new_job_application, only_prescriber=True) == expired_prescriber_diagnosis + + +class TestJobSeekerGeoDetailsForGEIQDiagnosis: """Check that QPV and ZRR details for job seeker are displayed in GEIQ eligibility diagnosis form""" - @classmethod - def setUpTestData(cls): - cls.job_seeker = JobSeekerFactory() - cls.job_seeker_in_qpv = JobSeekerWithAddressFactory(with_address_in_qpv=True) - cls.job_seeker_in_zrr = JobSeekerWithAddressFactory(with_city_in_zrr=True) - cls.geiq = CompanyWithMembershipAndJobsFactory(kind=CompanyKind.GEIQ, with_jobs=True) - cls.prescriber_org = PrescriberOrganizationWithMembershipFactory(authorized=True) - - def _setup_session(self): - apply_session = SessionNamespace(self.client.session, f"job_application-{self.geiq.pk}") - apply_session.init( - { - "selected_jobs": self.geiq.job_description_through.all(), - } - ) - apply_session.save() - - def test_job_seeker_not_resident_in_qpv_or_zrr(self): + def test_job_seeker_not_resident_in_qpv_or_zrr(self, client): # ZRR / QPV criteria info fragment is loaded before HTMX "zone" - diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=self.job_seeker) - job_application = JobApplicationFactory(job_seeker=self.job_seeker, to_company=diagnosis.author_geiq) + job_seeker = JobSeekerFactory() + diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=job_seeker) + job_application = JobApplicationFactory(job_seeker=job_seeker, to_company=diagnosis.author_geiq) url = reverse("apply:geiq_eligibility_criteria", kwargs={"job_application_id": job_application.pk}) - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get(url) - - self.assertTemplateNotUsed(response, "apply/includes/known_criteria.html") - - def test_job_seeker_not_resident_in_qpv_or_zrr_for_prescriber(self): - self.client.force_login(self.prescriber_org.members.first()) - self._setup_session() - response = self.client.get( + client.force_login(diagnosis.author_geiq.members.first()) + response = client.get(url) + + assertTemplateUsed(response, "apply/includes/geiq/geiq_administrative_criteria_form.html") + assertTemplateNotUsed(response, "apply/includes/known_criteria.html") + + def test_job_seeker_not_resident_in_qpv_or_zrr_for_prescriber(self, client): + job_seeker = JobSeekerFactory() + geiq = CompanyWithMembershipAndJobsFactory(kind=CompanyKind.GEIQ, with_jobs=True) + prescriber = PrescriberOrganizationWithMembershipFactory(authorized=True).members.get() + client.force_login(prescriber) + response = client.get( reverse( "apply:application_geiq_eligibility", - kwargs={"company_pk": self.geiq.pk, "job_seeker_public_id": self.job_seeker.public_id}, + kwargs={"company_pk": geiq.pk, "job_seeker_public_id": job_seeker.public_id}, ) ) + assertTemplateUsed(response, "apply/includes/geiq/geiq_administrative_criteria_form.html") + assertTemplateNotUsed(response, "apply/includes/known_criteria.html") - self.assertTemplateNotUsed(response, "apply/includes/known_criteria.html") - - def test_job_seeker_qpv_details_display(self): + def test_job_seeker_qpv_details_display(self, client): # Check QPV fragment is displayed: - diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=self.job_seeker_in_qpv) - job_application = JobApplicationFactory(job_seeker=self.job_seeker_in_qpv, to_company=diagnosis.author_geiq) + job_seeker_in_qpv = JobSeekerWithAddressFactory(with_address_in_qpv=True) + diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=job_seeker_in_qpv) + job_application = JobApplicationFactory(job_seeker=job_seeker_in_qpv, to_company=diagnosis.author_geiq) url = reverse("apply:geiq_eligibility_criteria", kwargs={"job_application_id": job_application.pk}) - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get(url) + client.force_login(diagnosis.author_geiq.members.first()) + response = client.get(url) - self.assertTemplateUsed(response, "apply/includes/known_criteria.html") - self.assertContains(response, "Résident QPV") + assertTemplateUsed(response, "apply/includes/known_criteria.html") + assertContains(response, "Résident QPV") - def test_job_seeker_qpv_details_display_for_prescriber(self): + def test_job_seeker_qpv_details_display_for_prescriber(self, client): # Check QPV fragment is displayed for prescriber: - self.client.force_login(self.prescriber_org.members.first()) - self._setup_session() - response = self.client.get( + job_seeker_in_qpv = JobSeekerWithAddressFactory(with_address_in_qpv=True) + prescriber = PrescriberOrganizationWithMembershipFactory(authorized=True).members.get() + client.force_login(prescriber) + geiq = CompanyWithMembershipAndJobsFactory(kind=CompanyKind.GEIQ, with_jobs=True) + response = client.get( reverse( "apply:application_geiq_eligibility", - kwargs={"company_pk": self.geiq.pk, "job_seeker_public_id": self.job_seeker_in_qpv.public_id}, + kwargs={"company_pk": geiq.pk, "job_seeker_public_id": job_seeker_in_qpv.public_id}, ) ) - self.assertTemplateUsed(response, "apply/includes/known_criteria.html") - self.assertContains(response, "Résident QPV") + assertTemplateUsed(response, "apply/includes/known_criteria.html") + assertContains(response, "Résident QPV") - def test_job_seeker_zrr_details_display(self): + def test_job_seeker_zrr_details_display(self, client): # Check ZRR fragment is displayed - - diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=self.job_seeker_in_zrr) - job_application = JobApplicationFactory(job_seeker=self.job_seeker_in_zrr, to_company=diagnosis.author_geiq) + job_seeker_in_zrr = JobSeekerWithAddressFactory(with_city_in_zrr=True) + diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True, job_seeker=job_seeker_in_zrr) + job_application = JobApplicationFactory(job_seeker=job_seeker_in_zrr, to_company=diagnosis.author_geiq) url = reverse("apply:geiq_eligibility_criteria", kwargs={"job_application_id": job_application.pk}) - self.client.force_login(diagnosis.author_geiq.members.first()) - response = self.client.get(url) + client.force_login(diagnosis.author_geiq.members.first()) + response = client.get(url) - self.assertTemplateUsed(response, "apply/includes/known_criteria.html") - self.assertContains(response, "Résident en ZRR") + assertTemplateUsed(response, "apply/includes/known_criteria.html") + assertContains(response, "Résident en ZRR") - def test_job_seeker_zrr_details_display_for_prescriber(self): + def test_job_seeker_zrr_details_display_for_prescriber(self, client): # Check QPV fragment is displayed for prescriber: - self.client.force_login(self.prescriber_org.members.first()) - self._setup_session() - response = self.client.get( + job_seeker_in_zrr = JobSeekerWithAddressFactory(with_city_in_zrr=True) + geiq = CompanyWithMembershipAndJobsFactory(kind=CompanyKind.GEIQ, with_jobs=True) + prescriber = PrescriberOrganizationWithMembershipFactory(authorized=True).members.get() + client.force_login(prescriber) + response = client.get( reverse( "apply:application_geiq_eligibility", - kwargs={"company_pk": self.geiq.pk, "job_seeker_public_id": self.job_seeker_in_zrr.public_id}, + kwargs={"company_pk": geiq.pk, "job_seeker_public_id": job_seeker_in_zrr.public_id}, ) ) - self.assertTemplateUsed(response, "apply/includes/known_criteria.html") - self.assertContains(response, "Résident en ZRR") + assertTemplateUsed(response, "apply/includes/known_criteria.html") + assertContains(response, "Résident en ZRR") - def test_jobseeker_cannot_create_geiq_diagnosis(self): - job_application = JobApplicationFactory(job_seeker=self.job_seeker, to_company=self.geiq) - self.client.force_login(self.job_seeker) + def test_jobseeker_cannot_create_geiq_diagnosis(self, client): + job_application = JobApplicationFactory(to_company__kind=CompanyKind.GEIQ) + client.force_login(job_application.job_seeker) # Needed to setup session - response = self.client.get( - reverse("apply:geiq_eligibility", kwargs={"job_application_id": job_application.pk}) - ) + response = client.get(reverse("apply:geiq_eligibility", kwargs={"job_application_id": job_application.pk})) assert response.status_code == 404 - response = self.client.post( + response = client.post( reverse("apply:geiq_eligibility_criteria", kwargs={"job_application_id": job_application.pk}), data={ "jeune_26_ans": "on", "sortant_ase": "on", }, ) - assert not self.job_seeker.geiq_eligibility_diagnoses.exists() + assert not job_application.job_seeker.geiq_eligibility_diagnoses.exists() assert response.status_code == 404 diff --git a/tests/www/apply/test_submit.py b/tests/www/apply/test_submit.py index 6d87906b59..19d36570f2 100644 --- a/tests/www/apply/test_submit.py +++ b/tests/www/apply/test_submit.py @@ -3903,7 +3903,6 @@ def test_geiq_eligibility_badge(self): # Badge is KO if job seeker has a valid diagnosis without allowance diagnosis = GEIQEligibilityDiagnosisFactory(from_geiq=True) assert diagnosis.allowance_amount == 0 - assert not diagnosis.eligibility_confirmed self.client.force_login(self.prescriber_org.members.first()) self._setup_session(diagnosis.author_geiq.pk)