예제 #1
0
    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()
예제 #2
0
파일: tests.py 프로젝트: NathHense/itou
    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)
예제 #3
0
    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
예제 #4
0
    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)
예제 #5
0
    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
예제 #6
0
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))
예제 #7
0
파일: tests.py 프로젝트: ikarius/itou
    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()
예제 #8
0
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)
예제 #9
0
파일: admin_views.py 프로젝트: ikarius/itou
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)
예제 #10
0
    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)
예제 #11
0
    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),
        )
예제 #12
0
    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.")
예제 #13
0
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)
예제 #14
0
    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()