feat(procedure): send notifications after closing
This commit is contained in:
parent
c147d9b36c
commit
c95f0f1cad
13 changed files with 307 additions and 11 deletions
|
@ -212,6 +212,38 @@ module Administrateurs
|
|||
redirect_to admin_procedures_path
|
||||
end
|
||||
|
||||
def closing_notification
|
||||
@procedure = current_administrateur.procedures.find(params[:procedure_id])
|
||||
@users_brouillon_count = @procedure.dossiers.not_archived.state_brouillon.count('distinct user_id')
|
||||
@users_en_cours_count = @procedure.dossiers.not_archived.state_en_construction_ou_instruction.count('distinct user_id')
|
||||
end
|
||||
|
||||
def notify_after_closing
|
||||
@procedure = current_administrateur.procedures.find(params[:procedure_id])
|
||||
@procedure.update!(notification_closing_params)
|
||||
|
||||
if (@procedure.closing_notification_brouillon? && params[:email_content_brouillon].blank?) || (@procedure.closing_notification_en_cours? && params[:email_content_en_cours].blank?)
|
||||
flash.alert = "Veuillez renseigner le contenu de l’email afin d’informer les usagers"
|
||||
redirect_to admin_procedure_closing_notification_path and return
|
||||
end
|
||||
|
||||
if @procedure.closing_notification_brouillon?
|
||||
user_ids = @procedure.dossiers.not_archived.state_brouillon.pluck(:user_id).uniq
|
||||
content = params[:email_content_brouillon]
|
||||
SendClosingNotificationJob.perform_later(user_ids, content, @procedure)
|
||||
flash.notice = "Les emails sont en cours d'envoi"
|
||||
end
|
||||
|
||||
if @procedure.closing_notification_en_cours?
|
||||
user_ids = @procedure.dossiers.not_archived.state_en_construction_ou_instruction.pluck(:user_id).uniq
|
||||
content = params[:email_content_en_cours]
|
||||
SendClosingNotificationJob.perform_later(user_ids, content, @procedure)
|
||||
flash.notice = "Les emails sont en cours d’envoi"
|
||||
end
|
||||
|
||||
redirect_to admin_procedures_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
procedure = current_administrateur.procedures.find(params[:id])
|
||||
|
||||
|
@ -304,6 +336,10 @@ module Administrateurs
|
|||
.update!(replaced_by_procedure: @procedure)
|
||||
end
|
||||
|
||||
# TO DO after data backfill add this condition before reset :
|
||||
# if @procedure.closing_reason.present?
|
||||
@procedure.reset_closing_params
|
||||
|
||||
redirect_to admin_procedure_confirmation_path(@procedure)
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
flash.alert = @procedure.errors.full_messages
|
||||
|
@ -509,6 +545,10 @@ module Administrateurs
|
|||
closing_params
|
||||
end
|
||||
|
||||
def notification_closing_params
|
||||
params.require(:procedure).permit(:closing_notification_brouillon, :closing_notification_en_cours)
|
||||
end
|
||||
|
||||
def allow_decision_access_params
|
||||
params.require(:experts_procedure).permit(:allow_decision_access)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
import { hide, show } from '@utils';
|
||||
|
||||
export class ClosingNotificationController extends ApplicationController {
|
||||
static targets = [
|
||||
'brouillonToggle',
|
||||
'emailContentBrouillon',
|
||||
'enCoursToggle',
|
||||
'emailContentEnCours',
|
||||
'submit'
|
||||
];
|
||||
|
||||
declare readonly brouillonToggleTarget: HTMLInputElement;
|
||||
declare readonly hasBrouillonToggleTarget: boolean;
|
||||
declare readonly enCoursToggleTarget: HTMLInputElement;
|
||||
declare readonly hasEnCoursToggleTarget: boolean;
|
||||
declare readonly emailContentBrouillonTarget: HTMLElement;
|
||||
declare readonly emailContentEnCoursTarget: HTMLElement;
|
||||
declare readonly submitTarget: HTMLButtonElement;
|
||||
|
||||
connect() {
|
||||
this.displayBrouillonInput();
|
||||
this.displayEnCoursInput();
|
||||
this.on('change', () => this.onChange());
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.displayBrouillonInput();
|
||||
this.displayEnCoursInput();
|
||||
}
|
||||
|
||||
displayBrouillonInput() {
|
||||
if (this.hasBrouillonToggleTarget) {
|
||||
const brouillonToggleElement = this
|
||||
.brouillonToggleTarget as HTMLInputElement;
|
||||
|
||||
const emailContentBrouillonElement = this
|
||||
.emailContentBrouillonTarget as HTMLElement;
|
||||
|
||||
if (emailContentBrouillonElement) {
|
||||
if (brouillonToggleElement.checked) {
|
||||
show(emailContentBrouillonElement);
|
||||
} else {
|
||||
hide(emailContentBrouillonElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayEnCoursInput() {
|
||||
if (this.hasEnCoursToggleTarget) {
|
||||
const enCoursToggleElement = this.enCoursToggleTarget as HTMLInputElement;
|
||||
|
||||
const emailContentEnCoursElement = this
|
||||
.emailContentEnCoursTarget as HTMLElement;
|
||||
|
||||
if (emailContentEnCoursElement) {
|
||||
if (enCoursToggleElement.checked) {
|
||||
show(this.emailContentEnCoursTarget);
|
||||
} else {
|
||||
hide(this.emailContentEnCoursTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enableSubmitOnClick() {
|
||||
if (
|
||||
this.element.querySelectorAll('input[type="checkbox"]:checked').length > 0
|
||||
) {
|
||||
this.submitTarget.disabled = false;
|
||||
} else {
|
||||
this.submitTarget.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
7
app/jobs/send_closing_notification_job.rb
Normal file
7
app/jobs/send_closing_notification_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class SendClosingNotificationJob < ApplicationJob
|
||||
def perform(user_ids, content, procedure)
|
||||
User.where(id: user_ids).find_each do |user|
|
||||
Expired::MailRateLimiter.new().send_with_delay(UserMailer.notify_after_closing(user, content, @procedure))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -74,6 +74,15 @@ class UserMailer < ApplicationMailer
|
|||
mail(to: user.email, subject: @subject)
|
||||
end
|
||||
|
||||
def notify_after_closing(user, content, procedure = nil)
|
||||
@user = user
|
||||
@subject = "Clôture d'une démarche sur Démarches simplifiées"
|
||||
@procedure = procedure
|
||||
@content = content
|
||||
|
||||
mail(to: user.email, subject: @subject, content: @content, procedure: @procedure)
|
||||
end
|
||||
|
||||
def self.critical_email?(action_name)
|
||||
[
|
||||
'france_connect_merge_confirmation',
|
||||
|
|
|
@ -1010,6 +1010,10 @@ class Procedure < ApplicationRecord
|
|||
.first
|
||||
end
|
||||
|
||||
def reset_closing_params
|
||||
update!(closing_reason: nil, closing_details: nil, replaced_by_procedure_id: nil, closing_notification_brouillon: false, closing_notification_en_cours: false)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pieces_jointes_list
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
= render partial: 'administrateurs/breadcrumbs',
|
||||
locals: { steps: [['Démarches', admin_procedures_back_path(@procedure)],
|
||||
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],[t('administrateurs.procedures.close.page_title')]],
|
||||
metadatas: true }
|
||||
|
||||
.fr-container
|
||||
.fr-grid-row
|
||||
.fr-col-12.fr-col-offset-md-2.fr-col-md-8
|
||||
%h1= t('administrateurs.procedures.closing_notification.page_title')
|
||||
- if @procedure.closing_reason == Procedure.closing_reasons.fetch(:other)
|
||||
%h2.fr-h5= I18n.t('administrateurs.procedures.closing_notification.page_subtitle', closing_path: closing_details_path(@procedure.path)).html_safe
|
||||
- else
|
||||
%h2.fr-h5= I18n.t('administrateurs.procedures.closing_notification.page_subtitle_with_redirection', redirection_path: commencer_path(@procedure.replaced_by_procedure.path)).html_safe
|
||||
|
||||
= render Dsfr::AlertComponent.new(state: :info, size: :sm, extra_class_names: 'fr-mb-2w') do |c|
|
||||
- c.with_body do
|
||||
%p
|
||||
= t('administrateurs.procedures.closing_notification.callout_content')
|
||||
|
||||
= form_for @procedure,
|
||||
url: admin_procedure_notify_after_closing_path(@procedure),
|
||||
method: :post,
|
||||
html: { "data-controller" => "closing-notification" } do |f|
|
||||
|
||||
%div{ data: { 'action': "click->closing-notification#enableSubmitOnClick" } }
|
||||
- if @users_brouillon_count != 0
|
||||
= render Dsfr::ToggleComponent.new(form: f,
|
||||
target: :closing_notification_brouillon,
|
||||
title: t("administrateurs.procedures.closing_notification.email_toggle_brouillon", count: @users_brouillon_count),
|
||||
toggle_labels: {checked: 'Oui', unchecked: 'Non'},
|
||||
opt: {"closing-notification-target" => "brouillonToggle"})
|
||||
|
||||
.fr-input-group{ "data-closing-notification-target" => "emailContentBrouillon" }
|
||||
= label_tag :email_content_brouillon, t("administrateurs.procedures.closing_notification.email_content_brouillon"), class: "fr-label"
|
||||
= text_area_tag :email_content_brouillon, '', class: "fr-input"
|
||||
|
||||
- if @users_en_cours_count != 0
|
||||
= render Dsfr::ToggleComponent.new(form: f,
|
||||
target: :closing_notification_en_cours,
|
||||
title: t("administrateurs.procedures.closing_notification.email_toggle_en_cours", count: @users_en_cours_count),
|
||||
toggle_labels: {checked: 'Oui', unchecked: 'Non'},
|
||||
opt: {"closing-notification-target" => "enCoursToggle"})
|
||||
|
||||
.fr-input-group{ "data-closing-notification-target" => "emailContentEnCours" }
|
||||
= label_tag :email_content_en_cours, t("administrateurs.procedures.closing_notification.email_content_en_cours"), class: "fr-label"
|
||||
= text_area_tag :email_content_en_cours, '', class: "fr-input"
|
||||
|
||||
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||
%li
|
||||
= submit_tag t('administrateurs.procedures.close.actions.notify_after_closing'), { class: "fr-btn", id: 'publish', disabled: true, data: { confirm: "Vous allez informer les usagers de la clôture de la démarche. Souhaitez-vous continuer ?", disable_with: "Envoi des notifications…", 'closing-notification-target': 'submit'} }
|
||||
%li
|
||||
= link_to t('administrateurs.procedures.close.actions.cancel'), admin_procedures_path, class: 'fr-btn fr-btn--secondary fr-ml-2w'
|
4
app/views/user_mailer/notify_after_closing.html.haml
Normal file
4
app/views/user_mailer/notify_after_closing.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
- content_for(:title, @subject)
|
||||
|
||||
%p
|
||||
= simple_format(@content)
|
|
@ -6,7 +6,22 @@ en:
|
|||
replacement_procedure_callout_title: You are about to close a procedure
|
||||
replacement_procedure_callout_content: Files « in construction » or « instructing » can be instructed, but no new files can be filed.
|
||||
actions:
|
||||
close_procedure: Close the procedure
|
||||
close_procedure: Close procedure
|
||||
notify_after_closing: Notify users
|
||||
cancel: Cancel
|
||||
closing_notification:
|
||||
page_title: Alert users
|
||||
page_subtitle: Your procedure has been successfully closed. The link of the procedure now redirects to this <a href="%{closing_path}" target="_blank" rel="noopener noreferrer">closing page</a>.
|
||||
page_subtitle_with_redirection: Your procedure has been successfully closed. The link of the procedure now redirects to this <a href="%{redirection_path}" target="_blank" rel="noopener noreferrer">procedure</a>.
|
||||
callout_content: You can continue to examine the submitted files. If you do not intend to examine these files, we invite you to inform users
|
||||
email_toggle_brouillon:
|
||||
one: You want to send an email to the user with a draft folder
|
||||
other: You want to send an email to %{count} users with a draft folder
|
||||
email_content_brouillon: You want to send an email to users with a draft file
|
||||
email_toggle_en_cours:
|
||||
one: You want to send an email to the user with a submitted file
|
||||
other: You want to send an email to %{count} users with a submitted file
|
||||
email_content_en_cours: You want to send an email to users with a file « in construction » or « instructing »
|
||||
preview_unavailable: Preview is unavailable due to procedure misconfiguration
|
||||
modifications:
|
||||
dossiers_en_construction_and_dossiers_en_instruction: "%{en_construction_count} files « in construction » and %{en_instruction_count} files « instructing » on this procedure version."
|
||||
|
|
|
@ -7,6 +7,21 @@ fr:
|
|||
replacement_procedure_callout_content: Les dossiers en cours pourront être instruits, mais aucun nouveau dossier ne pourra plus être déposé.
|
||||
actions:
|
||||
close_procedure: Clore la démarche
|
||||
notify_after_closing: Informer les usagers
|
||||
cancel: Retour
|
||||
closing_notification:
|
||||
page_title: Alerter les usagers
|
||||
page_subtitle: Votre démarche est close. Le lien de votre démarche redirige désormais vers cette <a href="%{closing_path}" target="_blank" rel="noopener noreferrer">page de fermeture</a>.
|
||||
page_subtitle_with_redirection: Votre démarche est close. Le lien public redirige désormais vers cette <a href="%{redirection_path}" target="_blank" rel="noopener noreferrer">démarche</a>.
|
||||
callout_content: Vous avez la possibilité de continuer à instruire les dossiers déposés. Si vous n’avez pas l’intention d’instruire ces dossiers, nous vous invitons à en informer les usagers.
|
||||
email_toggle_brouillon:
|
||||
one: Souhaitez-vous envoyer un email à l'utilisateur avec un dossier en brouillon ?
|
||||
other: Souhaitez-vous envoyer un email aux %{count} utilisateurs avec un dossier en brouillon ?
|
||||
email_content_brouillon: Contenu de l'email
|
||||
email_toggle_en_cours:
|
||||
one : Souhaitez-vous envoyer un email à l'utilisateur avec un dossier déposé ?
|
||||
other: Souhaitez-vous envoyer un email aux %{count} utilisateurs avec un dossier déposé ?
|
||||
email_content_en_cours: Contenu de l'email
|
||||
preview_unavailable: Aperçu non disponible car la démarche est mal configurée
|
||||
modifications:
|
||||
dossiers_en_construction_and_dossiers_en_instruction: Il y a %{en_construction_count} dossiers « en construction » et %{en_instruction_count} dossiers « en instruction » sur cette version de la démarche.
|
||||
|
|
|
@ -600,6 +600,8 @@ Rails.application.routes.draw do
|
|||
put 'publish_revision' => 'procedures#publish_revision', as: :publish_revision
|
||||
get 'transfert' => 'procedures#transfert', as: :transfert
|
||||
get 'close' => 'procedures#close', as: :close
|
||||
get 'closing_notification' => 'procedures#closing_notification', as: :closing_notification
|
||||
post 'notify_after_closing' => 'procedures#notify_after_closing', as: :notify_after_closing
|
||||
get 'confirmation' => 'procedures#confirmation', as: :confirmation
|
||||
post 'transfer' => 'procedures#transfer', as: :transfer
|
||||
resources :mail_templates, only: [:edit, :update, :show]
|
||||
|
|
|
@ -696,7 +696,7 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
|
||||
it 'archives the procedure' do
|
||||
expect(procedure.close?).to be_truthy
|
||||
expect(response).to redirect_to :admin_procedures
|
||||
expect(response).to redirect_to admin_procedure_path(procedure.id)
|
||||
expect(flash[:notice]).to have_content 'Démarche close'
|
||||
end
|
||||
|
||||
|
@ -724,8 +724,7 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
|
||||
it 'archives the procedure' do
|
||||
expect(procedure.close?).to be_truthy
|
||||
expect(response).to redirect_to :admin_procedures
|
||||
expect(flash[:notice]).to have_content 'Démarche close'
|
||||
expect(response).to redirect_to admin_procedure_closing_notification_path
|
||||
end
|
||||
|
||||
it 'does have a replacement procedure' do
|
||||
|
@ -743,15 +742,9 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
|
||||
it 'archives the procedure' do
|
||||
expect(procedure.close?).to be_truthy
|
||||
expect(response).to redirect_to :admin_procedures
|
||||
expect(response).to redirect_to admin_procedure_path(procedure.id)
|
||||
expect(flash[:notice]).to have_content 'Démarche close'
|
||||
end
|
||||
|
||||
it 'does have a replacement procedure' do
|
||||
expect(procedure.replaced_by_procedure).to eq(nil)
|
||||
expect(procedure.replaced_by_external_url).to eq('new_url.com')
|
||||
expect(procedure.closing_reason).to eq('external_procedure')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the admin is not an owner of the procedure' do
|
||||
|
@ -770,6 +763,42 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
expect(flash[:alert]).to have_content 'Démarche inexistante'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the admin is not an owner of the new procedure in DS' do
|
||||
let(:admin_2) { create(:administrateur) }
|
||||
let(:other_admin_procedure) { create(:procedure, :with_all_champs, administrateurs: [admin_2]) }
|
||||
|
||||
before do
|
||||
put :archive, params: { procedure_id: procedure.id, procedure: { closing_reason: 'internal_procedure', replaced_by_procedure_id: other_admin_procedure.id } }
|
||||
procedure.reload
|
||||
end
|
||||
|
||||
it 'closes the procedure without redirection to the new procedure in DS' do
|
||||
expect(response).to redirect_to admin_procedure_path(procedure.id)
|
||||
expect(flash[:notice]).to have_content 'Démarche close'
|
||||
expect(procedure.replaced_by_procedure).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #notify_after_closing' do
|
||||
let(:procedure_closed) { create(:procedure_with_dossiers, :closed, administrateurs: [admin]) }
|
||||
let(:user_ids) { [procedure_closed.dossiers.first.user.id] }
|
||||
let(:email_content) { "La démarche a fermé" }
|
||||
|
||||
subject do
|
||||
post :notify_after_closing, params: { procedure_id: procedure_closed.id, procedure: { closing_notification_brouillon: true }, email_content_brouillon: email_content }
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(admin.user)
|
||||
end
|
||||
|
||||
it 'redirects to admin procedures' do
|
||||
expect { subject }.to have_enqueued_job(SendClosingNotificationJob).with(user_ids, email_content, procedure_closed)
|
||||
expect(flash.notice).to eq("Les emails sont en cours d'envoi")
|
||||
expect(response).to redirect_to :admin_procedures
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
|
@ -1056,6 +1085,27 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'procedure was closed and is re opened' do
|
||||
before do
|
||||
procedure.publish!
|
||||
procedure.update!(closing_reason: 'internal_procedure', replaced_by_procedure_id: procedure2.id)
|
||||
procedure.close!
|
||||
procedure.update!(closing_notification_brouillon: true, closing_notification_en_cours: true)
|
||||
perform_request
|
||||
procedure.reload
|
||||
procedure2.reload
|
||||
end
|
||||
|
||||
it 'publish the given procedure and reset closing params' do
|
||||
expect(procedure.publiee?).to be_truthy
|
||||
expect(procedure.path).to eq(path)
|
||||
expect(procedure.closing_reason).to be_nil
|
||||
expect(procedure.replaced_by_procedure_id).to be_nil
|
||||
expect(procedure.closing_notification_brouillon).to be_falsy
|
||||
expect(procedure.closing_notification_en_cours).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'procedure path exists and is not owned by current administrator' do
|
||||
let(:path) { procedure3.path }
|
||||
let(:lien_site_web) { 'http://mon-site.gouv.fr' }
|
||||
|
|
|
@ -33,6 +33,10 @@ class UserMailerPreview < ActionMailer::Preview
|
|||
UserMailer.notify_inactive_close_to_deletion(user)
|
||||
end
|
||||
|
||||
def notify_after_closing
|
||||
UserMailer.notify_after_closing([user])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user
|
||||
|
|
|
@ -127,4 +127,22 @@ RSpec.describe UserMailer, type: :mailer do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.notify_after_closing' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
let(:content) { "Bonjour,\r\nsaut de ligne" }
|
||||
subject { described_class.notify_after_closing(user, content, procedure) }
|
||||
|
||||
it { expect(subject.to).to eq([user.email]) }
|
||||
it { expect(subject.body).to include("Clôture d'une démarche sur Démarches simplifiées") }
|
||||
it { expect(subject.body).to include("Bonjour,\r\n<br />saut de ligne") }
|
||||
|
||||
context 'when perform_later is called' do
|
||||
let(:custom_queue) { 'low_priority' }
|
||||
before { ENV['BULK_EMAIL_QUEUE'] = custom_queue }
|
||||
it 'enqueues email is custom queue for low priority delivery' do
|
||||
expect { subject.deliver_later }.to have_enqueued_job.on_queue(custom_queue)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue