diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 54e4ce7bf..e33e2d998 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -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 diff --git a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb index bf80c8995..b1f350a72 100644 --- a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb +++ b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb @@ -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 diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index fa72628fe..cb1f8f772 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -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})" diff --git a/app/models/bulk_message.rb b/app/models/bulk_message.rb new file mode 100644 index 000000000..376e231ad --- /dev/null +++ b/app/models/bulk_message.rb @@ -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 diff --git a/app/models/commentaire.rb b/app/models/commentaire.rb index 895bc2f3f..f68b462a5 100644 --- a/app/models/commentaire.rb +++ b/app/models/commentaire.rb @@ -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? diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index d3e2fc21b..588502504 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -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 diff --git a/app/views/dossier_mailer/notify_new_answer.html.haml b/app/views/dossier_mailer/notify_new_answer.html.haml index 4db0e9270..34b31536a 100644 --- a/app/views/dossier_mailer/notify_new_answer.html.haml +++ b/app/views/dossier_mailer/notify_new_answer.html.haml @@ -3,16 +3,19 @@ %p Bonjour, + %p + Vous avez reçu un + %strong nouveau message + de la part du service en charge de votre dossier. -%p - Vous avez reçu un - %strong nouveau message - de la part du service en charge de votre dossier. +- if !@dossier.brouillon? + %p + Pour consulter le message et y répondre, cliquez sur le bouton ci-dessous : -%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 diff --git a/app/views/instructeurs/procedures/email_usagers.html.haml b/app/views/instructeurs/procedures/email_usagers.html.haml new file mode 100644 index 000000000..b500eea17 --- /dev/null +++ b/app/views/instructeurs/procedures/email_usagers.html.haml @@ -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 + diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 5e2553b7b..95ed47128 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -10,7 +10,11 @@ .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' - if @procedure.routee? diff --git a/config/routes.rb b/config/routes.rb index 22a203093..6fbfb9841 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20210722083911_create_bulk_message_mails.rb b/db/migrate/20210722083911_create_bulk_message_mails.rb new file mode 100644 index 000000000..994140f7d --- /dev/null +++ b/db/migrate/20210722083911_create_bulk_message_mails.rb @@ -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 diff --git a/db/migrate/20210727172504_add_unique_index_to_bulk_messages_groupe_instructeurs.rb b/db/migrate/20210727172504_add_unique_index_to_bulk_messages_groupe_instructeurs.rb new file mode 100644 index 000000000..b73a461ab --- /dev/null +++ b/db/migrate/20210727172504_add_unique_index_to_bulk_messages_groupe_instructeurs.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index c642e473e..34629685e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index aa5b23ab0..3df4b1fa4 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -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 diff --git a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb index ab3590211..382c8a625 100644 --- a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb @@ -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 diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index 2448ca693..db4598fa7 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -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)) } diff --git a/spec/models/commentaire_spec.rb b/spec/models/commentaire_spec.rb index 4e33eb5c9..ac7a2cab8 100644 --- a/spec/models/commentaire_spec.rb +++ b/spec/models/commentaire_spec.rb @@ -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