Skip to content

Commit

Permalink
apply: Mask sender email when he left his organization
Browse files Browse the repository at this point in the history
  • Loading branch information
tonial committed Oct 23, 2024
1 parent 21246c1 commit 2271124
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 3 deletions.
17 changes: 17 additions & 0 deletions itou/templates/apply/includes/job_application_sender_info.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
{% load format_filters %}
{% load matomo %}

{% if job_application_sender_left_org %}
<div class="alert alert-warning alert-dismissible fade show" role="status">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Fermer"></button>
<div class="row">
<div class="col-auto pe-0">
<i class="ri-information-line ri-xl text-warning" aria-hidden="true"></i>
</div>
<div class="col">
<p class="mb-0">L’émetteur de cette candidature ne fait plus partie de l’organisation émettrice</p>
</div>
</div>
</div>


{% endif %}
<ul class="list-data list-data__two-column-md mb-3">
<li>
<small>Émetteur</small>
Expand All @@ -14,6 +29,8 @@
<small>Adresse e-mail</small>
{% if request.user.is_job_seeker and job_application.sender_kind != SenderKind.JOB_SEEKER %}
<strong>Non communiquée</strong>
{% elif job_application_sender_left_org %}
<div class="text-warning fst-italic">Les réponses seront transmises aux administrateurs de l’organisation</div>
{% else %}
<strong>{{ job_application.sender.email }}</strong>
{% matomo_event "candidature" "clic" "copied_sender_email" as matomo_event_attrs %}
Expand Down
15 changes: 15 additions & 0 deletions itou/www/apply/views/process_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ def _get_geiq_eligibility_diagnosis(job_application, only_prescriber):
).first()


def job_application_sender_left_org(job_app):
if job_app.sender_prescriber_organization_id:
return not (
job_app.sender.prescribermembership_set.active()
.filter(organization_id=job_app.sender_prescriber_organization_id)
.exists()
)
if job_app.sender_company_id:
return not job_app.sender.companymembership_set.active().filter(company_id=job_app.sender_company_id).exists()
return False


@login_required
def details_for_jobseeker(request, job_application_id, template_name="apply/process_details.html"):
"""
Expand Down Expand Up @@ -137,6 +149,7 @@ def details_for_jobseeker(request, job_application_id, template_name="apply/proc
"transition_logs": transition_logs,
"back_url": back_url,
"matomo_custom_title": "Candidature",
"job_application_sender_left_org": job_application_sender_left_org(job_application),
}

return render(request, template_name, context)
Expand Down Expand Up @@ -250,6 +263,7 @@ def details_for_company(request, job_application_id, template_name="apply/proces
PriorActionForm(action_only=True) if job_application.can_change_prior_actions else None
),
"matomo_custom_title": "Candidature",
"job_application_sender_left_org": job_application_sender_left_org(job_application),
}

return render(request, template_name, context)
Expand Down Expand Up @@ -335,6 +349,7 @@ def details_for_prescriber(request, job_application_id, template_name="apply/pro
"refused_by": refused_by,
"refusal_contact_email": refusal_contact_email,
"with_job_seeker_detail_url": True,
"job_application_sender_left_org": job_application_sender_left_org(job_application),
}

return render(request, template_name, context)
Expand Down
36 changes: 34 additions & 2 deletions tests/www/apply/__snapshots__/test_process.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@
# ---
# name: TestProcessViews.test_details_for_company_from_approval[job application detail for company]
dict({
'num_queries': 18,
'num_queries': 19,
'queries': list([
dict({
'origin': list([
Expand Down Expand Up @@ -1734,6 +1734,22 @@
LIMIT 1
''',
}),
dict({
'origin': list([
'job_application_sender_left_org[www/apply/views/process_views.py]',
'details_for_company[www/apply/views/process_views.py]',
]),
'sql': '''
SELECT %s AS "a"
FROM "prescribers_prescribermembership"
INNER JOIN "users_user" ON ("prescribers_prescribermembership"."user_id" = "users_user"."id")
WHERE ("prescribers_prescribermembership"."user_id" = %s
AND "users_user"."is_active"
AND "prescribers_prescribermembership"."is_active"
AND "prescribers_prescribermembership"."organization_id" = %s)
LIMIT 1
''',
}),
dict({
'origin': list([
'Company.has_admin[common_apps/organizations/models.py]',
Expand Down Expand Up @@ -1925,7 +1941,7 @@
# ---
# name: TestProcessViews.test_details_for_company_from_list[job application detail for company]
dict({
'num_queries': 18,
'num_queries': 19,
'queries': list([
dict({
'origin': list([
Expand Down Expand Up @@ -2714,6 +2730,22 @@
LIMIT 1
''',
}),
dict({
'origin': list([
'job_application_sender_left_org[www/apply/views/process_views.py]',
'details_for_company[www/apply/views/process_views.py]',
]),
'sql': '''
SELECT %s AS "a"
FROM "prescribers_prescribermembership"
INNER JOIN "users_user" ON ("prescribers_prescribermembership"."user_id" = "users_user"."id")
WHERE ("prescribers_prescribermembership"."user_id" = %s
AND "users_user"."is_active"
AND "prescribers_prescribermembership"."is_active"
AND "prescribers_prescribermembership"."organization_id" = %s)
LIMIT 1
''',
}),
dict({
'origin': list([
'Company.has_admin[common_apps/organizations/models.py]',
Expand Down
99 changes: 98 additions & 1 deletion tests/www/apply/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from itou.utils.urls import add_url_params
from itou.utils.widgets import DuetDatePickerWidget
from itou.www.apply.forms import AcceptForm
from itou.www.apply.views.process_views import job_application_sender_left_org
from tests.approvals.factories import (
ProlongationFactory,
ProlongationRequestFactory,
Expand All @@ -63,7 +64,7 @@
PriorActionFactory,
)
from tests.jobs.factories import create_test_romes_and_appellations
from tests.prescribers.factories import PrescriberOrganizationFactory
from tests.prescribers.factories import PrescriberMembershipFactory, PrescriberOrganizationFactory
from tests.siae_evaluations.factories import EvaluatedSiaeFactory
from tests.users.factories import EmployerFactory, JobSeekerFactory, LaborInspectorFactory, PrescriberFactory
from tests.utils.htmx.test import assertSoupEqual, update_page_with_htmx
Expand All @@ -79,6 +80,8 @@
"Si les détails apportés dans le message de réponse ne vous ont pas permis d’en savoir plus,"
" vous pouvez contacter l’employeur."
)
SENDER_LEFT_ORG = "Les réponses seront transmises aux administrateurs de l’organisation"
SENDER_LEFT_ORG_ALERT = "L’émetteur de cette candidature ne fait plus partie de l’organisation émettrice"

IAE_CANCELLATION_CONFIRMATION = (
"En validant, <strong>vous renoncez aux aides au poste</strong> liées à cette candidature "
Expand Down Expand Up @@ -259,6 +262,20 @@ def test_details_for_company_from_list(self, client, snapshot):
assertContains(response, resume_link)
assertNotContains(response, PRIOR_ACTION_SECTION_TITLE)

def test_details_when_sender_left_org(self, client):
job_application = JobApplicationFactory(sent_by_authorized_prescriber_organisation=True)
company = job_application.to_company
employer = company.members.first()
sender = job_application.sender
sender.prescribermembership_set.update(is_active=False)
client.force_login(employer)

url = reverse("apply:details_for_company", kwargs={"job_application_id": job_application.pk})
response = client.get(url)
assertNotContains(response, sender.email)
assertContains(response, SENDER_LEFT_ORG)
assertContains(response, SENDER_LEFT_ORG_ALERT)

def test_details_archived(self, client):
UNARCHIVE = "Désarchiver"
job_application = JobApplicationFactory(
Expand Down Expand Up @@ -403,6 +420,19 @@ def test_details_for_prescriber(self, client):
response = client.get(url)
assertContains(response, LackOfNIRReason.NIR_ASSOCIATED_TO_OTHER.label, html=True)

def test_details_for_prescriber_when_sender_left_org(self, client):
job_application = JobApplicationFactory(sent_by_authorized_prescriber_organisation=True)
prescriber = PrescriberMembershipFactory(organization=job_application.sender_prescriber_organization).user
sender = job_application.sender
sender.prescribermembership_set.update(is_active=False)
client.force_login(prescriber)

url = reverse("apply:details_for_prescriber", kwargs={"job_application_id": job_application.pk})
response = client.get(url)
assertNotContains(response, sender.email)
assertContains(response, SENDER_LEFT_ORG)
assertContains(response, SENDER_LEFT_ORG_ALERT)

def test_details_for_prescriber_as_company_when_i_am_not_the_sender(self, client):
job_application = JobApplicationFactory(sent_by_authorized_prescriber_organisation=True)
employer = job_application.to_company.members.first()
Expand Down Expand Up @@ -481,6 +511,19 @@ def test_details_for_job_seeker(self, client, snapshot):

assertNotContains(response, PRIOR_ACTION_SECTION_TITLE)

def test_details_for_job_seeker_when_sender_left_org(self, client):
job_application = JobApplicationFactory(sent_by_authorized_prescriber_organisation=True)
sender = job_application.sender
sender.prescribermembership_set.update(is_active=False)

client.force_login(job_application.job_seeker)

url = reverse("apply:details_for_jobseeker", kwargs={"job_application_id": job_application.pk})
response = client.get(url)
assertNotContains(response, sender.email)
assertNotContains(response, SENDER_LEFT_ORG) # A job seeker never sees the email of the sender
assertContains(response, SENDER_LEFT_ORG_ALERT)

def test_details_for_job_seeker_as_other_user(self, client, subtests):
job_application = JobApplicationFactory()
url = reverse("apply:details_for_jobseeker", kwargs={"job_application_id": job_application.pk})
Expand Down Expand Up @@ -3919,3 +3962,57 @@ def test_approval_status_includes(client, snapshot):
response,
"Date de fin prévisionnelle : 29/05/2025",
)


class TestJobApplicationSenderLeftOrg:
def test_sender_left_org_prescriber(self):
prescriber_membership = PrescriberMembershipFactory()
job_app = JobApplicationFactory(
sender=prescriber_membership.user, sender_prescriber_organization=prescriber_membership.organization
)
assert job_application_sender_left_org(job_app) is False

# membership is inactive
prescriber_membership.is_active = False
prescriber_membership.save(update_fields=["is_active"])
assert job_application_sender_left_org(job_app) is True

# prescriber is inactive
prescriber_membership.is_active = True
prescriber_membership.save(update_fields=["is_active"])
prescriber_membership.user.is_active = False
prescriber_membership.user.save(update_fields=["is_active"])
assert job_application_sender_left_org(job_app) is True

# membership was removed
prescriber_membership.user.is_active = True
prescriber_membership.user.save(update_fields=["is_active"])
prescriber_membership.delete()
assert job_application_sender_left_org(job_app) is True

def test_sender_left_org_employer(self):
company_membership = CompanyMembershipFactory()
job_app = JobApplicationFactory(sender=company_membership.user, sender_company=company_membership.company)
assert job_application_sender_left_org(job_app) is False

# membership is inactive
company_membership.is_active = False
company_membership.save(update_fields=["is_active"])
assert job_application_sender_left_org(job_app) is True

# prescriber is inactive
company_membership.is_active = True
company_membership.save(update_fields=["is_active"])
company_membership.user.is_active = False
company_membership.user.save(update_fields=["is_active"])
assert job_application_sender_left_org(job_app) is True

# membership was removed
company_membership.user.is_active = True
company_membership.user.save(update_fields=["is_active"])
company_membership.delete()
assert job_application_sender_left_org(job_app) is True

def test_sender_left_org_job_seeker(self):
job_app = JobApplicationSentByJobSeekerFactory()
assert job_application_sender_left_org(job_app) is False

0 comments on commit 2271124

Please sign in to comment.