diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index e7c34495c..81c7dd3a9 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -10,28 +10,41 @@ module CreateAvisConcern # the :emails parameter is a 1-element array. # Hence the call to first # https://github.com/rails/rails/issues/17225 - emails = create_avis_params[:emails].first.split(',').map(&:strip) + expert_emails = create_avis_params[:emails].first.split(',').map(&:strip) + allowed_dossiers = [dossier] + + if create_avis_params[:invite_linked_dossiers].present? + allowed_dossiers += dossier.linked_dossiers + end create_results = Avis.create( - emails.map do |email| - { - email: email, - introduction: create_avis_params[:introduction], - claimant: current_instructeur, - dossier: dossier, - confidentiel: confidentiel - } + expert_emails.flat_map do |email| + allowed_dossiers.map do |dossier| + { + email: email, + introduction: create_avis_params[:introduction], + claimant: current_instructeur, + dossier: dossier, + confidentiel: confidentiel + } + end end ) persisted, failed = create_results.partition(&:persisted?) if persisted.any? - sent_emails_addresses = persisted.map(&:email_to_display).join(", ") - flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses}" + sent_emails_addresses = [] persisted.each do |avis| - dossier.demander_un_avis!(avis) + avis.dossier.demander_un_avis!(avis) + + if avis.dossier == dossier + AvisMailer.avis_invitation(avis).deliver_later + sent_emails_addresses << avis.email_to_display + end end + + flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses.uniq.join(", ")}" end if failed.any? @@ -41,13 +54,13 @@ module CreateAvisConcern # When an error occurs, return the avis back to the controller # to give the user a chance to correct and resubmit - Avis.new(create_avis_params.merge(emails: [failed.map(&:email).join(", ")])) + Avis.new(create_avis_params.merge(emails: [failed.map(&:email).uniq.join(", ")])) else nil end end def create_avis_params - params.require(:avis).permit(:introduction, :confidentiel, emails: []) + params.require(:avis).permit(:introduction, :confidentiel, :invite_linked_dossiers, emails: []) end end diff --git a/app/models/avis.rb b/app/models/avis.rb index 5696d6411..f30cec28b 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -12,7 +12,6 @@ class Avis < ApplicationRecord before_validation -> { sanitize_email(:email) } before_create :try_to_assign_instructeur - after_create :notify_instructeur default_scope { joins(:dossier) } scope :with_answer, -> { where.not(answer: nil) } @@ -24,6 +23,7 @@ class Avis < ApplicationRecord # The form allows subtmitting avis requests to several emails at once, # hence this virtual attribute. attr_accessor :emails + attr_accessor :invite_linked_dossiers def email_to_display instructeur&.email || email @@ -49,10 +49,6 @@ class Avis < ApplicationRecord private - def notify_instructeur - AvisMailer.avis_invitation(self).deliver_later - end - def try_to_assign_instructeur instructeur = Instructeur.find_by(email: email) if instructeur diff --git a/app/models/champ.rb b/app/models/champ.rb index e64ecd583..f2e40be11 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -11,7 +11,7 @@ class Champ < ApplicationRecord belongs_to :etablissement, dependent: :destroy has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy - delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, to: :type_de_champ + delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, :dossier_link?, to: :type_de_champ scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) } scope :public_only, -> { where(private: false) } diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 497ddd0f4..07d5e73fc 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -517,6 +517,10 @@ class Dossier < ApplicationRecord self.individual = Individual.create_from_france_connect(fc_information) end + def linked_dossiers + Dossier.where(id: champs.filter(&:dossier_link?).map(&:value).compact) + end + private def log_dossier_operation(author, operation, subject = nil) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 6d7903eaa..249c1b6f4 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -157,6 +157,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:repetition) end + def dossier_link? + type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) + end + def public? !private? end diff --git a/app/views/instructeurs/avis/instruction.html.haml b/app/views/instructeurs/avis/instruction.html.haml index b8d5eb2f9..c396cd221 100644 --- a/app/views/instructeurs/avis/instruction.html.haml +++ b/app/views/instructeurs/avis/instruction.html.haml @@ -28,7 +28,7 @@ = f.submit 'Envoyer votre avis', class: 'button send' - if !@dossier.termine? - = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } + = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } - if @dossier.avis_for(current_instructeur).present? = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_instructeur), avis_seen_at: nil } diff --git a/app/views/instructeurs/dossiers/avis.html.haml b/app/views/instructeurs/dossiers/avis.html.haml index 544f6547d..65d661582 100644 --- a/app/views/instructeurs/dossiers/avis.html.haml +++ b/app/views/instructeurs/dossiers/avis.html.haml @@ -4,7 +4,7 @@ .container - if !@dossier.termine? - = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), must_be_confidentiel: false, avis: @avis } + = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: false, avis: @avis } - if @dossier.avis.present? = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis, avis_seen_at: @avis_seen_at } diff --git a/app/views/instructeurs/shared/avis/_form.html.haml b/app/views/instructeurs/shared/avis/_form.html.haml index a23a30ae2..1588633e3 100644 --- a/app/views/instructeurs/shared/avis/_form.html.haml +++ b/app/views/instructeurs/shared/avis/_form.html.haml @@ -6,6 +6,10 @@ = f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);" = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true + - if linked_dossiers.present? + = f.check_box :invite_linked_dossiers, value: false + = f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence) + .flex.justify-between.align-baseline - if must_be_confidentiel %p.confidentiel.flex diff --git a/config/locales/models/avis/fr.yml b/config/locales/models/avis/fr.yml index 9d1c31792..c458dffd9 100644 --- a/config/locales/models/avis/fr.yml +++ b/config/locales/models/avis/fr.yml @@ -5,3 +5,8 @@ fr: attributes: avis: answer: "Réponse" + helpers: + label: + invite_linked_dossiers: + one: Inviter aussi l'expert sur le dossier lié n° %{ids} + other: Inviter aussi l'expert sur les dossiers liés n° %{ids} diff --git a/spec/controllers/instructeurs/avis_controller_spec.rb b/spec/controllers/instructeurs/avis_controller_spec.rb index bd8bd8821..86e7a5bec 100644 --- a/spec/controllers/instructeurs/avis_controller_spec.rb +++ b/spec/controllers/instructeurs/avis_controller_spec.rb @@ -123,9 +123,10 @@ describe Instructeurs::AvisController, type: :controller do let(:intro) { 'introduction' } let(:created_avis) { Avis.last } let!(:old_avis_count) { Avis.count } + let(:invite_linked_dossiers) { nil } before do - post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel } } + post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers } } end context 'when an invalid email' do @@ -180,6 +181,34 @@ describe Instructeurs::AvisController, type: :controller do it { expect(created_avis.confidentiel).to be(true) } end end + + context 'with linked dossiers' do + let(:asked_confidentiel) { false } + let(:previous_avis_confidentiel) { false } + let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } + + it do + expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") + expect(Avis.count).to eq(old_avis_count + 1) + expect(created_avis.email).to eq("a@b.com") + expect(created_avis.dossier).to eq(dossier) + end + + context 'checked' do + let(:invite_linked_dossiers) { true } + let(:created_avis) { Avis.last(2).first } + let(:linked_avis) { Avis.last } + let(:linked_dossier) { dossier.reload.linked_dossiers.first } + + it do + expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") + expect(Avis.count).to eq(old_avis_count + 2) + expect(created_avis.email).to eq("a@b.com") + expect(created_avis.dossier).to eq(dossier) + expect(linked_avis.dossier).to eq(linked_dossier) + end + end + end end end diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index 163109eac..313b49d40 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -54,10 +54,30 @@ FactoryBot.define do trait :with_dossier_link do after(:create) do |dossier, _evaluator| + # create linked dossier linked_dossier = create(:dossier) - type_de_champ = dossier.procedure.types_de_champ.find { |t| t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) } - champ = dossier.champs.find { |c| c.type_de_champ == type_de_champ } + # find first type de champ dossier_link + type_de_champ = dossier.procedure.types_de_champ.find do |t| + t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) + end + + # if type de champ does not exist create it + if !type_de_champ + type_de_champ = create(:type_de_champ_dossier_link, procedure: dossier.procedure) + end + + # find champ with the type de champ + champ = dossier.reload.champs.find do |c| + c.type_de_champ == type_de_champ + end + + # if champ does not exist create it + if !champ + champ = create(:champ_dossier_link, dossier: dossier, type_de_champ: type_de_champ) + end + + # set champ value with linked dossier champ.value = linked_dossier.id champ.save! end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index c938c2f6d..08ceae5a3 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -8,7 +8,7 @@ feature 'Inviting an expert:' do let(:expert) { create(:instructeur, password: expert_password) } let(:expert_password) { 'mot de passe d’expert' } let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) } + let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } context 'as an Instructeur' do scenario 'I can invite an expert' do @@ -20,6 +20,7 @@ feature 'Inviting an expert:' do fill_in 'avis_emails', with: 'expert1@exemple.fr, expert2@exemple.fr' fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' + check 'avis_invite_linked_dossiers' page.select 'confidentiel', from: 'avis_confidentiel' perform_enqueued_jobs do @@ -34,6 +35,8 @@ feature 'Inviting an expert:' do expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.') end + expect(Avis.count).to eq(4) + expect(all_emails.size).to eq(2) invitation_email = open_email('expert2@exemple.fr') avis = Avis.find_by(email: 'expert2@exemple.fr') sign_up_link = sign_up_instructeur_avis_path(avis.id, avis.email) diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index 085b39417..52140f952 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -7,8 +7,7 @@ feature 'Instructing a dossier:' do let!(:instructeur) { create(:instructeur, password: password) } let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let!(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) } - + let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) } context 'the instructeur is also a user' do scenario 'a instructeur can fill a dossier' do visit commencer_path(path: procedure.path) diff --git a/spec/models/avis_spec.rb b/spec/models/avis_spec.rb index 4b807644c..74fb242e7 100644 --- a/spec/models/avis_spec.rb +++ b/spec/models/avis_spec.rb @@ -87,18 +87,6 @@ RSpec.describe Avis, type: :model do end end - describe '#notify_instructeur' do - context 'when an avis is created' do - before do - avis_invitation_double = double('avis_invitation', deliver_later: true) - allow(AvisMailer).to receive(:avis_invitation).and_return(avis_invitation_double) - Avis.create(claimant: claimant, email: 'email@l.com') - end - - it { expect(AvisMailer).to have_received(:avis_invitation) } - end - end - describe '#try_to_assign_instructeur' do let!(:instructeur) { create(:instructeur) } let(:avis) { Avis.create(claimant: claimant, email: email, dossier: create(:dossier)) }