def test_get_next_number(self): now = timezone.now().date() current_year = now.strftime("%y") # No pre-existing Approval objects. expected_number = f"99999{current_year}00001" self.assertEqual(Approval.get_next_number(), expected_number) # With pre-existing Approval objects. ApprovalFactory(number=f"99999{current_year}00038", start_at=now) ApprovalFactory(number=f"99999{current_year}00039", start_at=now) ApprovalFactory(number=f"99999{current_year}00040", start_at=now) expected_number = f"99999{current_year}00041" self.assertEqual(Approval.get_next_number(), expected_number) Approval.objects.all().delete() # Date of hiring in the past. date_of_hiring = now - relativedelta(years=3) year = date_of_hiring.strftime("%y") ApprovalFactory(number=f"99999{year}99998", start_at=date_of_hiring) expected_number = f"99999{year}99999" self.assertEqual(Approval.get_next_number(date_of_hiring), expected_number) Approval.objects.all().delete() # Date of hiring in the future. date_of_hiring = now + relativedelta(years=3) year = date_of_hiring.strftime("%y") ApprovalFactory(number=f"99999{year}00020", start_at=date_of_hiring) expected_number = f"99999{year}00021" self.assertEqual(Approval.get_next_number(date_of_hiring), expected_number) Approval.objects.all().delete()
def test_get_or_create_from_valid(self): # With an existing valid `PoleEmploiApproval`. user = JobSeekerFactory() valid_pe_approval = PoleEmploiApprovalFactory( pole_emploi_id=user.pole_emploi_id, birthdate=user.birthdate, number="625741810182A01" ) approvals_wrapper = ApprovalsWrapper(user) approval = Approval.get_or_create_from_valid(approvals_wrapper) self.assertTrue(isinstance(approval, Approval)) self.assertEqual(approval.start_at, valid_pe_approval.start_at) self.assertEqual(approval.end_at, valid_pe_approval.end_at) self.assertEqual(approval.number, valid_pe_approval.number[:12]) self.assertEqual(approval.user, user) self.assertEqual(approval.created_by, None) # With an existing valid `Approval`. user = JobSeekerFactory() valid_approval = ApprovalFactory(user=user, start_at=datetime.date.today() - relativedelta(days=1)) approvals_wrapper = ApprovalsWrapper(user) approval = Approval.get_or_create_from_valid(approvals_wrapper) self.assertTrue(isinstance(approval, Approval)) self.assertEqual(approval, valid_approval)
def find_or_create_approval(self, job_seeker, created_by): created = False redelivered_approval = False approval = None # If job seeker has already a valid approval: don't redeliver it. if job_seeker.approvals.valid().exists(): approval = job_seeker.approvals_wrapper.latest_approval # Unless it was issued by an AI who did not want to wait for our script to run. job_app_qs = approval.jobapplication_set.filter( state=JobApplicationWorkflow.STATE_ACCEPTED, to_siae=F("sender_siae"), created_at__gt=settings.AI_EMPLOYEES_STOCK_IMPORT_DATE, approval_delivery_mode=JobApplication.APPROVAL_DELIVERY_MODE_AUTOMATIC, ) count = job_app_qs.count() if count == 1: self.logger.info(f"Approval delivered by employer: {approval.pk}. Deleting it.") self.logger.info(f"Job application sent by employer: {job_app_qs.get().pk}. Deleting it.") if not self.dry_run: job_app_qs.delete() approval.delete() approval = None redelivered_approval = True elif count > 1: self.logger.info(f"Multiple accepted job applications linked to this approval: {approval.pk}.") if not approval: # `create_employee_record` prevents "Fiche salariés" from being created. approval = Approval( start_at=datetime.date(2021, 12, 1), end_at=datetime.date(2023, 11, 30), user_id=job_seeker.pk, created_by=created_by, created_at=settings.AI_EMPLOYEES_STOCK_IMPORT_DATE, ) if not self.dry_run: # In production, it can raise an IntegrityError if another PASS has just been delivered a few seconds ago. # Try to save with another number until it succeeds. succeeded = None while succeeded is None: try: # `Approval.save()` delivers an automatic number. approval.save() succeeded = True except IntegrityError: pass created = True return created, approval, redelivered_approval
def test_accept_after_cancel(self, *args, **kwargs): # A canceled job application is not linked to an approval # unless the job seeker has an accepted job application. create_test_cities(["54", "57"], num_per_department=2) city = City.objects.first() job_seeker = JobSeekerWithAddressFactory(city=city.name) job_application = JobApplicationSentByJobSeekerFactory( state=JobApplicationWorkflow.STATE_CANCELLED, job_seeker=job_seeker ) siae_user = job_application.to_siae.members.first() self.client.login(username=siae_user.email, password=DEFAULT_PASSWORD) url_accept = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) hiring_start_at = timezone.localdate() hiring_end_at = Approval.get_default_end_date(hiring_start_at) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "pole_emploi_id": job_application.job_seeker.pole_emploi_id, "answer": "", "address_line_1": job_seeker.address_line_1, "post_code": job_seeker.post_code, "city": city.name, "city_slug": city.slug, } response = self.client.post(url_accept, data=post_data) next_url = reverse("apply:details_for_siae", kwargs={"job_application_id": job_application.pk}) self.assertRedirects(response, next_url) job_application.refresh_from_db() self.assertEqual(job_seeker.approvals.count(), 1) approval = job_seeker.approvals.first() self.assertEqual(approval.start_at, job_application.hiring_start_at) self.assertTrue(job_application.state.is_accepted)
def accept(self, *args, **kwargs): accepted_by = kwargs.get("user") # Mark other related job applications as obsolete. for job_application in self.job_seeker.job_applications.exclude( pk=self.pk).pending(): job_application.render_obsolete(*args, **kwargs) # Notification email. emails = [self.email_accept] # Approval issuance logic. if self.to_siae.is_subject_to_eligibility_rules: approvals_wrapper = self.job_seeker.approvals_wrapper if (approvals_wrapper.has_in_waiting_period and not self.is_sent_by_authorized_prescriber): # Security check: it's supposed to be blocked upstream. raise xwf_models.AbortTransition( "Job seeker has an approval in waiting period.") if approvals_wrapper.has_valid: # Automatically reuse an existing valid Itou or PE approval. self.approval = Approval.get_or_create_from_valid( approvals_wrapper) emails.append(self.email_approval_number(accepted_by)) elif (self.job_seeker.pole_emploi_id or self.job_seeker.lack_of_pole_emploi_id_reason == self.job_seeker.REASON_NOT_REGISTERED): # Automatically create a new approval. new_approval = Approval( start_at=self.hiring_start_at, end_at=Approval.get_default_end_date(self.hiring_start_at), number=Approval.get_next_number(self.hiring_start_at), user=self.job_seeker, ) new_approval.save() self.approval = new_approval emails.append(self.email_approval_number(accepted_by)) elif (self.job_seeker.lack_of_pole_emploi_id_reason == self.job_seeker.REASON_FORGOTTEN): # Trigger a manual approval creation. emails.append( self.email_accept_trigger_manual_approval(accepted_by)) else: raise xwf_models.AbortTransition( "Job seeker has an invalid PE status, cannot issue approval." ) # Send emails in batch. connection = mail.get_connection() connection.send_messages(emails) if self.approval: self.approval_number_sent_by_email = True self.approval_number_sent_at = timezone.now() self.approval_delivery_mode = self.APPROVAL_DELIVERY_MODE_AUTOMATIC
class ApprovalFactory(factory.django.DjangoModelFactory): """Generate an Approval() object for unit tests.""" class Meta: model = Approval user = factory.SubFactory(JobSeekerFactory) number = factory.fuzzy.FuzzyText(length=7, chars=string.digits, prefix=Approval.ASP_ITOU_PREFIX) start_at = datetime.date.today() end_at = factory.LazyAttribute( lambda obj: Approval.get_default_end_date(obj.start_at))
def test_get_next_number(self): PREFIX = Approval.ASP_ITOU_PREFIX now = timezone.now().date() current_year = now.strftime("%y") # No pre-existing objects. expected_number = f"{PREFIX}{current_year}00001" self.assertEqual(Approval.get_next_number(), expected_number) # With pre-existing objects. ApprovalFactory(number=f"{PREFIX}{current_year}00038", start_at=now) ApprovalFactory(number=f"{PREFIX}{current_year}00039", start_at=now) ApprovalFactory(number=f"{PREFIX}{current_year}00040", start_at=now) expected_number = f"{PREFIX}{current_year}00041" self.assertEqual(Approval.get_next_number(), expected_number) Approval.objects.all().delete() # Date of hiring in the past. hiring_start_at = now - relativedelta(years=3) year = hiring_start_at.strftime("%y") ApprovalFactory(number=f"{PREFIX}{year}99998", start_at=hiring_start_at) expected_number = f"{PREFIX}{year}99999" self.assertEqual(Approval.get_next_number(hiring_start_at), expected_number) Approval.objects.all().delete() # Date of hiring in the future. hiring_start_at = now + relativedelta(years=3) year = hiring_start_at.strftime("%y") ApprovalFactory(number=f"{PREFIX}{year}00020", start_at=hiring_start_at) expected_number = f"{PREFIX}{year}00021" self.assertEqual(Approval.get_next_number(hiring_start_at), expected_number) Approval.objects.all().delete() # With pre-existing Pôle emploi approval. ApprovalFactory(number=f"625741810182", start_at=now) expected_number = f"{PREFIX}{current_year}00001" self.assertEqual(Approval.get_next_number(), expected_number) Approval.objects.all().delete() # With various pre-existing objects. ApprovalFactory(number=f"{PREFIX}{current_year}00222", start_at=now) ApprovalFactory(number=f"625741810182", start_at=now) expected_number = f"{PREFIX}{current_year}00223" self.assertEqual(Approval.get_next_number(), expected_number) Approval.objects.all().delete()
def manually_add_approval( request, model_admin, job_application_id, template_name="admin/approvals/manually_add_approval.html"): """ Custom admin view to manually add an approval. """ admin_site = model_admin.admin_site opts = model_admin.model._meta app_label = opts.app_label codename = get_permission_codename("add", opts) has_perm = request.user.has_perm(f"{app_label}.{codename}") if not has_perm: raise PermissionDenied queryset = JobApplication.objects.select_related( "job_seeker", "sender", "sender_siae", "sender_prescriber_organization", "to_siae") job_application = get_object_or_404( queryset, pk=job_application_id, state=JobApplicationWorkflow.STATE_ACCEPTED, approval=None, approval_manually_refused_at=None, approval_manually_refused_by=None, approval_number_sent_by_email=False, ) initial = { "start_at": job_application.hiring_start_at, "end_at": Approval.get_default_end_date(job_application.hiring_start_at), "user": job_application.job_seeker.pk, "created_by": request.user.pk, } form = ManuallyAddApprovalForm(initial=initial, data=request.POST or None) fieldsets = [(None, {"fields": list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) if request.method == "POST" and form.is_valid(): with transaction.atomic(): approval = form.save() job_application.approval = approval job_application.manually_deliver_approval( delivered_by=request.user) messages.success( request, f"Le PASS IAE {approval.number_with_spaces} a bien été créé et envoyé par e-mail." ) return HttpResponseRedirect( reverse("admin:approvals_approval_changelist")) context = { "add": True, "adminform": adminForm, "admin_site": admin_site.name, "app_label": app_label, "errors": admin.helpers.AdminErrorList(form, {}), "form": form, "job_application": job_application, "opts": opts, "title": "Ajout manuel d'un numéro d'agrément", **admin_site.each_context(request), } return render(request, template_name, context)
def manually_add_approval( request, model_admin, job_application_id, template_name="admin/approvals/manually_add_approval.html", ): """ Custom admin view to manually add an approval pre-filled with some FK. https://docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-views-to-admin-sites https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html """ opts = model_admin.model._meta app_label = opts.app_label codename = get_permission_codename("add", opts) has_perm = request.user.has_perm(f"{app_label}.{codename}") if not has_perm: raise PermissionDenied queryset = JobApplication.objects.select_related( "job_seeker", "sender", "sender_siae", "sender_prescriber_organization", "to_siae", ) job_application = get_object_or_404( queryset, pk=job_application_id, state=JobApplicationWorkflow.STATE_ACCEPTED, approval=None, ) initial = { "start_at": job_application.hiring_start_at, "end_at": Approval.get_default_end_date(job_application.hiring_start_at), "number": Approval.get_next_number(job_application.hiring_start_at), "user": job_application.job_seeker.pk, "created_by": request.user.pk, } form = ManuallyAddApprovalForm(initial=initial, data=request.POST or None) if request.method == "POST" and form.is_valid(): approval = form.save() job_application.approval = approval job_application.save() job_application.send_approval_number_by_email_manually( deliverer=request.user) messages.success( request, _(f"Le PASS IAE {approval.number_with_spaces} a bien été créé et envoyé par e-mail." ), ) return HttpResponseRedirect( reverse("admin:approvals_approval_changelist")) fieldsets = [(None, {"fields": list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) admin_site = model_admin.admin_site context = { "add": True, "adminform": adminForm, "admin_site": admin_site.name, "app_label": app_label, "errors": admin.helpers.AdminErrorList(form, {}), "form": form, "job_application": job_application, "opts": opts, "title": _("Ajout manuel d'un numéro d'agrément"), **admin_site.each_context(request), } return render(request, template_name, context)
def test_accept_and_update_hiring_start_date_of_two_job_applications(self, *args, **kwargs): create_test_cities(["54", "57"], num_per_department=2) city = City.objects.first() job_seeker = JobSeekerWithAddressFactory() base_for_post_data = { "address_line_1": job_seeker.address_line_1, "post_code": job_seeker.post_code, "city": city.name, "city_slug": city.slug, "pole_emploi_id": job_seeker.pole_emploi_id, "answer": "", } hiring_start_at = timezone.localdate() + relativedelta(months=2) hiring_end_at = hiring_start_at + relativedelta(months=2) approval_default_ending = Approval.get_default_end_date(start_at=hiring_start_at) # Send 3 job applications to 3 different structures job_application = JobApplicationSentByJobSeekerFactory( job_seeker=job_seeker, state=JobApplicationWorkflow.STATE_PROCESSING ) job_app_starting_earlier = JobApplicationSentByJobSeekerFactory( job_seeker=job_seeker, state=JobApplicationWorkflow.STATE_PROCESSING ) job_app_starting_later = JobApplicationSentByJobSeekerFactory( job_seeker=job_seeker, state=JobApplicationWorkflow.STATE_PROCESSING ) # SIAE 1 logs in and accepts the first job application. # The delivered approval should start at the same time as the contract. user = job_application.to_siae.members.first() self.client.login(username=user.email, password=DEFAULT_PASSWORD) url_accept = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), **base_for_post_data, } response = self.client.post(url_accept, data=post_data) self.assertEqual(response.status_code, 302) # First job application has been accepted. # All other job applications are obsolete. job_application.refresh_from_db() self.assertTrue(job_application.state.is_accepted) self.assertEqual(job_application.approval.start_at, job_application.hiring_start_at) self.assertEqual(job_application.approval.end_at, approval_default_ending) self.client.logout() # SIAE 2 accepts the second job application # but its contract starts earlier than the approval delivered the first time. # Approval's starting date should be brought forward. user = job_app_starting_earlier.to_siae.members.first() hiring_start_at = hiring_start_at - relativedelta(months=1) hiring_end_at = hiring_start_at + relativedelta(months=2) approval_default_ending = Approval.get_default_end_date(start_at=hiring_start_at) job_app_starting_earlier.refresh_from_db() self.assertTrue(job_app_starting_earlier.state.is_obsolete) self.client.login(username=user.email, password=DEFAULT_PASSWORD) url_accept = reverse("apply:accept", kwargs={"job_application_id": job_app_starting_earlier.pk}) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), **base_for_post_data, } response = self.client.post(url_accept, data=post_data) job_app_starting_earlier.refresh_from_db() # Second job application has been accepted. # The job seeker has now two part-time jobs at the same time. self.assertEqual(response.status_code, 302) self.assertTrue(job_app_starting_earlier.state.is_accepted) self.assertEqual(job_app_starting_earlier.approval.start_at, job_app_starting_earlier.hiring_start_at) self.assertEqual(job_app_starting_earlier.approval.end_at, approval_default_ending) self.client.logout() # SIAE 3 accepts the third job application. # Its contract starts later than the corresponding approval. # Approval's starting date should not be updated. user = job_app_starting_later.to_siae.members.first() hiring_start_at = hiring_start_at + relativedelta(months=6) hiring_end_at = hiring_start_at + relativedelta(months=2) job_app_starting_later.refresh_from_db() self.assertTrue(job_app_starting_later.state.is_obsolete) self.client.login(username=user.email, password=DEFAULT_PASSWORD) url_accept = reverse("apply:accept", kwargs={"job_application_id": job_app_starting_later.pk}) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), **base_for_post_data, } response = self.client.post(url_accept, data=post_data) job_app_starting_later.refresh_from_db() # Third job application has been accepted. # The job seeker has now three part-time jobs at the same time. self.assertEqual(response.status_code, 302) self.assertTrue(job_app_starting_later.state.is_accepted) self.assertEqual(job_app_starting_later.approval.start_at, job_app_starting_earlier.hiring_start_at)
def test_accept_with_active_suspension(self, *args, **kwargs): """Test the `accept` transition with active suspension for active user""" create_test_cities(["54", "57"], num_per_department=2) city = City.objects.first() today = timezone.localdate() # the old job of job seeker job_seeker_user = JobSeekerWithAddressFactory() old_job_application = JobApplicationWithApprovalFactory( state=JobApplicationWorkflow.STATE_ACCEPTED, job_seeker=job_seeker_user, # Ensure that the old_job_application cannot be canceled. hiring_start_at=today - relativedelta(days=100), ) # create suspension for the job seeker approval_job_seeker = old_job_application.approval siae_user = old_job_application.to_siae.members.first() susension_start_at = today suspension_end_at = today + relativedelta(days=50) SuspensionFactory( approval=approval_job_seeker, start_at=susension_start_at, end_at=suspension_end_at, created_by=siae_user, reason=Suspension.Reason.BROKEN_CONTRACT.value, ) # Now, another Siae wants to hire the job seeker other_siae = SiaeWithMembershipFactory() job_application = JobApplicationSentByJobSeekerFactory( approval=approval_job_seeker, state=JobApplicationWorkflow.STATE_PROCESSING, job_seeker=job_seeker_user, to_siae=other_siae, ) other_siae_user = job_application.to_siae.members.first() # login with other siae self.client.login(username=other_siae_user.email, password=DEFAULT_PASSWORD) url = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) hiring_start_at = today + relativedelta(days=20) hiring_end_at = Approval.get_default_end_date(hiring_start_at) post_data = { # Data for `JobSeekerPoleEmploiStatusForm`. "pole_emploi_id": job_application.job_seeker.pole_emploi_id, # Data for `AcceptForm`. "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "answer": "", "address_line_1": job_seeker_user.address_line_1, "post_code": job_seeker_user.post_code, "city": city.name, "city_slug": city.slug, } response = self.client.post(url, data=post_data) self.assertEqual(response.status_code, 302) get_job_application = JobApplication.objects.get(pk=job_application.pk) g_suspension = get_job_application.approval.suspension_set.in_progress().last() # The end date of suspension is set to d-1 of hiring start day self.assertEqual(g_suspension.end_at, get_job_application.hiring_start_at - relativedelta(days=1)) # Check if the duration of approval was updated correctly self.assertEqual( get_job_application.approval.end_at, approval_job_seeker.end_at + relativedelta(days=(g_suspension.end_at - g_suspension.start_at).days), )
def test_accept(self, *args, **kwargs): """Test the `accept` transition.""" create_test_cities(["54", "57"], num_per_department=2) city = City.objects.first() today = timezone.localdate() job_seeker = JobSeekerWithAddressFactory(city=city.name) address = { "address_line_1": job_seeker.address_line_1, "post_code": job_seeker.post_code, "city": city.name, "city_slug": city.slug, } siae = SiaeWithMembershipFactory() siae_user = siae.members.first() for state in JobApplicationWorkflow.CAN_BE_ACCEPTED_STATES: job_application = JobApplicationSentByJobSeekerFactory(state=state, job_seeker=job_seeker, to_siae=siae) self.client.login(username=siae_user.email, password=DEFAULT_PASSWORD) url = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) # Good duration. hiring_start_at = today hiring_end_at = Approval.get_default_end_date(hiring_start_at) post_data = { # Data for `JobSeekerPoleEmploiStatusForm`. "pole_emploi_id": job_application.job_seeker.pole_emploi_id, # Data for `AcceptForm`. "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "answer": "", **address, } response = self.client.post(url, data=post_data) next_url = reverse("apply:details_for_siae", kwargs={"job_application_id": job_application.pk}) self.assertRedirects(response, next_url) job_application = JobApplication.objects.get(pk=job_application.pk) self.assertEqual(job_application.hiring_start_at, hiring_start_at) self.assertEqual(job_application.hiring_end_at, hiring_end_at) self.assertTrue(job_application.state.is_accepted) ############## # Exceptions # ############## job_application = JobApplicationSentByJobSeekerFactory(state=state, job_seeker=job_seeker, to_siae=siae) url = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) # Wrong dates. hiring_start_at = today hiring_end_at = Approval.get_default_end_date(hiring_start_at) # Force `hiring_start_at` in past. hiring_start_at = hiring_start_at - relativedelta(days=1) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "answer": "", **address, } response = self.client.post(url, data=post_data) self.assertFormError(response, "form_accept", "hiring_start_at", JobApplication.ERROR_START_IN_PAST) # Wrong dates: end < start. hiring_start_at = today hiring_end_at = hiring_start_at - relativedelta(days=1) post_data = { "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "answer": "", **address, } response = self.client.post(url, data=post_data) self.assertFormError(response, "form_accept", None, JobApplication.ERROR_END_IS_BEFORE_START) # No address provided. job_application = JobApplicationSentByJobSeekerFactory( state=JobApplicationWorkflow.STATE_PROCESSING, to_siae=siae ) url = reverse("apply:accept", kwargs={"job_application_id": job_application.pk}) hiring_start_at = today hiring_end_at = Approval.get_default_end_date(hiring_start_at) post_data = { # Data for `JobSeekerPoleEmploiStatusForm`. "pole_emploi_id": job_application.job_seeker.pole_emploi_id, # Data for `AcceptForm`. "hiring_start_at": hiring_start_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "hiring_end_at": hiring_end_at.strftime(DuetDatePickerWidget.INPUT_DATE_FORMAT), "answer": "", } response = self.client.post(url, data=post_data) self.assertFormError(response, "form_user_address", "address_line_1", "Ce champ est obligatoire.") self.assertFormError(response, "form_user_address", "city", "Ce champ est obligatoire.") self.assertFormError(response, "form_user_address", "post_code", "Ce champ est obligatoire.")
def pe_approval_create(request, pe_approval_id): """ Final step of the PoleEmploiApproval's conversion process. Create a Approval and a JobApplication out of a (previously created) User and a PoleEmploiApproval. """ siae = get_current_siae_or_404(request) pe_approval = get_object_or_404(PoleEmploiApproval, pk=pe_approval_id) form = UserExistsForm(data=request.POST or None) if request.method != "POST" or not form.is_valid(): next_url = reverse("approvals:pe_approval_search_user", kwargs={"pe_approval_id": pe_approval_id}) return HttpResponseRedirect(next_url) # If there already is a user with this email, we take it, otherwise we create one email = form.cleaned_data["email"] job_seeker = User.objects.filter(email__iexact=email).first() if not job_seeker: job_seeker = User.create_job_seeker_from_pole_emploi_approval(request.user, email, pe_approval) # If the PoleEmploiApproval has already been imported, it is not possible to import it again. possible_matching_approval = Approval.objects.filter(number=pe_approval.number[:12]).order_by("-start_at").first() if possible_matching_approval: messages.info(request, "Cet agrément a déjà été importé.") job_application = JobApplication.objects.filter(approval=possible_matching_approval).first() next_url = reverse("apply:details_for_siae", kwargs={"job_application_id": job_application.id}) return HttpResponseRedirect(next_url) # It is not possible to attach an approval to a job seeker that already has a valid approval. if job_seeker.approvals_wrapper.has_valid and job_seeker.approvals_wrapper.latest_approval.is_pass_iae: messages.error(request, "Le candidat associé à cette adresse e-mail a déjà un PASS IAE valide.") next_url = reverse("approvals:pe_approval_search_user", kwargs={"pe_approval_id": pe_approval_id}) return HttpResponseRedirect(next_url) with transaction.atomic(): # Then we create an Approval based on the PoleEmploiApproval data approval_from_pe = Approval( start_at=pe_approval.start_at, end_at=pe_approval.end_at, user=job_seeker, # Only store 12 chars numbers. number=pe_approval.number[:12], ) approval_from_pe.save() # Then we create the necessary JobApplication for redirection job_application = JobApplication( job_seeker=job_seeker, to_siae=siae, state=JobApplicationWorkflow.STATE_ACCEPTED, approval=approval_from_pe, created_from_pe_approval=True, # This flag is specific to this process. sender=request.user, sender_kind=JobApplication.SENDER_KIND_SIAE_STAFF, sender_siae=siae, ) job_application.save() messages.success(request, "L'agrément a bien été importé, vous pouvez désormais le prolonger ou le suspendre.") next_url = reverse("apply:details_for_siae", kwargs={"job_application_id": job_application.id}) return HttpResponseRedirect(next_url)
def accept(self, *args, **kwargs): accepted_by = kwargs.get("user") # Mark other related job applications as obsolete. for job_application in self.job_seeker.job_applications.exclude( pk=self.pk).pending(): job_application.render_obsolete(*args, **kwargs) # Notification emails. emails = [self.email_accept_for_job_seeker] if self.is_sent_by_proxy: emails.append(self.email_accept_for_proxy) # Approval issuance logic. if not self.hiring_without_approval and self.to_siae.is_subject_to_eligibility_rules: approvals_wrapper = self.job_seeker.approvals_wrapper if approvals_wrapper.has_in_waiting_period: if approvals_wrapper.cannot_bypass_waiting_period( siae=self.to_siae, sender_prescriber_organization=self. sender_prescriber_organization): # Security check: it's supposed to be blocked upstream. raise xwf_models.AbortTransition( "Job seeker has an approval in waiting period.") if approvals_wrapper.has_valid: # Automatically reuse an existing valid Itou or PE approval. self.approval = Approval.get_or_create_from_valid( approvals_wrapper) if self.approval.start_at > self.hiring_start_at: # As a job seeker can have multiple contracts at the same time, # the approval should start at the same time as most recent contract. self.approval.update_start_date( new_start_date=self.hiring_start_at) emails.append(self.email_deliver_approval(accepted_by)) elif (self.job_seeker.pole_emploi_id or self.job_seeker.lack_of_pole_emploi_id_reason == self.job_seeker.REASON_NOT_REGISTERED): # Automatically create a new approval. new_approval = Approval( start_at=self.hiring_start_at, end_at=Approval.get_default_end_date(self.hiring_start_at), user=self.job_seeker, ) new_approval.save() self.approval = new_approval emails.append(self.email_deliver_approval(accepted_by)) elif self.job_seeker.lack_of_pole_emploi_id_reason == self.job_seeker.REASON_FORGOTTEN: # Trigger a manual approval creation. self.approval_delivery_mode = self.APPROVAL_DELIVERY_MODE_MANUAL emails.append( self.email_manual_approval_delivery_required_notification( accepted_by)) else: raise xwf_models.AbortTransition( "Job seeker has an invalid PE status, cannot issue approval." ) # Link to the job seeker's eligibility diagnosis. if self.to_siae.is_subject_to_eligibility_rules: self.eligibility_diagnosis = EligibilityDiagnosis.objects.last_considered_valid( self.job_seeker, for_siae=self.to_siae) if not self.eligibility_diagnosis and self.approval and self.approval.originates_from_itou: logger.error( "An eligibility diagnosis should've been found for job application %s", self.pk) # Send emails in batch. connection = mail.get_connection() connection.send_messages(emails) if self.approval: self.approval_number_sent_by_email = True self.approval_number_sent_at = timezone.now() self.approval_delivery_mode = self.APPROVAL_DELIVERY_MODE_AUTOMATIC self.approval.unsuspend(self.hiring_start_at) self.notify_pole_emploi_accepted()