Merge pull request #6347 from betagouv/feat/4386

ETQ admin/instructeur, je veux envoyer un message à tous les usagers d'un coup
This commit is contained in:
Kara Diaby 2021-07-28 11:08:30 +02:00 committed by GitHub
commit 4befa21a30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 239 additions and 17 deletions

View file

@ -219,6 +219,51 @@ module Instructeurs
@usual_traitement_time_by_month = @procedure.stats_usual_traitement_time_by_month_in_days
end
def email_usagers
@procedure = procedure
@commentaire = Commentaire.new
@email_usagers_dossiers = email_usagers_dossiers
@dossiers_count = @email_usagers_dossiers.count
@groupe_instructeurs = email_usagers_groupe_instructeurs_label
@bulk_messages = BulkMessage.includes(:groupe_instructeurs).where(groupe_instructeurs: { id: current_instructeur.groupe_instructeur_ids })
end
def create_multiple_commentaire
@procedure = procedure
errors = []
email_usagers_dossiers.each do |dossier|
commentaire = CommentaireService.build(current_instructeur, dossier, commentaire_params)
if commentaire.save
commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
else
errors << dossier.id
end
end
valid_dossiers_count = email_usagers_dossiers.count - errors.count
create_bulk_message_mail(valid_dossiers_count, Dossier.states.fetch(:brouillon))
if errors.empty?
flash[:notice] = "Tous les messages ont été envoyés avec succès"
else
flash[:alert] = "Envoi terminé. Cependant #{errors.count} messages n'ont pas été envoyés"
end
redirect_to instructeur_procedure_path(@procedure)
end
def create_bulk_message_mail(dossier_count, dossier_state)
BulkMessage.create(
dossier_count: dossier_count,
dossier_state: dossier_state,
body: commentaire_params[:body],
sent_at: Time.zone.now,
instructeur_id: current_instructeur.id,
piece_jointe: commentaire_params[:piece_jointe],
groupe_instructeurs: email_usagers_groupe_instructeurs
)
end
private
def assign_to_params
@ -288,5 +333,21 @@ module Instructeurs
def current_filters
@current_filters ||= procedure_presentation.filters[statut]
end
def email_usagers_dossiers
procedure.dossiers.state_brouillon.where(groupe_instructeur: current_instructeur.groupe_instructeur_ids).includes(:groupe_instructeur)
end
def email_usagers_groupe_instructeurs_label
email_usagers_dossiers.map(&:groupe_instructeur).uniq.map(&:label)
end
def email_usagers_groupe_instructeurs
email_usagers_dossiers.map(&:groupe_instructeur).uniq
end
def commentaire_params
params.require(:commentaire).permit(:body, :piece_jointe)
end
end
end

View file

@ -74,9 +74,19 @@ module NewAdministrateur
.without_group(@groupe_instructeur)
end
def reaffecter_bulk_messages(target_group)
bulk_messages = BulkMessage.joins(:groupe_instructeurs).where(groupe_instructeurs: { id: groupe_instructeur.id })
bulk_messages.each do |bulk_message|
bulk_message.groupe_instructeurs.delete(groupe_instructeur)
if !bulk_message.groupe_instructeur_ids.include?(target_group.id)
bulk_message.groupe_instructeurs << target_group
end
end
end
def reaffecter
target_group = procedure.groupe_instructeurs.find(params[:target_group])
reaffecter_bulk_messages(target_group)
groupe_instructeur.dossiers.with_discarded.find_each do |dossier|
dossier.assign_to_groupe_instructeur(target_group, current_administrateur)
end

View file

@ -18,10 +18,11 @@ class DossierMailer < ApplicationMailer
end
end
def notify_new_answer(dossier)
def notify_new_answer(dossier, body = nil)
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
@body = body
subject = "Nouveau message pour votre dossier nº #{dossier.id} (#{dossier.procedure.libelle})"

View file

@ -0,0 +1,18 @@
# == Schema Information
#
# Table name: bulk_messages
#
# id :bigint not null, primary key
# body :text not null
# dossier_count :integer
# dossier_state :string
# sent_at :datetime not null
# created_at :datetime not null
# updated_at :datetime not null
# instructeur_id :bigint not null
#
class BulkMessage < ApplicationRecord
belongs_to :instructeur
has_one_attached :piece_jointe
has_and_belongs_to_many :groupe_instructeurs
end

View file

@ -18,7 +18,7 @@ class Commentaire < ApplicationRecord
belongs_to :instructeur, optional: true
belongs_to :expert, optional: true
validate :messagerie_available?, on: :create
validate :messagerie_available?, on: :create, unless: -> { dossier.brouillon? }
has_one_attached :piece_jointe
@ -95,7 +95,7 @@ class Commentaire < ApplicationRecord
end
def notify_user
DossierMailer.notify_new_answer(dossier).deliver_later
DossierMailer.notify_new_answer(dossier, body).deliver_later
end
def messagerie_available?

View file

@ -26,6 +26,7 @@ class Instructeur < ApplicationRecord
has_many :previously_followed_dossiers, -> { distinct }, through: :previous_follows, source: :dossier
has_many :trusted_device_tokens, dependent: :destroy
has_many :archives
has_many :bulk_messages, dependent: :destroy
has_one :user, dependent: :nullify

View file

@ -3,16 +3,19 @@
%p
Bonjour,
%p
%p
Vous avez reçu un
%strong nouveau message
de la part du service en charge de votre dossier.
%p
- if !@dossier.brouillon?
%p
Pour consulter le message et y répondre, cliquez sur le bouton ci-dessous :
= round_button('Lire le message', messagerie_dossier_url(@dossier), :primary)
= round_button('Lire le message', messagerie_dossier_url(@dossier), :primary)
- else
%p{ style: "padding: 8px; color: #333333; background-color: #EEEEEE; font-size: 14px;" }
= @body
= render 'layouts/mailers/signature', service: @service

View file

@ -0,0 +1,17 @@
- content_for(:title, "Contacter les usagers pour #{@procedure.libelle}")
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)),
'Contacter les usagers en brouillon'] }
.messagerie.container
- if @bulk_messages.present?
%p.mb-2 Messages envoyés aux usagers :
- @bulk_messages.each do |bulk_message|
%li.mb-1 #{bulk_message.instructeur.email} a envoyé un message le #{bulk_message.sent_at.strftime('%d-%m-%Y')} à #{bulk_message.dossier_count} usagers, #{bulk_message.piece_jointe.blob.present? ? "avec" : "sans"} pièce jointe
- if @email_usagers_dossiers.present?
%p.notice.mb-2.mt-4 Vous allez envoyer un message à #{pluralize(@dossiers_count, 'personne')} dans les groupes instructeurs : #{@groupe_instructeurs.join(', ')}.
= render partial: 'shared/dossiers/messages/form', locals: { commentaire: @commentaire, form_url: create_multiple_commentaire_instructeur_procedure_path(@procedure) }
- else
.page-title.center
%h2 Il n'y a aucun dossier en brouillon dans vos groupes instructeurs

View file

@ -10,6 +10,10 @@
.procedure-header
%h1= procedure_libelle @procedure
= link_to 'gestion des notifications', email_notifications_instructeur_procedure_path(@procedure), class: 'header-link'
- if @procedure.dossiers.state_brouillon.where(groupe_instructeur: current_instructeur.groupe_instructeur_ids).includes(:groupe_instructeur).present?
= link_to 'contacter les usagers en brouillon', email_usagers_instructeur_procedure_path(@procedure), class: 'header-link'
|
= link_to 'statistiques', stats_instructeur_procedure_path(@procedure), class: 'header-link'

View file

@ -357,6 +357,8 @@ Rails.application.routes.draw do
get 'email_notifications'
patch 'update_email_notifications'
get 'deleted_dossiers'
get 'email_usagers'
post 'create_multiple_commentaire'
resources :dossiers, only: [:show], param: :dossier_id do
member do

View file

@ -0,0 +1,18 @@
class CreateBulkMessageMails < ActiveRecord::Migration[6.1]
def change
create_table :bulk_messages do |t|
t.text :body, null: false
t.integer :dossier_count
t.string :dossier_state
t.datetime :sent_at, null: false
t.bigint :instructeur_id, null: false
t.timestamps
end
create_join_table :bulk_messages, :groupe_instructeurs, column_options: { null: true, foreign_key: true } do |t|
t.index :bulk_message_id
t.index :groupe_instructeur_id, name: :index_bulk_messages_groupe_instructeurs_on_gi_id
end
end
end

View file

@ -0,0 +1,5 @@
class AddUniqueIndexToBulkMessagesGroupeInstructeurs < ActiveRecord::Migration[6.1]
def change
add_index :bulk_messages_groupe_instructeurs, [:bulk_message_id, :groupe_instructeur_id], unique: true, name: :index_bulk_msg_gi_on_bulk_msg_id_and_gi_id
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_07_22_133553) do
ActiveRecord::Schema.define(version: 2021_07_27_172504) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -156,6 +156,24 @@ ActiveRecord::Schema.define(version: 2021_07_22_133553) do
t.datetime "updated_at", null: false
end
create_table "bulk_messages", force: :cascade do |t|
t.text "body", null: false
t.integer "dossier_count"
t.string "dossier_state"
t.datetime "sent_at", null: false
t.bigint "instructeur_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "bulk_messages_groupe_instructeurs", id: false, force: :cascade do |t|
t.bigint "bulk_message_id"
t.bigint "groupe_instructeur_id"
t.index ["bulk_message_id", "groupe_instructeur_id"], name: "index_bulk_msg_gi_on_bulk_msg_id_and_gi_id", unique: true
t.index ["bulk_message_id"], name: "index_bulk_messages_groupe_instructeurs_on_bulk_message_id"
t.index ["groupe_instructeur_id"], name: "index_bulk_messages_groupe_instructeurs_on_gi_id"
end
create_table "champs", id: :serial, force: :cascade do |t|
t.string "value"
t.integer "type_de_champ_id"
@ -767,6 +785,8 @@ ActiveRecord::Schema.define(version: 2021_07_22_133553) do
add_foreign_key "attestation_templates", "procedures"
add_foreign_key "attestations", "dossiers"
add_foreign_key "avis", "experts_procedures"
add_foreign_key "bulk_messages_groupe_instructeurs", "bulk_messages"
add_foreign_key "bulk_messages_groupe_instructeurs", "groupe_instructeurs"
add_foreign_key "champs", "champs", column: "parent_id"
add_foreign_key "closed_mails", "procedures"
add_foreign_key "commentaires", "dossiers"

View file

@ -489,4 +489,48 @@ describe Instructeurs::ProceduresController, type: :controller do
end
end
end
describe '#create_multiple_commentaire' do
let(:instructeur) { create(:instructeur) }
let!(:gi_p1_1) { GroupeInstructeur.create(label: '1', procedure: procedure) }
let!(:gi_p1_2) { GroupeInstructeur.create(label: '2', procedure: procedure) }
let(:body) { "avant\napres" }
let!(:dossier) { create(:dossier, state: "brouillon", procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first) }
let!(:dossier_2) { create(:dossier, state: "brouillon", procedure: procedure, groupe_instructeur: gi_p1_1) }
let!(:dossier_3) { create(:dossier, state: "brouillon", procedure: procedure, groupe_instructeur: gi_p1_2) }
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
before do
sign_in(instructeur.user)
instructeur.groupe_instructeurs << gi_p1_1
procedure
post :create_multiple_commentaire,
params: {
procedure_id: procedure.id,
commentaire: { body: body }
}
end
it "creates a commentaire for 2 dossiers" do
expect(Commentaire.all.count).to eq(2)
expect(dossier.commentaires.first.body).to eq("avant\napres")
expect(dossier_2.commentaires.first.body).to eq("avant\napres")
expect(dossier_3.commentaires).to eq([])
end
it "creates a Bulk Message for 2 groupes instructeurs" do
expect(BulkMessage.all.count).to eq(1)
expect(BulkMessage.all.first.body).to eq("avant\napres")
expect(BulkMessage.all.first.groupe_instructeurs.sort).to match([procedure.groupe_instructeurs.first, gi_p1_1])
end
it "creates a flash notice" do
expect(flash.notice).to be_present
expect(flash.notice).to eq("Tous les messages ont été envoyés avec succès")
end
it "redirect to instructeur_procedure_path" do
expect(response).to redirect_to instructeur_procedure_path(procedure)
end
end
end

View file

@ -137,12 +137,15 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
describe '#reaffecter' do
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
let!(:gi_1_3) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 3') }
let!(:dossier12) { create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1) }
let!(:dossier_discarded) do
dossier = create(:dossier, :en_construction, :with_individual, procedure: procedure, groupe_instructeur: gi_1_1)
dossier.discard!
dossier
end
let!(:instructeur) { create(:instructeur) }
let!(:bulk_message) { BulkMessage.create(dossier_count: 2, dossier_state: "brouillon", body: "hello", sent_at: Time.zone.now, groupe_instructeurs: [gi_1_1, gi_1_3], instructeur: instructeur) }
describe 'when the new group is a group of the procedure' do
before do
@ -153,6 +156,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
target_group: gi_1_2.id
}
dossier12.reload
bulk_message.reload
end
it { expect(response).to redirect_to(admin_procedure_groupe_instructeurs_path(procedure)) }
@ -160,6 +164,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
it { expect(gi_1_2.dossiers.with_discarded.count).to be(2) }
it { expect(gi_1_2.dossiers.last.id).to be(dossier12.id) }
it { expect(dossier12.groupe_instructeur.id).to be(gi_1_2.id) }
it { expect(bulk_message.groupe_instructeurs).to eq([gi_1_2, gi_1_3]) }
end
describe 'when the target group is not a possible group' do
@ -174,9 +179,11 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
}
before do
dossier12.reload
bulk_message.reload
end
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
it { expect(bulk_message.groupe_instructeurs).to eq([gi_1_1, gi_1_3]) }
end
end

View file

@ -26,11 +26,23 @@ RSpec.describe DossierMailer, type: :mailer do
it_behaves_like 'a dossier notification'
end
describe '.notify_new_answer' do
describe '.notify_new_answer with dossier brouillon' do
let(:dossier) { create(:dossier, procedure: build(:simple_procedure)) }
subject { described_class.notify_new_answer(dossier) }
it { expect(subject.subject).to include("Nouveau message") }
it { expect(subject.subject).to include(dossier.id.to_s) }
it { expect(subject.body).not_to include(messagerie_dossier_url(dossier)) }
it_behaves_like 'a dossier notification'
end
describe '.notify_new_answer with dossier en construction' do
let(:dossier) { create(:dossier, state: "en_construction", procedure: build(:simple_procedure)) }
subject { described_class.notify_new_answer(dossier) }
it { expect(subject.subject).to include("Nouveau message") }
it { expect(subject.subject).to include(dossier.id.to_s) }
it { expect(subject.body).to include(messagerie_dossier_url(dossier)) }

View file

@ -3,7 +3,6 @@ describe Commentaire do
it { is_expected.to have_db_column(:body) }
it { is_expected.to have_db_column(:created_at) }
it { is_expected.to have_db_column(:updated_at) }
it { is_expected.to belong_to(:dossier) }
describe 'messagerie_available validation' do
subject { commentaire.valid?(:create) }
@ -18,7 +17,7 @@ describe Commentaire do
let(:dossier) { create :dossier, :archived }
let(:commentaire) { build :commentaire, dossier: dossier }
it { is_expected.to be_falsey }
it { is_expected.to be_truthy }
end
context 'on a dossier en_construction' do