diff --git a/app/assets/stylesheets/helpers.scss b/app/assets/stylesheets/helpers.scss index de1fff1ed..92755dac9 100644 --- a/app/assets/stylesheets/helpers.scss +++ b/app/assets/stylesheets/helpers.scss @@ -28,3 +28,7 @@ display: inline-block; width: 5px; } + +.text-center { + text-align: center; +} diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index a76c03f01..9854ec405 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -9,7 +9,8 @@ module CreateAvisConcern # the :emails parameter is a 1-element array. # Hence the call to first # https://github.com/rails/rails/issues/17225 - expert_emails = create_avis_params[:emails].first.split(',').map(&:strip) + expert_emails = create_avis_params[:emails].presence || [].to_json + expert_emails = JSON.parse(expert_emails).map(&:strip).map(&:downcase) allowed_dossiers = [dossier] if create_avis_params[:invite_linked_dossiers].present? @@ -65,6 +66,6 @@ module CreateAvisConcern end def create_avis_params - params.require(:avis).permit(:introduction_file, :introduction, :confidentiel, :invite_linked_dossiers, emails: []) + params.require(:avis).permit(:introduction_file, :introduction, :confidentiel, :invite_linked_dossiers, :emails) end end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 7ce205c9c..74930bfcb 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -60,6 +60,7 @@ module Instructeurs def avis @avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at @avis = Avis.new + @experts_emails = dossier.procedure.experts_procedures.where(revoked_at: nil).map(&:expert).map(&:email).sort end def personnes_impliquees diff --git a/app/controllers/new_administrateur/experts_procedures_controller.rb b/app/controllers/new_administrateur/experts_procedures_controller.rb new file mode 100644 index 000000000..1b965f0dc --- /dev/null +++ b/app/controllers/new_administrateur/experts_procedures_controller.rb @@ -0,0 +1,43 @@ +module NewAdministrateur + class ExpertsProceduresController < AdministrateurController + before_action :retrieve_procedure, only: [:add_expert_to_procedure, :revoke_expert_from_procedure] + + def add_expert_to_procedure + emails = params['emails'].presence || [].to_json + emails = JSON.parse(emails).map(&:strip).map(&:downcase) + + valid_users, invalid_users = emails + .map { |email| User.create_or_promote_to_expert(email, SecureRandom.hex) } + .partition(&:valid?) + + if invalid_users.any? + flash[:alert] = invalid_users + .filter { |user| user.errors.present? } + .map { |user| "#{user.email} : #{user.errors.full_messages_for(:email).join(', ')}" } + end + + if valid_users.present? + valid_users.each do |user| + experts_procedure = ExpertsProcedure.find_or_create_by(expert: user.expert, procedure: @procedure) + if !experts_procedure.revoked_at.nil? + experts_procedure.update!(revoked_at: nil) + end + end + + flash[:notice] = t('.experts_assignment', + count: valid_users.count, + value: valid_users.map(&:email).join(', '), + procedure: @procedure.id) + end + redirect_to admin_procedure_invited_expert_list_path(@procedure) + end + + def revoke_expert_from_procedure + expert_procedure = ExpertsProcedure.find_by!(procedure: @procedure, id: params[:expert_procedure][:id]) + expert_email = expert_procedure.expert.email + expert_procedure.update!(revoked_at: Time.zone.now) + flash[:notice] = "#{expert_email} a été révoqué de la démarche et ne pourra plus déposer d'avis." + redirect_to admin_procedure_invited_expert_list_path(@procedure) + end + end +end diff --git a/app/controllers/new_administrateur/procedures_controller.rb b/app/controllers/new_administrateur/procedures_controller.rb index 8b2c0431d..212878085 100644 --- a/app/controllers/new_administrateur/procedures_controller.rb +++ b/app/controllers/new_administrateur/procedures_controller.rb @@ -186,7 +186,8 @@ module NewAdministrateur end def invited_expert_list - @experts_procedure = @procedure.experts_procedures.sort_by { |expert_procedure| expert_procedure.expert.email } + @experts_procedure = @procedure.experts_procedures.where(revoked_at: nil).sort_by { |expert_procedure| expert_procedure.expert.email } + @experts_emails = experts_procedure_emails end def update_allow_decision_access @@ -198,6 +199,10 @@ module NewAdministrateur private + def experts_procedure_emails + @procedure.experts.map(&:email).sort + end + def apercu_tab params[:tab] || 'dossier' end diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index f77eff7ab..f03d28f19 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -360,12 +360,12 @@ module Users if champs_params[:dossier] @dossier.assign_attributes(champs_params[:dossier]) - # FIXME in some cases a removed repetition bloc row is submitted. - # In this case it will be trated as a new records and action will fail. + # FIXME: in some cases a removed repetition bloc row is submitted. + # In this case it will be treated as a new record, and the action will fail. @dossier.champs.filter(&:repetition?).each do |champ| champ.champs = champ.champs.filter(&:persisted?) end - if @dossier.champs.any?(&:changed?) + if @dossier.champs.any?(&:changed_for_autosave?) @dossier.last_champ_updated_at = Time.zone.now end if !@dossier.save diff --git a/app/models/champ.rb b/app/models/champ.rb index 864ced9fb..8cb1d8804 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -18,7 +18,7 @@ # type_de_champ_id :integer # class Champ < ApplicationRecord - belongs_to :dossier, -> { with_discarded }, inverse_of: :champs, touch: true, optional: false + belongs_to :dossier, -> { with_discarded }, inverse_of: false, touch: true, optional: false belongs_to :type_de_champ, inverse_of: :champ, optional: false belongs_to :parent, class_name: 'Champ', optional: true has_many :commentaires diff --git a/app/models/dossier.rb b/app/models/dossier.rb index aaf93e14f..2441035f0 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -65,8 +65,8 @@ class Dossier < ApplicationRecord has_one_attached :justificatif_motivation has_one_attached :pdf_export_for_instructeur - has_many :champs, -> { root.public_ordered }, inverse_of: :dossier, dependent: :destroy - has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy + has_many :champs, -> { root.public_ordered }, inverse_of: false, dependent: :destroy + has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy has_many :commentaires, inverse_of: :dossier, dependent: :destroy has_many :invites, dependent: :destroy has_many :follows, -> { active }, inverse_of: :dossier diff --git a/app/models/experts_procedure.rb b/app/models/experts_procedure.rb index 203f927fc..438086376 100644 --- a/app/models/experts_procedure.rb +++ b/app/models/experts_procedure.rb @@ -4,6 +4,7 @@ # # id :bigint not null, primary key # allow_decision_access :boolean default(FALSE), not null +# revoked_at :datetime # created_at :datetime not null # updated_at :datetime not null # expert_id :bigint not null diff --git a/app/models/export.rb b/app/models/export.rb index 208b68df3..ef521e01e 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # format :string not null -# key :text +# key :text not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/views/experts/shared/avis/_form.html.haml b/app/views/experts/shared/avis/_form.html.haml index a1bcf1fac..a8ec6a80c 100644 --- a/app/views/experts/shared/avis/_form.html.haml +++ b/app/views/experts/shared/avis/_form.html.haml @@ -1,30 +1,38 @@ -%section.ask-avis - %h1.tab-title Inviter des personnes à donner leur avis - %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. +- if @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis).blank? + %section.ask-avis + %h1.tab-title Inviter des personnes à donner leur avis + %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. - = form_for avis, url: url, html: { class: 'form' } do |f| - = 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 - %p.tab-title Ajouter une pièce jointe - .form-group - = text_upload_and_render f, avis.introduction_file + = form_for avis, url: url, html: { class: 'form' } do |f| + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag 'avis[emails]', nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: [], + selected: [], disabled: [], + hiddenFieldId: hidden_field_id, + label: 'avis_emails', + acceptNewValues: true) + = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true + %p.tab-title Ajouter une pièce jointe + .form-group + = text_upload_and_render f, avis.introduction_file - - if linked_dossiers.present? - = f.check_box :invite_linked_dossiers, {}, true, false - = f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence) + - if linked_dossiers.present? + = f.check_box :invite_linked_dossiers, {}, true, 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 - %span.icon.lock - %span - Cet avis sera confidentiel : il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. + .flex.justify-between.align-baseline + - if must_be_confidentiel + %p.confidentiel.flex + %span.icon.lock + %span + Cet avis sera confidentiel : il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. - - else - .confidentiel-wrapper - = f.label :confidentiel, 'Cet avis sera ' - = f.select :confidentiel, [['partagé avec les autres experts', false], ['confidentiel', true]], {}, onchange: "javascript:DS.toggleCondidentielExplanation(event);" - .confidentiel-explanation.hidden - Il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. + - else + .confidentiel-wrapper + = f.label :confidentiel, 'Cet avis sera ' + = f.select :confidentiel, [['partagé avec les autres experts', false], ['confidentiel', true]], {}, onchange: "javascript:DS.toggleCondidentielExplanation(event);" + .confidentiel-explanation.hidden + Il ne sera pas affiché aux autres experts consultés, mais sera visible par les instructeurs. - = f.submit 'Demander un avis', class: 'button primary send' + = f.submit 'Demander un avis', class: 'button primary send' diff --git a/app/views/instructeurs/shared/avis/_form.html.haml b/app/views/instructeurs/shared/avis/_form.html.haml index a1bcf1fac..466b48c66 100644 --- a/app/views/instructeurs/shared/avis/_form.html.haml +++ b/app/views/instructeurs/shared/avis/_form.html.haml @@ -1,9 +1,22 @@ %section.ask-avis %h1.tab-title Inviter des personnes à donner leur avis %p.avis-notice Les invités pourront consulter le dossier, donner un avis et contribuer au fil de messagerie. Ils ne pourront pas modifier le dossier. + - if @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis) + %p.avis-notice Choisissez des experts à qui vous souhaitez demander un avis parmi la liste prédéfinie par les administrateurs de la démarche + - else + %p.avis-notice Entrez les adresses email des experts à qui vous souhaitez demander un avis = form_for avis, url: url, html: { class: 'form' } do |f| - = f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);" + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag 'avis[emails]', nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis) ? @experts_emails : [], + selected: [], + disabled: [], + hiddenFieldId: hidden_field_id, + label: 'avis_emails', + id: 'avis_emails', + acceptNewValues: @dossier.procedure.feature_enabled?(:admin_affect_experts_to_avis).blank?) = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true %p.tab-title Ajouter une pièce jointe .form-group diff --git a/app/views/new_administrateur/procedures/invited_expert_list.html.haml b/app/views/new_administrateur/procedures/invited_expert_list.html.haml index 2d894e5b3..3272948d4 100644 --- a/app/views/new_administrateur/procedures/invited_expert_list.html.haml +++ b/app/views/new_administrateur/procedures/invited_expert_list.html.haml @@ -5,12 +5,35 @@ .container %h1.page-title.mt-2 Experts invités sur #{@procedure.libelle} + + .container.groupe-instructeur + + .card + .card-title Affecter des experts à la démarche + = form_for :experts_procedure, + url: admin_procedure_add_expert_to_procedure_path(@procedure), + html: { class: 'form' } do |f| + + .instructeur-wrapper + %p.notice Pendant l'instruction d'un dossier, les instructeurs peuvent demander leur avis à un ou plusieurs experts. + %p.notice Entrez les adresses email des experts que vous souhaitez affecter à cette démarche + - hidden_field_id = SecureRandom.uuid + = hidden_field_tag :emails, nil, data: { uuid: hidden_field_id } + = react_component("ComboMultipleDropdownList", + options: [], + selected: [], disabled: [], + hiddenFieldId: hidden_field_id, + label: 'email expert', + acceptNewValues: true) + + = f.submit 'Affecter à la démarche', class: 'button primary send' - if @experts_procedure.present? %table.table.mt-2 %thead %tr %th Liste des experts - - if @procedure.feature_enabled?(:make_experts_notifiable) + %th Nombre d'avis + - if @procedure.feature_enabled?(:admin_affect_experts_to_avis) %th Notifier des décisions sur les dossiers %tbody - @experts_procedure.each do |expert_procedure| @@ -18,8 +41,10 @@ %td %span.icon.person = expert_procedure.expert.email - - if @procedure.feature_enabled?(:make_experts_notifiable) - %td + %td.text-center + = expert_procedure.avis.count + - if @procedure.feature_enabled?(:admin_affect_experts_to_avis) + %td.text-center = form_for expert_procedure, url: admin_procedure_update_allow_decision_access_path(expert_procedure: expert_procedure), remote: true, @@ -31,6 +56,12 @@ %span.toggle-switch-control.round %span.toggle-switch-label.on %span.toggle-switch-label.off + %td.actions= button_to 'retirer', + { action: "revoke_expert_from_procedure", :controller=>"new_administrateur/experts_procedures" }, + { method: :put, + data: { confirm: "Êtes-vous sûr de vouloir révoquer l'expert « #{expert_procedure.expert.email} » de la démarche #{expert_procedure.procedure.libelle} ? Les instructeurs ne pourront plus lui demander d'avis" }, + params: { expert_procedure: { id: expert_procedure.id }}, + class: 'button' } - else .blank-tab %h2.empty-text Aucun expert invité pour le moment. diff --git a/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml b/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml index 6ac7df602..2243a9caf 100644 --- a/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml +++ b/config/locales/views/new_administrateur/groupe_instructeurs/fr.yml @@ -1,5 +1,12 @@ fr: new_administrateur: + experts_procedures: + wrong_address: + one: "%{value} n’est pas une adresse email valide" + other: "%{value} ne sont pas des adresses emails valides" + experts_assignment: + one: "L'expert %{value} a été affecté à la démarche n° %{procedure}" + other: "Les experts %{value} ont été affectés à la démarche n° %{procedure}" groupe_instructeurs: index: existing_groupe: diff --git a/config/routes.rb b/config/routes.rb index ab3874c57..81156cf26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -408,7 +408,8 @@ Rails.application.routes.draw do post 'transfer' => 'procedures#transfer', as: :transfer get 'invited_expert_list' put 'update_allow_decision_access' => 'procedures#update_allow_decision_access', as: :update_allow_decision_access - + post 'add_expert_to_procedure' => 'experts_procedures#add_expert_to_procedure', as: :add_expert_to_procedure + put 'revoke_expert_from_procedure' => 'experts_procedures#revoke_expert_from_procedure', as: :revoke_expert_from_procedure resources :mail_templates, only: [:edit, :update] resources :groupe_instructeurs, only: [:index, :show, :create, :update, :destroy] do diff --git a/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb b/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb new file mode 100644 index 000000000..916ac6b67 --- /dev/null +++ b/db/migrate/20210331184808_add_revoked_at_to_experts_procedures.rb @@ -0,0 +1,5 @@ +class AddRevokedAtToExpertsProcedures < ActiveRecord::Migration[6.1] + def change + add_column :experts_procedures, :revoked_at, :datetime + end +end diff --git a/db/migrate/20210402163003_exports_key_not_null.rb b/db/migrate/20210402163003_exports_key_not_null.rb new file mode 100644 index 000000000..3de3d70fc --- /dev/null +++ b/db/migrate/20210402163003_exports_key_not_null.rb @@ -0,0 +1,5 @@ +class ExportsKeyNotNull < ActiveRecord::Migration[6.1] + def change + change_column_null :exports, :key, false + end +end diff --git a/db/schema.rb b/db/schema.rb index 6f2234ccb..ac440622d 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_03_31_123709) do +ActiveRecord::Schema.define(version: 2021_04_02_163003) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -345,6 +345,7 @@ ActiveRecord::Schema.define(version: 2021_03_31_123709) do t.boolean "allow_decision_access", default: false, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.datetime "revoked_at" t.index ["expert_id", "procedure_id"], name: "index_experts_procedures_on_expert_id_and_procedure_id", unique: true t.index ["expert_id"], name: "index_experts_procedures_on_expert_id" t.index ["procedure_id"], name: "index_experts_procedures_on_procedure_id" @@ -354,7 +355,7 @@ ActiveRecord::Schema.define(version: 2021_03_31_123709) do t.string "format", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "key" + t.text "key", null: false t.index ["format", "key"], name: "index_exports_on_format_and_key", unique: true end diff --git a/spec/controllers/experts/avis_controller_spec.rb b/spec/controllers/experts/avis_controller_spec.rb index 3ce7d7e2d..af0b520dd 100644 --- a/spec/controllers/experts/avis_controller_spec.rb +++ b/spec/controllers/experts/avis_controller_spec.rb @@ -159,7 +159,7 @@ describe Experts::AvisController, type: :controller do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } let(:intro) { 'introduction' } - let(:emails) { ["toto@totomail.com"] } + let(:emails) { "[\"toto@totomail.com\"]" } let(:invite_linked_dossiers) { nil } before do @@ -176,7 +176,7 @@ describe Experts::AvisController, type: :controller do describe '#create_avis' do let!(:previous_avis) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, confidentiel: previous_avis_confidentiel) } - let(:emails) { ['a@b.com'] } + let(:emails) { "[\"a@b.com\"]" } let(:intro) { 'introduction' } let(:created_avis) { Avis.last } let!(:old_avis_count) { Avis.count } @@ -194,7 +194,7 @@ describe Experts::AvisController, type: :controller do context 'when an invalid email' do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } - let(:emails) { ["toto.fr"] } + let(:emails) { "[\"toto.fr\"]" } it { expect(response).to render_template :instruction } it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } @@ -205,7 +205,7 @@ describe Experts::AvisController, type: :controller do context 'ask review with attachment' do let(:previous_avis_confidentiel) { false } let(:asked_confidentiel) { false } - let(:emails) { ["toto@totomail.com"] } + let(:emails) { "[\"toto@totomail.com\"]" } it { expect(created_avis.introduction_file).to be_attached } it { expect(created_avis.introduction_file.filename).to eq("piece_justificative_0.pdf") } @@ -216,7 +216,7 @@ describe Experts::AvisController, type: :controller do context 'with multiple emails' do let(:asked_confidentiel) { false } let(:previous_avis_confidentiel) { false } - let(:emails) { ["toto.fr,titi@titimail.com"] } + let(:emails) { "[\"toto.fr\",\"titi@titimail.com\"]" } it { expect(response).to render_template :instruction } it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 82b08ad2d..b31cb81c0 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -448,7 +448,7 @@ describe Instructeurs::DossiersController, type: :controller do } end - let(:emails) { ['email@a.com'] } + let(:emails) { "[\"email@a.com\"]" } context "notifications updates" do context 'when an instructeur follows the dossier' do @@ -476,7 +476,7 @@ describe Instructeurs::DossiersController, type: :controller do it { expect(response).to redirect_to(avis_instructeur_dossier_path(dossier.procedure, dossier)) } context "with an invalid email" do - let(:emails) { ['emaila.com'] } + let(:emails) { "[\"emaila.com\"]" } before { subject } @@ -487,7 +487,7 @@ describe Instructeurs::DossiersController, type: :controller do end context 'with multiple emails' do - let(:emails) { ["toto.fr,titi@titimail.com"] } + let(:emails) { "[\"toto.fr\",\"titi@titimail.com\"]" } before { subject } diff --git a/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb b/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb new file mode 100644 index 000000000..39e28e3f9 --- /dev/null +++ b/spec/controllers/new_administrateur/experts_procedures_controller_spec.rb @@ -0,0 +1,49 @@ +describe NewAdministrateur::ExpertsProceduresController, type: :controller do + let(:admin) { create(:administrateur) } + + before do + sign_in(admin.user) + end + describe '#add_expert_to_procedure' do + let(:procedure) { create :procedure, administrateur: admin } + let(:expert) { create(:expert) } + let(:expert2) { create(:expert) } + + subject do + post :add_expert_to_procedure, + params: { procedure_id: procedure.id, emails: "[\"#{expert.email}\",\"#{expert2.email}\"]" } + end + + before do + subject + end + + context 'of multiple experts' do + it { expect(procedure.experts.include?(expert)).to be_truthy } + it { expect(procedure.experts.include?(expert2)).to be_truthy } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(admin_procedure_invited_expert_list_path(procedure)) } + end + end + + describe '#revoke_expert_from_procedure' do + let(:procedure) { create :procedure, administrateur: admin } + let(:expert) { create(:expert) } + let(:expert_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + + subject do + put :revoke_expert_from_procedure, params: { procedure_id: procedure.id, expert_procedure: { id: expert_procedure.id } } + end + + before do + subject + expert_procedure.reload + end + + context 'of multiple experts' do + it { expect(expert_procedure.revoked_at).to be_present } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(admin_procedure_invited_expert_list_path(procedure)) } + end + end +end diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 1bd747264..d69c83670 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -619,13 +619,14 @@ describe Users::DossiersController, type: :controller do it 'updates the champs' do subject expect(first_champ.reload.value).to eq('beautiful value') - expect(first_champ.dossier.reload.last_champ_updated_at).to eq(now) expect(piece_justificative_champ.reload.piece_justificative_file).to be_attached end - it 'updates the dossier modification date' do + it 'updates the dossier timestamps' do subject - expect(dossier.reload.updated_at.year).to eq(2100) + dossier.reload + expect(dossier.updated_at).to eq(now) + expect(dossier.last_champ_updated_at).to eq(now) end it 'updates the dossier state' do @@ -635,22 +636,34 @@ describe Users::DossiersController, type: :controller do it { is_expected.to redirect_to(demande_dossier_path(dossier)) } - context 'when only files champs are modified' do + context 'when only a single file champ are modified' do + # A bug in ActiveRecord causes records changed through grand-parent <-> parent <-> child + # relationships do not touch the grand-parent record on change. + # This situation is hit when updating just the attachment of a champ (and not the + # champ itself). + # + # This test ensures that, whatever workaround we wrote for this, it still works properly. + # + # See https://github.com/rails/rails/issues/26726 let(:submit_payload) do { id: dossier.id, dossier: { - champs_attributes: { - id: piece_justificative_champ.id, - piece_justificative_file: file - } + champs_attributes: [ + { + id: piece_justificative_champ.id, + piece_justificative_file: file + } + ] } } end - it 'updates the dossier modification date' do + it 'updates the dossier timestamps' do subject - expect(dossier.reload.updated_at.year).to eq(2100) + dossier.reload + expect(dossier.updated_at).to eq(now) + expect(dossier.last_champ_updated_at).to eq(now) end end end @@ -667,6 +680,12 @@ describe Users::DossiersController, type: :controller do it { expect(response).to render_template(:modifier) } it { expect(flash.alert).to eq(['nop']) } + it 'does not update the dossier timestamps' do + dossier.reload + expect(dossier.updated_at).not_to eq(now) + expect(dossier.last_champ_updated_at).not_to eq(now) + end + it 'does not send an email' do expect(NotificationMailer).not_to receive(:send_initiated_notification) diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index ea2353d45..1529421ae 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -1,4 +1,4 @@ -feature 'Inviting an expert:' do +feature 'Inviting an expert:', js: true do include ActiveJob::TestHelper include ActionView::Helpers @@ -21,7 +21,7 @@ feature 'Inviting an expert:' do click_on 'Avis externes' expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) - fill_in 'avis_emails', with: "#{expert.email}, #{expert2.email}" + page.execute_script("document.querySelector('#avis_emails').value = '[\"#{expert.email}\",\"#{expert2.email}\"]'") 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' diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index b1c5a794d..b0c9aeef6 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -1,4 +1,4 @@ -feature 'Instructing a dossier:' do +feature 'Instructing a dossier:', js: true do include ActiveJob::TestHelper let(:password) { 'my-s3cure-p4ssword' } @@ -101,11 +101,13 @@ feature 'Instructing a dossier:' do click_on 'Avis externes' expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) + expert_email_formated = "[\"expert@tps.com\"]" expert_email = 'expert@tps.com' - ask_confidential_avis(expert_email, 'a good introduction') + ask_confidential_avis(expert_email_formated, 'a good introduction') + expert_email_formated = "[\"#{instructeur2.email}\"]" expert_email = instructeur2.email - ask_confidential_avis(expert_email, 'a good introduction') + ask_confidential_avis(expert_email_formated, 'a good introduction') click_on 'Personnes impliquées' expect(page).to have_text(expert_email) @@ -206,7 +208,7 @@ feature 'Instructing a dossier:' do end def ask_confidential_avis(to, introduction) - fill_in 'avis_emails', with: to + page.execute_script("document.querySelector('#avis_emails').value = '#{to}'") fill_in 'avis_introduction', with: introduction select 'confidentiel', from: 'avis_confidentiel' click_on 'Demander un avis'