From 9186d2abdbae9592f66779e5ad7652c1d581f7c5 Mon Sep 17 00:00:00 2001 From: Xavier Fernandez Date: Mon, 9 Sep 2024 17:17:19 +0200 Subject: [PATCH] www.approvals_views: add new detail view --- itou/templates/approvals/includes/box.html | 4 + itou/templates/approvals/new_details.html | 283 +++++++++++++++++++++ itou/www/approvals_views/urls.py | 1 + itou/www/approvals_views/views.py | 112 +++++++- 4 files changed, 397 insertions(+), 3 deletions(-) create mode 100644 itou/templates/approvals/new_details.html diff --git a/itou/templates/approvals/includes/box.html b/itou/templates/approvals/includes/box.html index 11e37a3f485..c145cb46c3a 100644 --- a/itou/templates/approvals/includes/box.html +++ b/itou/templates/approvals/includes/box.html @@ -68,4 +68,8 @@ {% endif %} {% endwith %} {% endif %} + + + Afficher le PASS IAE + diff --git a/itou/templates/approvals/new_details.html b/itou/templates/approvals/new_details.html new file mode 100644 index 00000000000..41638faae93 --- /dev/null +++ b/itou/templates/approvals/new_details.html @@ -0,0 +1,283 @@ +{% extends "layout/base.html" %} +{% load matomo %} +{% load static %} +{% load str_filters %} +{% load format_filters %} + +{% block title %} + PASS IAE de {{ approval.user.get_full_name|mask_unless:can_view_personal_information }} {{ block.super }} +{% endblock %} + +{% block title_content %} +
+

PASS IAE de {{ approval.user.get_full_name|mask_unless:can_view_personal_information }}

+
+ {% if approval_deletion_form_url %} + + + Clôturer ce PASS IAE + + {% endif %} + {% if request.user.is_employer %} + + + Afficher l’attestation + + {% endif %} +
+
+{% endblock %} + +{% block title_prevstep %} + {% include "layout/previous_step.html" with back_url=back_url only %} +{% endblock %} + +{% block content %} +
+
+
+
+ {# Should we try to reuse "approvals/includes/box.html" ? #} +
+
+ + + PASS IAE {{ approval.get_state_display.lower }} + +
+
    +
  • + Numéro de PASS IAE + {{ approval.number|format_approval_number }} +
  • + {% if approval.state == "EXPIRED" %} +
  • + A expiré le + {{ approval.end_at|date:"d/m/Y" }} +
  • + {% else %} +
  • + Date de début + {{ approval.start_at|date:"d/m/Y" }} +
  • +
  • + Durée de validité + {{ approval.get_remainder_display }} +
  • +
  • + Date de fin prévisionnelle + {{ approval.remainder_as_date|date:"d/m/Y" }} +
  • + {% endif %} +
+ {% if approval.origin == ApprovalOrigin.PE_APPROVAL %} +

Ce PASS IAE a été importé depuis un agrément Pôle emploi.

+ {% endif %} +
+
+
+
+
+
+
+

Suspensions

+
+ {% if can_be_suspended_by_current_user %} + + + Suspendre + + {% elif request.user.is_employer %} + + {% endif %} +
+
+ {% if suspensions %} +
+ + + + + + + + + + + + + {% for suspension in suspensions %} + + + + + + + + + {% endfor %} + +
Liste des suspensions
StatutÉmetteurDuJusqu’auMotif +
+ {% if suspension.is_in_progress %} + En cours + {% else %} + Passée + {% endif %} + + {% if suspension.siae %} + {{ suspension.siae.display_name }} + {% else %} + - + {% endif %} + {{ suspension.start_at|date:"d/m/Y" }}{{ suspension.end_at|date:"d/m/Y" }}{{ suspension.get_reason_display }} + {% if suspension.can_be_handled_by_current_user %} + + + + Modifier la suspension + + + + + + {% endif %} +
+
+ {% else %} +

Aucune suspension enregistrée pour l’instant.

+ {% endif %} +
+ +
+

+ Retrouvez toutes les informations sur le fonctionnement des suspensions sur notre + + documentation + + . +

+
+
+
+ +
+
+
+
+
+
+

Prolongations

+
+ {% if can_be_prolonged_by_current_user %} + + + Prolonger + + {% elif request.user.is_employer %} + + {% endif %} +
+
+ {% if prolongations %} +
+ + + + + + + + + + + + + {% for prolongation in prolongations %} + + + + + + + + {% endfor %} + +
Liste des prolongations
StatutDuJusqu’auMotifValidée par
+ {% if prolongation.status %} + {# Prolongation request #} + {% if prolongation.status == "DENIED" %} + Refusée + {% elif prolongation.status == "PENDING" %} + En attente + {% endif %} + {% else %} + Acceptée + {% endif %} + {{ prolongation.start_at|date:"d/m/Y" }}{{ prolongation.end_at|date:"d/m/Y" }}{{ prolongation.get_reason_display }} + {% if prolongation.validated_by %} + {{ prolongation.validated_by }} + {% else %} + - + {% endif %} +
+
+ {% else %} +

Aucune prolongation enregistrée pour l’instant.

+ {% endif %} +
+ +
+

+ Retrouvez toutes les informations sur le fonctionnement des prolongations sur notre + + documentation + + . +

+
+
+
+
+
+
+
+{% endblock %} diff --git a/itou/www/approvals_views/urls.py b/itou/www/approvals_views/urls.py index a4d8f07971f..08233042aea 100644 --- a/itou/www/approvals_views/urls.py +++ b/itou/www/approvals_views/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ # PASS IAE path("detail/", views.approval_detail_redirect_to_employee_view, name="redirect_to_employee"), + path("details/", views.NewApprovalDetailView.as_view(), name="details"), path("display/", views.ApprovalPrintableDisplay.as_view(), name="display_printable_approval"), path("list", views.ApprovalListView.as_view(), name="list"), path("declare_prolongation/", views.declare_prolongation, name="declare_prolongation"), diff --git a/itou/www/approvals_views/views.py b/itou/www/approvals_views/views.py index b523a63f8c1..2223d6a29f4 100644 --- a/itou/www/approvals_views/views.py +++ b/itou/www/approvals_views/views.py @@ -1,25 +1,28 @@ import logging import pathlib +import urllib.parse from datetime import timedelta from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.core.exceptions import PermissionDenied from django.core.files.storage import default_storage from django.db import IntegrityError +from django.db.models import Prefetch from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse +from django.urls import reverse, reverse_lazy from django.utils import timezone from django.views import View from django.views.decorators.http import require_safe -from django.views.generic import ListView, TemplateView +from django.views.generic import DetailView, ListView, TemplateView from formtools.wizard.views import NamedUrlSessionWizardView from itou.approvals import enums as approvals_enums from itou.approvals.constants import PROLONGATION_REPORT_FILE_REASONS from itou.approvals.models import ( + SUSPENSION_DURATION_BEFORE_APPROVAL_DELETABLE, Approval, ProlongationRequest, ProlongationRequestDenyInformation, @@ -117,6 +120,109 @@ def get_context_data(self, **kwargs): return context +class NewApprovalDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView): + model = Approval + queryset = Approval.objects.select_related("user__jobseeker_profile").prefetch_related( + "suspension_set", + Prefetch( + "prolongationrequest_set", + queryset=ProlongationRequest.objects.select_related( + "declared_by", "validated_by", "processed_by", "prescriber_organization" + ), + ), + ) + template_name = "approvals/new_details.html" + + def test_func(self): + # TODO: make sure only relevant employer & prescriber have access to this page + return self.request.user.is_authenticated and ( + self.request.user.is_prescriber or self.request.user.is_employer + ) + + def get_prolongation_and_requests(self, approval): + prolongations = [] + seen_ids = set() + for prolongation in approval.prolongation_set.order_by("-start_at"): + validated_by_parts = [] + if validation_user := prolongation.validated_by: + validated_by_parts.append(validation_user.get_full_name()) + if validation_org := prolongation.prescriber_organization: + validated_by_parts.append(validation_org.display_name) + prolongation.validated_by_for_template = " - ".join(validated_by_parts) + prolongations.append(prolongation) + seen_ids.add(prolongation.pk) + + for prolongation_request in approval.prolongationrequest_set.all(): + if prolongation_request.prolongation_id in seen_ids: + continue + prolongation_request.validated_by_for_template = None # Not validated yet + prolongations.append(prolongation_request) + return sorted(prolongations, key=lambda p: p.start_at, reverse=True) + + def get_suspensions(self, approval): + suspensions = sorted(approval.suspension_set.all(), key=lambda s: s.start_at, reverse=True) + for suspension in suspensions: + if suspension.is_in_progress and self.request.user.is_employer: + suspension.can_be_handled_by_current_user = suspension.can_be_handled_by_siae( + self.request.current_organization + ) + else: + suspension.can_be_handled_by_current_user = False + return suspensions + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + approval = self.object + + context["can_view_personal_information"] = self.request.user.can_view_personal_information(approval.user) + context["matomo_custom_title"] = "Détail PASS IAE" + context["approval_deletion_form_url"] = None + context["back_url"] = get_safe_url(self.request, "back_url", fallback_url=reverse_lazy("approvals:list")) + context["suspensions"] = self.get_suspensions(approval) + context["prolongations"] = self.get_prolongation_and_requests(approval) + context["can_be_suspended_by_current_user"] = ( + self.request.user.is_employer and approval.can_be_suspended_by_siae(self.request.current_organization) + ) + context["can_be_prolonged_by_current_user"] = ( + self.request.user.is_employer + and self.request.current_organization.is_subject_to_eligibility_rules + and approval.can_be_prolonged + ) + + if approval.is_in_progress: + # suspension_set has already been loaded via prefetch_related for the remainder computation + for suspension in sorted(approval.suspension_set.all(), key=lambda s: s.start_at): + if suspension.is_in_progress: + suspension_duration = timezone.localdate() - suspension.start_at + has_hirings_after_suspension = False + else: + suspension_duration = suspension.duration + has_hirings_after_suspension = ( + approval.jobapplication_set.accepted().filter(hiring_start_at__gte=suspension.end_at).exists() + ) + + if ( + suspension_duration > SUSPENSION_DURATION_BEFORE_APPROVAL_DELETABLE + and not has_hirings_after_suspension + ): + context["approval_deletion_form_url"] = "https://tally.so/r/3je84Q?" + urllib.parse.urlencode( + { + "siaeID": self.siae.pk, + "nomSIAE": self.siae.display_name, + "prenomemployeur": self.request.user.first_name, + "nomemployeur": self.request.user.last_name, + "emailemployeur": self.request.user.email, + "userID": self.request.user.pk, + "numPASS": approval.number_with_spaces, + "prenomsalarie": approval.user.first_name, + "nomsalarie": approval.user.last_name, + } + ) + break + + return context + + class ApprovalPrintableDisplay(ApprovalBaseViewMixin, TemplateView): template_name = "approvals/printable_approval.html"