Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPS: suivre automatiquement les candidats lors de certaines actions #5501

Merged
merged 10 commits into from
Jan 31, 2025
4 changes: 4 additions & 0 deletions itou/eligibility/models/geiq.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CommonEligibilityDiagnosisQuerySet,
)
from itou.eligibility.utils import geiq_allowance_amount
from itou.gps.models import FollowUpGroup
from itou.prescribers.models import PrescriberOrganization
from itou.users.models import User

Expand Down Expand Up @@ -207,6 +208,9 @@ def create_eligibility_diagnosis(
if administrative_criteria:
result.administrative_criteria.set(administrative_criteria)

# Sync GPS groups
FollowUpGroup.objects.follow_beneficiary(job_seeker, author)

return result

@classmethod
Expand Down
5 changes: 5 additions & 0 deletions itou/eligibility/models/iae.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
AdministrativeCriteriaQuerySet,
CommonEligibilityDiagnosisQuerySet,
)
from itou.gps.models import FollowUpGroup


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -214,6 +215,10 @@ def create_diagnosis(cls, job_seeker, *, author, author_organization, administra
)
if administrative_criteria:
diagnosis.administrative_criteria.add(*administrative_criteria)

# Sync GPS groups
FollowUpGroup.objects.follow_beneficiary(job_seeker, author)

return diagnosis

@classmethod
Expand Down
9 changes: 8 additions & 1 deletion itou/gps/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ class FollowUpGroupMembershipAdmin(ItouModelAdmin):
"created_in_bulk",
)
raw_id_fields = ("follow_up_group", "member")
readonly_fields = ("creator", "created_at", "updated_at", "ended_at", "created_in_bulk")
readonly_fields = (
"creator",
"created_at",
"last_contact_at",
"updated_at",
"ended_at",
"created_in_bulk",
)
ordering = ["-created_at"]

def get_readonly_fields(self, request, obj=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.5 on 2025-01-27 10:49

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("gps", "0005_francetravailcontact"),
]

operations = [
migrations.AddField(
model_name="followupgroupmembership",
name="last_contact_at",
field=models.DateTimeField(null=True, verbose_name="date de dernier contact"),
),
]
31 changes: 21 additions & 10 deletions itou/gps/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,29 @@ def not_bulk_created(self):


class FollowUpGroupManager(models.Manager):
def follow_beneficiary(self, beneficiary, user, is_referent=False):
def follow_beneficiary(self, beneficiary, user, is_referent=None):
now = timezone.now()
with transaction.atomic():
group, _ = FollowUpGroup.objects.get_or_create(beneficiary=beneficiary)

updated = FollowUpGroupMembership.objects.filter(member=user, follow_up_group=group).update(
is_active=True, is_referent=is_referent
update_args = {
"is_active": True,
"last_contact_at": now,
}
if is_referent is not None:
update_args["is_referent"] = is_referent

create_args = update_args | {
"creator": user,
"created_at": now,
}

FollowUpGroupMembership.objects.update_or_create(
follow_up_group=group,
member=user,
defaults=update_args,
create_defaults=create_args,
)
if not updated:
FollowUpGroupMembership.objects.create(
follow_up_group=group,
member=user,
creator=user,
is_referent=is_referent,
)


class FollowUpGroupQueryset(BulkCreatedAtQuerysetProxy, models.QuerySet):
Expand Down Expand Up @@ -100,6 +109,8 @@ class Meta:
created_at = models.DateTimeField(verbose_name="date de création", default=timezone.now)
created_in_bulk = models.BooleanField(verbose_name="créé massivement", default=False, db_index=True)

last_contact_at = models.DateTimeField(verbose_name="date de dernier contact", null=True)

# Keep track of when the membership was ended
ended_at = models.DateTimeField(verbose_name="date de désactivation", null=True)

Expand Down
4 changes: 4 additions & 0 deletions itou/job_applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from itou.companies.models import CompanyMembership
from itou.eligibility.enums import AuthorKind
from itou.eligibility.models import EligibilityDiagnosis, SelectedAdministrativeCriteria
from itou.gps.models import FollowUpGroup
from itou.job_applications import notifications as job_application_notifications
from itou.job_applications.enums import (
ARCHIVABLE_JOB_APPLICATION_STATES_MANUAL,
Expand Down Expand Up @@ -1011,6 +1012,9 @@ def accept(self, *, user):
self.approval_delivery_mode = self.APPROVAL_DELIVERY_MODE_AUTOMATIC
self.approval.unsuspend(self.hiring_start_at)

# Sync GPS groups
FollowUpGroup.objects.follow_beneficiary(self.job_seeker, user)

@xwf_models.transition()
def postpone(self, *, user):
# Send notification.
Expand Down
5 changes: 5 additions & 0 deletions itou/www/apply/views/submit_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from itou.eligibility.models import EligibilityDiagnosis
from itou.eligibility.models.geiq import GEIQEligibilityDiagnosis
from itou.files.models import File
from itou.gps.models import FollowUpGroup
from itou.job_applications.models import JobApplication
from itou.users.enums import UserKind
from itou.users.models import User
Expand Down Expand Up @@ -663,6 +664,10 @@ def form_valid(self):
# The job application is now saved in DB, delete the session early to avoid any problems
self.apply_session.delete()

if self.request.user.kind in [UserKind.EMPLOYER, UserKind.PRESCRIBER]:
# New job application -> sync GPS groups if the sender is not a jobseeker
FollowUpGroup.objects.follow_beneficiary(self.job_seeker, self.request.user)

try:
# Send notifications
company_recipients = User.objects.filter(
Expand Down
10 changes: 3 additions & 7 deletions itou/www/job_seekers_views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,9 +432,7 @@ def post(self, request, *args, **kwargs):
# The NIR we found is correct
if self.form.data.get("confirm"):
if self.is_gps:
FollowUpGroup.objects.follow_beneficiary(
beneficiary=job_seeker, user=request.user, is_referent=True
)
FollowUpGroup.objects.follow_beneficiary(job_seeker, request.user, is_referent=True)
return HttpResponseRedirect(self.get_exit_url(job_seeker.public_id))

context = {
Expand Down Expand Up @@ -522,9 +520,7 @@ def post(self, request, *args, **kwargs):
logger.exception("step_job_seeker: error when saving job_seeker=%s nir=%s", job_seeker, nir)
else:
if self.is_gps:
FollowUpGroup.objects.follow_beneficiary(
beneficiary=job_seeker, user=request.user, is_referent=True
)
FollowUpGroup.objects.follow_beneficiary(job_seeker, request.user, is_referent=True)
return HttpResponseRedirect(self.get_exit_url(job_seeker.public_id))

return self.render_to_response(
Expand Down Expand Up @@ -796,7 +792,7 @@ def post(self, request, *args, **kwargs):
url = self.get_exit_url(self.profile.user.public_id, created=True)

if self.is_gps:
FollowUpGroup.objects.follow_beneficiary(beneficiary=user, user=request.user, is_referent=True)
FollowUpGroup.objects.follow_beneficiary(user, request.user, is_referent=True)

return HttpResponseRedirect(url)

Expand Down
11 changes: 11 additions & 0 deletions tests/eligibility/test_geiq.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from itou.companies.enums import CompanyKind
from itou.eligibility.enums import AdministrativeCriteriaAnnex, AdministrativeCriteriaLevel
from itou.eligibility.models import GEIQAdministrativeCriteria, GEIQEligibilityDiagnosis
from itou.gps.models import FollowUpGroup, FollowUpGroupMembership
from tests.companies.factories import CompanyWithMembershipAndJobsFactory
from tests.eligibility.factories import GEIQEligibilityDiagnosisFactory
from tests.job_applications.factories import JobApplicationFactory
Expand Down Expand Up @@ -53,6 +54,11 @@ def test_create_geiq_eligibility_diagnosis(administrative_criteria_annex_1):
)

assert diagnosis.pk
group = FollowUpGroup.objects.get()
assert group.beneficiary == diagnosis.job_seeker
membership = FollowUpGroupMembership.objects.get(follow_up_group=group)
assert membership.member == diagnosis.author
assert membership.creator == diagnosis.author

diagnosis = GEIQEligibilityDiagnosis.create_eligibility_diagnosis(
job_seeker=JobSeekerFactory(),
Expand All @@ -62,6 +68,11 @@ def test_create_geiq_eligibility_diagnosis(administrative_criteria_annex_1):
)

assert diagnosis.pk
group = FollowUpGroup.objects.exclude(pk=group.pk).get() # Get the newer group
assert group.beneficiary == diagnosis.job_seeker
membership = FollowUpGroupMembership.objects.get(follow_up_group=group)
assert membership.member == diagnosis.author
assert membership.creator == diagnosis.author

# bad cops:

Expand Down
17 changes: 17 additions & 0 deletions tests/eligibility/test_iae.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
AdministrativeCriteriaQuerySet,
)
from itou.eligibility.models.geiq import GEIQAdministrativeCriteria
from itou.gps.models import FollowUpGroup, FollowUpGroupMembership
from itou.utils.mocks.api_particulier import (
rsa_certified_mocker,
rsa_not_certified_mocker,
Expand Down Expand Up @@ -276,6 +277,14 @@ def test_create_diagnosis_employer(self):
assert diagnosis.administrative_criteria.count() == 0
assert diagnosis.expires_at == datetime.date(2025, 3, 5)

# Check GPS group
# ----------------------------------------------------------------------
group = FollowUpGroup.objects.get()
assert group.beneficiary == job_seeker
membership = FollowUpGroupMembership.objects.get(follow_up_group=group)
assert membership.member == user
assert membership.creator == user

@freeze_time("2024-12-03")
def test_create_diagnosis_prescriber(self):
job_seeker = JobSeekerFactory()
Expand All @@ -296,6 +305,14 @@ def test_create_diagnosis_prescriber(self):
assert diagnosis.administrative_criteria.count() == 0
assert diagnosis.expires_at == datetime.date(2025, 6, 3)

# Check GPS group
# ----------------------------------------------------------------------
group = FollowUpGroup.objects.get()
assert group.beneficiary == job_seeker
membership = FollowUpGroupMembership.objects.get(follow_up_group=group)
assert membership.member == prescriber
assert membership.creator == prescriber

def test_create_diagnosis_with_administrative_criteria(self):
job_seeker = JobSeekerFactory()
prescriber_organization = PrescriberOrganizationWithMembershipFactory(authorized=True)
Expand Down
15 changes: 9 additions & 6 deletions tests/gps/__snapshots__/test_views.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@
"gps_followupgroupmembership"."is_active",
"gps_followupgroupmembership"."created_at",
"gps_followupgroupmembership"."created_in_bulk",
"gps_followupgroupmembership"."last_contact_at",
"gps_followupgroupmembership"."ended_at",
"gps_followupgroupmembership"."updated_at",
"gps_followupgroupmembership"."follow_up_group_id",
Expand Down Expand Up @@ -770,7 +771,8 @@
"col7",
"col8",
"col9",
"col10"
"col10",
"col11"
FROM
(SELECT *
FROM
Expand All @@ -779,11 +781,12 @@
"gps_followupgroupmembership"."is_active" AS "col3",
"gps_followupgroupmembership"."created_at" AS "col4",
"gps_followupgroupmembership"."created_in_bulk" AS "col5",
"gps_followupgroupmembership"."ended_at" AS "col6",
"gps_followupgroupmembership"."updated_at" AS "col7",
"gps_followupgroupmembership"."follow_up_group_id" AS "col8",
"gps_followupgroupmembership"."member_id" AS "col9",
"gps_followupgroupmembership"."creator_id" AS "col10",
"gps_followupgroupmembership"."last_contact_at" AS "col6",
"gps_followupgroupmembership"."ended_at" AS "col7",
"gps_followupgroupmembership"."updated_at" AS "col8",
"gps_followupgroupmembership"."follow_up_group_id" AS "col9",
"gps_followupgroupmembership"."member_id" AS "col10",
"gps_followupgroupmembership"."creator_id" AS "col11",
ROW_NUMBER() OVER (PARTITION BY "gps_followupgroupmembership"."follow_up_group_id"
ORDER BY RANDOM() ASC) AS "qual0",
RANDOM() AS "qual1"
Expand Down
22 changes: 19 additions & 3 deletions tests/gps/test_management_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from itou.companies.enums import CompanyKind
from itou.gps.models import FollowUpGroup, FollowUpGroupMembership, FranceTravailContact
from itou.job_applications.enums import JobApplicationState
from itou.job_applications.models import JobApplicationTransitionLog
from itou.users.models import JobSeekerProfile
from tests.companies.factories import CompanyWith4MembershipsFactory
from tests.eligibility.factories import GEIQEligibilityDiagnosisFactory, IAEEligibilityDiagnosisFactory
Expand Down Expand Up @@ -86,7 +87,12 @@ def test_group_creation(self):
)
# Needed to create the transition log entries
user_who_accepted = job_application_with_approval.to_company.members.last()
job_application_with_approval.accept(user=user_who_accepted)
# Don't call accept that now creates a group and membership automatically
JobApplicationTransitionLog.objects.create(
job_application=job_application_with_approval,
to_state=JobApplicationState.ACCEPTED,
user=user_who_accepted,
)
should_be_created_groups_counter += 1
should_be_created_memberships += 3 # employer who accepted, employer who made the diagnosis and prescriber

Expand Down Expand Up @@ -172,7 +178,12 @@ def test_job_application_accepted(self):
state=JobApplicationState.PROCESSING,
)
user = job_application_accepted.to_company.members.first()
job_application_accepted.accept(user=user)
# Don't call accept that now creates a group and membership automatically
JobApplicationTransitionLog.objects.create(
job_application=job_application_accepted,
to_state=JobApplicationState.ACCEPTED,
user=user,
)

assert FollowUpGroup.objects.count() == 0
assert FollowUpGroupMembership.objects.count() == 0
Expand Down Expand Up @@ -341,7 +352,12 @@ def test_group_create_at_update(self):
# >>> Employer membership creation date
employer_membership_date = timezone.now()
# the employer accepts the last job app
accepted_job_app.accept(user=employer)
# Don't call accept that now creates a group and membership automatically
JobApplicationTransitionLog.objects.create(
job_application=accepted_job_app,
to_state=JobApplicationState.ACCEPTED,
user=employer,
)
with freeze_time("2022-06-01 00:00:08"):
# Another iae diag from the employer
IAEEligibilityDiagnosisFactory(job_seeker=job_seeker, from_employer=True, author=employer)
Expand Down
Loading