Merge pull request #10190 from demarches-simplifiees/add-AR-feature-ldu

ETQ Admin je peux configurer une démarche avec accusé de lecture
This commit is contained in:
Lisa Durand 2024-04-16 09:58:13 +00:00 committed by GitHub
commit 7a80574afc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 491 additions and 96 deletions

View file

@ -0,0 +1,5 @@
class Dossiers::AccuseLectureComponent < ApplicationComponent
def initialize(dossier:)
@dossier = dossier
end
end

View file

@ -0,0 +1,4 @@
---
en:
text_accuse_lecture: This procedure is subject to a reading acknowledgment. By requesting the display of the decision taken on your file, you accept the reading acknowledgment and thereby the triggering of the legal deadline in the event of an appeal.
btn_accuse_lecture: I would like to display the decision

View file

@ -0,0 +1,4 @@
---
fr:
text_accuse_lecture: Cette démarche est soumise à un accusé de lecture. En demandant laffichage de la décision prise sur votre dossier, vous acceptez laccusé de lecture et par là même le démarrage du délai légal en cas de recours.
btn_accuse_lecture: Je souhaite afficher la décision

View file

@ -0,0 +1,10 @@
= render Dsfr::CalloutComponent.new(title: nil) do |c|
- c.with_body do
= t('.text_accuse_lecture')
= form_for @dossier,
method: :get,
url: set_accuse_lecture_agreement_at_dossier_path(@dossier),
data: { controller: 'autosubmit', turbo: 'true' } do |f|
= f.submit t('.btn_accuse_lecture'), class: "fr-btn fr-mt-2w"

View file

@ -0,0 +1,5 @@
class Procedure::Card::AccuseLectureComponent < ApplicationComponent
def initialize(procedure:)
@procedure = procedure
end
end

View file

@ -0,0 +1,4 @@
---
fr:
title: Accusé de lecture
subtitle: Pour les démarches avec voies de recours

View file

@ -0,0 +1,11 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to accuse_lecture_admin_procedure_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.column.align-center.justify-between
- if @procedure.accuse_lecture.present?
%p.fr-badge.fr-badge--success Activé
- else
%p.fr-badge.fr-badge--info Désactivé
%div
%h3.fr-h6.fr-mt-10v= t('.title')
%p.fr-tile-subtitle= t('.subtitle')
%p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit')

View file

@ -3,7 +3,7 @@ module Administrateurs
layout 'all', only: [:all, :administrateurs]
respond_to :html, :xlsx
before_action :retrieve_procedure, only: [:champs, :annotations, :modifications, :edit, :zones, :monavis, :update_monavis, :jeton, :update_jeton, :publication, :publish, :transfert, :close, :confirmation, :allow_expert_review, :allow_expert_messaging, :experts_require_administrateur_invitation, :reset_draft, :publish_revision, :check_path]
before_action :retrieve_procedure, only: [:champs, :annotations, :modifications, :edit, :zones, :monavis, :update_monavis, :accuse_lecture, :update_accuse_lecture, :jeton, :update_jeton, :publication, :publish, :transfert, :close, :confirmation, :allow_expert_review, :allow_expert_messaging, :experts_require_administrateur_invitation, :reset_draft, :publish_revision, :check_path]
before_action :draft_valid?, only: [:apercu]
after_action :reset_procedure, only: [:update]
@ -276,6 +276,13 @@ module Administrateurs
render 'monavis'
end
def accuse_lecture
end
def update_accuse_lecture
@procedure.update!(procedure_params)
end
def jeton
end
@ -515,6 +522,7 @@ module Administrateurs
:logo,
:auto_archive_on,
:monavis_embed,
:accuse_lecture,
:api_entreprise_token,
:duree_conservation_dossiers_dans_ds,
{ zone_ids: [] },

View file

@ -124,6 +124,13 @@ module Users
@dossier = dossier
end
def set_accuse_lecture_agreement_at
@dossier = dossier
@dossier.update!(accuse_lecture_agreement_at: Time.zone.now)
flash.notice = 'Accusé de lecture accepté'
redirect_back(fallback_location: demande_dossier_path(@dossier))
end
def identite
@dossier = dossier
@user = current_user

View file

@ -78,6 +78,14 @@ module DossierHelper
end
end
def status_badge_user(dossier, alignment_class = '')
if dossier.hide_info_with_accuse_lecture?
tag.span 'traité', role: 'status', class: "fr-badge fr-badge--sm fr-badge--no-icon #{alignment_class}"
else
status_badge(dossier.state, alignment_class)
end
end
def status_badge(state, alignment_class = '')
status_text = dossier_display_state(state, lower: true)
tag.span status_text, role: 'status', class: class_names(

View file

@ -6,7 +6,7 @@
# The subject and body of a Notification can be customized by each demarche.
#
class NotificationMailer < ApplicationMailer
before_action :set_dossier, except: [:send_notification_for_tiers]
before_action :set_dossier, except: [:send_notification_for_tiers, :send_accuse_lecture_notification]
before_action :set_services_publics_plus, only: :send_notification
helper ServiceHelper
@ -42,6 +42,16 @@ class NotificationMailer < ApplicationMailer
mail(subject: @subject, to: @email, template_name: 'send_notification_for_tiers')
end
def send_accuse_lecture_notification(dossier)
@dossier = dossier
@subject = "La décision a été rendue pour votre démarche #{@dossier.procedure.libelle.truncate_words(50)}"
@email = @dossier.user_email_for(:notification)
@logo_url = procedure_logo_url(@dossier.procedure)
mail(subject: @subject, to: @email, template_name: 'send_accuse_lecture_notification')
end
def self.send_en_construction_notification(dossier)
with(dossier: dossier, state: Dossier.states.fetch(:en_construction)).send_notification
end

View file

@ -120,7 +120,11 @@ module DossierStateConcern
disable_notification = h.fetch(:disable_notification, false)
if !disable_notification
NotificationMailer.send_accepte_notification(self).deliver_later
if procedure.accuse_lecture?
NotificationMailer.send_accuse_lecture_notification(self).deliver_later
else
NotificationMailer.send_accepte_notification(self).deliver_later
end
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
end
@ -152,7 +156,11 @@ module DossierStateConcern
end
def after_commit_accepter_automatiquement
NotificationMailer.send_accepte_notification(self).deliver_later
if procedure.accuse_lecture?
NotificationMailer.send_accuse_lecture_notification(self).deliver_later
else
NotificationMailer.send_accepte_notification(self).deliver_later
end
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
send_dossier_decision_to_experts(self)
@ -184,7 +192,11 @@ module DossierStateConcern
disable_notification = h.fetch(:disable_notification, false)
if !disable_notification
NotificationMailer.send_refuse_notification(self).deliver_later
if procedure.accuse_lecture?
NotificationMailer.send_accuse_lecture_notification(self).deliver_later
else
NotificationMailer.send_refuse_notification(self).deliver_later
end
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
end
@ -209,7 +221,11 @@ module DossierStateConcern
end
def after_commit_refuser_automatiquement
NotificationMailer.send_refuse_notification(self).deliver_later
if procedure.accuse_lecture?
NotificationMailer.send_accuse_lecture_notification(self).deliver_later
else
NotificationMailer.send_refuse_notification(self).deliver_later
end
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
send_dossier_decision_to_experts(self)
@ -241,7 +257,11 @@ module DossierStateConcern
disable_notification = h.fetch(:disable_notification, false)
if !disable_notification
NotificationMailer.send_sans_suite_notification(self).deliver_later
if procedure.accuse_lecture?
NotificationMailer.send_accuse_lecture_notification(self).deliver_later
else
NotificationMailer.send_sans_suite_notification(self).deliver_later
end
NotificationMailer.send_notification_for_tiers(self).deliver_later if self.for_tiers?
end

View file

@ -1180,6 +1180,14 @@ class Dossier < ApplicationRecord
end
end
def hide_info_with_accuse_lecture?
procedure.accuse_lecture? && termine? && accuse_lecture_agreement_at.blank?
end
def termine_and_accuse_lecture?
procedure.accuse_lecture? && termine?
end
private
def champs_by_public_id

View file

@ -23,6 +23,7 @@ class DossiersFilter
dossiers_result = dossiers
dossiers_result = dossiers_result.where(state: state) if state.present? && state != Dossier::A_CORRIGER
dossiers_result = dossiers_result.with_pending_corrections if state.present? && state == Dossier::A_CORRIGER
dossiers_result = exclude_accuse_lecture(dossiers_result) if state.present? && Dossier::TERMINE.include?(state)
dossiers_result = dossiers_result.where('dossiers.created_at >= ?', from_created_at_date) if from_created_at_date.present?
dossiers_result = dossiers_result.where('dossiers.depose_at >= ?', from_depose_at_date) if from_depose_at_date.present?
dossiers_result
@ -47,4 +48,8 @@ class DossiersFilter
rescue Date::Error
nil
end
def exclude_accuse_lecture(dossiers)
dossiers.joins(:procedure).where.not('dossiers.accuse_lecture_agreement_at IS NULL AND procedures.accuse_lecture = TRUE ')
end
end

View file

@ -3,9 +3,13 @@ class MailTemplatePresenterService
include ActionView::Helpers::TextHelper
def self.create_commentaire_for_state(dossier, state)
service = new(dossier, state)
body = ["<p>[#{service.safe_subject}]</p>", service.safe_body].join('')
CommentaireService.create!(CONTACT_EMAIL, dossier, body: body)
if dossier.procedure.accuse_lecture? && Dossier::TERMINE.include?(state)
CommentaireService.create!(CONTACT_EMAIL, dossier, body: I18n.t('layouts.mailers.accuse_lecture.commentaire_html', service: dossier.procedure.service.nom))
else
service = new(dossier, state)
body = ["<p>[#{service.safe_subject}]</p>", service.safe_body].join('')
CommentaireService.create!(CONTACT_EMAIL, dossier, body: body)
end
end
def safe_body

View file

@ -0,0 +1,43 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [['Démarches', admin_procedures_back_path(@procedure)],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
['Accusé de lecture']] }
.fr-container
.fr-grid-row
.fr-col-12.fr-col-offset-md-2.fr-col-md-8
%h1.page-title
Accusé de lecture
= render Dsfr::CalloutComponent.new(title: nil) do |c|
- c.with_body do
%p
Pour les démarches avec voies de recours, il est possible dactiver laccusé de lecture.
%br
%p
Cette fonctionnalité permet à linstructeur de connaître la date de lecture de la décision finale par lusager.
%br
%p
Lusager na plus accès à la décision finale par mail, mais il doit se connecter sur la plateforme #{Current.application_name} pour en prendre connaissance et en accuser lecture.
%ul.fr-toggle__list
%li
= form_for @procedure,
method: :patch,
url: update_accuse_lecture_admin_procedure_path(@procedure),
data: { controller: 'autosubmit', turbo: 'true' } do |f|
= render Dsfr::ToggleComponent.new(form: f,
target: :accuse_lecture,
title: "Accusé de lecture de la démarche",
hint: "Laccusé de lecture est à activer uniquement pour les démarches avec voies de recours car il complexifie laccès à la décision finale pour les usagers",
opt: {"checked" => @procedure.accuse_lecture})
.padded-fixed-footer
.fixed-footer
.fr-container
.fr-grid-row
.fr-col-12.fr-col-offset-md-2.fr-col-md-8
%ul.fr-btns-group.fr-btns-group--inline-md
%li
= link_to 'Enregistrer et revenir à la page de suivi', admin_procedure_path(id: @procedure), class: 'fr-btn'

View file

@ -89,3 +89,4 @@
= render Procedure::Card::MonAvisComponent.new(procedure: @procedure)
= render Procedure::Card::DossierSubmittedMessageComponent.new(procedure: @procedure)
= render Procedure::Card::ChorusComponent.new(procedure: @procedure)
= render Procedure::Card::AccuseLectureComponent.new(procedure: @procedure)

View file

@ -0,0 +1,23 @@
- content_for :procedure_logo do
= render 'layouts/mailers/logo', url: @logo_url
%p
= t("layouts.mailers.accuse_lecture.good_morning")
%p
= t("layouts.mailers.accuse_lecture.first_part",
dossier_id: number_with_delimiter(@dossier.id))
%span{ :style => "font-weight: bold;" }
= @dossier.procedure.libelle
= t("layouts.mailers.accuse_lecture.second_part")
%p
= t("layouts.mailers.accuse_lecture.third_part")
= link_to APPLICATION_NAME, dossier_url(@dossier)
%p
= t(:best_regards, scope: [:views, :shared, :greetings])
%br
= t('layouts.mailers.signature.team')
= APPLICATION_NAME

View file

@ -15,8 +15,10 @@
- if @repasser_en_instruction
= t("layouts.mailers.for_tiers.repasser_en_instruction")
- elsif @dossier.hide_info_with_accuse_lecture?
= t("layouts.mailers.for_tiers.accuse_lecture", processed_at: l(@dossier.updated_at.to_date))
- else
= t("layouts.mailers.for_tiers.#{@dossier.state}", processed_at: l(@dossier.updated_at.to_date) )
= t("layouts.mailers.for_tiers.#{@dossier.state}", processed_at: l(@dossier.updated_at.to_date))
%p
= t("layouts.mailers.for_tiers.second_part")

View file

@ -5,11 +5,20 @@
.fr-container.counter-start-header-section.dossier-show{ class: class_names("dossier-show-instructeur" => profile =="instructeur") }
.fr-grid-row.fr-grid-row--center
.fr-col-12.fr-col-xl-8
- if profile == 'instructeur' && dossier.termine_and_accuse_lecture?
= render Dsfr::CalloutComponent.new(title: nil) do |c|
- c.with_html_body do
= t('views.shared.dossiers.demande.accuse_lecture')
- if dossier.accuse_lecture_agreement_at.present?
= t('views.shared.dossiers.demande.accuse_lecture_with_agreement', agreement: l(dossier.accuse_lecture_agreement_at, format: :long))
- else
= t('views.shared.dossiers.demande.accuse_lecture_without_agreement')
%h2.fr-h6.fr-background-alt--grey.fr-mb-0
.flex-grow.fr-py-3v.fr-px-2w= t('views.shared.dossiers.demande.en_construction')
- if dossier.depose_at.present?
= render partial: "shared/dossiers/infos_generales", locals: { dossier: dossier }
= render partial: "shared/dossiers/infos_generales", locals: { dossier: dossier, profile: profile }
- if dossier.for_tiers?

View file

@ -1,6 +1,6 @@
%h1
= procedure_libelle(dossier.procedure)
= status_badge(dossier.state, 'super')
= status_badge_user(dossier, 'super')
%h2
= t('views.users.dossiers.show.header.dossier_number', dossier_id: dossier.id)
= t('views.users.dossiers.show.header.created_date', date_du_dossier: I18n.l(dossier.created_at))

View file

@ -9,18 +9,22 @@
.fr-highlight
%p.fr-text--sm.fr-text-mention--grey Sauf mention contraire, les champs ont été saisis à la date du dépôt du dossier.
- if dossier.justificatif_motivation.attached?
= render Dossiers::RowShowComponent.new(label: "Justificatif") do |c|
- c.with_value do
.action
= render Attachment::ShowComponent.new(attachment: dossier.justificatif_motivation.attachment)
- if profile == 'usager' && dossier.hide_info_with_accuse_lecture?
= render Dossiers::AccuseLectureComponent.new(dossier: dossier)
- if dossier.motivation.present?
= render Dossiers::RowShowComponent.new(label: "Motivation") do |c|
- c.with_value do
= simple_format dossier.motivation
- else
- if dossier.justificatif_motivation.attached?
= render Dossiers::RowShowComponent.new(label: "Justificatif") do |c|
- c.with_value do
.action
= render Attachment::ShowComponent.new(attachment: dossier.justificatif_motivation.attachment)
- if dossier.attestation.present? && dossier.attestation.pdf.attached?
= render Dossiers::RowShowComponent.new(label: "Attestation") do |c|
- c.with_value do
= render Dsfr::DownloadComponent.new(attachment: dossier.attestation.pdf, name: t(:download_attestation, scope: [:views, :shared, :dossiers, :form]))
- if dossier.motivation.present?
= render Dossiers::RowShowComponent.new(label: "Motivation") do |c|
- c.with_value do
= simple_format dossier.motivation
- if dossier.attestation.present? && dossier.attestation.pdf.attached?
= render Dossiers::RowShowComponent.new(label: "Attestation") do |c|
- c.with_value do
= render Dsfr::DownloadComponent.new(attachment: dossier.attestation.pdf, name: t(:download_attestation, scope: [:views, :shared, :dossiers, :form]))

View file

@ -44,7 +44,7 @@
%span.fr-badge.fr-badge--sm.fr-badge--warning
= t('views.users.dossiers.dossiers_list.deleted_badge')
- else
= status_badge(dossier.state, 'fr-mb-1w')
= status_badge_user(dossier, 'fr-mb-1w')
- if dossier.pending_correction?
%br

View file

@ -2,7 +2,7 @@
.fr-container
%h1
= dossier.procedure.libelle
= status_badge(dossier.state, 'super')
= status_badge_user(dossier, 'super')
= pending_correction_badge(:for_user) if dossier.pending_correction?
%h2
= t('views.users.dossiers.show.header.dossier_number', dossier_id: dossier.id)

View file

@ -54,46 +54,50 @@
%p
= t('views.users.dossiers.show.status_overview.use_mailbox_for_questions_html', mailbox_url: messagerie_dossier_url(dossier))
- elsif dossier.accepte?
.accepte
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-checkbox-circle-fill fr-text-default--success')
= t('views.users.dossiers.show.status_overview.acceptee_html')
- elsif dossier.termine?
- if dossier.hide_info_with_accuse_lecture?
= render Dossiers::AccuseLectureComponent.new(dossier: dossier)
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.accepte_motivation')
%blockquote= simple_format(dossier.motivation)
- elsif dossier.accepte?
.accepte
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-checkbox-circle-fill fr-text-default--success')
= t('views.users.dossiers.show.status_overview.acceptee_html')
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.accepte_motivation')
%blockquote= simple_format(dossier.motivation)
- if dossier.attestation.present?
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
.action
= link_to attestation_dossier_path(dossier), class: "fr-btn fr-icon-download-line fr-btn--icon-left", **external_link_attributes do
= t('views.users.dossiers.show.status_overview.accepte_attestation')
- elsif dossier.refuse?
.refuse
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-close-circle-fill fr-text-default--error')
= t('views.users.dossiers.show.status_overview.refuse_html')
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.refuse_motivation')
%blockquote= simple_format(dossier.motivation)
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
.action
= link_to attestation_dossier_path(dossier), class: "fr-btn fr-icon-download-line fr-btn--icon-left", target: '_blank', rel: 'noopener' do
= t('views.users.dossiers.show.status_overview.accepte_attestation')
= link_to t('views.users.dossiers.show.status_overview.refuse_reply'), messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'fr-link'
- elsif dossier.sans_suite?
.sans-suite
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-intermediate-circle-fill')
= t('views.users.dossiers.show.status_overview.sans_suite_html')
- elsif dossier.refuse?
.refuse
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-close-circle-fill fr-text-default--error')
= t('views.users.dossiers.show.status_overview.refuse_html')
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.refuse_motivation')
%blockquote= simple_format(dossier.motivation)
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
.action
= link_to t('views.users.dossiers.show.status_overview.refuse_reply'), messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'fr-link'
- elsif dossier.sans_suite?
.sans-suite
%p.decision{ role: 'status' }
= dsfr_icon('fr-icon-intermediate-circle-fill')
= t('views.users.dossiers.show.status_overview.sans_suite_html')
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.sans_suite_motivation')
%blockquote= simple_format(dossier.motivation)
- if dossier.motivation.present?
%h3= t('views.users.dossiers.show.status_overview.sans_suite_motivation')
%blockquote= simple_format(dossier.motivation)

View file

@ -362,6 +362,9 @@ en:
form: "Form"
edit_siret: "Edit SIRET"
edit_identity: "Edit identity data"
accuse_lecture: This procedure is subject to a reading receipt.
accuse_lecture_with_agreement: The user has read the decision taken on his file on %{agreement}.
accuse_lecture_without_agreement: The user has not yet learned of the decision on their file.
gestionnaires:
groupe_gestionnaires:
delete: Delete

View file

@ -365,6 +365,9 @@ fr:
form: "Sections du formulaire"
edit_siret: "Modifier le SIRET"
edit_identity: "Modifier lidentité"
accuse_lecture: Cette démarche est soumise à un accusé de lecture.
accuse_lecture_with_agreement: Lusager a pris connaissance de la décision concernant son dossier le %{agreement}.
accuse_lecture_without_agreement: Lusager na pas encore pris connaissance de la décision concernant son dossier.
gestionnaires:
groupe_gestionnaires:
delete: Supprimer

View file

@ -4,4 +4,4 @@ fr:
mail:
initiated_mail:
default_subject: Votre dossier nº %{dossier_number} a bien été déposé (%{procedure_libelle})
proof_of_receipt: Accusé de réception
proof_of_receipt: Accusé de lecture

View file

@ -13,9 +13,9 @@ en:
brouillon: "Expires on %{date} (%{duree_conservation_totale} months after this file was created)"
en_construction: "Expires on %{date} (%{duree_conservation_totale} months after this file was last modified)"
en_instruction: "This file is being instructed, the administration will answer as soon as possible"
accepte: "Expires on %{date} (%{duree_conservation_totale} months after this file was accepted)"
refuse: "Expires on %{date} (%{duree_conservation_totale} months after this file was rejected)"
sans_suite: "Expires on %{date} (%{duree_conservation_totale} months after this file was closed)"
accepte: "Expires on %{date} (%{duree_conservation_totale} months after this file was processed)"
refuse: "Expires on %{date} (%{duree_conservation_totale} months after this file was processed)"
sans_suite: "Expires on %{date} (%{duree_conservation_totale} months after this file was processed)"
champs:
cnaf:
show:

View file

@ -13,9 +13,9 @@ fr:
brouillon: "Expirera le %{date} (%{duree_conservation_totale} mois après la création du dossier)"
en_construction: "Expirera le %{date} (%{duree_conservation_totale} mois après le dépôt du dossier)"
en_instruction: "Ce dossier est en instruction, il nexpirera pas"
accepte: "Expirera le %{date} (%{duree_conservation_totale} mois après lacceptation du dossier)"
refuse: "Expirera le %{date} (%{duree_conservation_totale} mois après le refus du dossier)"
sans_suite: "Expirera le %{date} (%{duree_conservation_totale} mois après le classement sans suite du dossier)"
accepte: "Expirera le %{date} (%{duree_conservation_totale} mois après le traitement du dossier)"
refuse: "Expirera le %{date} (%{duree_conservation_totale} mois après le traitement du dossier)"
sans_suite: "Expirera le %{date} (%{duree_conservation_totale} mois après le traitement dossier)"
champs:

View file

@ -24,3 +24,10 @@ en:
accepte: has been accepted on %{processed_at}.
refuse: has been refused on %{processed_at}.
sans_suite: has been closed without continuation on %{processed_at}.
accuse_lecture: has been processed on %{processed_at}.
accuse_lecture:
good_morning: Hello,
first_part: We inform you that a decision on file no. %{dossier_id} of the procedure
second_part: has been rendered.
third_part: To find out its nature, please log in to your account
commentaire_html: <p>Hello,</p><p>We inform you that a decision on your file has been rendered.</p>Sincerely,<br>%{service}

View file

@ -25,5 +25,13 @@ fr:
accepte: a été accepté le %{processed_at}.
refuse: a été refusé le %{processed_at}.
sans_suite: a été classé sans suite le %{processed_at}.
accuse_lecture: a été traité le %{processed_at}.
accuse_lecture:
good_morning: Bonjour,
first_part: Nous vous informons qu'une décision sur le dossier nº %{dossier_id} de la démarche
second_part: a été rendue.
third_part: Pour en connaitre la nature, veuillez vous connecter à votre compte
commentaire_html: <p>Bonjour,</p><p>Nous vous informons qu'une décision sur votre dossier a été rendue.</p>Cordialement,<br>%{service}
commentaire_groupe_gestionnaire_footer:
do_not_reply_html: Merci de ne pas répondre à cet email. Consultez votre message sur %{application_name} ou contactez votre expéditeur par <a href="mailto:%{sender_email}">mail</a>

View file

@ -369,6 +369,7 @@ Rails.application.routes.draw do
get 'attestation'
get 'transferer', to: 'dossiers#transferer'
get 'papertrail', format: :pdf
get 'set_accuse_lecture_agreement_at'
end
collection do
@ -571,6 +572,8 @@ Rails.application.routes.draw do
get 'modifications'
get 'monavis'
patch 'update_monavis'
get 'accuse_lecture'
patch 'update_accuse_lecture'
get 'jeton'
patch 'update_jeton'
put :allow_expert_review

View file

@ -0,0 +1,5 @@
class AddAccuseLectureToProcedures < ActiveRecord::Migration[7.0]
def change
add_column :procedures, :accuse_lecture, :boolean, default: false, null: false
end
end

View file

@ -0,0 +1,5 @@
class AddAccuseLectureAgreementToDossiers < ActiveRecord::Migration[7.0]
def change
add_column :dossiers, :accuse_lecture_agreement_at, :date
end
end

View file

@ -442,6 +442,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_04_16_062900) do
end
create_table "dossiers", id: :serial, force: :cascade do |t|
t.date "accuse_lecture_agreement_at"
t.string "api_entreprise_job_exceptions", array: true
t.boolean "archived", default: false
t.datetime "archived_at", precision: nil
@ -857,6 +858,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_04_16_062900) do
create_table "procedures", id: :serial, force: :cascade do |t|
t.string "aasm_state", default: "brouillon"
t.boolean "accuse_lecture", default: false, null: false
t.boolean "allow_expert_messaging", default: true, null: false
t.boolean "allow_expert_review", default: true, null: false
t.string "api_entreprise_token"

View file

@ -6,7 +6,9 @@ describe Instructeurs::DossiersController, type: :controller do
let(:administration) { create(:administration) }
let(:instructeurs) { [instructeur] }
let(:procedure) { create(:procedure, :published, :for_individual, instructeurs: instructeurs) }
let(:procedure_accuse_lecture) { create(:procedure, :published, :for_individual, :accuse_lecture, instructeurs: instructeurs) }
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) }
let(:dossier_accuse_lecture) { create(:dossier, :en_construction, :with_individual, procedure: procedure_accuse_lecture) }
let(:dossier_for_tiers) { create(:dossier, :en_construction, :for_tiers_with_notification, procedure: procedure) }
let(:dossier_for_tiers_without_notif) { create(:dossier, :en_construction, :for_tiers_without_notification, procedure: procedure) }
let(:fake_justificatif) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
@ -376,6 +378,34 @@ describe Instructeurs::DossiersController, type: :controller do
end
end
context "with accuse de lecture procedure" do
before do
dossier_accuse_lecture.passer_en_instruction!(instructeur: instructeur)
sign_in(instructeur.user)
end
context 'with classer_sans_suite' do
subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure_accuse_lecture.id, dossier_id: dossier_accuse_lecture.id }, format: :turbo_stream }
it 'Notification accuse de lecture email is sent and not the others' do
expect(NotificationMailer).to receive(:send_accuse_lecture_notification)
.with(dossier_accuse_lecture).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_later)
expect(NotificationMailer).not_to receive(:send_sans_suite_notification)
.with(dossier_accuse_lecture)
subject
end
it { expect(subject.body).to include('header-top') }
it 'creates a commentaire' do
expect { subject }.to change { Commentaire.count }.by(1)
expect(dossier_accuse_lecture.commentaires.last.body).to eq("<p>Bonjour,</p><p>Nous vous informons qu'une décision sur votre dossier a été rendue.</p>Cordialement,<br>#{procedure_accuse_lecture.service.nom}")
end
end
end
context "with classer_sans_suite" do
before do
dossier.passer_en_instruction!(instructeur: instructeur)

View file

@ -283,6 +283,10 @@ FactoryBot.define do
referentiel_de_programmation: { c: 3 })
end
end
trait :accuse_lecture do
accuse_lecture { true }
end
end
end

View file

@ -29,6 +29,25 @@ RSpec.describe NotificationMailer, type: :mailer do
end
end
describe 'send_notification_for_tiers with accuse lecture procedure' do
let(:dossier_for_tiers) { create(:dossier, :accepte, :for_tiers_with_notification, procedure: create(:procedure, :accuse_lecture, :for_individual)) }
subject { described_class.send_notification_for_tiers(dossier_for_tiers) }
it { expect(subject.subject).to include("Votre dossier rempli par le mandataire #{dossier_for_tiers.mandataire_first_name} #{dossier_for_tiers.mandataire_last_name} a été mis à jour") }
it { expect(subject.to).to eq([dossier_for_tiers.individual.email]) }
it { expect(subject.body).to include("a été traité le") }
it { expect(subject.body).to include("Pour en savoir plus, veuillez vous rapprocher de\r\n<a href=\"mailto:#{dossier_for_tiers.user.email}\">#{dossier_for_tiers.user.email}</a>.") }
end
describe 'send_accuse_lecture_notification' do
let(:dossier) { create(:dossier, :accepte, procedure: create(:procedure, :accuse_lecture)) }
subject { described_class.send_accuse_lecture_notification(dossier) }
it { expect(subject.subject).to include("La décision a été rendue pour votre démarche #{dossier.procedure.libelle}") }
it { expect(subject.body).to include("Pour en connaitre la nature, veuillez vous connecter à votre compte\r\n<a href=\"#{dossier_url(dossier)}\">demarches-simplifiees.fr</a>") }
end
describe 'send_en_construction_notification' do
let(:dossier) { create(:dossier, :en_construction, :with_individual, user: user, procedure:) }

View file

@ -23,6 +23,10 @@ class NotificationMailerPreview < ActionMailer::Preview
NotificationMailer.send_notification_for_tiers(dossier)
end
def send_accuse_lecture_notification
NotificationMailer.send_accuse_lecture_notification(dossier)
end
private
def dossier

View file

@ -1,5 +1,6 @@
describe 'user access to the list of their dossiers', js: true do
let(:user) { create(:user) }
let(:procedure_accuse_lecture) { create(:procedure, :accuse_lecture) }
let!(:dossier_brouillon) { create(:dossier, user: user) }
let!(:dossier_en_construction) { create(:dossier, :with_populated_champs, :en_construction, user: user) }
let!(:dossier_en_construction_2) { create(:dossier, :en_construction, user: user) }
@ -9,6 +10,8 @@ describe 'user access to the list of their dossiers', js: true do
let!(:dossier_a_corriger) { create(:dossier_correction, dossier: dossier_en_construction_2) }
let!(:dossier_archived) { create(:dossier, :en_instruction, :archived, user: user) }
let!(:dossier_for_tiers) { create(:dossier, :en_instruction, :for_tiers_with_notification, user: user) }
let!(:dossier_en_construction_with_accuse_lecture) { create(:dossier, :en_construction, user: user, procedure: procedure_accuse_lecture) }
let!(:dossier_accepte_with_accuse_lecture) { create(:dossier, :accepte, user: user, procedure: procedure_accuse_lecture) }
let(:dossiers_per_page) { 25 }
let(:last_updated_dossier) { dossier_en_construction }
@ -31,8 +34,8 @@ describe 'user access to the list of their dossiers', js: true do
expect(page).to have_content(dossier_en_construction.procedure.libelle)
expect(page).to have_content(dossier_en_instruction.procedure.libelle)
expect(page).to have_content(dossier_archived.procedure.libelle)
expect(page).to have_text('6 en cours')
expect(page).to have_text('2 traités')
expect(page).to have_text('7 en cours')
expect(page).to have_text('3 traités')
end
it 'the list must be ordered by last updated' do
@ -55,8 +58,8 @@ describe 'user access to the list of their dossiers', js: true do
page.click_link("Suivant")
page.click_link("Suivant")
expect(page).to have_link(dossier_en_instruction.procedure.libelle)
expect(page).to have_text('6 en cours')
expect(page).to have_text('2 traités')
expect(page).to have_text('7 en cours')
expect(page).to have_text('3 traités')
end
end
@ -70,9 +73,9 @@ describe 'user access to the list of their dossiers', js: true do
context 'when user uses filter' do
scenario 'user filters state on tab "en-cours"' do
expect(page).to have_text('6 en cours')
expect(page).to have_text('2 traités')
expect(page).to have_text('6 sur 6 dossiers')
expect(page).to have_text('7 en cours')
expect(page).to have_text('3 traités')
expect(page).to have_text('7 sur 7 dossiers')
click_on('Sélectionner un filtre')
expect(page).to have_select 'Statut', selected: 'Sélectionner un statut', options: ['Sélectionner un statut', 'Brouillon', 'En construction', 'En instruction', 'À corriger']
@ -92,9 +95,9 @@ describe 'user access to the list of their dossiers', js: true do
scenario 'user filters state on tab "traité"' do
visit dossiers_path(statut: 'traites')
expect(page).to have_text('6 en cours')
expect(page).to have_text('2 traités')
expect(page).to have_text('2 sur 2 dossiers')
expect(page).to have_text('7 en cours')
expect(page).to have_text('3 traités')
expect(page).to have_text('3 sur 3 dossiers')
click_on('Sélectionner un filtre')
expect(page).to have_select 'Statut', selected: 'Sélectionner un statut', options: ['Sélectionner un statut', 'Accepté', 'Refusé', 'Classé sans suite']
@ -105,18 +108,29 @@ describe 'user access to the list of their dossiers', js: true do
click_on('Sélectionner un filtre')
click_on('Annuler')
expect(page).to have_text('2 sur 2 dossiers')
click_on('Sélectionner un filtre')
expect(page).to have_select 'Statut', selected: 'Sélectionner un statut', options: ['Sélectionner un statut', 'Accepté', 'Refusé', 'Classé sans suite']
select('Accepté', from: 'Statut')
click_on('Appliquer les filtres')
# we expect 1 dossier because we want do hide decision for dossier with accuse de lecture
expect(page).to have_text('1 dossier')
expect(page).to have_select 'Statut', selected: 'Accepté', options: ['Sélectionner un statut', 'Accepté', 'Refusé', 'Classé sans suite']
click_on('Sélectionner un filtre')
click_on('Annuler')
expect(page).to have_text('3 sur 3 dossiers')
expect(page).to have_select 'Statut', selected: 'Sélectionner un statut', options: ['Sélectionner un statut', 'Accepté', 'Refusé', 'Classé sans suite']
end
scenario 'user filters by created_at' do
dossier_en_construction.update!(created_at: Date.yesterday)
expect(page).to have_text('6 sur 6 dossiers')
expect(page).to have_text('7 sur 7 dossiers')
click_on('Sélectionner un filtre')
fill_in 'from_created_at_date', with: Date.today
click_on('Appliquer les filtres')
expect(page).to have_text('5 sur 5 dossiers')
expect(page).to have_text('6 sur 6 dossiers')
end
scenario 'user uses multiple filters' do
@ -124,26 +138,26 @@ describe 'user access to the list of their dossiers', js: true do
expect(page).to have_select 'Statut', selected: 'Sélectionner un statut', options: ['Sélectionner un statut', 'Brouillon', 'En construction', 'En instruction', 'À corriger']
expect(page).to have_text('6 sur 6 dossiers')
expect(page).to have_text('7 sur 7 dossiers')
click_on('Sélectionner un filtre')
fill_in 'from_created_at_date', with: Date.today
click_on('Appliquer les filtres')
expect(page).to have_text('5 sur 5 dossiers')
expect(page).to have_text('6 sur 6 dossiers')
expect(page).to have_text('1 filtre actif')
click_on('Sélectionner un filtre')
select('En construction', from: 'Statut')
click_on('Appliquer les filtres')
expect(page).to have_text('1 dossier')
expect(page).to have_text('2 dossiers')
expect(page).to have_text('2 filtres actifs')
click_on('Sélectionner un filtre')
fill_in 'from_depose_at_date', with: Date.today
click_on('Appliquer les filtres')
expect(page).to have_text('1 dossier')
expect(page).to have_text('2 sur 2 dossiers')
expect(page).to have_text('3 filtres actifs')
click_on('3 filtres actifs')
expect(page).to have_text('6 sur 6 dossiers')
expect(page).to have_text('7 sur 7 dossiers')
expect(page).not_to have_text('5 filtres actifs')
end
end
@ -285,8 +299,8 @@ describe 'user access to the list of their dossiers', js: true do
describe "filter by procedure" do
context "when dossiers are on different procedures" do
it "can filter by procedure" do
expect(page).to have_text('6 en cours')
expect(page).to have_text('2 traités')
expect(page).to have_text('7 en cours')
expect(page).to have_text('3 traités')
expect(page).to have_select('procedure_id', selected: 'Toutes les démarches')
select dossier_brouillon.procedure.libelle, from: 'procedure_id'
expect(page).to have_text('1 en cours')

View file

@ -173,4 +173,32 @@ describe 'instructeurs/dossiers/show', type: :view do
end
end
end
describe 'accuse de lecture ' do
context 'dossier not termine' do
let(:dossier) { create(:dossier, :en_instruction, procedure: create(:procedure, :accuse_lecture)) }
it 'does not display a text about accuse de lecture for instructeur' do
expect(subject).not_to have_text('Cette démarche est soumise à un accusé de lecture')
end
end
context 'dossier termine with accuse de lecture not accepted by user' do
let(:dossier) { create(:dossier, :accepte, procedure: create(:procedure, :accuse_lecture)) }
it 'displays a text about accuse de lecture for instructeur' do
expect(subject).to have_text('Cette démarche est soumise à un accusé de lecture')
expect(subject).to have_text('Lusager na pas encore pris connaissance de la décision concernant son dossier')
end
end
context 'dossier termine with accuse de lecture accepted by user' do
let(:dossier) { create(:dossier, :accepte, accuse_lecture_agreement_at: Time.zone.now, procedure: create(:procedure, :accuse_lecture)) }
it 'displays a text about accuse de lecture for instructeur' do
expect(subject).to have_text('Cette démarche est soumise à un accusé de lecture')
expect(subject).to have_text('Lusager a pris connaissance de la décision concernant son dossier le')
end
end
end
end

View file

@ -1,6 +1,6 @@
describe 'shared/dossiers/_infos_generales', type: :view do
let(:dossier) { create(:dossier, :en_construction) }
subject { render }
subject { render 'shared/dossiers/infos_generales', dossier: dossier, profile: 'instructeur' }
before do
sign_in(current_role.user)
allow(view).to receive(:current_instructeur).and_return(current_role)
@ -32,6 +32,14 @@ describe 'shared/dossiers/_infos_generales', type: :view do
end
end
context 'with a motivation and procedure with accuse de lecture' do
let(:dossier) { create :dossier, :accepte, :with_justificatif, procedure: create(:procedure, :accuse_lecture) }
it 'still displays the motivation text for the instructeur' do
expect(subject).to have_content(dossier.motivation)
end
end
context 'with an attestation' do
let(:dossier) { create :dossier, :accepte, :with_attestation }

View file

@ -59,4 +59,23 @@ describe 'users/dossiers/demande', type: :view do
expect(rendered).to have_text(dossier.individual.email.to_s)
end
end
context 'when a dossier is accepte with motivation' do
let(:dossier) { create(:dossier, :accepte, :with_motivation) }
it 'displays the motivation' do
expect(rendered).not_to have_text('Cette démarche est soumise à un accusé de lecture.')
expect(rendered).to have_text('Motivation')
end
end
context 'when a dossier is accepte with motivation and with accuse de lecture' do
let(:dossier) { create(:dossier, :accepte, :with_motivation, procedure: create(:procedure, :accuse_lecture)) }
it 'display information about accuse de lecture and not the motivation' do
expect(rendered).to have_text('Cette démarche est soumise à un accusé de lecture.')
expect(rendered).not_to have_text('Motivation')
expect(rendered).not_to have_text('Lusager na pas encore pris connaissance de la décision concernant son dossier')
end
end
end

View file

@ -1,10 +1,13 @@
describe 'users/dossiers/index', type: :view do
let(:user) { create(:user) }
let(:procedure_accuse_lecture) { create(:procedure, :accuse_lecture) }
let(:dossier_brouillon) { create(:dossier, state: Dossier.states.fetch(:brouillon), user: user) }
let(:dossier_en_construction) { create(:dossier, state: Dossier.states.fetch(:en_construction), user: user) }
let(:dossier_en_construction_with_accuse_lecture) { create(:dossier, state: Dossier.states.fetch(:en_construction), user: user, procedure: procedure_accuse_lecture) }
let(:dossier_termine) { create(:dossier, state: Dossier.states.fetch(:accepte), user: user) }
let(:dossier_termine_with_accuse_lecture) { create(:dossier, state: Dossier.states.fetch(:accepte), user: user, procedure: procedure_accuse_lecture) }
let(:dossiers_invites) { [] }
let(:user_dossiers) { Kaminari.paginate_array([dossier_brouillon, dossier_en_construction, dossier_termine]).page(1) }
let(:user_dossiers) { Kaminari.paginate_array([dossier_brouillon, dossier_en_construction, dossier_termine, dossier_en_construction_with_accuse_lecture, dossier_termine_with_accuse_lecture]).page(1) }
let(:statut) { 'en-cours' }
let(:filter) { DossiersFilter.new(user, ActionController::Parameters.new(random_param: 'random_param')) }
@ -28,7 +31,7 @@ describe 'users/dossiers/index', type: :view do
end
it 'affiche les dossiers' do
expect(rendered).to have_selector('.card', count: 3)
expect(rendered).to have_selector('.card', count: 5)
end
it 'affiche les informations des dossiers' do
@ -40,6 +43,9 @@ describe 'users/dossiers/index', type: :view do
expect(rendered).to have_text(dossier_en_construction.id.to_s)
expect(rendered).to have_text(dossier_en_construction.procedure.libelle)
expect(rendered).to have_link(dossier_en_construction.procedure.libelle, href: dossier_path(dossier_en_construction))
expect(rendered).to have_selector('.fr-badge', text: 'traité', count: 1)
expect(rendered).to have_selector('.fr-badge', text: 'en construction', count: 2)
end
it 'naffiche pas une alerte pour continuer à remplir un dossier' do
@ -132,13 +138,13 @@ describe 'users/dossiers/index', type: :view do
it "cache key depends on dossiers list" do
render
expect(rendered).to have_text(/3\s+en cours/)
expect(rendered).to have_text(/5\s+en cours/)
assign(:user_dossiers, Kaminari.paginate_array(user_dossiers.concat([create(:dossier, :en_construction, user: user)])).page(1))
user.reload
render
expect(rendered).to have_text(/4\s+en cours/)
expect(rendered).to have_text(/6\s+en cours/)
end
it "cache key dpeends on dossier invites" do

View file

@ -18,6 +18,26 @@ describe 'users/dossiers/show/header', type: :view do
expect(rendered).to have_link("Demande", href: demande_dossier_path(dossier))
end
context "when the procedure is with accuse de lecture with a dossier en construction" do
let(:procedure) { create(:procedure, :accuse_lecture) }
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
it "affiche les informations du dossier" do
expect(rendered).to have_text("Dossier nº #{dossier.id}")
expect(rendered).to have_text("en construction")
end
end
context "when the procedure is with accuse de lecture with a dossier termine" do
let(:procedure) { create(:procedure, :accuse_lecture) }
let(:dossier) { create(:dossier, :accepte, procedure: procedure) }
it "n'affiche pas les informations de décision" do
expect(rendered).to have_text("Dossier nº #{dossier.id}")
expect(rendered).to have_text("traité")
end
end
context "when the procedure is closed with a dossier en construction" do
let(:procedure) { create(:procedure, :closed) }
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }

View file

@ -92,4 +92,12 @@ describe 'users/dossiers/show/_status_overview', type: :view do
it { is_expected.to have_selector('.status-explanation .sans-suite') }
it { is_expected.to have_text(dossier.motivation) }
end
context 'when terminé but the procedure has an accuse de lecture' do
let(:dossier) { create(:dossier, :sans_suite, :with_motivation, procedure: create(:procedure, :accuse_lecture)) }
it { is_expected.not_to have_selector('.status-explanation .sans-suite') }
it { is_expected.not_to have_text(dossier.motivation) }
it { is_expected.to have_text('Cette démarche est soumise à un accusé de lecture.') }
end
end